Ammo.js custom mesh collision with sphere - bullet

I'm trying to generate collisions for every object/mesh. They are all static and should collide with a ball/sphere, which is dynamic.
My code looks like this:
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(-1.1726552248001099, 2.6692488193511963, 0));
transform.setRotation(new Ammo.btQuaternion(0.5, -0.5, 0.5, 0.4999999701976776));
const motionState = new Ammo.btDefaultMotionState(transform);
// Vertices and indices are parsed by GLTF parser by loaders.gl
const vertices = Entity.vertices;
const indices = Entity.indices;
const scale = [0.15933185815811157, 1.1706310510635376, 0.15933185815811157];
// btConvexHullShape or btBvhTriangleMeshShape, see below.
const localInertia = new Ammo.btVector3(0, 0, 0);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(0, motionState, shape, localInertia);
const object = new Ammo.btRigidBody(rbInfo);
this._physicsWorld.addRigidBody(object);
I tried 2 methods: btConvexHullShape and btBvhTriangleMeshShape and both did not work.
btConvexHullShape
const shape = new Ammo.btConvexHullShape();
for (let i = 0; i < vertices.length / 3; i++) {
shape.addPoint(new Ammo.btVector3(vertices[i * 3] * scale[0], vertices[i * 3 + 1] * scale[1], vertices[i * 3 + 2] * scale[2]));
}
Path of the sphere using btConvexHullShape is something like this (it doesn't enter the hole):
I'm guessing Ammo.js connects top-most points? How can I control which points are connected based on indices? It also seems that this approach is very slow (30k vertices takes some time), so I tried:
btBvhTriangleMeshShape
const mesh = new Ammo.btTriangleMesh(true, true);
for (let i = 0; i * 3 < indices.length; i++) {
mesh.addTriangle(
new Ammo.btVector3(vertices[indices[i * 3]] * scale[0], vertices[indices[i * 3] + 1] * scale[1], vertices[indices[i * 3] + 2] * scale[2]),
new Ammo.btVector3(vertices[indices[i * 3 + 1]] * scale[0], vertices[indices[i * 3 + 1] + 1] * scale[1], vertices[indices[i * 3 + 1] + 2] * scale[2]),
new Ammo.btVector3(vertices[indices[i * 3 + 2]] * scale[0], vertices[indices[i * 3 + 2] + 1] * scale[1], vertices[indices[i * 3 + 2] + 2] * scale[2]),
false
);
}
const shape = new Ammo.btBvhTriangleMeshShape(mesh, true, true);
This method is a bit better, but the sphere just goes through the object when it bounces once (bounces off the circle and goes through the object (red circle)):

The reason why btConvexHullShape wasn't working (although the collision around it actually works) is because it isn't for concave shapes.
As for the btBvhTriangleMeshShape, I have been inputting the vertices the wrong way. I had to multiply the indices by 3 (because each vertex has 3 components).
Here's the full working code:
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(-1.1726552248001099, 2.6692488193511963, 0));
transform.setRotation(new Ammo.btQuaternion(0.5, -0.5, 0.5, 0.4999999701976776));
const motionState = new Ammo.btDefaultMotionState(transform);
// Vertices and indices are parsed by GLTF parser by loaders.gl
const vertices = Entity.vertices;
const indices = Entity.indices;
const scale = [0.15933185815811157, 1.1706310510635376, 0.15933185815811157];
const mesh = new Ammo.btTriangleMesh(true, true);
mesh.setScaling(new Ammo.btVector3(scale[0], scale[1], scale[2]));
for (let i = 0; i * 3 < indices.length; i++) {
mesh.addTriangle(
new Ammo.btVector3(vertices[indices[i * 3] * 3], vertices[indices[i * 3] * 3 + 1], vertices[indices[i * 3] * 3 + 2]),
new Ammo.btVector3(vertices[indices[i * 3 + 1] * 3], vertices[indices[i * 3 + 1] * 3 + 1], vertices[indices[i * 3 + 1] * 3 + 2]),
new Ammo.btVector3(vertices[indices[i * 3 + 2] * 3], vertices[indices[i * 3 + 2] * 3 + 1], vertices[indices[i * 3 + 2] * 3 + 2]),
false
);
}
const shape = new Ammo.btBvhTriangleMeshShape(mesh, true, true);
const localInertia = new Ammo.btVector3(0, 0, 0);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(0, motionState, shape, localInertia);
const object = new Ammo.btRigidBody(rbInfo);
this._physicsWorld.addRigidBody(object);

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>

