Ratio smoothing with delta time in JavaScript is buggy - javascript

Smoothing normally works as follows:
Some variable v has a target t. Every tick, v makes progress toward t based on a ratio. For example, a ratio of 2:3 or ⅗ will use this formula:
v=(v*3+t*2)/5;
Here you multiply the variables to make the ratio, and then divide it all up to make the smoothing ratio. t can be modified as you want and v will handle smoothing and output.
So you have delta time. You pick a target framerate and go from there. I figured it out on my own so I might be wrong about some terminology but whatever. In one frame, the smoothing will have progressed by ⅖ which means you have a remainder of ⅗. In two frames, you will have a remainder of ⅗×⅗. And I thought: how could I do that fractionally, though? And then I was like "oh wait, you can just make powers of these." So I just got the idea to use insertRatioHere**deltaTime and multiply it by the difference between the current and target state. At first it was something like
v=t-v+(v*(0.6**delta))
but that didn't really work, so I tried a lot of different formulas with small changes, none of which worked. Here's my current test code:
<HTML>
<head>
<title>delta</title>
</head>
<body>
<canvas id=myCanvas width=400 height=300 style="background-color:white;"></canvas>
<script>
var ctx=myCanvas.getContext("2d");
var time={first:new Date(),now:new Date(),delta:new Date()};
time.since=time.now-time.first;
time.delta=time.delta.getTime();
const frame=1000/60;
var lag=100;//change between 0, frame, 100 and other values when desired
var boxA={xPos:200,yPos:150,xAccel:0,yAccel:0,xSpeed:0,ySpeed:0,speed:0};
var boxB={xPos:200,yPos:150,xSpeed:0,ySpeed:0,speed:0};
var keysDown=[];
var keyBind={upA:{key:87,on:false},leftA:{key:65,on:false},
downA:{key:83,on:false},rightA:{key:68,on:false},
upB:{key:38,on:false},leftB:{key:37,on:false},
downB:{key:40,on:false},rightB:{key:39,on:false}};
var varLog=[];
addEventListener("keydown",whenKeyDown);
addEventListener("keyup",whenKeyUp);
tick=setInterval(tickBoxA,lag);
tick=setInterval(tickBoxB,frame);
function tickBoxA()
{time.now=new Date();
time.delta=time.now-time.first-time.since;
time.since=time.now-time.first;
if(keysDown.includes(keyBind.upA.key)){
if(!keyBind.upA.on){keyBind.upA.on=true}
}else{keyBind.upA.on=false}
if(keysDown.includes(keyBind.leftA.key)){
if(!keyBind.leftA.on){keyBind.leftA.on=true}
}else{keyBind.leftA.on=false}
if(keysDown.includes(keyBind.downA.key)){
if(!keyBind.downA.on){keyBind.downA.on=true}
}else{keyBind.downA.on=false}
if(keysDown.includes(keyBind.rightA.key)){
if(!keyBind.rightA.on){keyBind.rightA.on=true}
}else{keyBind.rightA.on=false}
boxA.xAccel=(keyBind.rightA.on-keyBind.leftA.on)*3*((keyBind.upA.on-keyBind.downA.on!=0)?Math.sin(45):1);
boxA.xSpeed=boxA.xAccel-boxA.xSpeed+(boxA.xSpeed*0.9**(time.delta/frame));
//boxA.xSpeed=boxA.xAccel+boxA.xSpeed-(boxA.xSpeed*0.9**(time.delta/frame));
//boxA.xSpeed=boxA.xAccel+(boxA.xSpeed-boxA.xAccel*0.9**(time.delta/frame));
//boxA.xSpeed=boxA.xAccel+(boxA.xAccel-boxA.xSpeed*0.9**(time.delta/frame));
boxA.yAccel=(keyBind.downA.on-keyBind.upA.on)*3*((keyBind.rightA.on-keyBind.leftA.on!=0)?Math.sin(45):1);
boxA.ySpeed=boxA.yAccel-boxA.ySpeed+(boxA.ySpeed*0.9**(time.delta/frame));
boxA.speed=Math.sqrt((boxA.xSpeed**2)+(boxA.ySpeed**2));
boxA.xPos+=boxA.xSpeed*(time.delta/frame);
boxA.yPos+=boxA.ySpeed*(time.delta/frame);
render()}
function tickBoxB()
{if(keysDown.includes(keyBind.upB.key)){
if(!keyBind.upB.on){keyBind.upB.on=true}
}else{keyBind.upB.on=false}
if(keysDown.includes(keyBind.leftB.key)){
if(!keyBind.leftB.on){keyBind.leftB.on=true}
}else{keyBind.leftB.on=false}
if(keysDown.includes(keyBind.downB.key)){
if(!keyBind.downB.on){keyBind.downB.on=true}
}else{keyBind.downB.on=false}
if(keysDown.includes(keyBind.rightB.key)){
if(!keyBind.rightB.on){keyBind.rightB.on=true}
}else{keyBind.rightB.on=false}
boxB.xSpeed=(boxB.xSpeed*9+(keyBind.rightB.on-keyBind.leftB.on)*3*((keyBind.upB.on-keyBind.downB.on!=0)?Math.sin(45):1))/10;
boxB.ySpeed=(boxB.ySpeed*9+(keyBind.downB.on-keyBind.upB.on)*3*((keyBind.rightB.on-keyBind.leftB.on!=0)?Math.sin(45):1))/10;
boxB.xPos+=boxB.xSpeed;
boxB.yPos+=boxB.ySpeed;
render()}
function whenKeyDown(keyDownEvent){
check=keyDownEvent.keyCode;
if(keysDown.findIndex(isSame)==-1)
{keysDown.push(keyDownEvent.keyCode)}}
function whenKeyUp(keyUpEvent){
check=keyUpEvent.keyCode;
keysDown.splice(keysDown.findIndex(isSame),1)}
function isSame(num){return num==check}
function render()
{ctx.clearRect(0,0,400,300);
ctx.fillStyle="#F00";
ctx.fillRect(boxA.xPos,boxA.yPos,20,20);
ctx.fillStyle="#0F0";
ctx.fillRect(boxB.xPos,boxB.yPos,20,20);
varLog.length=0;
varLog.push("Time: "+time.since);
varLog.push("Delta: "+time.delta);
varLog.push("Frame accuracy: "+time.delta/frame);
varLog.push("Standard framerate is 60FPS");
varLog.push("Red box target X: "+boxA.xAccel);
varLog.push("Red box current X: "+boxA.xSpeed);
varLog.push("Red box target Y: "+boxA.yAccel);
varLog.push("Red box current Y: "+boxA.ySpeed);
varLog.push("Red box speed: "+boxA.speed);
varLog.push("The red box is trying to handle smooth");
varLog.push("acceleration in delta time");
varLog.push("and yeah I messed up");
varLog.push("Green box speed X: "+boxB.xSpeed);
varLog.push("Green box speed Y: "+boxB.ySpeed);
varLog.push("Green box speed: "+boxB.speed);
varLog.push("The green box works as desired at 60FPS");
varLog.push("Keys pressed: "+keysDown);
varLog.push("Red=WASD, green=↑←↓→");
ctx.font="15px Courier";
ctx.fillStyle="#000";
for(run=0;run<varLog.length;run++){ctx.fillText(varLog[run],0,(run+1)*15)}}
</script>
</body>
</html>

