using UnityEngine; namespace GameSystems.Optimization { /// /// 렌더링 최적화를 위한 오브젝트 그룹 데이터 /// - 불변성(Immutability) 보장 /// - 캡슐화된 내부 데이터 /// public sealed class RenderGroup { #region Private Fields private readonly Vector3 _actualCenter; private readonly Renderer[] _renderers; private readonly float _boundingRadius; private bool _isCurrentlyVisible; #endregion #region Public Properties (Read-Only) /// /// 실제 메쉬의 중심점 (피벗이 아닌 bounds 기준) /// public Vector3 ActualCenter => _actualCenter; /// /// 오브젝트의 최대 반지름 (경계 구체) /// public float BoundingRadius => _boundingRadius; /// /// 현재 렌더링 상태 /// public bool IsVisible => _isCurrentlyVisible; /// /// 관리 중인 Renderer 개수 /// public int RendererCount => _renderers?.Length ?? 0; #endregion #region Constructor /// /// RenderGroup 생성자 /// /// 대상 Renderer 배열 (null 체크 수행) public RenderGroup(Renderer[] renderers) { if (renderers == null || renderers.Length == 0) { Debug.LogError("[RenderGroup] Renderer 배열이 null 또는 비어있습니다."); _renderers = System.Array.Empty(); _actualCenter = Vector3.zero; _boundingRadius = 0f; return; } // 방어적 복사 (외부에서 배열 수정 방지) _renderers = new Renderer[renderers.Length]; System.Array.Copy(renderers, _renderers, renderers.Length); // 실제 중심점 계산 _actualCenter = CalculateGroupCenter(_renderers); // 경계 반지름 계산 _boundingRadius = CalculateBoundingRadius(_renderers, _actualCenter); _isCurrentlyVisible = true; // 초기 상태는 표시 } #endregion #region Public Methods /// /// 거리 기반으로 렌더링 상태 업데이트 /// /// 기준 위치 (플레이어 등) /// 최대 렌더링 거리 /// 상태가 변경되었는지 여부 public bool UpdateVisibility(Vector3 referencePosition, float maxDistance) { // 거리 계산 (오브젝트 크기 고려) float distance = Vector3.Distance(referencePosition, _actualCenter); bool shouldBeVisible = (distance - _boundingRadius) <= maxDistance; // 상태 변경이 필요한 경우에만 처리 if (shouldBeVisible == _isCurrentlyVisible) { return false; // 변경 없음 } // Renderer 상태 일괄 변경 SetRenderersState(shouldBeVisible); _isCurrentlyVisible = shouldBeVisible; return true; // 변경됨 } /// /// 강제로 모든 Renderer 표시 /// public void ForceShow() { SetRenderersState(true); _isCurrentlyVisible = true; } /// /// 강제로 모든 Renderer 숨김 /// public void ForceHide() { SetRenderersState(false); _isCurrentlyVisible = false; } /// /// 그룹 유효성 검사 (Renderer가 파괴되었는지 확인) /// public bool IsValid() { if (_renderers == null || _renderers.Length == 0) return false; // 하나라도 유효한 Renderer가 있으면 true foreach (Renderer r in _renderers) { if (r != null) return true; } return false; } #endregion #region Private Methods /// /// 여러 Renderer의 통합 중심점 계산 /// private static Vector3 CalculateGroupCenter(Renderer[] renderers) { if (renderers.Length == 1 && renderers[0] != null) { return renderers[0].bounds.center; } // 모든 bounds를 포함하는 통합 경계 계산 Bounds? combinedBounds = null; foreach (Renderer r in renderers) { if (r == null) continue; if (!combinedBounds.HasValue) { combinedBounds = r.bounds; } else { Bounds temp = combinedBounds.Value; temp.Encapsulate(r.bounds); combinedBounds = temp; } } return combinedBounds?.center ?? Vector3.zero; } /// /// 오브젝트의 최대 반지름 계산 /// private static float CalculateBoundingRadius(Renderer[] renderers, Vector3 center) { float maxRadius = 0f; foreach (Renderer r in renderers) { if (r == null) continue; // 각 Renderer 중심에서의 거리 + extent 크기 float distanceFromCenter = Vector3.Distance(r.bounds.center, center); float extent = r.bounds.extents.magnitude; float totalRadius = distanceFromCenter + extent; if (totalRadius > maxRadius) { maxRadius = totalRadius; } } return maxRadius; } /// /// 모든 Renderer의 enabled 상태 일괄 변경 /// private void SetRenderersState(bool enabled) { for (int i = 0; i < _renderers.Length; i++) { Renderer r = _renderers[i]; // null 체크 + 상태가 다를 때만 변경 (성능 최적화) if (r != null && r.enabled != enabled) { r.enabled = enabled; } } } #endregion #region Cleanup /// /// 리소스 정리 (명시적 호출용) /// public void Dispose() { // Renderer 배열 초기화 (GC 도움) System.Array.Clear(_renderers, 0, _renderers.Length); } #endregion } }