Search code examples
javascriptsimulationgame-physicsphysicssimulate

Simulation of the rotation angular speed


I would like to be able to simulate the movement of the body on a "carousel" with respect to physics. (centripetal, centrifugal force, angular speed). Below is some sample code.

<!DOCTYPE html>
<html>
    <head>
        <script>
            
            var rotate = Math.PI / 180;
            var ballRotation = 1;
        
            function drawthis() {
    
                var friction = 0.5;

                context.setTransform(1, 0, 0, 1, 0, 0);
                context.clearRect(0, 0, cvs.width, cvs.height);
                context.translate(350, 350);
                context.rotate(rotate);
                context.beginPath();
                context.arc(1, 1, 12, 0, 2 * Math.PI, false);
                context.fill();
                context.beginPath();
                context.arc(0, 0, 150, 0, Math.PI * 2, false);
                context.lineWidth = 6;
                context.stroke();
                
                motion = ballRotation - friction;
                rotate += motion;
                requestAnimationFrame(drawthis);
}
            function init() {
                cvs = document.getElementById("canvas");
                context = cvs.getContext("2d");
                context.clearRect(0, 0, context.width, context.height);
                context.fillStyle = "#ff0000";
                requestAnimationFrame(drawthis);
}
        </script>
    </head>
    <body onload="init()">
            <canvas id="canvas" width="800" height="800"></canvas>
    </body>
</html>

I mean something like this


