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