# AI 꾸러미 — 등각 SVG 도해 (뒤 → 앞 칠하기 · 면 방향 명암 · 숨은 모서리) — Isometric projection
> 부품을 볼록 다면체(상자 · 원기둥 · 원뿔 · 기둥꼴)로 만들어 등각 투영하고, 뒷면을 버린 뒤 겹치는 부품끼리 「누가 앞인가」를 가르는 평면으로 정해 뒤에서 앞으로 칠하며 면 방향으로 몇 단 명암을 준다 — 맞물린 기어 · 꺾인 관 · 컨베이어가 있는 부품 210개 공장부터 쌓기나무 · 입체도형 · 곡면 그래프까지 한 엔진에서 인쇄해도 또렷한 SVG 로 나온다.  
> 견본: https://ai-techstudio.web.app/#t/i560

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

## 주문서

### 만들어 줘: 등각 SVG 도해 (뒤 → 앞 칠하기 · 면 방향 명암 · 숨은 모서리) — Isometric projection

#### 1. 목표
쌓기나무 · 원기둥 · 원뿔 문제 그림 (r · h 표시, 숨은 모서리 점선)을(를) 등각 SVG 도해로 그려 줘 — 부품을 상자 · 원기둥 · 원뿔 같은 볼록 다면체로 만들어 등각 투영하고, 뒷면을 버린 뒤 뒤에서 앞으로 칠하며 면 방향으로 4단 명암. 분위기는 교과서 — 옅은 면 + 검은 선 + 점선.

#### 2. 핵심 기술 용어
- **Isometric projection** — 세 축이 120° 로 보이는 투영 — 화면 x ∝ x − y, 화면 y ∝ (x + y)/2 − z
- **Painter's algorithm (back-to-front)** — 먼 부품부터 칠해 가까운 것이 덮게 — 깊이 = x + y + z
- **Back-face culling** — 보는 쪽(1, 1, 1)과 등진 면은 그리지 않기 — 볼록 부품은 이것만으로 안쪽 겹침이 없다
- **Faceted / quantized shading** — 면 법선 · 빛 방향의 내적을 몇 단으로 끊은 명암 — 윗면 · 왼쪽 앞 · 오른쪽 앞이 또렷이 다른 색
- **Separating axis / separating plane** — 두 볼록 부품 사이를 가르는 평면 — 그 평면에서 보는 쪽에 있는 부품이 앞
- **Topological sort (depth graph)** — 겹치는 쌍마다 「뒤 → 앞」 화살표를 긋고 위상 정렬 — 고리가 생기면 가장 뒤 것부터 끊기
- **Hidden-line (dashed) rendering** — 두 면 모두 안 보이는 모서리만 점선으로 — 교과서 입체도형 그림
- **Silhouette edge** — 매끈한 옆면(원기둥 · 원뿔)은 보임 ↔ 안 보임이 바뀌는 모서리만 윤곽선으로

#### 3. 환경
- 플랫폼: TypeScript + HTML · CSS · SVG (DOM), 라이브러리 없이 — 움직임은 transform · opacity 로
- 화면: 2D · 브라우저 — PC 와 폰(390px 폭) 모두, 60fps 목표
- SVG 하나에 <polygon> 을 칠하는 순서대로 이어 붙인다. 움직이면 프레임마다 문자열을 새로 만들어 innerHTML — 다각형 수백 개까지 가볍다. 결과를 그대로 .svg 파일로 저장할 수 있게 할 것.

#### 4. 조건
- 부품은 볼록 다면체로만 (오목한 모양은 볼록 조각 여러 개로 나눈다)
- 칠하는 순서는 겹치는 쌍마다 가르는 평면(x · y · z 축 → 면 법선 축)으로 정해 위상 정렬 — 바닥판은 늘 먼저, 못 가르면 기준점 깊이로 두고 얽힘으로 센다
- 긴 부품(관 · 레일 · 난간 · 굴뚝)은 마디로 쪼개고, 감싸는 테 · 날개는 마디 사이에 — 서로 파고드는 부품을 만들지 않기
- 겹침 검사 결과(얽힘 개수)를 화면에 띄우고 켜면 빨간 테로 — 완성 그림은 얽힘 0
- 면 법선은 무게중심 바깥으로 맞춘 뒤 뒷면 버리기 — 꼭짓점 순서(시계 · 반시계)에 기대지 않기
- 명암은 면 방향만으로 몇 단(기본 4) — 그라디언트 · 그림자 필터 쓰지 않기
- 원기둥 · 원뿔 옆면 사이 모서리는 그리지 않고 윤곽만, 숨은 모서리는 점선 (끄고 켤 수 있게)
- 결과는 viewBox 가 있는 SVG 하나 — 그대로 저장 · 인쇄되게

