added some more fun features for rendering
This commit is contained in:
@@ -39,21 +39,19 @@ struct spheredefaults {
|
|||||||
float reflection = 0.0f;
|
float reflection = 0.0f;
|
||||||
float refraction = 0.0f;
|
float refraction = 0.0f;
|
||||||
bool fillInside = false;
|
bool fillInside = false;
|
||||||
float voxelSize = 1.5f;
|
float voxelSize = 0.1f;
|
||||||
int numPoints = 15000;
|
int numPoints = 15000;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ceilingdefaults {
|
struct stardefaults {
|
||||||
float minX = 0.0f;
|
float x = 3000.0f;
|
||||||
float maxX = 512.0f;
|
float y = 0.0f;
|
||||||
float minZ = 0.0f;
|
float z = 0.0f;
|
||||||
float maxZ = 512.0f;
|
|
||||||
float yLevel = 450.0f; // Near the top
|
float color[3] = {1.0f, 0.95f, 0.8f};
|
||||||
float spacing = 10.0f; // Distance between light points
|
float emittance = 1000.0f;
|
||||||
float color[3] = {1.0f, 1.0f, 1.0f}; // White light
|
float size = 1000.0f;
|
||||||
float emittance = 5.0f; // Brightness
|
bool enabled = true;
|
||||||
float voxelSize = 2.0f;
|
|
||||||
bool enabled = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::mutex PreviewMutex;
|
std::mutex PreviewMutex;
|
||||||
@@ -63,91 +61,147 @@ bool updatePreview = false;
|
|||||||
bool previewRequested = false;
|
bool previewRequested = false;
|
||||||
using PointType = Eigen::Matrix<float, 3, 1>;
|
using PointType = Eigen::Matrix<float, 3, 1>;
|
||||||
|
|
||||||
|
// Render FPS tracking variables
|
||||||
|
double renderFrameTime = 0.0;
|
||||||
|
double avgRenderFrameTime = 0.0;
|
||||||
|
double renderFPS = 0.0;
|
||||||
|
const int FRAME_HISTORY_SIZE = 60;
|
||||||
|
std::vector<double> renderFrameTimes;
|
||||||
|
int frameHistoryIndex = 0;
|
||||||
|
bool firstFrameMeasured = false;
|
||||||
|
|
||||||
void createSphere(const defaults& config, const spheredefaults& sconfig, Octree<int>& grid) {
|
void createSphere(const defaults& config, const spheredefaults& sconfig, Octree<int>& grid) {
|
||||||
if (!grid.empty()) grid.clear();
|
if (!grid.empty()) grid.clear();
|
||||||
|
|
||||||
float phi = M_PI * (3.0f - std::sqrt(5.0f)); // Golden angle in radians
|
|
||||||
Eigen::Vector3f colorVec(sconfig.color[0], sconfig.color[1], sconfig.color[2]);
|
Eigen::Vector3f colorVec(sconfig.color[0], sconfig.color[1], sconfig.color[2]);
|
||||||
|
Eigen::Vector3f center(sconfig.centerX, sconfig.centerY, sconfig.centerZ);
|
||||||
|
|
||||||
// We treat sconfig.voxelSize as an overlap multiplier.
|
float voxelSize = sconfig.voxelSize;
|
||||||
// 1.0 gives mathematical coverage, >1.0 ensures overlap for solidity.
|
float radius = sconfig.radius;
|
||||||
float overlapMultiplier = std::max(0.1f, sconfig.voxelSize);
|
|
||||||
|
|
||||||
float currentRadius = sconfig.radius;
|
// Calculate how many voxels fit in the diameter
|
||||||
|
int voxelsPerDiameter = static_cast<int>(2.0f * radius / voxelSize);
|
||||||
|
if (voxelsPerDiameter < 1) voxelsPerDiameter = 1;
|
||||||
|
|
||||||
// Loop for shells. If fillInside is false, this loop runs once.
|
// Create a 3D grid that covers the sphere's bounding box
|
||||||
// If true, it runs until radius is negligible.
|
for (int i = 0; i <= voxelsPerDiameter; i++) {
|
||||||
while (currentRadius > 0.5f) {
|
for (int j = 0; j <= voxelsPerDiameter; j++) {
|
||||||
// To maintain uniform visual density, the number of points on an inner shell
|
for (int k = 0; k <= voxelsPerDiameter; k++) {
|
||||||
// should be proportional to surface area (radius^2).
|
// Calculate position in the grid
|
||||||
float scaleFactor = currentRadius / sconfig.radius;
|
float x = center.x() - radius + i * voxelSize;
|
||||||
int currentN = std::max(4, (int)(sconfig.numPoints * scaleFactor * scaleFactor));
|
float y = center.y() - radius + j * voxelSize;
|
||||||
|
float z = center.z() - radius + k * voxelSize;
|
||||||
|
|
||||||
// Calculate the point radius required to fully cover the surface area of the sphere.
|
Eigen::Vector3f pos(x, y, z);
|
||||||
// Surface Area = 4 * PI * R^2.
|
|
||||||
// Area per point = Surface Area / N.
|
|
||||||
// Approximate point radius r: PI * r^2 = Area per point.
|
|
||||||
// r = sqrt(4 * R^2 / N) = 2 * R / sqrt(N).
|
|
||||||
float calculatedSize = (2.0f * currentRadius) / std::sqrt((float)currentN);
|
|
||||||
|
|
||||||
// Apply user-defined multiplier for extra solidity/overlap
|
// Calculate distance from center
|
||||||
float finalSize = calculatedSize * overlapMultiplier * overlapMultiplier;
|
float dist = (pos - center).norm();
|
||||||
|
|
||||||
for (int i = 0; i < currentN; ++i) {
|
// For solid sphere: include all points within radius
|
||||||
// Fibonacci Sphere math
|
if (dist <= radius + voxelSize * 0.5f) {
|
||||||
float y = 1.0f - (i / (float)(currentN - 1)) * 2.0f; // y goes from 1 to -1
|
// Optional: For better surface quality, adjust surface points
|
||||||
float radiusAtY = std::sqrt(1.0f - y * y); // Radius at this height
|
if (dist > radius - voxelSize * 0.5f) {
|
||||||
float theta = phi * i; // Golden angle increment
|
// This is a surface voxel, adjust to exactly on surface
|
||||||
|
if (dist > 0.001f) {
|
||||||
|
pos = center + (pos - center).normalized() * radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float x = std::cos(theta) * radiusAtY;
|
|
||||||
float z = std::sin(theta) * radiusAtY;
|
|
||||||
|
|
||||||
PointType pos(
|
|
||||||
sconfig.centerX + x * currentRadius,
|
|
||||||
sconfig.centerY + y * currentRadius,
|
|
||||||
sconfig.centerZ + z * currentRadius
|
|
||||||
);
|
|
||||||
|
|
||||||
// Boundary check to prevent segfaults if radius pushes out of grid bounds
|
|
||||||
if (pos.x() >= 0 && pos.x() < config.gridSizecube &&
|
if (pos.x() >= 0 && pos.x() < config.gridSizecube &&
|
||||||
pos.y() >= 0 && pos.y() < config.gridSizecube &&
|
pos.y() >= 0 && pos.y() < config.gridSizecube &&
|
||||||
pos.z() >= 0 && pos.z() < config.gridSizecube) {
|
pos.z() >= 0 && pos.z() < config.gridSizecube) {
|
||||||
|
|
||||||
grid.set(1, pos, true, colorVec, finalSize, true, 1,
|
grid.set(1, pos, true, colorVec, voxelSize, true, 1,
|
||||||
|
sconfig.light, sconfig.emittance, sconfig.refraction, sconfig.reflection, Octree<int>::Shape::CUBE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we want a truly solid sphere without gaps, we need a second pass
|
||||||
|
if (sconfig.fillInside) {
|
||||||
|
// Scan for potential gaps in the interior
|
||||||
|
int interiorSteps = static_cast<int>(radius / voxelSize);
|
||||||
|
float interiorStep = voxelSize * 0.5f; // Half-step for gap checking
|
||||||
|
|
||||||
|
for (int i = 0; i <= interiorSteps * 2; i++) {
|
||||||
|
for (int j = 0; j <= interiorSteps * 2; j++) {
|
||||||
|
for (int k = 0; k <= interiorSteps * 2; k++) {
|
||||||
|
Eigen::Vector3f pos(
|
||||||
|
center.x() - radius + i * interiorStep,
|
||||||
|
center.y() - radius + j * interiorStep,
|
||||||
|
center.z() - radius + k * interiorStep
|
||||||
|
);
|
||||||
|
|
||||||
|
float dist = (pos - center).norm();
|
||||||
|
|
||||||
|
// If deep inside the sphere
|
||||||
|
if (dist < radius * 0.8f) {
|
||||||
|
// Check if position is valid
|
||||||
|
if (pos.x() >= 0 && pos.x() < config.gridSizecube &&
|
||||||
|
pos.y() >= 0 && pos.y() < config.gridSizecube &&
|
||||||
|
pos.z() >= 0 && pos.z() < config.gridSizecube) {
|
||||||
|
|
||||||
|
// Try to add the point
|
||||||
|
grid.set(1, pos, true, colorVec, voxelSize, true, 1,
|
||||||
sconfig.light, sconfig.emittance, sconfig.refraction, sconfig.reflection);
|
sconfig.light, sconfig.emittance, sconfig.refraction, sconfig.reflection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!sconfig.fillInside) break;
|
}
|
||||||
|
}
|
||||||
// Decrease radius by a fraction of the point size to ensure shells overlap
|
|
||||||
currentRadius -= (finalSize * 0.75f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addCeilingLight(const defaults& config, const ceilingdefaults& ceilingconf, Octree<int>& grid) {
|
void addStar(const defaults& config, const stardefaults& starconf, Octree<int>& grid) {
|
||||||
if (!ceilingconf.enabled) return;
|
if (!starconf.enabled) return;
|
||||||
|
|
||||||
Eigen::Vector3f colorVec(ceilingconf.color[0], ceilingconf.color[1], ceilingconf.color[2]);
|
Eigen::Vector3f colorVec(starconf.color[0], starconf.color[1], starconf.color[2]);
|
||||||
|
PointType pos(starconf.x, starconf.y, starconf.z);
|
||||||
|
|
||||||
// Iterate over X and Z within bounds, stepping by 'spacing'
|
grid.set(2, pos, true, colorVec, starconf.size, true, 2, true, starconf.emittance, 0.0f, 0.0f);
|
||||||
for (float x = ceilingconf.minX; x <= ceilingconf.maxX; x += ceilingconf.spacing) {
|
|
||||||
for (float z = ceilingconf.minZ; z <= ceilingconf.maxZ; z += ceilingconf.spacing) {
|
|
||||||
|
|
||||||
PointType pos(x, ceilingconf.yLevel, z);
|
|
||||||
|
|
||||||
grid.set(2, pos, true, colorVec, ceilingconf.voxelSize, true, 2, true, ceilingconf.emittance, 0.0f, 0.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
grid.printStats();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void livePreview(Octree<int>& grid, defaults& config, const Camera& cam) {
|
void livePreview(Octree<int>& grid, defaults& config, const Camera& cam) {
|
||||||
std::lock_guard<std::mutex> lock(PreviewMutex);
|
std::lock_guard<std::mutex> lock(PreviewMutex);
|
||||||
updatePreview = true;
|
updatePreview = true;
|
||||||
frame currentPreviewFrame = grid.renderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB, 4, 3, true);
|
|
||||||
|
|
||||||
|
// Measure render time
|
||||||
|
auto renderStart = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
frame currentPreviewFrame = grid.renderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB, 3, 1, true);
|
||||||
|
|
||||||
|
auto renderEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
renderFrameTime = std::chrono::duration<double>(renderEnd - renderStart).count();
|
||||||
|
|
||||||
|
// Update FPS calculations
|
||||||
|
if (!firstFrameMeasured) {
|
||||||
|
renderFrameTimes.resize(FRAME_HISTORY_SIZE, renderFrameTime);
|
||||||
|
firstFrameMeasured = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFrameTimes[frameHistoryIndex] = renderFrameTime;
|
||||||
|
frameHistoryIndex = (frameHistoryIndex + 1) % FRAME_HISTORY_SIZE;
|
||||||
|
|
||||||
|
// Calculate average frame time and FPS
|
||||||
|
avgRenderFrameTime = 0.0;
|
||||||
|
int validFrames = 0;
|
||||||
|
for (int i = 0; i < FRAME_HISTORY_SIZE; i++) {
|
||||||
|
if (renderFrameTimes[i] > 0) {
|
||||||
|
avgRenderFrameTime += renderFrameTimes[i];
|
||||||
|
validFrames++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (validFrames > 0) {
|
||||||
|
avgRenderFrameTime /= validFrames;
|
||||||
|
renderFPS = 1.0 / avgRenderFrameTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update texture
|
||||||
|
if (textu == 0) {
|
||||||
glGenTextures(1, &textu);
|
glGenTextures(1, &textu);
|
||||||
|
}
|
||||||
glBindTexture(GL_TEXTURE_2D, textu);
|
glBindTexture(GL_TEXTURE_2D, textu);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
@@ -156,13 +210,13 @@ void livePreview(Octree<int>& grid, defaults& config, const Camera& cam) {
|
|||||||
glBindTexture(GL_TEXTURE_2D, textu);
|
glBindTexture(GL_TEXTURE_2D, textu);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight(),
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight(),
|
||||||
0, GL_RGB, GL_UNSIGNED_BYTE, currentPreviewFrame.getData().data());
|
0, GL_RGB, GL_UNSIGNED_BYTE, currentPreviewFrame.getData().data());
|
||||||
//BMPWriter::saveBMP("output/frameoutput.bmp", currentPreviewFrame);
|
|
||||||
updatePreview = false;
|
updatePreview = false;
|
||||||
textureInitialized = true;
|
textureInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetView(Camera& cam, float gridSize) {
|
void resetView(Camera& cam, float gridSize) {
|
||||||
cam.origin = Vector3f(gridSize * 1.5f, gridSize * 1.5f, gridSize * 1.5f);
|
cam.origin = Vector3f(gridSize, gridSize, gridSize);
|
||||||
Vector3f center(gridSize / 2.0f, gridSize / 2.0f, gridSize / 2.0f);
|
Vector3f center(gridSize / 2.0f, gridSize / 2.0f, gridSize / 2.0f);
|
||||||
cam.lookAt(center);
|
cam.lookAt(center);
|
||||||
}
|
}
|
||||||
@@ -242,7 +296,7 @@ int main() {
|
|||||||
float ghalf = config.gridSizecube / 2.f;
|
float ghalf = config.gridSizecube / 2.f;
|
||||||
|
|
||||||
spheredefaults sphereConf;
|
spheredefaults sphereConf;
|
||||||
ceilingdefaults ceilingConf;
|
stardefaults starConf;
|
||||||
|
|
||||||
sphereConf.centerX = ghalf;
|
sphereConf.centerX = ghalf;
|
||||||
sphereConf.centerY = ghalf;
|
sphereConf.centerY = ghalf;
|
||||||
@@ -276,6 +330,15 @@ int main() {
|
|||||||
double lastMouseX = 0, lastMouseY = 0;
|
double lastMouseX = 0, lastMouseY = 0;
|
||||||
float deltaTime = 0.016f;
|
float deltaTime = 0.016f;
|
||||||
|
|
||||||
|
// Initialize render frame times vector
|
||||||
|
renderFrameTimes.resize(FRAME_HISTORY_SIZE, 0.0);
|
||||||
|
|
||||||
|
if (grid.load("output/Treegrid.yggs")) {
|
||||||
|
gridInitialized = true;
|
||||||
|
grid.printStats();
|
||||||
|
resetView(cam, config.gridSizecube);
|
||||||
|
}
|
||||||
|
|
||||||
while (!glfwWindowShouldClose(window)) {
|
while (!glfwWindowShouldClose(window)) {
|
||||||
double currentTime = glfwGetTime();
|
double currentTime = glfwGetTime();
|
||||||
static double lastFrameTime = currentTime;
|
static double lastFrameTime = currentTime;
|
||||||
@@ -374,13 +437,20 @@ int main() {
|
|||||||
ImGui::SliderFloat("Refraction", &sphereConf.refraction, 0.0f, 1.0f);
|
ImGui::SliderFloat("Refraction", &sphereConf.refraction, 0.0f, 1.0f);
|
||||||
ImGui::Checkbox("Fill Inside", &sphereConf.fillInside);
|
ImGui::Checkbox("Fill Inside", &sphereConf.fillInside);
|
||||||
|
|
||||||
if (ImGui::CollapsingHeader("Ceiling Light Parameters", ImGuiTreeNodeFlags_DefaultOpen)) {
|
if (ImGui::CollapsingHeader("Star/Sun Parameters", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
ImGui::Checkbox("Enable Ceiling Light", &ceilingConf.enabled);
|
ImGui::Checkbox("Enable Star", &starConf.enabled);
|
||||||
ImGui::DragFloat("Height (Y)", &ceilingConf.yLevel, 1.0f, 0.0f, (float)config.gridSizecube);
|
|
||||||
ImGui::DragFloat("Spacing", &ceilingConf.spacing, 0.5f, 1.0f, 100.0f);
|
// Allow large range for position to place it "far away"
|
||||||
ImGui::DragFloat("Light Emittance", &ceilingConf.emittance, 0.1f, 0.0f, 100.0f);
|
float starPos[3] = { starConf.x, starConf.y, starConf.z };
|
||||||
ImGui::ColorEdit3("Light Color", ceilingConf.color);
|
if (ImGui::DragFloat3("Position", starPos, 5.0f, -2000.0f, 2000.0f)) {
|
||||||
ImGui::DragFloat("Light Voxel Size", &ceilingConf.voxelSize, 0.1f, 0.1f, 10.0f);
|
starConf.x = starPos[0];
|
||||||
|
starConf.y = starPos[1];
|
||||||
|
starConf.z = starPos[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::DragFloat("Size (Radius)", &starConf.size, 1.0f, 1.0f, 500.0f);
|
||||||
|
ImGui::DragFloat("Brightness", &starConf.emittance, 1.0f, 0.0f, 1000.0f);
|
||||||
|
ImGui::ColorEdit3("Light Color", starConf.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -388,7 +458,7 @@ int main() {
|
|||||||
if (ImGui::Button("Create Sphere & Render")) {
|
if (ImGui::Button("Create Sphere & Render")) {
|
||||||
createSphere(config, sphereConf, grid);
|
createSphere(config, sphereConf, grid);
|
||||||
grid.printStats();
|
grid.printStats();
|
||||||
addCeilingLight(config, ceilingConf, grid);
|
addStar(config, starConf, grid);
|
||||||
gridInitialized = true;
|
gridInitialized = true;
|
||||||
|
|
||||||
resetView(cam, config.gridSizecube);
|
resetView(cam, config.gridSizecube);
|
||||||
@@ -404,6 +474,33 @@ int main() {
|
|||||||
{
|
{
|
||||||
ImGui::Begin("Preview");
|
ImGui::Begin("Preview");
|
||||||
|
|
||||||
|
// Display render FPS information
|
||||||
|
ImGui::Text("Render Performance:");
|
||||||
|
if (renderFPS > 0) {
|
||||||
|
// Color code based on FPS
|
||||||
|
ImVec4 fpsColor;
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
ImGui::Text("No render data yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
if (gridInitialized && textureInitialized) {
|
if (gridInitialized && textureInitialized) {
|
||||||
ImGui::Image((void*)(intptr_t)textu, ImVec2(config.outWidth, config.outHeight));
|
ImGui::Image((void*)(intptr_t)textu, ImVec2(config.outWidth, config.outHeight));
|
||||||
} else if (gridInitialized) {
|
} else if (gridInitialized) {
|
||||||
@@ -592,6 +689,8 @@ int main() {
|
|||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
|
grid.save("output/Treegrid.yggs");
|
||||||
|
|
||||||
glfwDestroyWindow(window);
|
glfwDestroyWindow(window);
|
||||||
if (textu != 0) {
|
if (textu != 0) {
|
||||||
glDeleteTextures(1, &textu);
|
glDeleteTextures(1, &textu);
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class Octree {
|
|||||||
public:
|
public:
|
||||||
using PointType = Eigen::Matrix<float, Dim, 1>;
|
using PointType = Eigen::Matrix<float, Dim, 1>;
|
||||||
using BoundingBox = std::pair<PointType, PointType>;
|
using BoundingBox = std::pair<PointType, PointType>;
|
||||||
|
|
||||||
|
enum class Shape {
|
||||||
|
SPHERE,
|
||||||
|
CUBE
|
||||||
|
};
|
||||||
|
|
||||||
struct NodeData {
|
struct NodeData {
|
||||||
T data;
|
T data;
|
||||||
PointType position;
|
PointType position;
|
||||||
@@ -39,15 +45,28 @@ public:
|
|||||||
float emittance;
|
float emittance;
|
||||||
float refraction;
|
float refraction;
|
||||||
float reflection;
|
float reflection;
|
||||||
|
Shape shape;
|
||||||
|
|
||||||
NodeData(const T& data, const PointType& pos, bool visible, Eigen::Vector3f color, float size = 0.01f,
|
NodeData(const T& data, const PointType& pos, bool visible, Eigen::Vector3f color, float size = 0.01f,
|
||||||
bool active = true, int objectId = -1, bool light = false, float emittance = 0.0f, float refraction = 0.0f,
|
bool active = true, int objectId = -1, bool light = false, float emittance = 0.0f,
|
||||||
float reflection = 0.0f) : data(data), position(pos), objectId(objectId), active(active), visible(visible),
|
float refraction = 0.0f, float reflection = 0.0f, Shape shape = Shape::SPHERE)
|
||||||
|
: data(data), position(pos), objectId(objectId), active(active), visible(visible),
|
||||||
color(color), size(size), light(light), emittance(emittance), refraction(refraction),
|
color(color), size(size), light(light), emittance(emittance), refraction(refraction),
|
||||||
reflection(reflection) {}
|
reflection(reflection), shape(shape) {}
|
||||||
|
|
||||||
NodeData() : objectId(-1), active(false), visible(false), size(0.0f), light(false),
|
NodeData() : objectId(-1), active(false), visible(false), size(0.0f), light(false),
|
||||||
emittance(0.0f), refraction(0.0f), reflection(0.0f) {}
|
emittance(0.0f), refraction(0.0f), reflection(0.0f), shape(Shape::SPHERE) {}
|
||||||
|
|
||||||
|
// Helper method to get half-size for cube
|
||||||
|
PointType getHalfSize() const {
|
||||||
|
return PointType(size * 0.5f, size * 0.5f, size * 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get bounding box for cube
|
||||||
|
BoundingBox getCubeBounds() const {
|
||||||
|
PointType halfSize = getHalfSize();
|
||||||
|
return {position - halfSize, position + halfSize};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OctreeNode {
|
struct OctreeNode {
|
||||||
@@ -185,6 +204,7 @@ private:
|
|||||||
writeVal(out, pt->emittance);
|
writeVal(out, pt->emittance);
|
||||||
writeVal(out, pt->refraction);
|
writeVal(out, pt->refraction);
|
||||||
writeVal(out, pt->reflection);
|
writeVal(out, pt->reflection);
|
||||||
|
writeVal(out, static_cast<int>(pt->shape));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Write bitmask of active children
|
// Write bitmask of active children
|
||||||
@@ -228,6 +248,9 @@ private:
|
|||||||
readVal(in, pt->emittance);
|
readVal(in, pt->emittance);
|
||||||
readVal(in, pt->refraction);
|
readVal(in, pt->refraction);
|
||||||
readVal(in, pt->reflection);
|
readVal(in, pt->reflection);
|
||||||
|
int shapeInt;
|
||||||
|
readVal(in, shapeInt);
|
||||||
|
pt->shape = static_cast<Shape>(shapeInt);
|
||||||
node->points.push_back(pt);
|
node->points.push_back(pt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -315,6 +338,62 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool raySphereIntersect(const PointType& origin, const PointType& dir, const PointType& center,
|
||||||
|
float radius, float& t) const {
|
||||||
|
PointType oc = origin - center;
|
||||||
|
float a = dir.dot(dir);
|
||||||
|
float b = 2.0f * oc.dot(dir);
|
||||||
|
float c = oc.dot(oc) - radius * radius;
|
||||||
|
float discriminant = b * b - 4 * a * c;
|
||||||
|
|
||||||
|
if (discriminant < 0) return false;
|
||||||
|
|
||||||
|
float sqrtDisc = sqrt(discriminant);
|
||||||
|
float t0 = (-b - sqrtDisc) / (2.0f * a);
|
||||||
|
float t1 = (-b + sqrtDisc) / (2.0f * a);
|
||||||
|
|
||||||
|
t = t0;
|
||||||
|
if (t0 < 0.001f) {
|
||||||
|
t = t1;
|
||||||
|
if (t1 < 0.001f) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray-cube intersection
|
||||||
|
bool rayCubeIntersect(const PointType& origin, const PointType& dir, const NodeData* cube,
|
||||||
|
float& t, PointType& normal, PointType& hitPoint) const {
|
||||||
|
// Use the cube's bounds for intersection
|
||||||
|
BoundingBox bounds = cube->getCubeBounds();
|
||||||
|
|
||||||
|
float tMin, tMax;
|
||||||
|
if (!rayBoxIntersect(origin, dir, bounds, tMin, tMax)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tMin < 0.001f) {
|
||||||
|
if (tMax < 0.001f) return false;
|
||||||
|
t = tMax;
|
||||||
|
} else {
|
||||||
|
t = tMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitPoint = origin + dir * t;
|
||||||
|
|
||||||
|
const float epsilon = 0.0001f;
|
||||||
|
normal = PointType::Zero();
|
||||||
|
|
||||||
|
for (int i = 0; i < Dim; ++i) {
|
||||||
|
if (std::abs(hitPoint[i] - bounds.first[i]) < epsilon) {
|
||||||
|
normal[i] = -1.0f;
|
||||||
|
} else if (std::abs(hitPoint[i] - bounds.second[i]) < epsilon) {
|
||||||
|
normal[i] = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
float randomValueNormalDistribution(uint32_t& state) {
|
float randomValueNormalDistribution(uint32_t& state) {
|
||||||
std::mt19937 gen(state);
|
std::mt19937 gen(state);
|
||||||
state = gen();
|
state = gen();
|
||||||
@@ -340,6 +419,7 @@ private:
|
|||||||
float rgbToGrayscale(const Eigen::Vector3f& color) const {
|
float rgbToGrayscale(const Eigen::Vector3f& color) const {
|
||||||
return 0.2126f * color[0] + 0.7152f * color[1] + 0.0722f * color[2];
|
return 0.2126f * color[0] + 0.7152f * color[1] + 0.0722f * color[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Octree(const PointType& minBound, const PointType& maxBound, size_t maxPointsPerNode=16, size_t maxDepth = 16) :
|
Octree(const PointType& minBound, const PointType& maxBound, size_t maxPointsPerNode=16, size_t maxDepth = 16) :
|
||||||
root_(std::make_unique<OctreeNode>(minBound, maxBound)), maxPointsPerNode(maxPointsPerNode),
|
root_(std::make_unique<OctreeNode>(minBound, maxBound)), maxPointsPerNode(maxPointsPerNode),
|
||||||
@@ -348,9 +428,10 @@ public:
|
|||||||
Octree() : root_(nullptr), maxPointsPerNode(16), maxDepth(16), size(0) {}
|
Octree() : root_(nullptr), maxPointsPerNode(16), maxDepth(16), size(0) {}
|
||||||
|
|
||||||
bool set(const T& data, const PointType& pos, bool visible, Eigen::Vector3f color, float size, bool active,
|
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, Shape shape = Shape::SPHERE) {
|
||||||
auto pointData = std::make_shared<NodeData>(data, pos, visible, color, size, active, objectId,
|
auto pointData = std::make_shared<NodeData>(data, pos, visible, color, size, active, objectId,
|
||||||
light, emittance, refraction, reflection);
|
light, emittance, refraction, reflection, shape);
|
||||||
if (insertRecursive(root_.get(), pointData, 0)) {
|
if (insertRecursive(root_.get(), pointData, 0)) {
|
||||||
this->size++;
|
this->size++;
|
||||||
return true;
|
return true;
|
||||||
@@ -375,6 +456,7 @@ public:
|
|||||||
serializeNode(out, root_.get());
|
serializeNode(out, root_.get());
|
||||||
|
|
||||||
out.close();
|
out.close();
|
||||||
|
std::cout << "successfully saved grid to " << filename << std::endl;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +483,7 @@ public:
|
|||||||
deserializeNode(in, root_.get());
|
deserializeNode(in, root_.get());
|
||||||
|
|
||||||
in.close();
|
in.close();
|
||||||
|
std::cout << "successfully loaded grid from " << filename << std::endl;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,8 +595,8 @@ public:
|
|||||||
|
|
||||||
bool update(const PointType& oldPos, const PointType& newPos, const T& newData = T(), bool newVisible = true,
|
bool update(const PointType& oldPos, const PointType& newPos, const T& newData = T(), bool newVisible = true,
|
||||||
Eigen::Vector3f newColor = Eigen::Vector3f(1.0f, 1.0f, 1.0f), float newSize = 0.01f, bool newActive = true,
|
Eigen::Vector3f newColor = Eigen::Vector3f(1.0f, 1.0f, 1.0f), float newSize = 0.01f, bool newActive = true,
|
||||||
int newObjectId = -2, bool newLight = false, float newEmittance = 0.0f, float newRefraction = 0.0f, float newReflection = 0.0f,
|
int newObjectId = -2, bool newLight = false, float newEmittance = 0.0f, float newRefraction = 0.0f,
|
||||||
float tolerance = 0.0001f) {
|
float newReflection = 0.0f, Shape newShape = Shape::SPHERE, float tolerance = 0.0001f) {
|
||||||
|
|
||||||
// Find the existing point
|
// Find the existing point
|
||||||
auto pointData = find(oldPos, tolerance);
|
auto pointData = find(oldPos, tolerance);
|
||||||
@@ -533,6 +616,7 @@ public:
|
|||||||
float emittanceCopy = pointData->emittance;
|
float emittanceCopy = pointData->emittance;
|
||||||
float refractionCopy = pointData->refraction;
|
float refractionCopy = pointData->refraction;
|
||||||
float reflectionCopy = pointData->reflection;
|
float reflectionCopy = pointData->reflection;
|
||||||
|
Shape shapeCopy = pointData->shape;
|
||||||
|
|
||||||
// Remove the old point
|
// Remove the old point
|
||||||
if (!remove(oldPos, tolerance)) {
|
if (!remove(oldPos, tolerance)) {
|
||||||
@@ -549,7 +633,8 @@ public:
|
|||||||
newLight ? newLight : lightCopy,
|
newLight ? newLight : lightCopy,
|
||||||
newEmittance > 0 ? newEmittance : emittanceCopy,
|
newEmittance > 0 ? newEmittance : emittanceCopy,
|
||||||
newRefraction >= 0 ? newRefraction : refractionCopy,
|
newRefraction >= 0 ? newRefraction : refractionCopy,
|
||||||
newReflection >= 0 ? newReflection : reflectionCopy);
|
newReflection >= 0 ? newReflection : reflectionCopy,
|
||||||
|
newShape);
|
||||||
} else {
|
} else {
|
||||||
// Just update properties in place
|
// Just update properties in place
|
||||||
pointData->data = newData;
|
pointData->data = newData;
|
||||||
@@ -563,6 +648,7 @@ public:
|
|||||||
pointData->emittance = newEmittance;
|
pointData->emittance = newEmittance;
|
||||||
pointData->refraction = newRefraction;
|
pointData->refraction = newRefraction;
|
||||||
pointData->reflection = newReflection;
|
pointData->reflection = newReflection;
|
||||||
|
pointData->shape = newShape;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,6 +716,14 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool setShape(const PointType& pos, Shape shape, float tolerance = 0.0001f) {
|
||||||
|
auto pointData = find(pos, tolerance);
|
||||||
|
if (!pointData) return false;
|
||||||
|
|
||||||
|
pointData->shape = shape;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::shared_ptr<NodeData>> voxelTraverse(const PointType& origin, const PointType& direction,
|
std::vector<std::shared_ptr<NodeData>> voxelTraverse(const PointType& origin, const PointType& direction,
|
||||||
float maxDist, bool stopAtFirstHit) const {
|
float maxDist, bool stopAtFirstHit) const {
|
||||||
std::vector<std::shared_ptr<NodeData>> hits;
|
std::vector<std::shared_ptr<NodeData>> hits;
|
||||||
@@ -643,16 +737,27 @@ public:
|
|||||||
for (const auto& pointData : node->points) {
|
for (const auto& pointData : node->points) {
|
||||||
if (!pointData->active) continue;
|
if (!pointData->active) continue;
|
||||||
|
|
||||||
PointType toPoint = pointData->position - origin;
|
if (pointData->shape == Shape::SPHERE) {
|
||||||
float projection = toPoint.dot(dir);
|
PointType center = pointData->position;
|
||||||
if (projection >= 0 && projection <= maxDist) {
|
float radius = pointData->size;
|
||||||
PointType closestPoint = origin + dir * projection;
|
float t;
|
||||||
float distSq = (pointData->position - closestPoint).squaredNorm();
|
|
||||||
if (distSq < pointData->size * pointData->size) {
|
if (raySphereIntersect(origin, dir, center, radius, t)) {
|
||||||
|
if (t >= 0 && t <= maxDist) {
|
||||||
hits.emplace_back(pointData);
|
hits.emplace_back(pointData);
|
||||||
if (stopAtFirstHit) return;
|
if (stopAtFirstHit) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
float t;
|
||||||
|
PointType normal, hitPoint;
|
||||||
|
if (rayCubeIntersect(origin, dir, pointData.get(), t, normal, hitPoint)) {
|
||||||
|
if (t >= 0 && t <= maxDist) {
|
||||||
|
hits.emplace_back(pointData);
|
||||||
|
if (stopAtFirstHit) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::array<std::pair<int, float>, 8> childOrder;
|
std::array<std::pair<int, float>, 8> childOrder;
|
||||||
@@ -737,6 +842,13 @@ public:
|
|||||||
|
|
||||||
auto obj = hits[0];
|
auto obj = hits[0];
|
||||||
|
|
||||||
|
PointType hitPoint;
|
||||||
|
PointType normal;
|
||||||
|
float t = 0.0f;
|
||||||
|
|
||||||
|
// Calculate intersection based on shape
|
||||||
|
if (obj->shape == Shape::SPHERE) {
|
||||||
|
// Sphere intersection
|
||||||
PointType center = obj->position;
|
PointType center = obj->position;
|
||||||
float radius = obj->size;
|
float radius = obj->size;
|
||||||
PointType L_vec = center - rayOrig;
|
PointType L_vec = center - rayOrig;
|
||||||
@@ -744,15 +856,21 @@ public:
|
|||||||
float d2 = L_vec.dot(L_vec) - tca * tca;
|
float d2 = L_vec.dot(L_vec) - tca * tca;
|
||||||
float radius2 = radius * radius;
|
float radius2 = radius * radius;
|
||||||
|
|
||||||
float t = tca;
|
|
||||||
if (d2 <= radius2) {
|
if (d2 <= radius2) {
|
||||||
float thc = std::sqrt(radius2 - d2);
|
float thc = std::sqrt(radius2 - d2);
|
||||||
t = tca - thc;
|
t = tca - thc;
|
||||||
if (t < 0.001f) t = tca + thc;
|
if (t < 0.001f) t = tca + thc;
|
||||||
}
|
}
|
||||||
|
|
||||||
PointType hitPoint = rayOrig + rayDir * t;
|
hitPoint = rayOrig + rayDir * t;
|
||||||
PointType normal = (hitPoint - center).normalized();
|
normal = (hitPoint - center).normalized();
|
||||||
|
} else {
|
||||||
|
// Cube intersection
|
||||||
|
PointType cubeNormal;
|
||||||
|
if (!rayCubeIntersect(rayOrig, rayDir, obj.get(), t, normal, hitPoint)) {
|
||||||
|
return space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Eigen::Vector3f finalColor = space;
|
Eigen::Vector3f finalColor = space;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user