create-playcanvas 0.5.1 → 0.6.1

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 (113) hide show
  1. package/README.md +20 -27
  2. package/dist/index.mjs +44 -42
  3. package/package.json +1 -1
  4. package/skills/calibrate-model/references/direct-engine.md +7 -4
  5. package/skills/calibrate-model/references/react.md +8 -3
  6. package/skills/calibrate-model/references/web-components.md +7 -4
  7. package/skills/configure-animation/references/web-components.md +18 -9
  8. package/skills/inspect-glb/SKILL.md +27 -18
  9. package/skills/inspect-glb/scripts/inspect.mjs +179 -50
  10. package/templates/_features/physics/assets/public/ammo/LICENSE +21 -0
  11. package/templates/_features/physics/assets/public/ammo/SOURCE.md +16 -0
  12. package/templates/_features/physics/assets/public/ammo/ammo.js +973 -0
  13. package/templates/_features/physics/assets/public/ammo/ammo.wasm.js +937 -0
  14. package/templates/_features/physics/assets/public/ammo/ammo.wasm.wasm +0 -0
  15. package/templates/_features/physics/react/package.json +5 -0
  16. package/templates/_features/physics/react/src/sync-ammo.d.ts +4 -0
  17. package/templates/_shared/ar-placement/README.md +7 -0
  18. package/templates/_shared/ar-placement/src/ar-placement.ts +123 -0
  19. package/templates/_shared/first-person-controller/README.md +7 -0
  20. package/templates/_shared/first-person-controller/src/controller.ts +18 -0
  21. package/templates/_shared/first-person-controller/src/scripts.d.ts +5 -0
  22. package/templates/_shared/interactive-sphere/README.md +7 -0
  23. package/templates/_shared/model-viewer/README.md +7 -0
  24. package/templates/_shared/physics-playground/README.md +7 -0
  25. package/templates/_shared/product-configurator/ASSET_LICENSES.md +3 -0
  26. package/templates/_shared/product-configurator/README.md +7 -0
  27. package/templates/_shared/product-configurator/src/product.css +14 -0
  28. package/templates/_shared/scene-editor/README.md +7 -0
  29. package/templates/_shared/scene-editor/src/scene-editor.ts +57 -0
  30. package/templates/_shared/spinning-cube/README.md +7 -0
  31. package/templates/_shared/splat-viewer/README.md +7 -0
  32. package/templates/_shared/sprite-game/README.md +7 -0
  33. package/templates/_shared/sprite-game/src/sprite-game.ts +197 -0
  34. package/templates/_shared/third-person-controller/README.md +7 -0
  35. package/templates/_shared/third-person-controller/src/controller.ts +53 -0
  36. package/templates/_shared/third-person-controller/src/scripts.d.ts +5 -0
  37. package/templates/_shared/vr-starter/README.md +7 -0
  38. package/templates/_shared/vr-starter/src/vr.ts +40 -0
  39. package/templates/_shared/vr-starter/src/xr.d.ts +11 -0
  40. package/templates/engine/ar-placement/src/main.ts +77 -0
  41. package/templates/engine/base/.prettierignore +1 -0
  42. package/templates/engine/base/README.md +1 -1
  43. package/templates/engine/base/_npmrc +1 -0
  44. package/templates/engine/base/eslint.config.mjs +1 -1
  45. package/templates/engine/base/index.html +13 -0
  46. package/templates/engine/base/src/starter.css +104 -0
  47. package/templates/engine/first-person-controller/src/main.ts +139 -0
  48. package/templates/engine/interactive-sphere/src/App.ts +9 -7
  49. package/templates/engine/interactive-sphere/src/main.ts +2 -29
  50. package/templates/engine/interactive-sphere/src/style.css +18 -97
  51. package/templates/engine/model-viewer/src/main.ts +3 -2
  52. package/templates/engine/physics-playground/src/main.ts +132 -0
  53. package/templates/engine/product-configurator/src/main.ts +156 -0
  54. package/templates/engine/scene-editor/src/main.ts +90 -0
  55. package/templates/engine/splat-viewer/src/main.ts +4 -3
  56. package/templates/engine/sprite-game/src/main.ts +72 -0
  57. package/templates/engine/third-person-controller/src/main.ts +216 -0
  58. package/templates/engine/vr-starter/src/main.ts +101 -0
  59. package/templates/index.ts +66 -5
  60. package/templates/react/ar-placement/src/App.tsx +58 -0
  61. package/templates/react/base/.prettierignore +1 -0
  62. package/templates/react/base/README.md +1 -1
  63. package/templates/react/base/_npmrc +1 -0
  64. package/templates/react/base/eslint.config.mjs +1 -1
  65. package/templates/react/base/index.html +13 -0
  66. package/templates/react/base/src/main.tsx +10 -0
  67. package/templates/react/base/src/starter.css +104 -0
  68. package/templates/react/first-person-controller/src/App.tsx +115 -0
  69. package/templates/react/interactive-sphere/src/App.css +18 -68
  70. package/templates/react/interactive-sphere/src/App.tsx +1 -22
  71. package/templates/react/interactive-sphere/src/Scene.tsx +7 -6
  72. package/templates/react/interactive-sphere/src/index.css +1 -48
  73. package/templates/react/model-viewer/src/App.tsx +2 -2
  74. package/templates/react/physics-playground/src/App.tsx +85 -0
  75. package/templates/react/product-configurator/src/App.tsx +157 -0
  76. package/templates/react/scene-editor/src/App.tsx +84 -0
  77. package/templates/react/splat-viewer/src/App.tsx +3 -3
  78. package/templates/react/sprite-game/src/App.tsx +38 -0
  79. package/templates/react/third-person-controller/src/App.tsx +193 -0
  80. package/templates/react/vr-starter/src/App.tsx +99 -0
  81. package/templates/web-components/ar-placement/index.html +44 -0
  82. package/templates/web-components/ar-placement/src/main.ts +13 -0
  83. package/templates/web-components/base/.prettierignore +1 -0
  84. package/templates/web-components/base/README.md +1 -1
  85. package/templates/web-components/base/_npmrc +1 -0
  86. package/templates/web-components/base/eslint.config.mjs +1 -1
  87. package/templates/web-components/base/package.json +1 -1
  88. package/templates/web-components/base/src/starter.css +104 -0
  89. package/templates/web-components/first-person-controller/index.html +168 -0
  90. package/templates/web-components/first-person-controller/src/main.ts +15 -0
  91. package/templates/web-components/interactive-sphere/index.html +5 -22
  92. package/templates/web-components/interactive-sphere/src/main.ts +5 -4
  93. package/templates/web-components/interactive-sphere/src/style.css +18 -76
  94. package/templates/web-components/model-viewer/index.html +1 -1
  95. package/templates/web-components/model-viewer/src/main.ts +2 -1
  96. package/templates/web-components/physics-playground/index.html +51 -0
  97. package/templates/web-components/physics-playground/src/main.ts +32 -0
  98. package/templates/web-components/product-configurator/index.html +51 -0
  99. package/templates/web-components/product-configurator/src/main.ts +93 -0
  100. package/templates/web-components/scene-editor/index.html +51 -0
  101. package/templates/web-components/scene-editor/src/main.ts +26 -0
  102. package/templates/web-components/spinning-cube/src/main.ts +1 -0
  103. package/templates/web-components/splat-viewer/index.html +2 -2
  104. package/templates/web-components/splat-viewer/src/main.ts +2 -1
  105. package/templates/web-components/sprite-game/index.html +34 -0
  106. package/templates/web-components/sprite-game/src/main.ts +9 -0
  107. package/templates/web-components/third-person-controller/index.html +183 -0
  108. package/templates/web-components/third-person-controller/src/main.ts +16 -0
  109. package/templates/web-components/vr-starter/index.html +80 -0
  110. package/templates/web-components/vr-starter/src/main.ts +12 -0
  111. package/templates/engine/interactive-sphere/src/assets/typescript.svg +0 -1
  112. package/templates/react/interactive-sphere/src/assets/react.svg +0 -1
  113. package/templates/web-components/interactive-sphere/src/assets/typescript.svg +0 -1