Solution

  • Ball on a turn table

    Below you will find a simple simulation of a point sliding on a turning wheel. The point represents the contact point of a ball.

    The simulation ignores the fact that the ball can roll, or has mass.

    The ball slides via a simple friction model, where friction is a scalar value applied to the difference between the balls speed vector, and the speed of the wheel at the point under the ball.

    There is only 1 force involved. It is the force tangential to the vector from the ball to the wheel center, subtracted by the ball movement vector and then multiplied by the friction coefficient.

    For details on how this is calculated see comments in the function ball.update()

    Notes

    • That if the ball starts at the dead center of the wheel nothing will happen.

    • I could not workout if it was the path of the ball you wanted or just the simulation of the ball, so I added both.

    • The ball resets after it leaves the wheel.

    • The wheel is marked with text and center cross so its rotation can be seen.

    const ROTATE = Math.PI / 50;
    const WHEEL_SIZE = 0.6;
    Math.rand = (min, max) => Math.random() * (max - min) + min;
    Math.randPow = (min, max, p) => Math.random() ** p * (max - min) + min;
    
    var friction = 0.35;
    const ctx = canvas.getContext("2d");
    requestAnimationFrame(mainLoop);
    ctx.font = "30px arial";
    ctx.textAlign = "center";
    scrollBy(0, canvas.height / 2 - canvas.height / 2 * WHEEL_SIZE);
    
    function mainLoop() {
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        wheel.update();
        ball.update(wheel, arrow);
        wheel.draw();
        path.draw();
        ball.draw();
        arrow.draw(ball);
        requestAnimationFrame(mainLoop);
    }
    
    const path = Object.assign([],{
        draw() {
            ctx.setTransform(1, 0, 0, 1, 0, 0);
            ctx.strokeStyle = "#F00";
            ctx.lineWidth = 1;
            ctx.beginPath();
            for (const p of this) { ctx.lineTo(p.x, p.y) }
            ctx.stroke();
        },
        reset() { this.length = 0 },
        add(point) {
            this.push({x: point.x, y: point.y});
            if (this.length > 1000) {  // prevent long lines from slowing render
                this.shift()
            }
        }
    });  
    const arrow = {
        dx: 0,dy: 0,
        draw(ball) {
            if (this.dx || this.dy) {
                const dir = Math.atan2(this.dy, this.dx);
                
                // len is converted from frame 1/60th second to seconds
                const len = Math.hypot(this.dy, this.dx) * 60; 
                const aXx = Math.cos(dir);
                const aXy = Math.sin(dir);        
                ctx.setTransform(aXx, aXy, -aXy, aXx, ball.x, ball.y);
                ctx.beginPath();
                ctx.lineTo(0,0);
                ctx.lineTo(len, 0);
                ctx.moveTo(len - 4, -2);
                ctx.lineTo(len, 0);
                ctx.lineTo(len - 4, 2);
                ctx.strokeStyle = "#FFF";
                ctx.lineWidth = 2;     
                ctx.stroke();
            }
        }
    };
    const ball = {
        x: canvas.width / 2 + 4,
        y: canvas.height / 2,
        dx: 0,  // delta pos Movement vector
        dy: 0,
        update(wheel, arrow) {
            // get distance from center
            const dist = Math.hypot(wheel.x - this.x, wheel.y - this.y);
            
            // zero force arrow
            arrow.dx = 0;
            arrow.dy = 0;
             
            // check if on wheel
            if (dist < wheel.radius) {
                // get tangent vector direction
                const tangent = Math.atan2(this.y - wheel.y, this.x - wheel.x) + Math.PI * 0.5 * Math.sign(wheel.dr);
                // get tangent as vector
                // which is distance times wheel rotation in radians.
                const tx = Math.cos(tangent) * dist * wheel.dr;
                const ty = Math.sin(tangent) * dist * wheel.dr;
    
                // get difference between ball vector and tangent vector scaling by friction
                const fx = arrow.dx = (tx - this.dx) * friction;
                const fy = arrow.dy = (ty - this.dy) * friction;
    
                // Add the force vector 
                this.dx += fx;
                this.dy += fy;
            } else if (dist > wheel.radius * 1.7) { // reset ball
    
                // to ensure ball is off center use random polar coord
                const dir = Math.rand(0, Math.PI * 2);
                const dist = Math.randPow(1, 20, 2);  // add bias to be close to center
                this.x = canvas.width / 2 + Math.cos(dir) * dist;
                this.y = canvas.height / 2 + Math.sin(dir) * dist;
                this.dx = 0;  
                this.dy = 0;        
                path.reset();
            }
            // move the ball
            this.x += this.dx;
            this.y += this.dy;      
            path.add(ball);
        },    
        draw() {
            ctx.fillStyle = "#0004";
            ctx.setTransform(1, 0, 0, 1, this.x + 5, this.y + 5);
            ctx.beginPath();
            ctx.arc(0, 0, 10, 0, 2 * Math.PI);
            ctx.fill();    
            ctx.fillStyle = "#f00";
            ctx.setTransform(1, 0, 0, 1, this.x, this.y);
            ctx.beginPath();
            ctx.arc(0, 0, 12, 0, 2 * Math.PI);
            ctx.fill();    
            ctx.fillStyle = "#FFF8";
            ctx.setTransform(1, 0, 0, 1, this.x - 5, this.y - 5);
            ctx.beginPath();
            ctx.ellipse(0, 0, 2, 3, -Math.PI * 0.75, 0, 2 * Math.PI);
            ctx.fill();  
        },
    }
    const wheel = {
        x: canvas.width / 2, y: canvas.height / 2, r: 0,
        dr: ROTATE, // delta rotate
        radius: Math.min(canvas.height, canvas.width) / 2 * WHEEL_SIZE,
        text: "wheel",
        update() { this.r += this.dr },
        draw() {
            const aXx = Math.cos(this.r);
            const aXy = Math.sin(this.r);
            ctx.setTransform(aXx, aXy, -aXy, aXx, this.x, this.y);
            ctx.fillStyle = "#CCC";
            ctx.strokeStyle = "#000";
            ctx.lineWidth = 6;
            ctx.beginPath();
            ctx.arc(0, 0, this.radius, 0, 2 * Math.PI);
            ctx.stroke();
            ctx.fill();    
            ctx.strokeStyle = ctx.fillStyle = "#aaa";
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.lineTo(-20,0);
            ctx.lineTo(20,0);
            ctx.moveTo(0,-20);
            ctx.lineTo(0,20);
            ctx.stroke();
            ctx.fillText(this.text, 0, this.radius - 16);
        },    
    }
    <canvas id="canvas" width="300" height="300"></canvas>

    Centripetal force

    Centripetal is the force towards the center of the turning wheel. However because the ball is sliding the force calculated is not a centripetal force.

    You can calculate the centripetal force by scaling the vector from the ball to the center by the dot product of the "vector to center" dot "force vector"

    The force vector on the ball is shown as a white arrow. The arrows size is the force as acceleration in pixels per second.

    The vector is towards the center but will never point directly at the center of the wheel.

    Approximation

    This simulation is an approximation. You will need an understanding of calculus and differential equations to get closer to reality.

    Using a more complex simulation would only be noticeable if the friction was very close or at 1 and it is easier then to just fix the ball to the wheel, scaling the position from center by an inverse power of the friction coefficient.