我已经为NASA Worldwind创建了一个输入处理程序,我试图复制Google地球就像缩放一样。
我试图对鼠标光标进行缩放,而不是屏幕中心(就像默认情况下那样)。
我有点工作 - 除了它不会一直向光标下方的纬度/长度变焦,它似乎漂移得太远了。我想要发生的是在缩放期间光标下面保持相同的纬度/经度。因此,例如,如果您将光标悬停在特定地标(如水体)上,则当滚轮滚动时,它将保持在光标下。
这是我的输入处理程序:
import java.awt.event.MouseWheelEvent;
import gov.nasa.worldwind.awt.AbstractViewInputHandler;
import gov.nasa.worldwind.awt.ViewInputAttributes;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.view.orbit.BasicOrbitView;
import gov.nasa.worldwind.view.orbit.OrbitViewInputHandler;
public class ZoomToCursorViewInputHandler extends OrbitViewInputHandler {
protected class ZoomActionHandler extends VertTransMouseWheelActionListener {
@Override
public boolean inputActionPerformed(AbstractViewInputHandler inputHandler, MouseWheelEvent mouseWheelEvent,
ViewInputAttributes.ActionAttributes viewAction) {
double zoomInput = mouseWheelEvent.getWheelRotation();
Position position = getView().computePositionFromScreenPoint(mousePoint.x, mousePoint.y);
// Zoom toward the cursor if we're zooming in. Move straight out when zooming
// out.
if (zoomInput < 0 && position != null)
return this.zoomToPosition(position, zoomInput, viewAction);
else
return super.inputActionPerformed(inputHandler, mouseWheelEvent, viewAction);
}
protected boolean zoomToPosition(Position position, double zoomInput,
ViewInputAttributes.ActionAttributes viewAction) {
double zoomChange = zoomInput * getScaleValueZoom(viewAction);
BasicOrbitView view = (BasicOrbitView) getView();
System.out.println("================================");
System.out.println("Center Position: \t\t"+view.getCenterPosition());
System.out.println("Mouse is on Position: \t\t"+position);
Vec4 centerVector = view.getCenterPoint();
Vec4 cursorVector = view.getGlobe().computePointFromLocation(position);
Vec4 delta = cursorVector.subtract3(centerVector);
delta = delta.multiply3(-zoomChange);
centerVector = centerVector.add3(delta);
Position newPosition = view.getGlobe().computePositionFromPoint(centerVector);
System.out.println("New Center Position is: \t"+newPosition);
setCenterPosition(view, uiAnimControl, newPosition, viewAction);
onVerticalTranslate(zoomChange, viewAction);
return true;
}
}
public ZoomToCursorViewInputHandler() {
ViewInputAttributes.ActionAttributes actionAttrs = this.getAttributes()
.getActionMap(ViewInputAttributes.DEVICE_MOUSE_WHEEL)
.getActionAttributes(ViewInputAttributes.VIEW_VERTICAL_TRANSLATE);
actionAttrs.setMouseActionListener(new ZoomActionHandler());
}
}
要启用,请在worldwind.xml中设置此属性以指向此类:
<Property name="gov.nasa.worldwind.avkey.ViewInputHandlerClassName"
value="gov.nasa.worldwindx.examples.ZoomToCursorViewInputHandler"/>
答案 0 :(得分:2)
在考虑过这个问题后,我相信没有封闭形式的分析解决方案。你只需要考虑很多事情:地球的形状,眼睛&#34;移动中心时移动。所以我认为你能做的最好的伎俩就是&#34;关注&#34;主要&#34;缩放&#34;动画并在每个动画步骤后进行小幅调整。由于动画步骤很小,计算错误也应该更小,并且它们应该累积得更少,因为在下一步中您会考虑所有先前的错误。所以我在代码中的想法大致如下:创建一个FixZoomPositionAnimator
类作为
static class FixZoomPositionAnimator extends BasicAnimator
{
static final String VIEW_ANIM_KEY = "FixZoomPositionAnimator";
static final double EPS = 0.005;
private final java.awt.Point mouseControlPoint;
private final Position mouseGeoLocation;
private final Vec4 mouseGeoPoint;
private final BasicOrbitView orbitView;
private final Animator zoomAnimator;
private int lastDxSign = 0;
private int lastDySign = 0;
int stepNumber = 0;
int stepsNoAdjustments = 0;
FixZoomPositionAnimator(BasicOrbitView orbitView, Animator zoomAnimator, java.awt.Point mouseControlPoint, Position mouseGeoLocation)
{
this.orbitView = orbitView;
this.zoomAnimator = zoomAnimator;
this.mouseControlPoint = mouseControlPoint;
this.mouseGeoLocation = mouseGeoLocation;
mouseGeoPoint = orbitView.getGlobe().computePointFromLocation(mouseGeoLocation);
}
public Point getMouseControlPoint()
{
return mouseControlPoint;
}
public Position getMouseGeoLocation()
{
return mouseGeoLocation;
}
private static int sign(double d)
{
if (Math.abs(d) < EPS)
return 0;
else if (d > 0)
return 1;
else
return -1;
}
double calcAccelerationK(double dSign, double lastDSign)
{
// as we are following zoom trying to catch up - accelerate adjustment
// but slow down if we overshot the target last time
if (!zoomAnimator.hasNext())
return 1.0;
else if (dSign != lastDSign)
return 0.5;
else
{
// reduce acceleration over time
if (stepNumber < 10)
return 5;
else if (stepNumber < 20)
return 3;
else
return 2;
}
}
static boolean isBetween(double checkedValue, double target1, double target2)
{
return ((target1 < checkedValue) && (checkedValue < target2))
|| ((target1 > checkedValue) && (checkedValue > target2));
}
static boolean isValid(Position position)
{
return isBetween(position.longitude.degrees, -180, 180)
&& isBetween(position.latitude.degrees, -90, 90);
}
@Override
public void next()
{
// super.next(); // do not call super to avoid NullPointerException!
nextWithTilt(); // works OK on tilted Earth
// nextOld(); // IMHO better looking but stops working is user tilts the Earth
}
private void nextOld()
{
stepNumber++;
Vec4 curProjection = orbitView.project(mouseGeoPoint);
Rectangle viewport = orbitView.getViewport();
// for Y sign is inverted
double dX = (mouseControlPoint.x - curProjection.x);
double dY = (mouseControlPoint.y + curProjection.y - viewport.getHeight());
if (Math.abs(dX) > EPS || Math.abs(dY) > EPS)
{
double dCX = (mouseControlPoint.x - viewport.getCenterX());
double dCY = (mouseControlPoint.y + viewport.getCenterY() - viewport.getHeight());
final double stepPx = 10;
// As the Earth is curved and we are not guaranteed to have a frontal view on it
// latitude an longitude lines are not really parallel to X or Y. But we assume that
// locally they are parallel enough both around the mousePoint and around the center.
// So we use reference points near center to calculate how we want to move the center.
Vec4 controlPointRight = new Vec4(viewport.getCenterX() + stepPx, viewport.getCenterY());
Vec4 geoPointRight = orbitView.unProject(controlPointRight);
Position positionRight = (geoPointRight != null) ? orbitView.getGlobe().computePositionFromPoint(geoPointRight) : null;
Vec4 controlPointUp = new Vec4(viewport.getCenterX(), viewport.getCenterY() - stepPx);
Vec4 geoPointUp = orbitView.unProject(controlPointUp);
Position positionUp = (geoPointUp != null) ? orbitView.getGlobe().computePositionFromPoint(geoPointUp) : null;
Position centerPosition = orbitView.getCenterPosition();
double newCenterLongDeg;
if (Math.abs(dCX) <= 1.0) // same X => same longitude
{
newCenterLongDeg = mouseGeoLocation.longitude.degrees;
}
else if (positionRight == null) // if controlPointRight is outside of the globe - don't try to fix this coordinate
{
newCenterLongDeg = centerPosition.longitude.degrees;
}
else
{
double scaleX = -dX / stepPx;
// apply acceleration if possible
int dXSign = sign(dX);
double accScaleX = scaleX * calcAccelerationK(dXSign, lastDxSign);
lastDxSign = dXSign;
newCenterLongDeg = centerPosition.longitude.degrees * (1 - accScaleX) + positionRight.longitude.degrees * accScaleX;
// if we overshot - use non-accelerated mode
if (!isBetween(newCenterLongDeg, centerPosition.longitude.degrees, mouseGeoLocation.longitude.degrees)
|| !isBetween(newCenterLongDeg, -180, 180))
{
newCenterLongDeg = centerPosition.longitude.degrees * (1 - scaleX) + positionRight.longitude.degrees * scaleX;
}
}
double newCenterLatDeg;
if (Math.abs(dCY) <= 1.0) // same Y => same latitude
{
newCenterLatDeg = mouseGeoLocation.latitude.degrees;
}
else if (positionUp == null) // if controlPointUp is outside of the globe - don't try to fix this coordinate
{
newCenterLatDeg = centerPosition.latitude.degrees;
}
else
{
double scaleY = -dY / stepPx;
// apply acceleration if possible
int dYSign = sign(dY);
double accScaleY = scaleY * calcAccelerationK(dYSign, lastDySign);
lastDySign = dYSign;
newCenterLatDeg = centerPosition.latitude.degrees * (1 - accScaleY) + positionUp.latitude.degrees * accScaleY;
// if we overshot - use non-accelerated mode
if (!isBetween(newCenterLatDeg, centerPosition.latitude.degrees, mouseGeoLocation.latitude.degrees)
|| !isBetween(newCenterLatDeg, -90, 90))
{
newCenterLatDeg = centerPosition.latitude.degrees * (1 - scaleY) + positionUp.latitude.degrees * scaleY;
}
}
Position newCenterPosition = Position.fromDegrees(newCenterLatDeg, newCenterLongDeg);
orbitView.setCenterPosition(newCenterPosition);
}
if (!zoomAnimator.hasNext())
stop();
}
private void nextWithTilt()
{
stepNumber++;
if (!zoomAnimator.hasNext() || (stepsNoAdjustments > 20))
{
System.out.println("Stop after " + stepNumber);
stop();
}
Vec4 curProjection = orbitView.project(mouseGeoPoint);
Rectangle viewport = orbitView.getViewport();
System.out.println("----------------------------------");
System.out.println("Mouse: mouseControlPoint = " + mouseControlPoint + "\t location = " + mouseGeoLocation + "\t viewSize = " + viewport);
System.out.println("Mouse: curProjection = " + curProjection);
double dX = (mouseControlPoint.x - curProjection.x);
double dY = (viewport.getHeight() - mouseControlPoint.y - curProjection.y); // Y is inverted
Vec4 dTgt = new Vec4(dX, dY);
// sometimes if you zoom close to the edge curProjection is calculated as somewhere
// way beyond where it is and it leads to overflow. This is a protection against it
if (Math.abs(dX) > viewport.width / 4 || Math.abs(dY) > viewport.height / 4)
{
Vec4 unproject = orbitView.unProject(new Vec4(mouseControlPoint.x, viewport.getHeight() - mouseControlPoint.y));
System.out.println("!!!End Mouse:"
+ " dX = " + dX + "\t" + " dY = " + dY
+ "\n" + "unprojectPt = " + unproject
+ "\n" + "unprojectPos = " + orbitView.getGlobe().computePositionFromPoint(unproject)
);
stepsNoAdjustments += 1;
return;
}
if (Math.abs(dX) <= EPS && Math.abs(dY) <= EPS)
{
stepsNoAdjustments += 1;
System.out.println("Mouse: No adjustment: " + " dX = " + dX + "\t" + " dY = " + dY);
return;
}
else
{
stepsNoAdjustments = 0;
}
// create reference points about 10px away from the center to the Up and to the Right
// and then map them to screen coordinates and geo coordinates
// Unfortunately unproject often generates points far from the Earth surface (and
// thus with significantly less difference in lat/long)
// So this longer but more fool-proof calculation is used
final double stepPx = 10;
Position centerPosition = orbitView.getCenterPosition();
Position eyePosition = orbitView.getEyePosition();
double pixelGeoSize = orbitView.computePixelSizeAtDistance(eyePosition.elevation - centerPosition.elevation);
Vec4 geoCenterPoint = orbitView.getCenterPoint();
Vec4 geoRightPoint = geoCenterPoint.add3(new Vec4(pixelGeoSize * stepPx, 0, 0));
Vec4 geoUpPoint = geoCenterPoint.add3(new Vec4(0, pixelGeoSize * stepPx, 0));
Position geoRightPosition = orbitView.getGlobe().computePositionFromPoint(geoRightPoint);
Position geoUpPosition = orbitView.getGlobe().computePositionFromPoint(geoUpPoint);
Vec4 controlCenter = orbitView.project(geoCenterPoint);
Vec4 controlRight = orbitView.project(geoRightPoint);
Vec4 controlUp = orbitView.project(geoUpPoint);
Vec4 controlRightDif = controlRight.subtract3(controlCenter);
controlRightDif = new Vec4(controlRightDif.x, controlRightDif.y); // ignore z for scale calculation
Vec4 controlUpDif = controlUp.subtract3(controlCenter);
controlUpDif = new Vec4(controlUpDif.x, controlUpDif.y); // ignore z for scale calculation
double scaleRight = -dTgt.dot3(controlRightDif) / controlRightDif.getLengthSquared3();
double scaleUp = -dTgt.dot3(controlUpDif) / controlUpDif.getLengthSquared3();
Position posRightDif = geoRightPosition.subtract(centerPosition);
Position posUpDif = geoUpPosition.subtract(centerPosition);
double totalLatDifDeg = posRightDif.latitude.degrees * scaleRight + posUpDif.latitude.degrees * scaleUp;
double totalLongDifDeg = posRightDif.longitude.degrees * scaleRight + posUpDif.longitude.degrees * scaleUp;
Position totalDif = Position.fromDegrees(totalLatDifDeg, totalLongDifDeg);
// don't copy elevation!
Position newCenterPosition = Position.fromDegrees(centerPosition.latitude.degrees + totalLatDifDeg,
centerPosition.longitude.degrees + totalLongDifDeg);
// if we overshot - try to slow down
if (!isValid(newCenterPosition))
{
newCenterPosition = Position.fromDegrees(centerPosition.latitude.degrees + totalLatDifDeg / 2,
centerPosition.longitude.degrees + totalLongDifDeg / 2);
if (!isValid(newCenterPosition))
{
System.out.println("Too much overshot: " + newCenterPosition);
stepsNoAdjustments += 1;
return;
}
}
System.out.println("Mouse:"
+ " dX = " + dX + "\t" + " dY = " + dY
+ "\n"
+ " centerPosition = " + centerPosition
+ "\n"
+ " geoUpPoint = " + geoUpPoint + "\t " + " geoUpPosition = " + geoUpPosition
+ "\n"
+ " geoRightPoint = " + geoRightPoint + "\t " + " geoRightPosition = " + geoRightPosition
+ "\n"
+ " posRightDif = " + posRightDif
+ "\t"
+ " posUpDif = " + posUpDif
+ "\n"
+ " scaleRight = " + scaleRight + "\t" + " scaleUp = " + scaleUp);
System.out.println("Mouse: oldCenterPosition = " + centerPosition);
System.out.println("Mouse: newCenterPosition = " + newCenterPosition);
orbitView.setCenterPosition(newCenterPosition);
}
}
<强>更新强>
FixZoomPositionAnimator
已更新,考虑到这样一个事实,即您可以假设经度和纬度线与X和Y平行。为了在中心周围的参考点周围工作用来计算调整。这意味着如果地球大小小于约20px(2*stepPx
)或者如果用户倾斜地球使得纬度/经度与X / Y显着不平行,则代码将不起作用。
更新结束
更新#2
我已将之前的逻辑移至nextOld
并添加了nextWithTilt
。即使世界倾斜,新功能也应该有效,但由于基本逻辑现在更加复杂,因此不再有加速,恕我直言会使更多典型情况更糟糕。还有nextWithTilt
内的日志记录日志,因为我不太确定它真的有效。使用风险自负。
更新结束#2
然后您可以将其用作
public class ZoomToCursorViewInputHandler extends OrbitViewInputHandler
{
public ZoomToCursorViewInputHandler()
{
ViewInputAttributes.ActionAttributes actionAttrs = this.getAttributes()
.getActionMap(ViewInputAttributes.DEVICE_MOUSE_WHEEL)
.getActionAttributes(ViewInputAttributes.VIEW_VERTICAL_TRANSLATE);
actionAttrs.setMouseActionListener(new ZoomActionHandler());
}
protected class ZoomActionHandler extends VertTransMouseWheelActionListener
{
@Override
public boolean inputActionPerformed(AbstractViewInputHandler inputHandler, MouseWheelEvent mouseWheelEvent,
final ViewInputAttributes.ActionAttributes viewAction)
{
double zoomInput = mouseWheelEvent.getWheelRotation();
Position position = wwd.getCurrentPosition();
Point mouseControlPoint = mouseWheelEvent.getPoint();
// Zoom toward the cursor if we're zooming in. Move straight out when zooming
// out.
if (zoomInput < 0 && position != null)
{
boolean res = super.inputActionPerformed(inputHandler, mouseWheelEvent, viewAction);
BasicOrbitView view = (BasicOrbitView) getView();
OrbitViewMoveToZoomAnimator zoomAnimator = (OrbitViewMoveToZoomAnimator) uiAnimControl.get(VIEW_ANIM_ZOOM);
// for continuous scroll preserve the original target if mouse was not moved
FixZoomPositionAnimator old = (FixZoomPositionAnimator) uiAnimControl.get(FixZoomPositionAnimator.VIEW_ANIM_KEY);
if (old != null && old.getMouseControlPoint().equals(mouseControlPoint))
{
position = old.getMouseGeoLocation();
}
FixZoomPositionAnimator fixZoomPositionAnimator = new FixZoomPositionAnimator(view, zoomAnimator, mouseControlPoint, position);
fixZoomPositionAnimator.start();
uiAnimControl.put(FixZoomPositionAnimator.VIEW_ANIM_KEY, fixZoomPositionAnimator);
return res;
}
else
{
uiAnimControl.remove(FixZoomPositionAnimator.VIEW_ANIM_KEY); // when zoom direction changes we don't want to make position adjustments anymore
return super.inputActionPerformed(inputHandler, mouseWheelEvent, viewAction);
}
}
}
// here goes aforementioned FixZoomPositionAnimator
}