Continuous Collision Detection with Havok

Playground: https://playground.babylonjs.com/#Z8HTUN#944

Hey guys, i need help with CCD (to simulate Table tennis paddle with ball collision) how can i properly implement it in Havok ? In the current playground when i move fast with Box the Sphere passes through the Box.

I’m not an expert but it seems like what’s happening is that when you press the up key to move the ground, it basically moves above the sphere.

If you reduce the amount you move the ground by, it works perfectly fine:

 window.addEventListener('keydown', function(event) {
        switch (event.key) {
            case 'ArrowUp':
                // Move the ground up
                ground.position.y += 0.02; // Adjust 0.1 to the step size you prefer
                break;
            case 'ArrowDown':
                // Move the ground down
                ground.position.y -= 0.02; // Adjust 0.1 to the step size you prefer
                break;
        }
        ground.physicsBody.updatePosition(ground.position);
    });

@AncientA hey, well this is just a prototype code, in reality i want to apply it to simulate physics where tabble-tennis-paddle hits with ball , where i move very fast with paddle.

I see!

In this case I’d recommend not adjusting the position of the paddle like you are doing now, but rather apply a new velocity or impulses. This is how I manage movement in my 3rd person controller template:

For managing inputs:

import { Scene, Vector3, DeviceSourceManager, DeviceType, DeviceSource, Nullable } from "@babylonjs/core";

export class InputControls {
    private deviceSourceManager: DeviceSourceManager;
    private keyboard: Nullable<DeviceSource<DeviceType.Keyboard>> = null;
    public currentAnimation: string = "idle"; // Default to idle animation

    constructor(scene: Scene) {
        this.deviceSourceManager = new DeviceSourceManager(scene.getEngine());

        // Listen for when a keyboard becomes active
        this.deviceSourceManager.onDeviceConnectedObservable.add((deviceSource) => {
            if (deviceSource.deviceType === DeviceType.Keyboard) {
                this.keyboard = deviceSource;
            }
        });

        // Optionally, listen for when a keyboard is disconnected
        this.deviceSourceManager.onDeviceDisconnectedObservable.add((deviceSource) => {
            if (deviceSource.deviceType === DeviceType.Keyboard) {
                this.keyboard = null;
            }
        });
    }