How to get length of path?

I want to know the length of a Path.
For example, if I have a straight line I can just compute the length with its start x,y and end x,y values. But it gets quickly very tricky if I use QuadCurves or CubicCurves.
Is there any way to get the length or an approximation of the length of a Path?
For example the following path:
Path path = new Path();
MoveTo moveTo = new MoveTo(start.getX(), start.getY());
double controlPointX = 50;
CubicCurveTo cubicCurveTo = new CubicCurveTo(start.getX() + controlPointX, start.getY(),
start.getX() + controlPointX, end.getY(), end.getX(), end.getY());
path.getElements().addAll(moveTo, cubicCurveTo);
I needed this recently as well. I couldn't find any solutions online, but it occurred to me PathTransition must be calculating it. It does, see PathTransition.recomputeSegment, where totalLength is calculated.
Unfortunately, it uses many internal APIs in Node and the PathElement to convert the Path to a java.awt.geom.Path2D. I extracted these methods out and replaced other usages of com.sun classes with java.awt ones, then pulled the parts relevant to calculating length out of PathTransition.recomputeSegments.
The resulting code is below. It is in Kotlin not Java, but it should be easy to convert it back to Java. I have not yet tested it extensively but it seems to be working on the fairly complex paths I have tested it against. I've compared my results to the length calculated by PathTransition and they are very close, I believe the discrepancies are due to my code using Path2D.Double where as Path2D.Float is used by PathElement.impl_addTo.
fun Transform.toAffineTransform(): AffineTransform {
if(!isType2D) throw UnsupportedOperationException("Conversion of 3D transforms is unsupported")
return AffineTransform(mxx, myx, mxy, myy, tx, ty)
}
val Path.totalLength: Double
get() {
var length = 0.0
val coords = DoubleArray(6)
var pt = 0 // Previous segment type
var px = 0.0 // Previous x-coordinate
var py = 0.0 // Previous y-coordinate
var mx = 0.0 // Last move to x-coordinate
var my = 0.0 // Last move to y-coordinate
val pit = toPath2D().getPathIterator(localToParentTransform.toAffineTransform(), 1.0)
while(!pit.isDone) {
val type = pit.currentSegment(coords)
val x = coords[0]
val y = coords[1]
when(type) {
PathIterator.SEG_MOVETO -> {
mx = x
my = y
}
PathIterator.SEG_LINETO -> {
val dx = x - px
val dy = y - py
val l = sqrt(dx * dx + dy * dy)
if(l >= 1 || pt == PathIterator.SEG_MOVETO) length += l
}
PathIterator.SEG_CLOSE -> {
val dx = x - mx
val dy = y - my
val l = sqrt(dx * dx + dy * dy)
if(l >= 1 || pt == PathIterator.SEG_MOVETO) length += l
}
}
pt = type
px = x
py = y
pit.next()
}
return length
}
fun Path.toPath2D(): Path2D {
val path: Path2D = Path2D.Double(if(fillRule == FillRule.EVEN_ODD) Path2D.WIND_EVEN_ODD else Path2D.WIND_NON_ZERO)
for(e in elements) {
when(e) {
is Arc2D -> append(e as ArcTo, path) // Why isn't this smart casted?
is ClosePath -> path.closePath()
is CubicCurveTo -> append(e, path)
is HLineTo -> append(e, path)
is LineTo -> append(e, path)
is MoveTo -> append(e, path)
is QuadCurveTo -> append(e, path)
is VLineTo -> append(e, path)
else -> throw UnsupportedOperationException("Path contains unknown PathElement type: " + e::class.qualifiedName)
}
}
return path
}
private fun append(arcTo: ArcTo, path: Path2D) {
val x0 = path.currentPoint.x
val y0 = path.currentPoint.y
val localX = arcTo.x
val localY = arcTo.y
val localSweepFlag = arcTo.isSweepFlag
val localLargeArcFlag = arcTo.isLargeArcFlag
// Determine target "to" position
val xto = if(arcTo.isAbsolute) localX else localX + x0
val yto = if(arcTo.isAbsolute) localY else localY + y0
// Compute the half distance between the current and the final point
val dx2 = (x0 - xto) / 2.0
val dy2 = (y0 - yto) / 2.0
// Convert angle from degrees to radians
val xAxisRotationR = Math.toRadians(arcTo.xAxisRotation)
val cosAngle = Math.cos(xAxisRotationR)
val sinAngle = Math.sin(xAxisRotationR)
//
// Step 1 : Compute (x1, y1)
//
val x1 = cosAngle * dx2 + sinAngle * dy2
val y1 = -sinAngle * dx2 + cosAngle * dy2
// Ensure radii are large enough
var rx = abs(arcTo.radiusX)
var ry = abs(arcTo.radiusY)
var Prx = rx * rx
var Pry = ry * ry
val Px1 = x1 * x1
val Py1 = y1 * y1
// check that radii are large enough
val radiiCheck = Px1 / Prx + Py1 / Pry
if (radiiCheck > 1.0) {
rx *= sqrt(radiiCheck)
ry *= sqrt(radiiCheck)
if(rx == rx && ry == ry) {/* not NANs */ }
else {
path.lineTo(xto, yto)
return
}
Prx = rx * rx
Pry = ry * ry
}
//
// Step 2 : Compute (cx1, cy1)
//
var sign = if (localLargeArcFlag == localSweepFlag) -1.0 else 1.0
var sq = (Prx * Pry - Prx * Py1 - Pry * Px1) / (Prx * Py1 + Pry * Px1)
sq = if (sq < 0.0) 0.0 else sq
val coef = sign * Math.sqrt(sq)
val cx1 = coef * (rx * y1 / ry)
val cy1 = coef * -(ry * x1 / rx)
//
// Step 3 : Compute (cx, cy) from (cx1, cy1)
//
val sx2 = (x0 + xto) / 2.0
val sy2 = (y0 + yto) / 2.0
val cx = sx2 + (cosAngle * cx1 - sinAngle * cy1)
val cy = sy2 + (sinAngle * cx1 + cosAngle * cy1)
//
// Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle)
//
val ux = (x1 - cx1) / rx
val uy = (y1 - cy1) / ry
val vx = (-x1 - cx1) / rx
val vy = (-y1 - cy1) / ry
// Compute the angle start
var n = sqrt(ux * ux + uy * uy)
var p = ux // (1 * ux) + (0 * uy)
sign = if (uy < 0.0) -1.0 else 1.0
var angleStart = (sign * Math.acos(p / n)).toDegrees()
// Compute the angle extent
n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy))
p = ux * vx + uy * vy
sign = if (ux * vy - uy * vx < 0.0) -1.0 else 1.0
var angleExtent = Math.toDegrees(sign * Math.acos(p / n))
if(!localSweepFlag && angleExtent > 0) angleExtent -= 360.0
else if(localSweepFlag && angleExtent < 0) angleExtent += 360.0
angleExtent %= 360
angleStart %= 360
//
// We can now build the resulting Arc2D
//
val arcX = cx - rx
val arcY = cy - ry
val arcW = rx * 2.0
val arcH = ry * 2.0
val arcStart = -angleStart
val arcExtent = -angleExtent
val arc = Arc2D.Double(OPEN).apply { setArc(arcX, arcY, arcW, arcH, arcStart, arcExtent, OPEN) }
val xform: AffineTransform? = when(xAxisRotationR) {
0.0 -> null
else -> AffineTransform().apply { setToRotation(xAxisRotationR, cx, cy) }
}
val pi = arc.getPathIterator(xform)
// RT-8926, append(true) converts the initial moveTo into a
// lineTo which can generate huge miter joins if the segment
// is small enough. So, we manually skip it here instead.
pi.next()
path.append(pi, true)
}
private fun append(cubicCurveTo: CubicCurveTo, path: Path2D) {
if(cubicCurveTo.isAbsolute) {
path.curveTo(cubicCurveTo.controlX1, cubicCurveTo.controlY1,
cubicCurveTo.controlX2, cubicCurveTo.controlY2,
cubicCurveTo.x, cubicCurveTo.y)
}
else {
val dx = path.currentPoint.x
val dy = path.currentPoint.y
path.curveTo(cubicCurveTo.controlX1 + dx, cubicCurveTo.controlY1 + dy,
cubicCurveTo.controlX2 + dx, cubicCurveTo.controlY2 + dy,
cubicCurveTo.x + dx, cubicCurveTo.y + dy)
}
}
private fun append(hLineTo: HLineTo, path: Path2D) {
if(hLineTo.isAbsolute) path.lineTo(hLineTo.x, path.currentPoint.y)
else path.lineTo(path.currentPoint.x + hLineTo.x, path.currentPoint.y)
}
private fun append(lineTo: LineTo, path: Path2D) {
if(lineTo.isAbsolute) path.lineTo(lineTo.x, lineTo.y)
else path.lineTo(path.currentPoint.x + lineTo.x, path.currentPoint.y + lineTo.y)
}
private fun append(moveTo: MoveTo, path: Path2D) {
if(moveTo.isAbsolute) path.moveTo(moveTo.x, moveTo.y)
else path.moveTo((path.currentPoint.x + moveTo.x), path.currentPoint.y + moveTo.y)
}
private fun append(quadCurveTo: QuadCurveTo, path: Path2D) {
if(quadCurveTo.isAbsolute) {
path.quadTo(quadCurveTo.controlX, quadCurveTo.controlY,
quadCurveTo.x, quadCurveTo.y)
}
else {
val dx = path.currentPoint.x
val dy = path.currentPoint.y
path.quadTo(quadCurveTo.controlX + dx, quadCurveTo.controlY + dy,
quadCurveTo.x + dx, quadCurveTo.y + dy)
}
}
private fun append(vLineTo: VLineTo, path: Path2D) {
if(vLineTo.isAbsolute) path.lineTo(path.currentPoint.x, vLineTo.y)
else path.lineTo(path.currentPoint.x, path.currentPoint.y + vLineTo.y)
}

