Ejemplo n.º 1
0
        private Vector3 GuessPalmToThumbAxis(Transform hand, Transform forearm)
        {
            if (hand.childCount == 0)
            {
                Debug.LogWarning("Hand " + hand.name + " does not have any fingers, VRIK can not guess the hand bone's orientation. Please assign 'Wrist To Palm Axis' and 'Palm To Thumb Axis' manually for both arms in VRIK settings.", hand);
                return(Vector3.zero);
            }

            float closestSqrMag = Mathf.Infinity;
            int   thumbIndex    = 0;

            for (int i = 0; i < hand.childCount; i++)
            {
                float sqrMag = Vector3.SqrMagnitude(hand.GetChild(i).position - hand.position);
                if (sqrMag < closestSqrMag)
                {
                    closestSqrMag = sqrMag;
                    thumbIndex    = i;
                }
            }

            Vector3 handNormal = Vector3.Cross(hand.position - forearm.position, hand.GetChild(thumbIndex).position - hand.position);
            Vector3 toThumb    = Vector3.Cross(handNormal, hand.position - forearm.position);
            Vector3 axis       = AxisTools.ToVector3(AxisTools.GetAxisToDirection(hand, toThumb));

            if (Vector3.Dot(toThumb, hand.rotation * axis) < 0f)
            {
                axis = -axis;
            }
            return(axis);
        }
Ejemplo n.º 2
0
        private static void CreateHandCollider(Transform hand, Transform lowerArm, Transform root, Options options)
        {
            Vector3 axis = hand.TransformVector(AxisTools.GetAxisVectorToPoint(hand, GetChildCentroid(hand, lowerArm.position)));

            Vector3 endPoint = hand.position - (lowerArm.position - hand.position) * 0.75f;

            endPoint = hand.position + Vector3.Project(endPoint - hand.position, axis).normalized *(endPoint - hand.position).magnitude;

            CreateCollider(hand, hand.position, endPoint, options.handColliders, options.colliderLengthOverlap, Vector3.Distance(endPoint, hand.position) * 0.5f);
        }
Ejemplo n.º 3
0
        private Vector3 GuessWristToPalmAxis(Transform hand, Transform forearm)
        {
            Vector3 toForearm = forearm.position - hand.position;
            Vector3 axis      = AxisTools.ToVector3(AxisTools.GetAxisToDirection(hand, toForearm));

            if (Vector3.Dot(toForearm, hand.rotation * axis) > 0f)
            {
                axis = -axis;
            }
            return(axis);
        }
Ejemplo n.º 4
0
        public static JointAxis GetSymmetricJointAxis(Transform jointTransform, Vector3 axis, Vector3 secondaryAxis, JointAxis jointAxis, Transform symmetricTransform, Vector3 symmetricAxis, Vector3 symmetricSecondaryAxis, Transform root)
        {
            Vector3 a      = JointAxisToVector3(axis, secondaryAxis, jointAxis);
            Vector3 aWorld = jointTransform.rotation * a;

            Vector3 aWorldMirror = SymmetryTools.Mirror(aWorld, root);
            Vector3 aLocalMirror = Quaternion.Inverse(symmetricTransform.rotation) * aWorldMirror;

            Vector3 sAVector = AxisTools.GetAxisVectorToDirection(symmetricTransform, aWorldMirror);
            Vector3 sA       = Vector3.Project(aLocalMirror, sAVector);

            return(GetJointAxis(symmetricAxis, symmetricSecondaryAxis, sA));
        }
