366 lines
12 KiB
C#
366 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using Newtonsoft.Json;
|
|
|
|
public class AvatarDataReader : MonoBehaviour
|
|
{
|
|
[Header("Avatar Configuration")]
|
|
[SerializeField] private Transform targetAvatarRoot;
|
|
[SerializeField] private string fileName = "avatar_sync_data.json";
|
|
[SerializeField] private bool readEveryFrame = true;
|
|
[SerializeField] private float updateRate = 60f; // Updates per second
|
|
[SerializeField] private bool smoothTransitions = true;
|
|
[SerializeField] private float transitionSpeed = 10f;
|
|
|
|
[Header("Synchronization Options")]
|
|
[SerializeField] private bool syncWorldPosition = true; // Apply avatar world position
|
|
[SerializeField] private bool syncWorldRotation = true; // Apply avatar world rotation
|
|
[SerializeField] private bool syncLocalScale = false; // Apply avatar local scale
|
|
|
|
[Header("Debug")]
|
|
[SerializeField] private bool showDebugInfo = false;
|
|
[SerializeField] private bool enableDebugMode = false;
|
|
[SerializeField] private string debugBoneName = "LeftArm"; // Bone to monitor
|
|
|
|
private string filePath;
|
|
private float lastUpdateTime;
|
|
private Dictionary<string, Transform> boneLookup;
|
|
private Dictionary<string, SkinnedMeshRenderer> skinnedMeshLookup;
|
|
private AvatarSyncData lastSyncData;
|
|
private bool isInitialized = false;
|
|
private int successfulReads = 0;
|
|
private int appliedBones = 0;
|
|
private Vector3 lastAppliedWorldPosition;
|
|
|
|
void Start()
|
|
{
|
|
// Set up file path
|
|
string syncFilesPath = Path.Combine(Application.dataPath, "Sync-Files");
|
|
filePath = Path.Combine(syncFilesPath, fileName);
|
|
|
|
// If targetAvatarRoot is not assigned, try to find it automatically
|
|
if (targetAvatarRoot == null)
|
|
{
|
|
targetAvatarRoot = transform;
|
|
}
|
|
|
|
// Cache target avatar components
|
|
CacheTargetAvatarComponents();
|
|
|
|
// Initialize debug tracking
|
|
if (enableDebugMode)
|
|
{
|
|
lastAppliedWorldPosition = targetAvatarRoot.position;
|
|
}
|
|
|
|
Debug.Log($"Avatar Data Reader initialized. Reading from: {filePath}");
|
|
Debug.Log($"World Position Sync: {syncWorldPosition}, World Rotation Sync: {syncWorldRotation}");
|
|
}
|
|
|
|
void CacheTargetAvatarComponents()
|
|
{
|
|
boneLookup = new Dictionary<string, Transform>();
|
|
skinnedMeshLookup = new Dictionary<string, SkinnedMeshRenderer>();
|
|
|
|
// Get all skinned mesh renderers
|
|
SkinnedMeshRenderer[] smrs = targetAvatarRoot.GetComponentsInChildren<SkinnedMeshRenderer>();
|
|
|
|
// Cache bones from SkinnedMeshRenderers
|
|
foreach (SkinnedMeshRenderer smr in smrs)
|
|
{
|
|
// Cache this skinned mesh renderer
|
|
if (!skinnedMeshLookup.ContainsKey(smr.gameObject.name))
|
|
{
|
|
skinnedMeshLookup.Add(smr.gameObject.name, smr);
|
|
}
|
|
|
|
// Cache all bones from this SkinnedMeshRenderer
|
|
if (smr.bones != null)
|
|
{
|
|
foreach (Transform bone in smr.bones)
|
|
{
|
|
if (bone != null && !boneLookup.ContainsKey(bone.name))
|
|
{
|
|
boneLookup.Add(bone.name, bone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
isInitialized = true;
|
|
|
|
if (showDebugInfo)
|
|
{
|
|
Debug.Log($"Cached {boneLookup.Count} bones and {skinnedMeshLookup.Count} skinned mesh renderers");
|
|
}
|
|
|
|
// Debug mode: Print some bone names and check for the debug bone
|
|
if (enableDebugMode)
|
|
{
|
|
Debug.Log("Reader - Found bones:");
|
|
int count = 0;
|
|
foreach (var kvp in boneLookup)
|
|
{
|
|
if (count < 10)
|
|
{
|
|
Debug.Log($" Reader Bone {count}: {kvp.Key}");
|
|
count++;
|
|
}
|
|
if (kvp.Key.Contains(debugBoneName))
|
|
{
|
|
Debug.Log($"Found debug bone in reader: {kvp.Key}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (!isInitialized || !File.Exists(filePath))
|
|
return;
|
|
|
|
if (readEveryFrame)
|
|
{
|
|
ReadAndApplyAvatarData();
|
|
}
|
|
else
|
|
{
|
|
// Rate-limited updates
|
|
if (Time.time - lastUpdateTime >= 1f / updateRate)
|
|
{
|
|
ReadAndApplyAvatarData();
|
|
lastUpdateTime = Time.time;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReadAndApplyAvatarData()
|
|
{
|
|
try
|
|
{
|
|
string json = File.ReadAllText(filePath);
|
|
AvatarSyncData syncData = JsonConvert.DeserializeObject<AvatarSyncData>(json);
|
|
|
|
if (syncData == null)
|
|
{
|
|
if (showDebugInfo)
|
|
Debug.LogWarning("Failed to deserialize avatar sync data");
|
|
return;
|
|
}
|
|
|
|
successfulReads++;
|
|
appliedBones = 0;
|
|
|
|
// Apply root transform data
|
|
ApplyRootTransformData(syncData.rootTransform);
|
|
|
|
// Apply bone data
|
|
ApplyBoneData(syncData.bones);
|
|
|
|
// Apply blendshape data
|
|
ApplyBlendShapeData(syncData.blendShapes);
|
|
|
|
if (enableDebugMode && successfulReads % 60 == 0) // Log every 60 reads (about once per second)
|
|
{
|
|
Debug.Log($"Reader stats: {successfulReads} successful reads, {appliedBones} bones applied in last read");
|
|
}
|
|
|
|
lastSyncData = syncData;
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (showDebugInfo)
|
|
Debug.LogError($"Error reading avatar data: {e.Message}");
|
|
}
|
|
}
|
|
|
|
void ApplyRootTransformData(RootTransformData rootData)
|
|
{
|
|
if (rootData == null)
|
|
return;
|
|
|
|
if (syncWorldPosition && rootData.worldPosition != null)
|
|
{
|
|
Vector3 targetWorldPosition = rootData.worldPosition.ToVector3();
|
|
|
|
// Debug: Log world position changes
|
|
if (enableDebugMode && Vector3.Distance(targetWorldPosition, lastAppliedWorldPosition) > 0.01f)
|
|
{
|
|
Debug.Log($"Applying world position: {targetWorldPosition} (was: {lastAppliedWorldPosition})");
|
|
lastAppliedWorldPosition = targetWorldPosition;
|
|
}
|
|
|
|
if (smoothTransitions)
|
|
{
|
|
targetAvatarRoot.position = Vector3.Lerp(
|
|
targetAvatarRoot.position,
|
|
targetWorldPosition,
|
|
transitionSpeed * Time.deltaTime
|
|
);
|
|
}
|
|
else
|
|
{
|
|
targetAvatarRoot.position = targetWorldPosition;
|
|
}
|
|
}
|
|
|
|
if (syncWorldRotation && rootData.worldRotation != null)
|
|
{
|
|
Quaternion targetWorldRotation = rootData.worldRotation.ToQuaternion();
|
|
|
|
if (smoothTransitions)
|
|
{
|
|
targetAvatarRoot.rotation = Quaternion.Lerp(
|
|
targetAvatarRoot.rotation,
|
|
targetWorldRotation,
|
|
transitionSpeed * Time.deltaTime
|
|
);
|
|
}
|
|
else
|
|
{
|
|
targetAvatarRoot.rotation = targetWorldRotation;
|
|
}
|
|
}
|
|
|
|
if (syncLocalScale && rootData.localScale != null)
|
|
{
|
|
Vector3 targetLocalScale = rootData.localScale.ToVector3();
|
|
|
|
if (smoothTransitions)
|
|
{
|
|
targetAvatarRoot.localScale = Vector3.Lerp(
|
|
targetAvatarRoot.localScale,
|
|
targetLocalScale,
|
|
transitionSpeed * Time.deltaTime
|
|
);
|
|
}
|
|
else
|
|
{
|
|
targetAvatarRoot.localScale = targetLocalScale;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ApplyBoneData(List<BoneData> boneDataList)
|
|
{
|
|
if (boneDataList == null)
|
|
return;
|
|
|
|
foreach (BoneData boneData in boneDataList)
|
|
{
|
|
if (boneLookup.TryGetValue(boneData.boneName, out Transform targetBone))
|
|
{
|
|
// Convert serializable types back to Unity types
|
|
Vector3 targetPosition = boneData.position.ToVector3();
|
|
Quaternion targetRotation = boneData.rotation.ToQuaternion();
|
|
Vector3 targetScale = boneData.scale.ToVector3();
|
|
|
|
// Debug mode: Log changes to the monitored bone
|
|
if (enableDebugMode && boneData.boneName.Contains(debugBoneName))
|
|
{
|
|
Debug.Log($"Applying to bone {boneData.boneName}: Pos={targetPosition}, Rot={targetRotation}");
|
|
}
|
|
|
|
if (smoothTransitions)
|
|
{
|
|
// Smooth interpolation
|
|
targetBone.localPosition = Vector3.Lerp(
|
|
targetBone.localPosition,
|
|
targetPosition,
|
|
transitionSpeed * Time.deltaTime
|
|
);
|
|
|
|
targetBone.localRotation = Quaternion.Lerp(
|
|
targetBone.localRotation,
|
|
targetRotation,
|
|
transitionSpeed * Time.deltaTime
|
|
);
|
|
|
|
targetBone.localScale = Vector3.Lerp(
|
|
targetBone.localScale,
|
|
targetScale,
|
|
transitionSpeed * Time.deltaTime
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Direct application
|
|
targetBone.localPosition = targetPosition;
|
|
targetBone.localRotation = targetRotation;
|
|
targetBone.localScale = targetScale;
|
|
}
|
|
|
|
appliedBones++;
|
|
}
|
|
else if (showDebugInfo)
|
|
{
|
|
Debug.LogWarning($"Bone not found: {boneData.boneName}");
|
|
}
|
|
}
|
|
}
|
|
|
|
void ApplyBlendShapeData(List<BlendShapeData> blendShapeDataList)
|
|
{
|
|
if (blendShapeDataList == null)
|
|
return;
|
|
|
|
foreach (BlendShapeData blendShapeData in blendShapeDataList)
|
|
{
|
|
if (skinnedMeshLookup.TryGetValue(blendShapeData.meshName, out SkinnedMeshRenderer targetSmr))
|
|
{
|
|
if (targetSmr.sharedMesh != null && blendShapeData.weights != null)
|
|
{
|
|
int maxBlendShapes = Mathf.Min(targetSmr.sharedMesh.blendShapeCount, blendShapeData.weights.Length);
|
|
|
|
for (int i = 0; i < maxBlendShapes; i++)
|
|
{
|
|
if (smoothTransitions)
|
|
{
|
|
float currentWeight = targetSmr.GetBlendShapeWeight(i);
|
|
float targetWeight = blendShapeData.weights[i];
|
|
float newWeight = Mathf.Lerp(currentWeight, targetWeight, transitionSpeed * Time.deltaTime);
|
|
targetSmr.SetBlendShapeWeight(i, newWeight);
|
|
}
|
|
else
|
|
{
|
|
targetSmr.SetBlendShapeWeight(i, blendShapeData.weights[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (showDebugInfo)
|
|
{
|
|
Debug.LogWarning($"SkinnedMeshRenderer not found: {blendShapeData.meshName}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetTargetAvatar(Transform newTargetRoot)
|
|
{
|
|
targetAvatarRoot = newTargetRoot;
|
|
if (isInitialized)
|
|
{
|
|
CacheTargetAvatarComponents();
|
|
}
|
|
}
|
|
|
|
public void SetFileName(string newFileName)
|
|
{
|
|
fileName = newFileName;
|
|
string syncFilesPath = Path.Combine(Application.dataPath, "Sync-Files");
|
|
filePath = Path.Combine(syncFilesPath, fileName);
|
|
}
|
|
|
|
public bool IsDataAvailable()
|
|
{
|
|
return File.Exists(filePath);
|
|
}
|
|
|
|
public float GetLastSyncTimestamp()
|
|
{
|
|
return lastSyncData?.timestamp ?? 0f;
|
|
}
|
|
} |