Canvas, animate a shape along square path using sine wave - javascript

I'm trying to make a simple shape animate along a square path based on a set 'radius'. Atm I'm using a sine wave to set the position over time, so its basically animating along a circular path.
Is there a way using maths to alter the sine wave to make the animation square. I know there are other ways to do this, but I'd be interested to learn the math behind it.
I have an example fiddle:
t = new Date().getTime()
r = 25
x = (r * Math.cos t * 0.005)
y = (r * Math.sin t * 0.005)
http://jsfiddle.net/Z5hrM/1/

We can do better than just circle or square! The equations for x and y can be generalized with an exponent D:
x = (r^D * cos(theta))^(1/D) and y = (r^D * sin(theta))^(1/D)
When D = 1 you have the familiar equations that give a circle. When D = 0.5 you get a diamond, when D < 0.5 you get pointed stars. When D > 1 you get increasingly blocky shapes, and as D -> infinity you get a square.
Give it a try with this snippet; you can type new values of D as the animation proceeds.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>animation problem</title>
<script type='text/javascript'>
function demo(){
var w = 400;
var ctx = document.getElementById("canvas").getContext("2d");
ctx.canvas.width = w;
ctx.canvas.height = w;
var r = w/4;
var theta = 0;
setInterval(function(){
ctx.canvas.width += 0; // clear the canvas
ctx.translate(w/2, w/2); // center it on (0,0)
var D = +document.getElementById("exponent").value;
var xSign = Math.cos(theta) < 0 ? -1 : 1; // Handle all quadrants this way
var ySign = Math.sin(theta) < 0 ? -1 : 1;
var x = xSign*Math.pow( Math.pow(r, D)*Math.abs(Math.cos(theta)), 1/D );
var y = ySign*Math.pow( Math.pow(r, D)*Math.abs(Math.sin(theta)), 1/D );
ctx.fillStyle = "blue";
ctx.arc( x, y, 20, 0, 6.2832, false );
ctx.fill();
theta += Math.PI/100;
}, 20);
}
</script>
</head>
<body onload='demo()'>
<input id='exponent' type=text value='1'\>
<br />
<canvas id='canvas'></canvas>
</body>
</html>

jsFiddle Demo
It actually isn't going to take much modification. Considering that the cosine represents the x coordinate, and the sin represents the y coordinate, it should be obvious that to make a square path one of these values must be set to a whole value instead of a partial value.
As a result, Math.cos t and Math.sin t will need to be regulated with a variable and a condition
xcos = Math.cos t * 0.005
ysin = Math.sin t * 0.005
if Math.abs(xcos) > Math.abs(ysin)
xcos = Math.round(xcos)
else
ysin = Math.round(ysin)
x = #cx + (radius * xcos)
y = #cy + (radius * ysin)

Your variable r should be a vector of two position (x,y) that will handle the position/increment on x and y respectively. See when you do this x = (0 * Math.cos t * 0.005) the circule just get moved from up to down. In order to get a shape behavior you need to control the vector (x and y positions) over the time and use remainder to wrap up x and y position (%).
Regards.

Related

Get coords of the intersection of the line in the plane