#### 5. 완성 기준 (이게 보이면 성공)
- 공장(부품 200개 넘게)에서 기어 셋이 맞물려 돌고 상자가 컨베이어로 와 프레스에 찍혀도, 어느 순간 · 어느 각도에서도 얽힘 0 이고 겹침이 맞다
- 「겹침 따져 줄 세우기」를 끄면(기준점 깊이만) 기어가 기어 상자 속에 묻히는 등 틀어진다
- 쌓기나무가 하나씩 떨어져 쌓여도 앞 블록이 늘 뒤 블록을 덮는다
- 원기둥 · 원뿔의 뒤쪽 밑면 테두리와 원뿔 높이 · 반지름이 점선으로 보이고, 끄면 사라진다
- 「뒤 → 앞 칠하기」를 끄면(앞 → 뒤로 칠하면) 몸통이 피스톤을 덮는 등 겹침이 틀어져 차이가 바로 보인다
- 명암 단계를 1 로 하면 입체감이 사라지고, 4 면 윗면 · 두 앞면이 또렷이 갈린다
- 돌려 보기를 켜면 장면이 돌며 프레임마다 다시 투영 · 정렬되고, z 축은 늘 뒤 모서리에 선다

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

## 원리
- 투영: 화면 x = ox + (x − y)·cos30°·s, 화면 y = oy + ((x + y)·½ − z)·s. 보는 쪽 방향은 (1, 1, 1) 이라 깊이 = x + y + z 가 클수록 앞이다.
- 부품은 볼록 다면체로만 만든다. 면 법선을 구해(Newell 공식) 무게중심 바깥으로 맞추고, 법선 · (1,1,1) ≤ 0 인 면은 버린다 — 볼록이라 남은 면끼리는 겹치지 않아 부품 안 정렬이 필요 없다.
- 부품끼리 순서: 화면 상자가 겹치는 쌍만 따진다. 먼저 x · y · z 로 갈리는지(3D 상자), 안 되면 두 부품의 면 법선 축으로 갈리는지 본다 — 가르는 평면에서 보는 쪽(1,1,1)에 있는 부품이 앞. 쌍마다 「뒤 → 앞」 화살표를 긋고 위상 정렬, 고리는 가장 뒤 것부터 끊는다. 바닥판은 늘 먼저, 곡면 조각끼리는 바닥 자리(x + y) 순서.
- 겹침 검사: 어떤 축으로도 못 가르는 쌍 = 서로 파고든 부품(굴뚝을 감싼 테 · 탱크 속으로 들어간 관 · 축을 뚫은 팔). 개수를 띄우고 빨간 테로 보여 준다 — 0 이 될 때까지 모양을 쪼개거나 고친다.
- 기준점 깊이(무게중심 x + y + z) 하나로만 줄 세우면 간단한 장면은 되지만, 큰 상자 위 작은 기어처럼 크기가 다른 부품이 섞이면 틀린다 — 견본에서 「겹침 따져 줄 세우기」를 끄면 기어가 상자 속에 묻힌다.
- 명암: k = 법선 · 빛, 이걸 n 단으로 반올림해 밝기를 정한다. 4단이면 윗면 · 왼쪽 앞 · 오른쪽 앞이 확실히 갈리고, 원기둥 옆면은 띠로 나뉘어 도해다운 맛이 난다.
- 모서리 규칙: 각진 면 사이는 한쪽만 보여도 실선, 같은 매끈 묶음(원기둥 옆면) 안은 보임 ↔ 안 보임이 바뀌는 곳(윤곽)만 실선, 두 면 다 안 보이면 점선(숨은 모서리).

