본문으로 건너뛰기
5분 AI.
TRENDSHIFT DAILY #13 딥다이브 · 2026-10-23 분석

3d-model-anything 딥다이브
— 아이폰으로 잰 방을 수정 가능한 코드로 다시 짓기

캡처 앱·재구성 도구

빠른 판단

무엇
LiDAR 실내 측정과 촬영 위치가 있는 사진을 근거로 수정 가능한 Three.js 공간을 만드는 캡처·재구성 프로젝트
이럴 때
실내를 코드 기반 3D 모델로 옮기며 센서 좌표 변환·사진 대조·에이전트 모델링을 함께 구성할 때
입력
LiDAR 지원 iPhone·iPad의 실내 스캔과 위치 정보가 담긴 사진
결과
브라우저에서 확인·수정할 수 있는 Three.js 방 모델
LiDAR가 공간을 재고, 사진이 표면의 모습을 기록하고, AI 코딩 에이전트가 그 증거를 읽어 Three.js 장면을 만듭니다. 이 글은 캡처 앱의 Swift 상태 관리부터 Python 좌표 변환, TypeScript 모델링과 GPU 최적화까지 한 흐름으로 따라갑니다. 저장소 wesbos/3d-model-anything · ⭐629 · 포크 106 · GitHub 주 언어 Swift · 소스 기준 31b2dc9. 메타데이터는 2026-10-23 GitHub 기준이며, 구현 설명은 커밋 31b2dc9 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

현실의 방을 입력으로, 사람이 고칠 수 있는 3D 코드를 결과로

3d-model-anything은 LiDAR가 달린 iPhone·iPad로 실내 공간을 기록하고, 측정값과 사진을 근거로 수정 가능한 Three.js 방을 재구성하는 프로젝트입니다. 스캔은 정답 모델을 만드는 버튼이 아니라, 모델을 만들고 검증할 때 계속 되돌아볼 증거입니다.

저장소는 네 부품을 함께 제공합니다. Swift로 작성한 캡처 앱, 브라우저 뷰어, 에이전트 작업 지침인 scan-to-threejs 스킬, 그리고 사무실 재구성 예제입니다. 모델의 벽·책상·의자·조명은 TypeScript 함수와 수치로 표현됩니다. 따라서 결과를 코드 리뷰하고, 특정 가구의 치수나 재질만 바꾸는 학습이 가능합니다.

한 문장 비유

휴대폰은 현장 조사원, 에이전트는 모형 제작자, 뷰어는 검수대입니다

현장 조사원은 줄자로 잰 치수와 여러 방향의 사진을 모읍니다. 모형 제작자는 그 자료를 보고 벽과 가구를 다시 만들고, 검수대에서는 원래 사진과 같은 위치에서 모형을 바라봅니다.

조사 자료에 의자가 직육면체로 기록됐다고 해서 실제 의자도 상자는 아닙니다. 상자는 대략적인 자리와 크기를 알려주며, 다리·등받이·재질은 사진을 보고 보완해야 합니다.

1-1. 무엇을 캡처하고, 무엇을 다시 만드는가

입력 증거담고 있는 정보재구성에서 쓰는 자리
RoomPlan JSON벽·문·창문·가구 범주와 치수·위치방 외곽과 가구 배치의 기준
LiDAR PLY삼각형 표면, 법선, 분류, 사진 커버리지천장 단차와 실제 표면의 형태 확인
위치가 붙은 사진JPEG, 카메라 자세, 내부 파라미터재질·윤곽 모델링과 같은 시점 비교
깊이 프레임거리·신뢰도·동시점 저해상도 RGB가려짐 검사, 선택적 표면 재구성
Three.js 코드기하·재질·조명·카메라 설정증거를 해석해서 만든 편집 가능한 결과
용어 풀이
파라메트릭 표현과 메시
파라메트릭 표현은 “폭 3m, 높이 2.4m인 벽”처럼 의미와 치수로 대상을 적는 방식입니다. 메시(mesh)는 표면을 작은 삼각형으로 나눈 것입니다. 전자는 벽을 수정하기 쉽고, 후자는 울퉁불퉁한 실제 형태를 담기 좋습니다. 이 프로젝트는 두 표현을 함께 보관합니다.

1-2. 공개 예제에서 실제로 볼 수 있는 범위

demos/index.ts의 ROOMS에는 office-v4 하나가 등록돼 있습니다. 그 구현은 office-latest를, 다시 그 구현은 office-v2를 사용합니다. 폴더 세 개는 서로 무관한 공개 방 세 개가 아니라, 현재 사무실을 구성하는 이전 모델링 계층입니다.

demos/office-v4/index.ts는 room.photos = []와 room.referenceAvailable = false를 설정합니다. 공개 예제에서 Scan·Overlay를 누를 수 없는 이유는 이 설정과 원본 자료의 비공개 처리입니다. 변환기가 존재한다는 사실과, 데모에 변환용 원본이 들어 있다는 주장은 구별해야 합니다.

학습 목표
한 언어의 문법보다, 여러 부품 사이의 약속을 배웁니다

Swift가 저장한 행렬을 Python이 해석하고, TypeScript가 같은 단위로 렌더링해야 합니다. 이 연결을 이해하면 센서 앱·이미지 처리·웹 시각화를 하나의 시스템으로 읽는 연습이 됩니다.

2왜 주목받는가

확인 가능한 화제의 맥락과, 소스에서 읽히는 차별점

2-1. 선정 사실과 인기의 원인은 구분하기

이 글의 선정 근거는 TrendShift Daily #13입니다. 2026-10-23 GitHub 기준으로 2026-10-21 생성, 10-22 최근 push, 10-23 조회 시 스타 629·포크 106입니다. 생성일과 조회일 사이에 관심을 모았다는 해석은 가능하지만, 이 수치만으로 스타 증가 추이나 유입 경로를 알 수는 없습니다.

커밋 31b2dc9의 메시지는 Link the project video from README using thumb-1입니다. README에도 프로젝트 소개 영상과 공개 뷰어가 연결돼 있습니다. 시각적인 결과를 바로 확인할 수 있고, 그 결과를 만드는 코드까지 열어 볼 수 있다는 점이 진입 장벽을 낮춥니다. 이것은 저장소 구성에서 도출한 해석입니다.

2-2. “AI가 만든 3D”를 검토 가능한 작업으로 바꾼다

스킬의 지침은 측정 → 모델링 → 비교입니다. 특히 .agents/skills/scan-to-threejs/scripts/cmp_photo.sh는 원본 사진과 같은 카메라의 렌더를 나란히 붙입니다. 비교 이미지를 만드는 도구이지, 자동으로 유사도를 판정하거나 정확성을 보증하는 도구는 아닙니다. 어느 벽이 길고 어느 가구가 큰지 사람이 판단할 수 있는 검수 재료를 만드는 것입니다.

이 저장소의 교육적 장점은 멋진 결과 화면보다, 결과가 틀렸는지 확인하는 경로까지 코드로 제공한다는 데 있습니다. 생성형 도구를 쓸 때도 입력 증거·중간 표현·결과 검수를 남겨야 한다는 원칙을 구체적으로 배울 수 있습니다.

2-3. 경쟁 도구와 비교하면 어디에 강점이 있는가

비교 대상주로 제공하는 것이 레포의 상대적 장점과 비용
Apple RoomPlan앱에 넣는 실내 측정 API와 파라메트릭 출력캡처·사진 관리·웹 모델링·비교까지 이어지는 예제. 대신 앱 개발과 모델링 작업이 추가됨
Polycam공식 안내 기준, 공간 메시·평면도 등 캡처 모드센서 처리와 재구성 코드를 직접 추적·수정할 수 있음. 완성된 제품의 사용 편의성을 같은 수준으로 보장하지 않음
범용 3D 모델 파일뷰어가 읽어 표시하는 기하·재질 데이터방 요소를 함수와 매개변수로 고치고 변경 내역을 검토하기 좋음. 모델링 규칙을 개발자가 관리해야 함
3d-model-anything캡처 앱 + 스킬 + 뷰어 + 상세 사무실 코드측정 근거와 코드 편집을 연결하는 학습에 적합. 한 번의 입력으로 완성되는 서비스는 아님