How can you make shortest line touching two circles at any given space?

Here is what I want to achieve.
The parameters are both circles radius, x - centers, and y - centers. The function to make line is line(x1, y1, x2, y2).
Here is what I have now using JavaScript.
var lineX1 = circleX1 + (circleRadius1 * Math.sin(Math.atan2(circleY2 - circleY1, circleX2 - circleX1)));
var lineY1 = circleY1 + (circleRadius1 * Math.cos(Math.atan2(circleY2 - circleY1, circleX2 - circleX1)));
var lineX2 = circleX2 - (circleRadius2 * Math.sin(Math.atan2(circleY1 - circleY2, circleX1 - circleX2)));
var lineY2 = circleY2 - (circleRadius2 * Math.cos(Math.atan2(circleY1 - circleY2, circleX1 - circleX2)));
line(lineX1, lineY1, lineX2, lineY2);
But it looks like this.
There is no need in trigonometric functions.
center difference vector
dx = cx2 - cx1
dy = cy2 - cy1
len = Math.Sqrt(dx*dx + dy*dy)
normalized
udx = dx / len
udy = dy / len
line ends
lx1 = cx1 + udx * r1
ly1 = cy1 + udy * r1
lx2 = cx2 - udx * r2
ly2 = cy2 - udy * r2
You are almost correct. As #welbog pointed out, you want to use Math.atan2. Also, you swapped your cos and sin for the x/y. Finally, you only need to calculate the angle once.
Here's a demonstration:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
draw();
function draw() {
svg.selectAll("*").remove();
var circleRadius1 = Math.random() * 100,
circleRadius2 = Math.random() * 100,
circleX1 = Math.random() * 500,
circleY1 = Math.random() * 500,
circleX2 = Math.random() * 500,
circleY2 = Math.random() * 500;
svg.append('circle')
.attr('r', circleRadius1)
.attr('cx', circleX1)
.attr('cy', circleY1)
.style('fill', 'none')
.style('stroke', 'steelblue');
svg.append('circle')
.attr('r', circleRadius2)
.attr('cx', circleX2)
.attr('cy', circleY2)
.style('fill', 'none')
.style('stroke', 'orange');
var angle = Math.atan2(circleY2 - circleY1, circleX2 - circleX1),
lineX1 = circleX1 + (circleRadius1 * Math.cos(angle)),
lineY1 = circleY1 + (circleRadius1 * Math.sin(angle)),
lineX2 = circleX2 - (circleRadius2 * Math.cos(angle)),
lineY2 = circleY2 - (circleRadius2 * Math.sin(angle));
svg.append('line')
.attr('x1', lineX1)
.attr('y1', lineY1)
.attr('x2', lineX2)
.attr('y2', lineY2)
.style('stroke','black')
setTimeout(draw, 1000);
}
</script>
</body>
</html>

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);
}
}

