#nullable enable using System; using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using GLTFast; using UnityEngine; using UnityEngine.UIElements; using UVC.Util; namespace SHI.Modal.ISOP { /// /// glTF/glb 3D 모델을 로드하고 UI Toolkit에서 실시간 렌더링하는 뷰 컴포넌트입니다. /// /// 개요: /// /// glTFast 라이브러리를 사용하여 3D 모델을 비동기 로드하고, /// 전용 카메라로 오프스크린 렌더링하여 RenderTexture를 UI Toolkit의 배경으로 출력합니다. /// 마우스로 모델을 회전, 이동, 확대/축소할 수 있습니다. /// /// /// 주요 기능: /// /// glTF/glb 모델 비동기 로드 (GLTFast) /// 오프스크린 렌더링 (전용 카메라 + RenderTexture) /// 마우스 조작: 가운데 버튼=이동, 오른쪽 버튼=회전, 휠=확대/축소 /// 와이어프레임 모드 토글 /// 항목 하이라이트 및 가시성 제어 /// TreeList와 연동되는 계층 구조 생성 /// /// /// 사용 예시: /// /// var modelView = root.Q<ISOPModelView>(); /// var items = await modelView.LoadModelAsync(gltfPath, cancellationToken); /// treeList.SetData(items.ToList()); /// modelView.FocusItemById(itemId); /// /// /// 렌더링 구조: /// /// ISOPModelViewRig - 카메라와 조명을 담는 부모 GameObject /// ISOPModelViewCamera - 전용 렌더링 카메라 (Layer 6만 렌더링) /// ISOPModelViewRoot - 로드된 모델의 루트 GameObject /// /// [UxmlElement] public partial class ISOPModelView : VisualElement, IDisposable { #region IDisposable private bool _disposed = false; #endregion #region 외부 이벤트 (Public Events) /// /// 뷰 내부에서 항목이 선택될 때 발생합니다. /// TreeList와의 선택 동기화에 사용됩니다. /// public Action? OnItemSelected; /// /// 모델 뷰 확장 버튼이 클릭될 때 발생합니다. /// ISOPModal에서 모델 뷰를 전체 화면으로 확장하는 데 사용됩니다. /// public Action? OnExpand; #endregion #region 상수 및 설정 (Constants and Configuration) /// UXML 파일 경로 (Resources 폴더 기준) private const string UXML_PATH = "SHI/Modal/ISOP/ISOPModelView"; /// 카메라 배경색 (흰색) private Color cameraBackgroundColor = new Color(1f, 1f, 1f, 1f); /// 모델이 렌더링되는 레이어 (Layer 6) private int modelLayer = 6; /// 기본 조명 생성 여부 private bool createDefaultLight = true; /// 이동 속도 (가운데 마우스 드래그) private float panSpeed = 1.0f; /// 회전 감도 (픽셀당 회전 각도) private float rotateDegPerPixel = 0.2f; /// 확대/축소 속도 (마우스 휠) private float zoomSpeed = 5f; /// 와이어프레임 모드 활성화 여부 private bool wireframeMode = true; #endregion private Camera? _viewCamera; private RenderTexture? _rt; private Material? _wireframeMat; private bool _wireframeApplied; // Orbit controls state private Vector3 _orbitTarget; private float _orbitDistance = 5f; private float _yaw = 0f; private float _pitch = 20f; // Drag state private bool _mmbDragging; private bool _rmbDragging; private Vector3 _mmbLastPos; private Vector3 _rmbStartPos; private float _yawStart; private float _pitchStart; private Quaternion _modelStartRot; private Vector3 _modelStartPos; private Vector3 _rmbPivot; private readonly Dictionary _idToObject = new Dictionary(); private readonly Dictionary _originalSharedByRenderer = new Dictionary(); private GameObject? _root; private int? _focusedId; // UI Element for rendering private VisualElement? _renderContainer; private Button? _expandBtn; private int itemIdSeed = 1; public ISOPModelView() { // UXML 로드 var visualTree = Resources.Load(UXML_PATH); if (visualTree != null) { visualTree.CloneTree(this); } else { Debug.LogError($"Failed to load UXML at path: {UXML_PATH}"); } // 렌더링 컨테이너 생성 _renderContainer = this.Q("render-container"); _expandBtn = this.Q