posts

웨이퍼맵 3D 설계 문서 - Three.js 기반 완전 신규 구현

Oct 1, 2025 updated Oct 1, 2025 3darchitectureawscsstypescript

프로젝트 개요

Three.js를 사용한 완전히 새로운 3D 웨이퍼맵 시각화 시스템을 설계합니다. 기존 2D 웨이퍼맵과는 독립적으로 개발되며, 3차원 웨이퍼 데이터를 전용으로 처리하는 Shot Mode와 Focus Spot Mode를 지원하는 고성능 3D 시각화 솔루션입니다.

핵심 설계 원칙

  • 완전 신규 구현: 2D 시스템 확장이 아닌 처음부터 3D 전용으로 설계
  • 3D 전용 데이터: 별도로 제공되는 3차원 웨이퍼 데이터 인터페이스 활용
  • Three.js 네이티브: WebGL 기반 고성능 3D 렌더링
  • 모듈화 아키텍처: 각 기능별 독립적 구현 및 확장성 보장

기능 요구사항 분석

Common 기능

기능명 기능정의 난이도 구현 방안
Wafer 웨이퍼를 원형으로 가정하여 중심을 기준으로 캔버스에 렌더링 Three.js CylinderGeometry로 원형 웨이퍼 구현
Scene 원점 및 중앙 가이드라인 렌더링, 줌/회전 지원 Three.js Scene, Camera, Controls 기본 설정
Mode Shot 모드와 Focus Spot 모드 지원 상태 관리 시스템으로 모드별 렌더링 분기
Toolbar 모드 전환, 시점 선택, 이미지 캡처 등 React 컴포넌트로 UI 구성, Three.js와 연동

Shot Mode 기능

Shot Mode 렌더링 프로세스 다이어그램

기능명 기능정의 난이도 구현 방안
카메라 시점 기본 시점: Top View Three.js Camera 위치 및 각도 설정
Shot 그리기 120~130개 사각형 Shot 배치, 25단계 색상 스펙트럼 InstancedMesh로 성능 최적화, 색상 매핑
Shot 선택 클릭 시 하이라이트, 보라색 선택 상태 Raycasting으로 선택 감지, Material 변경
텍스트 삽입 Shot 중앙에 번호 표시 Three.js Sprite 또는 CSS3DRenderer 활용

Focus Spot Mode 기능

Focus Spot Mode 렌더링 프로세스 다이어그램

기능명 기능정의 난이도 구현 방안
Focus Spot 그리기 다각형 형태 Focus Spot, 타입별 색상 구분 Custom Geometry로 다각형 생성, 타입별 Material
툴팁 Mouseover 툴팁, 데이터 인터페이스 DOM 오버레이 또는 CSS3DRenderer
영역 선택 마우스 드래그로 영역 선택/해제 최상 복잡한 선택 알고리즘, 3D 공간에서의 영역 계산
Toolbar 선택 영역 형태 (사각형, 원형, 다각형) 3D 공간에서의 기하학적 선택 도구 구현

Legend 기능

기능명 기능정의 난이도 구현 방안
Legend 그리기 Scene 우측에 Legend 렌더링 HTML/CSS 오버레이 또는 3D 텍스처
선택된 Shot 표시 선택된 Shot의 value를 Legend에 표시 상태 연동으로 UI 업데이트

Slider 기능

기능명 기능정의 난이도 구현 방안
높이 min/max 값 조정 높이 기반 필터링 3D 객체의 Z축 위치 기반 필터링

기술 스택 설계

핵심 라이브러리

// 3D 렌더링
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer';

// React 생태계
import React, { useRef, useEffect, useState, useCallback } from 'react';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { OrbitControls as R3FOrbitControls, Text, Html } from '@react-three/drei';

// 상태 관리
import { create } from 'zustand';

// 유틸리티
import { Vector3, Raycaster, Color } from 'three';

아키텍처 구조

3D 컴포넌트 구조 다이어그램

src/
├── components/
│   ├── wafermap-3d/
│   │   ├── WaferMap3D.tsx           # 메인 3D 컴포넌트
│   │   ├── Scene3D.tsx              # Three.js Scene 관리
│   │   ├── WaferGeometry.tsx        # 웨이퍼 기하학 구조
│   │   ├── ShotRenderer.tsx         # Shot 모드 렌더링
│   │   ├── FocusSpotRenderer.tsx    # Focus Spot 모드 렌더링
│   │   ├── CameraController.tsx     # 카메라 제어
│   │   ├── SelectionManager.tsx     # 선택 관리
│   │   └── tools/
│   │       ├── Toolbar3D.tsx        # 3D 툴바
│   │       ├── ViewControls.tsx     # 시점 제어
│   │       ├── ModeSelector.tsx     # 모드 선택
│   │       └── HeightSlider.tsx     # 높이 슬라이더
│   └── ui/
│       ├── Legend.tsx               # 범례 컴포넌트
│       ├── Tooltip.tsx              # 툴팁 컴포넌트
│       └── ContextMenu.tsx          # 컨텍스트 메뉴
├── hooks/
│   ├── useWaferData.ts              # 웨이퍼 데이터 관리
│   ├── useSelection.ts              # 선택 상태 관리
│   ├── useCamera.ts                 # 카메라 상태 관리
│   └── usePerformance.ts            # 성능 최적화
├── stores/
│   ├── waferStore.ts                # 웨이퍼 상태 저장소
│   ├── uiStore.ts                   # UI 상태 저장소
│   └── selectionStore.ts            # 선택 상태 저장소
├── utils/
│   ├── coordinateSystem.ts          # 3D 좌표계 변환
│   ├── geometryUtils.ts             # 기하학 유틸리티
│   ├── colorUtils.ts                # 색상 매핑 유틸리티
│   └── performanceUtils.ts          # 성능 최적화 유틸리티
└── types/
    ├── wafer3d.ts                   # 3D 웨이퍼 타입 정의
    ├── shot.ts                      # Shot 관련 타입
    └── focusSpot.ts                 # Focus Spot 관련 타입

3D 전용 데이터 구조 설계

3D 웨이퍼맵 전용 데이터 타입

// 완전히 새로운 3D 웨이퍼 데이터 구조
interface Wafer3D {
  id: string;                        // 웨이퍼 고유 ID
  diameter: number;                  // 웨이퍼 직경 (mm)
  thickness: number;                 // 웨이퍼 두께 (mm)
  center: Vector3;                   // 웨이퍼 중심점 3D 좌표
  orientation: number;               // 웨이퍼 방향 (라디안)
  layers: WaferLayer3D[];            // 3D 레이어 구조
  metadata: WaferMetadata;           // 웨이퍼 메타데이터
}

interface Shot3D {
  id: string;                        // Shot 고유 ID
  position: Vector3;                 // 3D 월드 좌표
  dimensions: Vector3;               // 3D 크기 (width, height, depth)
  value: number;                     // 측정값 (색상 매핑용)
  valueRange: [number, number];      // 값 범위 (min, max)
  color: Color;                      // 현재 색상
  borderColor: Color;                // 테두리 색상
  borderWidth: number;               // 테두리 두께
  selected: boolean;                 // 선택 상태
  visible: boolean;                  // 표시 여부
  transparency: number;              // 투명도 (0-1)
  label?: string;                    // 텍스트 라벨
  metadata: ShotMetadata;            // Shot 메타데이터
}

