示例#1
0
    def __iter__(self):
        cx, cz = self.chunkUpdate.chunk.chunkPosition

        sectionBounds = SectionBox(cx, self.y, cz)
        bounds = self.chunkUpdate.chunkInfo.worldScene.bounds
        if bounds:
            sectionBounds = sectionBounds.intersect(bounds)

        modelMesh = BlockModelMesh(self)
        with profiler.context("BlockModelMesh"):
            modelMesh.createVertexArrays()
        self.blockMeshes.append(modelMesh)
        yield
示例#2
0
    def __iter__(self):
        cx, cz = self.chunkUpdate.chunk.chunkPosition

        sectionBounds = SectionBox(cx, self.y, cz)
        bounds = self.chunkUpdate.chunkInfo.worldScene.bounds
        if bounds:
            sectionBounds = sectionBounds.intersect(bounds)

        modelMesh = BlockModelMesh(self)
        with profiler.context("BlockModelMesh"):
            modelMesh.createVertexArrays()
        self.blockMeshes.append(modelMesh)
        yield
示例#3
0
def advanceToChunk(ray, dimension, maxDistance):
    point, vector = ray
    inBounds = point in dimension.bounds

    for pos, face in _cast(point, vector, maxDistance, 16):

        x, y, z = pos
        x >>= 4
        y >>= 4
        z >>= 4
        if inBounds and pos not in dimension.bounds:
            raise RayBoundsError("Ray exited dimension bounds.")
        inBounds = pos in dimension.bounds

        if dimension.containsChunk(x, z):
            chunk = dimension.getChunk(x, z)
            section = chunk.getSection(y)
            if section is not None:
                # if (section.Blocks == 0).all():
                #     log.warn("Empty section found!!")
                #     continue
                box = SectionBox(x, y, z)
                if point in box:
                    return point
                ixs = rayIntersectsBox(box, ray)
                if ixs:
                    hitPoint = ixs[0][0]
                    return hitPoint

    raise RayBoundsError("Ray exited dimension bounds.")
示例#4
0
    def loadSections(self):
        selection = self.renderSelection
        if selection is None:
            self.loadTimer.setInterval(333)
            return
        else:
            self.loadTimer.setInterval(0)

        for cx, cz in selection.chunkPositions():
            if self.groupNode.containsChunkNode((cx, cz)):
                continue

            vertexArrays = []
            for cy in selection.sectionPositions(cx, cz):
                box = SectionBox(cx, cy, cz).expand(1)
                mask = selection.box_mask(box)
                if mask is not None:
                    vertexArrays.extend(self.buildSection(mask, cy))
            if len(vertexArrays):
                chunkNode = ChunkNode((cx, cz))
                vertexNode = VertexNode(vertexArrays)
                chunkNode.addChild(vertexNode)
                self.groupNode.addChunkNode(chunkNode)
            yield
        self.loadTimer.setInterval(333)
示例#5
0
    def box_mask(self, box):
        """

        :param box:
        :type box: BoundingBox
        :return:
        :rtype:
        """
        #if self.volume > 40:
        #    import pdb; pdb.set_trace()

        mask = numpy.zeros(shape=box.size, dtype=bool)

        for cx, cz in box.chunkPositions():
            try:
                chunk = self.dimension.getChunk(cx, cz)
            except ChunkNotPresent:
                continue
            for cy in box.sectionPositions(cx, cz):
                section = chunk.getSection(cy)
                if section is None:
                    continue
                sectionBox = box.intersect(SectionBox(cx, cy, cz))
                if sectionBox.volume == 0:
                    continue
                slices = numpy.s_[sectionBox.miny
                                  & 0xf:(sectionBox.miny & 0xf) +
                                  sectionBox.height, sectionBox.minz
                                  & 0xf:(sectionBox.minz & 0xf) +
                                  sectionBox.length, sectionBox.minx
                                  & 0xf:(sectionBox.minx & 0xf) +
                                  sectionBox.width, ]
                maskSlices = numpy.s_[sectionBox.miny -
                                      box.miny:sectionBox.maxy - box.miny,
                                      sectionBox.minz -
                                      box.minz:sectionBox.maxz - box.minz,
                                      sectionBox.minx -
                                      box.minx:sectionBox.maxx - box.minx, ]
                blocks = section.Blocks
                mask[maskSlices] = blocks[slices] != 0

        return mask
