THREE.js - moving a 3D ball with a rotation

我们两清 提交于 2020-01-15 06:33:29

问题


I'm new to THREE.js and with a very poor knowledge in physics - but I am trying to build a football game engine (viewed from top) and right now I'm struggling with the movement of the ball.

when trying to move the ball from side to side, the rotation is always facing one direction and I dont understand how to make this rotate in the direction its moving at.

Ive added a simple code showing this issue. your help is much appreciated.

     /*
            *
            * SET UP MOTION PARAMS
            * 
            */

            var degrees = 10;
            var power = 1;
            var angleRad = degrees * Math.PI / 120;

            var velocityX = Math.cos(angleRad) * power;
            var velocityY = Math.sin(angleRad) * power;
            var velocityZ = 1;

            var friction = 1;
            var gravity = 0.2;
            var bounciness = 0.9;



            window.onload = function (params) {

                /*
                *
                * SET UP THE WORLD
                * 
                */
                
                
                
                //set up the ratio
                var gWidth = window.innerWidth;
                var gHeight = window.innerHeight;
                var ratio = gWidth / gHeight;
                var borders = [40, 24] //indicate where the ball needs to move in mirror position


                //set the scene
                scene = new THREE.Scene();
                scene.background = new THREE.Color(0xeaeaea);

                //set the camera
                var camera = new THREE.PerspectiveCamera(35, ratio, 0.1, 1000);
                camera.position.z = 120;   

                //set the light
                var light = new THREE.SpotLight(0xffffff, 1);
                light.position.set(100, 1, 0); 		
                light.castShadow = true;         
                light.position.set(0, 0, 100);
                scene.add(light);

                //  set the renderer 
                var renderer = new THREE.WebGLRenderer();

                //properties for casting shadow
                renderer.shadowMap.enabled = true;
                renderer.shadowMap.type = THREE.PCFSoftShadowMap; 

                renderer.setSize(gWidth, gHeight);
                document.body.appendChild(renderer.domElement);



                
                /*
                *
                * ADD MESH TO SCENE
                * 
                */


                // create and add the ball
                var geometry = new THREE.SphereGeometry(5, 5, 5);
                var material = new THREE.MeshLambertMaterial({ color: 'gray' });
                var ball = new THREE.Mesh(geometry, material);

                ball.castShadow = true;
                ball.receiveShadow = false;
                scene.add(ball);


                
                // create and add the field
                var margin = 20;
                var fieldRatio = 105 / 68;

                var width = 90;
                var height = width / fieldRatio;

                var material = new THREE.MeshLambertMaterial({ color: 'green' });
                var geometry = new THREE.BoxGeometry(width, height, 1);
                var field = new THREE.Mesh(geometry, material);

                field.receiveShadow = true;
                field.position.z = -1;
                scene.add(field);




                /*
                * setting up rotation axis 
                */


                var rotation_matrix = null;

                var setQuaternions = function () {
                    setMatrix();
                    ball.rotation.set(Math.PI / 2, Math.PI / 4, Math.PI / 4); // Set initial rotation
                    ball.matrix.makeRotationFromEuler(ball.rotation); // Apply rotation to the object's matrix
                }

                var setMatrix = function () {
                    rotation_matrix = new THREE.Matrix4().makeRotationZ(angleRad); // Animated rotation will be in .01 radians along object's X axis
                }

                setQuaternions();


                /*
                *
                * ANIMATION STEP
                * 
                */

                var render = function (params) {

                    // add velocity to ball
                    ball.position.x += velocityX;
                    ball.position.z += velocityZ;
                    ball.position.y += velocityY;


                    //validate if ball is stop moving
                    if (Math.abs(velocityX) < 0.02 && Math.abs(velocityY) < 0.02) {
                        console.log("DONE!");
                        return;
                    }



                    // handle boucing effect
                    if (ball.position.z < 1) {
                        velocityZ *= -bounciness;
                        ball.position.z = 1
                    }


                    // Update the object's rotation & apply it
                    ball.matrix.multiply(rotation_matrix);
                    ball.rotation.setFromRotationMatrix(ball.matrix);


                    //reducing speed by friction
                    angleRad *= friction;
                    velocityX *= friction;
                    velocityY *= friction;
                    velocityZ *= friction;


                    //set up the matrix 
                    setMatrix();
                    


                    //validate ball is withing its borders otherwise go in the mirror direction
                    if (Math.abs(ball.position.x) > borders[0]) {
                        velocityX *= -1;
                        ball.position.x = (ball.position.x < 0) ? borders[0] * -1 : borders[0];
                    }

                    if (Math.abs(ball.position.y) > borders[1]) {
                        velocityY *= -1;
                        ball.position.y = (ball.position.y < 0) ? borders[1] * -1 : borders[1];
                    }


                    // reduce ball height with gravity
                    velocityZ -= gravity;

                    

                    //render the page
                    renderer.render(scene, camera);

                    requestAnimationFrame(render);
                }

                render();

            }
