# AI 꾸러미 — 층 벗기기 (해부) — Layer peeling (cutaway by transparency)
> 겹친 공 껍질을 바깥부터 한 겹씩 투명하게 바꿔, 지각 → 맨틀 → 외핵 → 내핵처럼 속 구조를 슬라이더 하나로 벗겨 본다.  
> 견본: https://ai-techstudio.web.app/#t/i43

이 문서를 코드 도우미(Claude Code · Cursor · ChatGPT 등)에 그대로 주면 돼요. 「## 주문서」 가 할 일, 나머지는 참고 자료예요.

## 주문서

### 만들어 줘: 층 벗기기 (해부) — Layer peeling (cutaway by transparency)

#### 1. 목표
지구 내부 (지각 · 맨틀 · 핵)을(를) 층 벗기기로 보여 줘 — 바깥 껍질부터 한 겹씩 투명해지며 속 층이 드러나고, 옆에 층 이름과 남은 불투명도(%) 막대가 보이게. 분위기는 어두운 우주 바탕.

#### 2. 핵심 기술 용어
- **Layer peeling (cutaway by transparency)** — 바깥 층부터 투명하게 벗기기
- **Transparent sorting (renderOrder · depthWrite)** — 투명 층 그리는 순서 · 깊이 쓰기 끄기
- **Nested shells** — 크기가 다른 껍질을 겹쳐 둔 모형

#### 3. 환경
- 플랫폼: three.js r186 (ES 모듈 · TypeScript, `import * as THREE from "three"`), WebGL2, 외부 라이브러리 추가 없이
- 화면: 3D · 브라우저 — PC 와 폰(가로 844×390 · 세로 390×844) 모두, 60fps 목표

#### 4. 조건
- 층마다 따로 재질 (transparent: true) — 같은 재질을 나눠 쓰지 않기
- 벗기기는 바깥 층부터 차례로 (peel − i 를 0~1 로 잘라 쓰기)
- 불투명할 때만 depthWrite, 투명해지면 끄기 · renderOrder 로 바깥을 나중에
- 옆에 층 이름 · 불투명도 % 막대, 슬라이더 「벗긴 층 수」와 저절로 켬/끔

#### 5. 완성 기준 (이게 보이면 성공)
- 슬라이더를 0 → 3 으로 끌면 지각 → 맨틀 → 외핵 차례로 투명해지고 빛나는 내핵이 드러난다
- 벗겨진 층도 옅은 윤곽이 남아 크기 비교가 된다
- 투명한 층 뒤로 안쪽 층이 깨지거나 사라지는 곳이 없다
- 층 막대의 % 가 실제 투명도와 같이 줄어든다

#### 6. 진행 방식
- 핵심 코드 위주로, 설명은 짧게. 내 프로젝트에 끼워 넣기 쉬운 함수 · 클래스로 나눠 줘.
- 처음 화면에 바로 결과가 보이게, 그리고 켬/끔(또는 전/후) 비교를 할 수 있게 만들어 줘.
- 그림 · 소리 · 모델 파일이 필요하면 코드로 만든 임시 대체물로 먼저 돌아가게 하고, 진짜 파일로 바꿀 자리를 표시해 줘.
- 마지막에 「확인 방법」(무엇을 보면 성공인지)과 「조절할 값」 목록을 짧게 정리해 줘.
- 답변과 코드 주석은 한국어로 해 줘.

## 원리
- 반지름이 다른 공 네 개(1 · 0.88 · 0.55 · 0.3)를 겹쳐 둔다. 바깥은 지구 무늬, 안쪽은 붉은 맨틀 · 주황 외핵 · 빛나는 내핵(emissive).
- 「벗긴 층 수」 peel 은 0 ~ 3 의 실수. 층 i 의 불투명도 = 1 − clamp(peel − i, 0, 1) × 0.92 — 한 층이 다 벗겨진 뒤 다음 층이 벗겨진다.
- 거의 다 벗긴 층도 8% 는 남겨 윤곽이 보인다. 가장 안쪽(내핵)은 늘 불투명.
- 투명 층은 바깥이 나중에 그려지도록 renderOrder = 10 − i, 불투명도가 0.97 아래면 depthWrite 를 꺼 안쪽이 가려지지 않게 한다.