Related

fabricjs - creating endless animation loop without lags

I am trying to create an endless animation loop of rotation with FabricJS. It works like this: the rotate function gets animation details (object to animate, duration of one animation loop, starting angle of rotation) as parameters, and invokes inner function rotateInner to launch the animation itself. rotateInner rotates given object using fabric.util.animate with linear easing function (to make rotation speed constant) to run one animation loop and to invoke itself after the loop ends, which results in an infinite animation loop.
The problem is that rotateInner lags every time between animation loops when it invokes itself through fabric.util.animate.
For example, I use animation loop with duration = 1 second and rotation angle = 180 degrees. In that case, animation makes lag every 1 second after rotating by 180 degrees.
I've found a solution to make lag occur N times less. Instead of rotating by 180 degrees for 1 second, I rotate object by 180*N degrees for N seconds. If I pick enough big N, the user will meet lag potentially never. But only potentially.
What are reasons of lags and is it possible to remove lags between animation loops completely? Or, perhaps, I went wrong way, and I should use something completely different to create endless animation loop?
Here's example which demonstrates lag (on jsfiddle). (The rotate function has additional argument - multiplier. It's N I've just wrote about above) Left rectangle rotates 180 degrees per 800 milliseconds, and the right rectangle rotates 180*10 degrees per 800*10 milliseconds, and thus the right one rotates quicker than the left one.
And, if you don't want to use jsfiddle or if example became inaccessible for some reason, here's the example itself in code:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.min.js"></script>
<style>canvas {border: 5px dotted black;}</style>
<title>FabricJS animation loop lag</title>
</head>
<body>
<canvas id="mycanvas" width="700" height="300"></canvas>
<script>
var canvas = new fabric.StaticCanvas('mycanvas');
var rectCreationOptions = {
left: canvas.getWidth()/4,
top: canvas.getHeight()/2,
fill: '#0bb',
width: canvas.getWidth()/5,
height: canvas.getHeight()/5,
originX: 'center',
originY: 'center'
};
// creating left rectangle:
var rect1 = new fabric.Rect(rectCreationOptions);
rectCreationOptions.left *= 3;
// creating right rectangle:
var rect2 = new fabric.Rect(rectCreationOptions);
canvas.add(rect1);
canvas.add(rect2);
function rotate(animatedObject, duration = 1000, multiplier = 1, startAngle = 0) {
duration *= multiplier;
var addAngle = 180 * multiplier;
(function rotateInner(startAngle) {
fabric.util.animate({
startValue: startAngle,
endValue: startAngle+addAngle,
duration: duration,
// linear easing function:
easing: function(t, b, c, d) { return c*t/d + b; },
onChange: function (value) {
animatedObject.angle = value;
canvas.renderAll();
},
onComplete: function() {
rotateInner(startAngle+addAngle);
}
});
})(startAngle);
}
rotate(rect1, 800, 1); // lags every 800 ms
rotate(rect2, 800, 10); // lags every 800*10 ms
</script>
</body>
</html>
I will be thankful for help.
Please, don't throw rotten tomatoes at me if I made tiny mistake. This is my first question on stackoverflow. :)
Best wishes everyone.

