Rotate character spine so weapon aims at target

Hi, I’m trying to rotate my characters spine so that his weapon aims at a specific point.

I’m close, but something is off. Here’s an image of my problem:

Red line is weapons direction, blue line is from barrel to target

and here’s my code so far:

//root
Transform a = GameObject.Find("swat").transform;

//spine
Transform s = a.FindChild("Hips/Spine"); 

//weapon
Transform w = a.FindChild("Hips/Spine/Spine1/Spine2/RightShoulder/RightArm/RightForeArm/RightHand/ak47");

Vector3 aimOrigin = w.TransformPoint(weapon.attackPoint);
Vector3 aimForward = target - aimOrigin;
Vector3 weaponForward = w.up;

//find the spine required for weapon to point at the target

aimRotation = Quaternion.FromToRotation(weaponForward, aimForward) * s.rotation;

//apply the local rotation back to spine

s.rotation = aimRotation;

any ideas? :wink:

We don’t have all the code, so I’m guessing at this, but…

I think what you’re doing is taking the weapon’s needed rotation and applying it to the spine. But they rotate from different points in space, so that isn’t going to work.

I would try finding the distance from the spine’s rotation point to the target, then finding the spot that distance from the gun’s rotation along its forward axis. Then find the rotation needed from the gun’s target location to the desired target location, from the spine’s rotation point. It won’t be completely accurate because the gun is offset from the spine, but I think it’d be better than you have.

But I haven’t tried that, and there could be a flaw in my thinking.

Take a look at head look Controller http://u3d.as/content/unity-technologies/head-look-controller/1qN
you can use it not only for the head, but for the arms.

thanks for the help.

Part of my problem is related to mecanim resetting animation rotations before the above code is run.
The head look controller is a little helpful, but it modifies the internal orientation of the human. I need my character to stay rigid.

Anyway, I’m gonna try using “MatchTarget” and see how that goes.

ok so MatchTarget doesn’t work.

However, I’ve found that if I rotate the root object (character) like so:

transform.rotation = Quaternion.FromToRotation(weaponForward, aimForward) * transform.rotation;

then the weapon aligns perfectly, but the entire character rotates :frowning:

iteration to the rescue!

breaks down though when objects are too close

Transform s = spine;
Transform w = weapon.transform;

float distance = float.PositiveInfinity;
float minDistance = 0.01f;
int iteration = 30;

while (distance > minDistance  iteration > 0) {

	Vector3 aimOrigin = w.TransformPoint(weapon.attackPoint);

	Quaternion rot = Quaternion.FromToRotation(w.forward, target - aimOrigin);

	distance = rot.eulerAngles.sqrMagnitude;
	iteration--;

	s.rotation = rot * s.rotation;
}

fixed the close object issue. Project weapon onto spine plane and use that as the target delta:

Transform s = spine;
Transform w = weapon.transform;

float distance = float.PositiveInfinity;
float minDistance = 0.01f;
int iteration = 30;

while (distance > minDistance  iteration > 0) {

	Vector3 aimOrigin = w.TransformPoint(weapon.attackPoint);
	Vector3 origin;

	if (!LinePlaneIntersection(out origin, aimOrigin, -w.forward, s.forward, s.position)) {

		//if for some readon the weapon cannot intersect with spine forward plane, just use the aim origin
		//hopefully this never happens as weapon should always be in front of spine
		origin = aimOrigin;
	}

	Vector3 weaponForward = w.forward;
	Vector3 aimForward = target - origin;

	Quaternion rot = Quaternion.FromToRotation(weaponForward, aimForward);

	distance = rot.eulerAngles.sqrMagnitude;
	iteration--;

	s.rotation = rot * s.rotation;
}

public static bool LinePlaneIntersection(out Vector3 intersection, Vector3 linePoint, Vector3 lineVec, Vector3 planeNormal, Vector3 planePoint) {

	float length;
	float dotNumerator;
	float dotDenominator;

	intersection = Vector3.zero;

	dotNumerator = Vector3.Dot((planePoint - linePoint), planeNormal);
	dotDenominator = Vector3.Dot(lineVec, planeNormal);

	if (dotDenominator != 0.0f) {
		
		length = dotNumerator / dotDenominator;
		intersection = linePoint + lineVec.normalized * length;
		return true;
	}
	else {
		
		return false;
	}
}

working nicely now. Iterations don’t usually go above 4. 2 at long distance. :smile:

Now to constrain the angles :frowning:

ok I’m done. Clamping and smoothing added:

Transform s = spine;
Transform w = weapon.transform;

float distance = float.PositiveInfinity;
float minDistance = 0.01f;
int iteration = 30;

Quaternion oldRotation = lastAimRotation;

while (distance > minDistance  iteration > 0) {

	Vector3 aimOrigin = w.TransformPoint(weapon.attackPoint);
	Vector3 origin;

	if (!LinePlaneIntersection(out origin, aimOrigin, -w.forward, s.forward, s.position)) {

		//if for some readon the weapon cannot intersect with spine forward plane, just use the aim origin
		//hopefully this never happens as weapon should always be in front of spine
		
		origin = aimOrigin;
	}

	Vector3 weaponForward = w.forward;
	Vector3 aimForward = target - origin;

	Quaternion rot = Quaternion.FromToRotation(weaponForward, aimForward);

	distance = rot.eulerAngles.sqrMagnitude;
	iteration--;

	s.rotation = rot * s.rotation;
}

//clamp angles

Vector3 angles = s.localEulerAngles;

//offset the clamped angles by any upper body offset
//we may need this if characters aren't aligned forward when rotation is identity

angles.x = ClampAngle(angles.x, minUpperBodyAngles.x + upperBodyAngleOffset.x, maxUpperBodyAngles.x + upperBodyAngleOffset.x);
angles.y = ClampAngle(angles.y, minUpperBodyAngles.y + upperBodyAngleOffset.y, maxUpperBodyAngles.y + upperBodyAngleOffset.y);
angles.z = ClampAngle(angles.z, minUpperBodyAngles.z + upperBodyAngleOffset.z, maxUpperBodyAngles.z + upperBodyAngleOffset.z);

s.localEulerAngles = angles;

//smooth if we have easing

if (aimRotationEasing > 0) {

	s.rotation = Quaternion.Slerp(lastAimRotation, s.rotation, Time.deltaTime * aimRotationEasing);
	lastAimRotation = s.rotation;
}
private static float ClampAngle(float ang, float min, float max) {

	if (ang > 180) ang -= 360;
	if (max > 180) max -= 360;
	if (min > 180) min -= 360;

	ang = Mathf.Clamp(ang, min, max);

	return ang < 0 ? ang + 360 : ang;
}
private static bool LinePlaneIntersection(out Vector3 intersection, Vector3 linePoint, Vector3 lineVec, Vector3 planeNormal, Vector3 planePoint) {

	float dotNumerator = Vector3.Dot(planePoint - linePoint, planeNormal);
	float dotDenominator = Vector3.Dot(lineVec, planeNormal);

	if (dotDenominator != 0.0f) {

		intersection = linePoint + lineVec.normalized * (dotNumerator / dotDenominator);
		return true;
	}
	else {
		
		intersection = Vector3.zero;
		return false;
	}
}

Hopefully this can help out anyone else wanting a character to aim at a target.

2 Likes

thanks for this…
one question though, is the weapon.attackPoint the target being aimed at?