How to create hollow cylinder and truncated cone with JavaFX? - math
I'm learning JavaFX 3D. So far I have not found a way how I can create the following objects:
Hollow cylinder
Truncated cone
Can someone please give me a short code example?
Any help will be appreciated. :-)
I know it's an old question but I've been trying to solve similar problem so here is my solution for truncated cone:
public class Cone extends Group{
int rounds = 360;
int r1 = 100;
int r2 = 50;
int h = 100;
public Cone() {
Group cone = new Group();
PhongMaterial material = new PhongMaterial(Color.BLUE);
float[] points = new float[rounds *12];
float[] textCoords = {
0.5f, 0,
0, 1,
1, 1
};
int[] faces = new int[rounds *12];
for(int i= 0; i<rounds; i++){
int index = i*12;
//0
points[index] = (float)Math.cos(Math.toRadians(i))*r2;
points[index+1] = (float)Math.sin(Math.toRadians(i))*r2;
points[index+2] = h/2;
//1
points[index+3] = (float)Math.cos(Math.toRadians(i))*r1;
points[index+4] = (float)Math.sin(Math.toRadians(i))*r1;
points[index+5] = -h/2;
//2
points[index+6] = (float)Math.cos(Math.toRadians(i+1))*r1;
points[index+7] = (float)Math.sin(Math.toRadians(i+1))*r1;
points[index+8] = -h/2;
//3
points[index+9] = (float)Math.cos(Math.toRadians(i+1))*r2;
points[index+10] = (float)Math.sin(Math.toRadians(i+1))*r2;
points[index+11] = h/2;
}
for(int i = 0; i<rounds ; i++){
int index = i*12;
faces[index]=i*4;
faces[index+1]=0;
faces[index+2]=i*4+1;
faces[index+3]=1;
faces[index+4]=i*4+2;
faces[index+5]=2;
faces[index+6]=i*4;
faces[index+7]=0;
faces[index+8]=i*4+2;
faces[index+9]=1;
faces[index+10]=i*4+3;
faces[index+11]=2;
}
TriangleMesh mesh = new TriangleMesh();
mesh.getPoints().addAll(points);
mesh.getTexCoords().addAll(textCoords);
mesh.getFaces().addAll(faces);
Cylinder circle1 = new Cylinder(r1, 0.1);
circle1.setMaterial(material);
circle1.setTranslateZ( -h / 2);
circle1.setRotationAxis(Rotate.X_AXIS);
circle1.setRotate(90);
Cylinder circle2 = new Cylinder(r2, 0.1);
circle2.setMaterial(material);
circle2.setTranslateZ( h / 2);
circle2.setRotationAxis(Rotate.X_AXIS);
circle2.setRotate(90);
MeshView meshView = new MeshView();
meshView.setMesh(mesh);
meshView.setMaterial(material);
//meshView.setDrawMode(DrawMode.LINE);
cone.getChildren().addAll(meshView);
Rotate r1 = new Rotate(90, Rotate.X_AXIS);
cone.getTransforms().add(r1);
getChildren().addAll(cone);
}
Hope this helps someone in the future!
I started using the code supplied in an other answer on this page, but I wanted to make it as a single mesh without the cylinders in the ends. I also calculated better texture coordinates and normals. (I use Vecmath for the normal calculation. This should probably be changed to Apache commons math or something more modern...)
/**
* Create a cone shape. Origin is center of base (bottom circle). Height is along the Y axis
* #param m A given TirangleMesh
* #param res The resolution of the circles
* #param radius The base radius
* #param topRadius The top radius. If this is > 0 the cone is capped
* #param height The height of the cone along the Y axis
* #param texture The texture
*/
public static void createCone(TriangleMesh m, int res, float radius, float topRadius, float height, TextureMapping texture)
{
if (texture == null)
texture = defaultTextureMapping;
m.setVertexFormat(VertexFormat.POINT_NORMAL_TEXCOORD);
float[] v = new float[res * 6]; //vertices
float[] n = new float[(res+2)*3]; //face normals
float[] uv = new float[(res * 8) + 4]; //texture coordinates
int[] f = new int[18*(2*res-2)]; // faces ((divisions * 18) + ((divisions-2)*18))
float radPerDiv = ((float)Math.PI * 2f) / res;
int tv = res * 3; //top plane vertices start index
int tuv = (res+1) * 2; //top plane uv start index
int bcuv = tuv * 2;//(res * 4) + 4; //bottom cap uv start index
int tcuv = bcuv + (res * 2); //bottom cap uv start index
for(int i = 0; i < res; i++)
{
int vi = i*3;
float cos = (float) Math.cos(radPerDiv*(i));
float sin = (float) Math.sin(radPerDiv*(i));
//bottom plane vertices
v[vi] = cos * radius; //X
v[vi + 1] = 0; //Y
v[vi + 2] = sin * radius; //Z
//top plane vertices
v[tv + vi] = cos * topRadius; //X
v[tv + vi + 1] = height; //Y
v[tv + vi + 2] = sin * topRadius; //Z
int uvi = i*2;
//texture coordinate side down
uv[uvi] = 1f-((float)i/(float)res);
uv[uvi + 1] = 1f;
//texture coordinate side up
uv[tuv + uvi] = uv[uvi];
uv[tuv + uvi + 1] = 0;
//texture coordinate bottom cap
uv[bcuv + uvi] = (1f+cos)/2f;
uv[bcuv + uvi + 1] = (1f+sin)/2f;
//texture coordinate top cap
uv[tcuv + uvi] = (1f-cos)/2f;
uv[tcuv + uvi + 1] = (1f+sin)/2f;
//face normals
if(i>0)
{
Vector3f p0 = new Vector3f(v[vi - 3], v[vi - 2], v[vi - 1]);
Vector3f p1 = new Vector3f(v[vi], v[vi + 1], v[vi + 2]);
Vector3f p2 = new Vector3f(v[tv + vi], v[tv + vi + 1], v[tv + vi + 2]);
p1.sub(p0);
p2.sub(p0);
p0.cross(p2, p1);
p0.normalize();
n[vi - 3] = p0.x;
n[vi - 2] = p0.y;
n[vi - 1] = p0.z;
}
if(i==res-1)
{
Vector3f p0 = new Vector3f(v[vi], v[vi + 1], v[vi + 2]);
Vector3f p1 = new Vector3f(v[0], v[1], v[2]);
Vector3f p2 = new Vector3f(v[tv], v[tv + 1], v[tv + 2]);
p1.sub(p0);
p2.sub(p0);
p0.cross(p2, p1);
p0.normalize();
n[vi] = p0.x;
n[vi + 1] = p0.y;
n[vi + 2] = p0.z;
}
//faces around
int fi = i*18;
//first triangle of face
f[fi] = i; //vertex
f[fi+1] = i; //normal
f[fi+2] = i; //uv
f[fi+3] = res+i; //vertex
f[fi+4] = i; //normal
f[fi+5] = res+1+i; //uv
f[fi+6] = i+1; //vertex
f[fi+7] = i+1; //normal
f[fi+8] = i+1; //uv
//second triangle of face
f[fi+9] = i+1; //vertex
f[fi+10] = i+1; //normal
f[fi+11] = i+1; //uv
f[fi+12] = res+i; //vertex
f[fi+13] = i; //normal
f[fi+14] = res+1+i; //uv
f[fi+15] = res+i+1; //vertex
f[fi+16] = i+1; //normal
f[fi+17] = res+2+i; //uv
//wrap around, use the first vertices/normals
if(i==res-1)
{
f[fi+6] = 0; //vertex
f[fi+9] = 0; //vertex
f[fi+15] = res; //vertex
f[fi+7] = 0; //normal
f[fi+10] = 0; //normal
f[fi+16] = 0; //normal
}
//top and bottom caps
int fi2 = (i*9)+(res*18); //start index for bottom cap. Start after cone side is done
int fi3 = fi2 + (res*9) - 18; //fi2 + ((divisions - 2) * 9) //start index for top cap. Start after the bottom cap is done
int uv2 = (res*2)+2; //start index of bottom cap texture coordinate
int uv3 = (res*3)+2; //start index of top cap texture coordinate
if(i<res-2)
{
//bottom cap
f[fi2] = 0;
f[fi2+1] = res; //normal
f[fi2+2] = uv2; //uv
f[fi2+3] = i+1;
f[fi2+4] = res; //normal
f[fi2+5] = uv2 + i+1; //uv
f[fi2+6] = i+2;
f[fi2+7] = res; //normal
f[fi2+8] = uv2 + i+2; //uv
//top cap
f[fi3] = res;
f[fi3+1] = res + 1; //normal
f[fi3+2] = uv3; //uv
f[fi3+3] = res+i+2;
f[fi3+4] = res + 1; //normal
f[fi3+5] = uv3 + i+2; //uv
f[fi3+6] = res+i+1;
f[fi3+7] = res + 1; //normal
f[fi3+8] = uv3 + i+1; //uv
}
}
//smooth normals
float[] ns = new float[n.length];
Vector3f n0 = new Vector3f();
Vector3f n1 = new Vector3f();
for(int i = 0; i < res; i++)
{
int p0 = i*3;
int p1 = (i-1)*3;
if(i==0)
p1 = (res-1)*3;
n0.set(n[p0], n[p0+1], n[p0+2]);
n1.set(n[p1], n[p1+1], n[p1+2]);
n0.add(n1);
n0.normalize();
ns[p0] = n0.x;
ns[p0+1] = n0.y;
ns[p0+2] = n0.z;
}
int ni = res * 3;
ns[ni + 1] = -1; //bottom cap normal Y axis
ns[ni + 4] = 1; //top cap normal Y axis
uv[tuv-1] = 1; //bottom ring end uv coordinate
//set all data to mesh
m.getPoints().setAll(v);
m.getNormals().setAll(ns);
m.getTexCoords().setAll(uv);
m.getFaces().setAll(f);
}
Edit:
And here is code for a hollow cone (or cylinder, just set both rads to same value) Here I am using JavaFX vectors in stead of Vecmath
/**
* Create a hollow cone shape. Origin is center of base (bottom circle). Height is along the Y axis
* #param m A given TirangleMesh
* #param res The resolution of the circles
* #param radius The base radius
* #param topRadius The top radius. If this is > 0 the cone is capped
* #param height The height of the cone along the Y axis
* #param thickness The thickness of the cone wall
* #param center If the origo shall be in the center, False
* #param texture The texture
*/
public static void createHollowCone(TriangleMesh m, int res, float radius, float topRadius, float height, float thickness, boolean center, TextureMapping texture)
{
if (texture == null)
texture = defaultTextureMapping;
m.setVertexFormat(VertexFormat.POINT_NORMAL_TEXCOORD);
float[] v = new float[res * 12]; //vertices
float[] n = new float[res*3]; //face normals (raw)
float[] uv = new float[(res * 24) + 8]; //texture coordinates
int[] f = new int[res*72]; // faces
float radPerDiv = ((float)Math.PI * 2f) / res;
float buvf = 1-(thickness/radius);
float tuvf = 1-(thickness/topRadius);
int otcvsi = res * 3; //outside top circle vertices start index
int ibcvsi = res * 6; //inside bottom circle vertices start index
int itcvsi = res * 9; //inside top circle vertices start index
int ifsi = res*18; //inside faces start index
int bfsi = res*36; //bottom faces start index
int tfsi = res*54; //bottom faces start index
int tuvsi = (res+1) * 2; //top plane uv start index
int bcuvsi = tuvsi * 2;//(res * 4) + 4; //bottom cap uv start index
int tcuvsi = bcuvsi + (res * 4); //bottom cap uv start index
int bcfuvsi = bcuvsi/2; //bottom cap faces uv start index
int tcfuvsi = tcuvsi/2; //top cap faces uv start index
for(int i = 0; i < res; i++)
{
int vi = i*3;
float cos = (float) Math.cos(radPerDiv*(i));
float sin = (float) Math.sin(radPerDiv*(i));
//outside bottom circle vertices
v[vi] = cos * radius; //X
v[vi + 1] = center?-height/2f:0; //Y
v[vi + 2] = sin * radius; //Z
//outside top circle vertices
v[otcvsi + vi] = cos * topRadius; //X
v[otcvsi + vi + 1] = center?height/2f:height; //Y
v[otcvsi + vi + 2] = sin * topRadius; //Z
//inside bottom circle vertices
v[ibcvsi + vi] = cos * (radius-thickness); //X
v[ibcvsi + vi + 1] = center?-height/2f:0; //Y
v[ibcvsi + vi + 2] = sin * (radius-thickness); //Z
//inside top circle vertices
v[itcvsi + vi] = cos * (topRadius-thickness); //X
v[itcvsi + vi + 1] = center?height/2f:height; //Y
v[itcvsi + vi + 2] = sin * (topRadius-thickness); //Z
int uvi = i*2;
//texture coordinate outer side down
uv[uvi] = 1f-((float)i/(float)res);
uv[uvi + 1] = 1f;
//texture coordinate outer side up
uv[tuvsi + uvi] = uv[uvi];
uv[tuvsi + uvi + 1] = 0;
//texture coordinate bottom
uv[bcuvsi + uvi] = (1f+cos)/2f;
uv[bcuvsi + uvi + 1] = (1f-sin)/2f;
uv[(res*2)+bcuvsi + uvi] = (1f+(cos*buvf))/2f;
uv[(res*2)+bcuvsi + uvi + 1] = (1f-(sin*buvf))/2f;
//texture coordinate top cap
uv[tcuvsi + uvi] = (1f+cos)/2f;
uv[tcuvsi + uvi + 1] = (1f+sin)/2f;
uv[(res*2)+tcuvsi + uvi] = (1f+(cos*tuvf))/2f;
uv[(res*2)+tcuvsi + uvi + 1] = (1f+(sin*tuvf))/2f;
//face normals
if(i>0)
{
Point3D p0 = new Point3D(v[vi - 3], v[vi - 2], v[vi - 1]);
Point3D p1 = new Point3D(v[vi], v[vi + 1], v[vi + 2]);
Point3D p2 = new Point3D(v[otcvsi + vi], v[otcvsi + vi + 1], v[otcvsi + vi + 2]);
p1 = p1.subtract(p0);
p2 = p2.subtract(p0);
p0 = p2.crossProduct(p1);
p0 = p0.normalize();
n[vi - 3] = (float)p0.getX();
n[vi - 2] = (float)p0.getY();
n[vi - 1] = (float)p0.getZ();
}
if(i==res-1)
{
Point3D p0 = new Point3D(v[vi], v[vi + 1], v[vi + 2]);
Point3D p1 = new Point3D(v[0], v[1], v[2]);
Point3D p2 = new Point3D(v[otcvsi], v[otcvsi + 1], v[otcvsi + 2]);
p1 = p1.subtract(p0);
p2 = p2.subtract(p0);
p0 = p2.crossProduct(p1);
p0 = p0.normalize();
n[vi] = (float)p0.getX();
n[vi + 1] = (float)p0.getY();
n[vi + 2] = (float)p0.getZ();
}
int fi = i*18;
//faces around outside
//first triangle of face
f[fi] = i; //vertex
f[fi+1] = i; //normal
f[fi+2] = i; //uv
f[fi+3] = res+i; //vertex
f[fi+4] = i; //normal
f[fi+5] = res+1+i; //uv
f[fi+6] = i+1; //vertex
f[fi+7] = i+1; //normal
f[fi+8] = i+1; //uv
//second triangle of face
f[fi+9] = i+1; //vertex
f[fi+10] = i+1; //normal
f[fi+11] = i+1; //uv
f[fi+12] = res+i; //vertex
f[fi+13] = i; //normal
f[fi+14] = res+1+i; //uv
f[fi+15] = res+i+1; //vertex
f[fi+16] = i+1; //normal
f[fi+17] = res+2+i; //uv
//faces around inside
//first triangle of face
f[ifsi+fi] = (res*2)+i; //vertex
f[ifsi+fi+1] = res+i; //normal
f[ifsi+fi+2] = res-i; //uv
f[ifsi+fi+3] = (res*2)+i+1; //vertex
f[ifsi+fi+4] = res+i+1; //normal
f[ifsi+fi+5] = res-1-i; //uv
f[ifsi+fi+6] = (res*3)+i; //vertex
f[ifsi+fi+7] = res+i; //normal
f[ifsi+fi+8] = res+res+1-i; //uv
//second triangle of face
f[ifsi+fi+9] = (res*2)+i+1; //vertex
f[ifsi+fi+10] = res+i+1; //normal
f[ifsi+fi+11] = res-1-i; //uv
f[ifsi+fi+12] = (res*3)+i+1; //vertex
f[ifsi+fi+13] = res+i+1; //normal
f[ifsi+fi+14] = res+res-i; //uv
f[ifsi+fi+15] = (res*3)+i; //vertex
f[ifsi+fi+16] = res+i; //normal
f[ifsi+fi+17] = res+res+1-i; //uv
//faces on bottom
//first triangle of face
f[bfsi+fi] = i; //vertex 0
f[bfsi+fi+1] = res*2; //normal
f[bfsi+fi+2] = bcfuvsi+i; //uv
f[bfsi+fi+3] = i+1; //vertex 1
f[bfsi+fi+4] = res*2; //normal
f[bfsi+fi+5] = bcfuvsi+i+1; //uv
f[bfsi+fi+6] = (res*2)+i+1; //vertex n+1
f[bfsi+fi+7] = res*2; //normal
f[bfsi+fi+8] = bcfuvsi+res+i+1; //uv
//second triangle of face
f[bfsi+fi+9] = (res*2)+i+1; //vertex n+1
f[bfsi+fi+10] = res*2; //normal
f[bfsi+fi+11] = bcfuvsi+res+i+1; //uv
f[bfsi+fi+12] = (res*2)+i; //vertex n
f[bfsi+fi+13] = res*2; //normal
f[bfsi+fi+14] = bcfuvsi+res+i; //uv
f[bfsi+fi+15] = i; //vertex 0
f[bfsi+fi+16] = res*2; //normal
f[bfsi+fi+17] = bcfuvsi+i; //uv
//faces on top
//first triangle of face
f[tfsi+fi] = res+i; //vertex 0
f[tfsi+fi+1] = res*2+1; //normal
f[tfsi+fi+2] = tcfuvsi+i; //uv
f[tfsi+fi+3] = (res*3)+i; //vertex n
f[tfsi+fi+4] = res*2+1; //normal
f[tfsi+fi+5] = tcfuvsi+res+i; //uv
f[tfsi+fi+6] = (res*3)+i+1; //vertex n+1
f[tfsi+fi+7] = res*2+1; //normal
f[tfsi+fi+8] = tcfuvsi+res+i+1; //uv
//second triangle of face
f[tfsi+fi+9] = (res*3)+i+1; //vertex n+1
f[tfsi+fi+10] = res*2+1; //normal
f[tfsi+fi+11] = tcfuvsi+res+i+1; //uv
f[tfsi+fi+12] = res+i+1; //vertex 1
f[tfsi+fi+13] = res*2+1; //normal
f[tfsi+fi+14] = tcfuvsi+i+1; //uv
f[tfsi+fi+15] = res+i; //vertex 0
f[tfsi+fi+16] = res*2+1; //normal
f[tfsi+fi+17] = tcfuvsi+i; //uv
//wrap around, use the first vertices/normals
if(i==res-1)
{
f[fi+6] = 0; //vertex
f[fi+9] = 0; //vertex
f[fi+15] = res; //vertex
f[ifsi+fi+3] = res*2; //vertex
f[ifsi+fi+9] = res*2; //vertex
f[ifsi+fi+12] = res*3; //vertex
f[bfsi+fi+3] = 0; //vertex
f[bfsi+fi+6] = res*2; //vertex
f[bfsi+fi+9] = res*2; //vertex
f[tfsi+fi+6] = res*3; //vertex
f[tfsi+fi+9] = res*3; //vertex
f[tfsi+fi+12] = res; //vertex
f[fi+7] = 0; //normal
f[fi+10] = 0; //normal
f[fi+16] = 0; //normal
f[ifsi+fi+4] = res; //normal
f[ifsi+fi+10] = res; //normal
f[ifsi+fi+13] = res; //normal
f[bfsi+fi+5] = bcfuvsi; //uv
f[bfsi+fi+8] = bcfuvsi+res; //uv
f[bfsi+fi+11] = bcfuvsi+res; //uv
f[tfsi+fi+8] = tcfuvsi+res; //uv
f[tfsi+fi+11] = tcfuvsi+res; //uv
f[tfsi+fi+14] = tcfuvsi; //uv
}
}
//smooth normals
float[] ns = new float[(n.length*2)+6];
int ni = res * 3;
for(int i = 0; i < res; i++)
{
int p0 = i*3;
int p1 = (i-1)*3;
if(i==0)
p1 = (res-1)*3;
Point3D n0 = new Point3D(n[p0], n[p0+1], n[p0+2]);
Point3D n1 = new Point3D(n[p1], n[p1+1], n[p1+2]);
n0 = n0.add(n1);
n0 = n0.normalize();
ns[p0] = (float)n0.getX();
ns[p0+1] = (float)n0.getY();
ns[p0+2] = (float)n0.getZ();
n0 = n0.multiply(-1);
ns[ni+p0] = (float)n0.getX();
ns[ni+p0+1] = (float)n0.getY();
ns[ni+p0+2] = (float)n0.getZ();
}
ni = res * 6;
ns[ni + 1] = -1; //bottom cap normal Y axis
ns[ni + 4] = 1; //top cap normal Y axis
uv[tuvsi-1] = 1; //bottom ring end uv coordinate
//set all data to mesh
m.getPoints().setAll(v);
m.getNormals().setAll(ns);
m.getTexCoords().setAll(uv);
m.getFaces().setAll(f);
}
The first - create a cylinder, and use two different appearance objects - one for cylinder, side, and one for the two bases. For the two bases, use an invisible appearance (rendering attributes). For info on how to set different appearances to bases, set thread:
http://forum.java.sun.com/thread.jspa?threadID=663825&tstart=0
The second - use a clipping plane to clip of the bottom and top of the cylinder, and you have a hollow cylinder.
Those two methods will give you a hollow cylinder with a COMPLETELY empty interior.
If you're looking to have walls with a given thickness, there is a boolean operations set available on the internet. Create a large cylinder, and subtract a smaller cylinder. For info on the boolean op set, set this thread: http://forum.java.sun.com/thread.jspa?threadID=658612&tstart=0
Finally, it's really pretty easy to create a cylinder's geometry yourself, and it can be automated really easily with a loop. I would create four separate geometries: one for the inner cylinder with the rendered faces on the inside, one for the outer cylinder, with the rendered faces on the outside, one for the base, a disc with a hole in the middle and bottom surfaces rendered, and one for the top, another disc with a hole in the middle, and top surfaces rendered.
That can all be done pretty easily with a triangle strip array.
Related
Calculate other angles based on known rotation angle
I am making a mechanical system animation on OpenGL and having a little trouble calculating the rotations angle of the connecting rods based on a known rotation angle A and the position of the point D. I need to calculate the angle CDE and CBG as well as the position of point E based on angle A and the position of D. But my high school is failing me right now. I have tried several ways but they all lead to nothing. The length of segment DA is also known. Do you have any ideas on how to do that? What should I do?
I have had to make a few assumptions, and while I was finding a solution I forgot to check the labels so below is an image to clarify point and line name, including in red the geometry used to solve. Assumptions. Points A and B are fixed. Lines BC, FC, and DC are all the same length L Point D is constrained to the line EG Angle not labeled is the angle you refer to in the question. The point F is on the circle centered at A. I forgot to label the radius and angle. Point A is at the origin {x: 0, y: 0} I also assume that you know the basics of vector math and that the problem is not finding the angle between lines or vectors, but rather solving to find the points C and D that is giving you troubles (hope so as this is going to be a long answer for me). Solving Depending on the value of L and the position of the constraining line EG there may not be a solution for all positions of F. The method below will result in either some values being NaN or the position of D will be incorrect. Find C Easy start. As A is at the origin then F is at F.x = cos(angle) * radius, F.y = sin(angle) * radius Now find the mid m point on the line FB and the length of the line Bm as b This forms the right triangle mBC and we know the length of BC === L and just calculated length of line Bm === b thus the length of the line mC is (L * L - b * b) ** 0.5 Create a unit vector (normalized) from F to B, rotate it clockwise 90 deg and scale it by the calculated length of mC. Add that vector to the point m and you have C // vector nx = B.x - F.x; ny = B.y - F.y; // Normalize, scale, rotate and add to m to get C. shorthand // mC len of line mC s = mC / (nx * nx + ny * ny) ** 0.5; C.x = m.x - ny * s; C.y = m.y + nx * s; // OR in steps // normalize len = (nx * nx + ny * ny) ** 0.5; nx /= len; ny /= len; // scale to length of mC nx *= mC; ny *= mC; // rotated 90CW and add to m to get C C.x = m.x - ny; C.y = m.y + nx; Find D Now that we have the point C we know that the point D is on the constraining line EG. Thus we know that the point D is at the point where a circle at C or radius L intercepts the line EG However there are two solutions to the intercept of circle and line, the point B is at one of these points if B is on the line EG. If B is not on the line EG then you will have to pick which of the two solutions you want. Likely the point D is the furthest from B There are several methods to find the intercepts of a line and a circle. The following is a little more complex but will help when picking which point to use // line EG as vec vxA = G.x - E.x; vyA = G.y - E.y; // square of length line EG lenA = vxA * vxA + vyA * vyA; // vector from E to C vxB = C.x - E.x; vyB = C.y - E.y; // square of length line EC lenB = vxB * vxB + vyB * vyB; // dot product A.B * - 2 b = -2 * (vxB * vxA + vyB * vyA); // Stuff I forget what its called d = (b * b - 4 * lenA * (lenB - L * L)) ** 0.5; // L is length of CD // is there a solution if not we are done if (isNaN(d)) { return } // there are two solution (even if the same point) // Solutions as unit distances along line EG u1 = (b - d) / (2 * lenA); u2 = (b + d) / (2 * lenA); // this is the one we want The second unit distance is the one that will fit your layout example. So now we just find the point at u2 on the line EG and we have the final point D D.x = E.x + u2 * (G.x - E.x); D.y = E.y + u2 * (G.y - E.y); The angles In your question it is a little ambiguous to me which angles you want. So I will just give you a method to find the angle between to lines. Eg CB and CD Convert both lines to vectors. The cross product of these vectors divided by the square root of the product of the squared lengths gives us the sin of the angle. However we still need the quadrant. We workout which quadrant by checking the sign of the dot product of the two vectors. Note this method will find the the smallest angle between the two lines and is invariant to the order of the lines Note the angle is in radians // vector CB xA = B.x - C.x; yA = B.y - C.y; // vector CD xB = D.x - C.x; yB = D.y - C.y; // square root of the product of the squared lengths l = ((xa * xa + ya * ya) * (xb * xb + yb * yb)) ** 0.5; // if this is 0 then angle between lines is 0 if (l === 0) { return 0 } // return angle angle = Math.asin((xa * yb - ya * xb) / l); // get angle quadrant undefined // if dot of the vectors is < 0 then angle is in quadrants 2 or 3. get angle and return if (xa * xb + ya * yb < 0) { return (angle< 0 ? -Math.PI: Math.PI) - angle; } // else the angle is in quads 1 or 4 so just return the angle return angle; DONE To make sure it all worked I have created an interactive diagram. The code of interest is at the top. Variables names are as in my diagram at top of answer. Most of the code is just cut and paste vector libs and UI stuff unrelated to the answer. To use The diagram will scale to fit the page so click full page if needed. Use the mouse to drag points with white circles around. For example to rotate F around A click and drag it. The white line segment El sets the length of the lines CF, CB, CD. The radius of circle at A is set by moving the white circle point to the right of it. Move mouse out of form to animate. Mouse only interface. Overkill but its done. setTimeout(() => { // points and lines as in diagram of answer const A = new Vec2(-100,100); const B = new Vec2(-240, - 100); const C = new Vec2(); const D = new Vec2(); const E = new Vec2(-300, -100); const F = new Vec2(); const G = new Vec2(200, -100); const AF = new Line2(A, F), FA = new Line2(F, A); const BC = new Line2(B, C), CB = new Line2(C, B); const CD = new Line2(C, D), DC = new Line2(D, C); const EG = new Line2(E, G), GE = new Line2(G, E); const FB = new Line2(F, B), BF = new Line2(B, F); const FC = new Line2(F, C), CF = new Line2(C, F); // Math to find points C and D function findCandD() { F.initPolar(angle, radius).add(A) // Get position of F FB.unitDistOn(0.5, m); // Find point midway between F, B, store as m // Using right triangle m, B, C the hypot BC length is L var c = (FB.length * 0.5) ** 2; // Half the length of FB squared const clLen = (L * L - c) ** 0.5 // Length of line mC FB.asVec(v1).rotate90CW().length = clLen; // Create vector v1 at 90 from FB and length clLen C.init(m).add(v1); // Add v1 to m to get point C const I = EG.unitInterceptsCircle(C, L, cI); // Point D is L dist from if (EG.unitInterceptsCircle(C, L, cI)) { // Point D is L dist from C. thus us the intercept of corcle radius L and constraining line EG EG.unitDistanceOn(cI.y, D) // Use second intercept as first could be at point B } else { C.x = NaN } // C is too far from constraining line EG for a solution // At this point, the line CD may be the wrong length. Check the length CD is correct blk = Math.isSmall(CD.length - L) ? black : red; // Mark all in red if no solution } // Here on down UI, and all the support code requestAnimationFrame(update); const refRes = 512; var scale = 1; const mousePos = new Vec2(); var w = 0, h = 0, cw = 0, ch = 0; var frame = 0; const m = new Vec2(); // holds mid point on line BF const m1 = new Vec2(); const v1 = new Vec2(); // temp vector const v2 = new Vec2(); // temp vector const cI = new Vec2(); // circle intercepts var radius = 100; var L = 200 var angle = 1; const aa = new Vec2(A.x + radius, A.y); const al = new Vec2(E.x + L, E.y); const rad = new Line2(A, aa); const cl = new Line2(m, C) const armLen = new Line2(E, al); var blk = "#000" const wht = "#FFF" const red = "#F00" const black = "#000" const ui = Vecs2([A, B, aa, E, G, al, F]) function update(timer){ frame ++; ctx.setTransform(1,0,0,1,0,0); // reset transform if (w !== innerWidth || h !== innerHeight){ cw = (w = canvas.width = innerWidth) / 2; ch = (h = canvas.height = innerHeight) / 2; scale = Math.min(w / refRes, h / refRes); } else { ctx.clearRect(0, 0, w, h); } ctx.clearRect(0, 0, canvas.width, canvas.height); mousePos.init(mouse); mousePos.x = (mousePos.x - canvas.width / 2) / scale; mousePos.y = (mousePos.y -canvas.height / 2) / scale; mousePos.button = mouse.button; ctx.font = "24px Arial black" ctx.textAlign = "center"; ctx.setTransform(scale,0,0,scale,canvas.width / 2, canvas.height / 2); const nearest = ui.dragable(mousePos, 20); if (nearest === A) { aa.y = A.y aa.x = A.x + radius; } else if(nearest === F){ angle = A.directionTo(F); } else if(nearest === aa){ aa.y = A.y radius = rad.length; } else if (nearest === E) { EG.distanceAlong(L, al) } else if (nearest === G || nearest === al) { EG.nearestOnLine(al, al) L = armLen.length; } if (nearest) { canvas.style.cursor = ui.dragging ? "none" : "move"; nearest.draw(ctx, "#F00", 2, 4); if (nearest.isLine2) { nearest.nearestOnLine(mousePos, onLine).draw(ctx, "#FFF", 2, 2) } } else { canvas.style.cursor = "default"; } angle += SPEED; findCandD(); ui.mark(ctx, wht, 1, 4); ui.mark(ctx, wht, 1, 14); armLen.draw(ctx,wht,2) EG.draw(ctx, wht, 1) ctx.fillStyle = wht; ctx.fillText("E", E.x, E.y - 16) ctx.fillText("G", G.x, G.y - 16) ctx.fillText("l", armLen.p2.x, armLen.p2.y - 16) FC.draw(ctx, blk, 4) BC.draw(ctx, blk, 4) CD.draw(ctx, blk, 4) A.draw(ctx, blk, 2, radius); C.draw(ctx, blk, 4, 4) F.draw(ctx, blk, 4, 4) B.draw(ctx, blk, 4, 4); D.draw(ctx, blk, 4, 4) ctx.fillStyle = blk; ctx.fillText("B", B.x, B.y - 16) ctx.fillText("A", A.x, A.y - 16) ctx.fillText("F", F.x, F.y + 26) ctx.fillText("D", D.x, D.y - 16) ctx.fillText("C", C.x, C.y - 16) ctx.font = "16px Arial"; drawAngle(C, CD, CB, 40, B.add(Vec2.Vec(60, -50), Vec2.Vec()), ctx, blk, 2); drawAngle(C, CF, CB, 50, A.add(Vec2.Vec(-160, 0), Vec2.Vec()), ctx, blk, 2); drawAngle(C, CD, CF, 60, A.add(Vec2.Vec(300, 20), Vec2.Vec()), ctx, blk, 2); blk = Math.isSmall(CD.length - L) ? black : red; requestAnimationFrame(update); } }, 0); const ctx = canvas.getContext("2d"); const mouse = {x: 0, y: 0, ox: 0, oy: 0, button: false, callback: undefined} function mouseEvents(e) { const bounds = canvas.getBoundingClientRect(); mouse.x = e.pageX - bounds.left - scrollX; mouse.y = e.pageY - bounds.top - scrollY; mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button; } ["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents)); var SPEED = 0.05; canvas.addEventListener("mouseover",() => SPEED = 0); canvas.addEventListener("mouseout",() => SPEED = 0.05); Math.EPSILON = 1e-6; Math.isSmall = val => Math.abs(val) < Math.EPSILON; Math.isUnit = u => !(u < 0 || u > 1); Math.uClamp = u => u <= 0 ? 0 : u >= 1 ? 1 : u; // almost 2* faster than Math.min, Math.Max method Math.TAU = Math.PI * 2; Math.rand = (m, M) => Math.random() * (M - m) + m; Math.randI = (m, M) => Math.random() * (M - m) + m | 0; Math.rad2Deg = r => r * 180 / Math.PI; Math.symbols = {}; Math.symbols.degrees = "°"; /* export {Vec2, Line2} */ // this should be a module var temp; function Vec2(x = 0, y = (temp = x, x === 0 ? (x = 0 , 0) : (x = x.x, temp.y))) { this.x = x; this.y = y } Vec2.Vec = (x, y) => ({x, y}); // Vec2 like Vec2.prototype = { isVec2: true, init(x, y = (temp = x, x = x.x, temp.y)) { this.x = x; this.y = y; return this }, // assumes x is a Vec2 if y is undefined initPolar(dir, length = (temp = dir, dir = dir.x, temp.y)) { this.x = Math.cos(dir) * length; this.y = Math.sin(dir) * length; return this }, toPolar(res = this) { const dir = this.direction, len = this.length; res.x = dir; res.y = length; return res; }, zero() { this.x = this.y = 0; return this }, initUnit(dir) { this.x = Math.cos(dir); this.y = Math.sin(dir); return this }, copy() { return new Vec2(this) }, equal(v) { return (this.x - v.x) === 0 && (this.y - v.y) === 0 }, isUnits() { return Math.isUnit(this.x) && Math.isUnit(this.y) }, add(v, res = this) { res.x = this.x + v.x; res.y = this.y + v.y; return res }, addScaled(v, scale, res = this) { res.x = this.x + v.x * scale; res.y = this.y + v.y * scale; return res }, sub(v, res = this) { res.x = this.x - v.x; res.y = this.y - v.y; return res }, scale(val, res = this) { res.x = this.x * val; res.y = this.y * val; return res }, invScale(val, res = this) { res.x = this.x / val; res.y = this.y / val; return res }, dot(v) { return this.x * v.x + this.y * v.y }, uDot(v, div) { return (this.x * v.x + this.y * v.y) / div }, cross(v) { return this.x * v.y - this.y * v.x }, uCross(v, div) { return (this.x * v.y - this.y * v.x) / div }, get direction() { return Math.atan2(this.y, this.x) }, set direction(dir) { this.initPolar(dir, this.length) }, get length() { return this.lengthSqr ** 0.5 }, set length(l) { this.scale(l / this.length) }, get lengthSqr() { return this.x * this.x + this.y * this.y }, set lengthSqr(lSqr) { this.scale(lSqr ** 0.5 / this.length) }, distanceFrom(vec) { return ((this.x - vec.x) ** 2 + (this.y - vec.y) ** 2) ** 0.5 }, distanceSqrFrom(vec) { return ((this.x - vec.x) ** 2 + (this.y - vec.y) ** 2) }, directionTo(vec) { return Math.atan2(vec.y - this.y, vec.x - this.x) }, normalize(res = this) { return this.invScale(this.length, res) }, rotate90CW(res = this) { const y = this.x; res.x = -this.y; res.y = y; return res; }, angleTo(vec) { const xa = this.x, ya = this.y; const xb = vec.x, yb = vec.y; const l = ((xa * xa + ya * ya) * (xb * xb + yb * yb)) ** 0.5; var ang = 0; if (l !== 0) { ang = Math.asin((xa * yb - ya * xb) / l); if (xa * xb + ya * yb < 0) { return (ang < 0 ? -Math.PI: Math.PI) - ang } } return ang; }, drawFrom(v, ctx, col = ctx.strokeStyle, lw = ctx.lineWidth, scale = 1) { ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.beginPath(); ctx.lineTo(v.x, v.y); ctx.lineTo(v.x + this.x * scale, v.y + this.y * scale); ctx.stroke(); }, draw(ctx, col = ctx.strokeStyle, lw = ctx.lineWidth, size = 4) { ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.beginPath(); ctx.arc(this.x, this.y, size, 0, Math.TAU); ctx.stroke(); }, path(ctx, size) { ctx.moveTo(this.x + size, this.y); ctx.arc(this.x, this.y, size, 0, Math.TAU); }, toString(digits = 3) { return "{x: " + this.x.toFixed(digits) + ", y: " + this.y.toFixed(digits) + "}" }, }; function Vecs2(vecsOrLength) { const vecs2 = Object.assign([], Vecs2.prototype); if (Array.isArray(vecsOrLength)) { vecs2.push(...vecsOrLength) } else if (vecsOrLength && vecsOrLength >= 1) { while (vecsOrLength-- > 0) { vecs2.push(new Vec2()) } } return vecs2; } Vecs2.prototype = { isVecs2: true, nearest(vec, maxDist = Infinity, tolerance = 1) { // max for argument semantic, used as semantic min in function var found; for (const v of this) { const dist = v.distanceFrom(vec); if (dist < maxDist) { if (dist <= tolerance) { return v } maxDist = dist; found = v; } } return found; }, copy() { var idx = 0; const copy = Vecs2(this.length); for(const p of this) { copy[idx++].init(p) } return copy; }, uniformTransform(rMat, pMat, res = this) { var idx = 0; for(const p of this) { p.uniformTransform(rMat, pMat, res[idx++]) } }, mark(ctx, col = ctx.strokeStyle, lw = ctx.lineWidth, size = 4) { ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.beginPath(); for (const p of this) { p.path(ctx, size) } ctx.stroke(); }, draw(ctx, close = false, col = ctx.strokeStyle, lw = ctx.lineWidth) { ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.beginPath(); for (const p of this) { ctx.lineTo(p.x, p.y) } close && ctx.closePath(); ctx.stroke(); }, path(ctx, first = true) { for (const p of this) { if (first) { first = false; ctx.moveTo(p.x, p.y); } else { ctx.lineTo(p.x, p.y) } } }, dragable(mouse, maxDist = Infinity, tolerance = 1) { var near; if (this.length) { if (!this.dragging) { if (!this.offset) { this.offset = new Vec2() } near = this.nearest(this.offset.init(mouse), maxDist, tolerance); // mouse may not be a Vec2 if (near && mouse.button) { this.dragging = near; this.offset.init(near).sub(mouse); } } if (this.dragging) { near = this.dragging; if (mouse.button) { this.dragging.init(mouse).add(this.offset) } else { this.dragging = undefined } } } return near; } } function Line2(p1 = new Vec2(), p2 = (temp = p1, p1 = p1.p1 ? p1.p1 : p1, temp.p2 ? temp.p2 : new Vec2())) { this.p1 = p1; this.p2 = p2; } Line2.prototype = { isLine2: true, init(p1, p2 = (temp = p1, p1 = p1.p1, temp.p2)) { this.p1.init(p1); this.p2.init(p2) }, copy() { return new Line2(this) }, asVec(res = new Vec2()) { return this.p2.sub(this.p1, res) }, unitDistOn(u, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(u).add(this.p1) }, unitDistanceOn(u, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(u).add(this.p1) }, distAlong(dist, res = new Vec2()) { return this.p2.sub(this.p1, res).uDot(res, res.length).add(this.p1) }, distanceAlong(dist, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(dist / res.length).add(this.p1) }, get length() { return this.lengthSqr ** 0.5 }, get lengthSqr() { return (this.p1.x - this.p2.x) ** 2 + (this.p1.y - this.p2.y) ** 2 }, get direction() { return this.asVec(wV2).direction }, translate(vec, res = this) { this.p1.add(vec, res.p1); this.p2.add(vec, res.p2); return res; }, reflect(line, u, res = line) { this.asVec(wV2).normalize(); line.asVec(wV1); line.unitDistOn(u, res.p1); const d = wV1.uDot(wV2, 0.5); wV3.init(wV2.x * d - wV1.x, wV2.y * d - wV1.y); res.p1.add(wV3.scale(1 - u), res.p2); return res; }, reflectAsUnitVec(line, u, res = new Vec2()) { this.asVec(res).normalize(); line.asVec(wV1); return res.scale(wV1.uDot(res, 0.5)).sub(wV1).normalize() }, angleTo(line) { return this.asVec(wV1).angleTo(line.asVec(wV2)) }, translateNormal(amount, res = this) { this.asVec(wV1).rot90CW().length = -amount; this.translate(wV1, res); return res; }, distanceNearestVec(vec) { // WARNING!! distanceLineFromVec is (and others are) dependent on vars used in this function return this.asVec(wV1).uDot(vec.sub(this.p1, wV2), wV1.length); }, unitNearestVec(vec) { // WARNING!! distanceLineFromVec is (and others are) dependent on vars used in this function return this.asVec(wV1).uDot(vec.sub(this.p1, wV2), wV1.lengthSqr); }, nearestOnLine(vec, res = new Vec2()) { return this.p1.addScaled(wV1, this.unitNearestVec(vec), res) }, nearestOnSegment(vec, res = new Vec2()) { return this.p1.addScaled(wV1, Math.uClamp(this.unitNearestVec(vec)), res) }, distanceLineFromVec(vec) { return this.nearestOnLine(vec, wV1).sub(vec).length }, distanceSegmentFromVec(vec) { return this.nearestOnSegment(vec, wV1).sub(vec).length }, unitInterceptsLine(line, res = new Vec2()) { // segments this.asVec(wV1); line.asVec(wV2); const c = wV1.cross(wV2); if (Math.isSmall(c)) { return } wV3.init(this.p1).sub(line.p1); res.init(wV1.uCross(wV3, c), wV2.uCross(wV3, c)); return res; }, unitInterceptsCircle(point, radius, res = new Vec2()) { this.asVec(wV1); var b = -2 * this.p1.sub(point, wV2).dot(wV1); const c = 2 * wV1.lengthSqr; const d = (b * b - 2 * c * (wV2.lengthSqr - radius * radius)) ** 0.5 if (isNaN(d)) { return } return res.init((b - d) / c, (b + d) / c); }, draw(ctx, col = ctx.strokeStyle, lw = ctx.lineWidth) { ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.beginPath(); ctx.lineTo(this.p1.x, this.p1.y); ctx.lineTo(this.p2.x, this.p2.y); ctx.stroke(); }, path(ctx) { ctx.moveTo(this.p1.x, this.p1.y); ctx.lineTo(this.p2.x, this.p2.y); }, toString(digits = 3) { return "{ p1: " + this.p1.toString(digits) + ", p2: " + this.p2.toString(digits) + "}" }, }; const wV1 = new Vec2(), wV2 = new Vec2(), wV3 = new Vec2(); // pre allocated work vectors used by Line2 functions const wVA1 = new Vec2(), wVA2 = new Vec2(), wVA3 = new Vec2(); // pre allocated work vectors const wVL1 = new Vec2(), wVL2 = new Vec2(), wVL3 = new Vec2(); // pre allocated work vectors used by Line2Array functions const wL1 = new Line2(), wL2 = new Line2(), wL3 = new Line2(); // pre allocated work lines function drawLable(text, from, to, ctx, col = ctx.strokeStyle, lw = ctx.lineWidth) { ctx.fillStyle = ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.beginPath(); ctx.lineTo(from.x, from.y); ctx.lineTo(to.x, to.y); ctx.stroke(); const w = ctx.measureText(text).width; var offset = 8; if (from.x < to.x) { ctx.fillText(text, to.x + offset + w / 2, to.y) } else { ctx.fillText(text, to.x - offset - w / 2, to.y) } } function drawAngle(pos, lineA, lineB, radius, lablePos, ctx, col = ctx.strokeStyle, lw = ctx.lineWidth) { ctx.strokeStyle = col; ctx.lineWidth = lw; const from = lineA.direction; const angle = lineA.angleTo(lineB); ctx.beginPath(); ctx.arc(pos.x, pos.y, radius, from, from + angle, angle < 0); ctx.stroke(); drawLable( Math.rad2Deg(angle).toFixed(2) + Math.symbols.degrees, Vec2.Vec( pos.x + Math.cos(from + angle / 2) * radius, pos.y + Math.sin(from + angle / 2) * radius ), lablePos, ctx, col, lw / 2, ); } canvas { position : absolute; top : 0px; left : 0px; background: #4D8; } <canvas id="canvas"></canvas>
What is the most practical way to create coordinate grid with JavaFX 3D?
I would like to create a 3D demo application with JavaFX to visualize movements of points in 3D space and first I need to set up a coordinate grid for visual reference. Unfortunately, I was not able to find a sample code for a grid like in this picture: Does anyone know what is the most practical way to create something like it?
There are a few solutions out there already. FXyz3D library has a CubeWorld class, that gives you precisely a reference grid. It is quite easy to use. Just import the 'org.fxyz3d:fxyz3d:0.3.0' dependency from JCenter and use it: CubeWorld cubeWorld = new CubeWorld(5000, 500, true); Sphere sphere = new Sphere(100); sphere.setMaterial(new PhongMaterial(Color.FIREBRICK)); sphere.getTransforms().add(new Translate(100, 200, 300)); Scene scene = new Scene(new Group(cubeWorld, sphere), 800, 800, true, SceneAntialiasing.BALANCED); As you can see, the solution is based on using 2D rectangles for each face, and the grid lines are created with 3D cylinders. It has very nice features (like self lightning or frontal faces according to camera don't show grid), but it is quite intensive in nodes (sample above has 168 nodes). There are other solutions that use a lower number of nodes. For instance, for this sample, that also happens to be related to Leap Motion, I used a TriangleMesh. This is an easy solution, and with just two meshes. However, you see the triangles, instead of squares. So let's try to get rid of the triangles. For that I'll use a PolygonMesh, as in this other question, based on the 3DViewer project that is available at the OpenJFX repository, contains already a PolygonalMesh implementation, that allows any number of points per face, so any polygon can be a face. This will give you a plane grid based in square faces: private PolygonMesh createQuadrilateralMesh(float width, float height, int subDivX, int subDivY) { final float minX = - width / 2f; final float minY = - height / 2f; final float maxX = width / 2f; final float maxY = height / 2f; final int pointSize = 3; final int texCoordSize = 2; // 4 point indices and 4 texCoord indices per face final int faceSize = 8; int numDivX = subDivX + 1; int numVerts = (subDivY + 1) * numDivX; float points[] = new float[numVerts * pointSize]; float texCoords[] = new float[numVerts * texCoordSize]; int faceCount = subDivX * subDivY; int faces[][] = new int[faceCount][faceSize]; // Create points and texCoords for (int y = 0; y <= subDivY; y++) { float dy = (float) y / subDivY; double fy = (1 - dy) * minY + dy * maxY; for (int x = 0; x <= subDivX; x++) { float dx = (float) x / subDivX; double fx = (1 - dx) * minX + dx * maxX; int index = y * numDivX * pointSize + (x * pointSize); points[index] = (float) fx; points[index + 1] = (float) fy; points[index + 2] = 0.0f; index = y * numDivX * texCoordSize + (x * texCoordSize); texCoords[index] = dx; texCoords[index + 1] = dy; } } // Create faces int index = 0; for (int y = 0; y < subDivY; y++) { for (int x = 0; x < subDivX; x++) { int p00 = y * numDivX + x; int p01 = p00 + 1; int p10 = p00 + numDivX; int p11 = p10 + 1; int tc00 = y * numDivX + x; int tc01 = tc00 + 1; int tc10 = tc00 + numDivX; int tc11 = tc10 + 1; faces[index][0] = p00; faces[index][1] = tc00; faces[index][2] = p10; faces[index][3] = tc10; faces[index][4] = p11; faces[index][5] = tc11; faces[index][6] = p01; faces[index++][7] = tc01; } } int[] smooth = new int[faceCount]; PolygonMesh mesh = new PolygonMesh(points, texCoords, faces); mesh.getFaceSmoothingGroups().addAll(smooth); return mesh; } So you can use 2 or 3 of them to create a coordinate system like this: public Group createGrid(float size, float delta) { if (delta < 1) { delta = 1; } final PolygonMesh plane = createQuadrilateralMesh(size, size, (int) (size / delta), (int) (size / delta)); final PolygonMesh plane2 = createQuadrilateralMesh(size, size, (int) (size / delta / 5), (int) (size / delta / 5)); PolygonMeshView meshViewXY = new PolygonMeshView(plane); meshViewXY.setDrawMode(DrawMode.LINE); meshViewXY.setCullFace(CullFace.NONE); PolygonMeshView meshViewXZ = new PolygonMeshView(plane); meshViewXZ.setDrawMode(DrawMode.LINE); meshViewXZ.setCullFace(CullFace.NONE); meshViewXZ.getTransforms().add(new Rotate(90, Rotate.X_AXIS)); PolygonMeshView meshViewYZ = new PolygonMeshView(plane); meshViewYZ.setDrawMode(DrawMode.LINE); meshViewYZ.setCullFace(CullFace.NONE); meshViewYZ.getTransforms().add(new Rotate(90, Rotate.Y_AXIS)); PolygonMeshView meshViewXY2 = new PolygonMeshView(plane2); meshViewXY2.setDrawMode(DrawMode.LINE); meshViewXY2.setCullFace(CullFace.NONE); meshViewXY2.getTransforms().add(new Translate(size / 1000f, size / 1000f, 0)); PolygonMeshView meshViewXZ2 = new PolygonMeshView(plane2); meshViewXZ2.setDrawMode(DrawMode.LINE); meshViewXZ2.setCullFace(CullFace.NONE); meshViewXZ2.getTransforms().add(new Translate(size / 1000f, size / 1000f, 0)); meshViewXZ2.getTransforms().add(new Rotate(90, Rotate.X_AXIS)); PolygonMeshView meshViewYZ2 = new PolygonMeshView(plane2); meshViewYZ2.setDrawMode(DrawMode.LINE); meshViewYZ2.setCullFace(CullFace.NONE); meshViewYZ2.getTransforms().add(new Translate(size / 1000f, size / 1000f, 0)); meshViewYZ2.getTransforms().add(new Rotate(90, Rotate.Y_AXIS)); return new Group(meshViewXY, meshViewXY2, meshViewXZ, meshViewXZ2 /*, meshViewYZ, meshViewYZ2 */); } Note that I've duplicated the plane to mock a wider stroke every 5 lines. Finally adding axes: public Group getAxes(double scale) { Cylinder axisX = new Cylinder(1, 200); axisX.getTransforms().addAll(new Rotate(90, Rotate.Z_AXIS), new Translate(0, -100, 0)); axisX.setMaterial(new PhongMaterial(Color.RED)); Cylinder axisY = new Cylinder(1, 200); axisY.getTransforms().add(new Translate(0, 100, 0)); axisY.setMaterial(new PhongMaterial(Color.GREEN)); Cylinder axisZ = new Cylinder(1, 200); axisZ.setMaterial(new PhongMaterial(Color.BLUE)); axisZ.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS), new Translate(0, 100, 0)); Group group = new Group(axisX, axisY, axisZ); group.getTransforms().add(new Scale(scale, scale, scale)); return group; } Now you have: final Group axes = getAxes(0.5); final Group grid = createGrid(200, 10); final Sphere sphere = new Sphere(5); sphere.getTransforms().add(new Translate(20, 15, 40)); Scene scene = new Scene(new Group(axes, grid, sphere), 800, 800, true, SceneAntialiasing.BALANCED); The total amount of nodes of this sample is 14. Of course, it can be improved to add labels and many other features.
Collision detection between two objects
The collision is not working According to that post Collision detection between 2 "linearly" moving objects in WGS84, I have the following data EDIT: I have updated the data for a collision that should occur in 10 seconds. m_sPosAV = {North=48.276111971715515 East=17.921031349301817 Altitude=6000.0000000000000 } Poi_Position = {North=48.806113707277042 East=17.977161602106488 Altitude=5656.0000000000000 } velocity.x = -189.80000000000001 // m/s velocity.y = -39.800000000000004 // m/s velocity.z = 9 // m/s m_sVelAV = {x=1.0000000000000000 y=1.0000000000000000 z=0.00000000000000000 } // m/s void WGS84toXYZ(double &x, double &y, double &z, double lon, double lat, double alt) { const double _earth_a = 6378141.4; // [m] equator radius const double _earth_b = 6356755.0; // [m] polar radius double a, b, h, l, c, s; a = lon; b = lat; h = alt; c = cos(b); s = sin(b); h = h + sqrt((_earth_a*_earth_a*c*c) + (_earth_b*_earth_b*s*s)); z = h*s; l = h*c; x = l*cos(a); y = l*sin(a); } bool CPoiFilterCollision::collisionDetection(const CPoiItem& poi) { const double _min_t = 10; // min_time const double _max_d = 500; // max_distance const double _max_t = 0.001; // max_time double dt; double d0, d1; double xAv, yAv, zAv; double xPoi, yPoi, zPoi; double x, y, z; double Ux, Uy, Uz; // [m] double Vx, Vy, Vz; // [m] double Wx, Wy, Wz; // [m] double da = 1.567e-7; // [rad] angular step ~ 1.0 m in lon direction double dl = 1.0; const double deg = pi / 180.0; // [m] altitide step 1.0 m WGS84toXYZ(xAv, yAv, zAv, m_sPosAV.GetLongitude(), m_sPosAV.GetLatitude(), m_sPosAV.GetAltitude()); // actual position WGS84toXYZ(xPoi, yPoi, zPoi, poi.Position().GetLongitude(), poi.Position().GetLatitude(), poi.Position().GetAltitude()); // actual position WGS84toXYZ(Ux, Uy, Uz, m_sPosAV.GetLongitude() + da, m_sPosAV.GetLatitude(), m_sPosAV.GetAltitude()); // lon direction Nort WGS84toXYZ(Vx, Vy, Vz, m_sPosAV.GetLongitude(), m_sPosAV.GetLatitude() + da, m_sPosAV.GetAltitude()); // lat direction East WGS84toXYZ(Wx, Wy, Wz, m_sPosAV.GetLongitude(), m_sPosAV.GetLatitude(), m_sPosAV.GetAltitude() + dl); // alt direction High/Up Ux -= xAv; Uy -= yAv; Uz -= zAv; Vx -= xAv; Vy -= yAv; Vz -= zAv; Wx -= xAv; Wy -= yAv; Wz -= zAv; normalize(Ux, Uy, Uz); normalize(Vx, Vy, Vz); normalize(Wx, Wy, Wz); double vx = m_sVelAV.x*Ux + m_sVelAV.y*Vx + m_sVelAV.z*Wx; double vy = m_sVelAV.x*Uy + m_sVelAV.y*Vy + m_sVelAV.z*Wy; double vz = m_sVelAV.x*Uz + m_sVelAV.y*Vz + m_sVelAV.z*Wz; const QList<QVariant> velocity = poi.Property(QLatin1String("VELOCITY")).toList(); if (velocity.size() == 3) { dt = _max_t; x = xAv - xPoi; y = yAv - yPoi; z = zAv - zPoi; d0 = sqrt((x*x) + (y*y) + (z*z)); x = xAv - xPoi + (vx - velocity.at(0).toDouble())*dt; y = yAv - yPoi + (vy - velocity.at(1).toDouble())*dt; z = zAv - zPoi + (vz - velocity.at(2).toDouble())*dt; d1 = sqrt((x*x) + (y*y) + (z*z)); if (d0 <= _max_d) { return true; } if (d0 <= d1) { return false; } double t = (_max_d - d0)*dt / (d1 - d0); if (t < _min_t) { qDebug() << "Collision at time " << t; return true; } } return false; }
Javafx 8 3D Complex Shape
I want to create complex shape like human bodys for my game project. Can I create complex shape in javafx 8 with trianglemesh. and also i read, it is possible to import 3ds model to javafx. can any one tell me how to import them and do things like rotate move by javafx code thanks for your help
Also, If you go to Oracle, a 3DViewer is in the jfx samples which will export the 3d file to FXML, thus creating appropriate Groups ... Also being able to re-import the FXML into the Viewer... Samples ... Although as the above comment stated, InteractiveMesh has some nice tools. Thats the easy way, if you already have a 3d file... otherwise the point,texCoord, and face arrays can be quite daunting. It took me quite a while to create a torus .. points were fine, texCoords were fine, the faces are a pain... this was just for my customizable torus /* Let the radius from the center of the hole to the center of the torus tube be "c", and the radius of the tube be "a". Then the equation in Cartesian coordinates for a torus azimuthally symmetric about the z-axis is (c-sqrt(x^2+y^2))^2+z^2=a^2 and the parametric equations are x = (c + a * cos(v)) * cos(u) y = (c + a * cos(v)) * sin(u) z = a * sin(v) (for u,v in [0,2pi). Three types of torus, known as the standard tori, are possible, depending on the relative sizes of a and c. c>a corresponds to the ring torus (shown above), c=a corresponds to a horn torus which is tangent to itself at the point (0, 0, 0), and c<a corresponds to a self-intersecting spindle torus (Pinkall 1986). */ public static TriangleMesh createToroidMesh(float radius, float tRadius, int tubeDivisions, int radiusDivisions) { int POINT_SIZE = 3, TEXCOORD_SIZE = 2, FACE_SIZE = 6; int numVerts = tubeDivisions * radiusDivisions; int faceCount = numVerts * 2; float[] points = new float[numVerts * POINT_SIZE], texCoords = new float[numVerts * TEXCOORD_SIZE]; int[] faces = new int[faceCount * FACE_SIZE], smoothingGroups; int pointIndex = 0, texIndex = 0, faceIndex = 0, smoothIndex = 0; float tubeFraction = 1.0f / tubeDivisions; float radiusFraction = 1.0f / radiusDivisions; float x, y, z; int p0 = 0, p1 = 0, p2 = 0, p3 = 0, t0 = 0, t1 = 0, t2 = 0, t3 = 0; // create points for (int tubeIndex = 0; tubeIndex < tubeDivisions; tubeIndex++) { float radian = tubeFraction * tubeIndex * 2.0f * 3.141592653589793f; for (int radiusIndex = 0; radiusIndex < radiusDivisions; radiusIndex++) { float localRadian = radiusFraction * radiusIndex * 2.0f * 3.141592653589793f; points[pointIndex] = x = (radius + tRadius * ((float) Math.cos(radian))) * ((float) Math.cos(localRadian)); points[pointIndex + 1] = y = (radius + tRadius * ((float) Math.cos(radian))) * ((float) Math.sin(localRadian)); points[pointIndex + 2] = z = (tRadius * (float) Math.sin(radian)); pointIndex += 3; float r = radiusIndex < tubeDivisions ? tubeFraction * radiusIndex * 2.0F * 3.141592653589793f : 0.0f; texCoords[texIndex] = (0.5F + (float) (Math.sin(r) * 0.5D));; texCoords[texIndex + 1] = ((float) (Math.cos(r) * 0.5D) + 0.5F); texIndex += 2; } } //create faces for (int point = 0; point < (tubeDivisions) ; point++) { for (int crossSection = 0; crossSection < (radiusDivisions) ; crossSection++) { p0 = point * radiusDivisions + crossSection; p1 = p0 >= 0 ? p0 + 1 : p0 - (radiusDivisions); p1 = p1 % (radiusDivisions) != 0 ? p0 + 1 : p0 - (radiusDivisions - 1); p2 = (p0 + radiusDivisions) < ((tubeDivisions * radiusDivisions)) ? p0 + radiusDivisions : p0 - (tubeDivisions * radiusDivisions) + radiusDivisions ; p3 = p2 < ((tubeDivisions * radiusDivisions) - 1) ? p2 + 1 : p2 - (tubeDivisions * radiusDivisions) + 1; p3 = p3 % (radiusDivisions) != 0 ? p2 + 1 : p2 - (radiusDivisions - 1); t0 = point * (radiusDivisions) + crossSection; t1 = t0 >= 0 ? t0 + 1 : t0 - (radiusDivisions); t1 = t1 % (radiusDivisions) != 0 ? t0 + 1 : t0 - (radiusDivisions - 1); t2 = (t0 + radiusDivisions) < ((tubeDivisions * radiusDivisions)) ? t0 + radiusDivisions : t0 - (tubeDivisions * radiusDivisions) + radiusDivisions ; t3 = t2 < ((tubeDivisions * radiusDivisions) - 1) ? t2 + 1 : t2 - (tubeDivisions * radiusDivisions) + 1; t3 = t3 % (radiusDivisions) != 0 ? t2 + 1 : t2 - (radiusDivisions - 1); try { faces[faceIndex] = (p2); faces[faceIndex + 1] = (t3); faces[faceIndex + 2] = (p0); faces[faceIndex + 3] = (t2); faces[faceIndex + 4] = (p1); faces[faceIndex + 5] = (t0); faceIndex += FACE_SIZE; faces[faceIndex] = (p2); faces[faceIndex + 1] = (t3); faces[faceIndex + 2] = (p1); faces[faceIndex + 3] = (t0); faces[faceIndex + 4] = (p3); faces[faceIndex + 5] = (t1); faceIndex += FACE_SIZE; } catch (Exception e) { e.printStackTrace(); } } } TriangleMesh localTriangleMesh = new TriangleMesh(); localTriangleMesh.getPoints().setAll(points); localTriangleMesh.getTexCoords().setAll(texCoords); localTriangleMesh.getFaces().setAll(faces); return localTriangleMesh; }
Consider it to be a personal opinion. Though, I don't question the power of Javafx and the possibility to build complex 3d models using Javafx, I would not suggest building such complex structures using it, when you can easily achieve them using AutoDesk and other softwares, with less effort ! You have options to import them in your Javafx applications using early access available here http://www.interactivemesh.org/models/jfx3dimporter.html This seems to be very promising ! have a look at this as well http://www.interactivemesh.org/models/jfx3dbrowser.html
I made a 2x6 in sketchup and imported it along with the chick/dude/androgyne. He/she/it is only 2D. You need to get the jar file (jimColModelImporterJFX.jar) here. import com.interactivemesh.jfx.importer.Viewpoint; import com.interactivemesh.jfx.importer.col.ColAsset; import com.interactivemesh.jfx.importer.col.ColModelImporter; //import com.interactivemesh.jfx.importer.stl.StlMeshImporter; import javafx.application.Application; import javafx.scene.*; import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.scene.paint.PhongMaterial; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; import java.io.File; import java.util.Map; public class Importer3D extends Application { //you'll have to make your own file in sketchup and save it as *.dae //http://help.sketchup.com/en/article/114347 private static final String DAE_FILENAME = ("2x6stud-1.dae"); private static final int VIEWPORT_SIZE = 800; private Group root; public Rotate rx = new Rotate(); { rx.setAxis(Rotate.X_AXIS); } public Rotate ry = new Rotate(); { ry.setAxis(Rotate.Y_AXIS); } public Rotate rz = new Rotate(); { rz.setAxis(Rotate.Z_AXIS); } Translate t = new Translate(); static Node[] loadNodes() { File file = new File(DAE_FILENAME); ColModelImporter importer = new ColModelImporter(); importer.read(file); Node[] nodes = importer.getImport(); ColAsset colAsset = importer.getAsset(); System.out.println("asset title " + colAsset.getTitle()); System.out.println("asset unit name " + colAsset.getUnitName()); System.out.println("asset unit meter " + colAsset.getUnitMeter()); System.out.println("asset up axis " + colAsset.getUpAxis()); Map<String, PhongMaterial> materials = importer.getNamedMaterials(); for (Map.Entry<String, PhongMaterial> e : materials.entrySet()) { System.out.println("phong material " + e.getKey() + " -> " + e.getValue()); } Map<String, Node> namedNodes = importer.getNamedNodes(); for (Map.Entry<String, Node> e : namedNodes.entrySet()) { System.out.println("nodes " + e.getKey() + " -> " + e.getValue()); } Viewpoint[] viewpoints = importer.getViewpoints(); if (viewpoints != null) for (Viewpoint v : viewpoints) { System.out.println("viewpoint " + v); } return nodes; } private Group buildScene() { Node[] nodes = loadNodes(); root = new Group(nodes); return root; } private PerspectiveCamera addCamera(Scene scene) { PerspectiveCamera camera = new PerspectiveCamera(); camera.getTransforms().addAll(t, rz, ry, rx); camera.setVerticalFieldOfView(true); camera.setFieldOfView(10d); System.out.println("Near Clip: " + camera.getNearClip()); System.out.println("Far Clip: " + camera.getFarClip()); System.out.println("FOV: " + camera.getFieldOfView()); scene.setCamera(camera); return camera; } #Override public void start(Stage stage) { Group group = buildScene(); group.setScaleX(10); group.setScaleY(10); group.setScaleZ(10); group.setTranslateX(VIEWPORT_SIZE / 2); group.setTranslateY(VIEWPORT_SIZE / 2); Scene scene = new Scene(group, VIEWPORT_SIZE, VIEWPORT_SIZE, true); scene.setFill(Color.rgb(10, 10, 40)); addCamera(scene); stage.setTitle("Collada importer"); stage.setScene(scene); stage.show(); scene.setOnKeyPressed((evt) -> { switch (evt.getCode()) { case UP: rx.setAngle(rx.getAngle() + 5); break; case DOWN: rx.setAngle(rx.getAngle() - 5); break; case RIGHT: t.setX(t.getX() + 10); //camera.setTranslateX(camera.getTranslateX()+10); break; case LEFT: t.setX(t.getX() - 10); //camera.setTranslateX(camera.getTranslateX()-10); break; case Z: double zoom = evt.isShortcutDown() ? -10 : +10; t.setZ(t.getZ() + zoom); //camera.setTranslateZ(camera.getTranslateZ()+zoom); break; } }); } public static void main(String[] args) { System.setProperty("prism.dirtyopts", "false"); launch(args); } }
OpenCL RGB->HSL and back
Ive been through every resource and cant fix my problem. My host code calls the rgb2hsl kernel, then calls the hsl2rgb kernel. I should end up with the same image that I started with, but I do not. My new image hue is off in certain areas. The red areas should not be there. Here is the screen shot of what happens: Here is the original picture Here is the code: #define E .0000001f bool fEqual(float x, float y) { return (x+E > y && x-E < y); } __kernel void rgb2hsl(__global float *values, int numValues) { // thread index and total int idx = get_global_id(0); int idxVec3 = idx*3; float3 gMem; if (idx < numValues) { gMem.x = values[idxVec3]; gMem.y = values[idxVec3+1]; gMem.z = values[idxVec3+2]; } barrier(CLK_LOCAL_MEM_FENCE); gMem /= 255.0f; //convert from 256 color to float //calculate chroma float M = max(gMem.x, gMem.y); M = max(M, gMem.z); float m = min(gMem.x, gMem.y); m = min(m, gMem.z); float chroma = M-m; //calculate chroma float lightness = (M+m)/2.0f; float saturation = chroma/(1.0f-fabs(2.0f*lightness-1.0f)); float hue = 0; if (fEqual(gMem.x, M)) hue = (int)((gMem.y - gMem.z)/chroma) % 6; if (fEqual(gMem.y, M)) hue = (((gMem.z - gMem.x))/chroma) + 2; if (fEqual(gMem.z, M)) hue = (((gMem.x - gMem.y))/chroma) + 4; hue *= 60.0f; barrier(CLK_LOCAL_MEM_FENCE); if (idx < numValues) { values[idxVec3] = hue; values[idxVec3+1] = saturation; values[idxVec3+2] = lightness; } } __kernel void hsl2rgb(__global float *values, int numValues) { // thread index and total int idx = get_global_id(0); int idxVec3 = idx*3; float3 gMem; if (idx < numValues) { gMem.x = values[idxVec3]; gMem.y = values[idxVec3+1]; gMem.z = values[idxVec3+2]; } barrier(CLK_LOCAL_MEM_FENCE); float3 rgb = (float3)(0,0,0); //calculate chroma float chroma = (1.0f - fabs( (float)(2.0f*gMem.z - 1.0f) )) * gMem.y; float H = gMem.x/60.0f; float x = chroma * (1.0f - fabs( fmod(H, 2.0f) - 1.0f )); switch((int)H) { case 0: rgb = (float3)(chroma, x, 0); break; case 1: rgb = (float3)(x, chroma, 0); break; case 2: rgb = (float3)(0, chroma, x); break; case 3: rgb = (float3)(0, x, chroma); break; case 4: rgb = (float3)(x, 0, chroma); break; case 5: rgb = (float3)(chroma, 0, x); break; default: rgb = (float3)(0, 0, 0); } barrier(CLK_LOCAL_MEM_FENCE); rgb += gMem.z - .5f*chroma; rgb *= 255; if (idx < numValues) { values[idxVec3] = rgb.x; values[idxVec3+1] = rgb.y; values[idxVec3+2] = rgb.z; } }
The problem was this line: hue = (int)((gMem.y - gMem.z)/chroma) % 6; It should be hue = fmod((gMem.y - gMem.z)/chroma, 6.0f); I did some more changes to remove artifacts: #define E .0000001f bool fEqual(float x, float y) { return (x+E > y && x-E < y); } __kernel void rgb2hsl(__global float *values, int numValues) { // thread index and total int idx = get_global_id(0); int idxVec3 = idx*3; float3 gMem; if (idx < numValues) { gMem.x = values[idxVec3]; gMem.y = values[idxVec3+1]; gMem.z = values[idxVec3+2]; } barrier(CLK_LOCAL_MEM_FENCE); gMem /= 255.0f; //convert from 256 color to float //calculate chroma float M = max(gMem.x, gMem.y); M = max(M, gMem.z); float m = min(gMem.x, gMem.y); m = min(m, gMem.z); float chroma = M-m; //calculate chroma float lightness = (M+m)/2.0f; float saturation = chroma/(1.0f-fabs(2.0f*lightness-1.0f)); float hue = 0; if (fEqual(gMem.x, M)) hue = fmod((gMem.y - gMem.z)/chroma, 6.0f); if (fEqual(gMem.y, M)) hue = (((gMem.z - gMem.x))/chroma) + 2; if (fEqual(gMem.z, M)) hue = (((gMem.x - gMem.y))/chroma) + 4; hue *= 60.0f; barrier(CLK_LOCAL_MEM_FENCE); if (M == m) hue = saturation = 0; barrier(CLK_GLOBAL_MEM_FENCE); if (idx < numValues) { //NOTE: ARTIFACTS SHOW UP if we do not cast to integer! values[idxVec3] = (int)hue; values[idxVec3+1] = saturation; values[idxVec3+2] = lightness; } }