fix(editor): rotation drag accumulates without wrap (UE session semantics)

Dragging rotation hit a wall at the wrap point: each frame re-read
euler from the quat and wrapped to [0,360), so dragging past 360
snapped the display back to 0 (and backwards dragging wrapped up to
360) -- values "jumped around" instead of accumulating.

Port UE TransformDetails session semantics: the rotation row shows a
local mirror value while the user edits it (accumulating freely in
both directions, never wrapping), and re-syncs from the quat only on
foreign changes (gizmo drag, physics writeback, entity switch).
Detection is exact component-wise quat equality: the mirror quat comes
from the same QuatFromEuler used by SetRotation, so an untouched pair
compares bit-equal, while any foreign writeback -- including sub-0.05
degree gizmo steps that a dot-product threshold would miss -- differs
in at least one float. Edit frames skip the check (mirror leads the
quat by one drag delta), gated on the previous frame's item-active
state.
This commit is contained in:
2026-10-04 02:00:42 +08:00
parent 4498bc7535
commit 90097a756e
2 changed files with 65 additions and 9 deletions
+56 -7
View File
@@ -78,7 +78,7 @@ namespace
// TransformComponent::GetRotation 注释)
bool DragVec3Row(const char* label, glm::vec3& v, float speed,
float mn = -3.4e38f, float mx = 3.4e38f,
const char* fmt = "%.2f")
const char* fmt = "%.2f", bool* editing = nullptr)
{
ImGui::TextUnformatted(label);
ImGui::SameLine(72.0f); // 固定标签列宽 → 三行输入框对齐
@@ -101,6 +101,11 @@ namespace
changed |= ImGui::DragFloat(
i == 0 ? "##x" : i == 1 ? "##y" : "##z",
&v[i], speed, mn, mx, fmt);
if (editing != nullptr)
{
// 拖拽中/输入框打开中(含无拖动的按住帧)
*editing |= ImGui::IsItemActive();
}
ImGui::PopStyleColor();
if (i < 2) ImGui::SameLine();
}
@@ -215,17 +220,61 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
{
t->SetPosition(pos);
}
glm::vec3 rot = t->GetRotation(); // 派生欧拉(度)
for (int i = 0; i < 3; ++i)
// ---- rotation 行:编辑 mirror(UE TransformDetails 会话语义)----
// 每帧回读 quat 反解 + wrap 会让拖动乱跳(360 → 0 折返)。
// UE 做法 = 编辑会话内显示本地累计值(正拖无限增大、反拖
// 无限减小、永不折返),只有"非编辑来源"的姿态改动(Gizmo、
// 物理、重选)才刷新显示。ImGui 无状态 → static mirror:
// 切换实体重置;非编辑态用 quat 位级等价检测判定外部改动
static unsigned s_MirrorEntity = 0;
static glm::vec3 s_RotMirror(0.0f);
static bool s_MirrorValid = false;
static bool s_RotEditingLast = false; // 上一帧是否在编辑
const unsigned entityId =
static_cast<unsigned>(entt::to_integral(selected));
if (entityId != s_MirrorEntity)
{
if (rot[i] == 0.0f) rot[i] = 0.0f; // 清 -0.0(atan2 副产物)
s_MirrorEntity = entityId;
s_MirrorValid = false;
}
// "%.1f" = UE Details 默认显示精度(反解噪声 ~0.02° @ pitch 90°,
// 存储端 quat 精确;手输 Ctrl+Click 仍全精度写入)
if (DragVec3Row("rotation", rot, 0.5f, -3.4e38f, 3.4e38f, "%.1f"))
bool editingRot = false;
if (s_MirrorValid)
{
if (!s_RotEditingLast)
{
// 非编辑帧才检测外部改动(编辑帧 mirror 领先 quat 一个
// 拖动增量,等价检测必触发误重置)。编辑态取上一帧
// ——检测先于本帧 DragVec3Row 执行。
// 判据 = 逐分量相等:QuatFromEuler 与 SetRotation 是
// 同一函数,mirror 未被外部动过时位级相同(== 无浮点
// 噪声问题,比 dot 阈值严——Gizmo 极慢拖 0.05°/帧
// 的 quat 差 ~1e-7,dot 阈值法会漏检导致显示不跟手)
const glm::quat mq =
Engine::ECS::TransformComponent::QuatFromEuler(s_RotMirror);
const glm::quat& aq = t->GetRotationQuat();
const bool untouched = mq.x == aq.x && mq.y == aq.y &&
mq.z == aq.z && mq.w == aq.w;
if (!untouched)
{
s_RotMirror = t->GetRotation(); // 外部改动 → 刷新
}
}
}
else
{
s_RotMirror = t->GetRotation();
s_MirrorValid = true;
}
glm::vec3 rot = s_RotMirror;
// "%.1f" = UE Details 默认显示精度。mirror 值直显(无 wrap、
// 无限累计);Ctrl+Click 手输全精度写入
if (DragVec3Row("rotation", rot, 0.5f, -3.4e38f, 3.4e38f,
"%.1f", &editingRot))
{
t->SetRotation(rot);
s_RotMirror = rot;
}
s_RotEditingLast = editingRot;
glm::vec3 scl = t->GetScale();
if (DragVec3Row("scale", scl, 0.02f, 0.001f, 100.0f))
{