示例#6
0
def copyBlocksIter(destDim, sourceDim, sourceSelection, destinationPoint, blocksToCopy=None, entities=True, create=False, biomes=False, updateLights=False):
    """
    Copy blocks and entities from the `sourceBox` area of `sourceDim` to `destDim` starting at `destinationPoint`.

    :param sourceDim: WorldEditorDimension
    :param destDim: WorldEditorDimension

    Optional parameters:
      - `blocksToCopy`: list of blockIDs to copy.
      - `entities`: True to copy Entities and TileEntities, False otherwise.
      - `create`: True to create new chunks in destLevel, False otherwise.
      - `biomes`: True to copy biome data, False otherwise.
    """

    (lx, ly, lz) = sourceSelection.size

    # needs work xxx
    log.info(u"Copying {0} blocks from {1} to {2}" .format(ly * lz * lx, sourceSelection, destinationPoint))
    startTime = time.time()

    destBox = BoundingBox(destinationPoint, sourceSelection.size)
    chunkCount = destBox.chunkCount
    i = 0
    entitiesCopied = 0
    tileEntitiesCopied = 0
    entitiesSeen = 0
    tileEntitiesSeen = 0

    if updateLights:
        allChangedX = []
        allChangedY = []
        allChangedZ = []

    makeSourceMask = sourceMaskFunc(blocksToCopy)

    copyOffset = destBox.origin - sourceSelection.origin

    # Visit each chunk in the source area
    #   Visit each section in this chunk
    #      Find the chunks and sections of the destination area corresponding to this section
    #          Compute slices for Blocks array and mask
    #          Use slices and mask to copy Blocks and Data
    #   Copy entities and tile entities from this chunk.
    sourceBiomeMask = None
    convertBlocks = blocktypes.blocktypeConverter(destDim.blocktypes, sourceDim.blocktypes)

    for sourceCpos in sourceSelection.chunkPositions():
        # Visit each chunk
        if not sourceDim.containsChunk(*sourceCpos):
            continue

        sourceChunk = sourceDim.getChunk(*sourceCpos)

        i += 1
        yield (i, chunkCount)
        if i % 20 == 0:
            log.info("Copying: Chunk {0}/{1}...".format(i, chunkCount))

        # Use sourceBiomeMask to accumulate a list of columns over all sections whose biomes should be copied.
        sourceBiomes = None
        if biomes and hasattr(sourceChunk, 'Biomes'):
            sourceBiomes = sourceChunk.Biomes
            sourceBiomeMask = numpy.zeros_like(sourceBiomes)

        for sourceCy in sourceChunk.sectionPositions():
            # Visit each section
            sourceSection = sourceChunk.getSection(sourceCy)
            if sourceSection is None:
                continue

            selectionMask = sourceSelection.section_mask(sourceCpos[0], sourceCy, sourceCpos[1])
            if selectionMask is None:
                continue

            typeMask = makeSourceMask(sourceSection.Blocks)
            sourceMask = selectionMask & typeMask

            # Update sourceBiomeMask
            if sourceBiomes is not None:
                sourceBiomeMask |= sourceMask.any(axis=0)

            # Find corresponding destination area(s)
            sectionBox = SectionBox(sourceCpos[0], sourceCy, sourceCpos[1])
            destBox = BoundingBox(sectionBox.origin + copyOffset, sectionBox.size)

            for destCpos in destBox.chunkPositions():
                if not create and not destDim.containsChunk(*destCpos):
                    continue
                destChunk = destDim.getChunk(*destCpos, create=True)

                for destCy in destBox.sectionPositions(*destCpos):
                    # Compute slices for source and dest arrays
                    destSectionBox = SectionBox(destCpos[0], destCy, destCpos[1])
                    intersect = destSectionBox.intersect(destBox)
                    if intersect.volume == 0:
                        continue

                    destSection = destChunk.getSection(destCy, create=True)
                    if destSection is None:
                        continue

                    destSlices = (
                        slice(intersect.miny - (destCy << 4), intersect.maxy - (destCy << 4)),
                        slice(intersect.minz - (destCpos[1] << 4), intersect.maxz - (destCpos[1] << 4)),
                        slice(intersect.minx - (destCpos[0] << 4), intersect.maxx - (destCpos[0] << 4)),
                    )

                    sourceIntersect = BoundingBox(intersect.origin - copyOffset, intersect.size)
                    sourceSlices = (
                        slice(sourceIntersect.miny - (sourceCy << 4), sourceIntersect.maxy - (sourceCy << 4)),
                        slice(sourceIntersect.minz - (sourceCpos[1] << 4), sourceIntersect.maxz - (sourceCpos[1] << 4)),
                        slice(sourceIntersect.minx - (sourceCpos[0] << 4), sourceIntersect.maxx - (sourceCpos[0] << 4)),
                    )
                    # Read blocks
                    sourceBlocks = sourceSection.Blocks[sourceSlices]
                    sourceData = sourceSection.Data[sourceSlices]
                    sourceMaskPart = sourceMask[sourceSlices]

                    # Convert blocks
                    convertedSourceBlocks, convertedSourceData = convertBlocks(sourceBlocks, sourceData)
                    convertedSourceBlocksMasked = convertedSourceBlocks[sourceMaskPart]

                    # Find blocks that need direct lighting update - block opacity or brightness changed

                    oldBrightness = destDim.blocktypes.brightness[destSection.Blocks[destSlices][sourceMaskPart]]
                    newBrightness = destDim.blocktypes.brightness[convertedSourceBlocksMasked]
                    oldOpacity = destDim.blocktypes.opacity[destSection.Blocks[destSlices][sourceMaskPart]]
                    newOpacity = destDim.blocktypes.opacity[convertedSourceBlocksMasked]
                    changedLight = (oldBrightness != newBrightness) | (oldOpacity != newOpacity)

                    # Write blocks
                    destSection.Blocks[destSlices][sourceMaskPart] = convertedSourceBlocks[sourceMaskPart]
                    destSection.Data[destSlices][sourceMaskPart] = convertedSourceData[sourceMaskPart]

                    if updateLights:
                        # Find coordinates of lighting updates
                        (changedFlat,) = changedLight.nonzero()
                        # Since convertedSourceBlocksMasked is a 1d array, changedFlat is an index
                        # into this array. Thus, changedFlat is also an index into the nonzero values
                        # of sourceMaskPart.

                        if len(changedFlat):
                            x, y, z = sourceMaskPart.nonzero()
                            changedX = x[changedFlat].astype('i4')
                            changedY = y[changedFlat].astype('i4')
                            changedZ = z[changedFlat].astype('i4')

                            changedX += intersect.minx
                            changedY += intersect.miny
                            changedZ += intersect.minz
                            if updateLights == "all":
                                allChangedX.append(changedX)
                                allChangedY.append(changedY)
                                allChangedZ.append(changedZ)
                            else:
                                # log.info("Updating section lights in %s blocks... (ob %s)",
                                #          changedFlat.shape,
                                #          oldBrightness.shape)
                                relight.updateLightsByCoord(destDim, changedX, changedY, changedZ)

                destChunk.dirty = True

        # Copy biomes
        if sourceBiomes is not None:
            bx, bz = sourceBiomeMask.nonzero()
            wbx = bx + (sourceCpos[0] << 4)
            wbz = bz + (sourceCpos[1] << 4)
            destDim.setBlocks(wbx, 1, wbz, Biomes=sourceBiomes[bx, bz])

        # Copy entities and tile entities
        if entities:
            entitiesSeen += len(sourceChunk.Entities)
            for entity in sourceChunk.Entities:
                if entity.Position in sourceSelection:
                    entitiesCopied += 1
                    newEntity = entity.copyWithOffset(copyOffset)
                    destDim.addEntity(newEntity)

        tileEntitiesSeen += len(sourceChunk.TileEntities)
        for tileEntity in sourceChunk.TileEntities:
            if tileEntity.Position in sourceSelection:
                tileEntitiesCopied += 1
                newEntity = tileEntity.copyWithOffset(copyOffset)
                destDim.addTileEntity(newEntity)

    duration = time.time() - startTime
    log.info("Duration: %0.3fs, %d/%d chunks, %0.2fms per chunk (%0.2f chunks per second)",
             duration, i, sourceSelection.chunkCount, 1000 * duration/i, i/duration)
    log.info("Copied %d/%d entities and %d/%d tile entities",
             entitiesCopied, entitiesSeen, tileEntitiesCopied, tileEntitiesSeen)

    if updateLights == "all":
        log.info("Updating all at once for %d sections (%d cells)", len(allChangedX), sum(len(a) for a in allChangedX))

        startTime = time.time()

        for i in range(len(allChangedX)):
            x = allChangedX[i]
            y = allChangedY[i]
            z = allChangedZ[i]
            relight.updateLightsByCoord(destDim, x, y, z)

        i = i or 1
        duration = time.time() - startTime
        duration = duration or 1

        log.info("Lighting complete.")
        log.info("Duration: %0.3fs, %d sections, %0.2fms per section (%0.2f sections per second)",
                 duration, i, 1000 * duration/i, i/duration)
