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

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

There is a newer version: 0.13.4
Show 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 com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.Joint;
import com.badlogic.gdx.physics.box2d.JointDef;
import com.badlogic.gdx.physics.box2d.Shape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.SnapshotArray;
import net.dermetfan.gdx.physics.box2d.utils.Box2DUtils;

/** Holds {@link #segments} and {@link #connections} to simulate a chain. Also provides modification methods that use a {@link Builder}.
 *  @author dermetfan */
public class Chain {

	/** used by a {@link Chain} to modify it
	 *  @author dermetfan */
	public static interface Builder {

		/** creates a segment that is going to be added to {@link Chain#segments}
		 *  @param index the index of the segment to create
		 *  @param length the desired length of the {@link Chain} that is being build
		 *  @param chain the {@link Chain} this segment will be added to
		 *  @return the created segment */
		public Body createSegment(int index, int length, Chain chain);

		/** connects two segments with each other using a {@link Connection}
		 *  @param seg1 the first segment
		 *  @param seg1index the index of the first segment
		 *  @param seg2 the second segment
		 *  @param seg2index the index of the second segment
		 *  @return the created {@link Connection} */
		public Connection createConnection(Body seg1, int seg1index, Body seg2, int seg2index);

	}

	/** a {@link Builder} that builds using a {@link BodyDef}, {@link FixtureDef} and {@link Joint}
	 *  @author dermetfan */
	public static class DefBuilder implements Builder {

		/** the {@link World} in which to create segments and joints */
		protected World world;

		/** the {@link BodyDef} to use in {@link #createSegment(int, int, Chain)} */
		protected BodyDef bodyDef;

		/** the {@link FixtureDef} to use in {@link #createSegment(int, int, Chain)} */
		protected FixtureDef fixtureDef;

		/** the {@link JointDef} to use in {@link #createConnection(Body, int, Body, int)} */
		protected JointDef jointDef;

		/** @param world the {@link #world}
		 *  @param bodyDef the {@link #bodyDef}
		 *  @param fixtureDef the {@link #fixtureDef}
		 *  @param jointDef the {@link #jointDef} */
		public DefBuilder(World world, BodyDef bodyDef, FixtureDef fixtureDef, JointDef jointDef) {
			this.world = world;
			this.bodyDef = bodyDef;
			this.fixtureDef = fixtureDef;
			this.jointDef = jointDef;
		}

		/** @return a new {@link Body segment} created with{@link #bodyDef} and {@link #fixtureDef} */
		@Override
		public Body createSegment(int index, int length, Chain chain) {
			return world.createBody(bodyDef).createFixture(fixtureDef).getBody();
		}

		/** @return a new {@link JointDef} created with {@link #jointDef} */
		@Override
		public Connection createConnection(Body seg1, int seg1index, Body seg2, int seg2index) {
			jointDef.bodyA = seg1;
			jointDef.bodyB = seg2;
			return new Connection(world.createJoint(jointDef));
		}

		/** @return the {@link #world} */
		public World getWorld() {
			return world;
		}

		/** @param world the {@link #world} to set */
		public void setWorld(World world) {
			this.world = world;
		}

		/** @return the {@link #bodyDef} */
		public BodyDef getBodyDef() {
			return bodyDef;
		}

		/** @param bodyDef the {@link #bodyDef} to set */
		public void setBodyDef(BodyDef bodyDef) {
			this.bodyDef = bodyDef;
		}

		/** @return the {@link #fixtureDef} */
		public FixtureDef getFixtureDef() {
			return fixtureDef;
		}

		/** @param fixtureDef the {@link #fixtureDef} to set */
		public void setFixtureDef(FixtureDef fixtureDef) {
			this.fixtureDef = fixtureDef;
		}

		/** @return the {@link #jointDef} */
		public JointDef getJointDef() {
			return jointDef;
		}

		/** @param jointDef the {@link #jointDef} to set */
		public void setJointDef(JointDef jointDef) {
			this.jointDef = jointDef;
		}

	}

	/** A {@link Builder} that builds using a {@link BodyDef}, {@link JointDef} and {@link Shape}. Should be {@link DefShapeBuilder#dispose() dispose} if no longer used.
	 *  @author dermetfan */
	public static class DefShapeBuilder implements Builder, Disposable {

		/** the {@link World} to create things in */
		protected World world;

		/** the {@link BodyDef} to use in {@link #createSegment(int, int, Chain)} */
		protected BodyDef bodyDef;

