Is JOODE mature enough for a simple torque demo?

Hi,

I’m writing a set of physics applets for a Finnish highschool physics schoolbook. I was planning to use JOODE, but after checking out the latest version in JOODE CVS repository (the one at SourceForge) I run into several problems. I wonder if the current revision is broken, as the most tests didn’t show anything moving (even though the stepper was operative).

So anyway, my torque applet would be rather simple: A seesaw with two movable mass elements on top of that, supported by a cylinder of which place can be moved as well. Actually, the support can be anything as long as it can be moved. So, the system would be following:

Four mass bodies:

  • red box
  • blue box
  • seesaw
  • support
  • dumb object (for the joint system between seesaw and support)

Connected by

  • hinge (seesaw, dumb object)
  • slider (dumb object, support)
  • slider (support, null) (for fixing the support to some axis)
  • slider (red box, seesaw)
  • slider (blue box, seesaw)

I think I was able to construct such system with JOODE, but I didn’t get the hinge rotate at all. After writing some bits of test code I noticed a few other problems, e.g. setting facc (or any property related to forces, acceleration or velocity) didn’t do anything to a body. So I think there’s probably something wrong with the current build available in CVS.

If the problems are minor, I’m willing to contribute to bug fixing, testing or whatsoever. I think this project is cool and deserves support. :wink:

Best regards,
Lauri Svan

Hi Isvan,

I guess we should get your sample running, even if the JOODE is still under development :wink:

First you should get the tests running, since they are known to be working. Try editing the class TestingWorld in the package net.java.dev.joode.test. There is a method step(), which gets called for every iteration. In the current version in CVS this methods contains a line world.step(0.01f). This tells the engine to do a fixed stepping of 0.01seconds per step. On slow machines this value could be too low and result in very slow movement. Try changing this to world.step(1/fps.getFPS()), which should result in real time behaviour. Now the tests should show at least some movement.

Next is the world.step() method itself. It is known to have some bugs, so some of the test will result in strange behaviour. There is another function world.quickStep(), which shows an appropriate behaviour for all tests. This function is less accurate for small scale systems than world.step(), but works fine for most applications.

Let me know, if you get appropriate results from the tests now.

Hi, hdietrich!

Thanks for such a quick reply. I modified the test code as you proposed and it seems to work fine. Threfore, I suggest there’s something wrong with my code. I think it’s better to paste some of my code, even though it doesn’t run as such. You’ll probably get a grasp of what I’m trying to to. Actually, If you ever need pure java test classes (without any bindings), I’d be happy to contribute my code bits (2d scenegraph with picking, dragging etc.)

So a few explanations of the code below:

  • I’m binding JOODE to my 2d scenegraph, where “Model” actually refers to geometry of an object
  • JOODEPhysicsAnimator is run in the normal “update world, handle paint” simulation loop (yes, it’s not yet thread-safe, and this probably will cause some problems as UI events are handled in a separate thread)
  • I’m trying to make red box, black box movable in the x axis direction of seesaw, support movable in the x axis of world (fixed in y axis of the world)
  • I haven’t implemented any collision callbacks, I suppose I don’t even need them if all elements are connected by some joints
  • When I get this working, I probably need to add some geometry for ground to limit the rotation of seesaw
  • Later on, I probably isolate all bits of JOODE to my JOODEPhysicsAnimator class

Here are some open questions:

  • When attaching a geometry to body, is the mass, by default equally divided throughout the body (constant density)?
  • … Um… there’s got to be more open thingies, I’ll return them to later on after writing some new bits of test code…

		// Setup physics
		JOODEPhysicsAnimator physics = new JOODEPhysicsAnimator();
		physics.setController(controller);
		bluePhysics = physics.createJOODEPhysics(blue, 1);
		redPhysics = physics.createJOODEPhysics(red, 1);
		seesawPhysics = physics.createJOODEPhysics(seesaw, 1);
		supportPhysics = physics.createJOODEPhysics(support, 1);
		physics.addJOODEPhysics(redPhysics);
		physics.addJOODEPhysics(bluePhysics);
		physics.addJOODEPhysics(seesawPhysics);
		physics.addJOODEPhysics(supportPhysics);
		
		// Attach support to the floor, make it slideable	
		World world = physics.getWorld();
		JointSlider js = world.createSlider(null);
		js.attach(supportPhysics.getBody(), null);
		js.dJointSetSliderAxis(1, 0, 0);
		
		// Attach seesaw to support, make it a hinge to allow rotation
		// Make hinge slideable by attaching a slider on top of that
		JointHinge jh = world.createHinge(null);
		Body tempBody = world.createBody();
		Vector2D v = seesaw.getTranslation();
		tempBody.pos.set((float) v.getX(), (float) v.getY(), 0f);
		
		jh.attach(supportPhysics.getBody(), tempBody);
		jh.dJointSetHingeAnchor((float) v.getX(), (float) v.getY(), 0f);
		jh.dJointSetHingeAxis(0, 0, 1);
		
		JointSlider jhs = world.createSlider(null);
		jhs.attach(tempBody, seesawPhysics.getBody());
		jhs.dJointSetSliderAxis(1, 0, 0);