示例#7
0
def copyBlocksIter(destDim,
                   sourceDim,
                   sourceSelection,
                   destinationPoint,
                   blocksToCopy=None,
                   entities=True,
                   create=False,
                   biomes=False):
    """
    Copy blocks and entities from the `sourceBox` area of `sourceDim` to `destDim` starting at `destinationPoint`.

    :param sourceDim: WorldEditorDimension
    :param destDim: WorldEditorDimension

    Optional parameters:
      - `blocksToCopy`: list of blockIDs to copy.
      - `entities`: True to copy Entities and TileEntities, False otherwise.
      - `create`: True to create new chunks in destLevel, False otherwise.
      - `biomes`: True to copy biome data, False otherwise.
    """

    (lx, ly, lz) = sourceSelection.size

    # needs work xxx
    log.info(u"Copying {0} blocks from {1} to {2}".format(
        ly * lz * lx, sourceSelection, destinationPoint))
    startTime = datetime.now()

    destBox = BoundingBox(destinationPoint, sourceSelection.size)
    chunkCount = destBox.chunkCount
    i = 0
    entitiesCopied = 0
    tileEntitiesCopied = 0
    entitiesSeen = 0
    tileEntitiesSeen = 0

    makeSourceMask = sourceMaskFunc(blocksToCopy)

    copyOffset = destBox.origin - sourceSelection.origin

    # Visit each chunk in the source area
    #   Visit each section in this chunk
    #      Find the chunks and sections of the destination area corresponding to this section
    #          Compute slices for Blocks array and mask
    #          Use slices and mask to copy Blocks and Data
    #   Copy entities and tile entities from this chunk.
    sourceBiomeMask = None
    convertBlocks = blocktypes.blocktypeConverter(destDim.blocktypes,
                                                  sourceDim.blocktypes)

    for sourceCpos in sourceSelection.chunkPositions():
        # Visit each chunk
        if not sourceDim.containsChunk(*sourceCpos):
            continue

        sourceChunk = sourceDim.getChunk(*sourceCpos)

        i += 1
        yield (i, chunkCount)
        if i % 100 == 0:
            log.info("Copying: Chunk {0}...".format(i))

        # Use sourceBiomeMask to accumulate a list of columns over all sections whose biomes should be copied.
        sourceBiomes = None
        if biomes and hasattr(sourceChunk, 'Biomes'):
            sourceBiomes = sourceChunk.Biomes
            sourceBiomeMask = numpy.zeros_like(sourceBiomes)

        for sourceCy in sourceChunk.sectionPositions():
            # Visit each section
            sourceSection = sourceChunk.getSection(sourceCy)
            if sourceSection is None:
                continue

            selectionMask = sourceSelection.section_mask(
                sourceCpos[0], sourceCy, sourceCpos[1])
            if selectionMask is None:
                continue

            typeMask = makeSourceMask(sourceSection.Blocks)
            sourceMask = selectionMask & typeMask

            # Update sourceBiomeMask
            if sourceBiomes is not None:
                sourceBiomeMask |= sourceMask.any(axis=0)

            # Find corresponding destination area(s)
            sectionBox = SectionBox(sourceCpos[0], sourceCy, sourceCpos[1],
                                    sourceSection)
            destBox = BoundingBox(sectionBox.origin + copyOffset,
                                  sectionBox.size)

            for destCpos in destBox.chunkPositions():
                if not create and not destDim.containsChunk(*destCpos):
                    continue
                destChunk = destDim.getChunk(*destCpos, create=True)

                for destCy in destBox.sectionPositions(*destCpos):
                    # Compute slices for source and dest arrays
                    destSectionBox = SectionBox(destCpos[0], destCy,
                                                destCpos[1])
                    intersect = destSectionBox.intersect(destBox)
                    if intersect.volume == 0:
                        continue

                    destSection = destChunk.getSection(destCy, create=True)
                    if destSection is None:
                        continue

                    # Recompute destSectionBox and intersect using the shape of destSection.Blocks
                    # after destChunk is loaded to work with odd shaped FakeChunkDatas XXXXXXXXXXXX
                    destSectionBox = SectionBox(destCpos[0], destCy,
                                                destCpos[1], destSection)
                    intersect = destSectionBox.intersect(destBox)
                    if intersect.volume == 0:
                        continue

                    destSlices = (
                        slice(intersect.miny - (destCy << 4),
                              intersect.maxy - (destCy << 4)),
                        slice(intersect.minz - (destCpos[1] << 4),
                              intersect.maxz - (destCpos[1] << 4)),
                        slice(intersect.minx - (destCpos[0] << 4),
                              intersect.maxx - (destCpos[0] << 4)),
                    )

                    sourceIntersect = BoundingBox(
                        intersect.origin - copyOffset, intersect.size)
                    sourceSlices = (
                        slice(sourceIntersect.miny - (sourceCy << 4),
                              sourceIntersect.maxy - (sourceCy << 4)),
                        slice(sourceIntersect.minz - (sourceCpos[1] << 4),
                              sourceIntersect.maxz - (sourceCpos[1] << 4)),
                        slice(sourceIntersect.minx - (sourceCpos[0] << 4),
                              sourceIntersect.maxx - (sourceCpos[0] << 4)),
                    )
                    # Read blocks
                    sourceBlocks = sourceSection.Blocks[sourceSlices]
                    sourceData = sourceSection.Data[sourceSlices]
                    sourceMaskPart = sourceMask[sourceSlices]

                    # Convert blocks
                    convertedSourceBlocks, convertedSourceData = convertBlocks(
                        sourceBlocks, sourceData)

                    # Write blocks
                    destSection.Blocks[destSlices][
                        sourceMaskPart] = convertedSourceBlocks[sourceMaskPart]
                    destSection.Data[destSlices][
                        sourceMaskPart] = convertedSourceData[sourceMaskPart]

                destChunk.dirty = True

        # Copy biomes
        if sourceBiomes is not None:
            bx, bz = sourceBiomeMask.nonzero()
            wbx = bx + (sourceCpos[0] << 4)
            wbz = bz + (sourceCpos[1] << 4)
            destDim.setBlocks(wbx, 1, wbz, Biomes=sourceBiomes[bx, bz])

        # Copy entities and tile entities
        if entities:
            entitiesSeen += len(sourceChunk.Entities)
            for entity in sourceChunk.Entities:
                if entity.Position in sourceSelection:
                    entitiesCopied += 1
                    newEntity = entity.copyWithOffset(copyOffset)
                    destDim.addEntity(newEntity)

        tileEntitiesSeen += len(sourceChunk.TileEntities)
        for tileEntity in sourceChunk.TileEntities:
            if tileEntity.Position in sourceSelection:
                tileEntitiesCopied += 1
                newEntity = tileEntity.copyWithOffset(copyOffset)
                destDim.addTileEntity(newEntity)

    log.info("Duration: {0}".format(datetime.now() - startTime))
    log.info("Copied %d/%d entities and %d/%d tile entities", entitiesCopied,
             entitiesSeen, tileEntitiesCopied, tileEntitiesSeen)