interface FocusSpot3D {
  id: string;                        // Focus Spot 고유 ID
  type: 'Focus' | 'Chuck';           // 타입 구분
  position: Vector3;                 // 3D 월드 좌표
  vertices: Vector3[];               // 다각형 꼭짓점 배열
  height: number;                    // 높이 (Z축 방향)
  rotation: Vector3;                 // 3D 회전 (Euler angles)
  scale: Vector3;                    // 3D 스케일
  color: Color;                      // 기본 색상
  hoverColor: Color;                 // 호버 시 색상
  selectedColor: Color;              // 선택 시 색상
  opacity: number;                   // 투명도
  visible: boolean;                  // 표시 여부
  selected: boolean;                 // 선택 상태
  hovered: boolean;                  // 호버 상태
  metadata: FocusSpotMetadata;       // Focus Spot 메타데이터
}

interface WaferLayer3D {
  id: string;                        // 레이어 고유 ID
  name: string;                      // 레이어 이름
  zIndex: number;                    // Z축 순서
  height: number;                    // 레이어 높이
  visible: boolean;                  // 레이어 표시 여부
  shots: Shot3D[];                   // 레이어 내 Shot 배열
  focusSpots: FocusSpot3D[];         // 레이어 내 Focus Spot 배열
  metadata: LayerMetadata;           // 레이어 메타데이터
}

// 메타데이터 인터페이스들
interface WaferMetadata {
  lotId: string;
  waferNumber: number;
  processStep: string;
  timestamp: Date;
  equipment: string;
  recipe: string;
}

interface ShotMetadata {
  shotNumber: number;
  dieCount: number;
  defectCount: number;
  yieldRate: number;
  processData: Record<string, any>;
}

interface FocusSpotMetadata {
  spotNumber: number;
  focusValue: number;
  chuckPosition: Vector3;
  measurementData: Record<string, any>;
}

interface LayerMetadata {
  layerType: string;
  processStep: string;
  measurementType: string;
  units: string;
}

상태 관리 스토어

상태 관리 아키텍처 다이어그램

// Zustand 기반 상태 관리
interface WaferStore {
  // 데이터
  waferData: Wafer3D | null;
  currentMode: 'shot' | 'focusSpot';

  // 시각화 설정
  showShots: boolean;
  showFocusSpots: boolean;
  showChuck: boolean;
  shotTransparency: boolean;
  focusSpotTransparency: boolean;

  // 높이 필터
  heightRange: [number, number];

  // 액션
  setWaferData: (data: Wafer3D) => void;
  setMode: (mode: 'shot' | 'focusSpot') => void;
  toggleShotVisibility: () => void;
  toggleFocusSpotVisibility: () => void;
  setHeightRange: (range: [number, number]) => void;
}

interface SelectionStore {
  selectedShots: Set<string>;
  selectedFocusSpots: Set<string>;
  selectionMode: 'single' | 'multiple' | 'area';
  selectionShape: 'rectangle' | 'circle' | 'polygon';

  selectShot: (id: string, multiple?: boolean) => void;
  selectFocusSpot: (id: string, multiple?: boolean) => void;
  selectArea: (area: SelectionArea) => void;
  clearSelection: () => void;
}

3D 좌표계 시스템

3D 좌표계 변환 시스템 다이어그램

웨이퍼맵 3D 좌표계 정의

class WaferMap3DCoordinateSystem {
  private waferCenter: Vector3;
  private waferRadius: number;
  private waferThickness: number;

  constructor(center: Vector3, radius: number, thickness: number) {
    this.waferCenter = center;
    this.waferRadius = radius;
    this.waferThickness = thickness;
  }

  // 웨이퍼 로컬 좌표를 3D 월드 좌표로 변환
  // 웨이퍼 중심을 원점으로 하는 로컬 좌표계에서 Three.js 월드 좌표계로 변환
  localToWorld(localCoord: Vector3): Vector3 {
    return new Vector3(
      this.waferCenter.x + localCoord.x,
      this.waferCenter.y + localCoord.y,
      this.waferCenter.z + localCoord.z
    );
  }

  // 3D 월드 좌표를 웨이퍼 로컬 좌표로 변환
  worldToLocal(worldCoord: Vector3): Vector3 {
    return new Vector3(
      worldCoord.x - this.waferCenter.x,
      worldCoord.y - this.waferCenter.y,
      worldCoord.z - this.waferCenter.z
    );
  }

  // 3D 월드 좌표를 화면 좌표로 변환 (Three.js 표준)
  worldToScreen(worldCoord: Vector3, camera: Camera): Vector2 {
    const screenCoord = worldCoord.clone().project(camera);
    return new Vector2(
      (screenCoord.x + 1) * window.innerWidth / 2,
      (-screenCoord.y + 1) * window.innerHeight / 2
    );
  }

  // 화면 좌표를 3D 월드 좌표로 역변환 (Raycasting 활용)
  screenToWorld(screenCoord: Vector2, camera: Camera, targetPlane?: Plane): Vector3 {
    const raycaster = new Raycaster();
    const normalizedCoord = new Vector2(
      (screenCoord.x / window.innerWidth) * 2 - 1,
      -(screenCoord.y / window.innerHeight) * 2 + 1
    );

    raycaster.setFromCamera(normalizedCoord, camera);

    // 기본적으로 웨이퍼 표면 평면과 교차점 계산
    const waferPlane = targetPlane || new Plane(new Vector3(0, 1, 0), -this.waferCenter.y);
    const intersection = raycaster.ray.intersectPlane(waferPlane, new Vector3());

    return intersection || new Vector3();
  }

  // 웨이퍼 경계 내부 여부 확인
  isInsideWafer(worldCoord: Vector3): boolean {
    const localCoord = this.worldToLocal(worldCoord);
    const distanceFromCenter = Math.sqrt(localCoord.x * localCoord.x + localCoord.z * localCoord.z);
    return distanceFromCenter <= this.waferRadius && 
           Math.abs(localCoord.y) <= this.waferThickness / 2;
  }

  // 웨이퍼 표면 높이 계산 (Y축 기준)
  getWaferSurfaceHeight(): number {
    return this.waferCenter.y + this.waferThickness / 2;
  }

  // 극좌표를 직교좌표로 변환 (웨이퍼 표면 기준)
  polarToCartesian(radius: number, angle: number, height: number = 0): Vector3 {
    return this.localToWorld(new Vector3(
      radius * Math.cos(angle),
      height,
      radius * Math.sin(angle)
    ));
  }

  // 직교좌표를 극좌표로 변환
  cartesianToPolar(worldCoord: Vector3): { radius: number; angle: number; height: number } {
    const localCoord = this.worldToLocal(worldCoord);
    const radius = Math.sqrt(localCoord.x * localCoord.x + localCoord.z * localCoord.z);
    const angle = Math.atan2(localCoord.z, localCoord.x);
    return { radius, angle, height: localCoord.y };
  }
}

카메라 시점 관리

interface CameraPreset {
  name: string;
  position: Vector3;
  target: Vector3;
  up: Vector3;
}

const CAMERA_PRESETS: Record<string, CameraPreset> = {
  top: {
    name: 'Top View',
    position: new Vector3(0, 100, 0),
    target: new Vector3(0, 0, 0),
    up: new Vector3(0, 0, -1)
  },
  side: {
    name: 'Side View',
    position: new Vector3(100, 0, 0),
    target: new Vector3(0, 0, 0),
    up: new Vector3(0, 1, 0)
  },
  angle45: {
    name: '45° View',
    position: new Vector3(70, 70, 70),
    target: new Vector3(0, 0, 0),
    up: new Vector3(0, 1, 0)
  }
};

