3D point rotation algorithm - javascript

I'm currently working on a JavaScript project which involves 3D point rotation. Using simple trigonometry, I have sketched my own 3D point rotation algorithm, but I have to deal with a huge amount of data (+300 000 points) and my function slows down the runtime substantially (the FPS rate drops from 60 to 12).
I'm looking for another 3D point rotation ALGORITHM which...
rotates points around origin by X, Y and Z axes' angles (PITCH, YAW and ROLL)
has a quite good efficiency (don't worry about this too much, it will always be faster than mine)
is written in JavaScript, C-like code or pseudo-code
Any help will be greatly appreciated :)
Context: 3D point cloud renderer (I want every point to be rotated)

A rotated vector can be described as a product of a rotation matrix with that vector. The German Wikipedia page on pitch, roll and yaw describes the rotation matrix for given Euler rotation angles.
With that information, the rotation of all points with the same angles can be written as JavaScript function, where the points array is global:
function rotate(pitch, roll, yaw) {
var cosa = Math.cos(yaw);
var sina = Math.sin(yaw);
var cosb = Math.cos(pitch);
var sinb = Math.sin(pitch);
var cosc = Math.cos(roll);
var sinc = Math.sin(roll);
var Axx = cosa*cosb;
var Axy = cosa*sinb*sinc - sina*cosc;
var Axz = cosa*sinb*cosc + sina*sinc;
var Ayx = sina*cosb;
var Ayy = sina*sinb*sinc + cosa*cosc;
var Ayz = sina*sinb*cosc - cosa*sinc;
var Azx = -sinb;
var Azy = cosb*sinc;
var Azz = cosb*cosc;
for (var i = 0; i < points.length; i++) {
var px = points[i].x;
var py = points[i].y;
var pz = points[i].z;
points[i].x = Axx*px + Axy*py + Axz*pz;
points[i].y = Ayx*px + Ayy*py + Ayz*pz;
points[i].z = Azx*px + Azy*py + Azz*pz;
}
}
Most of that is setting up the rotation matrix as described in the article. The last three lines inside the loop are the matrix multiplication. You have made a point of not wanting to get into matrices, but that's hardly intimidating, is it? Sooner or later you will encounter more matrices and you should be prepared to deal with them. The stuff you need – multiplication, mainly – is simple. The more complicated stuff like inverting matrices is not needed for your requirements.
Anyway, that performs reasonably fast for 300,000 points. I was able to rotate a point cloud of that size and render it on a 1000px &times 1000px canvas in about 10ms.

From wikipedia:
If you multiply your points by each of these matrices they will be rotated by the amount you want.
For example, if I want to rotate point [1, 0, 0] by 90° around the z axis (in the xy plane), sin(90) = 1 and cos(90) = 0 so you get this:
| 0 -1 0 | |1| |0|
| 1 0 0 | * |0| = |1|
| 0 0 1 | |0| |0|

Related

Making a sine wave curve around a path in javascript

