How to smoothly increase a number to Y in X milliseconds exponentially - javascript

I'm making a semi-realistic 'physics' engine in node.js, if you can even call it that and I want to accelerate exponentially. E.g. from 0m/s to 4.5m/s in 2 seconds, then maybe decelerate to 0m/s in 3 seconds. Obviously for the deceleration part I can probably get away with inputting a negative number.
Here's a picture of what I'm thinking of, not sure if what I expect in the graph is the same thing as exponents.
I don't have any code, I thought I could base it off something like setInterval, but that would be linear.

You're right SetInterval can only provide with a fixed speed whereas what you need is dynamic speed.
One way is to make two arrays with an equal number of items. With first array named duration and second name speed. The variable Y will be changed by the speed for the duration corresponding to the # of the speed. See here :
var Y = 0; // The variable that is to be changed
var speed = [4.5, 0, -4.5]; // The acceleration in m/s
var time = [2, 4, 2]; // Duration corresponding to each acceleration in s
var sec = 1000; // 1 second = 1000 milliseconds
var i = 0; // A counter to remember the item number in the arrays
var dur = 0; // A variable to hold the duration for which a particular speed has currently lasted. This variable helps to validate the duration for which the Y is changed.
// The function that holds the logic
function tick() {
// Checking if duration is equal to or has crossed the user specified duration for the speed
if(dur >= time[i]*1000){
// If yes, move on to the next speed and duration
i++;
dur = 0;
if(i > speed.length-1){clearInterval(loop);} // If there are no more items in the array stop the interval
}
else {
// If not, continue increasing Y and dur
Y = Math.round(Y*1000 + (speed[i]/50)*1000)/1000 // A simple workaround to avoid the error in arthimetic calculation that occurs while using floats (decimal numbers) in JS.
console.log(Y); // This line is only for debugging purposes. IT CAN BE REMOVED
dur += sec/50;
}
}
var loop = setInterval(tick, sec/50);

Related

Get a specific percentage chance of running something?

I read some threads on the site about getting percentages of running a function, but none of them guide me how to get a specific decimal percentage of running a function.
I tried something like:
Math.floor(Math.random() * 100) + 1 + '%'
However it doesn't return decimals.
What i'd like is for it to let's say, have a 0.5% / 100% chance of running console.log("0.5%"), is there any way to do this? Thank you very much!
Math.random() returns a random number between 0 and 1.
A percentage is just a fraction out of 100. Divide by 100 to get a number between 0 and 1.
So, to get a block of code that runs a certain percentage of the time, take the desired percentage, divide it by 100, and run the code if a random number is less than it.
if( Math.random() < 0.5/100) {
/* code that runs 0.5% of the time */
}
Here is what you are asking for.
The percentageFromRandom is explained in my comment above.
The runWithProbability function calls a given function with a certain probability, when this function is called.
The logWithProbability uses the runWithProbability function, but with the custom console.log functionality as your answer question for.
The init function shows an example of usage of the function, by running it 30 times with 30 random probability. In most cases it would log the larger %'s as they are more likely to have the console.log function be called.
//convert the random value into human readable percentage
function percentageFromRandom(value, fractionalDigits = 2){
return (value*100).toFixed(fractionalDigits)+'%';
}
//take a function and probability of running it
//if the probability is met, call the function.
function runWithProbability(fn, probability){
if(probability >= 1 || Math.random() < probability){
return fn(probability);
}
return false;
}
//make a console log with a certain probability,
//log the percentage probability if called
function logWithProbability(probability){
runWithProbability(()=>
console.log(percentageFromRandom(probability))
, probability);
}
// See console logs and their probability as
// a percentage of running.
const init = () => {
for(let i = 0; i < 30; i++){
logWithProbability(Math.random());
}
}
init();
The issue with the Math.floor(Math.random()) example is that Math.floor() removes all of the fractional values of a number. To get precision to a certain fixed point, multiply by the maximum whole number wanted, then adjust to a fixed decimal.
for (var i = 0; i < 10; i++) {
var num = 10 * Math.random(); // Max 10.000...
console.log(num, num.toFixed(1) + '%') // Fix (and round) the first decimal
}

Web Audio api : How to increase continuously the volume of the sound from the beginning

In order to not chock the listener I want to play a wave but the volume should not be played at 100% ratio from early, it should go from 0% to 100% in duration of 2secondes for instance.
I tought of a setTimeout and I increase the gain by the time, but I don't know if is there any other better approch
var source = aCtx.createBufferSource();
source.buffer = buf;
var gainNode = aCtx.createGain();
gainNode.gain.value = 0
source.connect(gainNode);
gainNode.connect(aCtx.destination);
source.start(0);
setTimeout(function() {
gainNode.gain.value = 0.5
}, 1000)
setTimeout(function() {
gainNode.gain.value = 1
}, 2000)
The Web Audio API provides a library function for this.
AudioParam.linearRampToValueAtTime(value, endTime)
value
A floating point number representing the value the AudioParam will ramp to by the given time.
endTime
A double representing the exact time (in seconds) after the ramping starts that the changing of the value will stop.
So in your case use
gainNode.gain.linearRampToValueAtTime(0, 0)
gainNode.gain.linearRampToValueAtTime(1, 2)
Make it dynamic by simply using some kind of lerp function, there are many out there. The basic idea is that you get a value being interpolated between two of your starting values.
Example:
value 1 = 5
value 2 = 1
interpolation amount = 0.4 (40%)
then the result should be exactly 2.6
said function might just look like this
Math.lerp = function (value1, value2, amount) {
amount = amount < 0 ? 0 : amount;
amount = amount > 1 ? 1 : amount;
return value1 + (value2 - value1) * amount;
};
In your specific scenario you would take the min and max volume as the both values and then regulate the amount using the time that has passed. Probably with some async function call back to this function.
hope i could help.