Three.js 핵심 개념 및 구현 원리

CylinderGeometry - 웨이퍼 기본 형태 구현

개념

CylinderGeometry는 Three.js에서 원통형 3D 객체를 생성하는 기하학 클래스입니다. 웨이퍼의 원형 디스크 형태를 표현하기에 최적화된 구조입니다.

작동 원리

// CylinderGeometry 생성자 매개변수
new CylinderGeometry(
  radiusTop: number,    // 상단 반지름
  radiusBottom: number, // 하단 반지름  
  height: number,       // 높이
  radialSegments: number, // 원주 방향 분할 수
  heightSegments: number, // 높이 방향 분할 수
  openEnded: boolean,   // 양 끝면 열림 여부
  thetaStart: number,   // 시작 각도 (라디안)
  thetaLength: number   // 각도 범위 (라디안)
);

웨이퍼 구현 방법

class WaferGeometry {
  private geometry: CylinderGeometry;
  private material: MeshStandardMaterial;
  private mesh: Mesh;

  constructor(diameter: number, thickness: number) {
    // 웨이퍼는 상하 반지름이 동일한 원통
    this.geometry = new CylinderGeometry(
      diameter / 2,     // 상단 반지름
      diameter / 2,     // 하단 반지름 (동일)
      thickness,        // 웨이퍼 두께
      64,              // 원주 분할 (부드러운 원형을 위해 64개)
      1,               // 높이 분할 (평면이므로 1개)
      false,           // 양 끝면 닫힘
      0,               // 시작 각도 0
      Math.PI * 2      // 전체 원 (360도)
    );

    // 웨이퍼 재질 설정
    this.material = new MeshStandardMaterial({
      color: 0x888888,        // 회색 실리콘 색상
      metalness: 0.1,         // 약간의 금속성
      roughness: 0.8,         // 거친 표면
      transparent: true,
      opacity: 0.3            // 반투명으로 내부 구조 표시
    });

    this.mesh = new Mesh(this.geometry, this.material);
  }

  // 웨이퍼 방향 마크 추가 (노치 구현)
  addNotch(notchWidth: number, notchDepth: number): void {
    // CSG (Constructive Solid Geometry) 연산으로 노치 생성
    const notchGeometry = new BoxGeometry(notchWidth, this.geometry.parameters.height, notchDepth);
    const notchMesh = new Mesh(notchGeometry);
    notchMesh.position.set(this.geometry.parameters.radiusTop - notchDepth/2, 0, 0);

    // 실제 구현에서는 CSG 라이브러리 사용 필요
    // this.geometry = CSG.subtract(this.geometry, notchGeometry);
  }
}

InstancedMesh - 대량 Shot 렌더링 최적화

개념

InstancedMesh는 동일한 기하학 구조를 가진 다수의 객체를 효율적으로 렌더링하는 Three.js 기능입니다. 120~130개의 Shot을 개별 Mesh로 생성하는 대신 하나의 InstancedMesh로 처리하여 성능을 대폭 향상시킵니다.

작동 원리

  • 단일 Draw Call: 모든 인스턴스가 하나의 GPU 호출로 렌더링
  • Matrix 변환: 각 인스턴스의 위치, 회전, 스케일을 4x4 행렬로 관리
  • Attribute 공유: 기본 기하학 구조와 재질을 모든 인스턴스가 공유

Shot 렌더링 구현

class ShotInstancedRenderer {
  private instancedMesh: InstancedMesh;
  private shots: Shot3D[];
  private colorAttribute: InstancedBufferAttribute;

  constructor(shots: Shot3D[]) {
    this.shots = shots;

    // 기본 Shot 기하학 구조 (박스 형태)
    const baseGeometry = new BoxGeometry(1, 1, 1);

    // Shot 기본 재질
    const baseMaterial = new MeshStandardMaterial({
      vertexColors: true  // 인스턴스별 색상 활성화
    });

    // InstancedMesh 생성
    this.instancedMesh = new InstancedMesh(
      baseGeometry,
      baseMaterial,
      shots.length  // 인스턴스 개수
    );

    this.setupInstances();
    this.setupColors();
  }

  private setupInstances(): void {
    const matrix = new Matrix4();

    this.shots.forEach((shot, index) => {
      // 각 Shot의 변환 행렬 설정
      matrix.compose(
        shot.position,           // 위치
        new Quaternion(),        // 회전 (기본값)
        shot.dimensions          // 스케일 (Shot 크기)
      );

      // 인스턴스 행렬 설정
      this.instancedMesh.setMatrixAt(index, matrix);
    });

    // GPU에 변경사항 전달
    this.instancedMesh.instanceMatrix.needsUpdate = true;
  }

  private setupColors(): void {
    // 인스턴스별 색상 배열 생성
    const colors = new Float32Array(this.shots.length * 3);

    this.shots.forEach((shot, index) => {
      colors[index * 3] = shot.color.r;
      colors[index * 3 + 1] = shot.color.g;
      colors[index * 3 + 2] = shot.color.b;
    });

    // 색상 속성 생성 및 적용
    this.colorAttribute = new InstancedBufferAttribute(colors, 3);
    this.instancedMesh.geometry.setAttribute('instanceColor', this.colorAttribute);
  }

  // Shot 선택 상태 업데이트
  updateShotSelection(shotId: string, selected: boolean): void {
    const shotIndex = this.shots.findIndex(shot => shot.id === shotId);
    if (shotIndex === -1) return;

    const shot = this.shots[shotIndex];
    shot.selected = selected;

    // 선택된 Shot의 색상 변경
    const selectionColor = selected ? new Color(0x9966ff) : shot.color;

    this.colorAttribute.setXYZ(shotIndex, selectionColor.r, selectionColor.g, selectionColor.b);
    this.colorAttribute.needsUpdate = true;
  }
}

ExtrudeGeometry - Focus Spot 다각형 구현

개념

ExtrudeGeometry는 2D 형태를 3D로 돌출시켜 입체 객체를 만드는 Three.js 기능입니다. Focus Spot의 다각형 형태를 높이를 가진 3D 객체로 변환하는 데 사용됩니다.

작동 원리

// ExtrudeGeometry 생성 과정
// 1. 2D Shape 정의 → 2. Extrude 설정 → 3. 3D Geometry 생성

Focus Spot 구현 방법

class FocusSpotGeometry {
  static createPolygonSpot(
    vertices: Vector3[], 
    height: number, 
    type: 'Focus' | 'Chuck'
  ): ExtrudeGeometry {

    // 1. 2D Shape 생성
    const shape = new Shape();

    // 첫 번째 점으로 이동
    shape.moveTo(vertices[0].x, vertices[0].z);

    // 나머지 점들로 선 그리기
    for (let i = 1; i < vertices.length; i++) {
      shape.lineTo(vertices[i].x, vertices[i].z);
    }

    // 2. Extrude 설정
    const extrudeSettings: ExtrudeGeometryOptions = {
      depth: height,              // 돌출 높이
      bevelEnabled: true,         // 모서리 둥글게
      bevelThickness: 0.1,        // 모서리 두께
      bevelSize: 0.05,           // 모서리 크기
      bevelOffset: 0,            // 모서리 오프셋
      bevelSegments: 3,          // 모서리 분할 수
      curveSegments: 12,         // 곡선 분할 수
      steps: 1                   // 높이 방향 분할 수
    };

    // 3. 3D Geometry 생성
    return new ExtrudeGeometry(shape, extrudeSettings);
  }

