Example #1
0
        //============================================================================================

        /**
         *  @brief
         *
         *********************************************************************************************/
        public PoseData(PoseData a_pose)
        {
            PoseId        = a_pose.PoseId;
            AnimId        = a_pose.AnimId;
            TracksId      = a_pose.TracksId;
            PrimaryClipId = a_pose.PrimaryClipId;
            Time          = a_pose.Time;
            LocalVelocity = a_pose.LocalVelocity;
            NextPoseId    = a_pose.NextPoseId;
            LastPoseId    = a_pose.LastPoseId;

            Favour = a_pose.Favour;

            JointsData = new JointData[a_pose.JointsData.Length];
            for (int i = 0; i < a_pose.JointsData.Length; ++i)
            {
                JointsData[i] = a_pose.JointsData[i];
            }

            Trajectory = new TrajectoryPoint[a_pose.Trajectory.Length];
            for (int i = 0; i < Trajectory.Length; ++i)
            {
                Trajectory[i] = a_pose.Trajectory[i];
            }

            Tags        = a_pose.Tags;
            FavourTags  = a_pose.FavourTags;
            GenericTags = a_pose.GenericTags;
            UserTags    = a_pose.UserTags;
            AnimType    = a_pose.AnimType;
        }
        //===========================================================================================

        /**
         *  @brief
         *
         *          *  This function is usually called automatically by the MxMAnimator following an action event
         *  dependent on the 'PostEventTrajectoryHandling' property (see MxMAnimator.cs).
         *
         *********************************************************************************************/
        public virtual void CopyGoalFromPose(ref PoseData a_poseData)
        {
            if (a_poseData.Trajectory.Length != p_goal.Length)
            {
                return;
            }

            SetGoal(a_poseData.Trajectory);
        }
Example #3
0
 //============================================================================================
 /**
 *  @brief Sets the status of the Playable State to chosen with the passed pose data
 *  
 *  @param [ref PoseData] a_pose - the pose to set as chosen
 *         
 *********************************************************************************************/
 public void SetAsChosenWithPose(ref PoseData a_pose)
 {
     Weight = 0f; //This may be wrong, start blend weight of 0?
     HighestWeight = 0f;
     AnimType = a_pose.AnimType;
     AnimId = a_pose.AnimId;
     StartPoseId = a_pose.PoseId;
     StartTime = a_pose.Time;
     Age = 0f;
     DecayAge = 0f;
     BlendStatus = EBlendStatus.Chosen;
 }
        //============================================================================================
        /**
        *  @brief Sets up a pose for inertial blending method (Experimental)
        *  
        *  In the case of inertial blending, playable clips are never destroyed. Rather each clip is
        *  setup at load time as a playable in a slot id which is the same as their clip Id. Therefore,
        *  no slot id is required
        *  
        *  @param [ref PoseData] a_pose - the pose to setup
        *  @param [float] a_speedMod - the speed modification to the clip playabck (default 1f)
        *         
        *********************************************************************************************/
        private void SetupPose(ref PoseData a_pose, float a_speedMod = 1f)
        {
            ClearActiveSlotWeights();

            switch (a_pose.AnimType)
            {
                case EMxMAnimtype.Composite: { SetupComposite (ref a_pose, a_speedMod); } break;
                case EMxMAnimtype.IdleSet: { SetupClip (ref a_pose, a_speedMod); } break;
                case EMxMAnimtype.BlendSpace: { SetupBlendSpace (ref a_pose, a_speedMod); } break;
                case EMxMAnimtype.Clip: { SetupClip (ref a_pose, a_speedMod); } break;
                case EMxMAnimtype.BlendClip: { SetupBlendClip (ref a_pose, a_speedMod); } break;
                case EMxMAnimtype.Sequence: { } break;
                case EMxMAnimtype.BlendSpace1D: { } break;
            }
        }
