mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat(editor): smooth entire tile, snap-to-ground toggle, object list improvements
- Smooth Entire Tile: global smoothing pass with configurable iterations (1-10). Smooths across chunk boundaries for seamless results. Updates inner vertices from smoothed outer grid. Great after noise generation. - Snap to Ground checkbox: on by default, objects placed at terrain surface. Disable for floating/airborne objects. - Random Rotation + Snap Ground checkboxes side-by-side for fast setup - Toast notifications on noise apply and smooth operations - Smooth pass uses cross-chunk neighbor averaging for edge continuity
This commit is contained in:
parent
5df007b7b9
commit
9bc05fae87
4 changed files with 82 additions and 1 deletions
|
|
@ -336,9 +336,16 @@ void EditorUI::renderBrushPanel(EditorApp& app) {
|
||||||
if (ImGui::Button("Apply Noise", ImVec2(-1, 0))) {
|
if (ImGui::Button("Apply Noise", ImVec2(-1, 0))) {
|
||||||
app.getTerrainEditor().applyNoise(noiseFreq, noiseAmp, noiseOctaves,
|
app.getTerrainEditor().applyNoise(noiseFreq, noiseAmp, noiseOctaves,
|
||||||
static_cast<uint32_t>(noiseSeed));
|
static_cast<uint32_t>(noiseSeed));
|
||||||
|
app.showToast("Noise applied");
|
||||||
|
}
|
||||||
|
static int smoothPasses = 2;
|
||||||
|
ImGui::SliderInt("Smooth Passes", &smoothPasses, 1, 10);
|
||||||
|
if (ImGui::Button("Smooth Entire Tile", ImVec2(-1, 0))) {
|
||||||
|
app.getTerrainEditor().smoothEntireTile(smoothPasses);
|
||||||
|
app.showToast("Tile smoothed");
|
||||||
}
|
}
|
||||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1),
|
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1),
|
||||||
"Adds procedural hills/valleys to entire tile");
|
"Generate terrain, then smooth for natural look");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
@ -466,6 +473,10 @@ void EditorUI::renderObjectPanel(EditorApp& app) {
|
||||||
bool randRot = placer.getRandomRotation();
|
bool randRot = placer.getRandomRotation();
|
||||||
if (ImGui::Checkbox("Random Rotation", &randRot))
|
if (ImGui::Checkbox("Random Rotation", &randRot))
|
||||||
placer.setRandomRotation(randRot);
|
placer.setRandomRotation(randRot);
|
||||||
|
ImGui::SameLine();
|
||||||
|
bool snap = placer.getSnapToGround();
|
||||||
|
if (ImGui::Checkbox("Snap Ground", &snap))
|
||||||
|
placer.setSnapToGround(snap);
|
||||||
float scale = placer.getPlacementScale();
|
float scale = placer.getPlacementScale();
|
||||||
if (ImGui::SliderFloat("Scale", &scale, 0.1f, 10.0f, "%.2f"))
|
if (ImGui::SliderFloat("Scale", &scale, 0.1f, 10.0f, "%.2f"))
|
||||||
placer.setPlacementScale(scale);
|
placer.setPlacementScale(scale);
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ public:
|
||||||
void setPlacementScale(float s) { placementScale_ = s; }
|
void setPlacementScale(float s) { placementScale_ = s; }
|
||||||
bool getRandomRotation() const { return randomRotation_; }
|
bool getRandomRotation() const { return randomRotation_; }
|
||||||
void setRandomRotation(bool v) { randomRotation_ = v; }
|
void setRandomRotation(bool v) { randomRotation_ = v; }
|
||||||
|
bool getSnapToGround() const { return snapToGround_; }
|
||||||
|
void setSnapToGround(bool v) { snapToGround_ = v; }
|
||||||
|
|
||||||
// Undo last placement
|
// Undo last placement
|
||||||
bool canUndoPlace() const { return !undoStack_.empty(); }
|
bool canUndoPlace() const { return !undoStack_.empty(); }
|
||||||
|
|
@ -81,6 +83,7 @@ private:
|
||||||
float placementRotY_ = 0.0f;
|
float placementRotY_ = 0.0f;
|
||||||
float placementScale_ = 1.0f;
|
float placementScale_ = 1.0f;
|
||||||
bool randomRotation_ = false;
|
bool randomRotation_ = false;
|
||||||
|
bool snapToGround_ = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
|
|
|
||||||
|
|
@ -588,6 +588,70 @@ void TerrainEditor::removeWater(const glm::vec3& center, float radius) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TerrainEditor::smoothEntireTile(int iterations) {
|
||||||
|
if (!terrain_) return;
|
||||||
|
|
||||||
|
for (int iter = 0; iter < iterations; iter++) {
|
||||||
|
// Snapshot all heights
|
||||||
|
std::array<std::array<float, 145>, 256> snap;
|
||||||
|
for (int ci = 0; ci < 256; ci++)
|
||||||
|
for (int v = 0; v < 145; v++)
|
||||||
|
snap[ci][v] = terrain_->chunks[ci].heightMap.heights[v];
|
||||||
|
|
||||||
|
for (int ci = 0; ci < 256; ci++) {
|
||||||
|
auto& chunk = terrain_->chunks[ci];
|
||||||
|
if (!chunk.hasHeightMap()) continue;
|
||||||
|
int cx = ci % 16, cy = ci / 16;
|
||||||
|
|
||||||
|
for (int v = 0; v < 145; v++) {
|
||||||
|
int row = v / 17, col = v % 17;
|
||||||
|
if (col > 8) continue; // smooth outer vertices only
|
||||||
|
|
||||||
|
float sum = snap[ci][v];
|
||||||
|
int count = 1;
|
||||||
|
|
||||||
|
// Same-chunk neighbors
|
||||||
|
if (col > 0) { sum += snap[ci][row * 17 + col - 1]; count++; }
|
||||||
|
if (col < 8) { sum += snap[ci][row * 17 + col + 1]; count++; }
|
||||||
|
if (row > 0) { sum += snap[ci][(row - 1) * 17 + col]; count++; }
|
||||||
|
if (row < 8) { sum += snap[ci][(row + 1) * 17 + col]; count++; }
|
||||||
|
|
||||||
|
// Cross-chunk neighbors at edges
|
||||||
|
if (col == 0 && cx > 0) { sum += snap[cy * 16 + cx - 1][row * 17 + 8]; count++; }
|
||||||
|
if (col == 8 && cx < 15) { sum += snap[cy * 16 + cx + 1][row * 17 + 0]; count++; }
|
||||||
|
if (row == 0 && cy > 0) { sum += snap[(cy - 1) * 16 + cx][8 * 17 + col]; count++; }
|
||||||
|
if (row == 8 && cy < 15) { sum += snap[(cy + 1) * 16 + cx][0 * 17 + col]; count++; }
|
||||||
|
|
||||||
|
chunk.heightMap.heights[v] = sum / static_cast<float>(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update inner vertices from smoothed outer vertices
|
||||||
|
for (int v = 0; v < 145; v++) {
|
||||||
|
int row = v / 17, col = v % 17;
|
||||||
|
if (col <= 8) continue;
|
||||||
|
int innerCol = col - 9;
|
||||||
|
// Average of 4 surrounding outer vertices
|
||||||
|
int tl = row * 17 + innerCol;
|
||||||
|
int tr = row * 17 + innerCol + 1;
|
||||||
|
int bl = (row + 1) * 17 + innerCol;
|
||||||
|
int br = (row + 1) * 17 + innerCol + 1;
|
||||||
|
if (tl < 145 && tr < 145 && bl < 145 && br < 145)
|
||||||
|
chunk.heightMap.heights[v] = (chunk.heightMap.heights[tl] +
|
||||||
|
chunk.heightMap.heights[tr] + chunk.heightMap.heights[bl] +
|
||||||
|
chunk.heightMap.heights[br]) * 0.25f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stitch all edges
|
||||||
|
for (int ci = 0; ci < 256; ci++)
|
||||||
|
stitchEdges(ci);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int ci = 0; ci < 256; ci++)
|
||||||
|
dirtyChunks_.push_back(ci);
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
void TerrainEditor::applyErode(float dt) {
|
void TerrainEditor::applyErode(float dt) {
|
||||||
float factor = std::min(1.0f, brush_.settings().strength * dt * 0.3f);
|
float factor = std::min(1.0f, brush_.settings().strength * dt * 0.3f);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,9 @@ public:
|
||||||
// Noise generator: applies procedural height noise to the terrain
|
// Noise generator: applies procedural height noise to the terrain
|
||||||
void applyNoise(float frequency, float amplitude, int octaves, uint32_t seed);
|
void applyNoise(float frequency, float amplitude, int octaves, uint32_t seed);
|
||||||
|
|
||||||
|
// Global smooth pass across entire tile (N iterations)
|
||||||
|
void smoothEntireTile(int iterations);
|
||||||
|
|
||||||
// Import/export heightmap (raw 16-bit grayscale, 129x129)
|
// Import/export heightmap (raw 16-bit grayscale, 129x129)
|
||||||
bool importHeightmap(const std::string& path, float heightScale);
|
bool importHeightmap(const std::string& path, float heightScale);
|
||||||
bool exportHeightmap(const std::string& path, float heightScale);
|
bool exportHeightmap(const std::string& path, float heightScale);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue