remove old junk
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
#ifndef GRID3_Serialization
|
||||
#define GRID3_Serialization
|
||||
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include "grid3.hpp"
|
||||
|
||||
constexpr char magic[4] = {'Y', 'G', 'G', '3'};
|
||||
|
||||
// inline bool VoxelGrid::serializeToFile(const std::string& filename) {
|
||||
// std::ofstream file(filename, std::ios::binary);
|
||||
// if (!file.is_open()) {
|
||||
// std::cerr << "failed to open file (serializeToFile): " << filename << std::endl;
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// file.write(magic, 4);
|
||||
// int dims[3] = {gridSize.x, gridSize.y, gridSize.z};
|
||||
// file.write(reinterpret_cast<const char*>(dims), sizeof(dims));
|
||||
// size_t voxelCount = voxels.size();
|
||||
// file.write(reinterpret_cast<const char*>(&voxelCount), sizeof(voxelCount));
|
||||
// for (const Voxel& voxel : voxels) {
|
||||
// auto write_member = [&file](const auto& member) {
|
||||
// file.write(reinterpret_cast<const char*>(&member), sizeof(member));
|
||||
// };
|
||||
|
||||
// std::apply([&write_member](const auto&... members) {
|
||||
// (write_member(members), ...);
|
||||
// }, voxel.members());
|
||||
// }
|
||||
|
||||
// file.close();
|
||||
// return !file.fail();
|
||||
// }
|
||||
|
||||
// std::unique_ptr<VoxelGrid> VoxelGrid::deserializeFromFile(const std::string& filename) {
|
||||
// std::ifstream file(filename, std::ios::binary);
|
||||
// if (!file.is_open()) {
|
||||
// std::cerr << "failed to open file (deserializeFromFile): " << filename << std::endl;
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// // Read and verify magic number
|
||||
// char filemagic[4];
|
||||
// file.read(filemagic, 4);
|
||||
// if (std::strncmp(filemagic, magic, 4) != 0) {
|
||||
// std::cerr << "Error: Invalid file format or corrupted file (expected "
|
||||
// << magic[0] << magic[1] << magic[2] << magic[3]
|
||||
// << ", got " << filemagic[0] << filemagic[1] << filemagic[2] << filemagic[3]
|
||||
// << ")" << std::endl;
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// // Create output grid
|
||||
// auto outgrid = std::make_unique<VoxelGrid>();
|
||||
|
||||
// // Read grid dimensions
|
||||
// int dims[3];
|
||||
// file.read(reinterpret_cast<char*>(dims), sizeof(dims));
|
||||
|
||||
// // Resize grid
|
||||
// outgrid->gridSize = Vec3i(dims[0], dims[1], dims[2]);
|
||||
// outgrid->voxels.resize(dims[0] * dims[1] * dims[2]);
|
||||
|
||||
// // Read voxel count
|
||||
// size_t voxelCount;
|
||||
// file.read(reinterpret_cast<char*>(&voxelCount), sizeof(voxelCount));
|
||||
|
||||
// // Verify voxel count matches grid dimensions
|
||||
// size_t expectedCount = static_cast<size_t>(dims[0]) * dims[1] * dims[2];
|
||||
// if (voxelCount != expectedCount) {
|
||||
// std::cerr << "Error: Voxel count mismatch. Expected " << expectedCount
|
||||
// << ", found " << voxelCount << std::endl;
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// // Read all voxels
|
||||
// for (size_t i = 0; i < voxelCount; ++i) {
|
||||
// auto members = outgrid->voxels[i].members();
|
||||
|
||||
// std::apply([&file](auto&... member) {
|
||||
// ((file.read(reinterpret_cast<char*>(&member), sizeof(member))), ...);
|
||||
// }, members);
|
||||
// }
|
||||
|
||||
// file.close();
|
||||
|
||||
// if (file.fail() && !file.eof()) {
|
||||
// std::cerr << "Error: Failed to read from file: " << filename << std::endl;
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// std::cout << "Successfully loaded grid: " << dims[0] << " x "
|
||||
// << dims[1] << " x " << dims[2] << std::endl;
|
||||
|
||||
// return outgrid;
|
||||
// }
|
||||
|
||||
#endif
|
||||
1706
util/grid/grid2.hpp
1706
util/grid/grid2.hpp
File diff suppressed because it is too large
Load Diff
@@ -1,118 +0,0 @@
|
||||
#ifndef GRID_HPP
|
||||
#define GRID_HPP
|
||||
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include "../vectorlogic/vec4.hpp"
|
||||
#include "../noise/pnoise2.hpp"
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
|
||||
//template <typename DataType, int Dimension>
|
||||
class Node {
|
||||
//static_assert(Dimension == 2 || Dimension == 3, "Dimensions must be 2 or 3");
|
||||
private:
|
||||
public:
|
||||
NodeType type;
|
||||
Vec3f minBounds;
|
||||
Vec3f maxBounds;
|
||||
Vec4ui8 color;
|
||||
enum NodeType {
|
||||
BRANCH,
|
||||
LEAF
|
||||
};
|
||||
std::array<Node, 8> children;
|
||||
Node(const Vec3f min, const Vec3f max, NodeType t = LEAF) : type(t), minBounds(min), maxBounds(max), color(0,0,0,0) {}
|
||||
|
||||
Vec3f getCenter() const {
|
||||
return (minBounds + maxBounds) * 0.5f;
|
||||
}
|
||||
|
||||
int getChildIndex(const Vec3f& pos) const {
|
||||
Vec3f c = getCenter();
|
||||
int index = 0;
|
||||
if (pos.x >= c.x) index |= 1;
|
||||
if (pos.y >= c.y) index |= 2;
|
||||
if (pos.z >= c.z) index |= 4;
|
||||
return index;
|
||||
}
|
||||
|
||||
std::pair<Vec3f, Vec3f> getChildBounds(int childIndex) const {
|
||||
Vec3f c = getCenter();
|
||||
Vec3f cMin = minBounds;
|
||||
Vec3f cMax = maxBounds;
|
||||
if (childIndex & 1) cMin.x = c.x;
|
||||
else cMax.x = c.x;
|
||||
|
||||
if (childIndex & 2) cMin.y = c.y;
|
||||
else cMax.y = c.y;
|
||||
|
||||
if (childIndex & 4) cMin.z = c.z;
|
||||
else cMax.z = c.z;
|
||||
|
||||
return {cMin, cMax};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class Grid {
|
||||
private:
|
||||
Node root;
|
||||
Vec4ui8 DefaultBackgroundColor;
|
||||
PNoise2 noisegen;
|
||||
std::unordered_map<Vec3f, Node, Vec3f::Hash> Cache;
|
||||
public:
|
||||
Grid() : {
|
||||
root = Node(Vec3f(0), Vec3f(0), Node::NodeType::BRANCH);
|
||||
};
|
||||
|
||||
size_t insertVoxel(const Vec3f& pos, const Vec4ui8& color) {
|
||||
if (!contains(pos)) {
|
||||
return -1;
|
||||
}
|
||||
return insertRecusive(root.get(), pos, color, 0);
|
||||
}
|
||||
|
||||
void removeVoxel(const Vec3f& pos) {
|
||||
bool removed = removeRecursive(root.get(), pos, 0);
|
||||
if (removed) {
|
||||
Cache.erase(pos);
|
||||
}
|
||||
}
|
||||
|
||||
Vec4ui8 getVoxel(const Vec3f& pos) const {
|
||||
Node* node = findNode(root.get(), pos, 0);
|
||||
if (node && node->isLeaf()) {
|
||||
return node->color;
|
||||
}
|
||||
return DefaultBackgroundColor;
|
||||
}
|
||||
|
||||
bool hasVoxel(const Vec3f& pos) const {
|
||||
Node* node = findNode(root.get(), pos, 0);
|
||||
return node && node->isLeaf();
|
||||
}
|
||||
|
||||
bool rayIntersect(const Ray3& ray, Vec3f& hitPos, Vec4ui8& hitColor) const {
|
||||
return rayintersectRecursive(root.get(), ray, hitPos, hitColor);
|
||||
}
|
||||
|
||||
std::unordered_map<Vec3f, Vec4ui8> queryRegion(const Vec3f& min, const Vec3f& max) const {
|
||||
std::unordered_map<Vec3f, Vec4ui8> result;
|
||||
queryRegionRecuse(root.get(), min, max, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
Grid& noiseGen(const Vec3f& min, const Vec3f& max, float minc = 0.1f, float maxc = 1.0f, bool genColor = true, int noiseMod = 42) {
|
||||
TIME_FUNCTION;
|
||||
noisegen = PNoise2(noiseMod);
|
||||
std::vector<Vec3f> poses;
|
||||
std::vector<Vec4ui8> colors;
|
||||
|
||||
size_t estimatedSize = (max.x - min.x) * (max.y - min.y) * (max.z - min.z) * (maxc - minc);
|
||||
poses.reserve(estimatedSize);
|
||||
colors.reserve(estimatedSize);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,745 +0,0 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include "../basicdefines.hpp"
|
||||
#include "../timing_decorator.hpp"
|
||||
|
||||
/// @brief Finds the index of the least significant bit set to 1 in a 64-bit integer.
|
||||
/// @details Uses compiler intrinsics (_BitScanForward64, __builtin_ctzll) where available,
|
||||
/// falling back to a De Bruijn sequence multiplication lookup for portability.
|
||||
/// @param v The 64-bit integer to scan.
|
||||
/// @return The zero-based index of the lowest set bit. Behavior is undefined if v is 0.
|
||||
static inline uint32_t FindLowestOn(uint64_t v)
|
||||
{
|
||||
#if defined(_MSC_VER) && defined(TREEXY_USE_INTRINSICS)
|
||||
unsigned long index;
|
||||
_BitScanForward64(&index, v);
|
||||
return static_cast<uint32_t>(index);
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && defined(TREEXY_USE_INTRINSICS)
|
||||
return static_cast<uint32_t>(__builtin_ctzll(v));
|
||||
#else
|
||||
static const unsigned char DeBruijn[64] = {
|
||||
0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28,
|
||||
62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11,
|
||||
63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10,
|
||||
51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12,
|
||||
};
|
||||
// disable unary minus on unsigned warning
|
||||
#if defined(_MSC_VER) && !defined(__NVCC__)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4146)
|
||||
#endif
|
||||
return DeBruijn[uint64_t((v & -v) * UINT64_C(0x022FDD63CC95386D)) >> 58];
|
||||
#if defined(_MSC_VER) && !defined(__NVCC__)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
/// @brief Counts the number of bits set to 1 (population count) in a 64-bit integer.
|
||||
/// @details Uses compiler intrinsics (__popcnt64, __builtin_popcountll) where available,
|
||||
/// falling back to a software Hamming weight implementation.
|
||||
/// @param v The 64-bit integer to count.
|
||||
/// @return The number of bits set to 1.
|
||||
inline uint32_t CountOn(uint64_t v)
|
||||
{
|
||||
#if defined(_MSC_VER) && defined(_M_X64)
|
||||
v = __popcnt64(v);
|
||||
#elif (defined(__GNUC__) || defined(__clang__))
|
||||
v = __builtin_popcountll(v);
|
||||
#else
|
||||
// Software Implementation
|
||||
v = v - ((v >> 1) & uint64_t(0x5555555555555555));
|
||||
v = (v & uint64_t(0x3333333333333333)) + ((v >> 2) & uint64_t(0x3333333333333333));
|
||||
v = (((v + (v >> 4)) & uint64_t(0xF0F0F0F0F0F0F0F)) * uint64_t(0x101010101010101)) >> 56;
|
||||
#endif
|
||||
return static_cast<uint32_t>(v);
|
||||
}
|
||||
|
||||
/// @brief A bitmask class for tracking active cells within a specific grid dimension.
|
||||
/// @tparam LOG2DIM The log base 2 of the dimension size.
|
||||
template<uint32_t LOG2DIM>
|
||||
class Mask {
|
||||
private:
|
||||
static constexpr uint32_t SIZE = std::pow(2, 3 * LOG2DIM);
|
||||
static constexpr uint32_t WORD_COUNT = SIZE / 64;
|
||||
uint64_t mWords[WORD_COUNT];
|
||||
|
||||
/// @brief Internal helper to find the linear index of the first active bit.
|
||||
/// @return The index of the first on bit, or SIZE if none are set.
|
||||
uint32_t findFirstOn() const {
|
||||
const uint64_t *w = mWords;
|
||||
uint32_t n = 0;
|
||||
while (n < WORD_COUNT && !*w) {
|
||||
++w;
|
||||
++n;
|
||||
}
|
||||
return n == WORD_COUNT ? SIZE : (n << 6) + FindLowestOn(*w);
|
||||
}
|
||||
|
||||
/// @brief Internal helper to find the next active bit after a specific index.
|
||||
/// @param start The index to start searching from (inclusive check, though logic implies sequential access).
|
||||
/// @return The index of the next on bit, or SIZE if none are found.
|
||||
uint32_t findNextOn(uint32_t start) const {
|
||||
uint32_t n = start >> 6;
|
||||
if (n >= WORD_COUNT) {
|
||||
return SIZE;
|
||||
}
|
||||
uint32_t m = start & 63;
|
||||
uint64_t b = mWords[n];
|
||||
if (b & (uint64_t(1) << m)) {
|
||||
return start;
|
||||
}
|
||||
b &= ~uint64_t(0) << m;
|
||||
while (!b && ++n < WORD_COUNT) {
|
||||
b = mWords[n];
|
||||
}
|
||||
return (!b ? SIZE : (n << 6) + FindLowestOn(b));
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief Returns the memory size of this Mask instance.
|
||||
static size_t memUsage() {
|
||||
return sizeof(Mask);
|
||||
}
|
||||
|
||||
/// @brief Returns the total capacity (number of bits) in the mask.
|
||||
static uint32_t bitCount() {
|
||||
return SIZE;
|
||||
}
|
||||
|
||||
/// @brief Returns the number of 64-bit words used to store the mask.
|
||||
static uint32_t wordCount() {
|
||||
return WORD_COUNT;
|
||||
}
|
||||
|
||||
/// @brief Retrieves a specific 64-bit word from the mask array.
|
||||
/// @param n The index of the word.
|
||||
/// @return The word value.
|
||||
uint64_t getWord(size_t n) const {
|
||||
return mWords[n];
|
||||
}
|
||||
|
||||
/// @brief Sets a specific 64-bit word in the mask array.
|
||||
/// @param n The index of the word.
|
||||
/// @param v The value to set.
|
||||
void setWord(size_t n, uint64_t v) {
|
||||
mWords[n] = v;
|
||||
}
|
||||
|
||||
/// @brief Calculates the total number of bits set to 1 in the mask.
|
||||
/// @return The count of active bits.
|
||||
uint32_t countOn() const {
|
||||
uint32_t sum = 0;
|
||||
uint32_t n = WORD_COUNT;
|
||||
for (const uint64_t* w = mWords; n--; ++w) {
|
||||
sum += CountOn(*w);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// @brief Iterator class for traversing set bits in the Mask.
|
||||
class Iterator {
|
||||
private:
|
||||
uint32_t mPos;
|
||||
const Mask* mParent;
|
||||
public:
|
||||
/// @brief Default constructor creating an invalid end iterator.
|
||||
Iterator() : mPos(Mask::SIZE), mParent(nullptr) {}
|
||||
|
||||
/// @brief Constructor for a specific position.
|
||||
/// @param pos The current bit index.
|
||||
/// @param parent Pointer to the Mask being iterated.
|
||||
Iterator(uint32_t pos, const Mask* parent) : mPos(pos), mParent(parent) {}
|
||||
|
||||
/// @brief Default assignment operator.
|
||||
Iterator& operator=(const Iterator&) = default;
|
||||
|
||||
/// @brief Dereference operator.
|
||||
/// @return The index of the current active bit.
|
||||
uint32_t operator*() const {
|
||||
return mPos;
|
||||
}
|
||||
|
||||
/// @brief Boolean conversion operator.
|
||||
/// @return True if the iterator is valid (not at end), false otherwise.
|
||||
operator bool() const {
|
||||
return mPos != Mask::SIZE;
|
||||
}
|
||||
|
||||
/// @brief Pre-increment operator. Advances to the next active bit.
|
||||
/// @return Reference to self.
|
||||
Iterator& operator++() {
|
||||
mPos = mParent -> findNextOn(mPos + 1);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Default constructor. Initializes all bits to 0 (off).
|
||||
Mask() {
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
mWords[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Constructor initializing all bits to a specific state.
|
||||
/// @param on If true, all bits are set to 1; otherwise 0.
|
||||
Mask(bool on) {
|
||||
const uint64_t v = on ? ~uint64_t(0) : uint64_t(0);
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
mWords[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Copy constructor.
|
||||
Mask(const Mask &other) {
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
mWords[i] = other.mWords[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Reinterprets internal words as a different type and retrieves one.
|
||||
/// @tparam WordT The type to cast the pointer to (e.g., uint32_t).
|
||||
/// @param n The index in the reinterpreted array.
|
||||
/// @return The value at index n.
|
||||
template<typename WordT>
|
||||
WordT getWord(int n) const {
|
||||
return reinterpret_cast<const WordT *>(mWords)[n];
|
||||
}
|
||||
|
||||
/// @brief Assignment operator.
|
||||
/// @param other The mask to copy from.
|
||||
/// @return Reference to self.
|
||||
Mask &operator=(const Mask &other) {
|
||||
// static_assert(sizeof(Mask) == sizeof(Mask), "Mismatching sizeof");
|
||||
// static_assert(WORD_COUNT == Mask::WORD_COUNT, "Mismatching word count");
|
||||
// static_assert(LOG2DIM == Mask::LOG2DIM, "Mismatching LOG2DIM");
|
||||
uint64_t *src = reinterpret_cast<const uint64_t* >(&other);
|
||||
uint64_t *dst = mWords;
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Equality operator.
|
||||
/// @param other The mask to compare.
|
||||
/// @return True if all bits match, false otherwise.
|
||||
bool operator==(const Mask &other) const {
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
if (mWords[i] != other.mWords[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Inequality operator.
|
||||
/// @param other The mask to compare.
|
||||
/// @return True if any bits differ.
|
||||
bool operator!=(const Mask &other) const {
|
||||
return !((*this) == other);
|
||||
}
|
||||
|
||||
/// @brief Returns an iterator to the first active bit.
|
||||
/// @return An Iterator pointing to the first set bit.
|
||||
Iterator beginOn() const {
|
||||
return Iterator(this->findFirstOn(), this);
|
||||
}
|
||||
|
||||
/// @brief Checks if a specific bit is set.
|
||||
/// @param n The bit index to check.
|
||||
/// @return True if the bit is 1, false if 0.
|
||||
bool isOn(uint32_t n) const {
|
||||
return 0 != (mWords[n >> 6] & (uint64_t(1) << (n&63)));
|
||||
}
|
||||
|
||||
/// @brief Checks if all bits are set to 1.
|
||||
/// @return True if fully saturated.
|
||||
bool isOn() const {
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
if (mWords[i] != ~uint64_t(0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Checks if all bits are set to 0.
|
||||
/// @return True if fully empty.
|
||||
bool isOff() const {
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
if (mWords[i] != ~uint64_t(0)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @brief Sets a specific bit to 1.
|
||||
/// @param n The bit index to set.
|
||||
/// @return True if the bit was already on, false otherwise.
|
||||
bool setOn(uint32_t n) {
|
||||
uint64_t &word = mWords[n >> 6];
|
||||
const uint64_t bit = (uint64_t(1) << (n & 63));
|
||||
bool wasOn = word & bit;
|
||||
word |= bit;
|
||||
return wasOn;
|
||||
}
|
||||
|
||||
/// @brief Sets a specific bit to 0.
|
||||
/// @param n The bit index to clear.
|
||||
void setOff(uint32_t n) {
|
||||
mWords[n >> 6] &= ~(uint64_t(1) << (n & 63));
|
||||
}
|
||||
|
||||
/// @brief Sets a specific bit to the boolean value `On`.
|
||||
/// @param n The bit index.
|
||||
/// @param On The state to set (true=1, false=0).
|
||||
void set(uint32_t n, bool On) {
|
||||
#if 1
|
||||
auto &word = mWords[n >> 6];
|
||||
n &= 63;
|
||||
word &= ~(uint64_t(1) << n);
|
||||
word |= uint64_t(On) << n;
|
||||
#else
|
||||
On ? this->setOn(n) : this->setOff(n);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// @brief Sets all bits to 1.
|
||||
void setOn() {
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
mWords[i] = ~uint64_t(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Sets all bits to 0.
|
||||
void setOff() {
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
mWords[i] = uint64_t(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Sets all bits to a specific boolean state.
|
||||
/// @param on If true, fill with 1s; otherwise 0s.
|
||||
void set(bool on) {
|
||||
const uint64_t v = on ? ~uint64_t(0) : uint64_t(0);
|
||||
for (uint32_t i = 0; i < WORD_COUNT; ++i) {
|
||||
mWords[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Inverts (flips) all bits in the mask.
|
||||
void toggle() {
|
||||
uint32_t n = WORD_COUNT;
|
||||
for (auto* w = mWords; n--; ++w) {
|
||||
*w = ~*w;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Inverts (flips) a specific bit.
|
||||
/// @param n The bit index to toggle.
|
||||
void toggle(uint32_t n) {
|
||||
mWords[n >> 6] ^= uint64_t(1) << (n & 63);
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Represents a generic grid block containing data and a presence mask.
|
||||
/// @tparam DataT The type of data stored in each cell.
|
||||
/// @tparam Log2DIM The log base 2 of the grid dimension (e.g., 3 for 8x8x8).
|
||||
template <typename DataT, int Log2DIM>
|
||||
class Grid {
|
||||
public:
|
||||
constexpr static int DIM = 1 << Log2DIM;
|
||||
constexpr static int SIZE = DIM * DIM * DIM;
|
||||
std::array<DataT, SIZE> data;
|
||||
Mask<Log2DIM> mask;
|
||||
};
|
||||
|
||||
/// @brief A sparse hierarchical voxel grid container.
|
||||
/// @details Implements a 3-level structure: Root Map -> Inner Grid -> Leaf Grid.
|
||||
/// @tparam DataT The type of data stored in the leaf voxels.
|
||||
/// @tparam INNER_BITS Log2 dimension of the inner grid nodes (intermediate layer).
|
||||
/// @tparam LEAF_BITS Log2 dimension of the leaf grid nodes (data layer).
|
||||
template <typename DataT, int INNER_BITS = 2, int LEAF_BITS = 3>
|
||||
class VoxelGrid {
|
||||
public:
|
||||
constexpr static int32_t Log2N = INNER_BITS + LEAF_BITS;
|
||||
using LeafGrid = Grid<DataT, LEAF_BITS>;
|
||||
using InnerGrid = Grid<std::shared_ptr<LeafGrid>, INNER_BITS>;
|
||||
using RootMap = std::unordered_map<Vec3i, InnerGrid>;
|
||||
RootMap root_map;
|
||||
const double resolution;
|
||||
const double inv_resolution;
|
||||
const double half_resolution;
|
||||
|
||||
/// @brief Constructs a VoxelGrid with a specific voxel size.
|
||||
/// @param voxel_size The size of a single voxel in world units.
|
||||
VoxelGrid(double voxel_size) : resolution(voxel_size), inv_resolution(1.0 / voxel_size), half_resolution(0.5 * voxel_size) {}
|
||||
|
||||
/// @brief Calculates the approximate memory usage of the grid structure.
|
||||
/// @return The size in bytes used by the map, inner grids, and leaf grids.
|
||||
size_t getMemoryUsage() const {
|
||||
size_t total_size = 0;
|
||||
for (unsigned i = 0; i < root_map.bucket_count(); ++i) {
|
||||
size_t bucket_size = root_map.bucket_size(i);
|
||||
if (bucket_size == 0) {
|
||||
total_size++;
|
||||
} else {
|
||||
total_size += bucket_size;
|
||||
}
|
||||
}
|
||||
size_t entry_size = sizeof(Vec3i) + sizeof(InnerGrid) + sizeof(void *);
|
||||
total_size += root_map.size() * entry_size;
|
||||
|
||||
for (const auto& [key, inner_grid] : root_map) {
|
||||
total_size += inner_grid.mask.countOn() * sizeof(LeafGrid);
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
/// @brief Converts a 3D float position to integer grid coordinates.
|
||||
/// @param x X coordinate.
|
||||
/// @param y Y coordinate.
|
||||
/// @param z Z coordinate.
|
||||
/// @return The integer grid coordinates.
|
||||
static inline Vec3i PosToCoord(float x, float y, float z) {
|
||||
// union VI {
|
||||
// __m128i m;
|
||||
// int32_t i[4];
|
||||
// };
|
||||
// static __m128 RES = _mm_set1_ps(inv_resolution);
|
||||
// __m128 vect = _mm_set_ps(x, y, z, 0.0);
|
||||
// __m128 res = _mm_mul_ps(vect, RES);
|
||||
// VI out;
|
||||
// out.m = _mm_cvttps_epi32(_mm_floor_ps(res));
|
||||
// return {out.i[3], out.i[2], out.i[1]};
|
||||
|
||||
return Vec3f(x,y,z).floorToI();
|
||||
}
|
||||
|
||||
/// @brief Converts a 3D double position to integer grid coordinates.
|
||||
/// @param x X coordinate.
|
||||
/// @param y Y coordinate.
|
||||
/// @param z Z coordinate.
|
||||
/// @return The integer grid coordinates.
|
||||
static inline Vec3i posToCoord(double x, double y, double z) {
|
||||
return Vec3f(x,y,z).floorToI();
|
||||
}
|
||||
|
||||
/// @brief Converts a Vec3d position to integer grid coordinates.
|
||||
/// @param pos The position vector.
|
||||
/// @return The integer grid coordinates.
|
||||
static inline Vec3i posToCoord(const Vec3d &pos) {
|
||||
return pos.floorToI();
|
||||
}
|
||||
|
||||
/// @brief Converts integer grid coordinates back to world position (center of voxel).
|
||||
/// @param coord The grid coordinate.
|
||||
/// @return The world position center of the voxel.
|
||||
Vec3d Vec3iToPos(const Vec3i& coord) const {
|
||||
return (coord.toDouble() * resolution) + half_resolution;
|
||||
}
|
||||
|
||||
/// @brief Iterates over every active cell in the grid and applies a visitor function.
|
||||
/// @tparam VisitorFunction The type of the callable (DataT& val, Vec3i pos).
|
||||
/// @param func The function to execute for each active voxel.
|
||||
template <class VisitorFunction>
|
||||
void forEachCell(VisitorFunction func) {
|
||||
constexpr static int32_t MASK_LEAF = ((1 << LEAF_BITS) - 1);
|
||||
constexpr static int32_t MASK_INNER = ((1 << INNER_BITS) - 1);
|
||||
for (auto& map_it : root_map) {
|
||||
const Vec3i& root_coord = map_it.first;
|
||||
int32_t xA = root_coord.x;
|
||||
int32_t yA = root_coord.y;
|
||||
int32_t zA = root_coord.z;
|
||||
InnerGrid& inner_grid = map_it.second;
|
||||
auto& mask2 = inner_grid.mask;
|
||||
for (auto inner_it = mask2.beginOn(); inner_it; ++inner_it) {
|
||||
const int32_t inner_index = *inner_it;
|
||||
int32_t xB = xA | ((inner_index & MASK_INNER) << LEAF_BITS);
|
||||
int32_t yB = yA | (((inner_index >> INNER_BITS) & MASK_INNER) << LEAF_BITS);
|
||||
int32_t zB = zA | (((inner_index >> (INNER_BITS* 2)) & MASK_INNER) << LEAF_BITS);
|
||||
|
||||
auto& leaf_grid = inner_grid.data[inner_index];
|
||||
auto& mask1 = leaf_grid->mask;
|
||||
for (auto leaf_it = mask1.beginOn(); leaf_it; ++leaf_it){
|
||||
const int32_t leaf_index = *leaf_it;
|
||||
Vec3i pos = Vec3i(xB | (leaf_index & MASK_LEAF),
|
||||
yB | ((leaf_index >> LEAF_BITS) & MASK_LEAF),
|
||||
zB | ((leaf_index >> (LEAF_BITS * 2)) & MASK_LEAF));
|
||||
func(leaf_grid->data[leaf_index], pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Helper class to accelerate random access to the VoxelGrid by caching recent paths.
|
||||
class Accessor {
|
||||
private:
|
||||
RootMap &root_;
|
||||
Vec3i prev_root_coord_;
|
||||
Vec3i prev_inner_coord_;
|
||||
InnerGrid* prev_inner_ptr_ = nullptr;
|
||||
LeafGrid* prev_leaf_ptr_ = nullptr;
|
||||
public:
|
||||
/// @brief Constructs an Accessor for a specific root map.
|
||||
/// @param root Reference to the grid's root map.
|
||||
Accessor(RootMap& root) : root_(root) {}
|
||||
|
||||
/// @brief Sets a value at a specific coordinate, creating nodes if they don't exist.
|
||||
/// @param coord The grid coordinate.
|
||||
/// @param value The value to store.
|
||||
/// @return True if the voxel was already active, false if it was newly activated.
|
||||
bool setValue(const Vec3i& coord, const DataT& value) {
|
||||
LeafGrid* leaf_ptr = prev_leaf_ptr_;
|
||||
const Vec3i inner_key = getInnerKey(coord);
|
||||
if (inner_key != prev_inner_coord_ || !prev_leaf_ptr_) {
|
||||
InnerGrid* inner_ptr = prev_inner_ptr_;
|
||||
const Vec3i root_key = getRootKey(coord);
|
||||
if (root_key != prev_root_coord_ || !prev_inner_ptr_) {
|
||||
auto root_it = root_.find(root_key);
|
||||
if (root_it == root_.end()) {
|
||||
root_it = root_.insert({root_key, InnerGrid()}).first;
|
||||
}
|
||||
|
||||
inner_ptr = &(root_it->second);
|
||||
prev_root_coord_ = root_key;
|
||||
prev_inner_ptr_ = inner_ptr;
|
||||
}
|
||||
|
||||
const uint32_t inner_index = getInnerIndex(coord);
|
||||
auto& inner_data = inner_ptr->data[inner_index];
|
||||
if (inner_ptr->mask.setOn(inner_index) == false) {
|
||||
inner_data = std::make_shared<LeafGrid>();
|
||||
}
|
||||
|
||||
leaf_ptr = inner_data.get();
|
||||
prev_inner_coord_ = inner_key;
|
||||
prev_leaf_ptr_ = leaf_ptr;
|
||||
}
|
||||
|
||||
const uint32_t leaf_index = getLeafIndex(coord);
|
||||
bool was_on = leaf_ptr->mask.setOn(leaf_index);
|
||||
leaf_ptr->data[leaf_index] = value;
|
||||
return was_on;
|
||||
}
|
||||
|
||||
/// @brief Retrieves a pointer to the value at a coordinate.
|
||||
/// @param coord The grid coordinate.
|
||||
/// @return Pointer to the data if active, otherwise nullptr.
|
||||
DataT* value(const Vec3i& coord) {
|
||||
LeafGrid* leaf_ptr = prev_leaf_ptr_;
|
||||
const Vec3i inner_key = getInnerKey(coord);
|
||||
if (inner_key != prev_inner_coord_ || !prev_inner_ptr_) {
|
||||
InnerGrid* inner_ptr = prev_inner_ptr_;
|
||||
const Vec3i root_key = getRootKey(coord);
|
||||
|
||||
if (root_key != prev_root_coord_ || !prev_inner_ptr_) {
|
||||
auto it = root_.find(root_key);
|
||||
if (it == root_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
inner_ptr = &(it->second);
|
||||
prev_inner_coord_ = root_key;
|
||||
prev_inner_ptr_ = inner_ptr;
|
||||
}
|
||||
|
||||
const uint32_t inner_index = getInnerIndex(coord);
|
||||
auto& inner_data = inner_ptr->data[inner_index];
|
||||
|
||||
if (!inner_ptr->mask.isOn(inner_index)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
leaf_ptr = inner_ptr->data[inner_index].get();
|
||||
prev_inner_coord_ = inner_key;
|
||||
prev_leaf_ptr_ = leaf_ptr;
|
||||
}
|
||||
|
||||
const uint32_t leaf_index = getLeafIndex(coord);
|
||||
if (!leaf_ptr->mask.isOn(leaf_index)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &(leaf_ptr->data[leaf_index]);
|
||||
}
|
||||
|
||||
/// @brief Returns the most recently accessed InnerGrid pointer.
|
||||
/// @return Pointer to the cached InnerGrid.
|
||||
const InnerGrid* lastInnerGrid() const {
|
||||
return prev_inner_ptr_;
|
||||
}
|
||||
|
||||
/// @brief Returns the most recently accessed LeafGrid pointer.
|
||||
/// @return Pointer to the cached LeafGrid.
|
||||
const LeafGrid* lastLeafGrid() const {
|
||||
return prev_leaf_ptr_;
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Creates a new Accessor instance for this grid.
|
||||
/// @return An Accessor object.
|
||||
Accessor createAccessor() {
|
||||
return Accessor(root_map);
|
||||
}
|
||||
|
||||
/// @brief Computes the key for the RootMap based on global coordinates.
|
||||
/// @param coord The global grid coordinate.
|
||||
/// @return The base coordinate for the root key (masked).
|
||||
static inline Vec3i getRootKey(const Vec3i& coord) {
|
||||
constexpr static int32_t MASK = ~((1 << Log2N) - 1);
|
||||
return {coord.x & MASK, coord.y & MASK, coord.z & MASK};
|
||||
}
|
||||
|
||||
/// @brief Computes the key for locating an InnerGrid (intermediate block).
|
||||
/// @param coord The global grid coordinate.
|
||||
/// @return The coordinate masked to the InnerGrid resolution.
|
||||
static inline Vec3i getInnerKey(const Vec3i &coord)
|
||||
{
|
||||
constexpr static int32_t MASK = ~((1 << LEAF_BITS) - 1);
|
||||
return {coord.x & MASK, coord.y & MASK, coord.z & MASK};
|
||||
}
|
||||
|
||||
/// @brief Computes the linear index within an InnerGrid for a given coordinate.
|
||||
/// @param coord The global grid coordinate.
|
||||
/// @return The linear index (0 to size of InnerGrid).
|
||||
static inline uint32_t getInnerIndex(const Vec3i &coord)
|
||||
{
|
||||
constexpr static int32_t MASK = ((1 << INNER_BITS) - 1);
|
||||
// clang-format off
|
||||
return ((coord.x >> LEAF_BITS) & MASK) +
|
||||
(((coord.y >> LEAF_BITS) & MASK) << INNER_BITS) +
|
||||
(((coord.z >> LEAF_BITS) & MASK) << (INNER_BITS * 2));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
/// @brief Computes the linear index within a LeafGrid for a given coordinate.
|
||||
/// @param coord The global grid coordinate.
|
||||
/// @return The linear index (0 to size of LeafGrid).
|
||||
static inline uint32_t getLeafIndex(const Vec3i &coord)
|
||||
{
|
||||
constexpr static int32_t MASK = ((1 << LEAF_BITS) - 1);
|
||||
// clang-format off
|
||||
return (coord.x & MASK) +
|
||||
((coord.y & MASK) << LEAF_BITS) +
|
||||
((coord.z & MASK) << (LEAF_BITS * 2));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
/// @brief Sets the color of a voxel at a specific world position.
|
||||
/// @details Assumes DataT is compatible with Vec3ui8.
|
||||
/// @param worldPos The 3D world position.
|
||||
/// @param color The color value to set.
|
||||
/// @return True if the voxel previously existed, false if created.
|
||||
bool setVoxelColor(const Vec3d& worldPos, const Vec3ui8& color) {
|
||||
Vec3i coord = posToCoord(worldPos);
|
||||
Accessor accessor = createAccessor();
|
||||
return accessor.setValue(coord, color);
|
||||
}
|
||||
|
||||
/// @brief Retrieves the color of a voxel at a specific world position.
|
||||
/// @details Assumes DataT is compatible with Vec3ui8.
|
||||
/// @param worldPos The 3D world position.
|
||||
/// @return Pointer to the color if exists, nullptr otherwise.
|
||||
Vec3ui8* getVoxelColor(const Vec3d& worldPos) {
|
||||
Vec3i coord = posToCoord(worldPos);
|
||||
Accessor accessor = createAccessor();
|
||||
return accessor.value(coord);
|
||||
}
|
||||
|
||||
/// @brief Renders the grid to an RGB buffer
|
||||
/// @details Iterates all cells and projects them onto a 2D plane defined by viewDir and upDir.
|
||||
/// @param buffer The output buffer (will be resized to width * height * 3).
|
||||
/// @param width Width of the output image.
|
||||
/// @param height Height of the output image.
|
||||
/// @param viewOrigin the position of the camera
|
||||
/// @param viewDir The direction the camera is looking.
|
||||
/// @param upDir The up vector of the camera.
|
||||
/// @param fov the field of view for the camera
|
||||
void renderToRGB(std::vector<uint8_t>& buffer, int width, int height, const Vec3d& viewOrigin,
|
||||
const Vec3d& viewDir, const Vec3d& upDir, float fov = 80) {
|
||||
TIME_FUNCTION;
|
||||
buffer.resize(width * height * 3);
|
||||
std::fill(buffer.begin(), buffer.end(), 0);
|
||||
|
||||
// Normalize view direction and compute right vector
|
||||
Vec3d viewDirN = viewDir.normalized();
|
||||
Vec3d upDirN = upDir.normalized();
|
||||
Vec3d rightDir = viewDirN.cross(upDirN).normalized();
|
||||
|
||||
// Recompute up vector to ensure orthogonality
|
||||
Vec3d realUpDir = rightDir.cross(viewDirN).normalized();
|
||||
|
||||
// Compute focal length based on FOV
|
||||
double aspectRatio = static_cast<double>(width) / static_cast<double>(height);
|
||||
double fovRad = fov * M_PI / 180.0;
|
||||
double focalLength = 0.5 / tan(fovRad * 0.5); // Reduced for wider view
|
||||
|
||||
// Pixel to world scaling
|
||||
double pixelWidth = 2.0 * focalLength / width;
|
||||
double pixelHeight = 2.0 * focalLength / height;
|
||||
|
||||
// Create an accessor for efficient voxel lookup
|
||||
Accessor accessor = createAccessor();
|
||||
|
||||
// For each pixel in the output image
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
// Calculate pixel position in camera space
|
||||
double u = (x - width * 0.5) * pixelWidth;
|
||||
double v = (height * 0.5 - y) * pixelHeight;
|
||||
|
||||
// Compute ray direction in world space
|
||||
Vec3d rayDirWorld = viewDirN * focalLength +
|
||||
rightDir * u +
|
||||
realUpDir * v;
|
||||
rayDirWorld = rayDirWorld.normalized();
|
||||
|
||||
// Set up ray marching
|
||||
Vec3d rayPos = viewOrigin;
|
||||
double maxDistance = 1000.0; // Increased maximum ray distance
|
||||
double stepSize = resolution * 0.5; // Smaller step size
|
||||
|
||||
// Ray marching loop
|
||||
bool hit = false;
|
||||
for (double t = 0; t < maxDistance && !hit; t += stepSize) {
|
||||
rayPos = viewOrigin + rayDirWorld * t;
|
||||
|
||||
// Check if we're inside the grid bounds
|
||||
if (rayPos.x < 0 || rayPos.y < 0 || rayPos.z < 0 ||
|
||||
rayPos.x >= 128 || rayPos.y >= 128 || rayPos.z >= 128) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert world position to voxel coordinate
|
||||
Vec3i coord = posToCoord(rayPos);
|
||||
|
||||
// Look up voxel value using accessor
|
||||
DataT* voxelData = accessor.value(coord);
|
||||
|
||||
if (voxelData) {
|
||||
// Voxel hit - extract color
|
||||
Vec3ui8* colorPtr = reinterpret_cast<Vec3ui8*>(voxelData);
|
||||
|
||||
// Get buffer index for this pixel
|
||||
size_t pixelIdx = (y * width + x) * 3;
|
||||
|
||||
// Simple distance-based attenuation
|
||||
double distance = t;
|
||||
double attenuation = 1.0 / (1.0 + distance * 0.01);
|
||||
|
||||
// Store color in buffer with attenuation
|
||||
buffer[pixelIdx] = static_cast<uint8_t>(colorPtr->x * attenuation);
|
||||
buffer[pixelIdx + 1] = static_cast<uint8_t>(colorPtr->y * attenuation);
|
||||
buffer[pixelIdx + 2] = static_cast<uint8_t>(colorPtr->z * attenuation);
|
||||
|
||||
hit = true;
|
||||
break; // Stop ray marching after hitting first voxel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,319 +0,0 @@
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include "../vectorlogic/vec4.hpp"
|
||||
#include "../vecmat/mat4.hpp"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "../output/frame.hpp"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846f
|
||||
#endif
|
||||
|
||||
struct Voxel {
|
||||
bool active;
|
||||
Vec3f color;
|
||||
};
|
||||
|
||||
class VoxelGrid {
|
||||
private:
|
||||
|
||||
public:
|
||||
int width, height, depth;
|
||||
std::vector<Voxel> voxels;
|
||||
VoxelGrid(int w, int h, int d) : width(w), height(h), depth(d) {
|
||||
voxels.resize(w * h * d);
|
||||
// Initialize all voxels as inactive
|
||||
for (auto& v : voxels) {
|
||||
v.active = false;
|
||||
v.color = Vec3f(0.5f, 0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
Voxel& get(int x, int y, int z) {
|
||||
return voxels[z * width * height + y * width + x];
|
||||
}
|
||||
|
||||
const Voxel& get(int x, int y, int z) const {
|
||||
return voxels[z * width * height + y * width + x];
|
||||
}
|
||||
|
||||
void set(int x, int y, int z, bool active, Vec3f color = Vec3f(0.8f, 0.3f, 0.2f)) {
|
||||
if (x >= 0 && x < width && y >= 0 && y < height && z >= 0 && z < depth) {
|
||||
Voxel& v = get(x, y, z);
|
||||
v.active = active;
|
||||
v.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
// Amanatides & Woo ray-grid traversal algorithm
|
||||
bool rayCast(const Vec3f& rayOrigin, const Vec3f& rayDirection, float maxDistance,
|
||||
Vec3f& hitPos, Vec3f& hitNormal, Vec3f& hitColor) const {
|
||||
|
||||
// Initialize step directions
|
||||
Vec3f step;
|
||||
Vec3f tMax, tDelta;
|
||||
|
||||
// Current voxel coordinates
|
||||
Vec3f voxel = rayOrigin.floor();
|
||||
|
||||
// Check if starting outside grid
|
||||
if (voxel.x < 0 || voxel.x >= width ||
|
||||
voxel.y < 0 || voxel.y >= height ||
|
||||
voxel.z < 0 || voxel.z >= depth) {
|
||||
|
||||
// Calculate distance to grid bounds
|
||||
Vec3f t0, t1;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (rayDirection[i] >= 0) {
|
||||
t0[i] = (0 - rayOrigin[i]) / rayDirection[i];
|
||||
t1[i] = (width - rayOrigin[i]) / rayDirection[i];
|
||||
} else {
|
||||
t0[i] = (width - rayOrigin[i]) / rayDirection[i];
|
||||
t1[i] = (0 - rayOrigin[i]) / rayDirection[i];
|
||||
}
|
||||
}
|
||||
|
||||
float tEnter = t0.maxComp();
|
||||
float tExit = t1.minComp();
|
||||
|
||||
if (tEnter > tExit || tExit < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tEnter > 0) {
|
||||
voxel = Vec3f((rayOrigin + rayDirection * tEnter).floor());
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize step and tMax based on ray direction
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (rayDirection[i] < 0) {
|
||||
step[i] = -1;
|
||||
tMax[i] = ((float)voxel[i] - rayOrigin[i]) / rayDirection[i];
|
||||
tDelta[i] = -1.0f / rayDirection[i];
|
||||
} else {
|
||||
step[i] = 1;
|
||||
tMax[i] = ((float)(voxel[i] + 1) - rayOrigin[i]) / rayDirection[i];
|
||||
tDelta[i] = 1.0f / rayDirection[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Main traversal loop
|
||||
float distance = 0;
|
||||
int maxSteps = width + height + depth;
|
||||
int steps = 0;
|
||||
while (distance < maxDistance && steps < maxSteps) {
|
||||
steps++;
|
||||
// Check current voxel
|
||||
if (voxel.x >= 0 && voxel.x < width &&
|
||||
voxel.y >= 0 && voxel.y < height &&
|
||||
voxel.z >= 0 && voxel.z < depth) {
|
||||
|
||||
const Voxel& current = get(voxel.x, voxel.y, voxel.z);
|
||||
if (current.active) {
|
||||
// Hit found
|
||||
hitPos = rayOrigin + rayDirection * distance;
|
||||
hitColor = current.color;
|
||||
|
||||
// Determine hit normal (which plane we hit)
|
||||
if (tMax.x <= tMax.y && tMax.x <= tMax.z) {
|
||||
hitNormal = Vec3f(-step.x, 0, 0);
|
||||
} else if (tMax.y <= tMax.z) {
|
||||
hitNormal = Vec3f(0, -step.y, 0);
|
||||
} else {
|
||||
hitNormal = Vec3f(0, 0, -step.z);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next voxel
|
||||
if (tMax.x < tMax.y) {
|
||||
if (tMax.x < tMax.z) {
|
||||
distance = tMax.x;
|
||||
tMax.x += tDelta.x;
|
||||
voxel.x += step.x;
|
||||
} else {
|
||||
distance = tMax.z;
|
||||
tMax.z += tDelta.z;
|
||||
voxel.z += step.z;
|
||||
}
|
||||
} else {
|
||||
if (tMax.y < tMax.z) {
|
||||
distance = tMax.y;
|
||||
tMax.y += tDelta.y;
|
||||
voxel.y += step.y;
|
||||
} else {
|
||||
distance = tMax.z;
|
||||
tMax.z += tDelta.z;
|
||||
voxel.z += step.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int getWidth() const { return width; }
|
||||
int getHeight() const { return height; }
|
||||
int getDepth() const { return depth; }
|
||||
};
|
||||
|
||||
float radians(float deg) {
|
||||
return deg * (M_PI / 180);
|
||||
}
|
||||
|
||||
// Simple camera class
|
||||
class Camera {
|
||||
public:
|
||||
Vec3f position;
|
||||
Vec3f forward;
|
||||
Vec3f up;
|
||||
float fov;
|
||||
|
||||
Camera() : position(0, 0, -10), forward(0, 0, 1), up(0, 1, 0), fov(80.0f) {}
|
||||
|
||||
Mat4f getViewMatrix() const {
|
||||
return lookAt(position, position + forward, up);
|
||||
}
|
||||
|
||||
Mat4f getProjectionMatrix(float aspectRatio) const {
|
||||
return perspective(radians(fov), aspectRatio, 0.1f, 100.0f);
|
||||
}
|
||||
|
||||
void rotate(float yaw, float pitch) {
|
||||
// Clamp pitch to avoid gimbal lock
|
||||
pitch = std::clamp(pitch, -89.0f * (static_cast<float>(M_PI) / 180.0f), 89.0f * (static_cast<float>(M_PI) / 180.0f));
|
||||
|
||||
forward = Vec3f(
|
||||
cos(yaw) * cos(pitch),
|
||||
sin(pitch),
|
||||
sin(yaw) * cos(pitch)
|
||||
).normalized();
|
||||
}
|
||||
};
|
||||
|
||||
// Simple renderer using ray casting
|
||||
class VoxelRenderer {
|
||||
private:
|
||||
|
||||
public:
|
||||
VoxelGrid grid;
|
||||
Camera camera;
|
||||
VoxelRenderer() : grid(20, 20, 20) { }
|
||||
|
||||
// Render to a frame object
|
||||
frame renderToFrame(int screenWidth, int screenHeight) {
|
||||
|
||||
// Create a frame with RGBA format for color + potential transparency
|
||||
frame renderedFrame(screenWidth, screenHeight, frame::colormap::RGBA);
|
||||
|
||||
float aspectRatio = float(screenWidth) / float(screenHeight);
|
||||
|
||||
// Get matrices
|
||||
Mat4f projection = camera.getProjectionMatrix(aspectRatio);
|
||||
Mat4f view = camera.getViewMatrix();
|
||||
Mat4f invViewProj = (projection * view).inverse();
|
||||
|
||||
// Get reference to frame data for direct pixel writing
|
||||
std::vector<uint8_t>& frameData = const_cast<std::vector<uint8_t>&>(renderedFrame.getData());
|
||||
|
||||
// Background color (dark gray)
|
||||
Vec3f backgroundColor(0.1f, 0.1f, 0.1f);
|
||||
|
||||
// Simple light direction
|
||||
Vec3f lightDir = Vec3f(1, 1, -1).normalized();
|
||||
|
||||
for (int y = 0; y < screenHeight; y++) {
|
||||
for (int x = 0; x < screenWidth; x++) {
|
||||
// Convert screen coordinates to normalized device coordinates
|
||||
float ndcX = (2.0f * x) / screenWidth - 1.0f;
|
||||
float ndcY = 1.0f - (2.0f * y) / screenHeight;
|
||||
|
||||
// Create ray in world space using inverse view-projection
|
||||
Vec4f rayStartNDC = Vec4f(ndcX, ndcY, -1.0f, 1.0f);
|
||||
Vec4f rayEndNDC = Vec4f(ndcX, ndcY, 1.0f, 1.0f);
|
||||
|
||||
// Transform to world space
|
||||
Vec4f rayStartWorld = invViewProj * rayStartNDC;
|
||||
Vec4f rayEndWorld = invViewProj * rayEndNDC;
|
||||
|
||||
// Perspective divide
|
||||
rayStartWorld /= rayStartWorld.w;
|
||||
rayEndWorld /= rayEndWorld.w;
|
||||
|
||||
// Calculate ray direction
|
||||
Vec3f rayStart = Vec3f(rayStartWorld.x, rayStartWorld.y, rayStartWorld.z);
|
||||
Vec3f rayEnd = Vec3f(rayEndWorld.x, rayEndWorld.y, rayEndWorld.z);
|
||||
Vec3f rayDir = (rayEnd - rayStart).normalized();
|
||||
|
||||
// Perform ray casting
|
||||
Vec3f hitPos, hitNormal, hitColor;
|
||||
bool hit = grid.rayCast(camera.position, rayDir, 100.0f, hitPos, hitNormal, hitColor);
|
||||
|
||||
// Calculate pixel index in frame data
|
||||
int pixelIndex = (y * screenWidth + x) * 4; // 4 channels for RGBA
|
||||
|
||||
if (hit) {
|
||||
// Simple lighting
|
||||
float diffuse = std::max(hitNormal.dot(lightDir), 0.2f);
|
||||
|
||||
// Apply lighting to color
|
||||
Vec3f finalColor = hitColor * diffuse;
|
||||
|
||||
// Clamp color values to [0, 1]
|
||||
finalColor.x = std::max(0.0f, std::min(1.0f, finalColor.x));
|
||||
finalColor.y = std::max(0.0f, std::min(1.0f, finalColor.y));
|
||||
finalColor.z = std::max(0.0f, std::min(1.0f, finalColor.z));
|
||||
|
||||
// Convert to 8-bit RGB and set pixel
|
||||
frameData[pixelIndex] = static_cast<uint8_t>(finalColor.x * 255); // R
|
||||
frameData[pixelIndex + 1] = static_cast<uint8_t>(finalColor.y * 255); // G
|
||||
frameData[pixelIndex + 2] = static_cast<uint8_t>(finalColor.z * 255); // B
|
||||
frameData[pixelIndex + 3] = 255; // A (fully opaque)
|
||||
|
||||
// Debug: mark center pixel with a crosshair
|
||||
if (x == screenWidth/2 && y == screenHeight/2) {
|
||||
// White crosshair for center
|
||||
frameData[pixelIndex] = 255;
|
||||
frameData[pixelIndex + 1] = 255;
|
||||
frameData[pixelIndex + 2] = 255;
|
||||
std::cout << "Center ray hit at: " << hitPos.x << ", "
|
||||
<< hitPos.y << ", " << hitPos.z << std::endl;
|
||||
}
|
||||
} else {
|
||||
// Background color
|
||||
frameData[pixelIndex] = static_cast<uint8_t>(backgroundColor.x * 255);
|
||||
frameData[pixelIndex + 1] = static_cast<uint8_t>(backgroundColor.y * 255);
|
||||
frameData[pixelIndex + 2] = static_cast<uint8_t>(backgroundColor.z * 255);
|
||||
frameData[pixelIndex + 3] = 255; // A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return renderedFrame;
|
||||
}
|
||||
|
||||
// Overload for backward compatibility (calls the new method and discards frame)
|
||||
void render(int screenWidth, int screenHeight) {
|
||||
frame tempFrame = renderToFrame(screenWidth, screenHeight);
|
||||
// Optional: print info about the rendered frame
|
||||
std::cout << "Rendered frame: " << tempFrame << std::endl;
|
||||
}
|
||||
|
||||
void rotateCamera(float yaw, float pitch) {
|
||||
camera.rotate(yaw, pitch);
|
||||
}
|
||||
|
||||
void moveCamera(const Vec3f& movement) {
|
||||
camera.position += movement;
|
||||
}
|
||||
|
||||
Camera& getCamera() { return camera; }
|
||||
|
||||
// Get reference to the voxel grid (for testing/debugging)
|
||||
VoxelGrid& getGrid() { return grid; }
|
||||
};
|
||||
@@ -1,530 +0,0 @@
|
||||
#ifndef VOXEL_GENERATORS_HPP
|
||||
#define VOXEL_GENERATORS_HPP
|
||||
|
||||
#include "grid3.hpp"
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "../noise/pnoise2.hpp"
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include <array>
|
||||
|
||||
class VoxelGenerators {
|
||||
public:
|
||||
// Basic Primitive Generators
|
||||
static void createSphere(VoxelGrid& grid, const Vec3f& center, float radius,
|
||||
const Vec3ui8& color = Vec3ui8(255, 255, 255),
|
||||
bool filled = true) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
Vec3i gridCenter = (center / grid.binSize).floorToI();
|
||||
Vec3i radiusVoxels = Vec3i(static_cast<int>(radius / grid.binSize));
|
||||
|
||||
Vec3i minBounds = gridCenter - radiusVoxels;
|
||||
Vec3i maxBounds = gridCenter + radiusVoxels;
|
||||
|
||||
// Ensure bounds are within grid
|
||||
minBounds = minBounds.max(Vec3i(0, 0, 0));
|
||||
maxBounds = maxBounds.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1));
|
||||
|
||||
float radiusSq = radius * radius;
|
||||
|
||||
for (int z = minBounds.z; z <= maxBounds.z; ++z) {
|
||||
for (int y = minBounds.y; y <= maxBounds.y; ++y) {
|
||||
for (int x = minBounds.x; x <= maxBounds.x; ++x) {
|
||||
Vec3f voxelCenter(x * grid.binSize, y * grid.binSize, z * grid.binSize);
|
||||
Vec3f delta = voxelCenter - center;
|
||||
float distanceSq = delta.lengthSquared();
|
||||
|
||||
if (filled) {
|
||||
// Solid sphere
|
||||
if (distanceSq <= radiusSq) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
} else {
|
||||
// Hollow sphere (shell)
|
||||
float shellThickness = grid.binSize;
|
||||
if (distanceSq <= radiusSq && distanceSq >= (radius - shellThickness) * (radius - shellThickness)) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
static void createCube(VoxelGrid& grid, const Vec3f& center, const Vec3f& size,
|
||||
const Vec3ui8& color = Vec3ui8(255, 255, 255),
|
||||
bool filled = true) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
Vec3f halfSize = size * 0.5f;
|
||||
Vec3f minPos = center - halfSize;
|
||||
Vec3f maxPos = center + halfSize;
|
||||
|
||||
Vec3i minVoxel = (minPos / grid.binSize).floorToI();
|
||||
Vec3i maxVoxel = (maxPos / grid.binSize).floorToI();
|
||||
|
||||
// Clamp to grid bounds
|
||||
minVoxel = minVoxel.max(Vec3i(0, 0, 0));
|
||||
maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1));
|
||||
|
||||
if (filled) {
|
||||
// Solid cube
|
||||
for (int z = minVoxel.z; z <= maxVoxel.z; ++z) {
|
||||
for (int y = minVoxel.y; y <= maxVoxel.y; ++y) {
|
||||
for (int x = minVoxel.x; x <= maxVoxel.x; ++x) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Hollow cube (just the faces)
|
||||
for (int z = minVoxel.z; z <= maxVoxel.z; ++z) {
|
||||
for (int y = minVoxel.y; y <= maxVoxel.y; ++y) {
|
||||
for (int x = minVoxel.x; x <= maxVoxel.x; ++x) {
|
||||
// Check if on any face
|
||||
bool onFace = (x == minVoxel.x || x == maxVoxel.x ||
|
||||
y == minVoxel.y || y == maxVoxel.y ||
|
||||
z == minVoxel.z || z == maxVoxel.z);
|
||||
|
||||
if (onFace) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
static void createCylinder(VoxelGrid& grid, const Vec3f& center, float radius, float height,
|
||||
const Vec3ui8& color = Vec3ui8(255, 255, 255),
|
||||
bool filled = true, int axis = 1) { // 0=X, 1=Y, 2=Z
|
||||
TIME_FUNCTION;
|
||||
|
||||
Vec3f halfHeight = Vec3f(0, 0, 0);
|
||||
halfHeight[axis] = height * 0.5f;
|
||||
|
||||
Vec3f minPos = center - halfHeight;
|
||||
Vec3f maxPos = center + halfHeight;
|
||||
|
||||
Vec3i minVoxel = (minPos / grid.binSize).floorToI();
|
||||
Vec3i maxVoxel = (maxPos / grid.binSize).floorToI();
|
||||
|
||||
minVoxel = minVoxel.max(Vec3i(0, 0, 0));
|
||||
maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1));
|
||||
|
||||
float radiusSq = radius * radius;
|
||||
|
||||
for (int k = minVoxel[axis]; k <= maxVoxel[axis]; ++k) {
|
||||
// Iterate through the other two dimensions
|
||||
for (int j = minVoxel[(axis + 1) % 3]; j <= maxVoxel[(axis + 1) % 3]; ++j) {
|
||||
for (int i = minVoxel[(axis + 2) % 3]; i <= maxVoxel[(axis + 2) % 3]; ++i) {
|
||||
Vec3i pos;
|
||||
pos[axis] = k;
|
||||
pos[(axis + 1) % 3] = j;
|
||||
pos[(axis + 2) % 3] = i;
|
||||
|
||||
Vec3f voxelCenter = pos.toFloat() * grid.binSize;
|
||||
|
||||
// Calculate distance from axis
|
||||
float dx = voxelCenter.x - center.x;
|
||||
float dy = voxelCenter.y - center.y;
|
||||
float dz = voxelCenter.z - center.z;
|
||||
|
||||
// Zero out the axis component
|
||||
if (axis == 0) dx = 0;
|
||||
else if (axis == 1) dy = 0;
|
||||
else dz = 0;
|
||||
|
||||
float distanceSq = dx*dx + dy*dy + dz*dz;
|
||||
|
||||
if (filled) {
|
||||
if (distanceSq <= radiusSq) {
|
||||
grid.set(pos, true, color);
|
||||
}
|
||||
} else {
|
||||
float shellThickness = grid.binSize;
|
||||
if (distanceSq <= radiusSq &&
|
||||
distanceSq >= (radius - shellThickness) * (radius - shellThickness)) {
|
||||
grid.set(pos, true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
static void createCone(VoxelGrid& grid, const Vec3f& baseCenter, float radius, float height,
|
||||
const Vec3ui8& color = Vec3ui8(255, 255, 255),
|
||||
bool filled = true, int axis = 1) { // 0=X, 1=Y, 2=Z
|
||||
TIME_FUNCTION;
|
||||
|
||||
Vec3f tip = baseCenter;
|
||||
tip[axis] += height;
|
||||
|
||||
Vec3f minPos = baseCenter.min(tip);
|
||||
Vec3f maxPos = baseCenter.max(tip);
|
||||
|
||||
// Expand by radius in other dimensions
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (i != axis) {
|
||||
minPos[i] -= radius;
|
||||
maxPos[i] += radius;
|
||||
}
|
||||
}
|
||||
|
||||
Vec3i minVoxel = (minPos / grid.binSize).floorToI();
|
||||
Vec3i maxVoxel = (maxPos / grid.binSize).floorToI();
|
||||
|
||||
minVoxel = minVoxel.max(Vec3i(0, 0, 0));
|
||||
maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1));
|
||||
|
||||
for (int k = minVoxel[axis]; k <= maxVoxel[axis]; ++k) {
|
||||
// Current height from base
|
||||
float h = (k * grid.binSize) - baseCenter[axis];
|
||||
if (h < 0 || h > height) continue;
|
||||
|
||||
// Current radius at this height
|
||||
float currentRadius = radius * (1.0f - h / height);
|
||||
|
||||
for (int j = minVoxel[(axis + 1) % 3]; j <= maxVoxel[(axis + 1) % 3]; ++j) {
|
||||
for (int i = minVoxel[(axis + 2) % 3]; i <= maxVoxel[(axis + 2) % 3]; ++i) {
|
||||
Vec3i pos;
|
||||
pos[axis] = k;
|
||||
pos[(axis + 1) % 3] = j;
|
||||
pos[(axis + 2) % 3] = i;
|
||||
|
||||
Vec3f voxelCenter = pos.toFloat() * grid.binSize;
|
||||
|
||||
// Calculate distance from axis
|
||||
float dx = voxelCenter.x - baseCenter.x;
|
||||
float dy = voxelCenter.y - baseCenter.y;
|
||||
float dz = voxelCenter.z - baseCenter.z;
|
||||
|
||||
// Zero out the axis component
|
||||
if (axis == 0) dx = 0;
|
||||
else if (axis == 1) dy = 0;
|
||||
else dz = 0;
|
||||
|
||||
float distanceSq = dx*dx + dy*dy + dz*dz;
|
||||
|
||||
if (filled) {
|
||||
if (distanceSq <= currentRadius * currentRadius) {
|
||||
grid.set(pos, true, color);
|
||||
}
|
||||
} else {
|
||||
float shellThickness = grid.binSize;
|
||||
if (distanceSq <= currentRadius * currentRadius &&
|
||||
distanceSq >= (currentRadius - shellThickness) * (currentRadius - shellThickness)) {
|
||||
grid.set(pos, true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
static void createTorus(VoxelGrid& grid, const Vec3f& center, float majorRadius, float minorRadius,
|
||||
const Vec3ui8& color = Vec3ui8(255, 255, 255)) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
float outerRadius = majorRadius + minorRadius;
|
||||
Vec3f minPos = center - Vec3f(outerRadius, outerRadius, minorRadius);
|
||||
Vec3f maxPos = center + Vec3f(outerRadius, outerRadius, minorRadius);
|
||||
|
||||
Vec3i minVoxel = (minPos / grid.binSize).floorToI();
|
||||
Vec3i maxVoxel = (maxPos / grid.binSize).floorToI();
|
||||
|
||||
minVoxel = minVoxel.max(Vec3i(0, 0, 0));
|
||||
maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1));
|
||||
|
||||
for (int z = minVoxel.z; z <= maxVoxel.z; ++z) {
|
||||
for (int y = minVoxel.y; y <= maxVoxel.y; ++y) {
|
||||
for (int x = minVoxel.x; x <= maxVoxel.x; ++x) {
|
||||
Vec3f pos(x * grid.binSize, y * grid.binSize, z * grid.binSize);
|
||||
Vec3f delta = pos - center;
|
||||
|
||||
// Torus equation: (sqrt(x² + y²) - R)² + z² = r²
|
||||
float xyDist = std::sqrt(delta.x * delta.x + delta.y * delta.y);
|
||||
float distToCircle = xyDist - majorRadius;
|
||||
float distanceSq = distToCircle * distToCircle + delta.z * delta.z;
|
||||
|
||||
if (distanceSq <= minorRadius * minorRadius) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
// Procedural Generators
|
||||
static void createPerlinNoiseTerrain(VoxelGrid& grid, float frequency = 0.1f, float amplitude = 10.0f,
|
||||
int octaves = 4, float persistence = 0.5f,
|
||||
const Vec3ui8& baseColor = Vec3ui8(34, 139, 34)) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
if (grid.getHeight() < 1) return;
|
||||
|
||||
PNoise2 noise;
|
||||
|
||||
for (int z = 0; z < grid.getDepth(); ++z) {
|
||||
for (int x = 0; x < grid.getWidth(); ++x) {
|
||||
// Generate height value using Perlin noise
|
||||
float heightValue = 0.0f;
|
||||
float freq = frequency;
|
||||
float amp = amplitude;
|
||||
|
||||
for (int octave = 0; octave < octaves; ++octave) {
|
||||
float nx = x * freq / 100.0f;
|
||||
float nz = z * freq / 100.0f;
|
||||
heightValue += noise.permute(Vec2f(nx, nz)) * amp;
|
||||
|
||||
freq *= 2.0f;
|
||||
amp *= persistence;
|
||||
}
|
||||
|
||||
// Normalize and scale to grid height
|
||||
int terrainHeight = static_cast<int>((heightValue + amplitude) / (2.0f * amplitude) * grid.getHeight());
|
||||
terrainHeight = std::max(0, std::min(grid.getHeight() - 1, terrainHeight));
|
||||
|
||||
// Create column of voxels
|
||||
for (int y = 0; y <= terrainHeight; ++y) {
|
||||
// Color gradient based on height
|
||||
float t = static_cast<float>(y) / grid.getHeight();
|
||||
Vec3ui8 color = baseColor;
|
||||
|
||||
// Add some color variation
|
||||
if (t < 0.3f) {
|
||||
// Water level
|
||||
color = Vec3ui8(30, 144, 255);
|
||||
} else if (t < 0.5f) {
|
||||
// Sand
|
||||
color = Vec3ui8(238, 214, 175);
|
||||
} else if (t < 0.8f) {
|
||||
// Grass
|
||||
color = baseColor;
|
||||
} else {
|
||||
// Snow
|
||||
color = Vec3ui8(255, 250, 250);
|
||||
}
|
||||
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
static void createMengerSponge(VoxelGrid& grid, int iterations = 3,
|
||||
const Vec3ui8& color = Vec3ui8(255, 255, 255)) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
// Start with a solid cube
|
||||
createCube(grid,
|
||||
Vec3f(grid.getWidth() * grid.binSize * 0.5f,
|
||||
grid.getHeight() * grid.binSize * 0.5f,
|
||||
grid.getDepth() * grid.binSize * 0.5f),
|
||||
Vec3f(grid.getWidth() * grid.binSize,
|
||||
grid.getHeight() * grid.binSize,
|
||||
grid.getDepth() * grid.binSize),
|
||||
color, true);
|
||||
|
||||
// Apply Menger sponge iteration
|
||||
for (int iter = 0; iter < iterations; ++iter) {
|
||||
int divisor = static_cast<int>(std::pow(3, iter + 1));
|
||||
|
||||
// Calculate the pattern
|
||||
for (int z = 0; z < grid.getDepth(); ++z) {
|
||||
for (int y = 0; y < grid.getHeight(); ++y) {
|
||||
for (int x = 0; x < grid.getWidth(); ++x) {
|
||||
// Check if this voxel should be removed in this iteration
|
||||
int modX = x % divisor;
|
||||
int modY = y % divisor;
|
||||
int modZ = z % divisor;
|
||||
|
||||
int third = divisor / 3;
|
||||
|
||||
// Remove center cubes
|
||||
if ((modX >= third && modX < 2 * third) &&
|
||||
(modY >= third && modY < 2 * third)) {
|
||||
grid.set(Vec3i(x, y, z), false, color);
|
||||
}
|
||||
if ((modX >= third && modX < 2 * third) &&
|
||||
(modZ >= third && modZ < 2 * third)) {
|
||||
grid.set(Vec3i(x, y, z), false, color);
|
||||
}
|
||||
if ((modY >= third && modY < 2 * third) &&
|
||||
(modZ >= third && modZ < 2 * third)) {
|
||||
grid.set(Vec3i(x, y, z), false, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
// Helper function to check if a point is inside a polygon (for 2D shapes)
|
||||
static bool pointInPolygon(const Vec2f& point, const std::vector<Vec2f>& polygon) {
|
||||
bool inside = false;
|
||||
size_t n = polygon.size();
|
||||
|
||||
for (size_t i = 0, j = n - 1; i < n; j = i++) {
|
||||
if (((polygon[i].y > point.y) != (polygon[j].y > point.y)) &&
|
||||
(point.x < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) /
|
||||
(polygon[j].y - polygon[i].y) + polygon[i].x)) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
// Utah Teapot (simplified voxel approximation)
|
||||
static void createTeapot(VoxelGrid& grid, const Vec3f& position, float scale = 1.0f,
|
||||
const Vec3ui8& color = Vec3ui8(200, 200, 200)) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
// Simplified teapot using multiple primitive components
|
||||
Vec3f center = position;
|
||||
|
||||
// Body (ellipsoid)
|
||||
createSphere(grid, center, 3.0f * scale, color, false);
|
||||
|
||||
// Spout (rotated cylinder)
|
||||
Vec3f spoutStart = center + Vec3f(2.0f * scale, 0, 0);
|
||||
Vec3f spoutEnd = center + Vec3f(4.0f * scale, 1.5f * scale, 0);
|
||||
createCylinderBetween(grid, spoutStart, spoutEnd, 0.5f * scale, color, true);
|
||||
|
||||
// Handle (semi-circle)
|
||||
Vec3f handleStart = center + Vec3f(-2.0f * scale, 0, 0);
|
||||
Vec3f handleEnd = center + Vec3f(-3.0f * scale, 2.0f * scale, 0);
|
||||
createCylinderBetween(grid, handleStart, handleEnd, 0.4f * scale, color, true);
|
||||
|
||||
// Lid (small cylinder on top)
|
||||
Vec3f lidCenter = center + Vec3f(0, 3.0f * scale, 0);
|
||||
createCylinder(grid, lidCenter, 1.0f * scale, 0.5f * scale, color, true, 1);
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
static void createCylinderBetween(VoxelGrid& grid, const Vec3f& start, const Vec3f& end, float radius,
|
||||
const Vec3ui8& color, bool filled = true) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
Vec3f direction = (end - start).normalized();
|
||||
float length = (end - start).length();
|
||||
|
||||
// Create local coordinate system
|
||||
Vec3f up(0, 1, 0);
|
||||
if (std::abs(direction.dot(up)) > 0.99f) {
|
||||
up = Vec3f(1, 0, 0);
|
||||
}
|
||||
|
||||
Vec3f right = direction.cross(up).normalized();
|
||||
Vec3f localUp = right.cross(direction).normalized();
|
||||
|
||||
Vec3f minPos = start.min(end) - Vec3f(radius, radius, radius);
|
||||
Vec3f maxPos = start.max(end) + Vec3f(radius, radius, radius);
|
||||
|
||||
Vec3i minVoxel = (minPos / grid.binSize).floorToI();
|
||||
Vec3i maxVoxel = (maxPos / grid.binSize).floorToI();
|
||||
|
||||
minVoxel = minVoxel.max(Vec3i(0, 0, 0));
|
||||
maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1));
|
||||
|
||||
float radiusSq = radius * radius;
|
||||
|
||||
for (int z = minVoxel.z; z <= maxVoxel.z; ++z) {
|
||||
for (int y = minVoxel.y; y <= maxVoxel.y; ++y) {
|
||||
for (int x = minVoxel.x; x <= maxVoxel.x; ++x) {
|
||||
Vec3f voxelPos(x * grid.binSize, y * grid.binSize, z * grid.binSize);
|
||||
|
||||
// Project point onto cylinder axis
|
||||
Vec3f toPoint = voxelPos - start;
|
||||
float t = toPoint.dot(direction);
|
||||
|
||||
// Check if within cylinder length
|
||||
if (t < 0 || t > length) continue;
|
||||
|
||||
Vec3f projected = start + direction * t;
|
||||
Vec3f delta = voxelPos - projected;
|
||||
float distanceSq = delta.lengthSquared();
|
||||
|
||||
if (filled) {
|
||||
if (distanceSq <= radiusSq) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
} else {
|
||||
float shellThickness = grid.binSize;
|
||||
if (distanceSq <= radiusSq &&
|
||||
distanceSq >= (radius - shellThickness) * (radius - shellThickness)) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate from mathematical function
|
||||
template<typename Func>
|
||||
static void createFromFunction(VoxelGrid& grid, Func func,
|
||||
const Vec3f& minBounds, const Vec3f& maxBounds,
|
||||
float threshold = 0.5f,
|
||||
const Vec3ui8& color = Vec3ui8(255, 255, 255)) {
|
||||
TIME_FUNCTION;
|
||||
|
||||
Vec3i minVoxel = (minBounds / grid.binSize).floorToI();
|
||||
Vec3i maxVoxel = (maxBounds / grid.binSize).floorToI();
|
||||
|
||||
minVoxel = minVoxel.max(Vec3i(0, 0, 0));
|
||||
maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1));
|
||||
|
||||
for (int z = minVoxel.z; z <= maxVoxel.z; ++z) {
|
||||
for (int y = minVoxel.y; y <= maxVoxel.y; ++y) {
|
||||
for (int x = minVoxel.x; x <= maxVoxel.x; ++x) {
|
||||
Vec3f pos(x * grid.binSize, y * grid.binSize, z * grid.binSize);
|
||||
|
||||
float value = func(pos.x, pos.y, pos.z);
|
||||
|
||||
if (value > threshold) {
|
||||
grid.set(Vec3i(x, y, z), true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.clearMeshCache();
|
||||
}
|
||||
|
||||
// Example mathematical functions
|
||||
static float sphereFunction(float x, float y, float z) {
|
||||
return 1.0f - (x*x + y*y + z*z);
|
||||
}
|
||||
|
||||
static float torusFunction(float x, float y, float z, float R = 2.0f, float r = 1.0f) {
|
||||
float d = std::sqrt(x*x + y*y) - R;
|
||||
return r*r - d*d - z*z;
|
||||
}
|
||||
|
||||
static float gyroidFunction(float x, float y, float z, float scale = 0.5f) {
|
||||
x *= scale; y *= scale; z *= scale;
|
||||
return std::sin(x) * std::cos(y) + std::sin(y) * std::cos(z) + std::sin(z) * std::cos(x);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,307 +0,0 @@
|
||||
#ifndef SPRITE2_HPP
|
||||
#define SPRITE2_HPP
|
||||
|
||||
#include "grid2.hpp"
|
||||
#include "../output/frame.hpp"
|
||||
|
||||
class SpriteMap2 : public Grid2 {
|
||||
private:
|
||||
// id, sprite
|
||||
std::unordered_map<size_t, frame> spritesComped;
|
||||
std::unordered_map<size_t, int> Layers;
|
||||
std::unordered_map<size_t, float> Orientations;
|
||||
|
||||
public:
|
||||
using Grid2::Grid2;
|
||||
|
||||
size_t addSprite(const Vec2& pos, frame sprite, int layer = 0, float orientation = 0.0f) {
|
||||
size_t id = addObject(pos, Vec4(0,0,0,0));
|
||||
spritesComped[id] = sprite;
|
||||
Layers[id] = layer;
|
||||
Orientations[id] = orientation;
|
||||
return id;
|
||||
}
|
||||
|
||||
frame getSprite(size_t id) {
|
||||
return spritesComped.at(id);
|
||||
}
|
||||
|
||||
void setSprite(size_t id, const frame& sprite) {
|
||||
spritesComped[id] = sprite;
|
||||
}
|
||||
|
||||
int getLayer(size_t id) {
|
||||
return Layers.at(id);
|
||||
}
|
||||
|
||||
size_t setLayer(size_t id, int layer) {
|
||||
Layers[id] = layer;
|
||||
return id;
|
||||
}
|
||||
|
||||
float getOrientation(size_t id) {
|
||||
return Orientations.at(id);
|
||||
}
|
||||
|
||||
size_t setOrientation(size_t id, float orientation) {
|
||||
Orientations[id] = orientation;
|
||||
return id;
|
||||
}
|
||||
|
||||
void getGridRegionAsBGR(const Vec2& minCorner, const Vec2& maxCorner, int& width, int& height, std::vector<uint8_t>& rgbData) const {
|
||||
TIME_FUNCTION;
|
||||
|
||||
// Calculate dimensions
|
||||
width = static_cast<int>(maxCorner.x - minCorner.x);
|
||||
height = static_cast<int>(maxCorner.y - minCorner.y);
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = height = 0;
|
||||
rgbData.clear();
|
||||
rgbData.shrink_to_fit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize RGBA buffer for compositing
|
||||
std::vector<Vec4> rgbaBuffer(width * height, Vec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
// Group sprites by layer for proper rendering order
|
||||
std::vector<std::pair<int, size_t>> layeredSprites;
|
||||
for (const auto& [id, pos] : Positions) {
|
||||
if (spritesComped.find(id) != spritesComped.end()) {
|
||||
layeredSprites.emplace_back(Layers.at(id), id);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by layer (lower layers first)
|
||||
std::sort(layeredSprites.begin(), layeredSprites.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
|
||||
// Render each sprite in layer order
|
||||
for (const auto& [layer, id] : layeredSprites) {
|
||||
const Vec2& pos = Positions.at(id);
|
||||
const frame& sprite = spritesComped.at(id);
|
||||
float orientation = Orientations.at(id);
|
||||
|
||||
// Decompress sprite if needed
|
||||
frame decompressedSprite = sprite;
|
||||
if (sprite.isCompressed()) {
|
||||
decompressedSprite.decompress();
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& spriteData = decompressedSprite.getData();
|
||||
size_t spriteWidth = decompressedSprite.getWidth();
|
||||
size_t spriteHeight = decompressedSprite.getHeight();
|
||||
|
||||
if (spriteData.empty() || spriteWidth == 0 || spriteHeight == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate sprite bounds in world coordinates
|
||||
float halfWidth = spriteWidth / 2.0f;
|
||||
float halfHeight = spriteHeight / 2.0f;
|
||||
|
||||
// Apply rotation if needed
|
||||
// TODO: Implement proper rotation transformation
|
||||
int pixelXm = static_cast<int>(pos.x - halfWidth - minCorner.x);
|
||||
int pixelXM = static_cast<int>(pos.x + halfWidth - minCorner.x);
|
||||
int pixelYm = static_cast<int>(pos.y - halfHeight - minCorner.y);
|
||||
int pixelYM = static_cast<int>(pos.y + halfHeight - minCorner.y);
|
||||
|
||||
// Clamp to output bounds
|
||||
pixelXm = std::max(0, pixelXm);
|
||||
pixelXM = std::min(width - 1, pixelXM);
|
||||
pixelYm = std::max(0, pixelYm);
|
||||
pixelYM = std::min(height - 1, pixelYM);
|
||||
|
||||
// Skip if completely outside bounds
|
||||
if (pixelXm >= width || pixelXM < 0 || pixelYm >= height || pixelYM < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Render sprite pixels
|
||||
for (int py = pixelYm; py <= pixelYM; ++py) {
|
||||
for (int px = pixelXm; px <= pixelXM; ++px) {
|
||||
// Calculate sprite-relative coordinates
|
||||
int spriteX = px - pixelXm;
|
||||
int spriteY = py - pixelYm;
|
||||
|
||||
// Skip if outside sprite bounds
|
||||
if (spriteX < 0 || spriteX >= spriteWidth || spriteY < 0 || spriteY >= spriteHeight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get sprite pixel color based on color format
|
||||
Vec4 spriteColor = getSpritePixelColor(spriteData, spriteX, spriteY, spriteWidth, spriteHeight, decompressedSprite.colorFormat);
|
||||
|
||||
// Alpha blending
|
||||
int bufferIndex = py * width + px;
|
||||
Vec4& dest = rgbaBuffer[bufferIndex];
|
||||
|
||||
float srcAlpha = spriteColor.a;
|
||||
if (srcAlpha > 0.0f) {
|
||||
float invSrcAlpha = 1.0f - srcAlpha;
|
||||
dest.r = spriteColor.r * srcAlpha + dest.r * invSrcAlpha;
|
||||
dest.g = spriteColor.g * srcAlpha + dest.g * invSrcAlpha;
|
||||
dest.b = spriteColor.b * srcAlpha + dest.b * invSrcAlpha;
|
||||
dest.a = srcAlpha + dest.a * invSrcAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also render regular colored objects (from base class)
|
||||
for (const auto& [id, pos] : Positions) {
|
||||
// Skip if this is a sprite (already rendered above)
|
||||
if (spritesComped.find(id) != spritesComped.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t size = Sizes.at(id);
|
||||
|
||||
// Calculate pixel coordinates for colored objects
|
||||
int pixelXm = static_cast<int>(pos.x - size/2 - minCorner.x);
|
||||
int pixelXM = static_cast<int>(pos.x + size/2 - minCorner.x);
|
||||
int pixelYm = static_cast<int>(pos.y - size/2 - minCorner.y);
|
||||
int pixelYM = static_cast<int>(pos.y + size/2 - minCorner.y);
|
||||
|
||||
pixelXm = std::max(0, pixelXm);
|
||||
pixelXM = std::min(width - 1, pixelXM);
|
||||
pixelYm = std::max(0, pixelYm);
|
||||
pixelYM = std::min(height - 1, pixelYM);
|
||||
|
||||
// Ensure within bounds
|
||||
if (pixelXM >= minCorner.x && pixelXm < width && pixelYM >= minCorner.y && pixelYm < height) {
|
||||
const Vec4& color = Colors.at(id);
|
||||
float srcAlpha = color.a;
|
||||
for (int py = pixelYm; py <= pixelYM; ++py) {
|
||||
for (int px = pixelXm; px <= pixelXM; ++px) {
|
||||
int index = py * width + px;
|
||||
Vec4& dest = rgbaBuffer[index];
|
||||
|
||||
float invSrcAlpha = 1.0f - srcAlpha;
|
||||
dest.r = color.r * srcAlpha + dest.r * invSrcAlpha;
|
||||
dest.g = color.g * srcAlpha + dest.g * invSrcAlpha;
|
||||
dest.b = color.b * srcAlpha + dest.b * invSrcAlpha;
|
||||
dest.a = srcAlpha + dest.a * invSrcAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert RGBA buffer to BGR output
|
||||
rgbData.resize(rgbaBuffer.size() * 3);
|
||||
for (size_t i = 0; i < rgbaBuffer.size(); ++i) {
|
||||
const Vec4& color = rgbaBuffer[i];
|
||||
size_t bgrIndex = i * 3;
|
||||
|
||||
// Convert from [0,1] to [0,255] and store as BGR
|
||||
rgbData[bgrIndex + 2] = static_cast<uint8_t>(color.r * 255); // R -> third position
|
||||
rgbData[bgrIndex + 1] = static_cast<uint8_t>(color.g * 255); // G -> second position
|
||||
rgbData[bgrIndex + 0] = static_cast<uint8_t>(color.b * 255); // B -> first position
|
||||
}
|
||||
}
|
||||
|
||||
size_t removeSprite(size_t id) {
|
||||
spritesComped.erase(id);
|
||||
Layers.erase(id);
|
||||
Orientations.erase(id);
|
||||
return removeID(id);
|
||||
}
|
||||
|
||||
// Remove sprite by position
|
||||
size_t removeSprite(const Vec2& pos) {
|
||||
size_t id = getPositionVec(pos);
|
||||
return removeSprite(id);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
Grid2::clear();
|
||||
spritesComped.clear();
|
||||
Layers.clear();
|
||||
Orientations.clear();
|
||||
spritesComped.rehash(0);
|
||||
Layers.rehash(0);
|
||||
Orientations.rehash(0);
|
||||
}
|
||||
|
||||
// Get all sprite IDs
|
||||
std::vector<size_t> getAllSpriteIDs() {
|
||||
return getAllIDs();
|
||||
}
|
||||
|
||||
// Check if ID has a sprite
|
||||
bool hasSprite(size_t id) const {
|
||||
return spritesComped.find(id) != spritesComped.end();
|
||||
}
|
||||
|
||||
// Get number of sprites
|
||||
size_t getSpriteCount() const {
|
||||
return spritesComped.size();
|
||||
}
|
||||
|
||||
private:
|
||||
// Helper function to extract pixel color from sprite data based on color format
|
||||
Vec4 getSpritePixelColor(const std::vector<uint8_t>& spriteData,
|
||||
int x, int y,
|
||||
size_t spriteWidth, size_t spriteHeight,
|
||||
frame::colormap format) const {
|
||||
size_t pixelIndex = y * spriteWidth + x;
|
||||
size_t channels = 3; // Default to RGB
|
||||
|
||||
switch (format) {
|
||||
case frame::colormap::RGB:
|
||||
channels = 3;
|
||||
if (pixelIndex * channels + 2 < spriteData.size()) {
|
||||
return Vec4(spriteData[pixelIndex * channels] / 255.0f,
|
||||
spriteData[pixelIndex * channels + 1] / 255.0f,
|
||||
spriteData[pixelIndex * channels + 2] / 255.0f,
|
||||
1.0f);
|
||||
}
|
||||
break;
|
||||
|
||||
case frame::colormap::RGBA:
|
||||
channels = 4;
|
||||
if (pixelIndex * channels + 3 < spriteData.size()) {
|
||||
return Vec4(spriteData[pixelIndex * channels] / 255.0f,
|
||||
spriteData[pixelIndex * channels + 1] / 255.0f,
|
||||
spriteData[pixelIndex * channels + 2] / 255.0f,
|
||||
spriteData[pixelIndex * channels + 3] / 255.0f);
|
||||
}
|
||||
break;
|
||||
|
||||
case frame::colormap::BGR:
|
||||
channels = 3;
|
||||
if (pixelIndex * channels + 2 < spriteData.size()) {
|
||||
return Vec4(spriteData[pixelIndex * channels + 2] / 255.0f, // BGR -> RGB
|
||||
spriteData[pixelIndex * channels + 1] / 255.0f,
|
||||
spriteData[pixelIndex * channels] / 255.0f,
|
||||
1.0f);
|
||||
}
|
||||
break;
|
||||
|
||||
case frame::colormap::BGRA:
|
||||
channels = 4;
|
||||
if (pixelIndex * channels + 3 < spriteData.size()) {
|
||||
return Vec4(spriteData[pixelIndex * channels + 2] / 255.0f, // BGRA -> RGBA
|
||||
spriteData[pixelIndex * channels + 1] / 255.0f,
|
||||
spriteData[pixelIndex * channels] / 255.0f,
|
||||
spriteData[pixelIndex * channels + 3] / 255.0f);
|
||||
}
|
||||
break;
|
||||
|
||||
case frame::colormap::B:
|
||||
channels = 1;
|
||||
if (pixelIndex < spriteData.size()) {
|
||||
float value = spriteData[pixelIndex] / 255.0f;
|
||||
return Vec4(value, value, value, 1.0f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Return transparent black if out of bounds
|
||||
return Vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,347 +0,0 @@
|
||||
#ifndef VOXELOCTREE_HPP
|
||||
#define VOXELOCTREE_HPP
|
||||
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include "../compression/zstd.hpp"
|
||||
#include "../inttypes.hpp"
|
||||
#include "../utils.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <bit>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
class VoxelData {
|
||||
private:
|
||||
};
|
||||
|
||||
static const uint32_t BitCount[] = {
|
||||
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
||||
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
|
||||
};
|
||||
|
||||
constexpr float EPSILON = 0.0000000000000000000000001;
|
||||
static const size_t CompressionBlockSize = 64*1024*1024;
|
||||
|
||||
class VoxelOctree {
|
||||
private:
|
||||
static const size_t MaxScale = 23;
|
||||
size_t _octSize;
|
||||
std::vector<uint32_t> _octree;
|
||||
VoxelData* _voxels;
|
||||
Vec3f _center;
|
||||
|
||||
size_t buildOctree(ChunkedAllocator<uint32_t>& allocator, int x, int y, int z, int size, size_t descriptorIndex) {
|
||||
_voxels->prepateDataAccess(x, y, z, size);
|
||||
|
||||
int halfSize = size >> 1;
|
||||
const std::array<Vec3f, 8> childPositions = {
|
||||
Vec3f{x + halfSize, y + halfSize, z + halfSize},
|
||||
Vec3f{x, y + halfSize, z + halfSize},
|
||||
Vec3f{x + halfSize, y, z + halfSize},
|
||||
Vec3f{x, y, z + halfSize},
|
||||
Vec3f{x + halfSize, y + halfSize, z},
|
||||
Vec3f{x, y + halfSize, z},
|
||||
Vec3f{x + halfSize, y, z},
|
||||
Vec3f{x, y, z}
|
||||
};
|
||||
uint64_t childOffset = static_cast<uint64_t>(allocator.size()) - descriptorIndex;
|
||||
|
||||
int childCount = 0;
|
||||
std::array<int, 8> childIndices{};
|
||||
uint32_t childMask = 0;
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (_voxels->cubeContainsVoxelsDestructive(childPositions[i].x, childPositions[i].y, childPositions[i].z, halfSize)) {
|
||||
childMask |= 128 >> i;
|
||||
childIndices[childCount++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasLargeChildren = false;
|
||||
uint32_t leafMask;
|
||||
if (halfSize == 1) {
|
||||
leafMask = 0;
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
int idx = childIndices[childCount - i - 1];
|
||||
allocator.pushBack(_voxels->getVoxelDestructive(childPositions[idx].x, childPositions[idx].y, childPositions[idx].z));
|
||||
}
|
||||
} else {
|
||||
leafMask = childMask;
|
||||
for (int i = 0; i < childCount; ++i) allocator.pushBack(0);
|
||||
std::array<uint64_t, 8> granChildOffsets{};
|
||||
uint64_t delta = 0;
|
||||
uint64_t insertionCount = allocator.insertionCount();
|
||||
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
int idx = childIndices[childCount - i - 1];
|
||||
granChildOffsets[i] = delta + buildOctree(allocator, childPositions[idx].x, childPositions[idx].y, childPositions[idx].z, halfSize, descriptorIndex + childOffset + i);
|
||||
delta += allocator.insertionCount() - insertionCount;
|
||||
insertionCount = allocator.insertionCount();
|
||||
if (granChildOffsets[i] > 0x3FFF) hasLargeChildren = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
uint64_t childIdx = descriptorIndex + childOffset + i;
|
||||
uint64_t offset = granChildOffsets[i];
|
||||
|
||||
if (hasLargeChildren) {
|
||||
offset += childCount - i;
|
||||
allocator.insert(childIdx + 1, static_cast<uint32_t>(offset));
|
||||
allocator[childIdx] |= 0x20000;
|
||||
offset >>= 32;
|
||||
}
|
||||
allocator[childIdx] |= static_cast<uint32_t>(offset << 18);
|
||||
}
|
||||
}
|
||||
|
||||
allocator[descriptorIndex] = (childMask << 8) | leafMask;
|
||||
if (hasLargeChildren) allocator[descriptorIndex] |= 0x10000;
|
||||
return childOffset;
|
||||
}
|
||||
public:
|
||||
VoxelOctree(const std::string& path) : _voxels(nullptr) {
|
||||
std::ifstream file = std::ifstream(path, std::ios::binary);
|
||||
if (!file.isopen()) {
|
||||
throw std::runtime_error(std::string("failed to open: ") + path);
|
||||
}
|
||||
|
||||
float cd[3];
|
||||
file.read(reinterpret_cast<char*>(cd), sizeof(float) * 3);
|
||||
_center = Vec3f(cd);
|
||||
|
||||
uint64_t octreeSize;
|
||||
file.read(reinterpret_cast<char*>(&octreeSize), sizeof(uint64_t));
|
||||
_octSize = octreeSize;
|
||||
|
||||
_octree.resize(_octSize);
|
||||
|
||||
std::vector<uint8_t> compressionBuffer(zstd(static_cast<int>(CompressionBlockSize)));
|
||||
|
||||
std::unique_ptr<ZSTD_Stream, decltype(&ZSTD_freeStreamDecode)> stream(ZSTD_freeStreamDecode);
|
||||
ZSTD_setStreamDecode(stream.get(), reinterpret_cast<char*>(_octree.data()), 0);
|
||||
|
||||
uint64_t compressedSize = 0;
|
||||
const size_t elementSize = sizeof(uint32_t);
|
||||
for (uint64_t offset = 0; offset < _octSize * elementSize; offset += CompressionBlockSize) {
|
||||
uint64_t compsize;
|
||||
file.read(reinterpret_cast<char*>(&compsize), sizeof(uint64_t));
|
||||
|
||||
if (compsize > compressionBuffer.size()) compressionBuffer.resize(compsize);
|
||||
file.read(compressionBuffer.data(), static_cast<std::streamsize>(compsize));
|
||||
|
||||
int outsize = std::min(_octSize * elementSize - offset, CompressionBlockSize);
|
||||
ZSTD_Decompress_continue(stream.get(), compressionBuffer.data(), reinterpret_cast<char*>(_octree.data()) + offset, outsize);
|
||||
|
||||
compressedSize += compsize + sizeof(uint64_t);
|
||||
}
|
||||
}
|
||||
|
||||
VoxelOctree(VoxelData* voxels) : _voxels(voxels) {
|
||||
std::unique_ptr<ChunkedAllocator<uint32_t>> octreeAllocator = std::make_unique<ChunkedAllocator<uint32_t>>();
|
||||
|
||||
octreeAllocator->pushBack(0);
|
||||
buildOctree(*octreeAllocator, 0, 0, 0, _voxels->sideLength(), 0);
|
||||
(*octreeAllocator)[0] |= 1 << 18;
|
||||
|
||||
_octSize = octreeAllocator->size() + octreeAllocator-> insertionCount();
|
||||
_octree = octreeAllocator->finalize();
|
||||
_center = _voxels->getCenter();
|
||||
}
|
||||
|
||||
void save(const char* path) {
|
||||
std::ofstream file(path, std::iod::binary);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error(std::string("failed to write: ") + path);
|
||||
}
|
||||
|
||||
float cd[3] = {_center.x,_center.y, _center.z};
|
||||
|
||||
file.write(reinterpret_cast<const char*>(cd), sizeof(float) * 3);
|
||||
|
||||
file.write(reinterpret_cast<const char*>(static_cast<uint64_t>(_octSize)), sizeof(uint64_t));
|
||||
std::vector<uint8_t> compressionBuffer(ZSTD_compressBound(static_cast<int>(CompressionBlockSize)));
|
||||
std::unique_ptr<ZSTD_stream_t, decltype(&ZSTD_freeStream)> stream(ZSTD_createStream(), ZSTD_freeStream);
|
||||
|
||||
ZSTD_resetStream(stream.get());
|
||||
|
||||
uint64_t compressedSize = 0;
|
||||
const size_t elementSize = sizeof(uint32_t);
|
||||
const char* src = reinterpret_cast<const char*>(_octree.data());
|
||||
|
||||
for (uint64_t offset = 0; offset < _octSize * elementSize; offset += CompressionBlockSize) {
|
||||
int outSize = _octSize * elementSize - offset, CompressionBlockSize;
|
||||
uint64_t compSize = ZSTD_Compress_continue(stream.get(), src+offset, compressionBuffer.data(), outSize);
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&compSize), sizeof(uint64_t));
|
||||
file.write(compressionBuffer.data(), static_cast<std::streamsize>(compSize));
|
||||
|
||||
compressedSize += compSize + sizeof(uint64_t);
|
||||
}
|
||||
}
|
||||
|
||||
bool rayMarch(const Vec3f& origin, const Vec3f& dest, float rayScale, uint32_t& normal, float& t) {
|
||||
struct StackEntry {
|
||||
uint64_t offset;
|
||||
float maxT;
|
||||
};
|
||||
|
||||
std::array<StackEntry, MaxScale + 1> rayStack;
|
||||
|
||||
Vec3 invAbsD = -dest.abs().safeInverse();
|
||||
uint8_t octantMask = dest.calculateOctantMask();
|
||||
Vec3f bT = invAbsD * origin;
|
||||
if (dest.x > 0) { bT.x = 3.0f * invAbsD.x - bT.x;}
|
||||
if (dest.y > 0) { bT.y = 3.0f * invAbsD.y - bT.y;}
|
||||
if (dest.z > 0) { bT.z = 3.0f * invAbsD.z - bT.z;}
|
||||
|
||||
float minT = (2.0f * invAbsD - bT).maxComp();
|
||||
float maxT = (invAbsD - bT).minComp();
|
||||
minT = std::max(minT, 0.0f);
|
||||
uint32_t curr = 0;
|
||||
uint64_t par = 0;
|
||||
|
||||
Vec3 pos(1.0f);
|
||||
|
||||
int idx = 0;
|
||||
Vec3 centerT = 1.5f * invAbsD - bT;
|
||||
if (centerT.x > minT) { idx ^= 1; pos.x = 1.5f; }
|
||||
if (centerT.y > minT) { idx ^= 2; pos.y = 1.5f; }
|
||||
if (centerT.z > minT) { idx ^= 4; pos.z = 1.5f; }
|
||||
|
||||
int scale = MaxScale - 1;
|
||||
float scaleExp2 = 0.5f;
|
||||
|
||||
while (scale < MaxScale) {
|
||||
if (curr == 0) curr = _octree[par];
|
||||
|
||||
Vec3 cornerT = pos * invAbsD - bT;
|
||||
float maxTC = cornerT.minComp();
|
||||
|
||||
int childShift = idx ^ octantMask;
|
||||
uint32_t childMasks = curr << childShift;
|
||||
if ((childMasks & 0x8000) && minT <= maxT) {
|
||||
if (maxTC * rayScale >= scaleExp2) {
|
||||
t = maxTC;
|
||||
return true;
|
||||
}
|
||||
|
||||
float maxTV = std::min(maxTC, maxT);
|
||||
float half = scaleExp2 * 0.5f;
|
||||
Vec3f centerT = Vec3(half) * invAbsD + cornerT;
|
||||
|
||||
if (minT <= maxTV) {
|
||||
uint64_t childOffset = curr >> 18;
|
||||
if (curr & 0x20000) childOffset = (childOffset << 32) | static_cast<uint64_t>(_octree[par+1]);
|
||||
if (!(childMasks & 0x80)) {
|
||||
uint32_t maskIndex = ((childMasks >> (8 + childShift)) << childShift) & 127;
|
||||
normal = _octree[childOffset + par + BitCount[maskIndex]];
|
||||
break;
|
||||
}
|
||||
rayStack[scale].offset = par;
|
||||
rayStack[scale].maxT = maxT;
|
||||
uint32_t siblingCount = BitCount[childMasks & 127];
|
||||
|
||||
par += childOffset + siblingCount;
|
||||
if (curr & 0x10000) par += siblingCount;
|
||||
idx = 0;
|
||||
|
||||
--scale;
|
||||
scaleExp2 = half;
|
||||
if (centerT.x > minT) {
|
||||
idx ^= 1;
|
||||
pos.x += scaleExp2;
|
||||
}
|
||||
if (centerT.y > minT) {
|
||||
idx ^= 1;
|
||||
pos.y += scaleExp2;
|
||||
}
|
||||
if (centerT.z > minT) {
|
||||
idx ^= 1;
|
||||
pos.z += scaleExp2;
|
||||
}
|
||||
|
||||
maxT = maxTV;
|
||||
curr = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int stepMask = 0;
|
||||
if (cornerT.x <= maxTC) {
|
||||
stepMask ^= 1;
|
||||
pos.x -= scaleExp2;
|
||||
}
|
||||
if (cornerT.y <= maxTC) {
|
||||
stepMask ^= 1;
|
||||
pos.y -= scaleExp2;
|
||||
}
|
||||
if (cornerT.z <= maxTC) {
|
||||
stepMask ^= 1;
|
||||
pos.z -= scaleExp2;
|
||||
}
|
||||
|
||||
minT = maxTC;
|
||||
idx ^= stepMask;
|
||||
|
||||
if ((idx & stepMask) != 0) {
|
||||
uint32_t differingBits = 0;
|
||||
if (stepMask & 1) {
|
||||
differingBits |= std::bit_cast<uint32_t>(pos.x) ^ std::bit_cast<uint32_t>(pos.x + scaleExp2);
|
||||
}
|
||||
if (stepMask & 2) {
|
||||
differingBits |= std::bit_cast<uint32_t>(pos.y) ^ std::bit_cast<uint32_t>(pos.y + scaleExp2);
|
||||
}
|
||||
if (stepMask & 4) {
|
||||
differingBits |= std::bit_cast<uint32_t>(pos.z) ^ std::bit_cast<uint32_t>(pos.z + scaleExp2);
|
||||
}
|
||||
|
||||
scale = (differingBits >> 23) - 127;
|
||||
scale = std::bit_cast<float>(static_cast<uint32_t>((scale - MaxScale + 127) << 23));
|
||||
|
||||
par = rayStack[scale].offset;
|
||||
maxT = rayStack[scale].maxT;
|
||||
|
||||
int shX = std::bit_cast<uint32_t>(pos.x) >> scale;
|
||||
int shY = std::bit_cast<uint32_t>(pos.y) >> scale;
|
||||
int shZ = std::bit_cast<uint32_t>(pos.z) >> scale;
|
||||
|
||||
pos.x = std::bit_cast<float>(shX << scale);
|
||||
pos.y = std::bit_cast<float>(shY << scale);
|
||||
pos.z = std::bit_cast<float>(shZ << scale);
|
||||
|
||||
idx = (shX & 1) | ((shY & 1) << 1) | ((shZ & 1) << 2);
|
||||
curr = 0;
|
||||
}
|
||||
}
|
||||
if (scale >=MaxScale) return false;
|
||||
t = minT;
|
||||
return true;
|
||||
}
|
||||
|
||||
Vec3f center() const {
|
||||
return _center;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user