Files

211 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace AIGame.Review
{
/// <summary>Scene-authored, synchronized skinning comparison. No UI/model construction at runtime.</summary>
public sealed class CharacterAnimationLab : MonoBehaviour
{
[Serializable] public sealed class Actor
{
public string label;
public GameObject rig;
public Transform hips;
public Transform rightHand;
public SkinnedMeshRenderer renderer;
// Optional full target-pose walk derived from the common source
// motion. Used where preserving source angles alone crosses feet.
public AnimationClip walkingClip;
// Optional bind translation adaptation. The shared source clip's
// rotation curves and time remain unchanged across characters.
public float translationScale = 1f;
public Vector3[] translationOffsets = Array.Empty<Vector3>();
public Quaternion[] translationDeltaRotations = Array.Empty<Quaternion>();
public int[] rotationParents = Array.Empty<int>();
public Quaternion[] bindRotationCorrections = Array.Empty<Quaternion>();
[NonSerialized] public Quaternion[] sampledWorldRotations;
[NonSerialized] public Quaternion[] mappedWorldRotations;
}
public Actor[] actors;
public AnimationClip[] clips;
public Vector3[] travel;
public Camera reviewCamera;
public Transform ui;
public Material texturedMaterial;
public Material clayMaterial;
public int motion;
public bool playing = true;
public bool inPlace = true;
public bool textured = true;
public float speed = 1f;
public float time;
public int focus = -1;
public int detail;
public float yaw;
TMP_Text stateText, timeText, pauseText, travelText, materialText, detailText;
RectTransform progress;
float progressWidth;
float zoom = 1f;
Vector3 lastMouse;
bool dragging;
SkinWeights previousWeights;
bool changedWeights;
bool previousRunInBackground;
public float Duration => clips != null && clips.Length > 0 ? clips[motion].length : 1;
public int FrameCount => Mathf.RoundToInt(Duration * 30) + 1;
void OnEnable()
{
if (!Application.isPlaying) return;
previousWeights = QualitySettings.skinWeights;
previousRunInBackground = Application.runInBackground;
Application.runInBackground = true;
QualitySettings.skinWeights = SkinWeights.Unlimited;
changedWeights = true;
}
void OnDisable()
{
if (changedWeights) { QualitySettings.skinWeights = previousWeights; Application.runInBackground = previousRunInBackground; }
changedWeights = false;
}
void Awake()
{
if (!ui) return;
var buttons = ui.GetComponentsInChildren<Button>(true).ToDictionary(b => b.name);
void Bind(string n, UnityEngine.Events.UnityAction action) => buttons[n].onClick.AddListener(action);
Bind("Btn_Walk", () => SetMotion(0));
Bind("Btn_Breathe", () => SetMotion(1));
Bind("Btn_Happy", () => SetMotion(2));
Bind("Btn_Play", () => { playing = !playing; RefreshUI(); });
Bind("Btn_Previous", () => Step(-1));
Bind("Btn_Next", () => Step(1));
Bind("Btn_Restart", () => { time = 0; Sample(); });
Bind("Btn_Speed025", () => SetSpeed(.25f));
Bind("Btn_Speed05", () => SetSpeed(.5f));
Bind("Btn_Speed1", () => SetSpeed(1));
Bind("Btn_Speed2", () => SetSpeed(2));
Bind("Btn_Travel", () => { inPlace = !inPlace; Sample(); });
Bind("Btn_Front", () => SetYaw(0));
Bind("Btn_Side", () => SetYaw(90));
Bind("Btn_Back", () => SetYaw(180));
Bind("Btn_Material", () => { textured = !textured; Sample(); });
Bind("Btn_All", () => SetFocus(-1));
Bind("Btn_Focus1", () => SetFocus(0));
Bind("Btn_Focus2", () => SetFocus(1));
Bind("Btn_Focus3", () => SetFocus(2));
if (buttons.TryGetValue("Btn_Focus4", out var focus4)) focus4.onClick.AddListener(() => SetFocus(3));
Bind("Btn_Detail", () => { detail = (detail + 1) % 3; if (detail > 0 && focus < 0) focus = 1; zoom = 1; Sample(); });
TMP_Text Text(string n) => ui.GetComponentsInChildren<TMP_Text>(true).First(t => t.name == n);
stateText = Text("Txt_State"); timeText = Text("Txt_Time");
pauseText = Text("Txt_Play"); travelText = Text("Txt_Travel");
materialText = Text("Txt_Material"); detailText = Text("Txt_Detail");
progress = ui.GetComponentsInChildren<RectTransform>(true).First(t => t.name == "Img_Progress");
progressWidth = ((RectTransform)progress.parent).rect.width;
var seek = ui.GetComponentInChildren<AnimationLabSeekBar>(true);
if (seek) seek.viewer = this;
Sample();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) playing = !playing;
if (Input.GetKeyDown(KeyCode.Alpha1)) SetMotion(0);
if (Input.GetKeyDown(KeyCode.Alpha2)) SetMotion(1);
if (Input.GetKeyDown(KeyCode.Alpha3)) SetMotion(2);
if (Input.GetKeyDown(KeyCode.LeftArrow)) Step(-1);
if (Input.GetKeyDown(KeyCode.RightArrow)) Step(1);
bool overUI = EventSystem.current && EventSystem.current.IsPointerOverGameObject();
if (Input.GetMouseButtonDown(0) && !overUI) { dragging = true; lastMouse = Input.mousePosition; }
if (!Input.GetMouseButton(0)) dragging = false;
if (dragging) { yaw -= (Input.mousePosition.x - lastMouse.x) * .35f; lastMouse = Input.mousePosition; }
if (!overUI && Mathf.Abs(Input.mouseScrollDelta.y) > .01f)
zoom = Mathf.Clamp(zoom * Mathf.Pow(.88f, Input.mouseScrollDelta.y), .35f, 2.5f);
if (playing) time = Mathf.Repeat(time + Time.unscaledDeltaTime * speed, Mathf.Max(.001f, Duration));
Sample();
}
public void SetMotion(int value) { motion = Mathf.Clamp(value, 0, clips.Length - 1); time = 0; Sample(); }
public void SetSpeed(float value) { speed = value; RefreshUI(); }
public void SetYaw(float value) { yaw = value; Sample(); }
public void SetFocus(int value) { focus = value; if (value < 0) detail = 0; zoom = 1; Sample(); }
public void Seek(float value) { playing = false; time = Mathf.Clamp01(value) * Duration; Sample(); }
public void Step(int frames) { playing = false; time = Mathf.Clamp(Mathf.Round(time * 30) + frames, 0, FrameCount - 1) / 30f; Sample(); }
public void Sample()
{
if (actors == null || clips == null || clips.Length == 0) return;
time = Mathf.Clamp(time, 0, Duration);
float phase = Duration > 0 ? time / Duration : 0;
foreach (var actor in actors)
{
actor.renderer.enabled = focus < 0 || actor == actors[focus];
bool mappedWalk = motion == 0 && actor.walkingClip;
(mappedWalk ? actor.walkingClip : clips[motion]).SampleAnimation(actor.rig, time);
bool adapt = !mappedWalk && actor.translationOffsets != null && actor.translationOffsets.Length == actor.renderer.bones.Length;
if (adapt)
{
var bones = actor.renderer.bones;
for (int i = 0; i < bones.Length; i++)
{
var delta = bones[i].localPosition * actor.translationScale;
if (actor.translationDeltaRotations != null && actor.translationDeltaRotations.Length == bones.Length)
delta = actor.translationDeltaRotations[i] * delta;
bones[i].localPosition = delta + actor.translationOffsets[i];
}
if (actor.bindRotationCorrections != null && actor.bindRotationCorrections.Length == bones.Length && actor.rotationParents.Length == bones.Length)
{
if (actor.sampledWorldRotations == null || actor.sampledWorldRotations.Length != bones.Length)
{
actor.sampledWorldRotations = new Quaternion[bones.Length];
actor.mappedWorldRotations = new Quaternion[bones.Length];
}
for (int i = 0; i < bones.Length; i++)
{
int parent = actor.rotationParents[i];
actor.sampledWorldRotations[i] = parent < 0 ? bones[i].localRotation : actor.sampledWorldRotations[parent] * bones[i].localRotation;
actor.mappedWorldRotations[i] = actor.sampledWorldRotations[i] * actor.bindRotationCorrections[i];
}
for (int i = 0; i < bones.Length; i++)
{
int parent = actor.rotationParents[i];
bones[i].localRotation = parent < 0 ? actor.mappedWorldRotations[i] : Quaternion.Inverse(actor.mappedWorldRotations[parent]) * actor.mappedWorldRotations[i];
}
}
}
if (inPlace) actor.hips.localPosition -= travel[motion] * phase * (adapt || mappedWalk ? actor.translationScale : 1f);
actor.rig.transform.localRotation = Quaternion.Euler(0, yaw, 0);
actor.renderer.sharedMaterial = textured ? texturedMaterial : clayMaterial;
}
UpdateCamera(); RefreshUI();
}
void UpdateCamera()
{
if (!reviewCamera) return;
float x = focus >= 0 ? actors[focus].rig.transform.parent.position.x : 0;
float y = detail == 1 ? 1.18f : detail == 2 ? .78f : .82f;
Vector3 target = new Vector3(x, y, 0);
if (detail == 2 && focus >= 0 && actors[focus].rightHand) target = actors[focus].rightHand.position;
reviewCamera.transform.position = target + new Vector3(0, .12f, -6);
reviewCamera.transform.LookAt(target);
reviewCamera.orthographicSize = (focus < 0 ? Mathf.Max(1.15f, (actors.Length >= 4 ? 1.8f : 1.65f) / Mathf.Max(.1f, reviewCamera.aspect)) : detail == 1 ? .62f : detail == 2 ? .37f : 1.08f) * zoom;
}
void RefreshUI()
{
if (!stateText) return;
string[] motions = { "行走", "呼吸待机", "轻松待机" };
stateText.text = motions[motion] + " · " + (focus < 0 ? (actors.Length == 4 ? "四版同步对比" : "三版同步对比") : actors[focus].label);
timeText.text = string.Format("{0:0.00} / {1:0.00} 秒 第 {2} / {3} 帧 {4:0.##}×", time, Duration, Mathf.RoundToInt(time * 30) + 1, FrameCount, speed);
pauseText.text = playing ? "暂停" : "播放";
travelText.text = inPlace ? "原地循环" : "保留位移";
materialText.text = textured ? "切灰模" : "切贴图";
detailText.text = new[] { "全身", "上身", "手部" }[detail];
if (progress)
{
if (progressWidth <= 0) progressWidth = ((RectTransform)progress.parent).rect.width;
progress.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Max(2, progressWidth * (time / Mathf.Max(.001f, Duration))));
}
}
}
}