Files
XRLib/Assets/Scripts/UVC/GLTF/LODGameObjectInstantiator.cs
2025-12-03 17:48:01 +09:00

228 lines
8.4 KiB
C#

#nullable enable
using GLTFast;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace UVC.GLTF
{
/// <summary>
/// GameObjectInstantiator를 확장하여 MSFT_lod 확장을 LODGroup으로 변환합니다.
/// 제대로 동작 않함. 수정 필요.
/// </summary>
public class LODGameObjectInstantiator : GameObjectInstantiator
{
// LOD 정보를 저장 (부모 노드 인덱스 -> LOD 노드 인덱스 리스트)
private readonly Dictionary<uint, List<uint>> m_LodNodes = new();
// MSFT_screencoverage 값 저장
private readonly Dictionary<uint, float[]> m_ScreenCoverages = new();
public LODGameObjectInstantiator(IGltfReadable gltf, Transform? parent)
: base(gltf, parent)
{
}
/// <summary>
/// 외부에서 파싱된 LOD 정보를 설정합니다.
/// </summary>
public void SetLodInfo(List<MsftLodParser.LodInfo> lodInfos)
{
m_LodNodes.Clear();
m_ScreenCoverages.Clear();
foreach (var info in lodInfos)
{
m_LodNodes[(uint)info.NodeIndex] = info.LodNodeIds.Select(id => (uint)id).ToList();
if (info.ScreenCoverages != null)
{
m_ScreenCoverages[(uint)info.NodeIndex] = info.ScreenCoverages;
}
}
Debug.Log($"LODGameObjectInstantiator: Set {m_LodNodes.Count} LOD nodes");
}
public override void EndScene(uint[] rootNodeIndices)
{
base.EndScene(rootNodeIndices);
// LODGroup 적용
ApplyLodGroups();
}
/// <summary>
/// MSFT_lod 정보를 기반으로 LODGroup을 생성합니다.
/// </summary>
private void ApplyLodGroups()
{
if (m_LodNodes.Count == 0)
{
Debug.Log("LODGameObjectInstantiator: No LOD nodes to apply");
return;
}
// m_Nodes는 protected이므로 직접 접근 가능
if (m_Nodes == null)
{
Debug.LogWarning("LODGameObjectInstantiator: m_Nodes is null");
return;
}
Debug.Log($"LODGameObjectInstantiator: m_Nodes count = {m_Nodes.Count}, LOD definitions = {m_LodNodes.Count}");
foreach (var kvp in m_LodNodes)
{
uint parentNodeIndex = kvp.Key;
List<uint> lodNodeIndices = kvp.Value;
if (!m_Nodes.TryGetValue(parentNodeIndex, out var parentGo))
{
Debug.LogWarning($"LODGameObjectInstantiator: Parent node {parentNodeIndex} not found in m_Nodes");
continue;
}
// LOD 레벨 수집 (LOD0 = parent, LOD1+ = lodNodeIndices)
var allLodObjects = new List<GameObject> { parentGo };
foreach (var lodIndex in lodNodeIndices)
{
if (m_Nodes.TryGetValue(lodIndex, out var lodGo))
{
allLodObjects.Add(lodGo);
}
else
{
Debug.LogWarning($"LODGameObjectInstantiator: LOD node {lodIndex} not found in m_Nodes");
}
}
if (allLodObjects.Count < 2)
{
Debug.LogWarning($"LODGameObjectInstantiator: Not enough LOD objects for node {parentNodeIndex}");
continue;
}
// 먼저 각 LOD 객체의 렌더러를 수집 (부모 이동 전에!)
// 각 LOD는 다른 LOD 객체들을 제외하고 렌더러를 수집
var lodRenderers = new List<Renderer[]>();
for (int i = 0; i < allLodObjects.Count; i++)
{
var lodObject = allLodObjects[i];
// 이 LOD 객체를 제외한 다른 LOD 객체들을 제외 목록에 추가
var excludeSet = new HashSet<GameObject>();
for (int j = 0; j < allLodObjects.Count; j++)
{
if (i != j)
{
excludeSet.Add(allLodObjects[j]);
}
}
var renderers = GetRenderersExcluding(lodObject, excludeSet);
lodRenderers.Add(renderers);
}
// MSFT_screencoverage 값 사용 또는 기본값 계산
float[] screenCoverages;
if (m_ScreenCoverages.TryGetValue(parentNodeIndex, out var coverage) && coverage.Length >= allLodObjects.Count)
{
screenCoverages = coverage;
}
else
{
// 기본 screen coverage 계산 (균등 분배)
screenCoverages = CalculateDefaultScreenCoverages(allLodObjects.Count);
}
// LOD1+ 객체들을 LOD0의 자식으로 이동
for (int i = 1; i < allLodObjects.Count; i++)
{
var lodObject = allLodObjects[i];
if (lodObject.transform.parent != parentGo.transform)
{
// 월드 위치/회전 유지하면서 부모 변경
lodObject.transform.SetParent(parentGo.transform, true);
}
}
// LODGroup 생성 및 설정
var lodGroup = parentGo.AddComponent<LODGroup>();
var lods = new LOD[allLodObjects.Count];
for (int i = 0; i < allLodObjects.Count; i++)
{
float screenRelativeHeight = screenCoverages[i];
lods[i] = new LOD(screenRelativeHeight, lodRenderers[i]);
Debug.Log($" LOD{i}: {lodRenderers[i].Length} renderers, screenHeight={screenRelativeHeight}");
}
lodGroup.SetLODs(lods);
lodGroup.RecalculateBounds();
Debug.Log($"LODGameObjectInstantiator: Applied LODGroup to '{parentGo.name}' with {allLodObjects.Count} LOD levels");
}
}
/// <summary>
/// GameObject와 자식들의 렌더러를 수집합니다.
/// excludeGameObjects에 포함된 GameObject와 그 자식들은 제외합니다.
/// </summary>
private Renderer[] GetRenderersExcluding(GameObject go, HashSet<GameObject> excludeGameObjects)
{
var renderers = new List<Renderer>();
CollectRenderersRecursive(go.transform, renderers, excludeGameObjects);
return renderers.ToArray();
}
/// <summary>
/// 재귀적으로 렌더러를 수집하되, 제외 목록에 있는 GameObject는 건너뜁니다.
/// </summary>
private void CollectRenderersRecursive(Transform current, List<Renderer> renderers, HashSet<GameObject> excludeGameObjects)
{
// 현재 객체가 제외 목록에 있으면 자식도 모두 건너뜀
if (excludeGameObjects.Contains(current.gameObject))
{
return;
}
// 현재 객체의 렌더러 추가
var renderer = current.GetComponent<Renderer>();
if (renderer != null)
{
renderers.Add(renderer);
}
// 자식들 재귀 탐색
foreach (Transform child in current)
{
CollectRenderersRecursive(child, renderers, excludeGameObjects);
}
}
/// <summary>
/// 기본 screen coverage 값을 계산합니다.
/// </summary>
private float[] CalculateDefaultScreenCoverages(int lodCount)
{
// 일반적인 LOD 전환 비율
// LOD0: 0.5 (50% 이상일 때)
// LOD1: 0.25 (25% 이상일 때)
// LOD2: 0.1 (10% 이상일 때)
// LOD3: 0 (0% 이상일 때)
// 등등...
float[] lodScreenPercentages = new float[] { 0.5f, 0.25f, 0.1f, 0f};
var coverages = new float[lodCount];
for (int i = 0; i < lodCount; i++)
{
coverages[i] = lodScreenPercentages[Mathf.Min(i, lodScreenPercentages.Length - 1)];
}
return coverages;
}
}
}