1
\$\begingroup\$

EDIT

I've actually commented out the motion in my code and adapted it to try reproduce this post.After a chat with @DMGregory he told me to use velocity reflection but as you can see in the following code I didn't achieve to make it work.

Original Post (with modified code)

I've created a ball with spring motion:

var camera, scene, renderer, geometry, material, mesh;
var ballT;
var mouseX, mouseY, positionX, positionY;
var isClicked = false;
var ax, ay;
const gravity = 0.19;
function vec(x,y){return {x,y}};

const onMouseDown = e => {
    ball.userData.vx = 0;
  ball.userData.vy = 0;
    isClicked = true;
}

const onMouseUp= e => isClicked = false

function onMouseMove(event){
    const x = ( event.clientX / window.innerWidth ) * 2 - 1;
    const y = - ( event.clientY / window.innerHeight ) * 2 + 1;
 const vec = new THREE.Vector3(x, y, 0.5);
 const pos = new THREE.Vector3();
 vec.unproject( camera );
 vec.sub( camera.position ).normalize();
 const distance = -camera.position.z / vec.z;
 pos.copy( camera.position ).add( vec.multiplyScalar( distance ) );
 mouseX = pos.x;
 mouseY = pos.y;
 positionX = ballT.position.x;
 positionY = ballT.position.y;
}

function onFrame(){
      //commented out original motion
      /*const deltaT = 0.02
      const distanceX = mouseX - ball.position.x;
      const distanceY = mouseY - ball.position.y;
      const inertia = 0.94;
      const k = 0.02;
      ax = isClicked ? k * distanceX : 0;
      ay = isClicked ? k * distanceY : 0;
      ball.userData.vx += ax;
      ball.userData.vy += ay;
      ball.position.x += ball.userData.vx;
      ball.position.y += ball.userData.vy;
      ball.userData.vx *= inertia;
      ball.userData.vy *= inertia;*/
      lines.update(); 
      balls.update();
      
}

function init() {

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.z = 500;
    scene.add(camera);

    geometry = new THREE.SphereGeometry(19, 40, 32)
    material = new THREE.MeshNormalMaterial();

    ballT = new THREE.Mesh(geometry, material);
    scene.add(ballT);
    
    const boxGeometry = new THREE.BoxGeometry(300, 200, 1)
    const boxMaterial = new THREE.MeshNormalMaterial();
    
    const box = new THREE.Mesh(boxGeometry, boxMaterial);
    
    scene.add(box);

    ballT.userData.vx = 0;
    ballT.userData.vy = 0;

    mouseX = ballT.position.x;
    mouseY = ballT.position.y;
    
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);

    document.body.appendChild(renderer.domElement);
        document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mousedown', onMouseDown);
    document.addEventListener('mouseup', onMouseUp);
}

function animate() {

    requestAnimationFrame(animate);
    onFrame();
    render();

}

function render() {

    renderer.render(scene, camera);

}

const line = {  
   p1 : { x : 0, y : 0 },
   p2 : { x : 0, y : 0 },
   vnorm : { x : 0, y : 0 }, // normalied vector
   vec : { x : 0, y : 0 }, // line as a vector
   len : 0,   
   update(){
     this.vec.x = this.p2.x - this.p1.x;
     this.vec.y = this.p2.y - this.p1.y;
     this.len = Math.sqrt(this.vec.x * this.vec.x + this.vec.y * this.vec.y);
     this.vnorm.x = this.vec.x / this.len;
     this.vnorm.y = this.vec.y / this.len;
   },
   testBall(ball){  // line must be updated befor this call
       // line is one sided and ball can only approch from the right
       // find line radius distance from line
       var x = this.p1.x + this.vnorm.y * ball.radius;
       var y = this.p1.y - this.vnorm.x * ball.radius;
       x = ball.ball.position.x - x;
       y = ball.ball.position.y - y;
       // get cross product to see if ball hits line
       var c = x * this.vec.y - y * this.vec.x;
       if(c <= 0){ // ball has hit the line

            // get perpendicular line away from line
            var ox = this.vnorm.y * ball.radius
            var oy = -this.vnorm.x * ball.radius
            // get relative ball pos
            var px = ball.ball.position.x - (this.p1.x + ox);
            var py = ball.ball.position.y - (this.p1.y + oy);
            // find ball position that contacts the line
            var ld = (px * this.vec.x + py * this.vec.y)/(this.len * this.len);
            ball.ball.position.x  = this.vec.x * ld + (this.p1.x+ox);
            ball.ball.position.y = this.vec.y * ld + (this.p1.y+oy);            
            // find the reflection delta (bounce direction)
            var dd = (ball.dnorm.x * this.vnorm.x + ball.dnorm.y * this.vnorm.y) * 2;
            ball.delta.x = this.vnorm.x * dd - ball.dnorm.x;
            ball.delta.y = this.vnorm.y * dd - ball.dnorm.y;
            
            // the ball has lost some speed (should not have but this is a fix)
            
           var m = Math.sqrt(ball.delta.x * ball.delta.x + ball.delta.y * ball.delta.y);
            ball.delta.x = (ball.delta.x / m) * ball.speed;
            ball.delta.y = (ball.delta.y / m) * ball.speed;
            
            ball.updateContact();
            ball.col = "red";
       
       }
   }
}