Paper.js Subraster Selecting Wrong Area

I'm working in a Paper.js project where we're essentially doing image editing. There is one large Raster. I'm attempting to use the getSubRaster method to copy a section of the image (raster) that the user can then move around.
After the raster to edit is loaded, selectArea is called to register these listeners:
var selectArea = function() {
if(paper.project != null) {
var startDragPoint;
paper.project.layers[0].on('mousedown', function(event) { // TODO should be layer 0 in long run? // Capture start of drag selection
if(event.event.ctrlKey && event.event.altKey) {
startDragPoint = new paper.Point(event.point.x + imageWidth/2, (event.point.y + imageHeight/2));
//topLeftPointOfSelectionRectangleCanvasCoordinates = new paper.Point(event.point.x, event.point.y);
}
});
paper.project.layers[0].on('mouseup', function(event) { // TODO should be layer 0 in long run? // Capture end of drag selection
if(event.event.ctrlKey && event.event.altKey) {
var endDragPoint = new paper.Point(event.point.x + imageWidth/2, event.point.y + imageHeight/2);
// Don't know which corner user started dragging from, aggregate the data we have into the leftmost and topmost points for constructing a rectangle
var leftmostX;
if(startDragPoint.x < endDragPoint.x) {
leftmostX = startDragPoint.x;
} else {
leftmostX = endDragPoint.x;
}
var width = Math.abs(startDragPoint.x - endDragPoint.x);
var topmostY;
if(startDragPoint.y < endDragPoint.y) {
topmostY = startDragPoint.y;
} else {
topmostY = endDragPoint.y;
}
var height = Math.abs(startDragPoint.y - endDragPoint.y);
var boundingRectangle = new paper.Rectangle(leftmostX, topmostY, width, height);
console.log(boundingRectangle);
console.log(paper.view.center);
var selectedArea = raster.getSubRaster(boundingRectangle);
var selectedAreaAsDataUrl = selectedArea.toDataURL();
var subImage = new Image(width, height);
subImage.src = selectedAreaAsDataUrl;
subImage.onload = function(event) {
var subRaster = new paper.Raster(subImage);
// Make movable
subRaster.onMouseEnter = movableEvents.showSelected;
subRaster.onMouseDrag = movableEvents.dragItem;
subRaster.onMouseLeave = movableEvents.hideSelected;
};
}
});
}
};
The methods are triggered at the right time and the selection box seems to be the right size. It does indeed render a new raster for me that I can move around, but the contents of the raster are not what I selected. They are close to what I selected but not what I selected. Selecting different areas does not seem to yield different results. The content of the generated subraster always seems to be down and to the right of the actual selection.
Note that as I build the points for the bounding selection rectangle I do some translations. This is because of differences in coordinate systems. The coordinate system where I've drawn the rectangle selection has (0,0) in the center of the image and x increases rightward and y increases downward. But for getSubRaster, we are required to provide the pixel coordinates, per the documentation, which start at (0,0) at the top left of the image and increase going rightward and downward. Consequently, as I build the points, I translate the points to the raster/pixel coordinates by adding imageWidth/2 and imageHeight/2`.
So why does this code select the wrong area? Thanks in advance.
EDIT:
Unfortunately I can't share the image I'm working with because it is sensitive company data. But here is some metadata:
Image Width: 4250 pixels
Image Height: 5500 pixels
Canvas Width: 591 pixels
Canvas Height: 766 pixels
My canvas size varies by the size of the browser window, but those are the parameters I've been testing in. I don't think the canvas dimensions are particularly relevant because I'm doing everything in terms of image pixels. When I capture the event.point.x and event.point.y to the best of my knowledge these are image scaled coordinates, but from a different origin - the center rather than the top left. Unfortunately I can't find any documentation on this. Observe how the coordinates work in this sketch.
I've also been working on a sketch to illustrate the problem of this question. To use it, hold Ctrl + Alt and drag a box on the image. This should trigger some logging data and attempt to get a subraster, but I get an operation insecure error, which I think is because of security settings in the image request header. Using the base 64 string instead of the URL doesn't give the security error, but doesn't do anything. Using that string in the sketch produces a super long URL I can't paste here. But to get that you can download the image (or any image) and convert it here, and put that as the img.src.
The problem is that the mouse events all return points relative to 0, 0 of the canvas. And getSubRaster expects the coordinates to be relative to the 0, 0 of the raster item it is extracting from.
The adjustment needs to be eventpoint - raster.bounds.topLeft. It doesn't really have anything to do with the image width or height. You want to adjust the event points so they are relative to 0, 0 of the raster, and 0, 0 is raster.bounds.topLeft.
When you adjust the event points by 1/2 the image size that causes event points to be offset incorrectly. For the Mona Lisa example, the raster size (image size) is w: 320, h: 491; divided by two they are w: 160, h: 245.5. But bounds.topLeft of the image (when I ran my sketch) was x: 252.5, y: 155.5.
Here's a sketch that shows it working. I've added a little red square highlighting the selected area just to make it easier to compare when it's done. I also didn't include the toDataURL logic as that creates the security issues you mentioned.
Here you go: Sketch
Here's code I put into an HTML file; I noticed that the sketch I put together links to a previous version of the code that doesn't completely work.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rasters</title>
<script src="./vendor/jquery-2.1.3.js"></script>
<script src="./vendor/paper-0.9.25.js"></script>
</head>
<body>
<main>
<h3>Raster Bug</h3>
<div>
<canvas id="canvas"></canvas>
</div>
<div id="position">
</div>
</main>
<script>
// initialization code
$(function() {
// setup paper
$("#canvas").attr({width: 600, height: 600});
var canvas = document.getElementById("canvas");
paper.setup(canvas);
// show a border to make range of canvas clear
var border = new paper.Path.Rectangle({
rectangle: {point: [0, 0], size: paper.view.size},
strokeColor: 'black',
strokeWidth: 2
});
var tool = new paper.Tool();
// setup mouse position tracking
tool.on('mousemove', function(e) {
$("#position").text("mouse: " + e.point);
});
// load the image from a dataURL to avoid CORS issues
var raster = new paper.Raster(dataURL);
raster.position = paper.view.center;
var lt = raster.bounds.topLeft;
var startDrag, endDrag;
console.log('rb', raster.bounds);
console.log('lt', lt);
// setup mouse handling
tool.on('mousedown', function(e) {
startDrag = new paper.Point(e.point);
console.log('sd', startDrag);
});
tool.on('mousedrag', function(e) {
var show = new paper.Path.Rectangle({
from: startDrag,
to: e.point,
strokeColor: 'red',
strokeWidth: 1
});
show.removeOn({
drag: true,
up: true
});
});
tool.on('mouseup', function(e) {
endDrag = new paper.Point(e.point);
console.log('ed', endDrag);
var bounds = new paper.Rectangle({
from: startDrag.subtract(lt),
to: endDrag.subtract(lt)
});
console.log('bounds', bounds);
var sub = raster.getSubRaster(bounds);
sub.bringToFront();
var subData = sub.toDataURL();
sub.remove();
var subRaster = new paper.Raster(subData);
subRaster.position = paper.view.center;
});
});
var dataURL = ; // insert data or real URL here
</script>
</body>
</html>

