lots of performance changes to g3. reverted to voxel grid from mesh.

This commit is contained in:
Yggdrasil75
2026-02-20 13:20:29 -05:00
parent 14b4d06495
commit 4f227df1d7
2 changed files with 426 additions and 430 deletions

View File

@@ -31,9 +31,9 @@ struct defaults {
bool slowRender = false;
bool globalIllumination = true;
bool useLod = true;
int rayCount = 3;
int rayCount = 5;
int reflectCount = 3;
int lodDist = 500;
int lodDist = 50000;
float lodDropoff = 0.1;
PNoise2 noise = PNoise2(42);
@@ -82,6 +82,12 @@ std::vector<double> renderFrameTimes;
int frameHistoryIndex = 0;
bool firstFrameMeasured = false;
// Stats update timer
std::chrono::steady_clock::time_point lastStatsUpdate;
const std::chrono::seconds STATS_UPDATE_INTERVAL(10);
std::string cachedStats;
bool statsNeedUpdate = true;
Scene scene;
bool meshNeedsUpdate = false;
@@ -182,38 +188,45 @@ void addStar(const defaults& config, const stardefaults& starconf, Octree<int>&
meshNeedsUpdate = true;
}
void updateStatsCache(Octree<int>& grid) {
std::stringstream gridstats;
grid.printStats(gridstats);
cachedStats = gridstats.str();
lastStatsUpdate = std::chrono::steady_clock::now();
statsNeedUpdate = false;
}
void livePreview(Octree<int>& grid, defaults& config, const Camera& cam) {
std::lock_guard<std::mutex> lock(PreviewMutex);
updatePreview = true;
if (meshNeedsUpdate) {
scene.clear();
std::shared_ptr<Mesh> planetMesh = grid.generateMesh(1, config.meshIsoLevel, pow(config.meshResolution, 2));
std::shared_ptr<Mesh> starMesh = grid.generateMesh(2, config.meshIsoLevel, config.meshResolution);
// if (meshNeedsUpdate) {
// scene.clear();
// std::shared_ptr<Mesh> planetMesh = grid.generateMesh(1, config.meshIsoLevel, pow(config.meshResolution, 2));
// std::shared_ptr<Mesh> starMesh = grid.generateMesh(2, config.meshIsoLevel, config.meshResolution);
scene.addMesh(planetMesh);
scene.addMesh(starMesh);
// scene.addMesh(planetMesh);
// scene.addMesh(starMesh);
// planetMesh.setResolution(config.meshResolution);
// planetMesh.setIsoLevel(config.meshIsoLevel);
// planetMesh.update(grid);
meshNeedsUpdate = false;
}
// // planetMesh.setResolution(config.meshResolution);
// // planetMesh.setIsoLevel(config.meshIsoLevel);
// // planetMesh.update(grid);
// meshNeedsUpdate = false;
// }
auto renderStart = std::chrono::high_resolution_clock::now();
frame currentPreviewFrame;
currentPreviewFrame = scene.render(cam, config.outWidth, config.outHeight, 0.1f, 10000.0f, frame::colormap::RGB);
// currentPreviewFrame = scene.render(cam, config.outWidth, config.outHeight, 0.1f, 10000.0f, frame::colormap::RGB);
// grid.setLODMinDistance(config.lodDist);
// grid.setLODFalloff(config.lodDropoff);
// if (config.slowRender) {
// currentPreviewFrame = grid.renderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB, config.rayCount, config.reflectCount, config.globalIllumination, config.useLod);
// } else {
// currentPreviewFrame = grid.fastRenderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB);
// }
grid.setLODMinDistance(config.lodDist);
grid.setLODFalloff(config.lodDropoff);
if (config.slowRender) {
currentPreviewFrame = grid.renderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB, config.rayCount, config.reflectCount, config.globalIllumination, config.useLod);
} else {
currentPreviewFrame = grid.fastRenderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB);
}
auto renderEnd = std::chrono::high_resolution_clock::now();
renderFrameTime = std::chrono::duration<double>(renderEnd - renderStart).count();
@@ -330,7 +343,7 @@ int main() {
defaults config;
PointType minBound(-config.gridSizecube, -config.gridSizecube, -config.gridSizecube);
PointType maxBound(config.gridSizecube, config.gridSizecube, config.gridSizecube);
Octree<int> grid(minBound, maxBound, 16, 16);
Octree<int> grid(minBound, maxBound, 8, 32);
bool gridInitialized = false;
float ghalf = config.gridSizecube / 2.f;
@@ -505,22 +518,22 @@ int main() {
ImGui::ColorEdit3("Color", sphereConf.color);
ImGui::Separator();
ImGui::Text("Marching Cubes Config");
if (ImGui::SliderInt("Mesh Resolution", &config.meshResolution, 1, 64)) {
meshNeedsUpdate = true;
}
if (ImGui::SliderFloat("Iso Level", &config.meshIsoLevel, 0.01f, 1.0f)) {
meshNeedsUpdate = true;
}
ImGui::Separator();
ImGui::Checkbox("Is Light", &sphereConf.light);
// if(sphereConf.light) {
// ImGui::DragFloat("Emittance", &sphereConf.emittance, 0.1f, 0.0f, 100.0f);
// ImGui::Text("Marching Cubes Config");
// if (ImGui::SliderInt("Mesh Resolution", &config.meshResolution, 1, 64)) {
// meshNeedsUpdate = true;
// }
// ImGui::SliderFloat("Reflection", &sphereConf.reflection, 0.0f, 1.0f);
// ImGui::SliderFloat("Refraction", &sphereConf.refraction, 0.0f, 1.0f);
// ImGui::Checkbox("Fill Inside", &sphereConf.fillInside);
// if (ImGui::SliderFloat("Iso Level", &config.meshIsoLevel, 0.01f, 1.0f)) {
// meshNeedsUpdate = true;
// }
// ImGui::Separator();
// ImGui::Checkbox("Is Light", &sphereConf.light);
if(sphereConf.light) {
ImGui::DragFloat("Emittance", &sphereConf.emittance, 0.1f, 0.0f, 100.0f);
}
ImGui::SliderFloat("Reflection", &sphereConf.reflection, 0.0f, 1.0f);
ImGui::SliderFloat("Refraction", &sphereConf.refraction, 0.0f, 1.0f);
ImGui::Checkbox("Fill Inside", &sphereConf.fillInside);
if (ImGui::CollapsingHeader("Star/Sun Parameters", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Checkbox("Enable Star", &starConf.enabled);
@@ -564,8 +577,56 @@ int main() {
fluidUI.renderUI();
}
scene.drawSceneWindow("Planet Preview", cam, 0.01, 1000);
scene.drawGridStats();
// scene.drawSceneWindow("Planet Preview", cam, 0.01, 1000);
// scene.drawGridStats();
{
ImGui::Begin("Planet Preview");
if (worldPreview) {
if (gridInitialized) {
livePreview(grid, config, cam);
}
}
if (gridInitialized && textureInitialized) {
ImGui::Image((void*)(intptr_t)textu, ImVec2(config.outWidth, config.outHeight));
} else if (gridInitialized) {
ImGui::Text("Preview not generated yet");
} else {
ImGui::Text("No grid generated");
}
ImGui::Text("Render Performance:");
if (renderFPS > 0) {
// Color code based on FPS
ImVec4 fpsColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
// if (renderFPS >= 30.0) {
// fpsColor = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); // Green for good FPS
// } else if (renderFPS >= 15.0) {
// fpsColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f); // Yellow for okay FPS
// } else {
// fpsColor = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Red for poor FPS
// }
ImGui::TextColored(fpsColor, "FPS: %.1f", renderFPS);
ImGui::Text("Frame time: %.1f ms", avgRenderFrameTime * 1000.0);
// Simple progress bar for frame time
ImGui::Text("%.1f/100 ms", avgRenderFrameTime * 1000.0);
// Show latest frame time
ImGui::Text("Latest: %.1f ms", renderFrameTime * 1000.0);
}
ImGui::Separator();
if (gridInitialized) {
auto now = std::chrono::steady_clock::now();
if ((now - lastStatsUpdate) > STATS_UPDATE_INTERVAL) updateStatsCache(grid);
ImGui::TextUnformatted(cachedStats.c_str());
}
ImGui::End();
}
{
ImGui::Begin("controls");
@@ -759,19 +820,18 @@ int main() {
ImGui::Separator();
ImGui::Checkbox("Continuous Preview", &worldPreview);
// ImGui::Checkbox("Continuous Preview", &worldPreview);
// Removed Raytracing specific controls (Global Illum, LOD) as they don't apply to raster mesh
// ImGui::Checkbox("update Preview", &worldPreview);
// ImGui::Checkbox("Use Slower renderer", &config.slowRender);
// if (config.slowRender) {
// ImGui::InputInt("Rays per pixel", &config.rayCount);
// ImGui::InputInt("Max reflections", &config.reflectCount);
// }
// ImGui::InputFloat("Lod dropoff", &config.lodDropoff);
// ImGui::InputInt("lod minimum Distance", &config.lodDist);
// ImGui::Checkbox("use Global illumination", &config.globalIllumination);
// ImGui::Checkbox("use Lod", &config.useLod);
ImGui::Checkbox("update Preview", &worldPreview);
ImGui::Checkbox("Use Slower renderer", &config.slowRender);
if (config.slowRender) {
ImGui::InputInt("Rays per pixel", &config.rayCount);
ImGui::InputInt("Max reflections", &config.reflectCount);
}
ImGui::InputFloat("Lod dropoff", &config.lodDropoff);
ImGui::InputInt("lod minimum Distance", &config.lodDist);
ImGui::Checkbox("use Global illumination", &config.globalIllumination);
ImGui::Checkbox("use Lod", &config.useLod);
ImGui::End();
}

View File

@@ -109,6 +109,8 @@ private:
std::map<std::pair<int, int>, std::shared_ptr<Mesh>> meshCache_;
std::set<std::pair<int, int>> dirtyMeshes_;
int nextSubIdGenerator_ = 1;
constexpr static float ior = 1.45f;
constexpr static float η = 1.0f / ior;
void invalidateMesh(int objectId, int subId) {
if (objectId < 0) return;
@@ -138,20 +140,23 @@ private:
PointType dir;
PointType invDir;
uint8_t sign[3];
uint8_t signMask;
Ray(const PointType& orig, const PointType& dir) : origin(orig), dir(dir) {
invDir = dir.cwiseInverse();
sign[0] = (invDir[0] < 0);
sign[1] = (invDir[1] < 0);
sign[2] = (invDir[2] < 0);
signMask = (sign[0] | sign[1] << 1 | sign[2] << 2);
}
};
uint8_t getOctant(const PointType& point, const PointType& center) const {
uint8_t octant = 0;
if (point[0] >= center[0]) octant |= 1;
if (point[1] >= center[1]) octant |= 2;
if (point[2] >= center[2]) octant |= 4;
return octant;
return (point[0] >= center[0]) | ((point[1] >= center[1]) << 1) | ((point[2] >= center[2]) << 2);
// uint8_t octant = 0;
// if (point[0] >= center[0]) octant |= 1;
// if (point[1] >= center[1]) octant |= 2;
// if (point[2] >= center[2]) octant |= 4;
// return octant;
}
BoundingBox createChildBounds(const OctreeNode* node, uint8_t octant) const {
@@ -227,13 +232,11 @@ private:
bool anyLight = false;
int count = 0;
if (node->isLeaf) {
if (node->points.size() == 1) {
node->lodData = node->points[0];
return;
} else if (node->points.size() == 0) {
return;
}
if (node->points.size() == 1) {
node->lodData = node->points[0];
return;
} else if (node->isLeaf && node->points.size() == 0) {
return;
}
auto accumulate = [&](const std::shared_ptr<NodeData>& item) {
@@ -280,6 +283,269 @@ private:
}
}
std::shared_ptr<NodeData> findRecursive(OctreeNode* node, const PointType& pos, float tolerance) const {
if (!node->contains(pos)) return nullptr;
if (node->isLeaf) {
for (const auto& pointData : node->points) {
if (!pointData->active) continue;
float distSq = (pointData->position - pos).squaredNorm();
if (distSq <= tolerance * tolerance) {
return pointData;
}
}
return nullptr;
} else {
int octant = getOctant(pos, node->center);
if (node->children[octant]) {
return findRecursive(node->children[octant].get(), pos, tolerance);
}
}
return nullptr;
}
bool removeRecursive(OctreeNode* node, const PointType& pos, float tolerance) {
{
std::lock_guard<std::mutex> lock(node->lodMutex);
node->lodData = nullptr;
}
if (!node->contains(pos)) return false;
if (node->isLeaf) {
bool found = false;
auto it = std::remove_if(node->points.begin(), node->points.end(),
[&](const std::shared_ptr<NodeData>& pointData) {
if (!pointData->active) return false;
float distSq = (pointData->position - pos).squaredNorm();
if (distSq <= tolerance * tolerance) {
found = true;
return true;
}
return false;
});
if (found) {
node->points.erase(it, node->points.end());
size--;
return true;
}
return false;
} else {
int octant = getOctant(pos, node->center);
if (node->children[octant]) {
return removeRecursive(node->children[octant].get(), pos, tolerance);
}
}
return false;
}
void searchNode(OctreeNode* node, const PointType& center, float radiusSq, int objectid,
std::vector<std::shared_ptr<NodeData>>& results) const {
PointType closestPoint;
for (int i = 0; i < Dim; ++i) {
closestPoint[i] = std::max(node->bounds.first[i], std::min(center[i], node->bounds.second[i]));
}
float distSq = (closestPoint - center).squaredNorm();
if (distSq > radiusSq) {
return;
}
if (node->isLeaf) {
for (const auto& pointData : node->points) {
if (!pointData->active) continue;
float pointDistSq = (pointData->position - center).squaredNorm();
if (pointDistSq <= radiusSq && (pointData->objectId == objectid || objectid == -1)) {
results.emplace_back(pointData);
}
}
} else {
for (const auto& child : node->children) {
if (child) {
searchNode(child.get(), center, radiusSq, objectid, results);
}
}
}
}
void voxelTraverseRecursive(OctreeNode* node, float tMin, float tMax, float maxDist,
bool enableLOD, const Ray& ray, std::shared_ptr<NodeData>& hit, float invLodf) const {
if (enableLOD && !node->isLeaf) {
float dist = (node->center - ray.origin).norm();
float ratio = dist / node->nodeSize;
if (dist > lodMinDistance_ && ratio > invLodf) {
ensureLOD(node);
float t;
PointType n;
PointType h;
if (rayCubeIntersect(ray, node->lodData.get(), t, n, h)) {
if (t >= 0 && t <= maxDist) hit =node->lodData;
}
return;
}
}
for (const auto& pointData : node->points) {
if (!pointData->active) continue;
float t;
PointType normal, hitPoint;
if (rayCubeIntersect(ray, pointData.get(), t, normal, hitPoint)) {
if (t <= maxDist) {
hit = pointData;
return;
}
}
}
// DDA Traversal
PointType center = node->center;
// Calculate plane intersections relative to center
Eigen::Vector3f ttt = (center - ray.origin).cwiseProduct(ray.invDir);
int currIdx = 0;
currIdx = ((tMin >= ttt.x()) ? 1 : 0) | ((tMin >= ttt.y()) ? 2 : 0) | ((tMin >= ttt.z()) ? 4 : 0);
float tNext;
while(tMin < tMax && !hit) {
Eigen::Vector3f next_t;
next_t[0] = (currIdx & 1) ? tMax : ttt[0];
next_t[1] = (currIdx & 2) ? tMax : ttt[1];
next_t[2] = (currIdx & 4) ? tMax : ttt[2];
tNext = next_t.minCoeff();
int physIdx = currIdx ^ ray.signMask;
if (node->children[physIdx]) {
voxelTraverseRecursive(node->children[physIdx].get(), tMin, tNext, maxDist, enableLOD, ray, hit, invLodf);
}
tMin = tNext;
currIdx |= ((next_t[0] <= tNext) ? 1 : 0) | ((next_t[1] <= tNext) ? 2 : 0) | ((next_t[2] <= tNext) ? 4 : 0);
}
}
Eigen::Vector3f traceRay(const PointType& rayOrig, const PointType& rayDir, int bounces, uint32_t& rngState,
int maxBounces, bool globalIllumination, bool useLod) {
if (bounces > maxBounces) return globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
auto hit = voxelTraverse(rayOrig, rayDir, std::numeric_limits<float>::max(), useLod);
if (!hit) {
if (bounces == 0) return backgroundColor_;
return globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
}
auto obj = hit;
PointType hitPoint;
PointType normal;
float t = 0.0f;
Ray ray(rayOrig, rayDir);
rayCubeIntersect(ray, obj.get(), t, normal, hitPoint);
if (obj->light) {
return obj->color * obj->emittance;
}
Eigen::Vector3f finalColor = globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
if (obj->light) return finalColor;
float refl = obj->reflection;
float refr = obj->refraction;
float diffuseProb = 1.0f - refl - refr;
float sum = refr + refl + diffuseProb;
if (sum <= 0.001f) return finalColor;
float roll = float(rand_r(&rngState)) / float(RAND_MAX);
Eigen::Vector3f npc = Eigen::Vector3f::Zero();
PointType nextDir;
PointType bias = normal * 0.002f;
bool didHit = false;
if (roll < diffuseProb) {
nextDir = randomInHemisphere(normal, rngState);
float cosTheta = std::max(0.0f, normal.dot(nextDir));
Eigen::Vector3f incoming = traceRay(hitPoint + bias, nextDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod);
npc = obj->color.cwiseProduct(incoming);
} else if (roll < diffuseProb + refl) {
nextDir = (rayDir - 2.0f * normal.dot(rayDir) * normal).normalized();
Eigen::Vector3f incoming = traceRay(hitPoint + bias, nextDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod);
npc = obj->color.cwiseProduct(incoming);
npc /= refl;
} else {
float = η;
PointType n_eff = normal;
float cosI = -normal.dot(rayDir);
if (cosI < 0) {
cosI = -cosI;
n_eff = -normal;
= ior;
}
float k = 1.0f - * * (1.0f - cosI * cosI);
if (k < 0.0f) {
nextDir = (rayDir - 2.0f * rayDir.dot(n_eff) * n_eff).normalized();
} else {
nextDir = ( * rayDir + ( * cosI - std::sqrt(k)) * n_eff).normalized();
}
Eigen::Vector3f incoming = traceRay(hitPoint - (n_eff * 0.002f), nextDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod);
npc = obj->color.cwiseProduct(incoming);
npc /= refr;
}
return finalColor + npc;
}
void clearNode(OctreeNode* node) {
if (!node) return;
node->points.clear();
node->points.shrink_to_fit();
node->lodData = nullptr;
for (int i = 0; i < 8; ++i) {
if (node->children[i]) {
clearNode(node->children[i].get());
node->children[i].reset(nullptr);
}
}
node->isLeaf = true;
}
void printStatsRecursive(const OctreeNode* node, size_t depth,
size_t& totalNodes, size_t& leafNodes, size_t& actualPoints,
size_t& maxTreeDepth, size_t& maxPointsInLeaf, size_t& minPointsInLeaf,
size_t& lodGeneratedNodes) const {
if (!node) return;
totalNodes++;
maxTreeDepth = std::max(maxTreeDepth, depth);
if (node->lodData) lodGeneratedNodes++;
if (node->isLeaf) {
leafNodes++;
size_t pts = node->points.size();
actualPoints += pts;
maxPointsInLeaf = std::max(maxPointsInLeaf, pts);
minPointsInLeaf = std::min(minPointsInLeaf, pts);
} else {
for (const auto& child : node->children) {
printStatsRecursive(child.get(), depth + 1, totalNodes, leafNodes, actualPoints,
maxTreeDepth, maxPointsInLeaf, minPointsInLeaf, lodGeneratedNodes);
}
}
}
template<typename V>
void writeVal(std::ofstream& out, const V& val) const {
out.write(reinterpret_cast<const char*>(&val), sizeof(V));
@@ -453,8 +719,7 @@ private:
return tMax >= std::max(0.0f, tMin);
}
bool rayCubeIntersect(const Ray& ray, const NodeData* cube,
float& t, PointType& normal, PointType& hitPoint) const {
bool rayCubeIntersect(const Ray& ray, const NodeData* cube, float& t, PointType& normal, PointType& hitPoint) const {
BoundingBox bounds = cube->getCubeBounds();
float tMin, tMax;
@@ -472,15 +737,13 @@ private:
hitPoint = ray.origin + ray.dir * t;
normal = PointType::Zero();
const float bias = 1.0001f;
if (std::abs(hitPoint[0] - bounds.first[0]) < EPSILON * bias) normal[0] = -1.0f;
else if (std::abs(hitPoint[0] - bounds.second[0]) < EPSILON * bias) normal[0] = 1.0f;
if (std::abs(hitPoint[1] - bounds.first[1]) < EPSILON * bias) normal[1] = -1.0f;
else if (std::abs(hitPoint[1] - bounds.second[1]) < EPSILON * bias) normal[1] = 1.0f;
if (std::abs(hitPoint[2] - bounds.first[2]) < EPSILON * bias) normal[2] = -1.0f;
else if (std::abs(hitPoint[2] - bounds.second[2]) < EPSILON * bias) normal[2] = 1.0f;
PointType dMin = (hitPoint - bounds.first).cwiseAbs();
PointType dMax = (hitPoint - bounds.second).cwiseAbs();
// float thresh = EPSILON * 1.00001f;
PointType nMin = (dMin.array() < EPSILON).cast<float>();
PointType nMax = (dMax.array() < EPSILON).cast<float>();
normal = -nMin + nMax;
return true;
}
@@ -663,11 +926,11 @@ private:
}
public:
Octree(const PointType& minBound, const PointType& maxBound, size_t maxPointsPerNode=16, size_t maxDepth = 16) :
root_(std::make_unique<OctreeNode>(minBound, maxBound)), maxPointsPerNode(maxPointsPerNode),
maxDepth(maxDepth), size(0) {}
Octree(const PointType& minBound, const PointType& maxBound, size_t maxPointsPerNode=8, size_t maxDepth = 16) :
root_(std::make_unique<OctreeNode>(minBound, maxBound)), maxPointsPerNode(maxPointsPerNode),
maxDepth(maxDepth), size(0) {}
Octree() : root_(nullptr), maxPointsPerNode(16), maxDepth(16), size(0) {}
Octree() : root_(nullptr), maxPointsPerNode(8), maxDepth(16), size(0) {}
void setSkylight(const Eigen::Vector3f& skylight) {
skylight_ = skylight;
@@ -694,8 +957,8 @@ public:
}
bool set(const T& data, const PointType& pos, bool visible, Eigen::Vector3f color, float size, bool active,
int objectId = -1, bool light = false, float emittance = 0.0f, float refraction = 0.0f,
float reflection = 0.0f) {
int objectId = -1, bool light = false, float emittance = 0.0f, float refraction = 0.0f,
float reflection = 0.0f) {
auto pointData = std::make_shared<NodeData>(data, pos, visible, color, size, active, objectId,
light, emittance, refraction, reflection);
if (insertRecursive(root_.get(), pointData, 0)) {
@@ -762,29 +1025,7 @@ public:
}
std::shared_ptr<NodeData> find(const PointType& pos, float tolerance = EPSILON) {
std::function<std::shared_ptr<NodeData>(OctreeNode*)> searchNode = [&](OctreeNode* node) -> std::shared_ptr<NodeData> {
if (!node->contains(pos)) return nullptr;
if (node->isLeaf) {
for (const auto& pointData : node->points) {
if (!pointData->active) continue;
float distSq = (pointData->position - pos).squaredNorm();
if (distSq <= tolerance * tolerance) {
return pointData;
}
}
return nullptr;
} else {
int octant = getOctant(pos, node->center);
if (node->children[octant]) {
return searchNode(node->children[octant].get());
}
}
return nullptr;
};
return searchNode(root_.get());
return findRecursive(root_.get(), pos, tolerance);
}
bool inGrid(PointType pos) {
@@ -792,44 +1033,7 @@ public:
}
bool remove(const PointType& pos, float tolerance = EPSILON) {
bool found = false;
std::function<bool(OctreeNode*)> removeNode = [&](OctreeNode* node) -> bool {
{
std::lock_guard<std::mutex> lock(node->lodMutex);
node->lodData = nullptr;
}
if (!node->contains(pos)) return false;
if (node->isLeaf) {
auto it = std::remove_if(node->points.begin(), node->points.end(),
[&](const std::shared_ptr<NodeData>& pointData) {
if (!pointData->active) return false;
float distSq = (pointData->position - pos).squaredNorm();
if (distSq <= tolerance * tolerance) {
found = true;
return true;
}
return false;
});
if (found) {
node->points.erase(it, node->points.end());
size--;
return true;
}
return false;
} else {
int octant = getOctant(pos, node->center);
if (node->children[octant]) {
return removeNode(node->children[octant].get());
}
}
return false;
};
return removeNode(root_.get());
return removeRecursive(root_.get(), pos, tolerance);
}
std::vector<std::shared_ptr<NodeData>> findInRadius(const PointType& center, float radius, int objectid = -1) const {
@@ -837,39 +1041,8 @@ public:
if (!root_) return results;
float radiusSq = radius * radius;
searchNode(root_.get(), center, radiusSq, objectid, results);
std::function<void(OctreeNode*)> searchNode = [&](OctreeNode* node) {
// Check if node's bounding box intersects with search sphere
PointType closestPoint;
for (int i = 0; i < Dim; ++i) {
closestPoint[i] = std::max(node->bounds.first[i],
std::min(center[i], node->bounds.second[i]));
}
float distSq = (closestPoint - center).squaredNorm();
if (distSq > radiusSq) {
return;
}
if (node->isLeaf) {
for (const auto& pointData : node->points) {
if (!pointData->active) continue;
float pointDistSq = (pointData->position - center).squaredNorm();
if (pointDistSq <= radiusSq && (pointData->objectId == objectid || objectid == -1)) {
results.emplace_back(pointData);
}
}
} else {
for (const auto& child : node->children) {
if (child) {
searchNode(child.get());
}
}
}
};
searchNode(root_.get());
return results;
}
@@ -925,8 +1098,8 @@ public:
auto pointData = find(pos);
if (!pointData) return false;
bool success = set(pointData->data, newPos, pointData->visible, pointData->color, pointData->size,
pointData->active, pointData->objectId, pointData->light, pointData->emittance,
pointData->refraction, pointData->reflection);
pointData->active, pointData->objectId, pointData->light, pointData->emittance,
pointData->refraction, pointData->reflection);
if (success) {
remove(pos);
return true;
@@ -997,78 +1170,22 @@ public:
return true;
}
std::vector<std::shared_ptr<NodeData>> voxelTraverse(const PointType& origin, const PointType& direction,
float maxDist, bool stopAtFirstHit, bool enableLOD = false) const {
std::vector<std::shared_ptr<NodeData>> hits;
std::shared_ptr<NodeData> voxelTraverse(const PointType& origin, const PointType& direction,
float maxDist, bool enableLOD = false) const {
std::shared_ptr<NodeData> hit;
float invLodf = 1.0f / lodFalloffRate_;
uint8_t raySignMask = (direction.x() < 0 ? 1 : 0) | (direction.y() < 0 ? 2 : 0) | (direction.z() < 0 ? 4 : 0);
Ray oray(origin, direction);
std::function<void(OctreeNode*, const PointType&, const PointType&, float, float)> traverseNode =
[&](OctreeNode* node, const PointType& origin, const PointType& dir, float tMin, float tMax) {
if (!node) return;
Ray ray(origin, dir);
if (enableLOD && !node->isLeaf) {
float dist = (node->center - origin).norm();
if (dist > lodMinDistance_) {
float ratio = dist / (node->nodeSize + EPSILON);
if (ratio > (invLodf)) {
ensureLOD(node);
if (node->lodData) {
float t;
PointType n;
PointType h;
if (rayCubeIntersect(ray, node->lodData.get(), t, n, h)) {
if (t >= 0 && t <= maxDist) hits.emplace_back(node->lodData);
}
return;
}
}
}
}
if (node->isLeaf) {
for (const auto& pointData : node->points) {
if (!pointData->active) continue;
float t;
PointType normal, hitPoint;
if (rayCubeIntersect(ray, pointData.get(), t, normal, hitPoint)) {
if (t >= 0 && t <= maxDist) {
hits.emplace_back(pointData);
if (stopAtFirstHit) return;
}
}
}
} else {
for (int i = 0; i < 8; ++i) {
int childIdx = i ^ raySignMask;
if (node->children[childIdx]) {
const auto& childBounds = node->children[childIdx]->bounds;
float childtMin = tMin;
float childtMax = tMax;
if (rayBoxIntersect(ray, childBounds, childtMin, childtMax)) {
traverseNode(node->children[childIdx].get(), origin, dir, childtMin, childtMax);
if (stopAtFirstHit && !hits.empty()) return;
}
}
}
}
};
float tMin, tMax;
if (rayBoxIntersect(oray, root_->bounds, tMin, tMax)) {
// if (rayBoxIntersect(oray, root_->bounds, tMin, tMax)) {
tMax = std::min(tMax, maxDist);
traverseNode(root_.get(), origin, direction, tMin, tMax);
}
return hits;
voxelTraverseRecursive(root_.get(), tMin, tMax, maxDist, enableLOD, oray, hit, invLodf);
// }
return hit;
}
frame renderFrame(const Camera& cam, int height, int width, frame::colormap colorformat = frame::colormap::RGB, int samplesPerPixel = 2,
int maxBounces = 4, bool globalIllumination = false, bool useLod = true) {
int maxBounces = 4, bool globalIllumination = false, bool useLod = true) {
PointType origin = cam.origin;
PointType dir = cam.direction.normalized();
PointType up = cam.up.normalized();
@@ -1085,75 +1202,6 @@ public:
float tanfovy = tanHalfFov;
float tanfovx = tanHalfFov * aspect;
std::function<Eigen::Vector3f(const PointType&, const PointType&, int, uint32_t&)> traceRay =
[&](const PointType& rayOrig, const PointType& rayDir, int bounces, uint32_t& rngState) -> Eigen::Vector3f {
Ray ray(rayOrig, rayDir);
if (bounces > maxBounces) return globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
auto hits = voxelTraverse(rayOrig, rayDir, std::numeric_limits<float>::max(), true, useLod);
if (hits.empty()) {
if (bounces == 0) {
return backgroundColor_;
}
return globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
}
auto obj = hits[0];
PointType hitPoint;
PointType normal;
float t = 0.0f;
// Calculate intersection
PointType cubeNormal;
if (!rayCubeIntersect(ray, obj.get(), t, normal, hitPoint)) {
return globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
}
Eigen::Vector3f finalColor = globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
if (obj->light) {
return obj->color * obj->emittance;
}
float refl = obj->reflection;
float refr = obj->refraction;
float diffuseProb = 1.0f - refl - refr;
if (diffuseProb > 0.001f) {
PointType scatterDir = randomInHemisphere(normal, rngState);
Eigen::Vector3f incomingLight = traceRay(hitPoint + normal * 0.002f, scatterDir, bounces + 1, rngState);
finalColor += obj->color.cwiseProduct(incomingLight) * diffuseProb;
}
if (refr > 0.001f) {
float ior = 1.45f;
float η = 1.0f / ior;
float cosI = -normal.dot(rayDir);
PointType n_eff = normal;
if (cosI < 0) {
cosI = -cosI;
n_eff = -normal;
η = ior;
}
float k = 1.0f - η * η * (1.0f - cosI * cosI);
PointType nextDir;
if (k >= 0.0f) {
nextDir = (η * rayDir + (η * cosI - std::sqrt(k)) * n_eff).normalized();
finalColor += traceRay(hitPoint - n_eff * 0.002f, nextDir, bounces + 1, rngState) * refr;
} else {
nextDir = (rayDir - 2.0f * rayDir.dot(n_eff) * n_eff).normalized();
finalColor += traceRay(hitPoint + n_eff * 0.002f, nextDir, bounces + 1, rngState) * refr;
}
}
return finalColor;
};
#pragma omp parallel for schedule(dynamic) collapse(2)
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
@@ -1170,7 +1218,7 @@ public:
Eigen::Vector3f accumulatedColor(0.0f, 0.0f, 0.0f);
for(int s = 0; s < samplesPerPixel; ++s) {
accumulatedColor += traceRay(origin, rayDir, 0, seed);
accumulatedColor += traceRay(origin, rayDir, 0, seed, maxBounces, globalIllumination, useLod);
}
Eigen::Vector3f color = accumulatedColor / static_cast<float>(samplesPerPixel);
@@ -1191,84 +1239,10 @@ public:
std::shared_ptr<NodeData> hit;
if (empty()) return hit;
float lodRatio = 1.0f / lodFalloffRate_;
std::function<void(OctreeNode*, const PointType&, const PointType&, float, float)> traverseNode =
[&](OctreeNode* node, const PointType& origin, const PointType& dir, float tMin, float tMax) {
if (!node || tMin > tMax) return;
// LOD Check for fast traverse
if (enableLOD && !node->isLeaf) {
float dist = (node->center - origin).norm();
float nodeSize = (node->bounds.second - node->bounds.first).norm();
if (dist > lodMinDistance_ && dist <= maxDist) {
float ratio = dist / (nodeSize + EPSILON);
if (ratio > lodRatio) {
ensureLOD(node);
if (node->lodData) {
PointType toPoint = node->lodData->position - origin;
float projection = toPoint.dot(dir);
if (projection >= 0) {
PointType closestPoint = origin + dir * projection;
float distSq = (node->lodData->position - closestPoint).squaredNorm();
if (distSq < node->lodData->size * node->lodData->size) {
hit = node->lodData;
return;
}
}
}
}
}
}
if (node->isLeaf) {
for (const auto& pointData : node->points) {
if (!pointData->active) continue;
PointType toPoint = pointData->position - origin;
float projection = toPoint.dot(dir);
if (projection >= 0 && projection <= maxDist) {
PointType closestPoint = origin + dir * projection;
float distSq = (pointData->position - closestPoint).squaredNorm();
if (distSq < pointData->size * pointData->size) {
hit = pointData;
return;
}
}
}
} else {
std::array<std::pair<int, float>, 8> childOrder;
PointType center = node->center;
for (int i = 0; i < 8; ++i) {
if (node->children[i]) {
PointType childCenter = node->children[i]->center;
float dist = (childCenter - origin).dot(dir);
childOrder[i] = {i, dist};
} else {
childOrder[i] = {i, std::numeric_limits<float>::lowest()};
}
}
bitonic_sort_8(childOrder);
for (int i = 0; i < 8; ++i) {
int childIdx = childOrder[i].first;
if (node->children[childIdx]) {
const auto& childBounds = node->children[childIdx]->bounds;
float childtMin = tMin;
float childtMax = tMax;
if (rayBoxIntersect(ray, childBounds, childtMin, childtMax)) {
traverseNode(node->children[childIdx].get(), origin, dir, childtMin, childtMax);
if (hit != nullptr) return;
}
}
}
}
};
float tMin, tMax;
if (rayBoxIntersect(ray, root_->bounds, tMin, tMax)) {
tMax = std::min(tMax, maxDist);
traverseNode(root_.get(), ray.origin, ray.dir, tMin, tMax);
voxelTraverseRecursive(root_.get(), tMin, tMax, maxDist, enableLOD, ray, hit, lodRatio);
}
return hit;
}
@@ -1593,29 +1567,8 @@ public:
size_t minPointsInLeaf = std::numeric_limits<size_t>::max();
size_t lodGeneratedNodes = 0;
std::function<void(const OctreeNode*, size_t)> traverse =
[&](const OctreeNode* node, size_t depth) {
if (!node) return;
totalNodes++;
maxTreeDepth = std::max(maxTreeDepth, depth);
if (node->lodData) lodGeneratedNodes++;
if (node->isLeaf) {
leafNodes++;
size_t pts = node->points.size();
actualPoints += pts;
maxPointsInLeaf = std::max(maxPointsInLeaf, pts);
minPointsInLeaf = std::min(minPointsInLeaf, pts);
} else {
for (const auto& child : node->children) {
traverse(child.get(), depth + 1);
}
}
};
traverse(root_.get(), 0);
printStatsRecursive(root_.get(), 0, totalNodes, leafNodes, actualPoints,
maxTreeDepth, maxPointsInLeaf, minPointsInLeaf, lodGeneratedNodes);
if (leafNodes == 0) minPointsInLeaf = 0;
double avgPointsPerLeaf = leafNodes > 0 ? (double)actualPoints / leafNodes : 0.0;
@@ -1654,23 +1607,6 @@ public:
void clear() {
if (root_) {
std::function<void(OctreeNode*)> clearNode = [&](OctreeNode* node) {
if (!node) return;
node->points.clear();
node->points.shrink_to_fit();
node->lodData = nullptr;
for (int i = 0; i < 8; ++i) {
if (node->children[i]) {
clearNode(node->children[i].get());
node->children[i].reset(nullptr);
}
}
node->isLeaf = true;
};
clearNode(root_.get());
}
PointType minBound = root_->bounds.first;