示例#8
0
    def areaBlocksOrData(self, arrayName):
        """
        Return the blocks in an 18-wide cube centered on this section. Only retrieves blocks from the
        6 sections neighboring this one along a major axis, so the corners are empty. That's fine since they
        aren't needed for any calculation we do.

        :return: Array of blocks in this chunk and six of its neighbors.
        :rtype: numpy.ndarray(shape=(18, 18, 18), dtype='uint16')
        """
        chunk = self.chunkUpdate.chunk

        chunkWidth, chunkLength, chunkHeight = self.chunkSection.Blocks.shape
        cy = self.chunkSection.Y

        areaBlocks = numpy.empty(
            (chunkWidth + 2, chunkLength + 2, chunkHeight + 2),
            numpy.uint16 if arrayName == "Blocks" else numpy.uint8)
        areaBlocks[(0, -1), :, :] = 0
        areaBlocks[:, (0, -1)] = 0
        areaBlocks[:, :, (0, -1)] = 0

        mask = None
        bounds = self.chunkUpdate.chunkInfo.worldScene.bounds
        if bounds:
            cx, cz = self.chunkUpdate.chunk.chunkPosition
            sectionBox = SectionBox(cx, cy, cz).expand(1)
            areaBox = BoundingBox(
                sectionBox.origin,
                (chunkWidth + 2, chunkHeight + 2, chunkLength + 2))

            mask = bounds.box_mask(areaBox)
            if mask is None:
                return areaBlocks

        areaBlocks[1:-1, 1:-1, 1:-1] = getattr(self.chunkSection, arrayName)
        neighboringChunks = self.chunkUpdate.neighboringChunks

        if faces.FaceXDecreasing in neighboringChunks:
            ncs = neighboringChunks[faces.FaceXDecreasing].getSection(cy)
            if ncs:
                areaBlocks[1:-1, 1:-1, :1] = getattr(ncs, arrayName)[:, :, -1:]

        if faces.FaceXIncreasing in neighboringChunks:
            ncs = neighboringChunks[faces.FaceXIncreasing].getSection(cy)
            if ncs:
                areaBlocks[1:-1, 1:-1, -1:] = getattr(ncs, arrayName)[:, :, :1]

        if faces.FaceZDecreasing in neighboringChunks:
            ncs = neighboringChunks[faces.FaceZDecreasing].getSection(cy)
            if ncs:
                areaBlocks[1:-1, :1,
                           1:-1] = getattr(ncs, arrayName)[:chunkWidth,
                                                           -1:, :chunkHeight]

        if faces.FaceZIncreasing in neighboringChunks:
            ncs = neighboringChunks[faces.FaceZIncreasing].getSection(cy)
            if ncs:
                areaBlocks[1:-1, -1:, 1:-1] = getattr(
                    ncs, arrayName)[:chunkWidth, :1, :chunkHeight]

        aboveSection = chunk.getSection(self.chunkSection.Y + 1)
        if aboveSection:
            areaBlocks[-1:, 1:-1, 1:-1] = getattr(aboveSection,
                                                  arrayName)[:1, :, :]

        belowSection = chunk.getSection(self.chunkSection.Y - 1)
        if belowSection:
            areaBlocks[:1, 1:-1, 1:-1] = getattr(belowSection,
                                                 arrayName)[-1:, :, :]

        if mask is not None:
            areaBlocks[~mask] = 0

        return areaBlocks
