서버가 미리 렌더링한 360° Animated WebP 자리표시자 + HTML/CSS 골조를 먼저 보냄(빠른 체감 로드). 뒤에서 modelLoader.loadModel(url)이 확장자로 디스패치하여 실제 모델을 비동기 로드/디코드. WebGL 준비 and 모델 로드 완료 둘 다 충족 시 WebP → 캔버스로 opacity 0.5s 페이드.
사용자가 .glb .gltf .obj .fbx .dae .ifc 드롭. URL.createObjectURL(file) → loadModel → 디코드 → 씬 추가 → URL.revokeObjectURL(성공/에러 .finally() 양쪽). 검증: extOf() 확장자.
| 포맷 | 확장자 | 로더 | 비고 | SSR | CSR | 검증(체감 로드) |
|---|---|---|---|---|---|---|
| glTF (Binary/JSON) | .glb .gltf |
GLTFLoader (+DRACO +KTX2 싱글톤) |
Draco 지오메트리 + KTX2/Basis·WebP 텍스처. 기준 경로. | ✔ | ✔ | PASS 229ms |
| Wavefront OBJ | .obj |
OBJLoader (지연 청크 8.8KB) | 지오메트리 전용. blob: URL에선 외부 .mtl/텍스처 미해결 → 기본 머티리얼. |
✔ | ✔ | PASS 173ms |
| Autodesk FBX | .fbx |
FBXLoader (지연 청크 47.8KB) | 바이너리(자체 포함). 애니메이션 가능(.animations) — 현재 정적 표시. |
✔ | ✔ | PASS 158ms |
| COLLADA | .dae |
ColladaLoader (지연 청크 41KB) | XML(DOMParser 필요). 외부 텍스처는 OBJ와 동일 제약. | ✔ | ✔ | PASS 178ms |
| IFC (BIM) | .ifc |
web-ifc IfcAPI(단일스레드 wasm, 지연 청크 3.5MB) |
StreamAllMeshes → BufferGeometry(인터리브 stride-6 pos+normal). 단일스레드라 COOP/COEP 헤더 불필요. geom.delete()+CloseModel로 wasm 메모리 해제. |
✔ | ✔ | PASS 463ms |
| 영역 | 선택 | 비고 |
|---|---|---|
| 렌더러 | three.js WebGLRenderer (r0.169) | WebGPU는 차후 옵션 |
| 로더(기준) | GLTFLoader + DRACOLoader + KTX2Loader | 각 1개 싱글톤 — 다중 인스턴스 크래시(#22445) |
| 로더(확장) | OBJ / FBX / Collada — three example loaders | 지연 import() → 별도 Vite 청크(초기 번들 미증가) |
| 로더(IFC) | web-ifc IfcAPI (v0.0.77) | 단일스레드 web-ifc.wasm 자가호스팅(/web-ifc/), SetWasmPath(...,true) |
| 적응형 품질 | FPS 측정 + pixelRatio 사다리 | 외부 의존성 0 — ui/fps.ts + viewer/adaptiveQuality.ts |
| 지오메트리 압축 | Draco | ~73% 축소 검증됨 |
| 텍스처 압축 | WebP(런타임) / KTX2·Basis(고급) | KTX2는 별도 CLI 단계(toktx) |
| 빌드 | Vite + TypeScript (strict) | three vendor + 포맷별 로더 청크 분리 |
| 디코더 호스팅 | Vite hashed Draco·Basis asset + public/web-ifc | Three.js import로 생성, single-thread IFC WASM만 postinstall 복사 |
| 에셋 파이프라인 | gltf-transform + draco3d | tools/preprocess.mjs (GLB 전용) |
src/
main.ts # 진입점 — ThreeDViewer + Dropzone 와이어링, ?model= Path A
viewer/
loaders.ts # getLoaders() 싱글톤 (GLTF+DRACO+KTX2)
modelLoader.ts # ★ loadModel() 포맷 디스패치 (glb/gltf/obj/fbx/dae/ifc) → Object3D
ThreeDViewer.ts # 핵심 클래스: 렌더러/씬/카메라/OrbitControls/로드/dispose + FPS·적응형 와이어링
adaptiveQuality.ts # ★ pixelRatio 사다리 + 히스테리시스 (60fps↓ 강등)
hydration.ts # createHydrationGate() — 양쪽 마크 시 페이드
dnd/dropzone.ts # HTML5 DnD + 클릭탐색 + extOf 검증 → loadLocalFile
ui/
progress.ts # showProgress/hideProgress/setProgress/setStatus
fps.ts # ★ 화면 FPS 오버레이 (EMA, 색상코딩)
public/
web-ifc/ # ★ web-ifc.wasm (단일스레드, 자동 복사)
samples/ # Box/Duck/Avocado/Duck.optimized + Cube.obj/dae/fbx/ifc
previews/ # Duck.webp + Cube.{obj,dae,fbx,ifc}.webp (24프레임 360°)
tools/
preprocess.mjs # gltf-transform Draco+WebP 압축 (GLB 전용)
copy-decoders.mjs # postinstall: single-thread web-ifc WASM 복사
prerender.mjs # 360° 턴테이블 → Animated WebP (포맷 무관)
perf-smoke.mjs # 헤드리스 체감 로드 측정 (<3s)
adaptive-smoke.mjs # ★ CPU 스로틀로 60fps↓ 강등 실증
에이전트가 PLAN.md + PROGRESS.md를 매 시작 시 읽어 다음 작업을 결정. 병렬 안전(파일 스코프 분리).
Phase 6/7은 다이나믹 Workflow multiformat-viewer로 구현 (7 에이전트, 296k 토큰): Research(3 병렬·읽기전용: web-ifc API / 적응형 성능 / three 로더 형태) → Core(viewer-core) → Enhance(FPS ‖ 에셋) → Verify(빌드 green). git 미사용 환경이라 worktree 격리 대신 병렬 웨이브별 파일 스코프 분리로 충돌 방지.
| 이름 | 역할 |
|---|---|
| task-lead | PLAN/PROGRESS 읽고 다음 작업 분배 |
| viewer-core | src/viewer/* 씬·로더·하이드레이션·FPS |
| dnd-handler | src/dnd/* 드래그드롭·검증 |
| asset-pipeline | tools/* 압축·프리렌더·샘플 |
| hydration | SSR 자리표시자→캔버스 페이드 |
| reviewer | 누수/에러/성능 감사(읽기 위주) |
명령: /bootstrap /next-task /report /optimize-asset /verify-viewer
스킬: threejs-viewer(로드 패턴), task-graph(조정 프로토콜)
훅: session-start.sh(PLAN/PROGRESS 읽기 알림), progress-nudge.sh(src 편집 시 알림)
| Phase | 범위 | 상태 |
|---|---|---|
| P0 Bootstrap | Vite/TS 셋업, 디코더, 기본 씬 | done · 빌드 검증 |
| P1 Core 뷰어 | 싱글톤 로더, loadServer/Local, OrbitControls, 프레이밍 | done · 빌드 검증 |
| P2 Drag & Drop | 드롭존 UI, 검증, loadLocalFile 와이어링 | done · 빌드 검증 |
| P3 Hydration | WebP 자리표시자, 게이트, UI 토글 | done |
| P4 에셋 파이프라인 | preprocess.mjs, 샘플셋, 프리렌더 | P4-1/2/3 done |
| P5 Hardening | 누수/에러 감사, 퍼포먼스, 리뷰 | P5-1/2/3/4 done 0 critical |
| P6 멀티포맷 로더 | OBJ/FBX/DAE/IFC, SSR+CSR, modelLoader 디스패치, 샘플+프리뷰 | P6-1..5 done 6포맷 검증 |
| P7 FPS + 적응형 품질 | 화면 FPS, pixelRatio 사다리, animate 와이어링 | P7-1/2/3 done 강등 실증 |
ui/fps.ts): 좌상단, 프레임 델타 EMA 평활, ~250ms 스로틀 DOM 갱신. 색상 — ≥60 녹색 / 30–59 주황 / <30 빨강.viewer/adaptiveQuality.ts): pixelRatio 5단계 [min(DPR,2) · 1.5 · 1 · 0.75 · 0.5]. 가장 고효율·저위험 런타임 노브(드로잉 버퍼 재할당, CSS 리플로우 없음).tools/adaptive-smoke.mjs): 헤드리스 swiftshader + CDP CPU 스로틀 6×로 ABeautifulGame(11.5MB) 로드 → 실측 FPS 19→4, 적응형 tier 0→1→2→3 (pr 1.5→1→0.75) 로그 캡처. 로직 미패치, 실제 프레임 측정으로 발화.tsc --noEmit 클린, npm run build 통과 (vite 5.4.21, 11.92s). 청크: 앱 138KB(gz 47) · three vendor 533KB(gz 136) · web-ifc 3.5MB(gz 398, 지연 로드) · OBJ 8.8 / Collada 41 / FBX 48KB.#status), DnD 검증 피드백, IFC wasm 메모리 해제 — reviewer 0 critical.# 개발 서버 (HMR) npm run dev # → http://127.0.0.1:3333 # 프로덕션 빌드 + 미리보기 npm run build # tsc --noEmit && vite build → dist/ npm run preview # → http://127.0.0.1:4173 # 사용 # CSR(Path B): .glb .gltf .obj .fbx .dae .ifc 를 드롭존에 드롭 / 클릭 선택 # SSR(Path A): http://127.0.0.1:4173/?model=/samples/Cube.ifc # 검증/도구 (Git-Bash는 MSYS_NO_PATHCONV=1 접두 필요) node tools/perf-smoke.mjs # 포맷별 체감 로드 <3s node tools/adaptive-smoke.mjs --throttle 6 --seconds 30 # 60fps↓ 강등 실증 node tools/preprocess.mjs <file.glb> # Draco+WebP 압축(GLB) node tools/prerender.mjs # 360° WebP 프리뷰 생성
blob: 제약으로 지오메트리 전용.AnimationMixer 재생(현재 정적). document.hidden 배경탭 FPS 가드.gltf-transform etc1s|uastc.