  // 육각형 Focus Spot 생성
  static createHexagonSpot(radius: number, height: number): ExtrudeGeometry {
    const vertices: Vector3[] = [];

    for (let i = 0; i < 6; i++) {
      const angle = (i / 6) * Math.PI * 2;
      vertices.push(new Vector3(
        Math.cos(angle) * radius,
        0,
        Math.sin(angle) * radius
      ));
    }

    return this.createPolygonSpot(vertices, height, 'Focus');
  }

  // 사각형 Chuck 생성
  static createRectangleChuck(width: number, depth: number, height: number): ExtrudeGeometry {
    const vertices = [
      new Vector3(-width/2, 0, -depth/2),
      new Vector3(width/2, 0, -depth/2),
      new Vector3(width/2, 0, depth/2),
      new Vector3(-width/2, 0, depth/2)
    ];

    return this.createPolygonSpot(vertices, height, 'Chuck');
  }
}

Raycasting - 3D 객체 선택 시스템

개념

Raycasting은 마우스 위치에서 3D 공간으로 가상의 광선을 쏘아 교차하는 객체를 찾는 기술입니다. 3D 웨이퍼맵에서 Shot이나 Focus Spot 선택에 핵심적으로 사용됩니다.

작동 원리

class RaycastingSelector {
  private raycaster: Raycaster;
  private mouse: Vector2;
  private camera: Camera;
  private selectableObjects: Object3D[];

  constructor(camera: Camera) {
    this.raycaster = new Raycaster();
    this.mouse = new Vector2();
    this.camera = camera;
    this.selectableObjects = [];
  }

  // 마우스 이벤트를 정규화된 좌표로 변환
  updateMousePosition(event: MouseEvent): void {
    const rect = (event.target as HTMLElement).getBoundingClientRect();

    this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
  }

  // 광선과 교차하는 객체 찾기
  getIntersectedObjects(): Intersection[] {
    // 카메라에서 마우스 방향으로 광선 설정
    this.raycaster.setFromCamera(this.mouse, this.camera);

    // 선택 가능한 객체들과의 교차점 계산
    const intersections = this.raycaster.intersectObjects(
      this.selectableObjects, 
      true  // 자식 객체도 포함
    );

    return intersections;
  }

  // Shot 선택 처리
  selectShot(event: MouseEvent): Shot3D | null {
    this.updateMousePosition(event);
    const intersections = this.getIntersectedObjects();

    if (intersections.length > 0) {
      const intersection = intersections[0];

      // InstancedMesh의 경우 인스턴스 ID 확인
      if (intersection.object instanceof InstancedMesh) {
        const instanceId = intersection.instanceId;
        return this.getShotByInstanceId(instanceId);
      }
    }

    return null;
  }

  // 영역 선택을 위한 Frustum 계산
  selectArea(startMouse: Vector2, endMouse: Vector2): Object3D[] {
    const selectedObjects: Object3D[] = [];

    // 선택 영역을 3D Frustum으로 변환
    const frustum = new Frustum();
    const cameraMatrix = new Matrix4();

    // 카메라 투영 행렬과 뷰 행렬 결합
    cameraMatrix.multiplyMatrices(
      this.camera.projectionMatrix, 
      this.camera.matrixWorldInverse
    );

    frustum.setFromProjectionMatrix(cameraMatrix);

    // 각 객체가 Frustum 내부에 있는지 확인
    this.selectableObjects.forEach(obj => {
      if (this.isObjectInSelection(obj, startMouse, endMouse)) {
        selectedObjects.push(obj);
      }
    });

    return selectedObjects;
  }

  private isObjectInSelection(
    object: Object3D, 
    startMouse: Vector2, 
    endMouse: Vector2
  ): boolean {
    // 객체의 월드 좌표를 화면 좌표로 변환
    const screenPosition = object.position.clone().project(this.camera);

    // 선택 영역 내부 여부 확인
    const minX = Math.min(startMouse.x, endMouse.x);
    const maxX = Math.max(startMouse.x, endMouse.x);
    const minY = Math.min(startMouse.y, endMouse.y);
    const maxY = Math.max(startMouse.y, endMouse.y);

    return screenPosition.x >= minX && screenPosition.x <= maxX &&
           screenPosition.y >= minY && screenPosition.y <= maxY;
  }
}

CSS3DRenderer - 3D 공간 UI 오버레이

개념

CSS3DRenderer는 HTML/CSS 요소를 3D 공간에 배치할 수 있게 해주는 Three.js 렌더러입니다. 툴팁, 라벨, UI 요소를 3D 객체와 함께 표시할 때 사용됩니다.

구현 방법

class CSS3DTooltipSystem {
  private css3DRenderer: CSS3DRenderer;
  private css3DScene: Scene;
  private tooltips: Map<string, CSS3DObject>;

  constructor(container: HTMLElement) {
    this.css3DRenderer = new CSS3DRenderer();
    this.css3DRenderer.setSize(container.clientWidth, container.clientHeight);
    this.css3DRenderer.domElement.style.position = 'absolute';
    this.css3DRenderer.domElement.style.top = '0';
    this.css3DRenderer.domElement.style.pointerEvents = 'none';

    container.appendChild(this.css3DRenderer.domElement);

    this.css3DScene = new Scene();
    this.tooltips = new Map();
  }

  // 3D 위치에 툴팁 생성
  createTooltip(id: string, position: Vector3, content: string): void {
    const tooltipElement = document.createElement('div');
    tooltipElement.className = 'tooltip-3d';
    tooltipElement.innerHTML = content;
    tooltipElement.style.cssText = `
      background: rgba(0, 0, 0, 0.8);
      color: white;
      padding: 8px 12px;
      border-radius: 4px;
      font-size: 12px;
      white-space: nowrap;
      pointer-events: none;
    `;

    const css3DObject = new CSS3DObject(tooltipElement);
    css3DObject.position.copy(position);
    css3DObject.position.y += 2; // 객체 위쪽에 표시

    this.css3DScene.add(css3DObject);
    this.tooltips.set(id, css3DObject);
  }

  // 툴팁 업데이트
  updateTooltip(id: string, position: Vector3, content?: string): void {
    const tooltip = this.tooltips.get(id);
    if (tooltip) {
      tooltip.position.copy(position);
      tooltip.position.y += 2;

      if (content) {
        (tooltip.element as HTMLElement).innerHTML = content;
      }
    }
  }

  // 툴팁 제거
  removeTooltip(id: string): void {
    const tooltip = this.tooltips.get(id);
    if (tooltip) {
      this.css3DScene.remove(tooltip);
      this.tooltips.delete(id);
    }
  }

  // 렌더링
  render(camera: Camera): void {
    this.css3DRenderer.render(this.css3DScene, camera);
  }
}

렌더링 시스템 설계

Shot 렌더링 (난이도: 상)

