I’ve been trying to do the above but it seems my understanding of this has come to a grinding halt.
Result wanted:
The player selects a star then click the “Plot course” button.
Using the Autopilot class that is attached to AppStates, the ship is steered and moved toward the destination until it arrives. At which point the ship stops and that’s it.
I’ve got pretty much everything working except for the angle gathering. From traces I’m printing, the angle seems wrong.
When turning the autopilot on, I compute the angles I need for the ship to face the destination. My guess is, that’s wrong. Here’s the method.
[java]
private void setDegreesToRotateToDestination() {
// get angle we have to move to face destination for each
// axis.
Vector3f v1 = playerShip.getLocalRotation().getRotationColumn(0);
Vector3f v2 = destination.clone().normalizeLocal();
float angleX = FastMath.acos(v1.dot(v2)) * FastMath.RAD_TO_DEG;
v1 = playerShip.getLocalRotation().getRotationColumn(1);
float angleY = FastMath.acos(v1.dot(v2)) * FastMath.RAD_TO_DEG;
v1 = playerShip.getLocalRotation().getRotationColumn(2);
float angleZ = FastMath.acos(v1.dot(v2)) * FastMath.RAD_TO_DEG;
degrees = new Vector3f(angleX, angleY, angleZ);
System.out.println("Degrees : " + degrees);
}
[/java]
Once I have that data, I rotate each axis at the speed that each axis allows (depending on the class of ship) then subtract that until I get to 0 which in that case, would mean that particular axis is facing the destination vector.
That doesn’t quite work either. So I’m wondering if the algorithm above is right.
Got it to somehow work…
The ship turns and stops when it thinks it points in the right direction but that direction is always wrong.
Could I get a yes/no about the above method? That would help a lot to troubleshoot knowing that it’s right or not.
COUGH
I think I’m on the verge of… either shooting myself or finding a solution. Don’t know which. I hope it’s a solution.
Why you do it so complicated, lookAt does basically what you need if I understand you right:
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies
lookAt will turn the ship in one frame.
Each ship class has it’s own turn rate. Battleships turn a lot slower than Corvette.
Because of that I have to gather the angles for each axis and turn the ship the number of radians it is allowed to at each frame until it faces the right direction. I have to do that for X, Y and Z axis.
Right now I’m contemplating using a hack, kinda, by using a node that I would turn using lookAt(destination, Unit_Y) and get the angles by using angleBetween of the ship and that node, for each axis.
Quaternion.slerp()?
I read a bit about slerp, but I don’t get 100% the interpolation and how it works.
The main problem is that destination is a position vector and slerp needs a quaternion. Afaik, there’s no way to make a quaternion out of a position vector.
… LookAt? Your brain is like a swiss cheese
You’re too clear. Can you be a little less clear?
If you have a direction vector then you can create a Quaternion. This is essentially what lookAt() is doing. So look in there to see.
Once you have a source quaternion and target quaternion then you can interpolate between them using slerp at whatever rate you like. It’s really the only good way to interpolate between two separate 3D rotations.
The only good way to do it with angles would be to limit your game mechanics to two dimensional flight so that you only have one rotation. Otherwise things get really messy since each interpolation in one axis potentially changes how much you should interpolate in the others. And then there’s gimbal lock, etc. to worry about, too.
I’m sorry but F* that.
I’ve been breaking my head in tiny bits and pieces without a solution so I’ll just play it by ear and have it work that way. Not efficient, ugly as hell but at least it’ll work.
I’ll just increase speed and check if the distance is bigger or smaller and adjust accordingly.
Actually, Quaternion.lookAt may do everything you want… no reason to look into it as I suggested. Sorry if that threw things off.
If you know the position of your ship and the position you’d like it to be looking (like, you want to look at some sun for example):
Something like:
[java]
Vector3f dir = sunPos.subtrace( shipPos ).normalize();
Quaternion targetRotation = new Quaternion().lookAt( dir, Vector3f.UNIT_Y );
[/java]
Then slerp between current rotation and target rotation. Or if you want something independent that you can just add every frame, use the slerp to find a delta and just keep multiplying that by your ship’s orientation each frame… just be aware of floating point error drifting in and correct it every so often.
…at least that’s one approach.
I’ll give this one last shot. If I don’t get some results I’ll postpone it indefinitely. It is becoming extremely frustrating. deep breath
Alright. So. I’m wondering here if it makes a difference that I’m using a spatial. I don’t think it would, but who knows.
Thank you Paul for your snippet. It’s very much appreciated although I had to modify it a bit mainly because Quaternion().lookAt returns void. Anyway. It looks like this:
[java]
private Quaternion test() {
Vector3f dir = destination.subtract(playerShip.getWorldTranslation()).normalize();
Quaternion rotator = playerShip.getWorldRotation();
rotator.lookAt(dir, Vector3f.UNIT_Y);
return rotator;
}
[/java]
As a test I take that rotator quaternion and do
[java]
// rotate spatial to point toward destination
playerShip.rotate(rotator)
[/java]
That does the trick. So it means the “lookat” quat is pointing in the right direction, as expected.
My problem is with using the slerp. I understand that that method will give me an interpolation between a starting rotation state to an ending rotation state. How many states (or updates in a way) to get from start to end is defined by setting the third param in the method (float t) which is from time 0.0 (original rotation) to time 1.0 (end rotation).
In my case, each ship class has a turn rate (on each axis) that is mostly unique to that class. As I explained above, a battleship turns much slower than a destroyer or a corvette. Those turn rates are radian-based and are defined in radians-per-second.
Now, for this exercise let’s assume the ship has no forward motion; is stationary. In that case, it should be, in principle, easy to rotate by using the slerp value by using an iteration derived from the turn rate (which should be the same as the float t parameter).
But, because the ship will move as soon as the angle is smaller than 90 degrees, everything flies off the window and everything has to be recalculated at each frame because the ship has slightly changed position and rotation.
That’s my understanding of the problem right now. Please, if I’m wrong, do tell me.
[java]
//low shipfactor for slow ships
float amount += shipfactor*tpf;
if(amount<=1){
Vector3f dir = destination.subtract(playerShip.getWorldTranslation()).normalize();
Quaternion rotator = playerShip.getWorldRotation();
//up vector has to be proper up vector for given object
Vector3f playerUp = playerShip.getLocalRotation().mult(Vector3f.UNIT_Y);
rotator.lookAt(dir, playerUp);
//rotate ship only by amount into direction
playerShip.setLocalRotation(playerShip.getLocalRotation().slerp(rotator, amount));
}
[/java]
That doesn’t sound right. spatial.setLocalTranslation() should be independent of spatial.setLocalRotation(), ie: the translation should put it in world space and the rotation should rotate it at that world space location. So something sounds weird about your setup.
Also, regarding the “turning a certain number of radians per second”, unless you are only doing rotations in 2D, I doubt this is really the case. Or if it is… how do you get the 3D delta in rotations such that the radians are at all meaningful? ie: can the ship pitch and yaw at the same time the same number of radians per second?
Anyway… given some arbitrary direction in space and some second arbitrary direction in space, it is possible to determine the “angle” between them using a 3D dot product and an asin(). There are probably some boundaries and edge cases to look out for though. The important thing to remember is that the dot product of two unit vectors is the sin of the angle between them. So when they point in the same direction, it’s 1.0 when they point in opposite directions then it’s -1.0 and at 90 degrees its 0, and so on.
In theory, it’s something like:
[java]
Vector3f shipDir = normalized ship direction…
Vector3f targetDir = destination.subtract(playerShip.getWorldTranslation()).normalize();
float sin = shipDir.dot(targetDir);
float totalRads = FastMath.asin(sin);
Quaternion targetRotation = new Quaternion();
targetRotation.lookAt(targetDir);
Quaternion currentRotation = playerShip.getWorldRotation();
// Now figure out the linear interpolation that would be x rads per second
// What ‘percentage’ is radsPerSecond of the total rads we need to turn?
// that will be the amount of slerp needed to be a radsPerSecond amount
// of rotation in that direction.
float amount = radsPerSecond / totalRads;
Quaternion deltaRotation = new Quaternion();
deltaRotation.slerp( currentRotation, targetRotation, amount );
[/java]
Then, in theory, each frame you can take the ships rotation, calculate the quat that is current * delta and then slerp( currentRotation, currentTimesDeltaRotation, tpf )… or something like that.
pspeed said:
That doesn't sound right. spatial.setLocalTranslation() should be independent of spatial.setLocalRotation(), ie: the translation should put it in world space and the rotation should rotate it at that world space location. So something sounds weird about your setup.
If you move a spatial in any direction then your angle from this point will be different from the previous point to your destination. I have to specify here that I use spatial.move(vector) and not setLocalTranslation when the ship moves. I don't know if it makes a difference though. Anyway, I do think the rotation will be different. Maybe I'm misunderstanding what you're saying.
Also, regarding the "turning a certain number of radians per second", unless you are only doing rotations in 2D, I doubt this is really the case. Or if it is... how do you get the 3D delta in rotations such that the radians are at all meaningful? ie: can the ship pitch and yaw at the same time the same number of radians per second?
For now, pitch/yaw/roll are set to the same value. HALF_PI / 60 which gives about 0.26 radian or 1.5 degrees if I'm not mistaken. I should not have said per second as this is untrue right now, but that's the plan to set it so the ship would turn X radian in a given time on each axis.
The original plan was to fetch the number of degrees (or radians) separating the current location to the destination for each axis and in the update() method decrease that number of radians by the number of radians a ship can rotate on those axis. To put it simply, if there were 100 radians of difference on the X axis, I would pitch the ship .26 radian then subtract .26 from the difference (100) until I'd get to zero. At that point, the current axis and the destination's should be parallel. When each axis is parallel, the ship should theoretically, be heading straight at the target.
Is that sound?
Anyway... given some arbitrary direction in space and some second arbitrary direction in space, it is possible to determine the "angle" between them using a 3D dot product and an asin(). There are probably some boundaries and edge cases to look out for though. The important thing to remember is that the dot product of two unit vectors is the sin of the angle between them. So when they point in the same direction, it's 1.0 when they point in opposite directions then it's -1.0 and at 90 degrees its 0, and so on.
Thanks for that explanation. If my memory serves and is still working, that would be Gimbal Lock.
In theory, it's something like:
[java]
Vector3f shipDir = normalized ship direction...
Vector3f targetDir = destination.subtract(playerShip.getWorldTranslation()).normalize();
float sin = shipDir.dot(targetDir);
float totalRads = FastMath.asin(sin);
Quaternion targetRotation = new Quaternion();
targetRotation.lookAt(targetDir);
Quaternion currentRotation = playerShip.getWorldRotation();
// Now figure out the linear interpolation that would be x rads per second
// What 'percentage' is radsPerSecond of the total rads we need to turn?
// that will be the amount of slerp needed to be a radsPerSecond amount
// of rotation in that direction.
float amount = radsPerSecond / totalRads;
Quaternion deltaRotation = new Quaternion();
deltaRotation.slerp( currentRotation, targetRotation, amount );
[/java]
Then, in theory, each frame you can take the ships rotation, calculate the quat that is current * delta and then slerp( currentRotation, currentTimesDeltaRotation, tpf )... or something like that.
Thanks a lot! I'll try that as soon as the headache is gone. ;)
It's very much appreciated. You have no idea how much.
normen said:
[java]
//low shipfactor for slow ships
float amount += shipfactor*tpf;
if(amount<=1){
Vector3f dir = destination.subtract(playerShip.getWorldTranslation()).normalize();
Quaternion rotator = playerShip.getWorldRotation();
//up vector has to be proper up vector for given object
Vector3f playerUp = playerShip.getLocalRotation().mult(Vector3f.UNIT_Y);
rotator.lookAt(dir, playerUp);
//rotate ship only by amount into direction
playerShip.setLocalRotation(playerShip.getLocalRotation().slerp(rotator, amount));
}
[/java]
Thanks @normen I appreciate that. I'll look and play with this and see how it goes.
madjack said:
If you move a spatial in any direction then your angle from this point will be different from the previous point to your destination. I have to specify here that I use spatial.move(vector) and not setLocalTranslation when the ship moves. I don't know if it makes a difference though. Anyway, I do think the rotation will be different. Maybe I'm misunderstanding what you're saying.
Yeah, ok. I get it. I just didn't understand the comment about it shooting off somewhere strange. The ship shouldn't move more than you're letting it move and if you re-evaluate the target direction as it moves then it should eventually zero in on the location... presuming that's a solvable problem. For example, it is possible for a large ship (ie: slow turning radius) to never be able to plot a course to a nearby point this way because it will never be able to move and turn enough to hit it... at least not without flying far away first.
pspeed said:
Yeah, ok. I get it. I just didn't understand the comment about it shooting off somewhere strange. The ship shouldn't move more than you're letting it move and if you re-evaluate the target direction as it moves then it should eventually zero in on the location... presuming that's a solvable problem. For example, it is possible for a large ship (ie: slow turning radius) to never be able to plot a course to a nearby point this way because it will never be able to move and turn enough to hit it... at least not without flying far away first.
Yes, you're right about that. There's this "dead angle" where a ship with a small turn radius would be unable to attain. But that can be solved by using the turn rads into consideration when the decision to apply forward thrust arrives. So if the turn radian is small, the point where full speed/medium speed/low speed is applied should be adjusted accordingly.
In short, instead of going full speed at 45 deg in a Corvette, a Battleship would probably do so at 15. Not all ships have their rotation "speed" set, only a couple and it's just there for testing purpose. Nonetheless, I will now keep this in mind.
Now all I want to do is have the ship turn as it is stationary according to its set rotation speed. Once that's done, it shouldn't be "too hard" to compensate for the rest. But huh, yeah. ;) We'll see about that.
Thanks!
It seems @normen is the ultimate winner.
I simply put that snippet and the magic works even while moving. Just shows how little I know.
I’m trying to understand what’s going on in those lines and I think I get it. But I’ll have to reread this many many times before I finally and completely get it.
I know how your guys’ time is valuable and important and I also know it’s not always easy, obvious or even tempting or fun to help some of us, but I’ll speak on everyone’s behalf and say that you all rock. You truly are class acts.
@normen @Momoko_fan @nehon @pspeed thank you!