I'm writing a custom editor window in Unity in which I would like to be able to both scroll in/out and drag the view around. To do so, I've been setting GUI.matrix
to Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one * scale)
, where I have control over offset
and scale
. This works fine, except when scrolling in/out, it anchors the top left of the window. I would like it to anchor on the mouse's position.
If this just requires changing the offset when zooming, that's great - I'm just not sure what the offset should be here. Matrix4x4
s are out of my comfort zone for math.
Here is how I'm currently handling zooming:
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?*/;
}
Let's try to understand whatthe GUI matrix does. It represents a transform that takes coordinates in world space (where your GUI objects live) and converts them to GUI space (more or less aligned with your window). Since we have no rotation, we can easily interpret what constructing the matrix with TRS()
does to a world-space point pWorld
:
pGUI = scale * pWorld + offset
Now you want to change scale
to scaleNew
. In doing so, you want to keep the same world position under the mouse.
If your mouse position is given in GUI space (e.g., from Event.current.mousePosition
), then we first need to find the corresponding world space point:
v3World = (1.0 / scaleOld) * (v3GUI - offsetOld)
And we want to fix this point under the mouse, i.e.:
v3GUI = scaleNew * v3World + offsetNew
v3GUI = scaleNew / scaleOld * (v3GUI - offsetOld) + offsetNew
We can solve this to get the new offset:
v3GUI = scaleNew / scaleOld * v3GUI - scaleNew / scaleOld * offsetOld + offsetNew
(1 - scaleNew / scaleOld) * v3GUI + scaleNew / scaleOld * offsetOld = offsetNew
And that's it.
Btw, you can also do this with matrix operations alone. This is what GUIUtility.ScaleAroundPivot()
does. This is how it looks:
newMatrix = T(v3GUI) * S(newScale / oldScale) * T(-v3GUI) * oldMatrix
T
represents a translation and S
a scaling. The translation pair T(v3GUI)
and T(-v3GUI)
move the temporary origin of the coordinate system to your mouse position and perform the scaling from there. You could then directly read offset and scale from this matrix.