const ShotRenderer: React.FC<{ shots: Shot3D[] }> = ({ shots }) => {
  const meshRef = useRef<InstancedMesh>(null);
  const [colorArray, setColorArray] = useState<Float32Array>();

  // 25단계 색상 스펙트럼 생성
  const generateColorSpectrum = useCallback((value: number, min: number, max: number) => {
    const normalized = (value - min) / (max - min);
    const hue = (1 - normalized) * 240; // 파랑(240°) → 빨강(0°)
    return new Color().setHSL(hue / 360, 1, 0.5);
  }, []);

  useEffect(() => {
    if (!meshRef.current) return;

    const colors = new Float32Array(shots.length * 3);
    const matrix = new Matrix4();

    shots.forEach((shot, index) => {
      // 위치 설정
      matrix.setPosition(shot.position);
      meshRef.current!.setMatrixAt(index, matrix);

      // 색상 설정
      const color = generateColorSpectrum(shot.value, 0, 100);
      colors[index * 3] = color.r;
      colors[index * 3 + 1] = color.g;
      colors[index * 3 + 2] = color.b;
    });

    setColorArray(colors);
    meshRef.current.instanceMatrix.needsUpdate = true;
  }, [shots, generateColorSpectrum]);

  return (
    <instancedMesh ref={meshRef} args={[undefined, undefined, shots.length]}>
      <boxGeometry args={[1, 0.1, 1]} />
      <meshStandardMaterial>
        {colorArray && (
          <instancedBufferAttribute
            attach="attributes-color"
            args={[colorArray, 3]}
          />
        )}
      </meshStandardMaterial>
    </instancedMesh>
  );
};

Focus Spot 렌더링 (난이도: 상)

const FocusSpotRenderer: React.FC<{ focusSpots: FocusSpot3D[] }> = ({ focusSpots }) => {
  const createPolygonGeometry = useCallback((vertices: Vector3[], height: number) => {
    const shape = new Shape();

    // 2D 형태 생성
    vertices.forEach((vertex, index) => {
      if (index === 0) {
        shape.moveTo(vertex.x, vertex.z);
      } else {
        shape.lineTo(vertex.x, vertex.z);
      }
    });

    // 3D 돌출
    const extrudeSettings = {
      depth: height,
      bevelEnabled: false
    };

    return new ExtrudeGeometry(shape, extrudeSettings);
  }, []);

  return (
    <group>
      {focusSpots.map((spot) => (
        <mesh
          key={spot.id}
          position={spot.position}
          rotation={[0, spot.angle, 0]}
          visible={spot.visible}
        >
          <primitive object={createPolygonGeometry(spot.vertices, spot.height)} />
          <meshStandardMaterial
            color={spot.color}
            transparent={spot.type === 'Chuck'}
            opacity={spot.type === 'Chuck' ? 0.7 : 1.0}
          />
        </mesh>
      ))}
    </group>
  );
};

선택 시스템 (난이도: 최상)

const SelectionManager: React.FC = () => {
  const { camera, raycaster } = useThree();
  const selectionStore = useSelectionStore();

  // 3D 공간에서의 영역 선택
  const handleAreaSelection = useCallback((startPoint: Vector2, endPoint: Vector2) => {
    const frustum = new Frustum();
    const cameraMatrix = new Matrix4();

    // 선택 영역을 3D Frustum으로 변환
    cameraMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
    frustum.setFromProjectionMatrix(cameraMatrix);

    // 영역 내 객체 검사
    const selectedObjects: string[] = [];

    // Shot 검사
    shots.forEach((shot) => {
      if (isPointInSelection(shot.position, startPoint, endPoint, camera)) {
        selectedObjects.push(shot.id);
      }
    });

    selectionStore.selectMultiple(selectedObjects);
  }, [camera, selectionStore]);

  // 3D 공간에서 점이 2D 선택 영역 내에 있는지 확인
  const isPointInSelection = useCallback((
    worldPoint: Vector3,
    startScreen: Vector2,
    endScreen: Vector2,
    camera: Camera
  ): boolean => {
    const screenPoint = worldPoint.clone().project(camera);
    const screenX = (screenPoint.x + 1) * window.innerWidth / 2;
    const screenY = (-screenPoint.y + 1) * window.innerHeight / 2;

    return (
      screenX >= Math.min(startScreen.x, endScreen.x) &&
      screenX <= Math.max(startScreen.x, endScreen.x) &&
      screenY >= Math.min(startScreen.y, endScreen.y) &&
      screenY <= Math.max(startScreen.y, endScreen.y)
    );
  }, []);

  return null; // 렌더링하지 않는 로직 컴포넌트
};

1. 인스턴싱 (Instancing)

// 대량의 Shot 객체를 효율적으로 렌더링
const useInstancedRendering = (objects: Shot3D[]) => {
  const instancedMesh = useRef<InstancedMesh>(null);

  useEffect(() => {
    if (!instancedMesh.current || objects.length === 0) return;

    const matrix = new Matrix4();
    const color = new Color();

    objects.forEach((obj, index) => {
      matrix.setPosition(obj.position);
      instancedMesh.current!.setMatrixAt(index, matrix);

      color.copy(obj.color);
      instancedMesh.current!.setColorAt(index, color);
    });

    instancedMesh.current.instanceMatrix.needsUpdate = true;
    if (instancedMesh.current.instanceColor) {
      instancedMesh.current.instanceColor.needsUpdate = true;
    }
  }, [objects]);

  return instancedMesh;
};

2. LOD (Level of Detail)

const useLOD = (distance: number) => {
  const [lodLevel, setLodLevel] = useState(0);

  useEffect(() => {
    if (distance < 50) setLodLevel(2);      // 고해상도
    else if (distance < 100) setLodLevel(1); // 중해상도
    else setLodLevel(0);                     // 저해상도
  }, [distance]);

  return lodLevel;
};

3. Frustum Culling

const useFrustumCulling = (objects: Object3D[], camera: Camera) => {
  const [visibleObjects, setVisibleObjects] = useState<Object3D[]>([]);

  useFrame(() => {
    const frustum = new Frustum();
    const cameraMatrix = new Matrix4();

    cameraMatrix.multiplyMatrices(
      camera.projectionMatrix,
      camera.matrixWorldInverse
    );
    frustum.setFromProjectionMatrix(cameraMatrix);

    const visible = objects.filter(obj => 
      frustum.intersectsObject(obj)
    );

    setVisibleObjects(visible);
  });

  return visibleObjects;
};

UI/UX 설계

툴바 컴포넌트

const Toolbar3D: React.FC = () => {
  const waferStore = useWaferStore();
  const [captureMode, setCaptureMode] = useState(false);

  const handleViewChange = useCallback((preset: string) => {
    const cameraPreset = CAMERA_PRESETS[preset];
    // 카메라 애니메이션 로직
  }, []);

  const handleCapture = useCallback(() => {
    // Three.js 렌더러에서 이미지 캡처
    const canvas = document.querySelector('canvas');
    if (canvas) {
      const link = document.createElement('a');
      link.download = 'wafermap-3d.png';
      link.href = canvas.toDataURL();
      link.click();
    }
  }, []);

  return (
    <div className="toolbar-3d">
      {/* 모드 선택 */}
      <div className="mode-selector">
        <button 
          className={waferStore.currentMode === 'shot' ? 'active' : ''}
          onClick={() => waferStore.setMode('shot')}
        >
          Shot Mode
        </button>
        <button 
          className={waferStore.currentMode === 'focusSpot' ? 'active' : ''}
          onClick={() => waferStore.setMode('focusSpot')}
        >
          Focus Spot Mode
        </button>
      </div>

      {/* 시점 선택 */}
      <div className="view-controls">
        {Object.keys(CAMERA_PRESETS).map(preset => (
          <button key={preset} onClick={() => handleViewChange(preset)}>
            {CAMERA_PRESETS[preset].name}
          </button>
        ))}
      </div>

      {/* 기능 버튼 */}
      <div className="action-buttons">
        <button onClick={handleCapture}>
          📷 Capture
        </button>
        <button onClick={() => waferStore.toggleShotVisibility()}>
          👁️ Toggle Shots
        </button>
      </div>
    </div>
  );
};

