Projext/Assets/Scripts/Systems/Optimization/RenderGroup.cs
2026-02-02 17:30:23 +09:00

233 lines
6.9 KiB
C#

using UnityEngine;
namespace GameSystems.Optimization
{
/// <summary>
/// 렌더링 최적화를 위한 오브젝트 그룹 데이터
/// - 불변성(Immutability) 보장
/// - 캡슐화된 내부 데이터
/// </summary>
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)
/// <summary>
/// 실제 메쉬의 중심점 (피벗이 아닌 bounds 기준)
/// </summary>
public Vector3 ActualCenter => _actualCenter;
/// <summary>
/// 오브젝트의 최대 반지름 (경계 구체)
/// </summary>
public float BoundingRadius => _boundingRadius;
/// <summary>
/// 현재 렌더링 상태
/// </summary>
public bool IsVisible => _isCurrentlyVisible;
/// <summary>
/// 관리 중인 Renderer 개수
/// </summary>
public int RendererCount => _renderers?.Length ?? 0;
#endregion
#region Constructor
/// <summary>
/// RenderGroup 생성자
/// </summary>
/// <param name="renderers">대상 Renderer 배열 (null 체크 수행)</param>
public RenderGroup(Renderer[] renderers)
{
if (renderers == null || renderers.Length == 0)
{
Debug.LogError("[RenderGroup] Renderer 배열이 null 또는 비어있습니다.");
_renderers = System.Array.Empty<Renderer>();
_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
/// <summary>
/// 거리 기반으로 렌더링 상태 업데이트
/// </summary>
/// <param name="referencePosition">기준 위치 (플레이어 등)</param>
/// <param name="maxDistance">최대 렌더링 거리</param>
/// <returns>상태가 변경되었는지 여부</returns>
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; // 변경됨
}
/// <summary>
/// 강제로 모든 Renderer 표시
/// </summary>
public void ForceShow()
{
SetRenderersState(true);
_isCurrentlyVisible = true;
}
/// <summary>
/// 강제로 모든 Renderer 숨김
/// </summary>
public void ForceHide()
{
SetRenderersState(false);
_isCurrentlyVisible = false;
}
/// <summary>
/// 그룹 유효성 검사 (Renderer가 파괴되었는지 확인)
/// </summary>
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
/// <summary>
/// 여러 Renderer의 통합 중심점 계산
/// </summary>
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;
}
/// <summary>
/// 오브젝트의 최대 반지름 계산
/// </summary>
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;
}
/// <summary>
/// 모든 Renderer의 enabled 상태 일괄 변경
/// </summary>
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
/// <summary>
/// 리소스 정리 (명시적 호출용)
/// </summary>
public void Dispose()
{
// Renderer 배열 초기화 (GC 도움)
System.Array.Clear(_renderers, 0, _renderers.Length);
}
#endregion
}
}