Ejemplo n.º 5
0
    private static void CalibrateLeg(Settings settings, Transform FootTransform, IKSolverVR.Leg leg, Transform lastBone, Vector3 rootForward, bool isLeft)
    {
        Transform footAdjusterTransform = leg.target == null ? new GameObject(isLeft ? "leftFootAdjuster" : "rightFootAdjuster").transform : leg.target;

        footAdjusterTransform.parent = FootTransform;

        // Space of the tracker heading
        Quaternion frontQuaternion = FootTransform.rotation * Quaternion.LookRotation(settings.footTrackerForward, settings.footTrackerUp);
        Vector3    frontVector     = frontQuaternion * Vector3.forward;

        frontVector.y   = 0f;
        frontQuaternion = Quaternion.LookRotation(frontVector);

        // Target position
        float inwardOffset = isLeft ? settings.footInwardOffset : -settings.footInwardOffset;

        footAdjusterTransform.position = FootTransform.position + frontQuaternion * new Vector3(inwardOffset, 0f, settings.footForwardOffset);
        footAdjusterTransform.position = new Vector3(footAdjusterTransform.position.x, lastBone.position.y, footAdjusterTransform.position.z);

        // Target rotation
        footAdjusterTransform.rotation = lastBone.rotation;

        // Rotate target forward towards tracker forward
        Vector3 footForward = AxisTools.GetAxisVectorToDirection(lastBone, rootForward);

        if (Vector3.Dot(lastBone.rotation * footForward, rootForward) < 0f)
        {
            footForward = -footForward;
        }
        Vector3 fLocal        = Quaternion.Inverse(Quaternion.LookRotation(footAdjusterTransform.rotation * footForward)) * frontVector;
        float   angle         = Mathf.Atan2(fLocal.x, fLocal.z) * Mathf.Rad2Deg;
        float   headingOffset = isLeft ? settings.footHeadingOffset : -settings.footHeadingOffset;

        footAdjusterTransform.rotation = Quaternion.AngleAxis(angle + headingOffset, Vector3.up) * footAdjusterTransform.rotation;
        leg.target         = footAdjusterTransform;
        leg.positionWeight = 1f;
        leg.rotationWeight = 1f;

        // Bend goal

        /*
         * Transform bendGoal = leg.bendGoal == null ? (new GameObject(name + " Leg Bend Goal")).transform : leg.bendGoal;
         * bendGoal.position = lastBone.position + frontQuaternion * Vector3.forward + frontQuaternion * Vector3.up;// * 0.5f;
         * bendGoal.parent = FootTransform;
         * leg.bendGoal = bendGoal;
         * leg.bendGoalWeight = 1f;
         */
        leg.bendGoal       = null;
        leg.bendGoalWeight = 0f;
    }
Ejemplo n.º 6
0
        public static Vector3 DeltaSize(Vector3 size, Transform t, Vector3 direction, float delta)
        {
            Axis axis = AxisTools.GetAxisToDirection(t, direction);

            switch (axis)
            {
            case Axis.X: size.x += delta; break;

            case Axis.Y: size.y += delta; break;

            case Axis.Z: size.z += delta; break;
            }

            return(size);
        }
Ejemplo n.º 7
0
        private static void CalibrateLeg(Settings settings, Transform tracker, IKSolverVR.Leg leg, Transform lastBone, Vector3 rootForward, bool isLeft)
        {
            string    name   = isLeft ? "Left" : "Right";
            Transform target = leg.target == null ? (new GameObject(name + " Foot Target")).transform : leg.target;

            // Space of the tracker heading
            Quaternion trackerSpace = tracker.rotation * Quaternion.LookRotation(settings.footTrackerForward, settings.footTrackerUp);
            Vector3    f            = trackerSpace * Vector3.forward;

            f.y          = 0f;
            trackerSpace = Quaternion.LookRotation(f);

            // Target position
            float inwardOffset = isLeft ? settings.footInwardOffset : -settings.footInwardOffset;

            target.position = tracker.position + trackerSpace * new Vector3(inwardOffset, 0f, settings.footForwardOffset);
            target.position = new Vector3(target.position.x, lastBone.position.y, target.position.z);

            // Target rotation
            target.rotation = lastBone.rotation;

            // Rotate target forward towards tracker forward
            Vector3 footForward = AxisTools.GetAxisVectorToDirection(lastBone, rootForward);

            if (Vector3.Dot(lastBone.rotation * footForward, rootForward) < 0f)
            {
                footForward = -footForward;
            }
            Vector3 fLocal        = Quaternion.Inverse(Quaternion.LookRotation(target.rotation * footForward)) * f;
            float   angle         = Mathf.Atan2(fLocal.x, fLocal.z) * Mathf.Rad2Deg;
            float   headingOffset = isLeft ? settings.footHeadingOffset : -settings.footHeadingOffset;

            target.rotation = Quaternion.AngleAxis(angle + headingOffset, Vector3.up) * target.rotation;

            target.parent = tracker;
            leg.target    = target;

            leg.positionWeight = 1f;
            leg.rotationWeight = 1f;

            // Bend goal
            Transform bendGoal = leg.bendGoal == null ? (new GameObject(name + " Leg Bend Goal")).transform : leg.bendGoal;

            bendGoal.position  = lastBone.position + trackerSpace * Vector3.forward + trackerSpace * Vector3.up;// * 0.5f;
            bendGoal.parent    = tracker;
            leg.bendGoal       = bendGoal;
            leg.bendGoalWeight = 1f;
        }