## 핵심 코드 — 겹친 껍질 + 바깥부터 차례로 투명하게
(발췌: demos/demosStructure.ts i43 make() 를 정리)
```ts
import * as THREE from 'three';
const layers = [
  { name: '지각', r: 1.0, mat: new THREE.MeshStandardMaterial({ map: earthTex, roughness: 0.7, transparent: true }) },
  { name: '맨틀', r: 0.88, mat: new THREE.MeshStandardMaterial({ color: 0xd8502a, emissive: 0x6a1a08, roughness: 0.6, transparent: true }) },
  { name: '외핵', r: 0.55, mat: new THREE.MeshStandardMaterial({ color: 0xffa23a, emissive: 0xa04a00, roughness: 0.5, transparent: true }) },
  { name: '내핵', r: 0.3, mat: new THREE.MeshStandardMaterial({ color: 0xfff2b0, emissive: 0xffd060, emissiveIntensity: 0.8, roughness: 0.4, transparent: true }) },
];
const earth = new THREE.Group();
layers.forEach((L, i) => {
  const m = new THREE.Mesh(new THREE.SphereGeometry(L.r, 48, 32), L.mat);
  m.renderOrder = 10 - i; // 바깥 층을 나중에 그린다
  earth.add(m);
});
scene.add(earth);

// peel: 0 ~ 3 (벗긴 층 수, 슬라이더 또는 자동)
function setPeel(peel: number) {
  layers.forEach((L, i) => {
    const o = i < 3 ? 1 - Math.min(1, Math.max(0, peel - i)) * 0.92 : 1; // 8% 는 윤곽으로 남김
    L.mat.opacity = o;
    L.mat.depthWrite = o > 0.97; // 투명해지면 깊이를 안 써서 안쪽이 보이게
  });
}
```

## 흔한 실수 · 확인 목록
- [ ] **투명 층이 깊이를 쓰면 안쪽 층이 통째로 안 보인다** — 불투명도가 1 아래로 내려가면 depthWrite 를 끈다.
- [ ] **그리는 순서가 섞여 투명 층이 깜박이거나 안쪽이 바깥 위에 그려진다** — renderOrder 로 안쪽부터 바깥 순서를 고정한다.
- [ ] **완전히 0 까지 투명하게 하면 층 크기를 비교할 수 없다** — 0.08 정도 남겨 옅은 윤곽을 둔다.

## 완성 기준 체크리스트
- [ ] 슬라이더를 0 → 3 으로 끌면 지각 → 맨틀 → 외핵 차례로 투명해지고 빛나는 내핵이 드러난다
- [ ] 벗겨진 층도 옅은 윤곽이 남아 크기 비교가 된다
- [ ] 투명한 층 뒤로 안쪽 층이 깨지거나 사라지는 곳이 없다
- [ ] 층 막대의 % 가 실제 투명도와 같이 줄어든다