		/** the {@link Shape} to use in {@link #createSegment(int, int, Chain)} */
		protected Shape shape;

		/** the density to use in {@link Body#createFixture(Shape, float)} */
		protected float density;

		/** the {@link JointDef} to use in {@link #createSegment(int, int, Chain)} */
		protected JointDef jointDef;

		/** @param world the {@link #world}
		 *  @param bodyDef the {@link #bodyDef}
		 *  @param shape the {@link Shape}
		 *  @param density the {@link #density}
		 *  @param jointDef the {@link #jointDef} */
		public DefShapeBuilder(World world, BodyDef bodyDef, Shape shape, float density, JointDef jointDef) {
			this.world = world;
			this.bodyDef = bodyDef;
			this.shape = shape;
			this.density = density;
			this.jointDef = jointDef;
		}

		/** creates a {@link Body segment} using {@link #bodyDef}, {@link #shape} and {@link #density}
		 *  @see Body#createFixture(Shape, float) */
		@Override
		public Body createSegment(int index, int length, Chain chain) {
			return world.createBody(bodyDef).createFixture(shape, density).getBody();
		}

		/** @return a new {@link Joint} created with {@link #jointDef} */
		@Override
		public Connection createConnection(Body seg1, int seg1index, Body seg2, int seg2index) {
			jointDef.bodyA = seg1;
			jointDef.bodyB = seg2;
			return new Connection(world.createJoint(jointDef));
		}

		/** {@link Shape#dispose() disposes} the {@link #shape} */
		@Override
		public void dispose() {
			shape.dispose();
		}

		/** @return the {@link #world} */
		public World getWorld() {
			return world;
		}

		/** @param world the {@link #world} to set */
		public void setWorld(World world) {
			this.world = world;
		}

		/** @return the {@link #bodyDef} */
		public BodyDef getBodyDef() {
			return bodyDef;
		}

		/** @param bodyDef the {@link #bodyDef} to set */
		public void setBodyDef(BodyDef bodyDef) {
			this.bodyDef = bodyDef;
		}

		/** @return the {@link #density} */
		public float getDensity() {
			return density;
		}

		/** @param density the {@link #density} to set */
		public void setDensity(float density) {
			this.density = density;
		}

		/** @return the {@link #jointDef} */
		public JointDef getJointDef() {
			return jointDef;
		}

		/** @param jointDef the {@link #jointDef} to set */
		public void setJointDef(JointDef jointDef) {
			this.jointDef = jointDef;
		}

		/** @return the {@link #shape} */
		public Shape getShape() {
			return shape;
		}

	}

	/** a {@link Builder} that {@link Box2DUtils#clone(Body) clones} a {@link Body} as template in {@link #createSegment(int, int, Chain)}
	 *  @author dermetfan */
	public static abstract class CopyBuilder implements Builder {

		/** the {@link Body} to {@link Box2DUtils#clone(Body) copy} in {@link #createSegment(int, int, Chain)} */
		protected Body template;

		/** @param template the {@link #template} */
		public CopyBuilder(Body template) {
			this.template = template;
		}

		/** @return a {@link Box2DUtils#clone(Body) copy} of {@link #template} */
		@Override
		public Body createSegment(int index, int length, Chain chain) {
			return Box2DUtils.clone(template);
		}

		/** @return the {@link #template} */
		public Body getTemplate() {
			return template;
		}

		/** @param template the {@link #template} to set */
		public void setTemplate(Body template) {
			this.template = template;
		}

	}

	/** a {@link CopyBuilder} that uses a {@link JointDef} in {@link #createConnection(Body, int, Body, int)}
	 *  @author dermetfan */
	public static class JointDefCopyBuilder extends CopyBuilder {

		/** the {@link JointDef} to use in {@link #createConnection(Body, int, Body, int)} */
		protected JointDef jointDef;

		/** @param template the {@link CopyBuilder#template}
		 *  @param jointDef the {@link #jointDef} */
		public JointDefCopyBuilder(Body template, JointDef jointDef) {
			super(template);
			this.jointDef = jointDef;
		}

		/** @return a new {@link Joint} created with {@link #jointDef} */
		@Override
		public Connection createConnection(Body seg1, int seg1index, Body seg2, int seg2index) {
			jointDef.bodyA = seg1;
			jointDef.bodyB = seg2;
			return new Connection(template.getWorld().createJoint(jointDef));
		}

