Oldbalo
Oldbalo

Reputation: 11

Three.js animating with speed and friction

Im trying to make a 3D spinning wheel animation in three.js, where i press a key to accelerate the wheel and when i release the key it decelerates by friction until it stops.

I've experimented with different examples on this site but i just cant get it to run properly. I understand the physics but im having a hard time implementing it in the render loop.

Also in the future i would like to implement a deceleration curve. Does anyone have a clue on how to make something like this?

Thanks in advance

Edit:

Finally got something working kinda like i want it! This is my code:`

var container, outputLeap;
var camera, scene, renderer;

var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;

var sceneRoot = new THREE.Group();
var wheelSpin = new THREE.Group();
var wheelMesh;

var clock = new THREE.Clock();

var speed = 0.1;
var decelRate = 100;

function onWindowResize() {
  windowHalfX = window.innerWidth / 2;
  windowHalfY = window.innerHeight / 2;

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

function onWindowResize() {
  windowHalfX = window.innerWidth / 2;
  windowHalfY = window.innerHeight / 2;

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}


function init() {
  outputLeap = document.getElementById('output-leap');
  container = document.getElementById('container');


  camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 80);

  scene = new THREE.Scene();

  var geometryWheel = new THREE.CylinderGeometry(3, 3, 0.05, 32);
  var materialWheel = new THREE.MeshBasicMaterial({
    color: 0xffff00,
    wireframe: true
  });
  var wheelMesh = new THREE.Mesh(geometryWheel, materialWheel);
  scene.add(sceneRoot);

  sceneRoot.add(wheelSpin);
  wheelSpin.add(wheelMesh);

  renderer = new THREE.WebGLRenderer();
  renderer.setClearColor(0x000000);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  container.appendChild(renderer.domElement);

  window.addEventListener('resize', onWindowResize, false);
}

function render() {

  var time = clock.getElapsedTime();
  var delta = clock.getDelta();

  camera.position.y = 15;

  if (speed > 0)
    speed = Math.max(0, speed - decelRate * delta);
  else
    speed = Math.min(0, speed + decelRate * delta);

  camera.lookAt(scene.position);
  wheelSpin.rotation.y -= speed;

  outputLeap.innerHTML = 'Rotation: ' + speed;

  renderer.render(scene, camera);
}


function animate() {
  requestAnimationFrame(animate); 
  render();
}


init();
animate();
<!DOCTYPE html>
<html lang="en">

<head>
  <title></title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>

<body>
  <div id="info-top">
    <div id="output"></div>
    <div id="output-leap"><br></div>
  </div>
  <div id="container"></div>
  <script src="http://threejs.org/build/three.min.js">
  </script>
  <script src='js/THREEx.KeyboardState.js'></script>
  <script>
  </script>
</body>

</html>

It seems like delta was the key to making the animation work. But now i wonder why? I also wonder how you translate the speed and deceleration variables into more "realistic" values?

Upvotes: 1

Views: 2620

Answers (1)

Blindman67
Blindman67

Reputation: 54128

There are many ways to simulate friction and related accelerations.

Here is one via simple integration.

Friction (drag) is a force related to a exponent of velocity, that force acts against the mass of the moving (spinning) object.

We can ignore the fact that we have a spinning object as the location of the friction is at the center. Also one expects that the axis is rotating on a bearing or lubricated shaft, which for the most part can be described as drag.

If we remove all the coefficients, areas, viscosity, rolling friction, and blah blah from the drag function (they are all linear in relation) and look at the core function. drag = 1/2 * v*v where v is velocity. Multiply that by some adjustable coefficient and we get the force.

The velocity is the movement of the part of the wheel touching the axle and we get from the radius of the axle and rate of spin.

Thus we can set up the sim.

wheel = {
   mass : 100,  // mass
   axleRadius : 40,  // the lower the radius the less the drag
   deltaRot : 0.3,  // rate of turn per unit time.
   dragCoff : 0.1, //coefficients of drag
}

Get the velocity against the axle

var velocity = wheel.deltaRot * axleRadius;

Get the drag from that velocity

var drag = 0.5 * velocity * velocity * wheel.dragCoff;

Apply that drag (as a force) on the wheel's mass f = ma

var accel = drag / wheel.mass;

Convert the acceleration back to velocity on the surface touching the axle

wheel.deltaRot -= accel / wheel.axleRadius;

And you have a good approximation of a wheel turning on a axle.

The axle radius has a big impact on the drag. The greater the radius the greater the drag. The dragCoff is all the factors we did not include, like surface area touching the axle, bearing rolling resistance, lubrication viscosity. They are all linear relations (as I assume you will not be changing the axle radius during the simulation) compared to the velocity squared so can be bundled as one number to suit your needs (less than one of course)

And the Mass, the greater the mass the longer the wheel will spin.

As a simple demo the function has been simplified a little

var wheel = {
   mass : 100,
   radius : 100,  // has no effect on the sim
   axleRadius : 30,
   deltaRot : 1.3,
   dragCoff : 0.2, //coefficients of drag
   rotation : 0,
}
function updateWheel(w){
    w.deltaRot -= ((0.5 * Math.pow(w.deltaRot * w.axleRadius,2) * w.dragCoff) / w.mass) / w.axleRadius;
    w.rotation += w.deltaRot;
}
function drawCircle(radius,x=0,y=0){
    ctx.beginPath();
    ctx.arc(0,0,radius,0,Math.PI * 2);
    ctx.fill();
}
function display() { 
    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    ctx.clearRect(0, 0, w, h);
    updateWheel(wheel);
    ctx.setTransform(1,0,0,1,cw,ch); // draw at center of canvas
    ctx.rotate(wheel.rotation);
    ctx.fillStyle = "black";
    drawCircle(wheel.radius);
    ctx.fillStyle = "red";
    drawCircle(wheel.radius-10);
    ctx.fillStyle = "black";
    ctx.fillRect(0,-10,wheel.radius-5,20);
    ctx.fillStyle = "white";
    drawCircle(wheel.axleRadius+2);
    ctx.fillStyle = "black";
    drawCircle(wheel.axleRadius);
}

var w, h, cw, ch, canvas, ctx, globalTime = 0, firstRun = true;
;(function(){
    var  createCanvas, resizeCanvas;
    createCanvas = function () {
        var c, cs;
        cs = (c = document.createElement("canvas")).style;
        cs.position = "absolute";
        cs.top = cs.left = "0px";
        cs.zIndex = 1000;
        document.body.appendChild(c);
        return c;
    }
    resizeCanvas = function () {
        if (canvas === undefined) {
            canvas = createCanvas();
        }
        canvas.width = innerWidth;
        canvas.height = innerHeight;
        ctx = canvas.getContext("2d");
        cw = (w = canvas.width) / 2;
        ch = (h = canvas.height) / 2;
        wheel.deltaRot = 1.3;
    }
    function update() { // Main update loop
        display(); // call demo code
        requestAnimationFrame(update);
    }
    setTimeout(function(){
        resizeCanvas();
        window.addEventListener("resize", resizeCanvas);
        requestAnimationFrame(update);
    },0);
})();

Upvotes: 2

Related Questions