fix(editor): 缩放轴病态判据与平移互补,按 op 分派 AxisMask

缩放独有老问题根因(源码实证):缩放 hover(GetScaleType)与拖拽
(HandleScale ratio 分母)依赖"法线=轴"平面求交,交点距离分子 numer
= -dist·dot(camToObj, 轴)。正对视角相机与物体无横偏时 numer≡0 →
len=0/垃圾 → 杆 hover 不到、ratio 分子分母乱跳(数值随机猛变)。
平移 hover 是纯屏幕距离、平面法线经正交化自适应;旋转是屏幕角数学
——故唯独缩放有此病。SCALE op 下判据换 |dot(camToObj,轴)|<0.1,
ROTATE 不 mask,TRANSLATE 保持 sinθ<0.25。中心块等比缩放
(MT_SCALE_XYZ,屏幕空间数学)不受 mask 恒可用。
This commit is contained in:
2026-10-03 12:11:16 +08:00
parent a130850276
commit 21b33d7d7f
+29 -12
View File
@@ -40,11 +40,17 @@ namespace
constexpr float kSnapRotateDeg = 15.0f; // 度 constexpr float kSnapRotateDeg = 15.0f; // 度
constexpr float kSnapScale = 0.1f; // 缩放步进 constexpr float kSnapScale = 0.1f; // 缩放步进
// 单轴拖拽灵敏度下限:sinθ(轴与"相机→物体"连线夹角)。低于该值 // 平移拖拽灵敏度下限:sinθ(轴与"相机→物体"连线夹角)。低于该值
// 时拖拽投影系数 <0.25 且激活平面与鼠标射线近共面——视觉上 // 时拖拽投影系数 <0.25 且激活平面与鼠标射线近共面——视觉上
// "看得见、抓了没反应"。≈14.5°,略宽于库默认可见角(≈11.5°), // "看得见、抓了没反应"。≈14.5°,略宽于库默认可见角(≈11.5°),
// 只藏真正病态的轴,不动可用轴 // 只藏真正病态的轴,不动可用轴
constexpr float kAxisMinSin = 0.25f; constexpr float kMoveMinSin = 0.25f;
// 缩放求交稳定性下限:|dot(camToObj, 轴)|。缩放的 hover 与拖拽
// 依赖"法线=轴"平面求交,交点距离分子 numer = -dist·该 dot——
// 轴⊥连线(正对视角相机无横偏)时 numer≈0,len→0/垃圾:杆
// hover 不到、ratio 分子分母乱跳(数值随机猛变)。≈84.3°
constexpr float kScaleMinDot = 0.1f;
// Vulkan proj → ImGuizmo 期望的 OpenGL 形式(Y-up、Z∈[-1,1]): // Vulkan proj → ImGuizmo 期望的 OpenGL 形式(Y-up、Z∈[-1,1]):
// Y 行翻回(抵消 Vulkan clip Y 向下翻转);z 行 = 2*z - w 即 // Y 行翻回(抵消 Vulkan clip Y 向下翻转);z 行 = 2*z - w 即
@@ -161,13 +167,16 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
const ImGuizmo::MODE mode = const ImGuizmo::MODE mode =
gizmo.localSpace ? ImGuizmo::LOCAL : ImGuizmo::WORLD; gizmo.localSpace ? ImGuizmo::LOCAL : ImGuizmo::WORLD;
// 退化轴隐藏(UE 语义:可激活性决定可见性)。库的单轴 // 退化轴隐藏(UE 语义:可激活性决定可见性)。三种
// 激活平面包含"轴 + 相机连线",鼠标射线∥连线时求交 // 操作病态几何不同,按 op 分派判据:
// denom→0 返回 -1 被当交点距离(L2347-2349),拖拽 // - 平移:拖拽灵敏度 = sinθ(轴与相机连线夹角),θ→0
// delta 在轴上的投影系数 = sinθ 也同时趋 0——表现为 // 时鼠标位移在轴上投影→0("数值不变")
// "杆看得见、抓了没反应/数值不变"。按灵敏度把病态轴 // - 缩放:hover(GetScaleType)与拖拽(HandleScale 的
// SetAxisMask 隐藏(库内按轴关绘制+激活,单轴 mask // ratio 分母)依赖"法线=轴"平面求交,分子 numer =
// 时保留含该轴的平面手柄兜底);相机一移动自动恢复 // -dist·dot(camToObj, 轴)——正对视角相机无横偏时
// numer≡0,len→0/垃圾:杆 hover 不到、数值随机
// 猛变(缩放独有的老问题,平移/旋转不走此路径)
// - 旋转:屏幕空间角度数学,无退化,不 mask
const glm::vec3 camPos(glm::inverse(view)[3]); const glm::vec3 camPos(glm::inverse(view)[3]);
glm::vec3 camToObj = glm::vec3(m[3]) - camPos; glm::vec3 camToObj = glm::vec3(m[3]) - camPos;
const float camDist = glm::length(camToObj); const float camDist = glm::length(camToObj);
@@ -175,14 +184,22 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
bool maskAxis[3] = {false, false, false}; bool maskAxis[3] = {false, false, false};
if (camDist > 1e-4f) { if (camDist > 1e-4f) {
camToObj /= camDist; camToObj /= camDist;
const bool isTranslate = (gizmo.op
& int(ImGuizmo::TRANSLATE)) == int(ImGuizmo::TRANSLATE);
const bool isScale = (gizmo.op
& int(ImGuizmo::SCALE)) == int(ImGuizmo::SCALE);
if (isTranslate || isScale) {
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
const glm::vec3 axis = gizmo.localSpace const glm::vec3 axis = gizmo.localSpace
? glm::normalize(rotM[i]) ? glm::normalize(rotM[i])
: glm::vec3(float(i == 0), float(i == 1), : glm::vec3(float(i == 0), float(i == 1),
float(i == 2)); float(i == 2));
maskAxis[i] = maskAxis[i] = isScale
glm::length(glm::cross(camToObj, axis)) ? glm::abs(glm::dot(camToObj, axis))
< kAxisMinSin; < kScaleMinDot
: glm::length(glm::cross(camToObj, axis))
< kMoveMinSin;
}
} }
} }
ImGuizmo::SetAxisMask(maskAxis[0], maskAxis[1], ImGuizmo::SetAxisMask(maskAxis[0], maskAxis[1],