And for reference, here’s the functions of my physics class that get called



import java.util.ArrayList;
import fi.tammi.math.Physics;
import fi.tammi.math.Vector2D;
import fi.tammi.util.Model;
import java.awt.geom.Rectangle2D;
import net.java.dev.joode.world.*;
import net.java.dev.joode.body.*;
import net.java.dev.joode.util.Vector3;
import net.java.dev.joode.geom.Box;
import net.java.dev.joode.geom.Geom;
import net.java.dev.joode.space.NearCallback;
import net.java.dev.joode.space.SimpleSpace;
import net.java.dev.joode.space.Space;
import net.java.dev.joode.util.Quaternion;


public class JOODEPhysicsAnimator implements AnimationListener {

	private ArrayList joodeObjects;
	private AbstractAnimationController controller;

	private double updated;	
	private Vector2D temp;
	
	// Joode objects
	private World world;
	private Space space;
	
	private Vector3 tempVect;

	public JOODEPhysicsAnimator() {
		joodeObjects = new ArrayList();
		updated = 0;
		temp = new Vector2D();
		tempVect = new Vector3();
		
		world = new World();
		world.gravity.set(0, 9.81f, 0);
		space = new SimpleSpace(null);
	}
	
	public JOODEPhysics createJOODEPhysics(Model m, double mass) {
		JOODEPhysics p = new JOODEPhysics();
		p.setModel(m);
		p.setMass(mass);
		
		return p;
	}

	public void addJOODEPhysics(JOODEPhysics p) {
		joodeObjects.add(p);
	}

	public void removeJOODEPhysics(JOODEPhysics p) {
		joodeObjects.remove(p);
	}

	public void animationFinished() {
	}

	public void animationStarted() {
		updated = 0;
	}

	/**
	 * Update physics every frame
	 */
	public void frameChanged(double now) {

		double timeDiff = now - updated;
		updated = now;
		
		// Update geometry  positions & translations
		for (int i = 0; i < joodeObjects.size(); i++) {			
			// Update geometry positions based on physics
			updatePhysics((JOODEPhysics) joodeObjects.get(i));
		}
		
		// Update physics
		//space.collide(null, null);
		// TODO Get rid of this kludge - instead, 
		// put JOODE to handle zero difference
		world.step((float) timeDiff + (0.0001f));
		
		// Update positions for all the models
		for (int i = 0; i < joodeObjects.size(); i++) {			
			// Update geometry positions based on physics
			updateGeometryPosition((JOODEPhysics) joodeObjects.get(i));
		}
	}

	private void updateGeometryPosition(JOODEPhysics p) {
		Model m = p.getModel();
		Body b = p.getBody();
		
		// Fetch the translation & rotation from geometry
		temp.set((double) b.pos.getX(),(double) b.pos.getY());
		m.setTranslation(temp);		
		double rot = toAxisAngle(b.q, tempVect);
		m.setRotation(rot);
	}
	
	private void updatePhysics(JOODEPhysics p) {
		Model m = p.getModel();
		Body b = p.getBody();
		
		Vector2D xlat = m.getTranslation();
		double angle = m.getRotation();
		
		// Fetch the translation & rotation from geometry
		b.pos.set((float) xlat.getX(),(float) xlat.getY(), 0f);
		tempVect.set(0f, 0f, 1f);
		toQuaternion(tempVect, angle, b.q);	
	}

	public AbstractAnimationController getController() {
		return controller;
	}

	public void setController(AbstractAnimationController controller) {
		if (controller == this.controller)
			return;

		if (this.controller != null) {
			this.controller.removeAnimationListener(this);
		}
		if (controller != null) {
			controller.addAnimationListener(this);
		}

		this.controller = controller;
	}
	
