EV 스튜디오 뷰포트 — 2D SVG → three.js 파라메트릭 3D 카셸 전환
개요
crowny-ev(ev.crowny.org:9616) 스튜디오 뷰포트의 2D SVG 측면 카를 three.js r128 실시간 파라메트릭 3D 카 셸로 교체. 사장님 지시("3D 트랙에서 진행, EV 세션에서 2D는 쓸 일 없음")에 따른 전환. EvConfig(roofH/gc/wheelIn/wing/splitter/diffuser/sig/paint/finish 등) 변경 시 3D 카가 라이브로 변형·회전.
아키텍처
- 영속 WebGL 캔버스:
EvApp._glCanvas를 1회 생성·보존.template()은 뷰포트 자리에<div id="ev-3d-slot">placeholder만 두고,render()직후_three_afterRender()가 캔버스를 슬롯에 재부착(appendChild). three.js scene/renderer/camera/car는EvApp._three에 영속 보관(최초 진입 시 lazy init). WebGL 재초기화 없음(매 render마다 new WebGLRenderer 금지 — 컨텍스트 누수 방지). 슬롯이 없는 뷰(parts)는renderer.setAnimationLoop(null)로 루프 정지 + 캔버스 detach. - 카빌더
buildCar(THREE, cfg) → THREE.Group(순수 함수, renderer 무관 — node 테스트 가능): 실루엣은 THREE.Shape(스테이션 좌표는 roofH/gc 기반 파라메트릭)를 ExtrudeGeometry(depth=전폭1996mm, bevel)로 압출. 오리지널 프로포션(실차 트레이드드레스 비복제). 캐노피(반투명 글래스), 휠4개(타이어+림+캘리퍼 2톤, CylinderGeometry), 에어로(스플리터/디퓨저/윙 none·덕테일·GT 3종), 라이트(프론트 시그니처 A/B/C + 테일, emissive)를 각각 그룹으로 빌드하고group.userData.refs에 보관. - 씬: PerspectiveCamera(35°) + OrbitControls(damping, maxPolarAngle 지면제한, min/maxDistance), HemisphereLight+DirectionalLight(그림자), 지면 plane. gloss 반사용 PMREMGenerator/RoomEnvironment는 벤더 미탑재로 생략 — 라이트만으로 폴백(주석 명시, 후속 고도화 여지).
- 라이브 업데이트
updateCar(THREE, group, cfg): 바디/유리는 geometry 교체(dispose 후 재생성), 휠/에어로/라이트는 그룹 전체 재구축(저폴리라 저비용, 포지셔닝 버그 회피). 드래그 슬라이더(_applyDragFrame)는 기존 RAF 스로틀(_dragRaf) 유지한 채 SVG setAttribute 대신updateCar호출로 재배선 — 구 2D setAttrs 블록(ev-body 등 id)은 제거(DOM에 해당 id가 더는 없어 no-op이었음). - 3D 공유 뷰: side/front/color/spec/order 뷰가 같은 3D 캔버스 공유. 카메라 프리셋(
EV3D_CAM_PRESETS) — side=측면, front=정면, color=3/4, spec=오버뷰, order=side와 동일(기존에도 isSide 그룹에 order 포함되어 같은 2D 뷰를 공유했던 구조 계승). parts 뷰는 기존 2D 분해도(exParts/BOM) 그대로 유지, 3D 분해는 후속 과제.
무엇을 했는지
/Users/ef/crowny-ev/public/index.html에<script src="/vendor/three.min.js">,<script src="/vendor/OrbitControls.js">추가(기존 서버 라우트 이미 서빙 중, 서버 무수정).// ===EV3D:BEGIN===~// ===EV3D:END===마커 블록에 순수 카빌더 함수군 삽입:_evStations,_evBuildBodyShape/GlassShape/BodyGeometry/GlassGeometry/BodyMesh/GlassMesh,_evBuildWheelGroup/WheelsGroup,_evBuildAero,_evBuildLights,_evApplyToggles,_evDisposeGroup,buildCar,updateCar.- 마커 밖에 DOM 의존 씬 초기화
_evSceneInit, 카메라 프리셋EV3D_CAM_PRESETS/_evApplyCameraPreset추가. EvApp에_three/_glCanvas필드,_three_configFromState/_ensure/_startLoop/_stopLoop/_afterRender메서드 추가.render()끝에_three_afterRender()호출 연결._applyDragFrame에서 구 2D setAttrs 블록(computeCarGeom 기반 ev-body/ev-glass/ev-wheel- 등) 제거,updateCar(THREE, this._three.car, cfg)호출로 대체. 슬라이더 thumb/fill/라벨 갱신 로직은 그대로 보존.template()의 SVG 뷰포트 조립부:svgSide/svgFront(2D 카 마크업) 생성 블록을 완전 제거(죽은코드 삭제, computeCarGeom/computeVals의 v.car/v.fv 산출 자체는 parts/dims 등 잔여 참조 보호를 위해 보존). 뷰포트는isSide||isFront일 때<div id="ev-3d-slot">, 아니면 기존<svg>(evgrid+paint gradient defs, svgParts+svgDims만) 분기.- 자동회전 토글 버튼(⟳)을 3D 슬롯 우하단에 오버레이로 추가(OrbitControls.autoRotate 토글).
검증
/private/tmp/.../ev3d_harness.js, index.html에서 EV3D 마커 블록을 정규식 추출해 new Function('THREE', src+...)로 격리 실행): 20개 assertion 전부 PASS.floorY - bevelSize(0.012)로 정확히 이동(베벨 오프셋 확인 후 통과).node --check: index.html 내 인라인 <script> 전체(1584줄) 구문 오류 0.curl http://127.0.0.1:9616/ 응답이 편집한 로컬 파일과 byte-identical(diff 없음) — 정적파일 즉시반영 확인. /vendor/three.min.js, /vendor/OrbitControls.js 200. 응답 본문에 ev-3d-slot, function buildCar, function updateCar grep 존재. 6뷰 패널 id(panel-side/front/color/spec/order/parts) 각 1회씩 정확히 존재(회귀 없음).crowny://ev 또는 https://ev.crowny.org에서 직접 확인 필요. node 환경은 WebGL 컨텍스트가 없어 실제 렌더 출력(픽셀)은 검증 불가, 지오메트리/토글/색상 로직만 격리 검증함.관련 파일
/Users/ef/crowny-ev/public/index.html— 이번 작업의 유일한 수정 파일(서버/데몬 무수정 원칙 준수)./Users/ef/crowny-ev/public/vendor/three.min.js,/Users/ef/crowny-ev/public/vendor/OrbitControls.js— 기존 배치된 벤더 파일(수정 없음, 이미 서버 라우트 서빙 검증됨)./Users/ef/crowny-ev/서버.한선— 무수정(정적 라우팅 확인만 함, 651~652줄에 vendor JS 라우트 기존 존재)./Users/ef/crowny-ev/EV엔진.한선§9 — geom_wheelR_x10/geom_rimR_x10/geom_botY_x10/geom_roofY_x10 등 2D 파생 수치코어 SSOT. 3D_evStations의 wheelR/rimR/floorY/roofY 공식은 이 §9 공식과 동일 유도(mm→m 스케일만 차이)./private/tmp/claude-501/-Users-ef/94a20429-4f7b-4f77-be0d-1b3ce3251306/scratchpad/ev3d_harness.js— node 검증 하네스(임시 스크래치패드, 제품 파일 아님).
한선씨 동반 관련 (의도적 예외 — 억지 동반 금지)
이번 작업 지시서에 명시된 대로: 3D 지오메트리 수치 코어(스케일·치수·휠반경 등)의 수학 공식은 EV엔진.한선 §9 geom_와 이미 동일 SSOT를 공유(위 "관련 파일" 참조). 신규로 추가된 3D 전용 수치(카메라 프리셋 좌표, three.js Shape 좌표 시퀀스, WebGL 렌더러/OrbitControls 설정값 등)는 DOM/WebGL 브라우저 런타임에 직접 종속되어 한선씨 VM(순수 3진 연산, DOM 없음)으로 대응시킬 대상이 없음. 억지로 하드웨어.한선/뷰.한선 등에 흉내만 낸 미러 파일을 만드는 것은 지시서가 명시적으로 금지한 "억지 동반"에 해당하여 생성하지 않음. 대신 이 문서와 코드 주석(index.html EV3D 블록 상단)에 정직히 명시.
잔여 이슈
- parts(부품 분해) 뷰의 3D 분해도는 이번 범위 밖 — 후속 과제(2D exParts BOM 그대로 유지).
- gloss 피니시의 IBL 반사(PMREMGenerator+RoomEnvironment)는 벤더 미탑재로 라이트만 폴백 — 필요 시 RoomEnvironment 셰이더를 벤더에 추가하고 고도화 가능.
- 육안 3D 렌더 확인(실제 픽셀 출력, 카메라 프리셋 구도, 조명 밸런스 등)은 브라우저 환경이 필요해 이 세션에서 직접 확인 불가 — 사용자가 crowny://ev 또는 https://ev.crowny.org에서 직접 확인 필요.
- OrbitControls 자동회전 버튼은 최소 구현(온오프 토글만, 스타일 다듬기는 후속 여지).
메인 세션 독립 재검증 (2026-07-22)
- 서빙 배선: curl :9616 → three script·orbit·ev-3d-slot(4)·buildCar·updateCar·_glCanvas(5) 존재, 파일=서빙 동일, 6뷰 무손상, vendor 200×2, JS 파스 0오류.
- 독립 카빌더 하네스(에이전트 것과 별개 재작성): buildCar 자식5·높이1.212m·폭2.02m(T-프레임 정합). roofH 1160→1.172m/1240→1.252m 반응. 15회 updateCar(색·에어로·휠·피니시 전환) 예외0(드래그 dispose 안전).
- 통합 갭 점검(중요): buildCar/updateCar는 중첩 cfg.paint.body 기대. 앱은 flat state(bodyC 등) → 세 호출부(init 1433·afterRender 1479·설정변경 1553) 전부 _three_configFromState() 어댑터로 중첩 변환. raw state 직접전달 없음 → 첫렌더 크래시 위험 없음 확인.
- 미검증: WebGL 픽셀출력(카메라·조명·머티리얼 룩) — 브라우저 필요, 사용자 crowny://ev 육안.