@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 +2 -1
- package/package.json +4 -3
- package/src/binary.ts +2 -2
- package/src/context.ts +3 -3
- package/src/convert.ts +29 -21
- package/src/errors.ts +4 -1
- package/src/index.ts +8 -12
- package/src/parse.ts +3 -3
- package/src/resources.ts +37 -40
- package/src/scene.ts +29 -32
- package/src/types.ts +8 -7
- package/tsconfig.json +10 -4
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulfram/gltf-loader",
|
|
3
|
-
"version": "0.22.
|
|
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.
|
|
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
|
|
7
|
-
return
|
|
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
|
|
11
|
-
return
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
117
|
+
baseColor: vec4.fromValues(1, 1, 1, 1),
|
|
121
118
|
surfaceType: 'opaque',
|
|
122
|
-
emissiveColor:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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 {
|
|
12
|
+
import { toQuat, toVec3 } from './convert';
|
|
12
13
|
import { ensureMaterial, ensurePrimitiveGeometry } from './resources';
|
|
13
|
-
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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:
|
|
37
|
-
rotation:
|
|
38
|
-
scale:
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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?:
|
|
19
|
-
rotation?:
|
|
20
|
-
scale?:
|
|
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:
|
|
39
|
-
rotation:
|
|
40
|
-
scale:
|
|
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": [
|
|
4
|
-
|
|
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": [
|
|
20
|
-
|
|
23
|
+
"include": [
|
|
24
|
+
"src"
|
|
25
|
+
]
|
|
26
|
+
}
|