I have a canvas with this params:
width = 400, height = 400
and have a line passing through the point cursor[x1,y1] at an angle Q (in degree)
I need get all coords of the intersection of the line in the plane and write it to array. Now i use this equation: y - y1 = k * (x - x1)
to check all point I use this code:
var rad = Q * Math.PI/180;
for (ctrY = 0; ctrY < 400; ctrY += 1) {
for (ctrX = 0; ctrX < 400; ctrX += 1) {
if ( (ctrY - cursor.y) ===
~~(Math.tan(rad) * (ctrX - cursor.x)) ) {
z.push([ctrX, ctrY]);
}
}
}
For example when 0 < Q < 90 and cursor[x1,y1] = [200,200] z.length = 0 and it's not correct.
Where i'm wrong? Maybe there is a more convenient algorithm?
P.S. Sorry for my english
Seems you need line rastering algorithm. Consider Bresenham algorithm.
You can also look at DDA algorithm
I imagine an algorithm like this. (I only consider the case when 0 < Q < 90). First I will want to calculate the points where the line will intersect the Ox and Oy axes, considering the origin (0,0) point the upper left corner and if we imagine that the negative x and y values are respectively to the left and to the top of this point. Let x2 and y2 be the values where the line will intersect Ox and Oy. We want to calculate these values. We now have a system with 2 unknown variables (x2 and y2): Math.tan(rad) = (y1 -y2)/x1 and Math.tan(rad) = y1/(x1-x2). We can deduct these equations by drawing the line on the coordinate system and analyzing a bit. If we solve the system of equations we find something like: x2 = (x1*y1 -x1 * x1 * Math.tan(rad)/(2 * y1-x1)) and y2= y1- x1 * Math.tan(rad) (These need to be verified, I haven't double checked my calculus). A linear equation can be defined by the formula y = a*x + b and in our case a = x2 and b = y2. We can then calculate the points like this:
for (xIdx = 0; xIdx < 400; xIdx += 1) {
var ctrX = xIdx;
var ctrY = x2 * ctrX + y2 //todo: replace with the respective calculated variables x2 and y2(we could also define two functions in js) and proper rounding
z.push([ctrX, ctrY]);
}
I'm not sure if I'm 100% accurate but I hope you understand my idea.

Get unrotated coordinates from a rotated div

I have a div that is getting rotated. I actually want to know it's orginal left and top coordinates before it was rotated.
I've been looking at different formulas but i think i need someone to help me a bit.
Thanks,
<html>
<body>
<div id="test" style="width:50px;height:50px;background-color:yellow;tranform:rotate(45deg);transform-orgin:top left">
</div>
</body>
</html>
As a formula, i found this one.
function rotatedPosition(pLeft, pTop, oLeft, oTop, angle){
// 1
var x = pLeft - oLeft;
var y = pTop - oTop;
// 2
var xRot = x * Math.cos(angle) - y * Math.sin(angle);
var yRot = x * Math.sin(angle) + y * Math.cos(angle);
// 3
var pLeftRot = xRot + oLeft;
var pTopRot = yRot + oTop
return {left: pLeftRot, top: pTopRot};
}
This will calculate the rotated coordinates. I tried to modifiy it so that it would calculate the original coordinates in stead of the rotated coordinates. But that kind of math is to long ago for me.
It is using a css transfrom: rotate(45deg); tranform-origin: top left;. I'm getting this div already rotated so i don't know it's unrotated coordinates. in the end i need to get the top and left position of the div as it would appear unrotated.
Ok, that's it... store the top and left values before calling rotatedPosition function.
By the way, I would recommend you to use let and const instead of var within the function body. That way those identifiers will only exist in there.
<html>
<script>
var original_Top = -1, original_Left = -1;
function rotatedPosition(pLeft, pTop, oLeft, oTop, angle){
original_Top = pTop;
original_Left = pLeft;
// 1
let x = pLeft - oLeft;
let y = pTop - oTop;
// 2
let xRot = x * Math.cos(angle) - y * Math.sin(angle);
let yRot = x * Math.sin(angle) + y * Math.cos(angle);
// 3
let pLeftRot = xRot + oLeft;
let pTopRot = yRot + oTop
return {left: pLeftRot, top: pTopRot};
}
</script>
<body>
<div id="test" style="width:50px;height:50px;background-color:yellow">
</div>
</body>
</html>
I don't know where you call the rotatedPosition function. This is only an idea, you also can store these values calling another function.

SVG: make a gradient to fill the whole shape for every angle given

Given an angle between 0° and 90°, generate a SVG gradient that fills entire rectangle.
SVG gradients accept two control points rather than angle. Here is the code of the first square on the picture above:
<linearGradient x1="0" y1="0" x2="1" y2="0.5">
The problem is that the gradient doesn’t cover the entire square. I want to extend the gradient just enough to fill the shape entirely so the red triangle would be not visible. Here is an interactive demo (tested in Chrome, Firefox and Safari) to give you a better idea.
Solution in JavaScript:
function angleToVector(angle) {
var od = Math.sqrt(2);
var op = Math.cos(Math.abs(Math.PI/4 - angle)) * od;
var x = op * Math.cos(angle);
var y = op * Math.sin(angle);
return {x: x, y: y};
}
For angle between -180° and 180°:
function angleToPoints(angle) {
var segment = Math.floor(angle / Math.PI * 2) + 2;
var diagonal = (1/2 * segment + 1/4) * Math.PI;
var op = Math.cos(Math.abs(diagonal - angle)) * Math.sqrt(2);
var x = op * Math.cos(angle);
var y = op * Math.sin(angle);
return {
x1: x < 0 ? 1 : 0,
y1: y < 0 ? 1 : 0,
x2: x >= 0 ? x : x + 1,
y2: y >= 0 ? y : y + 1
};
}
There might be a simpler solution for this.
So your question as I understand it is this: given a rectangle (whose top left corner is the origin O = (0, 0) and whose bottom right corner is D = (w, h)) and a line l through point O at angle a (with 0° <= a <= 90°), find the point P = (x2, y2) on l such that line DP makes a right angle with l.
If you draw the diagonal of the rectangle, OD, it completes a right triangle with the right angle at P. The angle of that diagonal is atan(h/w), and if you take the absolute difference of that from a (i.e. |atan(h/w) - a|), you'll get the angle in that right triangle at point O. Then you can take the cosine of that angle to get the distance between O and P along l as a proportion of the length of OD (the hypotenuse). You can multiply out the hypotenuse, and then just multiply that by cos(a) and sin(a) to get x2 and y2, respectively.
To summarize:
|OD| = sqrt(w*w + h*h)
|OP| = cos(|atan(h/w) - a|) * |OD|
x2 = |OP| * cos(a)
y2 = |OP| * sin(a)

Skew, size and rotate a rectangle to fit triangle perfectly

I'm trying to make half of a rectangle - devided diagonally - to fit inside a triangle.
Rotation works well, so does sizing of the rectangle. But once I try to skew it, it all gets messed up. Basically I want to simulate a 3D surface.
That means I have to find the angle of abc, where b is the center point. And then apply this angle as a skew to the rectangle. But for some reason that doesn't work as intended.
Here is a simple illustration of what I want to accomplish:
You will probably understand more once you take a look at the fiddle: http://jsfiddle.net/p7g7Y/11/
EDIT: Got the width right at least: http://jsfiddle.net/p7g7Y/12/
The piece of code you need to look at is at line 63 - 95.
Try comment out the transform, and you will see that rotation and size works well.
function triangle(a, b, c){
context.save();
//Draw the triangle
context.beginPath();
context.moveTo(a[0], a[1]);
context.lineTo(b[0], b[1]);
context.lineTo(c[0], c[1]);
context.lineTo(a[0], a[1]);
context.closePath();
context.stroke();
//Lets find the distance between a and b to set height of the image
var imgHeight = lineDistance(a, b);
//And the width b to c
var imgWidth = lineDistance(b, c);
//Now we gotta skew it acording to the rad between ba and bc
var skewAngle = find_angle(a,c,b); //Find angle and make it rad
//Find the angle of b to a line
var theta = Math.atan2(a[1] - b[1], a[0] - b[0]);
context.translate(a[0], a[1]); //Set origin of rotation
context.rotate(theta + 1.57079633); //Had to rotate it some more 1.57079633 = 90deg
context.transform(1, skewAngle, 0, 1, 0, 0);
context.rect( 0, 0, imgHeight, imgWidth);
context.stroke();
context.restore();
}
If anything is unclear, please ask! I would love some help on this!
It's easier if you solve the problem more generally: find a, b, c, d, e and f so that
// (x0, y0) maps to (x_0, y_0)
a*x0 + b*y0 + c = x_0
d*x0 + e*y0 + f = y_0
// (x1, y1) maps to (x_1, y_1)
a*x1 + b*y1 + c = x_1
d*x1 + e*y1 + f = y_1
// (x2, y2) maps to (x_2, y_2)
a*x2 + b*y2 + c = x_2
d*x2 + e*y2 + f = y_2
This 6x6 linear system is composed of two independent 3x3 linear systems:
a*x0 + b*y0 + c = x_0
a*x1 + b*y1 + c = x_1
a*x2 + b*y2 + c = x_2
d*x0 + e*y0 + f = y_0
d*x1 + e*y1 + f = y_1
d*x2 + e*y2 + f = y_2
Solving them gives you the 6 numbers to pass to setTransform to map any three points to other three points.
delta = x0*y1 + y0*x2 + x1*y2 - y1*x2 - y0*x1 - x0*y2
delta_a = x_0*y1 + y0*x_2 + x_1*y2 - y1*x_2 - y0*x_1 - x_0*y2
delta_b = x0*x_1 + x_0*x2 + x1*x_2 - x_1*x2 - x_0*x1 - x0*x_2
delta_c = x0*y1*x_2 + y0*x_1*x2 + x_0*x1*y2 - x_0*y1*x2 - y0*x1*x_2 - x0*x_1*y2
delta_d = y_0*y1 + y0*y_2 + y_1*y2 - y1*y_2 - y0*y_1 - y_0*y2
delta_e = x0*y_1 + y_0*x2 + x1*y_2 - y_1*x2 - y_0*x1 - x0*y_2
delta_f = x0*y1*y_2 + y0*y_1*x2 + y_0*x1*y2 - y_0*y1*x2 - y0*x1*y_2 - x0*y_1*y2
a = delta_a / delta
b = delta_b / delta
c = delta_c / delta
d = delta_d / delta
e = delta_e / delta
f = delta_f / delta
For a full description of 3d texture mapping using 2d canvas context see this more detailed answer.
Here’s how to calculate transforms necessary to fit a rectangle to a triangle:
Translate to the “pivot point” of your triangle – point B.
Rotate by the angle of side BC.
Skew in the X direction by the angle of corner B.
So, first translate:
// transform translate = pt2
var translate = pt2;
Then rotate:
// transform rotation = angleBC (based on slope of BC)
var rotation = Math.atan2((pt3.y-pt2.y),(pt3.x-pt2.x));
Finally skewX:
// transform skewX, based on angleB
var skewX = Math.tan(angleB-Math.PI/2);
Here’s how to get angleB for use in skewX:
// calculate segment lengths
var AB = Math.sqrt(Math.pow(pt2.x-pt1.x,2)+ Math.pow(pt2.y-pt1.y,2));
var BC = Math.sqrt(Math.pow(pt2.x-pt3.x,2)+ Math.pow(pt2.y-pt3.y,2));
var AC = Math.sqrt(Math.pow(pt3.x-pt1.x,2)+ Math.pow(pt3.y-pt1.y,2));
// calculate angleB using law of cosines
var angleB = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
You’ll also need the width and height of the rectangle to draw:
// rectangle height = triangle altitude
var rectHeight = AB * Math.sin(angleB);
// rectangle width = triangle BC
var rectWidth = BC;
A small “gotcha”:
Your translate point is B, but rectangles are drawn starting at top-left.
This means you must offset your rectangle vertically by the rectHeight:
ctx.rect(0, -rectHeight, rectWidth, rectHeight);
Also, not really a “gotcha”, but more of a natual limitation:
The angle at corner B must be <180.
So, if your triangle “inverts”, I you’ll have to compensate by flipping points A and C.
Interesting project you have there!
Would you share a bit when you’re done?
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/KKELu/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var pt1={x:100,y:100};
var pt2={x:150,y:225};
var pt3={x:250,y:150};
drawTriangle();
drawRectangle();
function drawRectangle(){
// calc transform info
var info=analyzeTriangle();
ctx.save();
ctx.translate(info.translate.x,info.translate.y);
ctx.rotate(info.rotation);
ctx.transform(1,0,info.skewX,1,0,0);
ctx.beginPath();
// since rects origin is top left, must offset y by -height
ctx.rect(0,-info.rectHeight,info.rectWidth,info.rectHeight);
ctx.strokeStyle="purple";
ctx.stroke();
ctx.restore();
}
function drawTriangle(){
ctx.beginPath();
ctx.strokeStyle="blue";
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.lineTo(pt3.x,pt3.y);
ctx.closePath();
ctx.stroke();
ctx.fillStyle="rgba(255,255,0,0.10)";
ctx.fill();
}
function analyzeTriangle(){
// segment lengths
var AB = Math.sqrt(Math.pow(pt2.x-pt1.x,2)+ Math.pow(pt2.y-pt1.y,2));
var BC = Math.sqrt(Math.pow(pt2.x-pt3.x,2)+ Math.pow(pt2.y-pt3.y,2));
var AC = Math.sqrt(Math.pow(pt3.x-pt1.x,2)+ Math.pow(pt3.y-pt1.y,2));
// angleB = using law of cosines
var angleB = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
// transform translate = pt2
var translate = pt2;
// transform rotation = angleBC (based on slope of BC)
var rotation = Math.atan2((pt3.y-pt2.y),(pt3.x-pt2.x));
// transform skewX, based on angleB
var skewX = Math.tan(angleB-Math.PI/2);
// rectangle height = triangle altitude
var rectHeight = AB * Math.sin(angleB);
// rectangle width = triangle BC
var rectWidth = BC;
return({
translate:translate,
rotation:rotation,
skewX:skewX,
rectHeight:rectHeight,
rectWidth:rectWidth
});
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>

Moving A Point Along Ellipse

I have created an ellipse on my canvas and now I need to draw three lines stemming from the origin. As an example let's say the first line is 90 degrees (vertical) so the point is (0, 10). I need the other two lines to be x pixels away from the point in both directions.
I'm sure I didn't describe this well enough but basically what I am trying to do is from a point on a known ellipse, find another point x distance away that lies on the ellipse.
I have tried looking for an arc of an ellipse but nothing seems to fit what I am looking for.
For an ellipse:
x = a cos(t)
y = b sin(t)
So:
x/a= cos(t)
t = acos(x/a)
y = b sin(acos(x/a))
Plug in your values of a, b, and x and you get y.
See https://www.mathopenref.com/coordparamellipse.html
Rather crudely:
var a=120;
var b=70;
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var xCentre=c.width / 2;
var yCentre=c.height / 2;
// draw axes
cxt.strokeStyle='blue';
cxt.beginPath();
cxt.moveTo(0, yCentre);
cxt.lineTo(xCentre*2, yCentre);
cxt.stroke();
cxt.beginPath();
cxt.moveTo(xCentre, 0);
cxt.lineTo(xCentre, yCentre*2);
cxt.stroke();
// draw ellipse
cxt.strokeStyle='black';
cxt.beginPath();
for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
xPos = xCentre - (a * Math.cos(i));
yPos = yCentre + (b * Math.sin(i));
if (i == 0) {
cxt.moveTo(xPos, yPos);
} else {
cxt.lineTo(xPos, yPos);
}
}
cxt.lineWidth = 2;
cxt.strokeStyle = "#232323";
cxt.stroke();
cxt.closePath();
// draw lines with x=+/- 40
var deltaX=40;
var y1=b*Math.sin(Math.acos(deltaX/a));
cxt.strokeStyle='red';
cxt.beginPath();
cxt.moveTo(xCentre+deltaX, yCentre-y1);
cxt.lineTo(xCentre, yCentre);
cxt.lineTo(xCentre-deltaX, yCentre-y1);
cxt.stroke();
<html>
<head><title>Ellipse</title></head>
<body>
<canvas id="myCanvas" style="position: absolute;" width="400" height="200"></canvas>
</body>
</html>
(Using https://www.scienceprimer.com/draw-oval-html5-canvas as a basis as I've never used HTML canvas before.)
Andrew Morton's answer is adequate, but you can it with one square root instead of a sin and an acos.
Suppose you have an ellipse centered at the origin, with a radius along the X-axis of a and a radius along the Y-axis of b. The equation of this ellipse is
x2/a2 + y2/b2 = 1.
Solving this for y gives
y = ± b sqrt(1 - x2/a2)
You can choose whichever sign is appropriate. Based on your post, you want the positive square root.
Translating to Javascript:
function yForEllipse(a, b, x) {
return b * Math.sqrt(1 - x*x / a * a);
}

Categories

Resources