233 lines
7.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|