		/** @return the {@link #jointDef} */
		public JointDef getJointDef() {
			return jointDef;
		}

		/** @param jointDef the {@link #jointDef} to set */
		public void setJointDef(JointDef jointDef) {
			this.jointDef = jointDef;
		}

	}

	/** holds one or more {@link Joint joints}
	 *  @author dermetfan */
	public static class Connection {

		/** the {@link Joint joints} of this {@link Connection} */
		public final Array joints = new Array<>(2);

		/** creates a new Connection and {@link #add(Joint) adds} the given joint */
		public Connection(Joint joint) {
			joints.add(joint);
		}

		/** creates a new Connection and {@link #add(Joint) adds} the given joints */
		public Connection(Joint joint1, Joint joint2) {
			joints.add(joint1);
			joints.add(joint2);
		}

		/** creates a new Connection and {@link #add(Joint) adds} the given joints */
		public Connection(Joint joint1, Joint joint2, Joint joint3) {
			joints.add(joint1);
			joints.add(joint2);
			joints.add(joint3);
		}

		/** creates a new Connection and {@link #add(Joint) adds} the given joints */
		public Connection(Joint... joints) {
			this.joints.addAll(joints);
		}

		/** {@link Array#add(Object) adds} the given joint to {@link #joints} */
		public void add(Joint joint) {
			joints.add(joint);
		}

		/** {@link Array#removeValue(Object, boolean) removes} the given joint from {@link #joints} */
		public boolean remove(Joint joint) {
			return joints.removeValue(joint, true);
		}

		/** {@link Array#removeIndex(int) removes} the joint at the given index from {@link #joints}
		 *  @return the removed {@link Joint} */
		public Joint remove(int index) {
			return joints.removeIndex(index);
		}

		/** {@link World#destroyJoint(Joint) destroys} all {@link #joints} */
		public void destroy() {
			for(Joint joint : joints)
				joint.getBodyA().getWorld().destroyJoint(joint);
		}

	}

	/** the {@link Builder} used for modifications of this Chain */
	private Builder builder;

	/** the {@link Body segments} of this Chain */
	private final Array segments = new Array<>();

	/** the {@link Joint Joints} of this Chain */
	private final Array connections = new Array<>();

	/** creates a shallow copy of the given {@link Chain} instance
	 *  @param other the {@link Chain} to copy */
	public Chain(Chain other) {
		builder = other.builder;
		segments.addAll(other.segments);
		connections.addAll(other.connections);
	}

	/** @param builder the {@link #builder} */
	public Chain(Builder builder) {
		this.builder = builder;
	}

	/** creates a new Chain and {@link #extend(int) builds} it to the given {@code length}
	 *  @param length the desired length of this Chain
	 *  @param builder the {@link #builder}
	 *  @see #Chain(int, Builder, boolean) */
	public Chain(int length, Builder builder) {
		this(length, builder, true);
	}

	/** creates a new Chain and {@link #extend(int) builds} it to the given {@code length} if {@code build} is true
	 *  @param length The desired length of this Chain. Will be ignored if {@code build} is false.
	 *  @param builder the {@link #builder}
	 *  @param build if this Chain should be {@link #extend(int) build} to the given {@code length} */
	public Chain(int length, Builder builder, boolean build) {
		segments.ensureCapacity(length - segments.size);
		connections.ensureCapacity(length - segments.size);
		this.builder = builder;
		if(build)
			extend(length);
	}

	/** creates a new Chain with the given {@code segments}
	 *  @param builder the {@link #builder}
	 *  @param segments the {@link #segments} */
	public Chain(Builder builder, Body... segments) {
		this.builder = builder;
		for(Body segment : segments)
			add(segment);
	}

	/** {@link #extend(int, Builder) extends} this chain by the given {@code length} using the {@link #builder}
	 *  @see #extend(int, Builder) */
	public Chain extend(int length) {
		return extend(length, builder);
	}

	/** {@link #extend(Builder) extends} this chain by the given {@code length} using the given {@link Builder}
	 *  @see #extend(Builder) */
	public Chain extend(int length, Builder builder) {
		for(; length > 0; length--)
			extend(builder);
		return this;
	}

	/** {@link #extend(Builder) extends} this Chain using the {@link #builder}
	 *  @see #extend(Builder) */
	public Body extend() {
		return extend(builder);
	}

