terrible branch.
This commit is contained in:
@@ -80,6 +80,7 @@ public:
|
||||
|
||||
// Update simulation objects like the Star
|
||||
sim.updateStar(deltaTime);
|
||||
sim.updateWeatherAndPhysics(deltaTime);
|
||||
|
||||
ImGui::Begin("WorldBox Simulation");
|
||||
if (ImGui::BeginTable("MainLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter)) {
|
||||
@@ -140,6 +141,34 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Weather & Physics", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Checkbox("Enable Gravity (Terrain)", &sim.config.enableGravity);
|
||||
ImGui::DragFloat3("Gravity", sim.config.gravity.data());
|
||||
ImGui::DragFloat3("Wind", sim.config.wind.data());
|
||||
ImGui::DragFloat("Physics Step (sec)", &sim.config.physicsStep, 0.01f, 0.01f, 1.0f);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Clouds & Rain");
|
||||
ImGui::DragInt("Cloud Count", &sim.config.cloudCount, 1, 0, 100);
|
||||
ImGui::DragFloat("Cloud Height", &sim.config.cloudHeight, 5.0f, 10.0f, 1000.0f);
|
||||
ImGui::DragFloat("Rain Spawn Rate", &sim.config.rainSpawnRate, 0.1f, 0.0f, 50.0f);
|
||||
ImGui::ColorEdit3("Cloud Color", sim.config.cloudColor.data());
|
||||
ImGui::ColorEdit3("Rain Color", sim.config.rainColor.data());
|
||||
|
||||
if (ImGui::Button("Generate Clouds", ImVec2(-1, 40))) {
|
||||
sim.generateClouds();
|
||||
applyDebugColorMode();
|
||||
statsNeedUpdate = true;
|
||||
}
|
||||
if (ImGui::Button("Clear Weather", ImVec2(-1, 30))) {
|
||||
for (auto& c : sim.clouds) sim.grid.remove(c.pos);
|
||||
for (auto& r : sim.rainDrops) sim.grid.remove(r.pos);
|
||||
sim.clouds.clear();
|
||||
sim.rainDrops.clear();
|
||||
statsNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Environment & Celestial", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Text("Star Settings");
|
||||
ImGui::Checkbox("Enable Star Rotation", &sim.config.enableStarRotation);
|
||||
@@ -246,10 +275,21 @@ public:
|
||||
}
|
||||
case DebugColorMode::BASE:
|
||||
default:
|
||||
if (p->data.type == 1) color = sim.config.baseRockColor;
|
||||
else if (p->data.type == 2) color = sim.config.grassColorBase;
|
||||
else if (p->data.type == 3) color = sim.config.starColor;
|
||||
else color = sim.config.baseDirtColor;
|
||||
if (p->data.type == 0) {
|
||||
v3 darkDirt = sim.config.baseDirtColor * 0.4f;
|
||||
color = sim.config.baseDirtColor * (1.0f - p->data.moisture) + darkDirt * p->data.moisture;
|
||||
} else if (p->data.type == 1) {
|
||||
color = sim.config.baseRockColor;
|
||||
} else if (p->data.type == 2) {
|
||||
v3 lushGrass = sim.config.grassColorBase * 1.5f;
|
||||
color = sim.config.grassColorBase * (1.0f - p->data.moisture) + lushGrass * p->data.moisture;
|
||||
} else if (p->data.type == 3) {
|
||||
color = sim.config.starColor;
|
||||
} else if (p->data.type == 4) {
|
||||
color = sim.config.cloudColor;
|
||||
} else if (p->data.type == 5) {
|
||||
color = sim.config.rainColor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,13 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct RaycastHit {
|
||||
std::shared_ptr<NodeData> node;
|
||||
float distance;
|
||||
PointType normal;
|
||||
PointType hitPoint;
|
||||
};
|
||||
|
||||
struct OctreeNode {
|
||||
BoundingBox bounds;
|
||||
std::vector<std::shared_ptr<NodeData>> points;
|
||||
@@ -634,6 +641,89 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void insertHit(std::vector<RaycastHit>& hits, size_t maxHits, const std::shared_ptr<NodeData>& node,
|
||||
float t, const PointType& normal, const PointType& hitPoint, float& maxDist) const {
|
||||
for (const auto& h : hits) {
|
||||
if (h.node == node) return;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(hits.begin(), hits.end(), t,
|
||||
[](const RaycastHit& a, float val) {
|
||||
return a.distance < val;
|
||||
});
|
||||
|
||||
hits.insert(it, {node, t, normal, hitPoint});
|
||||
|
||||
if (hits.size() > maxHits) {
|
||||
hits.pop_back();
|
||||
}
|
||||
|
||||
if (hits.size() == maxHits) {
|
||||
maxDist = std::min(maxDist, hits.back().distance);
|
||||
}
|
||||
}
|
||||
|
||||
void voxelTraverseMultipleRecursive(OctreeNode* node, float tMin, float tMax, float& maxDist, bool enableLOD,
|
||||
const Ray& ray, std::vector<RaycastHit>& hits, size_t maxHits, float invLodf) const {
|
||||
if (enableLOD && !node->isLeaf) {
|
||||
float dist = (node->center - ray.origin).norm();
|
||||
float ratio = dist / node->nodeSize;
|
||||
|
||||
if (dist > lodMinDistance_ && ratio > invLodf && node->lodData) {
|
||||
float t;
|
||||
PointType n;
|
||||
PointType h;
|
||||
if (rayCubeIntersect(ray, node->lodData.get(), t, n, h)) {
|
||||
if (t >= 0 && t <= maxDist) {
|
||||
insertHit(hits, maxHits, node->lodData, t, n, h, maxDist);
|
||||
}
|
||||
}
|
||||
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 >= 0 && t <= maxDist && t <= tMax + 0.001f) {
|
||||
insertHit(hits, maxHits, pointData, t, normal, hitPoint, maxDist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node->isLeaf) return;
|
||||
|
||||
// DDA Traversal
|
||||
PointType center = node->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 && tMin <= maxDist) {
|
||||
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]) {
|
||||
voxelTraverseMultipleRecursive(node->children[physIdx].get(), tMin, tNext, maxDist, enableLOD, ray, hits, maxHits, invLodf);
|
||||
}
|
||||
|
||||
tMin = tNext;
|
||||
currIdx |= ((next_t[0] <= tNext) ? 1 : 0) | ((next_t[1] <= tNext) ? 2 : 0) | ((next_t[2] <= tNext) ? 4 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
PointType sampleGGX(const PointType& n, float roughness, uint32_t& state) const {
|
||||
float alpha = std::max(EPSILON, roughness * roughness);
|
||||
float r1 = float(rand_r(&state)) / float(RAND_MAX);
|
||||
@@ -2280,6 +2370,11 @@ public:
|
||||
|
||||
size = 0;
|
||||
}
|
||||
|
||||
void collectNodesByObjectId(int id, std::vector<std::shared_ptr<NodeData>>& results) const {
|
||||
std::unordered_set<std::shared_ptr<NodeData>> seen;
|
||||
collectNodesByObjectIdRecursive(root_.get(), id, results, seen);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mutex>
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../grid/grid3eigen.hpp"
|
||||
#include "../timing_decorator.cpp"
|
||||
@@ -17,7 +18,7 @@ using v3 = Eigen::Vector3f;
|
||||
struct WorldVoxel {
|
||||
float nutrients = 1.0f;
|
||||
float moisture = 0.5f;
|
||||
int type = 0;
|
||||
int type = 0; // 0=Dirt, 1=Rock, 2=Grass, 3=Star, 4=Cloud, 5=Rain
|
||||
|
||||
WorldVoxel() = default;
|
||||
|
||||
@@ -48,6 +49,32 @@ struct WorldBoxConfig {
|
||||
v3 starColor = v3(1.0f, 0.95f, 0.8f);
|
||||
float starSpeed = 0.2f; // Radians per second
|
||||
float starAngle = 0.0f;
|
||||
|
||||
// Weather Config
|
||||
int cloudCount = 15;
|
||||
float cloudHeight = 150.0f;
|
||||
v3 cloudColor = v3(0.9f, 0.9f, 0.95f);
|
||||
float cloudBaseSize = 6.0f;
|
||||
|
||||
v3 rainColor = v3(0.2f, 0.4f, 0.9f);
|
||||
float rainDropSize = 0.5f;
|
||||
float rainSpawnRate = 1.0f;
|
||||
|
||||
// Physics Config
|
||||
bool enableGravity = true;
|
||||
v3 gravity = v3(0.0f, -60.0f, 0.0f);
|
||||
v3 wind = v3(20.0f, 0.0f, 10.0f);
|
||||
float physicsStep = 0.1f;
|
||||
};
|
||||
|
||||
struct CloudVoxel {
|
||||
v3 pos;
|
||||
float size;
|
||||
};
|
||||
|
||||
struct RainDrop {
|
||||
v3 pos;
|
||||
v3 vel;
|
||||
};
|
||||
|
||||
class worldboxsim {
|
||||
@@ -56,6 +83,10 @@ public:
|
||||
Octree<WorldVoxel> grid;
|
||||
std::mt19937 rng;
|
||||
std::vector<v3> starVoxelPositions;
|
||||
|
||||
std::vector<CloudVoxel> clouds;
|
||||
std::vector<RainDrop> rainDrops;
|
||||
float physicsTimer = 0.0f;
|
||||
|
||||
worldboxsim() : rng(42) {
|
||||
config = WorldBoxConfig();
|
||||
@@ -64,12 +95,10 @@ public:
|
||||
}
|
||||
|
||||
void updateStar(float dt) {
|
||||
// If turned off, despawn current star if it exists
|
||||
if (!config.enableStarRotation) {
|
||||
if (!starVoxelPositions.empty()) {
|
||||
WorldVoxel emptyVoxel;
|
||||
for(const auto& pos : starVoxelPositions) {
|
||||
grid.set(emptyVoxel, pos, false, v3(0,0,0), config.starVoxelSize, false, 0, 0);
|
||||
grid.remove(pos);
|
||||
}
|
||||
starVoxelPositions.clear();
|
||||
}
|
||||
@@ -80,17 +109,10 @@ public:
|
||||
config.starAngle += dt * config.starSpeed;
|
||||
if (config.starAngle > 2 * M_PI) config.starAngle -= 2 * M_PI;
|
||||
|
||||
// 1. Erase the old star voxels
|
||||
WorldVoxel emptyVoxel;
|
||||
for(const auto& pos : starVoxelPositions) {
|
||||
grid.set(emptyVoxel, pos, false, v3(0,0,0), config.starVoxelSize, false, 0, 0);
|
||||
}
|
||||
starVoxelPositions.clear();
|
||||
|
||||
// 2. Calculate new center of star (orbiting on the X/Y plane)
|
||||
// Calculate new center of star (orbiting on the X/Y plane)
|
||||
v3 starCenter(cos(config.starAngle) * config.starOrbitRadius, sin(config.starAngle) * config.starOrbitRadius, 0.0f);
|
||||
|
||||
// 3. Create a flat panel facing the origin
|
||||
// Create a flat panel facing the origin
|
||||
v3 n = -starCenter.normalized();
|
||||
v3 worldUp(0, 1, 0);
|
||||
if (std::abs(n.dot(worldUp)) > 0.99f) worldUp = v3(0, 0, 1);
|
||||
@@ -100,12 +122,181 @@ public:
|
||||
int halfGrid = std::max(1, static_cast<int>((config.starPanelSize / config.starVoxelSize) / 2.0f));
|
||||
WorldVoxel starVoxel(0.0f, 0.0f, 3); // Type 3 = Star
|
||||
|
||||
// Calculate the new ideal positions for this frame
|
||||
std::vector<v3> newPositions;
|
||||
newPositions.reserve((2 * halfGrid + 1) * (2 * halfGrid + 1));
|
||||
|
||||
for (int i = -halfGrid; i <= halfGrid; ++i) {
|
||||
for (int j = -halfGrid; j <= halfGrid; ++j) {
|
||||
v3 pos = starCenter + (right * (i * config.starVoxelSize)) + (up * (j * config.starVoxelSize));
|
||||
// Add star voxel with high material/emission flag
|
||||
grid.set(starVoxel, pos, true, config.starColor, config.starVoxelSize, true, 1, 1);
|
||||
starVoxelPositions.push_back(pos);
|
||||
newPositions.push_back(starCenter + (right * (i * config.starVoxelSize)) + (up * (j * config.starVoxelSize)));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply grid changes
|
||||
if (starVoxelPositions.empty()) {
|
||||
// Creation: Spawn voxels into the grid for the first time
|
||||
for (const auto& pos : newPositions) {
|
||||
// Injecting a high emittance factor (15.0f) to make it a bright emissive light source
|
||||
grid.set(starVoxel, pos, true, config.starColor, config.starVoxelSize, true, 1, 1, 15.0f);
|
||||
}
|
||||
starVoxelPositions = newPositions;
|
||||
} else if (starVoxelPositions.size() == newPositions.size()) {
|
||||
// Moving: Using grid.move() to smoothly transfer nodes in the Octree
|
||||
for (size_t i = 0; i < starVoxelPositions.size(); ++i) {
|
||||
grid.move(starVoxelPositions[i], newPositions[i]);
|
||||
}
|
||||
starVoxelPositions = newPositions;
|
||||
}
|
||||
}
|
||||
|
||||
void generateClouds() {
|
||||
std::uniform_real_distribution<float> randX(-config.worldSizeX/2, config.worldSizeX/2);
|
||||
std::uniform_real_distribution<float> randZ(-config.worldSizeZ/2, config.worldSizeZ/2);
|
||||
std::uniform_real_distribution<float> randY(config.cloudHeight - 10.0f, config.cloudHeight + 10.0f);
|
||||
|
||||
for (int i=0; i<config.cloudCount; ++i) {
|
||||
v3 center(randX(rng), randY(rng), randZ(rng));
|
||||
int numVoxels = 10 + (rng() % 20);
|
||||
|
||||
for (int j=0; j<numVoxels; ++j) {
|
||||
v3 offset(
|
||||
(rng() % 40) - 20,
|
||||
(rng() % 10) - 5,
|
||||
(rng() % 40) - 20
|
||||
);
|
||||
v3 pos = center + offset;
|
||||
float size = config.cloudBaseSize + (rng() % 6);
|
||||
|
||||
WorldVoxel vox(0.0f, 1.0f, 4); // Type 4 = Cloud
|
||||
// Adding to grid with transmission=0.4f (makes it partially transparent for RTX)
|
||||
grid.set(vox, pos, true, config.cloudColor, size, true, 4, 0, 0.0f, 1.0f, 0.0f, 0.4f);
|
||||
clouds.push_back({pos, size});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateWeatherAndPhysics(float dt) {
|
||||
float halfX = config.worldSizeX / 2.0f;
|
||||
float halfZ = config.worldSizeZ / 2.0f;
|
||||
|
||||
// 1. Clouds Update
|
||||
std::vector<CloudVoxel> nextClouds;
|
||||
for (auto& c : clouds) {
|
||||
v3 nextPos = c.pos + config.wind * dt;
|
||||
|
||||
// Screen wrap logic for wind drift
|
||||
if (nextPos.x() > halfX) nextPos.x() -= config.worldSizeX;
|
||||
if (nextPos.x() < -halfX) nextPos.x() += config.worldSizeX;
|
||||
if (nextPos.z() > halfZ) nextPos.z() -= config.worldSizeZ;
|
||||
if (nextPos.z() < -halfZ) nextPos.z() += config.worldSizeZ;
|
||||
|
||||
if (grid.move(c.pos, nextPos)) {
|
||||
c.pos = nextPos;
|
||||
} else {
|
||||
WorldVoxel vox(0.0f, 1.0f, 4);
|
||||
grid.set(vox, nextPos, true, config.cloudColor, c.size, true, 4, 0, 0.0f, 1.0f, 0.0f, 0.4f);
|
||||
c.pos = nextPos;
|
||||
}
|
||||
nextClouds.push_back(c);
|
||||
|
||||
// Spawn Rain
|
||||
std::uniform_real_distribution<float> dist(0, 1);
|
||||
if (dist(rng) < (config.rainSpawnRate * dt * 0.1f)) {
|
||||
RainDrop r = {c.pos - v3(0, c.size, 0), config.wind};
|
||||
rainDrops.push_back(r);
|
||||
WorldVoxel rv(0.0f, 1.0f, 5); // Type 5 = Rain
|
||||
grid.set(rv, r.pos, true, config.rainColor, config.rainDropSize, true, 5);
|
||||
}
|
||||
}
|
||||
clouds = nextClouds;
|
||||
|
||||
// 2. Rain Update
|
||||
std::vector<RainDrop> nextRain;
|
||||
for (auto& r : rainDrops) {
|
||||
r.vel += config.gravity * dt;
|
||||
v3 nextPos = r.pos + r.vel * dt;
|
||||
|
||||
v3 dir = (nextPos - r.pos);
|
||||
float distMag = dir.norm();
|
||||
|
||||
if (distMag > 0) {
|
||||
dir.normalize();
|
||||
auto hit = grid.voxelTraverse(r.pos, dir, distMag, false);
|
||||
|
||||
// If it hits solid terrain
|
||||
if (hit && hit->data.type != 4 && hit->data.type != 5) {
|
||||
if (hit->data.type == 0) { // Hit Dirt
|
||||
hit->data.moisture = std::min(1.0f, hit->data.moisture + 0.15f);
|
||||
v3 darkDirt = config.baseDirtColor * 0.4f;
|
||||
v3 wetColor = config.baseDirtColor * (1.0f - hit->data.moisture) + darkDirt * hit->data.moisture;
|
||||
grid.setColor(hit->position, wetColor);
|
||||
} else if (hit->data.type == 2) { // Hit Grass
|
||||
hit->data.moisture = std::min(1.0f, hit->data.moisture + 0.15f);
|
||||
v3 lushGrass = config.grassColorBase * 1.5f;
|
||||
v3 wetColor = config.grassColorBase * (1.0f - hit->data.moisture) + lushGrass * hit->data.moisture;
|
||||
grid.setColor(hit->position, wetColor);
|
||||
}
|
||||
|
||||
grid.remove(r.pos);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete if falls out of bounds
|
||||
if (nextPos.y() < -config.worldDepth - 20.0f) {
|
||||
grid.remove(r.pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (grid.move(r.pos, nextPos)) {
|
||||
r.pos = nextPos;
|
||||
nextRain.push_back(r);
|
||||
} else {
|
||||
WorldVoxel rv(0.0f, 1.0f, 5);
|
||||
grid.set(rv, nextPos, true, config.rainColor, config.rainDropSize, true, 5);
|
||||
r.pos = nextPos;
|
||||
nextRain.push_back(r);
|
||||
}
|
||||
}
|
||||
rainDrops = nextRain;
|
||||
|
||||
// 3. Apply Block Gravity
|
||||
if (config.enableGravity) {
|
||||
physicsTimer += dt;
|
||||
if (physicsTimer >= config.physicsStep) {
|
||||
applyTerrainGravity();
|
||||
physicsTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void applyTerrainGravity() {
|
||||
std::vector<std::shared_ptr<Octree<WorldVoxel>::NodeData>> nodes;
|
||||
grid.collectNodesByObjectId( -1, nodes);
|
||||
|
||||
std::vector<std::shared_ptr<Octree<WorldVoxel>::NodeData>> terrain;
|
||||
terrain.reserve(nodes.size());
|
||||
for (auto& n : nodes) {
|
||||
// Include Dirt, Rock, and Grass in gravity sweep
|
||||
if (n->data.type == 0 || n->data.type == 1 || n->data.type == 2) {
|
||||
terrain.push_back(n);
|
||||
}
|
||||
}
|
||||
|
||||
// Process Bottom-Up
|
||||
std::sort(terrain.begin(), terrain.end(), [](const auto& a, const auto& b) {
|
||||
return a->position.y() < b->position.y();
|
||||
});
|
||||
|
||||
for (auto& n : terrain) {
|
||||
v3 belowPos = n->position + v3(0, -config.voxelSize, 0);
|
||||
|
||||
// Bounds check so voxels don't fall infinitely
|
||||
if (belowPos.y() < -config.worldDepth) continue;
|
||||
|
||||
auto hit = grid.find(belowPos, config.voxelSize * 0.1f);
|
||||
if (!hit) {
|
||||
grid.move(n->position, belowPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,11 +370,6 @@ public:
|
||||
|
||||
int nodeCount = 0;
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 local_rng(rd());
|
||||
std::uniform_real_distribution<float> colorVar(-0.03f, 0.03f);
|
||||
std::uniform_real_distribution<float> nutrientVar(0.8f, 1.2f);
|
||||
|
||||
#pragma omp parallel for schedule(static) collapse(3)
|
||||
for (int i = 0; i < stepsX; ++i) {
|
||||
for (int j = 0; j < stepsZ; ++j) {
|
||||
@@ -226,6 +412,9 @@ public:
|
||||
|
||||
void clearWorld() {
|
||||
grid.clear();
|
||||
clouds.clear();
|
||||
rainDrops.clear();
|
||||
starVoxelPositions.clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user