All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dermetfan.gdx.physics.box2d.WorldObserver Maven / Gradle / Ivy

The newest version!
/** Copyright 2014 Robin Stumm ([email protected], http://dermetfan.net)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License. */

package net.dermetfan.gdx.physics.box2d;

import java.util.Objects;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.Filter;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.Joint;
import com.badlogic.gdx.physics.box2d.JointDef.JointType;
import com.badlogic.gdx.physics.box2d.MassData;
import com.badlogic.gdx.physics.box2d.Transform;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.joints.DistanceJoint;
import com.badlogic.gdx.physics.box2d.joints.FrictionJoint;
import com.badlogic.gdx.physics.box2d.joints.GearJoint;
import com.badlogic.gdx.physics.box2d.joints.MotorJoint;
import com.badlogic.gdx.physics.box2d.joints.MouseJoint;
import com.badlogic.gdx.physics.box2d.joints.PrismaticJoint;
import com.badlogic.gdx.physics.box2d.joints.RevoluteJoint;
import com.badlogic.gdx.physics.box2d.joints.RopeJoint;
import com.badlogic.gdx.physics.box2d.joints.WeldJoint;
import com.badlogic.gdx.physics.box2d.joints.WheelJoint;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.IntMap.Entry;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.Pool.Poolable;
import com.badlogic.gdx.utils.Pools;

/** Notifies a {@link Listener} of changes in the world.
 *  Does NOT work on HTML5!
 *  @author dermetfan
 *  @since 0.6.0 */
public class WorldObserver {

	/** The Listener to notify. May be null. */
	private Listener listener;

	/** the WorldChange used to track the World */
	private final WorldChange worldChange = new WorldChange();

	/** the BodyChanges used to track Bodies, keys are hashes computed by {@link com.badlogic.gdx.physics.box2d.Box2DUtils#hashCode(Body) Box2DUtils#hashCode(Body)} because a World pools its Bodies */
	private final IntMap bodyChanges = new IntMap<>();

	/** the FixtureChanges used to track Fixtures, keys are hashes computed by {@link com.badlogic.gdx.physics.box2d.Box2DUtils#hashCode(Fixture) Box2DUtils#hashCode(Fixture)} because a world pools its Fixtures */
	private final IntMap fixtureChanges = new IntMap<>();

	/** the JointChanges used to track Joints */
	private final ObjectMap jointChanges = new ObjectMap<>();

	/** temporary array used internally */
	private final Array tmpBodies = new Array<>();

	/** the Bodies by {@link com.badlogic.gdx.physics.box2d.Box2DUtils#hashCode(Body) hash} since this/the last time {@link #update(World, float)} was called */
	private final IntMap currentBodies = new IntMap<>(), previousBodies = new IntMap<>();

	/** the Fixtures by {@link com.badlogic.gdx.physics.box2d.Box2DUtils#hashCode(Fixture) hash} since this/the last time {@link #update(World, float)} was called */
	private final IntMap currentFixtures = new IntMap<>(), previousFixtures = new IntMap<>();

	/** the Joints since this/the last time {@link #update(World, float)} was called  */
	private final Array currentJoints = new Array<>(), previousJoints = new Array<>();

	/** creates a new WorldObserver with no {@link #listener} */
	public WorldObserver() {}

	/** @param listener the {@link #listener} */
	public WorldObserver(Listener listener) {
		setListener(listener);
	}

	/** @param world Ideally always the same World because its identity is not checked. Passing in another world instance will cause all differences between the two worlds to be processed.
	 *  @param step the time the world was last {@link World#step(float, int, int) stepped} with */
	public void update(World world, float step) {
		if(listener != null)
			listener.preUpdate(world, step);

		if(worldChange.update(world) && listener != null)
			listener.changed(world, worldChange);

		// destructions
		world.getBodies(tmpBodies);
		currentBodies.clear();
		currentFixtures.clear();
		for(Body body : tmpBodies) {
			currentBodies.put(com.badlogic.gdx.physics.box2d.Box2DUtils.hashCode(body), body);
			for(Fixture fixture : body.getFixtureList())
				currentFixtures.put(com.badlogic.gdx.physics.box2d.Box2DUtils.hashCode(fixture), fixture);
		}
		for(Entry entry : previousBodies.entries()) {
			if(!currentBodies.containsKey(entry.key)) {
				Pools.free(bodyChanges.remove(entry.key));
				if(listener != null)
					listener.destroyed(entry.value);
			}
		}
		previousBodies.clear();
		previousBodies.putAll(currentBodies);

		for(Entry entry : previousFixtures.entries()) {
			if(!currentFixtures.containsKey(entry.key)) {
				Pools.free(fixtureChanges.get(entry.key));
				if(listener != null)
					listener.destroyed(entry.value);
			}
		}
		previousFixtures.clear();
		previousFixtures.putAll(currentFixtures);

		// changes and creations
		for(Entry entry : currentBodies.entries()) {
			BodyChange bodyChange = bodyChanges.get(entry.key);
			if(bodyChange != null) {
				if(bodyChange.update(entry.value) && listener != null)
					listener.changed(entry.value, bodyChange);
			} else {
				bodyChange = Pools.obtain(BodyChange.class);
				bodyChange.update(entry.value);
				bodyChanges.put(entry.key, bodyChange);
				if(listener != null)
					listener.created(entry.value);
			}
		}
		for(Entry entry : currentFixtures.entries()) {
			FixtureChange fixtureChange = fixtureChanges.get(entry.key);
			if(fixtureChange != null) {
				if(fixtureChange.update(entry.value) && listener != null)
					listener.changed(entry.value, fixtureChange);
			} else {
				fixtureChange = Pools.obtain(FixtureChange.class);
				fixtureChange.created(entry.value.getBody());
				fixtureChange.update(entry.value);
				fixtureChanges.put(entry.key, fixtureChange);
				if(listener != null)
					listener.created(entry.value);
			}
		}

		// check for new or updated joints
		world.getJoints(currentJoints);
		for(Joint joint : currentJoints) {
			@SuppressWarnings("unchecked")
			JointChange jointChange = jointChanges.get(joint);
			if(jointChange != null) { // updated
				if(jointChange.update(joint) && listener != null)
					listener.changed(joint, jointChange);
			} else { // new
				@SuppressWarnings("unchecked")
				JointChange newJointChange = JointChange.obtainFor(joint.getType());
				newJointChange.update(joint);
				jointChanges.put(joint, newJointChange);
				if(listener != null)
					listener.created(joint);
			}
		}
		// check for destroyed joints
		previousJoints.removeAll(currentJoints, true);
		for(Joint joint : previousJoints) {
			JointChange change = jointChanges.remove(joint);
			assert change != null;
			Pools.free(change);
			if(listener != null)
				listener.destroyed(joint);
		}
		previousJoints.clear();
		previousJoints.addAll(currentJoints);

		if(listener != null)
			listener.postUpdate(world, step);
	}

