using System;
using System.Collections.Generic;
using LibMMD.Model;
using UnityEngine;
namespace LibMMD.Unity3D
{
/// Owns renderer discovery, visibility, bounds, shadows, and submesh visibility.
internal sealed class MmdRendererController : IDisposable
{
private readonly MmdSubMeshVisibilityController _subMeshes =
new MmdSubMeshVisibilityController();
private SkinnedMeshRenderer[] _active = Array.Empty();
internal bool RenderersVisible { get; private set; } = true;
internal int SubMeshCount => _subMeshes.SubMeshCount;
internal bool HasVisibleSubMesh => _subMeshes.HasVisibleSubMesh();
internal string[] GetSubMeshNames() => _subMeshes.GetSubMeshNames();
internal bool IsSubMeshVisible(int index) => _subMeshes.IsSubMeshVisible(index);
internal void SetSubMeshVisible(int index, bool visible) =>
_subMeshes.SetSubMeshVisible(index, visible);
internal int SetSubMeshVisible(string name, bool visible) =>
_subMeshes.SetSubMeshVisible(name, visible);
internal void Configure(MmdModel model, UnityEngine.Material[] materials,
SkinnedMeshRenderer[] renderers, bool usesMainMesh)
{
_active = renderers ?? Array.Empty();
_subMeshes.Configure(model, materials, _active, usesMainMesh, RenderersVisible);
_subMeshes.Reset();
}
internal void SetActiveRenderers(SkinnedMeshRenderer[] renderers) =>
_active = renderers ?? Array.Empty();
internal SkinnedMeshRenderer[] GetRenderers(bool visibleOnly = false)
{
var result = new List(_active.Length);
foreach (var renderer in _active)
{
if (renderer == null || renderer.sharedMesh == null) continue;
if (visibleOnly && (!renderer.enabled || renderer.forceRenderingOff ||
!renderer.gameObject.activeInHierarchy)) continue;
result.Add(renderer);
}
return result.ToArray();
}
internal bool TryGetVisibleWorldBounds(out Bounds bounds)
{
bounds = default;
var found = false;
foreach (var renderer in GetRenderers(true))
{
var rendererBounds = renderer.bounds;
if (IsFinite(rendererBounds) && rendererBounds.size.sqrMagnitude > 0.000001f)
{
if (found) bounds.Encapsulate(rendererBounds);
else { bounds = rendererBounds; found = true; }
}
foreach (var bone in renderer.bones)
{
if (bone == null || !IsFinite(bone.position)) continue;
if (found) bounds.Encapsulate(bone.position);
else { bounds = new Bounds(bone.position, Vector3.zero); found = true; }
}
}
if (found) bounds.Expand(Mathf.Max(0.02f, bounds.size.magnitude * 0.02f));
return found;
}
internal void ApplyShadowConfig(MmdUnityConfig config, SkinnedMeshRenderer[] fallback)
{
var cast = config.EnableCastShadow != MmdConfigSwitch.ForceFalse;
var receive = config.EnableDrawSelfShadow != MmdConfigSwitch.ForceFalse;
var renderers = _active.Length > 0 ? _active : fallback ?? Array.Empty();
foreach (var renderer in renderers)
{
if (renderer == null || renderer.sharedMesh == null) continue;
renderer.shadowCastingMode = cast
? UnityEngine.Rendering.ShadowCastingMode.On
: UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = receive;
}
}
internal void SetVisible(bool visible, SkinnedMeshRenderer[] fallback)
{
RenderersVisible = visible;
var renderers = _active.Length > 0 ? _active : fallback ?? Array.Empty();
foreach (var renderer in renderers)
{
if (renderer == null) continue;
renderer.enabled = true;
renderer.forceRenderingOff = !visible;
}
_subMeshes.SetRenderersVisible(visible);
}
internal void ApplyInitialVisibility(SkinnedMeshRenderer renderer)
{
if (renderer != null) renderer.forceRenderingOff = !RenderersVisible;
}
private static bool IsFinite(Bounds value) => IsFinite(value.center) && IsFinite(value.size);
private static bool IsFinite(Vector3 value) =>
float.IsFinite(value.x) && float.IsFinite(value.y) && float.IsFinite(value.z);
public void Dispose()
{
_subMeshes.Dispose();
_active = Array.Empty();
}
}
}