I'm trying to create a realistic slot machine animation, where the "spin" (actually a looped translation) gradually slows to a stop at a specified slot image. With my current implementation, it does actually stop at the right image, but it does so by spinning past it, then reversing direction and finally landing at the correct spot.
displacement = initial_velocity*delta_time + 1/2*acceleration*delta_time^2
to derive
acceleration = (2*total_displacement - 2*initial_velocity*delta_time) / delta_time^2.
private getAnimationDisplacement(): number {
if(!this.spinInProgress) return 0
// Time
const totalDuration = this.machine.spinDuration // ms
const curTime = new Date()
const dt = curTime.getTime() - (this.lastFrameTime || this.spinInProgress.startTime).getTime() // ms
this.lastFrameTime = curTime
// Calculate total (final) displacement for current spin
const spinCount = 1
const spinOffset = spinCount * this.spinInProgress.slotFaces // number of faces
const faceOffset = this.spinInProgress.getFaceOffset().offset // pixels
const totalOffset = new FaceOffset(spinOffset + faceOffset) // pixels
const totalDisplacement = totalOffset.offset * this.getFaceHeight() // pixels
// Acceleration
const acceleration = (2*totalDisplacement - 2*INIT_VELOCITY * totalDuration) / Math.pow(totalDuration, 2) // pixels / ms^2
// Diplacement
this.displacement += this.velocity * dt
// Velocity
this.velocity += acceleration * dt
// Modulo by max displacement causes animation to loop
return this.displacement % this.maxDisplacement
}
I can't figure out how this standard equation of motion is somehow going the right direction, then reversing direction, to finally land at the correct spot. I'm trying to avoid the weird reverse animation. Any insights would be greatly appreciated.
Related
I am having an issue calculating the velocity relative to bpm and time that are extracted from midi file.
The problem that the speed of the block falling is to fast and I want to be able to make it slower but also able to come down to the bottom of the screen at the right time.
I am really bad at math and really going mad by this issue.
Here is the code code that are trigger on each tick for each note
const calculate = (note: Note,
position: Position,
height: number,
file: MidiFile,
currentTime: number,
addToPos?: boolean
) => {
// the height of the window of the mobile
height += note.position?.height ?? 0
const hTop = addToPos && position ? height - position.top : height;
const applyData = () => {
const bpm = file.file.header.tempos[0].bpm; // 130
const crotchet = bpm / 60;
}
return {
bpm,
crotchet,
}
}
const bpmInfo = applyData()
// the note time
let time = note.time;
let secondsPerBeat = (1 / bpmInfo.crotchet);
// the height of the block
const noteHeight = position.height
// currentTime= the media that are cureently playing
const timeDis = ((currentTime) - time);
const speed = (hTop / secondsPerBeat)
// the position of the block based on time and current time and bpm
let songposition = (timeDis * speed);
if (addToPos && songposition > noteHeight)
songposition += position.top;
const y = songposition;
return {
top: y >= noteHeight ? y : (isNaN(y) ? height - (noteHeight * 3) : undefined),
speed: speed,
...bpmInfo
}
}
What am I doing wrong here.
here is the midi file if you are wondering
I'm trying to draw a noisy line (using perlin noise) between two specific points.
for example A(100, 200) and B(400,600).
The line could be a points series.
Drawing random noisy line is so clear but I dont know how can I calculate distance specific points.
working of P5.js.
I don't have any code written yet to upload.
Please can anyone help me?
I tried to add sufficient comments that you would be able to learn how such a thing is done. There are a number of things that you should make yourself aware of if you aren't already, and it's hard to say which if these you're missing:
for loops
drawing lines using beginShape()/vertex()/endShape()
Trigonometry (in this case sin/cos/atan2) which make it possible to find angles and determine 2d offsets in X and Y components at a given angle
p5.Vector() and its dist() function.
// The level of detail in the line in number of pixels between each point.
const pixelsPerSegment = 10;
const noiseScale = 120;
const noiseFrequency = 0.01;
const noiseSpeed = 0.1;
let start;
let end;
function setup() {
createCanvas(400, 400);
noFill();
start = createVector(10, 10);
end = createVector(380, 380);
}
function draw() {
background(255);
let lineLength = start.dist(end);
// Determine the number of segments, and make sure there is at least one.
let segments = max(1, round(lineLength / pixelsPerSegment));
// Determine the number of points, which is the number of segments + 1
let points = 1 + segments;
// We need to know the angle of the line so that we can determine the x
// and y position for each point along the line, and when we offset based
// on noise we do so perpendicular to the line.
let angle = atan2(end.y - start.y, end.x - start.x);
let xInterval = pixelsPerSegment * cos(angle);
let yInterval = pixelsPerSegment * sin(angle);
beginShape();
// Always start with the start point
vertex(start.x, start.y);
// for each point that is neither the start nor end point
for (let i = 1; i < points - 1; i++) {
// determine the x and y positions along the straight line
let x = start.x + xInterval * i;
let y = start.y + yInterval * i;
// calculate the offset distance using noice
let offset =
// The bigger this number is the greater the range of offsets will be
noiseScale *
(noise(
// The bigger the value of noiseFrequency, the more erretically
// the offset will change from point to point.
i * pixelsPerSegment * noiseFrequency,
// The bigger the value of noiseSpeed, the more quickly the curve
// fluxuations will change over time.
(millis() / 1000) * noiseSpeed
) - 0.5);
// Translate offset into x and y components based on angle - 90°
// (or in this case, PI / 2 radians, which is equivalent)
let xOffset = offset * cos(angle - PI / 2);
let yOffset = offset * sin(angle - PI / 2);
vertex(x + xOffset, y + yOffset);
}
vertex(end.x, end.y);
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
This code makes jaggy lines, but they could be smoothed using curveVertex(). Also, making the line pass through the start and end points exactly is a little tricky because the very next point may be offset by a large amount. You could fix this by making noiseScale very depending on how far from an endpoint the current point is. This could be done by multiplying noiseScale by sin(i / points.length * PI) for example.
Suppose I have a canvas that is 1200px. How do I get an object to move from the starting point (100px) to its endpoint (1000px) within a given time (eg. 10 seconds)? In such a way that it takes the object exactly 10 seconds to traverse from starting point to endpoint.
My code looks like this so far:
function setup()
{
createCanvas(img.width, img.height);
//Initialize x with the start value
x = startX;
}
function draw()
{
image(img, 0, 0);
x = min(endX, x);
x+=2;
//stop the object if it's near enough to endx and endy
if (abs(endX - x) < 30)
{
x = endX;
}
y = 114;
//stop the object if it goes off of the screen
x = min(x, 1200);
x = max(x, 0);
var spotlightSize = 114;
blendMode(BLEND);
background(10);
image(spotlight_image, x-spotlightSize/2, y-spotlightSize/2, spotlightSize, spotlightSize);
blendMode(DARKEST);
image(img, 0, 0);
}
If the frame rate was perfect and constant, you could simply divide the distance to travel by the amount of frames in the time that it takes. That result would be how far you need to travel in each frame. The frame rate is not perfect or constant, but we will write a program assuming a perfect frame rate because it will help us later.
What you need to do is:
Find how many frames will pass in the time you want to move - multiply the time to move by the frames per second
Find the displacement from the start to the end - subtract the start from the end
Divide the displacement by the amount of frames that will pass
Move that far each frame until you are close enough to the end
An example implementation: (you used only x-position but I used vectors as they will probably be useful to someone in the future)
new p5();
const fps = 60; // frames per second
const startPos = createVector(100, 50);
var position = startPos.copy();
const endPos = createVector(600, 450);
const stopAtDist = 30; // stop if it's this far from the end point in any direction
const distToTravel = p5.Vector.sub(endPos, startPos);
const moveDurationS = 10; // Move duration in seconds
const moveDurationFrames = moveDurationS / (1 / fps); // How many frames will it take to move the distance
const distToMovePerFrame = p5.Vector.div(distToTravel, moveDurationFrames); // How far to move each frame
var currentlyMoving = true;
function setup() {
createCanvas(800, 500);
frameRate(fps);
}
function draw() {
background(0);
// Draw the start pos
stroke('yellow');
strokeWeight(10);
point(startPos.x, startPos.y);
// Draw the end pos
stroke('green');
point(endPos.x, endPos.y);
// Draw the current position
stroke('red');
point(position.x, position.y);
// If it's currently moving, then move
if (currentlyMoving) {
position.add(distToMovePerFrame);
}
// If it is close enough to the end, then stop
if (abs(dist(position.x, position.y, endPos.x, endPos.y)) < stopAtDist) {
currentlyMoving = false;
}
}
The frame rate is not constant, though. Fortunately, p5 has a function that tells us how many milliseconds have passed in the last frame. So what we do is:
Find how many milliseconds pass in the time you want to move - multiply the seconds you want it to move for by 1000
Find out how far it will move per millisecond - divide the start/end displacement by the amount of milliseconds that will pass
Each frame, move the distance per millisecond multiplied by how many milliseconds have gone past in that frame.
Here's that translated into code:
new p5();
const fps = 60; // frames per second
const startPos = createVector(100, 50);
var position = startPos.copy();
const endPos = createVector(600, 450);
const stopAtDist = 30; // stop if it's this far from the end point in any direction
const distToTravel = p5.Vector.sub(endPos, startPos);
const moveDurationS = 10;
const moveDurationMs = moveDurationS * 1000;
const distToMovePerMs = p5.Vector.div(distToTravel, moveDurationMs);
var currentlyMoving = true;
function setup() {
createCanvas(800, 500);
frameRate(fps);
}
function draw() {
background(0);
// Draw the start pos
stroke('yellow');
strokeWeight(10);
point(startPos.x, startPos.y);
// Draw the end pos
stroke('green');
point(endPos.x, endPos.y);
// Draw the current position
stroke('red');
point(position.x, position.y);
// If it's currently moving, then move
if (currentlyMoving) {
var thisFrameMovement = p5.Vector.mult(distToMovePerMs, deltaTime);
position.add(thisFrameMovement);
}
// If it is close enough to the end, then stop
if (abs(dist(position.x, position.y, endPos.x, endPos.y)) < stopAtDist) {
currentlyMoving = false;
}
}
I tested the above code and it was pretty accurate - it averaged 0.75% off. I hope that this is what you're looking for in your answer!
I have a video being used as a background of a mobile device to show a reactive animation for when the phone is rotated its works (sort of) but when the rotation requires it to loop from start to end or end to start it does not loop and I have no clue why as the value it's trying to be set as should work.
this is the function that subscribes to the gyroscope and updates the angle with some maths
this.gyroscope.watch(this.options)
.subscribe((orientation: GyroscopeOrientation) => {
// need delta time to correctly calculate angle per update
this.time.now = new Date().getTime();
this.time.delta = this.time.now - this.time.last;
if (this.videoLoaded) {
// convert radians/sec to degree/sec and times by deltaTime
const degree = 180 / Math.PI * orientation.z;
this.targetAngle -= (this.time.delta / 1000) * degree;
// lerp target angle no clipping applied
this.angle = (1 - .1) * this.angle + .1 * this.targetAngle;
// convert lerped angle into clipped 0-360
let displayAngle = this.angle % 360;
if (displayAngle < 0) { displayAngle = 360 + displayAngle; }
// convert angle to time of video round to tenths dec
this.frame = Math.round((displayAngle * this.axeVideo.duration / 360) * 10) / 10;
// set video time
this.axeVideo.currentTime = this.frame;
} else {
// clear angle as gyro spits out large values at first
this.angle = this.targetAngle = 0;
}
// update last time for deltaTime calc
this.time.last = this.time.now;
});
this is all correct maths and logically works however when testing the video is going to either edge and locking up no matter how many rotations the phone does and to "unlock" it i must rotate the phone back the same amount.
Screen Capture of issue recording is a little laggy but normally super smooth (hence why i use this solution).
It is the ion-range that has [(ngModel)] on it overriding the set time.
After I saw a video from the Coding Train on youtube about fractal trees, I tried to build one myself. Which worked great and I played with some variables to get different results.
I would love to see the tree moving like it got hit by some wind. I tried different approaches like rotating the branches a little bit or some minor physics implementations but that failed miserably.
So my question is: What would be the best approach to render a fractal tree and give it some sort of "life" like little shakes from wind.
Is there some sort of good reference ?
Do I need physics ? -> If so where do I have to look ?
If not -> How could I fake such an effect?
I am glad about every help I can get.
Source for the idea: https://www.youtube.com/watch?v=0jjeOYMjmDU
Tree in the wind.
The following are some short points re bending a branch in the wind. As the whole solution is complex you will have to get what you can from the code.
The code includes a seeded random number functions. A random recursive tree renderer, a poor quality random wind generator, all drawn on canvas using an animation loop.
Wind
To apply wind you need to add a bending force to each branch that is proportional to the angle of the branch to the wind.
So if you have a branch in direction dir and a wind in the direct wDir the amount of scaling the bending force needs is
var x = Math.cos(dir); // get normalize vector for the branch
var y = Math.sin(dir);
var wx = Math.cos(wDir); // get normalize vector for the wind
var wy = Math.sin(wDir);
var forceScale = x * wy - y * wx;
The length of the branch also effects the amount of force to include that you lengthen the vector of the branch to be proportional to its length
var x = Math.cos(dir) * length; // get normalize vector for the branch
var y = Math.sin(dir) * length;
var wx = Math.cos(wDir); // get normalize vector for the wind
var wy = Math.sin(wDir);
var forceScale = x * wy - y * wx;
Using this method ensures that the branches do not bend into the wind.
There is also the thickness of the branch, this is a polynomial relationship related to the cross sectional area. This is unknown so is scaled to the max thickness of the tree (an approximation that assumes the tree base can not bend, but the end branches can bend a lot.)
Then the elastic force of the bent branch will have a force that moves the branch back to its normal position. This acts like a spring and is very much the same as the wind force. As the computational and memory load would start to overwhelm the CPU we can cheat and use the wind to also recoil with a little bit of springiness.
And the tree.
The tree needs to be random, yet being fractal you don't want to store each branch. So you will also need a seeded random generator that can be reset at the start of each rendering pass. The tree is rendered randomly with each iteration but because the random numbers start at the same seed each time you get the same tree.
The example
Draws random tree and wind in gusts. Wind is random so tree may not move right away.
Click tree image to reseed the random seed value for the tree.
I did not watch the video, but these things are quite standard so the recursive function should not be to far removed from what you may have. I did see the youTube cover image and it looked like the tree had no randomness. To remove randomness set the leng, ang, width min, max to be the same. eg angMin = angMax = 0.4; will remove random branch angles.
The wind strength will max out to cyclone strength (hurricane for those in the US) to see the max effect.
There are a zillion magic numbers the most important are as constants with comments.
const ctx = canvas.getContext("2d");
// click function to reseed random tree
canvas.addEventListener("click",()=> {
treeSeed = Math.random() * 10000 | 0;
treeGrow = 0.1; // regrow tree
});
/* Seeded random functions
randSeed(int) int is a seed value
randSI() random integer 0 or 1
randSI(max) random integer from 0 <= random < max
randSI(min, max) random integer from min <= random < max
randS() like Math.random
randS(max) random float 0 <= random < max
randS(min, max) random float min <= random < max
*/
const seededRandom = (() => {
var seed = 1;
return { max : 2576436549074795, reseed (s) { seed = s }, random () { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
/* TREE CONSTANTS all angles in radians and lengths/widths are in pixels */
const angMin = 0.01; // branching angle min and max
const angMax= 0.6;
const lengMin = 0.8; // length reduction per branch min and max
const lengMax = 0.9;
const widthMin = 0.6; // width reduction per branch min max
const widthMax = 0.8;
const trunkMin = 6; // trunk base width ,min and max
const trunkMax = 10;
const maxBranches = 200; // max number of branches
const windX = -1; // wind direction vector
const windY = 0;
const bendability = 8; // greater than 1. The bigger this number the more the thin branches will bend first
// the canvas height you are scaling up or down to a different sized canvas
const windStrength = 0.01 * bendability * ((200 ** 2) / (canvas.height ** 2)); // wind strength
// The wind is used to simulate branch spring back the following
// two number control that. Note that the sum on the two following should
// be below 1 or the function will oscillate out of control
const windBendRectSpeed = 0.01; // how fast the tree reacts to the wing
const windBranchSpring = 0.98; // the amount and speed of the branch spring back
const gustProbability = 1/100; // how often there is a gust of wind
// Values trying to have a gusty wind effect
var windCycle = 0;
var windCycleGust = 0;
var windCycleGustTime = 0;
var currentWind = 0;
var windFollow = 0;
var windActual = 0;
// The seed value for the tree
var treeSeed = Math.random() * 10000 | 0;
// Vars to build tree with
var branchCount = 0;
var maxTrunk = 0;
var treeGrow = 0.01; // this value should not be zero
// Starts a new tree
function drawTree(seed) {
branchCount = 0;
treeGrow += 0.02;
randSeed(seed);
maxTrunk = randSI(trunkMin, trunkMax);
drawBranch(canvas.width / 2, canvas.height, -Math.PI / 2, canvas.height / 5, maxTrunk);
}
// Recusive tree
function drawBranch(x, y, dir, leng, width) {
branchCount ++;
const treeGrowVal = (treeGrow > 1 ? 1 : treeGrow < 0.1 ? 0.1 : treeGrow) ** 2 ;
// get wind bending force and turn branch direction
const xx = Math.cos(dir) * leng * treeGrowVal;
const yy = Math.sin(dir) * leng * treeGrowVal;
const windSideWayForce = windX * yy - windY * xx;
// change direction by addition based on the wind and scale to
// (windStrength * windActual) the wind force
// ((1 - width / maxTrunk) ** bendability) the amount of bending due to branch thickness
// windSideWayForce the force depending on the branch angle to the wind
dir += (windStrength * windActual) * ((1 - width / maxTrunk) ** bendability) * windSideWayForce;
// draw the branch
ctx.lineWidth = width;
ctx.beginPath();
ctx.lineTo(x, y);
x += Math.cos(dir) * leng * treeGrowVal;
y += Math.sin(dir) * leng * treeGrowVal;
ctx.lineTo(x, y);
ctx.stroke();
// if not to thing, not to short and not to many
if (branchCount < maxBranches && leng > 5 && width > 1) {
// to stop recusive bias (due to branch count limit)
// random select direction of first recusive bend
const rDir = randSI() ? -1 : 1;
treeGrow -= 0.2;
drawBranch(
x,y,
dir + randS(angMin, angMax) * rDir,
leng * randS(lengMin, lengMax),
width * randS(widthMin, widthMax)
);
// bend next branch the other way
drawBranch(
x,y,
dir + randS(angMin, angMax) * -rDir,
leng * randS(lengMin, lengMax),
width * randS(widthMin, widthMax)
);
treeGrow += 0.2;
}
}
// Dont ask this is a quick try at wind gusts
// Wind needs a spacial component this sim does not include that.
function updateWind() {
if (Math.random() < gustProbability) {
windCycleGustTime = (Math.random() * 10 + 1) | 0;
}
if (windCycleGustTime > 0) {
windCycleGustTime --;
windCycleGust += windCycleGustTime/20
} else {
windCycleGust *= 0.99;
}
windCycle += windCycleGust;
currentWind = (Math.sin(windCycle/40) * 0.6 + 0.4) ** 2;
currentWind = currentWind < 0 ? 0 : currentWind;
windFollow += (currentWind - windActual) * windBendRectSpeed;
windFollow *= windBranchSpring ;
windActual += windFollow;
}
requestAnimationFrame(update);
function update() {
ctx.clearRect(0,0,canvas.width,canvas.height);
updateWind();
drawTree(treeSeed);
requestAnimationFrame(update);
}
body {
font-family : arial;
}
<canvas id="canvas" width="250" heigth="200"></canvas>
Click tree to reseed.
Update
I just noticed that the wind and branch length are absolute thus drawing the tree on a larger canvas will create a bending force too great and the branches will bend past the wind vector.
To scale the sim up either do it via a global scale transform, or reduce the windStrength constant to some smaller value. You will have to play with the value as its a 2nd order polynomial relation. My guess is multiply it with (200 ** 2) / (canvas.height ** 2) where the 200 is the size of the example canvas and the canvas.height is the new canvas size.
I have added the calculations to the example, but its not perfect so when you scale you will have to change the value windStrength (the first number) down or up if the bending is too far or not enough.