Skip to main content

Zooming around the mouse cursor.

1 reply [Last post]
william13163
Offline
Joined: 2008-03-19
Points: 0

Greetings,

I have a question regarding Zooming in and out around the cursor location. If anyone has time or knows a solution I would appreciate it. ;)

I have reached a point where I can't figure out how to solve it. What I wan't to do is be able to zoom where ever the cursor location is on the screen. I have a solution that "kind of" works but when you move the mouse to a new location the display jumps over to the new location, minus the new scaling. Mathematically I understand why it is jumping but I don't know how to solve it. Tons of other programs do this kind of thing all the time.

So I created a simple setup that has a two circles and a box with an axis showing at the world-origin. I can move the mouse somewhere and use the mouse wheel to zoom in and out. That works fine. But if I move the mouse to a new location and try zooming I get the "jump". This is the scenegraph of the simple test harness:

http://william.quartz.googlepages.com/temp_scenegraph.png

And here is the app running.

http://william.quartz.googlepages.com/temp_Screenshot.png

And here is the source:
http://william.quartz.googlepages.com/ScaleDemo2.java

Ctrl-MMB to zoom and LMB to slide the window around.

The orange arrow is my solution based on a typical scaling-about-a-center transform. It works as long as the mouse stays in one place. Basically you translate to the origin, scale, and translate back. This is the same thing done when scaling objects about their center.

So I thought why don't I just update the translation transforms while the mouse moves. This works but the "world" slides around, as you would predict.

Well anyway, I was wondering if anyone already knows the solution for this. My guess is that the solution has something to with an inverse scaling trick but I just can't seem to envision it.

Thanks for any kind ideas. ;)

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
william13163
Offline
Joined: 2008-03-19
Points: 0

Uhmm... I'm answering my own question. No worries, I'll have a discussion with myself about it later, but for now this is the solution for any of those interested. The event is attached to the mouse wheel.

The scene graph layout is as follows:
[pre]
JSGPanel
/
/
SGGroup
/
/
Translate
/
/
SGGroup
/
/
Scale
[/pre]
[code]
/*
* The point is used for the animations.
*/
private Point2D pointLocation = new Point2D.Double();
/*
* This is used for the SG.
*/
private SGTransform.Translate _cameraLocation;

private SGTransform.Scale _mapScale = null;

public void mouseWheelMoved(MouseWheelEvent e, SGNode node)
{
/*
* The key to this algorithm is that the cursor's global
* position changes when a scale occurs.
* The trick is to capture the local-space position going "in" and
* use that to find the new global position coming "out".
* Note:
* Global-space is synonymous with view-space which is the same as the
* JSGPanel-space, which is also the same as mouse-space.
* mouse-space is fixed to the dimensions of the view-port, where as
* view-space is infinite.
*/
Point2D pLocal = new Point2D.Double();
Point2D pGlobalIn = new Point2D.Double();

pGlobalIn.setLocation(e.getPoint());
/*
* First convert the current mouse position to its local-space
* equivalent. We remember this value because it will "pop out" in
* a different location after the scale.
*
* In essence we are "tunneling" through spaces seeing where the
* local-space coordinate enters and exits.
*/
_mapScale.globalToLocal(pGlobalIn, pLocal);

int rollCount = e.getWheelRotation();
double s = getScale();
if (rollCount > 0)
{
//Away
s -= 0.1;
if (s < 0.5f)
s = 0.5f;
} else
{
//Towards
s += 0.1;
}
setScale(s);

/*
* Now that the scale has taken place we take our local-space
* position and see where it "pops" back into view-space.
*/
Point2D pGlobalOut = new Point2D.Double();
_mapScale.localToGlobal(pGlobalOut, pLocal);

/*
* With these two view-space coordinates we can now find how much
* shift occurred between scales.
* Because we want to "pull" the camera back under the cursor
* we create a vector from where the cursor was (in) to where it is now (out).
* In <-- Out.
*/
Point2D pDelta = new Point2D.Double();
pDelta.setLocation(pGlobalIn.getX() - pGlobalOut.getX(), pGlobalIn.getY()
- pGlobalOut.getY());

translateBy(pDelta.getX(), pDelta.getY());
}

public double getScale()
{
return _scale;
}

public void setScale(double scale)
{
_scale = scale;
_mapScale.setScale(_scale, _scale);
}

public void translateBy(double dx, double dy)
{
if (pointLocation == null)
pointLocation = new Point2D.Double(dx, dy);
else
{
pointLocation.setLocation(pointLocation.getX() + dx, pointLocation.getY()
+ dy);
_cameraLocation.translateBy(dx, dy);
}
}
[/code]

Cheers.