	/** @param hash the hash of the Body (computed via {@link com.badlogic.gdx.physics.box2d.Box2DUtils#hashCode(Body) Box2DUtils#hashCode(Body)}) which associated BodyChange to return
	 *  @return the BodyChange from {@link #bodyChanges} currently used for the Body with the given hash, or null if not found */
	public BodyChange getBodyChange(int hash) {
		return bodyChanges.get(hash);
	}

	/** @param hash the hash of the Fixture (computed via {@link com.badlogic.gdx.physics.box2d.Box2DUtils#hashCode(Fixture) Box2DUtils#hashCode(Fixture)}) which associated FixtureChange to return
	 *  @return the FixtureChange from {@link #fixtureChanges} currently used for the Fixture with the given hash, or null if not found */
	public FixtureChange getFixtureChange(int hash) {
		return fixtureChanges.get(hash);
	}

	/** @param joint the joint which associated JointChange to return
	 *  @return the JointChange from {@link #jointChanges} currently used for the given Joint */
	public JointChange getJointChange(Joint joint) {
		return jointChanges.get(joint);
	}

	// getters and setters

	/** @return the {@link #worldChange} */
	public WorldChange getWorldChange() {
		return worldChange;
	}

	/** @return the {@link #listener} */
	public Listener getListener() {
		return listener;
	}

	/** @param listener the {@link #listener} to set */
	public void setListener(Listener listener) {
		if(this.listener != null)
			this.listener.removedFrom(this);
		this.listener = listener;
		if(listener != null)
			listener.setOn(this);
	}

	/** the listener notified by a {@link WorldObserver}
	 *  @author dermetfan
	 *  @since 0.6.0 */
	public interface Listener {

		/** @param observer the WorldObserver this Listener has just been {@link WorldObserver#setListener(Listener) set} on */
		void setOn(WorldObserver observer);

		/** @param observer the WorldObserver this Listener has just been {@link WorldObserver#setListener(Listener) removed} from */
		void removedFrom(WorldObserver observer);

		/** called at the very beginning of {@link WorldObserver#update(World, float)} */
		void preUpdate(World world, float step);

		/** called at the very end of {@link WorldObserver#update(World, float)} */
		void postUpdate(World world, float step);

		/** @param world the World that changed
		 *  @param change the change */
		void changed(World world, WorldChange change);

		/** @param body the Body that changed
		 *  @param change the change */
		void changed(Body body, BodyChange change);

		/** @param body the created Body */
		void created(Body body);

		/** @param body the destroyed Body */
		void destroyed(Body body);

		/** @param fixture the Fixture that changed
		 *  @param change the change */
		void changed(Fixture fixture, FixtureChange change);

		/** @param fixture the created Fixture */
		void created(Fixture fixture);

		/** @param fixture the destroyed Fixture */
		void destroyed(Fixture fixture);

		/** @param joint the Joint that changed
		 *  @param change the change */
		void changed(Joint joint, JointChange change);

		/** @param joint the created Joint */
		void created(Joint joint);

		/** @param joint the destroyed Joint */
		void destroyed(Joint joint);

		/** A class that implements Listener. Does nothing. Subclass this if you only want to override some methods.
		 *  @author dermetfan
		 *  @since 0.7.1 */
		class Adapter implements Listener {

			@Override
			public void setOn(WorldObserver observer) {}

			@Override
			public void removedFrom(WorldObserver observer) {}

			@Override
			public void preUpdate(World world, float step) {}

			@Override
			public void postUpdate(World world, float step) {}

			@Override
			public void changed(World world, WorldChange change) {}

			@Override
			public void changed(Body body, BodyChange change) {}

			@Override
			public void created(Body body) {}

			@Override
			public void destroyed(Body body) {}

			@Override
			public void changed(Fixture fixture, FixtureChange change) {}

			@Override
			public void created(Fixture fixture) {}

			@Override
			public void destroyed(Fixture fixture) {}

			@Override
			public void changed(Joint joint, JointChange change) {}

			@Override
			public void created(Joint joint) {}

			@Override
			public void destroyed(Joint joint) {}

		}

	}

	/** A Listener that calls another Listener on unpredictable/unexpected events.
	 *  In practice only {@link #changed(Body, WorldObserver.BodyChange)} can be predicted and therefore the other methods will be called normally.
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class UnexpectedListener implements Listener {

		/** the Listener to notify */
		private Listener listener;

		/** the ExpectationBases mapped to their Bodies */
		private final ObjectMap bases = new ObjectMap<>();

		/** the Pool used by this UnexpectedListener */
		private final Pool pool = new Pool(5, 75) {
			@Override
			protected ExpectationBase newObject() {
				return new ExpectationBase();
			}
		};

		/** the last time step */
		private float step;

		/** @param listener the {@link #listener} to set */
		public UnexpectedListener(Listener listener) {
			this.listener = listener;
		}

		@Override
		public void changed(Body body, BodyChange change) {
			boolean unexpected = change.newType != null || change.newAngularDamping != null || change.newGravityScale != null || change.newMassData != null || change.userDataChanged;
			ExpectationBase base = bases.get(body);
			if(!unexpected && change.newLinearVelocity != null && !change.newLinearVelocity.equals(base.linearVelocity.mulAdd(body.getWorld().getGravity(), step).scl(1 / (1 + step * body.getLinearDamping()))))
				unexpected = true;
			else if(change.newTransform != null && // the linear damping of the body must be applied to the linear velocity of the base already
					change.newTransform.vals[Transform.POS_X] != base.transform.vals[Transform.POS_X] + base.linearVelocity.x * step &&
					change.newTransform.vals[Transform.POS_Y] != base.transform.vals[Transform.POS_Y] + base.linearVelocity.y * step)
				unexpected = true;
			else if(change.newAngularVelocity != null && change.newAngularVelocity != base.angularVelocity * (1 / (1 + step * body.getAngularDamping())))
				unexpected = true;
			base.set(body);
			if(unexpected)
				listener.changed(body, change);
		}

		// always unexpected

