diff --git a/src/modules/voxelformat/CMakeLists.txt b/src/modules/voxelformat/CMakeLists.txt index ec79a1702..2c4b97665 100644 --- a/src/modules/voxelformat/CMakeLists.txt +++ b/src/modules/voxelformat/CMakeLists.txt @@ -17,6 +17,11 @@ set(SRCS private/benvoxel/BenBinary.h private/benvoxel/BenBinary.cpp private/benvoxel/BenJson.h private/benvoxel/BenJson.cpp private/benvoxel/BenShared.h private/benvoxel/BenShared.cpp + private/benvoxel/SparseVoxelOctree.h private/benvoxel/SparseVoxelOctree.cpp + private/benvoxel/Node.h private/benvoxel/Node.cpp + private/benvoxel/Branch.h private/benvoxel/Branch.cpp + private/benvoxel/Leaf.h private/benvoxel/Leaf.cpp + private/benvoxel/SeekableReadStreamAdapter.hpp private/binvox/BinVoxFormat.h private/binvox/BinVoxFormat.cpp private/chronovox/CSMFormat.h private/chronovox/CSMFormat.cpp diff --git a/src/modules/voxelformat/private/benvoxel/BenShared.cpp b/src/modules/voxelformat/private/benvoxel/BenShared.cpp index bf7919a4a..b1b0098f2 100644 --- a/src/modules/voxelformat/private/benvoxel/BenShared.cpp +++ b/src/modules/voxelformat/private/benvoxel/BenShared.cpp @@ -48,64 +48,6 @@ bool addPointNode(scenegraph::SceneGraph &sceneGraph, const core::String &name, return sceneGraph.emplace(core::move(pointNode), parent) != InvalidNodeId; } -// static bool loadLeaf(io::SeekableReadStream &stream, scenegraph::SceneGraphNode &node, const voxel::Voxel &voxel) { -// return false; -// } - -static bool loadBranch(io::SeekableReadStream &stream, scenegraph::SceneGraphNode &node, int depth) { - uint8_t header; - if (stream.readUInt8(header) != 0) { - Log::error("Failed to read header"); - return InvalidNodeId; - } - // const uint8_t octant = header & 7; - const uint8_t type = (header >> 6) & 3; - switch (type) { - case 0: { // regular branch - const uint8_t children = (header >> 3 & 7) + 1; - for (uint8_t i = 0; i < children; ++i) { - uint8_t childHeader; - if (stream.peekUInt8(childHeader) != 0) { - Log::error("Failed to read child header"); - return InvalidNodeId; - } - // const uint8_t childOctant = childHeader & 7; - if ((childHeader & 0b10000000) > 0) { - // TODO: VOXELFORMAT: load the Sparse Voxel Octree - // if (!loadLeaf(stream, node)) { - // return false; - // } - } else { - if (!loadBranch(stream, node, depth++)) { - return false; - } - } - } - break; - } - case 1: { // collapsed branch - uint8_t color; - if (stream.readUInt8(color) != 0) { - Log::error("Failed to read color for collapsed branch"); - return InvalidNodeId; - } - // TODO: VOXELFORMAT: load the Sparse Voxel Octree - // voxel::Voxel voxel = color == 0 ? voxel::Voxel() : voxel::createVoxel(node.palette(), color); - if (depth == 15) { - for (uint8_t c_octant = 0u; c_octant < 8u; c_octant++) { - // loadLeafVoxels(stream, node, c_octant, voxel); - } - } else { - for (uint8_t c_octant = 0u; c_octant < 8u; c_octant++) { - loadBranch(stream, node, /*voxel, */ depth); - } - } - break; - } - } - return true; -} - int createModelNode(scenegraph::SceneGraph &sceneGraph, const palette::Palette &palette, const core::String &name, int width, int height, int depth, io::SeekableReadStream &stream, const Metadata &globalMetadata, const Metadata &metadata) { @@ -116,13 +58,12 @@ int createModelNode(scenegraph::SceneGraph &sceneGraph, const palette::Palette & node.setPalette(palette); node.setVolume(v, true); - int svoDepth = 0; - while (!stream.eos()) { - if (!loadBranch(stream, node, svoDepth)) { - Log::error("Failed to load branch"); - return InvalidNodeId; - } - } + io::SeekableReadStreamAdapter adapter(stream); + BenVoxel::SparseVoxelOctree svo(adapter, static_cast(width), static_cast(depth), + static_cast(height)); + for (BenVoxel::Voxel voxel : svo.voxels()) + v->setVoxel(static_cast(voxel.x), static_cast(voxel.z), static_cast(voxel.y), + voxel::createVoxel(voxel::VoxelType::Generic, voxel.index, 255, 0)); int nodeId = sceneGraph.emplace(core::move(node)); if (nodeId == InvalidNodeId) { diff --git a/src/modules/voxelformat/private/benvoxel/BenShared.h b/src/modules/voxelformat/private/benvoxel/BenShared.h index 65d60acc1..af9a79e96 100644 --- a/src/modules/voxelformat/private/benvoxel/BenShared.h +++ b/src/modules/voxelformat/private/benvoxel/BenShared.h @@ -4,6 +4,8 @@ #pragma once +#include "SeekableReadStreamAdapter.hpp" +#include "SparseVoxelOctree.h" #include "core/collection/StringMap.h" #include "io/Stream.h" #include "palette/Palette.h" diff --git a/src/modules/voxelformat/private/benvoxel/Branch.cpp b/src/modules/voxelformat/private/benvoxel/Branch.cpp new file mode 100644 index 000000000..891f50036 --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/Branch.cpp @@ -0,0 +1,152 @@ +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include "Branch.h" +namespace BenVoxel { +Branch::Branch() : Node(nullptr, 0), children{} { +} +Branch::Branch(Branch *parent, std::uint8_t octant) : Node(parent, octant & 0b111), children{} { +} +Branch::Branch(Branch *parent, std::uint8_t octant, std::uint8_t color) : Node(parent, octant & 0b111), children{} { + expandCollapsed(color); +} +Branch::Branch(Branch *parent, std::istream &in) : Node(parent, in), children{} { + std::uint8_t header = readByte(in, "Failed to read branch header byte from input stream."); + switch (header & TYPE_MASK) { + case BRANCH_REGULAR: { + std::uint8_t count = ((header >> 3) & 0b111) + 1; + for (std::uint8_t child = 0; child < count; child++) + if (peekByte(in) >> 7) // Check if it's a leaf (both 2-byte and 8-byte start with 1) + set(std::make_unique(this, in)); + else + set(std::make_unique(this, in)); + break; + } + case BRANCH_COLLAPSED: { + expandCollapsed(readByte(in, "Failed to read collapsed branch value from input stream.")); + break; + } + default: + throw std::runtime_error("Invalid branch type in header"); + } +} +void Branch::expandCollapsed(std::uint8_t color) { + if (depth() == 15) + for (std::uint8_t i = 0; i < 8; i++) + set(std::make_unique(this, i, color)); + else + for (std::uint8_t i = 0; i < 8; i++) + set(std::make_unique(this, i, color)); +} +void Branch::write(std::ostream &out) const { + if (!parent && !first()) { // Empty model case + char branchHeaders[15] = {}; + out.write(branchHeaders, sizeof branchHeaders); + out.put(LEAF_2BYTE); // 2-byte payload leaf header + out.put(0); // Both foreground and background zero + out.put(0); + return; + } + std::uint8_t collapsedValue = tryCollapse(); + if (collapsedValue != 0) { + out.put(BRANCH_COLLAPSED | (octant & 0b111)); // Header + out.put(collapsedValue); + return; + } + out.put(BRANCH_REGULAR | ((count() - 1) << 3) | (octant & 0b111)); // Header + for (std::uint8_t i = 0; i < 8; i++) + if (children[i]) + children[i]->write(out); +} +std::uint8_t Branch::count() const { + std::uint8_t count = 0; + for (std::uint8_t i = 0; i < 8; i++) + if (children[i]) + count++; + return count; +} +Node *Branch::first() const { + for (std::uint8_t i = 0; i < 8; i++) + if (children[i]) + return children[i].get(); + return nullptr; +} +Node *Branch::nextValidChild(std::uint8_t previous) const { + for (std::uint8_t i = previous + 1; i < 8; i++) + if (children[i]) + return children[i].get(); + return nullptr; +} +Node *Branch::operator[](std::uint8_t child) const { + return children[child].get(); +} +Branch &Branch::operator=(Branch &&other) noexcept { + if (this != &other) { + octant = other.octant; + parent = other.parent; + children = std::move(other.children); + } + return *this; +} +void Branch::set(std::unique_ptr child) { + if (!child) + throw std::invalid_argument("child should not be nullptr."); + children[child->getOctant()] = std::move(child); +} +void Branch::remove(std::uint8_t child) { + children[child] = nullptr; + if (parent && !first()) + parent->remove(this->octant); +} +std::uint8_t Branch::tryCollapse() const { + return tryCollapsing(tryCollapseGetColor()); +} +std::uint8_t Branch::tryCollapsing(std::uint8_t color) const { + if (color == 0) + return 0; + for (const std::unique_ptr &child : children) { + if (!child) + return 0; + Leaf *leaf = dynamic_cast(child.get()); + if (leaf) { + for (std::uint8_t i = 0; i < 8; i++) + if (leaf->operator[](i) != color) + return 0; + } else { + Branch *branch = dynamic_cast(child.get()); + if (branch && branch->tryCollapsing(color) == 0) + return 0; + } + } + return color; +} +std::uint8_t Branch::tryCollapseGetColor() const { + if (!children[0]) + return 0; + Leaf *leaf = dynamic_cast(children[0].get()); + if (leaf) + return leaf->operator[](0); + Branch *branch = dynamic_cast(children[0].get()); + if (branch) + return branch->tryCollapseGetColor(); + return 0; +} +} // namespace BenVoxel diff --git a/src/modules/voxelformat/private/benvoxel/Branch.h b/src/modules/voxelformat/private/benvoxel/Branch.h new file mode 100644 index 000000000..4075e29ea --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/Branch.h @@ -0,0 +1,51 @@ +#pragma once +#include "Leaf.h" +#include "Node.h" +#include +#include +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +namespace BenVoxel { +class Branch : public Node { +protected: + std::array, 8> children; + +public: + Branch(); + Branch(Branch *parent, std::uint8_t octant); + Branch(Branch *parent, std::uint8_t octant, std::uint8_t color); + Branch(Branch *parent, std::istream &in); + virtual ~Branch() override = default; + void write(std::ostream &out) const override; + std::uint8_t count() const; + Node *first() const; + Node *nextValidChild(std::uint8_t previous) const; + Node *operator[](std::uint8_t child) const; + Branch &operator=(Branch &&other) noexcept; + void set(std::unique_ptr child); + void remove(std::uint8_t child); + void expandCollapsed(std::uint8_t color); + std::uint8_t tryCollapse() const; + std::uint8_t tryCollapsing(std::uint8_t color) const; + std::uint8_t tryCollapseGetColor() const; +}; +} // namespace BenVoxel diff --git a/src/modules/voxelformat/private/benvoxel/Leaf.cpp b/src/modules/voxelformat/private/benvoxel/Leaf.cpp new file mode 100644 index 000000000..2650c0ffb --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/Leaf.cpp @@ -0,0 +1,90 @@ +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include "Leaf.h" +namespace BenVoxel { +Leaf::Leaf(Branch *parent, std::uint8_t octant) : Node(parent, octant & 0b111), data{} { +} +Leaf::Leaf(Branch *parent, std::uint8_t octant, std::uint8_t color) : Node(parent, octant & 0b111), data{} { + data.fill(color); +} +Leaf::Leaf(Branch *parent, std::istream &in) : Node(parent, in), data{} { + std::uint8_t header = readByte(in, "Failed to read leaf header byte from input stream."); + switch (header & TYPE_MASK) { + case LEAF_2BYTE: { + std::uint8_t foreground = readByte(in, "Failed to read foreground voxel from input stream."), + background = readByte(in, "Failed to read background voxel from input stream."); + std::uint8_t where = (header >> 3) & 0b111; + for (std::uint8_t i = 0; i < 8; i++) + data[i] = (i == where) ? foreground : background; + break; + } + case LEAF_8BYTE: { + in.read(reinterpret_cast(data.data()), 8); + break; + } + default: + throw std::runtime_error("Invalid leaf node header type. Expected 10xxxxxx or 11xxxxxx"); + } +} +void Leaf::write(std::ostream &out) const { + std::array, 8> occurrences; + std::uint8_t uniqueCount = 0; + for (std::uint8_t value : data) { + std::array, 8>::iterator it = + std::find_if(occurrences.begin(), occurrences.begin() + uniqueCount, + [value](const std::pair &p) { return p.first == value; }); + if (it != occurrences.begin() + uniqueCount) + it->second++; + else if (uniqueCount < 8) + occurrences[uniqueCount++] = std::pair(value, 1); + } + // Sort by count (ascending), but only sort the portion we've used + std::sort(occurrences.begin(), occurrences.begin() + uniqueCount, + [](const std::pair &a, const std::pair &b) { + return a.second < b.second; + }); + if (uniqueCount == 1) { // Single color - use 2-byte payload leaf with same color + out.put(LEAF_2BYTE | octant & 0b111); // Header + out.put(occurrences[0].first); // Color for both foreground and background + out.put(occurrences[0].first); + } else if (uniqueCount == 2 && occurrences[0].second == 1) { // Two colors with one unique - use 2-byte payload leaf + std::uint8_t uniqueIndex = static_cast( + std::distance(data.begin(), std::find(data.begin(), data.end(), occurrences[0].first))); + out.put(LEAF_2BYTE | (uniqueIndex & 0b111) << 3 | octant & 0b111); // Header + out.put(occurrences[0].first); // Foreground (unique) + out.put(occurrences[1].first); // Background (repeated) + } else { // Multiple colors - use 8-byte payload leaf + out.put(LEAF_8BYTE | octant & 0b111); // Header + out.write(reinterpret_cast(data.data()), 8); // Data + } +} +std::uint8_t Leaf::operator[](std::uint8_t octant) const { + return data[octant]; +} +void Leaf::set(std::uint8_t octant, std::uint8_t index) { + data[octant] = index; + if (parent && + data == + std::array{}) // This checks for all zeroes and modern compilers won't allocate a new array + parent->remove(octant); +} +} // namespace BenVoxel diff --git a/src/modules/voxelformat/private/benvoxel/Leaf.h b/src/modules/voxelformat/private/benvoxel/Leaf.h new file mode 100644 index 000000000..f2d0cbfb6 --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/Leaf.h @@ -0,0 +1,41 @@ +#pragma once +#include "Branch.h" +#include "Node.h" +#include +#include +#include +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +namespace BenVoxel { +class Leaf : public Node { +protected: + std::array data; + +public: + Leaf(Branch *parent, std::uint8_t octant); + Leaf(Branch *parent, std::uint8_t octant, std::uint8_t color); + Leaf(Branch *parent, std::istream &in); + void write(std::ostream &out) const override; + std::uint8_t operator[](std::uint8_t octant) const; + void set(std::uint8_t octant, std::uint8_t index); +}; +} // namespace BenVoxel diff --git a/src/modules/voxelformat/private/benvoxel/Node.cpp b/src/modules/voxelformat/private/benvoxel/Node.cpp new file mode 100644 index 000000000..8bacb30f5 --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/Node.cpp @@ -0,0 +1,80 @@ +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include "Node.h" +#include "Branch.h" +namespace BenVoxel { +Position::Position(std::uint16_t x, std::uint16_t y, std::uint16_t z) : x(x), y(y), z(z) { +} +Node::Node(Branch *parent, std::istream &in) : parent(parent), octant(0) { + octant = peekByte(in, "Failed to peek at node header byte from input stream.") & 0b111; +} +Node::Node(Branch *parent, std::uint8_t header) : octant(header & 0b111), parent(parent) { +} +std::uint8_t Node::getOctant() const { + return octant & 0b111; +} +Branch *Node::getParent() const { + return const_cast(parent); +} +std::uint8_t Node::depth() const { + std::uint8_t d = 0; + const Node *current = this; + while (current->parent) { + d++; + current = current->parent; + } + return d; +} +Position Node::position() const { + std::stack stack = {}; + Node *node = const_cast(this); + while (node) { + stack.push(node); + node = dynamic_cast(node->parent); + } + std::uint8_t count = 17 - static_cast(stack.size()); + std::uint16_t x = 0, y = 0, z = 0; + while (!stack.empty()) { + node = stack.top(); + stack.pop(); + x = (x << 1) | node->getOctant() & 1; + y = (y << 1) | (node->getOctant() >> 1) & 1; + z = (z << 1) | (node->getOctant() >> 2) & 1; + } + x <<= count; + y <<= count; + z <<= count; + return Position(x, y, z); +} +std::uint8_t Node::readByte(std::istream &in, const char *errorMessage) { + int value = in.get(); + if (value < 0) + throw std::runtime_error(errorMessage); + return static_cast(value); +} +std::uint8_t Node::peekByte(std::istream &in, const char *errorMessage) { + int value = in.peek(); + if (value < 0) + throw std::runtime_error(errorMessage); + return static_cast(value); +} +} // namespace BenVoxel diff --git a/src/modules/voxelformat/private/benvoxel/Node.h b/src/modules/voxelformat/private/benvoxel/Node.h new file mode 100644 index 000000000..5e9ebe47d --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/Node.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +namespace BenVoxel { +class Branch; +struct Position { + const std::uint16_t x; + const std::uint16_t y; + const std::uint16_t z; + Position(std::uint16_t x, std::uint16_t y, std::uint16_t z); +}; +class Node { +protected: + std::uint8_t octant; + Branch *parent; // Not owned, nullptr indicates root Branch + static const std::uint8_t BRANCH_REGULAR = 0b00000000, BRANCH_COLLAPSED = 0b01000000, LEAF_2BYTE = 0b10000000, + LEAF_8BYTE = 0b11000000, TYPE_MASK = 0b11000000; + +public: + Node(Branch *parent, std::uint8_t header); + Node(Branch *parent, std::istream &in); + virtual ~Node() = default; + Node(const Node &) = delete; + Node &operator=(const Node &) = delete; + virtual void write(std::ostream &out) const = 0; + std::uint8_t getOctant() const; + Branch *getParent() const; + std::uint8_t depth() const; + Position position() const; + static std::uint8_t readByte(std::istream &in, const char *errorMessage = "Failed to read byte from input stream."); + static std::uint8_t peekByte(std::istream &in, + const char *errorMessage = "Failed to peek at byte from input stream."); +}; +} // namespace BenVoxel diff --git a/src/modules/voxelformat/private/benvoxel/SeekableReadStreamAdapter.hpp b/src/modules/voxelformat/private/benvoxel/SeekableReadStreamAdapter.hpp new file mode 100644 index 000000000..a65db8bcb --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/SeekableReadStreamAdapter.hpp @@ -0,0 +1,107 @@ +#pragma once +#include "io/Stream.h" +#include +#include +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +namespace io { + +class SeekableReadStreamBuf : public std::streambuf { +private: + SeekableReadStream &_stream; + static const int bufferSize = 4096; + char _buffer[bufferSize]; + +public: + explicit SeekableReadStreamBuf(SeekableReadStream &stream) : _stream(stream) { + setg(_buffer, _buffer, _buffer); + } + + explicit SeekableReadStreamBuf(SeekableReadStream *stream) : _stream(*stream) { + setg(_buffer, _buffer, _buffer); + } + +protected: + int_type underflow() override { + if (gptr() < egptr()) { + return traits_type::to_int_type(*gptr()); + } + + int bytesRead = _stream.read(_buffer, bufferSize); + if (bytesRead <= 0) { + return traits_type::eof(); + } + + setg(_buffer, _buffer, _buffer + bytesRead); + return traits_type::to_int_type(*gptr()); + } + + pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override { + if (!(which & std::ios_base::in)) { + return pos_type(off_type(-1)); + } + + int whence; + switch (dir) { + case std::ios_base::beg: + whence = SEEK_SET; + break; + case std::ios_base::cur: + off -= (egptr() - gptr()); + whence = SEEK_CUR; + break; + case std::ios_base::end: + whence = SEEK_END; + break; + default: + return pos_type(off_type(-1)); + } + + int64_t newPos = _stream.seek(off, whence); + if (newPos < 0) { + return pos_type(off_type(-1)); + } + + setg(_buffer, _buffer, _buffer); + return pos_type(newPos); + } + + pos_type seekpos(pos_type pos, std::ios_base::openmode which) override { + return seekoff(off_type(pos), std::ios_base::beg, which); + } +}; + +class SeekableReadStreamAdapter : public std::istream { +private: + SeekableReadStreamBuf _buf; + +public: + explicit SeekableReadStreamAdapter(SeekableReadStream &stream) : std::istream(nullptr), _buf(stream) { + rdbuf(&_buf); + } + + explicit SeekableReadStreamAdapter(SeekableReadStream *stream) : std::istream(nullptr), _buf(stream) { + rdbuf(&_buf); + } +}; + +} // namespace io diff --git a/src/modules/voxelformat/private/benvoxel/SparseVoxelOctree.cpp b/src/modules/voxelformat/private/benvoxel/SparseVoxelOctree.cpp new file mode 100644 index 000000000..e57637a08 --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/SparseVoxelOctree.cpp @@ -0,0 +1,163 @@ +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include "SparseVoxelOctree.h" +namespace BenVoxel { +Voxel::Voxel(std::uint16_t x, std::uint16_t y, std::uint16_t z, std::uint8_t index) : Position(x, y, z), index(index) { +} +SparseVoxelOctree::SparseVoxelOctree() : root(), sizeX(UINT16_MAX), sizeY(UINT16_MAX), sizeZ(UINT16_MAX) { +} +SparseVoxelOctree::SparseVoxelOctree(std::uint16_t sizeX, std::uint16_t sizeY, std::uint16_t sizeZ) + : root(), sizeX(sizeX), sizeY(sizeY), sizeZ(sizeZ) { +} +SparseVoxelOctree::SparseVoxelOctree(std::istream &in) : SparseVoxelOctree() { + std::uint16_t dimensions[3] = {0, 0, 0}; + in.read(reinterpret_cast(dimensions), sizeof(dimensions)); + if (!in.good()) + throw std::runtime_error("Failed to read model dimensions."); + sizeX = dimensions[0]; + sizeY = dimensions[1]; + sizeZ = dimensions[2]; + root = Branch(nullptr, in); +} +SparseVoxelOctree::SparseVoxelOctree(std::istream &in, std::uint16_t sizeX, std::uint16_t sizeY, std::uint16_t sizeZ) + : SparseVoxelOctree(sizeX, sizeY, sizeZ) { + root = Branch(nullptr, in); +} +SparseVoxelOctree::SparseVoxelOctree(std::list voxels, std::uint16_t sizeX, std::uint16_t sizeY, + std::uint16_t sizeZ) + : SparseVoxelOctree(sizeX, sizeY, sizeZ) { + for (const Voxel &voxel : voxels) + set(voxel); +} +SparseVoxelOctree::SparseVoxelOctree(const SparseVoxelOctree &other) + : SparseVoxelOctree(other.voxels(), other.getSizeX(), other.getSizeY(), other.getSizeZ()) { +} +void SparseVoxelOctree::write(std::ostream &out, bool includeSizes) const { + if (includeSizes) { + uint16_t dimensions[3] = {sizeX, sizeY, sizeZ}; + out.write(reinterpret_cast(dimensions), sizeof(dimensions)); + } + root.write(out); +} +std::uint8_t SparseVoxelOctree::operator[](Position &position) const { + return get(position.x, position.y, position.z); +} +std::uint8_t SparseVoxelOctree::get(std::uint16_t x, std::uint16_t y, std::uint16_t z) const { + Branch *branch = const_cast(&root); + for (std::uint8_t level = 15; level > 1; level--) { + Node *node = (*branch)[(z >> level & 1) << 2 | (y >> level & 1) << 1 | x >> level & 1]; + if (dynamic_cast(node)) + branch = dynamic_cast(node); + else + return 0; + } + Node *leaf = (*branch)[((z >> 1 & 1) << 2 | (y >> 1 & 1) << 1 | x >> 1 & 1)]; + if (dynamic_cast(leaf)) + return (*dynamic_cast(leaf))[(z & 1) << 2 | (y & 1) << 1 | x & 1]; + return 0; +} +std::list SparseVoxelOctree::voxels() const { + std::list list = {}; + std::stack stack = {}; + fillStack(stack, const_cast(&root)); + while (!stack.empty()) { + Branch *branch = stack.top(); + stack.pop(); + if (stack.size() == 14) + for (uint8_t octant = 0; octant < 8; octant++) { + Node *node = (*branch)[octant]; + if (dynamic_cast(node)) { + Leaf *leaf = dynamic_cast(node); + Position position = leaf->position(); + for (uint8_t octant = 0; octant < 8; octant++) { + uint8_t index = (*leaf)[octant]; + if (index) + list.push_back(Voxel(position.x + (octant & 1), position.y + ((octant >> 1) & 1), + position.z + ((octant >> 2) & 1), index)); + } + } + } + Branch *parent = branch->getParent(); + if (parent) { + Node *next = parent->nextValidChild(branch->getOctant()); + if (dynamic_cast(next)) + fillStack(stack, dynamic_cast(next)); + } + } + return list; +} +void SparseVoxelOctree::fillStack(std::stack &stack, Branch *branch) { + while (branch) { + stack.push(branch); + branch = dynamic_cast(branch->first()); + } +} +void SparseVoxelOctree::set(Voxel voxel) { + return set(voxel.x, voxel.y, voxel.z, voxel.index); +} +void SparseVoxelOctree::set(std::uint16_t x, std::uint16_t y, std::uint16_t z, std::uint8_t index) { + Branch *branch = &root; + std::uint8_t octant; + for (std::uint8_t level = 15; level > 1; level--) { + octant = (z >> level & 1) << 2 | (y >> level & 1) << 1 | x >> level & 1; + Node *node = (*branch)[octant]; + if (dynamic_cast(node)) + branch = dynamic_cast(node); + else { + if (index == 0) + return; + branch->set(std::make_unique(branch, octant)); + branch = dynamic_cast((*branch)[octant]); + } + } + octant = (z >> 1 & 1) << 2 | (y >> 1 & 1) << 1 | x >> 1 & 1; + Node *node = (*branch)[octant]; + Leaf *leaf = nullptr; + if (!dynamic_cast(node)) { + if (index == 0) + return; + branch->set(std::make_unique(branch, octant)); + leaf = dynamic_cast((*branch)[octant]); + } else { + leaf = dynamic_cast(node); + } + leaf->set((z & 1) << 2 | (y & 1) << 1 | x & 1, index); +} +SparseVoxelOctree::~SparseVoxelOctree() { + clear(); +} +std::uint16_t SparseVoxelOctree::getSizeX() const { + return sizeX; +} +std::uint16_t SparseVoxelOctree::getSizeY() const { + return sizeY; +} +std::uint16_t SparseVoxelOctree::getSizeZ() const { + return sizeZ; +} +void SparseVoxelOctree::clear() { + root = Branch(); + sizeX = UINT16_MAX; + sizeY = UINT16_MAX; + sizeZ = UINT16_MAX; +} +} // namespace BenVoxel diff --git a/src/modules/voxelformat/private/benvoxel/SparseVoxelOctree.h b/src/modules/voxelformat/private/benvoxel/SparseVoxelOctree.h new file mode 100644 index 000000000..d166d32eb --- /dev/null +++ b/src/modules/voxelformat/private/benvoxel/SparseVoxelOctree.h @@ -0,0 +1,60 @@ +#pragma once +#include "Branch.h" +#include +#include +#include +// MIT License +// +// Copyright (c) 2024 Ben McLean +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +namespace BenVoxel { +struct Voxel : Position { + const std::uint8_t index; + Voxel(std::uint16_t x, std::uint16_t y, std::uint16_t z, std::uint8_t index); +}; +class SparseVoxelOctree { +protected: + Branch root; + std::uint16_t sizeX; + std::uint16_t sizeY; + std::uint16_t sizeZ; + static void fillStack(std::stack &stack, Branch *branch); + +public: + SparseVoxelOctree(); + SparseVoxelOctree(std::uint16_t sizeX, std::uint16_t sizeY, std::uint16_t sizeZ); + SparseVoxelOctree(std::istream &in); + SparseVoxelOctree(std::istream &in, std::uint16_t sizeX, std::uint16_t sizeY, std::uint16_t sizeZ); + SparseVoxelOctree(std::list voxels, std::uint16_t sizeX = UINT16_MAX, std::uint16_t sizeY = UINT16_MAX, + std::uint16_t sizeZ = UINT16_MAX); + SparseVoxelOctree(const SparseVoxelOctree &other); + virtual ~SparseVoxelOctree(); + void write(std::ostream &out, bool includeSizes = true) const; + std::uint8_t operator[](Position &position) const; + std::uint16_t getSizeX() const; + std::uint16_t getSizeY() const; + std::uint16_t getSizeZ() const; + std::uint8_t get(std::uint16_t x, std::uint16_t y, std::uint16_t z) const; + void set(Voxel voxel); + void set(std::uint16_t x, std::uint16_t y, std::uint16_t z, std::uint8_t index); + std::list voxels() const; + void clear(); +}; +} // namespace BenVoxel