// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Modified by Unity from original:
// https://github.com/googlevr/daydream-elements/blob/master/Assets/DaydreamElements/Elements/ArmModels/Scripts/ArmModels/TransitionArmModel.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.Events;
#if ENABLE_VR || ENABLE_AR
using UnityEngine.Experimental.XR.Interaction;
using UnityEngine.SpatialTracking;
[assembly: InternalsVisibleTo("UnityEditor.XR.LegacyInputHelpers")]
namespace UnityEngine.XR.LegacyInputHelpers
{
[Serializable]
public class ArmModelTransition
{
[SerializeField]
String m_KeyName;
///
/// the string name that will be used to trigger a transition
///
public string transitionKeyName
{
get { return m_KeyName; }
set { m_KeyName = value; }
}
[SerializeField]
ArmModel m_ArmModel;
///
/// the arm model that will be transitioned to on receiving this event.
///
public ArmModel armModel
{
get { return m_ArmModel; }
set { m_ArmModel = value; }
}
}
public class TransitionArmModel : ArmModel
{
[SerializeField]
ArmModel m_CurrentArmModelComponent = null;
///
/// This field contains the current active arm model that will be used as the input to the tracked pose driver which is
/// using the transitional arm model.
///
public ArmModel currentArmModelComponent
{
get { return m_CurrentArmModelComponent; }
set { m_CurrentArmModelComponent = value; }
}
[SerializeField]
public List m_ArmModelTransitions = new List();
/// Max number of active transitions that can be going on at one time.
/// Transitions are only completed when the controller rotates, so if TransitionToArmModel
/// is called several times without the controller moving, the number of active transitions can
/// add up.
private const int MAX_ACTIVE_TRANSITIONS = 10;
/// When transitioning to a new arm model, drop any old transitions that have barely begun.
private const float DROP_TRANSITION_THRESHOLD = 0.035f;
/// Threshold for clamping transitions that have been completed.
private const float LERP_CLAMP_THRESHOLD = 0.95f;
/// Minimum amount of angular velocity on the controller before transitioning occurs.
private const float MIN_ANGULAR_VELOCITY = 0.2f;
/// Unit less weight for how much the angular velocity impacts the transition.
private const float ANGULAR_VELOCITY_DIVISOR = 45.0f;
internal struct ArmModelBlendData
{
public ArmModel armModel;
public float currentBlendAmount;
}
internal List armModelBlendData = new List(MAX_ACTIVE_TRANSITIONS);
ArmModelBlendData currentBlendingArmModel;
public bool Queue(string key)
{
// attempt to find the arm model to blend to using the supplied key.
foreach(var am in m_ArmModelTransitions)
{
if(am.transitionKeyName == key)
{
Queue(am.armModel);
return true;
}
}
return false;
}
public void Queue(ArmModel newArmModel)
{
if(newArmModel == null)
{
return;
}
if(m_CurrentArmModelComponent == null)
{
m_CurrentArmModelComponent = newArmModel;
}
RemoveJustStartingTransitions();
if (armModelBlendData.Count == MAX_ACTIVE_TRANSITIONS)
{
RemoveOldestTransition();
}
var ambd = new ArmModelBlendData();
ambd.armModel = newArmModel;
ambd.currentBlendAmount = 0.0f;
armModelBlendData.Add(ambd);
}
void RemoveJustStartingTransitions()
{
for( int i = 0; i < armModelBlendData.Count; ++i)
{
ArmModelBlendData ambd = armModelBlendData[i];
if (ambd.currentBlendAmount < DROP_TRANSITION_THRESHOLD)
{
armModelBlendData.RemoveAt(i);
}
}
}
void RemoveOldestTransition()
{
armModelBlendData.RemoveAt(0);
}
public override PoseDataFlags GetPoseFromProvider(out Pose output)
{
if (UpdateBlends())
{
output = finalPose;
return PoseDataFlags.Position | PoseDataFlags.Rotation;
}
output = Pose.identity;
return PoseDataFlags.NoData;
}
bool UpdateBlends()
{
if (currentArmModelComponent == null)
{
return false;
}
if (m_CurrentArmModelComponent.OnControllerInputUpdated())
{
m_NeckPosition = m_CurrentArmModelComponent.neckPosition;
m_ElbowPosition = m_CurrentArmModelComponent.elbowPosition;
m_WristPosition = m_CurrentArmModelComponent.wristPosition;
m_ControllerPosition = m_CurrentArmModelComponent.controllerPosition;
m_ElbowRotation = m_CurrentArmModelComponent.elbowRotation;
m_WristRotation = m_CurrentArmModelComponent.wristRotation;
m_ControllerRotation = m_CurrentArmModelComponent.controllerRotation;
#if UNITY_EDITOR
m_TorsoDirection = m_CurrentArmModelComponent.torsoDirection;
m_TorsoRotation = m_CurrentArmModelComponent.torsoRotation;
#endif
Vector3 angVel;
if (TryGetAngularVelocity(poseSource, out angVel) && armModelBlendData.Count > 0)
{
float angularVelocity = angVel.magnitude;
float lerpValue = Mathf.Clamp(((angularVelocity) - MIN_ANGULAR_VELOCITY) / ANGULAR_VELOCITY_DIVISOR, 0.0f, 0.1f);
for (int i = 0; i < armModelBlendData.Count; ++i)
{
ArmModelBlendData ambd = armModelBlendData[i];
ambd.currentBlendAmount = Mathf.Lerp(ambd.currentBlendAmount, 1.0f, lerpValue);
if (ambd.currentBlendAmount > LERP_CLAMP_THRESHOLD)
{
ambd.currentBlendAmount = 1.0f;
}
else
{
ambd.armModel.OnControllerInputUpdated();
m_NeckPosition = Vector3.Slerp(neckPosition, ambd.armModel.neckPosition, ambd.currentBlendAmount);
m_ElbowPosition = Vector3.Slerp(elbowPosition, ambd.armModel.elbowPosition, ambd.currentBlendAmount);
m_WristPosition = Vector3.Slerp(wristPosition, ambd.armModel.wristPosition, ambd.currentBlendAmount);
m_ControllerPosition = Vector3.Slerp(controllerPosition, ambd.armModel.controllerPosition, ambd.currentBlendAmount);
m_ElbowRotation = Quaternion.Slerp(elbowRotation, ambd.armModel.elbowRotation, ambd.currentBlendAmount);
m_WristRotation = Quaternion.Slerp(wristRotation, ambd.armModel.wristRotation, ambd.currentBlendAmount);
m_ControllerRotation = Quaternion.Slerp(controllerRotation, ambd.armModel.controllerRotation, ambd.currentBlendAmount);
}
// write back.
armModelBlendData[i] = ambd;
if (ambd.currentBlendAmount >= 1.0f)
{
m_CurrentArmModelComponent = ambd.armModel;
armModelBlendData.RemoveRange(0, i + 1);
}
}
}
else if (armModelBlendData.Count > 0)
{
Debug.LogErrorFormat(this.gameObject, "Unable to get angular acceleration for node");
return false;
}
finalPose = new Pose(controllerPosition, controllerRotation);
return true;
}
else
{
return false;
}
}
#if UNITY_EDITOR
internal List GetActiveBlends()
{
return armModelBlendData;
}
#endif
}
}
#endif