I am trying to figure out a nice way to make a sine wave flow naturally along a javascript path. I made something like this:
Which captures some of the intent but it's very forced and unnatural, especially around the change direction. I also would love to accommodate for higher slope, but not sure if that ives a more natural effect or not.
Any thoughts on how I might be able to accomplish this?
The intent was:
1) Take a set of points
2) Break into equal segments
3) Adjust the actual line's position by the difference of the sin coords and the actual line coords.
This gives a pretty weak display though, and I'd like to create something that was more natural and flowing as if to capture the flow of a sine wave travelling along a path.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = cw;
var h = ch * 0.3;
var amplitude = h;
var frequency = 0.01;
var phi = 0;
var frames = 0;
var stopped = true;
ctx.lineWidth = .4;
var offset = 100;
var points = interpolateLineRange( [ [0, 0], [ 95, 58], [84, 158], [350, 300], [540, 190] ], 20);
points = interpolateLineRange(points, 100);
ctx.moveTo(0, 0);
var distance_traveled = 0;
var current_slope = 0;
for (var ii in points) {
if (ii == 0) {
continue;
}
distance_traveled += dist(points[ii - 1], points[ii]);
current_slope = slope(points[ii - 1], points[ii]);
var newY = Math.sin(distance_traveled * .07) * 45 + points[ii][1];
var diff = newY - points[ii][1];
if (points[ii][1] > points[ii - 1][1]) {
ctx.lineTo(points[ii][0] - diff, newY);
} else {
ctx.lineTo(points[ii][0] + diff, newY);
}
}
ctx.stroke();
ctx.moveTo(0, 0);
for (var ii in points) {
ctx.lineTo(points[ii][0], points[ii][1]);
}
ctx.strokeStyle = 'red';
ctx.stroke();
The problem isn't really "drawing sine waves along a path": that part is actually trivial. Take your path section, express it in terms of a distance or time variable, and then draw the sines (or anything else) as an offset function:
for t=0; t<distance; t+=fraction of distance:
point = path.get(t)
normal = path.normal(t)
strength = sin(t)
if t=0:
ctx.moveTo(point + strength * normal)
else:
ctx.lineTo(point + strength * normal)
Easy enough, let's implement that: http://jsbin.com/nefemazovo/edit?js,output
Sure, it's a bit of code, but it's hardly complicated: just a class that models a polygonal path that tracks its length as we add points to it, and a draw function that draws the polygon, as well as some offset function, by sampling the polygon at regular intervals and computing the normal at each point.
The real question is: how are you going to deal with overlaps in your offset data? For instance, from the example above:
There's a pretty obvious area here where we're going to have to do ... something:
So what do we do? Turns out: no one knows, that's really up to you. For instance, you could draw "uneven" sines so that you always end up with a node at the end points of your polygonal sections. Might work, but you might also still have overlap if there's a small enough angle between consecutive segments. Plus your sines would be uneven, so would that look good? Ehh... up to you. Or, you could dampen the offset strength to zero at the polygon transition, and then ramp it back up to 100%, but will that look good? No idea, that is your call. You could also use interpolation so that the sine waves "blend" at the transition. Will that look good? Again, no idea, still up to you. You could even replace the offending section of polygon with something like a quadratic or cubic curve, so you always have smooth transitions along which sine offsets will "just work", but will that look good? ...you get the idea =)
The part of this question we can answer isn't super interesting, and the part that's interesting we unfortunately cannot answer for you...
We can give advice, though: I don't know what your polygon represents, but "curves" almost always work better as spines (almost, because curves can have discontinuities as well, which is the very thing you want to avoid), so if you can construct curves instead, probably worth it. However, that won't solve the problem of weird overlaps when your angles are too small:
You're still left with problems that can only be solved with "executive decisions" more than textbook "in this situation, do this: ..." solutions.

How to get the angle of a 3d plane using the position of the 4 corners

