/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.physics.simulation;
import java.util.ArrayList;
import java.util.Set;
import org.jagatoo.datatypes.Enableable;
import org.openmali.vecmath2.Vector3f;
import org.xith3d.loop.Updatable;
import org.xith3d.loop.UpdatingThread.TimingMode;
import org.xith3d.physics.collision.CollisionResolversManager;
import org.xith3d.physics.collision.Collision;
import org.xith3d.physics.simulation.joints.*;
/**
* A simulation world
*
* @author Amos Wenger (aka BlueSky)
* @author Marvin Froehlich (aka Qudus)
*/
public abstract class SimulationWorld implements Updatable, Enableable
{
protected SimulationEngine engine;
/** All our bodies */
private final ArrayList<Body> bodies = new ArrayList<Body>();
/** All our joints */
private final ArrayList<Joint> joints = new ArrayList<Joint>();
private final Vector3f gravity;
private boolean gravityEnabled = true;
private long stepMicros = -1L;
private long maxStepMicros = -1L;
private boolean enabled = true;
/**
* @return the {@link SimulationEngine}.
*/
public SimulationEngine getEngine()
{
return ( engine );
}
/** Put impl-specific stuff here. */
protected abstract void setGravityImpl( float x, float y, float z );
/**
* @param x the gravity to set
* @param y the gravity to set
* @param z the gravity to set
*/
public final void setGravity( float x, float y, float z )
{
this.gravity.set( x, y, z );
setGravityImpl( x, y, z );
}
/**
* @param gravity the gravity to set
*/
public final void setGravity( Vector3f gravity )
{
setGravity( gravity.getX(), gravity.getY(), gravity.getZ() );
}
/**
* Sets whether gravity is (world-)globally enabled or not.
*
* @param enabled
*/
public void setGravityEnabled( boolean enabled )
{
if ( enabled == this.gravityEnabled )
return;
this.gravityEnabled = enabled;
if ( enabled )
setGravityImpl( gravity.getX(), gravity.getY(), gravity.getZ() );
else
setGravityImpl( 0f, 0f, 0f );
}
/**
* @return whether gravity is (world-)globally enabled or not.
*/
public final boolean isGravityEnabled()
{
return ( gravityEnabled );
}
/**
* @return the gravity
*/
public final Vector3f getGravity()
{
return ( gravity.getReadOnly() );
}
/**
* Creates a new Body.
*
* @return the newly created Body.
*/
protected abstract Body newBodyImpl();
/**
* Creates a new Body.
*
* @return the newly created Body.
*/
public final Body newBody()
{
Body body = newBodyImpl();
//addBody(body);
return ( body );
}
/** Put impl-specific stuff here. */
protected abstract void addBodyImpl( Body body );
/**
* Adds a body to the world.
*
* @param body the body to be added
*/
public final void addBody( Body body )
{
if ( !this.bodies.contains( body ) )
{
this.bodies.add( body );
addBodyImpl( body );
}
}
/** Put impl-specific stuff here. */
protected abstract void removeBodyImpl( Body body );
/**
* Removes a body from the world.
*
* @param body the body to be removed
*/
public final void removeBody( Body body )
{
if (this.bodies.remove( body ) )
{
removeBodyImpl( body );
}
}
/**
* @return the number of bodies in the world.
*/
public final int numBodies()
{
return ( this.bodies.size() );
}
/**
* Gets a body from its index.
*
* @param i the index of the body
*
* @return the body
*/
public final Body getBody( int i )
{
return ( this.bodies.get( i ) );
}
protected JointLimitMotor newJointLimitMotor( float defaultCFM, float defaultERP )
{
return ( new JointLimitMotor( defaultCFM, defaultERP ) );
}
public final JointLimitMotor newJointLimitMotor()
{
return ( newJointLimitMotor( 0.2f, 1e-6f ) );
}
/**
* Creates a new BallJoint.
*
* @param body1
* @param body2
*
* @return the newly created BallJoint.
*/
protected abstract BallJoint newBallJointImpl( Body body1, Body body2 );
/**
* Creates a new BallJoint.
*
* @param body1
* @param body2
*
* @return the newly created BallJoint.
*/
public final BallJoint newBallJoint( Body body1, Body body2 )
{
BallJoint joint = newBallJointImpl( body1, body2 );
//addJoint( joint );
return ( joint );
}
/**
* Creates a new FixedJoint.
*
* @param body1
* @param body2
*
* @return the newly created FixedJoint.
*/
protected abstract FixedJoint newFixedJointImpl( Body body1, Body body2 );
/**
* Creates a new FixedJoint.
*
* @param body1
* @param body2
*
* @return the newly created Hinge2Joint.
*/
public final FixedJoint newFixedJoint( Body body1, Body body2 )
{
FixedJoint joint = newFixedJointImpl( body1, body2 );
//addJoint( joint );
return ( joint );
}
/**
* Creates a new HingeJoint.
*
* @param body1
* @param body2
*
* @return the newly created HingeJoint.
*/
protected abstract HingeJoint newHingeJointImpl( Body body1, Body body2 );
/**
* Creates a new HingeJoint.
*
* @param body1
* @param body2
*
* @return the newly created HingeJoint.
*/
public final HingeJoint newHingeJoint( Body body1, Body body2 )
{
HingeJoint joint = newHingeJointImpl( body1, body2 );
//addJoint( joint );
return ( joint );
}
/**
* Creates a new Hinge2Joint.
*
* @param body1
* @param body2
*
* @return the newly created Hinge2Joint.
*/
protected abstract Hinge2Joint newHinge2JointImpl( Body body1, Body body2 );
/**
* Creates a new Hinge2Joint.
*
* @param body1
* @param body2
*
* @return the newly created Hinge2Joint.
*/
public final Hinge2Joint newHinge2Joint( Body body1, Body body2 )
{
Hinge2Joint joint = newHinge2JointImpl( body1, body2 );
//addJoint( joint );
return ( joint );
}
/**
* Creates a new SliderJoint.
*
* @param body1
* @param body2
*
* @return the newly created SliderJoint.
*/
protected abstract SliderJoint newSliderJointImpl( Body body1, Body body2 );
/**
* Creates a new SliderJoint.
*
* @param body1
* @param body2
*
* @return the newly created SliderJoint.
*/
public final SliderJoint newSliderJoint( Body body1, Body body2 )
{
SliderJoint joint = newSliderJointImpl( body1, body2 );
//addJoint( joint );
return ( joint );
}
/** Put impl-specific stuff here. */
protected abstract void addJointImpl( Joint joint );
/**
* Adds a joint to the world.
*
* @param joint the joint to be added
*/
public final void addJoint( Joint joint )
{
if ( !this.joints.contains( joint ) )
{
this.joints.add( joint );
addJointImpl( joint );
}
}
/** Put impl-specific stuff here */
protected abstract void removeJointImpl( Joint joint );
/**
* Removes a joint from the world.
*
* @param joint the joint to be removed
*/
public final void removeJoint( Joint joint )
{
if ( this.joints.remove( joint ) )
{
removeJointImpl( joint );
}
}
/**
* @return the number of joints in the world
*/
public final int numJoints()
{
return ( this.joints.size() );
}
/**
* Gets a joint from its index.
*
* @param i the index of the joint
*
* @return the joint
*/
public final Joint getJoint( int i )
{
return ( this.joints.get( i ) );
}
/**
* @return a list of all functions available for stepping this world
*/
public abstract Set<String> getStepperFunctions();
/**
* Sets the stepping function to be used.
*
* @param stepperFunction the stepper function, it should be
* one of the stepper functions proposed by {@link #getStepperFunctions()}
* If stepperFunction is not a valid stepping function, the stepping
* function will not be changed.
* If stepperFunction is null, the default stepping function will be set.
*/
public abstract void setStepperFunction( String stepperFunction );
/**
* @return The current stepping function used
*/
public abstract String getStepperFunction();
/**
* Sets the constant internal step time in microseconds.
*
* @param micros
*/
public final void setStepSize( long micros )
{
if ( ( micros < 1L ) && ( micros != -1L ) )
throw new IllegalArgumentException( "micros must be -1 or greater then 0" );
this.stepMicros = micros;
}
/**
* @return the constant internal step time in microseconds.
*/
public final long getStepSize()
{
if ( stepMicros == -1L )
return ( engine.getStepSize() );
return ( stepMicros );
}
/**
* Sets the maximum internal step time in microseconds.
*
* @param micros
*/
public final void setMaxStepSize( long micros )
{
if ( ( micros < 1L ) && ( micros != -1L ) )
throw new IllegalArgumentException( "micros must be -1 or greater then 0" );
this.maxStepMicros = micros;
}
/**
* @return the maximum internal step time in microseconds.
*/
public final long getMaxStepSize()
{
if ( maxStepMicros == -1L )
return ( engine.getMaxStepSize() );
return ( maxStepMicros );
}
/**
* This method is called before the {@link #stepImpl(long)} method is
* called.
*/
protected abstract void beforeStep();
/**
* This method is called after the {@link #stepImpl(long)} method is
* called.
*/
protected abstract void afterStep();
/**
* Step the simulation = advance the time
* @param stepMicros the size of the step which should
* be taken. Note that 2 steps of .01f isn't equal to
* 1 step of .02f : the smaller is your stepSize, the
* more accurate your simulation (but computations take
* more time). As always, you should find a tradeoff
* between speed and accuracy (which is the whole point
* of physic simulation, anyway).
* Begin with .01f and adjust it later if you don't know
* what to put.
* Note : you could also make the stepSize "adaptative",
* ie proportional to the (real) time that has passed since
* the last step, but sometimes adaptive stepSize can
* cause some simulation problems (jumps, instability).
* If you have these problems you could also try to
* advance by the right quantity of time by several
* fixed size steps.
*
* @see #setStepperFunction(String)
*/
protected abstract void stepImpl( long stepMicros );
private long stepAccumulator = 0L;
/**
* Step the simulation = advance the time
* @param stepMicros the size of the step which should
* be taken. Note that 2 steps of .01f isn't equal to
* 1 step of .02f : the smaller is your stepSize, the
* more accurate your simulation (but computations take
* more time). As always, you should find a tradeoff
* between speed and accuracy (which is the whole point
* of physic simulation, anyway).
* Begin with .01f and adjust it later if you don't know
* what to put.
* Note : you could also make the stepSize "adaptative",
* ie proportional to the (real) time that has passed since
* the last step, but sometimes adaptive stepSize can
* cause some simulation problems (jumps, instability).
* If you have these problems you could also try to
* advance by the right quantity of time by several
* fixed size steps.
*
* @see #setStepperFunction(String)
*/
public final void step( long stepMicros, CollisionResolversManager collisionResolversManager )
{
final long step = getStepSize();
final long maxStep = getMaxStepSize();
if ( stepMicros > maxStep )
stepMicros = step;
beforeStep();
stepAccumulator += stepMicros;
while ( stepAccumulator >= step )
{
if ( collisionResolversManager != null )
{
collisionResolversManager.update();
}
stepImpl( step );
stepAccumulator -= step;
}
for ( int i = 0; i < bodies.size(); i++ )
{
bodies.get( i ).refresh();
}
for ( int i = 0; i < joints.size(); i++ )
{
joints.get( i ).refresh();
}
afterStep();
}
/**
* This method should be called by yourself
* (yeah, SimulationWorld is listening to you !!)
* when you want a collision to be resolved by the
* {@link SimulationWorld}, which normally means, that
* the two bodies won't interpenetrate (well, if the
* constraint is solvable).
* You can set one Body to null, it means just one Body
* needs to be moved (e.g. when a basket ball hits the
* ground, you want the basket ball to react, not the
* ground :) ).
*
* @param collision
* @param body1
* @param body2
* @param surfParams
*/
public abstract void resolveCollision( Collision collision, Body body1, Body body2, SurfaceParameters surfParams );
/**
* This method should be called by yourself
* (yeah, SimulationWorld is listening to you !!)
* when you want a collision to be resolved by the
* {@link SimulationWorld}, which normally means, that
* the two bodies won't interpenetrate (well, if the
* constraint is solvable).
* One of the Collideables can have no body, it means just one Body
* needs to be moved (e.g. when a basket ball hit the
* ground, you want the basket ball to react, not the
* ground :) ).
*
* @param collision
* @param surfParams
*/
public final void resolveCollision( Collision collision, SurfaceParameters surfParams )
{
resolveCollision( collision, collision.getCollideable1().getBody(), collision.getCollideable2().getBody(), surfParams );
}
/**
* Sets this SimulationWorld enabled/disabled.<br>
* If not enabled, the update() method will do nothing.
*
* @param enabled
*/
public final void setEnabled( boolean enabled )
{
this.enabled = enabled;
}
/**
* @return if this SimulationWorld is enabled.<br>
* If not enabled, the update() method will do nothing.
*/
public final boolean isEnabled()
{
return ( enabled );
}
/**
* {@inheritDoc}
*/
public final void update( long gameTime, long frameTime, TimingMode timingMode )
{
if ( isEnabled() )
{
step( timingMode.getMicroSeconds( frameTime ), null );
}
}
/**
* Creates a new simulation world.
*
* @param engine the simulation engine we belong to
*/
public SimulationWorld( SimulationEngine engine )
{
this.engine = engine;
this.gravity = new Vector3f( 0f, -9.81f, 0f );
}
}