RoomPlan의 역할은 Apple 공식 개요, Polycam의 모드 구분은 Polycam 공식 안내 기준입니다. 속도·정밀도·가격의 우열을 가리는 비교는 아닙니다.

비유

완성된 가구를 사는 것과 가구 제작 도면·공구·검수법을 함께 받는 것의 차이에 가깝습니다. 당장 편하게 쓰는 목적과, 작동 원리를 배우고 내 방식으로 고치는 목적은 선택 기준이 다릅니다.

2-4. 큰 코드가 짧은 공개 이력에 들어 있는 이유

docs/release-status.md는 공개 저장소를 새 이력으로 시작했고 사무실만 배포한다고 밝힙니다. 소스에는 공개 생성일보다 앞선 스캔 날짜가 등장합니다. 따라서 공개 후 경과일이나 새 공개 이력의 커밋 수만으로 전체 개발 기간을 판단할 수는 없습니다.

또한 2026-10-23 GitHub 기준 주 언어는 Swift이지만, 아래의 물리적 줄 수 집계에서는 상세 가구 모델링을 포함한 TypeScript가 더 많습니다. GitHub의 언어 판정과 학습용 줄 수 집계는 측정 방법이 같지 않습니다. 이 글은 두 값을 각각의 출처와 함께 표시합니다.

3기술 스택 전체 지도

서버 API보다 로컬 파일 계약이 중심인 혼합 언어 저장소

3-1. 프론트엔드: Three.js를 직접 다루는 TypeScript

루트 package.json에는 React·Vue가 없습니다. 화면은 DOM 이벤트와 Three.js로 구성하며, 뷰어 진입점은 apps/room-viewer/src/main.ts입니다. 패키지 자체는 private: true, 버전 0.0.0, ES 모듈 방식인 type: module로 설정돼 있습니다.

의존성package.json 범위package-lock.json 확정값역할
three^0.186.00.186.0장면·기하·조명·렌더러
@dimforge/rapier3d-compat^0.20.00.20.0선택적 물리 상호작용
typescript^7.0.27.0.2정적 타입 검사
vite^8.3.08.3.5개발 서버·프로덕션 빌드
@types/three^0.186.00.186.0Three.js 타입 선언
@gltf-transform/core^4.5.04.5.0glTF 측정 도구
gl-matrix / pngjs^3.4.4 / ^7.0.03.4.4 / 7.0.0행렬 계산·이미지 도구 의존성
용어 풀이
버전 범위와 잠금 파일
package.json은 허용할 버전의 범위를, package-lock.json은 이 스냅샷에서 선택된 버전을 적습니다. 예를 들어 Vite는 요구 범위가 ^8.3.0이지만 실제 잠금값은 8.3.5입니다. 재현할 환경을 설명할 때 둘을 혼동하면 안 됩니다.

3-2. 캡처와 데이터 처리: Swift 앱 + Python 도구

영역소스에서 확인한 구성버전의 근거와 한계
iOS 앱SwiftUI, ARKit, RoomPlan, SceneKit, CoreImage, simdapps/house-capture/project.yml: iOS 17.0, Swift 언어 모드 5.0
앱 프로젝트 생성XcodeGen의 apps/house-capture/project.ymlxcodeVersion: 27.0. 실제 사용한 컴파일러 버전의 증명은 아님
변환·집 가져오기Python 표준 라이브러리, JSON, 행렬 계산루트에 Python 의존성을 고정하는 파일은 없음. CI는 Python 3.12
사진 처리·비교Pillow, zsh, agent-browser CLI버전 고정 없음. 비교 실행 스크립트는 #!/bin/zsh로 시작하며, 공통 설정은 별도 파일에서 불러옴
선택적 깊이 재구성NumPy, Open3D, Pillow소스 import와 README 기준. 설치 버전 잠금 없음

SWIFT_VERSION: 5.0은 Swift 언어 호환 모드 설정입니다. “Swift 5.0 컴파일러만 설치해야 한다”는 뜻으로 읽지 않습니다. Apple 프레임워크들은 운영체제 SDK가 제공하며, 별도의 Swift 패키지 의존성 명세는 없습니다.

3-3. 백엔드: 별도 상주 서버 없이 파일로 이어 붙인다

이 스냅샷에는 서비스용 API 서버·데이터베이스·로그인 계층이 없습니다. 캡처 앱이 로컬 폴더를 만들고, 사용자가 내보낸 파일을 Python 도구가 읽으며, 정적 뷰어가 결과를 표시합니다. 개발 중 Vite 서버가 파일을 제공하지만 캡처를 처리하는 애플리케이션 백엔드와는 역할이 다릅니다.

.agents/skills/scan-to-threejs/scripts/fetch_textures.py는 외부 재질 사이트를 조회하는 선택적 도구입니다. 앱의 로컬 캡처와 스킬 실행 전체의 네트워크 사용을 같은 것으로 취급하면 안 됩니다. AI 에이전트의 실행 환경과 모델 서비스도 이 저장소가 자동으로 제공하는 서버가 아닙니다.

3-4. 인프라·빌드: 로컬 개발과 정적 배포

# package.json에 정의된 명령
"dev":     "vite --config apps/room-viewer/vite.config.ts"
"build":   "tsc && vite build --config apps/room-viewer/vite.config.ts"
"preview": "vite preview --config apps/room-viewer/vite.config.ts"

tsconfig.json은 target: ES2022, moduleResolution: bundler, strict: true, noEmit: true입니다. 즉 TypeScript 단계는 타입을 검사하고, 실제 배포 번들은 Vite가 만듭니다. “타입 검사 통과”는 사진과 모델이 일치한다는 시각 검증을 대신하지 않습니다.

apps/room-viewer/vite.config.ts는 개발 서버를 127.0.0.1:5188, strictPort: true로 고정합니다. publicDir은 data/web-assets, 출력은 apps/room-viewer/dist이고, 일반 뷰어·놀이 화면·비교 캡처 화면을 각각 빌드 입력으로 둡니다.

.github/workflows/check.yml은 Ubuntu에서 공개 자산 검사, 의존성 설치, 웹 빌드, Python 가져오기 테스트를 순서대로 선언합니다. .github/workflows/pages.yml은 VIEWER_BASE=/3d-model-anything/을 주고 GitHub Pages에 배포합니다. Dockerfile·Cargo.toml·go.mod·pyproject.toml은 없습니다.

이 레포를 읽을 때는 서버 요청의 흐름보다, 어떤 파일을 누가 쓰고 어떤 좌표계로 누가 읽는지를 먼저 추적하면 좋습니다.

4아키텍처 심화 분석

데이터 흐름 하나와, 실제 파일에 나타나는 네 가지 설계

4-1. 시스템 전체 구조

[LiDAR가 있는 iPhone / iPad] apps/house-capture/HouseCapture/CaptureController.swift | +-- 공유 ARSession ----------------------------+ | | | | | RoomPlan LiDAR 메시 사진 / 깊이 | 벽 / 가구 상자 커버리지 점수 자세 / 내부 파라미터 | | | | +------v-----------------v---------------------v--+ ScanBundle + HouseProject 로컬 JSON / PLY / JPEG / f16z / worldmap | 공유 시트 / 파일 내보내기 v [별도 작업 환경의 캡처 폴더] | .agents/skills/scan-to-threejs/scripts/ +--------------+--------------------------+ | | bundle_to_room.py house_to_rooms.py 방 좌표 정규화 active 스캔 / 배치 검증 | | +--------------v--------------------------+ 측정 JSON + 사진 메타데이터 | scan-to-threejs 지침을 읽는 에이전트 측정 -> 코드 모델링 -> 사진과 비교 | demos/<id>/ RoomModule.build() | room-runtime / 선택적 room-physics | Vite 정적 뷰어 / 비교 캡처 화면 | 사진과 같은 카메라의 렌더 결과 +----> 차이 확인 후 모델 수정

