载入中...
搜索中...
未找到
TransformGizmo.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4
5#include <glm/gtc/matrix_transform.hpp>
6#include <glm/gtc/type_ptr.hpp>
7
8#include <algorithm>
9#include <cmath>
10#include <cstring>
11
12namespace eve::editor {
13namespace {
14
15constexpr float kPi = 3.14159265358979323846f;
16constexpr float kAxisHitRadius = 0.12f;
17constexpr float kPlaneHitSize = 0.42f;
18constexpr float kRingThickness = 0.08f;
19
20float snapValue(float v, float step) {
21 if (step <= 0.f) return v;
22 return std::round(v / step) * step;
23}
24
25glm::vec3 safeNormalize(const glm::vec3 &v, const glm::vec3 &fallback) {
26 float len = glm::length(v);
27 if (len < 1e-6f) return fallback;
28 return v / len;
29}
30
31} // namespace
32
34
35void TransformGizmo::setMode(const std::string &mode) {
36 if (mode != "translate" && mode != "rotate" && mode != "scale" && mode != "bound") {
37 throw Exception("TransformGizmo::setMode: expected translate|rotate|scale|bound");
38 }
39 mode_ = mode;
41}
42
43void TransformGizmo::setSpace(const std::string &space) {
44 if (space != "local" && space != "world") {
45 throw Exception("TransformGizmo::setSpace: expected local|world");
46 }
47 space_ = space;
49}
50
52 if (size <= 0.f) throw Exception("TransformGizmo::setSize: size must be > 0");
53 size_ = size;
55}
56
57void TransformGizmo::setPosition(float x, float y, float z) {
58 position_ = {x, y, z};
60}
61
62void TransformGizmo::setRotationEuler(float x, float y, float z) {
63 rotation_ = {x, y, z};
65}
66
67void TransformGizmo::setScale(float x, float y, float z) {
68 scale_ = {x, y, z};
70}
71
72void TransformGizmo::setBounds(float minX, float minY, float minZ, float maxX, float maxY,
73 float maxZ) {
74 boundsMin_ = {std::min(minX, maxX), std::min(minY, maxY), std::min(minZ, maxZ)};
75 boundsMax_ = {std::max(minX, maxX), std::max(minY, maxY), std::max(minZ, maxZ)};
77}
78
79void TransformGizmo::setSnapTranslate(float x, float y, float z) { snapTranslate_ = {x, y, z}; }
80
81void TransformGizmo::setSnapRotate(float degrees) { snapRotateDeg_ = degrees; }
82
83void TransformGizmo::setSnapScale(float s) { snapScale_ = s; }
84
86 if (index < 0 || index > 15) throw Exception("TransformGizmo::getMatrix: index 0..15");
87 glm::mat4 m = worldMatrix();
88 const float *p = glm::value_ptr(m);
89 return p[index];
90}
91
92glm::mat4 TransformGizmo::localRotationMatrix() const {
93 glm::mat4 m(1.f);
94 m = glm::rotate(m, rotation_.z, glm::vec3(0.f, 0.f, 1.f));
95 m = glm::rotate(m, rotation_.y, glm::vec3(0.f, 1.f, 0.f));
96 m = glm::rotate(m, rotation_.x, glm::vec3(1.f, 0.f, 0.f));
97 return m;
98}
99
100glm::mat4 TransformGizmo::worldMatrix() const {
101 glm::mat4 m(1.f);
102 m = glm::translate(m, position_);
103 m *= localRotationMatrix();
104 m = glm::scale(m, scale_);
105 return m;
106}
107
108glm::vec3 TransformGizmo::axisWorld(int axis) const {
109 glm::vec3 local(0.f);
110 local[axis] = 1.f;
111 if (space_ == "world" && mode_ != "bound") return local;
112 glm::mat3 R(localRotationMatrix());
113 return safeNormalize(R * local, local);
114}
115
116void TransformGizmo::colorForAxis(const std::string &axis, glm::vec4 &out) const {
117 bool active = (axis == activeAxis_ || axis == hoverAxis_);
118 float boost = active ? 1.f : 0.75f;
119 if (axis == "x")
120 out = {1.f * boost, 0.2f, 0.2f, 1.f};
121 else if (axis == "y")
122 out = {0.2f, 1.f * boost, 0.2f, 1.f};
123 else if (axis == "z")
124 out = {0.25f, 0.45f, 1.f * boost, 1.f};
125 else if (axis == "xy")
126 out = {1.f * boost, 1.f * boost, 0.2f, 0.85f};
127 else if (axis == "yz")
128 out = {0.2f, 1.f * boost, 1.f * boost, 0.85f};
129 else if (axis == "xz")
130 out = {1.f * boost, 0.2f, 1.f * boost, 0.85f};
131 else if (axis == "xyz")
132 out = {0.9f * boost, 0.9f * boost, 0.9f * boost, 1.f};
133 else
134 out = {0.8f * boost, 0.8f * boost, 0.2f, 1.f};
135}
136
138 parts_.clear();
139 const glm::vec3 origin = position_;
140
141 auto addAxis = [&](const char *axisName, int ai, const char *kind) {
142 Part p;
143 p.kind = kind;
144 p.axis = axisName;
145 p.origin = origin;
146 p.dir = axisWorld(ai);
147 p.length = size_;
148 p.radius = size_ * 0.04f;
149 colorForAxis(p.axis, p.color);
150 parts_.push_back(p);
151 };
152
153 if (mode_ == "translate" || mode_ == "scale") {
154 addAxis("x", 0, "axis");
155 addAxis("y", 1, "axis");
156 addAxis("z", 2, "axis");
157 // plane handles
158 for (const char *plane : {"xy", "yz", "xz"}) {
159 Part p;
160 p.kind = "plane";
161 p.axis = plane;
162 p.origin = origin;
163 if (std::strcmp(plane, "xy") == 0)
164 p.dir = glm::normalize(axisWorld(0) + axisWorld(1));
165 else if (std::strcmp(plane, "yz") == 0)
166 p.dir = glm::normalize(axisWorld(1) + axisWorld(2));
167 else
168 p.dir = glm::normalize(axisWorld(0) + axisWorld(2));
169 p.length = size_ * kPlaneHitSize;
170 p.radius = size_ * kPlaneHitSize;
171 colorForAxis(p.axis, p.color);
172 parts_.push_back(p);
173 }
174 Part c;
175 c.kind = "center";
176 c.axis = "xyz";
177 c.origin = origin;
178 c.dir = {0.f, 1.f, 0.f};
179 c.length = 0.f;
180 c.radius = size_ * (mode_ == "scale" ? 0.08f : 0.06f);
181 colorForAxis(c.axis, c.color);
182 parts_.push_back(c);
183 } else if (mode_ == "rotate") {
184 addAxis("x", 0, "ring");
185 addAxis("y", 1, "ring");
186 addAxis("z", 2, "ring");
187 for (auto &p : parts_) {
188 p.length = 0.f;
189 p.radius = size_;
190 }
191 } else { // bound
192 const char *names[] = {"bx", "by", "bz", "bnx", "bny", "bnz"};
193 glm::vec3 centers[6] = {
194 {boundsMax_.x, 0.f, 0.f}, {0.f, boundsMax_.y, 0.f}, {0.f, 0.f, boundsMax_.z},
195 {boundsMin_.x, 0.f, 0.f}, {0.f, boundsMin_.y, 0.f}, {0.f, 0.f, boundsMin_.z},
196 };
197 glm::mat3 R(localRotationMatrix());
198 for (int i = 0; i < 6; ++i) {
199 Part p;
200 p.kind = "handle";
201 p.axis = names[i];
202 glm::vec3 local = centers[i];
203 // scale local handle offset by object scale
204 local *= scale_;
205 p.origin = position_ + R * local;
206 p.dir = axisWorld(i % 3);
207 p.radius = size_ * 0.07f;
208 colorForAxis((i % 3 == 0) ? "x" : (i % 3 == 1) ? "y" : "z", p.color);
209 parts_.push_back(p);
210 }
211 Part box;
212 box.kind = "box";
213 box.axis = "bound";
214 box.origin = position_;
215 box.dir = {0.f, 1.f, 0.f};
216 box.length = glm::length(boundsMax_ - boundsMin_) * 0.5f;
217 box.radius = box.length;
218 colorForAxis("xyz", box.color);
219 parts_.push_back(box);
220 }
221}
222
223float TransformGizmo::hitAxis(const glm::vec3 &ro, const glm::vec3 &rd, int axisIndex,
224 float &outT) const {
225 glm::vec3 a = axisWorld(axisIndex);
226 glm::vec3 p0 = position_;
227 glm::vec3 p1 = position_ + a * size_;
228 // Closest approach ray-segment
229 glm::vec3 u = p1 - p0;
230 glm::vec3 v = rd;
231 glm::vec3 w = p0 - ro;
232 float aa = glm::dot(u, u);
233 float bb = glm::dot(v, v);
234 float cc = glm::dot(u, v);
235 float dd = glm::dot(u, w);
236 float ee = glm::dot(v, w);
237 float denom = aa * bb - cc * cc;
238 float sn = 0.f, tn = 0.f;
239 if (std::fabs(denom) > 1e-8f) {
240 sn = (cc * ee - bb * dd) / denom;
241 tn = (aa * ee - cc * dd) / denom;
242 }
243 sn = std::clamp(sn, 0.f, 1.f);
244 if (tn < 0.f) return -1.f;
245 glm::vec3 closestU = p0 + u * sn;
246 glm::vec3 closestV = ro + v * tn;
247 float dist = glm::length(closestU - closestV);
248 float thresh = size_ * kAxisHitRadius;
249 if (dist > thresh) return -1.f;
250 outT = tn;
251 return dist;
252}
253
254float TransformGizmo::hitPlane(const glm::vec3 &ro, const glm::vec3 &rd, int planeMask,
255 float &outT) const {
256 // planeMask: bit0=x, bit1=y, bit2=z included in plane (two bits set)
257 glm::vec3 n(0.f);
258 if ((planeMask & 1) == 0) n = axisWorld(0);
259 else if ((planeMask & 2) == 0) n = axisWorld(1);
260 else n = axisWorld(2);
261 float denom = glm::dot(n, rd);
262 if (std::fabs(denom) < 1e-6f) return -1.f;
263 float t = glm::dot(position_ - ro, n) / denom;
264 if (t < 0.f) return -1.f;
265 glm::vec3 hit = ro + rd * t;
266 glm::vec3 d = hit - position_;
267 // project onto the two axes
268 int a0 = (planeMask & 1) ? 0 : ((planeMask & 2) ? 1 : 2);
269 int a1 = (planeMask & 4) ? 2 : ((planeMask & 2) && a0 != 1 ? 1 : (a0 == 0 ? 1 : 0));
270 if (planeMask == 3) {
271 a0 = 0;
272 a1 = 1;
273 } else if (planeMask == 6) {
274 a0 = 1;
275 a1 = 2;
276 } else if (planeMask == 5) {
277 a0 = 0;
278 a1 = 2;
279 }
280 float u = glm::dot(d, axisWorld(a0));
281 float v = glm::dot(d, axisWorld(a1));
282 float lim = size_ * kPlaneHitSize;
283 float lo = size_ * 0.18f;
284 if (u < lo || v < lo || u > lim || v > lim) return -1.f;
285 outT = t;
286 return 0.f;
287}
288
289float TransformGizmo::hitRing(const glm::vec3 &ro, const glm::vec3 &rd, int axisIndex,
290 float &outT) const {
291 glm::vec3 n = axisWorld(axisIndex);
292 float denom = glm::dot(n, rd);
293 if (std::fabs(denom) < 1e-6f) return -1.f;
294 float t = glm::dot(position_ - ro, n) / denom;
295 if (t < 0.f) return -1.f;
296 glm::vec3 hit = ro + rd * t;
297 float dist = glm::length(hit - position_);
298 float target = size_;
299 float thick = size_ * kRingThickness;
300 if (std::fabs(dist - target) > thick) return -1.f;
301 outT = t;
302 return std::fabs(dist - target);
303}
304
305float TransformGizmo::hitBoundHandle(const glm::vec3 &ro, const glm::vec3 &rd, int handle,
306 float &outT) const {
307 if (handle < 0 || handle >= static_cast<int>(parts_.size())) return -1.f;
308 const Part &p = parts_[handle];
309 if (p.kind != "handle") return -1.f;
310 // sphere hit
311 glm::vec3 oc = ro - p.origin;
312 float b = glm::dot(oc, rd);
313 float c = glm::dot(oc, oc) - p.radius * p.radius;
314 float disc = b * b - c;
315 if (disc < 0.f) return -1.f;
316 float t = -b - std::sqrt(disc);
317 if (t < 0.f) t = -b + std::sqrt(disc);
318 if (t < 0.f) return -1.f;
319 outT = t;
320 return 0.f;
321}
322
323std::string TransformGizmo::pick(float ox, float oy, float oz, float dx, float dy, float dz) {
324 glm::vec3 ro(ox, oy, oz);
325 glm::vec3 rd = safeNormalize(glm::vec3(dx, dy, dz), glm::vec3(0.f, 0.f, -1.f));
326 hoverAxis_.clear();
327 float bestT = 1e30f;
328 std::string best;
329
330 auto consider = [&](const std::string &axis, float score, float t) {
331 if (score < 0.f) return;
332 if (t < bestT) {
333 bestT = t;
334 best = axis;
335 }
336 };
337
338 if (mode_ == "translate") {
339 float t;
340 for (int i = 0; i < 3; ++i) {
341 float s = hitAxis(ro, rd, i, t);
342 consider(i == 0 ? "x" : i == 1 ? "y" : "z", s, t);
343 }
344 float tp;
345 if (hitPlane(ro, rd, 3, tp) >= 0.f) consider("xy", 0.f, tp);
346 if (hitPlane(ro, rd, 6, tp) >= 0.f) consider("yz", 0.f, tp);
347 if (hitPlane(ro, rd, 5, tp) >= 0.f) consider("xz", 0.f, tp);
348 // center sphere
349 glm::vec3 oc = ro - position_;
350 float b = glm::dot(oc, rd);
351 float c = glm::dot(oc, oc) - (size_ * 0.06f) * (size_ * 0.06f);
352 float disc = b * b - c;
353 if (disc >= 0.f) {
354 float tHit = -b - std::sqrt(disc);
355 if (tHit >= 0.f) consider("xyz", 0.f, tHit);
356 }
357 } else if (mode_ == "rotate") {
358 float t;
359 for (int i = 0; i < 3; ++i) {
360 float s = hitRing(ro, rd, i, t);
361 consider(i == 0 ? "x" : i == 1 ? "y" : "z", s, t);
362 }
363 } else if (mode_ == "scale") {
364 float t;
365 for (int i = 0; i < 3; ++i) {
366 float s = hitAxis(ro, rd, i, t);
367 consider(i == 0 ? "x" : i == 1 ? "y" : "z", s, t);
368 }
369 glm::vec3 oc = ro - position_;
370 float tp;
371 if (hitPlane(ro, rd, 3, tp) >= 0.f) consider("xy", 0.f, tp);
372 if (hitPlane(ro, rd, 6, tp) >= 0.f) consider("yz", 0.f, tp);
373 if (hitPlane(ro, rd, 5, tp) >= 0.f) consider("xz", 0.f, tp);
374 float rad = size_ * 0.08f;
375 float b = glm::dot(oc, rd);
376 float c = glm::dot(oc, oc) - rad * rad;
377 float disc = b * b - c;
378 if (disc >= 0.f) {
379 float tHit = -b - std::sqrt(disc);
380 if (tHit >= 0.f) consider("xyz", 0.f, tHit);
381 }
382 } else {
383 for (int i = 0; i < static_cast<int>(parts_.size()); ++i) {
384 float t;
385 float s = hitBoundHandle(ro, rd, i, t);
386 if (s >= 0.f) consider(parts_[i].axis, s, t);
387 }
388 }
389
390 hoverAxis_ = best;
391 rebuildParts();
392 return best;
393}
394
395glm::vec3 TransformGizmo::projectToDragPlane(const glm::vec3 &ro, const glm::vec3 &rd) const {
396 float denom = glm::dot(dragPlaneNormal_, rd);
397 if (std::fabs(denom) < 1e-8f) {
398 // fallback: project along axis using perpendicular plane from camera-ish guess
399 return dragStartHit_;
400 }
401 float t = glm::dot(dragStartHit_ - ro, dragPlaneNormal_) / denom;
402 return ro + rd * t;
403}
404
405void TransformGizmo::applySnapTranslate(glm::vec3 &v) const {
406 if (snapTranslate_.x > 0.f) v.x = snapValue(v.x, snapTranslate_.x);
407 if (snapTranslate_.y > 0.f) v.y = snapValue(v.y, snapTranslate_.y);
408 if (snapTranslate_.z > 0.f) v.z = snapValue(v.z, snapTranslate_.z);
409}
410
411float TransformGizmo::applySnapRotate(float radians) const {
412 if (snapRotateDeg_ <= 0.f) return radians;
413 float deg = radians * (180.f / kPi);
414 deg = snapValue(deg, snapRotateDeg_);
415 return deg * (kPi / 180.f);
416}
417
418float TransformGizmo::applySnapScale(float s) const {
419 if (snapScale_ <= 0.f) return s;
420 return snapValue(s, snapScale_);
421}
422
423bool TransformGizmo::beginDrag(const std::string &axis, float ox, float oy, float oz, float dx,
424 float dy, float dz) {
425 if (axis.empty()) return false;
426 activeAxis_ = axis;
427 hoverAxis_ = axis;
428 dragging_ = true;
429 dragStartPos_ = position_;
430 dragStartRot_ = rotation_;
431 dragStartScale_ = scale_;
432
433 glm::vec3 ro(ox, oy, oz);
434 glm::vec3 rd = safeNormalize(glm::vec3(dx, dy, dz), glm::vec3(0.f, 0.f, -1.f));
435
436 if (mode_ == "translate" || mode_ == "scale" || mode_ == "bound") {
437 if (axis == "x" || axis == "bx" || axis == "bnx")
438 dragAxisDir_ = axisWorld(0);
439 else if (axis == "y" || axis == "by" || axis == "bny")
440 dragAxisDir_ = axisWorld(1);
441 else if (axis == "z" || axis == "bz" || axis == "bnz")
442 dragAxisDir_ = axisWorld(2);
443 else if (axis == "xy")
444 dragPlaneNormal_ = axisWorld(2);
445 else if (axis == "yz")
446 dragPlaneNormal_ = axisWorld(0);
447 else if (axis == "xz")
448 dragPlaneNormal_ = axisWorld(1);
449 else
450 dragPlaneNormal_ = safeNormalize(glm::cross(rd, glm::vec3(0.f, 1.f, 0.f)),
451 glm::vec3(0.f, 0.f, 1.f));
452
453 if (axis == "x" || axis == "y" || axis == "z" || axis == "bx" || axis == "by" ||
454 axis == "bz" || axis == "bnx" || axis == "bny" || axis == "bnz") {
455 // plane facing camera containing axis
456 glm::vec3 side = glm::cross(dragAxisDir_, rd);
457 if (glm::length(side) < 1e-5f) side = glm::cross(dragAxisDir_, glm::vec3(0.f, 1.f, 0.f));
458 dragPlaneNormal_ = safeNormalize(glm::cross(side, dragAxisDir_), glm::vec3(0.f, 1.f, 0.f));
459 }
460 if (axis == "xyz") {
461 dragPlaneNormal_ = -rd;
462 // Screen-up projected into the camera-facing plane, independent of
463 // the previously dragged axis. Uniform scaling preserves proportions.
464 const glm::vec3 up(0.f, 1.f, 0.f);
465 dragAxisDir_ = safeNormalize(up - rd * glm::dot(up, rd), glm::vec3(1.f, 0.f, 0.f));
466 }
467 } else { // rotate
468 int ai = axis == "x" ? 0 : axis == "y" ? 1 : 2;
469 dragAxisDir_ = axisWorld(ai);
470 dragPlaneNormal_ = dragAxisDir_;
471 }
472
473 float denom = glm::dot(dragPlaneNormal_, rd);
474 if (std::fabs(denom) > 1e-8f) {
475 float t = glm::dot(position_ - ro, dragPlaneNormal_) / denom;
476 dragStartHit_ = ro + rd * t;
477 } else {
478 dragStartHit_ = position_;
479 }
480 rebuildParts();
481 return true;
482}
483
484bool TransformGizmo::updateDrag(float ox, float oy, float oz, float dx, float dy, float dz) {
485 if (!dragging_) return false;
486 glm::vec3 ro(ox, oy, oz);
487 glm::vec3 rd = safeNormalize(glm::vec3(dx, dy, dz), glm::vec3(0.f, 0.f, -1.f));
488 glm::vec3 hit = projectToDragPlane(ro, rd);
489 glm::vec3 delta = hit - dragStartHit_;
490
491 if (mode_ == "translate") {
492 glm::vec3 move(0.f);
493 if (activeAxis_ == "x" || activeAxis_ == "y" || activeAxis_ == "z") {
494 float dist = glm::dot(delta, dragAxisDir_);
495 move = dragAxisDir_ * dist;
496 } else if (activeAxis_ == "xy") {
497 move = axisWorld(0) * glm::dot(delta, axisWorld(0)) +
498 axisWorld(1) * glm::dot(delta, axisWorld(1));
499 } else if (activeAxis_ == "yz") {
500 move = axisWorld(1) * glm::dot(delta, axisWorld(1)) +
501 axisWorld(2) * glm::dot(delta, axisWorld(2));
502 } else if (activeAxis_ == "xz") {
503 move = axisWorld(0) * glm::dot(delta, axisWorld(0)) +
504 axisWorld(2) * glm::dot(delta, axisWorld(2));
505 } else if (activeAxis_ == "xyz") {
506 move = delta;
507 }
508 position_ = dragStartPos_ + move;
509 if (activeAxis_ == "xyz") {
510 applySnapTranslate(position_);
511 } else {
512 position_ = dragStartPos_;
513 for (int i = 0; i < 3; ++i) {
514 if (activeAxis_.find("xyz"[i]) == std::string::npos) continue;
515 const auto axis = axisWorld(i);
516 const float start = glm::dot(dragStartPos_, axis);
517 const float end = start + glm::dot(move, axis);
518 position_ += axis * (snapValue(end, snapTranslate_[i]) - start);
519 }
520 }
521 } else if (mode_ == "rotate") {
522 glm::vec3 from = safeNormalize(dragStartHit_ - position_, dragAxisDir_);
523 glm::vec3 to = safeNormalize(hit - position_, from);
524 // angle around dragAxisDir_
525 glm::vec3 c = glm::cross(from, to);
526 float sinA = glm::dot(c, dragAxisDir_);
527 float cosA = glm::clamp(glm::dot(from, to), -1.f, 1.f);
528 float ang = std::atan2(sinA, cosA);
529 ang = applySnapRotate(ang);
530 rotation_ = dragStartRot_;
531 if (activeAxis_ == "x")
532 rotation_.x = dragStartRot_.x + ang;
533 else if (activeAxis_ == "y")
534 rotation_.y = dragStartRot_.y + ang;
535 else
536 rotation_.z = dragStartRot_.z + ang;
537 } else if (mode_ == "scale") {
538 scale_ = dragStartScale_;
539 const float uniform =
540 std::max(0.01f, applySnapScale(1.f + glm::dot(delta, dragAxisDir_) / std::max(size_, 1e-3f)));
541 for (int i = 0; i < 3; ++i) {
542 if (activeAxis_.find("xyz"[i]) == std::string::npos) continue;
543 const float factor =
544 activeAxis_ == "xyz"
545 ? uniform
546 : std::max(0.01f, applySnapScale(1.f + glm::dot(delta, axisWorld(i)) / std::max(size_, 1e-3f)));
547 scale_[i] = dragStartScale_[i] * factor;
548 }
549 } else { // bound — move corresponding face by translating position / expanding bounds
550 float dist = glm::dot(delta, dragAxisDir_);
551 if (activeAxis_ == "bx")
552 boundsMax_.x = std::max(boundsMin_.x + 0.01f, boundsMax_.x + dist / std::max(scale_.x, 1e-3f));
553 else if (activeAxis_ == "by")
554 boundsMax_.y = std::max(boundsMin_.y + 0.01f, boundsMax_.y + dist / std::max(scale_.y, 1e-3f));
555 else if (activeAxis_ == "bz")
556 boundsMax_.z = std::max(boundsMin_.z + 0.01f, boundsMax_.z + dist / std::max(scale_.z, 1e-3f));
557 else if (activeAxis_ == "bnx")
558 boundsMin_.x = std::min(boundsMax_.x - 0.01f, boundsMin_.x + dist / std::max(scale_.x, 1e-3f));
559 else if (activeAxis_ == "bny")
560 boundsMin_.y = std::min(boundsMax_.y - 0.01f, boundsMin_.y + dist / std::max(scale_.y, 1e-3f));
561 else if (activeAxis_ == "bnz")
562 boundsMin_.z = std::min(boundsMax_.z - 0.01f, boundsMin_.z + dist / std::max(scale_.z, 1e-3f));
563 // reset start so incremental face edits accumulate smoothly
564 dragStartHit_ = hit;
565 }
566
567 rebuildParts();
568 return true;
569}
570
572 dragging_ = false;
573 activeAxis_.clear();
574 rebuildParts();
575}
576
577bool TransformGizmo::validPart(int index) const {
578 return index >= 0 && index < static_cast<int>(parts_.size());
579}
580
581std::string TransformGizmo::getPartKind(int index) const {
582 if (!validPart(index)) throw Exception("TransformGizmo::getPartKind: bad index");
583 return parts_[index].kind;
584}
585std::string TransformGizmo::getPartAxis(int index) const {
586 if (!validPart(index)) throw Exception("TransformGizmo::getPartAxis: bad index");
587 return parts_[index].axis;
588}
590 if (!validPart(index)) throw Exception("TransformGizmo::getPartColorR: bad index");
591 return parts_[index].color.r;
592}
594 if (!validPart(index)) throw Exception("TransformGizmo::getPartColorG: bad index");
595 return parts_[index].color.g;
596}
598 if (!validPart(index)) throw Exception("TransformGizmo::getPartColorB: bad index");
599 return parts_[index].color.b;
600}
602 if (!validPart(index)) throw Exception("TransformGizmo::getPartColorA: bad index");
603 return parts_[index].color.a;
604}
606 if (!validPart(index)) throw Exception("TransformGizmo::getPartOriginX: bad index");
607 return parts_[index].origin.x;
608}
610 if (!validPart(index)) throw Exception("TransformGizmo::getPartOriginY: bad index");
611 return parts_[index].origin.y;
612}
614 if (!validPart(index)) throw Exception("TransformGizmo::getPartOriginZ: bad index");
615 return parts_[index].origin.z;
616}
618 if (!validPart(index)) throw Exception("TransformGizmo::getPartDirX: bad index");
619 return parts_[index].dir.x;
620}
622 if (!validPart(index)) throw Exception("TransformGizmo::getPartDirY: bad index");
623 return parts_[index].dir.y;
624}
626 if (!validPart(index)) throw Exception("TransformGizmo::getPartDirZ: bad index");
627 return parts_[index].dir.z;
628}
630 if (!validPart(index)) throw Exception("TransformGizmo::getPartLength: bad index");
631 return parts_[index].length;
632}
634 if (!validPart(index)) throw Exception("TransformGizmo::getPartRadius: bad index");
635 return parts_[index].radius;
636}
637
638} // namespace eve::editor
LogicalId target
Duration start
bool & active
double score
Definition Agent.cpp:50
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string from
const std::string & s
float ang
float degrees
Definition CardTypes.cpp:35
glm::vec4 p[6]
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::uint32_t aa
float v
HexVec3 up
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
TokenKind kind
std::string local
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
bool hit
PrimitiveHandle handle
float d
float t
V3 origin
Definition RoadBake.cpp:138
float dz
float dy
float dx
std::map< Cell, int > best
ecs::EntityHandle side
float step
Definition TreeMesh.cpp:314
float size
Definition TreeMesh.cpp:156
uint32_t index
double oy
double ox
float m[16]
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
float getPartDirY(int index) const
Returns the part dir y.
void setSnapScale(float s)
Sets the snap scale.
void setSnapRotate(float degrees)
Sets the snap rotate.
float getPartColorR(int index) const
Returns the part color r.
void rebuildParts()
Rebuild draw parts for current mode/space (call after TRS/mode change).
float getPartDirX(int index) const
Returns the part dir x.
TransformGizmo()
Transform gizmo.
std::string pick(float ox, float oy, float oz, float dx, float dy, float dz)
Ray pick against active mode handles. Returns axis id: "x"|"y"|"z"|"xy"|"yz"|"xz"|"xyz"|"bx"|…|"bz"|"...
float getPartDirZ(int index) const
Returns the part dir z.
std::string getPartKind(int index) const
Returns the part kind.
float getPartRadius(int index) const
Returns the part radius.
float getMatrix(int index) const
Column-major matrix element 0..15 of current TRS.
void setSize(float size)
Sets the size.
float getPartColorG(int index) const
Returns the part color g.
void setBounds(float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
Local AABB extents for bound mode (relative to object origin).
float getPartColorB(int index) const
Returns the part color b.
bool beginDrag(const std::string &axis, float ox, float oy, float oz, float dx, float dy, float dz)
Begins drag.
float getPartOriginY(int index) const
Returns the part origin y.
void setSnapTranslate(float x, float y, float z)
Sets the snap translate.
float getPartLength(int index) const
Returns the part length.
void setPosition(float x, float y, float z)
Sets the position.
float getPartColorA(int index) const
Returns the part color a.
bool updateDrag(float ox, float oy, float oz, float dx, float dy, float dz)
Updates drag.
float getPartOriginX(int index) const
Returns the part origin x.
void setRotationEuler(float x, float y, float z)
Euler radians, XYZ order.
void setMode(const std::string &mode)
Sets the mode.
void setScale(float x, float y, float z)
Sets the scale.
std::string getPartAxis(int index) const
Returns the part axis.
float getPartOriginZ(int index) const
Returns the part origin z.
void setSpace(const std::string &space)
Sets the space.
int axis(int64_t a, size_t rank)
Axis.