[SOLVED] Controlling the rotation of a naval gun mounted on a ship

Okay, I thought this would be plain and simple, but apparently I am missing something. Let me sketch what I want to achieve:

I have my ship with a gun mounted on it. The gun has to track the destignated target and follow it by rotating around its y-axis. The gun has to be constrained when it comes to rotating to prevent it from shooting at the own ships bridge, for instance. So, in rest, the gun is pointing forward an when it starts to track, it is not allowed to rotate more then, let’s say, 120 degrees to both sides.

Tracking the target is not the problem, I solved that with something @pspeed mentioned in another topic:

targetVec = gun.getTarget().getWorldTranslation().subtract(gun.gunNode.getWorldTranslation());
gunDirection = gun.gunNode.getWorldRotation().mult(Vector3f.UNIT_Z.negate());
dotTargetToGun = gunDirection.dot(targetVec);

Rotation is then handled by:

               if (dotTargetToGun > 0.0f)
                {
                    rotationDirection = LEFT;
                    rotationSpeed = speedLow;
                    if (dotTargetToGun > 0.5f)
                    {
                        rotationSpeed = speedHigh;
                    }
                }
                else if (dotTargetToGun <= 0.0f)
                {
                    rotationDirection = RIGHT;
                    rotationSpeed = speedLow;
                    if (dotTargetToGun < -0.5f)
                    {
                        rotationSpeed = speedHigh;
                    }
                }
                gun.gunNode.rotate(0f, rotationDirection*rotationSpeed*tpf, 0f);

Which seems to be working just fine.

Problem is the rotation constraint. If I create 4 quadrants to see where the target is in relation to the ship (Port Bow = 1, Starboard Bow = 2, Starboard Stern = 3 and Port Stern = 4) I need the gun to behave smart. When the target moves at the port side, the gun turns left and when the target is more to the back of the ship (quadrant 4), the gun will reach its constrain and should stop. As soon as the target moves more to the front on the port side, the gun has to start following it again. But when the target goes from quadrant 4 to quadrant 3, the gun should turn right until it has reached its starboard constrain and then wait until it can start tracking the target again.

I have tried a lot, but nothing is working and I am running out of ideas. I thought creating quadrants was solving my problem (I create them by a dot-product of the ships direction with the targetVector and another dot product of the ships direction (but 90 degrees rotated and the targetVector. Then with some IF-statements, I can define the quadrant:

targetVec = gun.getTarget().getWorldTranslation().subtract(gun.gunNode.getWorldTranslation());
            shipsDirection = gun.ship.getDirectionVector();
            shipsDirection90 = new Quaternion().fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0)).mult(shipsDirection);
            dotTargetToGun = gunDirection.dot(targetVec);
            dotGunToShip = gunDirection.dot(shipsDirection);
            dotTargetToShip = shipsDirection.dot(targetVec);
            dotTargetToShip90 = shipsDirection90.dot(targetVec);               
//  Get the quadrant the target is in:
                if (dotTargetToShip >= 0 && dotTargetToShip90 < 0)
                {
                    targetQuadrant = 1;
                }
                else if (dotTargetToShip < 0 && dotTargetToShip90 < 0)
                {
                    targetQuadrant = 2;
                }
                else if (dotTargetToShip < 0 && dotTargetToShip90 >= 0)
                {
                    targetQuadrant = 3;
                }
                else if (dotTargetToShip >= 0 && dotTargetToShip90 >= 0)
                {
                    targetQuadrant = 4;
                } 

Whatever I try, everytime the gun bums into it’s constrain, it get’s stuck. I think it makes sence, because I am checking

if (dotGunToShip < traverseMax)

in which traverseMax is the angle where the constrain is (in this example 120 degrees) which I have converted to a value which I can compare with the dotGunToShip by simply

(1f - (angle/90f));

In which angle = traverseMax.

I assume, by rotating the gun it will never stop at exactly traversMax. I think it passes it and then if (dotGunToShip < traverseMax) will always be true and the gun will never rotate again.

Well, long story short: I am sure there is a much easier way to accomplish this, I just don’t see it and could appreciate some help. Something tells me I am just making this too complex.

Husky

1 Like

