@vulfram/gltf-loader 0.22.2-alpha → 0.22.4-alpha

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/README.md CHANGED
@@ -2,4 +2,5 @@
2
2
 
3
3
  Loads `.gltf` and `.glb` content into `@vulfram/engine` 3D worlds.
4
4
 
5
- Status: initial package scaffold. Runtime loading is not implemented yet.
5
+ The loader follows the engine host-side math convention and exposes transforms as
6
+ `gl-matrix` `vec3` / `quat`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vulfram/gltf-loader",
3
- "version": "0.22.2-alpha",
3
+ "version": "0.22.4-alpha",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,11 +19,12 @@
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/bun": "^1.3.10",
22
- "@vulfram/engine": "^0.22.2-alpha"
22
+ "@vulfram/engine": "^0.22.4-alpha"
23
23
  },
24
24
  "dependencies": {
25
25
  "@gltf-transform/core": "^4.3.0",
26
26
  "@gltf-transform/extensions": "^4.3.0",
27
- "@gltf-transform/functions": "^4.3.0"
27
+ "@gltf-transform/functions": "^4.3.0",
28
+ "gl-matrix": "^3.4.4"
28
29
  }
29
30
  }
package/src/binary.ts CHANGED
@@ -33,7 +33,7 @@ export function detectFormat(bytes: Uint8Array): GltfSourceFormat {
33
33
 
34
34
  throw new GltfLoaderError(
35
35
  'UNSUPPORTED_FORMAT',
36
- 'Unable to detect glTF format from payload bytes.',
36
+ 'Unable to detect glTF format from payload bytes.'
37
37
  );
38
38
  }
39
39
 
@@ -59,7 +59,7 @@ export function decodeDataUri(uri: string): Uint8Array {
59
59
 
60
60
  /** Normalizes external resource map to Uint8Array values. */
61
61
  export function normalizeResourceMap(
62
- resources?: Record<string, BinaryLike>,
62
+ resources?: Record<string, BinaryLike>
63
63
  ): Record<string, Uint8Array> {
64
64
  if (!resources) return {};
65
65
  const out: Record<string, Uint8Array> = {};
package/src/context.ts CHANGED
@@ -25,8 +25,8 @@ export function createContext(input: GltfLoadInput): LoaderContext {
25
25
  entities: 0,
26
26
  geometries: 0,
27
27
  materials: 0,
28
- textures: 0,
29
- },
28
+ textures: 0
29
+ }
30
30
  };
31
31
  }
32
32
 