## 핵심 코드 — 가르는 평면으로 앞뒤 정하기 + 위상 정렬 + 4단 명암
(발췌: demos/demosIso.ts 의 relate() · orderItems() · render() 를 줄여 정리)
```ts
type V3 = [number, number, number];
// rp = 꼭짓점, N = 바깥 면 법선, lo · hi = 3D 상자, bx = 화면 상자, d = 기준점 깊이
interface Item { rp: V3[]; N: V3[]; lo: V3; hi: V3; bx: number[]; d: number }
const span = (A: Item, n: V3) => { let a = Infinity, b = -Infinity;
  for (const q of A.rp) { const t = q[0] * n[0] + q[1] * n[1] + q[2] * n[2]; a = Math.min(a, t); b = Math.max(b, t); }
  return [a, b]; };

/** A 가 B 뒤면 -1 · 앞이면 1 · 못 가르면 0 (= 얽힘). 보는 쪽은 (1, 1, 1) */
function relate(A: Item, B: Item): number {
  for (let k = 0; k < 3; k++) { if (A.hi[k] <= B.lo[k]) return -1; if (B.hi[k] <= A.lo[k]) return 1; }
  for (const n of [...A.N, ...B.N]) {
    const v = n[0] + n[1] + n[2]; if (Math.abs(v) < 1e-4) continue;
    const [a0, a1] = span(A, n), [b0, b1] = span(B, n);
    if (a1 <= b0) return v > 0 ? -1 : 1;
    if (b1 <= a0) return v > 0 ? 1 : -1;
  }
  return 0;
}

function paintOrder(items: Item[]): Item[] {          // 겹치는 쌍마다 뒤 → 앞 화살표, 위상 정렬
  const out = items.map(() => [] as number[]), indeg = items.map(() => 0);
  items.forEach((A, i) => items.forEach((B, j) => {
    if (j <= i || A.bx[2] <= B.bx[0] || B.bx[2] <= A.bx[0] || A.bx[3] <= B.bx[1] || B.bx[3] <= A.bx[1]) return;
    const r = relate(A, B) || (A.d <= B.d ? -1 : 1);  // 0 이면 얽힘 — 세어 두고 깊이로
    const [from, to] = r < 0 ? [i, j] : [j, i]; out[from].push(to); indeg[to]++;
  }));
  const done = items.map(() => false), list: Item[] = [];
  for (let s = 0; s < items.length; s++) {
    let best = -1;
    for (let k = 0; k < items.length; k++) if (!done[k] && indeg[k] === 0 && (best < 0 || items[k].d < items[best].d)) best = k;
    if (best < 0) for (let k = 0; k < items.length; k++) if (!done[k] && (best < 0 || items[k].d < items[best].d)) best = k; // 고리
    done[best] = true; list.push(items[best]); out[best].forEach((m) => indeg[m]--);
  }
  return list;
}
// 칠하기: 면마다 n·(1,1,1) <= 0 이면 버리고, k = n·빛 을 steps 단으로 반올림해 밝기 → <polygon>
```