How to control the probablity of an event given a certain number of dice rolls

I have a turn based game. I set a condition on the player that for the next X fixed number of turns he/she has a Y percentage chance of triggering some other event (say game over).
So say x = 5 and y = 80%. I need to flip a coin 5 times (maximum) and on each coin flip, modify the outcome percentage (between true and false) so that the combined/average percentage likelihood of rolling true over all 5 turns is equal to 80%.
If I make each roll 80% chance then that doesn't average to 80% over 5 turns (it is much higher). So, how to force y % (roughly) given x coin flips?
I am working in JavaScript.
Edit:
From Thiatt suggestion I implement this code below (assuming 0.16 over 5 turn would yield 80%). In practice it turns out to be close to 68%.
This seems close, but when I implement it comes out closer to 70%:
for(var k = 0; k < 20; k++){
var totalHit = 0;
for(var i = 100; i >=0; i--){
var hit = false;
for(var j = 0; j < 5; j++){
var roll = rollDice(1, 100);
hit = roll <= 20;
if(hit){
totalHit++;
break;
}
}
}
debug('Hit ' + totalHit + '/100');
}
My parsing of your question is that, using your example numbers, you want the probability that the user doesn't trigger event Y over 5 turns to be 0.2. That is, they have a 0.8 chance of triggering event Y at least once (1, 2, 3, 4, or 5 times, not zero times).
You are using a coin so there are two possible events each round. The probability of not triggering in a single round will be (1-p) where p is the desired probability you are solving for. Over five rounds you have (1-p)(1-p)(1-p)(1-p)(1-p)=(1-p)^5=0.2, solving for p gives ~0.275 or 27.5%. If you have a 27.5% chance of triggering Y at each roll, the probability of triggering Y at least once is 80%.
Simply let 80% be true:
if(Math.random()<0.8) alert("we were the lucky 80%!");
Some explanation:
Lets say we roll a dice. If we want to win on 50%, we simply say, that one does win at 1,2,3 and loose at 4,5,6.
win=RollDice()<3;
The same principle can be used with Math.random. It returns a Number between 0 and 1, so 80% would be 0.8.
P = Y/X
For 5 rolls, if each roll has 16% chance of triggering the event, there's an 80% chance that the event will be triggered over 5 rolls.
Or if you want to keep probability 80% through all the remaining rolls:
P(n) = Y/(X-n+1)
So if the first roll is false, there's an 80% chance of getting true on the remaining 4. If the first two are false, there's an 80% change of getting true on the other 3, etc.

Smoothly snap a rotatable object to degree