깊이 기반 .agents/skills/scan-to-threejs/scripts/reconstruct_bundle.py는 이 흐름에서 선택적으로 참조 메시를 만드는 도구입니다. 그 결과와 에이전트가 작성하는 편집 가능한 방 모델은 별개입니다. 또한 .agents/skills/scan-to-threejs/scripts/house_to_rooms.py의 집 배치 정보 출력이 곧 웹 뷰어의 집 전체 보기 기능을 뜻하지는 않습니다.

4-2. 패턴 ① 상태 머신과 방 단위 체크포인트

apps/house-capture/HouseCapture/CaptureController.swift의 Phase는 작업 단계를 열거형으로 표현합니다. “스캔 중인지”와 “저장 중인지”를 여러 불리언 조합으로 추측하는 대신 현재 단계를 명시합니다. 대표 흐름은 다음과 같습니다.

betweenRooms -> scanning -> finishingRoom -> betweenRooms
                                                   |
                                            세션 종료 요청
                                                   v
                                              finishing -> done

// 재개할 때는 relocalizing에서 시작할 수 있음
// 일부 세션 저장 실패는 failed(String)으로 표현

finish(with:error:)는 사진 쓰기가 끝나기를 기다린 뒤 원시 CapturedRoomData를 먼저 저장하고, RoomBuilder로 레이아웃 생성을 시도합니다. 생성에 실패하면 record.file = ""로 표시하면서 메시·사진 저장을 계속 진행합니다. 좋은 예외 처리란 성공으로 위장하는 것이 아니라, 남은 증거와 실패한 결과를 구분하는 것입니다.

  1. drainPhotos(): 수락한 JPEG 쓰기를 기다리고, 늦은 고해상도 콜백의 요청 ID를 무효화합니다.
  2. 원시 데이터와 RoomPlan 결과를 저장하고 engine.exportPLY로 메시를 확보합니다.
  3. 깊이·사진 목록, HouseProject.modify, writeManifest()를 통해 방 기록을 남깁니다.
  4. saveWorldMap(final: false)로 이후 재개에 쓸 지도를 갱신하고 다음 방으로 넘어갑니다.

ScanBundle.writeManifest(_:)는 Data.write(..., options: .atomic)를 사용합니다. 이는 해당 파일 교체의 안전장치이지, 여러 JSON·메시·사진을 한꺼번에 묶는 데이터베이스 트랜잭션은 아닙니다. apps/house-capture/README.md의 “진행 중인 방 정도만 잃는다”는 설명은 저장이 성공하는 정상 경로의 의도이며, 코드에는 저장 실패 알림과 재시도 경로도 있습니다.

용어 풀이
체크포인트(checkpoint)
긴 작업의 중간 결과를 디스크에 남기는 지점입니다. 게임의 저장 지점처럼, 다음 작업이 실패해도 이미 끝낸 부분부터 다시 시작할 수 있게 합니다. 다만 저장 지점 자체가 실패했는지도 확인해야 합니다.

직접 읽을 곳: CaptureController.swift와 ScanBundle.swift. 특히 Manifest.currentFormat은 house-capture/5입니다. 루트 README 기준, 앱 표시 이름이 바뀌어도 이 형식 식별자는 유지합니다.

4-3. 패턴 ② 좌표계 변환을 명시적인 데이터로 남긴다

각 캡처 세션의 AR 원점은 달라질 수 있습니다. 같은 이름의 방을 다른 날 스캔했다고 같은 좌표가 되는 것은 아닙니다. .agents/skills/scan-to-threejs/scripts/bundle_to_room.py의 main()은 바닥 높이를 찾고, 벽 방향을 정렬하고, 방을 가운데로 옮기는 worldToRoom 행렬을 출력합니다.

# 실제 구현의 핵심: 점은 w=1, 방향은 w=0
W2R = mul(T, R)
to_room = lambda p: apply(W2R, p)
dir_room = lambda d: apply(W2R, d, 0.0)

# .agents/skills/scan-to-threejs/scripts/house_to_rooms.py에서 합성하는 관계
sessionToHouse = zone.toHouse × session.alignment.toZone
roomToHouse = sessionToHouse × inverse(worldToRoom)
pointHouse = roomToHouse × pointRoom

T는 이동, R은 회전입니다. 위치에는 이동을 적용하지만 카메라의 앞 방향·벽의 법선에는 이동을 적용하지 않습니다. 방향 벡터에 이동까지 더하면 카메라가 잘못된 곳을 보게 됩니다. 행렬을 저장할 때는 16개 수를 열 단위로 나열하는 column-major 규칙도 맞춰야 합니다.

벽 방향은 단순 평균하지 않습니다. atan2(x[2], x[0]) * 4로 직교하는 벽의 90° 대칭을 접고, 벽 길이로 가중한 뒤 각도를 4로 나눕니다. 긴 벽들이 가리키는 주된 방향을 기준으로 축을 잡는 방식입니다. 바닥 다각형이 있으면 내부 판정으로 법선 방향도 보정합니다.

.agents/skills/scan-to-threejs/scripts/house_to_rooms.py의 export_house()는 각 방의 active 참조를 선택합니다. zone.aligned가 거짓이거나 세션 정렬 방식이 허용 목록 밖이면 blocked로 기록합니다. rigid()는 유한한 16개 값, 회전의 직교성, 행렬식 등을 검사해 스케일·전단·반사를 배제합니다.

비유

여러 사람이 각자 다른 곳에 줄자의 0점을 놓고 방을 쟀다고 생각해 보세요. 숫자를 그대로 합치면 방들이 겹칩니다. “이 줄자의 원점은 집 현관에서 어느 방향으로 얼마나 떨어져 있는가”까지 기록해야 측정값을 함께 쓸 수 있습니다.

test_active_scan_and_noncommuting_transform은 회전과 이동의 순서를 검증합니다. 테스트의 세션 점 (4, 2.5, 2)는 구역 좌표 (6, 2.5, 5)를 거쳐 집 좌표 (15, 3.5, 14)가 돼야 합니다. “행렬이 16개 수로 돼 있다”보다 “알려진 점이 예상 위치로 옮겨진다”가 더 유용한 검증입니다.

직접 읽을 곳: bundle_to_room.py, house_to_rooms.py, .agents/skills/scan-to-threejs/scripts/test_house_import.py.

4-4. 패턴 ③ 최신 입력을 남기는 메일박스로 과부하를 줄인다

apps/house-capture/HouseCapture/CoverageEngine.swift의 CoverageEngine은 표면이 사진에 얼마나 잘 담겼는지 계산합니다. 카메라가 움직일 때마다 모든 갱신을 큐에 쌓으면, 사용자는 이미 지나간 장면의 계산을 기다리게 됩니다. 그래서 같은 앵커의 대기 데이터는 최신 스냅샷 하나로 대체합니다.

// CoverageMailbox.offer(_:_:), 일부 발췌
if values[key] == nil { order.append(key) }
else { replacements += 1 }
values[key] = value

// CoverageEngine.drain(), 일부 발췌
process(pending.take(limit: 2))
if pending.finish() { queue.async { self.drain() } }

CoverageMailbox<Key: Hashable, Value>는 잠금으로 사전·순서 목록·스케줄 상태를 보호합니다. 새 키는 순서 뒤에 붙이고, 기존 키는 값만 바꿉니다. 소비자는 한 번에 앵커 두 개만 처리한 뒤 큐에 차례를 넘겨 사진 평가나 진단 작업이 오래 밀리는 일을 줄입니다.

