FrictionCallBack causes drift

I have been developing a (very crude) pool game in order to learn jmephysics2. I ran into the problem that ODE doesn't support rolling friction (i.e the balls keep them same amount of force and never slow down). I searched the forums and found that DarkFrog had created the FrictionCallBack class to deal with this problem. I have added the callback and it kinda works but when the balls are slowing down they tend to drift away from the course you would expect them to take. If I take the callback out then the balls don't drift so I'm guessing it is the callback that is causing the problem.



Has anybody else experienced this problem?

yes - though not with a pool game.



I decreased the tirangle count of my sphere (so I could see its indentations - 10,10 I think), and I could see how it was spinning & rolling.



–> and look at the code for the frictioncallback - it uses simple clamping, so it shouldn't be causing drift… though I did over engineer it when looking for the problem. Then I cut it down (rolling friction only)… but I digress…





put on a texture / reduce the trimesh so that you can make out details like spin and roll…



oh - and now that I read your post… I'll post up my version… which is better in some ways, and not in others… it tends to clamp to axis (fine for me)…

You wrote a better friction callback?  Why didn't you contribute it sooner?  Mine was sort of hacked quickly together to fill the gap but if there's something better or updates someone wants to contribute I'm happy to make changes.

ummm… not better, I'm just clamp everything towards zero differently… its still drifts, but it doesn't seem to as much.



of course, it could just be the 'my code is better than your code' gene… I'm sure you are familiar with it.



well, don't laugh to much. the only real change is in applyForce… so to save embarrasment, I'll just post that (and the static vars)



it'd be nice if there was a non linear way to do this… but in order to cater for different times, I get the feeling a little more complex maths will be needed… and I really can't be bothered figuring out how to do integration. (velocity vs time - distance is the integration).



heh. darkfrog, don't take me too seriously. Just another grunt with a keyboard and more than 1 brain cell.

I don't like the way Friction Callback works - but it is the simplest… linear (addition) scales better than multiplication.




   private static final float CLAMP_POSITIVE = 0.00001f;
   private static final float CLAMP_NEGATIVE = -CLAMP_POSITIVE;

   private void applyFriction(float change) {
      if (store.x > CLAMP_POSITIVE) {
         store.x -= change;
         if (store.x < CLAMP_POSITIVE) store.x = 0.0f;
      } else if (store.x < CLAMP_NEGATIVE) {
         store.x = store.x + change;
         if (store.x > CLAMP_NEGATIVE) store.x = 0.0f;
      } else store.x = 0f;
      if (store.y > CLAMP_POSITIVE) {
         store.y = store.y - change;
         if (store.y < CLAMP_POSITIVE) store.y = 0.0f;
      } else if (store.y < CLAMP_NEGATIVE) {
         store.y = store.y + change;
         if (store.y > CLAMP_NEGATIVE) store.y = 0.0f;
      } else store.y = 0f;
      if (store.z > CLAMP_POSITIVE) {
         store.z = store.z - change;
         if (store.z < CLAMP_POSITIVE) store.z = 0.0f;
      } else if (store.z < CLAMP_NEGATIVE) {
         store.z = store.z + change;
         if (store.z > CLAMP_NEGATIVE) store.z = 0.0f;
      } else store.x = 0f;
   }



oh - and as to the 'drift' - that is to do with changing values of approximate (float) values - and you are doing it many times.

Thanks for the replies, didn't want to casue any trouble :-o


oh - and as to the 'drift' - that is to do with changing values of approximate (float) values - and you are doing it many times.


I wonder if the drift problem might be reduced if the friction was only applied every say 20th step (as the physics steps are very frequent this might not be noticeable to the user)

Thanks again

I don't think that this is a floating point accuracy problem.



The applyFriction method is just weird :P. It applies more friction if the velocity is not axis aligned and it does not even scale the velocity linearly in this case.



Replacing it with this code could help:


    private void applyFriction(float change) {
        float newLength = store.length() - change;
        if ( newLength < 0 )
        {
            store.set( 0, 0, 0 );
        } else {
            store.normalizeLocal().multLocal( newLength );
        }
    }


I don't have a test case to confirm that, though.

Hey irrisor.



you solution is accurate with direction, but is not consistent with time. I usually use this kind of method (gives x2 drag), but it requires a fixed time constant, which is not something that can be assumed.

I get the feeling that to work it out, you need integration (or derivative… one of them). It'd be cool (more realistic), but a little heavy on the processing.





However, I do think I have a solution… looking at your code made me think of something (thank goodness we all have different styles). I realised the bit that was missing…



LinearFrictionCallback coming up.



EDIT:

and while I'm grandstanding, I just figured out a way to hack up an x2 version… so simple (if a little hacky)…

ebola said:

but it requires a fixed time constant, which is not something that can be assumed.

It currently can be assumed, as both ODE and JOODE use fixed step size.
irrisor said:

It currently can be assumed, as both ODE and JOODE use fixed step size.

ok, then your method will work consistently... though now that I look at it, it looks linear and not x^2, so it seems you _do_ have an accurate solution.

so the step size is 0.01f from memory...
so if the call to physicsSpace.update(float) is 0.02f, then the method will be called twice?
I was just going to manually buffer your version to allow for differing input values... or for if someone manually triggers it (I almost did).

I'll still put together a couple of classes to test. it'd be good to have different kinds of friction callbacks. Plus - I'm having fun
ebola said:

so the step size is 0.01f from memory...
so if the call to physicsSpace.update(float) is 0.02f, then the method will be called twice?

yes

you can change the stepsize with physicsSpace.setAccuracy

