/* * Aphelion * Copyright (c) 2013 Joris van der Wel * * This file is part of Aphelion * * Aphelion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * Aphelion 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 General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Aphelion. If not, see <http://www.gnu.org/licenses/>. * * In addition, the following supplemental terms apply, based on section 7 of * the GNU Affero General Public License (version 3): * a) Preservation of all legal notices and author attributions * b) Prohibition of misrepresentation of the origin of this material, and * modified versions are required to be marked in reasonable ways as * different from the original version (for example by appending a copyright notice). * * Linking this library statically or dynamically with other modules is making a * combined work based on this library. Thus, the terms and conditions of the * GNU Affero General Public License cover the whole combination. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent modules, * and to copy and distribute the resulting executable under terms of your * choice, provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. An independent * module is a module which is not derived from or based on this library. */ package aphelion.shared.physics.valueobjects; import aphelion.shared.gameconfig.GCInteger; /** * Used to provide dead reckon convergence / smoothing. * Note that the results of this object might not be deterministic! * Also, the output depends on the order that the input is given (this is how dead * reckon convergence works). * It should not used be used for things that need strong consistency across clients, * or the results should be checked by later states for consistency, (eventual * consistency). * * @author Joris */ public class PhysicsPointHistorySmooth { private final PhysicsPointHistory positionHist; private final PhysicsPointHistory velocityHist; private final int HISTORY_LENGTH; /** The "baseline" is used so that multiple changes in the position do not cause multiple * steps in the convergence */ private final PhysicsPointHistory baseline; private final PhysicsPointHistory smooth; private SMOOTHING_ALGORITHM algorithm = SMOOTHING_ALGORITHM.NONE; private int lookAheadTicks = 20; private int stepRatio = 5000; private long smoothLimitDistanceSq = 10_000 * 10_000; public PhysicsPointHistorySmooth(long initial_tick, PhysicsPointHistory positionHist, PhysicsPointHistory velocityHist) { HISTORY_LENGTH = positionHist.HISTORY_LENGTH; assert HISTORY_LENGTH == velocityHist.HISTORY_LENGTH; // use the same index for ticks as positionHist for easy implementation this.positionHist = positionHist; this.velocityHist = velocityHist; positionHist.setListener(posListener); velocityHist.setListener(posListener); baseline = new PhysicsPointHistory(initial_tick, HISTORY_LENGTH); smooth = new PhysicsPointHistory(initial_tick, HISTORY_LENGTH); } public void setAlgorithm(SMOOTHING_ALGORITHM algorithm) { this.algorithm = algorithm; } public void setLookAheadTicks(int lookAheadTicks) { this.lookAheadTicks = lookAheadTicks; } /** * @param permille A ratio between 0 and 1048576 */ public void setStepRatio(int permille) { this.stepRatio = permille; } public void setSmoothLimitDistance(int smoothLimitDistance) { this.smoothLimitDistanceSq = (long) smoothLimitDistance * (long) smoothLimitDistance; } /** This method should be called before each iteration of the state. * It ensures that setting the actor position multiple times within the same state iteration, * has the exact same result as setting it only once. */ public void updateBaseLine() { baseline.set(smooth); } public void getSmooth(PhysicsPoint ret, long tick) { smooth.get(ret, tick); } public int getX(long tick) { return smooth.getX(tick); } public int getY(long tick) { return smooth.getY(tick); } private void calculate(long tick) { PhysicsPoint base = new PhysicsPoint(); PhysicsPoint desired = new PhysicsPoint(); PhysicsPoint velocity = new PhysicsPoint(); baseline.get(base, tick - 1); positionHist.get(desired, tick); velocityHist.get(velocity, tick); smooth.setHistory(tick, desired); if (base.set && base.distanceSquared(desired) <= smoothLimitDistanceSq) { if (algorithm == SMOOTHING_ALGORITHM.NONE) { } else if (algorithm == SMOOTHING_ALGORITHM.LINEAR && lookAheadTicks > 0) { PhysicsPoint smoothed = new PhysicsPoint(); smoothed.set(velocity); smoothed.multiply(lookAheadTicks); smoothed.add(desired); smoothed.sub(base); smoothed.applyRatio(stepRatio, GCInteger.RATIO_PRECISE); smoothed.add(base); if (base.distanceSquared(desired) <= base.distanceSquared(smoothed) ) { // do not bother with smoothed if the real position is closer! smoothed.set(desired); } smooth.setHistory(tick, smoothed); } } } private final PhysicsPointHistory.UpdateListener posListener = new PhysicsPointHistory.UpdateListener() { @Override public void updated(long tick, int index) { calculate(tick); } @Override public void updatedAll() { for (long t = positionHist.getLowestTick(); t <= positionHist.getHighestTick(); ++t) { calculate(t); } } }; public static enum SMOOTHING_ALGORITHM { NONE, LINEAR; // quadratic, cubic spline, etc } }