mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat(editor): path preview line, transform undo, complete undo coverage
- River/road tool now shows translucent blue path preview ribbon with edge lines between start and end points before applying - Preview follows cursor when waiting for end point, locks when set - Undo support for all remaining operations: rotateTerrain90, mirrorX, mirrorY, scaleHeights, offsetHeights, invertHeights, smoothBeaches - Every terrain-modifying operation in the editor is now undoable
This commit is contained in:
parent
7e02db73df
commit
acfbf19144
5 changed files with 106 additions and 0 deletions
|
|
@ -537,6 +537,17 @@ void EditorApp::updateTerrainEditing(float dt) {
|
|||
viewport_.setBrushIndicator({}, 0, false);
|
||||
viewport_.clearGhostPreview();
|
||||
}
|
||||
|
||||
// Path preview for river/road tool
|
||||
if (ui_.getPathCapture() == EditorUI::PathCapture::WaitingEnd ||
|
||||
ui_.isPathReady()) {
|
||||
glm::vec3 endPt = ui_.isPathReady() ? ui_.getPathEnd()
|
||||
: terrainEditor_.brush().getPosition();
|
||||
viewport_.setPathPreview(ui_.getPathStart(), endPt,
|
||||
ui_.getPathWidth(), true);
|
||||
} else {
|
||||
viewport_.setPathPreview({}, {}, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (painting_ && terrainEditor_.brush().isActive()) {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public:
|
|||
glm::vec3 getPathStart() const { return pathStart_; }
|
||||
glm::vec3 getPathEnd() const { return pathEnd_; }
|
||||
bool isPathReady() const { return pathStartSet_ && pathEndSet_; }
|
||||
float getPathWidth() const { return pathWidth_; }
|
||||
void clearPath() { pathStartSet_ = false; pathEndSet_ = false; pathCapture_ = PathCapture::None; }
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ void EditorViewport::shutdown() {
|
|||
|
||||
if (npcMarkerVB_) { vmaDestroyBuffer(vkCtx_->getAllocator(), npcMarkerVB_, npcMarkerVBAlloc_); npcMarkerVB_ = VK_NULL_HANDLE; }
|
||||
if (brushVB_) { vmaDestroyBuffer(vkCtx_->getAllocator(), brushVB_, brushVBAlloc_); brushVB_ = VK_NULL_HANDLE; }
|
||||
if (pathVB_) { vmaDestroyBuffer(vkCtx_->getAllocator(), pathVB_, pathVBAlloc_); pathVB_ = VK_NULL_HANDLE; }
|
||||
gizmo_.shutdown();
|
||||
waterRenderer_.shutdown();
|
||||
|
||||
|
|
@ -332,6 +333,64 @@ void EditorViewport::setBrushIndicator(const glm::vec3& center, float radius, bo
|
|||
}
|
||||
}
|
||||
|
||||
void EditorViewport::setPathPreview(const glm::vec3& start, const glm::vec3& end,
|
||||
float width, bool visible) {
|
||||
pathVisible_ = visible;
|
||||
if (pathVB_) {
|
||||
vmaDestroyBuffer(vkCtx_->getAllocator(), pathVB_, pathVBAlloc_);
|
||||
pathVB_ = VK_NULL_HANDLE;
|
||||
pathVertCount_ = 0;
|
||||
}
|
||||
if (!visible) return;
|
||||
|
||||
struct BV { float pos[3]; float color[4]; };
|
||||
std::vector<BV> verts;
|
||||
|
||||
glm::vec2 dir = glm::normalize(glm::vec2(end.x - start.x, end.y - start.y));
|
||||
glm::vec2 perp(-dir.y, dir.x);
|
||||
float z0 = start.z + 2.0f;
|
||||
float z1 = end.z + 2.0f;
|
||||
float hw = width * 0.5f;
|
||||
|
||||
// Path ribbon (semi-transparent)
|
||||
BV v;
|
||||
v.color[0] = 0.3f; v.color[1] = 0.6f; v.color[2] = 1.0f; v.color[3] = 0.35f;
|
||||
v.pos[0] = start.x - perp.x*hw; v.pos[1] = start.y - perp.y*hw; v.pos[2] = z0; verts.push_back(v);
|
||||
v.pos[0] = start.x + perp.x*hw; v.pos[1] = start.y + perp.y*hw; v.pos[2] = z0; verts.push_back(v);
|
||||
v.pos[0] = end.x - perp.x*hw; v.pos[1] = end.y - perp.y*hw; v.pos[2] = z1; verts.push_back(v);
|
||||
v.pos[0] = end.x - perp.x*hw; v.pos[1] = end.y - perp.y*hw; v.pos[2] = z1; verts.push_back(v);
|
||||
v.pos[0] = start.x + perp.x*hw; v.pos[1] = start.y + perp.y*hw; v.pos[2] = z0; verts.push_back(v);
|
||||
v.pos[0] = end.x + perp.x*hw; v.pos[1] = end.y + perp.y*hw; v.pos[2] = z1; verts.push_back(v);
|
||||
|
||||
// Edge lines (brighter)
|
||||
float lw = 0.8f;
|
||||
v.color[0] = 0.4f; v.color[1] = 0.8f; v.color[2] = 1.0f; v.color[3] = 0.8f;
|
||||
for (int side = -1; side <= 1; side += 2) {
|
||||
float s = static_cast<float>(side);
|
||||
glm::vec2 offset = perp * hw * s;
|
||||
glm::vec2 linePerp = perp * lw * s;
|
||||
v.pos[0] = start.x + offset.x - linePerp.x; v.pos[1] = start.y + offset.y - linePerp.y; v.pos[2] = z0; verts.push_back(v);
|
||||
v.pos[0] = start.x + offset.x + linePerp.x; v.pos[1] = start.y + offset.y + linePerp.y; v.pos[2] = z0; verts.push_back(v);
|
||||
v.pos[0] = end.x + offset.x - linePerp.x; v.pos[1] = end.y + offset.y - linePerp.y; v.pos[2] = z1; verts.push_back(v);
|
||||
v.pos[0] = end.x + offset.x - linePerp.x; v.pos[1] = end.y + offset.y - linePerp.y; v.pos[2] = z1; verts.push_back(v);
|
||||
v.pos[0] = start.x + offset.x + linePerp.x; v.pos[1] = start.y + offset.y + linePerp.y; v.pos[2] = z0; verts.push_back(v);
|
||||
v.pos[0] = end.x + offset.x + linePerp.x; v.pos[1] = end.y + offset.y + linePerp.y; v.pos[2] = z1; verts.push_back(v);
|
||||
}
|
||||
|
||||
pathVertCount_ = static_cast<uint32_t>(verts.size());
|
||||
VkBufferCreateInfo bufInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
bufInfo.size = verts.size() * sizeof(BV);
|
||||
bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
||||
VmaAllocationCreateInfo allocInfo{};
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
VmaAllocationInfo mapInfo{};
|
||||
if (vmaCreateBuffer(vkCtx_->getAllocator(), &bufInfo, &allocInfo,
|
||||
&pathVB_, &pathVBAlloc_, &mapInfo) == VK_SUCCESS) {
|
||||
std::memcpy(mapInfo.pMappedData, verts.data(), verts.size() * sizeof(BV));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorViewport::updateNpcMarkers(const std::vector<CreatureSpawn>& npcs) {
|
||||
if (npcMarkerVB_) {
|
||||
vmaDestroyBuffer(vkCtx_->getAllocator(), npcMarkerVB_, npcMarkerVBAlloc_);
|
||||
|
|
@ -491,6 +550,20 @@ void EditorViewport::render(VkCommandBuffer cmd) {
|
|||
}
|
||||
}
|
||||
|
||||
// Path preview line (river/road tool)
|
||||
if (pathVisible_ && pathVB_ && pathVertCount_ > 0) {
|
||||
auto* waterPipeline = waterRenderer_.getPipeline();
|
||||
auto* waterLayout = waterRenderer_.getPipelineLayout();
|
||||
if (waterPipeline && waterLayout) {
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, waterPipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, waterLayout,
|
||||
0, 1, &perFrameSet, 0, nullptr);
|
||||
VkDeviceSize off = 0;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &pathVB_, &off);
|
||||
vkCmdDraw(cmd, pathVertCount_, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
gizmo_.render(cmd, perFrameSet);
|
||||
|
||||
// NPC markers rendered last with no depth test (always on top via gizmo pipeline)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public:
|
|||
|
||||
TransformGizmo& getGizmo() { return gizmo_; }
|
||||
void setBrushIndicator(const glm::vec3& center, float radius, bool active);
|
||||
void setPathPreview(const glm::vec3& start, const glm::vec3& end, float width, bool visible);
|
||||
|
||||
void setWireframe(bool enabled);
|
||||
bool isWireframe() const { return wireframe_; }
|
||||
|
|
@ -111,6 +112,12 @@ private:
|
|||
VkBuffer npcMarkerVB_ = VK_NULL_HANDLE;
|
||||
VmaAllocation npcMarkerVBAlloc_ = VK_NULL_HANDLE;
|
||||
uint32_t npcMarkerVertCount_ = 0;
|
||||
|
||||
// Path preview line
|
||||
VkBuffer pathVB_ = VK_NULL_HANDLE;
|
||||
VmaAllocation pathVBAlloc_ = VK_NULL_HANDLE;
|
||||
uint32_t pathVertCount_ = 0;
|
||||
bool pathVisible_ = false;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
|
|
|||
|
|
@ -680,6 +680,7 @@ void TerrainEditor::resetToFlat() {
|
|||
|
||||
void TerrainEditor::scaleHeights(float factor) {
|
||||
if (!terrain_) return;
|
||||
recordGeneratorUndo();
|
||||
for (int ci = 0; ci < 256; ci++) {
|
||||
auto& chunk = terrain_->chunks[ci];
|
||||
if (!chunk.hasHeightMap()) continue;
|
||||
|
|
@ -690,10 +691,12 @@ void TerrainEditor::scaleHeights(float factor) {
|
|||
// Re-stitch all edges after scaling
|
||||
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
|
||||
dirty_ = true;
|
||||
commitGeneratorUndo();
|
||||
}
|
||||
|
||||
void TerrainEditor::mirrorX() {
|
||||
if (!terrain_) return;
|
||||
recordGeneratorUndo();
|
||||
for (int cy = 0; cy < 16; cy++) {
|
||||
for (int cx = 0; cx < 8; cx++) {
|
||||
int srcIdx = cy * 16 + cx;
|
||||
|
|
@ -713,10 +716,12 @@ void TerrainEditor::mirrorX() {
|
|||
}
|
||||
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
|
||||
dirty_ = true;
|
||||
commitGeneratorUndo();
|
||||
}
|
||||
|
||||
void TerrainEditor::mirrorY() {
|
||||
if (!terrain_) return;
|
||||
recordGeneratorUndo();
|
||||
for (int cy = 0; cy < 8; cy++) {
|
||||
for (int cx = 0; cx < 16; cx++) {
|
||||
int srcIdx = cy * 16 + cx;
|
||||
|
|
@ -736,6 +741,7 @@ void TerrainEditor::mirrorY() {
|
|||
}
|
||||
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
|
||||
dirty_ = true;
|
||||
commitGeneratorUndo();
|
||||
}
|
||||
|
||||
void TerrainEditor::carveRiver(const glm::vec3& start, const glm::vec3& end,
|
||||
|
|
@ -957,6 +963,7 @@ void TerrainEditor::createDunes(float wavelength, float amplitude, float directi
|
|||
|
||||
void TerrainEditor::rotateTerrain90() {
|
||||
if (!terrain_) return;
|
||||
recordGeneratorUndo();
|
||||
// Snapshot all outer vertex heights into a 129x129 grid
|
||||
std::array<std::array<float, 129>, 129> grid{};
|
||||
for (int cy = 0; cy < 16; cy++) {
|
||||
|
|
@ -998,10 +1005,12 @@ void TerrainEditor::rotateTerrain90() {
|
|||
}
|
||||
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
|
||||
dirty_ = true;
|
||||
commitGeneratorUndo();
|
||||
}
|
||||
|
||||
void TerrainEditor::offsetHeights(float amount) {
|
||||
if (!terrain_) return;
|
||||
recordGeneratorUndo();
|
||||
for (int ci = 0; ci < 256; ci++) {
|
||||
auto& chunk = terrain_->chunks[ci];
|
||||
if (!chunk.hasHeightMap()) continue;
|
||||
|
|
@ -1010,10 +1019,12 @@ void TerrainEditor::offsetHeights(float amount) {
|
|||
dirtyChunks_.push_back(ci);
|
||||
}
|
||||
dirty_ = true;
|
||||
commitGeneratorUndo();
|
||||
}
|
||||
|
||||
void TerrainEditor::invertHeights() {
|
||||
if (!terrain_) return;
|
||||
recordGeneratorUndo();
|
||||
// Find midpoint
|
||||
float minH = 1e30f, maxH = -1e30f;
|
||||
for (int ci = 0; ci < 256; ci++) {
|
||||
|
|
@ -1034,6 +1045,7 @@ void TerrainEditor::invertHeights() {
|
|||
}
|
||||
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
|
||||
dirty_ = true;
|
||||
commitGeneratorUndo();
|
||||
}
|
||||
|
||||
void TerrainEditor::fillWater(float height, uint16_t liquidType) {
|
||||
|
|
@ -1064,6 +1076,7 @@ void TerrainEditor::fillWater(float height, uint16_t liquidType) {
|
|||
|
||||
void TerrainEditor::smoothBeaches(float waterHeight, float beachWidth) {
|
||||
if (!terrain_) return;
|
||||
recordGeneratorUndo();
|
||||
for (int ci = 0; ci < 256; ci++) {
|
||||
auto& chunk = terrain_->chunks[ci];
|
||||
if (!chunk.hasHeightMap()) continue;
|
||||
|
|
@ -1083,6 +1096,7 @@ void TerrainEditor::smoothBeaches(float waterHeight, float beachWidth) {
|
|||
if (modified) { stitchEdges(ci); dirtyChunks_.push_back(ci); }
|
||||
}
|
||||
dirty_ = true;
|
||||
commitGeneratorUndo();
|
||||
}
|
||||
|
||||
void TerrainEditor::addDetailNoise(float amplitude, float frequency, uint32_t seed) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue