Nach einigem Nachdenken über dieses Problem glaube ich, dass es keine geschlossene Form analytische Lösung dafür gibt. Man muss nur zu viele Dinge berücksichtigen: Form der Erde, wie sich das "Auge" bewegt, wenn man das Zentrum bewegt. Also, der beste Trick, den ich denke, ist, der "Zoom" -Animation zu folgen und kleine Anpassungen nach jedem Animationsschritt vorzunehmen. Da Animationsschritte klein sind, sollten Berechnungsfehler auch kleiner sein und sie sollten weniger akkumulieren, weil Sie im nächsten Schritt alle vorherigen Fehler berücksichtigen. Also meine Idee im Code folgt grob: Erstellen Sie eine FixZoomPositionAnimator
Klasse als
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);
}
}
aktualisieren
FixZoomPositionAnimator
wurde aktualisiert, um die Tatsache zu berücksichtigen, dass man eine große Skala Sie nicht, dass die Länge annehmen kann und Breitenlinien verlaufen parallel zu X und Y. Um dies zu umgehen, werden Referenzpunkte um das Zentrum herum verwendet, um die Anpassung zu berechnen. Dies bedeutet, dass der Code nicht funktioniert, wenn die Globusgröße weniger als 20px (2*stepPx
) beträgt oder wenn der Benutzer die Erde gekippt hat, wodurch der Breitengrad/Längengrad deutlich nicht parallel zu X/Y ist.
Ende des Update
Update # 2
Ich habe vorherige Logik nextOld
bewegt und hinzugefügt nextWithTilt
. Die neue Funktion sollte funktionieren, auch wenn die Welt geneigt ist, aber da die Basislogik jetzt komplizierter ist, gibt es keine Beschleunigung mehr, was IMHO für typische Fälle etwas schlimmer macht. Auch gibt es noch ein Protokoll der Protokollierung innerhalb nextWithTilt
, weil ich nicht ganz sicher bin, dass es wirklich funktioniert, OK. Benutzung auf eigene Gefahr.
Ende Update # 2
und dann können Sie es verwenden, als
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
}
Versuchen mit delta = delta.multiply3 (zoomChange - 1); anstelle von delta = delta.multiply3 (-zoomChange); – vsoni
Ich habe das versucht und es scheint nicht zu funktionieren.Ist das die einzige Änderung zu versuchen? – systemoutprintln
Versuchen Sie delta = delta.multipl3 ((zoomChange - 1)/zoomChange); Noch eine Sache, bevor Sie das versuchen, wollte wissen, welchen Wertwert Sie in ZoomChange bekommen? Ist es absolut oder%? wenn ich sage, 150% zu vergrößern ... dann bekommst du 1,5 oder 150? Wenn Sie abs, d. H. 1,5 erhalten, dann sollte diese neue Änderung funktionieren. Wenn Sie 150 erhalten, müssen Sie es durch 100 teilen, um den ABS-Wert 1,5 zu erhalten. – vsoni