Ejemplo n.º 8
0
        protected static void CreateCollider(Transform t, Vector3 startPoint, Vector3 endPoint, ColliderType colliderType, float lengthOverlap, float width, float proportionAspect, Vector3 widthDirection)
        {
            if (colliderType == ColliderType.Capsule)
            {
                CreateCollider(t, startPoint, endPoint, colliderType, lengthOverlap, width * proportionAspect);
                return;
            }

            Vector3 direction = endPoint - startPoint;
            float   height    = direction.magnitude * (1f + lengthOverlap);

            Vector3 heightAxis = AxisTools.GetAxisVectorToDirection(t, direction);
            Vector3 widthAxis  = AxisTools.GetAxisVectorToDirection(t, widthDirection);

            if (widthAxis == heightAxis)
            {
                Debug.LogWarning("Width axis = height axis on " + t.name, t);
                widthAxis = new Vector3(heightAxis.y, heightAxis.z, heightAxis.x);
            }

            t.gameObject.AddComponent <Rigidbody>();

            Vector3 heightAdd = Vector3.Scale(heightAxis, new Vector3(height, height, height));
            Vector3 widthAdd  = Vector3.Scale(widthAxis, new Vector3(width, width, width));

            Vector3 size = heightAdd + widthAdd;

            if (size.x == 0f)
            {
                size.x = width * proportionAspect;
            }
            if (size.y == 0f)
            {
                size.y = width * proportionAspect;
            }
            if (size.z == 0f)
            {
                size.z = width * proportionAspect;
            }

            BoxCollider box = t.gameObject.AddComponent <BoxCollider>();

            box.size   = size / GetScaleF(t);
            box.center = t.InverseTransformPoint(Vector3.Lerp(startPoint, endPoint, 0.5f));
        }
Ejemplo n.º 9
0
        protected static void CreateCollider(Transform t, Vector3 startPoint, Vector3 endPoint, ColliderType colliderType, float lengthOverlap, float width)
        {
            Vector3 direction  = endPoint - startPoint;
            float   height     = direction.magnitude * (1f + lengthOverlap);
            Vector3 heightAxis = AxisTools.GetAxisVectorToDirection(t, direction);

            t.gameObject.AddComponent <Rigidbody>();
            float scaleF = GetScaleF(t);

            switch (colliderType)
            {
            case ColliderType.Capsule:
                CapsuleCollider capsule = t.gameObject.AddComponent <CapsuleCollider>();
                capsule.height    = Mathf.Abs(height / scaleF);
                capsule.radius    = Mathf.Abs((width * 0.75f) / scaleF);
                capsule.direction = DirectionVector3ToInt(heightAxis);
                capsule.center    = t.InverseTransformPoint(Vector3.Lerp(startPoint, endPoint, 0.5f));
                break;

            case ColliderType.Box:
                Vector3 size = Vector3.Scale(heightAxis, new Vector3(height, height, height));
                if (size.x == 0f)
                {
                    size.x = width;
                }
                if (size.y == 0f)
                {
                    size.y = width;
                }
                if (size.z == 0f)
                {
                    size.z = width;
                }

                BoxCollider box = t.gameObject.AddComponent <BoxCollider>();

                box.size   = size / scaleF;
                box.size   = new Vector3(Mathf.Abs(box.size.x), Mathf.Abs(box.size.y), Mathf.Abs(box.size.z));
                box.center = t.InverseTransformPoint(Vector3.Lerp(startPoint, endPoint, 0.5f));
                break;
            }
        }
