🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Rotation with limited turn rate in degrees

Started by
45 comments, last by the incredible smoker 5 years, 11 months ago

Hey guys,

I have a homing missile which I want to direct towards the target with a limited rotation speed.

The rotation should be limited by an angle in Degrees per second.

How do I accomplish this?

Advertisement

2D or 3D?

It's 3D


	vec curDir = turret.orientation.FrontDirection();
	vec targetDir = (target - turret.Position).Unit();
	// calculate axis and angle rotation between directions
	vec axis = (curDir.Cross(targetDir)).Unit(); // eventually reverse operands - not sure about it
	float angle = acos( curDir.Dot(targetDir) ); // Notice this is always positive (the direction of the axis defines if we rotate left or right)
	// limit angle if necessary
	const float maxAngularVelocity = 1.0f / 180.0f * 3.14f; // e.g. 1 degree per second
	const float timestep = 1.0f / 60.0f;
	float maxAnglePerUpdate = maxAngularVelocity / timestep;
	if (angle > maxAnglePerUpdate) angle = maxAnglePerUpdate;
	// finally rotate turret
	turret.orientation.RotateByAxisAndAngle (axis, angle);
	

I hope this is correct - written just out of my head and not tested.

Be sure to try the correct order for the cross product.

EDIT: bugfix: maxAnglePerUpdate = maxAngularVelovity * timestep

Thank you for your reply,

sadly it isn't working for me. The projectile either keeps spinning wildly or is going in a different direction

31 minutes ago, Twaxx said:

The projectile either keeps spinning wildly or is going in a different direction

Oops, used division instead multiplication, see Edit of above post.

I'v tested it now with this code:


static bool visTurret = 1; ImGui::Checkbox("visTurret", &visTurret);
if (visTurret)
{
	struct Turret
	{
		quat orientation;
		vec position;

		Turret ()
		{
			orientation.Identity();
			position = vec(0,0,0);
		}
	};
	static Turret turret;

	static vec target (10, 3, 0);
	ImGui::DragFloat3 ("target pos", (float*)&target, 0.01f);

	vec curDir = turret.orientation.Rotate(vec(1,0,0));
	vec targetDir = vec(target - turret.position).Unit();

	// calculate axis and angle rotation between directions
	float angle = acos( curDir.Dot(targetDir) ); // Notice this is always positive (the direction of the axis defines if we rotate left or right)
	if (angle > 1.0e-5f) // otherwise axis would be result of division by zero
	{
		vec axis = (curDir.Cross(targetDir)).Unit(); // eventually reverse operands - not sure about it

		// limit angle if necessary
		const float maxAngularVelocity = 10.0f / 180.0f * 3.14f; // e.g. 10 degree per second
		const float timestep = 1.0f / 500.0f; // i have 500 fps
		float maxAnglePerUpdate = maxAngularVelocity * timestep;
		if (angle > maxAnglePerUpdate) angle = maxAnglePerUpdate;

		// finally rotate turret
		quat rot; rot.FromAxisAndAngle (axis, angle);
		turret.orientation *= rot;
		turret.orientation.Normalize();
	}

	// visualize
	RenderLine (target, turret.position, 0.5,0.5,0.5);
	RenderQuat (turret.position, turret.orientation);
	RenderPoint (target, 1,1,1);
}