jQuery "slime menu" - pixel perfect rounding

I've successfully programmed my jQuery slime menu, and now it works beautiful. My only problem is that I move the arrow and the ending circle (the icon) of the slime separately, so if you move the cursor too away from the starting point, it will not connect to the slime precisely. It must be a rounding issue. Here's an image of what I'm talking about:
And here's the fiddle I'm working on:
http://jsfiddle.net/41Lcj653/4/
EDIT (Working): http://jsfiddle.net/41Lcj653/7/
Can anyone help with this issue? I use CSS transformations, and JS for update the CSS rules in each step.
The JS part of the code:
$(function(){
$(window).mousemove(function(e){
moveslime(e);
});
});
function moveslime(e){
var rad = $('.arrow').height() / 2;
var ofs = $('.arrow').offset();
var mPos = {x: e.pageX-25-rad, y: e.pageY-25-rad};
var dir = Math.round( -(Math.atan2(mPos.x, mPos.y)) / (Math.PI/180) );
$('.arrow').css('transform','rotate('+dir+'deg)');
$('.arrow').css('border-width','0 25px '+pdist(0, 0, mPos.x, mPos.y)+'px 25px');
$('.bubble').css('left', mPos.x+'px').css('top', mPos.y+'px');
}
function pdist(x1,y1,x2,y2) {
return Math.sqrt(Math.pow(x1-x2,2)+Math.pow(y1-y2,2));
}
the reason you see this 'not precisely connected' is that rotate goes in 360 steps (the 360 degrees of a circle). when you are near the center the effect can be neglected, but the more you get away from the center (increasing circle circumference!) the visible gap between the degrees increases.
Maybe try this with svg?
actually you can use float numbers a degree .. so dont round the deg!
https://jsfiddle.net/41Lcj653/5/
// without math.round
var dir = -(Math.atan2(mPos.x, mPos.y)) / (Math.PI/180);
Some jQuery method like height rounds decimals. You could try this:
$(".arrow")[0].getBoundingClientRect().height
More information: https://developer.mozilla.org/es/docs/Web/API/Element/getBoundingClientRect

