javascriptpiradians

How would you write a javascript function that slowly declerates a spin wheel to a pre-determined position?


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;
}

Solution

  • 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);
    }