Reputation: 11
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
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