## 흔한 실수 · 확인 목록
- [ ] **큰 바닥판이 그 위 작은 부품을 덮는다** — 바닥판의 무게중심은 한가운데라 뒤쪽 볼트보다 「앞」으로 잡힌다. 바닥판은 ground 로 늘 먼저 칠한다.
- [ ] **얹힌 부품이 밑 부품에 가려진다** — 돌아가는 크랭크 팔은 뒤를 향할 때 무게중심 깊이가 뚜껑보다 작아진다. 기준점을 회전축 위로 주면 늘 뚜껑 다음에 칠해진다.
- [ ] **돌려 보면 곡면이 뒤엉킨다** — 곡면 조각을 x + y + z 로 줄 세우면 높은 조각이 앞으로 튄다. 높이 그래프는 바닥 자리(x + y)로만 줄 세운다.
- [ ] **원기둥 옆면에 세로줄이 잔뜩 생긴다** — 옆면 조각 사이 모서리를 다 그려서다. 같은 매끈 묶음 안은 보임이 바뀌는 윤곽만 그리고, 면 테두리는 면과 같은 색으로 덮어 틈을 숨긴다.
- [ ] **돌려 보면 그림이 출렁이며 커졌다 작아진다** — 프레임마다 화면 맞춤을 다시 하면 그렇다. 여러 각도에서 한 번 재서 크기를 고정한다.
- [ ] **큰 부품 위 작은 부품이 묻힌다 (기준점 깊이만 쓸 때)** — 기어 상자의 무게중심이 그 위 기어보다 앞으로 잡혀서다. 겹치는 쌍마다 가르는 평면으로 순서를 정하면 풀린다.
- [ ] **얽힘이 19개 — 서로 파고든 부품** — 굴뚝을 감싼 테 · 바닥 볼트와 울타리 기둥 · 실린더 속으로 1mm 들어간 관 · 굴뚝 테에 닿은 지붕 끝이었다. 테는 마디 사이로, 관 끝은 다각형 겉면 안쪽까지만. 다각형 원기둥은 원보다 안쪽이라 원 기준으로 맞추면 살짝 파고든다.
- [ ] **곡면을 돌리면 모든 조각이 「얽힘」** — 이웃 조각은 모서리만 닿아도 화면 상자가 겹치고 휜 면이라 평면으로 못 가른다. 열린 곡면 조각끼리는 따지지 말고 바닥 자리 순서로.
- [ ] **서로 파고든 부품은 어떤 순서로도 틀린다** — 부품 순서 칠하기의 한계. 부품을 더 작게 쪼개거나(관을 탱크 밖에서 끊기) 겹침 검사로 찾아 고친다 — 안 되면 three.js 깊이 버퍼로.

## 완성 기준 체크리스트
- [ ] 공장(부품 200개 넘게)에서 기어 셋이 맞물려 돌고 상자가 컨베이어로 와 프레스에 찍혀도, 어느 순간 · 어느 각도에서도 얽힘 0 이고 겹침이 맞다
- [ ] 「겹침 따져 줄 세우기」를 끄면(기준점 깊이만) 기어가 기어 상자 속에 묻히는 등 틀어진다
- [ ] 쌓기나무가 하나씩 떨어져 쌓여도 앞 블록이 늘 뒤 블록을 덮는다
- [ ] 원기둥 · 원뿔의 뒤쪽 밑면 테두리와 원뿔 높이 · 반지름이 점선으로 보이고, 끄면 사라진다
- [ ] 「뒤 → 앞 칠하기」를 끄면(앞 → 뒤로 칠하면) 몸통이 피스톤을 덮는 등 겹침이 틀어져 차이가 바로 보인다
- [ ] 명암 단계를 1 로 하면 입체감이 사라지고, 4 면 윗면 · 두 앞면이 또렷이 갈린다
- [ ] 돌려 보기를 켜면 장면이 돌며 프레임마다 다시 투영 · 정렬되고, z 축은 늘 뒤 모서리에 선다