@@ -40,7 +40,7 @@ function allocBufferId(): number {
40
40
  export function uploadBytes(
41
41
  _ctx: LoaderContext,
42
42
  usage: 'image-data' | 'vertex-data' | 'index-data',
43
- bytes: Uint8Array,
43
+ bytes: Uint8Array
44
44
  ): number {
45
45
  const bufferId = allocBufferId();
46
46
  uploadBuffer(bufferId, usage, bytes);
package/src/convert.ts CHANGED
@@ -1,18 +1,30 @@
1
1
  import { Accessor, TextureInfo } from '@gltf-transform/core';
2
+ import {
3
+ quat,
4
+ vec3,
5
+ vec4,
6
+ type quat as Quat,
7
+ type vec3 as Vec3,
8
+ type vec4 as Vec4
9
+ } from 'gl-matrix';
2
10
  import type { GeometryPrimitiveEntry, SamplerMode } from '@vulfram/engine/types';
3
11
  import { U16_MAX } from './constants';
4
12
  import { GltfLoaderError } from './errors';
5
13
 
6
- export function toArray3(v: ArrayLike<number>): [number, number, number] {
7
- return [v[0] ?? 0, v[1] ?? 0, v[2] ?? 0];
14
+ export function toVec3(v: ArrayLike<number>): Vec3 {
15
+ return vec3.fromValues(v[0] ?? 0, v[1] ?? 0, v[2] ?? 0);
8
16
  }
9
17
 
10
- export function toArray4(v: ArrayLike<number>, fallbackW = 1): [number, number, number, number] {
11
- return [v[0] ?? 0, v[1] ?? 0, v[2] ?? 0, v[3] ?? fallbackW];
18
+ export function toVec4(v: ArrayLike<number>, fallbackW = 1): Vec4 {
19
+ return vec4.fromValues(v[0] ?? 0, v[1] ?? 0, v[2] ?? 0, v[3] ?? fallbackW);
20
+ }
21
+
22
+ export function toQuat(v: ArrayLike<number>, fallbackW = 1): Quat {
23
+ return quat.fromValues(v[0] ?? 0, v[1] ?? 0, v[2] ?? 0, v[3] ?? fallbackW);
12
24
  }
13
25
 
14
26
  export function semanticToPrimitiveType(
15
- semantic: string,
27
+ semantic: string
16
28
  ): GeometryPrimitiveEntry['primitiveType'] | null {
17
29
  switch (semantic) {
18
30
  case 'POSITION':
@@ -41,7 +53,7 @@ export function samplerFromTextureInfo(
41
53
  getWrapT(): number;
42
54
  getMagFilter(): number | null;
43
55
  getMinFilter(): number | null;
44
- } | null,
56
+ } | null
45
57
  ): SamplerMode {
46
58
  const WRAP_REPEAT = TextureInfo.WrapMode.REPEAT;
47
59
  const WRAP_MIRRORED_REPEAT = TextureInfo.WrapMode.MIRRORED_REPEAT;
@@ -56,18 +68,18 @@ export function samplerFromTextureInfo(
56
68
  const min = info?.getMinFilter();
57
69
 
58
70
  const repeat =
59
- wrapS === WRAP_REPEAT
60
- || wrapS === WRAP_MIRRORED_REPEAT
61
- || wrapT === WRAP_REPEAT
62
- || wrapT === WRAP_MIRRORED_REPEAT;
71
+ wrapS === WRAP_REPEAT ||
72
+ wrapS === WRAP_MIRRORED_REPEAT ||
73
+ wrapT === WRAP_REPEAT ||
74
+ wrapT === WRAP_MIRRORED_REPEAT;
63
75
 
64
76
  const linear =
65
- mag === null
66
- || mag === MAG_LINEAR
67
- || min === null
68
- || min === MIN_LINEAR
69
- || min === MIN_LINEAR_MIPMAP_LINEAR
70
- || min === MIN_LINEAR_MIPMAP_NEAREST;
77
+ mag === null ||
78
+ mag === MAG_LINEAR ||
79
+ min === null ||
80
+ min === MIN_LINEAR ||
81
+ min === MIN_LINEAR_MIPMAP_LINEAR ||
82
+ min === MIN_LINEAR_MIPMAP_NEAREST;
71
83
 
72
84
  if (repeat) return linear ? 'linear-repeat' : 'point-repeat';
73
85
  return linear ? 'linear-clamp' : 'point-clamp';
@@ -82,11 +94,7 @@ function toFloatArray(accessor: Accessor, components: number, fillTail = 0): Flo
82
94
  const srcArray = accessor.getArray();
83
95
  const elementSize = accessor.getElementSize();
84
96
 
85
- if (
86
- srcArray instanceof Float32Array
87
- && !accessor.getNormalized()
88
- && elementSize === components
89
- ) {
97
+ if (srcArray instanceof Float32Array && !accessor.getNormalized() && elementSize === components) {
90
98
  return srcArray;
91
99
  }
92
100
 
package/src/errors.ts CHANGED
@@ -2,7 +2,10 @@ import type { GltfLoaderErrorCode } from './types';
2
2
 
3
3
  /** Loader error with stable error code for callers. */
4
4
  export class GltfLoaderError extends Error {
5
- constructor(public readonly code: GltfLoaderErrorCode, message: string) {
5
+ constructor(
6
+ public readonly code: GltfLoaderErrorCode,
7
+ message: string
8
+ ) {
6
9
  super(message);
7
10
  this.name = 'GltfLoaderError';
8
11
  }
package/src/index.ts CHANGED
@@ -1,8 +1,4 @@
1
- import {
2
- dispose3DGeometry,
3
- dispose3DMaterial,
4
- dispose3DTexture,
5
- } from '@vulfram/engine/world3d';
1
+ import { dispose3DGeometry, dispose3DMaterial, dispose3DTexture } from '@vulfram/engine/world3d';
6
2
  import { createContext } from './context';
7
3
  import { GltfLoaderError } from './errors';
8
4
  import { readDocument } from './parse';
@@ -12,7 +8,7 @@ import type {
12
8
  GltfLoadInput,
13
9
  GltfLoadResult,
14
10
  GltfInstance,
15
- LoadedGltfAsset,
11
+ LoadedGltfAsset
16
12
  } from './types';
17
13
 
18
14
  export type {
@@ -27,7 +23,7 @@ export type {
27
23
  NodeTemplate,
28
24
  SceneTemplate,
29
25
  GltfSourceFormat,
30
- RootTransform,
26
+ RootTransform
31
27
  } from './types';
32
28
  export { GltfLoaderError } from './errors';
33
29
 
@@ -78,7 +74,7 @@ export async function loadGltfAsset(input: GltfLoadInput): Promise<LoadedGltfAss
78
74
  if (disposedAll) {
79
75
  throw new GltfLoaderError(
80
76
  'INVALID_INPUT',
81
- 'Cannot instantiate from a disposed glTF asset. Load it again.',
77
+ 'Cannot instantiate from a disposed glTF asset. Load it again.'
82
78
  );
83
79
  }
84
80
 
@@ -99,11 +95,11 @@ export async function loadGltfAsset(input: GltfLoadInput): Promise<LoadedGltfAss
99
95
  resources: {
100
96
  geometries: [...ctx.createdGeometryIds],
101
97
  materials: [...ctx.createdMaterialIds],
102
- textures: [...ctx.createdTextureIds],
98
+ textures: [...ctx.createdTextureIds]
103
99
  },
104
100
  instantiate,
105
101
  disposeEntities,
106
- disposeAll,
102
+ disposeAll
107
103
  };
108
104
  }
109
105
 
@@ -115,7 +111,7 @@ export async function loadGltfAsset(input: GltfLoadInput): Promise<LoadedGltfAss
115
111
  export async function loadGltfScene(input: GltfLoadInput): Promise<GltfLoadResult> {
116
112
  const asset = await loadGltfAsset(input);
117
113
  const instance = asset.instantiate({
118
- rootTransform: input.rootTransform,
114
+ rootTransform: input.rootTransform
119
115
  });
120
116
 
121
117
  return {
@@ -124,6 +120,6 @@ export async function loadGltfScene(input: GltfLoadInput): Promise<GltfLoadResul
124
120
  geometryCount: asset.resources.geometries.length,
125
121
  materialCount: asset.resources.materials.length,
126
122
  textureCount: asset.resources.textures.length,
127
- warnings: asset.warnings,
123
+ warnings: asset.warnings
128
124
  };
129
125
  }
package/src/parse.ts CHANGED
@@ -5,7 +5,7 @@ import type { GltfLoadInput } from './types';
5
5
 
6
6
  function parseGltfJsonDocument(
7
7
  bytes: Uint8Array,
8
- resources?: Record<string, import('./types').BinaryLike>,
8
+ resources?: Record<string, import('./types').BinaryLike>
9
9
  ): JSONDocument {
10
10
  const decoder = new TextDecoder('utf-8');
11
11
  let json: GLTF.IGLTF;
@@ -26,7 +26,7 @@ function parseGltfJsonDocument(
26
26
  }
27
27
  throw new GltfLoaderError(
28
28
  'MISSING_RESOURCE',
29
- `External resource not provided for URI "${uri}". Provide it in input.resources.`,
29
+ `External resource not provided for URI "${uri}". Provide it in input.resources.`
30
30
  );
31
31
  };
32
32
 
@@ -39,7 +39,7 @@ function parseGltfJsonDocument(
39
39
 
40
40
  return {
41
41
  json,
42
- resources: resourceMap as unknown as { [s: string]: Uint8Array<ArrayBuffer> },
42
+ resources: resourceMap as unknown as { [s: string]: Uint8Array<ArrayBuffer> }
43
43
  };
44
44
  }
45
45
 
package/src/resources.ts CHANGED
@@ -5,8 +5,9 @@ import {
5
5
  create3DTexture,
6
6
  type GeometryId,
7
7
  type MaterialId,
8
- type TextureId,
8
+ type TextureId
9
9
  } from '@vulfram/engine/world3d';
10
+ import { vec4 } from 'gl-matrix';
10
11
  import type { GeometryPrimitiveEntry } from '@vulfram/engine/types';
11
12
  import { uploadBytes } from './context';
12
13
  import {
@@ -14,15 +15,11 @@ import {
14
15
  accessorToStreamBytes,
15
16
  samplerFromTextureInfo,
16
17
  semanticToPrimitiveType,
17
- toArray4,
18
+ toVec4
18
19
  } from './convert';
19
20
  import type { LoaderContext } from './types';
20
21
 
21
- function uploadVertexAccessor(
22
- ctx: LoaderContext,
23
- accessor: Accessor,
24
- semantic: string,
25
- ): number {
22
+ function uploadVertexAccessor(ctx: LoaderContext, accessor: Accessor, semantic: string): number {
26
23
  const cached = ctx.uploadedVertexByAccessor.get(accessor);
27
24
  if (cached !== undefined) return cached;
28
25
 
@@ -46,14 +43,14 @@ function uploadIndexAccessor(ctx: LoaderContext, accessor: Accessor): number {
46
43
  export function ensureTexture(
47
44
  ctx: LoaderContext,
48
45
  texture: Texture,
49
- srgb: boolean,
46
+ srgb: boolean
50
47
  ): TextureId | null {
51
48
  const existing = ctx.textureBySource.get(texture);
52
49
  if (existing !== undefined) {
53
50
  const previous = ctx.textureColorSpaceHint.get(texture);
54
51
  if (previous !== undefined && previous !== srgb) {
55
52
  ctx.warnings.push(
56
- `Texture "${texture.getName() || 'unnamed'}" reused with mixed color-space expectations; keeping first value (srgb=${previous}).`,
53
+ `Texture "${texture.getName() || 'unnamed'}" reused with mixed color-space expectations; keeping first value (srgb=${previous}).`
57
54
  );
58
55
  }
59
56
  return existing;
@@ -62,7 +59,7 @@ export function ensureTexture(
62
59
  const image = texture.getImage();
63
60
  if (!image) {
64
61
  ctx.warnings.push(
65
- `Texture "${texture.getName() || 'unnamed'}" has no image bytes and was skipped.`,
62
+ `Texture "${texture.getName() || 'unnamed'}" has no image bytes and was skipped.`
66
63
  );
67
64
  return null;
68
65
  }
@@ -72,7 +69,7 @@ export function ensureTexture(
72
69
  label: `${ctx.labelPrefix}:tex:${texture.getName() || 'unnamed'}`,
73
70
  source: { type: 'buffer', bufferId },
74
71
  srgb,
75
- mode: 'standalone',
72
+ mode: 'standalone'
76
73
  });
77
74
 
78
75
  ctx.textureBySource.set(texture, textureId);
@@ -98,11 +95,11 @@ function ensureDefaultMaterial(ctx: LoaderContext): MaterialId {
98
95
  options: {
99
96
  type: 'standard',
100
97
  content: {
101
- baseColor: [1, 1, 1, 1],
98
+ baseColor: vec4.fromValues(1, 1, 1, 1),
102
99
  surfaceType: 'opaque',
103
- flags: 0,
104
- },
105
- },
100
+ flags: 0
101
+ }
102
+ }
106
103
  });
107
104
 
108
105
  ctx.defaultMaterialId = id;
@@ -117,16 +114,16 @@ function ensureDefaultMaterial(ctx: LoaderContext): MaterialId {
117
114
  options: {
118
115
  type: 'pbr',
119
116
  content: {
120
- baseColor: [1, 1, 1, 1],
117
+ baseColor: vec4.fromValues(1, 1, 1, 1),
121
118
  surfaceType: 'opaque',
122
- emissiveColor: [0, 0, 0, 1],
119
+ emissiveColor: vec4.fromValues(0, 0, 0, 1),
123
120
  metallic: 0,
124
121
  roughness: 1,
125
122
  ao: 1,
126
123
  normalScale: 1,
127
- flags: 0,
128
- },
129
- },
124
+ flags: 0
125
+ }
126
+ }
130
127
  });
131
128
 
132
129
  ctx.defaultMaterialId = id;
@@ -169,14 +166,14 @@ export function ensureMaterial(ctx: LoaderContext, material: Material | null): M
169
166
  ? {
170
167
  type: 'pbr' as const,
171
168
  content: {
172
- baseColor: toArray4(material.getBaseColorFactor(), 1),
169
+ baseColor: toVec4(material.getBaseColorFactor(), 1),
173
170
  surfaceType: alphaModeToSurfaceType(material.getAlphaMode()),
174
- emissiveColor: [
171
+ emissiveColor: vec4.fromValues(
175
172
  emissiveFactor[0] ?? 0,
176
173
  emissiveFactor[1] ?? 0,
177
174
  emissiveFactor[2] ?? 0,
178
- 1,
179
- ] as [number, number, number, number],
175
+ 1
176
+ ),
180
177
  metallic: material.getMetallicFactor(),
181
178
  roughness: material.getRoughnessFactor(),
182
179
  ao: material.getOcclusionStrength(),
@@ -191,20 +188,20 @@ export function ensureMaterial(ctx: LoaderContext, material: Material | null): M
191
188
  emissiveSampler,
192
189
  aoTexId,
193
190
  aoSampler,
194
- flags: material.getDoubleSided() ? 1 : 0,
195
- },
191
+ flags: material.getDoubleSided() ? 1 : 0
192
+ }
196
193
  }
197
194
  : {
198
195
  type: 'standard' as const,
199
196
  content: {
200
- baseColor: toArray4(material.getBaseColorFactor(), 1),
197
+ baseColor: toVec4(material.getBaseColorFactor(), 1),
201
198
  surfaceType: alphaModeToSurfaceType(material.getAlphaMode()),
202
- specColor: [
199
+ specColor: vec4.fromValues(
203
200
  material.getMetallicFactor(),
204
201
  material.getRoughnessFactor(),
205
202
  material.getOcclusionStrength(),
206
- 1,
207
- ] as [number, number, number, number],
203
+ 1
204
+ ),
208
205
  specPower: 32,
209
206
  baseTexId,
210
207
  baseSampler,
@@ -212,10 +209,10 @@ export function ensureMaterial(ctx: LoaderContext, material: Material | null): M
212
209
  normalSampler,
213
210
  specTexId: mrTexId,
214
211
  specSampler: mrSampler,
215
- flags: material.getDoubleSided() ? 1 : 0,
216
- },
217
- }),
218
- },
212
+ flags: material.getDoubleSided() ? 1 : 0
213
+ }
214
+ })
215
+ }
219
216
  });
