A Little Trigonometry

Started by AGP, May 30, 2009, 06:32:34 PM

Previous topic - Next topic

EgonOlsen

I tried this:


DataInputStream dir=new DataInputStream(new FileInputStream("tracks.rac"));
List<SimpleVector> markers=new ArrayList<SimpleVector>();
while (dir.available()>0) {
      SimpleVector s=new SimpleVector(dir.readFloat(), dir.readFloat(), dir.readFloat());
      System.out.println(s+"/"+dir.available());
      markers.add(s);
}

...it doesn't work. At the end of the file, 4 bytes remain, the loop tries to read them into 3 floats and crashes...

AGP

Sorry, I forgot to tell you that the first 4 bytes are an int that informs us how many SimpleVectors (not floats) are stored.

EgonOlsen

#47
OK...and the track lies within the X-Y-plane? When looking at the values, x and y differ and z doesn't. Seems a bit strange to me!?

AGP

No, that's what it ought to look like. The cars are moving on the X/Y plane with +Z going up. And, like I said, the car is following the tracks nicely.

EgonOlsen

The car is following nicely...so what doesn't work actually? Just the rotation?

EgonOlsen

#50
This seems to work...:


import java.io.*;
import java.util.*;

import org.lwjgl.opengl.Display;

import com.threed.jpct.*;
import com.threed.jpct.util.Light;

public class OnTrackZ
{
 public static void main(String[] args) throws Exception
 {
   Config.farPlane=60000;
   
   DataInputStream di=new DataInputStream(new FileInputStream("tracks.rac"));
   List<SimpleVector> markers=new ArrayList<SimpleVector>();
   di.readInt();
   while (di.available()>0) {
     SimpleVector s=new SimpleVector(di.readFloat(), di.readFloat(), di.readFloat());
     float y=s.y;
     s.y=-s.z;
     s.z=-y;
     markers.add(s);
   }
   
   FrameBuffer buffer=new FrameBuffer(800,600,FrameBuffer.SAMPLINGMODE_HARDWARE_ONLY);
   buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
   buffer.enableRenderer(IRenderer.RENDERER_OPENGL);
   
   World world=new World();
   
   Object3D car=Primitives.getCone(5, 2000);
   car.rotateX((float)Math.PI/2f);
   car.rotateMesh();
   car.setRotationMatrix(new Matrix());
   
   car.compile();
   car.translate(markers.get(0));
   
   SimpleVector initTrs=markers.get(1).calcSub(markers.get(0)).normalize();
   car.setRotationMatrix(initTrs.getRotationMatrix());
   
   world.addObject(car);
   world.setAmbientLight(100, 100, 100);
   world.buildAllObjects();
   
   Light light=new Light(world);
   light.setPosition(new SimpleVector(0,-1000,0));
   light.setAttenuation(-1);
   light.setIntensity(255, 0, 0);
   
   Camera cam=world.getCamera();
   cam.setPosition(car.getTranslation());
   cam.moveCamera(Camera.CAMERA_MOVEUP, 50000);
   cam.rotateX(-(float)Math.PI/2f);
   
   Matrix lastRot=car.getRotationMatrix();
   
   long start=0;
   int fps=0;
   
   int pos=0;
   while (!Display.isCloseRequested()) {
     int next=(pos+1)%markers.size();
     
     SimpleVector cur=car.getTranslation();
     SimpleVector dir=markers.get(next).calcSub(cur);
     SimpleVector trs=dir.normalize();
     
     if (trs.length()!=0) {
       Matrix softMat=trs.getRotationMatrix();
       lastRot.interpolate(lastRot, softMat, 0.008f);
       car.setRotationMatrix(lastRot);
     }
     
     trs.scalarMul(5);
     car.translate(trs);
     
     if (car.getTranslation().calcSub(markers.get(next)).length()<5.5f) {
       lastRot=trs.getRotationMatrix();
       car.setTranslationMatrix(new Matrix());
       car.translate(markers.get(next));
       pos++;
       pos%=markers.size();
       System.out.println("Switching to: "+pos);
     }
     
     buffer.clear();
     world.renderScene(buffer);
     world.draw(buffer);
     buffer.update();
     buffer.displayGLOnly();
     
     fps++;
     if (System.currentTimeMillis()-start>=1000) {
       start=System.currentTimeMillis();
       System.out.println(fps+" fps");
       fps=0;
     }
   }
   buffer.dispose();
   System.exit(0);
 }
}