I think your approach here makes sense, although I think you might be able to simplify the quadrant math a bit. (I’ll leave that to someone with more experience in dealing with rotations.) You’re right in that dotGunToShip will likely never stop at exactly traverseMax. I think you can solve your locking problem by checking the rotation against the constraint. If it passes the constraint, cap the motion by setting its rotation so that its rotation angle is equal to the constraint. That way it can never pass out of bounds and any if-in-bounds-rotate logic should continue to operate unmodified.

3 Likes

Yes clamp to constraint if constraint violated. That way it is always in a legal state which is typically important. Still have to take care of > vers >= etc. but that is mostly easy enough.

2 Likes

I only skimmed the second half… if it were me, I’d probably just keep two reference frames. One is the ‘center’ orientation of the gun. This never moves. The other is the actual orientation of the gun.

Using the same technique you use to track the gun you can also determine if the gun is going too far off of the ‘center’ orientation. Just do a dot product between the gun orientation and the center orientation and that should give you a value that will tell you if you have exceeded some turn amount.

If you need a different left/right range versus up/down range then you will have to do it with the side and up vectors instead. If your ranges are symmetric, though, you can do it with just a dot product on the ‘look’ vectors of each orientation.

…maybe that makes sense only to me. :slight_smile:

2 Likes

Makes sense indeed. I think I can do a setLocalRotation() to the constraint angle to prevent the locking indeed. The quadrant method is working as far as I can see, so I will try to get it working. I need the quadrants, because the dot-value does not make any difference in side (except front and rear). I will give it a try tomorrow. Feels like I am almost there. Frustrating :triumph:

1 Like

Uhm… I can follow your first part @pspeed, that makes sense to me and could be a real easy solution. The last paragraph needs some more explanation if I would decide to go there… LOL.

I will focus on the traverse first (horizontal rotation) and come to the elevation of the barrel later. In my previous version I had a nice working elevation formula for calculating the elevation based on target range and shell velocity. Need to dig that up again. But I remember struggling with the bearing that time as well. So first things first: solve the bearing or traverse or horizontal rotation or whatever it is called :grin:

1 Like

I in fact do it the pspeed way in my game. However i have only 2d gun rotation right now. But back when i did have full 3d. I would use projected vectors onto the respective 2d planes for angle checks. I also just use the vector3f.angleBetween method.

1 Like

Now I think about it, I think I already do what you are suggesting. My dotGunToShip is the dot between the direction vector of the gun and the direction vector of the ship. And the ship’s direction vector is of course my gun’s center. The gun is a child of the ship.

Need some sleep. Have to think this over again!

1 Like

By gun center I mean the center direction of where it’s possible for the gun to point. If you dot the gun’s current vector with this vector then you will get a value that is the cosine of the angle. Easily used for clamping a range. That range will be circular, however.

In any direction, there is the forward vector (Z) and the left vector (X) and the up vector (Y). If you want a ‘square’ range then you will have to do a dot product with the local X and local Y vectors instead of the Z vector. dir.dot(left) will be the sin of the angle on the local x axis. dir.dot(up) will be the sin of the angle on the local y axis.

1 Like

@pspeed
I don’'t think this works when the ship is free to move, right? Because the ship is moving on the waves and able to steer in all directions in the world, I need to get the actual world direction of the ship every frame, because the “gun center” is related to the ships hull, not to the world. Else it would not make any sense to do a dot calculation.

That’s why I do a dotGunToShip, because I need to know the rotation of the gun, relative to the ship, not to the world.

Or am I missing something (again)?

1 Like

Of COURSE the gun center orientation is related to the direction of the ship. Just like a child spatial. I didn’t even think that would be in question… but I don’t understand why that’s an issue.

1 Like

Yeah, I don’t understand it either. Where am I with my head? :slight_smile:

Somehow we are not at the same frequency here… I must be misinterpretating something and not using my brains. I will dig into the dot products and rotations some more and start from scratch. I am making a thinking error here which makes it impossible for me to see the (most likely simple) solution. Let me reset my head and start over with the advise and directions you all gave me and some more research of the math. How hard can it be? :joy: Must have been too long ago for me, I am getting rusty!

Need to find the logic again!

1 Like

The gun is mounted on the ship. There must be some concept of “this is the default orientation of the gun”… ie: it’s ‘center rotation’. That orientation in world space will have to be relative the ship, obviously.

Quaternion gunDefaultWorldRot = ship.getWorldRotation().mult(gunDefaultRotation) or whatever.