Ejemplo n.º 10
0
        private static void CreateFootCollider(Transform foot, Transform lowerLeg, Transform upperLeg, Transform root, Options options)
        {
            float   legHeight = (upperLeg.position - foot.position).magnitude;
            Vector3 axis      = foot.TransformVector(AxisTools.GetAxisVectorToPoint(foot, GetChildCentroid(foot, foot.position + root.forward) + root.forward * legHeight * 0.2f));

            Vector3 endPoint = foot.position + root.forward * legHeight * 0.25f;

            endPoint = foot.position + Vector3.Project(endPoint - foot.position, axis).normalized *(endPoint - foot.position).magnitude;

            float   width      = Vector3.Distance(endPoint, foot.position) * 0.5f;
            Vector3 startPoint = foot.position;

            bool    footBelowRoot = Vector3.Dot(root.up, foot.position - root.position) < 0f;
            Vector3 heightOffset  = footBelowRoot? Vector3.zero: Vector3.Project((startPoint - root.up * width * 0.5f) - root.position, root.up);

            Vector3 direction = endPoint - startPoint;

            startPoint -= direction * 0.2f;

            CreateCollider(foot, startPoint - heightOffset, endPoint - heightOffset, options.footColliders, options.colliderLengthOverlap, width);
        }
Ejemplo n.º 11
0
        private static Axis GetViewAxis(Joint joint)
        {
            if (joint.connectedBody == null)
            {
                return(Axis.Z);
            }

            CapsuleCollider capsule = joint.GetComponent <CapsuleCollider>();

            if (capsule != null && capsule.center != Vector3.zero)
            {
                return(AxisTools.ToAxis(capsule.center));
            }
            else
            {
                BoxCollider box = joint.GetComponent <BoxCollider>();
                if (box != null && box.center != Vector3.zero)
                {
                    return(AxisTools.ToAxis(box.center));
                }
            }

            return(AxisTools.GetAxisToPoint(joint.transform, joint.connectedBody.worldCenterOfMass));
        }
