how to draw curve through N points using javascript HTML5 canvas? - javascript

For a drawing application I'm saving the mouse movement coordinates to an array then drawing them with lineTo. The resulting line is not smooth. How can I produce a single curve between all the gathered points?
I've googled but I have only found 3 functions for drawing lines: For 2 sample points, simply use lineTo. For 3 sample points quadraticCurveTo, for 4 sample points, bezierCurveTo.
(I tried drawing a bezierCurveTo for every 4 points in the array, but this leads to kinks every 4 sample points, instead of a continuous smooth curve.)
How do I write a function to draw a smooth curve with 5 sample points and beyond?

You can use cardinal spline to do this:
The function for this is like this with array of points ordered as [x1, y1, x2, y2, ... xn, yn], tension between [0.0, 1.0] and optionally number of segments which dictates the resolution between each point.
Here's an online demo of this in action
UPDATE posted wrong version of my cardinal implementation, this is the correct one -
The result will be a new array with the smoothed line that you iterate -
function getCurvePoints(ptsa, tension, numOfSegments) {
// use input value if provided, or use a default value
tension = (tension != 'undefined') ? tension : 0.5;
numOfSegments = numOfSegments ? numOfSegments : 16;
var _pts = [], res = [], // clone array
x, y, // our x,y coords
t1x, t2x, t1y, t2y, // tension vectors
c1, c2, c3, c4, // cardinal points
st, t, i; // steps based on num. of segments
// clone array so we don't change the original
_pts = ptsa.slice(0);
_pts.unshift(pts[1]); //copy 1. point and insert at beginning
_pts.unshift(pts[0]);
_pts.push(pts[pts.length - 2]); //copy last point and append
_pts.push(pts[pts.length - 1]);
// ok, lets start..
// 1. loop goes through point array
// 2. loop goes through each segment between the two points + one point before and after
for (i=2; i < (_pts.length - 4); i+=2) {
// calc tension vectors
t1x = (_pts[i+2] - _pts[i-2]) * tension;
t2x = (_pts[i+4] - _pts[i]) * tension;
t1y = (_pts[i+3] - _pts[i-1]) * tension;
t2y = (_pts[i+5] - _pts[i+1]) * tension;
for (t=0; t <= numOfSegments; t++) {
// calc step
st = t / numOfSegments;
// calc cardinals
c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1;
c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);
c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st;
c4 = Math.pow(st, 3) - Math.pow(st, 2);
// calc x and y cords with common control vectors
x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;
//store points in array
res.push(x);
res.push(y);
}
}
return res;
}

Related

Geometric rotations of a group of objects

Referring to the first diagram, I am trying to copy the three objects, looking at them from an arbitrary angle(A1). The distance between where I'm and the first object does not matter just the relative location of the object to one another.
In the second diagram, I select a point to copy these objects, facing another arbitrary angle(B1).
Angle (C1) shows the approximate position of -90 degrees.
I can get this to work if A1 = 0,90,180,270 and even 45,135 etc but the equations I come up with only work for 0 and 180 or 90 and 270. I have to modify them to work in those directions by changing a hardcoded offset angle and putting/removing a negative sign before the offset.
I'm doing this is javascript (and its Minecraft) usually I can figure out this but I have been working on it for weeks.
Here is some pseudo-code that works some of the time in certain right-angle directions. I have updated this to be more accurate, the 1x and 2x are the blocks x coordinate, etc. - everything is relative from the (1) block.
Minecraft's coordinate system is a little different from normal - 0 is south, +90 is west, 180 is north, 270 is east.
the only difference is that I am making negative az, ax.
// works for north/south looking - A1 is either 180/0 , B1 can be anything
var x = 1x - 2x;
var z = 1z - 2z;
var direction = Math.atan2(z1, x1);
var L1 = Math.sqrt(Math.pow(x1, 2) + Math.pow(z1, 2));
var az = Math.round(L1 * Math.sin((B1 + A1 + (direction * 180 / Math.PI)) * Math.PI / 180));
var ax = Math.round(L1 * Math.cos((B1 + A1 + (direction * 180 / Math.PI)) * Math.PI / 180));
// works for east/west looking - A1 is either 90/270 , B1 can be anything
var x = 1x - 2x;
var z = 1z - 2z;
var direction = Math.atan2(z1, x1);
var L1 = Math.sqrt(Math.pow(x1, 2) + Math.pow(z1, 2));
var az = -Math.round(L1 * Math.sin((B1 + A1 + (direction * 180 / Math.PI)) * Math.PI / 180));
var ax = -Math.round(L1 * Math.cos((B1 + A1 + (direction * 180 / Math.PI)) * Math.PI / 180));
First diagram
Second diagram