Vector3f defaultDir = gunDefaultWorldRot.mult(Vector3f.UNIT_Z);
Vector3f gunDir = gunWorldRot.mult(Vector3f.UNIT_Z);

float dot = gunDir.dot(defaultDir);

if( dot < 0.707 ) {
    // gun is within 45 degrees of its default rotation in all directions
}
2 Likes

@husky
I found it useful to debug such things with helper dots sorta
new Line(Vector3f.ZERO, Vector3f.ZERO);
and setting their local translations according to calculated Vector3f’s, line by line if necessary. Sometimes those appear to be not what you think they are, and if you have few operations in a row, tracing an error just in your head may become a real madness.

2 Likes

Here’s the code I use to rotate my turret modules if it helps:

private void stepPadAndCannon(Vector3f localVec, float k){
	localVec.subtractLocal(0,-0.06661f,0.22759f);
	localVec.normalizeLocal();
	
	// cannon angle //
	
	float delta = FastMath.abs(dir.z-localVec.z)*50f;
	if(delta > 0.7f)
		delta = 0.7f;
	
	if(localVec.z > dir.z)
		angle+=delta*k;
	else if(localVec.z < dir.z)
		angle-=delta*k;
	
	
	if(angle < -10)
		angle = -10;
	else if(angle > 95)
		angle = 95;
	
	//-------------//
	
	
	// turret rotation
	
	float pad2 = -FastMath.atan2(localVec.x, localVec.y)*FastMath.RAD_TO_DEG;
	
	delta = FastMath.abs(pad-pad2)*50f;
	if(delta > 0.6f)
		delta = 0.6f;
	
	float diff = pad - pad2;
	if (diff < 0)
		delta = -delta;

	if (FastMath.abs(diff) > 180)
		delta = -delta;

	pad+=delta*k;
	
	//-------------//

	
	Quaternion rot = new Quaternion().fromAngleAxis(pad*FastMath.DEG_TO_RAD, Vector3f.UNIT_Z);
	turret.setUserTransforms(new Vector3f(), rot, Vector3f.UNIT_XYZ.clone());
	
	Quaternion rot2 = new Quaternion().fromAngleAxis(angle*FastMath.DEG_TO_RAD, Vector3f.UNIT_X);
	cannon.setUserTransforms(new Vector3f(0,-0.06661f,0.22759f), rot2, Vector3f.UNIT_XYZ.clone());
}

Called as:

stepPadAndCannon(worldToLocal(target.getWorldTranslation(), new Vector3f()),tpf*60);

Note that this isn’t restricted on the turret rotation axis and it can turn completely vertical if needed, but depress for only about 10-5 degrees.

Oh and ignore the (0,-0.06661f,0.22759f) value, that’s the cannon’s up/down rotational center location which is probably (0,0,0) for you or something.

The turret and cannon objects are bones and the cannon is linked to the turret so it follows its rotation.

How it looks:

2 Likes

Phew, that was harder than I thought… :sweat_smile:

But it is working now. Thanks for all your advise! I now have created a prototype which is showing a simple ship with two guns (just made out of boxes). Both guns are pointing in opposite directions. The guns are able to track a target and when they reach their constrains, they stop. But as soon as the target gets into reach again, they start moving. That was not the most dificult part, I have to admit. The hardest part was to define an algorithm which makes the guns move in such a way that they always try to move as close to the target as possible. I am now at the laptop, so I can’t create a screencapture (way too slow) to show it. I will try to do so tomorrow. The code I am using for my controller is the following:

package mygame;

import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;

/**
 *
 * @author Arjen van der Beek (Husky)
 */
public class GunTurretControl extends AbstractControl
{
    FiveInchGun gun;
    
    float gunMaxRot;
    float gunDefaultAngle;
    float gunMaxRotForDot;
    Quaternion gunDefaultRot;
    Quaternion gunDefaultWorldRot;
    Quaternion gunWorldRot;
    Vector3f targetVec = new Vector3f(0f, 0f, 0f);
    Vector3f defaultGunDir;
    Vector3f gunDir;  
    Vector3f gunDir90;    
    float dotGunRot, dotGunRot90;
    float dotGunTarget90; //dotGunTarget
    float dotShipTarget90;  //dotShipTarget
    float dotGunShip90;    //dotGunShip  
        
    float MAXSPEED = 0.4f;
    float MINSPEED = 0.1f;
    float STOPSPEED = 0.0f;
    float rotSpeed = STOPSPEED;
    