Modified Bessel functions of order (n)

I'm using Incanter and Parallel Colt for a project, and need to have a function that returns the modified Bessel function of an order n for a value v.
The Colt library has two methods for order 0 and order 1, but beyond that, only a method that return the Bessel function of order n for a value v (cern.jet.math.tdouble.Bessel/jn).
I'm trying to build the R function, dskellam(x,lambda1, lambda2) for the Skellam distribution, in Clojure/Java
Is there something I can do with the return value of the Bessel method to convert it to a modified Bessel?
No, the difference isn't a simple transformation, as these links make clear:
http://mathworld.wolfram.com/BesselFunctionoftheFirstKind.html
http://mathworld.wolfram.com/ModifiedBesselFunctionoftheFirstKind.html
I'd have a look at "Numerical Recipes" or Abramowitz & Stegun. It wouldn't be hard to implement your own in a short period of time.
Here's a Java implementation of the modified Bessel functions:
package math;
/**
* Functions that are not part of standard libraries
* User: Michael
* Date: 1/9/12
* Time: 9:22 PM
*/
public class Functions {
public static final double ACC = 4.0;
public static final double BIGNO = 1.0e10;
public static final double BIGNI = 1.0e-10;
public static void main(String[] args) {
double xmin = ((args.length > 0) ? Double.valueOf(args[0]) : 0.0);
double xmax = ((args.length > 1) ? Double.valueOf(args[1]) : 4.0);
double dx = ((args.length > 2) ? Double.valueOf(args[2]) : 0.1);
System.out.printf("%10s %10s %10s %10s\n", "x", "bessi0(x)", "bessi1(x)", "bessi2(x)");
for (double x = xmin; x < xmax; x += dx) {
System.out.printf("%10.6f %10.6f %10.6f %10.6f\n", x, bessi0(x), bessi1(x), bessi(2, x));
}
}
public static final double bessi0(double x) {
double answer;
double ax = Math.abs(x);
if (ax < 3.75) { // polynomial fit
double y = x / 3.75;
y *= y;
answer = 1.0 + y * (3.5156229 + y * (3.0899424 + y * (1.2067492 + y * (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2)))));
} else {
double y = 3.75 / ax;
answer = 0.39894228 + y * (0.1328592e-1 + y * (0.225319e-2 + y * (-0.157565e-2 + y * (0.916281e-2 + y * (-0.2057706e-1 + y * (0.2635537e-1 + y * (-0.1647633e-1 + y * 0.392377e-2)))))));
answer *= (Math.exp(ax) / Math.sqrt(ax));
}
return answer;
}
public static final double bessi1(double x) {
double answer;
double ax = Math.abs(x);
if (ax < 3.75) { // polynomial fit
double y = x / 3.75;
y *= y;
answer = ax * (0.5 + y * (0.87890594 + y * (0.51498869 + y * (0.15084934 + y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3))))));
} else {
double y = 3.75 / ax;
answer = 0.2282967e-1 + y * (-0.2895312e-1 + y * (0.1787654e-1 - y * 0.420059e-2));
answer = 0.39894228 + y * (-0.3988024e-1 + y * (-0.362018e-2 + y * (0.163801e-2 + y * (-0.1031555e-1 + y * answer))));
answer *= (Math.exp(ax) / Math.sqrt(ax));
}
return answer;
}
public static final double bessi(int n, double x) {
if (n < 2)
throw new IllegalArgumentException("Function order must be greater than 1");
if (x == 0.0) {
return 0.0;
} else {
double tox = 2.0/Math.abs(x);
double ans = 0.0;
double bip = 0.0;
double bi = 1.0;
for (int j = 2*(n + (int)Math.sqrt(ACC*n)); j > 0; --j) {
double bim = bip + j*tox*bi;
bip = bi;
bi = bim;
if (Math.abs(bi) > BIGNO) {
ans *= BIGNI;
bi *= BIGNI;
bip *= BIGNI;
}
if (j == n) {
ans = bip;
}
}
ans *= bessi0(x)/bi;
return (((x < 0.0) && ((n % 2) == 0)) ? -ans : ans);
}
}
}

Resources