org.oscim.map.Animator2 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vtm Show documentation
Show all versions of vtm Show documentation
OpenGL vector map library written in Java - running on Android, iOS, Desktop and within the browser.
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2016 Stephan Leuschner
* Copyright 2016 devemux86
* Copyright 2016 Izumi Kawashima
* Copyright 2017 Wolfgang Schramm
* Copyright 2018 Gustl22
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see .
*/
package org.oscim.map;
import org.oscim.backend.CanvasAdapter;
import org.oscim.core.Point;
import org.oscim.core.Tile;
import org.oscim.renderer.MapRenderer;
import org.oscim.utils.ThreadUtils;
import org.oscim.utils.animation.DragForce;
import org.oscim.utils.animation.Easing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.oscim.utils.FastMath.clamp;
public class Animator2 extends Animator {
private static final Logger log = LoggerFactory.getLogger(Animator2.class);
private static final int ANIM_KINETIC = 1 << 5;
/**
* The minimum changes that are pleasant for users.
*/
private static final float DEFAULT_MIN_VISIBLE_CHANGE_PIXELS = 0.6f;
private static final float DEFAULT_MIN_VISIBLE_CHANGE_RADIAN = 0.001f;
private static final float DEFAULT_MIN_VISIBLE_CHANGE_SCALE = 1f;
/**
* The friction scalar for fling movements (1 as base).
*/
public static float FLING_FRICTION_MOVE = 1.0f;
/**
* The friction scalar for fling rotations (1 as base).
*/
public static float FLING_FRICTION_ROTATE = 1.2f;
/**
* The friction scalar for fling scales (1 as base).
*/
public static float FLING_FRICTION_SCALE = 1.2f;
private final DragForce mFlingRotateForce = new DragForce();
private final DragForce mFlingScaleForce = new DragForce();
private final DragForce mFlingScrollForce = new DragForce();
private final Point mMovePoint = new Point();
private final Point mScrollRatio = new Point();
private long mFrameStart = -1;
private float mScrollDet2D = 1f;
public Animator2(Map map) {
super(map);
// Init fling force thresholds
mFlingRotateForce.setValueThreshold(DEFAULT_MIN_VISIBLE_CHANGE_RADIAN);
mFlingScrollForce.setValueThreshold(DEFAULT_MIN_VISIBLE_CHANGE_PIXELS);
mFlingScaleForce.setValueThreshold(DEFAULT_MIN_VISIBLE_CHANGE_SCALE);
}
/**
* Animates a physical fling for rotations.
*
* @param angularVelocity angular velocity in radians
*/
public void animateFlingRotate(float angularVelocity, float pivotX, float pivotY) {
ThreadUtils.assertMainThread();
float flingFactor = -0.25f; // Can be changed but should be standardized for all callers
angularVelocity *= flingFactor;
mFlingRotateForce.setFrictionScalar(FLING_FRICTION_ROTATE);
mFlingRotateForce.setValueAndVelocity(0f, angularVelocity);
if (!isActive()) {
mMap.getMapPosition(mStartPos);
mPivot.x = pivotX;
mPivot.y = pivotY;
animFlingStart(ANIM_ROTATE);
} else {
mState |= ANIM_ROTATE;
}
}
/**
* Animates a physical fling for scrolls.
*
* @param velocityX the x velocity depends on screen resolution
* @param velocityY the y velocity depends on screen resolution
*/
public void animateFlingScroll(float velocityX, float velocityY,
int xmin, int xmax, int ymin, int ymax) {
ThreadUtils.assertMainThread();
if (velocityX * velocityX + velocityY * velocityY < 2048)
return;
float flingFactor = 2.0f; // Can be changed but should be standardized for all callers
float screenFactor = CanvasAdapter.DEFAULT_DPI / CanvasAdapter.dpi;
velocityX *= screenFactor * flingFactor;
velocityY *= screenFactor * flingFactor;
velocityX = clamp(velocityX, xmin, xmax);
velocityY = clamp(velocityY, ymin, ymax);
float sumVelocity = Math.abs(velocityX) + Math.abs(velocityY);
mScrollRatio.x = velocityX / sumVelocity;
mScrollRatio.y = velocityY / sumVelocity;
mScrollDet2D = (float) (mScrollRatio.x * mScrollRatio.x + mScrollRatio.y * mScrollRatio.y);
mFlingScrollForce.setFrictionScalar(FLING_FRICTION_MOVE);
mFlingScrollForce.setValueAndVelocity(0f, (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
if (!isActive()) {
mMap.getMapPosition(mStartPos);
animFlingStart(ANIM_MOVE);
} else {
mState |= ANIM_MOVE;
}
}
/**
* Animates a physical fling for zooms.
*
* @param scaleVelocity the scale velocity depends on screen resolution
*/
public void animateFlingZoom(float scaleVelocity, float pivotX, float pivotY) {
ThreadUtils.assertMainThread();
float flingFactor = -1.0f; // Can be changed but should be standardized for all callers
float screenFactor = CanvasAdapter.DEFAULT_DPI / CanvasAdapter.dpi;
scaleVelocity *= flingFactor * screenFactor;
mFlingScaleForce.setFrictionScalar(FLING_FRICTION_SCALE);
mFlingScaleForce.setValueAndVelocity(0f, scaleVelocity);
if (!isActive()) {
mMap.getMapPosition(mStartPos);
mPivot.x = pivotX;
mPivot.y = pivotY;
animFlingStart(ANIM_SCALE);
} else {
mState |= ANIM_SCALE;
}
}
private void animFlingStart(int state) {
if (!isActive())
mMap.events.fire(Map.ANIM_START, mMap.mMapPosition);
mCurPos.copy(mStartPos);
mState |= ANIM_FLING | state;
mFrameStart = MapRenderer.frametime; // CurrentTimeMillis would cause negative delta
mMap.render();
}
/**
* Alternative implementation of Animator's animateFling
.
* Uses scheme of predictable animations using mDeltaPos.
*
* @param velocityX the x velocity depends on screen resolution
* @param velocityY the y velocity depends on screen resolution
*/
public void kineticScroll(float velocityX, float velocityY,
int xmin, int xmax, int ymin, int ymax) {
ThreadUtils.assertMainThread();
if (velocityX * velocityX + velocityY * velocityY < 2048)
return;
mMap.getMapPosition(mStartPos);
float duration = 500;
float screenFactor = CanvasAdapter.DEFAULT_DPI / CanvasAdapter.dpi;
velocityX = velocityX * screenFactor;
velocityY = velocityY * screenFactor;
velocityX = clamp(velocityX, xmin, xmax);
velocityY = clamp(velocityY, ymin, ymax);
if (Float.isNaN(velocityX) || Float.isNaN(velocityY)) {
log.debug("fling NaN!");
return;
}
double tileScale = mStartPos.scale * Tile.SIZE;
ViewController.applyRotation(-velocityX, -velocityY, mStartPos.bearing, mMovePoint);
mDeltaPos.setX(mMovePoint.x / tileScale);
mDeltaPos.setY(mMovePoint.y / tileScale);
animStart(duration, ANIM_KINETIC | ANIM_MOVE, Easing.Type.SINE_OUT);
}
/**
* called by MapRenderer at begin of each frame.
*/
@Override
void updateAnimation() {
if (mState == ANIM_NONE)
return;
ViewController v = mMap.viewport();
/* cancel animation when position was changed since last
* update, i.e. when it was modified outside the animator. */
if (v.getMapPosition(mCurPos)) {
log.debug("cancel anim - changed");
cancel();
return;
}
final long currentFrametime = MapRenderer.frametime;
if ((mState & ANIM_FLING) == 0) {
// Do predicted animations
long millisLeft = mAnimEnd - currentFrametime;
float adv = clamp(1.0f - millisLeft / mDuration, 1E-6f, 1);
// Avoid redundant calculations in case of linear easing
if (mEasingType != Easing.Type.LINEAR) {
adv = Easing.ease(0, (long) (adv * Long.MAX_VALUE), Long.MAX_VALUE, mEasingType);
adv = clamp(adv, 0, 1);
}
double scaleAdv = 1;
if ((mState & ANIM_SCALE) != 0) {
scaleAdv = doScale(v, adv);
}
if ((mState & ANIM_KINETIC) != 0) {
// Reduce value to simulate kinetic behaviour
adv = (float) Math.sqrt(adv);
}
if ((mState & ANIM_MOVE) != 0) {
v.moveTo(mStartPos.x + mDeltaPos.x * (adv / scaleAdv),
mStartPos.y + mDeltaPos.y * (adv / scaleAdv));
}
if ((mState & ANIM_ROTATE) != 0) {
v.setRotation(mStartPos.bearing + mDeltaPos.bearing * adv);
}
if ((mState & ANIM_TILT) != 0) {
v.setTilt(mStartPos.tilt + mDeltaPos.tilt * adv);
}
if (millisLeft <= 0) {
//log.debug("animate END");
cancel();
}
} else {
// Do physical fling animation
long deltaT = currentFrametime - mFrameStart;
mFrameStart = currentFrametime;
if ((mState & ANIM_SCALE) != 0) {
float valueDelta = mFlingScaleForce.updateValueAndVelocity(deltaT) / 1000f;
float velocity = mFlingScaleForce.getVelocity();
if (valueDelta != 0) {
valueDelta = valueDelta > 0 ? valueDelta + 1 : -1 / (valueDelta - 1);
v.scaleMap(valueDelta, (float) mPivot.x, (float) mPivot.y);
}
if (velocity == 0) {
mState &= (~ANIM_SCALE); // End scale mode
}
}
if ((mState & ANIM_MOVE) != 0) {
float valueDelta = mFlingScrollForce.updateValueAndVelocity(deltaT);
float velocity = mFlingScrollForce.getVelocity();
float valFactor = (float) Math.sqrt((valueDelta * valueDelta) / mScrollDet2D);
float dx = (float) mScrollRatio.x * valFactor;
float dy = (float) mScrollRatio.y * valFactor;
if (dx != 0 || dy != 0) {
v.moveMap(dx, dy);
}
if (velocity == 0) {
mState &= (~ANIM_MOVE); // End move mode
}
}
if ((mState & ANIM_ROTATE) != 0) {
float valueDelta = mFlingRotateForce.updateValueAndVelocity(deltaT);
float velocity = mFlingRotateForce.getVelocity();
v.rotateMap(valueDelta, (float) mPivot.x, (float) mPivot.y);
if (velocity == 0) {
mState &= (~ANIM_ROTATE); // End rotate mode
}
}
/*if ((mState & ANIM_TILT) != 0) {
// Do some tilt fling
if(velocity == 0) {
mState &= (~ANIM_TILT); // End tilt mode
}
}*/
if ((mState & (ANIM_MOVE | ANIM_ROTATE | ANIM_SCALE)) == 0) {
//log.debug("animate END");
cancel();
}
}
/* remember current map position */
final boolean changed = v.getMapPosition(mCurPos);
if (changed) {
mMap.updateMap(true);
} else {
mMap.postDelayed(updateTask, 10);
}
}
}