Error on drawing Fibonacci in web - javascript

Currently I have this fiddle from Blindman67 which draws Golden spiral figure 1(see image below).
function renderSpiral(pointA, pointB, turns){
var dx, dy, rad, i, ang, cx, cy, dist, a, c, angleStep, numberTurns, nTFPB, scale, styles;
// clear the canvas
ctx.clearRect(0, 0, ctx.canvas.width,ctx.canvas.height)
// spiral stuff
a = 1; // the larger this number the larger the spiral
c = 1.358456; // constant See https://en.wikipedia.org/wiki/Golden_spiral
angleStep = Math.PI/20; // set the angular resultion for drawing
numberTurns = 6; // total half turns drawn
nTFPB = 2; // numberOfTurnsForPointB is the number of turns to point
// B should be integer and describes the number off
// turns made befor reaching point B
// get the ang from pointA to B
ang = Math.atan2(pointB.y-pointA.y,pointB.x-pointA.x);
// get the distance from A to B
dist = Math.sqrt(Math.pow(pointB.y-pointA.y,2)+Math.pow(pointB.x-pointA.x,2));
if(dist === 0){
return; // this makes no sense so exit as nothing to draw
}
// get the spiral radius at point B
rad = Math.pow(c,ang + nTFPB * 2 * Math.PI); // spiral radius at point2
// now just need to get the correct scale so the spiral fist to the
// constraints requiered.
scale = dist / rad;
// ajust the number of turns so that the spiral fills the canvas
while(Math.pow(c,Math.PI*numberTurns)*scale < ctx.canvas.width){
numberTurns += 2;
}
// set the scale, and origin to centre
ctx.setTransform(scale, 0, 0, scale, pointA.x, pointA.y)
// make it look nice create some line styles
// first just draw the line A-B
ctx.strokeStyle = "black";
ctx.lineWidth = 2 * ( 1 / scale); // because it is scaled invert the scale
// can calculate the width requiered
// ready to draw
ctx.beginPath();
ctx.moveTo(0, 0) // start at center
ctx.lineTo((pointB.x-pointA.x)*(1/scale),(pointB.y-pointA.y)*(1/scale) ); // add line
ctx.stroke(); // draw it all
// Now draw the sporal. draw it for each style
styles.forEach( function(style) {
ctx.strokeStyle = style.colour;
ctx.lineWidth = style.width * ( 1 / scale); // because it is scaled invert the scale
// can calculate the width requiered
// ready to draw
ctx.beginPath();
for( i = 0; i <= Math.PI *numberTurns; i+= angleStep){
dx = Math.cos(i); // get the vector for angle i
dy = Math.sin(i);
var rad = Math.pow(c, i); // calculate the radius
if(i === 0) {
ctx.moveTo(0, 0) // start at center
}else{
ctx.lineTo(dx * rad, dy * rad ); // add line
}
}
ctx.stroke(); // draw it all
});
ctx.setTransform(1,0,0,1,0,0); // reset tranfrom to default;
}
What I want to obtain is figure 2 (see image below).
Q1. How can I change mine spiral so line AB will fit between first and second screw while A is the start of spiral?
You can also refer to my earlier question for better understanding of my problem.

To achieve the properties you need you need to adjust your spiral like following:
choose the right angular position of the line AB
I choose 1.5*M_PI [rad] for A and 3.5*M_PI [rad] for B (on unrotated spiral)
rotate your spiral by angle of your AB line
that is easy just add the angle to the final polar -> cartesian coordinates conversion and that will rotate entire spiral so computed angular positions of A,B on spiral will match the real points AB direction
rescale your spiral to match the AB size
So compute the radiuses for angular points A,B positons on spiral and then compute the scale=|AB|-(r(b)-r(a)). Now just multiply this to compute radius of each rendered point ...
I played a bit with the golden ratio and spiral a bit and here is the result
Yellow spiral is approximation by quarter circle arcs
Aqua is the Golden spiral
As you can see they do not match so much (this is with ratio*0.75 to make them more similar but it should be just ratio) Either I have a bug somewhere, or the origin of spiral is shifted (but does not look like it) or I have wrong ratio constant ratio = 0.3063489 or the Golden rectangles are introducing higher floating round errors then I taught or I am missing something stupid.
Here the C++ source code so you can extract what you need:
//---------------------------------------------------------------------------
#include <Math.h>
//---------------------------------------------------------------------------
bool _redraw=false; // just signal to repaint window after spiral change
double Ax,Ay,Bx,By; // mouse eddited points
double gr=0.75; // golden spiral ratio scale should be 1 !!!
void GoldenSpiral_draw(TCanvas *can) // GDI draw
{
double a0,a,b,l,x,y,r=5,ratio;
// draw AB line
can->Pen->Color=clWhite;
can->MoveTo(Ax,Ay);
can->LineTo(Bx,By);
// draw A,B points
can->Pen->Color=clBlue;
can->Brush->Color=clAqua;
can->Ellipse(Ax-r,Ay-r,Ax+r,Ay+r);
can->Ellipse(Bx-r,By-r,Bx+r,By+r);
// draw golden ratio rectangles
can->Pen->Color=clDkGray;
can->Brush->Style=bsClear;
ratio=1.6180339887;
a=5.0; b=a/ratio; x=Ax; y=Ay;
y-=0.5*b; x-=0.5*b; // bias to match real golden spiral
can->Rectangle(x,y,x+a,y+b); y-=a;
for (int i=0;i<5;i++)
{
can->Rectangle(x,y,x+a,y+a); b=a; a*=ratio; x-=a;
can->Rectangle(x,y,x+a,y+a); y+=a; b=a; a*=ratio;
can->Rectangle(x,y,x+a,y+a); x+=a; y-=b; b=a; a*=ratio;
can->Rectangle(x,y,x+a,y+a); x-=b; b=a; a*=ratio; y-=a;
}
// draw circle arc approximation of golden spiral
ratio=1.6180339887;
a=5.0; b=a/ratio; x=Ax; y=Ay; r=10000; y-=a;
y-=0.5*b; x-=0.5*b; // bias to match real golden spiral
can->Pen->Color=clYellow;
for (int i=0;i<5;i++)
{
can->Arc(x-a,y,x+a,y+a+a,+r, 0, 0,-r); b=a; a*=ratio; x-=a;
can->Arc(x,y,x+a+a,y+a+a, 0,-r,-r, 0); y+=a; b=a; a*=ratio;
can->Arc(x,y-a,x+a+a,y+a,-r, 0, 0,+r); x+=a; y-=b; b=a; a*=ratio;
can->Arc(x-a,y-a,x+a,y+a, 0,+r,+r, 0); x-=b; b=a; a*=ratio; y-=a;
}
can->Brush->Style=bsSolid;
// compute golden spiral parameters
ratio=0.3063489*gr;
x=Bx-Ax;
y=By-Ay;
l=sqrt(x*x+y*y); // l=|AB|
if (l<1.0) return; // prevent domain errors
a0=atan2(-y,x); // a=atan2(AB)
a0+=0.5*M_PI; // offset so direction of AB matches the normal
a=1.5*M_PI; r=a*exp(ratio*a); b=r;
a+=2.0*M_PI; r=a*exp(ratio*a); b=r-b;
b=l/r; // b=zoom of spiral to match AB screw distance
// draw golden spiral
can->Pen->Color=clAqua;
can->MoveTo(Ax,Ay);
for (a=0.0;a<100.0*M_PI;a+=0.001)
{
r=a*b*exp(ratio*a); if (r>512.0) break;
x=Ax+r*cos(a0+a);
y=Ay-r*sin(a0+a);
can->LineTo(x,y);
}
}
//---------------------------------------------------------------------------
You can ignore the golden ratio rectangles and circular arcs ...
change the drawings based on can-> to your gfx API. It is just GDI Canvas
Hard to say if your spiral is correct ... you can check with the golden ratio rectangles (as I did). If you got correct spiral then just apply the bullets #1,#2,#3 to it and you should be fine.

