331 lines
10 KiB
C#
331 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using Newtonsoft.Json;
|
|
|
|
[System.Serializable]
|
|
public class SerializableVector3
|
|
{
|
|
public float x, y, z;
|
|
|
|
public SerializableVector3() { }
|
|
|
|
public SerializableVector3(Vector3 vector)
|
|
{
|
|
x = vector.x;
|
|
y = vector.y;
|
|
z = vector.z;
|
|
}
|
|
|
|
public Vector3 ToVector3()
|
|
{
|
|
return new Vector3(x, y, z);
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class SerializableQuaternion
|
|
{
|
|
public float x, y, z, w;
|
|
|
|
public SerializableQuaternion() { }
|
|
|
|
public SerializableQuaternion(Quaternion quaternion)
|
|
{
|
|
x = quaternion.x;
|
|
y = quaternion.y;
|
|
z = quaternion.z;
|
|
w = quaternion.w;
|
|
}
|
|
|
|
public Quaternion ToQuaternion()
|
|
{
|
|
return new Quaternion(x, y, z, w);
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class RootTransformData
|
|
{
|
|
public SerializableVector3 worldPosition;
|
|
public SerializableQuaternion worldRotation;
|
|
public SerializableVector3 localScale;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class BoneData
|
|
{
|
|
public string boneName; // e.g. "LeftArm", "RightHand"
|
|
public SerializableVector3 position;
|
|
public SerializableQuaternion rotation;
|
|
public SerializableVector3 scale;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class BlendShapeData
|
|
{
|
|
public string meshName;
|
|
public float[] weights;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class AvatarSyncData
|
|
{
|
|
public RootTransformData rootTransform;
|
|
public List<BoneData> bones;
|
|
public List<BlendShapeData> blendShapes;
|
|
public float timestamp;
|
|
}
|
|
|
|
public class AvatarDataWriter : MonoBehaviour
|
|
{
|
|
[Header("Avatar Configuration")]
|
|
[SerializeField] private Transform avatarRoot;
|
|
[SerializeField] private string fileName = "avatar_sync_data.json";
|
|
[SerializeField] private bool writeEveryFrame = true;
|
|
[SerializeField] private float updateRate = 60f; // Updates per second
|
|
|
|
[Header("Synchronization Options")]
|
|
[SerializeField] private bool syncWorldPosition = true; // Sync avatar world position
|
|
[SerializeField] private bool syncWorldRotation = true; // Sync avatar world rotation
|
|
[SerializeField] private bool syncLocalScale = false; // Sync avatar local scale
|
|
|
|
[Header("Debug")]
|
|
[SerializeField] private bool enableDebugMode = false;
|
|
[SerializeField] private string debugBoneName = "LeftArm"; // Bone to monitor
|
|
|
|
private string filePath;
|
|
private float lastUpdateTime;
|
|
private List<Transform> allBones;
|
|
private List<SkinnedMeshRenderer> skinnedMeshRenderers;
|
|
private Vector3 lastDebugPosition;
|
|
private Quaternion lastDebugRotation;
|
|
private Vector3 lastRootWorldPosition;
|
|
|
|
void Start()
|
|
{
|
|
// Create Sync-Files directory if it doesn't exist
|
|
string syncFilesPath = Path.Combine(Application.dataPath, "Sync-Files");
|
|
if (!Directory.Exists(syncFilesPath))
|
|
{
|
|
Directory.CreateDirectory(syncFilesPath);
|
|
}
|
|
|
|
filePath = Path.Combine(syncFilesPath, fileName);
|
|
|
|
// If avatarRoot is not assigned, try to find it automatically
|
|
if (avatarRoot == null)
|
|
{
|
|
avatarRoot = transform;
|
|
}
|
|
|
|
// Cache all bones and skinned mesh renderers
|
|
CacheAvatarComponents();
|
|
|
|
// Initialize debug tracking
|
|
if (enableDebugMode)
|
|
{
|
|
Transform debugBone = FindBoneByName(debugBoneName);
|
|
if (debugBone != null)
|
|
{
|
|
lastDebugPosition = debugBone.localPosition;
|
|
lastDebugRotation = debugBone.localRotation;
|
|
Debug.Log($"Debug mode enabled. Monitoring bone: {debugBoneName}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Debug bone '{debugBoneName}' not found!");
|
|
}
|
|
|
|
lastRootWorldPosition = avatarRoot.position;
|
|
}
|
|
|
|
Debug.Log($"Avatar Data Writer initialized. Writing to: {filePath}");
|
|
Debug.Log($"World Position Sync: {syncWorldPosition}, World Rotation Sync: {syncWorldRotation}");
|
|
}
|
|
|
|
void CacheAvatarComponents()
|
|
{
|
|
allBones = new List<Transform>();
|
|
skinnedMeshRenderers = new List<SkinnedMeshRenderer>();
|
|
|
|
// Get all skinned mesh renderers
|
|
skinnedMeshRenderers.AddRange(avatarRoot.GetComponentsInChildren<SkinnedMeshRenderer>());
|
|
|
|
// Get all unique bones from all SkinnedMeshRenderers
|
|
HashSet<Transform> uniqueBones = new HashSet<Transform>();
|
|
foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers)
|
|
{
|
|
if (smr.bones != null)
|
|
{
|
|
foreach (Transform bone in smr.bones)
|
|
{
|
|
if (bone != null)
|
|
{
|
|
uniqueBones.Add(bone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
allBones.AddRange(uniqueBones);
|
|
|
|
Debug.Log($"Cached {allBones.Count} bones and {skinnedMeshRenderers.Count} skinned mesh renderers");
|
|
|
|
// Debug: Print some bone names to help with troubleshooting
|
|
if (enableDebugMode)
|
|
{
|
|
Debug.Log("Found bones:");
|
|
for (int i = 0; i < Mathf.Min(10, allBones.Count); i++)
|
|
{
|
|
Debug.Log($" Bone {i}: {allBones[i].name}");
|
|
}
|
|
}
|
|
}
|
|
|
|
Transform FindBoneByName(string name)
|
|
{
|
|
foreach (Transform bone in allBones)
|
|
{
|
|
if (bone.name.Contains(name))
|
|
{
|
|
return bone;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// Debug mode: Check if the monitored bone has changed
|
|
if (enableDebugMode)
|
|
{
|
|
Transform debugBone = FindBoneByName(debugBoneName);
|
|
if (debugBone != null)
|
|
{
|
|
if (Vector3.Distance(debugBone.localPosition, lastDebugPosition) > 0.001f ||
|
|
Quaternion.Angle(debugBone.localRotation, lastDebugRotation) > 0.1f)
|
|
{
|
|
Debug.Log($"Bone {debugBoneName} changed! Pos: {debugBone.localPosition}, Rot: {debugBone.localRotation}");
|
|
lastDebugPosition = debugBone.localPosition;
|
|
lastDebugRotation = debugBone.localRotation;
|
|
}
|
|
}
|
|
|
|
// Debug: Check if avatar root world position has changed
|
|
if (Vector3.Distance(avatarRoot.position, lastRootWorldPosition) > 0.01f)
|
|
{
|
|
Debug.Log($"Avatar root world position changed! From: {lastRootWorldPosition} To: {avatarRoot.position}");
|
|
lastRootWorldPosition = avatarRoot.position;
|
|
}
|
|
}
|
|
|
|
if (writeEveryFrame)
|
|
{
|
|
WriteAvatarData();
|
|
}
|
|
else
|
|
{
|
|
// Rate-limited updates
|
|
if (Time.time - lastUpdateTime >= 1f / updateRate)
|
|
{
|
|
WriteAvatarData();
|
|
lastUpdateTime = Time.time;
|
|
}
|
|
}
|
|
}
|
|
|
|
void WriteAvatarData()
|
|
{
|
|
try
|
|
{
|
|
AvatarSyncData syncData = new AvatarSyncData
|
|
{
|
|
rootTransform = new RootTransformData(),
|
|
bones = new List<BoneData>(),
|
|
blendShapes = new List<BlendShapeData>(),
|
|
timestamp = Time.time
|
|
};
|
|
|
|
// Capture root transform data
|
|
if (syncWorldPosition)
|
|
{
|
|
syncData.rootTransform.worldPosition = new SerializableVector3(avatarRoot.position);
|
|
}
|
|
else
|
|
{
|
|
syncData.rootTransform.worldPosition = new SerializableVector3(Vector3.zero);
|
|
}
|
|
|
|
if (syncWorldRotation)
|
|
{
|
|
syncData.rootTransform.worldRotation = new SerializableQuaternion(avatarRoot.rotation);
|
|
}
|
|
else
|
|
{
|
|
syncData.rootTransform.worldRotation = new SerializableQuaternion(Quaternion.identity);
|
|
}
|
|
|
|
if (syncLocalScale)
|
|
{
|
|
syncData.rootTransform.localScale = new SerializableVector3(avatarRoot.localScale);
|
|
}
|
|
else
|
|
{
|
|
syncData.rootTransform.localScale = new SerializableVector3(Vector3.one);
|
|
}
|
|
|
|
// Capture bone data
|
|
foreach (Transform bone in allBones)
|
|
{
|
|
BoneData boneData = new BoneData
|
|
{
|
|
boneName = bone.name,
|
|
position = new SerializableVector3(bone.localPosition),
|
|
rotation = new SerializableQuaternion(bone.localRotation),
|
|
scale = new SerializableVector3(bone.localScale)
|
|
};
|
|
syncData.bones.Add(boneData);
|
|
}
|
|
|
|
// Capture blendshape data
|
|
foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers)
|
|
{
|
|
if (smr.sharedMesh != null && smr.sharedMesh.blendShapeCount > 0)
|
|
{
|
|
float[] weights = new float[smr.sharedMesh.blendShapeCount];
|
|
for (int i = 0; i < weights.Length; i++)
|
|
{
|
|
weights[i] = smr.GetBlendShapeWeight(i);
|
|
}
|
|
|
|
BlendShapeData blendShapeData = new BlendShapeData
|
|
{
|
|
meshName = smr.gameObject.name, // Use GameObject name instead of path
|
|
weights = weights
|
|
};
|
|
syncData.blendShapes.Add(blendShapeData);
|
|
}
|
|
}
|
|
|
|
// Convert to JSON and write to file
|
|
string json = JsonConvert.SerializeObject(syncData, Formatting.Indented);
|
|
File.WriteAllText(filePath, json);
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"Error writing avatar data: {e.Message}");
|
|
}
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
// Write final data when component is disabled
|
|
if (allBones != null && allBones.Count > 0)
|
|
{
|
|
WriteAvatarData();
|
|
}
|
|
}
|
|
} |