I have been hacking on this problem for a while and can't seem to find a solution (I am not friends with trigonometry).
I am trying to build an element that the user can "grab" and then rotate. I have it working except for one last feature I can't seem to figure out:
http://codepen.io/megakoresh/pen/WbLLzZ?editors=001
(it's long, but I am only asking about how the snapTo() function should work).
What I want is for the object snap to degrees based on the increments value. This means that if snap==true, the code should calculate the closest estimated targets to the point of release of the mouse and based on the direction of rotation smoothly rotate the object to that target rotation:
Since the object is 'grabbed', I calculate the offset at mousedown to object's current rotation, thats where it comes from, so it doesn't just snap to mouse.
So in this case the user rotates the object clockwise and releases the mouse when the objects rotation is between 90° and 45°. Since the direction (identified by the sign of angle variable) was positive, the target will be after the Current rotation.
The task is to calculate that target and then smoothly rotate the object to it.
The function I have written for it is based on by autoSpin() function (executes when spin==false), which takes a flipped time exponent multiplier delta (calculated from the time elapse since mouse was released). delta will decrease along a flipped exponent as time passes and so the angle slows down.
There is spinTo() function, please don't judge me I have a feeling it is very stupid:
function snapTo() {
var elapsed, delta;
increments = (typeof increments === 'number') ? Math.floor(increments) : 4;
var ia = 360 / increments; //increment angle - snapping points should occur "every ia degrees"
if (Math.abs(angle % ia) > 0) { //if user turned for more than 1 increment
var a = lastRot % ia; //check the distance from
if (angle > 0){ //positive direction
amplitude = 50; //if snapping is on, force amplitude
target = (lastRot - a) + ia;
}
if (angle < 0){ //negative direction
amplitude = -50;
target = lastRot - a;
}
} else { //cancel the rotation
target = rotation;
}
elapsed = Date.now() - timestamp; //time passed since mouse was released
delta = -amplitude * Math.exp(-elapsed / timeConstant); //the greater the time from mouse release, the smaller this value
if (delta > 0.5 || delta < -0.5) { //while delta is decreasing...
rotate(target - delta - offset);
snapFrame = requestAnimationFrame(snapTo); //keep rotation
} else {
rotate(target - offset); //when enough time passes (based on timeConstant), make one final rotation and stop
}
}
What am I doing wrong?
*le sigh
Ok well I had to figure it out on my own. If anyone wants to do this kind of thing, here is one way of doing it:
function snapTo() {
var elapsed, ease, diff;
increments = (typeof increments === 'number') ? Math.floor(increments) : 4;
var ia = 360 / increments; //increment angle - this is how large each increment will be
var a = last % ia; //check the distance from point of mouse release to the previous increment
if (movement>0) {
elapsed = Date.now() - timestamp; //time passed since mouse was released
ease = 1 - Math.exp((-3*elapsed)/timeConstant); //your easing function formula goes here
if(a > ia/2) { //if halfway through the increment...
target = (last - a) + ia; //target next increment
}
else { //else revert to previous increment to the point of mouse release
target = last - a;
}
diff = target - last; //difference between target and rotation at time of mouse release
if(ease < 0.95){ //this depends on your easing formula
rotate(last+diff * ease);
requestAnimationFrame(snapTo); //don't forget to RAF
} else { //and for your final frame...
rotate(last+diff); //make it snappy :P
}
console.log(last); //traces of debugging were found in this derelict question...
}
}
So in here
elapsed is the time passed since the mouse was released
increments is the argument provided by user: how many snapping points will be there
ia is the computed increment in degrees: 360/increments
last is the angle recorded on mouseup event - "Current rotation" in the diagram. It includes the offset (in my code, its just a "snapshot" of rotation at point of release, that is then also reversed in sign because in DOM the coordinate system is y-flipped and I don't like working with that).
a is how much bigger the last is than the previous nearest increment point.
diff is the "target" on the diagram - difference between final rotation and last
ease is just the value that changes according to your easing function, based on which you either continue calling RAF or finish the animation.
timeConstant is time in milliseconds for how long the animation will take. You can have this or not, depends on your easing formula
A good resource to read in general: Understanding Easing Functions
Also Desmos graphing calculator is quite good for developing easing formulas.
Oh and: in case anyone is wondering, it doesn't seem possible to pass any kind of non-time-related arguments to RAF callback functions. Seems to break it if I do. So the only solution is to define the increments value elsewhere in the code.
Codepen of the working function
Anyone got a better solution, I am all eyes.

Javascript wait for one animation to finish before the next one begins

I have a Javascript function (Not jQuery) that animates a box opening or closing. The problem is that I close the box, run some code that changes the content, and then reopen it.
Now the "problem" is that the rest of the code is too fast, and so it never even manages to close, let alone reopen. I could make the animation not be allowed to run again internally unless the last one was finished, but this would limit it if I say, were to want to run it twice on two different objects.
So what's the best method to prevent this? My thought was possibly a timeout that says to wait before running the animation, but that seems hacky, an I wasn't sure if there was a better solution?
Thanks.
function animate(boxID, step, limit, speed){
// Add timeout property to animate function/object if it doesn't exist already
if(animate.timeout == undefined) animate.timeout = 0;
// Clear the current timeout
clearTimeout(animate.timeout);
// Initialize box and current box height
var box = document.getElementById(boxID);
var h = box.clientHeight;
// Check if we want the step needs to be changed to pos/neg based on which direction is wanted to be going
if(h < limit && step < 0 || // Positive
h > limit && step > 0){ // Negative
step *= -1;
}
// If the step is positive, then we need to be below the limit, or if negative, then greater than the limit
if((step > 0 && h <= limit - step) || (step < 0 && h >= limit - step)){
// Set new height
box.style.height = h + step + "px";
// Start new timeout
animate.timeout = setTimeout(function(){ animate(boxID, step, limit, speed, 1); }, speed);
}
else{
box.style.height = limit + "px"; // Set to the exact height
}
}
You could achieve this with a callback. Your animate function gets a plus parameter, a function to call when the animation is ready:
function animate(boxID, step, limit, speed, onReady){
When the animation is done, you call it:
else{
box.style.height = limit + "px"; // Set to the exact height
if (onReady) { onReady(); }
}
You also want to forward the callback to the timeout call:
setTimeout(function(){ animate(boxID, step, limit, speed, 1, onReady); }, speed);
So, you can call the function for multiple boxes like this:
animate(box1_id, close_step, close_limit, close_speed, function () {
// now box1 is closed, put something in. then:
animate(box1_id, open_step, open_limit, open_speed, null);
});
// then the same for box2, etc…
This way box1 and box2 will close simultaneously, and only reopen after the thing have been put inside.
Also, you can't store the timer on the function, because now it's running on multiple boxes. So you may store it on the boxes, or a separate place instead. For example create an object outside of the function and put all the boxes' timers in that:
var timeouts = {};
timeouts[box1_id] = setTimeout(…);

Categories