높이 슬라이더

const HeightSlider: React.FC = () => {
  const waferStore = useWaferStore();
  const [range, setRange] = useState<[number, number]>([0, 100]);

  const handleRangeChange = useCallback((newRange: [number, number]) => {
    setRange(newRange);
    waferStore.setHeightRange(newRange);
  }, [waferStore]);

  return (
    <div className="height-slider">
      <label>Height Range</label>
      <input
        type="range"
        min={0}
        max={100}
        value={range[0]}
        onChange={(e) => handleRangeChange([+e.target.value, range[1]])}
      />
      <input
        type="range"
        min={0}
        max={100}
        value={range[1]}
        onChange={(e) => handleRangeChange([range[0], +e.target.value])}
      />
      <span>{range[0]} - {range[1]}</span>
    </div>
  );
};

3D 전용 데이터 파싱 시스템

3D 웨이퍼맵 데이터 파서

// 3D 웨이퍼맵 전용 데이터 인터페이스
interface Wafer3DInputData {
  waferInfo: {
    id: string;
    diameter: number;
    thickness: number;
    center: [number, number, number];
    orientation: number;
  };
  shotData: {
    shots: Array<{
      id: string;
      position: [number, number, number];
      dimensions: [number, number, number];
      value: number;
      metadata: Record<string, any>;
    }>;
    valueRange: [number, number];
  };
  focusSpotData: {
    spots: Array<{
      id: string;
      type: 'Focus' | 'Chuck';
      position: [number, number, number];
      vertices: Array<[number, number, number]>;
      height: number;
      rotation: [number, number, number];
      scale: [number, number, number];
      metadata: Record<string, any>;
    }>;
  };
  layerData: {
    layers: Array<{
      id: string;
      name: string;
      zIndex: number;
      height: number;
      shotIds: string[];
      focusSpotIds: string[];
    }>;
  };
  metadata: Record<string, any>;
}

class WaferMap3DParser {
  private coordinateSystem: WaferMap3DCoordinateSystem;

  constructor() {
    // 기본 좌표계 설정 (나중에 데이터에 따라 업데이트)
    this.coordinateSystem = new WaferMap3DCoordinateSystem(
      new Vector3(0, 0, 0),
      100, // 기본 반지름
      1    // 기본 두께
    );
  }

  // 3D 웨이퍼맵 데이터 파싱 메인 함수
  parse(inputData: Wafer3DInputData): Wafer3D {
    // 웨이퍼 기본 정보 파싱
    const waferInfo = this.parseWaferInfo(inputData.waferInfo);

    // 좌표계 업데이트
    this.updateCoordinateSystem(waferInfo);

    // Shot 데이터 파싱
    const shots = this.parseShotData(inputData.shotData);

    // Focus Spot 데이터 파싱
    const focusSpots = this.parseFocusSpotData(inputData.focusSpotData);

    // 레이어 데이터 파싱
    const layers = this.parseLayerData(inputData.layerData, shots, focusSpots);

    // 메타데이터 파싱
    const metadata = this.parseMetadata(inputData.metadata);

    return {
      id: waferInfo.id,
      diameter: waferInfo.diameter,
      thickness: waferInfo.thickness,
      center: waferInfo.center,
      orientation: waferInfo.orientation,
      layers: layers,
      metadata: metadata
    };
  }

  private parseWaferInfo(waferInfo: Wafer3DInputData['waferInfo']): {
    id: string;
    diameter: number;
    thickness: number;
    center: Vector3;
    orientation: number;
  } {
    return {
      id: waferInfo.id,
      diameter: waferInfo.diameter,
      thickness: waferInfo.thickness,
      center: new Vector3(...waferInfo.center),
      orientation: waferInfo.orientation
    };
  }

  private updateCoordinateSystem(waferInfo: ReturnType<typeof this.parseWaferInfo>): void {
    this.coordinateSystem = new WaferMap3DCoordinateSystem(
      waferInfo.center,
      waferInfo.diameter / 2,
      waferInfo.thickness
    );
  }

  private parseShotData(shotData: Wafer3DInputData['shotData']): Shot3D[] {
    const { shots, valueRange } = shotData;

    return shots.map(shot => ({
      id: shot.id,
      position: new Vector3(...shot.position),
      dimensions: new Vector3(...shot.dimensions),
      value: shot.value,
      valueRange: valueRange,
      color: this.calculateShotColor(shot.value, valueRange),
      borderColor: new Color(0x666666),
      borderWidth: 0.1,
      selected: false,
      visible: true,
      transparency: 1.0,
      label: shot.id,
      metadata: this.parseShotMetadata(shot.metadata)
    }));
  }

  private parseFocusSpotData(focusSpotData: Wafer3DInputData['focusSpotData']): FocusSpot3D[] {
    return focusSpotData.spots.map(spot => ({
      id: spot.id,
      type: spot.type,
      position: new Vector3(...spot.position),
      vertices: spot.vertices.map(v => new Vector3(...v)),
      height: spot.height,
      rotation: new Vector3(...spot.rotation),
      scale: new Vector3(...spot.scale),
      color: this.getFocusSpotColor(spot.type),
      hoverColor: this.getFocusSpotHoverColor(spot.type),
      selectedColor: new Color(0x9966ff),
      opacity: spot.type === 'Chuck' ? 0.7 : 1.0,
      visible: true,
      selected: false,
      hovered: false,
      metadata: this.parseFocusSpotMetadata(spot.metadata)
    }));
  }

  private parseLayerData(
    layerData: Wafer3DInputData['layerData'],
    shots: Shot3D[],
    focusSpots: FocusSpot3D[]
  ): WaferLayer3D[] {
    return layerData.layers.map(layer => {
      const layerShots = shots.filter(shot => layer.shotIds.includes(shot.id));
      const layerFocusSpots = focusSpots.filter(spot => layer.focusSpotIds.includes(spot.id));

      return {
        id: layer.id,
        name: layer.name,
        zIndex: layer.zIndex,
        height: layer.height,
        visible: true,
        shots: layerShots,
        focusSpots: layerFocusSpots,
        metadata: {
          layerType: 'measurement',
          processStep: 'unknown',
          measurementType: 'height',
          units: 'mm'
        }
      };
    });
  }

  // 25단계 색상 스펙트럼 계산 (파랑 → 초록 → 노랑 → 빨강)
  private calculateShotColor(value: number, range: [number, number]): Color {
    const [min, max] = range;
    const normalized = Math.max(0, Math.min(1, (value - min) / (max - min)));

    // HSL 색상 공간에서 색상 계산
    // 240° (파랑) → 0° (빨강)
    const hue = (1 - normalized) * 240;
    const saturation = 1.0;
    const lightness = 0.5;

    return new Color().setHSL(hue / 360, saturation, lightness);
  }

  private getFocusSpotColor(type: 'Focus' | 'Chuck'): Color {
    return type === 'Focus' ? new Color(0x00ff00) : new Color(0xff0000);
  }

  private getFocusSpotHoverColor(type: 'Focus' | 'Chuck'): Color {
    return type === 'Focus' ? new Color(0x66ff66) : new Color(0xff6666);
  }