## 이 기술 정보
- id: `i43` · 분류: 물리 · 수학 원리 › 원리 설명 · 구조 · 3D · 난이도 보통 · 폰 부담 가벼움 (폰 OK) — 공 4개(48×32 조각). 투명 층이 겹쳐 같은 픽셀을 4번 칠하지만 화면 일부라 폰도 가볍다.
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i43
- 쓰면 좋을 때: 겉으로 안 보이는 속 구조를 설명할 때 / 층 · 껍질이 차례로 있는 대상 (지구 · 몸 · 양파) / 슬라이더 하나로 겹겹이 보여 주고 싶을 때
- 쓰지 말 때: 한 단면만 보면 될 때 — 잘라 낸 단면(클리핑 평면)이 더 또렷하다 / 부품이 옆으로 흩어져야 할 때 — 대신 분해도(i36)

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### i43 견본 항목 — `src/demos/demosStructure.ts:4535`
```ts
  i43: {
    kind: '3d',
    caption: '지구 껍질을 한 겹씩 투명하게 — 지각 → 맨틀 → 외핵 → 내핵 (해부 「피부 → 근육 → 뼈」와 같은 방법)',
    make() {
      const scene = darkScene();
      const earth = new THREE.Group();
      scene.add(earth);
      const cv = document.createElement('canvas');
      cv.width = 512;
      cv.height = 256;
      const cg = cv.getContext('2d')!;
      cg.fillStyle = '#2f7fd6';
      cg.fillRect(0, 0, 512, 256);
      let seed = 11;
      const rnd = (): number => ((seed = (seed * 16807) % 2147483647) / 2147483647);
      for (let i = 0; i < 26; i++) {
        const x = rnd() * 512;
        const y = 50 + rnd() * 156;
        cg.fillStyle = rnd() > 0.3 ? '#5bbf5a' : '#d9c27a';
        for (let k = 0; k < 9; k++) {
          cg.beginPath();
          cg.ellipse(x + (rnd() - 0.5) * 70, y + (rnd() - 0.5) * 40, 10 + rnd() * 26, 8 + rnd() * 16, rnd() * 3, 0, TAU);
          cg.fill();
        }
      }
      cg.fillStyle = '#f4f8ff';
      cg.fillRect(0, 0, 512, 18);
      cg.fillRect(0, 238, 512, 18);
      const tex = new THREE.CanvasTexture(cv);
      tex.colorSpace = THREE.SRGBColorSpace;
      const layers = [
        { name: '지각', r: 1.0, mat: new THREE.MeshStandardMaterial({ map: tex, roughness: 0.7, transparent: true }) },
        { name: '맨틀', r: 0.88, mat: new THREE.MeshStandardMaterial({ color: 0xd8502a, emissive: 0x6a1a08, roughness: 0.6, transparent: true }) },
        { name: '외핵', r: 0.55, mat: new THREE.MeshStandardMaterial({ color: 0xffa23a, emissive: 0xa04a00, roughness: 0.5, transparent: true }) },
        { name: '내핵', r: 0.3, mat: new THREE.MeshStandardMaterial({ color: 0xfff2b0, emissive: 0xffd060, emissiveIntensity: 0.8, roughness: 0.4, transparent: true }) },
      ];
      layers.forEach((L, i) => {
        const m = new THREE.Mesh(new THREE.SphereGeometry(L.r, 48, 32), L.mat);
        m.renderOrder = 10 - i;
        earth.add(m);
      });
      const cam = new THREE.PerspectiveCamera(32, 1, 0.1, 50);
      const hud = new Hud();
      let auto = true;
      let manual = 0;
      let peel = 0;
      return {
        scene,
        camera: cam,
        update(t) {
          peel = auto ? pingpong(t, 10) * 3 : manual;
          layers.forEach((L, i) => {
            const o = i < 3 ? 1 - clamp(peel - i, 0, 1) * 0.92 : 1;
            L.mat.opacity = o;
            L.mat.depthWrite = o > 0.97;
          });
          earth.rotation.y = t * 0.3;
          earth.rotation.z = 0.4;
        },
        resize(w, h) {
          const tn = Math.tan((cam.fov * Math.PI) / 360);
          const d = Math.max(1.25 / tn, 1.6 / (tn * (w / h)));
          cam.position.set(0, 0.2, d);
          cam.lookAt(0.25, 0, 0);
        },
        render(r, w, h) {
          r.render(scene, cam);
          hud.draw(r, w, h, `${w}x${h}${peel.toFixed(2)}`, (g) => {
            const s = fsz(w, h, 0.8);
            const x = w - s * 7.5;
            layers.forEach((L, i) => {
              const y = h / 2 - s * 3.6 + i * s * 1.9;
              const o = L.mat.opacity;
              g.fillStyle = 'rgba(255,255,255,0.12)';
              g.beginPath();
              g.roundRect(x, y, s * 6.8, s * 1.4, s * 0.7);
              g.fill();
              g.fillStyle = `rgba(255,200,80,${0.25 + o * 0.7})`;
              g.beginPath();
              g.roundRect(x, y, Math.max(s * 1.4, s * 6.8 * o), s * 1.4, s * 0.7);
              g.fill();
              g.fillStyle = '#fff';
              g.font = `700 ${s * 0.9}px ${FONT}`;
              g.textBaseline = 'middle';
              g.textAlign = 'left';
              g.fillText(`${L.name} ${Math.round(o * 100)}%`, x + s * 0.5, y + s * 0.72);
            });
          });
        },
        controls: [
          { type: 'toggle', label: '저절로', value: true, on: (v) => (auto = v) },
          { type: 'range', label: '벗긴 층 수', min: 0, max: 3, step: 0.01, value: 0, on: (v) => ((auto = false), (manual = v)) },
        ],
        dispose() {
          disposeScene(scene);
          hud.dispose();
        },
      };
    },
  }
```

## 관련 기술
- 먼저 알면 좋은 기술: [3D 를 따라가는 이름표 · 지시선](https://ai-techstudio.web.app/ai/t/i38.md) `i38`
- 다음에 해 볼 기술: [분해도 (exploded view)](https://ai-techstudio.web.app/ai/t/i36.md) `i36` · [단계 이야기 (scrollytelling · 다음 단추)](https://ai-techstudio.web.app/ai/t/i45.md) `i45`
- 참고 문서: [Zygote Body (층 벗기기 예)](https://www.zygotebody.com/)