ebola said:
Plus - I'm having fun

That's probably the most important thing with all the stuff :)

damn… the idea I had still tends towards the axis. So I'll be using the one below…

which will give consistent results regardless of the physicsspace.setAccuracy (though its not hard to take it out)

the reason… "currently"…



anyway, its x^2, so it attenuates (more like air resistance than rolling friction, but hey, at least it works).



irisor… your solution is linear… and sort of works, but it really doesn't feel the same. have a play.


/*
 * Copyright (c) 2005-2006 jME Physics 2
 * All rights reserved.
 *
 * 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 'jME Physics 2' 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) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package koncept.hs.game;

import java.lang.ref.*;
import java.util.*;
import java.util.concurrent.*;

import com.jme.math.*;
import com.jmex.physics.*;

/**
 * FrictionCallback provides features to apply friction per step for a DynamicPhysicsNode.
 * This class is thread-safe.
 *
 * @author Matthew D. Hicks
 *
 * modded by ebola (nK)
 */
public class DecreasingFrictionCallback implements PhysicsUpdateCallback {
   private ConcurrentLinkedQueue<DecreasingFrictionEncapsulation> nodes;
   private float buffer;
   private float limit;
   
   public DecreasingFrictionCallback() {
      nodes = new ConcurrentLinkedQueue<DecreasingFrictionEncapsulation>();
      buffer = 0;
      limit = 0.1f;
   }
   
   public DecreasingFrictionCallback(float limit) {
      this();
      this.limit = limit;
   }
   
   public float getLimit() {
      return limit;
   }
   
   public void setLimit(float limit) {
      this.limit = limit;
   }
   
   public boolean add(DynamicPhysicsNode node, float forceFriction, float angularFriction) {
      return nodes.add(new DecreasingFrictionEncapsulation(node, forceFriction, angularFriction));
   }
   
   public void afterStep(PhysicsSpace space, float time) {
      buffer += time;
      while (buffer > limit) {
         buffer -= limit;
         Iterator<DecreasingFrictionEncapsulation> iterator = nodes.iterator();
         DecreasingFrictionEncapsulation fe;
         while (iterator.hasNext()) {
            fe = iterator.next();
            if (!fe.call(space, limit)) {
               iterator.remove();
            }
         }
      }
   }

   public void beforeStep(PhysicsSpace space, float time) {
   }

   class DecreasingFrictionEncapsulation {
      private static final float CLAMP_POSITIVE = 0.00001f;

      private Vector3f store = new Vector3f();

      private WeakReference<DynamicPhysicsNode> ref;
      private float forceFriction;
      private float angularFriction;

      public DecreasingFrictionEncapsulation(DynamicPhysicsNode dpn, float forceFriction, float angularFriction) {
         ref = new WeakReference<DynamicPhysicsNode>(dpn);
         this.forceFriction = forceFriction;
         this.angularFriction = angularFriction;
      }

      public boolean call(PhysicsSpace space, float time) {
         DynamicPhysicsNode node = ref.get();
         if (node == null) return false; //remove node
         if (!node.isActive()) return true; //don't compute, don't remove
         
         if (forceFriction > CLAMP_POSITIVE) {
            node.getLinearVelocity(store);
            float change = forceFriction * time;
            applyFriction(change);
            node.setLinearVelocity(store);
         }
         if (angularFriction > CLAMP_POSITIVE) {
            node.getAngularVelocity(store);
            float change = angularFriction * time;
            applyFriction(change);
            node.setAngularVelocity(store);
         }
         return true;
      }

      private void applyFriction(float change) {
         if (change > 1.0f) throw new IllegalArgumentException("Total change cannot be greater than 1.0f");
         if (store.length() < CLAMP_POSITIVE) {
            store.set(0, 0, 0);
         } else {
            store.multLocal(1.0f - change);
         }
      }
   }
}

ebola said:

irisor... your solution is linear... and sort of works, but it really doesn't feel the same. have a play.

Do you have a demo/test?

I just plugged it into my app… its not really portalble as such (quite a few class files). I'll post the app when its far enough that its not embarrasing… only a few issues remain. a few more dev days (late next week? no promises… a few things to do before then…)



your solution doesn't have axial tendancies, but it really does not have much of an effect at slower speeds. almost like the DecreasingFrictionCallback  below, but not as pronounced…

I tried a few things last night (including buffering the xyz change and only applying when > clamp, but that just staggered the motion).



There is one thing I thought of that I didn't have time to do…



float oldLength = store.length();
float scale = (oldLength - change) / oldLength;
if (scale < CLAMP_POSITIVE) store.set(0, 0, 0);
else store.multLocal(scale);



which is in effect a re-working of your length method (should have the same effect, just a different impl);

If you want, I can put together a few files for you - Decreasing, your normaliseMult method, and this multToLinear... and :


Vector3f direction = new Vector3f(store);
direction.normaliseLocal().multLocal(scale);
scale.subtract(direction); //I think this is the same as x-x, y-y and z-z. because its directional, don't need to worry about -ve and +ve
if (scale.length < CLAMP_POSITIVE) scale.set(0, 0, 0);


which is darkfrogs method with a directional vector applied.

and a friction interface (just to make it easy to plug)... and call isActive on every node before applying (which reminds me... new helpme thread coming right up).

The linear ones seem to be slightly different (but that could be just me imagining things), so a choice for which method suits which ever app would be cool.

Anyway, I'll bolt a little package together for you. A few methods, an interface or two... you know, the kind of code shuffle that does nothing, achieves nothing, and yet gives the feeling of neatness.