  private parseShotMetadata(metadata: Record<string, any>): ShotMetadata {
    return {
      shotNumber: metadata.shotNumber || 0,
      dieCount: metadata.dieCount || 0,
      defectCount: metadata.defectCount || 0,
      yieldRate: metadata.yieldRate || 0,
      processData: metadata.processData || {}
    };
  }

  private parseFocusSpotMetadata(metadata: Record<string, any>): FocusSpotMetadata {
    return {
      spotNumber: metadata.spotNumber || 0,
      focusValue: metadata.focusValue || 0,
      chuckPosition: new Vector3(
        metadata.chuckPosition?.[0] || 0,
        metadata.chuckPosition?.[1] || 0,
        metadata.chuckPosition?.[2] || 0
      ),
      measurementData: metadata.measurementData || {}
    };
  }

  private parseMetadata(metadata: Record<string, any>): WaferMetadata {
    return {
      lotId: metadata.lotId || '',
      waferNumber: metadata.waferNumber || 0,
      processStep: metadata.processStep || '',
      timestamp: new Date(metadata.timestamp || Date.now()),
      equipment: metadata.equipment || '',
      recipe: metadata.recipe || ''
    };
  }

  // 웨이퍼 경계 내 유효성 검증
  validateShotPosition(shot: Shot3D): boolean {
    return this.coordinateSystem.isInsideWafer(shot.position);
  }

  // Focus Spot 다각형 생성 헬퍼 함수
  static createPolygonVertices(sides: number, radius: number, height: number = 0): Vector3[] {
    const vertices: Vector3[] = [];

    for (let i = 0; i < sides; i++) {
      const angle = (i / sides) * Math.PI * 2;
      vertices.push(new Vector3(
        Math.cos(angle) * radius,
        height,
        Math.sin(angle) * radius
      ));
    }

    return vertices;
  }
}

3D 웨이퍼맵 데이터 플로우

전체 데이터 플로우 개요

3D 웨이퍼맵 시스템의 데이터는 다음과 같은 흐름으로 처리됩니다:

3D 웨이퍼맵 데이터 플로우

관련 시각화 다이어그램

데이터 입력 단계

// 1. 3D 웨이퍼 데이터 수신
interface DataInputFlow {
  // 외부 시스템에서 3D 웨이퍼 데이터 수신
  receiveWaferData(source: string): Promise<Wafer3DInputData>;

  // 데이터 유효성 검증
  validateInputData(data: Wafer3DInputData): ValidationResult;

  // 데이터 전처리
  preprocessData(data: Wafer3DInputData): Wafer3DInputData;
}

class WaferDataReceiver implements DataInputFlow {
  async receiveWaferData(source: string): Promise<Wafer3DInputData> {
    // WebSocket, REST API, 파일 업로드 등을 통한 데이터 수신
    const rawData = await this.fetchFromSource(source);
    return this.parseRawData(rawData);
  }

  validateInputData(data: Wafer3DInputData): ValidationResult {
    const errors: string[] = [];

    // 웨이퍼 기본 정보 검증
    if (!data.waferInfo.id) errors.push('웨이퍼 ID 누락');
    if (data.waferInfo.diameter <= 0) errors.push('웨이퍼 직경 오류');

    // Shot 데이터 검증
    if (!data.shotData.shots.length) errors.push('Shot 데이터 없음');

    // Focus Spot 데이터 검증
    data.focusSpotData.spots.forEach((spot, index) => {
      if (spot.vertices.length < 3) {
        errors.push(`Focus Spot ${index}: 최소 3개 꼭짓점 필요`);
      }
    });

    return { isValid: errors.length === 0, errors };
  }
}

데이터 파싱 및 변환 단계

// 2. 3D 객체 생성 및 좌표 변환
class DataTransformationFlow {
  private parser: WaferMap3DParser;
  private coordinateSystem: WaferMap3DCoordinateSystem;

  constructor() {
    this.parser = new WaferMap3DParser();
  }

  // 입력 데이터를 3D 객체로 변환
  transformToWafer3D(inputData: Wafer3DInputData): Wafer3D {
    // 1. 기본 파싱
    const wafer3D = this.parser.parse(inputData);

    // 2. 좌표계 설정
    this.coordinateSystem = new WaferMap3DCoordinateSystem(
      wafer3D.center,
      wafer3D.diameter / 2,
      wafer3D.thickness
    );

    // 3. 좌표 유효성 검증 및 보정
    this.validateAndCorrectCoordinates(wafer3D);

    return wafer3D;
  }

  private validateAndCorrectCoordinates(wafer: Wafer3D): void {
    wafer.layers.forEach(layer => {
      // Shot 좌표 검증
      layer.shots = layer.shots.filter(shot => 
        this.coordinateSystem.isInsideWafer(shot.position)
      );

      // Focus Spot 좌표 검증
      layer.focusSpots = layer.focusSpots.filter(spot =>
        this.coordinateSystem.isInsideWafer(spot.position)
      );
    });
  }
}

Three.js 객체 생성 단계

// 3. Three.js 3D 객체 생성
class ThreeJSObjectCreation {
  private scene: Scene;
  private waferMesh: Mesh;
  private shotRenderer: ShotInstancedRenderer;
  private focusSpotMeshes: Map<string, Mesh>;

  constructor(scene: Scene) {
    this.scene = scene;
    this.focusSpotMeshes = new Map();
  }

  // 웨이퍼 3D 객체 생성
  createWaferObjects(wafer3D: Wafer3D): void {
    // 1. 웨이퍼 기본 형태 생성
    this.createWaferBase(wafer3D);

    // 2. Shot 객체들 생성
    this.createShotObjects(wafer3D);

    // 3. Focus Spot 객체들 생성
    this.createFocusSpotObjects(wafer3D);

    // 4. 가이드라인 및 보조 객체 생성
    this.createGuideObjects(wafer3D);
  }

  private createWaferBase(wafer3D: Wafer3D): void {
    const waferGeometry = new WaferGeometry(wafer3D.diameter, wafer3D.thickness);
    this.waferMesh = waferGeometry.mesh;
    this.waferMesh.position.copy(wafer3D.center);
    this.scene.add(this.waferMesh);
  }

  private createShotObjects(wafer3D: Wafer3D): void {
    const allShots = wafer3D.layers.flatMap(layer => layer.shots);
    this.shotRenderer = new ShotInstancedRenderer(allShots);
    this.scene.add(this.shotRenderer.instancedMesh);
  }

  private createFocusSpotObjects(wafer3D: Wafer3D): void {
    wafer3D.layers.forEach(layer => {
      layer.focusSpots.forEach(spot => {
        const geometry = FocusSpotGeometry.createPolygonSpot(
          spot.vertices, 
          spot.height, 
          spot.type
        );

        const material = new MeshStandardMaterial({
          color: spot.color,
          transparent: true,
          opacity: spot.opacity
        });

        const mesh = new Mesh(geometry, material);
        mesh.position.copy(spot.position);
        mesh.rotation.setFromVector3(spot.rotation);
        mesh.scale.copy(spot.scale);

        this.scene.add(mesh);
        this.focusSpotMeshes.set(spot.id, mesh);
      });
    });
  }
}

렌더링 및 상호작용 단계

