3D-Pong - Ball should rotate / have spin

Hi :slight_smile:



I started with jME because I wanted to write my own 3D-Pong since ages and now I’m on the beginning. Right now I have a simple Level with a wall instead of an enemy, a movable pad and the ball with a super bouncy material :smiley:



My problem right now is the missing rotation of the ball. When the pad is in move in the moment the ball arrives it should rotate (and alter the angle after colliding with other walls), but it doesn’t happen. Yesterday I found an old thread about Classic Pong where a user talked about motion in the contact and so I tried some surface motion. The ball then started to rotate after collisions, but that seems to be independent of the movement of the pad (the ball even starts rotating after colliding with the fixed wall).



The walls and the pad are static physic nodes (otherwise the pad would rotate and fly away itself after collision), the ball is a dynamic node, of course. Does anyone understand my problem and have any ideas how to get it solved?

I have an idea. The ball can't rotate, because the pad's speed is always a zero vector - I have written a TranslationController (found something like that somewhere in the forums) that just read the mouse position and updates the local translation of the pad - no real movement:


public class TranslationController extends Controller {

   private Spatial _spatial;
   private GameControl _positive;
   private GameControl _negative;
   private float _multiplier;
   
   private Vector3f _direction;
   
   public TranslationController(Spatial spatial, GameControl positive, GameControl negative, float multiplier, Vector3f direction) {
      _spatial = spatial;
      _positive = positive;
      _negative = negative;
      _multiplier = multiplier;
      _direction = direction;
   }
   
   @Override
   public void update(float time) {
      float value = _positive.getValue() - _negative.getValue();
      float delta = (value * time) * _multiplier;
      if (value != 0.0f) {
         _spatial.getLocalTranslation().addLocal(_direction.mult(delta));
      }
   }

}



I need to get the current velocity of the mouse (if I see it right, value is the current speed) and then I have to set the linear velocity of the pad - but then I have to work with a dynamic node instead of a static one and have to prevent the pad from moving because of the collision...

edit: Hm, the getValue method delivers strange values. Most of the time it's 0.0 and rarely there are values between 0.1 and 2.0. I move the mouse all the time and I have no idea, why there are so much 0.0? Or do I missinterpret this values? Because if this is the current speed of the mouse it has to be constant if I am moving the mouse...

well, first off you may be getting the mouse delta; which is the movement from the last polled point.  So if there is no movement in between updates you will get a zero, and greater delta values for greater moved distances.



The route I would investigate first would be to set the mass of the pad much higher than the ball and setting the MU value of the pad to be really 'abrasive'  (high I think), and setting the MU value on the walls to be really 'slick' (low I think).  Your best be for realistic game play would be to set the internal values of the physics 'components' correctly and letting the engine do the work.  However, once you do get the mouse issue worked out you may have to make the ball rotate by getting the linear velocity of the 'pad' then applying it as torque to the ball. 

Which GameControls do you use as inputs for the Controller?


ex-ratt said:

Yesterday I found an old thread about Classic Pong where a user talked about motion in the contact and so I tried some surface motion. The ball then started to rotate after collisions, but that seems to be independent of the movement of the pad (the ball even starts rotating after colliding with the fixed wall).

You could adjust the Surface motion dependent on the pad movement no?
if the pad dosen't move then apply no surface movement, if you move th pad upwards, also apply surface motion upwards or downwards.

if your pad is a dynamic node, maybe you can fix it with a Joint so it can only move along the Y axis.
Or you could make the joint, so your pad thrown back a little bit when the ball hits it.

This was the code that adds the controllers to the pad's node:


GameControlManager manager = new GameControlManager();
      
GameControl left = manager.addControl("left");
left.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_X, true));
GameControl right = manager.addControl("right");
right.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_X, false));

GameControl up = manager.addControl("up");
up.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_Y, false));
GameControl down = manager.addControl("down");
down.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_Y, true));

