// ───────────────────────────────────────────────────────────────────────────── // WLWeaponGlowFx.cs — 무기 흰 빛 덧그림 도우미 (#916 · 2026-10-17) // // 해머 각성(WLHammerQuake #912)과 같은 방식: 소켓 아래 무기 메시마다 같은 메시를 쓰는 자식을 만들어 WL/WeaponGlow 재질(가산 · 테두리)을 씌우고, // 세기 · 색 · 몸통 덮기 · 테두리를 렌더러별 프로퍼티 블록으로 쓴다(재질 인스턴스 0). End 에서 자식을 지운다. // 🔴 Debug.LogError 금지(원본 팝업). // ───────────────────────────────────────────────────────────────────────────── using System.Collections.Generic; using UnityEngine; namespace WL.Combat.Weapon { public sealed class WLWeaponGlowFx { static readonly Dictionary s_mats = new Dictionary(); static readonly int kGlow = Shader.PropertyToID("_Glow"), kColor = Shader.PropertyToID("_GlowColor"), kFill = Shader.PropertyToID("_Fill"), kRim = Shader.PropertyToID("_RimPower"), kColor2 = Shader.PropertyToID("_GlowColor2"), kAxis = Shader.PropertyToID("_GradAxis"), kLen = Shader.PropertyToID("_GradLen"); // #958 그라데이션 readonly List _gos = new List(); readonly List _rs = new List(); readonly List _axis = new List(); // #958 렌더러별 그라데이션 축(오브젝트 공간 · w = 손잡이 끝 투영) readonly List _len = new List(); // 1 / (칼끝 − 손잡이 투영) MaterialPropertyBlock _mpb; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Reset() { s_mats.Clear(); } public bool Active { get { return _rs.Count > 0; } } /// socket 아래 켜진 MeshRenderer 마다 덧그림 자식을 만든다 — 만든 수. public int Begin(Transform socket, string materialPath) { End(); if (socket == null || string.IsNullOrEmpty(materialPath)) return 0; Material mat; if (!s_mats.TryGetValue(materialPath, out mat)) { mat = Resources.Load(materialPath); s_mats[materialPath] = mat; } if (mat == null) return 0; foreach (var mf in socket.GetComponentsInChildren(false)) { var mr = mf.GetComponent(); if (mr == null || !mr.enabled || mf.sharedMesh == null || mf.name == "[WL] Glow") continue; var go = new GameObject("[WL] Glow") { layer = mf.gameObject.layer }; go.transform.SetParent(mf.transform, false); go.AddComponent().sharedMesh = mf.sharedMesh; var r = go.AddComponent(); var mats = new Material[Mathf.Max(1, mf.sharedMesh.subMeshCount)]; for (int i = 0; i < mats.Length; i++) mats[i] = mat; r.sharedMaterials = mats; r.enabled = false; r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; r.receiveShadows = false; r.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; r.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off; _gos.Add(go); _rs.Add(r); // #958 그라데이션 축 = 메시 경계의 가장 긴 축 · 방향 = 소켓(손잡이) 쪽 끝 → 먼 끝(칼끝) var b = mf.sharedMesh.bounds; var s = b.size; Vector3 ax = s.x >= s.y && s.x >= s.z ? Vector3.right : (s.y >= s.z ? Vector3.up : Vector3.forward); float lo = Vector3.Dot(b.min, ax), hi = Vector3.Dot(b.max, ax); float sock = Vector3.Dot(mf.transform.InverseTransformPoint(socket.position), ax); if (Mathf.Abs(sock - hi) < Mathf.Abs(sock - lo)) { ax = -ax; float t = lo; lo = -hi; hi = -t; } _axis.Add(new Vector4(ax.x, ax.y, ax.z, lo)); _len.Add(hi - lo > 1e-4f ? 1f / (hi - lo) : 0f); } return _rs.Count; } /// #958 손잡이 color → 칼끝 color2 그라데이션(WL/WeaponGlow 의 _GlowColor2 · _GradAxis · _GradLen). public void Set(float glow, Color color, Color color2, float fill, float rimPower) { if (_rs.Count == 0) return; if (_mpb == null) _mpb = new MaterialPropertyBlock(); for (int i = 0; i < _rs.Count; i++) { var r = _rs[i]; if (r == null) continue; r.enabled = glow > 0.001f; _mpb.Clear(); _mpb.SetFloat(kGlow, glow); _mpb.SetColor(kColor, color); _mpb.SetFloat(kFill, fill); _mpb.SetFloat(kRim, rimPower); _mpb.SetColor(kColor2, color2); _mpb.SetVector(kAxis, i < _axis.Count ? _axis[i] : Vector4.zero); _mpb.SetFloat(kLen, i < _len.Count ? _len[i] : 0f); r.SetPropertyBlock(_mpb); } } public void Set(float glow, Color color, float fill, float rimPower) { if (_rs.Count == 0) return; if (_mpb == null) _mpb = new MaterialPropertyBlock(); for (int i = 0; i < _rs.Count; i++) { var r = _rs[i]; if (r == null) continue; r.enabled = glow > 0.001f; _mpb.Clear(); _mpb.SetFloat(kGlow, glow); _mpb.SetColor(kColor, color); _mpb.SetFloat(kFill, fill); _mpb.SetFloat(kRim, rimPower); r.SetPropertyBlock(_mpb); } } public void End() { for (int i = 0; i < _gos.Count; i++) if (_gos[i] != null) Object.Destroy(_gos[i]); _gos.Clear(); _rs.Clear(); _axis.Clear(); _len.Clear(); } /// socket 아래 가장 큰 메시의 경계 중심(socket 로컬) — 무기 가운데. 없으면 원점. public static Vector3 CenterLocal(Transform socket) { if (socket == null) return Vector3.zero; Vector3 best = Vector3.zero; float bestSize = -1f; foreach (var mf in socket.GetComponentsInChildren(false)) { var mr = mf.GetComponent(); var mesh = mf.sharedMesh; if (mr == null || !mr.enabled || mesh == null || mf.name == "[WL] Glow") continue; float size = mesh.bounds.size.sqrMagnitude; if (size > bestSize) { bestSize = size; best = socket.InverseTransformPoint(mf.transform.TransformPoint(mesh.bounds.center)); } } return best; } } }