示例#9
0
def copyBlocksIter(destDim, sourceDim, sourceSelection, destinationPoint, blocksToCopy=None, entities=True, create=False, biomes=False):
    """
    Copy blocks and entities from the `sourceBox` area of `sourceDim` to `destDim` starting at `destinationPoint`.

    :param sourceDim: WorldEditorDimension
    :param destDim: WorldEditorDimension

    Optional parameters:
      - `blocksToCopy`: list of blockIDs to copy.
      - `entities`: True to copy Entities and TileEntities, False otherwise.
      - `create`: True to create new chunks in destLevel, False otherwise.
      - `biomes`: True to copy biome data, False otherwise.
    """

    (lx, ly, lz) = sourceSelection.size

    # needs work xxx
    log.info(u"Copying {0} blocks from {1} to {2}" .format(ly * lz * lx, sourceSelection, destinationPoint))
    startTime = datetime.now()

    destBox = BoundingBox(destinationPoint, sourceSelection.size)
    chunkCount = destBox.chunkCount
    i = 0
    entitiesCopied = 0
    tileEntitiesCopied = 0
    entitiesSeen = 0
    tileEntitiesSeen = 0

    makeSourceMask = sourceMaskFunc(blocksToCopy)

    copyOffset = destBox.origin - sourceSelection.origin

    # Visit each chunk in the source area
    #   Visit each section in this chunk
    #      Find the chunks and sections of the destination area corresponding to this section
    #          Compute slices for Blocks array and mask
    #          Use slices and mask to copy Blocks and Data
    #   Copy entities and tile entities from this chunk.
    sourceBiomeMask = None
    convertBlocks = blocktypes.blocktypeConverter(destDim.blocktypes, sourceDim.blocktypes)

    for sourceCpos in sourceSelection.chunkPositions():
        # Visit each chunk
        if not sourceDim.containsChunk(*sourceCpos):
            continue

        sourceChunk = sourceDim.getChunk(*sourceCpos)

        i += 1
        yield (i, chunkCount)
        if i % 100 == 0:
            log.info("Copying: Chunk {0}...".format(i))

        # Use sourceBiomeMask to accumulate a list of columns over all sections whose biomes should be copied.
        sourceBiomes = None
        if biomes and hasattr(sourceChunk, 'Biomes'):
            sourceBiomes = sourceChunk.Biomes
            sourceBiomeMask = numpy.zeros_like(sourceBiomes)

        for sourceCy in sourceChunk.sectionPositions():
            # Visit each section
            sourceSection = sourceChunk.getSection(sourceCy)
            if sourceSection is None:
                continue

            selectionMask = sourceSelection.section_mask(sourceCpos[0], sourceCy, sourceCpos[1])
            if selectionMask is None:
                continue

            typeMask = makeSourceMask(sourceSection.Blocks)
            sourceMask = selectionMask & typeMask

            # Update sourceBiomeMask
            if sourceBiomes is not None:
                sourceBiomeMask |= sourceMask.any(axis=0)

            # Find corresponding destination area(s)
            sectionBox = SectionBox(sourceCpos[0], sourceCy, sourceCpos[1], sourceSection)
            destBox = BoundingBox(sectionBox.origin + copyOffset, sectionBox.size)

            for destCpos in destBox.chunkPositions():
                if not create and not destDim.containsChunk(*destCpos):
                    continue
                destChunk = destDim.getChunk(*destCpos, create=True)

                for destCy in destBox.sectionPositions(*destCpos):
                    # Compute slices for source and dest arrays
                    destSectionBox = SectionBox(destCpos[0], destCy, destCpos[1])
                    intersect = destSectionBox.intersect(destBox)
                    if intersect.volume == 0:
                        continue

                    destSection = destChunk.getSection(destCy, create=True)
                    if destSection is None:
                        continue

                    # Recompute destSectionBox and intersect using the shape of destSection.Blocks
                    # after destChunk is loaded to work with odd shaped FakeChunkDatas XXXXXXXXXXXX
                    destSectionBox = SectionBox(destCpos[0], destCy, destCpos[1], destSection)
                    intersect = destSectionBox.intersect(destBox)
                    if intersect.volume == 0:
                        continue

                    destSlices = (
                        slice(intersect.miny - (destCy << 4), intersect.maxy - (destCy << 4)),
                        slice(intersect.minz - (destCpos[1] << 4), intersect.maxz - (destCpos[1] << 4)),
                        slice(intersect.minx - (destCpos[0] << 4), intersect.maxx - (destCpos[0] << 4)),
                    )

                    sourceIntersect = BoundingBox(intersect.origin - copyOffset, intersect.size)
                    sourceSlices = (
                        slice(sourceIntersect.miny - (sourceCy << 4), sourceIntersect.maxy - (sourceCy << 4)),
                        slice(sourceIntersect.minz - (sourceCpos[1] << 4), sourceIntersect.maxz - (sourceCpos[1] << 4)),
                        slice(sourceIntersect.minx - (sourceCpos[0] << 4), sourceIntersect.maxx - (sourceCpos[0] << 4)),
                    )
                    # Read blocks
                    sourceBlocks = sourceSection.Blocks[sourceSlices]
                    sourceData = sourceSection.Data[sourceSlices]
                    sourceMaskPart = sourceMask[sourceSlices]

                    # Convert blocks
                    convertedSourceBlocks, convertedSourceData = convertBlocks(sourceBlocks, sourceData)

                    # Write blocks
                    destSection.Blocks[destSlices][sourceMaskPart] = convertedSourceBlocks[sourceMaskPart]
                    destSection.Data[destSlices][sourceMaskPart] = convertedSourceData[sourceMaskPart]

                destChunk.dirty = True

        # Copy biomes
        if sourceBiomes is not None:
            bx, bz = sourceBiomeMask.nonzero()
            wbx = bx + (sourceCpos[0] << 4)
            wbz = bz + (sourceCpos[1] << 4)
            destDim.setBlocks(wbx, 1, wbz, Biomes=sourceBiomes[bx, bz])

        # Copy entities and tile entities
        if entities:
            entitiesSeen += len(sourceChunk.Entities)
            for entity in sourceChunk.Entities:
                if entity.Position in sourceSelection:
                    entitiesCopied += 1
                    newEntity = entity.copyWithOffset(copyOffset)
                    destDim.addEntity(newEntity)

        tileEntitiesSeen += len(sourceChunk.TileEntities)
        for tileEntity in sourceChunk.TileEntities:
            if tileEntity.Position in sourceSelection:
                tileEntitiesCopied += 1
                newEntity = tileEntity.copyWithOffset(copyOffset)
                destDim.addTileEntity(newEntity)

    log.info("Duration: {0}".format(datetime.now() - startTime))
    log.info("Copied %d/%d entities and %d/%d tile entities", entitiesCopied, entitiesSeen, tileEntitiesCopied, tileEntitiesSeen)