	/** {@link #createSegment(int, Builder) creates} and {@link #add(Body) adds} a new segment to this Chain
	 *  @return the {@link #createSegment(int, Builder) created} and {@link #add(Body) added} segment */
	public Body extend(Builder builder) {
		Body segment = createSegment(segments.size, builder);
		add(segment);
		return segment;
	}

	/** {@link #destroy(int) destorys} the last segment */
	public void shorten() {
		destroy(segments.size - 1);
	}

	/** {@link #destroy(int, int) destroys} the given amount of segments from the end of the Chain */
	public void shorten(int length) {
		destroy(segments.size - length, segments.size - 1);
	}

	/** {@link #createSegment(int, Builder) creates} a segment for the given index using the {@link #builder}
	 *  @see #createSegment(int, Builder) */
	public Body createSegment(int index) {
		return createSegment(index, builder);
	}

	/** Creates a {@link Body segment} using the given {@link Builder} passing the correct parameters to {@link Builder#createSegment(int, int, Chain)} specified by the given {@code index}. Does NOT add it to this Chain.
	 *  @see Builder#createSegment(int, int, Chain) */
	public Body createSegment(int index, Builder builder) {
		return builder.createSegment(index, segments.size + 1, this);
	}

	/** {@link #createConnection(int, int, Builder) creates} a new {@link Connection}
	 *  @see #createConnection(int, int, Builder) */
	public Connection createConnection(int segmentIndex1, int segmentIndex2) {
		return createConnection(segmentIndex1, segmentIndex2, builder);
	}

	/** Creates a {@link Connection} using the {@link Builder} passing the correct parameters to {@link Builder#createConnection(Body, int, Body, int)} specified by the given indices. Does NOT add it to this Chain.
	 *  @see Builder#createConnection(Body, int, Body, int) */
	public Connection createConnection(int segmentIndex1, int segmentIndex2, Builder builder) {
		Body seg1 = segments.get(segmentIndex1), seg2 = segments.get(segmentIndex2);
		return builder.createConnection(seg1, segmentIndex1, seg2, segmentIndex2);

	}

	/** {@link SnapshotArray#add(Object) adds} the given {@code segment} to the end of this Chain
	 *  @param segment the {@link Body segment} to add */
	public void add(Body segment) {
		segments.add(segment);
		if(segments.size > 1)
			connections.add(createConnection(segments.size - 2 < 0 ? 0 : segments.size - 2, segments.size - 1));
	}

	/** {@link #add(Body) adds} the given segments to the end of this Chain
	 *  @see #add(Body) */
	public void addAll(Body... segments) {
		for(Body segment : segments)
			add(segment);
	}

	/** {@link #createSegment(int) creates} a new {@link Body segment} and {@link #insert(int, Body) inserts} it into this Chain
	 *  @see #insert(int, Body) */
	public Body insert(int index) {
		Body segment = createSegment(index);
		insert(index, segment);
		return segment;
	}

	/** inserts a {@link Body segment} into this Chain
	 *  @param index the {@link #segments index} at which to insert the given {@code segment}
	 *  @param segment the {@link Body segment} to insert */
	public void insert(int index, Body segment) {
		if(index - 1 >= 0)
			connections.removeIndex(index - 1).destroy();
		segments.insert(index, segment);
		if(index - 1 >= 0)
			connections.insert(index - 1, createConnection(index - 1, index));
		if(index + 1 < segments.size)
			connections.insert(index, createConnection(index, index + 1));
	}

	/** @param index the index of the segment to replace
	 *  @param segment the {@link Body segment} to insert
	 *  @return the {@link Body segment} that was at the given {@code index} previously */
	public Body replace(int index, Body segment) {
		Body old = remove(index);
		insert(index, segment);
		return old;
	}

	/** @param segment The {@link Body} to remove. Must be a {@link #segments segment} of this {@link Chain}.
	 *  @return the given {@code body}
	 *  @see #remove(int) */
	public Body remove(Body segment) {
		if(!segments.contains(segment, true))
			throw new IllegalArgumentException("the given body is not a segment of this Chain");
		return remove(segments.indexOf(segment, true));
	}

	/** removes a {@link #segments segment} from this Chain
	 *  @param index the index of the {@link #segments segment} to remove
	 *  @return the removed {@link #segments segment}
	 *  @see Array#removeIndex(int) */
	public Body remove(int index) {
		Body previous = index - 1 >= 0 ? segments.get(index - 1) : null, next = index + 1 < segments.size ? segments.get(index + 1) : null, segment = segments.removeIndex(index);
		if(index - 1 >= 0)
			connections.removeIndex(--index).destroy();
		if(index < connections.size)
			connections.removeIndex(index).destroy();
		if(previous != null && next != null)
			connections.insert(index, builder.createConnection(previous, index, next, index + 1));
		return segment;
	}