Ejemplo n.º 12
0
        private static void CreateColliders(BipedRagdollReferences r, Options options)
        {
            // Torso
            Vector3 upperArmToHeadCentroid = GetUpperArmToHeadCentroid(r);

            if (r.spine == null)
            {
                options.spine = false;
            }
            if (r.chest == null)
            {
                options.chest = false;
            }

            Vector3 shoulderDirection     = r.rightUpperArm.position - r.leftUpperArm.position;
            float   torsoWidth            = shoulderDirection.magnitude;
            float   torsoProportionAspect = 0.6f;

            // Hips
            Vector3 hipsStartPoint = r.hips.position;

            // Making sure the hip bone is not at the feet
            float toHead = Vector3.Distance(r.head.position, r.root.position);
            float toHips = Vector3.Distance(r.hips.position, r.root.position);

            if (toHips < toHead * 0.2f)
            {
                hipsStartPoint = Vector3.Lerp(r.leftUpperLeg.position, r.rightUpperLeg.position, 0.5f);
            }

            Vector3 lastEndPoint = options.spine? r.spine.position: (options.chest? r.chest.position: upperArmToHeadCentroid);

            hipsStartPoint += (hipsStartPoint - upperArmToHeadCentroid) * 0.1f;
            float hipsWidth = options.spine || options.chest? torsoWidth * 0.8f: torsoWidth;

            CreateCollider(r.hips, hipsStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, hipsWidth, torsoProportionAspect, shoulderDirection);

            // Spine
            if (options.spine)
            {
                Vector3 spineStartPoint = lastEndPoint;
                lastEndPoint = options.chest? r.chest.position: upperArmToHeadCentroid;

                float spineWidth = options.chest? torsoWidth * 0.75f: torsoWidth;

                CreateCollider(r.spine, spineStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, spineWidth, torsoProportionAspect, shoulderDirection);
            }

            if (options.chest)
            {
                Vector3 chestStartPoint = lastEndPoint;
                lastEndPoint = upperArmToHeadCentroid;

                CreateCollider(r.chest, chestStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, torsoWidth, torsoProportionAspect, shoulderDirection);
            }

            // Head
            Vector3 headStartPoint = lastEndPoint;
            Vector3 headEndPoint   = headStartPoint + (headStartPoint - hipsStartPoint) * 0.45f;
            Vector3 axis           = r.head.TransformVector(AxisTools.GetAxisVectorToDirection(r.head, headEndPoint - headStartPoint));

            headEndPoint = headStartPoint + Vector3.Project(headEndPoint - headStartPoint, axis).normalized *(headEndPoint - headStartPoint).magnitude;

            CreateCollider(r.head, headStartPoint, headEndPoint, options.headCollider, options.colliderLengthOverlap, Vector3.Distance(headStartPoint, headEndPoint) * 0.8f);

            // Arms
            float armWidthAspect = 0.4f;

            float leftArmWidth = Vector3.Distance(r.leftUpperArm.position, r.leftLowerArm.position) * armWidthAspect;

            CreateCollider(r.leftUpperArm, r.leftUpperArm.position, r.leftLowerArm.position, options.armColliders, options.colliderLengthOverlap, leftArmWidth);
            CreateCollider(r.leftLowerArm, r.leftLowerArm.position, r.leftHand.position, options.armColliders, options.colliderLengthOverlap, leftArmWidth * 0.9f);

            float rightArmWidth = Vector3.Distance(r.rightUpperArm.position, r.rightLowerArm.position) * armWidthAspect;

            CreateCollider(r.rightUpperArm, r.rightUpperArm.position, r.rightLowerArm.position, options.armColliders, options.colliderLengthOverlap, rightArmWidth);
            CreateCollider(r.rightLowerArm, r.rightLowerArm.position, r.rightHand.position, options.armColliders, options.colliderLengthOverlap, rightArmWidth * 0.9f);

            // Legs
            float legWidthAspect = 0.3f;

            float leftLegWidth = Vector3.Distance(r.leftUpperLeg.position, r.leftLowerLeg.position) * legWidthAspect;

            CreateCollider(r.leftUpperLeg, r.leftUpperLeg.position, r.leftLowerLeg.position, options.legColliders, options.colliderLengthOverlap, leftLegWidth);
            CreateCollider(r.leftLowerLeg, r.leftLowerLeg.position, r.leftFoot.position, options.legColliders, options.colliderLengthOverlap, leftLegWidth * 0.9f);

            float rightLegWidth = Vector3.Distance(r.rightUpperLeg.position, r.rightLowerLeg.position) * legWidthAspect;

            CreateCollider(r.rightUpperLeg, r.rightUpperLeg.position, r.rightLowerLeg.position, options.legColliders, options.colliderLengthOverlap, rightLegWidth);
            CreateCollider(r.rightLowerLeg, r.rightLowerLeg.position, r.rightFoot.position, options.legColliders, options.colliderLengthOverlap, rightLegWidth * 0.9f);

            // Hands
            if (options.hands)
            {
                CreateHandCollider(r.leftHand, r.leftLowerArm, r.root, options);
                CreateHandCollider(r.rightHand, r.rightLowerArm, r.root, options);
            }

            // Feet
            if (options.feet)
            {
                CreateFootCollider(r.leftFoot, r.leftLowerLeg, r.leftUpperLeg, r.root, options);
                CreateFootCollider(r.rightFoot, r.rightLowerLeg, r.rightUpperLeg, r.root, options);
            }
        }
Ejemplo n.º 13
0
            protected override void OnRead(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, int rootIndex, int index)
            {
                Vector3    shoulderPosition = positions[index];
                Quaternion shoulderRotation = rotations[index];
                Vector3    upperArmPosition = positions[index + 1];
                Quaternion upperArmRotation = rotations[index + 1];
                Vector3    forearmPosition  = positions[index + 2];
                Quaternion forearmRotation  = rotations[index + 2];
                Vector3    handPosition     = positions[index + 3];
                Quaternion handRotation     = rotations[index + 3];

                if (!initiated)
                {
                    IKPosition = handPosition;
                    IKRotation = handRotation;
                    rotation   = IKRotation;

                    this.hasShoulder = hasShoulders;

                    bones = new VirtualBone[hasShoulder ? 4 : 3];

                    if (hasShoulder)
                    {
                        bones[0] = new VirtualBone(shoulderPosition, shoulderRotation);
                        bones[1] = new VirtualBone(upperArmPosition, upperArmRotation);
                        bones[2] = new VirtualBone(forearmPosition, forearmRotation);
                        bones[3] = new VirtualBone(handPosition, handRotation);
                    }
                    else
                    {
                        this.bones[0] = new VirtualBone(upperArmPosition, upperArmRotation);
                        this.bones[1] = new VirtualBone(forearmPosition, forearmRotation);
                        this.bones[2] = new VirtualBone(handPosition, handRotation);
                    }

                    Vector3 rootForward = rotations[0] * Vector3.forward;
                    chestForwardAxis = Quaternion.Inverse(rootRotation) * rootForward;
                    chestUpAxis      = Quaternion.Inverse(rootRotation) * (rotations[0] * Vector3.up);

                    // Get the local axis of the upper arm pointing towards the bend normal
                    Vector3 upperArmForwardAxis = AxisTools.GetAxisVectorToDirection(upperArmRotation, rootForward);
                    if (Vector3.Dot(upperArmRotation * upperArmForwardAxis, rootForward) < 0f)
                    {
                        upperArmForwardAxis = -upperArmForwardAxis;
                    }
                    upperArmBendAxis = Vector3.Cross(Quaternion.Inverse(upperArmRotation) * (forearmPosition - upperArmPosition), upperArmForwardAxis);
                    if (upperArmBendAxis == Vector3.zero)
                    {
                        Debug.LogWarning("VRIK can not calculate which way to bend the arms because the arms are perfectly straight. Please rotate the elbow bones slightly in their natural bending direction in the Editor.");
                    }
                }

                if (hasShoulder)
                {
                    bones[0].Read(shoulderPosition, shoulderRotation);
                    bones[1].Read(upperArmPosition, upperArmRotation);
                    bones[2].Read(forearmPosition, forearmRotation);
                    bones[3].Read(handPosition, handRotation);
                }
                else
                {
                    bones[0].Read(upperArmPosition, upperArmRotation);
                    bones[1].Read(forearmPosition, forearmRotation);
                    bones[2].Read(handPosition, handRotation);
                }
            }