	/** 
	 * Conversion from quaternion to axis angle, see the link below for formulas.
	 * 
	 *  http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
	 * 
	 * @param q Source quaternion
	 * @param dst Destination axis
	 * @return Destination angle
	 */
	public double toAxisAngle(Quaternion q, Vector3 dst) {			
		double angle = 2 * Math.acos(q.m[0]);
		dst.setX((float) (q.m[1] / Math.sqrt(1-q.m[3]*q.m[0])));
		dst.setY((float) (q.m[2] / Math.sqrt(1-q.m[3]*q.m[0])));
		dst.setZ((float) (q.m[3] / Math.sqrt(1-q.m[3]*q.m[0])));
		
		return angle;
	}
	
	/** 
	 * Conversion from axis angle to quaternion, see the link below for formulas.
	 * 
	 *  http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
	 * 
	 * @param v Source vector axis
	 * @param angle Source angle
	 * @param q Destination quaternion
	 * @return The quaternion transformed (q)
	 */		
	public Quaternion toQuaternion(Vector3 v, double angle, Quaternion q) {
		q.m[1] = v.getX() * (float) Math.sin(angle/2);
		q.m[2] = v.getY() * (float) Math.sin(angle/2);
		q.m[3] = v.getZ() * (float) Math.sin(angle/2);
		q.m[0] = (float) Math.cos(angle/2);
		
		return q;
	}	
	
	public class JOODEPhysics implements Physics {
		
		public static final double CONSTANT_DEPTH = 1.0;
		
		private Body body;
		private Vector2D velocityCache;
		private Vector2D accelerationCache;
		private double massCache;
		private Model model;
		private Geom geometry;		
		
		public JOODEPhysics() {
			body = world.createBody();
			velocityCache = new Vector2D();
			accelerationCache = new Vector2D();
			massCache = 0;
		}

		public Vector2D getVelocity() {
			velocityCache.set(body.avel.getX(), body.avel.getY());
			return velocityCache;
		}
		
		public void setMass(double mass) {
			massCache = mass;
			body.mass.adjust((float) mass);
		}

		public double getMass() {
			return massCache;
		}

		public Vector2D getAcceleration() {
			accelerationCache.set(body.facc.getX(), body.facc.getY());
			return accelerationCache;
		}
		
		public void setAcceleration() {
		
		}

		public Model getModel() {
			return model;
		}
		
		public void setModel(Model model) {
			this.model = model;
			
			// TODO Handle setting this to null
			
			// Update physics parameters
			// For now, update geometry as with bounding box
			Rectangle2D rect = model.getBoundingBox();
			float width = (float) rect.getWidth();
			float height = (float) rect.getHeight();
			float depth = (float) CONSTANT_DEPTH;
			geometry = new Box(space, width, height, depth);
			geometry.setBody(body);
			
			// Fetch the translation & rotation from model
			Vector2D xlat = model.getTranslation();
			double rot = model.getRotation();
			geometry.pos.set((float) xlat.getX(), (float) xlat.getY(), 0);			
			body.pos.set((float) xlat.getX(), (float) xlat.getY(), 0);
			// Set to pointing upwards from screen (z = 1, others 0)
			tempVect.set(0,0,1);
			toQuaternion(tempVect, rot, body.q);
		}		

		public Body getBody() {
			return body;
		}

		public Geom getGeometry() {
			return geometry;
		}
	}

	public Space getSpace() {
		return space;
	}

	public World getWorld() {
		return world;
	}	
}

I just had a short look at your code and detected a bug. Your method toAxisAngle is wrong. Correct code would be:

public double toAxisAngle(Quaternion q, Vector3 dst) {
 double angle = 2 * Math.acos(q.m[0]);
 dst.setX((float) (q.m[1] / Math.sqrt(1-q.m[0]*q.m[0])));
 dst.setY((float) (q.m[2] / Math.sqrt(1-q.m[0]*q.m[0])));
 dst.setZ((float) (q.m[3] / Math.sqrt(1-q.m[0]*q.m[0])));

 return angle;
}

Next thing I realized is that you do not supply a correct mass distribution momentOfIneratia (in accordance to your question). You have to add something like:

body.mass.createBox(density, lengthX, lengthY, lengthZ)

This will set create correct momentOfInertia value. You could add this in your setModel() method.

Could you explain to me whats the current behaviour of your code. Maybe I can see where your problems are if I understand what’s wrong with the behaviour.

Hi,

Thanks for the bugfixes. Even though they didn’t help me yet, they’ll sure be handy later on.

