diff --git a/core/src/Schematic.cpp b/core/src/Schematic.cpp new file mode 100644 index 0000000..e26e8b2 --- /dev/null +++ b/core/src/Schematic.cpp @@ -0,0 +1,160 @@ +#include "vf/Schematic.h" + +#include + +namespace vf { + +namespace { + +constexpr uint32_t schematicMagic = 0x56585343u; // "VXSC" +constexpr std::size_t maxSchematicCells = 1u << 26; +constexpr uint8_t voidMaskFlag = 0x01; + +std::size_t packedByteCount(std::size_t cells, uint8_t bits) { + return (cells * static_cast(bits) + 7u) / 8u; +} + +// Indices are packed low bits first. Every supported width divides eight, so a +// value never straddles two bytes and the reader can address it directly. +void packIndices(const std::vector& indices, uint8_t bits, ByteBuffer& out) { + std::size_t base = out.size(); + out.resize(base + packedByteCount(indices.size(), bits)); + uint32_t mask = (1u << bits) - 1u; + for (std::size_t i = 0; i < indices.size(); ++i) { + std::size_t bitPosition = i * bits; + std::size_t byteIndex = base + (bitPosition >> 3); + unsigned shift = static_cast(bitPosition & 7u); + out[byteIndex] |= static_cast((indices[i] & mask) << shift); + } +} + +void unpackIndices(const uint8_t* data, std::size_t cells, uint8_t bits, + std::vector& out) { + out.assign(cells, 0u); + uint32_t mask = (1u << bits) - 1u; + for (std::size_t i = 0; i < cells; ++i) { + std::size_t bitPosition = i * bits; + unsigned shift = static_cast(bitPosition & 7u); + out[i] = static_cast((data[bitPosition >> 3] >> shift) & mask); + } +} + +void buildPalette(const ClipboardBuffer& buffer, std::vector& palette, + std::vector& slotOfBlock) { + palette.clear(); + slotOfBlock.assign(static_cast(Block::Count), 0u); + std::vector used(static_cast(Block::Count), 0u); + used[static_cast(Block::Air)] = 1u; + + const Vec3i& dims = buffer.dims(); + for (int y = 0; y < dims.y; ++y) + for (int z = 0; z < dims.z; ++z) + for (int x = 0; x < dims.x; ++x) { + if (!buffer.filledAt(x, y, z)) continue; + used[static_cast(buffer.at(x, y, z))] = 1u; + } + + palette.push_back(Block::Air); + for (std::size_t id = 0; id < used.size(); ++id) { + if (used[id] == 0u || static_cast(id) == Block::Air) continue; + slotOfBlock[id] = static_cast(palette.size()); + palette.push_back(static_cast(id)); + } +} + +void putI32(ByteWriter& writer, int value) { writer.putU32(static_cast(value)); } + +bool getI32(ByteReader& reader, int& out) { + uint32_t raw = 0; + if (!reader.getU32(raw)) return false; + out = static_cast(static_cast(raw)); + return true; +} + +} // namespace + +ClipboardBuffer::ClipboardBuffer(const Vec3i& dims, const Vec3i& anchor) { + if (dims.x <= 0 || dims.y <= 0 || dims.z <= 0) return; + size = dims; + handle = anchor; + blocks.assign(volume(), Block::Air); + present = SelectionMask(Vec3i{0, 0, 0}, dims); +} + +std::size_t ClipboardBuffer::volume() const { + return static_cast(size.x) * static_cast(size.y) * + static_cast(size.z); +} + +bool ClipboardBuffer::inBounds(int x, int y, int z) const { + return x >= 0 && x < size.x && y >= 0 && y < size.y && z >= 0 && z < size.z; +} + +std::size_t ClipboardBuffer::indexOf(int x, int y, int z) const { + return (static_cast(y) * static_cast(size.z) + + static_cast(z)) * + static_cast(size.x) + + static_cast(x); +} + +Block ClipboardBuffer::at(int x, int y, int z) const { + if (!inBounds(x, y, z)) return Block::Air; + return blocks[indexOf(x, y, z)]; +} + +bool ClipboardBuffer::filledAt(int x, int y, int z) const { + if (!inBounds(x, y, z)) return false; + return present.contains(Vec3i{x, y, z}); +} + +void ClipboardBuffer::put(int x, int y, int z, Block block, bool filled) { + if (!inBounds(x, y, z)) return; + blocks[indexOf(x, y, z)] = block; + present.setCell(Vec3i{x, y, z}, filled); +} + +void ClipboardBuffer::clearCell(int x, int y, int z) { + if (!inBounds(x, y, z)) return; + blocks[indexOf(x, y, z)] = Block::Air; + present.remove(Vec3i{x, y, z}); +} + +ClipboardBuffer ClipboardBuffer::copyFromWorld(const World& world, const Selection& selection) { + if (selection.empty()) return ClipboardBuffer{}; + Vec3i origin = selection.min(); + ClipboardBuffer copy(selection.dims(), Vec3i{0, 0, 0}); + selection.forEach([&](const Vec3i& cell) { + copy.put(cell.x - origin.x, cell.y - origin.y, cell.z - origin.z, + world.blockAt(cell.x, cell.y, cell.z), true); + }); + return copy; +} + +ClipboardBuffer ClipboardBuffer::rotatedY(Rotation rotation) const { + if (rotation == Rotation::None || empty()) return *this; + + bool swapsAxes = rotation == Rotation::Cw90 || rotation == Rotation::Cw270; + Vec3i newDims = swapsAxes ? Vec3i{size.z, size.y, size.x} : size; + + auto mapCell = [&](int x, int y, int z, int& outX, int& outY, int& outZ) { + outY = y; + switch (rotation) { + case Rotation::Cw90: + outX = size.z - 1 - z; + outZ = x; + break; + case Rotation::Cw180: + outX = size.x - 1 - x; + outZ = size.z - 1 - z; + break; + case Rotation::Cw270: + outX = z; + outZ = size.x - 1 - x; + break; + case Rotation::None: + outX = x; + outZ = z; + break; + } + }; +