TranslationController translateX = new TranslationController(getNode(), left, right, 100, new Vector3f(1, 0, 0));
getNode().addController(translateX);
TranslationController translateY = new TranslationController(getNode(), up, down, 100, new Vector3f(0, 1, 0));
fetNode().addController(translateY);



In the TranslationController I call left.getValue() etc. - is that the current speed of the mouse in the given direction? When I move the mouse with a constant speed in a direction then the speed should be constant and greater than zero, the change of the position should also be greater than zero, but the values I get are 10 zeroes and one value bigger than that - I don't get it.

I will try the idea with the surface motion if no other ways work. First I will try the way with "real" movement of the pad and some Mu-Stuff (because it seems to me to be the right way). But how may I set the surface motion in the moment of the contact? Right now I can set it before the collisions in the MutableContactInfo of the Material.

But first I have to understand the mouse-controller-stuff - or maybe there is another way for applying the movement of the mouse to the pad?

the value is the speed or delta of the mouse yes.

You will get  positive values if you move the mouse up and negative if you move it down. (or left negatve, right positive). This is dependent on how you set up the GameControls tho.



the following GameControl set up gives you negative values if you move the mouse to the left, (if you set the last parameter (reverse) to true, you would get positive values when moving the mouse to the left.)

(true should actually give positive values if the mose is moved to the right, but its inverted to make it a bit more confusing :wink: )

left.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_X, true));




i'll post a small example, even tho i didn't change much :)
in the ForceController, i just added force instead of adding to the LocalTranslation


package physics;

import com.jme.bounding.BoundingBox;
import com.jme.input.InputHandler;
import com.jme.input.controls.GameControl;
import com.jme.input.controls.GameControlManager;
import com.jme.input.controls.binding.MouseAxisBinding;
import com.jme.input.controls.controller.Axis;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.shape.Box;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.material.Material;
import com.jmex.physics.util.SimplePhysicsGame;

public class PhysicsMovementController extends SimplePhysicsGame {

    private DynamicPhysicsNode padNode;
   
    @Override
    protected void simpleInitGame() {
        getPhysicsSpace().setDirectionalGravity(new Vector3f(0,0,0));
        input.removeAllFromAttachedHandlers();
        input = new InputHandler();
       
        Box pad = new Box("pad", new Vector3f(), 0.2f, 2f, 0.2f);
        pad.setModelBound(new BoundingBox());
        pad.updateModelBound();
        padNode = getPhysicsSpace().createDynamicNode();
        padNode.attachChild(pad);
        padNode.setMaterial(Material.IRON);
        padNode.generatePhysicsGeometry();
        padNode.computeMass();
       
        initInput();
       
        rootNode.attachChild(padNode);
       
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        new PhysicsMovementController().start();
    }

    private void initInput() {
        GameControlManager manager = new GameControlManager();
       
        GameControl left = manager.addControl("left");
        left.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_X, true));
        GameControl right = manager.addControl("right");
        right.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_X, false));

        GameControl up = manager.addControl("up");
        up.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_Y, false));
        GameControl down = manager.addControl("down");
        down.addBinding(new MouseAxisBinding(MouseAxisBinding.AXIS_Y, true));

        ForceController moveX = new ForceController(padNode, right, left, 10000, Axis.X);
//        padNode.addController(moveX);
        ForceController moveY = new ForceController(padNode, up, down, 100000, Axis.Y);
        padNode.addController(moveY);
    }
   
    public class ForceController extends Controller {
        private DynamicPhysicsNode node;
        private GameControl positive;
        private GameControl negative;
        private float multiplier;
       
        private Vector3f dir;
       
        public ForceController(DynamicPhysicsNode node, GameControl positive, GameControl negative, float multiplier, Axis axis) {
            this.node = node;
            this.positive = positive;
            this.negative = negative;
            this.multiplier = multiplier;
           
            if (axis == Axis.X) {
                dir = new Vector3f(1.0f, 0.0f, 0.0f);
            } else if (axis == Axis.Y) {
                dir = new Vector3f(0.0f, 1.0f, 0.0f);
            } else if (axis == Axis.Z) {
                dir = new Vector3f(0.0f, 0.0f, 1.0f);
            } else {
                throw new RuntimeException("Unknown axis: " + axis);
            }
        }

        public void update(float time) {
            float value = positive.getValue() - negative.getValue();
            float delta = (value * time) * multiplier;
            if (value != 0.0f) {
                System.out.println("value: " +value +" delta :" +delta);
//                spatial.getLocalTranslation().addLocal(dir.mult(delta));
                node.addForce(dir.mult(delta));
            }
        }
    }
}