About 6 months ago i started making a 3d graphics engine.
Its already looking very good. I already implemented rotation, translation, scaling, Z-buffer(painter's algoritm),... Im now working on a specular shader. For that i need some way to get the angle of he individual faces
My question is, how do i get the angle of a plane by only knowing the position of the four corners?
Here is what i got so far:
function faceAngle(verts,faces){
var arr = [];
for(var i=0;i<faces.length;i++){
var posA = verts[faces[i][0]];//the four corners
var posB = verts[faces[i][1]];// A B
var posC = verts[faces[i][2]];// -----
var posD = verts[faces[i][3]];// | |
// | |
var ar = []; // -----
ar.push(/*some Maths*/);//x // D C
ar.push(/*some Maths*/);//y
ar.push(/*some Maths*/);//z
arr.push(ar);
}
return arr;
}
Orientation of plane in the space is defined by normal vector. To get this vector, calculate cross product of two edges (belonging to the plane). So you need only three non-collinear points in the plane.
n = (posB - posA) x (posC - posA) //cross product of two vectors
Note that components of normalized (unit) normal vector are direction cosines

Incorrect angle, wrong side calculated

I need to calculate the angle between 3 points. For this, I do the following:
Grab the 3 points (previous, current and next, it's within a loop)
Calculate the distance between the points with Pythagoras
Calculate the angle using Math.acos
This seems to work fine for shapes without angels of over 180 degrees, however if a shape has such an corner it calculates the short-side. Here's an illustration to show what I mean (the red values are wrong):
This is the code that does the calculations:
// Pythagoras for calculating distance between two points (2D)
pointDistance = function (p1x, p1y, p2x, p2y) {
return Math.sqrt((p1x - p2x)*(p1x - p2x) + (p1y - p2y)*(p1y - p2y));
};
// Get the distance between the previous, current and next points
// vprev, vcur and vnext are objects that look like this:
// { x:float, y:float, z:float }
lcn = pointDistance(vcur.x, vcur.z, vnext.x, vnext.z);
lnp = pointDistance(vnext.x, vnext.z, vprev.x, vprev.z);
lpc = pointDistance(vprev.x, vprev.z, vcur.x, vcur.z);
// Calculate and print the angle
Math.acos((lcn*lcn + lpc*lpc - lnp*lnp)/(2*lcn*lpc))*180/Math.PI
Is there something wrong in the code, did I forget to do something, or should it be done a completely different way?
HI there your math and calculations are perfect. Your running into the same problem most people do on calculators, which is orientation. What I would do is find out if the point lies to the left or right of the vector made by the first two points using this code, which I found from
Determine which side of a line a point lies
isLeft = function(ax,ay,bx,by,cx,cy){
return ((bx - ax)*(cy - ay) - (by - ay)*(cx - ax)) > 0;
}
Where ax and ay make up your first point bx by your second and cx cy your third.
if it is to the left just add 180 to your angle
I've got a working but not necessarily brief example of how this can work:
var point1x = 0, point1y = 0,
point2x = 10, point2y = 10,
point3x = 20, point3y = 10,
point4x = 10, point4y = 20;
var slope1 = Math.atan2(point2y-point1y,point2x-point1x)*180/Math.PI;
var slope2 = Math.atan2(point3y-point2y,point3x-point2x)*180/Math.PI;
var slope3 = Math.atan2(point4y-point3y,point4x-point3x)*180/Math.PI;
alert(slope1);
alert(slope2);
alert(slope3);
var Angle1 = slope1-slope2;
var Angle2 = slope2-slope3;
alert(180-Angle1);
alert(180-Angle2);
(see http://jsfiddle.net/ZUESt/1/)
To explain the multiple steps the slopeN variables are the slopes of the individual line segments. AngleN is the amount turned at each junction (ie point N+1). A positive angle is a right turn and a negative angle a left turn.
You can then subtract this angle from 180 to get the actual interior angle that you want.
It should be noted that this code can of course be compressed and that five lines are merely outputting variables to see what is going on. I'll let you worry about optimizing it for your own use with this being a proof of concept.
You need to check boundary conditions (apparently, if points are colinear) and apply the proper calculation to find the angle.
Also, a triangle can't have any (interior) angle greater than 180 degress. Sum of angle of triangle is 180 degrees.

Detect if a set of points in an array that are the vertices of a complex polygon were defined in a clockwise or counterclockwise order?

EDIT: I updated the program with the answer and it works great!
I am making a program (feel free to try it out) that lets users draw polygons which it then triangulates. They can click to add vertices and hit enter to triangulate. Anyways, the algorithm works fine as long as I tell it if the points were drawn in a clockwise or counterclockwise fashion (right now I have it set only to work with clockwise polygons). I have been trying to figure this out for days, but have no idea how to determine whether the points are clockwise or counterclockwise. Try drawing shapes with the program mentioned earlier to get a better idea, you can experience what I am talking about better than I can try to explain it.
Here is how the points are defined:
function Point(x, y) {
this.x = x;
this.y = y;
}
var vertices = [];
// Called on click
function addPoint(mouseX, mouseY) {
vertices.push(new Point(mouseX, mouseY));
}
Here is an image of a clockwise polygon:
Here is an image of a counterclockwise polygon:
If you could help me figure out how to determine the "clockwise-ness" of the points, I would be very grateful!
Compute the polygon area using the shoelace formula, but without the absolute value sign. If the result is positive, the points are ordered counterclockwise, and if negative - clockwise.
function polygonArea() {
var area = 0;
for (var i = 0; i < vertices.length; i++) {
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
}
return area / 2;
}
var clockwise = polygonArea() > 0;
In case someone is using three.js the ShapeUtils comes with an inbuilt isClockWise method which internally uses the area method to determine the sign of the calculated area.
isClockWise: function ( pts ) {
return ShapeUtils.area( pts ) < 0;
}
The ShapeUtils.isClockWise Method can be found here.
area: function ( contour ) {
var n = contour.length;
var a = 0.0;
for ( var p = n - 1, q = 0; q < n; p = q ++ ) {
a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
}
return a * 0.5;
},
The ShapeUtils.area Method can be found here.
A general idea would be to take a look at the convex hull of your polygone and guess the orientation from there. However, I think that you do not need to build the whole hull to find the orientation, but just one segment belonging to it.
So:
Find two points of your polygones so that all the other points are on one side of this line.
If all the points are on the left (just check one of the points), it's counterclockwise. If they are on the right, it's clockwise.
Example:
On the top figure: 4-5 let the figure on the right, 5-11 let the figure on the right, ...
On the bottom figure: 6-7 let the figure on the left, 7-14 let the figure on the left, ...
Warning: While "walking" on your polygon, do not restart the numeration, otherwise it will be wrong. On the top figure, 4-(n-1) let the figure on the left!
Your intuitive definition of clockwisedness is not well defined. For example, If I draw a horseshoe:
/---a-b--\
/ _d_c_ \
/ / \ \
| | | |
| | | |
\ \ / /
\ \ / /
-- --
If 0 = a < b < b < d and I look at a and b I would conclude from your description that the shape has been drawn clockwise, but if 0 = c < d < a < b I would conclude that the shape has been drawn anticlockwise. Since both of these scenarios involve the same direction in which the points were drawn, just from different starting points, I can only conclude that your definition is lacking.
The horseshoe I drew isn't the best; the idea is that it is almost a circle with just a small hole at the bottom, to allow the other side to be drawn in the opposite direction.
If you are interested in defining things more strictly, then I suggest something along the following lines:
Considering any finite simple polygon as separating the plane into two distinct areas (one finite and one infinite), we can always consider the finite area to be the interior of the polygon. In such a scenario we define a vertex ordering to be clockwise iff the order of the points runs with the exterior along its right-hand side. This is called curve orientation.
Once you have this more solid definition, implementation can be as simple as counting the winding number. Take the midpoint of any ordered pair, say 0 and 1, take a line segment to the right of the ordered pair (at any angle, say perpendicular), and count how many intersections it has with other line segments: The curve is clockwise iff the number is odd.
This is simple to implement, linear in time O(n), and adds constant space O(1).
This a function function that specialized for OpenLayers. As You Can See The Condition Of Clockwise Polygon Is area<0 This Reference Confirm It.
function IsClockwise(feature)
{
if(feature.geometry==null)return -1;
var vertices=feature.geometry.getVertices();
var area=0;
for (var i = 0; i < (vertices.length); i++)
{
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
// console.log(area);
}
return (area < 0);
}

Sub pixel antialiasing canvas pixel shift algorithms in javascript

I'm trying to work out a way of doing pseudo 3d, distorting textures with the javascript canvas.
The best method for my needs so far has been to use displacement maps which I'm largely following from this tutorial and source code example.
The basic principle is to use the channel level (RGBA) from a selected pixel in the displacement map then applying a pixel shifting algorithm... all good so far.
The problem is that this method of shifting the texture image's pixels is very binary and renders a slightly 'jagged' edge due to the fact that - it's simply shifting 'full' pixels.
When compared to PhotoShop or some of the ImageMagick examples here the javascript method looks much less realistic. This is due to PS & IMs sub-pixel processing abilities whereby medians can be derived for inter-pixel data.
Question: Can anyone suggest a step which can be integrated into my algorithm to produce a gaussian/aliased smoothness to the output?
Perhaps I can simply run the imagedata through an FFT and back again? are there any examples of this in action?
I'm a little stumped and would very much appreciate some pointers.
1) You are mentionning two very differents algorithms : Displacement mapping is a 3D technique, so it involves 'Z', and projection, and the other one is a 2D pixel shifting algorithm way way simpler.
(The soundstep link provided uses the word 'displacement mapping', yet it is a pixel shifting
technique.)
2) Whatever the size of your MVC project, the algorithm should be isolated, and have a signature like :
var pixelShift = function (sourceCanvas, shiftCanvas, xOffset, yOffset)
and either return a new canvas OR change sourceCanvas in place.
If there's no such function, please do not talk about MVC, unless the M stands for 'Mess'. ;-) -
3) The algorithm is quite simple in fact, you must iterate through the destination pixel and look the color of the pixel they should come from (and not the other way around) :
var pixelShift = function (sourceCanvas, shiftCanvas, xOffset, yOffset) {
var shiftXY = { xS:0, yS:0 };
var shiftCanvasWidth = shiftCanvas.width ;
var shiftCanvasHeight = shiftCanvas.height;
for ( var x=0 ; x < shiftCanvasWidth ; x ++) {
for ( var y = 0 ; y < shiftCanvasHeight ; y++ ) {
readShift ( shiftCanvas, x, y, shiftXY );
var sourceColor = readPixelColor ( sourceCanvas,
xOffset + shiftXY.xS,
yOffset + shiftXY.yS) ;
writePixel(sourceCanvas, xOffset + x , yOffset + y, sourceColor );
}
}
};
// sourceColor represents the color within a 32 bits integer (r,g,b,a * 8 bits).
it would be too long to write everything here but :
-- within pixelShift loop you should not deal with the source canvas, but a with 32 bits performance array.
-- the shift canvas should be converted ONCE into a Int8Array array, and stored as this.
this array size is shiftWidth * shiftHeight
odd index contains x shift, even contains y shift.
the array is pre-processed, and contains the shift value - 128.
for this shiftArray :
shiftX (x,y) = shiftArray [ 2 * (x + y * shiftWidth) ] ;
shiftY (x,y) = shiftArray [ 2 * (x + y * shiftWidth) + 1 ] ;
-- So pixelShift should look like :
var pixelShift = function (sourceCanvas,
shiftArray, shiftWidth, shiftHeight, xOffset, yOffset) {
[ Get a 32 bit performance array out of the canvas's target area ]
[ process this array using the shiftArray ]
[ write back the processed array onto the canvas's target area ]
}
-- And the core loop can be processed in a linear fashion :
var areaSize = shiftWidth * shiftHeight ;
for ( pixelIndex=0 ; pixelIndex < areaSize ; pixelIndex++ ) {
var linearShift = shiftArray [ 2*pixelIndex ]
+ shiftWidth * shiftArray [ 2*pixelIndex + 1 ] ;
targetAreaArray [ pixelIndex ] = targetAreaArray [ pixelIndex + linearShift ] ;
}
-- Rq : you might want to perform a boundary check on (pixelIndex + linearShift) within [0, areaSize[.
-- I think now you cannot get any faster.
The performance bottleneck will be the getImageData and putImageData you need to get/put the target area, but as far as i know, there's no other way to get a binary view on a Canvas than those two slooooow functions.

Categories

Resources