/* * __ .__ .__ ._____. * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ * |__| \____/__/\_ \__|\___ >____/__||___ /____ > * \/ \/ \/ \/ * * Copyright (c) 2006-2011 Karsten Schmidt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * http://creativecommons.org/licenses/LGPL/2.1/ * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ package toxi.physics2d; import java.util.ArrayList; import java.util.List; import toxi.geom.Rect; import toxi.geom.SpatialIndex; import toxi.geom.Vec2D; import toxi.physics2d.behaviors.GravityBehavior2D; import toxi.physics2d.behaviors.ParticleBehavior2D; import toxi.physics2d.constraints.ParticleConstraint2D; /** * 3D particle physics engine using Verlet integration based on: * http://en.wikipedia.org/wiki/Verlet_integration * http://www.teknikus.dk/tj/gdc2001.htm * */ public class VerletPhysics2D { /** * * @param c * @param list */ public static void addConstraintToAll(ParticleConstraint2D c, List list) { list.forEach((p) -> { p.addConstraint(c); }); } /** * * @param c * @param list */ public static void removeConstraintFromAll(ParticleConstraint2D c, List list) { list.forEach((p) -> { p.removeConstraint(c); }); } /** * List of particles */ public ArrayList particles; /** * List of spring/stick connectors */ public ArrayList springs; /** * Default time step = 1.0 */ protected float timeStep; /** * Default iterations for verlet solver = 50 */ protected int numIterations; /** * Optional bounding rect to constrain particles too */ protected Rect worldBounds; /** * */ public final List behaviors = new ArrayList<>( 1); /** * */ public final List constraints = new ArrayList<>( 1); /** * */ protected float drag; /** * */ protected SpatialIndex index; /** * Initializes a Verlet engine instance using the default values. */ public VerletPhysics2D() { this(null, 50, 0, 1); } /** * Initializes an Verlet engine instance with the passed in configuration. * * @param gravity 3D gravity vector * @param numIterations iterations per time step for verlet solver * @param drag drag value 0...1 * @param timeStep time step for calculating forces */ public VerletPhysics2D(Vec2D gravity, int numIterations, float drag, float timeStep) { particles = new ArrayList<>(); springs = new ArrayList<>(); this.numIterations = numIterations; this.timeStep = timeStep; setDrag(drag); if (gravity != null) { addBehavior(new GravityBehavior2D(gravity)); } } /** * * @param behavior */ public final void addBehavior(ParticleBehavior2D behavior) { behavior.configure(timeStep); behaviors.add(behavior); } /** * * @param constraint */ public void addConstraint(ParticleConstraint2D constraint) { constraints.add(constraint); } /** * Adds a particle to the list * * @param p * @return itself */ public VerletPhysics2D addParticle(VerletParticle2D p) { particles.add(p); return this; } /** * Adds a spring connector * * @param s * @return itself */ public VerletPhysics2D addSpring(VerletSpring2D s) { if (getSpring(s.a, s.b) == null) { springs.add(s); } return this; } /** * Applies all global constraints and constrains all particle positions to the * world bounding rect set. */ protected void applyConstaints() { boolean hasGlobalConstraints = constraints.size() > 0; particles.stream().map((p) -> { if (hasGlobalConstraints) { constraints.forEach((c) -> { c.apply(p); }); } return p; }).map((p) -> { if (p.bounds != null) { p.constrain(p.bounds); } return p; }).filter((p) -> (worldBounds != null)).forEachOrdered((p) -> { p.constrain(worldBounds); }); } /** * * @return */ public VerletPhysics2D clear() { behaviors.clear(); constraints.clear(); particles.clear(); springs.clear(); return this; } /** * * @return */ public Rect getCurrentBounds() { Vec2D min = new Vec2D(Float.MAX_VALUE, Float.MAX_VALUE); Vec2D max = new Vec2D(Float.MIN_VALUE, Float.MIN_VALUE); particles.stream().map((p) -> { min.minSelf(p); return p; }).forEachOrdered((p) -> { max.maxSelf(p); }); return new Rect(min, max); } /** * * @return */ public float getDrag() { return 1f - drag; } /** * @return the index */ public SpatialIndex getIndex() { return index; } /** * @return the numIterations */ public int getNumIterations() { return numIterations; } /** * Attempts to find the spring element between the 2 particles supplied * * @param a particle 1 * @param b particle 2 * @return spring instance, or null if not found */ public VerletSpring2D getSpring(Vec2D a, Vec2D b) { for (VerletSpring2D s : springs) { if ((s.a == a && s.b == b) || (s.a == b && s.b == a)) { return s; } } return null; } /** * @return the timeStep */ public float getTimeStep() { return timeStep; } /** * @return the worldBounds */ public Rect getWorldBounds() { return worldBounds; } /** * * @param c * @return */ public boolean removeBehavior(ParticleBehavior2D c) { return behaviors.remove(c); } /** * * @param c * @return */ public boolean removeConstraint(ParticleConstraint2D c) { return constraints.remove(c); } /** * Removes a particle from the simulation. * * @param p particle to remove * @return true, if removed successfully */ public boolean removeParticle(VerletParticle2D p) { return particles.remove(p); } /** * Removes a spring connector from the simulation instance. * * @param s spring to remove * @return true, if the spring has been removed */ public boolean removeSpring(VerletSpring2D s) { return springs.remove(s); } /** * Removes a spring connector and its both end point particles from the * simulation * * @param s spring to remove * @return true, only if spring AND particles have been removed successfully */ public boolean removeSpringElements(VerletSpring2D s) { if (removeSpring(s)) { return (removeParticle(s.a) && removeParticle(s.b)); } return false; } /** * * @param drag */ public final void setDrag(float drag) { this.drag = 1f - drag; } /** * @param index the index to set */ public void setIndex(SpatialIndex index) { this.index = index; } /** * @param numIterations the numIterations to set */ public void setNumIterations(int numIterations) { this.numIterations = numIterations; } /** * @param timeStep the timeStep to set */ public void setTimeStep(float timeStep) { this.timeStep = timeStep; behaviors.forEach((b) -> { b.configure(timeStep); }); } /** * Sets bounding box * * @param world * @return itself */ public VerletPhysics2D setWorldBounds(Rect world) { worldBounds = world; return this; } /** * Progresses the physics simulation by 1 time step and updates all forces and * particle positions accordingly * * @return itself */ public VerletPhysics2D update() { updateParticles(); updateSprings(); applyConstaints(); updateIndex(); return this; } private void updateIndex() { if (index != null) { index.clear(); particles.forEach((p) -> { index.index(p); }); } } /** * Updates all particle positions */ protected void updateParticles() { behaviors.forEach((b) -> { if (index != null && b.supportsSpatialIndex()) { b.applyWithIndex(index); } else { particles.forEach((p) -> { b.apply(p); }); } }); particles.stream().map((p) -> { p.scaleVelocity(drag); return p; }).forEachOrdered((p) -> { p.update(); }); } /** * Updates all spring connections based on new particle positions */ protected void updateSprings() { if (springs.size() > 0) { for (int i = numIterations; i > 0; i--) { for (VerletSpring2D s : springs) { s.update(i == 1); } } } } }