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

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

/** 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.MassData;
import com.badlogic.gdx.physics.box2d.Transform;
import com.badlogic.gdx.physics.box2d.World;
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.Poolable;
import com.badlogic.gdx.utils.Pools;

/** notifies a {@link Listener} of changes in the world
 *  @since 0.5.1
 *  @author dermetfan */
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)} 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)} was called */
	private final IntMap currentFixtures = new IntMap<>(), previousFixtures = new IntMap<>();

	/** the Joints since this/the last time {@link #update(World)} 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. */
	public void update(World world) {
		if(listener != null)
			listener.preUpdate(world);

		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.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) {
			JointChange jointChange = jointChanges.get(joint);
			if(jointChange != null) { // updated
				if(jointChange.update(joint) && listener != null)
					listener.changed(joint, jointChange);
			} else { // new
				jointChange = Pools.obtain(JointChange.class);
				jointChange.update(joint);
				jointChanges.put(joint, jointChange);
				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);
	}

	/** @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}
	 *  @since 0.5.1
	 *  @author dermetfan */
	public static 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)} */
		void preUpdate(World world);

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

		/** @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);

	}

	/** the changes of an object in a world since the last time {@link #update(Object)} was called
	 *  @since 0.5.1
	 *  @author dermetfan */
	public static 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}
	 *  @since 0.5.1
	 *  @author dermetfan */
	public static class WorldChange implements Change {

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

		Boolean autoClearForces;
		Vector2 gravity;

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

			boolean changed = false;

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

			return changed;
		}

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

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

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

			autoClearForces = null;
			gravity = null;
		}

	}

	/** the changes of a {@link Body}
	 *  @since 0.5.1
	 *  @author dermetfan */
	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 oldGravityScale;
		private transient final Vector2 oldLinearVelocity = new Vector2();
		private transient final MassData oldMassData = new MassData();
		private transient Object oldUserData;

		Transform transform;
		BodyType type;
		Float angularDamping;
		Float angularVelocity;
		Float gravityScale;
		Vector2 linearVelocity;
		MassData massData;
		Object userData;

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

		private void updateOldTransform(Transform transform) {
			oldTransform.setPosition(transform.getPosition());
			oldTransform.setRotation(transform.getRotation());
		}

		@Override
		public boolean update(Body body) {
			Transform newTransform = body.getTransform();
			BodyType newType = body.getType();
			Float newAngularDamping = body.getAngularDamping();
			Float newAngularVelocity = body.getAngularVelocity();
			Float newGravityScale = body.getGravityScale();
			Vector2 newLinearVelocity = body.getLinearVelocity();
			MassData newMassData = body.getMassData();
			Object newUserData = body.getUserData();

			boolean changed = false;

			if(!newTransform.equals(oldTransform)) {
				updateOldTransform(transform = newTransform);
				changed = true;
			}
			if(!newType.equals(oldType)) {
				oldType = type = newType;
				changed = true;
			}
			if(!newAngularDamping.equals(oldAngularDamping)) {
				oldAngularDamping = angularDamping = newAngularDamping;
				changed = true;
			}
			if(!newAngularVelocity.equals(oldAngularVelocity)) {
				oldAngularVelocity = angularVelocity = newAngularVelocity;
				changed = true;
			}
			if(!newGravityScale.equals(oldGravityScale)) {
				oldGravityScale = gravityScale = newGravityScale;
				changed = true;
			}
			if(!newLinearVelocity.equals(oldLinearVelocity)) {
				updateOldLinearVelocity(linearVelocity = newLinearVelocity);
				changed = true;
			}
			if(!newMassData.equals(oldMassData)) {
				updateOldMassData(massData = newMassData);
				changed = true;
			}
			if(newUserData != null ? !newUserData.equals(oldUserData) : oldUserData != null) {
				oldUserData = userData = newUserData;
				changed = userDataChanged = true;
			}

			return changed;
		}

		private void updateOldLinearVelocity(Vector2 linearVelocity) {
			oldLinearVelocity.set(linearVelocity);
		}

		@Override
		public void apply(Body body) {
			if(transform != null)
				body.setTransform(transform.getPosition(), transform.getRotation());
			if(type != null)
				body.setType(type);
			if(angularDamping != null)
				body.setAngularDamping(angularDamping);
			if(angularVelocity != null)
				body.setAngularVelocity(angularVelocity);
			if(gravityScale != null)
				body.setGravityScale(gravityScale);
			if(linearVelocity != null)
				body.setLinearVelocity(linearVelocity);
			if(massData != null)
				body.setMassData(massData);
			if(userDataChanged)
				body.setUserData(userData);
		}

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

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof BodyChange))
				return false;
			BodyChange o = (BodyChange) other;
			boolean diff = !Objects.equals(transform, o.transform);
			diff |= !Objects.equals(type, o.type);
			diff |= !Objects.equals(angularDamping, o.angularDamping);
			diff |= !Objects.equals(angularVelocity, o.angularVelocity);
			diff |= !Objects.equals(gravityScale, o.gravityScale);
			diff |= !Objects.equals(linearVelocity, o.linearVelocity);
			diff |= !Objects.equals(massData, o.massData);
			diff |= !Objects.equals(userData, o.userData);
			return diff;
		}

		@Override
		public void reset() {
			oldTransform.setPosition(Vector2.Zero);
			oldTransform.setRotation(0);
			oldType = null;
			oldAngularDamping = null;
			oldAngularVelocity = null;
			oldGravityScale = null;
			oldLinearVelocity.setZero();
			oldMassData.mass = 0;
			oldMassData.I = 0;
			oldMassData.center.setZero();
			oldUserData = null;

			transform = null;
			type = null;
			angularDamping = null;
			angularVelocity = null;
			gravityScale = null;
			linearVelocity = null;
			massData = null;
			userData = 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 Object oldUserData;

		Float density;
		Float friction;
		Float restitution;
		Filter filter;
		Object userData;

		/** 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;
		}

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

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

			Float newDensity = fixture.getDensity();
			Float newFriction = fixture.getFriction();
			Float newRestitution = fixture.getRestitution();
			Filter newFilter = fixture.getFilterData();
			Object newUserData = fixture.getUserData();

			boolean changed = false;

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

			return changed;
		}

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

		/** @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(density != null)
				fixture.setDensity(density);
			if(friction != null)
				fixture.setFriction(friction);
			if(restitution != null)
				fixture.setRestitution(restitution);
			if(filter != null)
				fixture.setFilterData(filter);
			if(userDataChanged)
				fixture.setUserData(userData);
		}

		@Override
		public > boolean newValuesEqual(C other) {
			if(!(other instanceof FixtureChange))
				return false;
			FixtureChange o = (FixtureChange) other;
			boolean diff = !Objects.equals(density, o.density);
			diff |= !Objects.equals(friction, o.friction);
			diff |= !Objects.equals(restitution, o.restitution);
			diff |= !Objects.equals(filter, o.filter);
			diff |= !Objects.equals(userData, o.userData);
			return diff;
		}

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

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

			density = null;
			friction = null;
			restitution = null;
			filter = null;
			userData = null;

			userDataChanged = false;
		}

	}

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

		@Override
		public boolean update(Joint obj) {
			return false;
		}

		@Override
		public void apply(Joint obj) {}

		@Override
		public void reset() {}

		@Override
		public > boolean newValuesEqual(C other) {
			return true;
		}

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy