posts

웨이퍼맵 3D 구현 청사진 - <Wafermap3D/> 컴포넌트 아키텍처

Oct 1, 2025 updated Oct 1, 2025 3dcssexpoimplementationtypescript

📋 개요

이 문서는 단일 <Wafermap3D/> 컴포넌트로 모든 내부 파싱과 처리를 담당하는 3D 웨이퍼맵 시스템의 구현 청사진입니다. 상호작용(interaction)과 데이터 관리(data-management), 최적화는 우선 배제하고 핵심 렌더링 기능에 집중합니다.

🎯 구현 vs 라이브러리 구분

Three.js에서 제공하는 것 (가져다 쓰는 것)

🔧 기본 3D 엔진

// Three.js 핵심 클래스들 - 그대로 사용
import {
  Scene,                    // 3D 장면 관리
  PerspectiveCamera,        // 카메라 시점
  WebGLRenderer,           // WebGL 렌더링
  CylinderGeometry,        // 웨이퍼 원통 형태
  BoxGeometry,             // Shot 사각형 형태
  PlaneGeometry,           // 기본 평면
  ExtrudeGeometry,         // Focus Spot 돌출 형태
  InstancedMesh,           // 다중 객체 성능 최적화
  MeshStandardMaterial,    // 기본 재질
  Color,                   // 색상 처리
  Vector3,                 // 3D 벡터 연산
  Matrix4,                 // 변환 행렬
  Raycaster,              // 마우스 피킹
  AmbientLight,           // 환경 조명
  DirectionalLight        // 방향성 조명
} from 'three';

// Three.js 확장 기능들
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer';

🎨 React Three Fiber 생태계

// React Three Fiber - 그대로 사용
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { OrbitControls, Text, Html } from '@react-three/drei';

직접 구현해야 하는 것

📊 데이터 처리 레이어

// 1. 데이터 파싱 및 검증
interface WaferDataProcessor {
  validateWaferData(data: RawWaferData): ValidationResult;
  parseShots(shots: RawShot[]): ProcessedShot[];
  parseFocusSpots(spots: RawFocusSpot[]): ProcessedFocusSpot[];
}

// 2. 좌표계 변환
interface CoordinateTransformer {
  waferToWorld(waferCoord: WaferCoordinate): Vector3;
  worldToWafer(worldCoord: Vector3): WaferCoordinate;
  calculateShotPositions(shots: ProcessedShot[]): Vector3[];
}

// 3. 색상 매핑 시스템
interface ColorMapper {
  generateColorSpectrum(steps: number): Color[];
  mapValueToColor(value: number, range: ValueRange): Color;
  createColorPalette(values: number[]): ColorPalette;
}

🎯 렌더링 로직

// 4. Shot 렌더링 시스템
interface ShotRenderingSystem {
  createShotGeometry(size: number): BoxGeometry;
  setupInstancedMesh(shotCount: number): InstancedMesh;
  updateShotPositions(shots: ProcessedShot[]): void;
  updateShotColors(shots: ProcessedShot[]): void;
}

// 5. Focus Spot 렌더링 시스템
interface FocusSpotRenderingSystem {
  createPolygonGeometry(vertices: Vector2[]): ExtrudeGeometry;
  createFocusSpotMaterial(type: FocusSpotType): Material;
  renderFocusSpots(spots: ProcessedFocusSpot[]): Mesh[];
}

// 6. 웨이퍼 기하학 구조
interface WaferGeometrySystem {
  createWaferCylinder(diameter: number, thickness: number): CylinderGeometry;
  createNotch(position: NotchPosition): Geometry;
  createGuideLines(): LineSegments;
}

🎮 컴포넌트 아키텍처

// 7. 상태 관리 (Zustand)
interface WaferStore {
  data: WaferData | null;
  mode: 'shot' | 'focusSpot';
  selectedShots: Set<number>;
  cameraView: CameraView;
  // 액션들...
}

// 8. UI 컴포넌트들
interface UIComponents {
  Toolbar3D: React.FC;
  Legend: React.FC;
  ModeSelector: React.FC;
  ViewControls: React.FC;
}

🏗️ 컴포넌트 아키텍처

컴포넌트 구조도

<Wafermap3D>
├── 📊 DataProcessor          # 데이터 파싱 및 검증
├── 🎨 Scene3DContainer       # Three.js Scene 래퍼
│   ├── WaferGeometry         # 웨이퍼 원통 + 노치
│   ├── ShotRenderer          # Shot 모드 렌더링
│   ├── FocusSpotRenderer     # Focus Spot 모드 렌더링
│   ├── LightingSystem        # 조명 설정
│   └── CameraController      # 카메라 제어
├── 🎮 UIOverlay              # HTML/CSS UI 레이어
│   ├── Toolbar3D             # 상단 툴바
│   ├── Legend                # 우측 범례
│   └── StatusIndicator       # 상태 표시
└── 🔧 SystemManager          # 내부 시스템 관리
    ├── CoordinateSystem      # 좌표 변환
    ├── ColorMappingSystem    # 색상 매핑
    └── StateManager          # 상태 관리

메인 컴포넌트 인터페이스

interface Wafermap3DProps {
  // 필수 데이터
  data: WaferData;

  // 기본 설정
  initialMode?: 'shot' | 'focusSpot';
  initialView?: 'top' | 'side' | 'iso';

  // 스타일링
  width?: number;
  height?: number;
  backgroundColor?: string;

  // 이벤트 핸들러 (나중에 추가)
  // onShotSelect?: (shot: Shot) => void;
  // onFocusSpotSelect?: (spot: FocusSpot) => void;
}

const Wafermap3D: React.FC<Wafermap3DProps> = ({
  data,
  initialMode = 'shot',
  initialView = 'top',
  width = 800,
  height = 600,
  backgroundColor = '#f0f0f0'
}) => {
  // 내부 구현...
};

📁 파일 구조

src/components/wafermap-3d/
├── Wafermap3D.tsx                    # 메인 컴포넌트
├── core/                             # 핵심 시스템
│   ├── DataProcessor.ts              # 데이터 처리
│   ├── CoordinateTransformer.ts      # 좌표 변환
│   ├── ColorMapper.ts                # 색상 매핑
│   └── GeometryFactory.ts            # 지오메트리 생성
├── rendering/                        # 렌더링 시스템
│   ├── Scene3DContainer.tsx          # Scene 컨테이너
│   ├── WaferGeometry.tsx             # 웨이퍼 지오메트리
│   ├── ShotRenderer.tsx              # Shot 렌더링
│   ├── FocusSpotRenderer.tsx         # Focus Spot 렌더링
│   ├── LightingSystem.tsx            # 조명 시스템
│   └── CameraController.tsx          # 카메라 제어
├── ui/                               # UI 컴포넌트
│   ├── Toolbar3D.tsx                 # 툴바
│   ├── Legend.tsx                    # 범례
│   ├── ModeSelector.tsx              # 모드 선택
│   └── ViewControls.tsx              # 시점 제어
├── stores/                           # 상태 관리
│   ├── waferStore.ts                 # 메인 상태
│   └── uiStore.ts                    # UI 상태
├── types/                            # 타입 정의
│   ├── WaferData.ts                  # 데이터 타입
│   ├── RenderingTypes.ts             # 렌더링 타입
│   └── UITypes.ts                    # UI 타입
└── utils/                            # 유틸리티
    ├── constants.ts                  # 상수 정의
    ├── calculations.ts               # 계산 함수
    └── helpers.ts                    # 헬퍼 함수

🔄 데이터 플로우

1단계: 데이터 입력 및 검증

// Wafermap3D.tsx
const Wafermap3D: React.FC<Wafermap3DProps> = ({ data }) => {
  const [processedData, setProcessedData] = useState<ProcessedWaferData | null>(null);

  useEffect(() => {
    const processor = new DataProcessor();

    // 1. 데이터 검증
    const validation = processor.validateWaferData(data);
    if (!validation.isValid) {
      console.error('Invalid wafer data:', validation.errors);
      return;
    }

    // 2. 데이터 파싱
    const processed = processor.processWaferData(data);
    setProcessedData(processed);
  }, [data]);

  // ...
};

2단계: 좌표계 변환

// CoordinateTransformer.ts
export class CoordinateTransformer {
  private scale = 0.01; // 1mm = 0.01 Three.js unit

  waferToWorld(waferCoord: { x: number; y: number; z: number }): Vector3 {
    return new Vector3(
      waferCoord.x * this.scale,      // X축 그대로
      waferCoord.z * this.scale,      // Z → Y (높이)
      -waferCoord.y * this.scale      // Y → -Z (깊이, 뒤집기)
    );
  }

  calculateShotPositions(shots: ProcessedShot[]): Vector3[] {
    return shots.map(shot => this.waferToWorld(shot.position));
  }
}

3단계: 색상 매핑

// ColorMapper.ts
export class ColorMapper {
  generateColorSpectrum(steps: number = 25): Color[] {
    const colors: Color[] = [];

    for (let i = 0; i < steps; i++) {
      const hue = (240 - (i / (steps - 1)) * 240) / 360; // 파랑→빨강
      const color = new Color().setHSL(hue, 1, 0.5);
      colors.push(color);
    }

    return colors;
  }

  mapValueToColor(value: number, range: { min: number; max: number }): Color {
    const normalizedValue = (value - range.min) / (range.max - range.min);
    const colorIndex = Math.floor(normalizedValue * (this.colorSpectrum.length - 1));
    return this.colorSpectrum[Math.max(0, Math.min(colorIndex, this.colorSpectrum.length - 1))];
  }
}

4단계: 렌더링

// ShotRenderer.tsx
const ShotRenderer: React.FC<{ shots: ProcessedShot[] }> = ({ shots }) => {
  const instancedMeshRef = useRef<InstancedMesh>(null);

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

    const mesh = instancedMeshRef.current;
    const transformer = new CoordinateTransformer();
    const colorMapper = new ColorMapper();

    // 위치 설정
    shots.forEach((shot, index) => {
      const position = transformer.waferToWorld(shot.position);
      const matrix = new Matrix4().setPosition(position);
      mesh.setMatrixAt(index, matrix);
    });

    // 색상 설정
    const colorAttribute = mesh.geometry.getAttribute('instanceColor');
    shots.forEach((shot, index) => {
      const color = colorMapper.mapValueToColor(shot.value, shot.valueRange);
      colorAttribute.setXYZ(index, color.r, color.g, color.b);
    });

    mesh.instanceMatrix.needsUpdate = true;
    colorAttribute.needsUpdate = true;
  }, [shots]);

  return (
    <instancedMesh
      ref={instancedMeshRef}
      args={[new BoxGeometry(1, 1, 0.1), new MeshStandardMaterial(), shots.length]}
    >
      <instancedBufferAttribute
        attach="geometry-attributes-instanceColor"
        args={[new Float32Array(shots.length * 3), 3]}
      />
    </instancedMesh>
  );
};

🎨 렌더링 시스템 상세

WaferGeometry 컴포넌트

// WaferGeometry.tsx
const WaferGeometry: React.FC<{ wafer: WaferSpec }> = ({ wafer }) => {
  const waferMesh = useMemo(() => {
    const geometry = new CylinderGeometry(
      wafer.diameter / 2 * 0.01,  // 상단 반지름
      wafer.diameter / 2 * 0.01,  // 하단 반지름
      wafer.thickness * 0.01,     // 높이
      32                          // 세그먼트
    );

    const material = new MeshStandardMaterial({
      color: 0x888888,
      transparent: true,
      opacity: 0.3
    });

    return { geometry, material };
  }, [wafer]);

  return (
    <mesh geometry={waferMesh.geometry} material={waferMesh.material}>
      {/* 노치 추가 (나중에 구현) */}
    </mesh>
  );
};

Scene3DContainer 컴포넌트

// Scene3DContainer.tsx
const Scene3DContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return (
    <Canvas
      camera={{ position: [0, 5, 0], fov: 75 }}
      style={{ width: '100%', height: '100%' }}
    >
      {/* 조명 설정 */}
      <ambientLight intensity={0.4} />
      <directionalLight position={[10, 10, 5]} intensity={0.8} />

      {/* 카메라 컨트롤 */}
      <OrbitControls enablePan={true} enableZoom={true} enableRotate={true} />

      {/* 자식 컴포넌트들 */}
      {children}
    </Canvas>
  );
};

🎮 상태 관리

Zustand Store 구조

// waferStore.ts
interface WaferState {
  // 데이터
  rawData: WaferData | null;
  processedData: ProcessedWaferData | null;

  // 모드
  currentMode: 'shot' | 'focusSpot';

  // 카메라
  cameraView: 'top' | 'side' | 'iso';

  // 액션들
  setData: (data: WaferData) => void;
  setMode: (mode: 'shot' | 'focusSpot') => void;
  setCameraView: (view: 'top' | 'side' | 'iso') => void;
}

export const useWaferStore = create<WaferState>((set) => ({
  rawData: null,
  processedData: null,
  currentMode: 'shot',
  cameraView: 'top',

  setData: (data) => set({ rawData: data }),
  setMode: (mode) => set({ currentMode: mode }),
  setCameraView: (view) => set({ cameraView: view }),
}));

🎯 메인 컴포넌트 구현

