I'm coding a spin wheel where the result is already determined server-side. Having the wheel land on the pre-determined result is easy. You just set the number of radians at which the wheel stops spinning. Having the wheel slowly decelerate until hitting that result is another matter.
How would I code a function that slowly decelerates the wheel over a set number of radians, and still stops the wheel at the desired result: radiansUntilStop
? I have tried a few unsuccessful attempts at this.
I've put together a fiddle with how far I've managed to get here: https://jsfiddle.net/Bijingus/pzuhtren/51/
Here's the code:
<canvas id="wheel" width="300" height="300"></canvas>
<button id="spin" type="button">Spin</button><br>
Radians: <span id="radians"></span><br>
Speed: <span id="speed"></span><br>
const canvas = document.querySelector('#wheel');
const ctx = canvas.getContext('2d');
var radians = 0; // current wheel radians.
var maxSpeed = 0.2; // max radians that can be added per loop iteration.
var currentSpeed = 0; // current radians that are added per loop iteration
var radiansUntilStop = 47.8; // wheel speed needs to be at zero here.
var spin = false; // is the wheel spinning or not.
document.querySelector('#spin').addEventListener('click', function() {
// spin the wheel
spin = true;
currentSpeed = maxSpeed;
});
function update() {
if( radians >= radiansUntilStop ) {
// stop spinning the wheel
radians = radiansUntilStop;
spin = false;
}
if( spin === true ) {
// continue wheel spin
radians += currentSpeed;
}
debug();
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.arc(canvas.width/2, canvas.height/2, 150, 0, Math.PI * 2);
for( let i = 0; i < Math.PI * 2; i += Math.PI * 0.5 ) {
let fill = i === 0 ? true : false;
drawWheelSection(150, 0, radians + i, fill);
}
}
function loop() {
window.requestAnimationFrame(loop);
update();
draw();
};
loop();
function drawWheelSection(center, radius, wheelRadians, fill = false) {
ctx.beginPath();
ctx.moveTo(center, center);
ctx.arc(center, center, 140, 0 + wheelRadians, Math.PI * 0.5 + wheelRadians);
ctx.lineTo(center, center);
if( fill === true ) {
ctx.fillStyle = 'blue';
ctx.fill();
}
ctx.stroke();
ctx.beginPath();
ctx.moveTo(140, 0);
ctx.lineTo(150, 30);
ctx.lineTo(160, 0);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill();
ctx.stroke();
}
function debug() {
document.querySelector('#radians').innerHTML = radians;
document.querySelector('#speed').innerHTML = currentSpeed;
}
OK, I managed to come up with a solution. Here's the updated fiddle: https://jsfiddle.net/Bijingus/pzuhtren/142/
stoppingDistance
is the distance over which you'd like the spin wheel to decelerate.
stoppingDistanceTraveled
is the total amount in radians that the wheel has spun after reaching the beginning of the deceleration period.
Now, if you divide stoppingDistanceTraveled
by stoppingDistance
and subtract 1, you'll get the percentage of friction that you can apply to the maxSpeed
that the wheel can spin at.
const canvas = document.querySelector('#wheel');
const ctx = canvas.getContext('2d');
var radians = 0; // current wheel radians.
var maxSpeed = 0.2; // max radians that can be added per loop iteration.
var currentSpeed = 0; // current radians that are added per loop iteration
var radiansUntilStop = 47.8; // wheel speed needs to be at zero when radians reaches this amount.
var stoppingDistance = Math.PI * 4;
var stoppingDistanceTraveled = 0;
var spin = false; // is the wheel spinning or not.
document.querySelector('#spin').addEventListener('click', function() {
// spin the wheel
spin = true;
currentSpeed = maxSpeed;
});
function update() {
if( radians >= radiansUntilStop ) {
// stop spinning the wheel
radians = radiansUntilStop;
spin = false;
}
if( spin === true ) {
if( radiansUntilStop - radians < stoppingDistance ) {
var brakes = 1 - stoppingDistanceTraveled / stoppingDistance;
currentSpeed = brakes * maxSpeed;
stoppingDistanceTraveled += currentSpeed;
}
// continue wheel spin
radians += currentSpeed;
}
debug();
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.arc(canvas.width/2, canvas.height/2, 150, 0, Math.PI * 2);
for( let i = 0; i < Math.PI * 2; i += Math.PI * 0.5 ) {
let fill = i === 0 ? true : false;
drawWheelSection(150, 0, radians + i, fill);
}
}
function loop() {
window.requestAnimationFrame(loop);
update();
draw();
};
loop();
function drawWheelSection(center, radius, wheelRadians, fill = false) {
ctx.beginPath();
ctx.moveTo(center, center);
ctx.arc(center, center, 140, 0 + wheelRadians, Math.PI * 0.5 + wheelRadians);
ctx.lineTo(center, center);
if( fill === true ) {
ctx.fillStyle = 'blue';
ctx.fill();
}
ctx.stroke();
ctx.beginPath();
ctx.moveTo(140, 0);
ctx.lineTo(150, 30);
ctx.lineTo(160, 0);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill();
ctx.stroke();
}
function debug() {
document.querySelector('#radians').innerHTML = radians.toFixed(3);
document.querySelector('#speed').innerHTML = currentSpeed.toFixed(3);
}