Converting an equirectangular depth map into 3d point cloud

I have a 2D equirectangular depth map that is a 1024 x 512 array of floats, each ranging between 0 to 1. Here example (truncated to grayscale):
I want to convert it to a set of 3D points but I am having trouble finding the right formula to do so - it's sort of close - pseudocode here (using a vec3() library):
for(var y = 0; y < array_height; ++y) {
var lat = (y / array_height) * 180.0 - 90.0;
var rho = Math.cos(lat * Math.PI / 180.0);
for(var x = 0; x < array_width; ++x) {
var lng = (x / array_width) * 360.0 - 180.0;
var pos = new vec3();
pos.x = (r * Math.cos(lng * Math.PI / 180.0));
pos.y = (Math.sin(lat * Math.PI / 180.0));
pos.z = (r * Math.sin(lng * Math.PI / 180.0));
pos.norm();
var depth = parseFloat(depth[(y * array_width) + x] / 255);
pos.multiply(depth);
// at this point I can plot pos as an X, Y, Z point
}
}
What I end up with isn't quite right and I can't tell why not. I am certain the data is correct. Can anyone suggest what I am doing wrong.
Thank you.
Molly.
Well looks like the texture is half-sphere in spherical coordinates:
x axis is longitude angle a <0,180> [deg]
y axis is latitude angle b <-45,+45> [deg]
intensity is radius r <0,1> [-]
So for each pixel simply:
linearly convert x,y to a,b
in degrees:
a = x*180 / (width -1)
b = -45 + ( y* 90 / (height-1) )
or in radians:
a = x*M_PI / (width -1)
b = -0.25*M_PI + ( 0.5*y*M_PI / (height-1) )
apply spherical to cartesian conversion
x=r*cos(a)*cos(b);
y=r*sin(a)*cos(b);
z=r* sin(b);
Looks like you have wrongly coded this conversion as latitude angle should be in all x,y,z not just y !!! Also you should not normalize the resulting position that would corrupt the shape !!!
store point into point cloud.
When I put all together in VCL/C++ (sorry do not code in javascript):
List<double> pnt; // 3D point list x0,y0,z0,x1,y1,z1,...
void compute()
{
int x,y,xs,ys; // texture positiona and size
double a,b,r,da,db; // spherical positiona and angle steps
double xx,yy,zz; // 3D point
DWORD *p; // texture pixel access
// load and prepare BMP texture
Graphics::TBitmap *bmp=new Graphics::TBitmap;
bmp->LoadFromFile("map.bmp");
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
xs=bmp->Width;
ys=bmp->Height;
/*
// 360x180 deg
da=2.0*M_PI/double(xs-1);
db=1.0*M_PI/double(ys-1);
b=-0.5*M_PI;
*/
// 180x90 deg
da=1.0*M_PI/double(xs-1);
db=0.5*M_PI/double(ys-1);
b=-0.25*M_PI;
// proces all its pixels
pnt.num=0;
for ( y=0; y<ys; y++,b+=db)
for (p=(DWORD*)bmp->ScanLine[y],a=0.0,x=0; x<xs; x++,a+=da)
{
// pixel access
r=DWORD(p[x]&255); // obtain intensity from texture <0..255>
r/=255.0; // normalize to <0..1>
// convert to 3D
xx=r*cos(a)*cos(b);
yy=r*sin(a)*cos(b);
zz=r* sin(b);
// store to pointcloud
pnt.add(xx);
pnt.add(yy);
pnt.add(zz);
}
// clean up
delete bmp;
}
Here preview for 180x90 deg:
and preview for 360x180 deg:
Not sure which one is correct (as I do not have any context to your map) but the first option looks more correct to me ...
In case its the second just use different numbers (doubled) for the interpolation in bullet #1
Also if you want to remove the background just ignore r==1 pixels:
simply by testing the intensity to max value (before normalization) in my case by adding this line:
if (r==255) continue;
after this one
r=DWORD(p[x]&255);
In your case (you have <0..1> already) you should test r>=0.9999 or something like that instead.