Today I played around a bit with my VelocityController. Your ForceController is nice, but the reaction seem to be not that good. Right now I think the 'value' that you called the "delta of the mouse" is the distance one mouse axis moved from the previous point - not the speed, what I thought first. I tried to let the application sleep a little bit in my Physics Gamestate and then the times and the values were higher, because it needed more time to make the next update and the mouse moved a longer way.



With that in mind it isn't that good to multiply the value with the time to get the delta, because if the physics or anything else needs more time to update, the delta will get a lot higher and so the mouse speed depends on the time the update needs (or the cpu speed). It would be better to divide the value by the time to get the speed or just leave the value as it is, if one needs the way the mouse moved.



Yesterday I talked about the useless zeroes I get - the longer the update takes, the lesser zeroes I get. With sleeping like 100 ms I will get no zeroes if I constantly move the mouse around - ok, sounds right, because very little brakes just disappear in that time. But without the sleeping there seem to be much useless zeroes - there are 10 zeroes in a row, then a 1.0, then another 10 zeroes and so on. Maybe it's too much getValue() for the engine or my mouse driver or whatever and then the system responds by getting a zero and I don't know, if that is a 'real' zero or just that fill-value. So right now I fill an array with the last 10 values and take the average value and now it's all smooth.


public class VelocityController extends Controller {

   private DynamicPhysicsNode _node;
   private GameControl _positive;
   private GameControl _negative;
   private float _multiplier;
   
   private Vector3f _direction;
   
   private float[] _lastValues;
   private int _valueIndex;
   
   public VelocityController(DynamicPhysicsNode node, GameControl positive, GameControl negative, float multiplier, Vector3f direction) {
      _node = node;
      _positive = positive;
      _negative = negative;
      _multiplier = multiplier;
      _direction = direction.normalize();
      _lastValues = new float[10];
      _valueIndex = 0;
   }
   
   @Override
   public void update(float time) {
      //System.out.println(time + ": " + (_positive.getValue() - _negative.getValue()));
      // add the new velocity to the last values (velocities)
      // by dividing the delta of the mouse by the time
      _lastValues[_valueIndex] = (_positive.getValue() - _negative.getValue()) / time;
      // set the new index - if it is higher than the arrays length, it will be reseted
      if (_lastValues.length - 1 <= _valueIndex) {
         _valueIndex = 0;
      } else {
         _valueIndex++;
      }
      // get the average of the last values
      // that is due to the fact that even with a constant mouse movement,
      // many values are zero and just some of them seem to be useful
      float value = 0;
      for (float oldValue : _lastValues) {
         value += oldValue;
      }
      value = value / (float)_lastValues.length;
      // get the current velocity
      Vector3f velocity = _node.getLinearVelocity(null);
      // remove the part of the velocity going into our direction
      Vector3f part = _direction.mult(_direction.dot(velocity));
      velocity.subtractLocal(part);
      // add the new velocity value in our direction
      velocity.addLocal(_direction.mult(value * _multiplier));
      _node.setLinearVelocity(velocity);
   }

}



But that is not the best solution at this point, because if the update (of the physics or something else) takes a long time, I will need less values to get a useful average value (because there are less filling zeroes and it should feel like acting at the very next moment and not like 1s in the future).

Does anyone get my problem? :D Anyone had that problem before or even might have a solution or idea?