I'm receiving all distances between a random number of points in a 2 dimensional coordinate system.
How can I visualize this as coordinates on a map in my browser?
In case there are many solutions I just want to see the first possible one that my algorithm can come up with.
So here's an extremely easy example:
PointCount = 3
Distances:
0-1 = 2
0-2 = 4
1-2 = 2
Does anyone know an easy way (existing solution/framework maybe) to do it using whatever is out there to make it easier to implement?
I was thinking maybe using the html canvas element for drawing, but I don't know how to create an algorithm that could come up with possible coordinates for those points.
The above example is simplified -
Real distance values could look like this:
(0) (1) (2) (3)
(0) 0 2344 3333 10000
(1) 0 3566 10333
(2) 0 12520
I'm not sure this is relevant for SO, but anyway...
The way to do this is quite simply to place the points one by one using the data:
Pick a random location for the first point (let's say it's 0,0).
The second point is on a circle with radius d(0,1) with the first point as its center, so you can pick any point on the circle. Let's pick (d(0,1),0).
The third point is at the intersection of a circle with radius d(0,2) and center point 1, and a circle with radius d(1,2) and center point 2. You will get either 0, 1, 2 or an infinity of solutions. If the data comes from real points, 0 shouldn't happen. 1 and infinity are edge cases, but you should still handle them. Pick any of the solutions.
The fourth point is at the intersection of 3 circles. Unless you're very unlucky (but you should account for it), there should be only one solution.
Continue like this until all points have been placed.
Note that this doesn't mean you'll get the exact locations of the original points: you can have any combination of a translation (the choice of your first point), rotation (the choice of your second point) and symmetry (the choice of your third point) making the difference.
A quick and dirty implementation (not handling quite a few cases, and tested very little):
function distance(p1, p2) {
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
}
// adapted from https://stackoverflow.com/a/12221389/3527940
function intersection(x0, y0, r0, x1, y1, r1) {
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy * dy) + (dx * dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a / d);
y2 = y0 + (dy * a / d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0 * r0) - (a * a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h / d);
ry = dx * (h / d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [
[xi, yi],
[xi_prime, yi_prime]
];
}
function generateData(nbPoints) {
var i, j, k;
var originalPoints = [];
for (i = 0; i < nbPoints; i++) {
originalPoints.push([Math.random() * 20000 - 10000, Math.random() * 20000 - 10000]);
}
var data = [];
var distances;
for (i = 0; i < nbPoints; i++) {
distances = [];
for (j = 0; j < i; j++) {
distances.push(distance(originalPoints[i], originalPoints[j]));
}
data.push(distances);
}
//console.log("original points", originalPoints);
//console.log("distance data", data);
return data;
}
function findPointsForDistances(data, threshold) {
var points = [];
var solutions;
var solutions1, solutions2;
var point;
var i, j, k;
if (!threshold)
threshold = 0.01;
// First point, arbitrarily set at 0,0
points.push([0, 0]);
// Second point, arbitrarily set at d(0,1),0
points.push([data[1][0], 0]);
// Third point, intersection of two circles, pick any solution
solutions = intersection(
points[0][0], points[0][1], data[2][0],
points[1][0], points[1][1], data[2][1]);
//console.log("possible solutions for point 3", solutions);
points.push(solutions[0]);
//console.log("solution for points 1, 2 and 3", points);
found = true;
// Subsequent points, intersections of n-1 circles, use first two to find 2 solutions,
// the 3rd to pick one of the two
// then use others to check it's valid
for (i = 3; i < data.length; i++) {
// distances to points 1 and 2 give two circles and two possible solutions
solutions = intersection(
points[0][0], points[0][1], data[i][0],
points[1][0], points[1][1], data[i][1]);
//console.log("possible solutions for point " + (i + 1), solutions);
// try to find which solution is compatible with distance to point 3
found = false;
for (j = 0; j < 2; j++) {
if (Math.abs(distance(solutions[j], points[2]) - data[i][2]) <= threshold) {
point = solutions[j];
found = true;
break;
}
}
if (!found) {
console.log("could not find solution for point " + (i + 1));
console.log("distance data", data);
console.log("solution for points 1, 2 and 3", points);
console.log("possible solutions for point " + (i + 1), solutions);
console.log("distances to point 3",
distance(solutions[0], points[2]),
distance(solutions[1], points[2]),
data[i][2]
);
break;
}
// We have found a solution, we need to check it's valid
for (j = 3; j < i; j++) {
if (Math.abs(distance(point, points[j]) - data[i][j]) > threshold) {
console.log("Could not verify solution", point, "for point " + (i + 1) + " against distance to point " + (j + 1));
found = false;
break;
}
}
if (!found) {
console.log("stopping");
break;
}
points.push(point);
}
if (found) {
//console.log("complete solution", points);
return points;
}
}
console.log(findPointsForDistances([
[],
[2344],
[3333, 3566],
[10000, 10333, 12520],
]));
console.log(findPointsForDistances([
[],
[2],
[4, 2],
]));
console.log(findPointsForDistances([
[],
[4000],
[5000, 3000],
[3000, 5000, 4000]
]));
console.log(findPointsForDistances([
[],
[2928],
[4938, 3437],
[10557, 10726, 13535]
]));
var nbPoints, i;
for (nbPoints = 4; nbPoints < 8; nbPoints++) {
for (i = 0; i < 10; i++) {
console.log(findPointsForDistances(generateData(nbPoints)));
}
}
Fiddle here: https://jsfiddle.net/jacquesc/82aqmpnb/15/
Minimum working example. Remember that in canvas coordinates, the y value is inverted but you could do something like:
y = canvasHeight - y
If you also have negative points then if would take a little bit of extra work. Also it may be helpful in that case to draw lines and tick marks to visualize the axis.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let scale = 10;
let radius = 10;
function point(x, y) {
ctx.fillRect(x*scale, y*scale, radius, radius);
}
// test
point(10, 15);
point(20, 8);
<html>
<body>
<canvas id="canvas" width=1000 height=1000></canvas>
</body>
</html>
There are plenty of libraries out there.
chartist.js is easy to use and responsive JavaS cript library. I used it last year for basic charts after trying many others but it was the only one that scaling easily in different screen sizes.
chartJS is another better looking library.
And you can use html5 canvas it's easy and fun but it will take time especially in scaling.
To scale and position, you should use the minimum and maximum values for x and y.
Good luck
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.
I have a simple hexagonal grid, where I'm selecting a group of hexagons, which will then be filled with some random points.
Let me explain the exact procedure of generating the points:
Using a list of hex coordinates I'm selecting hexagons.
Group hexagons into areas.
Store all edge points, for each area individually.
Loop through areas:
Calculate area boundary ( required for definig the range for random point generator )
Draw area (bounding) square. (to see whether the computation was correct)
Based on the bounding square min,max values generate random points
Test if point is inside the area shape. (The shape is defined by area edge points)
If the point passed the above test, it's the pushed into an array.
loop through the array of points and draw them on screen.
Now I tried these two methods to determine whether a point lies inside a specific shape.
cn_PnPoly: function( P, V, n ){ ///// Point , array of vertices , array size ////
var cn = 0, vt = 0.0;
for (var i=0; i< (n-1); i++) { // edge from V[i] to V[i+1]
if (((V[i].y <= P.y) && (V[i+1].y > P.y)) // an upward crossing
|| ((V[i].y > P.y) && (V[i+1].y <= P.y))) { // a downward crossing
// compute the actual edge-ray intersect x-coordinate
vt = (P.y - V[i].y) / (V[i+1].y - V[i].y);
if (P.x < V[i].x + vt * (V[i+1].x - V[i].x)) // P.x < intersect
++cn; // a valid crossing of y=P.y right of P.x
}
}
return (cn&1); // 0 if even (out), and 1 if odd (in)
},
result:
isInHexBounary: function( p, points, l ){ ///// Point , array of vertices , array size ////
var result = false;
for (i = 0, j = l - 1; i < l; j = i++) {
if ((points[i].y > p.y) != (points[j].y > p.y) && (p.x < (points[j].x - points[i].x) * (p.y - points[i].y) / (points[j].y-points[i].y) + points[i].x)) {
result = !result;
}
}
return result;
},
result:
I suppose the first method requires to have all the points in specific order, and that's why it's not working properly.
But the second one seems to be working almost correct , apart from some parts. Any idea what I'm doing wrong?
Update:
It's turning out that for mos tof the algorhytms it's required to have the points in a specific order, so I calculated angle of each point relative to the average center point. And then sorted all the points by angle. Both algorithms now return similar results. Although there is sitll some points leaking through the area shape.
findCenter: function( points ){
var x = 0, y = 0, i, len = points.length;
for (i = 0; i < len; i++) {
x += points[i].x;
y += points[i].y;
}
return {x: x / len, y: y / len};
},
findAngles: function(c, points){
var i, len = points.length, p, dx, dy;
for (i = 0; i < len; i++) {
p = points[i];
dx = p.x - c.x;
dy = p.y - c.y;
p.angle = Math.atan2(dy, dx);
}
},
sortByAngle: function( points ){
points.sort(function(a, b) {
if (a.angle > b.angle) return 1;
else if (a.angle < b.angle) return -1;
return 0;
});
},
result:
Frankly, I would make it simpler.
Make sampling in one single hexagon. Just make bounding box with size (2s,2s cos(30)), sample (x,y) and reject it if it is outside the hexagon. Efficiency should be 3/4 (check hexagon area). Position this sampling hexagon at (0,0) such that sampling is very easy, and, what's more important, very TESTABLE
For each area maintain array of hexagon centers, say, with size N
Sample in two steps. First, sample integer 1...N and select what
hexagon in the area is about to be selected. Second, sample from your
(x,y) from step #1, from your single hexagon at (0,0). Last,
shift sampled (x,y) by randomly selected hexagon center
UPDATE
Actually, I believe there is a way to sample point (x,y) in a single hexagon with 100% efficiency without any rejection/acceptance. Look at picture above. Red is whole hexagon, and rectangular blue area is where you sample points. If sampled point is within red area you take it and move on. If it is not in the red and inside blue A triangle, you map it into black A' triangle. If point is not in the red but in the blue B triangle, you remap it into black B' triangle
Remapping is quite simple linear transformation.
Finally you have sampling with three random numbers as input (one used to select
destination hexagon, and two used to sample random point) and it will guaranteed to return random point somewhere in the desired area
UPDATE II
As noted by Denis Sheremet, better mapping would be A->B' and B->A'. Assuming hexagon center at (0,0), overall just two reflections - one over center and another over middle of the triangle
I implemented Severin Pappadeux's awesome answer in C++ (but should be trivially convertible to any other language):
// randA and randB must be in the range [0, 1]
static glm::vec2 SampleHexagon(float randA, float randB) {
// Algorithm based on https://stackoverflow.com/a/39262805/3841944 by user Severin Pappadeux
//
// Hexagon map:
// ___________________
// | /| |\ |
// | / | | \b'|
// | / a| |A \ |
// |/___| C |___\|
// |\ | | /|
// | \ b| |B / |
// | \ | | /a'|
// | \___________/___|
// | | | <- "startA"
// | | |<-->| <- "widthA"
// | | <- "startC"
// |<----------------->| "width"
//
// C = center part
// a, b = left (upper and lower) part
// A, B = right (upper and lower) part
// a' -> area that will be remapped to a
// b' -> area that will be remapped to b
// The algorithm is to:
// 1. Generate random points in the rectangle spanning C, A, B, b' and a'
// 2. Move all points in a' and b' into a and b
// 3. (Optional) Make hexagon regular
// 4. (Optional) Remap coordinates to range [-1, 1]
// Coordinates are in the range [0, 1]
const float width = 1;
const float widthC = width / 2;
const float widthA = (width - widthC) / 2;
const float startC = widthA;
const float startA = startC + widthC;
const float slope = .5f / widthA;
// 1. Remap x into the rectangle spanning C, A, B, b' and a'
float x = startC + randA * .75f;
float y = randB;
// 2. Move all points in a' and b' into a and b
// If we are in the last third (A, b', B and a')
if (startA < x) {
float localX = x - startA;
if (y > .5f) { // And the upper half of it (in A or b')
float localY = y - .5f;
if (localY > .5f - localX * slope) { // And above the diagonal
// Move the point so it is in the triangle to 'b'
x -= startA;
y -= .5f;
}
} else { // And the lower half of it (in B or a')
float localY = y;
if (localY < localX * slope) { // And we are below the diagonal
// Move the point so it is in the triangle to 'a'
x -= startA;
y += .5f;
}
}
}
// 3. Make to regular hexagon (currently the hexagon is too high, because we assumed width == height)
// Divide the hexagon into 6 regular triangles and use the Pythagorean theorem, and this calculation should be fairly obvious
float regularHexagonWidthToHeightRatio = std::sqrt(1.f*1.f - .5f*.5f);
y = (y - .5f) * regularHexagonWidthToHeightRatio + .5f; // Center around 0, scale, center to .5
// 4. Remap coordinates to -1 to 1 (optional)
x = x * 2 - 1;
y = y * 2 - 1;
return { x, y };
}
Again many thanks to Severin Pappadeux, upvote his answer.