Constrain jquery draggable to stay only on the path of a circle

So Im trying to make something where the user is able to drag planets along their orbits and it will continuously update the other planets. Ideally I would like this to work with ellipses too.
So far I can drag an image node with jquery and check/change the coordinates, but i cannot update the position reliably while the user is dragging the object. I know there is an axis and a rectangle containment for draggable but this doesn't really help me.
I have a site for calculating planetary orbits http://www.stjarnhimlen.se/comp/ppcomp.html and a formula i think should help me if i can figure out how to constrain the draggable object with coordinate checks Calculating point on a circle's circumference from angle in C#?
But it seems like there should be an easier way to have a user drag a sphere along a circular track while it updates coords for other spheres
here's what i have so far. It's not much
http://jsfiddle.net/b3247uc2/2/
Html
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.2/jquery-ui.js"></script>
Js
var $newPosX = 100,
$newPosY = 100;
//create image node
var x = document.createElement("IMG");
x.src = "http://upload.wikimedia.org/wikipedia/commons/a/a4/Sol_de_Mayo_Bandera_Argentina.png";
x.width = 100;
x.height = 100;
x.id = "sun";
x.hspace = 100;
x.vspace = 100;
document.body.appendChild(x);
//coords
var text = document.createTextNode($newPosX + " " + $newPosY);
document.body.appendChild(text);
//make sun draggable and update coords
$("#sun").draggable({
drag: function (event, ui) {
$newPosX = $(this).offset().left;
$newPosY = $(this).offset().top;
}
});
//0 millisecond update for displayed coords
setInterval(check, 0);
function check() {
//tries to constrain movement, doesn't work well
/*
if ($newPosX > 300) {
$("#sun").offset({
left: 200
});
}
*/
text.nodeValue = ($newPosX + " " + $newPosY);
}
Edit:
I found this and am trying to modify it to suit my purposes but have so far not had luck.
http://jsfiddle.net/7Asn6/
Ok so i got it to drag
http://jsfiddle.net/7Asn6/103/
This is pretty close to what I want but can be moved without being clicked on directly
http://jsfiddle.net/7Asn6/104/
Ok final edit on this question. This one seems to work well and have fixed my problems. I would still very much like to hear peoples implementation ideas or ideas to make it work smoother.
http://jsfiddle.net/7Asn6/106/