    public getInputVector(): Vector3 {
        let inputVector = Vector3.Zero();
        let moving = false;


        // Ensure that the keyboard is connected
        if (this.keyboard) {
            if (this.keyboard.getInput(87) || this.keyboard.getInput(38)) { // W
                inputVector.addInPlace(Vector3.Forward());
                moving = true;
                this.currentAnimation = "forward";
            }
            if (this.keyboard.getInput(83) || this.keyboard.getInput(40)) { // S
                inputVector.addInPlace(Vector3.Backward());
                moving = true;
                this.currentAnimation = "backward"; // Assuming you have a backward animation
            }
            if (this.keyboard.getInput(65) || this.keyboard.getInput(37)) { // A
                inputVector.addInPlace(Vector3.Left());
                moving = true;
                this.currentAnimation = "left";
            }
            if (this.keyboard.getInput(68) || this.keyboard.getInput(39)) { // D
                inputVector.addInPlace(Vector3.Right());
                moving = true;
                this.currentAnimation = "right";
            }
        }

        // If no movement keys are pressed, revert to idle animation
        if (!moving) {
            this.currentAnimation = "idle";
        }

        return inputVector.normalize();
    }
       

For movement:

public move(inputVector: Vector3, isSprinting: boolean, animationRatio: number) {

    let forward = this.camera.getForwardRay().direction;
    forward.y = 0; // Ensure horizontal movement only
    forward.normalize();
    const right = Vector3.Cross(Vector3.Up(), forward).normalize();

    let moveDirection = forward.scale(inputVector.z).add(right.scale(inputVector.x));
    if (!moveDirection.equals(Vector3.Zero())) {
        moveDirection = moveDirection.normalize();
    }

    // Apply animation ratio directly to base speed for frame rate independence
    const baseSpeed = 25; // Base speed adjusted by animation ratio
    const sprintMultiplier = isSprinting && this.stamina > 0 ? 2 : 1; // Determine sprint multiplier

    let adjustedSpeed = baseSpeed *  sprintMultiplier; // Adjusted speed based on sprinting

    // Consume stamina if sprinting
    if (isSprinting && this.stamina > 0) {
        this.stamina -= this.sprintStaminaConsumptionRate * animationRatio;
    }

    // Apply the adjusted speed to the movement direction
    let velocity = this.mesh.physicsBody.getLinearVelocity();
    let newVelocity = new Vector3(moveDirection.x * adjustedSpeed, velocity.y, moveDirection.z * adjustedSpeed);
    this.mesh.physicsBody.setLinearVelocity(newVelocity);

    // Logging
    const currentTime = performance.now();
    if (currentTime - this.lastLogTime > 3000) {

    console.log(`Sprint Multiplier: ${sprintMultiplier}`);
    console.log(`Base Speed: ${baseSpeed}`);
    console.log(`Adjusted Speed: ${adjustedSpeed}`);
    console.log(`New Velocity: ${newVelocity}`);
    this.lastLogTime = currentTime; // Update the last log time
    }
}

I know your use-case is different, but the idea will be the same. You can manage the movement of the paddle by listening to the keyboard events, then apply a move logic that uses velocity or forces.

Hey, thanks for the code. I will see what i can do with it. But the problem is that im using VR hand controller to control paddle, so it moves very fast. I need to figure if i could get Paddles forward direction without ray casting, the same with a ball

Ahhh okay, sorry i don’t have any knowledge about the VR stuff, but it is definitely doable.

Maybe you’ll find the answer on this page?

https://doc.babylonjs.com/features/featuresDeepDive/webXR/webXRInputControllerSupport

sadly no, the thing is it just does not detect paddle if i wing fast… i do not know if it my code or the physics just does not work properly. here is part of the code

