I have an application, where all scene objects are loaded from .obj files (they are exported from Blender). And I need to rotate one of this objects around specific point. Currently, I have the following code:
public void rotateTo() {
// translate to origin, rotate, translate back
Vector3f origin = new Vector3f();
Vector3f pivot = new Vector3f(.0f, .5f, .0f);
this.getTransform(this.transform);
this.transform.get(origin);
double angle = -Math.PI / 2;
double newX = origin.getX() + Math.cos(angle) * (pivot.getX() - origin.getX()) - Math.sin(angle) * (pivot.getY() - origin.getY());
double newY = origin.getY() + Math.sin(angle) * (pivot.getX() - origin.getX()) + Math.cos(angle) * (pivot.getY() - origin.getY());
this.transform.set(new Vector3f((float)newX, (float)newY, .0f), 0.15f);
this.setTransform(this.transform);
this.transform.set(origin, 0.15f);
this.setTransform(this.transform);
}
Where this refers to TransformGroup object, that I need to rotate, and this.transform refers to Transform3D object.
But this method doesn't work as I expected. I have also tried this and this solutions, but they also didn't work for me.
I think, maybe, try to perform this manipulation with the help of GeometryArray, but I can't understand, how to do this. Also, I can try to export my objects with different origin points, but it's not clean solution, I think, because then I would need to move them to right positions with code.
Here's my object:
I'm trying to rotate it around green point, but in all cases it rotates around red point, or doesn't rotate at all, or translates to some point without rotation.
P.S. I know, that Java3D is an old library and can use more powerful tools, but it's my university professor's requirement and I can't refuse using it.
Composition of transformations can be achieved using matrix multiplication. Below is an example that rotates a cube 180° about the X axis with the pivot at the centre of its top face.
There are three steps as mentioned in the question: translate so that the pivot is at the origin, rotate, and translate back. Comment out steps to see what is happening.
With all steps commented out, the box appears at the centre with its red side facing forward.
Uncomment the first step. The box shifts down so its top is at the centre of the screen.
Uncomment the second step. The box rotates about the X axis (the left-right axis, through the top of the box). The green side now faces forward, and the bottom of the box is now at the centre of the screen.
Uncomment the third step. The box shifts up, and the overall effect is that it rotated about a left-right axis through its top face.
The application of a transformation T to a vector v is defined as T*v in Java 3D, so the composition of two transformations T and U where T is to be done first and U is to be done second is U*T. Note how the order is reversed. In general, the composition of the transformations T1, T2, ..., Tn where T1 is done first and Tn is done last is Tn*...*T2*T1.
This can be expressed in Java 3D using the following pattern:
Transform3D t1 = ..., t2 = ..., t3 = ...;
// first do t1, then t2, then t3
Transform3D all = new Transform3D(); // all = identity
all.mul(t1, all); // all = t1 * all
all.mul(t2, all); // all = t2 * all
all.mul(t3, all); // all = t3 * all
// now all == t3 * t2 * t1
Methods such as Transform3D.set(Vector3d,double) and TransformGroup.setTransform() overwrite the existing transformation rather than compose with it, which is one of the reasons your code does not work.
I found the article http://www.developer.com/java/other/article.php/3717101/Understanding-Transforms-in-Java-3D.htm helpful.
// based on http://www.java3d.org/starting.html
import com.sun.j3d.utils.geometry.ColorCube;
import com.sun.j3d.utils.universe.SimpleUniverse;
import javax.media.j3d.*;
import javax.vecmath.*;
public class Demo
{
public Demo() {
SimpleUniverse universe = new SimpleUniverse();
BranchGroup group = new BranchGroup();
group.addChild(createModel());
universe.getViewingPlatform().setNominalViewingTransform();
universe.addBranchGraph(group);
}
Node createModel() {
double radius = 0.3;
ColorCube cube = new ColorCube(radius);
// rotation of cube about the center of the top face
// i.e. the point (x=0, y=radius, z=0)
Transform3D transform = new Transform3D();
// step 1: translate cube down so that (0, radius, 0) is at the origin
Transform3D translate1 = new Transform3D();
translate1.setTranslation(new Vector3d(0.0, -radius, 0.0));
transform.mul(translate1, transform);
// step 2: rotate cube about X axis by 180 degrees
Transform3D rotate = new Transform3D();
rotate.rotX(Math.PI);
transform.mul(rotate, transform);
// step 3: translate cube back up
Transform3D translate2 = new Transform3D();
translate2.setTranslation(new Vector3d(0.0, +radius, 0.0));
transform.mul(translate2, transform);
// create a TransformGroup
TransformGroup tg = new TransformGroup();
tg.setTransform(transform);
tg.addChild(cube);
return tg;
}
public static void main(String[] args) {
new Demo();
}
}
Related
I have tried to create NPC character that can "see" the player by using cones of vision.
The NPC will rotate back and forth at all times.
My problem is that the arc has a generic and unchanging position, but when its drawn to the screen it looks correct.
[Screenshots of the collisions in action][1]
[GitHub link for java files][2]
I'm using Arc2D to draw the shape like this in my NPC class
// Update the shapes used in the npc
rect.setRect(x, y, w, h);
ellipse.setFrame(rect);
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle, visionAngle * 2, Arc2D.PIE);
/ CenterX, CenterY (of the npc),
/ the distance from the arc to the npc
/ a constant value around 45 degrees and a constant value around 90 degress (to make a pie shape)
I've tried multiplying the position and the angles by the sin and cosine of the NPC's current angle
something like these
visionArc.setArcByCenter(cx * (Math.cos(Math.toRadians(angle))), cy (Math.sin(Math.toRadians(angle)), visionDistance, visionAngle, visionAngle * 2, Arc2D.PIE);
or
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle - angle, (visionAngle + angle) * 2, Arc2D.PIE);
or
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle * (Math.cos(Math.toRadians(angle))), visionAngle * 2, Arc2D.PIE);
I've tried a lot but can't seem to find what works. Making the vision angles not constant makes an arc that expands and contracts, and multiplying the position by the sin or cosine of the angle will make the arc fly around the screen, which doesn't really work either.
This is the function that draws the given NPC
public void drawNPC(NPC npc, Graphics2D g2, AffineTransform old) {
// translate to the position of the npc and rotate
AffineTransform npcTransform = AffineTransform.getRotateInstance(Math.toRadians(npc.angle), npc.x, npc.y);
// Translate back a few units to keep the npc rotating about its own center
// point
npcTransform.translate(-npc.halfWidth, -npc.halfHeight);
g2.setTransform(npcTransform);
// g2.draw(npc.rect); //<-- show bounding box if you want
g2.setColor(npc.outlineColor);
g2.draw(npc.visionArc);
g2.setColor(Color.BLACK);
g2.draw(npc.ellipse);
g2.setTransform(old);
}
This is my collision detection algorithim - NPC is a superclass to ninja (Shorter range, higher peripheral)
public void checkNinjas(Level level) {
for (int i = 0; i < level.ninjas.size(); i++) {
Ninja ninja = level.ninjas.get(i);
playerRect = level.player.rect;
// Check collision
if (playerRect.getBounds2D().intersects(ninja.visionArc.getBounds2D())) {
// Create an area of the object for greater precision
Area area = new Area(playerRect);
area.intersect(new Area(ninja.visionArc));
// After checking if the area intersects a second time make the NPC "See" the player
if (!area.isEmpty()) {
ninja.seesPlayer = true;
}
else {
ninja.seesPlayer = false;
}
}
}
}
Can you help me correct the actual positions of the arcs for my collision detection? I have tried creating new shapes so I can have one to do math on and one to draw to the screen but I scrapped that and am starting again from here.
[1]: https://i.stack.imgur.com/rUvTM.png
[2]: https://github.com/ShadowDraco/ArcCollisionDetection
After a few days of coding and learning and testing new ideas I came back to this program and implemented the collision detection using my original idea (ray casting) and have created the equivalent with rays!
Screenshot of the new product
Github link to the project that taught me the solution
Here's the new math
public void setRays() {
for (int i = 0; i < rays.length; i++) {
double rayStartAngleX = Math.sin(Math.toRadians((startAngle - angle) + i));
double rayStartAngleY = Math.cos(Math.toRadians((startAngle - angle) + i));
rays[i].setLine(cx, cy, cx + visionDistance * rayStartAngleX, cy + visionDistance * rayStartAngleY);
}
}
Here is a link the the program I started after I asked this question and moved on to learn more, and an image to what the new product looks like
(The original github page has been updated with a new branch :) I'm learning git hub right now too
I do not believe that using Arc2D in the way I intended is possible, however there is .setArcByTangent method, it may be possible to use that but I wasn't going to get into that. Rays are cooler.
I'm trying to add a 2D overlay for a 3D scene in Java3D, part of this overlay is to draw a line from a 2D object to a corresponding point in the 3D scene...
Searched transformation from 3D to 2D and read those threads:
Translate Java 3D coordinates to 2D screen coordinates
3D to 2D projection
from code inside walrus:
https://github.com/CAIDA/walrus/blob/master/H3ViewParameters.java
copied a method to a class extending Canvas3D:
public Transform3D getObjectToEyeTransform() {
Point3d m_eye = new Point3d();
getCenterEyeInImagePlate(m_eye);
Transform3D m_imageToEye = new Transform3D();
m_imageToEye.set(new Vector3d(-m_eye.x, -m_eye.y, 0.0));
Transform3D m_vworldToImage = new Transform3D();
getVworldToImagePlate(m_vworldToImage);
Transform3D transform = new Transform3D(m_imageToEye);
transform.mul(m_vworldToImage);
//transform.mul(m_objectTransform);
return transform;
}
and then in my overlay in method postRender i try to do the following:
Transform3D viewTrans3d = getObjectToEyeTransform();
Vector3d point = new Vector3d(1,1,1);
viewTrans3d.invert();
viewTrans3d.transform(point);
this.getGraphics2D().drawLine(0, 0, (int)point.x, (int)point.y);
Getting very weird line, which do change in a quite logical pattern (when i rotate and tilt the view) but far from what i expect...
Questions:
commented the m_objectTransform matrix multiplication because i
don't understand its purpose, any idea?
Why do i need to invert the transform matrix? without the invert the results are even weirder...
Is there a simpler way to do this??? sounds like something solved eons ago...
This can be done by using getVworldToImagePlate and then getPixelLocationFromImagePlate in the Canvas3D class. For example:
public Point2d getPosition2d(Point3d point) {
Transform3D transform = new Transform3D();
getVworldToImagePlate(transform);
transform.mul(objectTransform);
Point3d newPoint = new Point3d(point);
transform.transform(newPoint);
Point2d point2d = new Point2d();
getPixelLocationFromImagePlate(newPoint, point2d);
return point2d;
}
The objectTransform variable should be the transform of any TransformGroup in the scene that is applied to the 3d objects that are displayed. If you don't have any TransformGroup, then you can leave this out. Also, the transform shouldn't be inverted, just use it as it is.
I have a very simple Java3D application, it contains two cubes, of which one of them orbits the other.
Like so:
As you can see, the default position for the viewing platform is from a 'kind of' birds eye view.
The code that looks like so
TransformGroup cameraTG = u.getViewingPlatform().
getViewPlatformTransform();
Vector3f translate = new Vector3f();
Transform3D T3D = new Transform3D();
translate.set( 0.0f, 0.0f, 20.0f);
T3D.setTranslation(translate);
cameraTG.setTransform(T3D);
My question is, is it possible to set the viewing platform to track the larger cube (the cube that rotates around the smaller square). Or more straight forward, is it possible for the viewing platform to rotate around a given body?
More information:
My goal is to have a miniature solar system, containing the Sun, Earth and moon. And I would like to view it from the point of view of the earth (almost like a view from the ISS)
Any help or pointers would be fantastic. Please feel free to ask for more information if needed.
You have a nice example here http://java3d.nl/Tutorials/Java/Java3d/Controlthecamera_12.php
The original http://java3d.nl website is no longer available, but you can still use the cached version from the Internet Archive: https://web.archive.org/web/20131218022035/http://java3d.nl/Tutorials/Java/Java3d/Controlthecamera_12.php
this.camera = this.universe.getViewingPlatform().getViewPlatformTransform();
//Add things to the universe
this.root = new BranchGroup();
this.root.addChild(new ColorCube(0.2));
this.universe.addBranchGraph(root);
My idea is like this:
BranchGroup b=new BranchGroup();
b.addChild(cube);
b.addChild(camera);
then in a loop where you rotate the block:
while(true) {
... b.getChild()......... etc
apply transform
}
or more specifically
for(j=0; j<group.numChildren(); j++) {
Node ni=group.getChild(j);
if(ni instanceof TransformGroup) {
Transform3D tdi=new Transform3D();
TransformGroup tgi=(TransformGroup)group.getChild(j);
tgi.getTransform(tdi);
Transform3D rotation = new Transform3D();
rotation.rotX(Math.toRadians(0.001*i));
tdi.mul(rotation);
tgi.setTransform(tdi);
}
}
There is a simpler solution. Instaed of trying to control the viewer gaze using angles and rotation, you can merely say what 3D point to look at. In the ViewingTransform, you can call "lookAt(...)":
TransformGroup viewingTransformGroup = simpleUniv.getViewingPlatform().getViewPlatformTransform();
Transform3D viewingTransform = new Transform3D();
Point3d eye = viewersLocation;
Point3d center = gazePoint;
Vector3d up = new Vector3d(0,1,0); //assumes +y-axis points up
viewingTransform.lookAt(eye, center, up);
viewingTransform.invert();
viewingTransformGroup.setTransform(viewingTransform);
I have a Cone I drew in Java 3D with the following code:
Cone cone = new Cone(2f, 3f);
Transform3D t3d = new Transform3D();
TransformGroup coneTransform = new TransformGroup(t3d);
coneTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
t3d.setTranslation(new Vector3f(0f,0f,0f);
coneTransform.setTransform(t3d);
coneTransform.addChild(cone);
this.addChild(coneTransform);
Suppose I have the cone sitting at point (1,1,1) and I want the tip of the cone to point down an imaginary line running through (0,0,0) and (1,1,1)... how can I do this?
Here's an example of what I've been trying:
Transform3D t3d = new Transform3D();
Vector3f direction = new Vector3f(1,2,1);
final double angleX = direction.angle(new Vector3f(1,0,0));
final double angleY = direction.angle(new Vector3f(0,1,0));
final double angleZ = direction.angle(new Vector3f(0,0,1));
t3d.rotX(angleX);
t3d.rotY(angleY);
t3d.rotZ(angleZ);
t3d.setTranslation(direction);
coneTransform.setTransform(t3d);
Thanks in advance for all help!
I'm just learning Java 3D myself at the moment, and from my current knowledge, the rotation methods set the transform to a rotation about that axis only.
Therefore, if you wish to perform rotations about multiple axes, then you will need to use a second Transform3D.
ie:
Transform3D rotation = new Transform3D();
Transform3D temp = new Transform3D();
rotation.rotX(Math.PI/2);
temp.rotZ(Math.PI/2);
rotation.mul(temp); // multiply the 2 transformation matrices together.
As for the reason for Math.PI, this is because it uses radians instead of degrees, where Math.PI is equivalent to 180 degrees.
Finding the angle between your current orientation and your intended orientation isn't too hard - you could use Vector3fs, with the angle() method. A Vector would be set up with the initial orientation, and another in the intended.
However, this doesn't tell you in which axes the angle lies. Doing so would require examination of the vectors to see which segments are set. [of course, there may be something that I am currently unaware of in the API]
This is not a java3D specific answer.
In general a matrix can be built such that there are 4 vectors that describe it.
1) A side (or lateral) vector
2) An up vector
3) A direction vector
4) A position
Each row of a 4x4 matrix.
Thus for a simple identity matrix we have the following matrix (I'll define a column major matrix, for a row major matrix all you need to do is swap the matrix indices around such that row 2 col 3 becomes row 3 col 2 throughout the matrix).
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
in this the first column is the side vector. The second column the up vector. The third the direction and the fourth the position.
Logically we can see that the vector (1, 0, 0, 0) points along the x axis (and thus is the side vector). The vector (0, 1, 0, 0) points along the y axis (and thus is the up vector). The third (0, 0, 1, 0) points along the Z-axis (and thus is the direction vector). The fourth (0, 0, 0, 1) indicates that the objects does not move at all.
Now lets say we wanted to face along the X-axis.
Obviously that would mean we have a vector of (1, 0, 0, 0 ) for our direction vector. Up would still be (0, 1, 0, 0) and position still 0, 0, 0 1. So what would our side vector be? Well, logically it would point along the z-axis. But which way? Well hold your fingers such that one finger points forward, one to the side and one up. Now rotate so that the forward finger is facing the same direction as the side pointing finger. Which way is the side pointing finger pointing now? The opposite direction to the original direction pointing finger. Thus the matrix is
0 0 1 0
0 1 0 0
-1 0 0 0
0 0 0 1
At this point things seemingly get a little more complicated. It is simple enough to take an arbitrary position and an arbitrary point to look at (I'll call them vPos and vFocus). It is easy enough to form a vector from vPos to vFocus by subtracting vPos from vFocus (vFocus.x - vPos.x, vFocus.y - vPos.y, vFocus.z - vPos.z, vFocus.w - vPos.w ). Bear in mind all positions should be defined with a '1' in the w position where all directions should have a '0'. This is automatically taken care of when you do the subtraction above as the 1 in both ws will cancel out and leave 0. Anyway, we now have a vector pointing from the position towards vFocus we'll call it vDir. Unfortunately it has the length of the difference between vPos and vFocus. However if we divide the vDir vector by its length (vDir.x / length, vDir.y / length, vDir.z / length, vDir.w / length) then we normalise it and we have a direction with a total length of 1.
At this ponit we now have our 3rd and 4th columns of our matrix. Now, lets assuem up is still (0, 1, 0, 0) or vUp. We can assume that the crossproduct of the direction and vUp will produce a vector that is perpendicular (and also of unit length) to the plane formed by vDir and vUp. This gives us our side vector or vLat. Now .. we did kind of assume the up vector so its not strictly correct. We can now calculate it exactly by taking the cross product of vLat and vDir and we have all 4 vectors.
The final matrix is thus defined as follows
vLat.x vUp.x vDir.x vPos.x
vLat.y vUp.y vDir.y vPos.y
vLat.z vUp.z vDir.z vPos.z
vLat.w vUp.w vDir.w vPos.w
This isn't strictly the full answer as you will get problems as you look towards a point near to your (0, 1, 0, 0) vector but that should work for most cases.
I finally figured out what I wanted to do by using Quaternions, which I learned about here: http://www.cs.uic.edu/~jbell/Courses/Eng591_F1999/outline_2.html Here's my solution.
Creating the cone:
private void attachCone(float size) {
Cone cone = new Cone(size, size* 2);
// The group for rotation
arrowheadRotationGroup = new TransformGroup();
arrowheadRotationGroup.
setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
arrowheadRotationGroup.addChild(cone);
// The group for positioning the cone
arrowheadPositionGroup = new TransformGroup();
arrowheadPositionGroup.
setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
arrowheadPositionGroup.addChild(arrowheadRotationGroup);
super.addChild(arrowheadPositionGroup);
}
Now, when I want to rotate the cone to point in a certain direction specified as the vector from the point (0,0,0) to (direction.x, direction.y, direction.z), I use:
private final Vector3f yAxis = new Vector3f(0f, 1f, 0f);
private Vector3f direction;
private void rotateCone() {
// Get the normalized axis perpendicular to the direction
Vector3f axis = new Vector3f();
axis.cross(yAxis, direction);
axis.normalize();
// When the intended direction is a point on the yAxis, rotate on x
if (Float.isNaN(axis.x) && Float.isNaN(axis.y) && Float.isNaN(axis.z))
{
axis.x = 1f;
axis.y = 0f;
axis.z = 0f;
}
// Compute the quaternion transformations
final float angleX = yAxis.angle(direction);
final float a = axis.x * (float) Math.sin(angleX / 2f);
final float b = axis.y * (float) Math.sin(angleX / 2f);
final float c = axis.z * (float) Math.sin(angleX / 2f);
final float d = (float) Math.cos(angleX / 2f);
Transform3D t3d = new Transform3D();
Quat4f quat = new Quat4f(a, b, c, d);
t3d.set(quat);
arrowheadRotationGroup.setTransform(t3d);
Transform3D translateToTarget = new Transform3D();
translateToTarget.setTranslation(this.direction);
arrowheadPositionGroup.setTransform(translateToTarget);
}
I think this should do it:
coneTransform.rotX(Math.PI / 4);
coneTransform.rotY(Math.PI / 4);
you can give your Transform3D a rotation matrix. you can get a rotation matrix using Rotation matrix calculator online: http://toolserver.org/~dschwen/tools/rotationmatrix.html here's my example:
Matrix3f mat = new Matrix3f(0.492403876506104f, 0.586824088833465f,
-0.642787609686539f, 0.413175911166535f, 0.492403876506104f,
0.766044443118978f, 0.766044443118978f, -0.642787609686539f, 0f);
Transform3D trans = new Transform3D();
trans.set(mat);
I think it can be done by applying the transformation matrix of the scenegraph to z-normal (0, 0, 1), but it doesn't work. My code goes like this:
Vector3f toScreenVector = new Vector3f(0, 0, 1);
Transform3D t3d = new Transform3D();
tg.getTransform(t3d); //tg is Transform Group of all objects in a scene
t3d.transform(toScreenVector);
Then I tried something like this too:
Point3d eyePos = new Point3d();
Point3d mousePos = new Point3d();
canvas.getCenterEyeInImagePlate(eyePos);
canvas.getPixelLocationInImagePlate(new Point2d(Main.WIDTH/2, Main.HEIGHT/2), mousePos); //Main is the class for main window.
Transform3D motion = new Transform3D();
canvas.getImagePlateToVworld(motion);
motion.transform(eyePos);
motion.transform(mousePos);
Vector3d toScreenVector = new Vector3f(eyePos);
toScreenVector.sub(mousePos);
toScreenVector.normalize();
But still this doesn't work correctly. I think there must be an easy way to create such vector. Do you know what's wrong with my code or better way to do so?
If I get this right, you want a vector that is normal to the screen plane, but in world coordinates?
In that case you want to INVERT the transformation from World -> Screen and do Screen -> World of (0,0,-1) or (0,0,1) depending on which axis the screen points down.
Since the ModelView matrix is just a rotation matrix (ignoring the homogeneous transformation part), you can simply pull this out by taking the transpose of the rotational part, or simple reading in the bottom row - as this transposes onto the Z coordinate column under transposition.
Yes, you got my question right. Sorry that I was a little bit confused yesterday. Now I have corrected the code by following your suggestion and mixing two pieces of code in the question together:
Vector3f toScreenVector = new Vector3f(0, 0, 1);
Transform3D t3d = new Transform3D();
canvas.getImagePlateToVworld(t3d);
t3d.transform(toScreenVector);
tg.getTransform(t3d); //tg is Transform Group of all objects in a scene
t3d.transform(toScreenVector);
Thank you.