220
217
 
221
218
  ctx.materialBySource.set(material, materialId);
@@ -227,14 +224,14 @@ export function ensureMaterial(ctx: LoaderContext, material: Material | null): M
227
224
  /** Ensures a core geometry exists for source primitive. */
228
225
  export function ensurePrimitiveGeometry(
229
226
  ctx: LoaderContext,
230
- primitive: Primitive,
227
+ primitive: Primitive
231
228
  ): GeometryId | null {
232
229
  const existing = ctx.geometryByPrimitive.get(primitive);
233
230
  if (existing !== undefined) return existing;
234
231
 
235
232
  if (primitive.getMode() !== Primitive.Mode.TRIANGLES) {
236
233
  ctx.warnings.push(
237
- `Primitive mode ${primitive.getMode()} is not supported in v1 loader (triangles only). Primitive skipped.`,
234
+ `Primitive mode ${primitive.getMode()} is not supported in v1 loader (triangles only). Primitive skipped.`
238
235
  );
239
236
  return null;
240
237
  }
@@ -244,7 +241,7 @@ export function ensurePrimitiveGeometry(
244
241
  if (indexAccessor) {
245
242
  entries.push({
246
243
  primitiveType: 'index',
247
- bufferId: uploadIndexAccessor(ctx, indexAccessor),
244
+ bufferId: uploadIndexAccessor(ctx, indexAccessor)
248
245
  });
249
246
  }
250
247
 
@@ -254,7 +251,7 @@ export function ensurePrimitiveGeometry(
254
251
  'TANGENT',
255
252
  'COLOR_0',
256
253
  'TEXCOORD_0',
257
- 'TEXCOORD_1',
254
+ 'TEXCOORD_1'
258
255
  ] as const;
259
256
 
260
257
  let hasPosition = false;
@@ -273,7 +270,7 @@ export function ensurePrimitiveGeometry(
273
270
 
274
271
  entries.push({
275
272
  primitiveType,
276
- bufferId: uploadVertexAccessor(ctx, accessor, semantic),
273
+ bufferId: uploadVertexAccessor(ctx, accessor, semantic)
277
274
  });
278
275
  }
279
276
 
@@ -285,7 +282,7 @@ export function ensurePrimitiveGeometry(
285
282
  const geometryId = create3DGeometry(ctx.worldId, {
286
283
  type: 'custom',
287
284
  label: `${ctx.labelPrefix}:geo:${primitive.getName() || 'unnamed'}`,
288
- entries,
285
+ entries
289
286
  });
290
287
 
291
288
  ctx.geometryByPrimitive.set(primitive, geometryId);
package/src/scene.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Node, Scene } from '@gltf-transform/core';
2
+ import type { quat as Quat, vec3 as Vec3 } from 'gl-matrix';
2
3
  import {
3
4
  create3DEntity,
4
5
  create3DModel,
@@ -6,16 +7,19 @@ import {
6
7
  set3DParent,
7
8
  update3DTransform,
8
9
  type EntityId,
9
- type World3DId,
10
+ type World3DId
10
11
  } from '@vulfram/engine/world3d';
11
- import { toArray3, toArray4 } from './convert';
12
+ import { toQuat, toVec3 } from './convert';
12
13
  import { ensureMaterial, ensurePrimitiveGeometry } from './resources';
13
- import type {
14
- GltfInstance,
15
- GltfInstantiateOptions,
16
- LoaderContext,
17
- SceneTemplate,
18
- } from './types';
14
+ import type { GltfInstance, GltfInstantiateOptions, LoaderContext, SceneTemplate } from './types';
15
+
16
+ function isIdentityVec3(value: Vec3, identity: number): boolean {
17
+ return value[0] === identity && value[1] === identity && value[2] === identity;
18
+ }
19
+
20
+ function isIdentityQuat(value: Quat): boolean {
21
+ return value[0] === 0 && value[1] === 0 && value[2] === 0 && value[3] === 1;
22
+ }
19
23
 
20
24
  function createNodeTemplate(ctx: LoaderContext, node: Node, nodes: SceneTemplate['nodes']): number {
21
25
  const mesh = node.getMesh();
@@ -33,11 +37,11 @@ function createNodeTemplate(ctx: LoaderContext, node: Node, nodes: SceneTemplate
33
37
  const nodeIndex = nodes.length;
34
38
  nodes.push({
35
39
  name: node.getName() || undefined,
36
- translation: toArray3(node.getTranslation()),
37
- rotation: toArray4(node.getRotation(), 1),
38
- scale: toArray3(node.getScale()),
40
+ translation: toVec3(node.getTranslation()),
41
+ rotation: toQuat(node.getRotation(), 1),
42
+ scale: toVec3(node.getScale()),
39
43
  children: [],
40
- primitives,
44
+ primitives
41
45
  });
42
46
 
43
47
  for (const child of node.listChildren()) {
@@ -63,21 +67,18 @@ export function buildSceneTemplate(ctx: LoaderContext, scene: Scene): SceneTempl
63
67
  function setInstanceRootTransform(
64
68
  worldId: World3DId,
65
69
  rootEntityId: EntityId,
66
- options: GltfInstantiateOptions | undefined,
70
+ options: GltfInstantiateOptions | undefined
67
71
  ): void {
68
72
  const rootTransform = options?.rootTransform;
69
- update3DTransform(worldId, rootEntityId, {
70
- position: rootTransform?.position ?? [0, 0, 0],
71
- rotation: rootTransform?.rotation ?? [0, 0, 0, 1],
72
- scale: rootTransform?.scale ?? [1, 1, 1],
73
- });
73
+ if (!rootTransform) return;
74
+ update3DTransform(worldId, rootEntityId, rootTransform);
74
75
  }
75
76
 
76
77
  /** Instantiates entities/models/parents from a previously built scene template. */
77
78
  export function instantiateTemplate(
78
79
  worldId: World3DId,
79
80
  template: SceneTemplate,
80
- options?: GltfInstantiateOptions,
81
+ options?: GltfInstantiateOptions
81
82
  ): GltfInstance {
82
83
  const entityIds: EntityId[] = [];
83
84
  let disposed = false;
@@ -96,11 +97,12 @@ export function instantiateTemplate(
96
97
  nodeEntityIds[i] = nodeEntityId;
97
98
  entityIds.push(nodeEntityId);
98
99
 
99
- update3DTransform(worldId, nodeEntityId, {
100
- position: node.translation,
101
- rotation: node.rotation,
102
- scale: node.scale,
103
- });
100
+ const transformPatch = {
101
+ position: isIdentityVec3(node.translation, 0) ? undefined : node.translation,
102
+ rotation: isIdentityQuat(node.rotation) ? undefined : node.rotation,
103
+ scale: isIdentityVec3(node.scale, 1) ? undefined : node.scale
104
+ };
105
+ update3DTransform(worldId, nodeEntityId, transformPatch);
104
106
  }
105
107
 
106
108
  for (const rootIndex of template.roots) {
@@ -126,7 +128,7 @@ export function instantiateTemplate(
126
128
  const primitive = node.primitives[0]!;
127
129
  create3DModel(worldId, parentEntity, {
128
130
  geometryId: primitive.geometryId,
129
- materialId: primitive.materialId,
131
+ materialId: primitive.materialId
130
132
  });
131
133
  continue;
132
134
  }
@@ -134,15 +136,10 @@ export function instantiateTemplate(
134
136
  for (const primitive of node.primitives) {
135
137
  const modelEntity = create3DEntity(worldId);
136
138
  entityIds.push(modelEntity);
137
- update3DTransform(worldId, modelEntity, {
138
- position: [0, 0, 0],
139
- rotation: [0, 0, 0, 1],
140
- scale: [1, 1, 1],
141
- });
142
139
  set3DParent(worldId, modelEntity, parentEntity);
143
140
  create3DModel(worldId, modelEntity, {
144
141
  geometryId: primitive.geometryId,
145
- materialId: primitive.materialId,
142
+ materialId: primitive.materialId
146
143
  });
147
144
  }
148
145
  }
@@ -159,6 +156,6 @@ export function instantiateTemplate(
159
156
  remove3DEntity(worldId, entityId);
160
157
  }
161
158
  }
162
- },
159
+ }
163
160
  };
164
161
  }
package/src/types.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { Accessor, Material, Node, Primitive, Texture } from '@gltf-transform/core';
2
+ import type { quat, vec3 } from 'gl-matrix';
2
3
  import type {
3
4
  EntityId,
4
5
  GeometryId,
5
6
  MaterialId,
6
7
  TextureId,
7
- World3DId,
8
+ World3DId
8
9
  } from '@vulfram/engine/world3d';
9
10
 
10
11
  /** Supported source formats for glTF scene loading. */
@@ -15,9 +16,9 @@ export type BinaryLike = Uint8Array | ArrayBuffer | ArrayBufferView;
15
16
 
16
17
  /** Optional root transform applied to the imported scene root entity. */
17
18
  export type RootTransform = {
18
- position?: [number, number, number];
19
- rotation?: [number, number, number, number];
20
- scale?: [number, number, number];
19
+ position?: vec3;
20
+ rotation?: quat;
21
+ scale?: vec3;
21
22
  };
22
23
 
23
24
  /** Instantiation options for a loaded glTF asset template. */
@@ -35,9 +36,9 @@ export interface GltfInstance {
35
36
  /** Static template node with local transform and mesh primitive bindings. */
36
37
  export interface NodeTemplate {
37
38
  name?: string;
38
- translation: [number, number, number];
39
- rotation: [number, number, number, number];
40
- scale: [number, number, number];
39
+ translation: vec3;
40
+ rotation: quat;
41
+ scale: vec3;
41
42
  children: number[];
42
43
  primitives: Array<{
43
44
  geometryId: GeometryId;
package/tsconfig.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "lib": ["ESNext"],
4
- "types": ["bun"],
3
+ "lib": [
4
+ "ESNext"
5
+ ],
6
+ "types": [
7
+ "bun"
8
+ ],
5
9
  "target": "ESNext",
6
10
  "module": "Preserve",
7
11
  "moduleDetection": "force",
@@ -16,5 +20,7 @@
16
20
  "noUncheckedIndexedAccess": true,
17
21
  "noImplicitOverride": true
18
22
  },
19
- "include": ["src"]
20
- }
23
+ "include": [
24
+ "src"
25
+ ]
26
+ }