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:
@@ -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))
|
||||
{
|
||||
|
||||
@@ -98,13 +98,20 @@ namespace Engine::ECS {
|
||||
TransformComponent& SetPosition(const glm::vec3& p) { if (m_Position != p) { m_Position = p; m_Dirty = true; } return *this; }
|
||||
// 欧拉角(度)入口:UI/游戏逻辑用。转 quat 存(q = qy·qx·qz,
|
||||
// 与原 Ry·Rx·Rz 合成严格同构;不能用 glm::quat(euler) ctor——
|
||||
// 那是 ZYX 航向序,语义不同)
|
||||
// 那是 ZYX 航向序,语义不同)。输入域无限制:三角函数周期性
|
||||
// 自动归一(720 ≡ -45 ≡ 315),编辑器拖动累计值直接喂
|
||||
TransformComponent& SetRotation(const glm::vec3& r)
|
||||
{
|
||||
return SetRotationQuat(QuatFromEuler(r));
|
||||
}
|
||||
// 欧拉(度)→ quat 的唯一转换点(qy·qx·qz)。static 供编辑器
|
||||
// mirror 的旋转等价检测复用(同一公式构造 → dot 判定严格)
|
||||
static glm::quat QuatFromEuler(const glm::vec3& r)
|
||||
{
|
||||
const glm::quat qx = glm::angleAxis(glm::radians(r.x), glm::vec3(1, 0, 0));
|
||||
const glm::quat qy = glm::angleAxis(glm::radians(r.y), glm::vec3(0, 1, 0));
|
||||
const glm::quat qz = glm::angleAxis(glm::radians(r.z), glm::vec3(0, 0, 1));
|
||||
return SetRotationQuat(qy * qx * qz);
|
||||
return qy * qx * qz;
|
||||
}
|
||||
// 四元数直写(UE FTransform 语义):Gizmo 拖拽/物理写回用,
|
||||
// 不经欧拉往返 → 零漂移、无万向锁
|
||||
|
||||
Reference in New Issue
Block a user