public void Update(ref bool dataChanged, ref bool sceneChanged)
        {
            // frame number and timestamp
            scene.frameNumber = Time.frameCount;
            scene.timestamp   = Time.time;

            // poll hand controllers to force detection
            // InputTracking.GetLocalPosition(XRNode.LeftHand);
            // InputTracking.GetLocalPosition(XRNode.RightHand);

            // get new node data
            InputTracking.GetNodeStates(nodeStates);

            foreach (XRNodeState state in nodeStates)
            {
                Actor actor = null;
                if (actors.TryGetValue(state.uniqueID, out actor))
                {
                    // update position, orientation, and tracking state
                    Bone bone = actor.bones[0];
                    bone.tracked = state.tracked;
                    if (bone.tracked)
                    {
                        Vector3 pos;
                        if (state.TryGetPosition(out pos))
                        {
                            bone.CopyFrom(pos);
                        }
                        else
                        {
                            bone.tracked = false;
                        }
                        Quaternion rot;
                        if (state.TryGetRotation(out rot))
                        {
                            bone.CopyFrom(rot);
                        }
                        else
                        {
                            bone.tracked = false;
                        }
                    }
                    dataChanged = true;
                }
                else
                {
                    actor = CreateActor(state);
                    Debug.Log("New actor added: " + actor.name);
                    sceneChanged = true;
                }
            }

            dataChanged = true;
        }
        public void Update(ref bool dataChanged, ref bool sceneChanged)
        {
            compositor.GetLastPoses(poses, gamePoses);

            // frame number and timestamp
            scene.frameNumber = Time.frameCount;
            scene.timestamp   = Time.time;

            for (int idx = 0; idx < trackedDevices.Count; idx++)
            {
                // update position, orientation, and tracking state
                int  controllerIdx = trackedDevices[idx].controllerIdx;
                Bone bone          = scene.actors[idx].bones[0];

                HmdMatrix34_t pose = poses[controllerIdx].mDeviceToAbsoluteTracking;
                Matrix4x4     m    = Matrix4x4.identity;
                m[0, 0] = pose.m0; m[0, 1] = pose.m1; m[0, 2] = pose.m2;  m[0, 3] = pose.m3;
                m[1, 0] = pose.m4; m[1, 1] = pose.m5; m[1, 2] = pose.m6;  m[1, 3] = pose.m7;
                m[2, 0] = pose.m8; m[2, 1] = pose.m9; m[2, 2] = pose.m10; m[2, 3] = pose.m11;
                MathUtil.ToggleLeftRightHandedMatrix(ref m);
                bone.CopyFrom(MathUtil.GetTranslation(m), MathUtil.GetRotation(m));

                bone.tracked = poses[controllerIdx].bDeviceIsConnected && poses[controllerIdx].bPoseIsValid;

                // if this is also an input device, update inputs
                Device dev = trackedDevices[idx].device;
                if (dev != null)
                {
                    system.GetControllerStateWithPose(
                        ETrackingUniverseOrigin.TrackingUniverseStanding,
                        (uint)controllerIdx,
                        ref state, (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VRControllerState_t)),
                        ref poses[controllerIdx]);

                    if (trackedDevices[idx].deviceClass == ETrackedDeviceClass.Controller)
                    {
                        // hand controller
                        // trigger button
                        dev.channels[0].value = state.rAxis1.x;
                        // menu button
                        dev.channels[1].value = (state.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_ApplicationMenu)) != 0 ? 1 : 0;
                        // grip button
                        dev.channels[2].value = (state.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_Grip)) != 0 ? 1 : 0;
                        // touchpad (button4, axis1/2 and axis1raw/2raw)
                        float touchpadPressed = (state.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_SteamVR_Touchpad)) != 0 ? 1 : 0;
                        dev.channels[3].value = touchpadPressed;
                        dev.channels[4].value = state.rAxis0.x * touchpadPressed;
                        dev.channels[5].value = state.rAxis0.y * touchpadPressed;
                        dev.channels[6].value = state.rAxis0.x;
                        dev.channels[7].value = state.rAxis0.y;

                        // touchpad as buttons
                        Vector2 touchpad = new Vector2(state.rAxis0.x, state.rAxis0.y) * touchpadPressed;
                        float   distance = touchpad.magnitude;
                        if (distance < 0.3f)
                        {
                            touchpad = Vector2.zero;
                        }
                        // using angle to determine which circular segment the finger is on
                        // instead of purely <> comparisons on coordinates
                        float angle = Mathf.Rad2Deg * Mathf.Atan2(touchpad.y, touchpad.x);
                        //    +135  +90  +45
                        // +180/-180       0   to allow for overlap, angles are 67.5 around a direction
                        //    -135  -90  -45
                        dev.channels[8].value  = ((angle > 0 - 67.5f) && (angle < 0 + 67.5f)) ? touchpadPressed : 0;                              // right
                        dev.channels[9].value  = ((angle > 180 - 67.5f) || (angle < -180 + 67.5f)) ? touchpadPressed : 0;                         // left
                        dev.channels[10].value = ((angle > 90 - 67.5f) && (angle < 90 + 67.5f)) ? touchpadPressed : 0;                            // up
                        dev.channels[11].value = ((angle > -90 - 67.5f) && (angle < -90 + 67.5f)) ? touchpadPressed : 0;                          // down
                        // rumble output > convert value [0...1] to time                                                                                          // rumble output > convert value [0...1] to time
                        float duration = Mathf.Clamp01(dev.channels[12].value) * 1000.0f * 1000.0f / updateRate;
                        duration = Mathf.Clamp(duration, 0, 30000);
                        system.TriggerHapticPulse((uint)controllerIdx, 0, (ushort)duration);
                    }
                    else if (trackedDevices[idx].deviceClass == ETrackedDeviceClass.GenericTracker)
                    {
                        // generic tracker
                        // grip button
                        dev.channels[0].value = (state.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_Grip)) != 0 ? 1 : 0;
                        // trigger button
                        dev.channels[1].value = (state.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_SteamVR_Trigger)) != 0 ? 1 : 0;
                        // touchpad
                        dev.channels[2].value = (state.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_SteamVR_Touchpad)) != 0 ? 1 : 0;
                        // menu button
                        dev.channels[3].value = (state.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_ApplicationMenu)) != 0 ? 1 : 0;
                        // rumble output > convert value [0...1] to time
                        float duration = Mathf.Clamp01(dev.channels[4].value) * 1.0f;
                        duration = Mathf.Clamp(duration, 0, 30000);
                        system.TriggerHapticPulse((uint)controllerIdx, 0, (ushort)duration);
                    }
                }
            }
            dataChanged = true;
        }