@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +2 -0
  3. package/README.md +50 -2
  4. package/dist/bake.d.ts +80 -0
  5. package/dist/bake.js +214 -0
  6. package/dist/browser.d.ts +89 -0
  7. package/dist/browser.js +534 -0
  8. package/dist/compat.d.ts +17 -0
  9. package/dist/compat.js +153 -0
  10. package/dist/compiler.d.ts +18 -0
  11. package/dist/compiler.js +1264 -0
  12. package/dist/diagnostics.d.ts +5 -0
  13. package/dist/diagnostics.js +8 -0
  14. package/dist/export.d.ts +54 -0
  15. package/dist/export.js +272 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.js +3 -0
  18. package/dist/legacy.d.ts +36 -0
  19. package/dist/legacy.js +180 -0
  20. package/dist/math.d.ts +16 -0
  21. package/dist/math.js +62 -0
  22. package/dist/mesh.d.ts +11 -0
  23. package/dist/mesh.js +153 -0
  24. package/dist/motion.d.ts +63 -0
  25. package/dist/motion.js +1476 -0
  26. package/dist/pixel.d.ts +268 -0
  27. package/dist/pixel.js +942 -0
  28. package/dist/required.d.ts +2 -0
  29. package/dist/required.js +6 -0
  30. package/dist/shapes.d.ts +71 -0
  31. package/dist/shapes.js +396 -0
  32. package/dist/sprite-brief.d.ts +5 -0
  33. package/dist/sprite-brief.js +47 -0
  34. package/dist/sprite-compiler.d.ts +62 -0
  35. package/dist/sprite-compiler.js +1745 -0
  36. package/dist/sprite-decompile.d.ts +68 -0
  37. package/dist/sprite-decompile.js +786 -0
  38. package/dist/sprite-format.d.ts +4 -0
  39. package/dist/sprite-format.js +155 -0
  40. package/dist/sprite-guide.d.ts +40 -0
  41. package/dist/sprite-guide.js +330 -0
  42. package/dist/sprite-scene.d.ts +277 -0
  43. package/dist/sprite-scene.js +664 -0
  44. package/dist/sprite-types.d.ts +110 -0
  45. package/dist/sprite-types.js +1 -0
  46. package/dist/sprite.d.ts +8 -0
  47. package/dist/sprite.js +8 -0
  48. package/dist/svg/schema.d.ts +71 -0
  49. package/dist/svg/schema.js +217 -0
  50. package/dist/svg/types.d.ts +148 -0
  51. package/dist/svg/types.js +1 -0
  52. package/dist/svg.d.ts +26 -0
  53. package/dist/svg.js +539 -0
  54. package/dist/text.d.ts +12 -0
  55. package/dist/text.js +75 -0
  56. package/dist/types.d.ts +478 -0
  57. package/dist/types.js +1 -0
  58. package/docs/svg/README.md +150 -0
  59. package/docs/svg/specification.md +108 -0
  60. package/examples/svg/arcane-shield.json +261 -0
  61. package/examples/svg/aurora-ribbon.json +158 -0
  62. package/examples/svg/bouncing-ember.json +76 -0
  63. package/examples/svg/ember-fountain.json +224 -0
  64. package/examples/svg/lantern-keeper.json +1159 -0
  65. package/examples/svg/night-workshop.json +1120 -0
  66. package/examples/svg/waygate-platform.json +64 -0
  67. package/examples/svg/waygate-spark.json +92 -0
  68. package/examples/svg/waygate-wisp.json +105 -0
  69. package/examples/svg/waygate.json +485 -0
  70. package/package.json +132 -4
  71. package/schemas/asset-assembly-0.1.schema.json +354 -0
  72. package/schemas/asset-assembly-0.2.schema.json +429 -0
  73. package/schemas/asset-assembly-0.3.schema.json +572 -0
  74. package/schemas/motion-set-0.1.schema.json +178 -0
  75. package/schemas/pixel-sprite-0.1.schema.json +892 -0
  76. package/schemas/pixel-sprite-scene-0.1.schema.json +149 -0
  77. package/schemas/svg-asset-0.1.schema.json +1157 -0
  78. package/tools/svg-asset.mjs +106 -0