body {
    padding: 0;
    margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<html>

<head>

</head>

<body>
</body>

</html>

回答1:


This is actually a pretty advanced bit of physics to do in a super realistic way if you want to include friction and inertia, etc. But you can take some shortcuts to get a decent visual rolling effect...

If you take the vector in the movement direction of the ball, you can get a perpendicular vector.. by taking the .cross product of the movement vector, with the world up vector.

That vector is the axis that a ball would rotate around if it had complete friction with the ground. Once you have that axis, you can use .rotateOnWorldAxis ( axis : Vector3, angle : Float ) with the object..

then you have to figure out how much to rotate, based on the radius of the ball, and the distance travelled.. so it's the length (called magnitude in my code below) of the movement vector * (PI*2) / the circumference of the ball.

Let me know if this helps...

p.s - Your "angleRad" computation was dividing by 120 instead of 180.. i fixed that.

/*
 *
 * SET UP MOTION PARAMS
 * 
 */

var degrees = 35;
var power = 0.45;
var angleRad = degrees * Math.PI / 180;

var velocityX = Math.cos(angleRad) * power;
var velocityY = Math.sin(angleRad) * power;
var velocityZ = 1;

var friction = 1;
var gravity = 0.2;
var bounciness = 0.9;

var ballRadius = 5;
var ballCircumference = Math.PI * ballRadius * 2;
var ballVelocity = new THREE.Vector3();
var ballRotationAxis = new THREE.Vector3(0, 1, 0);


window.onload = function(params) {

  /*
   *
   * SET UP THE WORLD
   * 
   */



  //set up the ratio
  var gWidth = window.innerWidth;
  var gHeight = window.innerHeight;
  var ratio = gWidth / gHeight;
  var borders = [40, 24] //indicate where the ball needs to move in mirror position


  //set the scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xeaeaea);

  //set the camera
  var camera = new THREE.PerspectiveCamera(35, ratio, 0.1, 1000);
  camera.position.z = 120;

  //set the light
  var light = new THREE.SpotLight(0xffffff, 1);
  light.position.set(100, 1, 0);
  light.castShadow = true;
  light.position.set(0, 0, 35);
  scene.add(light);

  //  set the renderer 
  var renderer = new THREE.WebGLRenderer();

  //properties for casting shadow
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  renderer.setSize(gWidth, gHeight);
  document.body.appendChild(renderer.domElement);




  /*
   *
   * ADD MESH TO SCENE
   * 
   */


  // create and add the ball
  var geometry = new THREE.SphereGeometry(ballRadius, 8, 8);

  //make a checkerboard texture for the ball...
  var canv = document.createElement('canvas')
  canv.width = canv.height = 256;
  var ctx = canv.getContext('2d')
  ctx.fillStyle = 'white';
  ctx.fillRect(0, 0, 256, 256);
  ctx.fillStyle = 'black';
  
  for (var y = 0; y < 16; y++)
    for (var x = 0; x < 16; x++)
      if ((x & 1) != (y & 1)) ctx.fillRect(x * 16, y * 16, 16, 16);
  var ballTex = new THREE.Texture(canv);
  ballTex.needsUpdate = true;


  var material = new THREE.MeshLambertMaterial({
    map: ballTex
  });
  var ball = new THREE.Mesh(geometry, material);

  ball.castShadow = true;
  ball.receiveShadow = false;
  scene.add(ball);



  // create and add the field
  var margin = 20;
  var fieldRatio = 105 / 68;

  var width = 90;
  var height = width / fieldRatio;

  var material = new THREE.MeshLambertMaterial({
    color: 'green'
  });
  var geometry = new THREE.BoxGeometry(width, height, 1);
  var field = new THREE.Mesh(geometry, material);

  field.receiveShadow = true;
  field.position.z = -1;
  scene.add(field);




  /*
   * setting up rotation axis 
   */


  var rotation_matrix = null;

  var setQuaternions = function() {
    setMatrix();
    ball.rotation.set(Math.PI / 2, Math.PI / 4, Math.PI / 4); // Set initial rotation
    ball.matrix.makeRotationFromEuler(ball.rotation); // Apply rotation to the object's matrix
  }

  var setMatrix = function() {
    rotation_matrix = new THREE.Matrix4().makeRotationZ(angleRad); // Animated rotation will be in .01 radians along object's X axis
  }

  setQuaternions();


  /*
   *
   * ANIMATION STEP
   * 
   */

  var render = function(params) {

    // add velocity to ball
    ball.position.x += velocityX;
    ball.position.z += velocityZ;
    ball.position.y += velocityY;


    //validate if ball is stop moving
    if (Math.abs(velocityX) < 0.02 && Math.abs(velocityY) < 0.02) {
      console.log("DONE!");
      return;
    }



    // handle boucing effect
    if (ball.position.z < 1) {
      velocityZ *= -bounciness;
      ball.position.z = 1
    }


    // Update the object's rotation & apply it
    /*
                    ball.matrix.multiply(rotation_matrix);   ball.rotation.setFromRotationMatrix(ball.matrix);
    //set up the matrix 
    setMatrix();
*/

    // Figure out the rotation based on the velocity and radius of the ball...
    ballVelocity.set(velocityX, velocityY, velocityZ);
    ballRotationAxis.set(0, 0, 1).cross(ballVelocity).normalize();
    var velocityMag = ballVelocity.length();
    var rotationAmount = velocityMag * (Math.PI * 2) / ballCircumference;
    ball.rotateOnWorldAxis(ballRotationAxis, rotationAmount)


    //reducing speed by friction
    angleRad *= friction;
    velocityX *= friction;
    velocityY *= friction;
    velocityZ *= friction;





    //validate ball is withing its borders otherwise go in the mirror direction
    if (Math.abs(ball.position.x) > borders[0]) {
      velocityX *= -1;
      ball.position.x = (ball.position.x < 0) ? borders[0] * -1 : borders[0];
    }

    if (Math.abs(ball.position.y) > borders[1]) {
      velocityY *= -1;
      ball.position.y = (ball.position.y < 0) ? borders[1] * -1 : borders[1];
    }


    // reduce ball height with gravity
    velocityZ -= gravity;



    //render the page
    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  render();

}
body {
  padding: 0;
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<html>

<head>

</head>

<body>
</body>

</html>



回答2:


If I understand your situation correctly, then you'll want to apply a rotation to the ball, that is based around the "right axis" of the ball's local space.

THREE.js provides a number of helper methods to simplify this math, namely the makeRotationAxis() method on the THREE.Matrix4 class.

Conceptually and practically, a minor set of adjustments to your ball.rotation math should achieve what you are wanting. Please see the following code snippet to see how this can be done (or see this working jsFiddle):

   /*
            *
            * SET UP MOTION PARAMS
            * 
            */
            
            var rotationAngle = 0;

            var degrees = 10;
            var power = 1;
            var angleRad = degrees * Math.PI / 120;

            var velocityX = Math.cos(angleRad) * power;
            var velocityY = Math.sin(angleRad) * power;
            var velocityZ = 1;

            var friction = 1;
            var gravity = 0.2;
            var bounciness = 0.9;



            window.onload = function (params) {

                /*
                *
                * SET UP THE WORLD
                * 
                */
                
                
                
                //set up the ratio
                var gWidth = window.innerWidth;
                var gHeight = window.innerHeight;
                var ratio = gWidth / gHeight;
                var borders = [40, 24] //indicate where the ball needs to move in mirror position


                //set the scene
                scene = new THREE.Scene();
                scene.background = new THREE.Color(0xeaeaea);

                //set the camera
                var camera = new THREE.PerspectiveCamera(35, ratio, 0.1, 1000);
                camera.position.z = 120;   

                //set the light
                var light = new THREE.SpotLight(0xffffff, 1);
                light.position.set(100, 1, 0); 		
                light.castShadow = true;         
                light.position.set(0, 0, 100);
                scene.add(light);

                //  set the renderer 
                var renderer = new THREE.WebGLRenderer();

                //properties for casting shadow
                renderer.shadowMap.enabled = true;
                renderer.shadowMap.type = THREE.PCFSoftShadowMap; 

                renderer.setSize(gWidth, gHeight);
                document.body.appendChild(renderer.domElement);



                
                /*
                *
                * ADD MESH TO SCENE
                * 
                */


                // create and add the ball
                var geometry = new THREE.SphereGeometry(5, 5, 5);
                var material = new THREE.MeshLambertMaterial({ color: 'gray' });
                var ball = new THREE.Mesh(geometry, material);

                ball.castShadow = true;
                ball.receiveShadow = false;
                scene.add(ball);


                
                // create and add the field
                var margin = 20;
                var fieldRatio = 105 / 68;

                var width = 90;
                var height = width / fieldRatio;

                var material = new THREE.MeshLambertMaterial({ color: 'green' });
                var geometry = new THREE.BoxGeometry(width, height, 1);
                var field = new THREE.Mesh(geometry, material);

                field.receiveShadow = true;
                field.position.z = -1;
                scene.add(field);




                /*
                * setting up rotation axis 
                */


                var rotation_matrix = null;

                var setQuaternions = function () {
                    setMatrix();
                    ball.rotation.set(Math.PI / 2, Math.PI / 4, Math.PI / 4); // Set initial rotation
                    ball.matrix.makeRotationFromEuler(ball.rotation); // Apply rotation to the object's matrix
                }

                var setMatrix = function () {
                    rotation_matrix = new THREE.Matrix4().makeRotationZ(angleRad); // Animated rotation will be in .01 radians along object's X axis
                }

                setQuaternions();


                /*
                *
                * ANIMATION STEP
                * 
                */

                var render = function (params) {

                    // add velocity to ball
                    ball.position.x += velocityX;
                    ball.position.z += velocityZ;
                    ball.position.y += velocityY;


                    //validate if ball is stop moving
                    if (Math.abs(velocityX) < 0.02 && Math.abs(velocityY) < 0.02) {
                        console.log("DONE!");
                        return;
                    }



                    // handle boucing effect
                    if (ball.position.z < 1) {
                        velocityZ *= -bounciness;
                        ball.position.z = 1
                    }


                    // Update the object's rotation & apply it
                  //                    ball.matrix.multiply(rotation_matrix);
                  // Compute the direction vector of the balls current forward direction of motion
                  var vectorDirection = new THREE.Vector3(velocityX, velocityY, velocityZ);

                  // Compute the vector about which the balls rotation is calculated. This is at a 
                  // right angle to the vectorDirection, and so we use the cross product to 
                  // calculate this
                  var axisOfRotation = new THREE.Vector3().crossVectors(vectorDirection, new THREE.Vector3(0,0,1) );

                  // Normalise the rotation axis to unit length
                  axisOfRotation.normalize();

                  // Build a rotation matrix around the rotation axis.
                  var rotation = new THREE.Matrix4();
                  rotation .makeRotationAxis(axisOfRotation, rotationAngle)
                  ball.rotation.setFromRotationMatrix(rotation );

                  // Decrement the rotation angle to achieve the rolling effect
                  rotationAngle -= 0.1;
                    
                   // ball.rotation.setFromRotationMatrix(ball.matrix);


                    //reducing speed by friction
                    angleRad *= friction;
                    velocityX *= friction;
                    velocityY *= friction;
                    velocityZ *= friction;


                    //set up the matrix 
                    setMatrix();
                    


                    //validate ball is withing its borders otherwise go in the mirror direction
                    if (Math.abs(ball.position.x) > borders[0]) {
                        velocityX *= -1;
                        ball.position.x = (ball.position.x < 0) ? borders[0] * -1 : borders[0];
                    }

                    if (Math.abs(ball.position.y) > borders[1]) {
                        velocityY *= -1;
                        ball.position.y = (ball.position.y < 0) ? borders[1] * -1 : borders[1];
                    }


                    // reduce ball height with gravity
                    velocityZ -= gravity;

                    

                    //render the page
                    renderer.render(scene, camera);

                    requestAnimationFrame(render);
                }

                render();

            }
body {
    padding: 0;
    margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<html>

<head>

</head>

<body>
</body>

</html>


来源:https://stackoverflow.com/questions/52133918/three-js-moving-a-3d-ball-with-a-rotation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!