// create a ball
function createLine(x,y,x1,y1){
    var l = Object.assign({},line,{
       p1 : vec(x,y),
       p2 : vec(x1,y1),
       vec : vec(0,0),
       vnorm : vec(0,0),
    
    });
    l.update();
    return l;
}
const lines = {
   items : [],
   add(line){ lines.items.push(line); return line },
   update(){
        var i;
        for(i = 0; i < lines.items.length; i++){
            lines.items[i].update();
        }
        return lines;
    },
    testBall(ball){
        var i;
        for(i = 0; i < lines.items.length; i++){
            lines.items[i].testBall(ball);
        }
        return lines;
    }
    

};

var ball = {
   pos : { x : 0, y: 0},
   radius : 0,
   speed : 0,
   col : "black",
   ball: undefined,
   delta : { x : 0, y : 0},  // movement as vector
   dnorm : { x : 0, y: 0},  // normalised delta
   updateContact(){ // update after ball hits wall
      this.speed = Math.sqrt(this.delta.x * this.delta.x + this.delta.y * this.delta.y);
      this.dnorm.x = this.delta.x / this.speed;
      this.dnorm.y = this.delta.y / this.speed;   
   },
   update(){
      this.delta.y += gravity;
      this.ball.position.x += this.delta.x;
      this.ball.position.y += this.delta.y;
      this.speed = Math.sqrt(this.delta.x * this.delta.x + this.delta.y * this.delta.y);
      this.dnorm.x = this.delta.x / this.speed;
      this.dnorm.y = this.delta.y / this.speed;   
   }
}
// create a ball
function createBall(x,y,r, ballT){
    var b = Object.assign({},ball,{
      pos : vec(x,y),
      radius : r,
      delta : vec(0,0),
      dnorm : vec(0,0),
      ball:ballT
    });
    return b;
}

const balls = {
    items : [],
    add(b){
        balls.items.push(b);
    },
    update(){
        var i;
        for(i = 0; i < balls.items.length; i++){
            balls.items[i].update();
            lines.testBall(balls.items[i]);
        }
        return balls;
    }
}

init();

balls.add(createBall(0, 0, 19, ballT));
lines.add(createLine(-150,100,150,100)); 
lines.add(createLine(-150,-100,150,-100));
lines.add(createLine(-150,-100,-150,100)); 
lines.add(createLine(150,-100,150,100));

animate();
body {
  margin: 0;
  overflow: hidden;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/libs/dat.gui.min.js"></script>

However, I can't seem to figure out how to make it collide with the sides of the canvas. Basically, I would like the motion simply invert when colliding. I've been struggling to do this as my algorithm relies heavily on the current position of the ball inverting it's position doesn't give me the good result.

Also it seems that when the ball is out of the boundaries there can be two or more frames where the value is out (so a simple if condition to invert sign when reaching the limit can be triggered muliple times leading to unwanted behaviours).

Is there a simple way to make my ball bounce back in the opposite direction?

\$\endgroup\$
5
  • \$\begingroup\$ You state "a simple if condition to invert sign when reaching the limit can be triggered muliple times leading to unwanted behaviours"; have you tried using a corrective action instead of checking the limit? For example, if (x < 0) correctionNeeded = true; then later you work on it while corrective action is needed. \$\endgroup\$ Aug 13 '21 at 17:45
  • 1
    \$\begingroup\$ @Tacoタコス not sure what you mean by corrective action. Do you mean a flag? also I've been chatting with one of the moderator which told me to use collision normal and velocity reflection. If you have any idea how to code this I'm your guy. At this point I'm looking at this post \$\endgroup\$
    – JSmith
    Aug 13 '21 at 17:48
  • \$\begingroup\$ What DMGregory told you will be the smoothest outcome and IMO, I'd keep pursuing that path. What I'm referring to will only be minimally smooth, it'll still be jumpy compared to DMGregory's solution. \$\endgroup\$ Aug 13 '21 at 17:54
  • \$\begingroup\$ @Tacoタコス thank you answering anyway, if you are able to code a working solution that is not using collision normal I'm in. I've said the boundaries where the canvas size but any size could be good. I've tested many ways so just to say if you can show me your jumpy solution I'll be delighted. \$\endgroup\$
    – JSmith
    Aug 13 '21 at 17:58
  • \$\begingroup\$ @Tacoタコス I've added a code with velocity reflection, if you have time looking at it that would be great. It doens't work as expected. \$\endgroup\$
    – JSmith
    Aug 14 '21 at 13:40

You must log in to answer this question.

Browse other questions tagged .