diff --git a/core/src/Schematic.cpp b/core/src/Schematic.cpp index e26e8b2..1ed9bcd 100644 --- a/core/src/Schematic.cpp +++ b/core/src/Schematic.cpp @@ -158,3 +158,166 @@ ClipboardBuffer ClipboardBuffer::rotatedY(Rotation rotation) const { } }; + int anchorX = 0, anchorY = 0, anchorZ = 0; + mapCell(handle.x, handle.y, handle.z, anchorX, anchorY, anchorZ); + ClipboardBuffer turned(newDims, Vec3i{anchorX, anchorY, anchorZ}); + + for (int y = 0; y < size.y; ++y) + for (int z = 0; z < size.z; ++z) + for (int x = 0; x < size.x; ++x) { + int tx = 0, ty = 0, tz = 0; + mapCell(x, y, z, tx, ty, tz); + turned.put(tx, ty, tz, at(x, y, z), filledAt(x, y, z)); + } + return turned; +} + +ClipboardBuffer ClipboardBuffer::mirrored(MirrorAxis axis) const { + if (empty()) return *this; + auto mapCell = [&](int x, int y, int z, int& outX, int& outY, int& outZ) { + outX = axis == MirrorAxis::X ? size.x - 1 - x : x; + outY = axis == MirrorAxis::Y ? size.y - 1 - y : y; + outZ = axis == MirrorAxis::Z ? size.z - 1 - z : z; + }; + + int anchorX = 0, anchorY = 0, anchorZ = 0; + mapCell(handle.x, handle.y, handle.z, anchorX, anchorY, anchorZ); + ClipboardBuffer flipped(size, Vec3i{anchorX, anchorY, anchorZ}); + + for (int y = 0; y < size.y; ++y) + for (int z = 0; z < size.z; ++z) + for (int x = 0; x < size.x; ++x) { + int tx = 0, ty = 0, tz = 0; + mapCell(x, y, z, tx, ty, tz); + flipped.put(tx, ty, tz, at(x, y, z), filledAt(x, y, z)); + } + return flipped; +} + +ClipboardBuffer ClipboardBuffer::trimmed() const { + Vec3i lo{size.x, size.y, size.z}; + Vec3i hi{-1, -1, -1}; + for (int y = 0; y < size.y; ++y) + for (int z = 0; z < size.z; ++z) + for (int x = 0; x < size.x; ++x) { + if (!filledAt(x, y, z) || isAir(at(x, y, z))) continue; + lo = {std::min(lo.x, x), std::min(lo.y, y), std::min(lo.z, z)}; + hi = {std::max(hi.x, x), std::max(hi.y, y), std::max(hi.z, z)}; + } + if (hi.x < lo.x) return ClipboardBuffer{}; + + ClipboardBuffer tight({hi.x - lo.x + 1, hi.y - lo.y + 1, hi.z - lo.z + 1}, + {handle.x - lo.x, handle.y - lo.y, handle.z - lo.z}); + for (int y = lo.y; y <= hi.y; ++y) + for (int z = lo.z; z <= hi.z; ++z) + for (int x = lo.x; x <= hi.x; ++x) + tight.put(x - lo.x, y - lo.y, z - lo.z, at(x, y, z), filledAt(x, y, z)); + return tight; +} + +bool ClipboardBuffer::sameContent(const ClipboardBuffer& other) const { + if (!(size == other.size)) return false; + for (int y = 0; y < size.y; ++y) + for (int z = 0; z < size.z; ++z) + for (int x = 0; x < size.x; ++x) { + if (filledAt(x, y, z) != other.filledAt(x, y, z)) return false; + if (!filledAt(x, y, z)) continue; + if (at(x, y, z) != other.at(x, y, z)) return false; + } + return true; +} + +uint8_t Schematic::bitsForPalette(std::size_t paletteSize) { + if (paletteSize <= 2u) return 1u; + if (paletteSize <= 4u) return 2u; + if (paletteSize <= 16u) return 4u; + return 8u; +} + +const char* Schematic::errorText(SchematicError error) { + switch (error) { + case SchematicError::None: return "ok"; + case SchematicError::BadMagic: return "not a schematic"; + case SchematicError::UnsupportedVersion: return "unsupported schematic version"; + case SchematicError::Truncated: return "truncated schematic"; + case SchematicError::BadChecksum: return "checksum mismatch"; + case SchematicError::DimensionMismatch: return "dimensions do not match payload"; + case SchematicError::BadPalette: return "invalid block palette"; + } + return "unknown schematic error"; +} + +Schematic Schematic::capture(const World& world, const Selection& selection, + const std::string& name, const std::string& author, uint64_t tick) { + return fromBuffer(ClipboardBuffer::copyFromWorld(world, selection), name, author, tick); +} + +Schematic Schematic::fromBuffer(const ClipboardBuffer& buffer, const std::string& name, + const std::string& author, uint64_t tick) { + Schematic out; + out.cells = buffer; + out.meta.version = SchematicVersion::V2; + out.meta.name = name; + out.meta.author = author; + out.meta.tick = tick; + out.refreshHeaderFromBuffer(); + return out; +} + +void Schematic::refreshHeaderFromBuffer() { + std::vector palette; + std::vector slotOfBlock; + buildPalette(cells, palette, slotOfBlock); + meta.dims = cells.dims(); + meta.anchor = cells.anchor(); + meta.paletteSize = static_cast(palette.size()); + meta.indexBits = bitsForPalette(palette.size()); + meta.hasVoidMask = cells.filledCount() != cells.volume(); +} + +void Schematic::setAnchor(const Vec3i& anchor) { + cells.setAnchor(anchor); + meta.anchor = anchor; +} + +void Schematic::rotate(Rotation rotation) { + cells = cells.rotatedY(rotation); + refreshHeaderFromBuffer(); +} + +void Schematic::mirror(MirrorAxis axis) { + cells = cells.mirrored(axis); + refreshHeaderFromBuffer(); +} + +void Schematic::trim() { + cells = cells.trimmed(); + refreshHeaderFromBuffer(); +} + +ByteBuffer Schematic::encode() const { return encode(SchematicVersion::V2); } + +ByteBuffer Schematic::encode(SchematicVersion version) const { + std::vector palette; + std::vector slotOfBlock; + buildPalette(cells, palette, slotOfBlock); + uint8_t bits = bitsForPalette(palette.size()); + + const Vec3i& dims = cells.dims(); + std::size_t cellCount = cells.volume(); + bool writeVoidMask = + version == SchematicVersion::V2 && cells.filledCount() != cellCount; + + std::vector indices; + indices.reserve(cellCount); + std::vector presence; + presence.reserve(writeVoidMask ? cellCount : 0u); + for (int y = 0; y < dims.y; ++y) + for (int z = 0; z < dims.z; ++z) + for (int x = 0; x < dims.x; ++x) { + bool filled = cells.filledAt(x, y, z); + Block block = filled ? cells.at(x, y, z) : Block::Air; + indices.push_back(slotOfBlock[static_cast(block)]); + if (writeVoidMask) presence.push_back(filled ? 1u : 0u); + } +