Ejemplo n.º 14
0
            protected override void OnRead(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, int rootIndex, int index)
            {
                Vector3    shoulderPosition = positions[index];
                Quaternion shoulderRotation = rotations[index];
                Vector3    upperArmPosition = positions[index + 1];
                Quaternion upperArmRotation = rotations[index + 1];
                Vector3    forearmPosition  = positions[index + 2];
                Quaternion forearmRotation  = rotations[index + 2];
                Vector3    handPosition     = positions[index + 3];
                Quaternion handRotation     = rotations[index + 3];

                if (!initiated)
                {
                    IKPosition = handPosition;
                    IKRotation = handRotation;
                    rotation   = IKRotation;

                    this.hasShoulder = hasShoulders;

                    bones = new VirtualBone[hasShoulder ? 4 : 3];

                    if (hasShoulder)
                    {
                        bones[0] = new VirtualBone(shoulderPosition, shoulderRotation);
                        bones[1] = new VirtualBone(upperArmPosition, upperArmRotation);
                        bones[2] = new VirtualBone(forearmPosition, forearmRotation);
                        bones[3] = new VirtualBone(handPosition, handRotation);
                    }
                    else
                    {
                        this.bones[0] = new VirtualBone(upperArmPosition, upperArmRotation);
                        this.bones[1] = new VirtualBone(forearmPosition, forearmRotation);
                        this.bones[2] = new VirtualBone(handPosition, handRotation);
                    }

                    Vector3 rootForward = rotations[0] * Vector3.forward;
                    chestForwardAxis = Quaternion.Inverse(rootRotation) * rootForward;
                    chestUpAxis      = Quaternion.Inverse(rootRotation) * (rotations[0] * Vector3.up);

                    // Get the local axis of the upper arm pointing towards the bend normal
                    Vector3 upperArmForwardAxis = AxisTools.GetAxisVectorToDirection(upperArmRotation, rootForward);
                    if (Vector3.Dot(upperArmRotation * upperArmForwardAxis, rootForward) < 0f)
                    {
                        upperArmForwardAxis = -upperArmForwardAxis;
                    }
                    upperArmBendAxis = Vector3.Cross(Quaternion.Inverse(upperArmRotation) * (forearmPosition - upperArmPosition), upperArmForwardAxis);
                }

                if (hasShoulder)
                {
                    bones[0].Read(shoulderPosition, shoulderRotation);
                    bones[1].Read(upperArmPosition, upperArmRotation);
                    bones[2].Read(forearmPosition, forearmRotation);
                    bones[3].Read(handPosition, handRotation);
                }
                else
                {
                    bones[0].Read(upperArmPosition, upperArmRotation);
                    bones[1].Read(forearmPosition, forearmRotation);
                    bones[2].Read(handPosition, handRotation);
                }
            }