@@ -2,11 +2,14 @@
2
2
  // zero-dep glb metadata inspector. bounds come from decoded vertex positions, not accessor min/max,
3
3
  // so a rotated node reports its true extent and models can be placed touching.
4
4
  import fs from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
5
6
 
6
7
  const GLB_MAGIC = 0x46546c67;
7
8
  const JSON_CHUNK = 0x4e4f534a;
8
9
  const BIN_CHUNK = 0x004e4942;
9
10
  const IDENTITY = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
11
+ const MAX_ACCESSOR_VALUES = 30_000_000;
12
+ const TYPES = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT2: 4, MAT3: 9, MAT4: 16 };
10
13
 
11
14
  // componentType -> byte size, signedness and normalized divisor (glTF 2.0 table 3.1 / 5.1)
12
15
  const COMPONENTS = {
@@ -18,6 +21,14 @@ const COMPONENTS = {
18
21
  5126: { size: 4, signed: true, norm: 1, read: (b, o) => b.readFloatLE(o) }
19
22
  };
20
23
 
24
+ const tryCatch = (fn) => {
25
+ try {
26
+ return [null, fn()];
27
+ } catch (error) {
28
+ return [error];
29
+ }
30
+ };
31
+
21
32
  const mul = (a, b) => {
22
33
  const o = new Array(16);
23
34
  for (let c = 0; c < 4; c++) {
@@ -73,41 +84,99 @@ const round = (v) => Math.round(v * 1e4) / 1e4 + 0;
73
84
 
74
85
  // buffer 0 of a GLB is the BIN chunk; other buffers are external files or data URIs
75
86
  const bufferBytes = (g, bin, index) => {
76
- const uri = g.buffers?.[index]?.uri;
77
- if (uri === undefined) return bin;
87
+ const buffer = g.buffers?.[index];
88
+ if (!buffer) return null;
89
+ const uri = buffer.uri;
90
+ if (uri === undefined) return index === 0 ? bin : null;
78
91
  const base64 = /^data:[^,]*;base64,(.*)$/s.exec(uri);
79
92
  return base64 ? Buffer.from(base64[1], 'base64') : null;
80
93
  };
81
94
 
82
- // widen min/max with every decoded POSITION transformed by matrix. returns null on success,
83
- // otherwise a reason the vertices could not be read so the caller can fall back and say so.
84
- const accumulate = (g, bin, accessor, matrix, min, max) => {
95
+ const readAccessor = (g, bin, accessor, type = 'VEC3') => {
85
96
  const acc = g.accessors?.[accessor];
86
- if (!acc) return 'no-position-accessor';
87
- if (acc.sparse) return 'sparse-accessor';
88
- if (acc.bufferView === undefined) return 'no-vertex-data';
97
+ if (!acc) return ['no-position-accessor'];
98
+ if (acc.bufferView === undefined && !acc.sparse) return ['no-vertex-data'];
89
99
  const comp = COMPONENTS[acc.componentType];
90
- if (!comp || acc.type !== 'VEC3') return 'unsupported-accessor';
91
- const view = g.bufferViews?.[acc.bufferView];
92
- if (!view) return 'unsupported-accessor';
93
- if (view.extensions?.EXT_meshopt_compression) return 'meshopt-compressed';
94
- const bytes = bufferBytes(g, bin, view.buffer ?? 0);
95
- if (!bytes) return 'external-buffer';
96
- const stride = view.byteStride ?? comp.size * 3;
97
- const base = (view.byteOffset ?? 0) + (acc.byteOffset ?? 0);
98
- if (!acc.count || base + (acc.count - 1) * stride + comp.size * 3 > bytes.length) return 'truncated-buffer';
100
+ const size = TYPES[acc.type];
101
+ if (!comp || !size || acc.type !== type || !Number.isInteger(acc.count) || acc.count < 1) {
102
+ return ['unsupported-accessor'];
103
+ }
104
+ if (acc.count * size > MAX_ACCESSOR_VALUES) return ['accessor-too-large'];
105
+ const data = new Float32Array(acc.count * size);
99
106
  const scale = acc.normalized ? comp.norm : 1;
100
- for (let v = 0; v < acc.count; v++) {
101
- const o = base + v * stride;
102
- let x = comp.read(bytes, o) / scale;
103
- let y = comp.read(bytes, o + comp.size) / scale;
104
- let z = comp.read(bytes, o + comp.size * 2) / scale;
105
- if (acc.normalized && comp.signed) {
106
- if (x < -1) x = -1;
107
- if (y < -1) y = -1;
108
- if (z < -1) z = -1;
107
+ const read = (bytes, offset) => {
108
+ const value = comp.read(bytes, offset) / scale;
109
+ return acc.normalized && comp.signed && value < -1 ? -1 : value;
110
+ };
111
+
112
+ if (acc.bufferView !== undefined) {
113
+ const view = g.bufferViews?.[acc.bufferView];
114
+ if (!view || !Number.isInteger(view.byteLength)) return ['unsupported-accessor'];
115
+ if (view.extensions?.EXT_meshopt_compression) return ['meshopt-compressed'];
116
+ const bytes = bufferBytes(g, bin, view.buffer ?? 0);
117
+ if (!bytes) return ['external-buffer'];
118
+ const width = comp.size * size;
119
+ const stride = view.byteStride ?? width;
120
+ const base = (view.byteOffset ?? 0) + (acc.byteOffset ?? 0);
121
+ const used = (acc.byteOffset ?? 0) + (acc.count - 1) * stride + width;
122
+ if (stride < width || base < 0 || used > view.byteLength || base + (acc.count - 1) * stride + width > bytes.length) {
123
+ return ['truncated-buffer'];
124
+ }
125
+ for (let i = 0; i < acc.count; i++) {
126
+ for (let k = 0; k < size; k++) data[i * size + k] = read(bytes, base + i * stride + k * comp.size);
127
+ }
128
+ }
129
+
130
+ if (acc.sparse) {
131
+ const sparse = acc.sparse;
132
+ const idx = sparse.indices;
133
+ const values = sparse.values;
134
+ const idxComp = idx && [5121, 5123, 5125].includes(idx.componentType) ? COMPONENTS[idx.componentType] : null;
135
+ const idxView = g.bufferViews?.[idx?.bufferView];
136
+ const valueView = g.bufferViews?.[values?.bufferView];
137
+ if (!Number.isInteger(sparse.count) || sparse.count < 1 || sparse.count > acc.count || !idxComp ||
138
+ !Number.isInteger(idxView?.byteLength) || !Number.isInteger(valueView?.byteLength)) {
139
+ return ['invalid-sparse-accessor'];
140
+ }
141
+ if (idxView.extensions?.EXT_meshopt_compression || valueView.extensions?.EXT_meshopt_compression) {
142
+ return ['meshopt-compressed'];
143
+ }
144
+ const idxBytes = bufferBytes(g, bin, idxView.buffer ?? 0);
145
+ const valueBytes = bufferBytes(g, bin, valueView.buffer ?? 0);
146
+ if (!idxBytes || !valueBytes) return ['external-buffer'];
147
+ const idxBase = (idxView.byteOffset ?? 0) + (idx.byteOffset ?? 0);
148
+ const valueBase = (valueView.byteOffset ?? 0) + (values.byteOffset ?? 0);
149
+ const valueWidth = comp.size * size;
150
+ const idxUsed = (idx.byteOffset ?? 0) + sparse.count * idxComp.size;
151
+ const valueUsed = (values.byteOffset ?? 0) + sparse.count * valueWidth;
152
+ if (idxBase < 0 || valueBase < 0 || idxUsed > idxView.byteLength || valueUsed > valueView.byteLength ||
153
+ idxBase + sparse.count * idxComp.size > idxBytes.length || valueBase + sparse.count * valueWidth > valueBytes.length) {
154
+ return ['truncated-buffer'];
155
+ }
156
+ let prev = -1;
157
+ for (let i = 0; i < sparse.count; i++) {
158
+ const index = idxComp.read(idxBytes, idxBase + i * idxComp.size);
159
+ if (index <= prev || index >= acc.count) return ['invalid-sparse-accessor'];
160
+ prev = index;
161
+ for (let k = 0; k < size; k++) {
162
+ data[index * size + k] = read(valueBytes, valueBase + i * valueWidth + k * comp.size);
163
+ }
164
+ }
165
+ }
166
+
167
+ return [null, data];
168
+ };
169
+
170
+ const accumulate = (data, matrix, min, max, morphs = []) => {
171
+ for (let i = 0; i < data.length; i += 3) {
172
+ let x = data[i];
173
+ let y = data[i + 1];
174
+ let z = data[i + 2];
175
+ for (const [weight, delta] of morphs) {
176
+ x += delta[i] * weight;
177
+ y += delta[i + 1] * weight;
178
+ z += delta[i + 2] * weight;
109
179
  }
110
- // inlined to avoid allocating a vector per vertex on million-vertex meshes
111
180
  const px = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12];
112
181
  const py = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13];
113
182
  const pz = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14];
@@ -118,13 +187,12 @@ const accumulate = (g, bin, accessor, matrix, min, max) => {
118
187
  if (py > max[1]) max[1] = py;
119
188
  if (pz > max[2]) max[2] = pz;
120
189
  }
121
- return null;
122
190
  };
123
191
 
124
192
  // loose fallback: bound the 8 corners of the accessor's local box. over-estimates a mesh that does
125
193
  // not fill its box when the node rotation is not a multiple of 90 degrees.
126
194
  const accumulateCorners = (acc, matrix, min, max) => {
127
- if (!acc?.min || !acc?.max) return;
195
+ if (!acc?.min || !acc?.max) return false;
128
196
  for (let c = 0; c < 8; c++) {
129
197
  const point = xform(matrix, [
130
198
  c & 1 ? acc.max[0] : acc.min[0],
@@ -136,35 +204,55 @@ const accumulateCorners = (acc, matrix, min, max) => {
136
204
  if (point[k] > max[k]) max[k] = point[k];
137
205
  }
138
206
  }
207
+ return true;
139
208
  };
140
209
 
141
210
  export const inspectGlb = (buf) => {
142
211
  if (buf.length < 20 || buf.readUInt32LE(0) !== GLB_MAGIC || buf.readUInt32LE(16) !== JSON_CHUNK) {
143
212
  throw new Error('not a GLB (or JSON chunk missing)');
144
213
  }
214
+ if (buf.readUInt32LE(4) !== 2) throw new Error('unsupported GLB version');
215
+ if (buf.readUInt32LE(8) !== buf.length) throw new Error('invalid GLB length');
145
216
  const jsonLength = buf.readUInt32LE(12);
217
+ if (20 + jsonLength > buf.length) throw new Error('invalid JSON chunk length');
146
218
  const g = JSON.parse(buf.toString('utf8', 20, 20 + jsonLength));
147
219
  const binAt = 20 + jsonLength;
148
- const bin =
149
- binAt + 8 <= buf.length && buf.readUInt32LE(binAt + 4) === BIN_CHUNK
150
- ? buf.subarray(binAt + 8, binAt + 8 + buf.readUInt32LE(binAt))
151
- : null;
220
+ let bin = null;
221
+ if (binAt < buf.length) {
222
+ if (binAt + 8 > buf.length || buf.readUInt32LE(binAt + 4) !== BIN_CHUNK) {
223
+ throw new Error('invalid BIN chunk');
224
+ }
225
+ const length = buf.readUInt32LE(binAt);
226
+ if (binAt + 8 + length !== buf.length) throw new Error('invalid BIN chunk length');
227
+ bin = buf.subarray(binAt + 8);
228
+ }
152
229
  const nodes = g.nodes ?? [];
153
230
  const nodeName = (i) => nodes[i]?.name ?? `node_${i}`;
154
231
  const parents = new Array(nodes.length).fill(-1);
155
232
  for (let i = 0; i < nodes.length; i++) {
156
- for (const child of nodes[i].children ?? []) parents[child] = i;
233
+ for (const child of nodes[i].children ?? []) {
234
+ if (!Number.isInteger(child) || !nodes[child] || parents[child] !== -1) throw new Error('invalid node graph');
235
+ parents[child] = i;
236
+ }
157
237
  }
158
238
  const nodePath = (i) => {
159
239
  const names = [];
160
- for (; i >= 0; i = parents[i]) names.unshift(nodeName(i));
240
+ const seen = new Set();
241
+ for (; i >= 0; i = parents[i]) {
242
+ if (seen.has(i)) throw new Error('invalid node graph');
243
+ seen.add(i);
244
+ names.unshift(nodeName(i));
245
+ }
161
246
  return names.join('/');
162
247
  };
163
248
  const children = new Set(nodes.flatMap((n) => n.children ?? []));
164
249
  const roots = g.scenes?.[g.scene ?? 0]?.nodes ?? nodes.map((_, i) => i).filter((i) => !children.has(i));
165
250
 
166
251
  const world = new Array(nodes.length);
252
+ const seen = new Set();
167
253
  const walk = (i, parent) => {
254
+ if (!Number.isInteger(i) || !nodes[i] || seen.has(i)) throw new Error('invalid node graph');
255
+ seen.add(i);
168
256
  const n = nodes[i];
169
257
  const local = n.matrix ?? compose(n.translation ?? [0, 0, 0], n.rotation ?? [0, 0, 0, 1], n.scale ?? [1, 1, 1]);
170
258
  world[i] = mul(parent, local);
@@ -176,28 +264,67 @@ export const inspectGlb = (buf) => {
176
264
  const max = [-Infinity, -Infinity, -Infinity];
177
265
  const notes = new Set();
178
266
  let meshNodes = 0;
267
+ let morphed = false;
268
+ let fallback = false;
269
+ let incomplete = false;
179
270
  for (let i = 0; i < nodes.length; i++) {
180
271
  if (nodes[i].mesh === undefined || !world[i]) continue;
181
272
  meshNodes++;
182
273
  // glTF 2.0: "the transform of the skinned mesh node MUST be ignored" - joints drive the pose
183
274
  const matrix = nodes[i].skin === undefined ? world[i] : IDENTITY;
184
- for (const prim of g.meshes?.[nodes[i].mesh]?.primitives ?? []) {
185
- const note = prim.extensions?.KHR_draco_mesh_compression
186
- ? 'draco-compressed'
187
- : accumulate(g, bin, prim.attributes?.POSITION, matrix, min, max);
188
- if (!note) continue;
189
- notes.add(note);
190
- accumulateCorners(g.accessors?.[prim.attributes?.POSITION], matrix, min, max);
275
+ const mesh = g.meshes?.[nodes[i].mesh];
276
+ for (const prim of mesh?.primitives ?? []) {
277
+ const [error, data] = prim.extensions?.KHR_draco_mesh_compression
278
+ ? ['draco-compressed']
279
+ : readAccessor(g, bin, prim.attributes?.POSITION);
280
+ if (error) {
281
+ notes.add(error);
282
+ if (error === 'meshopt-compressed' || !accumulateCorners(g.accessors?.[prim.attributes?.POSITION], matrix, min, max)) {
283
+ incomplete = true;
284
+ } else {
285
+ fallback = true;
286
+ }
287
+ continue;
288
+ }
289
+ const morphs = [];
290
+ if (prim.targets?.length) {
291
+ morphed = true;
292
+ const weights = nodes[i].weights ?? mesh.weights ?? [];
293
+ for (let k = 0; k < prim.targets.length; k++) {
294
+ const weight = weights[k] ?? 0;
295
+ if (!Number.isFinite(weight)) {
296
+ notes.add('invalid-morph-weight');
297
+ incomplete = true;
298
+ continue;
299
+ }
300
+ const accessor = prim.targets[k].POSITION;
301
+ if (!weight || accessor === undefined) continue;
302
+ const [morphError, delta] = readAccessor(g, bin, accessor);
303
+ if (morphError || delta.length !== data.length) {
304
+ notes.add(`morph-${morphError ?? 'accessor-size'}`);
305
+ incomplete = true;
306
+ continue;
307
+ }
308
+ morphs.push([weight, delta]);
309
+ }
310
+ }
311
+ accumulate(data, matrix, min, max, morphs);
191
312
  }
192
313
  }
193
314
 
194
315
  const ok = min[0] !== Infinity;
316
+ const morphAnimated = (g.animations ?? []).some((a) =>
317
+ (a.channels ?? []).some((c) => c.target?.path === 'weights')
318
+ );
319
+ const skinned = !!g.skins?.length;
195
320
  return {
196
321
  aabb: ok ? { min: min.map(round), max: max.map(round) } : null,
197
322
  dims: ok ? max.map((v, i) => round(v - min[i])) : null,
198
323
  center: ok ? max.map((v, i) => round((v + min[i]) / 2)) : null,
199
324
  groundOffset: ok ? round(-min[1]) : null,
200
- boundsSource: ok ? (notes.size ? 'accessor-minmax' : 'vertices') : null,
325
+ boundsSource: ok ? (incomplete ? 'incomplete' : fallback ? 'accessor-minmax' : 'vertices') : null,
326
+ boundsPose: skinned ? 'bind' : morphed ? 'default-morph' : 'static',
327
+ requiresRuntimeCheck: skinned || morphAnimated || notes.size > 0,
201
328
  ...(notes.size ? { boundsNotes: [...notes].sort() } : {}),
202
329
  nodes: nodes.length,
203
330
  nodePaths: nodes.map((_, i) => nodePath(i)).sort(),
@@ -217,11 +344,15 @@ export const inspectGlb = (buf) => {
217
344
  )
218
345
  )
219
346
  ].sort(),
220
- skinned: !!g.skins?.length
347
+ morphed,
348
+ morphAnimated,
349
+ skinned
221
350
  };
222
351
  };
223
352
 
224
- if (import.meta.main ?? import.meta.filename === process.argv[1]) {
353
+ const main = import.meta.main ?? (process.argv[1] &&
354
+ fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(process.argv[1]));
355
+ if (main) {
225
356
  const files = process.argv.slice(2);
226
357
  if (!files.length) {
227
358
  console.error('usage: node inspect.mjs <file.glb> [more.glb ...]');
@@ -229,11 +360,9 @@ if (import.meta.main ?? import.meta.filename === process.argv[1]) {
229
360
  }
230
361
  // report per-file failures so one unreadable file cannot discard a whole glob
231
362
  const out = files.map((file) => {
232
- try {
233
- return { file, ...inspectGlb(fs.readFileSync(file)) };
234
- } catch (e) {
235
- return { file, error: e.message };
236
- }
363
+ const [error, result] = tryCatch(() => inspectGlb(fs.readFileSync(file)));
364
+ return error ? { file, error: error instanceof Error ? error.message : String(error) } : { file, ...result };
237
365
  });
366
+ if (out.some((result) => result.error)) process.exitCode = 1;
238
367
  console.log(JSON.stringify(out.length === 1 ? out[0] : out, null, 2));
239
368
  }
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2011 ammo.js contributors
2
+
3
+ This software is provided 'as-is', without any express or implied
4
+ warranty. In no event will the authors be held liable for any damages
5
+ arising from the use of this software.
6
+
7
+ Permission is granted to anyone to use this software for any purpose,
8
+ including commercial applications, and to alter it and redistribute it
9
+ freely, subject to the following restrictions:
10
+
11
+ 1. The origin of this software must not be misrepresented; you must not
12
+ claim that you wrote the original software. If you use this software
13
+ in a product, an acknowledgment in the product documentation would be
14
+ appreciated but is not required.
15
+ 2. Altered source versions must be plainly marked as such, and must not be
16
+ misrepresented as being the original software.
17
+ 3. This notice may not be removed or altered from any source distribution.
18
+
19
+ ---
20
+
21
+ See AUTHORS for list of contributors.
@@ -0,0 +1,16 @@
1
+ # Ammo.js
2
+
3
+ These files are copied from `playcanvas/engine` tag `v2.21.4` (commit
4
+ `e287e0c67f3c20c689a52b7c53d2b7fedbe887da`) at
5
+ `examples/assets/wasm/ammo/`.
6
+
7
+ They are a WebAssembly build of [ammo.js](https://github.com/kripken/ammo.js),
8
+ distributed under the zlib license in `LICENSE`.
9
+
10
+ ## SHA-256
11
+
12
+ ```text
13
+ ef166d1315bc4a6441a8de341ecdf6ac4e7d69055caec65c523ed1a4e8e19b15 ammo.js
14
+ 5645b5a0c4f03be9d9d1ae604ffacd5e5e525310cfd1d0ed27474cdd1f34aab0 ammo.wasm.js
15
+ a61b504d4a6ce6bb93bd843e0f61edb8115e7317f1b3462247031a83ddb25d09 ammo.wasm.wasm
16
+ ```