@ludic-labs/assembly 0.0.0-stage → 0.4.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.
- package/CHANGELOG.md +78 -0
- package/LICENSE +2 -0
- package/README.md +50 -2
- package/dist/bake.d.ts +80 -0
- package/dist/bake.js +214 -0
- package/dist/browser.d.ts +89 -0
- package/dist/browser.js +534 -0
- package/dist/compat.d.ts +17 -0
- package/dist/compat.js +153 -0
- package/dist/compiler.d.ts +18 -0
- package/dist/compiler.js +1264 -0
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +8 -0
- package/dist/export.d.ts +54 -0
- package/dist/export.js +272 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/legacy.d.ts +36 -0
- package/dist/legacy.js +180 -0
- package/dist/math.d.ts +16 -0
- package/dist/math.js +62 -0
- package/dist/mesh.d.ts +11 -0
- package/dist/mesh.js +153 -0
- package/dist/motion.d.ts +63 -0
- package/dist/motion.js +1476 -0
- package/dist/pixel.d.ts +268 -0
- package/dist/pixel.js +942 -0
- package/dist/required.d.ts +2 -0
- package/dist/required.js +6 -0
- package/dist/shapes.d.ts +71 -0
- package/dist/shapes.js +396 -0
- package/dist/sprite-brief.d.ts +5 -0
- package/dist/sprite-brief.js +47 -0
- package/dist/sprite-compiler.d.ts +62 -0
- package/dist/sprite-compiler.js +1745 -0
- package/dist/sprite-decompile.d.ts +68 -0
- package/dist/sprite-decompile.js +786 -0
- package/dist/sprite-format.d.ts +4 -0
- package/dist/sprite-format.js +155 -0
- package/dist/sprite-guide.d.ts +40 -0
- package/dist/sprite-guide.js +330 -0
- package/dist/sprite-scene.d.ts +277 -0
- package/dist/sprite-scene.js +664 -0
- package/dist/sprite-types.d.ts +110 -0
- package/dist/sprite-types.js +1 -0
- package/dist/sprite.d.ts +8 -0
- package/dist/sprite.js +8 -0
- package/dist/svg/schema.d.ts +71 -0
- package/dist/svg/schema.js +217 -0
- package/dist/svg/types.d.ts +148 -0
- package/dist/svg/types.js +1 -0
- package/dist/svg.d.ts +26 -0
- package/dist/svg.js +539 -0
- package/dist/text.d.ts +12 -0
- package/dist/text.js +75 -0
- package/dist/types.d.ts +478 -0
- package/dist/types.js +1 -0
- package/docs/svg/README.md +150 -0
- package/docs/svg/specification.md +108 -0
- package/examples/svg/arcane-shield.json +261 -0
- package/examples/svg/aurora-ribbon.json +158 -0
- package/examples/svg/bouncing-ember.json +76 -0
- package/examples/svg/ember-fountain.json +224 -0
- package/examples/svg/lantern-keeper.json +1159 -0
- package/examples/svg/night-workshop.json +1120 -0
- package/examples/svg/waygate-platform.json +64 -0
- package/examples/svg/waygate-spark.json +92 -0
- package/examples/svg/waygate-wisp.json +105 -0
- package/examples/svg/waygate.json +485 -0
- package/package.json +132 -4
- package/schemas/asset-assembly-0.1.schema.json +354 -0
- package/schemas/asset-assembly-0.2.schema.json +429 -0
- package/schemas/asset-assembly-0.3.schema.json +572 -0
- package/schemas/motion-set-0.1.schema.json +178 -0
- package/schemas/pixel-sprite-0.1.schema.json +892 -0
- package/schemas/pixel-sprite-scene-0.1.schema.json +149 -0
- package/schemas/svg-asset-0.1.schema.json +1157 -0
- package/tools/svg-asset.mjs +106 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
export type Vec3 = [number, number, number];
|
|
2
|
+
export interface MeshData {
|
|
3
|
+
positions: number[];
|
|
4
|
+
normals: number[];
|
|
5
|
+
colors: number[];
|
|
6
|
+
indices: number[];
|
|
7
|
+
/**
|
|
8
|
+
* Per-vertex joint indices. Legacy compatibility meshes use adapter-defined IDs. Schema 0.2
|
|
9
|
+
* compilations index `CompiledAsset.skeleton.joints`; -1 marks a static vertex.
|
|
10
|
+
*/
|
|
11
|
+
joints?: number[];
|
|
12
|
+
/** Optional joint pivot heights, owned by the consuming adapter. */
|
|
13
|
+
rigPivots?: readonly [number, number];
|
|
14
|
+
}
|
|
15
|
+
export type Scalar = number | {
|
|
16
|
+
parameter: string;
|
|
17
|
+
};
|
|
18
|
+
export interface Point {
|
|
19
|
+
x: Scalar;
|
|
20
|
+
y: Scalar;
|
|
21
|
+
z: Scalar;
|
|
22
|
+
}
|
|
23
|
+
export type Anchor = "center" | "top-center" | "bottom-center" | "left-center" | "right-center" | "front-center" | "back-center";
|
|
24
|
+
export interface Size {
|
|
25
|
+
width: Scalar;
|
|
26
|
+
height: Scalar;
|
|
27
|
+
depth: Scalar;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `sides` (schema 0.3) is an integer from 3 to 32; round shapes default to 16 sides and spheres
|
|
31
|
+
* and ellipsoids to 12 meridians.
|
|
32
|
+
*/
|
|
33
|
+
export type Shape = {
|
|
34
|
+
type: "box";
|
|
35
|
+
size: Size;
|
|
36
|
+
bevel?: Scalar;
|
|
37
|
+
} | {
|
|
38
|
+
type: "cylinder";
|
|
39
|
+
radius: Scalar;
|
|
40
|
+
height: Scalar;
|
|
41
|
+
sides?: number;
|
|
42
|
+
} | {
|
|
43
|
+
type: "funnel";
|
|
44
|
+
bottomRadius: Scalar;
|
|
45
|
+
topRadius: Scalar;
|
|
46
|
+
height: Scalar;
|
|
47
|
+
sides?: number;
|
|
48
|
+
} | {
|
|
49
|
+
type: "beam";
|
|
50
|
+
from: Point;
|
|
51
|
+
to: Point;
|
|
52
|
+
width: Scalar;
|
|
53
|
+
} | {
|
|
54
|
+
type: "tube";
|
|
55
|
+
from: Point;
|
|
56
|
+
to: Point;
|
|
57
|
+
radius: Scalar;
|
|
58
|
+
sides?: number;
|
|
59
|
+
} | {
|
|
60
|
+
type: "coil";
|
|
61
|
+
radius: Scalar;
|
|
62
|
+
thickness: Scalar;
|
|
63
|
+
sides?: number;
|
|
64
|
+
}
|
|
65
|
+
/** Schema 0.3. Bottom-centered: spans y = 0 to 2 × radius. */
|
|
66
|
+
| {
|
|
67
|
+
type: "sphere";
|
|
68
|
+
radius: Scalar;
|
|
69
|
+
sides?: number;
|
|
70
|
+
}
|
|
71
|
+
/** Schema 0.3. Bottom-centered, spanning the given size. */
|
|
72
|
+
| {
|
|
73
|
+
type: "ellipsoid";
|
|
74
|
+
size: Size;
|
|
75
|
+
sides?: number;
|
|
76
|
+
}
|
|
77
|
+
/** Schema 0.3. Base at y = 0, apex at y = height. */
|
|
78
|
+
| {
|
|
79
|
+
type: "cone";
|
|
80
|
+
radius: Scalar;
|
|
81
|
+
height: Scalar;
|
|
82
|
+
sides?: number;
|
|
83
|
+
}
|
|
84
|
+
/** Schema 0.3. Full-height back face (+Z); the slope descends to the front-bottom edge. */
|
|
85
|
+
| {
|
|
86
|
+
type: "wedge";
|
|
87
|
+
size: Size;
|
|
88
|
+
}
|
|
89
|
+
/** Schema 0.3. Surface of revolution about the local Y axis; y is non-decreasing. */
|
|
90
|
+
| {
|
|
91
|
+
type: "lathe";
|
|
92
|
+
profile: {
|
|
93
|
+
radius: Scalar;
|
|
94
|
+
y: Scalar;
|
|
95
|
+
}[];
|
|
96
|
+
sides?: number;
|
|
97
|
+
}
|
|
98
|
+
/** Schema 0.3. A simple X-Z outline extruded from y = 0 to height. */
|
|
99
|
+
| {
|
|
100
|
+
type: "extrude";
|
|
101
|
+
outline: {
|
|
102
|
+
x: Scalar;
|
|
103
|
+
z: Scalar;
|
|
104
|
+
}[];
|
|
105
|
+
height: Scalar;
|
|
106
|
+
};
|
|
107
|
+
/** Schema 0.3: degrees about the local origin, applied X, then Y, then Z (fixed axes). */
|
|
108
|
+
export interface Rotation {
|
|
109
|
+
x?: Scalar;
|
|
110
|
+
y?: Scalar;
|
|
111
|
+
z?: Scalar;
|
|
112
|
+
}
|
|
113
|
+
/** Schema 0.3: copies of one part. Instance IDs are part/0 … part/n−1. */
|
|
114
|
+
export type ArraySpec = {
|
|
115
|
+
type: "line";
|
|
116
|
+
count: number;
|
|
117
|
+
step: Point;
|
|
118
|
+
centered?: boolean;
|
|
119
|
+
} | {
|
|
120
|
+
type: "grid";
|
|
121
|
+
count: {
|
|
122
|
+
x?: number;
|
|
123
|
+
y?: number;
|
|
124
|
+
z?: number;
|
|
125
|
+
};
|
|
126
|
+
spacing: {
|
|
127
|
+
x?: Scalar;
|
|
128
|
+
y?: Scalar;
|
|
129
|
+
z?: Scalar;
|
|
130
|
+
};
|
|
131
|
+
centered?: boolean;
|
|
132
|
+
} | {
|
|
133
|
+
type: "radial";
|
|
134
|
+
count: number;
|
|
135
|
+
radius: Scalar;
|
|
136
|
+
axis?: "x" | "y" | "z";
|
|
137
|
+
start?: Scalar;
|
|
138
|
+
sweep?: Scalar;
|
|
139
|
+
orient?: boolean;
|
|
140
|
+
};
|
|
141
|
+
export type Placement = {
|
|
142
|
+
type: "absolute";
|
|
143
|
+
selfAnchor: string;
|
|
144
|
+
position: Point;
|
|
145
|
+
} | {
|
|
146
|
+
type: "attach";
|
|
147
|
+
selfAnchor: string;
|
|
148
|
+
target: {
|
|
149
|
+
part: string;
|
|
150
|
+
anchor: string;
|
|
151
|
+
};
|
|
152
|
+
offset?: Point;
|
|
153
|
+
} | {
|
|
154
|
+
type: "repeat";
|
|
155
|
+
pattern: "corners";
|
|
156
|
+
selfAnchor: string;
|
|
157
|
+
target: {
|
|
158
|
+
part: string;
|
|
159
|
+
face: "top" | "bottom";
|
|
160
|
+
};
|
|
161
|
+
edgeInset: {
|
|
162
|
+
x: Scalar;
|
|
163
|
+
z: Scalar;
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
export interface PartSpec {
|
|
167
|
+
shape: Shape;
|
|
168
|
+
material: string;
|
|
169
|
+
placement: Placement;
|
|
170
|
+
parent?: string;
|
|
171
|
+
anchors?: Record<string, Point>;
|
|
172
|
+
/** Schema 0.2: rigid binding to a joint. Attached parts inherit their target's joint. */
|
|
173
|
+
joint?: string;
|
|
174
|
+
/** Schema 0.2: author the character's left (−X) side; the right side is its mirror image. */
|
|
175
|
+
sides?: "both";
|
|
176
|
+
/** Schema 0.2: the part exists only when its slot selects this option. */
|
|
177
|
+
slot?: string;
|
|
178
|
+
option?: string;
|
|
179
|
+
/** Schema 0.2: visible only while every listed state channel is in one of its states. */
|
|
180
|
+
when?: Record<string, string[]>;
|
|
181
|
+
/** Schema 0.3: static orientation of the shape and its custom anchors before placement. */
|
|
182
|
+
rotation?: Rotation;
|
|
183
|
+
/** Schema 0.3: repeat the placed part along a line, on a grid or around a circle. */
|
|
184
|
+
array?: ArraySpec;
|
|
185
|
+
}
|
|
186
|
+
export type Axis = "+x" | "-x" | "+y" | "-y" | "+z" | "-z";
|
|
187
|
+
export interface DofSpec {
|
|
188
|
+
name: string;
|
|
189
|
+
axis: Axis;
|
|
190
|
+
/** Degrees. The rest value 0 must lie within the limits. */
|
|
191
|
+
limits: [number, number];
|
|
192
|
+
/**
|
|
193
|
+
* Schema 0.3: a free-spinning DOF (wheels, rotors, clock hands). A looping clip may close after
|
|
194
|
+
* whole turns, e.g. keys 0° at 0 and 360° at 1, and keeps turning instead of rewinding.
|
|
195
|
+
*/
|
|
196
|
+
continuous?: boolean;
|
|
197
|
+
}
|
|
198
|
+
export interface JointSpec {
|
|
199
|
+
parent?: string;
|
|
200
|
+
sides?: "both";
|
|
201
|
+
/** Face joints are animated by expressions; all other joints by poses. */
|
|
202
|
+
face?: boolean;
|
|
203
|
+
pivot: {
|
|
204
|
+
part: string;
|
|
205
|
+
anchor: string;
|
|
206
|
+
} | {
|
|
207
|
+
position: Point;
|
|
208
|
+
};
|
|
209
|
+
dofs: DofSpec[];
|
|
210
|
+
}
|
|
211
|
+
export interface IdentitySpec {
|
|
212
|
+
parameters?: Record<string, number>;
|
|
213
|
+
materials?: Record<string, {
|
|
214
|
+
color: string;
|
|
215
|
+
}>;
|
|
216
|
+
slots?: Record<string, string>;
|
|
217
|
+
}
|
|
218
|
+
export type SchemaVersion = "asset-assembly/0.1" | "asset-assembly/0.2" | "asset-assembly/0.3";
|
|
219
|
+
export interface AssetSpec {
|
|
220
|
+
schema: SchemaVersion;
|
|
221
|
+
units: "meters" | "tiles";
|
|
222
|
+
parameters?: Record<string, number>;
|
|
223
|
+
materials: Record<string, {
|
|
224
|
+
color: string;
|
|
225
|
+
tintable?: boolean;
|
|
226
|
+
}>;
|
|
227
|
+
groups?: Record<string, {
|
|
228
|
+
parent?: string;
|
|
229
|
+
position: Point;
|
|
230
|
+
}>;
|
|
231
|
+
parts: Record<string, PartSpec>;
|
|
232
|
+
groundPivot?: Point;
|
|
233
|
+
/** Schema 0.2. */
|
|
234
|
+
joints?: Record<string, JointSpec>;
|
|
235
|
+
/** Schema 0.2: ordered options; the first is the default. */
|
|
236
|
+
slots?: Record<string, string[]>;
|
|
237
|
+
/** Schema 0.2: bounded overrides of declared parameters, colors and slots. */
|
|
238
|
+
identities?: Record<string, IdentitySpec>;
|
|
239
|
+
/** Schema 0.2: animated visibility channels; the first state is the rest state. */
|
|
240
|
+
states?: Record<string, string[]>;
|
|
241
|
+
/** Schema 0.2: chains a clip may solve to keep planted effectors in place. */
|
|
242
|
+
limbs?: Record<string, LimbSpec>;
|
|
243
|
+
}
|
|
244
|
+
export interface LimbSpec {
|
|
245
|
+
sides?: "both";
|
|
246
|
+
/** The point kept planted, e.g. the ankle anchor of a foot. */
|
|
247
|
+
effector: {
|
|
248
|
+
part: string;
|
|
249
|
+
anchor: string;
|
|
250
|
+
};
|
|
251
|
+
/** One to three DOFs solved to place the effector, from the root of the chain outward. */
|
|
252
|
+
reach: {
|
|
253
|
+
joint: string;
|
|
254
|
+
dof: string;
|
|
255
|
+
}[];
|
|
256
|
+
/** Optional DOF solved to keep the effector part level (its front horizontal). */
|
|
257
|
+
level?: {
|
|
258
|
+
joint: string;
|
|
259
|
+
dof: string;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
export interface CompiledLimb {
|
|
263
|
+
id: string;
|
|
264
|
+
effector: string;
|
|
265
|
+
anchor: string;
|
|
266
|
+
reach: {
|
|
267
|
+
joint: string;
|
|
268
|
+
dof: string;
|
|
269
|
+
}[];
|
|
270
|
+
level?: {
|
|
271
|
+
joint: string;
|
|
272
|
+
dof: string;
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
export interface Diagnostic {
|
|
276
|
+
code: string;
|
|
277
|
+
path: string;
|
|
278
|
+
message: string;
|
|
279
|
+
part?: string;
|
|
280
|
+
targetPart?: string;
|
|
281
|
+
received?: unknown;
|
|
282
|
+
expected?: readonly string[];
|
|
283
|
+
/** Identity whose compilation produced the diagnostic (schema 0.2 validation). */
|
|
284
|
+
identity?: string;
|
|
285
|
+
}
|
|
286
|
+
export interface Bounds {
|
|
287
|
+
min: Vec3;
|
|
288
|
+
max: Vec3;
|
|
289
|
+
}
|
|
290
|
+
export interface ResolvedInstance {
|
|
291
|
+
id: string;
|
|
292
|
+
part: string;
|
|
293
|
+
material: string;
|
|
294
|
+
translation: Vec3;
|
|
295
|
+
bounds: Bounds;
|
|
296
|
+
anchors: Record<string, Vec3>;
|
|
297
|
+
mesh: MeshData;
|
|
298
|
+
/** Schema 0.2: compiled joint ID this instance follows, or null when static. */
|
|
299
|
+
joint?: string | null;
|
|
300
|
+
/** Schema 0.2: effective visibility conditions, including those inherited from attachment targets. */
|
|
301
|
+
when?: Record<string, string[]>;
|
|
302
|
+
}
|
|
303
|
+
export interface CompiledDof {
|
|
304
|
+
name: string;
|
|
305
|
+
/** Unit rotation axis in the asset frame (sign included; mirrored for right-side joints). */
|
|
306
|
+
axis: Vec3;
|
|
307
|
+
limits: [number, number];
|
|
308
|
+
/** Present and true for continuous DOFs (schema 0.3). */
|
|
309
|
+
continuous?: true;
|
|
310
|
+
}
|
|
311
|
+
export interface CompiledJoint {
|
|
312
|
+
id: string;
|
|
313
|
+
/** Declaring joint ID; equal to `id` unless the joint is sided. */
|
|
314
|
+
joint: string;
|
|
315
|
+
side?: "left" | "right";
|
|
316
|
+
/** Schema 0.3: index of this copy when the joint pivots on an arrayed part (IDs <joint>/<copy>). */
|
|
317
|
+
copy?: number;
|
|
318
|
+
/** Present and true for face joints. */
|
|
319
|
+
face?: true;
|
|
320
|
+
parent: string | null;
|
|
321
|
+
/** Rest-pose pivot in the asset frame. Rest frames are translation-only. */
|
|
322
|
+
pivot: Vec3;
|
|
323
|
+
dofs: CompiledDof[];
|
|
324
|
+
}
|
|
325
|
+
export interface Skeleton {
|
|
326
|
+
/** Parents precede children; ties are ordered by ID. */
|
|
327
|
+
joints: CompiledJoint[];
|
|
328
|
+
}
|
|
329
|
+
export interface CompiledAsset {
|
|
330
|
+
schema: SchemaVersion;
|
|
331
|
+
units: "meters" | "tiles";
|
|
332
|
+
sourceHash: string;
|
|
333
|
+
layers: Record<string, MeshData>;
|
|
334
|
+
materials: AssetSpec["materials"];
|
|
335
|
+
instances: ResolvedInstance[];
|
|
336
|
+
bounds: Bounds;
|
|
337
|
+
groundPivot: Vec3;
|
|
338
|
+
triangles: number;
|
|
339
|
+
/** Schema 0.2 only. */
|
|
340
|
+
skeleton?: Skeleton;
|
|
341
|
+
identity?: string | null;
|
|
342
|
+
slots?: Record<string, string>;
|
|
343
|
+
/** Declared state channels and their states (rest state first). */
|
|
344
|
+
states?: Record<string, string[]>;
|
|
345
|
+
/** Instances hidden in the rest states; poseAsset shows them when a pose selects their states. */
|
|
346
|
+
alternates?: ResolvedInstance[];
|
|
347
|
+
limbs?: CompiledLimb[];
|
|
348
|
+
/** Present on assets returned by poseAsset. */
|
|
349
|
+
pose?: {
|
|
350
|
+
clip: string;
|
|
351
|
+
phase: number;
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
export interface CompileOptions {
|
|
355
|
+
maxInstances?: number;
|
|
356
|
+
maxTriangles?: number;
|
|
357
|
+
/** Schema 0.2: compile one declared identity. Omit for the base description. */
|
|
358
|
+
identity?: string;
|
|
359
|
+
}
|
|
360
|
+
export type JointValues = Record<string, Record<string, number>>;
|
|
361
|
+
export interface MotionKeySpec {
|
|
362
|
+
at: number;
|
|
363
|
+
pose?: string;
|
|
364
|
+
expression?: string;
|
|
365
|
+
joints?: JointValues;
|
|
366
|
+
states?: Record<string, string>;
|
|
367
|
+
}
|
|
368
|
+
export type Grounding = {
|
|
369
|
+
type: "none";
|
|
370
|
+
} | {
|
|
371
|
+
type: "contacts";
|
|
372
|
+
} | {
|
|
373
|
+
type: "anchor";
|
|
374
|
+
part: string;
|
|
375
|
+
anchor: string;
|
|
376
|
+
};
|
|
377
|
+
export interface ClipSpec {
|
|
378
|
+
duration: number;
|
|
379
|
+
loop: boolean;
|
|
380
|
+
interpolation?: "smooth" | "linear" | "step";
|
|
381
|
+
symmetry?: "none" | "alternate";
|
|
382
|
+
keys: MotionKeySpec[];
|
|
383
|
+
contacts?: Record<string, [number, number][]>;
|
|
384
|
+
grounding?: Grounding;
|
|
385
|
+
travel?: "contacts" | {
|
|
386
|
+
x: number;
|
|
387
|
+
y: number;
|
|
388
|
+
z: number;
|
|
389
|
+
};
|
|
390
|
+
/** Solve declared limbs so planted effectors stay where they touched down. */
|
|
391
|
+
solve?: "none" | "planted";
|
|
392
|
+
}
|
|
393
|
+
export interface MotionSpec {
|
|
394
|
+
schema: "motion-set/0.1";
|
|
395
|
+
poses?: Record<string, {
|
|
396
|
+
joints: JointValues;
|
|
397
|
+
}>;
|
|
398
|
+
expressions?: Record<string, {
|
|
399
|
+
joints?: JointValues;
|
|
400
|
+
states?: Record<string, string>;
|
|
401
|
+
}>;
|
|
402
|
+
clips: Record<string, ClipSpec>;
|
|
403
|
+
}
|
|
404
|
+
export interface MotionChannel {
|
|
405
|
+
joint: string;
|
|
406
|
+
dof: string;
|
|
407
|
+
/** Sorted key times (normalized 0–1) and degrees. */
|
|
408
|
+
times: number[];
|
|
409
|
+
values: number[];
|
|
410
|
+
/** Hermite tangents in degrees per normalized time unit. */
|
|
411
|
+
tangents: number[];
|
|
412
|
+
/** Continuous DOFs: degrees added per loop (a whole number of turns). */
|
|
413
|
+
cycle?: number;
|
|
414
|
+
}
|
|
415
|
+
export interface StateTrack {
|
|
416
|
+
channel: string;
|
|
417
|
+
/** Sorted key times; each state holds until the next key (step). */
|
|
418
|
+
times: number[];
|
|
419
|
+
values: string[];
|
|
420
|
+
}
|
|
421
|
+
export interface ContactTrack {
|
|
422
|
+
effector: string;
|
|
423
|
+
intervals: [number, number][];
|
|
424
|
+
}
|
|
425
|
+
export interface ClipMetrics {
|
|
426
|
+
/** Largest planted-effector horizontal drift from its touchdown point, in asset units. */
|
|
427
|
+
maxSlip: number;
|
|
428
|
+
/** Largest planted-effector height above the ground, in asset units. */
|
|
429
|
+
maxLift: number;
|
|
430
|
+
/** Lowest sampled vertex height (ground grounding only), in asset units. */
|
|
431
|
+
minHeight: number;
|
|
432
|
+
/** Root travel speed in asset units per second. */
|
|
433
|
+
speed: number;
|
|
434
|
+
}
|
|
435
|
+
export interface CompiledClip {
|
|
436
|
+
name: string;
|
|
437
|
+
duration: number;
|
|
438
|
+
loop: boolean;
|
|
439
|
+
interpolation: "smooth" | "linear" | "step";
|
|
440
|
+
symmetry: "none" | "alternate";
|
|
441
|
+
channels: MotionChannel[];
|
|
442
|
+
states: StateTrack[];
|
|
443
|
+
/** True when the clip keys only face joints and states, so it can overlay any body clip. */
|
|
444
|
+
face: boolean;
|
|
445
|
+
contacts: ContactTrack[];
|
|
446
|
+
grounding: Grounding;
|
|
447
|
+
solve: "none" | "planted";
|
|
448
|
+
/** Root displacement per clip cycle in asset units. */
|
|
449
|
+
travel: Vec3;
|
|
450
|
+
metrics: ClipMetrics;
|
|
451
|
+
}
|
|
452
|
+
export interface CompiledMotion {
|
|
453
|
+
schema: "motion-set/0.1";
|
|
454
|
+
sourceHash: string;
|
|
455
|
+
assetHash: string;
|
|
456
|
+
poses: Record<string, CompiledClip>;
|
|
457
|
+
expressions: Record<string, CompiledClip>;
|
|
458
|
+
clips: Record<string, CompiledClip>;
|
|
459
|
+
}
|
|
460
|
+
/** A rigid transform as a row-major 3×4 matrix: [r00 r01 r02 tx r10 r11 r12 ty r20 r21 r22 tz]. */
|
|
461
|
+
export type Transform = number[];
|
|
462
|
+
export interface SampledPose {
|
|
463
|
+
clip: string;
|
|
464
|
+
phase: number;
|
|
465
|
+
/** Face overlay clip name, when one was sampled. */
|
|
466
|
+
face?: string;
|
|
467
|
+
/** Degrees per compiled joint and DOF. */
|
|
468
|
+
values: JointValues;
|
|
469
|
+
/** Current state per declared channel. */
|
|
470
|
+
states: Record<string, string>;
|
|
471
|
+
/** Grounding offset applied to the whole asset. */
|
|
472
|
+
root: Vec3;
|
|
473
|
+
/** Cumulative travel for the host to apply to the entity; not applied by poseAsset. */
|
|
474
|
+
travel: Vec3;
|
|
475
|
+
transforms: Record<string, Transform>;
|
|
476
|
+
/** Effectors planted at this phase. */
|
|
477
|
+
contacts: string[];
|
|
478
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# SVG Asset Notation 0.1
|
|
2
|
+
|
|
3
|
+
**Proposal:** use `svg-asset/0.1`, a typed JSON construction language whose output is SVG. An asset consists of named parts in local coordinate systems, reusable symbols, bounded parameters, and named animation clips. The same parameters construct a still image and animate it. Characters, interiors and effects share one compiler; there are no built-in fox, room or portal node types.
|
|
4
|
+
|
|
5
|
+
The 0.1 implementation is complete for the contract below. It is an initial version, not a claim of universal asset generation, visual approval, natural motion or a stable 1.0 API. It was first included in package version 0.4.0; **0.1 is the notation version**. This work adds the isolated `@ludic-labs/assembly/svg` entry point without changing the existing mesh compiler. The documentation includes [Ember’s Waygate](https://libraries.ludic-assembly.victorqueiroz.dev/svg/), a playable C++23/WebAssembly and WebGL 1.0 showcase, plus an [asset atelier](https://libraries.ludic-assembly.victorqueiroz.dev/svg/atelier/) for editing and exporting the examples.
|
|
6
|
+
|
|
7
|
+
## What 0.1 delivers
|
|
8
|
+
|
|
9
|
+
The effect collection includes the original waygate plus [aurora ribbon](../../examples/svg/aurora-ribbon.json), [ember fountain](../../examples/svg/ember-fountain.json) and [arcane shield](../../examples/svg/arcane-shield.json). Each has bounded editable parameters and a looping clip; the atelier exports the exact JSON, still SVG and self-playing SVG. Homepage previews start still and offer an explicit play/pause control. These are original examples, not a new notation version or a performance qualification.
|
|
10
|
+
|
|
11
|
+
| Area | Implemented |
|
|
12
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| Construction | Ordered groups; local translate, rotate, scale and pivot; named symbol instances with local numeric arguments; bounded repetition |
|
|
14
|
+
| Geometry | Rounded rectangles, ellipses, absolute lines, quadratic/cubic Béziers and elliptical arcs, multiple path subpaths |
|
|
15
|
+
| Appearance | Palette tokens, fill/stroke, line caps/joins, fill rules, opacity, linear/radial gradients, clips, luminance masks and Gaussian blur |
|
|
16
|
+
| Animation | Named clips, seconds-based keys, linear/smooth/hold interpolation, loops, one-shots, direct seeking, parameter-driven path deformation and articulated groups |
|
|
17
|
+
| Effects | Seeded, repeated particle instances; loop/burst lifetimes, velocity/acceleration and age-driven size/opacity/geometry |
|
|
18
|
+
| Delivery | Static SVG, self-playing SVG without scripts, SVG frame sequences with timing manifests, a CLI and a browser authoring preview |
|
|
19
|
+
| Validation | TypeScript types, JSON Schema, structured diagnostics, reference/scope/cycle checks, numeric and expansion limits |
|
|
20
|
+
|
|
21
|
+
Arbitrary Bézier paths, composition and reusable definitions give broad **2D vector expressiveness**. They do not give infinite computational capacity, automatically infer good art, or cover every feature of SVG. A producer still authors or generates a valid definition. In particular, 0.1 has no raster import, font-dependent text, boolean geometry solver, automatic rigging, IK, skinning, physics, clip blending, topology-changing path interpolation, 3D reconstruction, or text-to-image model. Complex artwork can be represented with more authored paths within the declared budgets.
|
|
22
|
+
|
|
23
|
+
## Research basis
|
|
24
|
+
|
|
25
|
+
The language and implementation are our engineering synthesis. These original works inform specific decisions; none of their learned models or complete algorithms is reproduced.
|
|
26
|
+
|
|
27
|
+
| Original source | Evidence reviewed | Decision in 0.1 | Boundary |
|
|
28
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| Jones et al., [ShapeAssembly: Learning to Generate Programs for 3D Shape Structure Synthesis](https://arxiv.org/html/2009.08026v1), ACM TOG 39(6), 2020, DOI 10.1145/3414685.3417812 | Abstract, introduction and language discussion: hierarchical part programs and continuous parameters describe editable families | Named local groups, explicit symbols and arguments, repeat operations, parameters retained in the source | This is a 2D authoring language; no cuboid attachment solver, PartNet extraction, VAE or learned synthesis |
|
|
30
|
+
| Lasseter, [Principles of Traditional Animation Applied to 3D Computer Animation](https://www.cs.cmu.edu/afs/cs/academic/class/15462-f13/www/lec_slides/Lesseter.pdf), SIGGRAPH 1987, pp. 35–44 | Original scanned paper, pp. 36–38: squash/stretch, timing, anticipation, staging and their relation to key poses | Independent pose parameters, explicit clip timing, local pivots, squash/stretch through scale, authored secondary motion | Smoothstep is our interpolation choice; exposing controls does not guarantee appealing movement or preserve volume automatically |
|
|
31
|
+
| Reeves, [Particle Systems—A Technique for Modeling a Class of Fuzzy Objects](https://graphics.stanford.edu/courses/cs448-01-spring/papers/reeves.pdf), ACM TOG 2(2), 1983, pp. 91–108, DOI 10.1145/357318.357320 | Abstract and §2: particle birth, attributes, lifetime, transformation and stochastic control | Bounded emitters, age/lifetime variables, seeded variation and analytic per-sample movement | Our particles are SVG subtrees, with fixed populations and direct seeking; no volumetric rendering, fluid model or motion blur |
|
|
32
|
+
| Li et al., [Differentiable Vector Graphics Rasterization for Editing and Learning](https://people.csail.mit.edu/tzumao/diffvg/), ACM TOG 39(6), 2020 | Author-hosted abstract and application descriptions | Retain numeric curve controls and keep construction separate from raster evaluation, so a later optimizer could adjust declared parameters | No differentiable rasterizer, optimization loop or image-fitting result is implemented or claimed |
|
|
33
|
+
|
|
34
|
+
The animation export uses SVG's discrete `animate` timing semantics, verified against [MDN keyTimes](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/keyTimes) and the browser engines. The transform, paint-order and definition model follows [SVG 2](https://www.w3.org/TR/SVG2/). These are implementation standards, not research evidence for visual quality.
|
|
35
|
+
|
|
36
|
+
All three example designs are original vector constructions authored for this implementation. No itch.io artwork was copied, downloaded or traced. The earlier reviewed fox geometry, owner preferences, captured hashes and private SVG projector remain separate; this new fox is not the accepted fur-C asset.
|
|
37
|
+
|
|
38
|
+
## A complete small definition
|
|
39
|
+
|
|
40
|
+
`$lift` reads a parameter. `['add', a, b]` is data describing arithmetic, not executable JavaScript. This asset bobs and squashes using a one-second loop:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"schema": "svg-asset/0.1",
|
|
45
|
+
"name": "Bouncing ember",
|
|
46
|
+
"viewBox": [0, 0, 100, 100],
|
|
47
|
+
"params": {
|
|
48
|
+
"lift": { "default": 0, "min": -25, "max": 0 },
|
|
49
|
+
"squash": { "default": 1, "min": 0.8, "max": 1.2 }
|
|
50
|
+
},
|
|
51
|
+
"nodes": [
|
|
52
|
+
{
|
|
53
|
+
"id": "ember",
|
|
54
|
+
"kind": "ellipse",
|
|
55
|
+
"cx": 50,
|
|
56
|
+
"cy": 65,
|
|
57
|
+
"rx": 16,
|
|
58
|
+
"ry": 20,
|
|
59
|
+
"style": { "fill": "#edb95f" },
|
|
60
|
+
"transform": {
|
|
61
|
+
"translate": [0, "$lift"],
|
|
62
|
+
"pivot": [50, 85],
|
|
63
|
+
"scale": [["div", 1, "$squash"], "$squash"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"animations": {
|
|
68
|
+
"bounce": {
|
|
69
|
+
"duration": 1,
|
|
70
|
+
"loop": true,
|
|
71
|
+
"tracks": {
|
|
72
|
+
"lift": [
|
|
73
|
+
{ "time": 0, "value": 0, "ease": "smooth" },
|
|
74
|
+
{ "time": 0.5, "value": -25, "ease": "smooth" },
|
|
75
|
+
{ "time": 1, "value": 0 }
|
|
76
|
+
],
|
|
77
|
+
"squash": [
|
|
78
|
+
{ "time": 0, "value": 0.8, "ease": "smooth" },
|
|
79
|
+
{ "time": 0.5, "value": 1.2, "ease": "smooth" },
|
|
80
|
+
{ "time": 1, "value": 0.8 }
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"defaultAnimation": "bounce"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Here reciprocal X/Y scale preserves the ellipse's area. More complicated shapes and overlapping groups still require artistic judgment. This sample is also available as [bouncing-ember.json](../../examples/svg/bouncing-ember.json).
|
|
90
|
+
|
|
91
|
+
## Use the implementation
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import {
|
|
95
|
+
compileSvgAsset,
|
|
96
|
+
renderSvgFrame,
|
|
97
|
+
exportSvgAnimation,
|
|
98
|
+
sampleSvgParameters,
|
|
99
|
+
} from "@ludic-labs/assembly/svg";
|
|
100
|
+
|
|
101
|
+
const result = compileSvgAsset(sourceJSON);
|
|
102
|
+
if (!result.ok) {
|
|
103
|
+
console.error(result.diagnostics); // { code, path, message }[]
|
|
104
|
+
} else {
|
|
105
|
+
const asset = result.asset;
|
|
106
|
+
const still = renderSvgFrame(asset, { time: 0.5, prefix: "keeper1" });
|
|
107
|
+
const motion = exportSvgAnimation(asset, { animation: "idle", fps: 24 });
|
|
108
|
+
const pose = sampleSvgParameters(asset, { animation: "idle", time: 0.5 });
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The compiler accepts `unknown`, clones it, validates it, and freezes its result. It has no DOM, Node, renderer or host imports. Runtime evaluation failures throw `SvgAssetError`, whose `diagnostics` use the same structure. Handle these at an editor or application boundary. A successful compile validates structure, references, scopes, key ordering and the rest/default-zero frames; it does **not** prove every possible parameter combination is renderable. Evaluated geometry is checked on every render, including intermediate animation samples.
|
|
113
|
+
|
|
114
|
+
The root package remains the existing 3D assembly API. Import the new entry point explicitly. A JSON Schema consumer can use `@ludic-labs/assembly/schema/svg-asset-0.1`; semantic compilation is still required after schema validation. The [specification](specification.md) defines exact behavior and limits.
|
|
115
|
+
|
|
116
|
+
The package includes these documents, the original JSON examples and the `ludic-svg` CLI. After installing this package revision, `ludic-svg source.json output.svg --animated --clip idle` uses the same implementation. The packed-package smoke check runs that CLI after extracting the tarball outside the checkout.
|
|
117
|
+
|
|
118
|
+
From this checkout:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
npm ci
|
|
122
|
+
npm run build:svg-demo
|
|
123
|
+
node tools/serve.mjs qualification/local/svg-demo
|
|
124
|
+
# Open the localhost URL printed by the server.
|
|
125
|
+
|
|
126
|
+
node tools/svg-asset.mjs examples/svg/lantern-keeper.json /tmp/keeper.svg --time 0.5
|
|
127
|
+
node tools/svg-asset.mjs examples/svg/lantern-keeper.json /tmp/walk.svg --animated --clip walk
|
|
128
|
+
node tools/svg-asset.mjs examples/svg/waygate.json /tmp/summon-frames --frames --clip summon --fps 24
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The CLI accepts `--params overrides.json` to apply numeric overrides. Static output samples the selected/default clip at `--time` (zero by default). `--frames` emits a `svg-frames/0.1` manifest with viewBox, loop flag, duration, requested/effective sampling rate, ordered filenames and sample times. Consumers can rasterize those frames into their engine's atlas format. PNG/atlas packing is outside this notation's core.
|
|
132
|
+
|
|
133
|
+
The gallery shows the complete JSON beside each preview and supports clip selection, playback, scrub, parameter overrides, source editing, structured errors, source download and both SVG exports. It honors reduced motion for automatic playback. Downloaded animated SVG has its own timeline: use static exports in reduced-motion contexts or hosts that strip SVG animation.
|
|
134
|
+
|
|
135
|
+
## 0.1 completion gates
|
|
136
|
+
|
|
137
|
+
| Gate | Evidence |
|
|
138
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| A versioned grammar with documented semantics | [Specification](specification.md), [schema](../../schemas/svg-asset-0.1.schema.json), [types](../../src/svg/types.ts) |
|
|
140
|
+
| One portable implementation used by Node and the browser | [Core](../../src/svg.ts), [CLI](../../tools/svg-asset.mjs), [viewer](../../demos/svg/viewer.mjs) |
|
|
141
|
+
| All three requested asset classes and animation | [Character](../../examples/svg/lantern-keeper.json), [interior](../../examples/svg/night-workshop.json), [effect](../../examples/svg/waygate.json) |
|
|
142
|
+
| Determinism, invalid input and numerical boundary coverage | [Contract tests](../../tests/svg.test.mjs) |
|
|
143
|
+
| Real browser pixels and a complete authoring/export flow | [Browser verifier](../../tools/verify-svg.mjs), [qualification record](../../qualification/svg/verification.json) |
|
|
144
|
+
| Compatibility with the existing project | `npm test`, `npm run build:docs`, `npm run test:browser`, typecheck and lint; recorded in qualification |
|
|
145
|
+
|
|
146
|
+
Run `npm run test:svg-browser` for the new browser checks. It builds the local gallery, uses one browser at a time, and writes screenshots/report to `qualification/local/svg-verification`. No cloud service, model download, GPU rental, or reference-image capture is needed.
|
|
147
|
+
|
|
148
|
+
After editing `src/svg/schema.ts`, run `npm run build` and `node tools/build-svg-schema.mjs` to regenerate the published schema. A contract test detects drift between the two.
|
|
149
|
+
|
|
150
|
+
The tests establish bounded technical behavior. The included images have been inspected for visible construction and clipping issues; owner aesthetic acceptance is still pending. File size is measured separately: sampled SVG duplicates geometry across frames and is intentionally limited to short clips. It is a working interchange format, not a demonstrated optimal delivery encoding. A later version can introduce compact native attribute tracks, rig/deformation adapters or image-guided optimization after separate compatibility and visual qualification.
|