I am trying to make a simple simulation of pendulum using Runge–Kutta fourth-order method. I am using p5.js. Usually it calculates the angles properly but sometimes it just starts spinning randomly etc. I have no idea how to resolve this issue it seems to be a problem with my implementation of said algorithm I think?
I used code from pang tao's introduction to Computational Physics as an inspiration and it seems quite similar
First part of said code in Fortran
Second part
let screenWidth = 1300;
let screenHight = 1970;
let angleChangeDifference;
let gSlider;
let lSlider;
let aFrequencySlider;
let dumpingSlider;
let startAngleSlider;
let timestepSlider;
let timeMaxSlider;
let dForceSlider;
let initForceSlider;
let startAngle = 0;
let currentAngle = 0;
let circleX = 0;
let circleY = 200;
let circleRWidth = 100;
let circleRHeight = 100;
let lineXStart = 0;
let lineYStart = 0;
let lineXEnd = circleX;
let lineYEnd = (circleY - circleRHeight / 2);
function setup() {
createCanvas(screenWidth, screenHight);
changeScreenDeafultStartingPoint(screenWidth / 2, 100);
frameRate(60)
createSliders();
setInterval(showSliderValue, 100);
startButton.mouseClicked(start);
restartButton.mouseClicked(restart);
chartButton.mouseClicked(enableChart);
background(200);
}
let angleSign = '\u00B0';
let omegaSign = '\u03C9';
let chartOn = false;
let step = 0;
function draw() {
startupConfiguration()
showSliderValue()
if (step >= 1) {
rotatePendulum();
if (step == 1) {}
if (step == 2)
startButton.remove();
chartButton.position(20, 340);
if (chartOn == true) {
createChart(0, 1, 'czas (s)', 'kat (' + angleSign + ')', degreesArr);
createChart(0, 320, 'czas (s)', omegaSign + ' (' + angleSign + '/s)', omegaArr);
scale(2);
createPhaseChart(290, 80, 'kat (' + angleSign + ')', omegaSign + ' (' + angleSign + '/s)', degreesArr);
scale(0.5);
}
}
line(lineXStart, lineYStart, lineXEnd, lineYEnd + (20 * lSlider.value()));
fill(200, 76, 43)
ellipse(circleX, circleY + (20 * lSlider.value()), circleRWidth, circleRHeight);
}
function createSliders() {
gSlider = createSlider(0.05, 20, 9.81, 0.01);
gSlider.position(1100, -90);
lSlider = createSlider(0.5, 10, 5, 0.5);
lSlider.position(1100, -50);
initForceSlider = createSlider(-5, 5, 0, 0.5);
initForceSlider.position(1100, 50);
dForceSlider = createSlider(-2, 2, 0.9, 0.05)
dForceSlider.position(1100, 90);
aFrequencySlider = createSlider(-2, 2, 2 / 3, 1 / 3);
aFrequencySlider.position(1100, 130);
dumpingSlider = createSlider(0.00, 1.5, 0.5, 0.05);
dumpingSlider.position(1100, 170);
startAngleSlider = createSlider(-Math.PI / 2, Math.PI / 2, 0, Math.PI / 32);
startAngleSlider.position(1100, 210);
timestepSlider = createSlider(0, 1000, 100, 10);
timestepSlider.position(1100, 250);
timeMaxSlider = createSlider(10, 10000, 1000, 10);
timeMaxSlider.position(1100, 290);
startButton = createButton('ZATWIERDZ', false);
startButton.position(100, 310)
restartButton = createButton('RESTART', false);
restartButton.position(20, 310)
chartButton = createButton('WYKRES', false);
chartButton.position(-200, 340);
}
function showSliderValue() {
background(200);
fill(0, 0, 0)
text('sila poczatkowa', 440, -60)
text(initForceSlider.value(), 400, -42)
text('sila sprawcza', 440, -20)
text(dForceSlider.value(), 400, -2)
text('czestosc katowa', 440, 20)
text(aFrequencySlider.value(), 400, 42)
text('tlumienie', 440, 60)
text(dumpingSlider.value(), 400, 82)
text('kat poczatkowy', 440, 100)
text(int(degrees(startAngleSlider.value())), 400, 122)
text('krok czasowy (N1)', 440, 140)
text(timestepSlider.value(), 400, 162)
text('dlugosc symulacji (N2)', 440, 180)
text(timeMaxSlider.value(), 400, 202)
}
function start() {
angleIndex = 0;
step++;
startAngle = startAngleSlider.value();
currentAngle = startAngle;
angleChangeDifference = simulate();
rotatePendulum()
startButton.html("START")
}
function restart() {
window.location.reload();
}
function enableChart() {
chartOn = true;
}
function createChart(moveByX, moveByY, xName, yName, table) {
rotate(-currentAngle);
scale(1.1);
translate(moveByX, moveByY);
strokeWeight(1);
line(-500, 500, 530, 500);
line(-500, 700, 530, 700);
line(-500, 500, -500, 700);
line(530, 500, 530, 700);
strokeWeight(1);
let counter = 0;
for (i = 510; i < 700; i += 10) {
if (counter < 9 && counter % 2 == 0)
text(90 - 10 * counter, -520, i + 5)
else if (counter == 9 && counter % 2 == 0)
text(90 - 10 * counter, -515, i + 5)
else if (counter > 9 && counter % 2 == 0)
text(90 - 10 * counter, -525, i + 5)
line(-505, i, 530, i);
counter++;
}
textSize(25);
text(xName, -20, 750)
textSize(12);
counter = 0;
for (i = -490; i < 535; i += 25) {
line(i, 500, i, 705);
if (counter % 4 == 0) {
line(i, 500, i, 705);
text(counter * 2.5, i - 5, 715);
}
counter++;
}
rotate(-90);
textSize(25);
text(yName, -670, -550)
textSize(12);
rotate(90);
fillChartByTableValues(table);
translate(-moveByX, -moveByY);
scale(0.91);
rotate(currentAngle);
}
function fillChartByTableValues(table) {
strokeWeight(2);
stroke(0, 0, 255);
for (i = 0; i < timeArr.length - 1; i++) {
FirstPointX = -490 + timeArr[i] * 10;
FirstPointY = 600 + table[i] * (-1);
SecondPointX = -490 + timeArr[i + 1] * 10;
SecondPointY = 600 + table[i + 1] * (-1);
line(FirstPointX, FirstPointY, SecondPointX, SecondPointY);
}
stroke(0, 0, 0);
strokeWeight(0.1);
}
function createPhaseChart(moveByX, moveByY, xName, yName, table) {
rotate(-currentAngle);
scale(1.1);
translate(moveByX, moveByY);
strokeWeight(1);
line(-500, 500, -300, 500);
line(-500, 700, -300, 700);
line(-500, 500, -500, 700);
line(-300, 500, -300, 700);
strokeWeight(1);
let counter = 0;
textSize(8);
for (i = 510; i < 700; i += 10) {
if (counter < 9 && counter % 2 == 0)
text(90 - 10 * counter, -517, i + 3)
else if (counter == 9 && counter % 2 == 0)
text(90 - 10 * counter, -512, i + 3)
else if (counter > 9 && counter % 2 == 0)
text(90 - 10 * counter, -520, i + 3)
line(-505, i, -300, i);
counter++;
}
textSize(12);
textSize(15);
text(xName, -430, 735)
textSize(12);
counter = 0;
textSize(8);
for (i = -490; i < -300; i += 10) {
line(i, 500, i, 705);
if (counter < 9 && counter % 2 == 0)
text(-90 + 10 * counter, i - 7, 715);
else if (counter == 9 && counter % 2 == 0)
text(-90 + 10 * counter, i - 2, 715);
else if (counter > 9 && counter % 2 == 0)
text(-90 + 10 * counter, i - 4, 715);
counter++;
}
textSize(12);
rotate(-90);
textSize(15);
text(yName, -620, -528)
textSize(12);
rotate(90);
fillPhaseChartByTableValues(degreesArr, omegaArr)
translate(-moveByX, -moveByY);
scale(0.91);
rotate(currentAngle);
}
function fillPhaseChartByTableValues(tableX, tableY) {
translate(-400, 600);
strokeWeight(1);
stroke(0, 0, 255);
for (i = 0; i < tableX.length; i++) {
ellipse(tableX[i], tableY[i], 0.5, 0.5);
}
translate(400, -600);
stroke(0, 0, 0);
strokeWeight(0.1);
}
function startupConfiguration() {
background(200);
angleMode(DEGREES);
changeScreenDeafultStartingPoint(screenWidth / 2, 100);
}
function changeScreenDeafultStartingPoint(x, y) {
translate(x, y);
}
let angleIndex = 0;
function rotatePendulum() {
currentAngle = angleChangeDifference[angleIndex] * (180 / PI);
rotate(currentAngle);
if (step > 1) {
angleIndex++
}
}
function calculateIntegral(t, q, dt, f) {
let k1 = f(t, q).map(val => val * dt);
let temp = k1.map(val => val * 0.5);
temp = temp.map((val, index) => val + q[index])
let k2 = f(t + 0.5 * dt, temp).map(val => val * dt);
temp = k2.map(val => val * 0.5);
temp = temp.map((val, index) => val + q[index])
let k3 = f(t + 0.5 * dt, temp).map(val => val * dt);
temp = q.map((val, index) => val + k3[index]);
let k4 = f(t + dt, temp).map(val => val * dt);
temp = k2.map((val, index) => val + k3[index])
temp = temp.map(val => val * 2)
temp = temp.map((val, index) => (val + k1[index] + k4[index]) / 6)
temp = temp.map((val, index) => val + q[index])
return [t + dt, temp];
}
function modelPendulum(t, q) {
let c = dumpingSlider.value();
let fw = dForceSlider.value();
let w = aFrequencySlider.value();
let x1 = q[0];
let x2 = q[1];
return [x2, -(Math.sin(x1)) - (c * x2) + (fw * Math.cos(w * t))];
}
let degreesArr, timeArr;
function simulate() {
let t = 0.0;
let dt = (3 * Math.PI) / timestepSlider.value()
let tf = timeMaxSlider.value() * dt
let q = [startAngle, initForceSlider.value()];
let Nt = int(Math.round((tf - t) / dt)) + 1;
let solution = new Array(q.length + 1);
for (i = 0; i < q.length + 1; i++) {
solution[i] = new Array(Nt).fill(0);
}
solution[0][0] = t;
solution[1][0] = q[0];
solution[2][0] = q[1];
k = 1;
while (t <= tf) {
let temporaryResult = [];
temporaryResult = calculateIntegral(t, q, dt, modelPendulum);
t = temporaryResult[0];
q = temporaryResult[1];
solution[0][k] = t;
solution[1][k] = q[0];
solution[2][k] = q[1];
k = k + 1
}
timeArr = solution[0];
degreesArr = solution[1];
omegaArr = solution[2];
let counter = 0;
let ifChaos = false;
while (counter != degreesArr.length - 1 && ifChaos != true) {
if (degreesArr[counter] > 13.5 || degreesArr[counter] < -13.5) {
ifChaos = true;
}
counter++;
}
if (ifChaos == true) {
degreesArr = degreesArr.map(val => val * 5.32);
omegaArr = omegaArr.map(val => val * 23.32);
} else {
degreesArr = degreesArr.map(val => val * 35.32);
omegaArr = omegaArr.map(val => val * 35.32);
}
return solution[1]
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.1.9/lib/p5.js"></script>
Am not familiar with P5, so here's a solution using ThreeJS leveraging the Runge-Kutta algorithm from the Mathematics Stack Exchange.
For convenience, I have wrapped the Runge-Kutta algorithm in a class, with the constructor taking the initial parameters of:
the gravity acceleration constant g (for earth, 9.81 meters/sec/sec),
the pendulum length (in meters),
the initial angle of the pendulum (where 0 is straight down),
the initial angular velocity (in meters/sec), and
the max time increment. (Since the Runge-Kutta is employed to solve a second order differential equation using time as the variable, it appears based on this author's experimentation that one cannot overextend the delta time increment and still retain accuracy of the resulting pendulum position and velocity. This parameter simply limits the maximum t value passed in the updatePosition method, with a default of 0.1s.)
To assist in the use of the Runta-Kutta algorithm, the code below simulates two 1 meter pendulums:
The first having an initial position of -PI/2 (-90 deg) with no starting angular velocity.
The second having an initial position of PI (180 deg) with a very small starting angular velocity.
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.module.js';
class RungeKutta {
constructor( g, pendulumLength, initialAngle, angularVelocity, maxTimeDelta ) {
this.g = g;
this.pendulumLength = pendulumLength;
this.theta = initialAngle;
this.omega = angularVelocity;
this.maxTimeDelta = maxTimeDelta || 0.1;
}
updatePosition( t ) {
let self = this;
function omegaDot( theta ){
return -( self.g / self.pendulumLength ) * Math.sin( theta );
}
function thetaDot( omega ){
return omega;
}
// If the browser tab becomes inactive, then there will be a large
// time delta, which will disrupt the RungeKutta algorithm. If more
// than max allowed seconds has lapsed, then reset the timer.
if ( self.maxTimeDelta < t ) {
t = self.maxTimeDelta;
}
let aomega = omegaDot( self.theta );
let atheta = thetaDot( self.omega );
let bomega = omegaDot( self.theta + 0.5 * t * atheta );
let btheta = thetaDot( self.omega + 0.5 * t * aomega );
let comega = omegaDot( self.theta + 0.5 * t * btheta );
let ctheta = thetaDot( self.omega + 0.5 * t * bomega );
let domega = omegaDot( self.theta + t * ctheta );
let dtheta = thetaDot( self.omega + t * comega );
self.omega = self.omega + ( t / 6 ) * ( aomega + 2 * bomega + 2 * comega + domega );
self.theta = self.theta + ( t / 6 ) * ( atheta + 2 * btheta + 2 * ctheta + dtheta );
return self;
}
}
//
// Set up the ThreeJS environment.
//
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
camera.position.set( 0, 0, 100 );
camera.lookAt( 0, 0, 0 );
var scene = new THREE.Scene();
//
// Create the pendulum mesh.
//
var length = 30, width = 1;
var shape = new THREE.Shape();
shape.moveTo( -width / 2, 0 );
shape.lineTo( +width / 2, 0 );
shape.lineTo( +width / 2, -length );
shape.lineTo( -width / 2, -length );
shape.lineTo( -width / 2, 0 );
var extrudeSettings = {
steps: 2,
depth: 2,
bevelEnabled: true,
bevelThickness: .25,
bevelSize: .25,
bevelOffset: 0,
bevelSegments: 1
};
var geometry = new THREE.ExtrudeBufferGeometry( shape, extrudeSettings );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var mesh0 = new THREE.Mesh( geometry, material );
mesh0.position.x = -17;
scene.add( mesh0 );
var mesh1 = mesh0.clone();
mesh1.position.x = +17;
scene.add( mesh1 );
//
// And now animate the pendulum using RungeKutta.
//
let pendulumState0 = new RungeKutta( 9.81, 1, -Math.PI / 2, 0, 0.1 );
let pendulumState1 = new RungeKutta( 9.81, 1, Math.PI, 0.01, 0.1 );
let now = performance.now();
let lastTimer = now;
var animate = function () {
requestAnimationFrame( animate );
now = performance.now();
pendulumState0.updatePosition( ( now - lastTimer ) / 1000 );
pendulumState1.updatePosition( ( now - lastTimer ) / 1000 );
lastTimer = now;
mesh0.rotation.z = pendulumState0.theta;
mesh1.rotation.z = pendulumState1.theta;
renderer.render( scene, camera );
};
animate();
</script>
Hopefully this will assist with your P5 implementation.
This is an example of a pendulum with p5, using Leapfrog method, but it can be easily adapted to use Runge-Kutta 4 (Leapfrog has the advantage that it is symplectic, at variance with RK).
For a simple pendulum you have to solve the second order differential (Newton or Euler-Lagrange) equation
d^2 O / dt^2 = -(g/l) sin(O).
where O is the angle from the vertical.
To apply numerical methods is convenient first to translate it into an equivalent system of two first order differential equations
dw/dt = -(g/l) sin(O)
dO/dt = w
you can solve this system for instance with Runge-Kutta4 or other methods. A very simple one to start with is the simple 2nd order Leapfrog method. You discretize times in steps dt so
w_n == w((n-1/2)dt)
O_n == O(n dt)
and then do the following iteratively in a loop
w_{n+1} = w_n -(g/l) sin(O_n) dt
O_{n+1} = O_n + w_{n+1} dt
This method is implemented in a pendulum class in this code, together with its parameters, a very simple render() function for visualization in p5, and a method for calculating energy for instance (can be used to check conservation of energy if not forced). It is very simple and you can test it. Of course, small time-steps dt should be used in order to have good accuracy.
If you wish to use Runge-Kutta4 or other high order method it is convenient to write functions Fw(t,w,O) and FO(t,w,O) such that the equations read
dw/dt = Fw(t,w,O) (== -(g/l) sin(O) + F0 cos(W t))
dO/dt = FO(t,w,O) (== w )
where I have now included a forcing F0 cos(W t) to show, just in case, how to add any time dependent force.
You can then discretize in time (no leapfrog steps)
t_n == n dt
w_n == w(n dt)
O_n == O(n dt)
and calculate the quantities k1, k2, k3, k4 of RK4 each time step n.
Note that for this case the ki are two dimensional vectors {kiw,kiO}, and the function f(t,w,O) =={Fw(t,w,O),FO(t,w,O)} is also a 2d vector.
Taking this into account, and defining separately the functions Fw and FO you can easily replace the Leapfrog method of the p5 example 1 by a RK4 method, and add forcing if desired.
If you see very strange behaviour, you should check signs and a proper setup of the RK4 method.
I hope this can help.
let P1;
function setup() {
createCanvas(720, 400);
P1=new PenduloSimple(3.14,0.0);
}
function draw() {
background(220);
P1.render();
P1.leapFrog();
}
class PenduloSimple {
constructor(ang, velang) {
this.ang = ang;
this.velang = velang;
this.g = 9.8;
this.dt = 0.01;
this.l = 1.0;
this.m = 0.1;
this.E0 = this.Energy();
}
Energy() {
let E=
this.m * this.l * this.l * this.velang * this.velang * 0.5 - this.m * this.g * this.l * cos(this.ang);
return E;
}
leapFrog() {
// Método de Leapfrog
this.velang =
this.velang + this.dt*(-this.g/this.l)*sin(this.ang);
this.ang = this.ang + this.dt*this.velang;
}
render(){
var x0 = width / 2;
var y0 = height / 2;
var mult0 = 100;
var x = this.l * sin(this.ang);
var y = this.l * cos(this.ang);
var xplot = x0 + x * mult0;
var yplot = y0 + y * mult0;
// Draw a circle
stroke(50);
fill(100);
ellipse(xplot, yplot, 24, 24);
// La cuerda
stroke(50);
line(x0, y0, xplot, yplot);
let E = this.Energy();
stroke(0);
fill(255, 0, 0);
rect(10, height * 0.5, 20, -E * height * 0.45 / this.E0);
fill(0, 255, 0);
rect(30, height * 0.5, 20, -this.ang * height * 0.4 / 6.28);
fill(0, 0, 255);
rect(50, height * 0.5, 20, -this.velang * mult0 * 0.1);
fill(1);
stroke(255);
fill(255, 0, 0);
text("E: " + E, width * 0.1, 20);
fill(50, 200, 50);
text("ang: " + this.ang, width * 0.1, 30);
fill(0, 0, 255);
text("velang: " + this.velang, width * 0.1, 40);
}
}
I am developing a game in ionic 4 'spin the wheel'. so far I am able to spin the wheel which stops randomly on any of the sections. Can anyone help me with setting probability to sections so that users will have fewer chances to win? I would like to pre-define each section with some percentage value. for eg section with 0 will have 50% 4 will have 10% etc, and the greatest value will have a 1% chance to win. below code, I have tried.
SpinWheel.ts
export class SpinWheel {
#ViewChild('myCanvas', { static: false }) myCanvas: ElementRef;
colors = ["#2a8251", "#dc0927", "#414243", "#dc0927", "#2E2C75",
"#dc0927", "#414243", "#dc0927", "#2E2C75",
"#dc0927"];
restaraunts = ["0", "32", "15", "19",
"4", "21", "2", "25",
"17", "34"];
startAngle = 0;
arc = 2 * Math.PI / 10;
spinTimeout = null;
spinArcStart = 10;
spinTime = 0;
spinTimeTotal = 0;
ctx;
spinAngleStart;
constructor() {
}
ngOnInit() {
}
ngAfterViewInit(): void {
this.draw();
}
draw() {
this.drawRouletteWheel();
}
drawRouletteWheel() {
var canvas = document.getElementById("wheelcanvas");
var outsideRadius = 140;
var textRadius = 120;
var insideRadius = 25;
this.ctx = (<HTMLCanvasElement>this.myCanvas.nativeElement).getContext('2d');
this.ctx.clearRect(0, 0, 500, 500);
this.ctx.strokeStyle = "black";
this.ctx.lineWidth = 2;
this.ctx.font = 'bold 12px sans-serif';
for (var i = 0; i < 10; i++) {
var angle = this.startAngle + i * this.arc;
this.ctx.fillStyle = this.colors[i];
this.ctx.beginPath();
this.ctx.arc(150, 150, outsideRadius, angle, angle + this.arc, false);
this.ctx.arc(150, 150, insideRadius, angle + this.arc, angle, true);
this.ctx.stroke();
this.ctx.fill();
this.ctx.save();
this.ctx.shadowOffsetX = -1;
this.ctx.shadowOffsetY = -1;
this.ctx.shadowBlur = 0;
//this.ctx.shadowColor = "rgb(220,220,220)";
this.ctx.fillStyle = "white";
this.ctx.translate(150 + Math.cos(angle + this.arc / 2) * textRadius, 150 + Math.sin(angle + this.arc / 2) * textRadius);
this.ctx.rotate(angle + this.arc / 2 + Math.PI / 2);
var text = this.restaraunts[i];
this.ctx.fillText(text, -this.ctx.measureText(text).width / 2, 0);
this.ctx.restore();
}
//Arrow
this.ctx.fillStyle = "black";
this.ctx.beginPath();
this.ctx.moveTo(150 - 4, 150 - (outsideRadius + 5));
this.ctx.lineTo(150 + 4, 150 - (outsideRadius + 5));
this.ctx.lineTo(150 + 4, 150 - (outsideRadius - 5));
this.ctx.lineTo(150 + 9, 150 - (outsideRadius - 5));
this.ctx.lineTo(150 + 0, 150 - (outsideRadius - 13));
this.ctx.lineTo(150 - 9, 150 - (outsideRadius - 5));
this.ctx.lineTo(150 - 4, 150 - (outsideRadius - 5));
this.ctx.lineTo(150 - 4, 150 - (outsideRadius + 5));
this.ctx.fill();
}
spin() {
this.spinAngleStart = Math.random() * 10 + 10;
this.spinTime = 0;
this.spinTimeTotal = Math.random() * 3 + 4 * 1000;
this.rotateWheel();
}
rotateWheel() {
this.spinTime += 30;
if (this.spinTime >= this.spinTimeTotal) {
this.stopRotateWheel();
return;
}
var spinAngle = this.spinAngleStart - this.easeOut(this.spinTime, 0, this.spinAngleStart, this.spinTimeTotal);
this.startAngle += (spinAngle * Math.PI / 180);
this.drawRouletteWheel();
this.spinTimeout = setTimeout(() => {
this.rotateWheel();
}, 30);
}
stopRotateWheel() {
clearTimeout(this.spinTimeout);
var degrees = this.startAngle * 180 / Math.PI + 90;
var arcd = this.arc * 180 / Math.PI;
var index = Math.floor((360 - degrees % 360) / arcd);
this.ctx.save();
this.ctx.font = 'bold 30px sans-serif';
var text = this.restaraunts[index]
//this.ctx.fillText(text, 150 - this.ctx.measureText(text).width / 2, 150 + 10);
alert("You got:\n" + text);
this.ctx.restore();
}
// t: current time
// b: start value
// c: change in value
// d: duration
easeOut(t, b, c, d) {
return c * Math.sin(t/d * (Math.PI/2)) + b;
}
}
spin_wheel.html
<div class="wheel">
<canvas #myCanvas width="auto" height="300"></canvas>
<div class="icon-center"><img class="app-logo" (click)="spin()" src="../../assets/icon/favicon.png"/></div>
</div>
See attached image spin wheel screen
Thanks in advance.
I am working on small project of lucky draw number, am able to spin the wheel after user clicks button but not able to do it automatically, means I want to show count Down after time is reached wheel spins. I have used set Interval method but its not working.
window.onload = function () {
var fiveMinutes = 10 * 1,
display = document.querySelector('#count');
startTimer(fiveMinutes, display);
};
function startTimer(duration, display) {
var timer = duration, minutes, seconds;
setInterval(function ()
{
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.textContent = "Wheel Will Spin After: "+minutes + ":" + seconds;
if (--timer < 0) {
//timer = duration;
spin();
if(timer<=-1)
{
display.textContent = "Wheel Will Spin After: ";
}
}
}, 1000);
var colors = ["#B8D430", "#3AB745", "#029990", "#3501CB",
"#2E2C75", "#673A7E", "#CC0071", "#F80120",
"#F35B20", "#FB9A00", "#FFCC00", "#FEF200","#FEFAAA","#FEFA1A"];
var restaraunts = ["1", "2", "3", "4","5", "6", "7", "8","9", "10", "11", "12","13","14"];
var startAngle = 0;
var arc = Math.PI / 7;
var spinTimeout = null;
var spinArcStart = 10;
var spinTime = 0;
var spinTimeTotal = 0;
var ctx;
function draw() {
drawRouletteWheel();
}
function drawRouletteWheel() {
var canvas = document.getElementById("wheelcanvas");
if (canvas.getContext) {
var outsideRadius = 200;
var textRadius = 160;
var insideRadius = 125;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 20px Times New Roman';
for(var i = 0; i < 14; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 0;
ctx.shadowColor = "rgb(220,220,220)";
ctx.fillStyle = "black";
ctx.translate(250 + Math.cos(angle + arc / 2) * textRadius, 250 + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var text = restaraunts[i];
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
//Arrow
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(250 - 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 0, 250 - (outsideRadius - 13));
ctx.lineTo(250 - 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius + 5));
ctx.fill();
}
}
function spin() {
spinAngleStart = Math.random() * 10 + 10;
spinTime = 5;
spinTimeTotal = Math.random() * 3 + 4 * 2000;
rotateWheel();
}
function rotateWheel() {
spinTime += 20;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawRouletteWheel();
spinTimeout = setTimeout('rotateWheel()', 10);
}
function stopRotateWheel() {
clearTimeout(spinTimeout);
var degrees = startAngle * 180 / Math.PI + 90;
var arcd = arc * 180 / Math.PI;
var index = Math.floor((360 - degrees % 360) / arcd);
ctx.save();
ctx.font = 'bold 12px sans-serif';
var text = restaraunts[index]
//ctx.fillText(text, 250 - ctx.measureText(text).width / 2, 250 + 10);
//window.location = "navto://"+restaraunts[index]+"_stack";
//alert(text);
document.getElementById("msg").innerHTML=text;
ctx.restore();
}
function easeOut(t, b, c, d) {
var ts = (t/=d)*t;
var tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}
draw();
}
<html>
<body>
<div id= "count" class="category_heading" >Wheel Will Spin After: </div>
<div id= "msg" class="category_heading" >LUCKY NUMBER IS : </div>
<canvas id="wheelcanvas" width="500" height="500"></canvas>
</body>
</html>
First the solution. Replace
spinTimeout = setTimeout('rotateWheel()', 10);
with
spinTimeout = setTimeout( rotateWheel, 10);
The reason the error occurs is because rotateWheel is a nested function within the startTimer function and not in global scope. The code snippet "rotateWheel()" which is passed to setTimeout as text is compiled into a function in global scope which when executed can't find the rotateWheel function. Replacing the text with a function reference (which is in scope) removes the need to compile the source text in the first place.
In general avoid using JavaScript source snippets within code unless it is a quick and dirty solution to something no-one else is going to see :-).
I take it this is work in progress and there may be more bugs before the code is complete. Good luck.
In response to comment, the wheel continues to rotate because spin() is being called during each interval timer callback after timer goes negative.
var timerId = setInterval(function ()
{
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.textContent = "Wheel Will Spin After: "+minutes + ":" + seconds;
if (--timer < 0) {
//timer = duration;
spin();
clearInterval( timerId)
display.textContent = "Wheel Will Spin After: ";
}`
}, 1000);
The solution above is to call spin once and then stop the interval timer. Notice that if( --timer < 0) followed by if(timer<=-1) effectively test the same condition for integers which means the second test is not required.
i've been playing with jason brown's let it snow plug-in.
his code only accommodates for a single custom image, and i've been trying to figure out how to change the code so it accommodates multiple custom images within a random range.
!function($){
var defaults = {
speed: 0,
interaction: true,
size: 2,
count: 200,
opacity: 0,
color: "#ffffff",
windPower: 0,
image: false
};
$.fn.let_it_snow = function(options){
var settings = $.extend({}, defaults, options),
el = $(this),
flakes = [],
canvas = el.get(0),
ctx = canvas.getContext("2d"),
flakeCount = settings.count,
mX = -100,
mY = -100;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
function snow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = flakes[i],
x = mX,
y = mY,
minDist = 100,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
switch (settings.windPower) {
case false:
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
break;
case 0:
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
break;
default:
flake.velX += 0.01 + (settings.windPower/100);
}
}
var s = settings.color;
var patt = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/;
var matches = patt.exec(s);
var rgb = parseInt(matches[1], 16)+","+parseInt(matches[2], 16)+","+parseInt(matches[3], 16);
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
if (settings.image == false) {
ctx.fillStyle = "rgba(" + rgb + "," + flake.opacity + ")"
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
} else {
ctx.drawImage($("img#lis_flake").get(0), flake.x, flake.y, flake.size * 2, flake.size * 2);
}
}
requestAnimationFrame(snow);
};
function reset(flake) {
if (settings.windPower == false || settings.windPower == 0) {
flake.x = Math.floor(Math.random() * canvas.width);
flake.y = 0;
} else {
if (settings.windPower > 0) {
var xarray = Array(Math.floor(Math.random() * canvas.width), 0);
var yarray = Array(0, Math.floor(Math.random() * canvas.height))
var allarray = Array(xarray, yarray)
var selected_array = allarray[Math.floor(Math.random()*allarray.length)];
flake.x = selected_array[0];
flake.y = selected_array[1];
} else {
var xarray = Array(Math.floor(Math.random() * canvas.width),0);
var yarray = Array(canvas.width, Math.floor(Math.random() * canvas.height))
var allarray = Array(xarray, yarray)
var selected_array = allarray[Math.floor(Math.random()*allarray.length)];
flake.x = selected_array[0];
flake.y = selected_array[1];
}
}
flake.size = (Math.random() * 3) + settings.size;
flake.speed = (Math.random() * 1) + settings.speed;
flake.velY = flake.speed;
flake.velX = 0;
flake.opacity = (Math.random() * 0.5) + settings.opacity;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = Math.floor(Math.random() * canvas.width),
y = Math.floor(Math.random() * canvas.height),
size = (Math.random() * 3) + settings.size,
speed = (Math.random() * 1) + settings.speed,
opacity = (Math.random() * 0.5) + settings.opacity;
flakes.push({
speed: speed,
velY: speed,
velX: 0,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
angle: 180,
opacity: opacity
});
}
snow();
}
if (settings.image != false) {
$("<img src='"+settings.image+"' style='display: none' id='lis_flake'>").prependTo("body")
}
init();
$(window).resize(function() {
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
el2 = el.clone();
el2.insertAfter(el);
el.remove();
el2.let_it_snow(settings);
}, 200);
});
if (settings.interaction == true) {
canvas.addEventListener("mousemove", function(e) {
mX = e.clientX,
mY = e.clientY
});
}}}(window.jQuery);
right at the top of the code, in the defaults properties, is where you point the url of the image. below is what i've put in, so it chooses between image1.jpeg and image2.jpeg
image: "img/image'+Math.floor((Math.random() * 2) + 1)+'.jpeg";
however, when the "snowflakes" respawn, the image stays the same instead of choosing a random number again. what do i have to change so that when a snowflake is created, it chooses a random image and becomes it?
i hope my questions is clear, let me know if you need more clarity. i'm new to j-script, any help would be appreciated.
The plugin author loads a single custom image into a hidden element with
if (settings.image != false) {
$("<img src='"+settings.image+"' style='display: none' id='lis_flake'>").prependTo("body")
}
and loads the snowflakes with this
ctx.drawImage($("img#lis_flake").get(0)
This solution hasn't been tested, but if you are willing to hack the plugin a bit, you could potentially make the following changes to use two optional images:
1) Add this just before the init(); function call. It adds another hidden image from settings.image2.
if (settings.image2) {
$("<img src='"+settings.image2+"' style='display: none' id='lis_flake2'>").prependTo("body")
}
2) Modify the function init() with
flakes.push({
// Create a new property called 'imgNum' to hold either "" or "2";
imgNum: (settings.image2 && Math.floor(Math.random() * 2) === 0 ? "2" : ""),
// rest of the code ...
speed: speed,
3) Change
ctx.drawImage($("img#lis_flake").get(0), flake.x, flake.y, flake.size * 2, flake.size * 2);
to
ctx.drawImage($("img#lis_flake" + flake.imgNum).get(0), flake.x, flake.y, flake.size * 2, flake.size * 2);
to chose the image based on the flake.imgNum set randomly in init().
4) Change the options you pass into the plugin like so:
options = {
// other options
image: "img/image1.jpeg",
image2: "img/image2.jpeg",
// other options
}
These changes should allow the plugin to still work if image2 isn't set.
I have some html5 and javascript code to create a spinning roulette wheel. It is split up into 14 outcomes. My question is, how can I get some unique HTML code to display per each outcome of the wheel. For example, if the wheel lands on Business, some text will appear below the wheel that has information on Business.
Here is my HTML code and below it is a link to a working example:
<!--[if IE]><script type="text/javascript" src="/sites/default/files/1010/source/excanvas.js"></script><![endif]-->
<input type="button" value="spin" onclick="spin();" style="float: left;" />
<canvas id="wheelcanvas" width="800" height="750"></canvas>
<script type="application/javascript">
var colors = ["#B8D430", "#3AB745", "#029990", "#3501CB",
"#2E2C75", "#673A7E", "#CC0071", "#F80120",
"#F35B20", "#FB9A00", "#FFCC00", "#FEF200", "#B2DF00", "#5C8300"];
var classes = ["Business", "Office Education", "Continuing Care Assistant", "Practical Nursing",
"Primary Care Paramedic", "Early Childhood Education", "Cooking", "Electrician",
"Heavy Equipment Operator", "Industrial Mechanic - Millwright", "Plumbing & Pipefitting", "Truck Driver Training", "Welding", "Power Engineering"];
var startAngle = 0;
var arc = Math.PI / 7;
var spinTimeout = null;
var spinArcStart = 10;
var spinTime = 0;
var spinTimeTotal = 0;
var ctx;
function draw() {
drawRouletteWheel();
}
function drawRouletteWheel() {
var canvas = document.getElementById("wheelcanvas");
if (canvas.getContext) {
var outsideRadius = 300;
var textRadius = 260;
var insideRadius = 100;
ctx = canvas.getContext("2d");
ctx.clearRect(5,0,1000,1000);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 10px sans-serif';
for(var i = 0; i < 14; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(400, 400, outsideRadius, angle, angle + arc, false);
ctx.arc(400, 400, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 0;
ctx.shadowColor = "rgb(220,220,220)";
ctx.fillStyle = "black";
ctx.translate(400 + Math.cos(angle + arc / 2) * textRadius, 400 + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var text = classes[i];
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
//Arrow
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(400 - 4, 400 - (outsideRadius + 5));
ctx.lineTo(400 + 4, 400 - (outsideRadius + 5));
ctx.lineTo(400 + 4, 400 - (outsideRadius - 5));
ctx.lineTo(400 + 9, 400 - (outsideRadius - 5));
ctx.lineTo(400 + 0, 400 - (outsideRadius - 13));
ctx.lineTo(400 - 9, 400 - (outsideRadius - 5));
ctx.lineTo(400 - 4, 400 - (outsideRadius - 5));
ctx.lineTo(400 - 4, 400 - (outsideRadius + 5));
ctx.fill();
}
}
function spin() {
spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * 3 + 4 * 1000;
rotateWheel();
}
function rotateWheel() {
spinTime += 30;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawRouletteWheel();
spinTimeout = setTimeout('rotateWheel()', 30);
}
function stopRotateWheel() {
clearTimeout(spinTimeout);
var degrees = startAngle * 180 / Math.PI + 90;
var arcd = arc * 180 / Math.PI;
var index = Math.floor((360 - degrees % 360) / arcd);
ctx.save();
ctx.font = 'bold 30px sans-serif';
var text = classes[index]
ctx.fillText(text, 400 - ctx.measureText(text).width / 2, 400 + 10);
ctx.restore();
document.getElementById('wheelResult').innerHTML = 'Test' + text;
}
function easeOut(t, b, c, d) {
var ts = (t/=d)*t;
var tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}
draw();
</script>
<p id="wheelResult"></p>
http://www.ctrc.sk.ca/wheel.html
There are a couple of ways to do it but this is the way I would do it.
You could create a div tag with the css style of display:none and then set it with the info desired and hide and show it as needed.
Example:
<script>
var htmlData = ['<h1>Business</h1><br><span>What is Business?</span><br>...', ...];
....
function spin()
{
//Hide description box.
document.getElementById('idDescriptionBox').style.display = 'none';
spinAngleStart = Math.random() * 10 + 10;
....
}
....
function stopRotateWheel()
{
....
//Set description and display it.
var descriptionBox = document.getElementById('idDescriptionBox');
descriptionBox.innerHTML = htmlData[index];
descriptionBox.style.display = 'block';
}
....
</script>
<div id="idDescriptionBox" style="display:none;">
<!--Description info in here-->
....
</div>