	/** {@link #remove(int) removes} all segmetns from beginIndex to endIndex
	 *  @param beginIndex the first index to remove
	 *  @param endIndex the last index to remove
	 *  @return an Array holding the removed segments
	 *  @see #tmpSegments */
	public Array remove(int beginIndex, int endIndex) {
		tmpSegments.clear();
		for(; endIndex >= beginIndex; endIndex--)
			tmpSegments.add(remove(endIndex));
		return tmpSegments;
	}

	/** @param segment the {@link Body segment} to destroy
	 *  @see #destroy(int) */
	public void destroy(Body segment) {
		if(!segments.contains(segment, true))
			throw new IllegalArgumentException("the given body must be a segment of this Chain");
		destroy(segments.indexOf(segment, true));
	}

	/** @param index the index of the {@link #segments segment} to {@link World#destroyBody(Body) destroy}
	 *  @see #remove(int) */
	public void destroy(int index) {
		Body segment = remove(index);
		segment.getWorld().destroyBody(segment);
	}

	/** {@link #destroy(int) destroys} all segments from beginIndex to endIndex
	 *  @param beginIndex the first index to destroy
	 *  @param endIndex the last index to destroy */
	public void destroy(int beginIndex, int endIndex) {
		Array removed = remove(beginIndex, endIndex);
		for(Body body : removed)
			body.getWorld().destroyBody(body);
		removed.clear();
	}

	/** @param connection the {@link Connection} in {@link #connections} to split
	 *  @return the new {@link Chain}
	 *  @see #split(int) */
	public Chain split(Connection connection) {
		if(!connections.contains(connection, true))
			throw new IllegalArgumentException("the joint must be part of this Chain");
		return split(connections.indexOf(connection, true));
	}

	/** splits this Chain at the given index and returns a new Chain consisting of the {@link #segments} up to the given {@code index}
	 *  @param connectionIndex the index of the {@link #connections connection} to destroy
	 *  @return a Chain consisting of the segments before the given index */
	public Chain split(int connectionIndex) {
		Body[] segs = new Body[connectionIndex + 1];
		for(int i = 0; i <= connectionIndex; i++) {
			connections.removeIndex(0).destroy();
			segs[i] = segments.removeIndex(0);
		}
		return new Chain(builder, segs);
	}

	/** @return the amount of {@link #segments} in this Chain
	 *  @see Array#size */
	public int length() {
		return segments.size;
	}

	/** @param index the index of the desired segment
	 *  @return the {@link #segments segment} at the given index */
	public Body getSegment(int index) {
		return segments.get(index);
	}

	/** @param index the index of the desired {@link #connections Joint}
	 *  @return the {@link #connections Joint} at the given index */
	public Connection getConnection(int index) {
		return connections.get(index);
	}

	/** Warning: This is used by multiple methods and may be modified unexpectedly. It's highly recommended to {@link Array#addAll(Array) add} its items to your own collection. */
	private final Array tmpSegments = new Array<>();

	/** Warning: The returned Array is used by other methods and may be modified unexpectedly. It's highly recommended to {@link Array#addAll(Array) add} its items to your own collection.
	 *  @return the {@link #segments}
	 *  @see #tmpSegments */
	public Array getSegments() {
		tmpSegments.clear();
		tmpSegments.addAll(segments);
		return tmpSegments;
	}

	/** Warning: This is used by multiple methods and may be modified unexpectedly. It's highly recommended to {@link Array#addAll(Array) add} its items to your own collection. */
	private final Array tmpConnections = new Array<>();

	/** Warning: The returned Array is used by other methods and may be modified unexpectedly. It's highly recommended to {@link Array#addAll(Array) add} its items to your own collection. 
	 *  @return an Array representing the current state of {@link #connections}
	 *  @see #tmpConnections */
	public Array getConnections() {
		tmpConnections.clear();
		tmpConnections.addAll(connections);
		return tmpConnections;
	}

	/** @return the {@link #builder} */
	public Builder getBuilder() {
		return builder;
	}

	/** @param builder the {@link #builder} to set */
	public void setBuilder(Builder builder) {
		if(builder == null)
			throw new IllegalArgumentException("builder must not be null");
		this.builder = builder;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy