/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* 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.
*/
using UnityEngine;
using UnityEngine.Assertions;
///
/// OVR Component to drive blend shapes on a SkinnedMeshRenderer based on Face Tracking provided by .
///
///
/// Intended to be used as a base type that is inherited from, in order to provide mapping logic from blend shape indices.
/// The mapping of to blend shapes is accomplished by overriding .
/// Needs to be linked to an component to fetch tracking data from.
///
[RequireComponent(typeof(SkinnedMeshRenderer))]
[HelpURL("https://developer.oculus.com/reference/unity/latest/class_o_v_r_face")]
public class OVRFace : MonoBehaviour
{
///
/// Start this instance.
/// Will validate that all properties are set correctly
///
public OVRFaceExpressions FaceExpressions
{
get => _faceExpressions;
set => _faceExpressions = value;
}
public float BlendShapeStrengthMultiplier
{
get => _blendShapeStrengthMultiplier;
set => _blendShapeStrengthMultiplier = value;
}
internal SkinnedMeshRenderer RetrieveSkinnedMeshRenderer()
{
return GetComponent();
}
[SerializeField]
[Tooltip("The OVRFaceExpressions Component to fetch the Face Tracking weights from that are to be applied")]
protected internal OVRFaceExpressions _faceExpressions;
[SerializeField]
[Tooltip("A multiplier to the weights read from the OVRFaceExpressions to exaggerate facial expressions")]
protected internal float _blendShapeStrengthMultiplier = 100.0f;
private SkinnedMeshRenderer _skinnedMeshRenderer;
protected SkinnedMeshRenderer SkinnedMesh => _skinnedMeshRenderer;
protected virtual void Awake()
{
if (_faceExpressions == null)
{
_faceExpressions = SearchFaceExpressions();
Debug.Log($"Found OVRFaceExpression reference in {_faceExpressions.name} due to unassigned field.");
}
}
internal OVRFaceExpressions SearchFaceExpressions() => gameObject.GetComponentInParent();
protected virtual void Start()
{
Assert.IsNotNull(_faceExpressions, "OVRFace requires OVRFaceExpressions to function.");
_skinnedMeshRenderer = GetComponent();
Assert.IsNotNull(_skinnedMeshRenderer);
Assert.IsNotNull(_skinnedMeshRenderer.sharedMesh);
}
protected virtual void Update()
{
if (!_faceExpressions.FaceTrackingEnabled || !_faceExpressions.enabled)
{
return;
}
if (_faceExpressions.ValidExpressions)
{
int numBlendshapes = _skinnedMeshRenderer.sharedMesh.blendShapeCount;
for (int blendShapeIndex = 0; blendShapeIndex < numBlendshapes; ++blendShapeIndex)
{
if (GetWeightValue(blendShapeIndex, out var currentWeight))
{
_skinnedMeshRenderer.SetBlendShapeWeight(blendShapeIndex, Mathf.Clamp(currentWeight, 0f, 100f));
}
}
}
}
///
/// Fetches the for a given blend shape index on the shared mesh of the SkinnedMeshRenderer on the same component
///
///
/// Override this function to provide the mapping between blend shapes and face expressions
///
/// The index of the blend shape, will be in-between 0 and the number of blend shapes on the shared mesh.
/// Returns the to drive the bland shape identified by .
internal protected virtual OVRFaceExpressions.FaceExpression GetFaceExpression(int blendShapeIndex) =>
OVRFaceExpressions.FaceExpression.Invalid;
///
/// Calculates the value for the specific target blend shape of the shared mesh SkinnedMeshRenderer
///
/// Index of the blend shape of the shared mesh SkinnedMeshRenderer
/// Calculated value
/// true if value was calculated, false if no value available for that blend shape
internal protected virtual bool GetWeightValue(int blendShapeIndex, out float weightValue)
{
OVRFaceExpressions.FaceExpression blendShapeToFaceExpression = GetFaceExpression(blendShapeIndex);
if (blendShapeToFaceExpression >= OVRFaceExpressions.FaceExpression.Max || blendShapeToFaceExpression < 0)
{
weightValue = 0;
return false;
}
weightValue = _faceExpressions[blendShapeToFaceExpression] * _blendShapeStrengthMultiplier;
return true;
}
}