diff --git a/core/include/vf/Schematic.h b/core/include/vf/Schematic.h new file mode 100644 index 0000000..e28cc35 --- /dev/null +++ b/core/include/vf/Schematic.h @@ -0,0 +1,156 @@ +#pragma once + +#include +#include +#include +#include + +#include "Block.h" +#include "Codec.h" +#include "EditHistory.h" +#include "Math.h" +#include "Selection.h" +#include "World.h" + +namespace vf { + +// Quarter turns around the vertical axis, clockwise seen from above. +enum class Rotation : uint8_t { + None = 0, + Cw90 = 1, + Cw180 = 2, + Cw270 = 3, +}; + +enum class MirrorAxis : uint8_t { + X = 0, + Y = 1, + Z = 2, +}; + +// On-disk format revisions. v1 is dimensions plus a palette-indexed block +// stream; v2 adds metadata and the void mask that lets a non-box selection keep +// its holes. The reader accepts both and hands back v2 either way. +enum class SchematicVersion : uint8_t { + V1 = 1, + V2 = 2, +}; + +enum class SchematicError : uint8_t { + None = 0, + BadMagic = 1, + UnsupportedVersion = 2, + Truncated = 3, + BadChecksum = 4, + DimensionMismatch = 5, + BadPalette = 6, +}; + +// A dense block volume with a per-cell presence flag. Presence is what makes a +// sphere copied out of a world paste back as a sphere instead of as its +// bounding box. +class ClipboardBuffer { +public: + enum class PasteMode : uint8_t { + Overwrite = 0, + IntoAirOnly = 1, + ReplaceExisting = 2, + }; + + ClipboardBuffer() = default; + ClipboardBuffer(const Vec3i& dims, const Vec3i& anchor); + + static ClipboardBuffer copyFromWorld(const World& world, const Selection& selection); + + const Vec3i& dims() const { return size; } + const Vec3i& anchor() const { return handle; } + void setAnchor(const Vec3i& anchor) { handle = anchor; } + + std::size_t volume() const; + std::size_t filledCount() const { return present.count(); } + bool empty() const { return volume() == 0u; } + + bool inBounds(int x, int y, int z) const; + Block at(int x, int y, int z) const; + bool filledAt(int x, int y, int z) const; + void put(int x, int y, int z, Block block, bool filled = true); + void clearCell(int x, int y, int z); + + ClipboardBuffer rotatedY(Rotation rotation) const; + ClipboardBuffer mirrored(MirrorAxis axis) const; + ClipboardBuffer trimmed() const; + + bool sameContent(const ClipboardBuffer& other) const; + + const std::vector& raw() const { return blocks; } + const SelectionMask& presence() const { return present; } + +private: + std::size_t indexOf(int x, int y, int z) const; + + Vec3i size{0, 0, 0}; + Vec3i handle{0, 0, 0}; + std::vector blocks; + SelectionMask present; +}; + +// Everything the reader needs before it touches the block stream. paletteSize +// and indexBits are kept in sync with the buffer, so they describe what the +// next encode() would emit. +struct SchematicHeader { + SchematicVersion version = SchematicVersion::V2; + Vec3i dims{0, 0, 0}; + Vec3i anchor{0, 0, 0}; + std::string name; + std::string author; + uint64_t tick = 0; + uint16_t paletteSize = 0; + uint8_t indexBits = 0; + bool hasVoidMask = false; +}; + +// A portable construction: header plus block volume, encoded into a byte buffer +// and nothing else. No path, no stream, no clock - the creation time is a game +// tick handed in by the caller. +class Schematic { +public: + Schematic() = default; + + static Schematic capture(const World& world, const Selection& selection, + const std::string& name, const std::string& author, uint64_t tick); + static Schematic fromBuffer(const ClipboardBuffer& buffer, const std::string& name, + const std::string& author, uint64_t tick); + + const SchematicHeader& header() const { return meta; } + const ClipboardBuffer& buffer() const { return cells; } + bool migratedFromV1() const { return migrated; } + + void setName(const std::string& name) { meta.name = name; } + void setAuthor(const std::string& author) { meta.author = author; } + void setTick(uint64_t tick) { meta.tick = tick; } + void setAnchor(const Vec3i& anchor); + + ByteBuffer encode() const; + ByteBuffer encode(SchematicVersion version) const; + static SchematicError decode(const uint8_t* data, std::size_t size, Schematic& out); + + void rotate(Rotation rotation); + void mirror(MirrorAxis axis); + void trim(); + + std::size_t pasteInto(World& world, const Vec3i& target, ClipboardBuffer::PasteMode mode) const; + std::size_t pasteInto(World& world, EditHistory& history, const Vec3i& target, + ClipboardBuffer::PasteMode mode, uint64_t tick) const; + + static const char* errorText(SchematicError error); + static uint8_t bitsForPalette(std::size_t paletteSize); + +private: + void refreshHeaderFromBuffer(); + + SchematicHeader meta; + ClipboardBuffer cells; + bool migrated = false; +}; + +} // namespace vf