Currently I’m trying a very simple thing - to get the seesaw rotate as a result of moving the support under it. (e.g. if the support is placed to the other end of seesaw, the seesaw should start rotating around the support. Later on, I should make the support movable and place two movable mass cubes over the seesaw.

I just got the problem solved - it was due to setting hinge anchor improperly. I’ll keep you updated if I run into any problems.

Thanks!

Isvan, one more thing you should be aware of is that if you are changing the position or rotation of a body or geometry the whole physics might result in a wrong behaviour. This is because joints might get in a invalid state because of the changes position. It would be better to apply forces and a torque to the body, which makes the body move in the desired direction.

Btw. if you apply forces or torque to a body the forces will be only used for the next step of the iteration. After each step the external forces get reseted. If you want to apply a permanent force you have to add the forces before every update of add a motor to a joint.

You could reverse integrate (i.e. differentiate) the acceleration, velocity and positional formulae to get the force required for a deltaP for that amount of time. But use that with caution as the force might be HUGE resulting in undesired effects.

Also, that force, although its been reset, will cause the next frame and so on to propegate further. So unless your not worried about that, then differentiating the formulae will do good.

DP

Hi,

If I understood correctly, translating bodies may break the system, so I should apply forces to move the objects. Is it safe to reset velocities in each step? I’d like to maintain a drag’n’drop type feeling where the user could '“freeze the simulation”, pick and move the object, and restart the animation with changes applied.

Currently I’m having the following problem in setting proper forces: My drag’n’drop events are executed in different thread than the physics stepper, so the object movement (and therefore velocity and acceleration) cannot be directly calculated from the time between mouse events. I guess that could be overcome by just maintaining information on target position (for each moved object) and calculating the required force on each step until the end position is reached. Right?

setting the velocity is better than changing the position directly, but think, what’ll happen in case of a collision? JOODE changes in such case simply the acceleration to get the required effect. So setting the velocity directly might result in strange effects.

I think my reverse integration code works fine now, and I’m able to move the objects. My problem currently is that I’m able to constraint object movement with different joints (e.g. a hinge for rotation only, slider for movement in one axis etc.), but I haven’t found a way to turn them on/off when needed. I’m aiming to be able to drag objects at any time, and when dragging is not active, attach the objects to each other when drag is not active.

What is your proposal, should I just dynamically create two different joint groups and switch between them? I guess this means dynamically creating and destroying the joint groups, or is there a possibility of disabling them?

  • Lauri

The hinge and the slider joints have a construct JointLimitMotor. This can be used for constraining the joint (limit) and apply a momentum to the joint (motor). Maybe this will be the right place to look for.

BTW when your testcase works fine, I think it would be a great thing to add to the samples! :slight_smile:

Umm… Am Iooking at wrong place, if don’t find any JointLimitMotors in CVS? Looking the source code of Hinge and Slider, the both are currently unpowered (the motors & limits related code is commented out). Do you know if they can be safely uncommented, or should I actually rewrite the missing bits?

Are your sure you have the latest code? The current JointHinge and JointSlider code contains a line

public JointLimitMotor limot = new JointLimitMotor();

and the class JointLimitMotor must be present in the joint package as well.

No, I don’t and I assume neither does JOODE CVS Repository: http://cvs.sourceforge.net/viewcvs.py/joode/joode/src/net/java/dev/joode/joint/JointHinge.java?only_with_tag=HEAD&view=markup looks pretty old to me. Which repository are you using, and have you checked in the latest changes? ???

If not, I would really appreciate checking them in, as they’d be super useful for me. :slight_smile:

And for Arne, I’m glad to add my stuff to test cases when they’re working. As there’s plenty of stuff, I could probably add them as a separate packages to contrib direction (when it becomes available).

Oh, pew, looks like the anonymous cvs for the project is out of sync with the developer cvs. Is there a difference? Anybody an idea why it is ike this?

BTW: I cannot access cvs with my username and password at the moment. Sourceforge seems to have some problems.

Isvan: Are you really working with code almost 3 month old? I just wonder if there is anything working.

mmh the webpage cvs also shows an old version :frowning:

maybe we should send a bug-report to sourceforge?

Isvan, maybe you will be positively surprised by what happend. I can confirm that this code has been check in already some time ago.

Currently Sourceforge has some problems with the CVS repository anyway.

From http://sourceforge.net/docs/A04/:
( 2006-05-10 04:43:14 - Project CVS Service ) As of 2006-05-09 the developer CVS server had a disk-failure. As the new CVS infrastructure is in its final phases of rollout, we’ll be deploying it, in place of the current infrastructure, by end of week. We’ll be sending out an email to project administrators with further details later in the day, regarding how to access the new CVS servers and the changes that occurred with the new infrastructure.

Hmm SourceForge is really odd in its way of updating stuff from developer cvs to the public one. Right now the web cvs browser seems to be operative, but a normal cvs access giver the old files…