@luma.gl/gltf 9.2.6 → 9.3.0-alpha.10
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/dist/dist.dev.js +1362 -313
- package/dist/dist.min.js +98 -46
- package/dist/gltf/animations/animations.d.ts +16 -4
- package/dist/gltf/animations/animations.d.ts.map +1 -1
- package/dist/gltf/animations/interpolate.d.ts +4 -3
- package/dist/gltf/animations/interpolate.d.ts.map +1 -1
- package/dist/gltf/animations/interpolate.js +27 -36
- package/dist/gltf/animations/interpolate.js.map +1 -1
- package/dist/gltf/create-gltf-model.d.ts +15 -1
- package/dist/gltf/create-gltf-model.d.ts.map +1 -1
- package/dist/gltf/create-gltf-model.js +154 -48
- package/dist/gltf/create-gltf-model.js.map +1 -1
- package/dist/gltf/create-scenegraph-from-gltf.d.ts +39 -2
- package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
- package/dist/gltf/create-scenegraph-from-gltf.js +76 -6
- package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
- package/dist/gltf/gltf-animator.d.ts +26 -0
- package/dist/gltf/gltf-animator.d.ts.map +1 -1
- package/dist/gltf/gltf-animator.js +22 -19
- package/dist/gltf/gltf-animator.js.map +1 -1
- package/dist/gltf/gltf-extension-support.d.ts +10 -0
- package/dist/gltf/gltf-extension-support.d.ts.map +1 -0
- package/dist/gltf/gltf-extension-support.js +173 -0
- package/dist/gltf/gltf-extension-support.js.map +1 -0
- package/dist/index.cjs +1302 -276
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/parse-gltf-animations.d.ts +1 -0
- package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
- package/dist/parsers/parse-gltf-animations.js +73 -28
- package/dist/parsers/parse-gltf-animations.js.map +1 -1
- package/dist/parsers/parse-gltf-lights.d.ts +5 -0
- package/dist/parsers/parse-gltf-lights.d.ts.map +1 -0
- package/dist/parsers/parse-gltf-lights.js +163 -0
- package/dist/parsers/parse-gltf-lights.js.map +1 -0
- package/dist/parsers/parse-gltf.d.ts +19 -2
- package/dist/parsers/parse-gltf.d.ts.map +1 -1
- package/dist/parsers/parse-gltf.js +101 -61
- package/dist/parsers/parse-gltf.js.map +1 -1
- package/dist/parsers/parse-pbr-material.d.ts +115 -2
- package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
- package/dist/parsers/parse-pbr-material.js +570 -54
- package/dist/parsers/parse-pbr-material.js.map +1 -1
- package/dist/pbr/pbr-environment.d.ts +10 -4
- package/dist/pbr/pbr-environment.d.ts.map +1 -1
- package/dist/pbr/pbr-environment.js +18 -15
- package/dist/pbr/pbr-environment.js.map +1 -1
- package/dist/pbr/pbr-material.d.ts +13 -3
- package/dist/pbr/pbr-material.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
- package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +11 -5
- package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-sampler.js +16 -12
- package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -9
- package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-topology.js +2 -14
- package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
- package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
- package/package.json +8 -9
- package/src/gltf/animations/animations.ts +17 -5
- package/src/gltf/animations/interpolate.ts +49 -68
- package/src/gltf/create-gltf-model.ts +214 -48
- package/src/gltf/create-scenegraph-from-gltf.ts +134 -11
- package/src/gltf/gltf-animator.ts +34 -25
- package/src/gltf/gltf-extension-support.ts +214 -0
- package/src/index.ts +11 -2
- package/src/parsers/parse-gltf-animations.ts +94 -33
- package/src/parsers/parse-gltf-lights.ts +218 -0
- package/src/parsers/parse-gltf.ts +170 -90
- package/src/parsers/parse-pbr-material.ts +870 -80
- package/src/pbr/pbr-environment.ts +44 -21
- package/src/pbr/pbr-material.ts +18 -3
- package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
- package/src/webgl-to-webgpu/convert-webgl-sampler.ts +38 -29
- package/src/webgl-to-webgpu/convert-webgl-topology.ts +2 -14
- package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
- package/dist/utils/deep-copy.d.ts +0 -3
- package/dist/utils/deep-copy.d.ts.map +0 -1
- package/dist/utils/deep-copy.js +0 -21
- package/dist/utils/deep-copy.js.map +0 -1
- package/src/utils/deep-copy.ts +0 -22
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
import {GLTFNodePostprocessed} from '@loaders.gl/gltf';
|
|
2
1
|
import {log} from '@luma.gl/core';
|
|
3
2
|
import {Quaternion} from '@math.gl/core';
|
|
4
|
-
import {
|
|
3
|
+
import {GLTFAnimationPath, GLTFAnimationSampler} from './animations';
|
|
4
|
+
import {GroupNode} from '@luma.gl/engine';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/** Applies an evaluated animation value to a scenegraph node. */
|
|
7
|
+
function updateTargetPath(
|
|
8
|
+
target: GroupNode,
|
|
9
|
+
path: GLTFAnimationPath,
|
|
10
|
+
newValue: number[]
|
|
11
|
+
): GroupNode | null {
|
|
12
|
+
switch (path) {
|
|
13
|
+
case 'translation':
|
|
14
|
+
return target.setPosition(newValue).updateMatrix();
|
|
7
15
|
|
|
16
|
+
case 'rotation':
|
|
17
|
+
return target.setRotation(newValue).updateMatrix();
|
|
18
|
+
|
|
19
|
+
case 'scale':
|
|
20
|
+
return target.setScale(newValue).updateMatrix();
|
|
21
|
+
|
|
22
|
+
default:
|
|
23
|
+
log.warn(`Bad animation path ${path}`)();
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Evaluates a glTF animation sampler at the supplied time and applies the result to a node. */
|
|
8
29
|
export function interpolate(
|
|
9
30
|
time: number,
|
|
10
31
|
{input, interpolation, output}: GLTFAnimationSampler,
|
|
11
|
-
target:
|
|
12
|
-
path:
|
|
32
|
+
target: GroupNode,
|
|
33
|
+
path: GLTFAnimationPath
|
|
13
34
|
) {
|
|
14
35
|
const maxTime = input[input.length - 1];
|
|
15
36
|
const animationTime = time % maxTime;
|
|
@@ -17,44 +38,18 @@ export function interpolate(
|
|
|
17
38
|
const nextIndex = input.findIndex(t => t >= animationTime);
|
|
18
39
|
const previousIndex = Math.max(0, nextIndex - 1);
|
|
19
40
|
|
|
20
|
-
if (!Array.isArray(target[path])) {
|
|
21
|
-
switch (path) {
|
|
22
|
-
case 'translation':
|
|
23
|
-
target[path] = [0, 0, 0];
|
|
24
|
-
break;
|
|
25
|
-
|
|
26
|
-
case 'rotation':
|
|
27
|
-
target[path] = [0, 0, 0, 1];
|
|
28
|
-
break;
|
|
29
|
-
|
|
30
|
-
case 'scale':
|
|
31
|
-
target[path] = [1, 1, 1];
|
|
32
|
-
break;
|
|
33
|
-
|
|
34
|
-
default:
|
|
35
|
-
log.warn(`Bad animation path ${path}`)();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// assert(target[path].length === output[previousIndex].length);
|
|
40
41
|
const previousTime = input[previousIndex];
|
|
41
42
|
const nextTime = input[nextIndex];
|
|
42
43
|
|
|
43
44
|
switch (interpolation) {
|
|
44
45
|
case 'STEP':
|
|
45
|
-
stepInterpolate(target, path, output[previousIndex]
|
|
46
|
+
stepInterpolate(target, path, output[previousIndex]);
|
|
46
47
|
break;
|
|
47
48
|
|
|
48
49
|
case 'LINEAR':
|
|
49
50
|
if (nextTime > previousTime) {
|
|
50
51
|
const ratio = (animationTime - previousTime) / (nextTime - previousTime);
|
|
51
|
-
linearInterpolate(
|
|
52
|
-
target,
|
|
53
|
-
path,
|
|
54
|
-
output[previousIndex] as number[],
|
|
55
|
-
output[nextIndex] as number[],
|
|
56
|
-
ratio
|
|
57
|
-
);
|
|
52
|
+
linearInterpolate(target, path, output[previousIndex], output[nextIndex], ratio);
|
|
58
53
|
}
|
|
59
54
|
break;
|
|
60
55
|
|
|
@@ -63,10 +58,10 @@ export function interpolate(
|
|
|
63
58
|
const ratio = (animationTime - previousTime) / (nextTime - previousTime);
|
|
64
59
|
const tDiff = nextTime - previousTime;
|
|
65
60
|
|
|
66
|
-
const p0 = output[3 * previousIndex + 1]
|
|
67
|
-
const outTangent0 = output[3 * previousIndex + 2]
|
|
68
|
-
const inTangent1 = output[3 * nextIndex + 0]
|
|
69
|
-
const p1 = output[3 * nextIndex + 1]
|
|
61
|
+
const p0 = output[3 * previousIndex + 1];
|
|
62
|
+
const outTangent0 = output[3 * previousIndex + 2];
|
|
63
|
+
const inTangent1 = output[3 * nextIndex + 0];
|
|
64
|
+
const p1 = output[3 * nextIndex + 1];
|
|
70
65
|
|
|
71
66
|
cubicsplineInterpolate(target, path, {p0, outTangent0, inTangent1, p1, tDiff, ratio});
|
|
72
67
|
}
|
|
@@ -78,34 +73,31 @@ export function interpolate(
|
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
|
|
76
|
+
/** Applies linear interpolation between two keyframes. */
|
|
81
77
|
function linearInterpolate(
|
|
82
|
-
target:
|
|
83
|
-
path:
|
|
78
|
+
target: GroupNode,
|
|
79
|
+
path: GLTFAnimationPath,
|
|
84
80
|
start: number[],
|
|
85
81
|
stop: number[],
|
|
86
82
|
ratio: number
|
|
87
83
|
) {
|
|
88
|
-
if (!target[path]) {
|
|
89
|
-
throw new Error();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
84
|
if (path === 'rotation') {
|
|
93
85
|
// SLERP when path is rotation
|
|
94
|
-
|
|
95
|
-
for (let i = 0; i < scratchQuaternion.length; i++) {
|
|
96
|
-
target[path][i] = scratchQuaternion[i];
|
|
97
|
-
}
|
|
86
|
+
updateTargetPath(target, path, new Quaternion().slerp({start, target: stop, ratio}));
|
|
98
87
|
} else {
|
|
99
88
|
// regular interpolation
|
|
89
|
+
const newVal = [];
|
|
100
90
|
for (let i = 0; i < start.length; i++) {
|
|
101
|
-
|
|
91
|
+
newVal[i] = ratio * stop[i] + (1 - ratio) * start[i];
|
|
102
92
|
}
|
|
93
|
+
updateTargetPath(target, path, newVal);
|
|
103
94
|
}
|
|
104
95
|
}
|
|
105
96
|
|
|
97
|
+
/** Applies glTF cubic spline interpolation between two keyframes. */
|
|
106
98
|
function cubicsplineInterpolate(
|
|
107
|
-
target:
|
|
108
|
-
path:
|
|
99
|
+
target: GroupNode,
|
|
100
|
+
path: GLTFAnimationPath,
|
|
109
101
|
{
|
|
110
102
|
p0,
|
|
111
103
|
outTangent0,
|
|
@@ -122,32 +114,21 @@ function cubicsplineInterpolate(
|
|
|
122
114
|
ratio: number;
|
|
123
115
|
}
|
|
124
116
|
) {
|
|
125
|
-
if (!target[path]) {
|
|
126
|
-
throw new Error();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
117
|
// TODO: Quaternion might need normalization
|
|
130
|
-
|
|
118
|
+
const newVal = [];
|
|
119
|
+
for (let i = 0; i < p0.length; i++) {
|
|
131
120
|
const m0 = outTangent0[i] * tDiff;
|
|
132
121
|
const m1 = inTangent1[i] * tDiff;
|
|
133
|
-
|
|
122
|
+
newVal[i] =
|
|
134
123
|
(2 * Math.pow(t, 3) - 3 * Math.pow(t, 2) + 1) * p0[i] +
|
|
135
124
|
(Math.pow(t, 3) - 2 * Math.pow(t, 2) + t) * m0 +
|
|
136
125
|
(-2 * Math.pow(t, 3) + 3 * Math.pow(t, 2)) * p1[i] +
|
|
137
126
|
(Math.pow(t, 3) - Math.pow(t, 2)) * m1;
|
|
138
127
|
}
|
|
128
|
+
updateTargetPath(target, path, newVal);
|
|
139
129
|
}
|
|
140
130
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
value: number[]
|
|
145
|
-
) {
|
|
146
|
-
if (!target[path]) {
|
|
147
|
-
throw new Error();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
for (let i = 0; i < value.length; i++) {
|
|
151
|
-
target[path][i] = value[i];
|
|
152
|
-
}
|
|
131
|
+
/** Applies step interpolation by copying the current keyframe value. */
|
|
132
|
+
function stepInterpolate(target: GroupNode, path: GLTFAnimationPath, value: number[]) {
|
|
133
|
+
updateTargetPath(target, path, value);
|
|
153
134
|
}
|
|
@@ -2,56 +2,111 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
Buffer,
|
|
7
|
+
Device,
|
|
8
|
+
Sampler,
|
|
9
|
+
Texture,
|
|
10
|
+
TextureView,
|
|
11
|
+
type Binding,
|
|
12
|
+
type RenderPipelineParameters,
|
|
13
|
+
log
|
|
14
|
+
} from '@luma.gl/core';
|
|
15
|
+
import {DynamicTexture} from '@luma.gl/engine';
|
|
16
|
+
import {pbrMaterial, skin} from '@luma.gl/shadertools';
|
|
17
|
+
import {
|
|
18
|
+
Geometry,
|
|
19
|
+
Material,
|
|
20
|
+
MaterialFactory,
|
|
21
|
+
Model,
|
|
22
|
+
ModelNode,
|
|
23
|
+
type ModelProps
|
|
24
|
+
} from '@luma.gl/engine';
|
|
8
25
|
import {type ParsedPBRMaterial} from '../pbr/pbr-material';
|
|
9
26
|
|
|
10
27
|
const SHADER = /* WGSL */ `
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
struct VertexInputs {
|
|
29
|
+
@location(0) positions: vec3f,
|
|
30
|
+
#ifdef HAS_NORMALS
|
|
31
|
+
@location(1) normals: vec3f,
|
|
32
|
+
#endif
|
|
33
|
+
#ifdef HAS_TANGENTS
|
|
34
|
+
@location(2) TANGENT: vec4f,
|
|
35
|
+
#endif
|
|
36
|
+
#ifdef HAS_UV
|
|
37
|
+
@location(3) texCoords: vec2f,
|
|
38
|
+
#endif
|
|
39
|
+
#ifdef HAS_SKIN
|
|
40
|
+
@location(4) JOINTS_0: vec4u,
|
|
41
|
+
@location(5) WEIGHTS_0: vec4f,
|
|
42
|
+
#endif
|
|
43
|
+
};
|
|
20
44
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
struct FragmentInputs {
|
|
46
|
+
@builtin(position) position: vec4f,
|
|
47
|
+
@location(0) pbrPosition: vec3f,
|
|
48
|
+
@location(1) pbrUV: vec2f,
|
|
49
|
+
@location(2) pbrNormal: vec3f,
|
|
50
|
+
#ifdef HAS_TANGENTS
|
|
51
|
+
@location(3) pbrTangent: vec4f,
|
|
52
|
+
#endif
|
|
53
|
+
};
|
|
25
54
|
|
|
26
55
|
@vertex
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
|
|
57
|
+
var outputs: FragmentInputs;
|
|
58
|
+
var position = vec4f(inputs.positions, 1.0);
|
|
59
|
+
var normal = vec3f(0.0, 0.0, 1.0);
|
|
60
|
+
var tangent = vec4f(1.0, 0.0, 0.0, 1.0);
|
|
61
|
+
var uv = vec2f(0.0, 0.0);
|
|
62
|
+
|
|
63
|
+
#ifdef HAS_NORMALS
|
|
64
|
+
normal = inputs.normals;
|
|
65
|
+
#endif
|
|
66
|
+
#ifdef HAS_UV
|
|
67
|
+
uv = inputs.texCoords;
|
|
68
|
+
#endif
|
|
69
|
+
#ifdef HAS_TANGENTS
|
|
70
|
+
tangent = inputs.TANGENT;
|
|
71
|
+
#endif
|
|
72
|
+
#ifdef HAS_SKIN
|
|
73
|
+
let skinMatrix = getSkinMatrix(inputs.WEIGHTS_0, inputs.JOINTS_0);
|
|
74
|
+
position = skinMatrix * position;
|
|
75
|
+
normal = normalize((skinMatrix * vec4f(normal, 0.0)).xyz);
|
|
76
|
+
#ifdef HAS_TANGENTS
|
|
77
|
+
tangent = vec4f(normalize((skinMatrix * vec4f(tangent.xyz, 0.0)).xyz), tangent.w);
|
|
78
|
+
#endif
|
|
79
|
+
#endif
|
|
80
|
+
|
|
81
|
+
let worldPosition = pbrProjection.modelMatrix * position;
|
|
82
|
+
|
|
83
|
+
#ifdef HAS_NORMALS
|
|
84
|
+
normal = normalize((pbrProjection.normalMatrix * vec4f(normal, 0.0)).xyz);
|
|
85
|
+
#endif
|
|
86
|
+
#ifdef HAS_TANGENTS
|
|
87
|
+
let worldTangent = normalize((pbrProjection.modelMatrix * vec4f(tangent.xyz, 0.0)).xyz);
|
|
88
|
+
outputs.pbrTangent = vec4f(worldTangent, tangent.w);
|
|
89
|
+
#endif
|
|
90
|
+
|
|
91
|
+
outputs.position = pbrProjection.modelViewProjectionMatrix * position;
|
|
92
|
+
outputs.pbrPosition = worldPosition.xyz / worldPosition.w;
|
|
93
|
+
outputs.pbrUV = uv;
|
|
94
|
+
outputs.pbrNormal = normal;
|
|
95
|
+
return outputs;
|
|
96
|
+
}
|
|
47
97
|
|
|
48
98
|
@fragment
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
99
|
+
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
|
|
100
|
+
fragmentInputs.pbr_vPosition = inputs.pbrPosition;
|
|
101
|
+
fragmentInputs.pbr_vUV = inputs.pbrUV;
|
|
102
|
+
fragmentInputs.pbr_vNormal = inputs.pbrNormal;
|
|
103
|
+
#ifdef HAS_TANGENTS
|
|
104
|
+
let tangent = normalize(inputs.pbrTangent.xyz);
|
|
105
|
+
let bitangent = normalize(cross(inputs.pbrNormal, tangent)) * inputs.pbrTangent.w;
|
|
106
|
+
fragmentInputs.pbr_vTBN = mat3x3f(tangent, bitangent, inputs.pbrNormal);
|
|
107
|
+
#endif
|
|
108
|
+
return pbr_filterColor(vec4f(1.0));
|
|
109
|
+
}
|
|
55
110
|
`;
|
|
56
111
|
|
|
57
112
|
// TODO rename attributes to POSITION/NORMAL etc
|
|
@@ -76,6 +131,11 @@ const vs = /* glsl */ `\
|
|
|
76
131
|
in vec2 texCoords;
|
|
77
132
|
#endif
|
|
78
133
|
|
|
134
|
+
#ifdef HAS_SKIN
|
|
135
|
+
in uvec4 JOINTS_0;
|
|
136
|
+
in vec4 WEIGHTS_0;
|
|
137
|
+
#endif
|
|
138
|
+
|
|
79
139
|
void main(void) {
|
|
80
140
|
vec4 _NORMAL = vec4(0.);
|
|
81
141
|
vec4 _TANGENT = vec4(0.);
|
|
@@ -93,8 +153,17 @@ const vs = /* glsl */ `\
|
|
|
93
153
|
_TEXCOORD_0 = texCoords;
|
|
94
154
|
#endif
|
|
95
155
|
|
|
96
|
-
|
|
97
|
-
|
|
156
|
+
vec4 pos = positions;
|
|
157
|
+
|
|
158
|
+
#ifdef HAS_SKIN
|
|
159
|
+
mat4 skinMat = getSkinMatrix(WEIGHTS_0, JOINTS_0);
|
|
160
|
+
pos = skinMat * pos;
|
|
161
|
+
_NORMAL = skinMat * _NORMAL;
|
|
162
|
+
_TANGENT = vec4((skinMat * vec4(_TANGENT.xyz, 0.)).xyz, _TANGENT.w);
|
|
163
|
+
#endif
|
|
164
|
+
|
|
165
|
+
pbr_setPositionNormalTangentUV(pos, _NORMAL, _TANGENT, _TEXCOORD_0);
|
|
166
|
+
gl_Position = pbrProjection.modelViewProjectionMatrix * pos;
|
|
98
167
|
}
|
|
99
168
|
`;
|
|
100
169
|
|
|
@@ -108,14 +177,52 @@ const fs = /* glsl */ `\
|
|
|
108
177
|
}
|
|
109
178
|
`;
|
|
110
179
|
|
|
180
|
+
/** Options used to instantiate a `ModelNode` for one glTF primitive. */
|
|
111
181
|
export type CreateGLTFModelOptions = {
|
|
182
|
+
/** Optional id assigned to the generated model. */
|
|
112
183
|
id?: string;
|
|
184
|
+
/** Vertex count override for non-indexed primitives. */
|
|
113
185
|
vertexCount?: number;
|
|
186
|
+
/** Geometry converted from the glTF primitive. */
|
|
114
187
|
geometry: Geometry;
|
|
188
|
+
/** Parsed PBR material state for the primitive. */
|
|
115
189
|
parsedPPBRMaterial: ParsedPBRMaterial;
|
|
190
|
+
/** Pre-created material aligned with the source glTF material entry, when available. */
|
|
191
|
+
material?: Material | null;
|
|
192
|
+
/** Additional model props merged into the generated model. */
|
|
116
193
|
modelOptions?: Partial<ModelProps>;
|
|
117
194
|
};
|
|
118
195
|
|
|
196
|
+
export type CreateGLTFMaterialOptions = {
|
|
197
|
+
id?: string;
|
|
198
|
+
parsedPPBRMaterial: ParsedPBRMaterial;
|
|
199
|
+
materialFactory?: MaterialFactory;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export function createGLTFMaterial(device: Device, options: CreateGLTFMaterialOptions): Material {
|
|
203
|
+
const materialFactory =
|
|
204
|
+
options.materialFactory || new MaterialFactory(device, {modules: [pbrMaterial]});
|
|
205
|
+
|
|
206
|
+
const pbrMaterialProps = {...options.parsedPPBRMaterial.uniforms};
|
|
207
|
+
delete pbrMaterialProps.camera;
|
|
208
|
+
const materialBindings = Object.fromEntries(
|
|
209
|
+
Object.entries({
|
|
210
|
+
...pbrMaterialProps,
|
|
211
|
+
...options.parsedPPBRMaterial.bindings
|
|
212
|
+
}).filter(
|
|
213
|
+
([name, value]) => materialFactory.ownsBinding(name) && isMaterialBindingResource(value)
|
|
214
|
+
)
|
|
215
|
+
) as Record<string, Binding | DynamicTexture>;
|
|
216
|
+
|
|
217
|
+
const material = materialFactory.createMaterial({
|
|
218
|
+
id: options.id,
|
|
219
|
+
bindings: materialBindings
|
|
220
|
+
});
|
|
221
|
+
material.setProps({pbrMaterial: pbrMaterialProps});
|
|
222
|
+
|
|
223
|
+
return material;
|
|
224
|
+
}
|
|
225
|
+
|
|
119
226
|
/** Creates a luma.gl Model from GLTF data*/
|
|
120
227
|
export function createGLTFModel(device: Device, options: CreateGLTFModelOptions): ModelNode {
|
|
121
228
|
const {id, geometry, parsedPPBRMaterial, vertexCount, modelOptions = {}} = options;
|
|
@@ -144,22 +251,81 @@ export function createGLTFModel(device: Device, options: CreateGLTFModelOptions)
|
|
|
144
251
|
geometry,
|
|
145
252
|
topology: geometry.topology,
|
|
146
253
|
vertexCount,
|
|
147
|
-
modules: [pbrMaterial
|
|
254
|
+
modules: [pbrMaterial, skin],
|
|
148
255
|
...modelOptions,
|
|
149
256
|
|
|
150
257
|
defines: {...parsedPPBRMaterial.defines, ...modelOptions.defines},
|
|
151
258
|
parameters: {...parameters, ...parsedPPBRMaterial.parameters, ...modelOptions.parameters}
|
|
152
259
|
};
|
|
153
260
|
|
|
261
|
+
const material =
|
|
262
|
+
options.material ||
|
|
263
|
+
createGLTFMaterial(device, {
|
|
264
|
+
id: id ? `${id}-material` : undefined,
|
|
265
|
+
parsedPPBRMaterial
|
|
266
|
+
});
|
|
267
|
+
modelProps.material = material;
|
|
268
|
+
|
|
154
269
|
const model = new Model(device, modelProps);
|
|
155
270
|
|
|
156
|
-
const
|
|
271
|
+
const sceneShaderInputValues = {
|
|
157
272
|
...parsedPPBRMaterial.uniforms,
|
|
158
273
|
...modelOptions.uniforms,
|
|
159
274
|
...parsedPPBRMaterial.bindings,
|
|
160
275
|
...modelOptions.bindings
|
|
161
276
|
};
|
|
162
|
-
|
|
163
|
-
|
|
277
|
+
const sceneShaderInputProps = getSceneShaderInputProps(
|
|
278
|
+
model.shaderInputs.getModules(),
|
|
279
|
+
material,
|
|
280
|
+
sceneShaderInputValues
|
|
281
|
+
);
|
|
282
|
+
model.shaderInputs.setProps(sceneShaderInputProps);
|
|
164
283
|
return new ModelNode({managedResources, model});
|
|
165
284
|
}
|
|
285
|
+
|
|
286
|
+
function isMaterialBindingResource(value: unknown): boolean {
|
|
287
|
+
return (
|
|
288
|
+
value instanceof Buffer ||
|
|
289
|
+
value instanceof DynamicTexture ||
|
|
290
|
+
value instanceof Sampler ||
|
|
291
|
+
value instanceof Texture ||
|
|
292
|
+
value instanceof TextureView
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function getSceneShaderInputProps(
|
|
297
|
+
modules: Array<{
|
|
298
|
+
name: string;
|
|
299
|
+
uniformTypes?: Readonly<Record<string, unknown>>;
|
|
300
|
+
bindingLayout?: ReadonlyArray<{name: string}>;
|
|
301
|
+
}>,
|
|
302
|
+
material: Material,
|
|
303
|
+
shaderInputValues: Record<string, unknown>
|
|
304
|
+
): Record<string, Record<string, unknown>> {
|
|
305
|
+
const propertyToModuleNameMap = new Map<string, string>();
|
|
306
|
+
for (const module of modules) {
|
|
307
|
+
for (const uniformName of Object.keys(module.uniformTypes || {})) {
|
|
308
|
+
propertyToModuleNameMap.set(uniformName, module.name);
|
|
309
|
+
}
|
|
310
|
+
for (const binding of module.bindingLayout || []) {
|
|
311
|
+
propertyToModuleNameMap.set(binding.name, module.name);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const sceneShaderInputProps: Record<string, Record<string, unknown>> = {};
|
|
316
|
+
for (const [propertyName, value] of Object.entries(shaderInputValues)) {
|
|
317
|
+
if (value === undefined) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const moduleName = propertyToModuleNameMap.get(propertyName);
|
|
322
|
+
if (!moduleName || material.ownsModule(moduleName)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
sceneShaderInputProps[moduleName] ||= {};
|
|
327
|
+
sceneShaderInputProps[moduleName][propertyName] = value;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return sceneShaderInputProps;
|
|
331
|
+
}
|
|
@@ -3,25 +3,148 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import {Device} from '@luma.gl/core';
|
|
6
|
-
import {GroupNode} from '@luma.gl/engine';
|
|
6
|
+
import {GroupNode, Material} from '@luma.gl/engine';
|
|
7
7
|
import {GLTFPostprocessed} from '@loaders.gl/gltf';
|
|
8
|
+
import {Light} from '@luma.gl/shadertools';
|
|
8
9
|
import {parseGLTF, type ParseGLTFOptions} from '../parsers/parse-gltf';
|
|
10
|
+
import {parseGLTFLights} from '../parsers/parse-gltf-lights';
|
|
9
11
|
import {GLTFAnimator} from './gltf-animator';
|
|
10
12
|
import {parseGLTFAnimations} from '../parsers/parse-gltf-animations';
|
|
11
|
-
import {
|
|
13
|
+
import {getGLTFExtensionSupport, type GLTFExtensionSupport} from './gltf-extension-support';
|
|
12
14
|
|
|
15
|
+
export type GLTFScenegraphBounds = {
|
|
16
|
+
/** World-space axis-aligned bounds for the scene or model. */
|
|
17
|
+
bounds: [[number, number, number], [number, number, number]] | null;
|
|
18
|
+
/** World-space center of the bounds. */
|
|
19
|
+
center: [number, number, number];
|
|
20
|
+
/** World-space bounds size on each axis. */
|
|
21
|
+
size: [number, number, number];
|
|
22
|
+
/** Half of the world-space bounds diagonal, clamped to a small practical minimum. */
|
|
23
|
+
radius: number;
|
|
24
|
+
/** Suggested orbit distance for a 60-degree field of view camera. */
|
|
25
|
+
recommendedOrbitDistance: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Scenegraph bundle returned from a parsed glTF asset. */
|
|
29
|
+
export type GLTFScenegraphs = {
|
|
30
|
+
/** Scene roots produced from the glTF scenes array. */
|
|
31
|
+
scenes: GroupNode[];
|
|
32
|
+
/** Materials aligned with the source glTF `materials` array. */
|
|
33
|
+
materials: Material[];
|
|
34
|
+
/** Animation controller for glTF animations. */
|
|
35
|
+
animator: GLTFAnimator;
|
|
36
|
+
/** Parsed punctual lights from the asset. */
|
|
37
|
+
lights: Light[];
|
|
38
|
+
/** Extensions reported by the asset and whether luma.gl supports them. */
|
|
39
|
+
extensionSupport: Map<string, GLTFExtensionSupport>;
|
|
40
|
+
/** Camera-friendly bounds for each scene in `scenes`, in matching order. */
|
|
41
|
+
sceneBounds: GLTFScenegraphBounds[];
|
|
42
|
+
/** Combined camera-friendly bounds for the entire loaded asset. */
|
|
43
|
+
modelBounds: GLTFScenegraphBounds;
|
|
44
|
+
|
|
45
|
+
/** Map from glTF mesh ids to generated mesh group nodes. */
|
|
46
|
+
gltfMeshIdToNodeMap: Map<string, GroupNode>;
|
|
47
|
+
/** Map from glTF node indices to generated scenegraph nodes. */
|
|
48
|
+
gltfNodeIndexToNodeMap: Map<number, GroupNode>;
|
|
49
|
+
/** Map from glTF node ids to generated scenegraph nodes. */
|
|
50
|
+
gltfNodeIdToNodeMap: Map<string, GroupNode>;
|
|
51
|
+
|
|
52
|
+
/** Original post-processed glTF document. */
|
|
53
|
+
gltf: GLTFPostprocessed;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Converts a post-processed glTF asset into luma.gl scenegraph nodes and animation helpers. */
|
|
13
57
|
export function createScenegraphsFromGLTF(
|
|
14
58
|
device: Device,
|
|
15
59
|
gltf: GLTFPostprocessed,
|
|
16
60
|
options?: ParseGLTFOptions
|
|
17
|
-
): {
|
|
18
|
-
scenes
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
gltf = deepCopy(gltf);
|
|
22
|
-
const scenes = parseGLTF(device, gltf, options);
|
|
23
|
-
// Note: There is a nasty dependency on injected nodes in the glTF
|
|
61
|
+
): GLTFScenegraphs {
|
|
62
|
+
const {scenes, materials, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap} =
|
|
63
|
+
parseGLTF(device, gltf, options);
|
|
64
|
+
|
|
24
65
|
const animations = parseGLTFAnimations(gltf);
|
|
25
|
-
const animator = new GLTFAnimator({animations});
|
|
26
|
-
|
|
66
|
+
const animator = new GLTFAnimator({animations, gltfNodeIdToNodeMap});
|
|
67
|
+
const lights = parseGLTFLights(gltf);
|
|
68
|
+
const extensionSupport = getGLTFExtensionSupport(gltf);
|
|
69
|
+
const sceneBounds = scenes.map(scene => getScenegraphBounds(scene.getBounds()));
|
|
70
|
+
const modelBounds = getCombinedScenegraphBounds(sceneBounds);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
scenes,
|
|
74
|
+
materials,
|
|
75
|
+
animator,
|
|
76
|
+
lights,
|
|
77
|
+
extensionSupport,
|
|
78
|
+
sceneBounds,
|
|
79
|
+
modelBounds,
|
|
80
|
+
gltfMeshIdToNodeMap,
|
|
81
|
+
gltfNodeIdToNodeMap,
|
|
82
|
+
gltfNodeIndexToNodeMap,
|
|
83
|
+
gltf
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getScenegraphBounds(bounds: [number[], number[]] | null): GLTFScenegraphBounds {
|
|
88
|
+
if (!bounds) {
|
|
89
|
+
return {
|
|
90
|
+
bounds: null,
|
|
91
|
+
center: [0, 0, 0],
|
|
92
|
+
size: [0, 0, 0],
|
|
93
|
+
radius: 0.5,
|
|
94
|
+
recommendedOrbitDistance: 1
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const normalizedBounds: [[number, number, number], [number, number, number]] = [
|
|
99
|
+
[bounds[0][0], bounds[0][1], bounds[0][2]],
|
|
100
|
+
[bounds[1][0], bounds[1][1], bounds[1][2]]
|
|
101
|
+
];
|
|
102
|
+
const size: [number, number, number] = [
|
|
103
|
+
normalizedBounds[1][0] - normalizedBounds[0][0],
|
|
104
|
+
normalizedBounds[1][1] - normalizedBounds[0][1],
|
|
105
|
+
normalizedBounds[1][2] - normalizedBounds[0][2]
|
|
106
|
+
];
|
|
107
|
+
const center: [number, number, number] = [
|
|
108
|
+
normalizedBounds[0][0] + size[0] * 0.5,
|
|
109
|
+
normalizedBounds[0][1] + size[1] * 0.5,
|
|
110
|
+
normalizedBounds[0][2] + size[2] * 0.5
|
|
111
|
+
];
|
|
112
|
+
const maxHalfExtent = Math.max(size[0], size[1], size[2]) * 0.5;
|
|
113
|
+
const radius = Math.max(0.5 * Math.hypot(size[0], size[1], size[2]), 0.001);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
bounds: normalizedBounds,
|
|
117
|
+
center,
|
|
118
|
+
size,
|
|
119
|
+
radius,
|
|
120
|
+
recommendedOrbitDistance: Math.max(
|
|
121
|
+
(Math.max(maxHalfExtent, 0.001) / Math.tan(Math.PI / 6)) * 1.15,
|
|
122
|
+
radius * 1.1
|
|
123
|
+
)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getCombinedScenegraphBounds(sceneBounds: GLTFScenegraphBounds[]): GLTFScenegraphBounds {
|
|
128
|
+
let combinedBounds: [[number, number, number], [number, number, number]] | null = null;
|
|
129
|
+
|
|
130
|
+
for (const sceneBoundInfo of sceneBounds) {
|
|
131
|
+
if (!sceneBoundInfo.bounds) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!combinedBounds) {
|
|
136
|
+
combinedBounds = [
|
|
137
|
+
[...sceneBoundInfo.bounds[0]] as [number, number, number],
|
|
138
|
+
[...sceneBoundInfo.bounds[1]] as [number, number, number]
|
|
139
|
+
];
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
144
|
+
combinedBounds[0][axis] = Math.min(combinedBounds[0][axis], sceneBoundInfo.bounds[0][axis]);
|
|
145
|
+
combinedBounds[1][axis] = Math.max(combinedBounds[1][axis], sceneBoundInfo.bounds[1][axis]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return getScenegraphBounds(combinedBounds);
|
|
27
150
|
}
|