diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index 8537303b..6eb8b004 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -124,7 +124,9 @@ void EditorApp::run() { rpInfo.renderArea.extent = vkCtx->getSwapchainExtent(); VkClearValue clearValues[4]{}; - clearValues[0].color = {{0.15f, 0.15f, 0.2f, 1.0f}}; + float cr, cg, cb; + viewport_.getClearColor(cr, cg, cb); + clearValues[0].color = {{cr, cg, cb, 1.0f}}; clearValues[1].depthStencil = {1.0f, 0}; rpInfo.clearValueCount = 2; rpInfo.pClearValues = clearValues; @@ -594,6 +596,23 @@ void EditorApp::requestQuit() { window_->setShouldClose(true); } +void EditorApp::setSkyPreset(int preset) { + switch (preset) { + case 0: // Day + viewport_.setClearColor(0.4f, 0.6f, 0.9f); + viewport_.setLightDir(glm::normalize(glm::vec3(0.5f, -1.0f, 0.8f))); + break; + case 1: // Dusk + viewport_.setClearColor(0.6f, 0.3f, 0.2f); + viewport_.setLightDir(glm::normalize(glm::vec3(0.8f, -0.3f, 0.1f))); + break; + case 2: // Night + viewport_.setClearColor(0.05f, 0.05f, 0.12f); + viewport_.setLightDir(glm::normalize(glm::vec3(0.2f, -0.5f, 0.8f))); + break; + } +} + void EditorApp::startGizmoMode(TransformMode mode) { auto& giz = viewport_.getGizmo(); giz.setMode(mode); diff --git a/tools/editor/editor_app.hpp b/tools/editor/editor_app.hpp index 163f7678..79b40017 100644 --- a/tools/editor/editor_app.hpp +++ b/tools/editor/editor_app.hpp @@ -69,6 +69,7 @@ public: void startGizmoMode(TransformMode mode); void setGizmoAxis(TransformAxis axis); + void setSkyPreset(int preset); // 0=day, 1=dusk, 2=night void snapSelectedToGround(); // Multi-tile support diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 92dff1b6..7d45737f 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -101,6 +101,13 @@ void EditorUI::renderMenuBar(EditorApp& app) { if (ImGui::MenuItem("Wireframe", "F3", &wf)) app.setWireframe(wf); if (ImGui::MenuItem("Reset Camera")) app.resetCamera(); ImGui::Separator(); + if (ImGui::BeginMenu("Sky / Lighting")) { + if (ImGui::MenuItem("Day")) app.setSkyPreset(0); + if (ImGui::MenuItem("Dusk")) app.setSkyPreset(1); + if (ImGui::MenuItem("Night")) app.setSkyPreset(2); + ImGui::EndMenu(); + } + ImGui::Separator(); if (ImGui::MenuItem("Save Bookmark", "F5")) app.saveBookmark(""); auto& bmarks = app.getBookmarks(); if (!bmarks.empty() && ImGui::BeginMenu("Load Bookmark")) { @@ -230,6 +237,24 @@ void EditorUI::renderBrushPanel(EditorApp& app) { if (ImGui::IsItemHovered()) ImGui::SetTooltip("Set target height from cursor position"); } + ImGui::Separator(); + if (ImGui::CollapsingHeader("Noise Generator")) { + static float noiseFreq = 0.005f; + static float noiseAmp = 20.0f; + static int noiseOctaves = 4; + static int noiseSeed = 42; + ImGui::SliderFloat("Frequency", &noiseFreq, 0.001f, 0.05f, "%.4f"); + ImGui::SliderFloat("Amplitude", &noiseAmp, 1.0f, 200.0f, "%.0f"); + ImGui::SliderInt("Octaves", &noiseOctaves, 1, 8); + ImGui::InputInt("Seed", &noiseSeed); + if (ImGui::Button("Apply Noise", ImVec2(-1, 0))) { + app.getTerrainEditor().applyNoise(noiseFreq, noiseAmp, noiseOctaves, + static_cast(noiseSeed)); + } + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), + "Adds procedural hills/valleys to entire tile"); + } + ImGui::Separator(); ImGui::Text("Terrain Holes (cave entrances):"); auto& brush = app.getTerrainEditor().brush(); diff --git a/tools/editor/editor_viewport.cpp b/tools/editor/editor_viewport.cpp index 84e71b5b..468af930 100644 --- a/tools/editor/editor_viewport.cpp +++ b/tools/editor/editor_viewport.cpp @@ -568,7 +568,7 @@ void EditorViewport::updatePerFrameUBO() { data.view = camera_->getViewMatrix(); data.projection = camera_->getProjectionMatrix(); data.lightSpaceMatrix = glm::mat4(1.0f); - data.lightDir = glm::vec4(glm::normalize(glm::vec3(0.5f, -1.0f, 0.3f)), 0.0f); + data.lightDir = glm::vec4(lightDir_, 0.0f); data.lightColor = glm::vec4(1.0f, 0.95f, 0.85f, 0.0f); data.ambientColor = glm::vec4(0.3f, 0.3f, 0.35f, 0.0f); data.viewPos = glm::vec4(camera_->getPosition(), 0.0f); diff --git a/tools/editor/editor_viewport.hpp b/tools/editor/editor_viewport.hpp index 7cdebd0a..b1e1546f 100644 --- a/tools/editor/editor_viewport.hpp +++ b/tools/editor/editor_viewport.hpp @@ -55,6 +55,11 @@ public: void setWireframe(bool enabled); bool isWireframe() const { return wireframe_; } + void setClearColor(float r, float g, float b) { clearR_=r; clearG_=g; clearB_=b; } + void getClearColor(float& r, float& g, float& b) const { r=clearR_; g=clearG_; b=clearB_; } + void setLightDir(const glm::vec3& d) { lightDir_ = d; } + glm::vec3 getLightDir() const { return lightDir_; } + rendering::TerrainRenderer* getTerrainRenderer() { return terrainRenderer_.get(); } private: @@ -85,6 +90,8 @@ private: VkSampler shadowSampler_ = VK_NULL_HANDLE; bool wireframe_ = false; + float clearR_ = 0.15f, clearG_ = 0.15f, clearB_ = 0.2f; + glm::vec3 lightDir_ = glm::normalize(glm::vec3(0.5f, -1.0f, 0.3f)); // Ghost preview state std::string ghostModelPath_; diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index 30f587b5..4e048b7d 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -586,6 +586,57 @@ void TerrainEditor::removeWater(const glm::vec3& center, float radius) { } } +void TerrainEditor::applyNoise(float frequency, float amplitude, int octaves, uint32_t seed) { + if (!terrain_) return; + + // Simple value noise with octaves + auto hash2d = [](int x, int y, uint32_t s) -> float { + uint32_t h = static_cast(x * 374761393 + y * 668265263 + s * 1274126177); + h = (h ^ (h >> 13)) * 1274126177; + h = h ^ (h >> 16); + return static_cast(h & 0xFFFF) / 65535.0f * 2.0f - 1.0f; + }; + + auto smoothNoise = [&](float fx, float fy, uint32_t s) -> float { + int ix = static_cast(std::floor(fx)); + int iy = static_cast(std::floor(fy)); + float fracX = fx - ix; + float fracY = fy - iy; + // Smoothstep + fracX = fracX * fracX * (3.0f - 2.0f * fracX); + fracY = fracY * fracY * (3.0f - 2.0f * fracY); + float v00 = hash2d(ix, iy, s); + float v10 = hash2d(ix + 1, iy, s); + float v01 = hash2d(ix, iy + 1, s); + float v11 = hash2d(ix + 1, iy + 1, s); + float i0 = v00 + (v10 - v00) * fracX; + float i1 = v01 + (v11 - v01) * fracX; + return i0 + (i1 - i0) * fracY; + }; + + 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++) { + glm::vec3 wpos = chunkVertexWorldPos(ci, v); + + float total = 0.0f; + float amp = amplitude; + float freq = frequency; + for (int o = 0; o < octaves; o++) { + total += smoothNoise(wpos.x * freq, wpos.y * freq, seed + o * 97) * amp; + freq *= 2.0f; + amp *= 0.5f; + } + chunk.heightMap.heights[v] += total; + } + dirtyChunks_.push_back(ci); + } + dirty_ = true; +} + void TerrainEditor::punchHole(const glm::vec3& center, float radius) { if (!terrain_) return; auto affected = getAffectedChunks(center, radius); diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 959cd33e..ad381449 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -51,6 +51,9 @@ public: // Recalculate normals for modified chunks (improves lighting after sculpt) void recalcNormals(const std::vector& chunkIndices); + // Noise generator: applies procedural height noise to the terrain + void applyNoise(float frequency, float amplitude, int octaves, uint32_t seed); + // Water editing void setWaterLevel(const glm::vec3& center, float radius, float waterHeight, uint16_t liquidType = 0); void removeWater(const glm::vec3& center, float radius);