@hatiolab/figure-model 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,327 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Figure 정본 검증.
5
+ *
6
+ * 두 갈래로 돌려준다.
7
+ *
8
+ * errors 형식·정합성이 깨진 것. **컴파일할 수 없다.**
9
+ * violations 형식은 맞으나 정책을 넘은 것. **컴파일을 막지 않는다.**
10
+ *
11
+ * 둘을 가르는 기준은 「이대로 세울 수 있는가」다. 치수가 없으면 세울 수 없으므로
12
+ * error 다. 재질이 넷이면 세울 수는 있고 다만 draw call 이 는다 — violation 이다.
13
+ *
14
+ * 정책 위반을 막지 않는 것은 뜻이 있다. 막으면 사용자가 우회하고, **숫자로 보여
15
+ * 주면 아낀다.** 랜드마크 설비 하나는 정교해도 되며, 그 대가가 도면 예산에 드러나면
16
+ * 된다.
17
+ *
18
+ * 결측을 기본값으로 갈음하지 않는다. 값이 없는 것과 0 인 것은 다르고, 갈음하면
19
+ * 저작자가 빠뜨린 것을 알 수 없다.
20
+ */
21
+ import { ANIMATION_KINDS, AXES, DETAIL_LEVELS, LABEL_WHENS, LIMITS, MATERIAL_PRESETS, MATERIAL_SLOTS, PART_LIMIT, PIVOT_REQUIRED, PRIMITIVE_KINDS, SEGMENTED_PRIMITIVES, SEGMENT_PRESETS, SIZING_RULES, SURFACE_KINDS } from "./types.js";
22
+ import { groupKeyOf } from "./keys.js";
23
+ function isObject(v) {
24
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
25
+ }
26
+ function isFiniteNumber(v) {
27
+ return typeof v === 'number' && Number.isFinite(v);
28
+ }
29
+ function isNonEmptyString(v) {
30
+ return typeof v === 'string' && v.trim().length > 0;
31
+ }
32
+ class Collector {
33
+ errors = [];
34
+ violations = [];
35
+ error(code, path, message) {
36
+ this.errors.push({ code, path, message });
37
+ }
38
+ violate(code, message, actual, limit) {
39
+ const v = { code, message };
40
+ if (actual !== undefined)
41
+ v.actual = actual;
42
+ if (limit !== undefined)
43
+ v.limit = limit;
44
+ this.violations.push(v);
45
+ }
46
+ /** 유한한 수인지. 아니면 error 를 남기고 false. */
47
+ finite(v, path, label) {
48
+ if (isFiniteNumber(v))
49
+ return true;
50
+ this.error('not-finite', path, `${label}는 유한한 수여야 한다`);
51
+ return false;
52
+ }
53
+ /** 양수인지. */
54
+ positive(v, path, label) {
55
+ if (!this.finite(v, path, label))
56
+ return false;
57
+ if (v > 0)
58
+ return true;
59
+ this.error('not-positive', path, `${label}는 0 보다 커야 한다`);
60
+ return false;
61
+ }
62
+ /** 열거 값인지. */
63
+ oneOf(v, allowed, path, label) {
64
+ if (typeof v === 'string' && allowed.includes(v))
65
+ return true;
66
+ this.error('not-allowed', path, `${label}는 ${allowed.join(' · ')} 중 하나여야 한다`);
67
+ return false;
68
+ }
69
+ }
70
+ function validateVec3(c, v, path, label) {
71
+ if (!isObject(v)) {
72
+ c.error('not-object', path, `${label}는 { x, y, z } 여야 한다`);
73
+ return;
74
+ }
75
+ c.finite(v.x, `${path}.x`, `${label}.x`);
76
+ c.finite(v.y, `${path}.y`, `${label}.y`);
77
+ c.finite(v.z, `${path}.z`, `${label}.z`);
78
+ }
79
+ function validateTransform(c, part, path) {
80
+ const t = part.transform;
81
+ if (!isObject(t)) {
82
+ c.error('missing-transform', `${path}.transform`, 'transform 이 없다');
83
+ return;
84
+ }
85
+ c.finite(t.x, `${path}.transform.x`, 'x');
86
+ c.finite(t.y, `${path}.transform.y`, 'y');
87
+ c.finite(t.z, `${path}.transform.z`, 'z');
88
+ // 평면 단면(extrude)은 depth 가 0 일 수 있으나 width·height 는 있어야 한다.
89
+ c.positive(t.width, `${path}.transform.width`, 'width');
90
+ c.positive(t.height, `${path}.transform.height`, 'height');
91
+ if (t.depth !== undefined)
92
+ c.finite(t.depth, `${path}.transform.depth`, 'depth');
93
+ else
94
+ c.error('missing-depth', `${path}.transform.depth`, 'depth 가 없다 — 0 으로 갈음하지 않는다');
95
+ if (t.rotation !== undefined) {
96
+ if (!isObject(t.rotation)) {
97
+ c.error('not-object', `${path}.transform.rotation`, 'rotation 은 객체여야 한다');
98
+ }
99
+ else {
100
+ for (const axis of AXES) {
101
+ const value = t.rotation[axis];
102
+ if (value !== undefined)
103
+ c.finite(value, `${path}.transform.rotation.${axis}`, `rotation.${axis}`);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ function validatePart(c, raw, index, seen) {
109
+ const path = `parts[${index}]`;
110
+ if (!isObject(raw)) {
111
+ c.error('not-object', path, '부품은 객체여야 한다');
112
+ return;
113
+ }
114
+ // 이름 — 저장되는 식별자다
115
+ if (!isNonEmptyString(raw.name)) {
116
+ c.error('missing-name', `${path}.name`, '부품 이름이 없다 — 바인딩·애니메이션이 이 이름을 가리킨다');
117
+ }
118
+ else if (seen.has(raw.name)) {
119
+ c.error('duplicate-name', `${path}.name`, `부품 이름이 겹친다: ${raw.name}`);
120
+ }
121
+ else {
122
+ seen.add(raw.name);
123
+ }
124
+ const isPrimitive = c.oneOf(raw.primitive, PRIMITIVE_KINDS, `${path}.primitive`, 'primitive');
125
+ validateTransform(c, raw, path);
126
+ // 분할 수 — 곡면이 있는 프리미티브만 뜻이 있다
127
+ if (raw.segments !== undefined) {
128
+ if (!isFiniteNumber(raw.segments) || !Number.isInteger(raw.segments)) {
129
+ c.error('not-integer', `${path}.segments`, 'segments 는 정수여야 한다');
130
+ }
131
+ else if (raw.segments < LIMITS.minSegments) {
132
+ c.error('too-few-segments', `${path}.segments`, `segments 는 ${LIMITS.minSegments} 이상이어야 한다`);
133
+ }
134
+ else if (!SEGMENT_PRESETS.includes(raw.segments)) {
135
+ c.violate('segments-off-preset', `${raw.name ?? path}: 분할 수 ${raw.segments} 는 프리셋(${SEGMENT_PRESETS.join(' · ')}) 이 아니다`, raw.segments);
136
+ }
137
+ if (isPrimitive && !SEGMENTED_PRIMITIVES.includes(raw.primitive)) {
138
+ c.violate('segments-ignored', `${raw.name ?? path}: ${raw.primitive} 는 분할 수를 쓰지 않는다`);
139
+ }
140
+ }
141
+ // extrude 계열의 단면
142
+ if (raw.primitive === 'polygon') {
143
+ const shape = raw.shape;
144
+ const pathPoints = isObject(shape) ? shape.path : undefined;
145
+ if (!Array.isArray(pathPoints) || pathPoints.length < 3) {
146
+ c.error('missing-shape-path', `${path}.shape.path`, 'polygon 은 3 점 이상의 path 가 필요하다');
147
+ }
148
+ else {
149
+ pathPoints.forEach((p, i) => {
150
+ if (!isObject(p) || !isFiniteNumber(p.x) || !isFiniteNumber(p.y)) {
151
+ c.error('bad-shape-point', `${path}.shape.path[${i}]`, 'path 의 점은 { x, y } 여야 한다');
152
+ }
153
+ });
154
+ }
155
+ }
156
+ // 재질 — 색은 토큰으로 온다
157
+ if (!isObject(raw.material)) {
158
+ c.error('missing-material', `${path}.material`, 'material 이 없다');
159
+ }
160
+ else {
161
+ const m = raw.material;
162
+ if (!isNonEmptyString(m.token) && m.preset === undefined) {
163
+ c.error('material-unresolvable', `${path}.material`, 'token 이나 preset 중 하나는 있어야 한다');
164
+ }
165
+ if (m.preset !== undefined)
166
+ c.oneOf(m.preset, MATERIAL_PRESETS, `${path}.material.preset`, 'preset');
167
+ if (m.flatShading !== undefined && typeof m.flatShading !== 'boolean') {
168
+ c.error('not-boolean', `${path}.material.flatShading`, 'flatShading 은 참·거짓이어야 한다');
169
+ }
170
+ if (m.transparent !== undefined && typeof m.transparent !== 'boolean') {
171
+ c.error('not-boolean', `${path}.material.transparent`, 'transparent 는 참·거짓이어야 한다');
172
+ }
173
+ // 팔레트 밖 표면 — 미감의 예외다. 형태만 여기서 본다.
174
+ if (m.surface !== undefined) {
175
+ const u = m.surface;
176
+ if (!isObject(u)) {
177
+ c.error('not-object', `${path}.material.surface`, 'surface 는 객체여야 한다');
178
+ }
179
+ else {
180
+ c.oneOf(u.kind, SURFACE_KINDS, `${path}.material.surface.kind`, 'kind');
181
+ if (!isNonEmptyString(u.ref)) {
182
+ c.error('missing-surface-ref', `${path}.material.surface.ref`, 'ref 가 없다 — 무엇을 쓸지 모른다');
183
+ }
184
+ }
185
+ }
186
+ }
187
+ if (raw.slot !== undefined)
188
+ c.oneOf(raw.slot, MATERIAL_SLOTS, `${path}.slot`, 'slot');
189
+ if (raw.sizing !== undefined)
190
+ c.oneOf(raw.sizing, SIZING_RULES, `${path}.sizing`, 'sizing');
191
+ // repeat — sizing 이 repeat 면 필수
192
+ if (raw.sizing === 'repeat') {
193
+ if (!isObject(raw.repeat)) {
194
+ c.error('missing-repeat', `${path}.repeat`, "sizing 이 'repeat' 이면 repeat 가 필요하다");
195
+ }
196
+ else {
197
+ c.oneOf(raw.repeat.axis, AXES, `${path}.repeat.axis`, 'repeat.axis');
198
+ c.positive(raw.repeat.gap, `${path}.repeat.gap`, 'repeat.gap');
199
+ }
200
+ }
201
+ else if (raw.repeat !== undefined) {
202
+ c.violate('repeat-ignored', `${raw.name ?? path}: sizing 이 'repeat' 가 아니면 repeat 는 쓰이지 않는다`);
203
+ }
204
+ // 애니메이션 포인트
205
+ if (raw.animation !== undefined) {
206
+ if (!isObject(raw.animation)) {
207
+ c.error('not-object', `${path}.animation`, 'animation 은 객체여야 한다');
208
+ }
209
+ else {
210
+ const a = raw.animation;
211
+ const kindOk = c.oneOf(a.kind, ANIMATION_KINDS, `${path}.animation.kind`, 'animation.kind');
212
+ if (a.axis !== undefined)
213
+ c.oneOf(a.axis, AXES, `${path}.animation.axis`, 'animation.axis');
214
+ if (kindOk && a.kind === 'rotation' && a.axis === undefined) {
215
+ c.error('missing-axis', `${path}.animation.axis`, '회전은 축이 있어야 한다');
216
+ }
217
+ // 피봇을 적지 않으면 부품 자기 중심으로 돈다 — geometry.center() 때문
218
+ if (kindOk && PIVOT_REQUIRED.includes(a.kind)) {
219
+ if (a.pivot === undefined) {
220
+ c.error('missing-pivot', `${path}.animation.pivot`, '회전은 피봇이 있어야 한다 — 없으면 부품 자기 중심으로 돈다');
221
+ }
222
+ else {
223
+ validateVec3(c, a.pivot, `${path}.animation.pivot`, 'pivot');
224
+ }
225
+ }
226
+ else if (a.pivot !== undefined) {
227
+ validateVec3(c, a.pivot, `${path}.animation.pivot`, 'pivot');
228
+ }
229
+ }
230
+ }
231
+ // 라벨
232
+ if (raw.label !== undefined) {
233
+ if (!isObject(raw.label)) {
234
+ c.error('not-object', `${path}.label`, 'label 은 객체여야 한다');
235
+ }
236
+ else {
237
+ if (!isNonEmptyString(raw.label.name)) {
238
+ c.error('missing-label-name', `${path}.label.name`, 'label.name 이 없다');
239
+ }
240
+ if (!isNonEmptyString(raw.label.source)) {
241
+ c.error('missing-label-source', `${path}.label.source`, 'label.source 가 없다 — 무엇을 보일지 모른다');
242
+ }
243
+ c.oneOf(raw.label.when, LABEL_WHENS, `${path}.label.when`, 'label.when');
244
+ }
245
+ }
246
+ }
247
+ function checkPolicy(c, source) {
248
+ const parts = source.parts;
249
+ // 등급 — 없으면 예산을 잡을 수 없다. 조용히 넘기지 않는다.
250
+ if (source.detailLevel === undefined) {
251
+ c.violate('detail-level-missing', '디테일 등급이 없다 — 부품 수 예산을 잡을 수 없다');
252
+ }
253
+ else {
254
+ const limit = PART_LIMIT[source.detailLevel];
255
+ if (parts.length > limit) {
256
+ c.violate('too-many-parts', `등급 ${source.detailLevel} 의 부품 수 한도를 넘었다`, parts.length, limit);
257
+ }
258
+ }
259
+ const animated = parts.filter(p => p.animation !== undefined);
260
+ if (animated.length > LIMITS.animationPoints) {
261
+ c.violate('too-many-animation-points', '애니메이션 포인트가 많다 — 묶음 수와 프레임당 갱신이 는다', animated.length, LIMITS.animationPoints);
262
+ }
263
+ // 묶음 수 = 재질·슬롯 조합 수 + 애니메이션 포인트 수
264
+ const staticGroups = new Set(parts.filter(p => p.animation === undefined).map(groupKeyOf));
265
+ if (staticGroups.size > LIMITS.materialGroups) {
266
+ c.violate('too-many-material-groups', '재질·슬롯 조합이 많다 — 자산당 draw call 이 는다', staticGroups.size, LIMITS.materialGroups);
267
+ }
268
+ const transparent = new Set(parts.filter(p => p.material?.transparent).map(groupKeyOf));
269
+ if (transparent.size > LIMITS.transparentMaterials) {
270
+ c.violate('too-many-transparent', '투명 재질이 많다 — 정렬 비용이 크다', transparent.size, LIMITS.transparentMaterials);
271
+ }
272
+ // 팔레트 밖 표면 — 단색 규칙의 예외다. **종류 수**를 센다(같은 ref 는 한 텍스처다).
273
+ const surfaces = new Set(parts
274
+ .map(p => p.material?.surface)
275
+ .filter((u) => u !== undefined)
276
+ .map(u => `${u.kind}:${u.ref}`));
277
+ if (surfaces.size > LIMITS.surfaceTextures) {
278
+ c.violate('too-many-surface-textures', '팔레트 밖 표면이 많다 — 단색 규칙을 벗어나고 텍스처마다 GPU 메모리를 쓴다', surfaces.size, LIMITS.surfaceTextures);
279
+ }
280
+ const always = parts.filter(p => p.label?.when === 'always');
281
+ if (always.length > 1) {
282
+ c.violate('too-many-always-labels', "'always' 라벨이 여럿이다 — 대량 배치에서 DOM·텍스처가 는다", always.length, 1);
283
+ }
284
+ }
285
+ /**
286
+ * 정본을 검증한다.
287
+ *
288
+ * 바깥에서 온 데이터(DB · API · 파일)를 받으므로 `unknown` 을 받는다.
289
+ * `errors` 가 비어 있으면 `source` 는 `FigureSource` 로 다룰 수 있다.
290
+ */
291
+ export function validate(source) {
292
+ const c = new Collector();
293
+ if (!isObject(source)) {
294
+ c.error('not-object', '', '정본은 객체여야 한다');
295
+ return { errors: c.errors, violations: c.violations };
296
+ }
297
+ if (!isNonEmptyString(source.type)) {
298
+ c.error('missing-type', 'type', 'type 이 없다 — 저장되는 식별자다');
299
+ }
300
+ if (!isObject(source.base)) {
301
+ c.error('missing-base', 'base', 'base 가 없다 — 배치 비율의 근거다');
302
+ }
303
+ else {
304
+ c.positive(source.base.width, 'base.width', 'base.width');
305
+ c.positive(source.base.height, 'base.height', 'base.height');
306
+ c.positive(source.base.depth, 'base.depth', 'base.depth');
307
+ }
308
+ if (source.anchor !== undefined)
309
+ validateVec3(c, source.anchor, 'anchor', 'anchor');
310
+ if (source.detailLevel !== undefined) {
311
+ c.oneOf(source.detailLevel, DETAIL_LEVELS, 'detailLevel', 'detailLevel');
312
+ }
313
+ if (source.styleKit !== undefined && !isNonEmptyString(source.styleKit)) {
314
+ c.error('bad-style-kit', 'styleKit', 'styleKit 은 비어 있지 않은 문자여야 한다');
315
+ }
316
+ if (!Array.isArray(source.parts) || source.parts.length === 0) {
317
+ c.error('missing-parts', 'parts', 'parts 가 없다 — 부품이 하나는 있어야 한다');
318
+ return { errors: c.errors, violations: c.violations };
319
+ }
320
+ const seen = new Set();
321
+ source.parts.forEach((part, i) => validatePart(c, part, i, seen));
322
+ // 정책 검사는 형식이 성립할 때만 뜻이 있다
323
+ if (c.errors.length === 0)
324
+ checkPolicy(c, source);
325
+ return { errors: c.errors, violations: c.violations };
326
+ }
327
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
package/docs/design.md ADDED
@@ -0,0 +1,192 @@
1
+ # Figure Blueprint — things-scene 에서 작동될 수 있는 모델
2
+
3
+ - 상태: 계약 정의 + 코어 구현 (2026-09-02)
4
+ - 연관: `things-factory/docs/design/component-authoring-tool.md` (저작도구 전체 설계)
5
+
6
+ 저작도구(things-factory)가 만든 Figure 를 **things-scene 이 실제로 세울 수 있는
7
+ 형태**로 정의한다. 이 문서는 엔진 쪽 계약만 다룬다.
8
+
9
+ ---
10
+
11
+ ## 1. 무엇을 세울 수 있나 — 지금 사실
12
+
13
+ ### 프리미티브와 상태 키
14
+
15
+ 저작에 쓸 프리미티브는 두 계열이고 상태 키가 다르다.
16
+
17
+ | 프리미티브 | 계열 | 치수 상태 키 | 근거 |
18
+ | ---------- | ------- | ---------------------------- | -------------------------- |
19
+ | `cube` | mesh | `width` · `height` · `depth` | `factories/cube.ts:22` |
20
+ | `wall` | mesh | `width` · `height` · `depth` | `factories/wall.ts:29` |
21
+ | `cylinder` | mesh | `rx` · `ry` · `depth` | `factories/cylinder.ts:21` |
22
+ | `sphere` | mesh | `rx` · `ry` · `rz` | `factories/sphere.ts:21` |
23
+ | `rect` | extrude | `width` · `height` · `round` | `factories/rect.ts:11` |
24
+ | `polygon` | extrude | `path` · `round` | `factories/polygon.ts:30` |
25
+
26
+ 공통으로 `zPos`(바닥에서 높이) · `depth`(두께) · `material3d`(재질)를 갖는다
27
+ (`real-object.ts:489` · `:617`).
28
+
29
+ ### 이미 되는 것
30
+
31
+ | | 무엇 | 어디 |
32
+ | ---------------- | --------------------------------------------------------------------- | -------------------------------------------- |
33
+ | 재질 공유 | 인스턴스 무관. `base + 색 변형` | `threed/material-bank.ts` |
34
+ | 타입 런타임 등록 | `Component.register(type, class)` | `types/scene-component.ts:29` |
35
+ | 애니메이션 | fade · heartbeat · moving · outline · rotation · vibration · waypoint | `animation/animations/` |
36
+ | 데이터 바인딩 | `mappings` · `accessor` · `data` | `components/component.ts:78` · `:722` |
37
+ | 이벤트 | `state.eventHandlers` | `components/component.ts:263` |
38
+ | 정사영 톱뷰 | `setOrthoView('top')` | `threed/managers/camera-manager.ts:97` |
39
+ | 변환 기즈모 | translate · rotate · scale + 회전 스냅 | `threed/managers/gizmo-manager.ts:4` · `:86` |
40
+
41
+ ### 안 되는 것 — 신설이 필요하다
42
+
43
+ | | 무엇 | 왜 필요한가 |
44
+ | --- | --------------------------------------------------------------------- | --------------------------- |
45
+ | 1 | **분할 수가 하드코딩** — cylinder 25, sphere 32×16 | 로우폴리 · geometry 캐시 키 |
46
+ | 2 | **`Material3D.flatShading` 미노출** — `material-bank.ts:32` 에는 있다 | 로우폴리 미학 |
47
+ | 3 | **geometry 공유 없음** | 대량 배치 |
48
+ | 4 | **팔레트 토큰 해석 없음** — `fillStyle` 은 값 그대로 | 스타일 추종 |
49
+ | 5 | **톱뷰 윤곽 계산 없음** | 2D 표현 |
50
+ | 6 | **부품 병합 없음** | draw call · `fill()` 호출 |
51
+
52
+ ---
53
+
54
+ ## 2. 병목 — 2D 와 3D 가 같다
55
+
56
+ | | 3D | 2D |
57
+ | -------------------------- | -------------- | -------------------- |
58
+ | 병목 | **draw call** | **`fill()` 호출 수** |
59
+ | 자산 500 개 × 부품 8 개 | 4000 draw call | 4000 fill |
60
+ | 재질(색) 3 종으로 병합하면 | 1500 | 1500 |
61
+
62
+ **같은 병합이 양쪽에 이득이다.** 그래서 청사진은 2D 와 3D 를 **한 묶음 구조**로
63
+ 다룬다 — 재질별로 묶고, 각 묶음이 3D geometry 와 2D 윤곽을 함께 갖는다.
64
+
65
+ 삼각형 수는 병목이 아니다. 등급 M(자산당 1000) × 500 = 50 만이고 GPU 는 이 정도를
66
+ 신경 쓰지 않는다. **재는 것은 묶음 수다.**
67
+
68
+ ---
69
+
70
+ ## 3. 계약
71
+
72
+ ### 정본 (`FigureSource`) — 저작 결과, 편집 가능
73
+
74
+ ```
75
+ FigureSource
76
+ type 컴포넌트 타입 이름. 저장되는 식별자
77
+ base 기준 치수 { width, height, depth } — 배치 비율의 근거
78
+ anchor? 기준점
79
+ detailLevel? 'S' | 'M' | 'L'
80
+ styleKit? StyleKit 참조
81
+ parts[] FigurePart
82
+ ```
83
+
84
+ ```
85
+ FigurePart
86
+ name 부품 이름. 저장되는 식별자 — 바인딩·애니메이션·슬롯의 지시자
87
+ primitive 'cube' | 'cylinder' | 'sphere' | 'wall' | 'rect' | 'polygon'
88
+ transform { x, y, z, width, height, depth, rotation? }
89
+ segments? 분할 수 (mesh 계열)
90
+ material { token?, preset?, flatShading? } ← 색은 토큰
91
+ slot? 'cap' | 'side' 상태 색이 연동될 재질 슬롯
92
+ sizing? 'scale' | 'fixed' | 'stretch' | 'repeat'
93
+ repeat? { axis, gap } sizing === 'repeat' 일 때
94
+ animation? { kind, axis?, pivot? } 애니메이션 포인트
95
+ label? { name, source, when } 텍스트 표현 자리
96
+ ```
97
+
98
+ ### 청사진 (`FigureBlueprint`) — 보드가 소비, 파생·캐시
99
+
100
+ ```
101
+ FigureBlueprint
102
+ version 스키마 버전
103
+ type 정본과 같다
104
+ base, anchor
105
+ budget { triangles, groups }
106
+ groups[] BlueprintGroup 재질별로 병합된 묶음 (2D·3D 공통)
107
+ points[] BlueprintPoint 애니메이션 포인트
108
+ labels[] BlueprintLabel
109
+ violations[] Violation 제약 위반 — 막지 않고 알린다
110
+ ```
111
+
112
+ ```
113
+ BlueprintGroup
114
+ materialKey MaterialBank 키
115
+ material { token?, preset?, flatShading? }
116
+ slot? 이 묶음이 상태에 연동되는 슬롯
117
+ animated 이 묶음이 애니메이션 포인트에 걸렸나 (걸리면 단독 묶음)
118
+ members[] { name, geometryKey, primitive, transform, sizing, repeat?, triangles }
119
+ ```
120
+
121
+ `members` 가 **병합 대상**이다. 3D 는 이들의 geometry 를 합치고, 2D 는 이들의 윤곽을
122
+ 한 path 로 합쳐 한 번에 채운다.
123
+
124
+ ### 병합 규칙
125
+
126
+ 같은 묶음에 들어가는 조건은 셋을 모두 만족할 때다.
127
+
128
+ ```
129
+ 1. materialKey 가 같다
130
+ 2. slot 이 같다 ← 다르면 상태 색이 따로 바뀌어야 한다
131
+ 3. 애니메이션 포인트가 없다 ← 있으면 따로 움직여야 한다
132
+ ```
133
+
134
+ 애니메이션이 걸린 부품은 **부품마다 단독 묶음**이 된다.
135
+
136
+ ```
137
+ 자산당 묶음 수 = 재질·슬롯 조합 수 + 애니메이션 포인트 수
138
+ ```
139
+
140
+ ### 키 규약
141
+
142
+ ```
143
+ geometryKey {primitive}|{정규화 치수}|s{segments}
144
+ cylinder|300x300x800|s12
145
+ cube|200x100x400
146
+
147
+ materialKey {token}|{preset}|{flatShading}
148
+ palette.primary|metal|f
149
+ ```
150
+
151
+ **치수를 정규화한다.** 비례 그리드(저작도구 §4.11)로 치수가 이산적이므로 키 조합 수가
152
+ 작고 캐시 적중률이 높다. 정규화는 그 전제를 엔진 쪽에서 한 번 더 보장한다.
153
+
154
+ ---
155
+
156
+ ## 4. 제약 검증 — 막지 않고 알린다
157
+
158
+ | 제약 | 값 |
159
+ | -------------------- | ---------------------- |
160
+ | 재질·슬롯 조합 수 | ≤ 3 |
161
+ | 애니메이션 포인트 수 | ≤ 2 |
162
+ | 부품 수 | S ≤ 3 · M ≤ 8 · L ≤ 20 |
163
+ | 투명 재질 | ≤ 1 |
164
+
165
+ 위반은 `violations[]` 로 돌려준다. **컴파일을 막지 않는다** — 저작도구가 숫자로
166
+ 보여 주고 사용자가 등급을 올리거나 줄인다.
167
+
168
+ ---
169
+
170
+ ## 5. 이번에 구현한 것
171
+
172
+ ```
173
+ src/figure/types.ts 계약
174
+ src/figure/blueprint.ts 정본 → 청사진 컴파일 (병합 · 키 · 예산 · 검증)
175
+ src/figure/index.ts 재출력
176
+ test/unit/figure/test-blueprint.js
177
+ ```
178
+
179
+ **순수 함수다.** 씬을 세우지 않고 시험한다 — `model/arrange.ts` · `utils/polygon-shape.ts`
180
+ 와 같은 방식이다.
181
+
182
+ ## 6. 남은 것
183
+
184
+ | | 무엇 |
185
+ | --- | ------------------------------------------------------------ |
186
+ | 1 | `segments` 상태 — 하드코딩된 분할 수를 뺀다 |
187
+ | 2 | `Material3D.flatShading` 노출 |
188
+ | 3 | `GeometryBank` — `material-bank` 를 본뜬다 |
189
+ | 4 | 톱뷰 윤곽 계산 — `RealObject.topOutline` |
190
+ | 5 | 팔레트 토큰 해석 |
191
+ | 6 | `FigureInstance` 컴포넌트 — 청사진을 세운다 |
192
+ | 7 | 부품 병합 실제 구현 (BufferGeometry 합치기 · 2D path 합치기) |
@@ -0,0 +1,108 @@
1
+ # 기존 3D 모델 포맷 검토 — 왜 자체 형식인가
2
+
3
+ 자체 형식을 만들기 전에 쓸 수 있는 표준이 있는지 먼저 본다. 결론은 **정본으로 쓸
4
+ 만한 것이 없다**이고, 이 문서는 그 근거다.
5
+
6
+ ---
7
+
8
+ ## 우리가 담아야 하는 것
9
+
10
+ 형상만이 아니다. 세 층이 필요하다.
11
+
12
+ | 층 | 무엇 | 표준 포맷에 있나 |
13
+ | ------------- | ----------------------------------------------------------------------------- | ---------------- |
14
+ | ① 부품 그래프 | 프리미티브 · 치수 · 재질 · 변환 | **있다** |
15
+ | ② 반응 규칙 | 크기 반응(`scale`·`fixed`·`stretch`·`repeat`) · 애니메이션 포인트 · 상태 슬롯 | **없다** |
16
+ | ③ 결합 지점 | 태그 · 라벨(무엇을·언제) · 기준점 | **없다** |
17
+
18
+ 표준 3D 포맷은 **형상 교환**을 위해 만들어졌다. 우리가 필요한 것은 형상보다
19
+ **②③** 이고, 그것이 없으면 저작 결과가 보드에서 살아 움직이지 않는다.
20
+
21
+ ---
22
+
23
+ ## 후보별 검토
24
+
25
+ ### glTF 2.0
26
+
27
+ | | |
28
+ | ------------- | ------------------------------------------------------------------------- |
29
+ | 강점 | 사실상 표준. 웹 로더가 성숙. 재질·애니메이션 규격이 있다 |
30
+ | **막히는 것** | **구운 메시다.** 정점 배열이라 치수·분할 수·프리미티브 종류가 남지 않는다 |
31
+
32
+ 정본으로 쓰면 넷이 무너진다.
33
+
34
+ - **StyleKit 추종 불가** — 스타일을 바꿔도 이미 구운 것은 안 바뀐다
35
+ - **톱뷰 계산 불가** — 형상을 모르니 2D 윤곽을 계산할 수 없다 (스냅샷으로 떨어진다)
36
+ - **변경 반영 불가** — 고쳐서 배치된 것에 반영할 수 없다
37
+ - **`parts` 이름 · 상태 슬롯 소실** — 우리 의미가 정점 배열에 남지 않는다
38
+
39
+ `extras` / `extensions` 로 ②③을 끼워 넣을 수는 있다. 그러나 그러면 **glTF 를 쓰는
40
+ 것이 아니라 glTF 안에 우리 형식을 넣는 것**이고, 형상은 여전히 구워져 있어 ①의
41
+ 이득(파라메트릭 편집·공유)이 없다.
42
+
43
+ **결론 — 내보내기에는 쓴다. 정본으로는 못 쓴다.**
44
+
45
+ ### USD / USDZ
46
+
47
+ | | |
48
+ | ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
49
+ | 강점 | 파라메트릭 · 레이어링 · 참조(composition arcs)를 제대로 갖췄다. 우리가 필요한 「정의 하나, 인스턴스 여럿」이 언어 차원에 있다 |
50
+ | **막히는 것** | 스펙이 크다. 웹 런타임 파서가 무겁고, 우리가 쓸 부분은 전체의 아주 일부다 |
51
+
52
+ ②의 크기 반응 규칙은 USD 에도 없다. 결국 스키마를 얹어야 하는데, **그 대가로 무거운
53
+ 파서를 끌어온다.** 로우폴리 자산 하나가 몇 KB 인 것에 비해 균형이 맞지 않는다.
54
+
55
+ **결론 — 오버킬.**
56
+
57
+ ### three.js `Object3D.toJSON()`
58
+
59
+ geometry 가 구워지고 three 전용이다. glTF 와 같은 문제에 표준성까지 없다.
60
+
61
+ **결론 — 안 된다.**
62
+
63
+ ### OpenSCAD · CSG 스크립트
64
+
65
+ 파라메트릭이라는 점은 맞다. 그러나 **부울 연산 중심**이고 우리는 부울을 범위 밖으로
66
+ 두었다(로우폴리 미학의 도구가 아니고, geometry 캐시가 깨진다). 형상을 코드로 적는
67
+ 것은 저작도구의 사용자(현장 엔지니어)와 맞지 않는다.
68
+
69
+ **결론 — 방향이 다르다.**
70
+
71
+ ### SVG `<symbol>` / `<use>`
72
+
73
+ 「정의 하나, 인스턴스 여럿 + viewBox 비율」 구조가 우리가 원하는 것과 가깝다. 다만
74
+ **2D 전용**이다.
75
+
76
+ **결론 — 개념만 참고.**
77
+
78
+ ### things-scene 모델 JSON
79
+
80
+ | | |
81
+ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
82
+ | 강점 | 이미 파라메트릭이다. 컴포넌트 타입 + state 로 치수·재질·분할이 값으로 남는다. `mappings`·`eventHandlers`·`animation` 이 이미 state 키에 있다 |
83
+ | 부족한 것 | ②의 크기 반응 규칙과 ③의 라벨·태그 선언이 없다. 병합·묶음 개념이 없다 |
84
+
85
+ **가장 가깝다.** 그리고 부족한 것은 **얹으면 되는 것**이지 구조가 어긋나는 것이
86
+ 아니다.
87
+
88
+ ---
89
+
90
+ ## 결론
91
+
92
+ ```
93
+ 정본 자체 형식 (FigureSource)
94
+ things-scene 모델 JSON 과 친화적으로 — 치수·재질·분할이 값으로 남는다
95
+ ②반응 규칙 · ③결합 지점을 얹는다
96
+
97
+ 산출물 자체 형식 (FigureBlueprint)
98
+ 정본에서 파생. 재질별 병합 · 키 참조 · 예산
99
+
100
+ 내보내기 glTF 외부 뷰어·교환용. 단방향
101
+ ```
102
+
103
+ **자체 형식을 만드는 근거는 「표준이 부족해서」가 아니라 「담을 것이 다른 층에
104
+ 있어서」다.** 표준 포맷은 형상을 옮기려고 만들어졌고, 우리는 **반응하는 자산**을
105
+ 정의하려 한다.
106
+
107
+ 의존을 0 으로 두는 이유도 여기에 있다 — 이 형식은 특정 렌더러의 것이 아니다. 서버가
108
+ 검증하고, 저작도구가 만들고, 보드가 소비한다.