Find angle between three points, ([x,y], [x,y], [x,y]) allow for obtuse results. (Math and Javascript)

I have the following function, to find the angle between 3 points.
(Where a point is defined as an array containing the x position as its first element and the y position as its second element, and the angle to measure is the angle created when a line is drawn through all three points.)
findAngle: function(a, b, c) {
var ab = Math.sqrt(Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2));
var bc = Math.sqrt(Math.pow(b[0] - c[0], 2) + Math.pow(b[1] - c[1], 2));
var ac = Math.sqrt(Math.pow(c[0] - a[0], 2) + Math.pow(c[1] - a[1], 2));
var o1 = (bc * bc + ab * ab - ac * ac) / (2 * bc * ab);
var o2 = Math.acos(o1);
var o3 = o2 * (180 / Math.PI);
return o3;
}
However, this only ever returns angles between 0 and 180 degrees. How can I have it return values between 0 - 180 degrees, and values between 180 and 360 when the angle is in fact obtuse?
Use atan2 function that has two arguments and returns result in range -Pi..Pi (add 2*Pi to negative angles if needed)
angle = Math.atan2(crossproduct(c-b,b-a), dotproduct(c-b,b-a))
where
crossproduct(c-b,b-a) = (c.x-b.x)*(b.y-a.y) - (c.y-b.y)*(b.x-a.x)
dotproduct(c-b,b-a) = (c.x-b.x)*(b.x-a.x) + (c.y-b.y)*(b.y-a.y)

Instancing PointCloud(ParticleCloud) in THREE.js

i am just a beginner in Threejs so please excuse if its a noobie question. but i haven't worked with particles.
How do i put points(particles) inside a custom geometry of a text geometry?
What i want to achieve is instance points inside a geometry or text then explode it to the world position. if someone direct me to the path, would be much helpful.
i know there's an example https://threejs.org/examples/#webgl_points_dynamic
but i cant understand whats happening in the render loop.
This is not the ultimate solution, but just a starting point.
You can set points from any type of geometry (usual geometry or buffer geometry).
Let's imagine that you have a THREE.TextGeometry(), then you can set points from it as:
textGeo = new THREE.TextGeometry("ABC", {
font: font,
size: 2,
height: 0.25,
curveSegments: 1,
bevelEnabled: false
});
textGeo.computeBoundingBox();
textGeo.computeVertexNormals();
textGeo.center();
fillWithPoints(textGeo, 1000); // fill our text geometry with 1000 random points
textGeo.vertices.forEach(function(vertex){
vertex.startPoint = vertex.clone(); // remember the starting position of a vertex
vertex.direction = vertex.clone().normalize(); // set direction
})
textPoints = new THREE.Points(textGeo, new THREE.PointsMaterial({color: 0x00ff00, size: 0.1})); // all you need is to have a geometry and THREE.PointsMaterial()
scene.add(textPoints);
To determine if a random point is inside our geometry, we can do the trick with projection of all the faces of our text geometry into 2D (x,y) and check if the point (with its x,y coordinates) is inside of one of projected triangles (faces):
function isPointInside(point, geometry) {
var retVal = false;
for (var i = 0; i < geometry.faces.length; i++) { //loop through faces
face = geometry.faces[i];
a = geometry.vertices[face.a];
b = geometry.vertices[face.b];
c = geometry.vertices[face.c];
if (ptInTriangle(point, a, b, c)) {
var retVal = true;
break; // exit the loop if the point is in a projected triangle
}
}
return retVal;
}
where
function ptInTriangle(p, p0, p1, p2) {
// credits: http://jsfiddle.net/PerroAZUL/zdaY8/1/
var A = 1/2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
var sign = A < 0 ? -1 : 1;
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
return s > 0 && t > 0 && (s + t) < 2 * A * sign;
}
and then in the animation loop we'll use the stuff of a vertex (startPoint and direction):
textGeo.vertices.forEach(function(vertex){
vertex.copy(vertex.startPoint).addScaledVector(vertex.direction, 5 + Math.sin(Date.now() * 0.001) * 5);
});
textGeo.verticesNeedUpdate = true; // this is the most important thing, you have to set it to true after each rendering
jsfiddle example

