www.jpct.net

jPCT - a 3d engine for Java => Support => Topic started by: AGP on May 30, 2009, 06:32:34 pm

Title: A Little Trigonometry
Post by: AGP on May 30, 2009, 06:32:34 pm
I need to find the theta of the following triangle. The only trigonometry book I kept from school doesn't answer this very well. Can anyone help?

(http://www.agpgames.com/ThetaOfWeirdTri.png)
Title: Re: A Little Trigonometry
Post by: EgonOlsen on May 30, 2009, 07:07:24 pm
You have the vectors from center to direction (a) and from center to pointer (b). With that, you can do

cos(a,b)=a*b/(|a||b|)

(with * being the scala/dot product between of a and b). Calculate the arccos from this value you should have your angle.
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 07:40:07 pm
I don't think I got this right:
Code: [Select]
SimpleVector center = opponentCar.getTransformedCenter();
SimpleVector a =  new SimpleVector(center);
a.add(direction);
SimpleVector b = new SimpleVector(center);
b.add(opponentPointer.getTransformedCenter());
float cosine = a.calcDot(b)/(Math.abs(a.x*a.y)*Math.abs(b.x*b.y));
float arcCosine = (float)Math.acos(cosine);
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 08:18:01 pm
Another method is to use normal vectors and the law of cosines.

Before I start, are vectors Direction and CarPointer world coordinates or are they direction vectors from CarCenter?

If they are world coordinates, get them into direction vectors first:
Code: [Select]
SimpleVector a = directionCoordinate.calcSub( carCenter ).normalize();
SimpleVector b = carPointerCoordinate.calcSub( carCenter ).normalize();

If they are already direction vectors from CarCenter, then just normalize them:
Code: [Select]
SimpleVector a = new SimpleVector( direction ).normalize();
SimpleVector b = newSimpleVector( carPointer ).normalize();

Now think of vectors "a" and "b" as two sides of a triangle, both with length=1 (since we normalized them).  To get the length of the third side:
Code: [Select]
float lengthC = b.calcSub( a ).length();
So now you have three sides of a triangle.  To calculate the angle between sides A and B, use the law of cosines (plugging in values A=1, B=1, and C=lengthC):
Code: [Select]
float theta = (float) Math.acos( (2.0f - (lengthC * lengthC) ) / 2.0f );
NOTE: I used acos in the above formula, so theta will never come out to be more than PI radians (if the actual angle is larger, the formula will return 2*PI - angle).
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 08:45:11 pm
They're world coordinates. Pointer is taken from the center of the car's child (which is a lot like that target I made for my ship to follow in that other game, if you remember). Direction is a world coordinate that the car has to chase (it's pretty much on rails--I'll come up with a pathfinding algorithm later). So I did this, but the car still doesn't rotate right. Do you have any idea why? And thanks in advance, pal.
Code: [Select]
SimpleVector a = direction.calcSub(center).normalize();
SimpleVector b = opponentPointer.getTransformedCenter().calcSub(center).normalize();
float lengthC = b.calcSub( a ).length();
float theta = (float) Math.acos( (2.0f - (lengthC * lengthC) ) / 2.0f );
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 09:03:14 pm
So I did this, but the car still doesn't rotate right. Do you have any idea why? And thanks in advance, pal.
Code: [Select]
SimpleVector a = direction.calcSub(center).normalize();
SimpleVector b = opponentPointer.getTransformedCenter().calcSub(center).normalize();
float lengthC = b.calcSub( a ).length();
float theta = (float) Math.acos( (2.0f - (lengthC * lengthC) ) / 2.0f );
Hmm.. this looks right to me.  What kind of behavior are you seeing?

Perhaps it is a problem with the rotation axis.  Are your car, pointer, and direction all aligned on the x/z plane (i.e. is -y up)?  If not, you should rotate around the following axis:
Code: [Select]
SimpleVector rotationAxis = a.calcCross( b ).normalize(); // may need to switch a and b here
However, if everything is on a flat surface (-y is up), you should be able to just rotate around the y-axis.  Does it help to reverse the sign of theta? (perhaps it is rotating the wrong direction)
Code: [Select]
theta = -theta
Title: Re: A Little Trigonometry
Post by: EgonOlsen on May 30, 2009, 09:04:53 pm
Using the dot product, it should be something like this...

Code: [Select]
SimpleVector a= new SimpleVector(direction);
SimpleVector b = new SimpleVector(opponentPointer.getTransformedCenter());
b=b.calcSub(opponentCar.getTransformedCenter());
float cosine = a.calcDot(b)/(a.length()*b.length());
float arcCosine = (float)Math.acos(cosine);
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 09:18:36 pm
I'm rotating around the Z axis (+Z is up, the car moves on the X/Y plane). I'm not sure what your rotationAxis is suppose to do or for that matter where to plug it.
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 09:22:48 pm
And Egon, yours also doesn't work. Both are turning the car in odd directions, and I think it might be because my +Z is up. Sorry for the mathematical ignorance, but you forget so much after ten years without looking at this stuff much. Now, I'm thinking I'll brush up later. :-)

But here's a question: which method should be faster (yours or paulscode's), even if trivially? Are their results equally reliable?
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 09:33:18 pm
I'm rotating around the Z axis (+Z is up, the car moves on the X/Y plane). I'm not sure what your rotationAxis is suppose to do or for that matter where to plug it.
Using the X/Y plane should work the same as using the X/Z plane.  My point was, there might be a problem if "up" wasn't the same for all entities involved.  This doesn't sound like the case, though.