    int LEFT = 1;
    int RIGHT = -1;
    int rotDir;
    
    boolean constrained = false;
    
    public GunTurretControl(FiveInchGun fig, float maxRot, float defaultangle)
    {
        gun = fig;
        gunMaxRot = FastMath.DEG_TO_RAD*maxRot;
        gunDefaultAngle = FastMath.DEG_TO_RAD*defaultangle;
        gunMaxRotForDot = FastMath.cos(gunMaxRot);  //  Create a value which "dot" can use as comparison.
        gunDefaultRot = new Quaternion().fromAngleAxis(gunDefaultAngle, new Vector3f(0,1,0));  //  The default rotation in relation to the ship.
        gun.gunNode.setLocalRotation(gunDefaultRot);
    }

    @Override
    protected void controlUpdate(float tpf) { 
        gunDefaultWorldRot = gun.ship.getRotation().mult(gunDefaultRot);  //  Calculate the world rotation of the gun center position, taking into account the rotation of the ship.
	defaultGunDir = gunDefaultWorldRot.mult(Vector3f.UNIT_X);  //  Create a direction vector for the default gun direction.
	gunWorldRot = gun.gunNode.getWorldRotation();        
	gunDir = (gunWorldRot.mult(Vector3f.UNIT_X)).normalizeLocal();  //  Create a direction vector for the actual gun direction.
        gunDir90 = (gunWorldRot.mult(Vector3f.UNIT_Z)).normalizeLocal();  //  Create a direction vector perpendicular to the gun direction to determine which side the target is..
        targetVec = (gun.getTarget().getWorldTranslation().subtract(gun.gunNode.getWorldTranslation())).normalizeLocal();
                       
        //  dotGunRot  //
        //  1:  Pointing at guns default rotation
        //  -1: pointing backwards
        //  0:  Pointing to port or starboard
        dotGunRot = gunDir.dot(defaultGunDir); 
        
        //  dotGunRot90  //
        //  1:  Pointing to left side of guns' default rotation
        //  -1: Pointing to right side of guns' default rotation
        //  0:  Pointing at guns default rotation or in opposite direction
        dotGunRot90 = gunDir90.dot(defaultGunDir);
        
        //  dotGunTarget  //
        //  1:  Target is in front of gun
        //  -1: Target is behind gun
        //  0:  Target is to left or right of gun
        //dotGunTarget = gunDir.dot(targetVec);
        
        //  dotGunTarget90  //
        //  1:  Target is right of gun
        //  -1: Target is left of gun
        //  0:  Target in front or behind gun
        dotGunTarget90 = gunDir90.dot(targetVec);        
        
        //  dotShipTarget  //
        //  1:  Target is behind ship
        //  -1: Target is in front of ship
        //  0:  Target is left or right of ship
        //dotShipTarget = (gun.ship.getRotation().mult(Vector3f.UNIT_X)).normalizeLocal().dot(targetVec);
        
        //  dotShipTarget90  //
        //  1:  Target is at right of ship
        //  -1: Target is at left of ship
        //  0:  Target is in front or behind ship
        dotShipTarget90 = (gun.ship.getRotation().mult(Vector3f.UNIT_Z)).normalizeLocal().dot(targetVec);
        
        //  dotGunShip  //
        //  1:  Gun pointing to bow
        //  -1: Gun pointing to stern
        //  0:  Gun pointing left or right
        //dotGunShip = gunDir.dot((gun.ship.getRotation().mult(Vector3f.UNIT_X)).normalizeLocal());
        
        //  dotGunShip90  //
        //  1:  Gun pointing to left
        //  -1: Gun pointing to right
        //  0:  Gun pointing bow or stern
        
        dotGunShip90 = gunDir90.dot((gun.ship.getRotation().mult(Vector3f.UNIT_X)).normalizeLocal());
        
        if (gun.ship.isTracking())
        {
            if (dotGunRot < gunMaxRotForDot)  //  Constrain reached, gun can't rotate further.
            {    
                //  Stop the gun from rotating and set it to its precise constrained rotation:
                if (dotGunRot90 >= 0.0f)  //  Gun at left side of guns' default rotation.
                {
                    //  Fix the gun at its constrained position: at left side of ship:
                    gun.gunNode.setLocalRotation(new Quaternion().fromAngleAxis(gunMaxRot+gunDefaultAngle, new Vector3f(0,1,0)));
                }
                if (dotGunRot90 < 0.0f)  //  Gun at right side of guns' default rotation.
                {
                    //  Fix the gun at its constrained position: at right side of ship:
                    gun.gunNode.setLocalRotation(new Quaternion().fromAngleAxis(-gunMaxRot+gunDefaultAngle, new Vector3f(0,1,0)));
                }
                rotSpeed = STOPSPEED;
            }
                       
            //  Now check whether the target is in reach of the gun so the gun can start rotating:            
            //  For gun pointing backwards in default rotation:
            if (gunDefaultAngle == FastMath.PI)
            {
                //  Target at left side of ship:
                if (dotShipTarget90 < 0.0f)
                {
                    //  Gun at left side of ship and target at left side of gun:
                    if (dotGunShip90 >= 0.0f && dotGunTarget90 < 0.0f)
                    {
                        rotDir = LEFT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at left side of ship and target at right side of gun:
                    else if (dotGunShip90 >= 0.0f && dotGunTarget90 >= 0.0f)
                    {
                        rotDir = RIGHT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at right side of ship:
                    else if (dotGunShip90 < 0.0f)
                    {
                        rotDir = RIGHT;
                        rotSpeed = MAXSPEED;
                    }                        
                }
                //  Target at right side of ship:
                else if (dotShipTarget90 >= 0.0f)
                {
                    //  Gun at right side of ship and target at right side of gun:
                    if (dotGunShip90 < 0.0f && dotGunTarget90 >= 0.0f)
                    {
                        rotDir = RIGHT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at right side of ship and target at left side of gun:
                    else if (dotGunShip90 < 0.0f && dotGunTarget90 < 0.0f)
                    {
                        rotDir = LEFT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at left side of ship:
                    else if (dotGunShip90 >= 0.0f)
                    {
                        rotDir = LEFT;
                        rotSpeed = MAXSPEED;
                    }
                }                    
            }

            //  For gun pointing forwards in default rotation:
            else if (gunDefaultAngle == 0.0f)
            {
                //  Target at left side of ship:
                if (dotShipTarget90 < 0.0f)
                {
                    //  Gun at left side of ship and target at right side of gun:
                    if (dotGunShip90 >= 0.0f && dotGunTarget90 >= 0.0f)
                    {
                        rotDir = RIGHT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at left side of ship and target at left side of gun:
                    else if (dotGunShip90 >= 0.0f && dotGunTarget90 < 0.0f)
                    {
                        rotDir = LEFT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at right side of ship:
                    else if (dotGunShip90 < 0.0f)
                    {
                        rotDir = LEFT;
                        rotSpeed = MAXSPEED;
                    }                        
                }
                //  Target at right side of ship:
                else if (dotShipTarget90 >= 0.0f)
                {
                    //  Gun at right side of ship and target at left side of gun:
                    if (dotGunShip90 < 0.0f && dotGunTarget90 < 0.0f)
                    {
                        rotDir = LEFT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at right side of ship and target at right side of gun:
                    else if (dotGunShip90 < 0.0f && dotGunTarget90 >= 0.0f)
                    {
                        rotDir = RIGHT;
                        rotSpeed = MAXSPEED;
                    }
                    //  Gun at left side of ship:
                    else if (dotGunShip90 >= 0.0f)
                    {
                        rotDir = RIGHT;
                        rotSpeed = MAXSPEED;
                    }
                } 
            }  
            gun.gunNode.rotate(0f, rotDir*rotSpeed*tpf, 0f);            
        }
    }    

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {        
    }    
}

Still not sure whether this is the most optimal code, but it works, so for now I am happy with it :grin:. I am quite satisfied with the fact I don’t have to use any cos or sin or tan anymore!

As soon as I am at the desktop again I will port this code into Enemy Ahead and start working on calculating the required elevation of the guns to actually hit a target at a distance.

1 Like

As promised, here is the link to the result (prototype):

It is a simplified prototype to test the gun turret controller for Enemy Ahead. The guns start tracking the target as soon as they receive the order and will keep themselves in their dedicated rotation range. As soon as they reach their maximum rotation, they will stop and check where they have to go to be as close to the target as possible.

I consider this topic to be solved. Thanks again for all the input (and patience :wink:).

Husky

3 Likes