Drawing a line with perpendicular decoration

I need to draw a line in the following manner:
 
For now, it will be only drawn in code, no user input.
My question is, how to draw perpendiculars to a line, if I draw it point by point? (Obviously, this will be the case, because drawing with bezier curves will not give me the possibility to somehow impact the drawing).
The closest answer I found was possibly this one, but I can't reverse the equations to derive C. Also there is no length of the decoration mentioned, so I think this will not work as I'd like it to.
Find the segment perpendicular to another one is quite easy.
Say we have points A, B.
Compute vector AB.
Normalize it to compute NAB (== the 'same' vector, but having a length of 1).
Then if a vector has (x,y) as coordinates, its normal vector has (-y,x) as coordinates, so
you can have PNAB easily (PNAB = perpendicular normal vector to AB).
// vector AB
var ABx = B.x - A.x ;
var ABy = B.y - A.y ;
var ABLength = Math.sqrt( ABx*ABx + ABy*ABy );
// normalized vector AB
var NABx = ABx / ABLength;
var NABy = ABy / ABLength;
// Perpendicular + normalized vector.
var PNABx = -NABy ;
var PNABy = NABx ;
last step is to compute D, the point that is at a distance l of A : just add l * PNAB to A :
// compute D = A + l * PNAB
var Dx = A.x + l* PNAB.x;
var Dy = A.y + l *PNAB.y;
Updated JSBIN :
http://jsbin.com/bojozibuvu/1/edit?js,output
Edit :
A second step is to draw the decorations at regular distance, since it's Christmas time, here's how i would do it :
http://jsbin.com/gavebucadu/1/edit?js,console,output
function drawDecoratedSegment(A, B, l, runningLength) {
// vector AB
var ABx = B.x - A.x;
var ABy = B.y - A.y;
var ABLength = Math.sqrt(ABx * ABx + ABy * ABy);
// normalized vector AB
var NABx = ABx / ABLength;
var NABy = ABy / ABLength;
// Perpendicular + normalized vector.
var PNAB = { x: -NABy, y: NABx };
//
var C = { x: 0, y: 0 };
var D = { x: 0, y: 0 };
//
drawSegment(A, B);
// end length of drawn segment
var endLength = runningLength + ABLength;
// while we can draw a decoration on this line
while (lastDecorationPos + decorationSpacing < endLength) {
// compute relative position of decoration.
var decRelPos = (lastDecorationPos + decorationSpacing) - runningLength;
// compute C, the start point of decoration
C.x = A.x + decRelPos * NABx;
C.y = A.y + decRelPos * NABy;
// compute D, the end point of decoration
D.x = C.x + l * PNAB.x;
D.y = C.y + l * PNAB.y;
// draw
drawSegment(C, D);
// iterate
lastDecorationPos += decorationSpacing;
}
return ABLength;
}
All you need is direction of curve (or polyline segment) in every point, where you want to draw perpendicular.
If direction vector in point P0 is (dx, dy), then perpendicular (left one) will have direction vector (-dy, dx). To draw perpendicular with length Len, use this pseudocode:
Norm = Sqrt(dx*dx + dy*dy) //use Math.Hypot if available
P1.X = P0.X - Len * dy / Norm
P1.Y = P0.Y + Len * dx / Norm
P.S. If you know direction angle A, then direction vector
(dx, dy) = (Cos(A), Sin(A))
and you don't need to calculate Norm, it is equal to 1.0

Categories

Resources