Here's a fiddle which I believe gives you the output your looking for.
https://jsfiddle.net/8a7fdg3d/4/
The main problem was starting the spiral from 0 results in the initial straight line.
Starting the spiral from 1 removes this part of the graph and then you just had to adjust the starting point of your black |AB| line.
This was done by adjusting
for( i = 0; i <= Math.PI *numberTurns; i+= angleStep)
to
for( i = 1; i <= Math.PI *numberTurns; i+= angleStep)
to change the starting point of the spiral, then changing
// ready to draw
ctx.beginPath();
ctx.moveTo(0, 0) // start at center
to
// ready to draw
ctx.beginPath();
dx = Math.cos(1); // get the vector for angle i
dy = Math.sin(1);
var rad = Math.pow(c, 1); // calculate the radius
ctx.moveTo(dx * rad, dy * rad ) // start at center
to make your |AB| line match up.

Related

Moving object from A to B smoothly across canvas

I am trying to move an object smoothly from point A to point B using HTML canvas and regular javascript.
Point A is a set of coordinates
Point B is in the case the cursor location.
I made a jsfiddle of what I have so far: https://jsfiddle.net/as9fhmw8/
while(projectile.mouseX > projectile.x && projectile.mouseY < projectile.y)
{
ctx.save();
ctx.beginPath();
ctx.translate(projectile.x, projectile.y);
ctx.arc(0,0,5,0,2*Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.stroke();
ctx.restore();
if(projectile.mouseX > projectile.x && projectile.mouseY < projectile.y)
{
var stepsize = (projectile.mouseX - projectile.x) / (projectile.y - projectile.mouseY);
projectile.x += (stepsize + 1);
}
if(projectile.mouseY < projectile.y)
{
var stepsize = (projectile.y - projectile.mouseY) / (projectile.mouseX - projectile.x);
projectile.y -= (stepsize + 1);
}
}
Essentially what I can't figure out to do is to make the while loop slower (so that it appears animated in stead of just going through every iteration and showing the result).
I also can't figure out how to prevent the Arc from duplicating so that it creates a line that is permanent, instead of appearing to move from point a to point b.
Smooth animation here is really about determining how far to move your object for each iteration of the loop.
There is a little math involved here, but it's not too bad.
Velocity
Velocity in your case is just the speed at which your particles travel in any given direction over a period of time. If you want your particle to travel 200px over the course of 4 seconds, then the velocity would be 50px / second.
With this information, you can easily determine how many pixels to move (animate) a particle given some arbitrary length of time.
pixels = pixelsPerSecond * seconds
This is great to know how many pixels to move, but doesn't translate into individual X and Y coordinates. That's where vectors come in.
Vectors
A vector in mathematics is a measurement of both direction and magnitude. For our purposes, it's like combining our velocity with an angle (47°).
One of the great properties of vectors is it can be broken down into it's individual X and Y components (for 2-Dimensional space).
So if we wanted to move our particle at 50px / second at a 47° angle, we could calculate a vector for that like so:
function Vector(magnitude, angle){
var angleRadians = (angle * Math.PI) / 180;
this.magnitudeX = magnitude * Math.cos(angleRadians);
this.magnitudeY = magnitude * Math.sin(angleRadians);
}
var moveVector = new Vector(50, 47);
The wonderful thing about this is that these values can simply be added to any set of X and Y coordinates to move them based on your velocity calculation.
Mouse Move Vector
Modeling your objects in this way has the added benefit of making things nice and mathematically consistent. The distance between your particle and the mouse is just another vector.
We can back calculate both the distance and angle using a little bit more math. Remember that guy Pythagoras? Turns out he was pretty smart.
function distanceAndAngleBetweenTwoPoints(x1, y1, x2, y2){
var x = x2 - x1,
y = y2 - y1;
return {
// x^2 + y^2 = r^2
distance: Math.sqrt(x * x + y * y),
// convert from radians to degrees
angle: Math.atan2(y, x) * 180 / Math.PI
}
}
var mouseCoords = getMouseCoords();
var data = distanceAndAngleBetweenTwoPoints(particle.x, particle.y, mouse.x, mouse.y);
//Spread movement out over three seconds
var velocity = data.distance / 3;
var toMouseVector = new Vector(velocity, data.angle);
Smoothly Animating
Animating your stuff around the screen in a way that isn't jerky means doing the following:
Run your animation loop as fast as possible
Determine how much time has passed since last time
Move each item based on elapsed time.
Re-paint the screen
For the animation loop, I would use the requestAnimationFrame API instead of setInterval as it will have better overall performance.
Clearing The Screen
Also when you re-paint the screen, just draw a big rectangle over the entire thing in whatever background color you want before re-drawing your items.
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
Putting It All Together
Here is a Fiddle demonstrating all these techniques: https://jsfiddle.net/jwcarroll/2r69j1ok/3/

Apply Rotation to Cylinder based on Tube Ending Normal

I am attempting to make a curved 3D arrow in three.js. To accomplish this task, I have created a Tube that follows a curved path and a Cylinder shaped as a cone (by setting radiusTop to be tiny). They currently look like so:
I am attempting to position the Arrow Head (Cylinder shaped as a cone) at the end of the Tube like so: (Photoshopped)
I am not terribly strong in math and pretty new to three.js. Could someone help me understand how to connect the two?
Here is my current code:
import T from 'three';
var findY = function(r, x)
{
return Math.sqrt((r * r) - (x * x));
}
var radius = 25;
var x = 0;
var z = 0;
var numberOfPoints = 10;
var interval = (radius/numberOfPoints);
var points = [];
for (var i = numberOfPoints; i >= 0; i--)
{
var y = findY(radius, x);
points.push(new T.Vector3(x, y, z))
x = x + interval;
}
x = x - interval;
for (var i = numberOfPoints - 1 ; i >= 0; i--)
{
y = findY(radius, x) * -1;
points.push(new T.Vector3(x, y, z));
x = x - interval;
}
var path = new T.CatmullRomCurve3(points);
var tubeGeometry = new T.TubeGeometry(
path, //path
10, //segments
radius / 10, //radius
8, //radiusSegments
false //closed
);
var coneGeometry = new T.CylinderGeometry(
radiusTop = 0.1,
radiusBottom = radius/5,
height = 10,
radialSegments = 10,
heightSegments = 10,
openEnded = 1
);
var material = new T.MeshBasicMaterial( { color: 0x00ff00 } );
var tube = new T.Mesh( tubeGeometry, material );
var cone = new T.Mesh( coneGeometry, material );
// Translate and Rotate cone?
I would greatly appreciate if someone could attempt a simple explanation of what is necessary mathematically and programmatically accomplish
Finding the normal located at the end of the tube
Shifting the Cone to the correct location
Any help is appreciated!
Do not use rotation for this when you can create the arrowhead directly in place. Similarly the bended tube can be done this way too. Only thing you need for it is the last line segment defined by A,B endpoints.
Let A be the sharp point and B the disc base center. To create arrowhead you need 2 additional basis vectors let call them U,V and radius r of base disc. From them you can create disc points with simple circle formula like this:
obtain AB endpoints
compute U,V basis vectors
The U,V should lie in the disc base of arrowhead and should be perpendicular to each other. direction of the arrowhead (line |BA|) is the disc base normal so exploit cross product which returns perpendicular vector to the multiplied ones so:
W = B-A;
W /= |W|; // unit vector
T = (1,0,0); // temp any non zero vector not parallel to W
if ( |(W.T)|>0.75 ) T = (0,1,0); // if abs dot product of T and W is close to 1 it means they are close to parallel so chose different T
U = (T x W) // U is perpendicular to T,W
V = (U x W) // V is perpendicular to U,W
create/render arrowhead geometry
That is easy booth A,B are centers of triangle fan (need 2) and the disc base points are computed like this:
P(ang) = B + U.r.cos(ang) + V.r.sin(ang)
So just loop ang through the whole circle with some step so you got enough points (usually 36 is enough) and do both triangle fans from them. Do not forget the last disc point must be the same as the first one otherwise you will got ugly seems or hole on the ang = 0 or 360 deg.
If you still want to go for rotations instead then this is doable like this. compute U,V,W in the same way as above and construct transformation matrix from them. the origin O will be point B and axises X,Y,Z will be U,V,W the order depends on your arrowhead model. W should match the model axis. U,V can be in any order. So just copy all the vectors to their places and use this matrix for rendering. For more info see:
Understanding 4x4 homogenous transform matrices
[Notes]
If you do not know how to compute vector operations like cross/dot products or absolute value see:
// cross product: W = U x V
W.x=(U.y*V.z)-(U.z*V.y)
W.y=(U.z*V.x)-(U.x*V.z)
W.z=(U.x*V.y)-(U.y*V.x)
// dot product: a = (U.V)
a=U.x*V.x+U.y*V.y+U.z*V.z
// abs of vector a = |U|
a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))
[Edit1] simple GL implementation
I do not code in your environment but as downvote and comment suggest you guys are not able to put this together on your own which is odd considering you got this far so here simple C++/GL exmaple of how to do this (you can port this to your environment):
void glArrowRoundxy(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat a2)
{
const int _glCircleN=50; // points per circle
const int n=3*_glCircleN;
int i,j,ix,e;
float x,y,z,x1,y1,z1,a,b,da,db=pi2/(_glCircleN-1);
float ux,uy,uz,vx,vy,vz,u,v;
// buffers
GLfloat ptab[6*_glCircleN],*p0,*p1,*n0,*n1,*p;
p0=ptab+(0*_glCircleN); // previous tube segment circle points
p1=ptab+(3*_glCircleN); // actual tube segment circle points
da=+db; if (a0>a1) da=-db; // main angle step direction
ux=0.0; // U is normal to arrow plane
uy=0.0;
uz=1.0;
// arc interpolation a=<a0,a1>
for (e=1,j=0,a=a0;e;j++,a+=da)
{
// end conditions
if ((da>0.0)&&(a>=a1)) { a=a1; e=0; }
if ((da<0.0)&&(a<=a1)) { a=a1; e=0; }
// compute actual tube ceneter
x1=x0+(r*cos(a));
y1=y0+(r*sin(a));
z1=z0;
// V is direction from (x0,y0,z0) to (x1,y1,z1)
vx=x1-x0;
vy=y1-y0;
vz=z1-z0;
// and unit of coarse
b=sqrt((vx*vx)+(vy*vy)+(vz*vz));
if (b>1e-6) b=1.0/b; else b=0.0;
vx*=b;
vy*=b;
vz*=b;
// tube segment
for (ix=0,b=0.0,i=0;i<_glCircleN;i++,b+=db)
{
u=r0*cos(b);
v=r0*sin(b);
p1[ix]=x1+(ux*u)+(vx*v); ix++;
p1[ix]=y1+(uy*u)+(vy*v); ix++;
p1[ix]=z1+(uz*u)+(vz*v); ix++;
}
if (!j)
{
glBegin(GL_TRIANGLE_FAN);
glVertex3f(x1,y1,z1);
for (ix=0;ix<n;ix+=3) glVertex3fv(p1+ix);
glEnd();
}
else{
glBegin(GL_QUAD_STRIP);
for (ix=0;ix<n;ix+=3)
{
glVertex3fv(p0+ix);
glVertex3fv(p1+ix);
}
glEnd();
}
// swap buffers
p=p0; p0=p1; p1=p;
p=n0; n0=n1; n1=p;
}
// arrowhead a=<a1,a2>
for (ix=0,b=0.0,i=0;i<_glCircleN;i++,b+=db)
{
u=r1*cos(b);
v=r1*sin(b);
p1[ix]=x1+(ux*u)+(vx*v); ix++;
p1[ix]=y1+(uy*u)+(vy*v); ix++;
p1[ix]=z1+(uz*u)+(vz*v); ix++;
}
glBegin(GL_TRIANGLE_FAN);
glVertex3f(x1,y1,z1);
for (ix=0;ix<n;ix+=3) glVertex3fv(p1+ix);
glEnd();
x1=x0+(r*cos(a2));
y1=y0+(r*sin(a2));
z1=z0;
glBegin(GL_TRIANGLE_FAN);
glVertex3f(x1,y1,z1);
for (ix=n-3;ix>=0;ix-=3) glVertex3fv(p1+ix);
glEnd();
}
This renders bended arrow in XY plane with center x,y,z and big radius r. The r0 is tube radius and r1 is arrowhead base radius. As I do not have your curve definition I choose circle in XY plane. The a0,a1,a2 are angles where arrow starts (a0), arrowhead starts (a1) and ends (a2). The pi2 is just constant pi2=6.283185307179586476925286766559.
The idea is to remember actual and previous tube segment circle points so there for the ptab,p0,p1 otherwise you would need to compute everything twice.
As I chose XY plane directly then I know that one base vector is normal to it. and second is perpendicular to it and to arrow direction luckily circle properties provides this on its own therefore no need for cross products in this case.
Hope it is clear enough if not comment me.
[Edit2]
I needed to add this to my engine so here is the 3D version (not bound just to axis aligned arrows and the cone is bended too). It is the same except the basis vector computation and I also change the angles a bit in the header <a0,a1> is the whole interval and aa is the arrowhead size but latter in code it is converted to the original convention. I added also normals for lighting computations. I added also linear Arrow where the computation of basis vectors is not taking advantage of circle properties in case you got different curve. Here result:
//---------------------------------------------------------------------------
const int _glCircleN=50; // points per circle
//---------------------------------------------------------------------------
void glCircleArrowxy(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
{
double pos[3]={ x0, y0, z0};
double nor[3]={0.0,0.0,1.0};
double bin[3]={1.0,0.0,0.0};
glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
}
//---------------------------------------------------------------------------
void glCircleArrowyz(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
{
double pos[3]={ x0, y0, z0};
double nor[3]={1.0,0.0,0.0};
double bin[3]={0.0,1.0,0.0};
glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
}
//---------------------------------------------------------------------------
void glCircleArrowxz(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
{
double pos[3]={ x0, y0, z0};
double nor[3]={0.0,1.0,0.0};
double bin[3]={0.0,0.0,1.0};
glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
}
//---------------------------------------------------------------------------
void glCircleArrow3D(double *pos,double *nor,double *bin,double r,double r0,double r1,double a0,double a1,double aa)
{
// const int _glCircleN=20; // points per circle
int e,i,j,N=3*_glCircleN;
double U[3],V[3],u,v;
double a,b,da,db=pi2/double(_glCircleN-1),a2,rr;
double *ptab,*p0,*p1,*n0,*n1,*pp,p[3],q[3],c[3],n[3],tan[3];
// buffers
ptab=new double [12*_glCircleN]; if (ptab==NULL) return;
p0=ptab+(0*_glCircleN);
n0=ptab+(3*_glCircleN);
p1=ptab+(6*_glCircleN);
n1=ptab+(9*_glCircleN);
// prepare angles
a2=a1; da=db; aa=fabs(aa);
if (a0>a1) { da=-da; aa=-aa; }
a1-=aa;
// compute missing basis vectors
vector_copy(U,nor); // U is normal to arrow plane
vector_mul(tan,nor,bin); // tangent is perpendicular to normal and binormal
// arc interpolation a=<a0,a2>
for (e=0,j=0,a=a0;e<5;j++,a+=da)
{
// end conditions
if (e==0) // e=0
{
if ((da>0.0)&&(a>=a1)) { a=a1; e++; }
if ((da<0.0)&&(a<=a1)) { a=a1; e++; }
rr=r0;
}
else{ // e=1,2,3,4
if ((da>0.0)&&(a>=a2)) { a=a2; e++; }
if ((da<0.0)&&(a<=a2)) { a=a2; e++; }
rr=r1*fabs(divide(a-a2,a2-a1));
}
// compute actual tube segment center c[3]
u=r*cos(a);
v=r*sin(a);
vector_mul(p,bin,u);
vector_mul(q,tan,v);
vector_add(c,p, q);
vector_add(c,c,pos);
// V is unit direction from arrow center to tube segment center
vector_sub(V,c,pos);
vector_one(V,V);
// tube segment interpolation
for (b=0.0,i=0;i<N;i+=3,b+=db)
{
u=cos(b);
v=sin(b);
vector_mul(p,U,u); // normal
vector_mul(q,V,v);
vector_add(n1+i,p,q);
vector_mul(p,n1+i,rr); // vertex
vector_add(p1+i,p,c);
}
if (e>1) // recompute normals for cone
{
for (i=3;i<N;i+=3)
{
vector_sub(p,p0+i ,p1+i);
vector_sub(q,p1+i-3,p1+i);
vector_mul(p,p,q);
vector_one(n1+i,p);
}
vector_sub(p,p0 ,p1);
vector_sub(q,p1+N-3,p1);
vector_mul(p,q,p);
vector_one(n1,p);
if (da>0.0) for (i=0;i<N;i+=3) vector_neg(n1+i,n1+i);
if (e== 3) for (i=0;i<N;i+=3) vector_copy(n0+i,n1+i);
}
// render base disc
if (!j)
{
vector_mul(n,U,V);
glBegin(GL_TRIANGLE_FAN);
glNormal3dv(n);
glVertex3dv(c);
if (da<0.0) for (i=N-3;i>=0;i-=3) glVertex3dv(p1+i);
else for (i= 0;i< N;i+=3) glVertex3dv(p1+i);
glEnd();
}
// render tube
else{
glBegin(GL_QUAD_STRIP);
if (da<0.0) for (i=0;i<N;i+=3)
{
glNormal3dv(n1+i); glVertex3dv(p1+i);
glNormal3dv(n0+i); glVertex3dv(p0+i);
}
else for (i=0;i<N;i+=3)
{
glNormal3dv(n0+i); glVertex3dv(p0+i);
glNormal3dv(n1+i); glVertex3dv(p1+i);
}
glEnd();
}
// swap buffers
pp=p0; p0=p1; p1=pp;
pp=n0; n0=n1; n1=pp;
// handle r0 -> r1 edge
if (e==1) a-=da;
if ((e==1)||(e==2)||(e==3)) e++;
}
// release buffers
delete[] ptab;
}
//---------------------------------------------------------------------------
void glLinearArrow3D(double *pos,double *dir,double r0,double r1,double l,double al)
{
// const int _glCircleN=20; // points per circle
int e,i,N=3*_glCircleN;
double U[3],V[3],W[3],u,v;
double a,da=pi2/double(_glCircleN-1),r,t;
double *ptab,*p0,*p1,*n1,*pp,p[3],q[3],c[3],n[3];
// buffers
ptab=new double [9*_glCircleN]; if (ptab==NULL) return;
p0=ptab+(0*_glCircleN);
p1=ptab+(3*_glCircleN);
n1=ptab+(6*_glCircleN);
// compute basis vectors
vector_one(W,dir);
vector_ld(p,1.0,0.0,0.0);
vector_ld(q,0.0,1.0,0.0);
vector_ld(n,0.0,0.0,1.0);
a=fabs(vector_mul(W,p)); pp=p; t=a;
a=fabs(vector_mul(W,q)); if (t>a) { pp=q; t=a; }
a=fabs(vector_mul(W,n)); if (t>a) { pp=n; t=a; }
vector_mul(U,W,pp);
vector_mul(V,U,W);
vector_mul(U,V,W);
for (e=0;e<4;e++)
{
// segment center
if (e==0) { t=0.0; r= r0; }
if (e==1) { t=l-al; r= r0; }
if (e==2) { t=l-al; r= r1; }
if (e==3) { t=l; r=0.0; }
vector_mul(c,W,t);
vector_add(c,c,pos);
// tube segment interpolation
for (a=0.0,i=0;i<N;i+=3,a+=da)
{
u=cos(a);
v=sin(a);
vector_mul(p,U,u); // normal
vector_mul(q,V,v);
vector_add(n1+i,p,q);
vector_mul(p,n1+i,r); // vertex
vector_add(p1+i,p,c);
}
if (e>2) // recompute normals for cone
{
for (i=3;i<N;i+=3)
{
vector_sub(p,p0+i ,p1+i);
vector_sub(q,p1+i-3,p1+i);
vector_mul(p,p,q);
vector_one(n1+i,p);
}
vector_sub(p,p0 ,p1);
vector_sub(q,p1+N-3,p1);
vector_mul(p,q,p);
vector_one(n1,p);
}
// render base disc
if (!e)
{
vector_neg(n,W);
glBegin(GL_TRIANGLE_FAN);
glNormal3dv(n);
glVertex3dv(c);
for (i=0;i<N;i+=3) glVertex3dv(p1+i);
glEnd();
}
// render tube
else{
glBegin(GL_QUAD_STRIP);
for (i=0;i<N;i+=3)
{
glNormal3dv(n1+i);
glVertex3dv(p0+i);
glVertex3dv(p1+i);
}
glEnd();
}
// swap buffers
pp=p0; p0=p1; p1=pp;
}
// release buffers
delete[] ptab;
}
//---------------------------------------------------------------------------
usage:
glColor3f(0.5,0.5,0.5);
glCircleArrowyz(+3.5,0.0,0.0,0.5,0.1,0.2,0.0*deg,+270.0*deg,45.0*deg);
glCircleArrowyz(-3.5,0.0,0.0,0.5,0.1,0.2,0.0*deg,-270.0*deg,45.0*deg);
glCircleArrowxz(0.0,+3.5,0.0,0.5,0.1,0.2,0.0*deg,+270.0*deg,45.0*deg);
glCircleArrowxz(0.0,-3.5,0.0,0.5,0.1,0.2,0.0*deg,-270.0*deg,45.0*deg);
glCircleArrowxy(0.0,0.0,+3.5,0.5,0.1,0.2,0.0*deg,+270.0*deg,45.0*deg);
glCircleArrowxy(0.0,0.0,-3.5,0.5,0.1,0.2,0.0*deg,-270.0*deg,45.0*deg);
glColor3f(0.2,0.2,0.2);
glLinearArrow3D(vector_ld(+2.0,0.0,0.0),vector_ld(+1.0,0.0,0.0),0.1,0.2,2.0,0.5);
glLinearArrow3D(vector_ld(-2.0,0.0,0.0),vector_ld(-1.0,0.0,0.0),0.1,0.2,2.0,0.5);
glLinearArrow3D(vector_ld(0.0,+2.0,0.0),vector_ld(0.0,+1.0,0.0),0.1,0.2,2.0,0.5);
glLinearArrow3D(vector_ld(0.0,-2.0,0.0),vector_ld(0.0,-1.0,0.0),0.1,0.2,2.0,0.5);
glLinearArrow3D(vector_ld(0.0,0.0,+2.0),vector_ld(0.0,0.0,+1.0),0.1,0.2,2.0,0.5);
glLinearArrow3D(vector_ld(0.0,0.0,-2.0),vector_ld(0.0,0.0,-1.0),0.1,0.2,2.0,0.5);
and overview of the arows (on the right side of image):
I am using my vector lib so here are some explanations:
vector_mul(a[3],b[3],c[3]) is cross product a = b x c
vector_mul(a[3],b[3],c) is simple multiplication by scalar a = b.c
a = vector_mul(b[3],c[3]) is dot product a = (b.c)
vector_one(a[3],b[3]) is unit vector a = b/|b|
vector_copy(a[3],b[3]) is just copy a = b
vector_add(a[3],b[3],c[3]) is adding a = b + c
vector_sub(a[3],b[3],c[3]) is substracting a = b - c
vector_neg(a[3],b[3]) is negation a = -b
vector_ld(a[3],x,y,z) is just loading a = (x,y,z)
The pos is the center position of your circle arrow and nor is normal of the plane in which the arrow lies. bin is bi-normal and the angles are starting from this axis. should be perpendicular to nor. r,r0,r1 are the radiuses of the arrow (bend,tube,cone)
The linear arrow is similar the dir is direction of the arrow, l is arrow size and al is arrowhead size.

How to make a parallel lines in html canvas

I have these conditions:
Point A, B and C are created.
Point A to Point B will create a line.
Parallel lines are created depending on the position of Point A, B and C (refer to the figure below).
If you move Point A, the lines will also move but Points B and C remain on their respective positions.
They can be moved at any position.
What I want is to create this:
Consider the figure 1 below (I'm sure you already know this basic 2D geometry but without this my answer would be incomplete):
Coordinates for points A and B are known and we want to find function that can be used to calculate y-coordinate whenever x-coordinate is known, in such a way that point (x,y) lies on the line. From the figure 1:
k = tan(alpha) = (y2 - y1) / (x2 - x1) - the slope of line
Putting coordinates of either A or B into well known line equation y = kx + m we can calculate m to make the equation complete. Having this equation, for any coordinate x we can calculate coordinate y using this equation. The good thing about it is that it doesn't depend on the position of point A and B or slop (angle) of the line - you will have to take care of the special case of vertical/horizontal lines where y/x will be infinite according to this equation.
Back to your question. Take a look at figure 2 below:
We have very similar situation here, there is a line between points A and C, and line between points B and D. I assumed that point A is at the center of the coordinate system! This generally won't be the case but this is really not a restriction as you can perform translation that will put A in the center, then make your calculations and then translate everything back.
Using the technique described at the beginning, you can find the line equation for the line that connects A and C points and for the line that connects B and D points (D coordinates can be easily calculated). Let's assume you did just that:
A-C: y = k1*x (m is zero as line goes through the center A)
B-D: y = k2*x + m2 (m2 is not zero as line doesn't go through the center A)
Finally the algorithm you could use to draw these parallel lines:
Choose a space with which you want to take x-coordinates between x1 and x3. For example, if you want 4 lines this space will be s = (x3 - x1) / 4 and so on.
Set value x_start = x1 + s (and later x_start += s), and calculate y-coordinate using the equation for A-C line y_end = k1*x_start. This will give you point that lies on the line A-C and this is the start of your line.
Similarly, calculate the end point that will lie on the line that connects B and D:
x_end = x2 + s (later x_end += s)
y_end = k2*x_end + m2
Using these equations calculate points (x_start,y_start) and (x_end,y_end) for all lines that you want to draw (there is |x3 - x1| / desired_num_of_lines of them).
You'll have to form new equations each time point A moves out of the current A-C line, as every time this happens the slop of the A-C (and B-D) line changes invalidating the current equations.
I'm not going to write any JS code, but having the logic behind the possible solution should give you more then enough information to move forward with you own implementation.
Always think, when using the Context2D, that using the transforms (translate, rotate, scale), can spare you some math.
With those transforms you can think of your drawing like you would do with a pen : where do you put the pen ? where do you move next (translate) ? do you rotate the page ? do you get closer or further from the page (scale) ?
Here you want to start at A, then move along AC.
Each step on the way, you want to draw the AB vector.
Here's how you could code it, as you see, just simple vector math here, so if you remember that AB vector has (B.x-A.x, B.y-A.y) coordinates, you know most of the math you'll need.
// boilerPlate
var ctx = document.getElementById('cv').getContext('2d');
ctx.strokeStyle = '#000';
// params : Points : {x,y}
var A, B, C;
A = { x: 20, y: 170 };
B = { x: 80, y: 60 };
C = { x: 140, y: 120 };
// param : number of lines to draw.
var stepCount = 5;
// ----------
// compute AB vector = B - A
var AB = { x: B.x - A.x, y: B.y - A.y };
// compute step : ( C - A ) / stepCount
var step = { x: (C.x - A.x) / stepCount, y: (C.y - A.y) / stepCount };
// -- start draw
ctx.save();
// Move pen to A
ctx.translate(A.x, A.y);
for (var i = 0; i <= stepCount; i++) {
// draw AB vector at current position
ctx.lineWidth= ( i==0 || i==stepCount ) ? 2 : 1 ;
drawVector(AB);
// move pen one step further
ctx.translate(step.x, step.y);
}
ctx.restore();
// --
// draws vector V at the current origin ((0,0)) of the context.
function drawVector(V) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(V.x, V.y);
ctx.stroke();
}
// ----------
// legend
drawPoint(A, 'A');
drawPoint(B, 'B');
drawPoint(C, 'C');
function drawPoint(P, name) {
ctx.beginPath();
ctx.arc(P.x, P.y, 3, 0, 6.28);
ctx.fill();
ctx.strokeText(name, P.x + 6, P.y + 6);
}
<canvas id='cv' width=300 height=200></canvas>
Džanan has it right, and in simple terms, you need the X and Y offsets between the starting points of the two lines, i'e' point A and point C. When drawing the line that starts at C, and assuming that it ends at D, you will need to add the same X and Y offsets, e.g., if you draw AB with starting coordinates (100, 150) as follows:
context.beginPath();
context.moveTo(100, 150);
context.lineTo(450, 50);
context.stroke();
And if C has to start at (150, 200), the offset here would be
X: 50, Y:50
so CD would be drawn as
context.beginPath();
context.moveTo(150, 200);
context.lineTo((450+50), (50+50));
context.stroke();
Now this assumes that the length of both the lines are going to be same. If they are to differ, the equation will be slightly more complex.

HTML 5 Canvas rotate mulitple text items around circular point

I'm trying to display numbers around the spokes of a bicycle wheel. In the process of creating the 'spokes' for the wheel I can't seem to get the text to rotate without messing up the rotation of the wheel.
var arr = ['1','2','3','4','5','1','2','3','4','5','1','2','3','4','5','1','2','3','4','5'];
function drawNumber() {
var cID = document.getElementById('cogs');
var canW = cID.width,
canH = cID.height;
if (cID && cID.getContext){
var ctx = cID.getContext('2d');
if(ctx) {
ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
var radian = (Math.PI / 180) * 18;
var i = 0
for (var degrees = 0; degrees < 360; degrees += 18) {
var fillNum = arr[i];
ctx.font = "12pt Arial"
ctx.fillStyle = 'white';
ctx.rotate(radian);
rotateText(fillNum);
i++;
}
ctx.translate(-canW/2, -canH/2);
}
}
}
function rotateText(i){
var cID = document.getElementById('cogs');
ctx = cID.getContext('2d');
ctx.fillText(i, -5, 150);
}
drawNumber();
http://jsfiddle.net/rdo64wv1/8/
If I add a rotate to the rotate text function it doesn't rotate the text, it just moves around the spokes further. Any ideas?
If I understand you correctly, you want to numbers to continue along the spoke direction at 90°. What you mean exactly is a bit unclear as what direction is text continuing at in the first place. Considering that the fiddle shows the text continuing at the y-axis, here is how to draw text with the text result continuing at the x-axis instead (if this is not what you're after, please include a mock-up of what result you expect - just adjust the angle at the commented-out line as needed).
Think of the process as an arm: shoulder is rotated first, then the elbow, both at their pivot points, but elbow is always relative to shoulder angle.
So, first rotate at center of wheel to get the spoke angle. Then translate to the origin of the text along that spoke (x-axis in canvas as 0° points right) to get to the "elbow" pivot point, or origin. Rotate (if needed) and draw text, finally reset transformation and repeat for next number.
Here's an updated example with some additional adjustments:
var arr = ['1','2','3','4','5','1','2','3','4','5','1','2','3','4','5','1','2','3','4','5'];
function drawNumber() {
var cID = document.getElementById('cogs'),
cx = cID.width * 0.5, // we'll only use the center value
cy = cID.height * 0.5;
if (cID && cID.getContext){
var ctx = cID.getContext('2d');
if(ctx) {
ctx.font = "12pt Arial" // set font only once..
ctx.textAlign = "center"; // align to center so we don't
ctx.textBaseline = "middle"; // need to calculate center..
var step = Math.PI * 2 / arr.length; // step based on array length (2xPI=360°)
for (var angle = 0, i = 0; angle < Math.PI * 2; angle += step) {
ctx.setTransform(1,0,0,1,cx, cy); // hard reset transforms + translate
ctx.rotate(angle); // absolute rotate wheel center
ctx.translate(cx - 10, 0); // translate along x axis
//ctx.rotate(-Math.PI*0.5); // 90°, if needed...
ctx.fillText(arr[i++], 0, 0); // draw at new origin
}
}
}
ctx.setTransform(1,0,0,1,0,0); // reset all transforms
}
drawNumber();
<canvas id='cogs' width='300' height='300'></canvas>

Drawing arc to fit angle in html canvas by Circle tangles point with known radius 2D

It's hard to explain the problem. but I'm gonna try my best.
First I make 2 lines, a line contain a start point, and a end point, like this
line = {
startPoint{x: , y:}
endPoint{x: , y:}
}
And then I draw the two lines on the canvas forming something like a corner of a triangle like this.
I now move the lines away from each other with the length Radius*2 like shown below
Then how can i now draw a arc using both endpoint as tangents point, like shown below
Do I need to use arc to this or can I do it with arcto? And if it's arc; how do I then give it of begin drawing and ending point so it draw it like shown on the image in the last figure. Thank for your time, any input helps. Sorry again for the bad description of the problem
UPDATE -
its seems i did not explain my problem fully. So here is a little update. Using the examples giving here. I end up with a Oval circle. an what i 'm trying to get is a round circle between the lines.
Yes, you can use arcTo():
Set your first line start point with moveTo()
then the intersection point between the two lines as first pair
then the end point of the second line (what you call "startpoint line 2") the last pair.
Provide a radius
To actually draw the last line (it's only used for calculation with arcTo()) add a lineTo() for the last point pair in the arc, stroke/fill.
If you want to move the lines apart but do not know the intersection point you have to manually calculate (see getIntersection() in that answer) it by first interpolating the lines beyond their original length.
var ctx = document.querySelector("canvas").getContext("2d");
ctx.moveTo(0, 0); // start point
ctx.arcTo(50, 150, 100, 0, 20); // intersection, outpoint, radius
ctx.lineTo(100, 0); // line from arc-end to outpoint
ctx.translate(130, 0);
ctx.moveTo(0, 0); // start point
ctx.arcTo(50, 150, 80, 50, 8); // intersection, outpoint, radius
ctx.lineTo(80, 50); // line from arc-end to outpoint
ctx.stroke();
<canvas></canvas>
Here is how you can extend the lines to find the intersection point if you move them apart without knowing the intersection point:
function extendLine(line, scale) {
var sx = line.startPoint.x,
sy = line.startPoint.y,
ex = line.endPoint.x,
ey = line.endPoint.y;
return {
startPoint: {x: sx, y: sy},
endPoint: {
x: sx + (ex - sx) * scale,
y: sy + (ey - sy) * scale
}
}
}
Scale can be a ridiculous value as we need to make sure that at very steep angles the lines will intersect somewhere. It does not affect calculation speed.
So then with the two lines (make sure the second line continues from first line's end-point, meaning you'll have to probably reverse the coordinates - if you want to do this dynamically you can measure the distance for each point in the second line to the end point of the first line, the shortest distance comes first as startPoint):
The execution steps would then be:
var line1 = ...,
line2 = ...,
line1tmp = extendLine(line1, 10000),
line2tmp = extendLine(line2, 10000),
ipoint = getIntersection(line1, line2); // see link above
// define the line + arcTo
ctx.moveTo(line1.startPoint.x, line1.startPoint.y);
ctx.arcTo(ipoint.x, ipoint.y,
line2.endPoint.x, line2.endPoint.y,
Math.abs(line2.startPoint.x - line1.endPoint.x) / 2);
ctx.lineTo(line2.endPoint.x, line2.endPoint.y);
ctx.stroke();
Given 2 line segments that meet at a common point, you can apply a rounded intersection to them using a cubic Bezier curve:
Here's how...
Given a point p1 and line segments that extend to from p1 to p0 (P10) and from p1 to p2 (P12):
var p0={x:50,y:50};
var p1={x:100,y:150};
var p2={x:250,y:100};
Calculate points on P10 & P12 that are a specified percent of the way from the common point (p1) back towards their respective starting points (p0 & p2):
var lerp=function(a,b,x){ return(a+x*(b-a)); };
var dx,dy,length;
var offsetPct=0.15;
// calc a point on P10 that is 15% of the way from p1 to p0
dx=p1.x-p0.x;
dy=p1.y-p0.y;
p00={ x:lerp(p1.x,p0.x,offsetPct), y:lerp(p1.y,p0.y,offsetPct) }
// calc a point on P12 that is 15% of the way from p1 to p2
dx=p1.x-p2.x;
dy=p1.y-p2.y;
p22={ x:lerp(p1.x,p2.x,offsetPct), y:lerp(p1.y,p2.y,offsetPct) }
Then you can draw your rounded intersection using the shortened line segments and a cubic Bezier curve:
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var p0={x:50,y:50};
var p1={x:100,y:150};
var p2={x:150,y:50};
roundedIntersection(p0,p1,p2,0.15);
function roundedIntersection(p0,p1,p2,offsetPct){
var lerp=function(a,b,x){ return(a+x*(b-a)); };
var dx,dy,length;
dx=p1.x-p0.x;
dy=p1.y-p0.y;
p00={ x:lerp(p1.x,p0.x,offsetPct), y:lerp(p1.y,p0.y,offsetPct) }
dx=p1.x-p2.x;
dy=p1.y-p2.y;
p22={ x:lerp(p1.x,p2.x,offsetPct), y:lerp(p1.y,p2.y,offsetPct) }
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p00.x,p00.y);
ctx.bezierCurveTo( p1.x,p1.y, p1.x,p1.y ,p22.x,p22.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
dot(p0.x,p0.y);
dot(p1.x,p1.y);
dot(p2.x,p2.y);
dot(p00.x,p00.y);
dot(p22.x,p22.y);
function dot(x,y){
ctx.beginPath();
ctx.arc(x,y,2,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>

Categories