mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Mirror editing: a symmetric shape stays so (a point moved, its twin across X moved the mirrored way; points on the middle stay on it); in Tool options and the command search
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c742c937c6
commit
445628b0d8
1 changed files with 39 additions and 1 deletions
40
src/app.cpp
40
src/app.cpp
|
|
@ -563,6 +563,7 @@ public:
|
|||
Object* o = self->editing();
|
||||
if (!o) return;
|
||||
self->pushUndo();
|
||||
const Mesh before = o->mesh;
|
||||
self->drag_.soft.clear();
|
||||
if (self->softSel_) self->findSoft();
|
||||
const glm::mat4 m = objectMatrix(*o);
|
||||
|
|
@ -570,6 +571,7 @@ public:
|
|||
for (int v : self->selectedPoints()) o->mesh.verts[size_t(v)] = glm::vec3(local * glm::vec4(o->mesh.verts[size_t(v)], 1.0f));
|
||||
for (const auto& [v, w] : self->drag_.soft) o->mesh.verts[size_t(v)] = glm::mix(o->mesh.verts[size_t(v)], glm::vec3(local * glm::vec4(o->mesh.verts[size_t(v)], 1.0f)), w);
|
||||
self->drag_.soft.clear();
|
||||
if (self->liveMirror_) self->mirrorEdits(*o, before);
|
||||
self->changedShape(*o, true);
|
||||
} else if (a[1] == "soft") self->softSel_ = a.size() < 3 || a[2] != "0", self->softReach_ = a.size() > 3 ? std::stof(a[3]) : self->softReach_;
|
||||
}, "Fry3DX: shape editing: fry_shapeop smooth | bridge | push metres | soft 1 [reach] | move x y z");
|
||||
|
|
@ -4744,6 +4746,39 @@ private:
|
|||
}
|
||||
|
||||
void updateDrag(const ImGuiIO& io) {
|
||||
dragNow(io);
|
||||
if (liveMirror_ && mode_ == Mode::Edit)
|
||||
if (Object* o = editing())
|
||||
for (const Object& b : drag_.before.objects)
|
||||
if (b.id == o->id) mirrorEdits(*o, b.mesh);
|
||||
}
|
||||
// Mirror editing: each point that moved, its twin across the shape's own X (the point that was
|
||||
// where it is, mirrored) moved the mirrored way. Points on the middle stay on it.
|
||||
void mirrorEdits(Object& o, const Mesh& before) {
|
||||
if (before.verts.size() != o.mesh.verts.size() || before.verts.empty()) return; // (points made or gone: nothing to match)
|
||||
const float tol = std::max(glm::length(boundsMax(before) - boundsMin(before)) * 1e-5f, 1e-7f);
|
||||
auto key = [&](const glm::vec3& p) {
|
||||
const glm::ivec3 c(glm::round(p / tol));
|
||||
return (uint64_t(uint32_t(c.x) & 0x1FFFFF) << 42) | (uint64_t(uint32_t(c.y) & 0x1FFFFF) << 21) | uint64_t(uint32_t(c.z) & 0x1FFFFF);
|
||||
};
|
||||
std::unordered_map<uint64_t, int> at;
|
||||
for (size_t i = 0; i < before.verts.size(); ++i) at.emplace(key(before.verts[i]), int(i));
|
||||
std::vector<char> moved(before.verts.size());
|
||||
for (size_t i = 0; i < moved.size(); ++i) moved[i] = glm::distance(before.verts[i], o.mesh.verts[i]) > tol * 0.01f;
|
||||
for (size_t i = 0; i < moved.size(); ++i) {
|
||||
if (!moved[i]) continue;
|
||||
const glm::vec3 b = before.verts[i];
|
||||
if (std::abs(b.x) <= tol) { // (on the middle)
|
||||
o.mesh.verts[i].x = 0;
|
||||
continue;
|
||||
}
|
||||
const auto it = at.find(key(glm::vec3(-b.x, b.y, b.z)));
|
||||
if (it == at.end() || moved[size_t(it->second)]) continue; // (no twin, or it's moved by hand too)
|
||||
const glm::vec3 n = o.mesh.verts[i];
|
||||
o.mesh.verts[size_t(it->second)] = glm::vec3(-n.x, n.y, n.z);
|
||||
}
|
||||
}
|
||||
void dragNow(const ImGuiIO& io) {
|
||||
const Ray ray = mouseRay(io.MousePos);
|
||||
const bool snapping = snap_ != io.KeyCtrl; // Ctrl flips snapping while held
|
||||
scene_ = drag_.before; // every frame from where it started: no drift
|
||||
|
|
@ -5932,7 +5967,7 @@ private:
|
|||
add("shape.soft", "Soft selection on / off", "Shape", "Shift+O", [this] { softSel_ = !softSel_; }, {}, "proportional falloff");
|
||||
add("shape.separate", "Make into its own object", "Shape", "P", [this] { separateSelected(); }, editing, "separate split");
|
||||
add("shape.slide", "Slide the points along their edges...", "Shape", "Alt+G", [this] { startSlide(); }, editing, "edge slide vertex slide");
|
||||
add("shape.mirror", "Mirror editing (both sides change together)", "Shape", "", [this] { liveMirror_ = !liveMirror_; }, {}, "symmetry symmetric x");
|
||||
add("shape.mirror", "Mirror editing (both sides change together)", "Shape", "", [this] { liveMirror_ = !liveMirror_, say(liveMirror_ ? "Mirror editing on: both sides change together" : "Mirror editing off"); }, {}, "symmetry symmetric x");
|
||||
add("view.solid", "Show: solid", "View", "Z", [this] { shading_ = Shading::Solid; });
|
||||
add("view.rendered", "Show: rendered (sky, sun, shadows)", "View", "Z", [this] { shading_ = Shading::Rendered; });
|
||||
add("view.wire", "Show: wireframe", "View", "Shift+Z", [this] { shading_ = Shading::Wire; });
|
||||
|
|
@ -6498,6 +6533,8 @@ private:
|
|||
void softChoice(float w) {
|
||||
if (mode_ != Mode::Edit) return;
|
||||
ImGui::Checkbox("Soft selection (Shift+O)", &softSel_);
|
||||
ImGui::Checkbox("Mirror editing (across X)", &liveMirror_);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("A symmetric shape stays so: move a point, its twin on the other side moves too");
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Points near the selection follow it, less the further they are");
|
||||
if (!softSel_) return;
|
||||
ImGui::SetNextItemWidth(w);
|
||||
|
|
@ -7928,6 +7965,7 @@ private:
|
|||
if (to >= 0) o->mesh.verts[size_t(slide_.pts[i])] = glm::mix(p, slide_.from.verts[size_t(to)], t), shown = std::max(shown, t);
|
||||
}
|
||||
}
|
||||
if (liveMirror_) mirrorEdits(*o, slide_.from);
|
||||
touched(o->id);
|
||||
message_ = std::format("Sliding {:.0f}% of the way along (click: keep, Esc: as it was)", shown * 100), messageAt_ = now();
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue