Is there an accuracy problem in the cinematics’ positioning?

Hello,



I wrote an acceleration function that I believe is accurate (after numerous attempts from @nehon - thank you!). however I am not getting the expected results when dealing with smaller numbers. I am not getting the accuracy needed for my project and am very intrigued to know why not doubting a possibility of something wrong with the engine or a possibility that I have missed something here.



To simplify the problem, I have a vehicle, that accelerates and decelerates at a constant acceleration. For example, at acceleration = 1 m/s for a duration of 0.84s you would expect the speed to reach 0.84m/s at the end of the duration - which happens perfectly. However, the issue is with the positioning of the spatial, it is expected to reach (0.355; 0.0; 0.0) from its initial position (0,0,0) but it only reaches (0.122676626, 0.0, 0.0) at time 0.84s (using the function x(t) = x(0) + v(0)*t + 1/2(at^2)) although the speed at that time is 0.84s. Could there be a problem is the positioning itself?? If not, what am I missing?



If we go with larger units, you will notice the change to be minimal, but unfortunately I am dealing with fractions of units and the calculation accuracy is very important to me.



PS: my example for now deals with the acceleration part (and not the deceleration so don’t worry about it for now) - you solve one, you solve the other



Below is the full function for your reference:



[java]

public class AccelerateTrack extends AbstractCinematicEvent {



private static final Logger log = Logger.getLogger(AccelerateTrack.class.getName());

private Vector3f startPosition;

private Vector3f endPosition;

private Spatial spatial;

private String spatialName = “”;

private float value = 0;

private float maxSpeed;

private float currentSpeed;

private float acceleration;

private float previousSpeed;

float epsilon = 0.001f; // used to compare floats accurately



// create an instance of the Simpleapplication inside Jme3Cinematics

private Jme3Cinematics myApp = Jme3Cinematics.getApp();



// reads the asset manager initialized in Jme3Cinematics

private AssetManager assetManager = myApp.getAssetManager();





public AccelerateTrack(Spatial spatial, Vector3f endPosition) {

this.endPosition = endPosition;

this.spatial = spatial;

spatialName = spatial.getName();

}







public AccelerateTrack(Spatial spatial, Vector3f endPosition, float initialDuration,

float acceleration, float previousSpeed, float maxSpeed,

LoopMode loopMode)

{

super(initialDuration, loopMode);

this.endPosition = endPosition;

this.spatial = spatial;

//this.accelerationFactor = accelerationFactor;

this.acceleration = acceleration;

this.maxSpeed = maxSpeed;

this.previousSpeed = previousSpeed;

this.spatialName = spatial.getName();

//speed = previousSpeed; // speed is a protected variable in cinemtics events (not used)

currentSpeed = previousSpeed; // speed is a protected variable in cinemtics events

}





@Override

public void initEvent(Application app, Cinematic cinematic) {

super.initEvent(app, cinematic);

if (spatial == null) {



spatial = cinematic.getScene().getChild(spatialName);

if (spatial == null) {

} else {

log.log(Level.WARNING, “spatial {0} not found in the scene”, spatialName);

}

}

}





@Override

public void onPause() {

// do nothing

}



@Override

public void onPlay() {

if (playState != playState.Paused) {

startPosition = spatial.getWorldTranslation().clone();

}

if (initialDuration == 0 && spatial != null) {



spatial.setLocalTranslation(endPosition);

}



}



@Override

public void onStop() {

value = 0;



}





@Override

public void onUpdate(float tpf) {



// to be synchronized with setTime()

if (spatial != null)

{

// if positive acceleration

if(acceleration > epsilon)

{

currentSpeed = (time * acceleration); // * myApp.getAnimationSpeed());

//currentSpeed += tpf * (acceleration * myApp.getAnimationSpeed());



// limit speed to a max speed

if( currentSpeed > maxSpeed )

{

currentSpeed = maxSpeed ;

}



}



// if negative acceleration (or deceleration)

else if(acceleration < -epsilon)

{

//currentSpeed += tpf * (acceleration * myApp.getAnimationSpeed()); // will result in drive braking excessively and stopping earlier

currentSpeed = maxSpeed + ( time * (acceleration));// / myApp.getAnimationSpeed())); // we divide b/c deceleration is reduced (by nx) with faster animation



// assuring vehicle stops completely when 0 speed is reached

if( currentSpeed < -epsilon )

{

currentSpeed = 0;

}

}



else if( FastMath.abs(currentSpeed) < -epsilon )

{

currentSpeed = 0;

}

}



// the exact current position is x(t) = x(0) + v(0)*t + 1/2(at^2)

// which is the integral of the current speed - previousPosition

if (spatial != null) {

Vector3f dir = endPosition.subtract( startPosition );

dir.normalize();

Vector3f velocity = dir.mult( currentSpeed * tpf + acceleration *tpf * tpf * 0.5f );

Vector3f pos = spatial.getLocalTranslation();

spatial.setLocalTranslation( pos.add(velocity) );





}

}





@Override

public void write(JmeExporter ex) throws IOException {

super.write(ex);

OutputCapsule oc = ex.getCapsule(this);

oc.write(spatialName, “spatialName”, “”);

oc.write(endPosition, “endPosition”, null);

}



@Override

public void read(JmeImporter im) throws IOException {

super.read(im);

InputCapsule ic = im.getCapsule(this);

spatialName = ic.readString(“spatialName”, “”);

endPosition = (Vector3f) ic.readSavable(“endPosition”, null);

}











}

