// Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using static Oculus.Movement.FaceTrackingTooltips;
namespace Meta.XR.Movement.FaceTracking.Samples
{
///
/// Inherits from to map source tracking weights
/// to a set of traget weights based on a JSON configuration file.
///
public class FaceRetargeterComponent : WeightsProvider
{
///
/// Retargeter config JSON.
///
[SerializeField]
[Tooltip(RetargeterComponentTooltips.RetargeterConfig)]
protected TextAsset _retargeterConfig;
///
public TextAsset RetargeterConfig
{
get => _retargeterConfig;
set => _retargeterConfig = value;
}
///
/// Override config filename loaded from application's persistent path.
///
[SerializeField]
[Tooltip(RetargeterComponentTooltips.RetargeterConfigOverride)]
protected string _retargeterConfigOverride;
///
public string RetargeterConfigOverride
{
get => _retargeterConfigOverride;
set => _retargeterConfigOverride = value;
}
///
/// The source weights provide to provide to the input mapper.
///
[SerializeField]
[Tooltip(RetargeterComponentTooltips.WeightsProvider)]
protected WeightsProvider _weightsProvider;
///
public WeightsProvider WeightsProvider
{
get => _weightsProvider;
set => _weightsProvider = value;
}
private Mapper _inputMapper;
private Retargeter _retargeter;
private float[] _input;
private float[] _output;
private string[] _inputSignals;
private string[] _outputSignals;
//
public override bool IsValid => _retargeter != null;
private void Awake()
{
EnsureInitialized();
}
//
public override string[] GetWeightNames()
{
EnsureInitialized();
return _outputSignals ??= _retargeter.OutputSignals.ToArray();
}
//
public override float[] GetWeights()
{
EnsureInitialized();
_inputMapper.Map(_weightsProvider.GetWeights(), _input);
_retargeter.Eval(_input, _output);
return _output;
}
///
/// Retrieves input signals.
///
/// Input signal names.
public IReadOnlyList GetInputNames()
{
EnsureInitialized();
return _inputSignals ??= _retargeter.InputSignals.ToArray();
}
private void EnsureInitialized()
{
if (_retargeter != null) return;
// Setup weights provider
Debug.Assert(_weightsProvider != null);
var retargeterConfigContent = "";
// Load config from override preferentially
var overridePath = Path.Join(Application.persistentDataPath, _retargeterConfigOverride);
if (File.Exists(overridePath) && _retargeterConfigOverride.Length > 0)
{
Debug.Log($"Loading retargeter config from override: {overridePath}");
retargeterConfigContent = File.ReadAllText(overridePath);
}
// Otherwise use the text asset hardwired in the app (which can be null if we decide we want to rely on the
// override asset only to guarantee a valid config, like in UXR studies)
else if (_retargeterConfig != null)
{
Debug.Log($"Loading retargeter config from text asset: {_retargeterConfig.name}");
retargeterConfigContent = _retargeterConfig.text;
}
if (retargeterConfigContent.Length == 0)
{
Debug.LogError($"A valid retargeter configuration not found! (config={_retargeterConfig.name}, override={_retargeterConfigOverride})");
return;
}
// And instantiate the retargeter
_retargeter = new Retargeter(retargeterConfigContent);
_inputMapper = new Mapper(_weightsProvider.GetWeightNames().ToList(), _retargeter.InputSignals, (
missedInputs =>
{
Debug.LogWarning($"RetargeterComponent {name}: Input signals {string.Join(", ", missedInputs)} are not set up in the retargeter config, and will not be used!");
}),
missedDrivers =>
{
Debug.LogWarning($"RetargeterComponent {name}: Output signals {string.Join(", ", missedDrivers)} are set up in the retargeter config but have no driving signals, and will not be used!");
}
);
_input = Enumerable.Repeat(0.0f, _retargeter.InputSignals.Length).ToArray();
_output = Enumerable.Repeat(0.0f, _retargeter.OutputSignals.Length).ToArray();
}
}
}