update(snapshot:)는 최초 소비를 0.15초 뒤에 예약합니다. exportPLY(to:completion:)는 내보내기 전에 대기 중인 데이터를 반영하므로, 화면 갱신이 합쳐져도 저장할 최종 기하를 놓치지 않도록 설계돼 있습니다. pendingMeshUpdates와 coalescedMeshUpdates는 이 동작을 관측하는 수치입니다.

용어 풀이
합치기(coalescing)와 동시성 책임
동일한 대상의 반복 갱신을 최신 값 하나로 모으는 방식입니다. 이 구현은 전용 직렬 큐와 NSLock을 사용합니다. @unchecked Sendable은 컴파일러가 안전성을 전부 증명했다는 뜻이 아니라, 개발자가 공유 상태의 동기화를 책임지겠다는 선언입니다.

직접 읽을 곳: CoverageEngine.swift의 update(snapshot:)·drain()·CoverageMailbox.offer·exportPLY. 이 아이디어는 센서 화면뿐 아니라 검색 제안·진행률·실시간 차트에서도 응용할 수 있습니다.

4-5. 패턴 ④ 방 모듈 계약과 표시 단위 보존

packages/room-runtime/types.ts는 방이 뷰어에 무엇을 제공해야 하는지 정합니다. RoomModule.build()가 BuiltRoom을 반환하면, 뷰어는 특정 책상이나 의자의 만드는 법을 몰라도 그 방을 표시할 수 있습니다.

// 실제 타입에서 핵심만 발췌
export interface RoomModule {
  id: string;
  label: string;
  build(): Promise<BuiltRoom>;
}
// BuiltRoom에는 다음과 같은 항목이 포함됨
group: THREE.Group;
lights: THREE.Group;
walkable(x: number, z: number): boolean;
loadReference(onProgress?: (p: number) => void): Promise<THREE.Object3D>;

officeV4.build()는 이전 계층의 방을 만든 뒤 책상·실내·건축 요소를 수정합니다. demos/office-v2/index.ts의 buildDetailedOffice()는 import.meta.glob으로 부품 모듈을 읽고, 파일별 build(ctx)를 호출합니다. 한 부품 로드가 실패하면 로그를 남기고 다른 부품은 계속 만드는 경로도 있습니다.

packages/room-runtime/batch.ts의 batchStatic()은 같은 재질을 사용하는 정적 메시를 합칩니다. 그러나 재질만 같다고 모두 합치지는 않습니다. 벽을 숨길 때 함께 사라져야 하는 가구, 천장, 그림자 설정, 물리 분류 등도 그룹 경계로 사용합니다. 투명 재질과 일부 특수 메시는 제외합니다.

일반 뷰어는 room.optimize?.({ static: true })로 움직이지 않는 물체까지 합칠 수 있습니다. 물리 화면에서는 물체별 움직임이 필요하므로 같은 최적화 선택을 그대로 적용하면 안 됩니다. “덜 그리기”가 물체의 기능을 망가뜨리지 않는지 함께 살펴야 합니다.

좋은 최적화는 계산량만 줄이는 것이 아니라, 이후에도 따로 움직이거나 숨겨야 할 경계를 보존합니다.

직접 읽을 곳: types.ts, batch.ts, apps/room-viewer/src/main.ts의 loadRoom()과 renderFrame().

5디렉토리 구조 해부

앱, 재사용 부품, 예제, 원본 증거를 나누어 읽기
3d-model-anything/ +-- apps/ | +-- house-capture/ | | +-- project.yml XcodeGen 설정 | | +-- HouseCapture/ Swift 캡처·저장·미리보기 | | +-- release/ 배포 전 검사·스토어 초안 | +-- room-viewer/ | +-- src/ 일반 뷰어·평면도·놀이 화면 | +-- tools/capture.ts 측정·사진 비교용 렌더 | +-- vite.config.ts 개발 서버·빌드 설정 +-- packages/ | +-- room-runtime/ 벽·재질·조명·렌더링·타입 | +-- room-physics/ 충돌·플레이어·상호작용 +-- demos/ | +-- index.ts 공개 방 등록 | +-- office-v2/ 상세 모델의 바탕·가구 부품 | +-- office-latest/ 중간 수정 계층 | +-- office-v4/ 현재 공개 사무실 +-- data/ | +-- demos/ 변환된 측정 JSON·정렬 정보 | +-- web-assets/ 배포할 텍스처·모델·정적 페이지 +-- .agents/skills/scan-to-threejs/ | +-- SKILL.md 에이전트 작업 순서 | +-- scripts/ 변환·측정·사진·비교 도구 | +-- references/ 포맷과 모델링 안내 | +-- assets/BRIEF.example.md 모델링 요구사항 예시 +-- tools/audit-public.py 공개 경계·자산 해시 검사 +-- docs/public-assets.json 배포 데이터의 SHA-256 목록 +-- .github/workflows/ 웹 검사·Pages 배포 +-- package.json / package-lock.json 웹 의존성과 명령 +-- LICENSE / ASSETS-LICENSE.md 코드와 자산의 다른 조건

5-1. 초보자가 처음 열어 볼 파일 다섯 개

  1. packages/room-runtime/types.ts: 완성된 방의 계약부터 파악합니다.
  2. demos/index.ts: 뷰어가 어떤 방을 선택하는지 확인합니다.
  3. demos/office-v4/index.ts: 기존 모델을 조립하고 바꾸는 흐름을 봅니다.
  4. .agents/skills/scan-to-threejs/scripts/bundle_to_room.py: 측정 데이터를 모델링하기 좋은 좌표로 바꾸는 과정을 봅니다. 여기서 .agents/skills/scan-to-threejs/scripts/는 변환·비교 도구 폴더입니다.
  5. apps/house-capture/HouseCapture/ScanBundle.swift: 변환기의 입력이 어디서 생기는지 거슬러 올라갑니다.

가구 파일부터 무작정 전부 읽으면 기하 생성 함수가 너무 많이 나옵니다. 먼저 입출력 타입을 보고, 원하는 물체 한 개를 선택한 뒤 그 물체가 쓰는 재질·좌표·헬퍼만 추적하는 편이 이해하기 쉽습니다.

5-2. 언어별 코드 규모

언어 / 위치파일 수물리적 줄 수주로 읽는 내용
TypeScript 전체7521,509뷰어·런타임·상세 사무실
Swift2911,779캡처·저장·복구·미리보기
Python141,376변환·이미지 처리·검사
JavaScript (.mjs)2465측정 보조·예제 생성 도구
CSS / HTML2 / 7189 / 171뷰어 스타일·페이지·도움말
셸 / YAML4 / 397 / 96비교 캡처·CI·앱 프로젝트 설정

집계 대상은 커밋 31b2dc9의 파일이며 .git은 제외했습니다. 숨김 폴더의 스킬 스크립트, 테스트, 빈 줄, 주석을 포함하고 마지막 개행 없는 줄도 한 줄로 계산합니다. JSON 데이터·Markdown 문서·이미지·바이너리 자산은 위 언어 표에서 제외했습니다. 전체 비 Git 파일은 374개입니다.

TypeScript 중 demos/가 48파일·16,520줄, packages/가 17파일·3,108줄, apps/room-viewer/가 10파일·1,881줄입니다. 큰 비중은 앱 프레임워크보다 사물의 모양을 재현하는 코드에 있습니다. 주 언어 메타만 보고 웹 부분을 작게 생각하면 중요한 학습 대상을 놓칩니다.

폴더를 읽는 기준
publicDir에 들어간 것은 웹으로 배포될 수 있습니다

.gitignore는 개인 입력인 data/captures-in/, 중간 산출물인 data/scans/·data/captures/ 등을 제외합니다. 반면 data/web-assets/는 Vite의 공개 자산 폴더입니다. 원본 사진을 “프로젝트 안”에 넣는 것과 “공개 자산 안”에 넣는 것은 다릅니다.