[/java]

I had a bunch of answers but the text got truncated somehow. Not sure why. Gonna check some more tho. This was interesting.



Did nobody mention you can use RigidBodyControl for velocity tho? It seems like it would be good here?

I looked at the method and it seems the position stuff is not applicable here, because pos isn’t x(0).



There is probably better ways but storing displacement from each loop to the next would be good, like this:



Make sure the class got a member variable “prevDisp” or something, and initialize it to 0.



// the exact current position is x(t) = x(0) + v(0)t + 1/2(at^2)

// which is the integral of the current speed - previousPosition

if (spatial != null) {

Vector3f dir = endPosition.subtract( startPosition );

dir.normalize();



float disp = currentSpeed
time0.5f; //Because v(0) is 0, and x(0) is 0.

Vector3f velocity = dir.mult(disp - prevDisp);

prevDisp = disp;



Vector3f pos = spatial.getLocalTranslation();

spatial.setLocalTranslation( pos.add(velocity) );







The reason for using currentSpeed
time0.5f instead of accelerationtimetime0.5f is because you might have capped currentSpeed, so the two expressions are not necessarily the same. This maybe caused the issues before.



Note that if currentSpeed = maxSpeed the displacement will be constant (maxSpeedtpf0.5f) every frame, as expected when acceleration is 0.



This was interesting. Please update if you try this out.

It happened again… some of the text I write gets truncated. Wonder if its some browser cache crap. I think its all in there tho, except the last part of the code snippet but its the same as in your code anyways.

If its calculation accuracy you’re after then floating point numbers are not what you want. This paper is the best, and most in-depth, piece I’ve seen written on the subject: What Every Computer Scientist Should Know About Floating-Point Arithmetic



That said, you should consider your units of measurement and how they’re being scaled, if at all.

Thanks guys I am currently on the road and don’t have computer access, I’ll update you as soon as I try it out on Monday.



thanks again

so I see where you’re coming from but (excuse my Math noobness) what will be my prevDisp that you’re storing?



[java]float prevDisp = previousSpeed * ?[/java]

@sbook thanks for the article. I know, float numbers is probably not the best but i’m interested to know if @andolo’s solution wold improve things. Although the accuracy I currently have is good enough for my project, I am having a synch issue whenever I pause and it won’t hurt seeing if the extra accuracy would solve it.

The problem is probably not the accuracy per se but how you fail to account for it in your code. Just don’t try and combine huge and tiny values, no good can come out of that. like e.g. adding 1000000000000 and 0.00000000000001 will probably result in 1000000000000 and not in 1000000000000.00000000000001 whereas adding 0.00000000000001 and 0.00000000000001 will work correctly and yield 0.00000000000002

@garnaout said:
so I see where you're coming from but (excuse my Math noobness) what will be my prevDisp that you're storing?

[java]float prevDisp = previousSpeed * ?[/java]


Include it as a member variable. Don't initialize, it's supposed to be 0 at first anyways. Look in the snippet, you see its updated every time from there.

also to avoid issues you might want to make your counter variable a “double”

Besides all the float precision stuff, I read the code some more and there’s lots of things I don’t get.



Are the spatials just gonna move in straight lines? like from point (0,0,0) to (1,1,1)? That seems like a weird kind of animation system. Is it like a chess game or something?



If so, you could remove that Vector3f dir = endPosition.subtract( startPosition ); from the update loop, and calculate it when start and end-pos is set instead. If not, this thing will break, because if they start moving in curves its no longer linear acceleration.



What’s the point of those epsilon things? What happens if acceleration is between epsilon and 0 that you have to account for? Since acceleration is supposed to be linear, couldn’t you just check it when its set, rather then checking each update?



In case you don’t know, you can check negative acceleration against -0f, that’s a valid number and its different from 0 (if that’s what you’re worried about).



Finally, why is the animation track thing not working here to begin with? Isn’t it supposed to move spatials around automatically? And if it doesn’t, is that the reason rigidbody can’t be used? No animation expert heh, is it some time sync weirdo thing I have no idea.

1- lesson learned , I will avid floats from now on. I really don’t see why can’t we always use doubles instead of floats.



@androlo, I tried the new code and the old one was actually more accurate. It is fine since the old code you provided was accurate enough to continue my work. Might be probably an issue of float inaccuracy and like normen said that I am not taking into account that I am using floats.



FYI I am animating a simulation of cars/robots that accelerate at a constant speed then coast at a constant speed than decelerate just before reaching the target until coming to a complete stop. I read the simulation trace through an adapter I created to convert it to cinematics readable language.



There is no animationTrack in Jme3 cinematics there’s positionTrack. In fact most of the code I am working on is already deprecated and replaced with spatial animation.



the final code I posted, works just fine for the purpose of my project.



Again thanks a lot

That means there are still bugs. Running the disp and prevDisp stuff would give correct values, just it might not be the most effective way of doing things. The problem could be with the time variable, or the way this code interacts with the animation stuff.



This is easy to debug. Just check that time, currentSpeed and prevDisp are all 0 when the animation starts. Put a debug control point inside onUpdate and check that everything looks right.



Anything other then near-perfect precision in this case is due to code bugs, not floating point errors. In your first example you get from 0.0 to 0.12 instead of 0.34. You loose like 2/3rds of the distance, that is not because of rounding. The float errors they’re talking about are very small. You’re not doing some weird stuff btw, just adding small values repeatedly to a variable, so when you reach the target it’s been thousands of frames of rounding anyways, it will pretty much even out. What could be a problem tho is you never really hit the endpoint, you only come near it.



I’m about to do a similar thing for a bobber in a fishing thing btw, that’s why I was interested in this. Would have been nice to see what caused the errors. :slight_smile:

there were issues indeed with time and it will be fixed in the next SVN so i’m going to give it a shot tomorrow.

Really?



Great for both of us then :slight_smile:

So I am running into the same issue again. We reduced the size of the objects, so now I reallly need perfect accuracy. I converted everything to double and here’s my latest code. If u look at the printout I am still getting an inaccuracy around 0.1-0.3 now since the units are in meters - thats bad! see code below (is there anything I could improve?)



[java]

public class AccelerateTrack extends AbstractCinematicEvent {



private static final Logger log = Logger.getLogger(AccelerateTrack.class.getName());

private Vector3f startPosition;

private Vector3f endPosition;

private Spatial spatial;

private String spatialName = “”;

private double value = 0;

private double maxSpeed;

private double currentSpeed;

private double acceleration;

private double previousSpeed;

float epsilon = 0.001f; // used to compare floats accurately

float animationSpeed;

double prevDisp;



// create an instance of the Simpleapplication inside Jme3Cinematics

private Jme3Cinematics myApp = Jme3Cinematics.getApp();



// reads the asset manager initialized in Jme3Cinematics

private AssetManager assetManager = myApp.getAssetManager();





public AccelerateTrack(Spatial spatial, Vector3f endPosition) {

this.endPosition = endPosition;

this.spatial = spatial;

spatialName = spatial.getName();

}







public AccelerateTrack(Spatial spatial, Vector3f endPosition, double initialDuration,

double acceleration, double previousSpeed, double maxSpeed,

LoopMode loopMode)

{

super((float) initialDuration, loopMode);

this.endPosition = endPosition;

this.spatial = spatial;

this.acceleration = acceleration;

this.maxSpeed = maxSpeed;

this.previousSpeed = previousSpeed;

this.spatialName = spatial.getName();

currentSpeed = previousSpeed;

}





@Override

public void initEvent(Application app, Cinematic cinematic) {

super.initEvent(app, cinematic);

if (spatial == null) {



spatial = cinematic.getScene().getChild(spatialName);

if (spatial == null) {

} else {

log.log(Level.WARNING, “spatial {0} not found in the scene”, spatialName);

}

}

}





@Override

public void onPause() {

// do nothing

}



@Override

public void onPlay() {

if (playState != playState.Paused) {

startPosition = spatial.getWorldTranslation().clone();

}

if (initialDuration == 0 && spatial != null) {



spatial.setLocalTranslation(endPosition);

}



}



@Override

public void onStop() {

value = 0;



}



// note that reverse here is done via acceleration, then deceleration

// and NOT deceleration than acceleration (like the sim)

// tpf : time elapsed since the last frame (not constant)

// time: timer since the beginning of the animation event (0) until its duration

@Override

public void onUpdate(float tpf) {



// to be synchronized with setTime()

if (spatial != null)

{

// the animation speed (e.g. 2x)

animationSpeed = myApp.getAnimationSpeed();



// if positive acceleration

if(acceleration > epsilon)

{

currentSpeed = (time * acceleration) * animationSpeed;



//System.out.println("Accelerated Speed: " +currentSpeed);

//System.out.println("Acceleration time: " +time);

//System.out.println("Animation Speed: "+myApp.getAnimationSpeed());



// limit speed to a max speed

if( currentSpeed > maxSpeed * animationSpeed )

{

currentSpeed = maxSpeed ;

}



}



// if negative acceleration (or deceleration)

else if(acceleration < -epsilon)

{

//current speed = maxSpeed + (-acceleration speed so far) or deceleration

currentSpeed = (previousSpeed * animationSpeed) + ( time * (acceleration) * animationSpeed);

//currentSpeed = (maxSpeed * animationSpeed) + ( time * (acceleration) * animationSpeed);



//System.out.println("Reduced Speed: "+currentSpeed);

//System.out.println("Deceleration Time: “+time);



// assuring vehicle stops completely when 0 speed is reached

if( currentSpeed < -epsilon )

{

currentSpeed = 0;

}

}



// TODO: might not be needed - implemented for vehicle going in reverse bug

else if( currentSpeed < -epsilon )

{

currentSpeed = 0;

//System.out.println(“FORCE STOP SPATIAL!”);

}



System.out.println(“Final SPEED: “+currentSpeed);

System.out.println(”>>>>>>>>> Spatial Position is >>>>>>>>>>>>>: " +spatial.getLocalTranslation());

System.out.println(”>>>>>>>>> Spatial Destination is >>>>>>>>>>>>>: " +endPosition);

System.out.println(”>>>>>>>>> Spatial Start Position is >>>>>>>>>>>>>: " +startPosition);

}



// the exact current position is x(t) = x(0) + v(0)*t + 1/2(at^2)

// which is the integral of the current speed - previousPosition

if (spatial != null) {



Vector3f dir = endPosition.subtract( startPosition );

dir.normalize();

//double disp = currentSpeed * time * 0.5f;

//Vector3f velocity = dir.mult((float) (disp - prevDisp));

//prevDisp = disp;

Vector3f velocity = dir.mult( (float) (currentSpeed * tpf + acceleration *tpf * tpf * 0.5f) ); // this is a position

Vector3f pos = spatial.getLocalTranslation();

spatial.setLocalTranslation( pos.add(velocity) );





}

}



@Override

public void write(JmeExporter ex) throws IOException {

super.write(ex);

OutputCapsule oc = ex.getCapsule(this);

oc.write(spatialName, “spatialName”, “”);

oc.write(endPosition, “endPosition”, null);

}



@Override

public void read(JmeImporter im) throws IOException {

super.read(im);

InputCapsule ic = im.getCapsule(this);

spatialName = ic.readString(“spatialName”, “”);

endPosition = (Vector3f) ic.readSavable(“endPosition”, null);

}



}

[/java]



thanks again

your bug is variable “time” in update, why do you have it as floating point number :

a) java stores time as a long, look at java.lang.Date,

b) java returns time as long with System.currentTimeMillis method.

so if I have it as a long:



[java] currentSpeed = (previousSpeed * animationSpeed) + ( (long) time * (acceleration) * animationSpeed); [/java]





it’s actually less accurate than keeping it as a float

if you have it as a long you cant use jme’s float tpf, you will have to create your own timer and use system.CurrentSystemMillis();