package/dist/mesh.js ADDED
@@ -0,0 +1,153 @@
1
+ import { requireValue } from "./required.js";
2
+ import { cross, sub } from "./math.js";
3
+ import { linear } from "./compat.js";
4
+ export function meshForAsset(asset, tint) {
5
+ const result = emptyMesh();
6
+ if (tint !== undefined && !/^#[a-fA-F0-9]{6}$/.test(tint))
7
+ throw new RangeError("Tint must be #rrggbb");
8
+ const color = tint ? [1, 3, 5].map((i) => linear(parseInt(tint.slice(i, i + 2), 16))) : [1, 1, 1];
9
+ for (const [material, mesh] of Object.entries(asset.layers))
10
+ appendMesh(result, {
11
+ ...mesh,
12
+ colors: requireValue(asset.materials[material]).tintable && tint
13
+ ? mesh.colors.map((v, i) => v * requireValue(color[i % 3]))
14
+ : mesh.colors,
15
+ });
16
+ return result;
17
+ }
18
+ export const emptyMesh = () => ({ positions: [], normals: [], colors: [], indices: [] });
19
+ export function appendMesh(target, source, offset = [0, 0, 0]) {
20
+ const first = target.positions.length / 3;
21
+ for (let i = 0; i < source.positions.length; i++)
22
+ target.positions.push(requireValue(source.positions[i]) + requireValue(offset[i % 3]));
23
+ for (const n of source.normals)
24
+ target.normals.push(n);
25
+ for (const c of source.colors)
26
+ target.colors.push(c);
27
+ for (const i of source.indices)
28
+ target.indices.push(i + first);
29
+ // Joint indices are carried only when either side is rigged; static vertices use -1.
30
+ if (source.joints || target.joints) {
31
+ target.joints ??= new Array(first).fill(-1);
32
+ if (source.joints)
33
+ for (const j of source.joints)
34
+ target.joints.push(j);
35
+ else
36
+ for (let i = 0; i < source.positions.length / 3; i++)
37
+ target.joints.push(-1);
38
+ }
39
+ return target;
40
+ }
41
+ export function meshBounds(mesh) {
42
+ if (!mesh.positions.length)
43
+ throw new RangeError("Bounds require nonempty geometry");
44
+ const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
45
+ for (let i = 0; i < mesh.positions.length; i++) {
46
+ const v = requireValue(mesh.positions[i]), a = i % 3;
47
+ min[a] = Math.min(requireValue(min[a]), v);
48
+ max[a] = Math.max(requireValue(max[a]), v);
49
+ }
50
+ return { min, max };
51
+ }
52
+ /** Remove collapsed bevel bands without changing legacy MeshBuilder output. */
53
+ export function removeDegenerateTriangles(mesh) {
54
+ const indices = [];
55
+ for (let i = 0; i < mesh.indices.length; i += 3) {
56
+ const ids = mesh.indices.slice(i, i + 3);
57
+ const [a, b, c] = ids.map((id) => mesh.positions.slice(id * 3, id * 3 + 3));
58
+ if (Math.hypot(...cross(sub(requireValue(b), requireValue(a)), sub(requireValue(c), requireValue(a)))) >
59
+ 1e-16)
60
+ indices.push(...ids);
61
+ }
62
+ mesh.indices = indices;
63
+ return mesh;
64
+ }
65
+ export function validateMesh(mesh) {
66
+ const errors = [], n = mesh.positions.length;
67
+ if (!n ||
68
+ n % 3 ||
69
+ mesh.normals.length !== n ||
70
+ mesh.colors.length !== n ||
71
+ !mesh.indices.length ||
72
+ mesh.indices.length % 3)
73
+ errors.push("ATTRIBUTE_LENGTH");
74
+ if ([...mesh.positions, ...mesh.normals, ...mesh.colors].some((v) => !Number.isFinite(v)))
75
+ errors.push("NON_FINITE_GEOMETRY");
76
+ if (mesh.indices.some((i) => !Number.isInteger(i) || i < 0 || i >= n / 3))
77
+ errors.push("INVALID_INDEX");
78
+ for (let i = 0; i < mesh.normals.length; i += 3) {
79
+ const x = mesh.normals[i], y = mesh.normals[i + 1], z = mesh.normals[i + 2];
80
+ if (typeof x !== "number" || typeof y !== "number" || typeof z !== "number") {
81
+ errors.push("INVALID_NORMAL");
82
+ break;
83
+ }
84
+ const length = Math.hypot(x, y, z);
85
+ if (!Number.isFinite(length) || Math.abs(length - 1) > 1e-5) {
86
+ errors.push("INVALID_NORMAL");
87
+ break;
88
+ }
89
+ }
90
+ return errors;
91
+ }
92
+ export function canonicalJSON(value) {
93
+ if (Array.isArray(value))
94
+ return `[${value.map(canonicalJSON).join(",")}]`;
95
+ if (value && typeof value === "object")
96
+ return `{${Object.keys(value)
97
+ .sort()
98
+ .map((k) => `${JSON.stringify(k)}:${canonicalJSON(value[k])}`)
99
+ .join(",")}}`;
100
+ return JSON.stringify(value);
101
+ }
102
+ /** Portable SHA-256 over canonical JSON. No Node, browser or asynchronous crypto dependency. */
103
+ export function sourceHash(value) {
104
+ const bytes = new TextEncoder().encode(canonicalJSON(value)), bitLength = bytes.length * 8;
105
+ const data = new Uint8Array(Math.ceil((bytes.length + 9) / 64) * 64);
106
+ data.set(bytes);
107
+ data[bytes.length] = 128;
108
+ const dv = new DataView(data.buffer);
109
+ dv.setUint32(data.length - 8, Math.floor(bitLength / 2 ** 32));
110
+ dv.setUint32(data.length - 4, bitLength >>> 0);
111
+ const primes = [];
112
+ for (let n = 2; primes.length < 64; n++)
113
+ if (!primes.some((p) => n % p === 0))
114
+ primes.push(n);
115
+ const k = primes.map((p) => ((Math.cbrt(p) % 1) * 2 ** 32) >>> 0);
116
+ const h = primes.slice(0, 8).map((p) => ((Math.sqrt(p) % 1) * 2 ** 32) >>> 0);
117
+ const rotr = (x, n) => (x >>> n) | (x << (32 - n));
118
+ for (let offset = 0; offset < data.length; offset += 64) {
119
+ const w = new Uint32Array(64);
120
+ for (let i = 0; i < 16; i++)
121
+ w[i] = dv.getUint32(offset + i * 4);
122
+ for (let i = 16; i < 64; i++) {
123
+ const x = requireValue(w[i - 15]), y = requireValue(w[i - 2]);
124
+ w[i] =
125
+ (requireValue(w[i - 16]) +
126
+ (rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3)) +
127
+ requireValue(w[i - 7]) +
128
+ (rotr(y, 17) ^ rotr(y, 19) ^ (y >>> 10))) >>>
129
+ 0;
130
+ }
131
+ let [a, b, c, d, e, f, g, j] = h;
132
+ for (let i = 0; i < 64; i++) {
133
+ const t1 = (j +
134
+ (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) +
135
+ ((e & f) ^ (~e & g)) +
136
+ requireValue(k[i]) +
137
+ requireValue(w[i])) >>>
138
+ 0;
139
+ const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
140
+ j = g;
141
+ g = f;
142
+ f = e;
143
+ e = (d + t1) >>> 0;
144
+ d = c;
145
+ c = b;
146
+ b = a;
147
+ a = (t1 + t2) >>> 0;
148
+ }
149
+ for (const [i, v] of [a, b, c, d, e, f, g, j].entries())
150
+ h[i] = (requireValue(h[i]) + v) >>> 0;
151
+ }
152
+ return h.map((v) => v.toString(16).padStart(8, "0")).join("");
153
+ }
@@ -0,0 +1,63 @@
1
+ import type { CompiledAsset, CompiledClip, CompiledMotion, Diagnostic, MeshData, SampledPose, Vec3 } from "./types.js";
2
+ /** Motion sets for asset-assembly/0.2 characters. */
3
+ export declare const MOTION_SCHEMA: "motion-set/0.1";
4
+ /** Lift and ground-penetration tolerance as a fraction of the asset's rest height. */
5
+ export declare const CONTACT_TOLERANCE = 0.02;
6
+ /** Planted-effector drift tolerance as a fraction of the asset's rest height. */
7
+ export declare const SLIP_TOLERANCE = 0.04;
8
+ /** Uniform verification samples per clip; contact intervals receive 16 additional samples each. */
9
+ export declare const VERIFICATION_SAMPLES = 48;
10
+ /** Normalized clip time over which the planted correction blends in before touchdown and out after lift-off. */
11
+ export declare const SOLVE_BLEND = 0.08;
12
+ /**
13
+ * Validate a motion set against a compiled rigged asset and verify its declared contacts.
14
+ * Throws AssetCompileError with every diagnostic; use checkMotion to keep measured-only results.
15
+ */
16
+ export declare function compileMotion(input: unknown, asset: CompiledAsset): CompiledMotion;
17
+ /** Diagnostics that measure a valid motion rather than reject its description. */
18
+ export declare const MEASURED_CODES: readonly string[];
19
+ /**
20
+ * For editors and repair loops: returns the compiled motion together with its diagnostics whenever
21
+ * every diagnostic is a measurement (slip, lift, penetration). Structural or semantic errors leave
22
+ * `motion` undefined. compileMotion remains the strict path.
23
+ */
24
+ export declare function checkMotion(input: unknown, asset: CompiledAsset): {
25
+ motion?: CompiledMotion;
26
+ diagnostics: Diagnostic[];
27
+ };
28
+ export declare function validateMotion(input: unknown, asset: CompiledAsset): Diagnostic[];
29
+ /**
30
+ * Sample a compiled clip, pose or expression at an integer frame, or at a normalized phase.
31
+ * `face` overlays a face-only clip (an expression or face clip): it replaces every face DOF and state,
32
+ * sampled at the same time on its own duration, while body joints, grounding and travel come from `clip`.
33
+ */
34
+ export declare function samplePose(asset: CompiledAsset, clip: CompiledClip, time: {
35
+ frame: number;
36
+ fps: number;
37
+ } | {
38
+ phase: number;
39
+ }, options?: {
40
+ face?: CompiledClip;
41
+ }): SampledPose;
42
+ /**
43
+ * A new compiled asset with posed geometry, bounds and anchors, containing the instances visible in the
44
+ * pose's states. Rest `translation` values are kept.
45
+ */
46
+ export declare function poseAsset(asset: CompiledAsset, pose: SampledPose): CompiledAsset;
47
+ /**
48
+ * Place a character clip that uses anchor grounding on a furniture anchor and measure floor clearance.
49
+ * Both assets share the frame convention (front is −Z), so no rotation is involved.
50
+ */
51
+ export declare function seatOn(character: CompiledAsset, clip: CompiledClip, furniture: CompiledAsset, seat: {
52
+ part: string;
53
+ anchor: string;
54
+ }): {
55
+ offset: Vec3;
56
+ clearance: {
57
+ min: number;
58
+ max: number;
59
+ };
60
+ diagnostics: Diagnostic[];
61
+ };
62
+ /** Combine visible posed geometry without constructing posed instances, anchors or bounds. */
63
+ export declare function posedMesh(asset: CompiledAsset, pose: SampledPose, tint?: string): MeshData;