I was wondering whether I made a math mistake in my particle collision simulation found here.
The particles don't seem to separate properly during collision resolution. Here is a code snippet from the function which separates particles and changes their velocities:
//particle 1
var pi = particles[i];
//particle 2
var pj = particles[j];
//particle 1 to particle 2
var pimpj = pi.mesh.position.clone().sub(pj.mesh.position);
//particle 2 to particle 1
var pjmpi = pj.mesh.position.clone().sub(pi.mesh.position);
//if colliding (radius is 20)
if(pimpj.length() < 20 && pimpj.length() != 0)
{
//reflect velocity off of 1->2
pi.velocity = pi.velocity.reflect(pimpj.clone().normalize()).multiplyScalar(restitution);
//reflect velocity off of 2->1
pj.velocity = pj.velocity.reflect(pjmpi.clone().normalize()).multiplyScalar(restitution);
//move particle 1 to appropiate location based off of distance in between
var pip = pi.velocity.clone().normalize().multiplyScalar(20-pimpj.length());
//move particle 2
var pjp = pj.velocity.clone().normalize().multiplyScalar(20-pimpj.length());
pi.mesh.position.add(pip);
pj.mesh.position.add(pjp);
}
I have tried reversing pimpj with pjmpi while changing pi.velocity, but to no effect.
note: I am using three.js
Firstly, the particle collisions you seem to be looking for are Elastic collisions, for which there is maths covering the calculation of the velocities after a collision.
The collision is simplest in the centre of momentum frame, so if you first calculate that frame V = (m1v1 + m2v2)/(m1+m2), then you can subtract it from both particles, do a simple symmetric collision and add the frame velocity back on afterwards.
From there, calculate the velocities using the formulae in the 2 & 3d section of that page.
Specific points on your code:
pimpj = -pjmpi, so you don't need both
A collision occurs when the paths between the last frame and this frame got too close; if you only check the distance at each frame you will have problems where particles fly through each other at high speed, and that you have to keep shifting their positions because they are already overlapping when you detect the collision.
Ideally calculate the positions on impact and use those to redirect them.
For speed, only calculate pimpj.clone().normalize() once, and store it - you're not changing this direction unit vector later, so you don't need to keep recalculating it, or calculating pjmpi-derived equivalents (see #1)
I think part of the problem is that your model of collision is overly simplistic. The basic rules for collision are conservation of momentum and conservation of energy. Looking at the 1D case. If both particles have the same mass m and u1 and u2 are you velocities beforehand and v1, v2 are the velocities after then
m u1 + m2 u2 = m v1 + m v2
conservation of energy for a perfect collision gives
1/2 m u1.u1 + 1/2 m u2.u2 = 1/2 m v1.v1 + 1/2 m v2.v2.
These two equations have the solution v1 = u2, v2 = u1. That is the velocities switch. In particular if one velocity is zero before collision then after collision the other velocity becomes zero after the collision. You can see this happen when using a newton's cradle.
In 2D we can resolve in a coordinate system with 1 direction along to the plane of contact and one direction perpendicular to it. The force only occurs in the perpendicular direction, this means the velocities along the pane don't change but the perpendicular velocities switch.
var u = pjmpi.clone().normalize();
var v = new THREE.Vector3(u.y,-u.x,0);
// resolve in two directions
var piu = pi.velocity.dot(u);
var piv = pi.velocity.dot(v);
pi.velocity = new THREE.Vector3(
pju * u.x + piv * v.x,
pju * u.y + piv * v.y,
0);
pj.velocity = new THREE.Vector3(
piu * u.x + pjv * v.x,
piu * u.y + pjv * v.y,
0);
That works for a perfectly elastic collision. See Wikipedia elastic collision which has an nice illustration. The formula at the end simplifies a bit if you take the masses equal.
For an partially inelastic collision with restitution R we can look at the end of http://www.plasmaphysics.org.uk/collision2d.htm. Now take the velocity of the center of mass w. This will not change after the collision because the total momentum is conserved. So w=(u1+u2)/2 = (v1+v2)/2. Take the velocities relative to this center of mass
v1' = v1-w, v2' = v2-w, apply the restitution v1'' = R (v1'-w), v2'' = R(v2'-w) and add the velocity of the center of mass.
v1''' = R(v1-v2)/2 + (v1+v2)/2
v2''' = R(v1-v2)/2 + (v1+v2)/2
Also see wikipedia Inelastic collision which has the same formula in 1D.
This translate to code as
var u = pjmpi.clone().normalize();
var v = new THREE.Vector3(u.y,-u.x,0);
// resolve in two directions
var piu = pi.velocity.dot(u);
var piv = pi.velocity.dot(v);
var pju = pj.velocity.dot(u);
var pjv = pj.velocity.dot(v);
// velocities after collision
var v1x = pju * u.x + piv * v.x;
var v1y = pju * u.y + piv * v.y;
var v2x = piu * u.x + pjv * v.x;
var v2y = piu * u.y + pjv * v.y;
// vel center of mass
var wx = (v1x+v2x)/2;
var wy = (v1y+v2y)/2;
// difference
var dx = (v1x-v2x)/2;
var dy = (v1y-v2y)/2;
// final velocities
pi.velocity = new THREE.Vector3(
wx + restitution * dx,
wy + restitution * dy,
0);
pj.velocity = new THREE.Vector3(
wx - restitution * dx,
wy - restitution * dy,
0);
// We can print the KE and momentum before and after to check
console.log("KE before ",
pi.velocity.lengthSq()+pj.velocity.lengthSq());
console.log("M before ",
pi.velocity.x+pj.velocity.x ,
pi.velocity.y+pj.velocity.y);
console.log("KE after",v1x*v1x+v1y*v1y + v2x*v2x + v2y*v2y);
console.log("M after ", v1x+v2x, v1y+v2y);
console.log("KE rest",
pi.velocity.lengthSq()+pj.velocity.lengthSq());
console.log("M rest ",
pi.velocity.x+pj.velocity.x ,
pi.velocity.y+pj.velocity.y);
This can simplify nicely. Start by taking the mean and half the difference of the two particles. Reflect the difference and apply the restitution
var len = pjmpi.length();
// unit vector normal to plane of collision
var nx = pjmpi.x / len;
var ny = pjmpi.y / len;
// unit vector tangent to plane of collision
var tx = -ny;
var ty = nx;
// center of mass
var wx = (pi.velocity.x+pj.velocity.x)/2;
var wy = (pi.velocity.y+pj.velocity.y)/2;
// half difference
var dx = (pi.velocity.x-pj.velocity.x)/2;
var dy = (pi.velocity.y-pj.velocity.y)/2;
// resolve in two directions
var a = dx * nx + dy * ny;
var b = dx * tx + dy * ty;
// reflect difference in normal
var cx = -a * nx + b * tx;
var cy = -a * ny + b * ty;
// apply restitution and add back center of mass
pi.velocity.set(
wx + restitution * cx,
wy + restitution * cy,
0);
pj.velocity.set(
wx - restitution * cx,
wy - restitution * cy,
0);
I've used THREE as little as possible to avoid creating too many objects.
I've saved this as a fiddle at http://jsfiddle.net/SalixAlba/8axnL59k/. I've reduced number of points, and removed gravity to make things a bit simpler to see.
Besides the very good points in the other answers, you only want to do the velocity changes if the particles move towards each other. This is the case of the derivative of the distance is negative, which can be transformed to the scalar product of pi-pj and vi-vj being negative.
Without that you may enter an infinite loop where the velocities get reflected back and forth while the distance stays below the critical radius.
Related
I'm working on a canvas-based animation, and I'm trying to get a 3D effect in a 2D canvas.
So far, things are going well! I've got my "orbiting line of triangles" working very well:
var c = document.createElement('canvas');
c.width = c.height = 100;
document.body.appendChild(c);
var ctx = c.getContext("2d");
function Triangles() {
this.rotation = {
x: Math.random()*Math.PI*2,
y: Math.random()*Math.PI*2,
z: Math.random()*Math.PI*2
};
/* Uncomment this for testing perspective...
this.rotation = {
x: Math.PI/2,
y: 0,
z: 0
};
*/
}
Triangles.prototype.draw = function(t) {
this.rotation.z += t/1000;
var i, points;
for( i=0; i<15; i++) {
points = [
this.computeRotation(Math.cos(0.25*i),-Math.sin(0.25*i),0),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),-0.1),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),0.1)
];
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(50+40*points[0][0],50+40*points[0][1]);
ctx.lineTo(50+40*points[1][0],50+40*points[1][1]);
ctx.lineTo(50+40*points[2][0],50+40*points[2][1]);
ctx.closePath();
ctx.fill();
}
};
Triangles.prototype.computeRotation = function(x,y,z) {
var rz, ry, rx;
rz = [
Math.cos(this.rotation.z) * x - Math.sin(this.rotation.z) * y,
Math.sin(this.rotation.z) * x + Math.cos(this.rotation.z) * y,
z
];
ry = [
Math.cos(this.rotation.y) * rz[0] + Math.sin(this.rotation.y) * rz[2],
rz[1],
-Math.sin(this.rotation.y) * rz[0] + Math.cos(this.rotation.y) * rz[2]
];
rx = [
ry[0],
Math.cos(this.rotation.x) * ry[1] - Math.sin(this.rotation.x) * ry[2],
Math.sin(this.rotation.x) * ry[1] + Math.cos(this.rotation.x) * ry[2]
];
return rx;
};
var tri = new Triangles();
requestAnimationFrame(function(start) {
function step(t) {
var delta = t-start;
ctx.clearRect(0,0,100,100)
tri.draw(delta);
start = t;
requestAnimationFrame(step);
}
step(start);
});
As you can see it's using rotation matrices for calculating the position of the points after their rotation, and I'm using this to draw the triangles using the output x and y coordinates.
I want to take this a step further by using the z coordinate and adding perspective to this animation, which will make the triangles slightly bigger when in the foreground, and smaller when in the background. However, I'm not sure how to go about doing this.
I guess this is more of a maths question than a programming one, sorry about that!
Define a focal length to control the amount of perspective. The greater the value the less the amount of perspective. Then
var fl = 200; // focal length;
var px = 100; // point in 3D space
var py = 200;
var pz = 500;
Then to get the screen X,Y
var sx = (px * fl) / pz;
var sy = (py * fl) / pz;
The resulting point is relative to the center of the veiw so you need to center it to the canvas.
sx += canvas.width/2;
sy += canvas.height/2;
That is a point.
It assumes that the point being viewed is in front of the view and further than the focal length from the focal point.
I've managed to figure out a basic solution, but I'm sure there's better ones, so if you have a more complete answer feel free to add it! But for now...
Since the coordinate system is already based around the origin with the viewpoint directly on the Z axis looking at the (x,y) plane, it's actually sufficient to just multiply the (x,y) coordinates by a value proportional to z. For example, x * (z+2)/2 will do just fine in this case
There's bound to be a more proper, general solution though!
I want to construct a circle of nested squares like this:
In the moment, I am programming in JavaScript/HTML5 canvas. This is my code:
<html>
<head>
<title>Circle of squares</title>
<script type="text/javascript">
var r = 150, u = 20, nests = 200; //radius in pixels, circumference in squares, nests in squares
var w = r; //any number != 0
function getNewW()
{
if(u < 3)
alert("Error: u < 3 (" + u + " < 3)!");
var tangents = new Array(new Array(0, w/2), new Array(Math.sin((1/u*360)*(Math.PI/180))*(w/2), -Math.cos((1/u*360)*(Math.PI/180))*(w/2)));
var sta = new Array(new Array(r, 0), new Array(Math.cos((1/u*360)*(Math.PI/180))*r, Math.sin((1/u*360)*(Math.PI/180))*r));
var end = new Array(new Array(sta[0][0]+tangents[0][0], sta[0][1]+tangents[0][1]), new Array(sta[1][0]+tangents[1][0], sta[1][1]+tangents[1][1]));
var pts = new Array(sta[0], end[0], sta[1], end[1]);
var intersect = new Array(((pts[0][0]*pts[1][1]-pts[0][1]*pts[1][0])*(pts[2][0]-pts[3][0]) - (pts[0][0]-pts[1][0])*(pts[2][0]*pts[3][1]-pts[2][1]*pts[3][0])) / ((pts[0][0]-pts[1][0])*(pts[2][1]-pts[3][1]) - (pts[0][1]-pts[1][1])*(pts[2][0]-pts[3][0])), ((pts[0][0]*pts[1][1]-pts[0][1]*pts[1][0])*(pts[2][1]-pts[3][1]) - (pts[0][1]-pts[1][1])*(pts[2][0]*pts[3][1]-pts[2][1]*pts[3][0])) / ((pts[0][0]-pts[1][0])*(pts[2][1]-pts[3][1]) - (pts[0][1]-pts[1][1])*(pts[2][0]-pts[3][0]))); //Formula from http://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
//distTo0 should be equal to distTo1
var distTo0 = Math.sqrt(Math.pow(sta[0][0]-intersect[0], 2) + Math.pow(sta[0][1]-intersect[1], 2));
var distTo1 = Math.sqrt(Math.pow(sta[1][0]-intersect[0], 2) + Math.pow(sta[1][1]-intersect[1], 2));
if(Math.round(distTo0*100)/100 != Math.round(distTo1*100)/100)
alert("Error: distTo0 != distTo1 (" + distTo0 + " != " + distTo1 + ")!");
return distTo0*2;
}
function start()
{
var canvas = document.getElementById("outputCanvas");
canvas.setAttribute("width", 600);
canvas.setAttribute("height", 600);
if(canvas.getContext)
{
var ctx = canvas.getContext("2d");
ctx.translate(300, 300);
w = getNewW();
for(var i=0; i<u; i++)
{
ctx.rotate((1/u*360)*(Math.PI/180));
ctx.fillRect(r, -w/2, w, w);
}
for(var j=1; j<nests; j++)
{
var oldr = r;
var temp1 = 1/(10*j+1);
while(r+w > oldr) //This is the while-loop that makes the program slow
{
r -= temp1;
w = getNewW();
}
if(r < 0) //When the radius gets smaller than 0, the center is reached -> no new squares have to be drawn
break;
var temp2 = (1/u*360)*(Math.PI/180);
for(var i=0; i<u; i++)
{
ctx.rotate(temp2);
ctx.fillRect(r, -w/2, w, w);
}
}
}
}
</script>
</head>
<body id="main" onload="start()">
<canvas style="border:1px #000000 solid;" width="0" height="0" id="outputCanvas">Canvas not supported...</canvas>
<div id="info"> </div>
</body>
</html>
But because I don't have a formula for the solution, I use a while-loop to get closer and closer to the solution (until it has reached zero because of float-inaccuracy), that's why it's quite slow.
So, what formula can be used to calculate the width of the next square inside the (thought) circle and, if necessary, how could the code be optimized elsewhere?
Near the center of the circle, where the squares are small enough, you can approximate the length of the side (w) by the arc length - that is, how long one uth of the inner circle would be if you drew it as an actual circle. That's just the angle in radians (2 π/u) times the radius of the circle that goes through the inner corners of the square. Since you have r varying in your code, I'll call the specific radius value under consideration at a single moment r2; that makes the arc length this:
w_approx = (2 * Math.PI / u) * r2
But for most of the squares in your picture, the difference between that and the actual value of w is too great; if you use that as the side length, you'll get overlapping squares. Fortunately, we can calculate the true value of w directly, too; it just requires a little trigonometry.
If you draw lines from the inner corners of the square to the center of the circle, those two lines plus the inner side of the square form a triangle. We know how long those two lines we just drew are; they're equal to the inner radius. We don't know how long the third side is - that's the value of w we're looking for - but we do know the angle opposite it. Those three pieces of information are enough to calculate w.
Here's a picture to show what I'm talking about:
The angle at the center of the circle, labeled α (alpha) in the picture, is just one uth of a full circle, which is 2 π /u radians (or 360/u degrees, but the trig functions all expect radians):
alpha = 2 * Math.PI / u
The other two angles of the triangle are equal (they have to be, because they're opposite sides that are of equal length), so they're both labeled β. Since the three angles of a triangle always add up to π radians (or 180º), we can calculate β; it's equal to (π - α)/2 radians:
beta = (Math.PI - alpha)/2
By the Law of Sines, if you divide the length of any side of any triangle by the sine of the angle opposite that side, the result is the same no matter which of the three sides you picked. That tells us that w/sin α must be the same as r2/sin β. Solving that equation for w gets us this:
w = r2 * Math.sin(alpha) / Math.sin(beta)
Solution is quite easy :
What are the parameters ?
• The start radius of your circle.
• The end radius of your circle.
• The number of square per circle.
Then what do you need to compute ?
• The rotation to be performed between two circles : easy ,that's just a full rotation divided by the number of square per circle :
var angle = 2 * Math.PI / squaresPerCircle;
• The size of each square, given the current radius. Easy also : compute the circumference of the current circle (2*PI*radius), then the size of one square is approximately this circumference divided by the number of squares (since you want to fill the circle) :
squareSize = 2 * Math.PI * currentRadius / squaresPerCircle;
approximation is good enough even for like 10 squares per circles.
(
Otherwise the 'real' way to get the height when you have radius and angle is done with :
squareSize = 2 * currentRadius * Math.tan(angle/2);
)
Snippet :
// parameters
var startRadius = 5;
var maxRadius = 200;
var squaresPerCircle = 20;
function start() {
// boilerplate
var canvas = document.getElementById("outputCanvas");
var ctx = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 600;
//
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
var currentRadius = startRadius;
var angle = 2 * Math.PI / squaresPerCircle;
// loop on each ring
do {
squareSize = 2 * Math.PI * currentRadius / squaresPerCircle;
// squareSize = 2 * currentRadius * Math.tan(angle/2);
ctx.save();
// loop on every square of a single ring
for (var cIndex = 0; cIndex < squaresPerCircle; cIndex++) {
ctx.fillRect(currentRadius, -squareSize / 2,
squareSize, squareSize);
ctx.rotate(angle);
};
ctx.restore();
currentRadius += squareSize;
} while (currentRadius < maxRadius);
ctx.restore();
}
onload = start;
<canvas style="border:1px #000000 solid;" width="0" height="0" id="outputCanvas">Canvas not supported...</canvas>
I was trying to do a perspective grid on my canvas and I've changed the function from another website with this result:
function keystoneAndDisplayImage(ctx, img, x, y, pixelHeight, scalingFactor) {
var h = img.height,
w = img.width,
numSlices = Math.abs(pixelHeight),
sliceHeight = h / numSlices,
polarity = (pixelHeight > 0) ? 1 : -1,
heightScale = Math.abs(pixelHeight) / h,
widthScale = (1 - scalingFactor) / numSlices;
for(var n = 0; n < numSlices; n++) {
var sy = sliceHeight * n,
sx = 0,
sHeight = sliceHeight,
sWidth = w;
var dy = y + (sliceHeight * n * heightScale * polarity),
dx = x + ((w * widthScale * n) / 2),
dHeight = sliceHeight * heightScale,
dWidth = w * (1 - (widthScale * n));
ctx.drawImage(img, sx, sy, sWidth, sHeight,
dx, dy, dWidth, dHeight);
}
}
It creates almost-good perspective grid, but it isn't scaling the Height, so every square has got the same height. Here's a working jsFiddle and how it should look like, just below the canvas. I can't think of any math formula to distort the height in proportion to the "perspective distance" (top).
I hope you understand. Sorry for language errors. Any help would be greatly appreciatedRegards
There is sadly no proper way besides using a 3D approach. But luckily it is not so complicated.
The following will produce a grid that is rotatable by the X axis (as in your picture) so we only need to focus on that axis.
To understand what goes on: We define the grid in Cartesian coordinate space. Fancy word for saying we are defining our points as vectors and not absolute coordinates. That is to say one grid cell can go from 0,0 to 1,1 instead of for example 10,20 to 45, 45 just to take some numbers.
At the projection stage we project these Cartesian coordinates into our screen coordinates.
The result will be like this:
ONLINE DEMO
Ok, lets dive into it - first we set up some variables that we need for projection etc:
fov = 512, /// Field of view kind of the lense, smaller values = spheric
viewDist = 22, /// view distance, higher values = further away
w = ez.width / 2, /// center of screen
h = ez.height / 2,
angle = -27, /// grid angle
i, p1, p2, /// counter and two points (corners)
grid = 10; /// grid size in Cartesian
To adjust the grid we don't adjust the loops (see below) but alter the fov and viewDist as well as modifying the grid to increase or decrease the number of cells.
Lets say you want a more extreme view - by setting fov to 128 and viewDist to 5 you will get this result using the same grid and angle:
The "magic" function doing all the math is as follows:
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180; /// convert angle into radians
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca; /// convert y value as we are rotating
rz = y * sa; /// only around x. Z will also change
/// Project the new coords into screen coords
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
And that's it. Worth to mention is that it is the combination of the new Y and Z that makes the lines smaller at the top (at this angle).
Now we can create a grid in Cartesian space like this and rotate those points directly into screen coordinate space:
/// create vertical lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]); //from easyCanvasJS, see demo
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]);
}
Also notice that position 0,0 is center of screen. This is why we use negative values to get out on the left side or upwards. You can see that the two center lines are straight lines.
And that's all there is to it. To color a cell you simply select the Cartesian coordinate and then convert it by calling rotateX() and you will have the coordinates you need for the corners.
For example - a random cell number is picked (between -10 and 10 on both X and Y axis):
c1 = rotateX(cx, cy); /// upper left corner
c2 = rotateX(cx + 1, cy); /// upper right corner
c3 = rotateX(cx + 1, cy + 1); /// bottom right corner
c4 = rotateX(cx, cy + 1); /// bottom left corner
/// draw a polygon between the points
ctx.beginPath();
ctx.moveTo(c1[0], c1[1]);
ctx.lineTo(c2[0], c2[1]);
ctx.lineTo(c3[0], c3[1]);
ctx.lineTo(c4[0], c4[1]);
ctx.closePath();
/// fill the polygon
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fill();
An animated version that can help see what goes on.
I'm trying to create a html canvas where the user can define a start- and endpoint, between the start and endpoint I want to draw a waved line, I'm doing this by drawing bezierCurveTo.
a sample:
the code I use to draw this is the following:
var wave = new Kinetic.Shape({
drawFunc: function (canvas) {
var ctx = canvas.getContext();
ctx.beginPath();
ctx.moveTo(50, 50);
var waveCount = 0;
var controlPoint1X = 55;
var controlPoint2X = 60;
var endPointX = 65;
while(waveCount < 10) {
ctx.bezierCurveTo(controlPoint1X, 35, controlPoint2X, 65, endPointX, 50);
controlPoint1X += 20;
controlPoint2X += 20;
endPointX += 20;
waveCount++;
}
ctx.stroke(_this);
},
stroke: '#000000',
strokeWidth: 2
});
I can make this work as long as only the x or only the y coordinate changes. Now I want to be able to create a waved line like shown above but with a different x,y coordinate. For example startpoint x: 50 y: 50 and endpoint x: 100 y: 100. I know I have to calculate the controlpoints, but I can't find out what formula I have to use. Can someone help me out?
Let's simulate a circle and sinewave on a straight line. For a semi-circle, each "period" consists of two segments, with segment one being:
cDist = 4/3 * amplitude
(we know this from http://pomax.github.com/bezierinfo/#circles_cubic)
S = (x1, 0),
C1 = (x1, cDist)
C2 = (x2, cDist)
E = (x2, 0)
and segment two being:
S = (x2, 0),
C1 = (x2, -cDist)
C2 = (x3, -cDist)
E = (x3, 0)
For a sine wave, the control points are almost the same; the y coordinate stays at the same height, but we need to shift the x coordinates so that the shape has the corrected angle at the start and end points (for a circle they're vertical, for a sine wave they're diagonal):
S = (x1, 0),
C1 = (x1 + cDist/2, cDist)
C2 = (x2 - cDist/2, cDist)
E = (x2, 0)
and segment two is:
S = (x2, 0),
C1 = (x2+cDist, -cDist)
C2 = (x3-cDist, -cDist)
E = (x3, 0)
I put up a demonstrator of this at: http://jsfiddle.net/qcUyC/6
If you want these lines to be at a fixed angle, my advice is: rotate your context. Don't actually change your coordinates. Just use context.rotate(...) and you're done. See http://jsfiddle.net/qcUyC/7
But, if you absolutely need coordinates that aren't just drawn in the right place, but have coordinates that represent a real angled line, then start with your angle:
angle = some value you picked, in radians (somewhere between 0 and 2*pi)
with that angle, we can place our points:
dx = some fixed value we pick
dy = some fixed value we pick
ox = the x-offset w.r.t. 0 for the first coordinate in our line
oy = the y-offset w.r.t. 0 for the first coordinate in our line
x1 = ox
y1 = oy
x2 = (dx * cos(angle) - dy * sin(angle)) + ox
y2 = (dx * sin(angle) + dy * cos(angle)) + oy
x3 = (2*dx * cos(angle) - 2*dy * sin(angle)) + ox
y3 = (2*dx * sin(angle) + 2*dy * cos(angle)) + oy
...
xn = ((n-1)*dx * cos(angle) - (n-1)*dy * sin(angle)) + ox
yn = ((n-1)*dx * sin(angle) + (n-1)*dy * cos(angle)) + oy
you then have to treat your control points as vectors relative to the start point in your segments, so C1' = C1-S, and C2' = C2-S, and then you rotate those with the same transformation. You then add those vectors back up to your starting point and you now have the correctly rotated control point.
That said, don't do that. Let the canvas2d API do the rotation for you and just draw straight lines. It makes life so much easier.
I have a javascript Matrix class for affine transforms, and a function to set the rotation to an absolute number of radians, around a given center:
this.setRotation = function (radians, center) {
var cos = Math.cos(radians);
var sin = Math.sin(radians);
this.a = cos;
this.b = sin;
this.c = -sin;
this.d = cos;
this.tx += center.x - center.x * cos + center.y * sin;
this.ty += center.y - center.x * sin - center.y * cos;
}
I'm trying to rotate around the center of the object itself, so I'm passing in a center point of half the object's width, and half its height, so for a 100 x 100 object I'm passing in 50, 50.
If my object starts from a rotation of zero, this works fine:
... but if I rotate the shape again, or start with a rotation of other than zero, the tx and ty values end up wrong:
What's wrong with my formula above? Setting the rotation seems to be accurate, but not the tx and ty.
I have seen a few other questions on this subject, in particular this one, but nothing that has helped.
Update To add some numbers to this:
If I begin with a 100x100 rectangle, positioned at 100,100, then my initial matrix is: {Matrix: [a:1, b:0, c:0, d:1, tx:100, ty:100]}
To rotate this clockwise 45 degrees, I feed the above function 0.7853981633974483 (45 degrees in radians), and the center: {Point: [x:50, y: 50]}
This produces the following matrix:
{Matrix: [a:0.7071067812, b:0.7071067812, c:-0.7071067812, d:0.7071067812, tx:150, ty:79.28932188]} which is exactly right.
But if I then start with that matrix, and try to return it to zero, by feeding the function arguments of 0 and {Point: [x:70.71067812000001, y: 70.71067812000001]} (the center of the new, rotated shape), then the output is {Matrix: [a:1, b:0, c:0, d:1, tx:150, ty:79.28932188]}, which is the correct rotation but not the correct translation. I'm expecting to get {Matrix: [a:1, b:0, c:0, d:1, tx:100, ty:100]}
I've tried several other variants of the function, including using the center of the rectangle in the parent coordinate space 150,150 instead of the local center of 50,50, and replacing += with = as suggested below, but nothing seems to help. If I comment out the tx calculation then my shape rotates beautifully around it's origin, so the issue must be either with the tx/ty calculation, or with the center point that I'm passing in.
Does anyone have any further ideas?
Apart from possibly an incorrect sign (although that depends on the direction of your coordinate axes), I think the problem is simply that you're using += instead of = when you fill in the tx and ty portions of your affine matrix.
My problem was to do with getting the object back to the origin - removing the top left coordinates as well as half the height and width before rotating, then adding them back again.
I found it easier to use a rotate function than a setRotate function, and ended up with the following code:
// Matrix class functions:
this.rotate = function (radians) {
var cos = parseFloat(Math.cos(radians).toFixed(10));
var sin = parseFloat(Math.sin(radians).toFixed(10));
a = this.a,
b = this.b,
c = this.c,
d = this.d,
tx = this.tx,
ty = this.ty;
this.a = a * cos - b * sin;
this.b = a * sin + b * cos;
this.c = c * cos - d * sin;
this.d = c * sin + d * cos;
this.tx = tx * cos - ty * sin;
this.ty = tx * sin + ty * cos;
}
this.setRotation = function (radians) {
var rotation = this.rotation();
this.rotate(radians - rotation);
}
this.translate = function (tx, ty) {
this.tx += tx;
this.ty += ty;
}
// Called from outside the class
var transformedBounds = pe.Model.ReadTransformedBounds(selection[0]);
var itemTransform = pe.Model.ReadItemTransform(selection[0]);
// Cache the coordinates before translating them away:
var ox = transformedBounds.Left + (transformedBounds.Width / 2);
var oy = transformedBounds.Top + (transformedBounds.Height / 2);
itemTransform.translate(-ox, -oy);
// Rotate:
itemTransform.setRotation(radians);
// Restore the translation:
itemTransform.translate(ox, oy);
All of which is probably pretty obvious if you can actually do sums, but I post it here in case anyone has a day as dim as mine...