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;
}
}
}
}