## 이 기술 정보
- id: `i560` · 분류: 물리 · 수학 원리 › 원리 설명 · 구조 · 2D · 난이도 보통 · 폰 부담 보통 (폰 주의) — 부품 20개 장면은 1ms 안. 부품 210개 · 보이는 면 900개 공장은 정렬 1ms + SVG 글자 만들기 5ms 남짓 — 초당 20~30번만 다시 그린다. 더 크면 움직이지 않는 부품을 한 번 굽거나 캔버스로.
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i560
- 쓰면 좋을 때: 수학 문제 그림 — 쌓기나무 · 각기둥 · 원기둥 · 원뿔 · 전개도 · 겉넓이 · 부피 / 개념을 기계에 빗댄 설명 그림, 문서 · 글 · 첫 화면 삽화 / 인쇄 · 확대해도 깨지지 않아야 하는 그림, 파일로 내려받을 그림
- 쓰지 말 때: 보통의 2D 함수 그래프 · 차트 — 평범한 SVG 축 그리기가 낫다 / 서로 파고드는(맞물린) 부품이 많은 장면 — 부품 순서만으로는 못 푼다, 부품을 쪼개거나 three.js 깊이 버퍼로 / 원근감 · 그림자 · 반사가 필요한 사실적인 장면 (i545 경로 추적)

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### isoFigure — `src/demos/demosIso.ts:613`
```ts
function isoFigure(box: HTMLElement): ReturnType<DemoDom['make']> {
  const css = `
    .hd{position:absolute;left:8px;top:6px;white-space:nowrap;font:600 8.5px ${F};color:#1f2733;letter-spacing:.01em}
    .hd b{color:hsl(24 85% 48%);font-weight:700}
    .hd i{font-style:normal;color:#6b7280;font-weight:500}
    .hd em{font-style:normal;color:#dc2626;font-weight:700}
    svg{position:absolute;left:0;top:0;overflow:visible}
    .lb{font:600 7.5px ${F};fill:#1f2733;paint-order:stroke;stroke:#f5f3ee;stroke-width:2.2px;stroke-linejoin:round}`;
  const S = stage(box, css, `<div class="hd"></div><svg width="${SW}" height="${SH}" viewBox="0 0 ${SW} ${SH}"></svg>`, '#f5f3ee');
  const hd = S.st.querySelector('.hd') as HTMLElement;
  const svg = S.st.querySelector('svg') as SVGSVGElement;
  const o = { s: 1, order: true, topo: true, steps: 4, hidden: true, audit: false, spin: false, pin: -1 };
  let clock = 0;
  let wait = 0;
  /** 지금 장면 번호 · 장면 안 시각 */
  const at = (): { si: number; lt: number } => {
    if (o.pin >= 0) return { si: o.pin, lt: clock % SCENES[o.pin]!.dur };
    let m = clock % TOTAL;
    for (let si = 0; si < SCENES.length; si++) {
      if (m < SCENES[si]!.dur) return { si, lt: m };
      m -= SCENES[si]!.dur;
    }
    return { si: 0, lt: 0 };
  };
  const fits = new Map<string, { s: number; ox: number; oy: number }>();
  /** 화면에 맞추기 — 장면의 다 쌓인 모습을, 돌려 보기면 여러 각도에서 재서 한 번 정한다 (프레임마다 바꾸면 출렁인다) */
  const fitOf = (si: number): { s: number; ox: number; oy: number } => {
    const key = si + '|' + (o.spin ? 1 : 0);
    const got = fits.get(key);
    if (got) return got;
    let x0 = Infinity;
    let x1 = -Infinity;
    let y0 = Infinity;
    let y1 = -Infinity;
    const ths = o.spin ? [...Array(12).keys()].map((k) => (k / 12) * Math.PI * 2) : [0];
    for (const lt of [2, 7.5])
      for (const th of ths)
        for (const P of SCENES[si]!.f(lt).parts)
          for (const q of P.p) {
            const [x, y] = iso(rot(q, th), { th, s: 1, ox: 0, oy: 0 });
            x0 = Math.min(x0, x);
            x1 = Math.max(x1, x);
            y0 = Math.min(y0, y);
            y1 = Math.max(y1, y);
          }
    const s = Math.min((BOX.x1 - BOX.x0) / (x1 - x0), (BOX.y1 - BOX.y0) / (y1 - y0)) * 0.92;
    const f = { s, ox: (BOX.x0 + BOX.x1) / 2 - ((x0 + x1) / 2) * s, oy: (BOX.y0 + BOX.y1) / 2 - ((y0 + y1) / 2) * s };
    fits.set(key, f);
    return f;
  };
  const DEFS = `<defs><marker id="ar" viewBox="0 0 6 6" refX="5" refY="3" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M0,0L6,3L0,6z" fill="${INK}"/></marker></defs>`;
  return {
    update(_t, dt) {
      clock += Math.min(dt, 0.1) * o.s;
      wait -= dt;
      if (wait > 0) return;
      wait = box.clientWidth < 480 ? 1 / 20 : 1 / 30;
      const { si, lt } = at();
      const sc = SCENES[si]!.f(lt);
      const cam: Cam = { th: o.spin ? clock * 0.35 : 0, ...fitOf(si) };
      const P = (q: V3): [number, number] => iso(rot(q, cam.th), cam);
      const t0 = performance.now();
      const r = render(sc.parts, cam, { order: o.order, topo: o.topo, steps: o.steps, hidden: o.hidden, ink: sc.ink });
      // 겹침 검사 — 사이를 가르는 평면을 못 찾은(서로 파고든) 부품에 빨간 테
      const audit = o.audit
        ? r.tangled.map(({ bx }) => `<rect x="${(bx[0] - 1).toFixed(1)}" y="${(bx[1] - 1).toFixed(1)}" width="${(bx[2] - bx[0] + 2).toFixed(1)}" height="${(bx[3] - bx[1] + 2).toFixed(1)}" fill="none" stroke="#dc2626" stroke-width=".8" stroke-dasharray="2 1.2"/>`).join('')
        : '';
      svg.innerHTML = DEFS + (sc.under?.(P) ?? '') + r.svg + (sc.over?.(P) ?? '') + audit;
      svg.dataset['ms'] = (performance.now() - t0).toFixed(2);
      const how = !o.order ? '앞 → 뒤 (틀림)' : o.topo ? '겹침 따져 줄 세움' : '기준점 깊이만';
      const tg = o.topo ? (r.tangled.length ? ` · <em>얽힘 ${r.tangled.length}</em>` : ' · 얽힘 0') : '';
      hd.innerHTML = `<b>${sc.name}</b> <i>· 부품 ${sc.parts.length} · 면 ${r.n} · ${how}${tg}</i>`;
    },
    controls: [
      { type: 'button', label: '장면 바꾸기 (공장 · 기계 · 입체도형 · 곡면)', on: () => (o.pin = (at().si + 1) % SCENES.length) },
      { type: 'toggle', label: '뒤 → 앞 칠하기 (끄면 거꾸로)', value: true, on: (v) => (o.order = v) },
      { type: 'toggle', label: '겹침 따져 줄 세우기 (끄면 기준점 깊이만)', value: true, on: (v) => (o.topo = v) },
      { type: 'toggle', label: '겹침 검사 표시 (얽힌 부품 빨간 테)', value: false, on: (v) => (o.audit = v) },
      { type: 'range', label: '면 방향 명암 단계', min: 1, max: 6, step: 1, value: 4, on: (v) => (o.steps = v) },
      { type: 'toggle', label: '숨은 모서리 점선', value: true, on: (v) => (o.hidden = v) },
      { type: 'toggle', label: '돌려 보기 (프레임마다 다시 투영 · 정렬)', value: false, on: (v) => (o.spin = v) },
      speedCtl(o),
    ],
    dispose: () => S.dispose(),
  };
}
```

### i560 견본 항목 — `src/demos/demosIso.ts:701`
```ts
  i560: { kind: 'dom', caption: '부품을 등각 투영해 겹침을 따져 뒤에서 앞으로 칠한 SVG — 작은 공장 · 기계 · 쌓기나무 · 입체도형 · 곡면 그래프', make: isoFigure }
```

## 관련 기술
- 먼저 알면 좋은 기술: [분해도 (exploded view)](https://ai-techstudio.web.app/ai/t/i36.md) `i36` · [기구학 연결 (크랭크 · 인벌류트 톱니 · 캠)](https://ai-techstudio.web.app/ai/t/i37.md) `i37` · [3D 를 따라가는 이름표 · 지시선](https://ai-techstudio.web.app/ai/t/i38.md) `i38`
- 다음에 해 볼 기술: [단면 자르기 (스텐실 단면 · 원뿔 곡선)](https://ai-techstudio.web.app/ai/t/i35.md) `i35` · [선반 회전체 + 장식 (윤곽선 하나로)](https://ai-techstudio.web.app/ai/t/i450.md) `i450` · [영상으로 굽기 (헤드리스 브라우저 → mp4)](https://ai-techstudio.web.app/ai/t/i82.md) `i82`
- 참고 문서: [Anatomy — 개념을 기계로 그리는 등각 SVG 스킬 (MIT)](https://skills.wheresryan.sh/anatomy) · [Anatomy 소스 (GitHub)](https://github.com/wheresryan22/anatomy) · [Wikipedia — Isometric projection](https://en.wikipedia.org/wiki/Isometric_projection) · [Wikipedia — Painter's algorithm](https://en.wikipedia.org/wiki/Painter%27s_algorithm)