There is still one problem: If curDir and TargetDir point to opposite halfspace, the turret aligns to the back side :(

But i doubt it is worth to fix this, because a real turret has some joint limits, and can not roll. It would be better to start a new approach that builds two rotations from two Euler angles (like a FPS camera - no roll there as well).

I suggest you try it out anyways and tell me what you think...

Thank you very much, I got it working

I just wanted to mention that Slerp https://en.wikipedia.org/wiki/Slerp also does this and solves some simple problems you get from this kind of rotation.

You would calculate the offset to find the rotation to the target. Then use speed as the time value, updating each tic. This is a real easy way if you have a math library already hooked up.For example the Unity version would look like this:

Spoiler

Unity:



    void HomeOnTarget()
    {
	//Get offset and make it a rotation
        Vector3 offset = Target.transform.position - this.transform.position;
        Vector3 TargetDirection = offset.normalized;
		
      	//Slerp between current rotation and target rotation based on speed
        this.transform.rotation = Quaternion.Slerp(this.transform.rotation,
            Quaternion.LookRotation(TargetDirection, this.transform.up), Time.deltaTime/ Speed);
      
	//Move in rotation direction
        this.transform.position += this.transform.forward * Speed * Time.fixedDeltaTime;
    }


 

Slerp makes a lot of rotation math simple.

3 hours ago, Scouting Ninja said:

Quaternion.LookRotation(TargetDirection, this.transform.up)

That's nice. But it would be better to use global up direction instead the turrets local, then the rolling is already prevented.

My backwards bug still bothered me and while fixing it i tried this and it works nicely, also added a damping so motion is smooth (constant speed always looks so cheap).

Other than that our approaches should be equivalent, assuming Unity already chooses the shorter arc for the slerp internally.


	struct Turret
	{
		quat orientation;
		vec position;

		Turret ()
		{
			orientation.Identity();
			position = vec(0,0,0);
		}
	};
	static Turret turret;

	static vec target (10, 3, 0);
	ImGui::DragFloat3 ("target pos", (float*)&target, 0.01f);

	vec curDir = turret.orientation.Rotate(vec(1,0,0));
	vec targetDir = vec(target - turret.position).Unit();

	float dot = curDir.Dot(targetDir);
	if (fabs(dot) < 1.0f - 1.0e-5f) // otherwise axis would be result of division by zero
	{
		// calculate axis and angle rotation between directions
		float angle = acos(dot); // Notice this is always positive (the direction of the axis defines if we rotate left or right)
		vec axis = (curDir.Cross(targetDir)).Unit();

		// optional: some damping to make it smooth. This simple approach assumes constant timestep and needs to be adjusted to it.
		const float damp = 0.03f; 
		angle *= damp;

		// limit angle if necessary
		const float maxAngularVelocity = 30.0f / 180.0f * 3.14f; // e.g. 30 degrees per second
		const float timestep = 1.0f / 500.0f; // i have 500 fps
		float maxAnglePerUpdate = maxAngularVelocity * timestep;
		if (angle > maxAnglePerUpdate) angle = maxAnglePerUpdate;

		// finally rotate turret
		quat rot; rot.FromAxisAndAngle (axis, angle);

		if (0) // no upvector - turret will roll and go out of place after some time
		{
			//turret.orientation *= rot; // depending on conventions, multiplication order decides to do the rotation in either local or global space, which i never remember. So this line caused the backwards bug for me i've mentioned before.
			turret.orientation = rot * turret.orientation;
			turret.orientation.Normalize();
		}
		else // using upvector, turret Y axis remains on the ground plane
		{
			const vec globalUp (0,1,0);
			vec newTurretDir = rot.Rotate(curDir);
			if (fabs( globalUp.Dot(newTurretDir) ) < 1.0f - 1.0e-5f) // turret would be clueless uf pointing exactly up / downwards
			{
				vec side = vec(globalUp.Cross(newTurretDir)).Unit();
				vec localUp = newTurretDir.Cross(side);

				matrix temp;
				temp[0] = newTurretDir;
				temp[1] = side;
				temp[2] = localUp;
				temp.Rotation()->ToQuat(turret.orientation);
			}

		}
		
	}

	// visualize
	RenderLine (target, turret.position, 0.5,0.5,0.5);
	RenderQuat (turret.position, turret.orientation);
	RenderPoint (target, 1,1,1);
	RenderCircle (1, turret.position, vec(0,1,0), 0,1,0);

 

 

The way I would do it is:

  • Compute the quaternion that would produce the "most natural rotation" that would align your missile with the target.
  • Look at its real coordinate to see if the rotation is larger than the limit. The real part should be at least cos(limit_angle/2). If needed, modify the quaternion so its real part is exactly cos(limit_angle/2), scaling the imaginary parts to keep the quaternion having unit length.
  • Apply the quaternion to rotate the missile.

You can find code to compute the "most natural rotation" in this thread

Depending on the genre, a more realistic Physics simulation of the missile's dynamics and a control mechanism (like a PID loop) to guide the missile could give you better results.

 

This topic is closed to new replies.

Advertisement