5#include <glm/gtc/matrix_transform.hpp>
6#include <glm/gtc/type_ptr.hpp>
15constexpr float kPi = 3.14159265358979323846f;
16constexpr float kAxisHitRadius = 0.12f;
17constexpr float kPlaneHitSize = 0.42f;
18constexpr float kRingThickness = 0.08f;
20float snapValue(
float v,
float step) {
21 if (
step <= 0.f)
return v;
25glm::vec3 safeNormalize(
const glm::vec3 &
v,
const glm::vec3 &fallback) {
26 float len = glm::length(
v);
27 if (len < 1e-6f)
return fallback;
36 if (mode !=
"translate" && mode !=
"rotate" && mode !=
"scale" && mode !=
"bound") {
37 throw Exception(
"TransformGizmo::setMode: expected translate|rotate|scale|bound");
44 if (space !=
"local" && space !=
"world") {
45 throw Exception(
"TransformGizmo::setSpace: expected local|world");
52 if (
size <= 0.f)
throw Exception(
"TransformGizmo::setSize: size must be > 0");
58 position_ = {
x,
y,
z};
63 rotation_ = {
x,
y,
z};
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)};
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);
92glm::mat4 TransformGizmo::localRotationMatrix()
const {
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));
100glm::mat4 TransformGizmo::worldMatrix()
const {
102 m = glm::translate(
m, position_);
103 m *= localRotationMatrix();
104 m = glm::scale(
m, scale_);
108glm::vec3 TransformGizmo::axisWorld(
int axis)
const {
109 glm::vec3
local(0.f);
111 if (space_ ==
"world" && mode_ !=
"bound")
return local;
112 glm::mat3 R(localRotationMatrix());
116void TransformGizmo::colorForAxis(
const std::string &axis, glm::vec4 &out)
const {
118 float boost =
active ? 1.f : 0.75f;
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};
134 out = {0.8f * boost, 0.8f * boost, 0.2f, 1.f};
139 const glm::vec3
origin = position_;
141 auto addAxis = [&](
const char *axisName,
int ai,
const char *
kind) {
146 p.dir = axisWorld(ai);
148 p.radius = size_ * 0.04f;
149 colorForAxis(
p.axis,
p.color);
153 if (mode_ ==
"translate" || mode_ ==
"scale") {
154 addAxis(
"x", 0,
"axis");
155 addAxis(
"y", 1,
"axis");
156 addAxis(
"z", 2,
"axis");
158 for (
const char *plane : {
"xy",
"yz",
"xz"}) {
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));
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);
178 c.dir = {0.f, 1.f, 0.f};
180 c.radius = size_ * (mode_ ==
"scale" ? 0.08f : 0.06f);
181 colorForAxis(
c.axis,
c.color);
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_) {
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},
197 glm::mat3 R(localRotationMatrix());
198 for (
int i = 0; i < 6; ++i) {
202 glm::vec3
local = centers[i];
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);
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);
223float TransformGizmo::hitAxis(
const glm::vec3 &ro,
const glm::vec3 &rd,
int axisIndex,
225 glm::vec3
a = axisWorld(axisIndex);
226 glm::vec3 p0 = position_;
227 glm::vec3 p1 = position_ +
a * size_;
229 glm::vec3
u = p1 - p0;
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;
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;
254float TransformGizmo::hitPlane(
const glm::vec3 &ro,
const glm::vec3 &rd,
int planeMask,
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_;
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) {
273 }
else if (planeMask == 6) {
276 }
else if (planeMask == 5) {
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;
289float TransformGizmo::hitRing(
const glm::vec3 &ro,
const glm::vec3 &rd,
int axisIndex,
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_);
299 float thick = size_ * kRingThickness;
300 if (std::fabs(dist -
target) > thick)
return -1.f;
302 return std::fabs(dist -
target);
305float TransformGizmo::hitBoundHandle(
const glm::vec3 &ro,
const glm::vec3 &rd,
int handle,
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;
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;
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));
330 auto consider = [&](
const std::string &axis,
float score,
float t) {
331 if (
score < 0.f)
return;
338 if (mode_ ==
"translate") {
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);
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);
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;
354 float tHit = -
b - std::sqrt(disc);
355 if (tHit >= 0.f) consider(
"xyz", 0.f, tHit);
357 }
else if (mode_ ==
"rotate") {
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);
363 }
else if (mode_ ==
"scale") {
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);
369 glm::vec3 oc = ro - position_;
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;
379 float tHit = -
b - std::sqrt(disc);
380 if (tHit >= 0.f) consider(
"xyz", 0.f, tHit);
383 for (
int i = 0; i < static_cast<int>(parts_.size()); ++i) {
385 float s = hitBoundHandle(ro, rd, i,
t);
386 if (
s >= 0.f) consider(parts_[i].axis,
s,
t);
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) {
399 return dragStartHit_;
401 float t = glm::dot(dragStartHit_ - ro, dragPlaneNormal_) / denom;
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);
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);
418float TransformGizmo::applySnapScale(
float s)
const {
419 if (snapScale_ <= 0.f)
return s;
420 return snapValue(
s, snapScale_);
424 float dy,
float dz) {
425 if (axis.empty())
return false;
429 dragStartPos_ = position_;
430 dragStartRot_ = rotation_;
431 dragStartScale_ = scale_;
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));
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);
450 dragPlaneNormal_ = safeNormalize(glm::cross(rd, glm::vec3(0.f, 1.f, 0.f)),
451 glm::vec3(0.f, 0.f, 1.f));
453 if (axis ==
"x" || axis ==
"y" || axis ==
"z" || axis ==
"bx" || axis ==
"by" ||
454 axis ==
"bz" || axis ==
"bnx" || axis ==
"bny" || axis ==
"bnz") {
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));
461 dragPlaneNormal_ = -rd;
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));
468 int ai = axis ==
"x" ? 0 : axis ==
"y" ? 1 : 2;
469 dragAxisDir_ = axisWorld(ai);
470 dragPlaneNormal_ = dragAxisDir_;
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;
478 dragStartHit_ = position_;
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_;
491 if (mode_ ==
"translate") {
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") {
508 position_ = dragStartPos_ + move;
509 if (activeAxis_ ==
"xyz") {
510 applySnapTranslate(position_);
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);
521 }
else if (mode_ ==
"rotate") {
522 glm::vec3
from = safeNormalize(dragStartHit_ - position_, dragAxisDir_);
523 glm::vec3
to = safeNormalize(
hit - position_,
from);
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;
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;
546 : std::max(0.01f, applySnapScale(1.f + glm::dot(delta, axisWorld(i)) / std::max(size_, 1e-3f)));
547 scale_[i] = dragStartScale_[i] * factor;
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));
577bool TransformGizmo::validPart(
int index)
const {
578 return index >= 0 && index < static_cast<int>(parts_.size());
582 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartKind: bad index");
583 return parts_[
index].kind;
586 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartAxis: bad index");
587 return parts_[
index].axis;
590 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartColorR: bad index");
591 return parts_[
index].color.r;
594 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartColorG: bad index");
595 return parts_[
index].color.g;
598 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartColorB: bad index");
599 return parts_[
index].color.b;
602 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartColorA: bad index");
603 return parts_[
index].color.a;
606 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartOriginX: bad index");
607 return parts_[
index].origin.x;
610 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartOriginY: bad index");
611 return parts_[
index].origin.y;
614 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartOriginZ: bad index");
615 return parts_[
index].origin.z;
618 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartDirX: bad index");
619 return parts_[
index].dir.x;
622 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartDirY: bad index");
623 return parts_[
index].dir.y;
626 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartDirZ: bad index");
627 return parts_[
index].dir.z;
630 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartLength: bad index");
631 return parts_[
index].length;
634 if (!validPart(
index))
throw Exception(
"TransformGizmo::getPartRadius: bad index");
635 return parts_[
index].radius;
HexCoordinates to
Cell the unit walks towards on this segment.
std::map< Cell, int > best
EVENGINE_API_FOUNDATION public API.
int axis(int64_t a, size_t rank)
Axis.