Advertisement

Rotate character spine so weapon aims at target

Started by April 25, 2014 01:21 AM
4 comments, last by mr_malee 10 years, 9 months ago

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

rotation.jpg

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 rotation required for weapon to point at the target

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

//apply the rotation back to spine

s.rotation = aimRotation;

any ideas?

everything I've found online assumes the child is a direct descendant of the parent when trying to resolve the rotation.

Haven't found anything where the child is within a deep hierarchy , which makes me think this is either an FK or IK problem.

been on this problem for about 8 hours now. :(

Advertisement
Indeed, it is an IK problem. But it should be a fairly simple one. If you can measure how far off you are from the target, you can compute a quaternion for the desired rotation (of the last bone) and move every joint in the branch by a small amount in the direction of that quaternion.

solved it using iteration.

Also, had to project weapon forward onto spines forward plane as the origin.

added clamping and smoothing too: :)


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;
	}
}
Another solution that the programmers at Naughty Dog used is to have static poses per weapon direction, like one pose aiming front (base pose), another aiming above (90 degrees on X axis), another aiming right (90 degrees on the Y axis) etc.
Then you blend between the desired poses with additive animation.

http://www.gdcvault.com/play/1012300/Animation-and-Player-Control-in

The benefit of this is that you can use not only the spine but the arms and shoulders to aim (so it looks more natural), there's no need for iteration and additive animation is procedural, so no memory cost except for the individual poses.
This is possible to do with your Unity.

nice, thanks for sharing. Maybe I can convince my animator to pose something for me :)

This topic is closed to new replies.

Advertisement