Files
EnglewoodLAB/Assets/Scripts/UVC/GLTF/MsftLodParser.cs

233 lines
7.3 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
namespace UVC.GLTF
{
/// <summary>
/// glTF/glb 파일에서 MSFT_lod 확장 정보를 파싱합니다.
/// GLTFast가 MSFT_lod를 지원하지 않으므로 직접 JSON을 파싱합니다.
/// </summary>
public static class MsftLodParser
{
/// <summary>
/// LOD 정보를 담는 클래스
/// </summary>
public class LodInfo
{
public int NodeIndex { get; set; }
public int[] LodNodeIds { get; set; } = Array.Empty<int>();
public float[]? ScreenCoverages { get; set; }
}
/// <summary>
/// glb 또는 gltf 파일에서 MSFT_lod 정보를 파싱합니다.
/// </summary>
public static List<LodInfo> ParseFromFile(string filePath)
{
var result = new List<LodInfo>();
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
return result;
}
try
{
string json;
string extension = Path.GetExtension(filePath).ToLowerInvariant();
if (extension == ".glb")
{
json = ExtractJsonFromGlb(filePath);
}
else
{
json = File.ReadAllText(filePath);
}
if (string.IsNullOrEmpty(json))
{
return result;
}
return ParseMsftLodFromJson(json);
}
catch (Exception ex)
{
Debug.LogError($"MsftLodParser: Failed to parse {filePath}: {ex.Message}");
return result;
}
}
/// <summary>
/// glb 파일에서 JSON 청크를 추출합니다.
/// </summary>
private static string ExtractJsonFromGlb(string filePath)
{
using var stream = File.OpenRead(filePath);
using var reader = new BinaryReader(stream);
// GLB 헤더: magic(4) + version(4) + length(4) = 12 bytes
uint magic = reader.ReadUInt32();
if (magic != 0x46546C67) // "glTF" in little-endian
{
Debug.LogError("MsftLodParser: Invalid GLB magic number");
return string.Empty;
}
uint version = reader.ReadUInt32();
uint length = reader.ReadUInt32();
// JSON 청크: chunkLength(4) + chunkType(4) + chunkData
uint jsonChunkLength = reader.ReadUInt32();
uint jsonChunkType = reader.ReadUInt32();
if (jsonChunkType != 0x4E4F534A) // "JSON" in little-endian
{
Debug.LogError("MsftLodParser: First chunk is not JSON");
return string.Empty;
}
byte[] jsonBytes = reader.ReadBytes((int)jsonChunkLength);
return Encoding.UTF8.GetString(jsonBytes);
}
/// <summary>
/// JSON 문자열에서 MSFT_lod 정보를 파싱합니다.
/// Unity의 JsonUtility는 중첩 객체를 잘 처리하지 못하므로 수동 파싱합니다.
/// </summary>
private static List<LodInfo> ParseMsftLodFromJson(string json)
{
var result = new List<LodInfo>();
// "nodes" 배열 찾기
int nodesStart = json.IndexOf("\"nodes\"");
if (nodesStart < 0) return result;
int nodesArrayStart = json.IndexOf('[', nodesStart);
if (nodesArrayStart < 0) return result;
// 노드 배열의 끝 찾기 (괄호 매칭)
int nodesArrayEnd = FindMatchingBracket(json, nodesArrayStart, '[', ']');
if (nodesArrayEnd < 0) return result;
string nodesJson = json.Substring(nodesArrayStart, nodesArrayEnd - nodesArrayStart + 1);
// 각 노드를 개별적으로 파싱
int nodeIndex = 0;
int searchPos = 0;
while (true)
{
// 다음 노드 객체 찾기
int nodeStart = nodesJson.IndexOf('{', searchPos);
if (nodeStart < 0) break;
int nodeEnd = FindMatchingBracket(nodesJson, nodeStart, '{', '}');
if (nodeEnd < 0) break;
string nodeJson = nodesJson.Substring(nodeStart, nodeEnd - nodeStart + 1);
// MSFT_lod 확장 확인
int msftLodStart = nodeJson.IndexOf("\"MSFT_lod\"");
if (msftLodStart >= 0)
{
var lodInfo = ParseLodInfoFromNode(nodeJson, nodeIndex);
if (lodInfo != null)
{
result.Add(lodInfo);
}
}
searchPos = nodeEnd + 1;
nodeIndex++;
}
return result;
}
/// <summary>
/// 노드 JSON에서 LOD 정보를 파싱합니다.
/// </summary>
private static LodInfo? ParseLodInfoFromNode(string nodeJson, int nodeIndex)
{
// "ids" 배열 찾기
int idsStart = nodeJson.IndexOf("\"ids\"");
if (idsStart < 0) return null;
int idsArrayStart = nodeJson.IndexOf('[', idsStart);
if (idsArrayStart < 0) return null;
int idsArrayEnd = nodeJson.IndexOf(']', idsArrayStart);
if (idsArrayEnd < 0) return null;
string idsContent = nodeJson.Substring(idsArrayStart + 1, idsArrayEnd - idsArrayStart - 1);
// 쉼표로 구분된 숫자 파싱
var idStrings = idsContent.Split(',');
var ids = new List<int>();
foreach (var idStr in idStrings)
{
string trimmed = idStr.Trim();
if (int.TryParse(trimmed, out int id))
{
ids.Add(id);
}
}
if (ids.Count == 0) return null;
return new LodInfo
{
NodeIndex = nodeIndex,
LodNodeIds = ids.ToArray()
};
}
/// <summary>
/// 매칭되는 괄호의 위치를 찾습니다.
/// </summary>
private static int FindMatchingBracket(string json, int startPos, char openBracket, char closeBracket)
{
int depth = 0;
bool inString = false;
char prevChar = '\0';
for (int i = startPos; i < json.Length; i++)
{
char c = json[i];
// 문자열 내부 처리
if (c == '"' && prevChar != '\\')
{
inString = !inString;
}
else if (!inString)
{
if (c == openBracket)
{
depth++;
}
else if (c == closeBracket)
{
depth--;
if (depth == 0)
{
return i;
}
}
}
prevChar = c;
}
return -1;
}
}
}