// Wafermap3D.tsx
const Wafermap3D: React.FC<Wafermap3DProps> = ({
  data,
  initialMode = 'shot',
  initialView = 'top',
  width = 800,
  height = 600,
  backgroundColor = '#f0f0f0'
}) => {
  const [processedData, setProcessedData] = useState<ProcessedWaferData | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // 상태 관리
  const { currentMode, cameraView, setMode, setCameraView } = useWaferStore();

  // 데이터 처리
  useEffect(() => {
    const processData = async () => {
      try {
        setIsLoading(true);
        setError(null);

        const processor = new DataProcessor();
        const transformer = new CoordinateTransformer();
        const colorMapper = new ColorMapper();

        // 1. 데이터 검증
        const validation = processor.validateWaferData(data);
        if (!validation.isValid) {
          throw new Error(`Invalid data: ${validation.errors.join(', ')}`);
        }

        // 2. 데이터 파싱
        const shots = processor.parseShots(data.shots);
        const focusSpots = processor.parseFocusSpots(data.focusSpots);

        // 3. 좌표 변환
        const shotPositions = transformer.calculateShotPositions(shots);
        const focusSpotGeometries = focusSpots.map(spot => 
          transformer.createFocusSpotGeometry(spot)
        );

        // 4. 색상 매핑
        const shotColors = shots.map(shot => 
          colorMapper.mapValueToColor(shot.value, { min: 0, max: 100 })
        );

        setProcessedData({
          wafer: data.wafer,
          shots: shots.map((shot, index) => ({
            ...shot,
            worldPosition: shotPositions[index],
            color: shotColors[index]
          })),
          focusSpots: focusSpots.map((spot, index) => ({
            ...spot,
            geometry: focusSpotGeometries[index]
          }))
        });

      } catch (err) {
        setError(err instanceof Error ? err.message : 'Unknown error');
      } finally {
        setIsLoading(false);
      }
    };

    processData();
  }, [data]);

  // 로딩 상태
  if (isLoading) {
    return (
      <div style={{ width, height, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <div>Loading wafer data...</div>
      </div>
    );
  }

  // 에러 상태
  if (error) {
    return (
      <div style={{ width, height, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <div style={{ color: 'red' }}>Error: {error}</div>
      </div>
    );
  }

  // 메인 렌더링
  return (
    <div style={{ width, height, position: 'relative', backgroundColor }}>
      {/* 3D Scene */}
      <Scene3DContainer>
        {processedData && (
          <>
            {/* 웨이퍼 기하학 구조 */}
            <WaferGeometry wafer={processedData.wafer} />

            {/* 모드별 렌더링 */}
            {currentMode === 'shot' && (
              <ShotRenderer shots={processedData.shots} />
            )}
            {currentMode === 'focusSpot' && (
              <FocusSpotRenderer spots={processedData.focusSpots} />
            )}
          </>
        )}
      </Scene3DContainer>

      {/* UI 오버레이 */}
      <UIOverlay>
        <Toolbar3D 
          mode={currentMode} 
          onModeChange={setMode}
          view={cameraView}
          onViewChange={setCameraView}
        />
        <Legend 
          colorSpectrum={processedData?.colorSpectrum}
          valueRange={processedData?.valueRange}
        />
      </UIOverlay>
    </div>
  );
};

export default Wafermap3D;

📊 타입 정의

// types/WaferData.ts
export interface WaferData {
  wafer: {
    id: string;
    diameter: number;        // mm
    thickness: number;       // mm
    notchPosition: 'top' | 'bottom' | 'left' | 'right';
  };
  shots: RawShot[];
  focusSpots: RawFocusSpot[];
}

export interface RawShot {
  id: number;
  x: number;              // mm from center
  y: number;              // mm from center
  z?: number;             // mm height
  value: number;          // measurement value
  isValid: boolean;
}

export interface ProcessedShot extends RawShot {
  worldPosition: Vector3;
  color: Color;
  size: number;
}

export interface RawFocusSpot {
  id: number;
  type: 'focus' | 'chuck';
  vertices: { x: number; y: number }[];
  height: number;
  metadata?: Record<string, any>;
}

export interface ProcessedFocusSpot extends RawFocusSpot {
  geometry: ExtrudeGeometry;
  material: Material;
}

🎉 결론

이 청사진은 <Wafermap3D/> 컴포넌트가 모든 내부 처리를 담당하면서도 명확하게 구조화된 아키텍처를 제공합니다:

✅ 구현해야 할 것

  • 데이터 파싱 및 검증 로직
  • 좌표계 변환 시스템
  • 색상 매핑 알고리즘
  • Shot/Focus Spot 렌더링 로직
  • 상태 관리 시스템
  • UI 컴포넌트들

✅ Three.js에서 가져다 쓸 것

  • 기본 3D 엔진 (Scene, Camera, Renderer)
  • 지오메트리 클래스들 (Cylinder, Box, Extrude)
  • 재질 시스템 (Material)
  • 수학 유틸리티 (Vector3, Matrix4, Color)
  • 컨트롤 시스템 (OrbitControls)

이 구조를 통해 단일 컴포넌트로 완전한 3D 웨이퍼맵 시스템을 구현할 수 있습니다.