// 4. 렌더링 루프 및 상호작용 처리
class RenderingAndInteractionFlow {
  private renderer: WebGLRenderer;
  private css3DRenderer: CSS3DRenderer;
  private camera: PerspectiveCamera;
  private controls: OrbitControls;
  private raycaster: RaycastingSelector;

  constructor(container: HTMLElement) {
    this.setupRenderers(container);
    this.setupCamera();
    this.setupControls();
    this.setupInteraction();
  }

  // 렌더링 루프
  startRenderLoop(): void {
    const animate = () => {
      requestAnimationFrame(animate);

      // 1. 컨트롤 업데이트
      this.controls.update();

      // 2. 상호작용 처리
      this.processInteractions();

      // 3. 3D 렌더링
      this.renderer.render(this.scene, this.camera);

      // 4. CSS3D 오버레이 렌더링
      this.css3DRenderer.render(this.css3DScene, this.camera);

      // 5. 성능 모니터링
      this.monitorPerformance();
    };

    animate();
  }

  private processInteractions(): void {
    // 마우스 이벤트 처리
    this.handleMouseEvents();

    // 키보드 이벤트 처리
    this.handleKeyboardEvents();

    // 터치 이벤트 처리 (모바일)
    this.handleTouchEvents();
  }
}

상태 관리 및 업데이트 플로우

// 5. 상태 관리 및 실시간 업데이트
class StateManagementFlow {
  private waferStore: WaferStore;
  private selectionStore: SelectionStore;
  private uiStore: UIStore;

  constructor() {
    this.setupStores();
    this.setupStateSubscriptions();
  }

  // 상태 변경 감지 및 3D 객체 업데이트
  private setupStateSubscriptions(): void {
    // Shot 선택 상태 변경 감지
    this.selectionStore.subscribe(
      (state) => state.selectedShots,
      (selectedShots) => {
        this.updateShotSelection(selectedShots);
      }
    );

    // 모드 변경 감지
    this.waferStore.subscribe(
      (state) => state.currentMode,
      (mode) => {
        this.switchRenderingMode(mode);
      }
    );

    // 높이 필터 변경 감지
    this.waferStore.subscribe(
      (state) => state.heightRange,
      (range) => {
        this.applyHeightFilter(range);
      }
    );
  }

  private updateShotSelection(selectedShots: Set<string>): void {
    // InstancedMesh의 색상 속성 업데이트
    selectedShots.forEach(shotId => {
      this.shotRenderer.updateShotSelection(shotId, true);
    });
  }

  private switchRenderingMode(mode: 'shot' | 'focusSpot'): void {
    if (mode === 'shot') {
      this.shotRenderer.instancedMesh.visible = true;
      this.focusSpotMeshes.forEach(mesh => mesh.visible = false);
    } else {
      this.shotRenderer.instancedMesh.visible = false;
      this.focusSpotMeshes.forEach(mesh => mesh.visible = true);
    }
  }
}

성능 최적화 플로우

// 6. 성능 최적화 및 메모리 관리
class PerformanceOptimizationFlow {
  private frameRate: number = 60;
  private lastFrameTime: number = 0;
  private memoryUsage: MemoryInfo;

  // 적응형 품질 조절
  adaptiveQualityControl(): void {
    const currentFrameTime = performance.now();
    const deltaTime = currentFrameTime - this.lastFrameTime;
    const currentFPS = 1000 / deltaTime;

    if (currentFPS < 30) {
      // 성능이 낮으면 품질 감소
      this.reduceLOD();
      this.enableFrustumCulling();
      this.reduceParticleCount();
    } else if (currentFPS > 55) {
      // 성능이 좋으면 품질 향상
      this.increaseLOD();
      this.enableHighQualityShaders();
    }

    this.lastFrameTime = currentFrameTime;
  }

  // 메모리 사용량 모니터링
  monitorMemoryUsage(): void {
    if ('memory' in performance) {
      this.memoryUsage = (performance as any).memory;

      if (this.memoryUsage.usedJSHeapSize > this.memoryUsage.totalJSHeapSize * 0.8) {
        // 메모리 사용량이 80% 초과 시 정리
        this.cleanupUnusedObjects();
        this.forceGarbageCollection();
      }
    }
  }
}

고려사항 및 도전과제

1. 성능 최적화 (난이도: 최상)

  • 대용량 데이터: 120~130개 Shot + 다수 Focus Spot 실시간 렌더링
  • 해결방안:
    • InstancedMesh 활용으로 Draw Call 최소화
    • LOD 시스템으로 거리별 상세도 조절
    • Frustum Culling으로 화면 밖 객체 제거
    • Web Worker를 통한 데이터 처리 분리

2. 3D 상호작용 (난이도: 최상)

  • 복잡한 선택: 3D 공간에서의 영역 선택 (사각형, 원형, 다각형)
  • 해결방안:
    • Raycasting 기반 정확한 객체 선택
    • 2D 화면 좌표와 3D 월드 좌표 간 정확한 변환
    • 복잡한 기하학적 계산 최적화

3. 메모리 관리 (난이도: 상)

  • 대용량 Geometry: 다수의 3D 객체로 인한 메모리 사용량 증가
  • 해결방안:
    • Geometry 재사용 및 공유
    • 불필요한 객체 적시 해제
    • 메모리 풀링 시스템 구현

4. 브라우저 호환성 (난이도: 중)

  • WebGL 지원: 구형 브라우저에서의 WebGL 지원 이슈
  • 해결방안:
    • WebGL 지원 여부 감지 및 Fallback 제공
    • 성능에 따른 자동 품질 조절

개발 단계별 계획

Phase 1: 기본 3D 환경 구축 (2주)

  • Three.js 기본 Scene 설정
  • 카메라 컨트롤 구현
  • 기본 웨이퍼 Geometry 생성
  • 좌표계 변환 시스템 구현

Phase 2: Shot Mode 구현 (3주)

  • Shot 렌더링 시스템 구현
  • 색상 스펙트럼 매핑
  • Shot 선택 기능 구현
  • 텍스트 라벨 시스템 구현

Phase 3: Focus Spot Mode 구현 (4주)

  • Focus Spot Geometry 생성
  • 타입별 렌더링 구현
  • 복잡한 영역 선택 시스템 구현
  • 툴팁 시스템 구현

Phase 4: UI/UX 완성 (2주)

  • 툴바 및 컨트롤 구현
  • Legend 시스템 구현
  • 높이 슬라이더 구현
  • 이미지 캡처 기능 구현

Phase 5: 성능 최적화 (2주)

  • 인스턴싱 최적화
  • LOD 시스템 구현
  • 메모리 최적화
  • 브라우저 호환성 테스트

결론

Three.js 기반 3D 웨이퍼맵은 기존 2D 시스템의 모든 기능을 3차원으로 확장하면서도 새로운 시각화 가능성을 제공합니다. 특히 높이 정보를 활용한 데이터 분석과 직관적인 3D 상호작용을 통해 사용자 경험을 크게 향상시킬 수 있습니다.

핵심 성공 요소:

  1. 성능 최적화: 대용량 3D 데이터의 실시간 렌더링
  2. 정확한 상호작용: 3D 공간에서의 정밀한 선택 및 조작
  3. 직관적 UI: 복잡한 3D 기능을 쉽게 사용할 수 있는 인터페이스
  4. 확장성: 향후 추가 기능을 쉽게 통합할 수 있는 아키텍처

이러한 설계를 바탕으로 단계적 개발을 진행하면 고품질의 3D 웨이퍼맵 시스템을 구축할 수 있을 것입니다.