		@Override
		public void setOn(WorldObserver observer) {
			listener.setOn(observer);
		}

		@Override
		public void removedFrom(WorldObserver observer) {
			listener.removedFrom(observer);
		}

		@Override
		public void preUpdate(World world, float step) {
			listener.preUpdate(world, this.step = step);
		}

		@Override
		public void postUpdate(World world, float step) {
			listener.postUpdate(world, this.step = step);
		}

		@Override
		public void changed(World world, WorldChange change) {
			listener.changed(world, change);
		}

		@Override
		public void created(Body body) {
			bases.put(body, pool.obtain().set(body));
			listener.created(body);
		}

		@Override
		public void destroyed(Body body) {
			pool.free(bases.remove(body));
			listener.destroyed(body);
		}

		@Override
		public void changed(Fixture fixture, FixtureChange change) {
			listener.changed(fixture, change);
		}

		@Override
		public void created(Fixture fixture) {
			listener.created(fixture);
		}

		@Override
		public void destroyed(Fixture fixture) {
			listener.destroyed(fixture);
		}

		@Override
		public void changed(Joint joint, JointChange change) {
			listener.changed(joint, change);
		}

		@Override
		public void created(Joint joint) {
			listener.created(joint);
		}

		@Override
		public void destroyed(Joint joint) {
			listener.destroyed(joint);
		}

		// getters and setters

		/** @return the {@link #listener} */
		public Listener getListener() {
			return listener;
		}

		/** @param listener the {@link #listener} to set */
		public void setListener(Listener listener) {
			this.listener = listener;
		}

		/** Only for internal use. Stores the last change of predictable data.
		 *  @author dermetfan
		 *  @since 0.7.1 */
		private static class ExpectationBase implements Poolable {

			final Transform transform = new Transform();
			final Vector2 linearVelocity = new Vector2();
			float angularVelocity;

			public ExpectationBase set(Body body) {
				Transform bodyTransform = body.getTransform();
				transform.vals[Transform.POS_X] = bodyTransform.vals[Transform.POS_X];
				transform.vals[Transform.POS_Y] = bodyTransform.vals[Transform.POS_Y];
				transform.vals[Transform.COS] = bodyTransform.vals[Transform.COS];
				transform.vals[Transform.SIN] = bodyTransform.vals[Transform.SIN];
				linearVelocity.set(body.getLinearVelocity());
				angularVelocity = body.getAngularVelocity();
				return this;
			}