6학습 포인트 (기술별)

기능 하나를 설명하고, 작은 입력으로 검증하는 연습

6-1. Swift: 화면 상태와 계산 작업의 경계

CaptureController는 @MainActor이며 @Published 속성으로 화면에 상태 변화를 알립니다. 한편 CoverageEngine은 전용 직렬 큐에서 표면 계산을 수행합니다. UI가 쓰는 상태와 무거운 계산의 소유자를 구분하는 예제입니다. 여기에 Task.detached로 원시 캡처 데이터를 저장하는 경로도 있으므로 “비동기 함수는 전부 같은 큐에서 돈다”고 생각하면 안 됩니다.

작은 실습: phase가 바뀌는 곳과 recordingSaveFailed가 설정되는 곳을 각각 찾아 표로 적어 보세요. 방 저장과 세션 저장의 실패가 UI에 어떤 선택지를 남기는지 설명하는 것이 목표입니다. 현 코드를 Swift 6 전용 문법으로 바꾸는 것은 이 읽기 실습의 전제 조건이 아닙니다.

6-2. 선형대수: 위치, 방향, 카메라를 구분하기

.agents/skills/scan-to-threejs/scripts/bundle_to_room.py의 apply(m, p, w=1.0)에서 w를 바꾸며 위치와 방향을 구분합니다. demos/office-v4/index.ts도 위치에는 applyMatrix4, 방향에는 transformDirection을 사용합니다. 같은 정렬 행렬을 쓰더라도 벡터의 의미에 따라 연산이 달라집니다.

작은 실습: 회전 없이 X축으로 2m 이동하는 행렬을 종이에 적고, 점 (1, 0, 0)은 (3, 0, 0)으로, 방향 (1, 0, 0)은 그대로 남는지 확인하세요. 그다음 90° 회전을 앞뒤로 붙여 이동과 회전의 순서가 결과를 바꾸는지 비교합니다.

6-3. 컴퓨터 비전: 좋은 사진은 장수로만 세지 않는다

CoverageEngine.contribution(_:_:_:twoSided:)는 표면 점을 카메라 좌표로 바꾼 뒤 카메라 기준 깊이·화면 안 위치·법선 방향·깊이 데이터에 따른 가려짐을 검사합니다. 카메라 앞 방향이 −Z이므로 깊이 항은 d = -c.z입니다. 아래 식은 해당 함수의 핵심 계산을 풀어 쓴 것입니다.

# 화면에 투영되는 픽셀 좌표
u = fx * cameraX / d + cx
v = fy * (-cameraY) / d + cy

# 사진의 세부 표현 능력을 추정
pxPerCm = fx * cosT / d / 100
score = min(1, pxPerCm / 4) * sharpness * occlusionFactor

# 카메라 축 방향 깊이 조건: 0.25m 초과, 5m 미만
# 화면 가장자리 3%는 제외, cosT는 0.2 초과

fx는 픽셀 단위 초점거리, cosT는 표면을 정면에서 보는 정도입니다. d는 카메라 축 방향 깊이로, 카메라와 표면 점 사이의 직선거리와는 다릅니다. 가려짐을 확인할 깊이가 없으면 계수 0.9를 사용하고, 앞에 다른 표면이 있으면 점수를 0으로 만듭니다. 사진 수가 많아도 멀거나 비스듬하거나 흔들린 사진이면 점수가 낮을 수 있습니다.

용어 풀이
카메라 내부 파라미터와 자세(pose)
내부 파라미터 fx, fy, cx, cy는 렌즈가 공간의 점을 이미지 어디에 맺히게 하는지 설명합니다. 자세는 카메라가 어디에 있고 어느 방향을 보는지 설명합니다. “같은 사진 시점”을 재현하려면 둘 다 필요합니다.

작은 실습: 초점거리·정면 정도를 고정하고 카메라 기준 깊이를 두 배로 바꾸면 pxPerCm가 절반이 되는지 계산하세요. 이어 Stats.percent를 읽고, 점수가 높아도 아직 스캔하지 않은 방 뒤편의 존재는 알 수 없다는 이유를 설명해 보세요.

6-4. 이미지 처리: 사진 회전은 한 번만 한다

앱의 PhotoRecord는 원래 센서 방향의 픽셀에 맞는 내부 파라미터를 보관합니다. .agents/skills/scan-to-threejs/scripts/prepare_photos.py의 prepare()는 변환된 방 JSON의 photos 배열 순서대로 읽고, ROTATE_270으로 시계 방향 90° 회전한 다음 기본 긴 변 1280px·JPEG 품질 90으로 내보냅니다.

파일명 정렬만으로 사진 인덱스를 다시 만들지 않는 것이 중요합니다. 원본이 0009.jpg 다음 0003.jpg 순서로 선택됐다면 새 결과의 0000.jpg는 원본 0009.jpg여야 합니다. EXIF 자동 회전을 또 적용하면 카메라 방향과 이미지가 어긋납니다.

.agents/skills/scan-to-threejs/references/house-capture.md에는 비교 도구가 여백을 자른다는 설명이 남아 있지만, 현재 .agents/skills/scan-to-threejs/scripts/cmp_photo.sh는 검은 장면 픽셀을 자르지 않고 원본 사진을 종횡비를 유지한 채 렌더 높이에 맞춰 조정합니다. 픽셀 비교에서는 문서의 설명과 실제 스크립트를 함께 확인해야 합니다. 비교 도구의 기본 주소는 http://localhost:5173이므로, 이 저장소의 개발 서버에 연결하려면 스킬 안내대로 CAP_URL=http://localhost:5188을 지정합니다.

6-5. TypeScript와 기하: 구멍과 외곽선은 다르다

packages/room-runtime/walls.ts의 wallShape()는 창문처럼 벽 내부에 있는 개구부는 구멍으로, kind가 rect이고 v0가 정확히 0인 개구부는 외곽선의 패인 부분으로 만듭니다. 내부 구멍이 외곽선에 닿는 모양은 기하 생성에서 문제가 될 수 있기 때문입니다. buildWall()은 이 2D 형태를 두께 방향으로 밀어 벽을 만듭니다.

작은 실습: 종이에 같은 직사각형 벽을 두 번 그리고, 창문은 닫힌 내부 사각형으로, 문은 아래 변에서 이어지는 외곽선으로 표시해 보세요. 이후 Opening의 판별 유니언과 o.kind === 'rect' 조건이 모델링 규칙을 어떻게 좁히는지 읽습니다.

6-6. GPU와 렌더링: 한 프레임의 비용을 쪼개기

packages/room-runtime/render.ts의 SceneAOPass.render()는 불투명 표면을 그린 깊이를 AO 계산에 재사용하고, 투명 물체는 그 뒤에 그립니다. 투명 의자 등받이 뒤의 물체가 엉뚱한 어두운 윤곽으로 번지는 문제를 줄이려는 구성입니다. bloom 버퍼 크기도 가로·세로를 절반으로 줄입니다.

apps/room-viewer/src/main.ts의 renderFrame()은 dirty || moved일 때 렌더하고, 움직임이 잦아들면 다음 프레임 예약을 멈춥니다. 자동 품질은 이동 중 픽셀 수를 줄이고 정지하면 최대 기기 픽셀 비율 2로 돌아갑니다. 정적인 방 화면에서 배울 만한 것은 “항상 60번 그리기”보다 “필요할 때 그리기”입니다.

화면이 맞는지, 기능이 동작하는지, 빠르게 그려지는지는 각각 별도의 증거로 확인해야 합니다. 사진 비교는 모양의 근거이고, 타입 검사·테스트는 코드 동작의 근거이며, 고정된 조건의 프레임 측정은 성능의 근거입니다.

7하드웨어 / 시스템 요구사항

읽기, 공개 데모 보기, 개인 공간 캡처의 준비물이 다릅니다
하려는 일필요한 환경확인 근거
소스 읽기텍스트 편집기. LiDAR·Xcode·패키지 설치 불필요정적 소스와 공개 측정 JSON으로 학습 가능
웹 뷰어 개발README 권장 Node.js 22.12+와 npmVite 잠금값의 엔진 범위는 ^20.19.0 || >=22.12.0
공개 뷰어 보기WebGL 2와 GPU 가속을 지원하는 브라우저소스의 WebGLRenderer 사용과 Three.js 공식 문서
웹 걷기 모드마우스 포인터 잠금을 지원하는 브라우저·입력 장치requestPointerLock 미지원이면 버튼 비활성화
새 방 스캔LiDAR가 있는 iPhone·iPad, iOS/iPadOS 17+, 카메라 허용apps/house-capture/project.yml, CaptureReadiness.prepare()
앱 빌드·기기 설치Mac, 적합한 Xcode·iOS SDK, XcodeGen, 개발 서명 설정앱 README와 apps/house-capture/project.yml. XcodeGen 버전 고정 없음
사진 처리Python·Pillow, 캡처 비교에는 zsh·agent-browser각 도구의 import와 실행 파일 선언
깊이 재구성Python·NumPy·Open3D·Pillow, 충분한 메모리·저장 공간.agents/skills/scan-to-threejs/scripts/reconstruct_bundle.py. 최소 RAM 수치는 제시되지 않음

브라우저 조건은 Three.js WebGLRenderer 공식 문서 기준입니다. 모든 GPU·모바일 브라우저에서 같은 프레임 속도가 보장되지는 않습니다.

7-1. 저장 공간과 열 상태는 코드에 들어 있는 제약이다

apps/house-capture/HouseCapture/ReleaseSupport.swift의 CaptureReadiness.reserve는 500_000_000바이트, pauseReserve는 200_000_000바이트입니다. 시작·재개 때 여유 공간을 확인하고, 스캔 중 후자 아래로 내려가면 일시정지합니다. 이 값은 캡처 한 건의 예상 크기가 아니라 디스크 여유분 기준입니다.

CaptureBudget 정책일반 상태제약 상태위험한 열 상태
깊이 저장 간격0.2초0.5초1초
평가 간격0.1초0.3초0.3초
사진 간격 정책값0.4초1.2초1.2초

apps/house-capture/HouseCapture/CaptureQuality.swift는 메모리 압박, 방 사진 150장 이상, 심각하거나 위험한 열 상태를 제약 조건으로 삼습니다. 위 숫자는 작업을 시도할 간격 정책이지, 실제 사진 저장이 그 빈도로 보장된다는 뜻은 아닙니다. 사진에는 추적 안정성·선명도·추가 커버리지 조건도 적용됩니다.

CaptureController.start는 재사용할 지도가 35,000,000바이트 이상이거나 해당 구역에 방이 네 개 이상이면 새 구역을 만듭니다. 무조건 한 세션을 길게 유지하기보다 좌표 연결을 기록하면서 작업 단위를 나누는 선택입니다. 물리 스캔에는 최적화가 켜지는 Release 빌드를 사용하라고 앱 README가 안내합니다.

7-2. 카메라가 끊겼을 때 “계속 저장”은 위험하다

apps/house-capture/HouseCapture/Diagnostics.swift의 TrackingRecovery.update(problem:now:)는 정상 추적이 1.5초 유지돼야 캡처 가능 상태로 되돌립니다. CaptureController.updateRecovery는 프레임이 0.5초 이상 오래됐는지도 확인합니다. 위치를 신뢰할 수 없는 순간에는 사진·깊이·커버리지 입력을 멈추는 방식입니다.

기기와 검증 범위
시뮬레이터 빌드와 실내 스캔 성공은 별개입니다

LiDAR 없는 기기는 가져오기·검토·공유를 할 수 있지만 새 스캔은 할 수 없습니다. 시뮬레이터로 저장 형식과 일부 정책을 확인해도 현장의 추적 실패·열·메모리 문제를 재현했다고 볼 수 없습니다. App Store 배포와 실기기 확인도 별도 단계입니다.

앱의 폴더 가져오기는 manifest.json이 들어 있는 방·세션 번들용이며, 집 전체 폴더 가져오기는 지원하지 않는다고 README가 밝힙니다. 반면 Python의 .agents/skills/scan-to-threejs/scripts/house_to_rooms.py는 집 전체 내보내기를 읽습니다. 비슷한 “가져오기”라는 이름 아래 지원 범위가 다릅니다.

8직접 해볼 수 있는 실습 과제

공개 자료만으로 시작하고, 개인 캡처는 필요한 과제에서 추가하기

아래 과제는 실습용 복사본에서 진행하는 학습안입니다. 원본 캡처가 없는 공개 사무실만으로도 과제 1~3을 시작할 수 있습니다.

과제 1 · 입문 · 추가 장비 없음

방이 표시되기까지의 호출 경로를 한 장으로 그리기

목표: 파일을 읽을 때 “무슨 라이브러리인가”보다 “누가 누구를 부르는가”를 먼저 찾는 연습입니다.

  1. demos/index.ts에서 기본 방 ID와 roomById()의 대체 선택 규칙을 찾습니다.
  2. officeV4.build() → officeLatest.build() → buildDetailedOffice()를 연결합니다.
  3. loadRoom()에서 방의 그룹·조명·참조 자료가 뷰어에 연결되는 지점을 표시합니다.
  4. Scan·Overlay 비활성화의 원인을 referenceAvailable 설정까지 추적합니다.

제출물: 파일 경로 다섯 개 이상과 함수명이 들어 있는 호출 그림, 공개 원본 사진이 없는 이유를 설명하는 세 문장입니다.

완료 기준: 알 수 없는 방 ID를 넣었을 때 무엇이 선택되는지, 폴더가 여러 개여도 공개 선택지가 하나인 이유를 설명할 수 있어야 합니다.

과제 2 · 입문 → 중급 · 웹 개발 환경

가구 하나의 치수를 읽고 작은 수정안을 만들기

목표: 미터 단위 수치와 화면의 물체를 연결합니다. 시작 파일은 demos/office-v2/items/desk.ts입니다.

// 원본 SLAB 상수의 일부
export const SLAB = {
  x0: 0.245, x1: 2.07,
  back: -0.865, top: 0.825, thick: 0.052
};
// 읽기 문제: 좌우 폭은 2.07 - 0.245 = 1.825m
// 설계 문제: 상판 높이를 5cm 올리면 다리·장비도 확인해야 함
  1. 폭·높이·두께를 cm로 바꾸고, 상수의 각 값이 쓰이는 곳을 찾습니다.
  2. 작업용 복사본에서 먼저 책상 부품만 대상으로 변경안을 만듭니다.
  3. 같은 카메라의 전후 화면에서 상판·다리·장비가 서로 떨어지지 않는지 확인합니다.

주의할 점: office-v4는 이후 수정 계층을 적용합니다. 초기 책상 상수를 바꾼 결과가 현재 공개 장면의 최종 치수와 같다고 곧바로 가정하지 말고, demos/office-v4/desk-updates.ts까지 읽어야 합니다.

제출물: 변경 이유, 영향받는 함수 목록, 동일 시점의 전후 이미지 두 장입니다. 장면이 열리는 것만으로 물체 배치 검증을 끝내지 않습니다.

웹 실습을 선택했을 때의 명령 예시

# 독자의 별도 작업용 복사본에서 선택적으로 실행
# README 기준 Node.js 22.12+ 준비 후
npm ci
npm run dev
# 브라우저 주소 입력란: http://localhost:5188

# 타입 검사와 정적 빌드. 시각 정확성은 별도 확인
npm run build
과제 3 · 중급 · Python 코드 읽기

변환 순서를 틀리면 잡아내는 예제를 설계하기

목표: 눈으로 그럴듯한 배치와 수학적으로 검증된 배치를 구별합니다. 기준 파일은 .agents/skills/scan-to-threejs/scripts/test_house_import.py와 .agents/skills/scan-to-threejs/scripts/house_to_rooms.py입니다.

  1. test_active_scan_and_noncommuting_transform의 예상 점 (15, 3.5, 14)를 손으로 계산합니다.
  2. 회전·이동의 순서를 뒤집었을 때 달라지는 좌표를 적습니다.
  3. 새 스캔의 생성 날짜보다 active 참조가 우선돼야 하는 예를 만듭니다.
  4. 정렬하지 않은 세션에서 roomToHouse가 생기면 안 되는 이유를 설명합니다.
입력 상황기대 결과
유효한 정렬과 active 참조ready와 변환된 방 파일
세션 방식이 unalignedblocked, 집 배치 행렬 미제공
출력 폴더가 이미 존재덮어쓰기 거부
집 폴더 밖을 가리키는 세션 경로가져오기 실패 사유 기록

제출물: 정상 예 한 개와 실패 예 세 개의 입력·기대 결과 표입니다. 이후 코드를 수정한다면 이 표를 동작 테스트의 근거로 사용할 수 있습니다.

과제 4 · 중급 → 고급 · 개인 캡처 또는 합성 사진

사진 순서와 회전이 한 번만 적용되는지 확인하기

목표: 모델이 틀린 문제와 비교 도구가 틀린 문제를 분리합니다. 공개 사무실에는 원본 사진이 없으므로, 사진이 필요한 부분은 개인 캡처나 글자·색 띠가 있는 합성 이미지로 준비합니다.

  1. 위쪽에 “위”, 오른쪽에 “오른쪽”을 적은 가로 이미지를 만듭니다.
  2. 사진 메타데이터의 순서를 파일명 정렬과 다르게 정합니다.
  3. prepare()가 배열 순서로 번호를 붙이고 시계 방향으로 한 번 돌리는 과정을 추적합니다.
  4. 개인 방에 photos를 연결한 경우 apps/room-viewer/tools/capture.ts가 자세·시야각·종횡비를 어떻게 쓰는지 확인합니다.

완료 기준: 결과 0000.jpg가 어느 원본인지 설명하고, EXIF 회전을 추가로 적용했을 때 생기는 오류를 그림으로 보일 수 있어야 합니다. 이미지 여백을 임의로 잘라 비교 구도를 바꾸지 않습니다.

과제 5 · 고급 · GPU 측정이 가능한 웹 환경

배칭이 성능과 기능에 주는 영향을 함께 기록하기

목표: “빨라졌다”는 느낌 대신 조건과 관측값을 남깁니다. 읽을 곳은 batchStatic(), apps/room-viewer/tools/capture.ts의 stats 처리, 뷰어의 renderFrame()입니다.

  1. 작업용 복사본에서 같은 카메라·해상도·효과 설정을 고정한 전후 비교를 설계합니다.
  2. 배칭 전후 draw call 수, 삼각형 수, 측정한 프레임 시간 중앙값을 기록합니다.
  3. 벽 숨김과 투명 물체 정렬이 유지되는지 같은 시점에서 확인합니다.
  4. 물리 모드의 동적 물체는 개별로 움직여야 하므로 정적 뷰어와 별도로 검토합니다.

완료 기준: 기기·브라우저·해상도·측정 방법을 함께 적고 전후 이미지까지 제출합니다. 내 기기 결과를 저장소 전체의 보장 FPS로 바꾸어 표현하지 않습니다.

실습의 완성 기준은 코드를 조금 바꾸는 것이 아니라, 어떤 근거로 결과가 맞다고 판단했는지 설명하는 것입니다.

9관련 기술 심화 학습 로드맵

6주 동안 입력 계약에서 검증 가능한 재구성까지

초보자는 처음부터 캡처 앱 전체와 고급 셰이더를 동시에 읽기보다, 매주 한 종류의 입출력을 설명할 수 있게 만드는 편이 좋습니다. 아래 순서는 주 3~5시간 정도를 잡는 학습 제안이며, 프로젝트의 공식 일정이나 수강 시간은 아닙니다.

주차배울 개념읽을 코드남길 결과
1주TypeScript 타입, 장면 그래프, 모듈 packages/room-runtime/types.ts, demos/index.ts, demos/office-v4/index.ts RoomModule 호출 경로와 공개 자료 범위
2주벡터, 행렬, 점·방향, 로컬 좌표 .agents/skills/scan-to-threejs/scripts/bundle_to_room.py, wallPoint(), wallMatrix() 위치와 방향 변환의 손계산 예
3주카메라 투영, 사진 회전, 가려짐 PhotoRecord, .agents/skills/scan-to-threejs/scripts/prepare_photos.py, contribution() 같은 사진 시점을 재현하는 데 필요한 필드 표
4주Swift 상태 머신, 실패 복구, 파일 저장 CaptureController, ScanBundle, TrackingRecovery 정상·추적 상실·저장 실패의 상태 그림
5주작업 큐, 배칭, 깊이 버퍼, 정지 시 렌더 중단 CoverageMailbox, packages/room-runtime/batch.ts, packages/room-runtime/render.ts 성능 변경 한 건의 측정·화면 비교 기록
6주다중 세션 배치, 데이터 검증, 정적 배포 .agents/skills/scan-to-threejs/scripts/house_to_rooms.py, .agents/skills/scan-to-threejs/scripts/test_house_import.py, tools/audit-public.py 합성 입력으로 정상·실패 경계를 설명하는 보고서

9-1. 관심사에 따라 이어 갈 갈래

물리 엔진에 관심이 생기면 packages/room-physics/physics.ts를 읽습니다. loadRapier()는 패키지를 동적으로 읽고 R.init()를 기다립니다. FIXED_DT = 1 / 60, GRAVITY = -9.81처럼 시뮬레이션의 시간·단위도 명시돼 있습니다.

깊이 재구성으로 넘어가면 .agents/skills/scan-to-threejs/scripts/reconstruct_bundle.py의 실제 파서 기본값부터 읽습니다. voxel=0.02, trunc=8, max-depth=3.5, min-confidence=1, faces=0입니다. 파일 맨 위 사용 예의 0.015·400000은 현재 기본값과 다릅니다. 주석보다 실행 인자를 정하는 코드를 먼저 확인하는 습관을 들이세요.

한 주가 끝날 때마다 함수 하나의 입력, 출력, 실패 조건을 자기 말로 설명할 수 있으면 다음 단계로 넘어가도 좋습니다.

공부 순서의 비유

집을 배울 때 수도 배관·벽돌·전기 회로를 한날에 모두 외우기보다, 먼저 한 방의 평면도를 이해하는 것과 같습니다. 작은 방 하나에서 좌표·카메라·저장을 연결할 수 있으면 집 전체로 넓히기가 쉬워집니다.

10핵심 키워드 사전

코드에서 다시 만났을 때 바로 찾아볼 짧은 설명
용어쉬운 설명
LiDAR빛을 이용해 주변 표면까지의 거리를 재는 센서. 사진의 색과 별개로 공간의 깊이를 얻습니다.
RoomPlanApple의 실내 측정 프레임워크. 벽·문·가구 같은 의미와 치수를 함께 제공합니다.
ARSession카메라·센서의 관측과 기기 위치 추적이 이어지는 작업 단위. 이 앱은 하나를 RoomPlan과 공유합니다.
앵커(anchor)추적 좌표계 안에서 특정 공간 정보와 위치를 묶는 기준. 메시 갱신은 앵커별로 관리합니다.
메시(mesh)표면을 정점과 삼각형으로 표현한 것. 점만 모인 점군과는 다릅니다.
법선(normal)표면이 향하는 방향. 빛 반사나 정면 사진인지 판단할 때 사용합니다.
pose위치와 방향을 묶은 자세 정보. 사진 파일만으로는 알 수 없는 촬영 위치를 담습니다.
intrinsics초점거리와 이미지 중심 등 카메라 내부 파라미터. 3D 점을 픽셀로 옮기는 계산에 씁니다.
FOV / aspect카메라가 보는 각도와 이미지 가로세로 비율. 같은 시점 비교의 구도를 결정합니다.
EXIF 방향이미지를 어떻게 돌려 보여 줄지 적는 메타데이터. 실제 픽셀 배열 방향과 다를 수 있습니다.
좌표 등록서로 다른 스캔의 원점과 축을 맞추는 작업. 이름이 같다고 정렬된 것은 아닙니다.
강체 변환크기와 모양을 유지하며 회전·이동만 하는 변환. 스케일 변경이나 찌그러뜨림은 제외합니다.
column-major행렬 수치를 열 단위로 나열하는 저장 순서. 다른 도구와 교환할 때 순서를 맞춰야 합니다.
커버리지이 앱에서는 관측한 표면에 좋은 사진이 확보된 정도. 방 전체를 얼마나 다 스캔했는지와 다릅니다.
가려짐(occlusion)물체가 다른 표면 뒤에 숨어 카메라에 보이지 않는 상태. 숨은 곳에 사진 색을 잘못 칠하지 않게 검사합니다.
PLY / USDZ3D 데이터 형식들. 이 앱의 PLY에는 메시와 커버리지 등이, USDZ에는 내보낸 3D 결과가 들어갑니다.
glTF / GLB3D 장면 교환 형식과 그 바이너리 컨테이너. 여기서는 참조 메시 등에도 사용합니다.
TSDF / voxel여러 깊이 관측을 공간 격자에 모아 표면을 추정하는 방식과 그 격자 단위. 선택적 복원 도구가 사용합니다.
장면 그래프물체를 부모·자식 관계로 묶은 구조. 벽과 부착물을 같은 그룹으로 숨길 수 있습니다.
draw callGPU에 그리기 작업을 요청하는 단위. 작은 물체가 많으면 삼각형 수 외에 요청 횟수도 비용이 됩니다.
배칭 / 인스턴싱여러 기하를 합치거나 같은 기하를 반복 사용해 그리기 요청을 줄이는 방법입니다.
AO / bloom틈과 접촉면을 어둡게 하는 효과, 밝은 부분이 주변으로 번지는 효과입니다.
깊이 버퍼화면의 각 픽셀이 얼마나 먼 표면인지 저장한 값. 가림 처리와 AO 계산 등에 씁니다.
톤 매핑장면의 넓은 밝기 범위를 화면에 표현 가능한 범위로 바꾸는 과정입니다.
dirty 플래그화면이 바뀌어 다시 그려야 한다는 표시. 변화가 없을 때 렌더링을 쉬는 데 씁니다.
메일박스생산자가 넣은 최신 작업 데이터를 소비자가 가져가는 보관함. 여기서는 키별 최신 값으로 대체합니다.
체크포인트 / atomic중간 저장 지점과 파일 교체 방식. 한 파일의 안전한 교체가 전체 번들의 일괄 성공은 아닙니다.
active 스캔방의 여러 기록 중 현재 선택된 기록. 날짜가 가장 최신인 기록과 항상 같지는 않습니다.
잠금 파일실제로 선택된 의존성 버전을 기록하는 파일. 이 레포는 package-lock.json을 포함합니다.
에이전트 스킬에이전트가 따를 작업 절차와 도구 모음. 이 스킬 자체가 학습된 3D 생성 모델은 아닙니다.
정적 배포미리 빌드한 HTML·JS·자산을 제공하는 방식. 이 레포는 GitHub Pages 배포 설정을 갖고 있습니다.

11참고 링크

설명은 고정 커밋에, 추가 학습은 공식 문서에 연결하기

11-1. 공식 저장소와 재현 기준

11-2. 관련 기술의 공식 학습 자료

현재 상태 (2026-10-23 확인)

항목확인 내용출처 / 해석
스타 / 포크629 / 1062026-10-23 GitHub 기준
주 언어SwiftGitHub 메타. 물리적 줄 수 순위와는 별도
생성일2026-10-21공개 저장소 생성일. 내부 작업 시작일의 증명은 아님
최근 push2026-10-222026-10-23 GitHub 기준
분석한 최근 커밋31b2dc9 · 2026-10-22Link the project video from README using thumb-1
전체 커밋 식별자31b2dc9da3fb541e501f2f0936e55b32afb70ee8커밋 31b2dc9의 전체 식별자
최신 GitHub 릴리스없음2026-10-23 GitHub 기준. 소스·데모는 공개돼 있음
코드 규모TypeScript 21,509줄 / Swift 11,779줄 / Python 1,376줄커밋 31b2dc9의 파일 기준. .git 제외, 빈 줄·주석·테스트 포함
파일 규모비 Git 파일 374개커밋 31b2dc9의 파일 수, .git 제외
라이선스 메타NOASSERTION2026-10-23 GitHub 기준. 이용 조건은 아래 라이선스 파일 참고
실제 소스 라이선스코드·스킬은 MIT, 데이터·썸네일은 제외LICENSE와 ASSETS-LICENSE.md
패키지 / 앱 설정 버전웹 0.0.0 / 앱 1.0, 빌드 1설정 파일의 값. 정식 배포 버전이라는 증거는 아님
배포 검증 기록웹 빌드·브라우저 확인·합성 테스트·시뮬레이터 빌드docs/release-status.md 기준

11-3. 소스 사실과 문서 주장의 경계

docs/release-status.md에는 깨끗한 의존성 설치와 웹 빌드, 브라우저 확인, 합성 가져오기 테스트 일곱 개, 서명 없는 Release 시뮬레이터 빌드를 로컬에서 검증했다고 적혀 있습니다. 이는 해당 문서에 기록된 검증 범위이며, 실기기 스캔이나 App Review 성공을 의미하지는 않습니다.

독자가 그대로 재현하려면 외부 도구 버전과 실기기 조건을 별도로 맞춰야 합니다. 특히 Python 선택 의존성과 브라우저 캡처 CLI는 버전이 고정돼 있지 않습니다. 최신 공식 문서는 개념 학습에 쓰고, 이 글의 동작 설명은 고정 커밋 소스로 확인하는 방식이 적절합니다.

쓰기 전에 알아둘 한계
완성된 범용 스캐너보다, 증거를 보존하며 재구성하는 개발 워크플로입니다

자동화의 범위: 변환은 측정 기준선을 마련합니다. 가구의 상세 모양·재질·조명과 여러 세션의 연결에는 모델링과 검토가 필요합니다. Python의 집 배치 출력이 자동 정렬이나 웹 집 전체 탐색을 완성해 주지는 않습니다.

데모와 배포: 공개본은 사무실 하나이며 원본 스캔·사진 모음은 배포하지 않습니다. GitHub 릴리스는 아직 없고, 소스 공개만으로 App Store 배포를 가정할 수 없습니다. 공개 예제만으로 원본 사진 비교를 재현할 수는 없습니다.

측정의 한계: 사진 커버리지는 방의 완전성·기하 정확도와 다릅니다. 센서 추적·가려짐·사진 흐림 때문에 누락이나 오차가 생길 수 있습니다. 중요한 치수는 실제 측정과 대조해야 합니다.

자료와 권리: 개인 공간의 사진·메시를 공개 자산 폴더에 옮기기 전에 내용을 검토하세요. 코드의 MIT 조건을 데모 이미지·상표·작품에 그대로 적용할 수 없습니다. 별도 자산 조건은 학습용 예제 실행·연구와 그 밖의 재사용을 구분하며, 파일별 출처 장부도 아직 완전히 복원되지 않았다고 밝힙니다.

검증 범위: docs/release-status.md의 검증 기록만으로 실기기 추적 복구, 처리 시간, 모델 정확도, 모든 브라우저의 호환성이 보장되지는 않습니다.