Example #5
0
        //============================================================================================
        /**
        *  @brief Copies the data from one pose to another
        *  
        *  @param [ref PoseData] a_fromPose - the pose to copy data from
        *  @param [ref PoseData] a_toPose - the pose to copy data to
        *         
        *********************************************************************************************/
        public static void CopyPose(ref PoseData a_fromPose, ref PoseData a_toPose)
        {
            a_toPose.PoseId = a_fromPose.PoseId;
            a_toPose.AnimId = a_fromPose.AnimId;
            a_toPose.Time = a_fromPose.Time;
            a_toPose.NextPoseId = a_fromPose.NextPoseId;
            a_toPose.LastPoseId = a_fromPose.LastPoseId;
            a_toPose.Favour = a_fromPose.Favour;
            a_toPose.LocalVelocity = a_fromPose.LocalVelocity;
            a_toPose.Tags = a_fromPose.Tags;
            a_toPose.GenericTags = a_fromPose.GenericTags;
            a_toPose.AnimType = a_fromPose.AnimType;

            if (a_toPose.JointsData == null)
                a_toPose.JointsData = new JointData[a_fromPose.JointsData.Length];

            if (a_toPose.Trajectory == null)
                a_toPose.Trajectory = new TrajectoryPoint[a_fromPose.Trajectory.Length];

            System.Array.Copy(a_fromPose.JointsData, a_toPose.JointsData, a_fromPose.JointsData.Length);
            System.Array.Copy(a_fromPose.Trajectory, a_toPose.Trajectory, a_fromPose.Trajectory.Length);
        }
        private List<int> m_activeSlots = null; //A list of inputs to the animation mixer that are active

        //============================================================================================
        /**
        *  @brief Sets up a pose in a specific input on the Motion Matching mixer playable
        *  
        *  @param [ref PoseData] a_pose - the pose to extract the animation data from
        *  @param [int] a_slotId - the Id of the slot (or in put) to use
        *  @param [float] a_speedMod - any speed modification for animation playback (default 1f)
        *         
        *********************************************************************************************/
        private void SetupPoseInSlot(ref PoseData a_pose, int a_slotId, float a_speedMod = 1f)
        {
#if UNITY_2019_1_OR_NEWER
            if (p_riggingIntegration != null)
                p_riggingIntegration.FixRigTransforms();
#endif
            var playable = m_animationMixer.GetInput(a_slotId);

            if(playable.IsValid())
                ClearPlayablesInSlot(a_slotId);

            switch (a_pose.AnimType)
            {
                case EMxMAnimtype.Composite: { SetupCompositeInSlot(ref a_pose, a_slotId, a_speedMod); } break;
                case EMxMAnimtype.IdleSet: { SetupClipInSlot(ref a_pose, a_slotId, a_speedMod); } break;
                case EMxMAnimtype.BlendSpace: { SetupBlendSpaceInSlot(ref a_pose, a_slotId, a_speedMod); } break;
                case EMxMAnimtype.Clip: { SetupClipInSlot(ref a_pose, a_slotId, a_speedMod); } break;
                case EMxMAnimtype.BlendClip: { SetupBlendClipInSlot(ref a_pose, a_slotId, a_speedMod); } break;
                case EMxMAnimtype.Sequence: { } break;
                case EMxMAnimtype.BlendSpace1D: { } break;
            }
        }
        //============================================================================================

        /**
         *  @brief Stops pose debugging and destroys the playable graph
         *
         *********************************************************************************************/
        public void StopPoseDebug()
        {
            if (!m_debugPosesActive)
            {
                return;
            }

            if (m_animData.Length > 0 && m_animData[0] != null)
            {
                CurrentAnimData = m_animData[0];
            }

            if (CurrentAnimData != null)
            {
                m_debugPosesActive = false;

                PoseData      pose = CurrentAnimData.Poses[0];
                AnimationClip clip = CurrentAnimData.Clips[pose.PrimaryClipId];

                var clipPlayable = m_animationMixer.GetInput(0);
                if (!clipPlayable.IsNull())
                {
                    m_animationMixer.DisconnectInput(0);
                    clipPlayable.Destroy();
                }

                clipPlayable = AnimationClipPlayable.Create(MxMPlayableGraph, clip);
                MxMPlayableGraph.Connect(clipPlayable, 0, m_animationMixer, 0);
                clipPlayable.SetTime(0.0);
                clipPlayable.SetTime(0.0);
                MxMPlayableGraph.Evaluate(0f);
                SceneView.RepaintAll();

                MxMPlayableGraph.Destroy();
                transform.SetPositionAndRotation(basePosition, baseRotation);
            }
        }
        //============================================================================================

        /**
         *  @brief Draws the debugging gizmos for the MxMAnimator.
         *
         *  This includes drawing of the desired trajectory, animation trajectory and pose joint data.
         *
         *********************************************************************************************/
        private void OnDrawGizmos()
        {
            if (p_trajectoryGenerator != null && CurrentAnimData != null && m_animMixerConnected && enabled)
            {
                if (m_fsm.CurrentStateId == (uint)EMxMStates.Event)
                {
                    Gizmos.color = Color.green;
                    Gizmos.DrawSphere(m_desiredEventRootWorld.Position, 0.1f);
                    Quaternion rotation = Quaternion.AngleAxis(m_desiredEventRootWorld.RotationY, Vector3.up);
                    DrawArrow.ForGizmo(m_desiredEventRootWorld.Position, rotation * Vector3.forward, 0.8f, 20f);
                    Gizmos.color = Color.blue;
                    Gizmos.DrawSphere(m_currentEventRootWorld.Position, 0.1f);
                    rotation = Quaternion.AngleAxis(m_currentEventRootWorld.RotationY, Vector3.up);
                    DrawArrow.ForGizmo(m_currentEventRootWorld.Position, rotation * Vector3.forward, 0.8f, 20f);

                    Quaternion contactRotation = Quaternion.AngleAxis(m_desiredEventRootWorld.RotationY, Vector3.up);

                    if (CurEventContacts != null && CurEventContacts.Length > m_curEventContactIndex)
                    {
                        Gizmos.color = Color.red;
                        Gizmos.DrawSphere(CurEventContacts[m_curEventContactIndex].Position, 0.05f);
                    }

                    Gizmos.color = Color.cyan;
                    Gizmos.DrawSphere(transform.position, 0.05f);
                    DrawArrow.ForGizmo(transform.position, transform.rotation * Vector3.forward, 0.4f, 20f);
                }

                if (m_debugGoal)
                {
                    Array.Copy(m_desiredGoal, m_debugDesiredGoal, m_debugDesiredGoal.Length);
                    float rot = transform.rotation.eulerAngles.y;
                    for (int i = 0; i < m_debugDesiredGoal.Length; ++i)
                    {
                        Vector3 newPos = transform.TransformVector(m_debugDesiredGoal[i].Position);
                        float   newRot = m_debugDesiredGoal[i].FacingAngle + rot;

                        m_debugDesiredGoal[i] = new TrajectoryPoint(newPos, newRot);
                    }

                    //Draw Desired Trajectory
                    int doneCenter = 0;
                    for (int i = 0; i < m_debugDesiredGoal.Length; ++i)
                    {
                        TrajectoryPoint curPoint = m_debugDesiredGoal[i];

                        if (CurrentAnimData.PosePredictionTimes[i] < 0f)
                        {
                            Gizmos.color = new Color(0f, 0.5f, 0f);
                        }
                        else
                        {
                            if (doneCenter == 0)
                            {
                                Gizmos.color  = Color.cyan;
                                Handles.color = Color.cyan;
                                Handles.DrawWireDisc(transform.position, Vector3.up, 0.3f);
                                Gizmos.color = Color.blue;
                                Vector3 centerPos   = transform.position;
                                Vector3 centerPosUp = centerPos + Vector3.up * 0.14f;

                                if (m_debugArrowMesh != null)
                                {
                                    Gizmos.DrawMesh(m_debugArrowMesh, centerPos, Quaternion.LookRotation(transform.forward, Vector3.up));
                                }
                                else
                                {
                                    Gizmos.DrawSphere(centerPos, 0.06f);
                                    Gizmos.DrawLine(centerPos, centerPosUp);
                                    DrawArrow.ForGizmo(centerPosUp, transform.forward * 0.2f, 0.05f, 20f);
                                }

                                doneCenter = 1;

                                Vector3 point1;
                                Vector3 point2;
                                if (m_transformGoal)
                                {
                                    point1 = transform.position + m_debugDesiredGoal[i - 1].Position;
                                    point2 = transform.position + m_debugDesiredGoal[i].Position;
                                }
                                else
                                {
                                    point1 = transform.TransformPoint(m_debugDesiredGoal[i - 1].Position);
                                    point2 = transform.TransformPoint(m_debugDesiredGoal[i].Position);
                                }

                                Gizmos.color = new Color(0f, 0.5f, 0f);
                                Gizmos.DrawLine(centerPos, point1);
                                Gizmos.color = Color.green;
                                Gizmos.DrawLine(centerPos, point2);
                            }

                            Gizmos.color = Color.green;
                        }

                        Vector3 pointPos;

                        float angle = curPoint.FacingAngle * Mathf.Deg2Rad;

                        var direction = new Vector3(0.2f * Mathf.Sin(angle), 0f, 0.2f * Mathf.Cos(angle));

                        if (m_transformGoal)
                        {
                            pointPos = transform.position + curPoint.Position;
                        }
                        else
                        {
                            pointPos  = transform.TransformPoint(curPoint.Position);
                            direction = transform.TransformDirection(direction);
                        }

                        if (m_debugArrowMesh != null)
                        {
                            Gizmos.DrawMesh(m_debugArrowMesh, pointPos, Quaternion.LookRotation(direction, Vector3.up));
                        }
                        else
                        {
                            Gizmos.DrawSphere(pointPos, 0.06f);
                            Gizmos.DrawLine(pointPos, new Vector3(pointPos.x, pointPos.y + 0.14f, pointPos.z));
                            DrawArrow.ForGizmo(new Vector3(pointPos.x, pointPos.y + 0.14f, pointPos.z),
                                               direction, 0.05f, 20f);
                        }

                        if (i != 0)
                        {
                            Vector3 pointPosPrev;

                            if (m_transformGoal)
                            {
                                pointPosPrev = transform.position + m_debugDesiredGoal[i - 1].Position;
                            }
                            else
                            {
                                pointPosPrev = transform.TransformPoint(m_debugDesiredGoal[i - 1].Position);
                            }

                            if (doneCenter != 1)
                            {
                                Gizmos.DrawLine(pointPos, pointPosPrev);
                            }
                            else
                            {
                                doneCenter = 2;
                            }
                        }
                    }
                }

                if (m_debugCurrentPose)
                {
                    Gizmos.color = Color.yellow;

                    for (int i = 0; i < m_curInterpolatedPose.JointsData.Length; ++i)
                    {
                        Vector3 pos = transform.TransformPoint(m_curInterpolatedPose.JointsData[i].Position);
                        Gizmos.DrawWireSphere(pos, 0.04f);
                        DrawArrow.ForGizmo(pos, transform.TransformVector(m_curInterpolatedPose.JointsData[i].Velocity), 0.05f);
                    }
                }

                if (m_debugChosenTrajectory)
                {
                    TrajectoryPoint[] curGoal = CurrentAnimData.Poses[m_curInterpolatedPose.PoseId].Trajectory;

                    int doneCenter = 0;
                    for (int i = 0; i < curGoal.Length; ++i)
                    {
                        TrajectoryPoint curPoint = curGoal[i];

                        if (CurrentAnimData.PosePredictionTimes[i] < 0f)
                        {
                            Gizmos.color = new Color(0.5f, 0f, 0f);
                        }
                        else
                        {
                            if (doneCenter == 0)
                            {
                                Gizmos.color  = Color.cyan;
                                Handles.color = Color.cyan;
                                Handles.DrawWireDisc(transform.position, Vector3.up, 0.3f);

                                DrawArrow.ForGizmo(transform.position,
                                                   transform.TransformVector(m_curInterpolatedPose.LocalVelocity), 0.4f, 20f);

                                Gizmos.color = Color.blue;
                                Vector3 centerPos   = transform.position;
                                Vector3 centerPosUp = centerPos + Vector3.up * 0.14f;

                                if (m_debugArrowMesh != null)
                                {
                                    Gizmos.DrawMesh(m_debugArrowMesh, centerPos, Quaternion.LookRotation(transform.forward, Vector3.up));
                                }
                                else
                                {
                                    Gizmos.DrawSphere(centerPos, 0.06f);
                                    Gizmos.DrawLine(centerPos, centerPosUp);
                                    DrawArrow.ForGizmo(centerPosUp, transform.forward * 0.2f, 0.05f, 20f);
                                }

                                doneCenter = 1;

                                Gizmos.color = new Color(0.5f, 0f, 0f);
                                Gizmos.DrawLine(centerPos, transform.TransformPoint(curGoal[i - 1].Position));
                                Gizmos.color = Color.red;
                                Gizmos.DrawLine(centerPos, transform.TransformPoint(curGoal[i].Position));
                            }

                            Gizmos.color = Color.red;
                        }

                        Vector3 pointPos = transform.TransformPoint(curPoint.Position);
                        float   angle    = curPoint.FacingAngle * Mathf.Deg2Rad;

                        Vector3 direction = transform.TransformDirection(new Vector3(0.2f * Mathf.Sin(angle),
                                                                                     0f, 0.2f * Mathf.Cos(angle)));

                        if (m_debugArrowMesh != null)
                        {
                            Gizmos.DrawMesh(m_debugArrowMesh, pointPos, Quaternion.LookRotation(direction, Vector3.up));
                        }
                        else
                        {
                            Gizmos.DrawSphere(pointPos, 0.06f);
                            Gizmos.DrawLine(pointPos, new Vector3(pointPos.x, pointPos.y + 0.14f, pointPos.z));
                            DrawArrow.ForGizmo(new Vector3(pointPos.x, pointPos.y + 0.14f, pointPos.z), direction, 0.05f, 20f);
                        }

                        if (i != 0)
                        {
                            if (doneCenter != 1)
                            {
                                Gizmos.DrawLine(pointPos, transform.TransformPoint(curGoal[i - 1].Position));
                            }
                            else
                            {
                                doneCenter = 2;
                            }
                        }
                    }
                }
            }
            else
            {
                //Draw Chosen Pose Trajectory
                if (m_debugPoses && m_debugPosesActive && CurrentAnimData != null)
                {
                    if (m_debugPoseId >= CurrentAnimData.Poses.Length)
                    {
                        m_debugPoseId = CurrentAnimData.Poses.Length - 1;
                    }

                    PoseData pose = CurrentAnimData.Poses[m_debugPoseId];
                    UpdatePoseDebug();
                    Gizmos.color = Color.yellow;
                    for (int i = 0; i < pose.JointsData.Length; ++i)
                    {
                        Vector3 pos = transform.TransformPoint(pose.JointsData[i].Position);
                        Gizmos.DrawWireSphere(pos, 0.04f);
                        DrawArrow.ForGizmo(pos, transform.TransformVector(
                                               pose.JointsData[i].Velocity), 0.05f);
                    }

                    if (m_debugPoseId < CurrentAnimData.Poses.Length)
                    {
                        m_chosenPose = CurrentAnimData.Poses[m_debugPoseId];
                    }
                    else
                    {
                        m_chosenPose = CurrentAnimData.Poses[CurrentAnimData.Poses.Length - 1];
                    }

                    TrajectoryPoint[] curGoal = m_chosenPose.Trajectory;

                    int doneCenter = 0;
                    for (int i = 0; i < curGoal.Length; ++i)
                    {
                        TrajectoryPoint curPoint = curGoal[i];

                        if (CurrentAnimData.PosePredictionTimes[i] < 0f)
                        {
                            Gizmos.color = new Color(0.5f, 0f, 0f);
                        }
                        else
                        {
                            if (doneCenter == 0)
                            {
                                Gizmos.color  = Color.cyan;
                                Handles.color = Color.cyan;
                                Handles.DrawWireDisc(transform.position, Vector3.up, 0.3f);
                                DrawArrow.ForGizmo(transform.position,
                                                   transform.TransformVector(m_chosenPose.LocalVelocity), 0.4f, 20f);

                                Gizmos.color = Color.blue;
                                Vector3 centerPos   = transform.position;
                                Vector3 centerPosUp = centerPos + Vector3.up * 0.14f;

                                if (m_debugArrowMesh != null)
                                {
                                    Gizmos.DrawMesh(m_debugArrowMesh, centerPos, Quaternion.LookRotation(transform.forward, Vector3.up));
                                }
                                else
                                {
                                    Gizmos.DrawSphere(centerPos, 0.06f);
                                    Gizmos.DrawLine(centerPos, centerPosUp);
                                    DrawArrow.ForGizmo(centerPosUp, transform.forward * 0.2f, 0.05f, 20f);
                                }
                                doneCenter = 1;

                                Gizmos.color = new Color(0.5f, 0f, 0f);
                                Gizmos.DrawLine(centerPos, transform.TransformPoint(curGoal[i - 1].Position));
                                Gizmos.color = Color.red;
                                Gizmos.DrawLine(centerPos, transform.TransformPoint(curGoal[i].Position));
                            }

                            Gizmos.color = Color.red;
                        }

                        Vector3 pointPos = transform.TransformPoint(curPoint.Position);
                        float   angle    = curPoint.FacingAngle * Mathf.Deg2Rad;


                        Gizmos.DrawSphere(pointPos, 0.06f);
                        DrawArrow.ForGizmo(new Vector3(pointPos.x, pointPos.y + 0.14f, pointPos.z),
                                           transform.TransformDirection(new Vector3(0.2f * Mathf.Sin(angle),
                                                                                    0f, 0.2f * Mathf.Cos(angle))), 0.05f, 20f);
                        Gizmos.DrawLine(pointPos, new Vector3(pointPos.x, pointPos.y + 0.14f, pointPos.z));

                        if (i != 0)
                        {
                            if (doneCenter != 1)
                            {
                                Gizmos.DrawLine(pointPos, transform.TransformPoint(curGoal[i - 1].Position));
                            }
                            else
                            {
                                doneCenter = 2;
                            }
                        }
                    }
                }
            }
        }
        //============================================================================================

        /**
         *  @brief Prints all used pose analytics data to the console
         *
         *********************************************************************************************/
        public void DumpUsedPoseAnalytics()
        {
            if (m_usedPoses != null)
            {
                AnimationClip currentClip = null;

                int maxUseCount = 0;

                foreach (KeyValuePair <int, int> kvp in m_usedPoses)
                {
                    if (kvp.Value > maxUseCount)
                    {
                        maxUseCount = kvp.Value;
                    }
                }

                StringBuilder clipStats = new StringBuilder();

                int uniqueUsedPoses    = 0;
                int clipTotalUsedPoses = 0;

                MxMAnimData curAnimData = m_targetAnimator.CurrentAnimData;

                for (int index = 0; index < curAnimData.Poses.Length; index++)
                {
                    PoseData animDataPose = curAnimData.Poses[index];

                    int clipId = 0;
                    switch (animDataPose.AnimType)
                    {
                    case EMxMAnimtype.Composite: { clipId = curAnimData.Composites[animDataPose.AnimId].ClipIdA; } break;

                    case EMxMAnimtype.BlendSpace: { clipId = curAnimData.BlendSpaces[animDataPose.AnimId].ClipIds[0]; } break;

                    case EMxMAnimtype.Clip: { clipId = curAnimData.ClipsData[animDataPose.AnimId].ClipId; } break;
                    }

                    var newClip = curAnimData.Clips[clipId] != currentClip;

                    if (newClip)
                    {
                        if (currentClip)
                        {
                            clipStats.Append(clipTotalUsedPoses.ToString());

                            Debug.Log(clipStats.ToString(), currentClip);

                            clipTotalUsedPoses = 0;

                            clipStats.Clear();
                        }

                        currentClip = curAnimData.Clips[clipId];

                        clipStats.Append(currentClip.name);
                    }

                    int usedCount;

                    if (m_usedPoses.TryGetValue(index, out usedCount))
                    {
                        uniqueUsedPoses++;
                        clipTotalUsedPoses++;

                        int usedDigit = usedCount * 9 / maxUseCount;

                        clipStats.Append(usedDigit);
                    }
                    else
                    {
                        clipStats.Append('_');
                    }
                }

                if (currentClip)
                {
                    clipStats.Append(clipTotalUsedPoses.ToString());

                    Debug.Log(clipStats.ToString(), currentClip);

                    clipTotalUsedPoses = 0;

                    clipStats.Clear();
                }

                Debug.Log(string.Format("used {0}/{1} poses ", uniqueUsedPoses, curAnimData.Poses.Length));
            }
        }
 //============================================================================================
 /**
 *  @brief Sets up a single clip in an input slot on the motion matching mixer.
 *  
 *  @param [ref PoseData] a_pose - a reference to the pose data containing the information on the clip
 *  @param [int] a_slotId - the input slot id to set the clip up in.
 *  @param [float] a_speedMod - the speed modification on the clip playback (default 1f)
 *         
 *********************************************************************************************/
 private void SetupClipInSlot(ref PoseData a_pose, int a_slotId, float a_speedMod = 1f)
 {
     ref MxMPlayableState playableState = ref m_animationStates[a_slotId];