			@Override
			public void reset() {
				transform.vals[Transform.POS_X] = transform.vals[Transform.POS_Y] = transform.vals[Transform.COS] = transform.vals[Transform.SIN] = 0;
				angularVelocity = 0;
			}

		}

	}

	/** the changes of an object in a world since the last time {@link #update(Object)} was called
	 *  @author dermetfan
	 *  @since 0.6.0 */
	public interface Change extends Poolable {

		/** @param obj the object to check for changes since the last time this method was called
		 *  @return if anything changed */
		boolean update(T obj);

		/** @param obj the object to apply the changes since {@link #update(Object)} to */
		void apply(T obj);

		/** if the values applied in {@link #apply(Object)} equal */
		> boolean newValuesEqual(C other);

	}

	/** the changes of a {@link World}
	 *  @author dermetfan
	 *  @since 0.6.0 */
	public static class WorldChange implements Change {

		private transient Boolean oldAutoClearForces;
		private transient final Vector2 oldGravity = new Vector2();

		public Boolean newAutoClearForces;
		public Vector2 newGravity;

		@Override
		public boolean update(World world) {
			Boolean autoClearForces = world.getAutoClearForces();
			Vector2 gravity = world.getGravity();

			boolean changed = false;

			if(!autoClearForces.equals(oldAutoClearForces)) {
				oldAutoClearForces = newAutoClearForces = autoClearForces;
				changed = true;
			} else
				newAutoClearForces = null;
			if(!gravity.equals(oldGravity)) {
				oldGravity.set(newGravity = gravity);
				changed = true;
			} else
				newAutoClearForces = null;

			return changed;
		}

		@Override
		public void apply(World world) {
			if(newAutoClearForces != null)
				world.setAutoClearForces(newAutoClearForces);
			if(newGravity != null)
				world.setGravity(newGravity);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof WorldChange))
				return false;
			WorldChange o = (WorldChange) other;
			boolean diff = !Objects.equals(newAutoClearForces, o.newAutoClearForces);
			diff |= !Objects.equals(newGravity, o.newGravity);
			return diff;
		}

		@Override
		public void reset() {
			oldAutoClearForces = null;
			oldGravity.setZero();

			newAutoClearForces = null;
			newGravity = null;
		}

	}

	/** the changes of a {@link Body}
	 *  @author dermetfan
	 *  @since 0.6.0 */
	public static class BodyChange implements Change {

		private transient final Transform oldTransform = new Transform();
		private transient BodyType oldType;
		private transient float oldAngularDamping;
		private transient float oldAngularVelocity;
		private transient float oldLinearDamping;
		private transient float oldGravityScale;
		private transient final Vector2 oldLinearVelocity = new Vector2();
		private transient final MassData oldMassData = new MassData();
		private transient boolean oldFixedRotation;
		private transient boolean oldBullet;
		private transient boolean oldAwake;
		private transient boolean oldActive;
		private transient boolean oldSleepingAllowed;
		private transient Object oldUserData;

		public Transform newTransform;
		public BodyType newType;
		public Float newAngularDamping;
		public Float newAngularVelocity;
		public Float newLinearDamping;
		public Float newGravityScale;
		public Vector2 newLinearVelocity;
		public MassData newMassData;
		public Boolean newFixedRotation;
		public Boolean newBullet;
		public Boolean newAwake;
		public Boolean newActive;
		public Boolean newSleepingAllowed;
		public Object newUserData;

		/** if the {@link Body#userData} changed */
		private boolean userDataChanged;

		private void updateOldTransform(Transform transform) {
			oldTransform.vals[Transform.POS_X] = transform.vals[Transform.POS_X];
			oldTransform.vals[Transform.POS_Y] = transform.vals[Transform.POS_Y];
			oldTransform.vals[Transform.COS] = transform.vals[Transform.COS];
			oldTransform.vals[Transform.SIN] = transform.vals[Transform.SIN];
		}

		private void updateOldMassData(MassData massData) {
			oldMassData.center.set(massData.center);
			oldMassData.mass = massData.mass;
			oldMassData.I = massData.I;
		}

		@Override
		public boolean update(Body body) {
			Transform transform = body.getTransform();
			BodyType type = body.getType();
			float angularDamping = body.getAngularDamping();
			float angularVelocity = body.getAngularVelocity();
			float linearDamping = body.getLinearDamping();
			float gravityScale = body.getGravityScale();
			Vector2 linearVelocity = body.getLinearVelocity();
			MassData massData = body.getMassData();
			boolean fixedRotation = body.isFixedRotation();
			boolean bullet = body.isBullet();
			boolean awake = body.isAwake();
			boolean active = body.isActive();
			boolean sleepingAllowed = body.isSleepingAllowed();
			Object userData = body.getUserData();

			boolean changed = false;

			if(!Box2DUtils.equals(transform, oldTransform)) {
				updateOldTransform(newTransform = transform);
				changed = true;
			} else
				newTransform = null;
			if(!type.equals(oldType)) {
				oldType = newType = type;
				changed = true;
			} else
				newType = null;
			if(angularDamping != oldAngularDamping) {
				oldAngularDamping = newAngularDamping = angularDamping;
				changed = true;
			} else
				newAngularDamping = null;
			if(angularVelocity != oldAngularVelocity) {
				oldAngularVelocity = newAngularVelocity = angularVelocity;
				changed = true;
			} else
				newAngularVelocity = null;
			if(linearDamping != oldLinearDamping) {
				oldLinearDamping = newLinearDamping = linearDamping;
				changed = true;
			} else
				newLinearDamping = null;
			if(gravityScale != oldGravityScale) {
				oldGravityScale = newGravityScale = gravityScale;
				changed = true;
			} else
				newGravityScale = null;
			if(!linearVelocity.equals(oldLinearVelocity)) {
				oldLinearVelocity.set(newLinearVelocity = linearVelocity);
				changed = true;
			} else
				newLinearVelocity = null;
			if(!Box2DUtils.equals(massData, oldMassData)) {
				updateOldMassData(newMassData = massData);
				changed = true;
			} else
				newMassData = null;
			if(fixedRotation != oldFixedRotation) {
				newFixedRotation = oldFixedRotation = fixedRotation;
				changed = true;
			} else
				newFixedRotation = null;
			if(bullet != oldBullet) {
				oldBullet = newBullet = bullet;
				changed = true;
			} else
				newBullet = null;
			if(awake != oldAwake) {
				oldAwake = newAwake = awake;
				changed = true;
			} else
				newAwake = null;
			if(active != oldActive) {
				newActive = oldActive = active;
				changed = true;
			} else
				newActive = null;
			if(sleepingAllowed != oldSleepingAllowed) {
				newSleepingAllowed = oldSleepingAllowed = sleepingAllowed;
				changed = true;
			} else
				newSleepingAllowed = null;
			if(userData != null ? !userData.equals(oldUserData) : oldUserData != null) {
				oldUserData = newUserData = userData;
				changed = userDataChanged = true;
			} else {
				newUserData = null;
				userDataChanged = false;
			}

			return changed;
		}

		@Override
		public void apply(Body body) {
			if(newTransform != null)
				body.setTransform(newTransform.vals[Transform.POS_X], newTransform.vals[Transform.POS_Y], newTransform.getRotation());
			if(newType != null)
				body.setType(newType);
			if(newAngularDamping != null)
				body.setAngularDamping(newAngularDamping);
			if(newAngularVelocity != null)
				body.setAngularVelocity(newAngularVelocity);
			if(newLinearDamping != null)
				body.setLinearDamping(newLinearDamping);
			if(newGravityScale != null)
				body.setGravityScale(newGravityScale);
			if(newLinearVelocity != null)
				body.setLinearVelocity(newLinearVelocity);
			if(newMassData != null)
				body.setMassData(newMassData);
			if(newFixedRotation != null)
				body.setFixedRotation(newFixedRotation);
			if(newBullet != null)
				body.setBullet(newBullet);
			if(newAwake != null)
				body.setAwake(newAwake);
			if(newActive != null)
				body.setActive(newActive);
			if(newSleepingAllowed != null)
				body.setSleepingAllowed(newSleepingAllowed);
			if(userDataChanged)
				body.setUserData(newUserData);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof BodyChange))
				return false;
			BodyChange o = (BodyChange) other;
			return Objects.equals(newTransform, o.newTransform) &&
					Objects.equals(newType, o.newType) &&
					Objects.equals(newAngularDamping, o.newAngularDamping) &&
					Objects.equals(newAngularVelocity, o.newAngularVelocity) &&
					Objects.equals(newLinearDamping, o.newLinearDamping) &&
					Objects.equals(newGravityScale, o.newGravityScale) &&
					Objects.equals(newLinearVelocity, o.newLinearVelocity) &&
					Objects.equals(newMassData, o.newMassData) &&
					Objects.equals(newFixedRotation, o.newFixedRotation) &&
					Objects.equals(newBullet, o.newBullet) &&
					Objects.equals(newAwake, o.newAwake) &&
					Objects.equals(newActive, o.newActive) &&
					Objects.equals(newSleepingAllowed, o.newSleepingAllowed) &&
					Objects.equals(newUserData, o.newUserData);
		}

		@Override
		public void reset() {
			oldTransform.vals[Transform.POS_X] = oldTransform.vals[Transform.POS_Y] = oldTransform.vals[Transform.COS] = oldTransform.vals[Transform.SIN] = 0;
			oldType = null;
			oldAngularDamping = 0;
			oldAngularVelocity = 0;
			oldLinearDamping = 0;
			oldGravityScale = 0;
			oldLinearVelocity.setZero();
			oldMassData.mass = 0;
			oldMassData.I = 0;
			oldMassData.center.setZero();
			oldFixedRotation = false;
			oldBullet = false;
			oldAwake = false;
			oldActive = false;
			oldSleepingAllowed = false;
			oldUserData = null;

			newTransform = null;
			newType = null;
			newAngularDamping = null;
			newAngularVelocity = null;
			newLinearDamping = null;
			newGravityScale = null;
			newLinearVelocity = null;
			newMassData = null;
			newFixedRotation = null;
			newBullet = null;
			newAwake = null;
			newActive = null;
			newSleepingAllowed = null;
			newUserData = null;

			userDataChanged = false;
		}

	}

	/** the changes of a {@link Fixture} */
	public static class FixtureChange implements Change {

		private transient Body oldBody;
		private transient boolean destroyed;

		private transient float oldDensity;
		private transient float oldFriction;
		private transient float oldRestitution;
		private transient final Filter oldFilter = new Filter();
		private transient boolean oldSensor;
		private transient Object oldUserData;

		public Float newDensity;
		public Float newFriction;
		public Float newRestitution;
		public Filter newFilter;
		public Boolean newSensor;
		public Object newUserData;

		/** if the {@link Fixture#userData} changed */
		boolean userDataChanged;

		/** this should be called when this FixtureChange is going to be used for a fixture on another body to make {@link #destroyed} work correctly */
		void created(Body body) {
			oldBody = body;
		}

		private void updateOldFilter(Filter newFilter) {
			oldFilter.categoryBits = newFilter.categoryBits;
			oldFilter.groupIndex = newFilter.groupIndex;
			oldFilter.maskBits = newFilter.maskBits;
		}

		/** @return the {@link #destroyed} */
		public boolean isDestroyed() {
			return destroyed;
		}

		@Override
		public boolean update(Fixture fixture) {
			Body body = fixture.getBody();

			if(body != oldBody) {
				destroyed = true;
				oldBody = body;
				return false;
			}

			float density = fixture.getDensity();
			float friction = fixture.getFriction();
			float restitution = fixture.getRestitution();
			Filter filter = fixture.getFilterData();
			boolean sensor = fixture.isSensor();
			Object userData = fixture.getUserData();

			boolean changed = false;

			if(density != oldDensity) {
				oldDensity = newDensity = density;
				changed = true;
			} else
				newDensity = null;
			if(friction != oldFriction) {
				oldFriction = newFriction = friction;
				changed = true;
			} else
				newFriction = null;
			if(restitution != oldRestitution) {
				oldRestitution = newRestitution = restitution;
				changed = true;
			} else
				newRestitution = null;
			if(!Box2DUtils.equals(filter, oldFilter)) {
				updateOldFilter(newFilter = filter);
				changed = true;
			} else
				newFilter = null;
			if(sensor != oldSensor) {
				oldSensor = newSensor = sensor;
				changed = true;
			} else
				newSensor = null;
			if(userData != null ? !userData.equals(oldUserData) : oldUserData != null) {
				oldUserData = newUserData = userData;
				changed = userDataChanged = true;
			} else {
				newUserData = null;
				userDataChanged = false;
			}

			return changed;
		}

		/** @throws IllegalStateException if the fixture has been {@link #destroyed} */
		@Override
		public void apply(Fixture fixture) {
			if(destroyed)
				throw new IllegalStateException("destroyed FixtureChanges may not be applied");
			if(newDensity != null)
				fixture.setDensity(newDensity);
			if(newFriction != null)
				fixture.setFriction(newFriction);
			if(newRestitution != null)
				fixture.setRestitution(newRestitution);
			if(newFilter != null)
				fixture.setFilterData(newFilter);
			if(newSensor != null)
				fixture.setSensor(newSensor);
			if(userDataChanged)
				fixture.setUserData(newUserData);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof FixtureChange))
				return false;
			FixtureChange o = (FixtureChange) other;
			return Objects.equals(newDensity, o.newDensity) &&
					Objects.equals(newFriction, o.newFriction) &&
					Objects.equals(newRestitution, o.newRestitution) &&
					Objects.equals(newFilter, o.newFilter) &&
					Objects.equals(newSensor, o.newSensor) &&
					Objects.equals(newUserData, o.newUserData);
		}

		@Override
		public void reset() {
			oldBody = null;
			destroyed = false;

			oldDensity = 0;
			oldFriction = 0;
			oldRestitution = 0;
			oldFilter.categoryBits = 0x0001;
			oldFilter.maskBits = -1;
			oldFilter.groupIndex = 0;
			oldSensor = false;
			oldUserData = null;

			newDensity = null;
			newFriction = null;
			newRestitution = null;
			newFilter = null;
			newSensor = null;
			newUserData = null;

			userDataChanged = false;
		}

	}

	/** the changes of a {@link Joint}
	 *  @author dermetfan
	 *  @since 0.6.0 */
	public static class JointChange implements Change {

		/** @return a concrete JointChange from {@link Pools#obtain(Class)} */
		public static JointChange obtainFor(JointType type) {
			Class changeType;
			switch(type) {
			case RevoluteJoint:
				changeType = RevoluteJointChange.class;
				break;
			case PrismaticJoint:
				changeType = PrismaticJointChange.class;
				break;
			case DistanceJoint:
				changeType = DistanceJointChange.class;
				break;
			case PulleyJoint:
				changeType = JointChange.class; // no named PulleyJointChange needed
				break;
			case MouseJoint:
				changeType = MouseJointChange.class;
				break;
			case GearJoint:
				changeType = GearJointChange.class;
				break;
			case WheelJoint:
				changeType = WheelJointChange.class;
				break;
			case WeldJoint:
				changeType = WeldJointChange.class;
				break;
			case FrictionJoint:
				changeType = FrictionJointChange.class;
				break;
			case RopeJoint:
				changeType = RopeJointChange.class;
				break;
			case MotorJoint:
				changeType = MotorJointChange.class;
				break;
			default:
			case Unknown:
				changeType = JointChange.class;
			}
			return Pools.obtain(changeType);
		}

		private transient Object oldUserData;

		public Object newUserData;

		boolean userDataChanged;

		@Override
		public boolean update(T joint) {
			Object userData = joint.getUserData();

			boolean changed = false;

			if(userData != null ? !userData.equals(oldUserData) : oldUserData != null) {
				oldUserData = newUserData = userData;
				changed = userDataChanged = true;
			} else {
				newUserData = null;
				userDataChanged = false;
			}

			return changed;
		}

		@Override
		public void apply(T joint) {
			if(userDataChanged)
				joint.setUserData(newUserData);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			return other instanceof JointChange && Objects.equals(newUserData, ((JointChange) other).newUserData);
		}

		@Override
		public void reset() {
			oldUserData = null;
			newUserData = null;
			userDataChanged = false;
		}

	}

	/** the changes of a {@link RevoluteJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class RevoluteJointChange extends JointChange {

		private transient float oldLowerLimit;
		private transient float oldUpperLimit;
		private transient float oldMaxMotorTorque;
		private transient float oldMotorSpeed;

		public Float newLowerLimit;
		public Float newUpperLimit;
		public Float newMaxMotorTorque;
		public Float newMotorSpeed;

		@Override
		public boolean update(RevoluteJoint joint) {
			float lowerLimit = joint.getLowerLimit();
			float upperLimit = joint.getUpperLimit();
			float maxMotorTorque = joint.getMaxMotorTorque();
			float motorSpeed = joint.getMotorSpeed();

			boolean changed = super.update(joint);

			if(lowerLimit != oldLowerLimit) {
				newLowerLimit = oldLowerLimit = lowerLimit;
				changed = true;
			} else
				newLowerLimit = null;
			if(upperLimit != oldUpperLimit) {
				newUpperLimit = oldUpperLimit = upperLimit;
				changed = true;
			} else
				newUpperLimit = null;
			if(maxMotorTorque != oldMaxMotorTorque) {
				newMaxMotorTorque = oldMaxMotorTorque = maxMotorTorque;
				changed = true;
			} else
				newMaxMotorTorque = null;
			if(motorSpeed != oldMotorSpeed) {
				newMotorSpeed = oldMotorSpeed = motorSpeed;
				changed = true;
			} else
				newMotorSpeed = null;

			return changed;
		}

		@Override
		public void apply(RevoluteJoint joint) {
			super.apply(joint);
			if(newLowerLimit != null || newUpperLimit != null)
				joint.setLimits(newLowerLimit != null ? newLowerLimit : joint.getLowerLimit(), newUpperLimit != null ? newUpperLimit : joint.getUpperLimit());
			if(newMaxMotorTorque != null)
				joint.setMaxMotorTorque(newMaxMotorTorque);
			if(newMotorSpeed != null)
				joint.setMotorSpeed(newMotorSpeed);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof RevoluteJointChange))
				return false;
			RevoluteJointChange o = (RevoluteJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newLowerLimit, o.newLowerLimit) &&
					Objects.equals(newUpperLimit, o.newUpperLimit) &&
					Objects.equals(newMaxMotorTorque, o.newMaxMotorTorque) &&
					Objects.equals(newMotorSpeed, o.newMotorSpeed);
		}

		@Override
		public void reset() {
			super.reset();

			oldLowerLimit = 0;
			oldUpperLimit = 0;
			oldMaxMotorTorque = 0;
			oldMotorSpeed = 0;

			newLowerLimit = null;
			newUpperLimit = null;
			newMaxMotorTorque = null;
			newMotorSpeed = null;
		}

	}

	/** the changes of a {@link PrismaticJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class PrismaticJointChange extends JointChange {

		private transient float oldLowerLimit;
		private transient float oldUpperLimit;
		private transient float oldMaxMotorTorque;
		private transient float oldMotorSpeed;

		public Float newLowerLimit;
		public Float newUpperLimit;
		public Float newMaxMotorForce;
		public Float newMotorSpeed;

		@Override
		public boolean update(PrismaticJoint joint) {
			float lowerLimit = joint.getLowerLimit();
			float upperLimit = joint.getUpperLimit();
			float maxMotorTorque = joint.getMaxMotorForce();
			float motorSpeed = joint.getMotorSpeed();

			boolean changed = super.update(joint);

			if(lowerLimit != oldLowerLimit) {
				newLowerLimit = oldLowerLimit = lowerLimit;
				changed = true;
			} else
				newLowerLimit = null;
			if(upperLimit != oldUpperLimit) {
				newUpperLimit = oldUpperLimit = upperLimit;
				changed = true;
			} else
				newUpperLimit = null;
			if(maxMotorTorque != oldMaxMotorTorque) {
				newMaxMotorForce = oldMaxMotorTorque = maxMotorTorque;
				changed = true;
			} else
				newMaxMotorForce = null;
			if(motorSpeed != oldMotorSpeed) {
				newMotorSpeed = oldMotorSpeed = motorSpeed;
				changed = true;
			} else
				newMotorSpeed = null;

			return changed;
		}

		@Override
		public void apply(PrismaticJoint joint) {
			super.apply(joint);
			if(newLowerLimit != null || newUpperLimit != null)
				joint.setLimits(newLowerLimit != null ? newLowerLimit : joint.getLowerLimit(), newUpperLimit != null ? newUpperLimit : joint.getUpperLimit());
			if(newMaxMotorForce != null)
				joint.setMaxMotorForce(newMaxMotorForce);
			if(newMotorSpeed != null)
				joint.setMotorSpeed(newMotorSpeed);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof PrismaticJointChange))
				return false;
			PrismaticJointChange o = (PrismaticJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newLowerLimit, o.newLowerLimit) &&
					Objects.equals(newUpperLimit, o.newUpperLimit) &&
					Objects.equals(newMaxMotorForce, o.newMaxMotorForce) &&
					Objects.equals(newMotorSpeed, o.newMotorSpeed);
		}

		@Override
		public void reset() {
			super.reset();

			oldLowerLimit = 0;
			oldUpperLimit = 0;
			oldMaxMotorTorque = 0;
			oldMotorSpeed = 0;

			newLowerLimit = null;
			newUpperLimit = null;
			newMaxMotorForce = null;
			newMotorSpeed = null;
		}

	}

	/** the changes of a {@link DistanceJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class DistanceJointChange extends JointChange {

		private transient float oldDampingRatio;
		private transient float oldFrequency;
		private transient float oldLength;

		public Float newDampingRatio;
		public Float newFrequency;
		public Float newLength;

		@Override
		public boolean update(DistanceJoint joint) {
			float dampingRatio = joint.getDampingRatio();
			float frequency = joint.getFrequency();
			float length = joint.getLength();

			boolean changed = super.update(joint);

			if(dampingRatio != oldDampingRatio) {
				newDampingRatio = oldDampingRatio = dampingRatio;
				changed = true;
			} else
				newDampingRatio = null;
			if(frequency != oldFrequency) {
				newFrequency = oldFrequency = frequency;
				changed = true;
			} else
				newFrequency = null;
			if(length != oldLength) {
				newLength = oldLength = length;
				changed = true;
			} else
				newLength = null;

			return changed;
		}

		@Override
		public void apply(DistanceJoint joint) {
			super.apply(joint);
			if(newDampingRatio != null)
				joint.setDampingRatio(newDampingRatio);
			if(newFrequency != null)
				joint.setFrequency(newFrequency);
			if(newLength != null)
				joint.setLength(newLength);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof DistanceJointChange))
				return false;
			DistanceJointChange o = (DistanceJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newDampingRatio, o.newDampingRatio) &&
					Objects.equals(newFrequency, o.newFrequency) &&
					Objects.equals(newLength, o.newLength);
		}

		@Override
		public void reset() {
			super.reset();

			oldDampingRatio = 0;
			oldFrequency = 0;
			oldLength = 0;

			newDampingRatio = null;
			newFrequency = null;
			newLength = null;
		}

	}

	/** the changes of a {@link MouseJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class MouseJointChange extends JointChange {

		private transient float oldDampingRatio;
		private transient float oldFrequency;
		private transient float oldMaxForce;
		private transient final Vector2 oldTarget = new Vector2();

		public Float newDampingRatio;
		public Float newFrequency;
		public Float newMaxForce;
		public Vector2 newTarget;

		@Override
		public boolean update(MouseJoint joint) {
			float dampingRatio = joint.getDampingRatio();
			float frequency = joint.getFrequency();
			float maxForce = joint.getMaxForce();
			Vector2 target = joint.getTarget();

			boolean changed = super.update(joint);

			if(dampingRatio != oldDampingRatio) {
				newDampingRatio = oldDampingRatio = dampingRatio;
				changed = true;
			} else
				newDampingRatio = null;
			if(frequency != oldFrequency) {
				newFrequency = oldFrequency = frequency;
				changed = true;
			} else
				newFrequency = null;
			if(maxForce != oldMaxForce) {
				newMaxForce = oldMaxForce = maxForce;
				changed = true;
			} else
				newMaxForce = null;
			if(!target.equals(oldTarget)) {
				oldTarget.set(newTarget = target);
				changed = true;
			} else
				newTarget = null;

			return changed;
		}

		@Override
		public void apply(MouseJoint joint) {
			super.apply(joint);
			if(newDampingRatio != null)
				joint.setDampingRatio(newDampingRatio);
			if(newFrequency != null)
				joint.setFrequency(newFrequency);
			if(newMaxForce != null)
				joint.setMaxForce(newMaxForce);
			if(newTarget != null)
				joint.setTarget(newTarget);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof MouseJointChange))
				return false;
			MouseJointChange o = (MouseJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newDampingRatio, o.newDampingRatio) &&
					Objects.equals(newFrequency, o.newFrequency) &&
					Objects.equals(newMaxForce, o.newMaxForce);
		}

		@Override
		public void reset() {
			super.reset();

			oldDampingRatio = 0;
			oldFrequency = 0;
			oldMaxForce = 0;
			oldTarget.setZero();

			newDampingRatio = null;
			newFrequency = null;
			newMaxForce = null;
			newTarget = null;
		}

	}

	/** the changes of a {@link GearJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class GearJointChange extends JointChange {

		private transient float oldRatio;

		public Float newRatio;

		@Override
		public boolean update(GearJoint joint) {
			float ratio = joint.getRatio();

			boolean changed = super.update(joint);

			if(ratio != oldRatio) {
				newRatio = oldRatio = ratio;
				changed = true;
			} else
				newRatio = null;

			return changed;
		}

		@Override
		public void apply(GearJoint joint) {
			super.apply(joint);
			if(newRatio != null)
				joint.setRatio(newRatio);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			return other instanceof GearJointChange && super.newValuesEqual(other) && Objects.equals(newRatio, ((GearJointChange) other).newRatio);
		}

		@Override
		public void reset() {
			super.reset();
			oldRatio = 0;
			newRatio = null;
		}

	}

	/** the changes of a {@link WheelJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class WheelJointChange extends JointChange {

		private transient float oldSpringDampingRatio;
		private transient float oldSpringFrequencyHz;
		private transient float oldMaxMotorTorque;
		private transient float oldMotorSpeed;

		public Float newSpringDampingRatio;
		public Float newSpringFrequencyHz;
		public Float newMaxMotorTorque;
		public Float newMotorSpeed;

		@Override
		public boolean update(WheelJoint joint) {
			float sprintDampingRatio = joint.getSpringDampingRatio();
			float springFrequencyHz = joint.getSpringFrequencyHz();
			float maxMotorTorque = joint.getMaxMotorTorque();
			float motorSpeed = joint.getMotorSpeed();

			boolean changed = super.update(joint);

			if(sprintDampingRatio != oldSpringDampingRatio) {
				newSpringDampingRatio = oldSpringDampingRatio = sprintDampingRatio;
				changed = true;
			} else
				newSpringDampingRatio = null;
			if(springFrequencyHz != oldSpringFrequencyHz) {
				newSpringFrequencyHz = oldSpringFrequencyHz = springFrequencyHz;
				changed = true;
			} else
				newSpringFrequencyHz = null;
			if(maxMotorTorque != oldMaxMotorTorque) {
				newMaxMotorTorque = oldMaxMotorTorque = maxMotorTorque;
				changed = true;
			} else
				newMaxMotorTorque = null;
			if(motorSpeed != oldMotorSpeed) {
				newMotorSpeed = oldMotorSpeed = motorSpeed;
				changed = true;
			} else
				newMotorSpeed = null;

			return changed;
		}

		@Override
		public void apply(WheelJoint joint) {
			super.apply(joint);
			if(newSpringDampingRatio != null)
				joint.setSpringDampingRatio(newSpringDampingRatio);
			if(newSpringFrequencyHz != null)
				joint.setSpringFrequencyHz(newSpringFrequencyHz);
			if(newMaxMotorTorque != null)
				joint.setMaxMotorTorque(newMaxMotorTorque);
			if(newMotorSpeed != null)
				joint.setMotorSpeed(newMotorSpeed);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof WheelJointChange))
				return false;
			WheelJointChange o = (WheelJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newSpringDampingRatio, o.newSpringDampingRatio) &&
					Objects.equals(newSpringFrequencyHz, o.newSpringFrequencyHz) &&
					Objects.equals(newMaxMotorTorque, o.newMaxMotorTorque) &&
					Objects.equals(newMotorSpeed, o.newMotorSpeed);
		}

		@Override
		public void reset() {
			super.reset();

			oldSpringDampingRatio = 0;
			oldSpringFrequencyHz = 0;
			oldMaxMotorTorque = 0;
			oldMotorSpeed = 0;

			newSpringDampingRatio = null;
			newSpringFrequencyHz = null;
			newMaxMotorTorque = null;
			newMotorSpeed = null;
		}

	}

	/** the changes of a {@link WeldJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class WeldJointChange extends JointChange {

		private transient float oldDampingRatio;
		private transient float oldFrequency;

		public Float newDampingRatio;
		public Float newFrequency;

		@Override
		public boolean update(WeldJoint joint) {
			float dampingRatio = joint.getDampingRatio();
			float frequency = joint.getFrequency();

			boolean changed = super.update(joint);

			if(dampingRatio != oldDampingRatio) {
				newDampingRatio = oldDampingRatio = dampingRatio;
				changed = true;
			} else
				newDampingRatio = null;
			if(frequency != oldFrequency) {
				newFrequency = oldFrequency = frequency;
				changed = true;
			} else
				newFrequency = null;

			return changed;
		}

		@Override
		public void apply(WeldJoint joint) {
			super.apply(joint);
			if(newDampingRatio != null)
				joint.setDampingRatio(newDampingRatio);
			if(newFrequency != null)
				joint.setFrequency(newFrequency);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof WeldJointChange))
				return false;
			WeldJointChange o = (WeldJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newDampingRatio, o.newDampingRatio) &&
					Objects.equals(newFrequency, o.newFrequency);
		}

		@Override
		public void reset() {
			super.reset();

			oldDampingRatio = 0;
			oldFrequency = 0;

			newDampingRatio = null;
			newFrequency = null;
		}

	}

	/** the changes of a {@link FrictionJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class FrictionJointChange extends JointChange {

		private transient float oldMaxForce;
		private transient float oldMaxTorque;

		public Float newMaxForce;
		public Float newMaxTorque;

		@Override
		public boolean update(FrictionJoint joint) {
			float maxForce = joint.getMaxForce();
			float maxTorque = joint.getMaxTorque();

			boolean changed = super.update(joint);

			if(maxForce != oldMaxForce) {
				newMaxForce = oldMaxForce = maxForce;
				changed = true;
			} else
				newMaxForce = null;
			if(maxTorque != oldMaxTorque) {
				newMaxTorque = oldMaxTorque = maxTorque;
				changed = true;
			} else
				newMaxTorque = null;

			return changed;
		}

		@Override
		public void apply(FrictionJoint joint) {
			super.apply(joint);
			if(newMaxForce != null)
				joint.setMaxForce(newMaxForce);
			if(newMaxTorque != null)
				joint.setMaxTorque(newMaxTorque);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof FrictionJointChange))
				return false;
			FrictionJointChange o = (FrictionJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newMaxForce, o.newMaxForce) &&
					Objects.equals(newMaxTorque, o.newMaxTorque);
		}

		@Override
		public void reset() {
			super.reset();

			oldMaxForce = 0;
			oldMaxTorque = 0;

			newMaxForce = null;
			newMaxTorque = null;
		}

	}

	/** the changes of a {@link RopeJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class RopeJointChange extends JointChange {

		private transient float oldMaxLength;

		public Float newMaxLength;

		@Override
		public boolean update(RopeJoint joint) {
			float maxLength = joint.getMaxLength();

			boolean changed = super.update(joint);

			if(maxLength != oldMaxLength) {
				newMaxLength = oldMaxLength = maxLength;
				changed = true;
			} else
				newMaxLength = null;

			return changed;
		}

		@Override
		public void apply(RopeJoint joint) {
			super.apply(joint);
			if(newMaxLength != null)
				joint.setMaxLength(newMaxLength);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			return other instanceof RopeJointChange && super.newValuesEqual(other) && Objects.equals(newMaxLength, ((RopeJointChange) other).newMaxLength);
		}

		@Override
		public void reset() {
			super.reset();
			oldMaxLength = 0;
			newMaxLength = null;
		}

	}

	/** the changes of a {@link MotorJoint}
	 *  @author dermetfan
	 *  @since 0.7.1 */
	public static class MotorJointChange extends JointChange {

		private transient float oldMaxForce;
		private transient float oldMaxTorque;
		private transient float oldCorrectionFactor;
		private transient float oldAngularOffset;
		private transient final Vector2 oldLinearOffset = new Vector2();

		public Float newMaxForce;
		public Float newMaxTorque;
		public Float newCorrectionFactor;
		public Float newAngularOffset;
		public Vector2 newLinearOffset;

		@Override
		public boolean update(MotorJoint joint) {
			float maxForce = joint.getMaxForce();
			float maxTorque = joint.getMaxTorque();
			float correctionFactor = joint.getCorrectionFactor();
			float angularOffset = joint.getAngularOffset();
			Vector2 linearOffset = joint.getLinearOffset();

			boolean changed = super.update(joint);

			if(maxForce != oldMaxForce) {
				newMaxForce = oldMaxForce = maxForce;
				changed = true;
			} else
				newMaxForce = null;
			if(maxTorque != oldMaxTorque) {
				newMaxTorque = oldMaxTorque = maxTorque;
				changed = true;
			} else
				newMaxTorque = null;
			if(correctionFactor != oldCorrectionFactor) {
				newCorrectionFactor = oldCorrectionFactor = correctionFactor;
				changed = true;
			} else
				newCorrectionFactor = null;
			if(angularOffset != oldAngularOffset) {
				newAngularOffset = oldAngularOffset = angularOffset;
				changed = true;
			} else
				newAngularOffset = null;
			if(!linearOffset.equals(oldLinearOffset)) {
				oldLinearOffset.set(newLinearOffset = linearOffset);
				changed = true;
			} else
				newLinearOffset = null;

			return changed;
		}

		@Override
		public void apply(MotorJoint joint) {
			super.apply(joint);
			if(newMaxForce != null)
				joint.setMaxForce(newMaxForce);
			if(newMaxTorque != null)
				joint.setMaxForce(newMaxTorque);
			if(newCorrectionFactor != null)
				joint.setCorrectionFactor(newCorrectionFactor);
			if(newAngularOffset != null)
				joint.setAngularOffset(newAngularOffset);
			if(newLinearOffset != null)
				joint.setLinearOffset(newLinearOffset);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof MotorJointChange))
				return false;
			MotorJointChange o = (MotorJointChange) other;
			return super.newValuesEqual(other) &&
					Objects.equals(newAngularOffset, o.newAngularOffset) &&
					Objects.equals(newCorrectionFactor, o.newCorrectionFactor) &&
					Objects.equals(newLinearOffset, o.newLinearOffset) &&
					Objects.equals(newMaxForce, o.newMaxForce);
		}

		@Override
		public void reset() {
			super.reset();

			oldMaxForce = 0;
			oldMaxTorque = 0;
			oldCorrectionFactor = 0;
			oldAngularOffset = 0;
			oldLinearOffset.setZero();

			newMaxForce = null;
			newMaxTorque = null;
			newCorrectionFactor = null;
			newAngularOffset = null;
			newLinearOffset = null;
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy