회중시계 속
뒷뚜껑을 연 회중시계 안에서 톱니와 탈진기가 째깍째깍 맞물려 돈다.
프롬프트를 복사해 claude.ai 에 붙여 넣으면 바로 실행됩니다 · Opus 5.5 · Effort High 기준
- 권장 모델
- Claude Opus 5.5 · Effort High
- 화면비
- 화면 맞춤 (넓은 화면 16:9 · 폰 3:4)
- 조작
- 끌어 돌리기 · 두 손가락이나 휠로 확대 · 용두를 끌어 태엽 감기
- 결과물
- HTML 파일 하나 · three.js 를 인터넷에서 불러옵니다
프롬프트
Claude 가 읽는 제작 지시문입니다. 맨 위 [바꿀 곳]의 값을 바꿔 붙여 넣어도 됩니다.
뒷뚜껑을 연 회중시계 속 기계를 three.js 3D 장면으로, HTML 파일 하나로 만들어 줘.
핵심 아이디어: 황동 톱니바퀴 다섯 축과 탈진기가 실제 시계의 톱니 수·비율·위상으로 맞물려 돈다. 1초에 다섯 번 째깍 — 탈진 바퀴가 12°씩 튀고 팔레트 포크가 탁 넘어가며, 그 멈춤이 톱니를 타고 번져 초바퀴가 1.2°씩 걷는다. 아래 좌표·높이·비율은 이미 맞춰 둔 값이니 다시 설계하지 말고 그대로 쓴다.
[바꿀 곳]
- 초당 째깍 beatsPerSec = 5, 밸런스 진폭 balanceAmpDeg = 270
- 태엽 지속 windRunSec = 900, 시작 태엽 startWind = 1
- 자동 카메라 주기 autoCycleSec = 30
- 황동 #d4a94f, 다리판 #e0c27a, 지판 #cbb27a, 강철 #c8ccd2, 푸른 나사 #2a3f78, 루비 #a0102c, 케이스 #d9b871, 바탕 #1d1a17, 받침 천 #2b2621
[크기]
지판 반지름 10, 케이스 바깥 반지름 11.4. 화면 짧은 변 S, k = min(1, 가로/세로). 세로 시야 40° 에서 거리 d 의 짧은 변 폭 = 0.728·d·k.
- 첫 화면 거리 dFull = 36.84 / k → 케이스 지름 22.8 이 0.85 S. 가로 화면(k = 1)만 dFull 에 1.15 를 곱한다(케이스 0.74 S) — 뒤에 누운 뚜껑까지 화면 안에 들어온다.
- 근접 거리 dEsc = 8.79 / k → 탈진 바퀴 지름 3.2 가 0.5 S.
- 확대 범위 d = 2.2 ~ 1.5·dFull. near 0.02, far 400.
[장면]
시계는 받침 천 위에 눕혀 있고 무브먼트가 위(+y)를 본다. 지판 윗면 y = 0, 두께 0.8. 평면 좌표는 (x, z).
각 규약: 회전은 모두 rotation.y, 점 (x, z) 의 각 = atan2(−z, x). 톱니 윤곽은 Shape 로 그려 ExtrudeGeometry(bevelEnabled false, curveSegments 2) 후 geometry.rotateX(−π/2) — 그러면 Shape 의 각이 위 각과 같고 두께가 +y 로 선다.
톱니(모듈 m: 피치 반지름 r = m·z/2, 끝 r+m, 뿌리 r−1.25m, 이 하나 = 뿌리·옆·끝 두 점·옆·뿌리 6점, 이 k 의 중심각 = θ + 2πk/z):
- 배럴 B (−3.055, −3.055): 바퀴 96이 m 0.08, 북(원통) y 0.15~1.35, 톱니는 아래 띠 y 0.15~0.35.
- 센터 C (0, 0): 피니언 12잎 y 0.12~0.40, 바퀴 80이 m 0.08 y 1.45~1.60.
- 3번 T (3.6, 0): 피니언 10잎 y 1.40~1.65, 바퀴 75이 m 0.07 y 0.75~0.90.
- 4번 F (3.6, 2.975): 피니언 10잎 y 0.70~0.95, 바퀴 80이 m 0.05 y 0.30~0.45.
- 탈진 E (1.695, 4.075): 피니언 8잎 y 0.25~0.50, 탈진 바퀴 15이(갈고리 모양 끝) 반지름 1.6 y 1.00~1.12.
맞물림: B→C(m 0.08), C→T(0.08), T→F(0.07), F→E(0.05). 높이가 엇갈려 있어야 겹치는 바퀴끼리 부딪히지 않는다. 바퀴(80·75·96이)는 다섯 살로 속을 파낸다. 축은 강철 원기둥 반지름 0.08, y 0~2.0.
맞물림 위상: 바퀴 A 가 피니언 B 를 물 때 α = A→B 중심선 각,
θB = α + π + π/zB − (θA − α)·zA/zB
배럴 각에서 이 식을 B→C→T→F→E 로 차례로 넣는다. 축마다 따로 속도를 곱해 돌리지 않는다.
째깍: 시계 시간 τ, b = beatsPerSec, k = floor(τ·b), f = min(1, (τ·b − k)/0.2), P = k + easeOutCubic(f). 배럴 각 = −2π·P/144000 → 탈진 바퀴가 째깍마다 12° 튀고 6초에 한 바퀴, 4번 바퀴 60초에 한 바퀴.
탈진기: 포크 축 Pf = (0.770, 5.677), y 1.00~1.10, 강철 막대(Pf→E 쪽 0.9, 반대쪽 1.6). 루비 돌 둘(0.12×0.22×0.10 상자)이 E 중심 반지름 1.62, 중심선 E→Pf 에서 ±30° 자리 — 포크의 자식. 포크 각 = ±8°, 째깍마다 부호가 바뀌며 같은 easeOutCubic(f) 로 넘어간다.
밸런스: 중심 Bal = (−0.230, 7.409), 황동 테 반지름 2.0 굵기 0.15(TorusGeometry), y 2.25, 살 셋, 테 위 작은 나사 8개, 축 y 1.0~2.9. 각 = A·sin(2π·(b/2)·τ), A = balanceAmpDeg·(0.55+0.45·wind)°.
헤어스프링: Bal 위 y 2.45, 10바퀴 나선, 반지름 0.25→1.2, 300점 Line, 매 프레임 점을 다시 쓴다: u(0~1) 의 각 = 2π·10·u + 밸런스각·(1−u).
다리판(금도금 #e0c27a): 배럴 덮개 원판(B, 반지름 3.6, y 1.70~1.95), 3번·4번 축을 잇는 띠(폭 0.9, y 1.75~1.95), 밸런스 받침 팔(+z 가장자리 → Bal, y 2.75~2.95). 다리판을 지나는 축 끝마다 루비 베어링 하나 — 납작한 돔(SphereGeometry 반지름 0.2 위 반구를 y 0.5배, 금 테 Torus 두름). 둥근 면이라 어느 시점에서도 머리 위 빛이 작은 점으로 맺힌다. 포크의 루비 돌도 모서리를 살린 상자 그대로. 다리판 고정 푸른 나사 6개. 탈진 바퀴와 포크 위는 덮지 않는다.
감기: 배럴 덮개 위 y 1.95~2.15 에 래칫 바퀴(60이, 반지름 2.4, 중심 B).
케이스: LatheGeometry 테(안 10.1, 바깥 11.4, y −1.8~1.2). 용두 (0, −0.2, −12.2): 반지름 0.9 원통(옆면 홈 24개) + 고리 Torus. 뒷뚜껑: +z 쪽 경첩 (0, −1.7, 11.4) 에서 180° 젖혀 받침 위에 안쪽 면이 위로 눕는다 — 중심 (0, −1.75, 22.8), 반지름 11.3, 두께 0.1, 둘레 테 높이 0.6. 세워 두지 않는다: 선 뚜껑은 어두운 큰 원판이 되어 화면을 막고, 반대편에서 보면 탈진기를 가린다. 안쪽 면은 512 캔버스에 2px 간격 동심 링을 밝게/어둡게 번갈아 그린 roughnessMap(0.15/0.4)으로 결이 보이게.
받침: 반지름 60 원판.
태엽 wind(시작 startWind)는 τ 가 흐를 때 초당 1/windRunSec 줄고, 0이면 τ 가 멈춘다.
[카메라]
세로 시야 40°. 위치 = 바라보는 점 + d·(cos pitch·sin yaw, sin pitch, cos pitch·cos yaw).
- 첫 화면: yaw = yaw0, pitch 0.95, d = dFull, 바라보는 점 H = (0, 0.8, zH), zH = min(11.3, 20·(1−k)) — 가로 화면은 0, 폰 세로(k ≈ 0.46)는 약 10.8 이라 케이스와 뒤에 누운 뚜껑이 세로 화면 위아래를 함께 채운다(아래가 빈 천으로 남지 않는다). 카메라가 용두 쪽(−z)에 있어 용두가 화면 아래, 뚜껑이 위. 첫 프레임부터 톱니가 돈다.
- yaw0 = 가로 화면(k = 1) 2.6, 세로 화면(k < 1) π. 까닭: 비스듬한 yaw 에서 바라보는 점을 뚜껑 쪽(+z)으로 옮기면 케이스가 화면 옆으로 밀려 오른쪽이 잘린다. 정면(π)이면 뚜껑이 케이스 바로 위에 서서 둘이 세로로 쌓이고, 좌우는 케이스 지름 0.85 S 안에 든다.
- 바라보는 점: s = 0~1 로 자른 (dFull − d)/(dFull − dEsc) 의 smoothstep 만큼 H 에서 Q = (1.232, 1.05, 4.876)(E 와 Pf 사이)로 옮긴다.
- 자동(주기 30초 안 시각 c): yaw = yaw0 + A·sin(2π·t/60), A = 가로 화면 0.6 · 세로 화면 0.25(세로에서 크게 돌면 다시 옆이 잘린다). d: c 0~1.5 dFull, 1.5~6.5 easeInOutCubic 으로 dEsc, 6.5~12 머묾, 12~17 dFull 로, 17~30 머묾. pitch 도 같은 구간에서 0.95 ↔ 0.62.
- 시그니처: t = 6.5초, 화면 가운데 탈진 바퀴가 0.5 S, 루비 돌이 0.2초마다 탁 넘어가며 빛을 받고 뒤로 초바퀴 톱니가 1.2°씩 걷는다.
- 끌기: 가로 px × 0.006 = yaw, 세로 px × 0.005 = pitch(0.25~1.45). 두 손가락 벌림은 거리 비율로 d 를 나누고, 휠은 d·exp(deltaY·0.0012). 마지막 입력 3초 뒤 1.5초 동안 자동 값으로 섞여 돌아간다.
- 용두 감기: pointerdown 이 용두(반지름 1.6 보이지 않는 구)에 맞으면 감기 모드. 끈 px(가로+세로) × 0.02 = 용두 회전, 양수만 wind += 회전/(2π)·0.04 (최대 1). 래칫 각 = 용두 회전 × 0.5.
- 첫 6초 화면 아래 가운데(아래에서 24px) 안내(13px, #e8dcc4, 불투명 0.8, 뒤에 rgba(20,17,14,0.55) 둥근 띠), 이후 1초에 사라짐. 화면 너비 ≥ 560px 이면 한 줄 「끌어 돌리기 · 두 손가락이나 휠로 확대 · 용두를 끌어 태엽 감기」, 그보다 좁으면 두 줄 — 「끌어 돌리기 · 두 손가락으로 확대」 / 「용두를 끌어 태엽 감기」. 줄마다 span 하나에 white-space: nowrap 을 걸어 낱말 중간에서 끊기지 않게 한다(폰에서 「태/엽」으로 쪼개졌다). 띠 너비는 글자에 맞추고 max-width 92vw. 안내 div 는 position fixed, z-index 10, pointer-events none, 캔버스는 z-index 0 — 캔버스를 나중에 body 에 붙여도 안내가 그 위에 보여야 한다. 이 한 줄이 용두 감기를 알려 주는 유일한 길이다.
[빛]
- outputEncoding sRGBEncoding, ACESFilmicToneMapping 노출 1.0, shadowMap PCFSoft. background #1d1a17, 안개 없음(세로 화면은 카메라가 80 넘게 멀어 흐려진다).
- 환경맵 = 머리 위 큰 소프트박스 스튜디오. 512×256 캔버스(세로 0 = 바로 위, 0.5 = 수평선):
세로 0~0.33(고도 30°~90°) 전체를 소프트박스로 — 가운데 #f6ead2 에서 세로 0.33 쪽 #b8a68a 로 그러데이션, 그 안에 어두운 세로 틈 둘(가로 0.30~0.34, 0.66~0.70, #4a4038). 세로 0.33~0.5 는 #7a6e60 → #3e362e, 아래 반구 #2a241e.
까닭: 카메라 기울기가 0.62~0.95 rad(35°~54°)라 위를 보는 평평한 황동 면은 고도 35°~54° 하늘을 비춘다 — 그 자리가 밝아야 첫 화면부터 금빛이 나고 푸른 나사·래칫이 검게 죽지 않는다. 틈 둘은 도는 톱니 위로 반짝임이 지나가게 한다.
CanvasTexture(EquirectangularReflectionMapping, sRGBEncoding) → PMREMGenerator.fromEquirectangular → scene.environment.
- 주광 DirectionalLight #fff1d6 세기 1.8(밝아진 환경맵과 합쳐 지판이 하얗게 타지 않게), (−8, 18, −6), 그림자 2048, 그림자 카메라 ±13, bias −0.0004. 채움 HemisphereLight #d9e2ef / #2a2018 세기 0.25.
- 재질 MeshStandardMaterial(루비만 MeshPhysicalMaterial): 황동 metal 1 rough 0.22 envMapIntensity 1.5 / 다리판 metal 1 rough 0.28 / 지판 metal 1 rough 0.45 / 강철 metal 1 rough 0.15 / 푸른 나사 metal 1 rough 0.2 / 케이스 metal 1 rough 0.12 envMapIntensity 1.6 / 뚜껑 안쪽 metal 1 rough 0.15~0.4(링 맵) / 루비 #b0122f metal 0 rough 0.06 clearcoat 1 clearcoatRoughness 0.02 envMapIntensity 3 / 받침 metal 0 rough 0.95. 래칫은 강철.
[구현]
- HTML 파일 하나로 만든다. 라이브러리는 three.js 한 줄만 쓴다: <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> — 이 r128 판에 없는 것(모듈 import, OrbitControls 같은 확장 파일, CapsuleGeometry, outputColorSpace)은 쓰지 않는다. 그 밖의 바깥 파일·글꼴·네트워크 요청은 쓰지 않는다.
- three.js 다음 <script> 맨 앞에 CONFIG 객체를 두고 [바꿀 곳]의 값과 이 글에 나온 숫자(크기·색·빛·속도)를 이름 붙여 모은다.
- 장면은 requestAnimationFrame 의 타임스탬프로 구한 시간 t(초)로 진행한다. 한 프레임에 밀린 시간은 0.1초까지만 반영한다. setTimeout·setInterval·CSS 애니메이션으로 움직이지 않는다.
- 난수는 시드 고정 난수(mulberry32 등)만 쓴다. Math.random 은 쓰지 않는다.
- 텍스처는 코드로 만든다(CanvasTexture·DataTexture). 바깥 그림 파일을 쓰지 않는다.
- 캔버스는 늘 창 전체 크기다. 창 크기가 바뀌면 카메라 비율과 렌더러 크기를 다시 맞춘다. 스크롤이 생기지 않게 한다. renderer.setPixelRatio 는 2 까지만.
- 입력이 없으면 카메라가 스스로 천천히 움직인다. 마우스로 끌거나 손가락으로 끌면 시점이 돈다(가로 = 좌우 회전, 세로 = 위아래 기울기, 범위 제한). 손을 떼고 3초 뒤 자동 움직임으로 부드럽게 돌아간다. 터치는 페이지 스크롤·확대가 일어나지 않게 막는다(touch-action: none).
- 시점 각도를 window.__view = { yaw, pitch } (라디안)에 두고 매 프레임 갱신한다. 카메라는 이 두 각도로 놓는다 — 자동 움직임도 끌기도 이 각도를 바꾼다.
- 브라우저 저장소(localStorage·sessionStorage·IndexedDB·쿠키)를 쓰지 않는다 — 미리보기 칸에서 막혀 오류가 난다.
- 탭이 숨으면 멈추고 돌아오면 이어서 한다.
- WebGL 을 못 쓰는 기기에서는 오류 대신 화면에 짧은 안내 문구를 띄운다.
- 글자를 쓰면 시스템 글꼴만: 'Pretendard', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif.
- 모든 함수·상수를 먼저 정의하고, 시작은 스크립트 맨 끝에서 한 번만 부른다.
- 콘솔에 오류가 나지 않게 한다. 스스로 대조하는 표는 console.table, 어긋남은 console.warn 으로만 낸다.
이 작품만의 구현:
- 짜임: gearShape(z, m, spokes) · escapeShape() · trainAngles(P) (배럴 각 → 위상식 넷) · tick(τ) → P·포크각·밸런스각 · updateHairspring() · updateCamera() · selfCheck(). 메시는 한 번만 만들고 매 프레임 rotation.y 만 바꾼다. 나사·루비 베어링은 geometry 하나를 나눠 쓴다.
- 삼각형 15만 안팎(30만 이하), 그림자 한 장, 후처리 없음. 전체 코드는 짧고 곧게 — 장식 부품을 더하지 않는다.
- Pointer Events, 손가락 둘이면 확대만. 휠은 passive: false. 용두 판정은 pointerdown 에서 Raycaster 한 번.
- selfCheck: 시작 때 console.table 로 「째깍/초 = 5」「탈진 바퀴 한 바퀴 = 6초」「4번 바퀴 한 바퀴 = 60초(trainAngles(300) − trainAngles(0) 의 4번 각 차 = 2π)」「삼각형 < 300000」을 내고, 벗어난 줄은 console.warn.
[하지 말 것]
- 축마다 따로 속도를 곱해 돌리지 않는다 — 위상이 어긋나 이가 파고든다.
- 탈진 바퀴를 매끄럽게 돌리지 않는다 — 째깍 멈춤이 맥박이다.
- 블룸·렌즈 플레어·입자·발광(emissive)을 넣지 않는다 — 금속과 루비의 반사를 덮는다.
- 바늘·문자판·상표·화면 라벨을 넣지 않는다 — 뒤쪽 기계 장면이고 글자는 안내 한 줄뿐.
- 소리를 넣지 않는다 — 자동 재생이 막혀 오류가 난다.
- 톱니를 텍스처 그림으로 때우지 않는다 — 확대하면 이가 입체로 보여야 한다.
- 바깥 글꼴·그림·HDR 파일을 쓰지 않는다 — 환경맵도 캔버스로 그린다.
- 환경맵 위쪽을 어둡게 두지 않는다 — 금속은 비추는 것만큼만 빛나서, 위가 어두우면 황동이 탁한 갈색, 나사·래칫이 검은 덩어리가 된다.
사용 방법
필요한 것: claude.ai 계정 — 무료 계정에서도 Claude Opus 5.5 를 고를 수 있습니다
- claude.ai 에서 새 대화를 엽니다.
- 모델 메뉴에서 Claude Opus 5.5 를 고르고, 같은 메뉴의 Effort(답변 깊이)를 High 로 둡니다.
- 복사한 프롬프트를 붙여 넣고 전송합니다. 결과로 나온 HTML 파일을 브라우저로 열면 바로 해 볼 수 있습니다.
추가 요청 예시
결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.
- 케이스를 은색으로 바꾸고 다리판을 금빛으로 해 줘
- 째깍 소리를 넣고 소리 끄기 단추를 달아 줘
- 째깍 빠르기를 1초에 여섯 번으로 바꿔 줘
3D