The rotationAxis would be for if the initial "up" for the car is different than the destination "up" (driving on a hill for example).  In that case you couldn't rotate the car around its Z axis or it wouldn't end up in the correct orientation.  So instead of a call to car.rotateZ( theta ); you would use car.rotateAxis( rotationAxis, theta );
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 09:37:17 pm
Got it, thanks. I'm going to turn this into an applet really fast and show you how it's turning with that code you posted (and I reposted!). But no, it's not a simple question of turning the opposite way unfortunately.
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 09:39:13 pm
Ok, great.  I had many problems with strange rotation behavior when working on my ship navigation system recently, so maybe it will be something I recognize.
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 10:47:47 pm
Do you happen to know what lwjgl's AppletLoader's error 7 is? It doesn't print anything other than that it's error 7 in the Java console.
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 11:05:50 pm
I've never received the AppletLoader Error 7 before myself, but Melssj5 had that problem before (this thread (http://www.jpct.net/forum2/index.php?topic=1123.0)).  He never mentioned if he figured out what was causing the problem, though.  Have you used the AppletLoader before?
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 11:19:22 pm
I searched the lwjgl forums a little.  Matzon stated that this indicates the AppletLoader failed in the STATE_SWITCHING_APPLET part (whatever that means).  Someone mentioned seeing this error when accidentally using an incorrect jar version.  You might check to make sure all the LWJGL jars are from the same version, and make sure any other jars you are linking to (i.e. jpct.jar) are the same versions you compiled with.

The other thing that was mentioned is signing issues, but I have never had any problem with this myself (using the LWJGL signed jars to load unsigned jars seems to work fine, and is really the whole point of the AppletLoader anyway).  Could you post the HTML you are using to launch the AppletLoader?
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 11:31:37 pm
Sure, and thanks for your suggestion, but I recopied the lwjgl applet files and nothing changed (same error 7).

Code: [Select]
<html>
  <applet code="org.lwjgl.util.applet.AppletLoader" archive="lwjgl_util_applet.jar, lzma.jar" codebase="."

width="800" height="600">
    <param name="java_arguments" value="-Xmx320m">
    <param name="al_title" value="Racer v. 0.1">
    <param name="al_main" value="Racer">
    <!-- logo to paint while loading, will be centered -->
    <param name="al_logo" value="Car.gif">
    <!-- progressbar to paint while loading. Will be painted on top of logo, width clipped to percentage done -->
    <param name="al_progressbar" value="appletprogress.gif">
   
    <!-- signed windows natives jar in a jar -->
    <param name="al_windows" value="windows_natives.jar.lzma">

    <!-- List of Jars to add to classpath -->
    <param name="al_jars" value="Racer.jar, jpct.jar, lwjgl_applet.jar.pack.lzma, lwjgl.jar.pack.lzma,

jinput.jar.pack.lzma, lwjgl_util.jar.pack.lzma, res.jar.lzma">
   
    <!-- signed linux natives jar in a jar -->
    <param name="al_linux" value="linux_natives.jar.lzma">
   
    <!-- signed mac osx natives jar in a jar -->
    <param name="al_mac" value="macosx_natives.jar.lzma">

    <!-- signed solaris natives jar in a jar -->
    <param name="al_solaris" value="solaris_natives.jar.lzma">
   
    <!-- Tags under here are optional -->
   
    <!-- Version of Applet, important otherwise applet won't be cached, version change will update applet, must be