Plotting with HTML5 Canvas

I decided to day to embark on element and I can say so far it have been nightmare to get it work. All I want is to plot a sine graph. So after good reading I still cannot either get origins nor get it plot. Below is what I have tried (my first time ever with that tag so excuse my ignorance). What makes me wonder is the guy here have it but the codes are hard to understand for beginner like me.
HTML
<!DOCTYPE html>
<html>
<head>
<title>Graphing</title>
<link type="text/css" rel="Stylesheet" href="graph.css" />
<script type="text/JavaScript" src="graph.js" ></script>
</head>
<body>
<canvas id="surface">Canvas not Supported</canvas>
</body>
</html>
CSS
#surface
{
width:300;
height:225;
border: dotted #FF0000 1px;
}
JavScript
window.onload = function()
{
var canvas = document.getElementById("surface");
var context = canvas.getContext("2d");
arr = [0,15, 30,45,60, 90,105, 120, 135, 150, 165, 180 ];
var x=0;
var y = 0;
for(i=0; i<arr.length; i++)
{
angle = arr[i]*(Math.PI/180); //radians
sine = Math.sin(angle);
context.moveTo(x,y);
context.lineTo(angle,sine);
context.stroke();
//set current varibles for next move
x = angle;
y = sine;
}
}
Since the range of sin x is [-1,1], it will only return numbers between -1 and 1, and that means all you will be drawing is a dot on the screen.
Also I see that you have an array ranging from 0 to 180. I believe you are trying to draw the curve with x from 0 degree to 180 degree? You don't really need to do this (anyway 12 points are not enough to draw a smooth line). Just do it with a for loop, with lines being the number of fragments.
First we start off by moving the point to the left of the canvas:
context.moveTo(0, 100 /*somewhere in the middle*/); //initial point
In most cases the first point won't be in the middle. But for sine it is. (You might want to fix it later though.)
for (var i = 0; i < lines; i++) {
//draw line
}
That's the loop drawing the curve. But what should we put inside? Well you can just take the number returned by the sine function and scale it up, flip it upside down, and shift it down half the way. I do that because the coordinate system in JavaScript is 0,0 in the top left instead of in the bottom left.
var sine = Math.sin(i/scale*2)*scale;
context.lineTo(i*frag, -sine+scale);
//i * frag = the position of x scaled up
//-sine + scale = the position of y, flipped, scaled, shifted down
//i/scale*2 = random scale I put in... you might want to figure out the
// correct scale with some math
So that's it. Viola, you have successfully plotted a graph in JavaScript.
Oh yes, don't forget to actually tell it to draw it on the canvas after the for loop has done its job:
context.stroke();
The demo: http://jsfiddle.net/DerekL/hK5rC/
PS: I see that you are trying to resize the canvas using CSS. Trust me, it won't work. :) You will have to define the dimension in HTML.

Categories

Resources