...BUT: As said, this method relies on getRotationMatrix() of SimpleVector. getRotationMatrix() is very similar to the usual lookAt-method, which means that its outcome is correct in terms of what the Javadoc states that it does, but it depends on the internal command order how the result looks like. In other words: There are millions of ways to look at something from a given position because you can always turn yourself around your own z-axis. The method works fine for the usual x-z-plane worlds, but when used in the y-x-plane, it creates wrap-arounds at the poles. Again, this is correct in terms of what it is supposed to do, but its not what you want in this case.
This is why my test code transforms the markers from the x-y-plane to the x-z-plane. It seems to work much better that way. Give it a try, maybe it helps somehow...

AGP

I understand so little of this code it's hard to plug it into my program. Let's start with what part of this converts between X/Z and X/Y?

AGP

Never mind my question, I can see it in your loading code. So then, can you explain the rest to me?

EgonOlsen

That happens right where i load the SimpleVectors by simply swapping z and y (with an additional sign change). The actual move/rotation code is pretty simple: It has a current pos and a next pos into the marker list and creates a direction vector from the current translation of the car to the next marker. It gets the rotation matrix from this vector and interpolates between this matrix and the current rotation matrix (lastRot) to get a smooth transition between them. If the car is located close to the next marker, it increases the pos and goes ahead.
It should be possible to do the calculations even when remaining in the y-x-plane, but you would have to do some conversions from y-x-plane to the x-z-plane, do the calulations and revert the transform....i tried that, but did something wrong, because it didn't work. So i decided to transform the track instead. It's more intuitive this way anyway IMHO.

AGP

Yeah, but it's not like I rotated any of the models. X/Y is just what I ended up with when I loaded them. Anyway, of my Quest for Glory you wrote the opposite, because I have an X/Z plane and I remember you saying that the mouse-walking method had been meant for X/Y. So should I rotate everything -90 degrees along their x-axis?

AGP

Rotating obviously messed everything up. What about world.rotateAxis(world.getXAxis(), float) methods? I think it would be very handy.

EgonOlsen

Quote from: AGP on June 05, 2009, 07:13:28 PM
Yeah, but it's not like I rotated any of the models. X/Y is just what I ended up with when I loaded them. Anyway, of my Quest for Glory you wrote the opposite, because I have an X/Z plane and I remember you saying that the mouse-walking method had been meant for X/Y. So should I rotate everything -90 degrees along their x-axis?
Mouse picking in jPCT usually happens in camera space, where even your X/Y-plane is transformed into X/Z, because that's what is going to be rendered. The picking plane itself lies within X/Y (aka screen space)...maybe that was, what i was writing about...i can't remember it.

About the rotation: Yes, i suggest to do this. It's something that i'm doing with every model i'm loading from 3ds, because 3ds simply uses another coordinate system. World.rotateAxis() doesn't make much sense, because the world's rotation actually is the inverted camera rotation...but that doesn't help in this case. What i suggest is to rotate your loaded models by -90° around x right after loading them and make this rotation permanent (i.e. apply rotateMesh() on them and reset the rotation matrix afterwards). You may have to adjust some things in your code, but it should be worth the effort.

EgonOlsen

Quote from: AGP on June 05, 2009, 07:41:58 PM
Rotating obviously messed everything up.
I'll invest some time and try to make the transformations work even with your X/Y-plane...shouldn't be too hard, but i failed once so maybe it's harder then i think it is...

EgonOlsen

#58
This one works with everything left in the X/Y-plane. I'm not sure about the initial rotation of the car...maybe it needs some tweaking depending on how you load your model. You'll find my test model here: http://www.jpct.net/download/misc/droid3.3DS

The basic idea of this thing is, that everything graphics related happen in the X/Y-plane (as in your code), but the actual rotation calculation and interpolation happens in the X/Z-plane. Some rotates are used to convert between the two.