    const ballPhysics = new PhysicsAggregate(pingPongBall,
      PhysicsShapeType.SPHERE, { mass: 0.0027, restitution: 0.85, friction: 0 }, scene);
    pingPongBall.physicsBody = ballPhysics.body;
    pingPongBall.physicsBody.disablePreStep = false;
    pingPongBall.physicsBody.setCollisionCallbackEnabled(true)
    const observable = pingPongBall.physicsBody.getCollisionObservable()
    observable.add(async (collisionEvent) => {
      const name = collisionEvent.collidedAgainst.transformNode.name
      if (name === "paddle") {
            const object = collisionEvent.collidedAgainst.transformNode;
            const forward = controller.grip.forward.normalize();
            let ballVelocity = pingPongBall.physicsBody.getLinearVelocity();
            let paddleVelocity = object.physicsBody.getLinearVelocity();
            let relativeVelocity = ballVelocity.subtract(paddleVelocity);
            let impulse = forward.scale(0.0001);
            impulse = impulse.addInPlace(relativeVelocity.scale(0.0001));
            impulse = impulse.addInPlace(paddleVelocity.scale(0.0001));
            pingPongBall.physicsBody.applyImpulse(impulse, pingPongBall.getAbsolutePosition());
            
      }
    });

Are you saying it works at normal speeds, but not when you are swinging very fast?

I’m a noob, but for me your code seems correct. Having said that, I found this on the forums:

There is a limit on velocity on physics objects in Havok, which is meant to be pretty fast, but maybe not fast enough for your use-case. There is a way to override this though however:

function setMaxLinVel(havokPlugin, maxLinVel, maxAngVel) {
        const heap = havokPlugin._hknp.HEAP8.buffer;
        const world1 = new Int32Array(heap, Number(havokPlugin.world), 100);
        const world2 = new Int32Array(heap, world1[9], 500);
        const mplib = new Int32Array(heap, world2[428], 100);
        const tsbuf = new Float32Array(heap, mplib[8], 300);

        tsbuf[32] = maxLinVel;
        tsbuf[33] = maxAngVel;
        tsbuf[60] = maxLinVel;
        tsbuf[61] = maxAngVel;
        tsbuf[88] = maxLinVel;
        tsbuf[89] = maxAngVel;
        tsbuf[144] = maxLinVel;
        tsbuf[145] = maxAngVel;
        tsbuf[172] = maxLinVel;
        tsbuf[173] = maxAngVel;
        tsbuf[200] = maxLinVel;
        tsbuf[201] = maxAngVel;
        tsbuf[228] = maxLinVel;
        tsbuf[229] = maxAngVel;
    }

I recommend looking through the post, and maybe someone else can jump in from the community to help out if this doesn’t work.

Best of luck, let me know how it goes and hopefully see your project in action soon :slight_smile:


I just recorded how it looks, ok i will see maybe i can change velocity of physics, thanks

Made this changes, nothing has changed same thing.

function setMaxLinVel(havokPlugin, maxLinVel, maxAngVel) {
  const heap = havokPlugin._hknp.HEAP8.buffer;
  const world1 = new Int32Array(heap, Number(havokPlugin.world), 100);
  const world2 = new Int32Array(heap, world1[8], 500);
  const mplib = new Int32Array(heap, world2[428], 100);
  const tsbuf = new Float32Array(heap, mplib[8], 300);

  tsbuf[32] = maxLinVel;
  tsbuf[33] = maxAngVel;
  tsbuf[60] = maxLinVel;
  tsbuf[61] = maxAngVel;
  tsbuf[88] = maxLinVel;
  tsbuf[89] = maxAngVel;
  tsbuf[144] = maxLinVel;
  tsbuf[145] = maxAngVel;
  tsbuf[172] = maxLinVel;
  tsbuf[173] = maxAngVel;
  tsbuf[200] = maxLinVel;
  tsbuf[201] = maxAngVel;
  tsbuf[228] = maxLinVel;
}  
const plugin = new HavokPlugin(true, await HavokPhysics());
  setMaxLinVel(plugin, 4000, 4000);
  const gravity = new Vector3(0, -9.81, 0);
  scene.enablePhysics(gravity, plugin);
  scene.getPhysicsEngine().setTimeStep(1 / 600);

Oh man sorry to hear! It looks so sick btw, I really hope you get it sorted out somehow!

@Cedric has been very nice to help me out when I had some issues (hope you don’t mind me tagging you here Cedric!) He might be able to help out, or he might know someone who knows more about the VR stuff!

Not sure if this is transferable to VR but I tried with pointer drag: https://playground.babylonjs.com/#Z8HTUN#947 I can punch the box with the ball even if I move the mouse very quickly. The dragging is a bit weird - a top down camera angle worked best.

Tried, the same thing and even worse to control…

Let me add @Cedric to the thread

I would try to use shape cast
some docs here : Character Controller with Physics V2 | by Babylon.js | Mar, 2024 | Medium

and here : Babylon.js docs

basically, before setting new raquet postion/orientation, cast it to see if it collides with the ball. and add impulse accordingly. it would be like a pre broadphase collision detection.
Other solution: increase collision shape at the back of the racket accordingly to its motion speed.

Hey thanks for resources, i will have a glance,

i added collisionMargin and set continuousCollisionDetection to true , it kinda works, but not sure. At the begining i thought about raycast, but i had trouble to place raycast on ball or paddle.

      const ballPhysics = new PhysicsAggregate(pingPongBall,
        PhysicsShapeType.SPHERE, { mass: 0.0027, restitution: 0.85, friction: 0, collisionMargin: 0.05 }, 
      scene);
      pingPongBall.physicsBody = ballPhysics.body;
      pingPongBall.physicsBody.disablePreStep = false;
      pingPongBall.physicsBody.checkCollisions = true;
      pingPongBall.physicsBody.continuousCollisionDetection = true;

well, i found the problem, is that the ball is 0.04 diam, and Havok cannot detect fast collision. I need to do raycast from ball, that what works. Im closing the issue

2 Likes