
SIMVEX
제4회 블레이버스 MVP 개발 해커톤에서 진행한 프로젝트입니다. 기계공학 교육에서 2D 교재만으로는 이해하기 어려운 기계 구조를, 3D 인터랙티브 분해·조립 시뮬레이션과 SSE 스트리밍 기반 AI 어시스턴트로 직관적으로 학습할 수 있는 서비스입니다. 7가지 기계 장치(드론, V4 엔진, 로봇팔, 로봇 그리퍼, 판스프링, 만력기, 서스펜션)를 3D로 분해하고 학습할 수 있으며, 부품별 AI 설명·퀴즈·메모·워크플로우·PDF 내보내기까지 학습 흐름 전체를 한 곳에서 관리합니다.
* 현재 백엔드 서버가 중단되어 MSW(Mock)로 동작 중입니다.
역할별 수행 내용
프론트엔드
- @react-three/fiber + drei 기반 3D 뷰어 구현 — OrbitControls, Bounds 자동 프레이밍, EffectComposer(Bloom·N8AO·ToneMapping) 후처리
- 부품 변형(Transform) 충돌 방지를 위한 Outer/Inner 이중 그룹 구조 설계
- 4개 독립 Zustand 스토어 설계 — ModelStore(분해 레벨·숨김), RenderStore(조명·재질·카메라), EditStore(도구·히스토리), SimulatorStore(재생 타임라인)
- Undo/Redo 히스토리 구현 (최대 50개) — Zustand subscribe로 3D 오브젝트 자동 동기화
- 시뮬레이터 타임라인과 분해 애니메이션 동기화 — useFrame에서 currentTime/duration 비율로 explodeLevel 제어
- SSE(Server-Sent Events) 기반 스트리밍 AI 응답 파싱 — ReadableStream + 30ms 인터벌 타이핑 애니메이션 구현
- 퀴즈 시스템 — 사전 정의 질문 + 부품명 기반 자동 생성으로 문제 풀(Pool) 구성, 매회 5문제 랜덤 출제
- jsPDF + html2canvas로 3D 캡처·메모·AI 대화를 하나의 PDF로 내보내기
- @xyflow/react 기반 학습 워크플로우 노드 편집
주요 성과
- 7가지 기계 장치(드론, V4 엔진, 로봇팔, 로봇 그리퍼, 판스프링, 만력기, 서스펜션) 3D 분해·조립 학습 콘텐츠 구현
- 피보나치 스피어 알고리즘 기반 자연스러운 부품 분해 애니메이션 구현
- SSE 스트리밍 AI 응답 + 타이핑 애니메이션으로 실시간 학습 보조 경험 제공
- Undo/Redo·워크플로우·퀴즈·메모·PDF 내보내기를 통합한 완결된 학습 흐름 설계
프로젝트 회고
Transform 충돌 방지 — 이중 그룹 구조
3D 편집에서 문제가 하나 있었습니다. 분해 애니메이션은 부품을 절대 위치로 이동시키고, 사용자의 Transform 편집은 부품을 상대적으로 이동시킵니다. 두 변환을 같은 오브젝트에 적용하면 서로 덮어씁니다.
해결 방법은 두 계층으로 분리하는 것입니다. 바깥 그룹(Outer Group)이 분해 애니메이션을 담당하고, 안쪽 그룹(Inner Group)이 사용자의 Transform 편집을 담당합니다. 두 변환은 독립적으로 적용되어 서로 간섭하지 않습니다.
<group ref={outerGroupRef}> {/* 분해 애니메이션 담당 */}
<group ref={innerGroupRef}> {/* 사용자 Transform 담당 */}
<primitive object={scene} />
</group>
</group>
// useFrame에서 outerGroup에만 분해 위치 적용
outerGroupRef.current.position.lerp(explodeTarget, 0.1);
// TransformControls는 innerGroup에 연결
<TransformControls object={innerGroupRef.current} />Undo/Redo도 이 구조 덕분에 깔끔하게 구현했습니다. 히스토리에는 innerGroup의 position/rotation만 저장하고,useEditStore.subscribe로 히스토리 변화를 감지해 innerGroup을 직접 복원합니다.
SSE 스트리밍 AI 응답 — 타이핑 애니메이션
AI 어시스턴트는 SSE(Server-Sent Events)로 응답을 스트리밍합니다. 백엔드에서 text/event-stream 형식으로 청크를 보내고, 프론트엔드는 ReadableStream을 파싱하여 실시간으로 화면에 표시합니다.
// SSE 스트림 파싱
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n").filter(l => l.startsWith("data:"));
for (const line of lines) {
const data = line.replace(/^data:\s?/, "");
try {
const parsed = JSON.parse(data);
appendToTypingQueue(parsed.answer);
} catch {
appendToTypingQueue(data); // JSON 파싱 실패 시 원문 그대로
}
}
}스트리밍이 완료된 텍스트를 바로 표시하면 응답이 한꺼번에 나타나 어색합니다. 큐(Queue)에 쌓아두고 30ms 인터벌로 2글자씩 화면에 추가해 사람이 타이핑하는 것처럼 보이도록 했습니다. 스트리밍 수신과 화면 렌더링을 분리한 덕분에 네트워크 속도와 무관하게 일정한 타이핑 속도를 유지할 수 있습니다.
성장과 배움
해커톤 특성상 짧은 시간에 많은 기능을 구현해야 했습니다. Three.js를 처음 제대로 써본 프로젝트였는데, 3D 렌더링의 좌표계, 애니메이션 루프, 상태 동기화 방식이 일반적인 React 패턴과 꽤 다르다는 것을 직접 부딪히며 배웠습니다.
이 프로젝트를 통해 얻은 것:
- Outer/Inner 이중 그룹 구조 — 두 변환 시스템이 충돌하지 않도록 계층을 나누는 설계 패턴
- useFrame 기반 애니메이션 — React 렌더 사이클이 아닌 Three.js 렌더 루프에서 상태를 제어하는 방식
- SSE 스트리밍 파싱 + 타이핑 애니메이션 — 수신과 표시를 분리하여 자연스러운 AI 응답 UX 구현
- Zustand subscribe 패턴 — 상태 변화를 3D 씬에 직접 반영하는 브릿지 구조