import java.io.*;
import java.util.*;

import org.lwjgl.opengl.Display;

import com.threed.jpct.*;
import com.threed.jpct.util.Light;

public class OnTrackZ {
public static void main(String[] args) throws Exception {
float rot = (float) Math.PI / 2f;

Config.farPlane = 60000;

DataInputStream di = new DataInputStream(new FileInputStream(
"tracks.rac"));
List<SimpleVector> markers = new ArrayList<SimpleVector>();
di.readInt();
while (di.available() > 0) {
SimpleVector s = new SimpleVector(di.readFloat(), di.readFloat(), di.readFloat());
markers.add(s);
}

FrameBuffer buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_HARDWARE_ONLY);
buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
buffer.enableRenderer(IRenderer.RENDERER_OPENGL);

World world = new World();

Object3D car = Object3D.mergeAll(Loader.load3DS("droid3.3DS", 80));
car.rotateX(rot);
car.rotateMesh();
car.setRotationMatrix(new Matrix());

car.compile();
car.translate(markers.get(0));

// Set the cars initial rotation rotation matrix and lastRot.
// The inital rotation is in the X/Y-plane while lastRot is actually
// the same thing but transformed into the X/Z-plane.
SimpleVector initTrs = markers.get(1).calcSub(markers.get(0)).normalize();
car.setRotationMatrix(initTrs.getRotationMatrix()); // X/Y-plane...
initTrs.rotateX(rot);
Matrix lastRot = initTrs.getRotationMatrix(); // X/Z-plane

world.addObject(car);
world.setAmbientLight(100, 100, 100);
world.buildAllObjects();

Light light = new Light(world);
light.setPosition(new SimpleVector(0, -1000, 0));
light.setAttenuation(-1);
light.setIntensity(255, 0, 0);

Camera cam = world.getCamera();
cam.setPosition(car.getTranslation());
cam.moveCamera(Camera.CAMERA_MOVEOUT, 50000);

long start = 0;
int fps = 0;

int pos = 0;
while (!Display.isCloseRequested()) {
int next = (pos + 1) % markers.size();

// Get the current position
SimpleVector cur = car.getTranslation();

// Get the direction vector from "current" to the next marker
SimpleVector dir = markers.get(next).calcSub(cur);

// Normalize that...
SimpleVector trs = dir.normalize();

// Make a copy of the direction vector for matrix generation
SimpleVector tr = new SimpleVector(trs);

// Rotate the copy into the X/Z-plane
tr.rotateX(rot);

// Is there any length? (Should always be the case, but you never
// know...)
if (trs.length() != 0) {
// Create a rotation matrix out of the tranformed direction
// vector
Matrix softMat = tr.getRotationMatrix();
// interpolate between the matrices
lastRot.interpolate(lastRot, softMat, 0.008f);
// make a copy of lastRot...
Matrix mr = new Matrix(lastRot);
// ...and transform it back into X/Y-plane...
mr.rotateX(rot);
// ...to use it as an actual rotation matrix for the car!
car.setRotationMatrix(mr);
}

// apply the translation (in X/Y)
trs.scalarMul(5f);
car.translate(trs);

// A marker has been reached?
if (car.getTranslation().calcSub(markers.get(next)).length() < 5.5f) {
// Fix lastRot. This prevents rounding and accuracy errors.
lastRot = tr.getRotationMatrix();
// The same for the car's position...
car.setTranslationMatrix(new Matrix());
car.translate(markers.get(next));

// Next marker, please...
pos++;
pos %= markers.size();
}

// Render that thing
buffer.clear();
world.renderScene(buffer);
world.draw(buffer);
buffer.update();
buffer.displayGLOnly();

// Simple fps counter
fps++;
if (System.currentTimeMillis() - start >= 1000) {
start = System.currentTimeMillis();
System.out.println(fps + " fps");
fps = 0;
}
}
buffer.dispose();
System.exit(0);
}
}


Edit: Fixed a flaw in the code, added some comments.

AGP

Egon, THANKS A WHOLE LOT. Works great. I'll try and get the applet up and running by tonight to show it to you. It's a good lesson, too, this doing it by matrices, for future programs. I appreciate it, pal.