int or float -->
    <!-- <param name="al_version" value="0.8"> -->
   
    <!-- background color to paint with, defaults to white -->
    <!-- <param name="al_bgcolor" value="000000"> -->
   
    <!-- foreground color to paint with, defaults to black -->
    <!-- <param name="al_fgcolor" value="ffffff"> -->
   
    <!-- error color to paint with, defaults to red -->
    <!-- <param name="al_errorcolor" value="ff0000"> -->
   
    <!-- whether to run in debug mode -->
    <!-- <param name="al_debug" value="true"> -->
   
    <!-- whether to prepend host to cache path - defaults to true -->
    <param name="al_prepend_host" value="false">

  </applet>

  <p>
    if <code>al_debug</code> is true the applet will load and extract resources with a delay, to be able to see the

loader process.
  </p>

  </body>
</html>
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 11:43:56 pm
Does it help to use -Xmx300m instead of 320, or removing the parameter alltogether?
Title: Re: A Little Trigonometry
Post by: AGP on May 30, 2009, 11:56:48 pm
I took it out and it doesn't even get to the OutofMemoryError, but that was a good suggestion.
Title: Re: A Little Trigonometry
Post by: paulscode on May 30, 2009, 11:59:29 pm
That is really strange.  Have you tried loading a simpler applet to make sure the AppletLoader is able to load anything at all?
Title: Re: A Little Trigonometry
Post by: AGP on May 31, 2009, 12:04:58 am
Yes, and it loads fine. Ever since they made appletviewer (and just to be clear I'm not using it, I'm using Explorer and Firefox) security permission-aware, I've found applet writing and testing to be a nightmare, which is why I first wrote this program as an application. But I'll solve it soon enough, I suppose. Really, my only rush is that I don't want you to go offline! :-)
Title: Re: A Little Trigonometry
Post by: paulscode on May 31, 2009, 12:07:05 am
LOL, I'm working on my target shooter, so I should be around for a few hours.
Title: Re: A Little Trigonometry
Post by: AGP on June 03, 2009, 06:40:56 am
Here's a note: while Egon's code rotates the car even on straight lines, paulscode's code doesn't. So if either of you is reading, I think that's an important observation. I'll try and get this thing working in applet form and post a link here tomorrow.
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 03, 2009, 07:51:17 am
Here's a note: while Egon's code rotates the car even on straight lines, paulscode's code doesn't.
...is that a good or a bad thing... ???
Title: Re: A Little Trigonometry
Post by: paulscode on June 03, 2009, 12:57:31 pm
Here's a note: while Egon's code rotates the car even on straight lines, paulscode's code doesn't.
Since they don't behave exactly the same way, then one of the formulas is probably wrong (and guessing from past experience it is most likely mine).

I went back through this post to try and reconstruct what you are doing (I want to play around with a simple test case to try and get it working).  Correct me if the following is incorrect:

You have a car object, whose world coordinate is called "car center".  It has a child dummy object called "car pointer" which is in front of the car (or whatever direction the car is currently traveling - could be in reverse or sliding on gravel, for example).  There is a world coordinate called "direction", which is where the car is trying to end up.  You move the car some amount each frame, in the direction of the "car pointer" child.  You also rotate the car some angle amount each frame (not greater than "theta", which is the angle between "car pointer" and "direction").  In theory, this should cause your car to eventually end up at (or at least orbit around) world coordinate "direction".
Title: Re: A Little Trigonometry
Post by: AGP on June 03, 2009, 05:45:45 pm
Egon: it's a bad thing in that the car rotates awckwardly even on straight lines. Then again, if the formula eventually works...

