using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using LibMMD.Model; using LibMMD.Motion; using UnityEngine; namespace LibMMD.Unity3D.Physics { /// Physics construction and runtime behavior for an MMD model. public interface IMmdPhysicsBackend : IDisposable { IEnumerator Build(MmdPhysicsBuildContext context); bool CalculateMorphBeforePhysics { get; } bool[] TransformSkipMask { get; } void Reset(); void Step(float deltaTime, float physicsFps, int maxSubSteps); } /// Data exposed to an optional physics backend while a model is being built. public sealed class MmdPhysicsBuildContext { private readonly long _budgetTicks; private long _sliceStarted; private bool _resumingAfterYield; public Transform Root { get; } public Transform BoneRoot { get; } public bool EnableLogging { get; } public MmdModel Model { get; } public Poser Poser { get; } public GameObject[] Bones { get; } public SkinnedMeshRenderer MainRenderer { get; } public SkinnedMeshRenderer[] PartRenderers { get; } public IReadOnlyList> PartIndexes { get; } public string[] SkirtSleeveBoneNames { get; } public string[] NonSkirtSleeveBoneNames { get; } public string[] SkirtSleeveMaterialNames { get; } public string[] NonSkirtSleeveMaterialNames { get; } public float SkirtSleeveMaterialDominanceThreshold { get; } internal MmdGameObject Owner { get; } internal MmdPhysicsBuildContext(MmdGameObject owner, MmdModel model, Poser poser, GameObject boneRoot, GameObject[] bones, SkinnedMeshRenderer mainRenderer, SkinnedMeshRenderer[] partRenderers, List> partIndexes) { Owner = owner; Root = owner.transform; BoneRoot = boneRoot.transform; EnableLogging = owner.EnableLogging; Model = model; Poser = poser; Bones = bones; MainRenderer = mainRenderer; PartRenderers = partRenderers; PartIndexes = partIndexes; var cloth = owner.OptionsSnapshot.Cloth; SkirtSleeveBoneNames = cloth.IncludedBoneNames; NonSkirtSleeveBoneNames = cloth.ExcludedBoneNames; SkirtSleeveMaterialNames = cloth.IncludedMaterialNames; NonSkirtSleeveMaterialNames = cloth.ExcludedMaterialNames; SkirtSleeveMaterialDominanceThreshold = cloth.MaterialDominanceThreshold; _budgetTicks = (long)(owner.OptionsSnapshot.LoadingFrameBudgetMilliseconds * Stopwatch.Frequency / 1000d); _sliceStarted = Stopwatch.GetTimestamp(); } /// Returns true when construction should continue on the next frame. public bool ShouldYield() { var now = Stopwatch.GetTimestamp(); if (_resumingAfterYield) { _resumingAfterYield = false; _sliceStarted = now; return false; } if (_budgetTicks <= 0) { _resumingAfterYield = true; return true; } if (now - _sliceStarted < _budgetTicks) return false; _resumingAfterYield = true; return true; } } /// Registration point used by optional integration assemblies. public static class MmdPhysicsBackendRegistry { private static readonly Dictionary> Factories = new Dictionary>(); private static Func _offlineBulletFactory; public static void Register(MmdPhysicsMode mode, Func factory) { if (factory == null) throw new ArgumentNullException(nameof(factory)); Factories[mode] = factory; } public static void Unregister(MmdPhysicsMode mode) { Factories.Remove(mode); } public static void RegisterOfflineBulletFactory(Func factory) { _offlineBulletFactory = factory ?? throw new ArgumentNullException(nameof(factory)); } public static void UnregisterOfflineBulletFactory() => _offlineBulletFactory = null; internal static PyhsicsReactor CreateOfflineBulletReactor() { if (_offlineBulletFactory == null) throw new InvalidOperationException("The LibMmd Bullet integration is not installed or registered."); return _offlineBulletFactory(); } internal static bool TryCreate(MmdPhysicsMode mode, out IMmdPhysicsBackend backend) { if (Factories.TryGetValue(mode, out var factory)) { backend = factory(); return backend != null; } switch (mode) { case MmdPhysicsMode.None: backend = new NoneMmdPhysicsBackend(); return true; case MmdPhysicsMode.Unity: backend = new UnityMmdPhysicsBackend(); return true; default: backend = null; return false; } } } }