public bool CalculateModelSpaceAndLength()
        {
            // findout model front/up axis
            var wrist    = m_modelJoints[HandJointName.Wrist];
            var furthest = FindFurthestFingerTip();
            var thinnest = FindMostThinnestFingerTip();
            var thickest = FindMostThickestFingerTip();

            if (wrist == null || furthest == null || thinnest == null || thickest == null || thinnest == thickest)
            {
                Debug.LogError(logPrefix + "Unable to fine model space because no valid finger found. furthest:" + (furthest ? furthest.name : "null") + " furthest:" + (thinnest ? thinnest.name : "null") + " furthest:" + (thickest ? thickest.name : "null"));
                m_isValidModel = false;
                return(false);
            }

            // find forward
            var forward   = NormalizeAxis(wrist.InverseTransformPoint(furthest.position));
            var vThinnest = wrist.InverseTransformPoint(thinnest.position);
            var vThickest = wrist.InverseTransformPoint(thickest.position);
            var up        = NormalizeAxis(m_modelHanded == Handed.Right ? Vector3.Cross(vThickest, vThinnest) : Vector3.Cross(vThinnest, vThickest));

            if (Vector3.Dot(forward, up) != 0f)
            {
                Debug.LogError(logPrefix + "Unable to find valid model forward/up. forward:" + forward + " up:" + up);
                m_isValidModel = false;
                return(false);
            }

            m_isValidModel       = true;
            m_modelOffset        = new RigidPose(Vector3.zero, Quaternion.LookRotation(forward, up));
            m_modelOffsetInverse = m_modelOffset.GetInverse();
            m_modelLength        = CalculateModelLength();
            return(true);
        }
        private void ControlDeviceGroup(IVRModuleDeviceStateRW[] deviceStates)
        {
            var hmdPose      = deviceStates[VRModule.HMD_DEVICE_INDEX].pose;
            var hmdPoseEuler = hmdPose.rot.eulerAngles;

            var oldRigPose = new RigidPose(hmdPose.pos, Quaternion.Euler(0f, hmdPoseEuler.y, 0f));

            var deltaAngle    = Time.unscaledDeltaTime * VIUSettings.simulatorMouseRotateSpeed;
            var deltaKeyAngle = Time.unscaledDeltaTime * VIUSettings.simulatorKeyRotateSpeed;

            // translate and rotate HMD
            hmdPoseEuler.x = Mathf.Repeat(hmdPoseEuler.x + 180f, 360f) - 180f;

            var pitchDelta = -Input.GetAxisRaw("Mouse Y") * deltaAngle;

            if (pitchDelta > 0f)
            {
                if (hmdPoseEuler.x < 90f && hmdPoseEuler.x > -180f)
                {
                    hmdPoseEuler.x = Mathf.Min(90f, hmdPoseEuler.x + pitchDelta);
                }
            }
            else if (pitchDelta < 0f)
            {
                if (hmdPoseEuler.x < 180f && hmdPoseEuler.x > -90f)
                {
                    hmdPoseEuler.x = Mathf.Max(-90f, hmdPoseEuler.x + pitchDelta);
                }
            }

            if (Input.GetKey(KeyCode.DownArrow))
            {
                if (hmdPoseEuler.x < 90f && hmdPoseEuler.x > -180f)
                {
                    hmdPoseEuler.x = Mathf.Min(90f, hmdPoseEuler.x + deltaKeyAngle);
                }
            }

            if (Input.GetKey(KeyCode.UpArrow))
            {
                if (hmdPoseEuler.x < 180f && hmdPoseEuler.x > -90f)
                {
                    hmdPoseEuler.x = Mathf.Max(-90f, hmdPoseEuler.x - deltaKeyAngle);
                }
            }

            if (Input.GetKey(KeyCode.RightArrow))
            {
                hmdPoseEuler.y += deltaKeyAngle;
            }
            if (Input.GetKey(KeyCode.LeftArrow))
            {
                hmdPoseEuler.y -= deltaKeyAngle;
            }
            if (Input.GetKey(KeyCode.C))
            {
                hmdPoseEuler.z += deltaKeyAngle;
            }
            if (Input.GetKey(KeyCode.Z))
            {
                hmdPoseEuler.z -= deltaKeyAngle;
            }
            if (Input.GetKey(KeyCode.X))
            {
                hmdPoseEuler.z = 0f;
            }

            hmdPoseEuler.y += Input.GetAxisRaw("Mouse X") * deltaAngle;

            hmdPose.rot = Quaternion.Euler(hmdPoseEuler);

            var deltaMove   = Time.unscaledDeltaTime * VIUSettings.simulatorKeyMoveSpeed;
            var moveForward = Quaternion.Euler(0f, hmdPoseEuler.y, 0f) * Vector3.forward;
            var moveRight   = Quaternion.Euler(0f, hmdPoseEuler.y, 0f) * Vector3.right;

            if (Input.GetKey(KeyCode.D))
            {
                hmdPose.pos += moveRight * deltaMove;
            }
            if (Input.GetKey(KeyCode.A))
            {
                hmdPose.pos -= moveRight * deltaMove;
            }
            if (Input.GetKey(KeyCode.E))
            {
                hmdPose.pos += Vector3.up * deltaMove;
            }
            if (Input.GetKey(KeyCode.Q))
            {
                hmdPose.pos -= Vector3.up * deltaMove;
            }
            if (Input.GetKey(KeyCode.W))
            {
                hmdPose.pos += moveForward * deltaMove;
            }
            if (Input.GetKey(KeyCode.S))
            {
                hmdPose.pos -= moveForward * deltaMove;
            }

            deviceStates[VRModule.HMD_DEVICE_INDEX].pose = hmdPose;

            var rigPoseOffset = new RigidPose(hmdPose.pos, Quaternion.Euler(0f, hmdPose.rot.eulerAngles.y, 0f)) * oldRigPose.GetInverse();

            for (int i = deviceStates.Length - 1; i >= 0; --i)
            {
                if (i == VRModule.HMD_DEVICE_INDEX)
                {
                    continue;
                }

                var state = deviceStates[i];
                if (!state.isConnected)
                {
                    continue;
                }

                state.pose = rigPoseOffset * state.pose;
            }
        }