Paulscode: yeah, the child object is directly in front of it, but no, the car isn't using pointer to move, it's chasing an array of SimpleVectors. I put the pointer there just so I could get a triangle from which to calculate theta. And the car is moving exactly as it should except that after the first turn it's not facing forward.
Title: Re: A Little Trigonometry
Post by: paulscode on June 03, 2009, 05:48:04 pm
Hmm, having trouble visualizing.  So you are always moving in the correct direction, but the mesh is not lined up, correct?  Do the array of SimpleVectors represent "direction" then?  Perhaps a diagram and some code segments might help.
Title: Re: A Little Trigonometry
Post by: AGP on June 03, 2009, 05:54:57 pm
The world coordinates are invisible. I store them by setting the program in store mode, at which point the opponent car doesn't move and I drive in its place while my program stores my position. The rest you can see in the screenshot:
(http://www.agpgames.com/CarRacer.jpg)
Title: Re: A Little Trigonometry
Post by: paulscode on June 03, 2009, 06:04:18 pm
Ok, I think I got how it works.  Let me rephrase, and tell me if I still don't have the system correct:

You store a number of world coordinates in an array, which more or less defines a path for the car to follow.  Each frame, the car moves some amount toward the first coordinate, and when it gets there, it moves toward the second, etc.  Since the coordinates are not always directly in front of the car, it must rotate.  The car's world position is "car center", the coordinate that the car is currently moving toward is called "direction", and the car has a dummy child object called "car pointer" which is in front of the car.  In order to get the car pointed in the direction that it is traveling, you also rotate the car some angle amount each frame (not greater than "theta", which is the angle between "car pointer" and "direction").
Title: Re: A Little Trigonometry
Post by: AGP on June 03, 2009, 06:10:41 pm
Almost right, except that I'm only rotating the car every time I increment the array index of world coordinates, as opposed to in every iteration of the game loop.
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 03, 2009, 07:33:51 pm
The rest you can see in the screenshot:
(http://www.agpgames.com/CarRacer.jpg)
...nifty!
Title: Re: A Little Trigonometry
Post by: AGP on June 03, 2009, 07:58:59 pm
Thanks, man, and I wish I could take credit for the model, but I bought it. :-) I'm preparing a website with a bunch of things I did over the years (since I didn't finish a lot there's a lot to do), but I'll be posting the links here as I finish them.
Title: Re: A Little Trigonometry
Post by: paulscode on June 04, 2009, 02:03:55 am
Ok, I made a test applet.  I found one problem with the formula I wrote - namely that it only produces a positive angle, so you can't simply use rotateZ() or the car will only rotate counterclockwise.  I fixed that problem by defining the rotationAxis as I mentioned in an earlier post:
Code: [Select]
                SimpleVector rotationAxis = a.calcCross( b );
                car.rotateAxis( rotationAxis, theta );

This greatly improves the movement, but there is still a problem with some of the rotations being not quite right.  I'll work on this some more to see if I can solve the problem.

Here is the test applet (let me know if this is the same behavior you are seeing in your program):

Car Navigation Applet (http://www.paulscode.com/source/CarNavigation/)  (Source (http://www.paulscode.com/source/CarNavigation/CarNavigation.java))
Title: Re: A Little Trigonometry
Post by: AGP on June 04, 2009, 03:07:21 am
I applied your change and the result is conservative (it does most of the rotation, maybe 70% of it, but never quite enough). Also, since my streets always turn left, I didn't get to test a right turn, although I supposed your applet does. Here's a quick question: why doesn't the following code produce an even speed?

Code: [Select]
float translationAmount = 72f/distance; //MOVE % OF distance THAT EQUALS 72
SimpleVector directionMoved;
directionMoved = moveTowardsMinusZ(opponentCar, rails[currentDirectionMarker], translationAmount);
//THE DISTANCE IS UPDATED WHENEVER calculateDistance(opponentCar.getTransformedCenter(), rails[currentDirectionMarker]) < 1f AND currentDirectionMarker IS INCREMENTED
//THEN THE MOVETOWARDS METHOD CALCULATES THE DELTAS:
float deltaX = (direction.x -origin.x)*translationAmount;
float deltaY = (direction.y -origin.y)*translationAmount;
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 04, 2009, 07:15:49 am
Here's a quick question: why doesn't the following code produce an even speed?
Maybe because of an uneven frame rate? Just a wild guess...
Title: Re: A Little Trigonometry
Post by: AGP on June 04, 2009, 08:06:13 am
It's not supposed to be uneven, it sleeps 40 milliseconds, and draws. Obviously some parts of the model might take a little longer to render, but it should hardly be perceptible, and it really really is.

Egon, on yet a separate question (not to draw too much attention away from the bigger turning problem), how do I draw an image onto the buffer using the hardware renderer outside of an AWTGLCanvas? I've tried reading the lwjgl docs, but I found nothing there. I even tried looking for my C OpenGL programs of so many years ago, but I can't find them right now. Do you know?
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 04, 2009, 08:21:58 am
You could load the image into a texture and blit it. Or does it change constantly?

Edit: About the turning problem: Wasn't my approach sufficient?
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 04, 2009, 08:24:01 am
It's not supposed to be uneven, it sleeps 40 milliseconds, and draws. Obviously some parts of the model might take a little longer to render, but it should hardly be perceptible, and it really really is.
Have you printed out deltaX and deltaY to see how they actually change over time?
Title: Re: A Little Trigonometry
Post by: AGP on June 04, 2009, 08:39:30 am
Thanks for the blitting suggestion, I'm about to try it out. But yes, it's really an animated gif I want drawn (but at this point I'll settle for a static one).

And no, your approach constantly rotates the car in odd angles. The closest one so far has been paulscode's latest, but even that doesn't quite do it.

And I solved the speed issue (I was working from a constantly-updated carCenter). Sorry if I waste your time sometimes, but I really try not to. This particular question took me a couple of days to ask. I think something happens with me after I ask a question (unburden myself, I suppose!) that I think more clearly. So, down to two problems. :-)
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 04, 2009, 09:06:53 am
Well, if all you want to to rotate something to match another vectors direction, maybe getRotationMatrix() in SimpleVector helps to solve this? I usually prefer to work with matrices instead of euler angles...causes less trouble in the end.
Title: Re: A Little Trigonometry
Post by: AGP on June 04, 2009, 09:22:18 am
If you mean something like the following, it didn't do it for me either.

Code: [Select]
opponentCar.setRotationMatrix(rails[currentDirectionMarker].getRotationMatrix());
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 04, 2009, 09:57:34 am
But it should...are you sure that the problem isn't somewhere else? Maybe in the direction markers themselves?
Title: Re: A Little Trigonometry
Post by: paulscode on June 04, 2009, 12:55:59 pm
A note on constant speed (regardless of framerate).  I usually define a "distancePerMilli" float and a "lastTick" long:

Code: [Select]
    private float distPerMilli = 0.018f;
    private long lastTick;

When initializing, I give "lastTick" its initial value:
Code: [Select]
        lastTick = System.currentTimeMillis();
Then when moving the object, I determine how long it's been since the last move, and calculate how far to move the object:
Code: [Select]
        long currentTime = System.currentTimeMillis();
        long millisPast = currentTime - lastTick;
        lastTick = currentTime;

        float distance = millisPast * distPerMilli;

Then given a direction, I normalize, scalarMul by the distance, and apply the translation:
Code: [Select]
SimpleVector trans = new SimpleVector( direction ).normalize();
trans.scalarMul( distance );
myObject.translate( trans );

I also use this type of setup for rotations, special effects, and so on.  Higher framerates, of course, have smoother movement, but things will move the same speed for both high and low framerates.
Title: Re: A Little Trigonometry
Post by: AGP on June 05, 2009, 01:11:59 am
Egon, what kind of a problem could the direction markers have? I store the center of my car and the opponent car follows the markers perfectly.

And paulscode, thanks for the tip, but on a PC (where performance differences won't be that significant) isn't that a little bit overkill?
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 09:10:56 am
You should always time your game logic based on ticks or real ms (whatever you prefer, i prefer ticks as they are easier to handle IMHO). Imagine the game running on a slow PC or with AA enabled or swapping causes a slow down or...

Could you provide the marker data in form of a simple ASCII-file or something? I would like to try this myself to see what's actually going on. Getting the rotation matrix out of the direction vector and apply it has to work IMHO... ???
Title: Re: A Little Trigonometry
Post by: AGP on June 05, 2009, 09:36:24 am
I e-mailed it to you. It's not an ASCII, but all you need is DataInputReader to read the floats.
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 09:50:36 am
I tried this:

Code: [Select]
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...
Title: Re: A Little Trigonometry
Post by: AGP on June 05, 2009, 09:56:03 am
Sorry, I forgot to tell you that the first 4 bytes are an int that informs us how many SimpleVectors (not floats) are stored.
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 10:02:46 am
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!?
Title: Re: A Little Trigonometry
Post by: AGP on June 05, 2009, 10:07:02 am
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.
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 10:22:04 am
The car is following nicely...so what doesn't work actually? Just the rotation?
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 03:19:03 pm
This seems to work...:

Code: [Select]
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...
Title: Re: A Little Trigonometry
Post by: AGP on June 05, 2009, 06:38:07 pm
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?
Title: Re: A Little Trigonometry
Post by: AGP on June 05, 2009, 07:02:55 pm
Never mind my question, I can see it in your loading code. So then, can you explain the rest to me?
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 07:03:20 pm
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.
Title: Re: A Little Trigonometry
Post by: 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?
Title: Re: A Little Trigonometry
Post by: AGP on June 05, 2009, 07:41:58 pm
Rotating obviously messed everything up. What about world.rotateAxis(world.getXAxis(), float) methods? I think it would be very handy.
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 07:59:20 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.
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 08:03:34 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...
Title: Re: A Little Trigonometry
Post by: EgonOlsen on June 05, 2009, 08:57:41 pm
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 (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.

Code: [Select]
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.
Title: Re: A Little Trigonometry
Post by: AGP on June 06, 2009, 10:33:05 pm
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.