如何围绕点缩放Matrix4x4?

问题描述

我正在 Unity 中编写一个自定义编辑器窗口,我希望能够在其中滚动进/出并拖动视图。为此,我已将 GUI.matrix 设置为 Matrix4x4.TRS(offset,Quaternion.identity,Vector3.one * scale),在那里我可以控制 offsetscale。这工作正常,除非在滚动进/出时,它锚定窗口的左上角我希望它锚定在鼠标的位置

如果这只是需要在缩放时更改偏移量,那就太好了 - 我只是不确定这里的偏移量应该是多少。 Matrix4x4 超出了我的数学舒适区。

这是我目前处理缩放的方式:

if (Event.current.type == EventType.ScrollWheel)
{
    _scale *= Math.Sign(Event.current.delta.y) == 1 ? 1.1f : 1f / 1.1f;
    _offset += Math.Sign(Event.current.delta.y) * /*What do I put here?*/;
}

解决方法

让我们试着理解 GUI 矩阵的作用。它表示一种变换,它采用世界空间(您的 GUI 对象所在的位置)中的坐标并将它们转换为 GUI 空间(或多或少与您的窗口对齐)。由于我们没有旋转,我们可以很容易地解释用 TRS() 构造矩阵对世界空间点 pWorld 的作用:

pGUI = scale * pWorld + offset

现在您要将 scale 更改为 scaleNew。这样做时,您希望在鼠标下保持相同的世界位置。

如果您的鼠标位置是在 GUI 空间中给出的(例如,来自 Event.current.mousePosition),那么我们首先需要找到相应的世界空间点:

v3World = (1.0 / scaleOld) * (v3GUI - offsetOld)

我们想在鼠标下修复这个点,即:

v3GUI = scaleNew * v3World + offsetNew
v3GUI = scaleNew / scaleOld * (v3GUI - offsetOld) + offsetNew 

我们可以解决这个问题以获得新的偏移量:

v3GUI = scaleNew / scaleOld * v3GUI - scaleNew / scaleOld * offsetOld + offsetNew
(1 - scaleNew / scaleOld) * v3GUI + scaleNew / scaleOld * offsetOld = offsetNew

就是这样。

顺便说一句,你也可以单独使用矩阵运算来做到这一点。这就是 GUIUtility.ScaleAroundPivot() 所做的。这是它的外观:

newMatrix = T(v3GUI) * S(newScale / oldScale) * T(-v3GUI) * oldMatrix

T 表示平移,S 表示缩放。平移对 T(v3GUI)T(-v3GUI) 将坐标系的临时原点移动到您的鼠标位置并从那里执行缩放。然后你可以直接从这个矩阵中读取偏移量和比例。