three-stdlib 2.5.4 → 2.5.8
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/controls/ArcballControls.cjs.js +1 -1
- package/controls/ArcballControls.js +24 -23
- package/controls/TrackballControls.cjs.js +1 -1
- package/controls/TrackballControls.js +1 -1
- package/csm/Shader.cjs.js +1 -1
- package/csm/Shader.js +7 -7
- package/exporters/GLTFExporter.cjs.js +1 -1
- package/exporters/GLTFExporter.d.ts +257 -6
- package/exporters/GLTFExporter.js +1526 -1300
- package/index.cjs.js +1 -1
- package/package.json +1 -1
@@ -1,232 +1,296 @@
|
|
1
|
-
import
|
1
|
+
import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
|
2
|
+
import { PropertyBinding, InterpolateLinear, Vector3, RGBAFormat, RGBFormat, ShaderMaterial, MeshStandardMaterial, MeshBasicMaterial, MeshPhysicalMaterial, MeshMatcapMaterial, MeshNormalMaterial, MeshPhongMaterial, MeshToonMaterial, MeshLambertMaterial, DoubleSide, LineSegments, LineLoop, Line, Points, MeshDepthMaterial, InterleavedBufferAttribute, BufferAttribute, Object3D, Material, OrthographicCamera, PerspectiveCamera, MathUtils, InterpolateDiscrete, SkinnedMesh, Matrix4, Mesh, Camera, Scene, DirectionalLight, PointLight, SpotLight, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping } from 'three';
|
3
|
+
|
4
|
+
class GLTFExporter {
|
5
|
+
constructor() {
|
6
|
+
_defineProperty(this, "pluginCallbacks", void 0);
|
2
7
|
|
3
|
-
const GLTFExporter = (() => {
|
4
|
-
function GLTFExporter() {
|
5
8
|
this.pluginCallbacks = [];
|
6
|
-
this.register(
|
7
|
-
|
8
|
-
|
9
|
+
this.register(function (writer) {
|
10
|
+
return new GLTFLightExtension(writer);
|
11
|
+
});
|
12
|
+
this.register(function (writer) {
|
13
|
+
return new GLTFMaterialsUnlitExtension(writer);
|
14
|
+
});
|
15
|
+
this.register(function (writer) {
|
16
|
+
return new GLTFMaterialsPBRSpecularGlossiness(writer);
|
17
|
+
});
|
18
|
+
this.register(function (writer) {
|
19
|
+
return new GLTFMaterialsTransmissionExtension(writer);
|
20
|
+
});
|
21
|
+
this.register(function (writer) {
|
22
|
+
return new GLTFMaterialsVolumeExtension(writer);
|
23
|
+
});
|
9
24
|
}
|
10
25
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
this.pluginCallbacks.push(callback);
|
16
|
-
}
|
26
|
+
register(callback) {
|
27
|
+
if (this.pluginCallbacks.indexOf(callback) === -1) {
|
28
|
+
this.pluginCallbacks.push(callback);
|
29
|
+
}
|
17
30
|
|
18
|
-
|
19
|
-
|
20
|
-
unregister: function (callback) {
|
21
|
-
if (this.pluginCallbacks.indexOf(callback) !== -1) {
|
22
|
-
this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(callback), 1);
|
23
|
-
}
|
31
|
+
return this;
|
32
|
+
}
|
24
33
|
|
25
|
-
|
26
|
-
|
34
|
+
unregister(callback) {
|
35
|
+
if (this.pluginCallbacks.indexOf(callback) !== -1) {
|
36
|
+
this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(callback), 1);
|
37
|
+
}
|
27
38
|
|
28
|
-
|
29
|
-
|
30
|
-
* @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes
|
31
|
-
* @param {Function} onDone Callback on completed
|
32
|
-
* @param {Object} options options
|
33
|
-
*/
|
34
|
-
parse: function (input, onDone, options) {
|
35
|
-
const writer = new GLTFWriter();
|
36
|
-
const plugins = [];
|
39
|
+
return this;
|
40
|
+
}
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
parse(input, onDone, options) {
|
43
|
+
const writer = new GLTFWriter();
|
44
|
+
const plugins = [];
|
41
45
|
|
42
|
-
|
43
|
-
|
46
|
+
for (let i = 0, il = this.pluginCallbacks.length; i < il; i++) {
|
47
|
+
plugins.push(this.pluginCallbacks[i](writer));
|
44
48
|
}
|
45
|
-
}; //------------------------------------------------------------------------------
|
46
|
-
// Constants
|
47
|
-
//------------------------------------------------------------------------------
|
48
|
-
|
49
|
-
const WEBGL_CONSTANTS = {
|
50
|
-
POINTS: 0x0000,
|
51
|
-
LINES: 0x0001,
|
52
|
-
LINE_LOOP: 0x0002,
|
53
|
-
LINE_STRIP: 0x0003,
|
54
|
-
TRIANGLES: 0x0004,
|
55
|
-
TRIANGLE_STRIP: 0x0005,
|
56
|
-
TRIANGLE_FAN: 0x0006,
|
57
|
-
UNSIGNED_BYTE: 0x1401,
|
58
|
-
UNSIGNED_SHORT: 0x1403,
|
59
|
-
FLOAT: 0x1406,
|
60
|
-
UNSIGNED_INT: 0x1405,
|
61
|
-
ARRAY_BUFFER: 0x8892,
|
62
|
-
ELEMENT_ARRAY_BUFFER: 0x8893,
|
63
|
-
NEAREST: 0x2600,
|
64
|
-
LINEAR: 0x2601,
|
65
|
-
NEAREST_MIPMAP_NEAREST: 0x2700,
|
66
|
-
LINEAR_MIPMAP_NEAREST: 0x2701,
|
67
|
-
NEAREST_MIPMAP_LINEAR: 0x2702,
|
68
|
-
LINEAR_MIPMAP_LINEAR: 0x2703,
|
69
|
-
CLAMP_TO_EDGE: 33071,
|
70
|
-
MIRRORED_REPEAT: 33648,
|
71
|
-
REPEAT: 10497
|
72
|
-
};
|
73
|
-
const THREE_TO_WEBGL = {};
|
74
|
-
THREE_TO_WEBGL[NearestFilter] = WEBGL_CONSTANTS.NEAREST;
|
75
|
-
THREE_TO_WEBGL[NearestMipmapNearestFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
|
76
|
-
THREE_TO_WEBGL[NearestMipmapLinearFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
|
77
|
-
THREE_TO_WEBGL[LinearFilter] = WEBGL_CONSTANTS.LINEAR;
|
78
|
-
THREE_TO_WEBGL[LinearMipmapNearestFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
|
79
|
-
THREE_TO_WEBGL[LinearMipmapLinearFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
|
80
|
-
THREE_TO_WEBGL[ClampToEdgeWrapping] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
|
81
|
-
THREE_TO_WEBGL[RepeatWrapping] = WEBGL_CONSTANTS.REPEAT;
|
82
|
-
THREE_TO_WEBGL[MirroredRepeatWrapping] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
|
83
|
-
const PATH_PROPERTIES = {
|
84
|
-
scale: 'scale',
|
85
|
-
position: 'translation',
|
86
|
-
quaternion: 'rotation',
|
87
|
-
morphTargetInfluences: 'weights'
|
88
|
-
}; // GLB constants
|
89
|
-
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
|
90
|
-
|
91
|
-
const GLB_HEADER_BYTES = 12;
|
92
|
-
const GLB_HEADER_MAGIC = 0x46546c67;
|
93
|
-
const GLB_VERSION = 2;
|
94
|
-
const GLB_CHUNK_PREFIX_BYTES = 8;
|
95
|
-
const GLB_CHUNK_TYPE_JSON = 0x4e4f534a;
|
96
|
-
const GLB_CHUNK_TYPE_BIN = 0x004e4942; //------------------------------------------------------------------------------
|
97
|
-
// Utility functions
|
98
|
-
//------------------------------------------------------------------------------
|
99
|
-
|
100
|
-
/**
|
101
|
-
* Compare two arrays
|
102
|
-
* @param {Array} array1 Array 1 to compare
|
103
|
-
* @param {Array} array2 Array 2 to compare
|
104
|
-
* @return {Boolean} Returns true if both arrays are equal
|
105
|
-
*/
|
106
49
|
|
107
|
-
|
108
|
-
|
50
|
+
writer.setPlugins(plugins);
|
51
|
+
writer.write(input, onDone, options);
|
109
52
|
}
|
110
53
|
/**
|
111
|
-
*
|
112
|
-
* @param {string} text
|
113
|
-
* @return {ArrayBuffer}
|
54
|
+
* Static utility functions
|
114
55
|
*/
|
115
56
|
|
116
57
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
}
|
58
|
+
} //------------------------------------------------------------------------------
|
59
|
+
// Constants
|
60
|
+
//------------------------------------------------------------------------------
|
121
61
|
|
122
|
-
const array = new Uint8Array(new ArrayBuffer(text.length));
|
123
62
|
|
124
|
-
|
125
|
-
|
63
|
+
_defineProperty(GLTFExporter, "Utils", {
|
64
|
+
insertKeyframe: function (track, time) {
|
65
|
+
const tolerance = 0.001; // 1ms
|
126
66
|
|
127
|
-
|
128
|
-
}
|
67
|
+
const valueSize = track.getValueSize(); // @ts-expect-error
|
129
68
|
|
130
|
-
|
131
|
-
}
|
132
|
-
/**
|
133
|
-
* Is identity matrix
|
134
|
-
*
|
135
|
-
* @param {Matrix4} matrix
|
136
|
-
* @returns {Boolean} Returns true, if parameter is identity matrix
|
137
|
-
*/
|
69
|
+
const times = new track.TimeBufferType(track.times.length + 1); // @ts-expect-error
|
138
70
|
|
71
|
+
const values = new track.ValueBufferType(track.values.length + valueSize);
|
72
|
+
/**
|
73
|
+
* NOTE: createInterpolant does not exist in the type, but it does exist as a property of the class
|
74
|
+
* https://github.com/mrdoob/three.js/blob/77480d339d737b7505b335101ffd3cf29a30738d/src/animation/KeyframeTrack.js#L117
|
75
|
+
*/
|
76
|
+
// @ts-expect-error
|
139
77
|
|
140
|
-
|
141
|
-
|
142
|
-
}
|
143
|
-
/**
|
144
|
-
* Get the min and max vectors from the given attribute
|
145
|
-
* @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count
|
146
|
-
* @param {Integer} start
|
147
|
-
* @param {Integer} count
|
148
|
-
* @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
|
149
|
-
*/
|
78
|
+
const interpolant = track.createInterpolant(new track.ValueBufferType(valueSize));
|
79
|
+
let index;
|
150
80
|
|
81
|
+
if (track.times.length === 0) {
|
82
|
+
times[0] = time;
|
151
83
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
max: new Array(attribute.itemSize).fill(Number.NEGATIVE_INFINITY)
|
156
|
-
};
|
84
|
+
for (let i = 0; i < valueSize; i++) {
|
85
|
+
values[i] = 0;
|
86
|
+
}
|
157
87
|
|
158
|
-
|
159
|
-
|
160
|
-
|
88
|
+
index = 0;
|
89
|
+
} else if (time < track.times[0]) {
|
90
|
+
if (Math.abs(track.times[0] - time) < tolerance) return 0;
|
91
|
+
times[0] = time;
|
92
|
+
times.set(track.times, 1);
|
93
|
+
values.set(interpolant.evaluate(time), 0);
|
94
|
+
values.set(track.values, valueSize);
|
95
|
+
index = 0;
|
96
|
+
} else if (time > track.times[track.times.length - 1]) {
|
97
|
+
if (Math.abs(track.times[track.times.length - 1] - time) < tolerance) {
|
98
|
+
return track.times.length - 1;
|
99
|
+
}
|
161
100
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
101
|
+
times[times.length - 1] = time;
|
102
|
+
times.set(track.times, 0);
|
103
|
+
values.set(track.values, 0);
|
104
|
+
values.set(interpolant.evaluate(time), track.values.length);
|
105
|
+
index = times.length - 1;
|
106
|
+
} else {
|
107
|
+
for (let i = 0; i < track.times.length; i++) {
|
108
|
+
if (Math.abs(track.times[i] - time) < tolerance) return i;
|
109
|
+
|
110
|
+
if (track.times[i] < time && track.times[i + 1] > time) {
|
111
|
+
times.set(track.times.slice(0, i + 1), 0);
|
112
|
+
times[i + 1] = time;
|
113
|
+
times.set(track.times.slice(i + 1), i + 2);
|
114
|
+
values.set(track.values.slice(0, (i + 1) * valueSize), 0);
|
115
|
+
values.set(interpolant.evaluate(time), (i + 1) * valueSize);
|
116
|
+
values.set(track.values.slice((i + 1) * valueSize), (i + 2) * valueSize);
|
117
|
+
index = i + 1;
|
118
|
+
break;
|
167
119
|
}
|
168
|
-
|
169
|
-
output.min[a] = Math.min(output.min[a], value);
|
170
|
-
output.max[a] = Math.max(output.max[a], value);
|
171
120
|
}
|
172
121
|
}
|
173
122
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
123
|
+
track.times = times;
|
124
|
+
track.values = values;
|
125
|
+
return index;
|
126
|
+
},
|
127
|
+
mergeMorphTargetTracks: function (clip, root) {
|
128
|
+
const tracks = [];
|
129
|
+
const mergedTracks = {};
|
130
|
+
const sourceTracks = clip.tracks;
|
131
|
+
|
132
|
+
for (let i = 0; i < sourceTracks.length; ++i) {
|
133
|
+
let sourceTrack = sourceTracks[i];
|
134
|
+
const sourceTrackBinding = PropertyBinding.parseTrackName(sourceTrack.name);
|
135
|
+
const sourceTrackNode = PropertyBinding.findNode(root, sourceTrackBinding.nodeName);
|
136
|
+
|
137
|
+
if (sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined) {
|
138
|
+
// Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is.
|
139
|
+
tracks.push(sourceTrack);
|
140
|
+
continue;
|
141
|
+
}
|
184
142
|
|
143
|
+
if ( // @ts-expect-error
|
144
|
+
sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && // @ts-expect-error
|
145
|
+
sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear) {
|
146
|
+
// @ts-expect-error
|
147
|
+
if (sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) {
|
148
|
+
// This should never happen, because glTF morph target animations
|
149
|
+
// affect all targets already.
|
150
|
+
throw new Error('THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.');
|
151
|
+
}
|
185
152
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
* Returns a buffer aligned to 4-byte boundary.
|
191
|
-
*
|
192
|
-
* @param {ArrayBuffer} arrayBuffer Buffer to pad
|
193
|
-
* @param {Integer} paddingByte (Optional)
|
194
|
-
* @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
|
195
|
-
*/
|
153
|
+
console.warn('THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.');
|
154
|
+
sourceTrack = sourceTrack.clone();
|
155
|
+
sourceTrack.setInterpolation(InterpolateLinear);
|
156
|
+
}
|
196
157
|
|
158
|
+
const targetCount = sourceTrackNode.morphTargetInfluences.length;
|
159
|
+
const targetIndex = sourceTrackNode.morphTargetDictionary[sourceTrackBinding.propertyIndex];
|
197
160
|
|
198
|
-
|
199
|
-
|
200
|
-
|
161
|
+
if (targetIndex === undefined) {
|
162
|
+
throw new Error('THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex);
|
163
|
+
}
|
201
164
|
|
202
|
-
|
203
|
-
|
204
|
-
array.set(new Uint8Array(arrayBuffer));
|
165
|
+
let mergedTrack; // If this is the first time we've seen this object, create a new
|
166
|
+
// track to store merged keyframe data for each morph target.
|
205
167
|
|
206
|
-
if (
|
207
|
-
|
208
|
-
array[i] = paddingByte;
|
209
|
-
}
|
210
|
-
}
|
168
|
+
if (mergedTracks[sourceTrackNode.uuid] === undefined) {
|
169
|
+
mergedTrack = sourceTrack.clone(); // @ts-expect-error
|
211
170
|
|
212
|
-
|
171
|
+
const values = new mergedTrack.ValueBufferType(targetCount * mergedTrack.times.length);
|
172
|
+
|
173
|
+
for (let j = 0; j < mergedTrack.times.length; j++) {
|
174
|
+
values[j * targetCount + targetIndex] = mergedTrack.values[j];
|
175
|
+
} // We need to take into consideration the intended target node
|
176
|
+
// of our original un-merged morphTarget animation.
|
177
|
+
|
178
|
+
|
179
|
+
mergedTrack.name = (sourceTrackBinding.nodeName || '') + '.morphTargetInfluences';
|
180
|
+
mergedTrack.values = values;
|
181
|
+
mergedTracks[sourceTrackNode.uuid] = mergedTrack;
|
182
|
+
tracks.push(mergedTrack);
|
183
|
+
continue;
|
184
|
+
} // @ts-expect-error
|
185
|
+
|
186
|
+
|
187
|
+
const sourceInterpolant = sourceTrack.createInterpolant(new sourceTrack.ValueBufferType(1));
|
188
|
+
mergedTrack = mergedTracks[sourceTrackNode.uuid]; // For every existing keyframe of the merged track, write a (possibly
|
189
|
+
// interpolated) value from the source track.
|
190
|
+
|
191
|
+
for (let j = 0; j < mergedTrack.times.length; j++) {
|
192
|
+
mergedTrack.values[j * targetCount + targetIndex] = sourceInterpolant.evaluate(mergedTrack.times[j]);
|
193
|
+
} // For every existing keyframe of the source track, write a (possibly
|
194
|
+
// new) keyframe to the merged track. Values from the previous loop may
|
195
|
+
// be written again, but keyframes are de-duplicated.
|
196
|
+
|
197
|
+
|
198
|
+
for (let j = 0; j < sourceTrack.times.length; j++) {
|
199
|
+
const keyframeIndex = this.insertKeyframe(mergedTrack, sourceTrack.times[j]);
|
200
|
+
mergedTrack.values[keyframeIndex * targetCount + targetIndex] = sourceTrack.values[j];
|
201
|
+
}
|
213
202
|
}
|
214
203
|
|
215
|
-
|
204
|
+
clip.tracks = tracks;
|
205
|
+
return clip;
|
216
206
|
}
|
207
|
+
});
|
208
|
+
|
209
|
+
const WEBGL_CONSTANTS = {
|
210
|
+
POINTS: 0x0000,
|
211
|
+
LINES: 0x0001,
|
212
|
+
LINE_LOOP: 0x0002,
|
213
|
+
LINE_STRIP: 0x0003,
|
214
|
+
TRIANGLES: 0x0004,
|
215
|
+
TRIANGLE_STRIP: 0x0005,
|
216
|
+
TRIANGLE_FAN: 0x0006,
|
217
|
+
UNSIGNED_BYTE: 0x1401,
|
218
|
+
UNSIGNED_SHORT: 0x1403,
|
219
|
+
FLOAT: 0x1406,
|
220
|
+
UNSIGNED_INT: 0x1405,
|
221
|
+
ARRAY_BUFFER: 0x8892,
|
222
|
+
ELEMENT_ARRAY_BUFFER: 0x8893,
|
223
|
+
NEAREST: 0x2600,
|
224
|
+
LINEAR: 0x2601,
|
225
|
+
NEAREST_MIPMAP_NEAREST: 0x2700,
|
226
|
+
LINEAR_MIPMAP_NEAREST: 0x2701,
|
227
|
+
NEAREST_MIPMAP_LINEAR: 0x2702,
|
228
|
+
LINEAR_MIPMAP_LINEAR: 0x2703,
|
229
|
+
CLAMP_TO_EDGE: 33071,
|
230
|
+
MIRRORED_REPEAT: 33648,
|
231
|
+
REPEAT: 10497
|
232
|
+
};
|
233
|
+
const THREE_TO_WEBGL = {};
|
234
|
+
THREE_TO_WEBGL[NearestFilter] = WEBGL_CONSTANTS.NEAREST;
|
235
|
+
THREE_TO_WEBGL[NearestMipmapNearestFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
|
236
|
+
THREE_TO_WEBGL[NearestMipmapLinearFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
|
237
|
+
THREE_TO_WEBGL[LinearFilter] = WEBGL_CONSTANTS.LINEAR;
|
238
|
+
THREE_TO_WEBGL[LinearMipmapNearestFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
|
239
|
+
THREE_TO_WEBGL[LinearMipmapLinearFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
|
240
|
+
THREE_TO_WEBGL[ClampToEdgeWrapping] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
|
241
|
+
THREE_TO_WEBGL[RepeatWrapping] = WEBGL_CONSTANTS.REPEAT;
|
242
|
+
THREE_TO_WEBGL[MirroredRepeatWrapping] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
|
243
|
+
const PATH_PROPERTIES = {
|
244
|
+
scale: 'scale',
|
245
|
+
position: 'translation',
|
246
|
+
quaternion: 'rotation',
|
247
|
+
morphTargetInfluences: 'weights'
|
248
|
+
}; // GLB constants
|
249
|
+
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
|
250
|
+
|
251
|
+
const GLB_HEADER_BYTES = 12;
|
252
|
+
const GLB_HEADER_MAGIC = 0x46546c67;
|
253
|
+
const GLB_VERSION = 2;
|
254
|
+
const GLB_CHUNK_PREFIX_BYTES = 8;
|
255
|
+
const GLB_CHUNK_TYPE_JSON = 0x4e4f534a;
|
256
|
+
const GLB_CHUNK_TYPE_BIN = 0x004e4942;
|
257
|
+
/**
|
258
|
+
* Writer
|
259
|
+
*/
|
260
|
+
|
261
|
+
class GLTFWriter {
|
262
|
+
constructor() {
|
263
|
+
_defineProperty(this, "plugins", void 0);
|
264
|
+
|
265
|
+
_defineProperty(this, "options", void 0);
|
266
|
+
|
267
|
+
_defineProperty(this, "pending", void 0);
|
268
|
+
|
269
|
+
_defineProperty(this, "buffers", void 0);
|
270
|
+
|
271
|
+
_defineProperty(this, "byteOffset", void 0);
|
272
|
+
|
273
|
+
_defineProperty(this, "nodeMap", void 0);
|
274
|
+
|
275
|
+
_defineProperty(this, "skins", void 0);
|
276
|
+
|
277
|
+
_defineProperty(this, "extensionsUsed", void 0);
|
278
|
+
|
279
|
+
_defineProperty(this, "uids", void 0);
|
280
|
+
|
281
|
+
_defineProperty(this, "uid", void 0);
|
282
|
+
|
283
|
+
_defineProperty(this, "json", void 0);
|
284
|
+
|
285
|
+
_defineProperty(this, "cache", void 0);
|
286
|
+
|
287
|
+
_defineProperty(this, "cachedCanvas", void 0);
|
217
288
|
|
218
|
-
let cachedCanvas = null;
|
219
|
-
/**
|
220
|
-
* Writer
|
221
|
-
*/
|
222
|
-
|
223
|
-
function GLTFWriter() {
|
224
289
|
this.plugins = [];
|
225
290
|
this.options = {};
|
226
291
|
this.pending = [];
|
227
292
|
this.buffers = [];
|
228
293
|
this.byteOffset = 0;
|
229
|
-
this.buffers = [];
|
230
294
|
this.nodeMap = new Map();
|
231
295
|
this.skins = [];
|
232
296
|
this.extensionsUsed = {};
|
@@ -246,68 +310,69 @@ const GLTFExporter = (() => {
|
|
246
310
|
textures: new Map(),
|
247
311
|
images: new Map()
|
248
312
|
};
|
313
|
+
this.cachedCanvas = null;
|
314
|
+
}
|
315
|
+
|
316
|
+
setPlugins(plugins) {
|
317
|
+
this.plugins = plugins;
|
249
318
|
}
|
319
|
+
/**
|
320
|
+
* Parse scenes and generate GLTF output
|
321
|
+
* @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes
|
322
|
+
* @param {Function} onDone Callback on completed
|
323
|
+
* @param {Object} options options
|
324
|
+
*/
|
250
325
|
|
251
|
-
GLTFWriter.prototype = {
|
252
|
-
constructor: GLTFWriter,
|
253
|
-
setPlugins: function (plugins) {
|
254
|
-
this.plugins = plugins;
|
255
|
-
},
|
256
326
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
}, options);
|
275
|
-
|
276
|
-
if (this.options.animations.length > 0) {
|
277
|
-
// Only TRS properties, and not matrices, may be targeted by animation.
|
278
|
-
this.options.trs = true;
|
279
|
-
}
|
327
|
+
write(input, onDone, options) {
|
328
|
+
this.options = Object.assign({}, {
|
329
|
+
// default options
|
330
|
+
binary: false,
|
331
|
+
trs: false,
|
332
|
+
onlyVisible: true,
|
333
|
+
truncateDrawRange: true,
|
334
|
+
embedImages: true,
|
335
|
+
maxTextureSize: Infinity,
|
336
|
+
animations: [],
|
337
|
+
includeCustomExtensions: false
|
338
|
+
}, options);
|
339
|
+
|
340
|
+
if (this.options.animations !== undefined && this.options.animations.length > 0) {
|
341
|
+
// Only TRS properties, and not matrices, may be targeted by animation.
|
342
|
+
this.options.trs = true;
|
343
|
+
}
|
280
344
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
345
|
+
this.processInput(input);
|
346
|
+
const writer = this;
|
347
|
+
Promise.all(this.pending).then(() => {
|
348
|
+
const buffers = writer.buffers;
|
349
|
+
const json = writer.json;
|
350
|
+
const options = writer.options;
|
351
|
+
const extensionsUsed = writer.extensionsUsed; // Merge buffers.
|
288
352
|
|
289
|
-
|
290
|
-
|
291
|
-
|
353
|
+
const blob = new Blob(buffers, {
|
354
|
+
type: 'application/octet-stream'
|
355
|
+
}); // Declare extensions.
|
292
356
|
|
293
|
-
|
294
|
-
|
357
|
+
const extensionsUsedList = Object.keys(extensionsUsed);
|
358
|
+
if (extensionsUsedList.length > 0) json.extensionsUsed = extensionsUsedList; // Update bytelength of the single buffer.
|
295
359
|
|
296
|
-
|
360
|
+
if (json.buffers && json.buffers.length > 0) json.buffers[0].byteLength = blob.size;
|
297
361
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
362
|
+
if (options.binary) {
|
363
|
+
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
|
364
|
+
const reader = new window.FileReader();
|
365
|
+
reader.readAsArrayBuffer(blob);
|
302
366
|
|
303
|
-
|
367
|
+
reader.onloadend = () => {
|
368
|
+
if (reader.result !== null && typeof reader.result !== 'string') {
|
304
369
|
// Binary chunk.
|
305
|
-
const binaryChunk = getPaddedArrayBuffer(reader.result);
|
370
|
+
const binaryChunk = this.getPaddedArrayBuffer(reader.result);
|
306
371
|
const binaryChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES));
|
307
372
|
binaryChunkPrefix.setUint32(0, binaryChunk.byteLength, true);
|
308
373
|
binaryChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_BIN, true); // JSON chunk.
|
309
374
|
|
310
|
-
const jsonChunk = getPaddedArrayBuffer(stringToArrayBuffer(JSON.stringify(json)), 0x20);
|
375
|
+
const jsonChunk = this.getPaddedArrayBuffer(this.stringToArrayBuffer(JSON.stringify(json)), 0x20);
|
311
376
|
const jsonChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES));
|
312
377
|
jsonChunkPrefix.setUint32(0, jsonChunk.byteLength, true);
|
313
378
|
jsonChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_JSON, true); // GLB header.
|
@@ -324,205 +389,221 @@ const GLTFExporter = (() => {
|
|
324
389
|
const glbReader = new window.FileReader();
|
325
390
|
glbReader.readAsArrayBuffer(glbBlob);
|
326
391
|
|
327
|
-
glbReader.onloadend = ()
|
328
|
-
|
392
|
+
glbReader.onloadend = function () {
|
393
|
+
if (glbReader.result !== null && typeof glbReader.result !== 'string') {
|
394
|
+
onDone(glbReader.result);
|
395
|
+
}
|
329
396
|
};
|
330
|
-
}
|
331
|
-
}
|
332
|
-
|
333
|
-
|
334
|
-
|
397
|
+
}
|
398
|
+
};
|
399
|
+
} else {
|
400
|
+
if (json.buffers && json.buffers.length > 0) {
|
401
|
+
const reader = new window.FileReader();
|
402
|
+
reader.readAsDataURL(blob);
|
335
403
|
|
336
|
-
|
337
|
-
|
404
|
+
reader.onloadend = function () {
|
405
|
+
const base64data = reader.result;
|
406
|
+
|
407
|
+
if (json.buffers !== undefined && base64data !== null) {
|
338
408
|
json.buffers[0].uri = base64data;
|
339
409
|
onDone(json);
|
340
|
-
}
|
341
|
-
}
|
342
|
-
|
343
|
-
|
410
|
+
}
|
411
|
+
};
|
412
|
+
} else {
|
413
|
+
onDone(json);
|
344
414
|
}
|
345
|
-
}
|
346
|
-
}
|
415
|
+
}
|
416
|
+
});
|
417
|
+
}
|
418
|
+
/**
|
419
|
+
* Serializes a userData.
|
420
|
+
*
|
421
|
+
* @param {THREE.Object3D|THREE.Material} object
|
422
|
+
* @param {Object} objectDef
|
423
|
+
*/
|
347
424
|
|
348
|
-
/**
|
349
|
-
* Serializes a userData.
|
350
|
-
*
|
351
|
-
* @param {THREE.Object3D|THREE.Material} object
|
352
|
-
* @param {Object} objectDef
|
353
|
-
*/
|
354
|
-
serializeUserData: function (object, objectDef) {
|
355
|
-
if (Object.keys(object.userData).length === 0) return;
|
356
|
-
const options = this.options;
|
357
|
-
const extensionsUsed = this.extensionsUsed;
|
358
425
|
|
359
|
-
|
360
|
-
|
426
|
+
serializeUserData(object, objectDef) {
|
427
|
+
if (Object.keys(object.userData).length === 0) return;
|
428
|
+
const options = this.options;
|
429
|
+
const extensionsUsed = this.extensionsUsed;
|
361
430
|
|
362
|
-
|
363
|
-
|
431
|
+
try {
|
432
|
+
const json = JSON.parse(JSON.stringify(object.userData));
|
364
433
|
|
365
|
-
|
366
|
-
|
367
|
-
extensionsUsed[extensionName] = true;
|
368
|
-
}
|
434
|
+
if (options.includeCustomExtensions && json.gltfExtensions) {
|
435
|
+
if (objectDef.extensions === undefined) objectDef.extensions = {};
|
369
436
|
|
370
|
-
|
437
|
+
for (const extensionName in json.gltfExtensions) {
|
438
|
+
objectDef.extensions[extensionName] = json.gltfExtensions[extensionName];
|
439
|
+
extensionsUsed[extensionName] = true;
|
371
440
|
}
|
372
441
|
|
373
|
-
|
374
|
-
} catch (error) {
|
375
|
-
console.warn(`THREE.GLTFExporter: userData of '${object.name}' won't be serialized because of JSON.stringify error - ${error.message}`);
|
442
|
+
delete json.gltfExtensions;
|
376
443
|
}
|
377
|
-
},
|
378
|
-
|
379
|
-
/**
|
380
|
-
* Assign and return a temporal unique id for an object
|
381
|
-
* especially which doesn't have .uuid
|
382
|
-
* @param {Object} object
|
383
|
-
* @return {Integer}
|
384
|
-
*/
|
385
|
-
getUID: function (object) {
|
386
|
-
if (!this.uids.has(object)) this.uids.set(object, this.uid++);
|
387
|
-
return this.uids.get(object);
|
388
|
-
},
|
389
444
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
* @returns {Boolean}
|
395
|
-
*/
|
396
|
-
isNormalizedNormalAttribute: function (normal) {
|
397
|
-
const cache = this.cache;
|
398
|
-
if (cache.attributesNormalized.has(normal)) return false;
|
399
|
-
const v = new Vector3();
|
400
|
-
|
401
|
-
for (let i = 0, il = normal.count; i < il; i++) {
|
402
|
-
// 0.0005 is from glTF-validator
|
403
|
-
if (Math.abs(v.fromBufferAttribute(normal, i).length() - 1.0) > 0.0005) return false;
|
445
|
+
if (Object.keys(json).length > 0) objectDef.extras = json;
|
446
|
+
} catch (error) {
|
447
|
+
if (error instanceof Error) {
|
448
|
+
console.warn("THREE.GLTFExporter: userData of '" + object.name + "' " + "won't be serialized because of JSON.stringify error - " + error.message);
|
404
449
|
}
|
450
|
+
}
|
451
|
+
}
|
452
|
+
/**
|
453
|
+
* Assign and return a temporal unique id for an object
|
454
|
+
* especially which doesn't have .uuid
|
455
|
+
* @param {Object} object
|
456
|
+
* @return {Integer}
|
457
|
+
*/
|
405
458
|
|
406
|
-
return true;
|
407
|
-
},
|
408
459
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
const attribute = normal.clone();
|
420
|
-
const v = new Vector3();
|
421
|
-
|
422
|
-
for (let i = 0, il = attribute.count; i < il; i++) {
|
423
|
-
v.fromBufferAttribute(attribute, i);
|
424
|
-
|
425
|
-
if (v.x === 0 && v.y === 0 && v.z === 0) {
|
426
|
-
// if values can't be normalized set (1, 0, 0)
|
427
|
-
v.setX(1.0);
|
428
|
-
} else {
|
429
|
-
v.normalize();
|
430
|
-
}
|
460
|
+
getUID(object) {
|
461
|
+
if (!this.uids.has(object)) this.uids.set(object, this.uid++);
|
462
|
+
return this.uids.get(object);
|
463
|
+
}
|
464
|
+
/**
|
465
|
+
* Checks if normal attribute values are normalized.
|
466
|
+
*
|
467
|
+
* @param {BufferAttribute} normal
|
468
|
+
* @returns {Boolean}
|
469
|
+
*/
|
431
470
|
|
432
|
-
attribute.setXYZ(i, v.x, v.y, v.z);
|
433
|
-
}
|
434
471
|
|
435
|
-
|
436
|
-
|
437
|
-
|
472
|
+
isNormalizedNormalAttribute(normal) {
|
473
|
+
const cache = this.cache;
|
474
|
+
if (cache.attributesNormalized.has(normal)) return false;
|
475
|
+
const v = new Vector3();
|
438
476
|
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
* @param {Object} mapDef
|
444
|
-
* @param {THREE.Texture} texture
|
445
|
-
*/
|
446
|
-
applyTextureTransform: function (mapDef, texture) {
|
447
|
-
let didTransform = false;
|
448
|
-
const transformDef = {};
|
477
|
+
for (let i = 0, il = normal.count; i < il; i++) {
|
478
|
+
// 0.0005 is from glTF-validator
|
479
|
+
if (Math.abs(v.fromBufferAttribute(normal, i).length() - 1.0) > 0.0005) return false;
|
480
|
+
}
|
449
481
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
482
|
+
return true;
|
483
|
+
}
|
484
|
+
/**
|
485
|
+
* Creates normalized normal buffer attribute.
|
486
|
+
*
|
487
|
+
* @param {BufferAttribute} normal
|
488
|
+
* @returns {BufferAttribute}
|
489
|
+
*
|
490
|
+
*/
|
454
491
|
|
455
|
-
if (texture.rotation !== 0) {
|
456
|
-
transformDef.rotation = texture.rotation;
|
457
|
-
didTransform = true;
|
458
|
-
}
|
459
492
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
493
|
+
createNormalizedNormalAttribute(normal) {
|
494
|
+
const cache = this.cache;
|
495
|
+
if (cache.attributesNormalized.has(normal)) return cache.attributesNormalized.get(normal);
|
496
|
+
const attribute = normal.clone();
|
497
|
+
const v = new Vector3();
|
498
|
+
|
499
|
+
for (let i = 0, il = attribute.count; i < il; i++) {
|
500
|
+
v.fromBufferAttribute(attribute, i);
|
464
501
|
|
465
|
-
if (
|
466
|
-
|
467
|
-
|
468
|
-
|
502
|
+
if (v.x === 0 && v.y === 0 && v.z === 0) {
|
503
|
+
// if values can't be normalized set (1, 0, 0)
|
504
|
+
v.setX(1.0);
|
505
|
+
} else {
|
506
|
+
v.normalize();
|
469
507
|
}
|
470
|
-
},
|
471
508
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
509
|
+
attribute.setXYZ(i, v.x, v.y, v.z);
|
510
|
+
}
|
511
|
+
|
512
|
+
cache.attributesNormalized.set(normal, attribute);
|
513
|
+
return attribute;
|
514
|
+
}
|
515
|
+
/**
|
516
|
+
* Applies a texture transform, if present, to the map definition. Requires
|
517
|
+
* the KHR_texture_transform extension.
|
518
|
+
*
|
519
|
+
* @param {Object} mapDef
|
520
|
+
* @param {THREE.Texture} texture
|
521
|
+
*/
|
483
522
|
|
484
|
-
buffers.push(buffer);
|
485
|
-
return 0;
|
486
|
-
},
|
487
523
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
*/
|
497
|
-
processBufferView: function (attribute, componentType, start, count, target) {
|
498
|
-
const json = this.json;
|
499
|
-
if (!json.bufferViews) json.bufferViews = []; // Create a new dataview and dump the attribute's array into it
|
524
|
+
applyTextureTransform(mapDef, texture) {
|
525
|
+
let didTransform = false;
|
526
|
+
const transformDef = {};
|
527
|
+
|
528
|
+
if (texture.offset.x !== 0 || texture.offset.y !== 0) {
|
529
|
+
transformDef.offset = texture.offset.toArray();
|
530
|
+
didTransform = true;
|
531
|
+
}
|
500
532
|
|
501
|
-
|
533
|
+
if (texture.rotation !== 0) {
|
534
|
+
transformDef.rotation = texture.rotation;
|
535
|
+
didTransform = true;
|
536
|
+
}
|
502
537
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
}
|
538
|
+
if (texture.repeat.x !== 1 || texture.repeat.y !== 1) {
|
539
|
+
transformDef.scale = texture.repeat.toArray();
|
540
|
+
didTransform = true;
|
541
|
+
}
|
542
|
+
|
543
|
+
if (didTransform) {
|
544
|
+
mapDef.extensions = mapDef.extensions || {};
|
545
|
+
mapDef.extensions['KHR_texture_transform'] = transformDef;
|
546
|
+
this.extensionsUsed['KHR_texture_transform'] = true;
|
547
|
+
}
|
548
|
+
}
|
549
|
+
/**
|
550
|
+
* Process a buffer to append to the default one.
|
551
|
+
* @param {ArrayBuffer} buffer
|
552
|
+
* @return {Integer}
|
553
|
+
*/
|
510
554
|
|
511
|
-
const byteLength = getPaddedBufferSize(count * attribute.itemSize * componentSize);
|
512
|
-
const dataView = new DataView(new ArrayBuffer(byteLength));
|
513
|
-
let offset = 0;
|
514
555
|
|
515
|
-
|
516
|
-
|
517
|
-
|
556
|
+
processBuffer(buffer) {
|
557
|
+
const json = this.json;
|
558
|
+
const buffers = this.buffers;
|
559
|
+
if (!json.buffers) json.buffers = [{
|
560
|
+
byteLength: 0
|
561
|
+
}]; // All buffers are merged before export.
|
518
562
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
563
|
+
buffers.push(buffer);
|
564
|
+
return 0;
|
565
|
+
}
|
566
|
+
/**
|
567
|
+
* Process and generate a BufferView
|
568
|
+
* @param {BufferAttribute} attribute
|
569
|
+
* @param {number} componentType
|
570
|
+
* @param {number} start
|
571
|
+
* @param {number} count
|
572
|
+
* @param {number} target (Optional) Target usage of the BufferView
|
573
|
+
* @return {Object}
|
574
|
+
*/
|
575
|
+
|
576
|
+
|
577
|
+
processBufferView(attribute, componentType, start, count, target) {
|
578
|
+
const json = this.json;
|
579
|
+
if (!json.bufferViews) json.bufferViews = []; // Create a new dataview and dump the attribute's array into it
|
580
|
+
|
581
|
+
let componentSize;
|
582
|
+
|
583
|
+
if (componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE) {
|
584
|
+
componentSize = 1;
|
585
|
+
} else if (componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT) {
|
586
|
+
componentSize = 2;
|
587
|
+
} else {
|
588
|
+
componentSize = 4;
|
589
|
+
}
|
590
|
+
|
591
|
+
const byteLength = this.getPaddedBufferSize(count * attribute.itemSize * componentSize);
|
592
|
+
const dataView = new DataView(new ArrayBuffer(byteLength));
|
593
|
+
let offset = 0;
|
594
|
+
|
595
|
+
for (let i = start; i < start + count; i++) {
|
596
|
+
for (let a = 0; a < attribute.itemSize; a++) {
|
597
|
+
let value;
|
598
|
+
|
599
|
+
if (attribute.itemSize > 4) {
|
600
|
+
// no support for interleaved data for itemSize > 4
|
601
|
+
value = attribute.array[i * attribute.itemSize + a];
|
602
|
+
} else {
|
603
|
+
if (a === 0) value = attribute.getX(i);else if (a === 1) value = attribute.getY(i);else if (a === 2) value = attribute.getZ(i);else if (a === 3) value = attribute.getW(i);
|
604
|
+
}
|
525
605
|
|
606
|
+
if (value !== undefined) {
|
526
607
|
if (componentType === WEBGL_CONSTANTS.FLOAT) {
|
527
608
|
dataView.setFloat32(offset, value, true);
|
528
609
|
} else if (componentType === WEBGL_CONSTANTS.UNSIGNED_INT) {
|
@@ -532,48 +613,51 @@ const GLTFExporter = (() => {
|
|
532
613
|
} else if (componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE) {
|
533
614
|
dataView.setUint8(offset, value);
|
534
615
|
}
|
535
|
-
|
536
|
-
offset += componentSize;
|
537
616
|
}
|
617
|
+
|
618
|
+
offset += componentSize;
|
538
619
|
}
|
620
|
+
}
|
539
621
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
622
|
+
const bufferViewDef = {
|
623
|
+
buffer: this.processBuffer(dataView.buffer),
|
624
|
+
byteOffset: this.byteOffset,
|
625
|
+
byteLength: byteLength
|
626
|
+
};
|
627
|
+
if (target !== undefined) bufferViewDef.target = target;
|
546
628
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
629
|
+
if (target === WEBGL_CONSTANTS.ARRAY_BUFFER) {
|
630
|
+
// Only define byteStride for vertex attributes.
|
631
|
+
bufferViewDef.byteStride = attribute.itemSize * componentSize;
|
632
|
+
}
|
551
633
|
|
552
|
-
|
553
|
-
|
634
|
+
this.byteOffset += byteLength;
|
635
|
+
json.bufferViews.push(bufferViewDef); // @TODO Merge bufferViews where possible.
|
554
636
|
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
637
|
+
const output = {
|
638
|
+
id: json.bufferViews.length - 1,
|
639
|
+
byteLength: 0
|
640
|
+
};
|
641
|
+
return output;
|
642
|
+
}
|
643
|
+
/**
|
644
|
+
* Process and generate a BufferView from an image Blob.
|
645
|
+
* @param {Blob} blob
|
646
|
+
* @return {Promise<Integer>}
|
647
|
+
*/
|
561
648
|
|
562
|
-
/**
|
563
|
-
* Process and generate a BufferView from an image Blob.
|
564
|
-
* @param {Blob} blob
|
565
|
-
* @return {Promise<Integer>}
|
566
|
-
*/
|
567
|
-
processBufferViewImage: function (blob) {
|
568
|
-
const writer = this;
|
569
|
-
const json = writer.json;
|
570
|
-
if (!json.bufferViews) json.bufferViews = [];
|
571
|
-
return new Promise(resolve => {
|
572
|
-
const reader = new window.FileReader();
|
573
|
-
reader.readAsArrayBuffer(blob);
|
574
649
|
|
575
|
-
|
576
|
-
|
650
|
+
processBufferViewImage(blob) {
|
651
|
+
const writer = this;
|
652
|
+
const json = writer.json;
|
653
|
+
if (!json.bufferViews) json.bufferViews = [];
|
654
|
+
return new Promise(resolve => {
|
655
|
+
const reader = new window.FileReader();
|
656
|
+
reader.readAsArrayBuffer(blob);
|
657
|
+
|
658
|
+
reader.onloadend = () => {
|
659
|
+
if (reader.result !== null && typeof reader.result !== 'string' && json.bufferViews !== undefined) {
|
660
|
+
const buffer = this.getPaddedArrayBuffer(reader.result);
|
577
661
|
const bufferViewDef = {
|
578
662
|
buffer: writer.processBuffer(buffer),
|
579
663
|
byteOffset: writer.byteOffset,
|
@@ -581,128 +665,139 @@ const GLTFExporter = (() => {
|
|
581
665
|
};
|
582
666
|
writer.byteOffset += buffer.byteLength;
|
583
667
|
resolve(json.bufferViews.push(bufferViewDef) - 1);
|
584
|
-
}
|
585
|
-
});
|
586
|
-
},
|
587
|
-
|
588
|
-
/**
|
589
|
-
* Process attribute to generate an accessor
|
590
|
-
* @param {BufferAttribute} attribute Attribute to process
|
591
|
-
* @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
|
592
|
-
* @param {Integer} start (Optional)
|
593
|
-
* @param {Integer} count (Optional)
|
594
|
-
* @return {Integer|null} Index of the processed accessor on the "accessors" array
|
595
|
-
*/
|
596
|
-
processAccessor: function (attribute, geometry, start, count) {
|
597
|
-
const options = this.options;
|
598
|
-
const json = this.json;
|
599
|
-
const types = {
|
600
|
-
1: 'SCALAR',
|
601
|
-
2: 'VEC2',
|
602
|
-
3: 'VEC3',
|
603
|
-
4: 'VEC4',
|
604
|
-
16: 'MAT4'
|
668
|
+
}
|
605
669
|
};
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
} else {
|
617
|
-
throw new Error('THREE.GLTFExporter: Unsupported bufferAttribute component type.');
|
618
|
-
}
|
670
|
+
});
|
671
|
+
}
|
672
|
+
/**
|
673
|
+
* Process attribute to generate an accessor
|
674
|
+
* @param {BufferAttribute} attribute Attribute to process
|
675
|
+
* @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
|
676
|
+
* @param {Integer} start (Optional)
|
677
|
+
* @param {Integer} count (Optional)
|
678
|
+
* @return {Integer|null} Index of the processed accessor on the "accessors" array
|
679
|
+
*/
|
619
680
|
|
620
|
-
if (start === undefined) start = 0;
|
621
|
-
if (count === undefined) count = attribute.count; // @TODO Indexed buffer geometry with drawRange not supported yet
|
622
681
|
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
682
|
+
processAccessor(attribute, geometry, start, count) {
|
683
|
+
const options = this.options;
|
684
|
+
const json = this.json;
|
685
|
+
const types = {
|
686
|
+
1: 'SCALAR',
|
687
|
+
2: 'VEC2',
|
688
|
+
3: 'VEC3',
|
689
|
+
4: 'VEC4',
|
690
|
+
16: 'MAT4'
|
691
|
+
};
|
692
|
+
let componentType; // Detect the component type of the attribute array (float, uint or ushort)
|
693
|
+
|
694
|
+
if (attribute.array.constructor === Float32Array) {
|
695
|
+
componentType = WEBGL_CONSTANTS.FLOAT;
|
696
|
+
} else if (attribute.array.constructor === Uint32Array) {
|
697
|
+
componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
|
698
|
+
} else if (attribute.array.constructor === Uint16Array) {
|
699
|
+
componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
|
700
|
+
} else if (attribute.array.constructor === Uint8Array) {
|
701
|
+
componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;
|
702
|
+
} else {
|
703
|
+
throw new Error('THREE.GLTFExporter: Unsupported bufferAttribute component type.');
|
704
|
+
}
|
630
705
|
|
706
|
+
if (start === undefined) start = 0;
|
707
|
+
if (count === undefined) count = attribute.count; // @TODO Indexed buffer geometry with drawRange not supported yet
|
631
708
|
|
632
|
-
|
633
|
-
const
|
634
|
-
|
635
|
-
|
709
|
+
if (options.truncateDrawRange && geometry !== undefined && geometry.index === null) {
|
710
|
+
const end = start + count;
|
711
|
+
const end2 = geometry.drawRange.count === Infinity ? attribute.count : geometry.drawRange.start + geometry.drawRange.count;
|
712
|
+
start = Math.max(start, geometry.drawRange.start);
|
713
|
+
count = Math.min(end, end2) - start;
|
714
|
+
if (count < 0) count = 0;
|
715
|
+
} // Skip creating an accessor if the attribute doesn't have data to export
|
636
716
|
|
637
|
-
if (geometry !== undefined) {
|
638
|
-
bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
|
639
|
-
}
|
640
717
|
|
718
|
+
if (count === 0) return null;
|
719
|
+
const minMax = this.getMinMax(attribute, start, count);
|
720
|
+
let bufferViewTarget; // If geometry isn't provided, don't infer the target usage of the bufferView. For
|
721
|
+
// animation samplers, target must not be set.
|
722
|
+
|
723
|
+
if (geometry !== undefined) {
|
724
|
+
bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
|
725
|
+
}
|
726
|
+
|
727
|
+
if (bufferViewTarget !== undefined) {
|
641
728
|
const bufferView = this.processBufferView(attribute, componentType, start, count, bufferViewTarget);
|
642
729
|
const accessorDef = {
|
643
730
|
bufferView: bufferView.id,
|
644
731
|
byteOffset: bufferView.byteOffset,
|
645
|
-
componentType,
|
646
|
-
count,
|
732
|
+
componentType: componentType,
|
733
|
+
count: count,
|
647
734
|
max: minMax.max,
|
648
735
|
min: minMax.min,
|
649
736
|
type: types[attribute.itemSize]
|
650
737
|
};
|
651
|
-
if (attribute.normalized
|
738
|
+
if (attribute.normalized) accessorDef.normalized = true;
|
652
739
|
if (!json.accessors) json.accessors = [];
|
653
740
|
return json.accessors.push(accessorDef) - 1;
|
654
|
-
}
|
741
|
+
}
|
742
|
+
}
|
743
|
+
/**
|
744
|
+
* Process image
|
745
|
+
* @param {Image} image to process
|
746
|
+
* @param {Integer} format of the image (e.g. RGBFormat, RGBAFormat etc)
|
747
|
+
* @param {Boolean} flipY before writing out the image
|
748
|
+
* @return {Integer} Index of the processed texture in the "images" array
|
749
|
+
*/
|
655
750
|
|
656
|
-
/**
|
657
|
-
* Process image
|
658
|
-
* @param {Image} image to process
|
659
|
-
* @param {Integer} format of the image (e.g. RGBFormat, RGBAFormat etc)
|
660
|
-
* @param {Boolean} flipY before writing out the image
|
661
|
-
* @return {Integer} Index of the processed texture in the "images" array
|
662
|
-
*/
|
663
|
-
processImage: function (image, format, flipY) {
|
664
|
-
const writer = this;
|
665
|
-
const cache = writer.cache;
|
666
|
-
const json = writer.json;
|
667
|
-
const options = writer.options;
|
668
|
-
const pending = writer.pending;
|
669
|
-
if (!cache.images.has(image)) cache.images.set(image, {});
|
670
|
-
const cachedImages = cache.images.get(image);
|
671
|
-
const mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg';
|
672
|
-
const key = `${mimeType}:flipY/${flipY.toString()}`;
|
673
|
-
if (cachedImages[key] !== undefined) return cachedImages[key];
|
674
|
-
if (!json.images) json.images = [];
|
675
|
-
const imageDef = {
|
676
|
-
mimeType
|
677
|
-
};
|
678
751
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
752
|
+
processImage(image, format, flipY) {
|
753
|
+
const writer = this;
|
754
|
+
const cache = writer.cache;
|
755
|
+
const json = writer.json;
|
756
|
+
const options = writer.options;
|
757
|
+
const pending = writer.pending;
|
758
|
+
if (!cache.images.has(image)) cache.images.set(image, {});
|
759
|
+
const cachedImages = cache.images.get(image);
|
760
|
+
const mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg';
|
761
|
+
const key = mimeType + ':flipY/' + flipY.toString();
|
762
|
+
if (cachedImages !== undefined && cachedImages[key] !== undefined) return cachedImages[key];
|
763
|
+
if (!json.images) json.images = [];
|
764
|
+
const imageDef = {
|
765
|
+
mimeType: mimeType
|
766
|
+
};
|
684
767
|
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
768
|
+
if (options.embedImages && options.maxTextureSize !== undefined) {
|
769
|
+
const canvas = this.cachedCanvas = this.cachedCanvas || document.createElement('canvas');
|
770
|
+
canvas.width = Math.min(image.width, options.maxTextureSize);
|
771
|
+
canvas.height = Math.min(image.height, options.maxTextureSize);
|
772
|
+
const ctx = canvas.getContext('2d');
|
689
773
|
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
console.error('GLTFExporter: Only RGB and RGBA formats are supported.');
|
695
|
-
}
|
774
|
+
if (flipY) {
|
775
|
+
ctx === null || ctx === void 0 ? void 0 : ctx.translate(0, canvas.height);
|
776
|
+
ctx === null || ctx === void 0 ? void 0 : ctx.scale(1, -1);
|
777
|
+
}
|
696
778
|
|
697
|
-
|
698
|
-
|
699
|
-
|
779
|
+
if (typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas || typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap) {
|
780
|
+
ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
781
|
+
} else {
|
782
|
+
if (format !== RGBAFormat && format !== RGBFormat) {
|
783
|
+
console.error('GLTFExporter: Only RGB and RGBA formats are supported.');
|
784
|
+
}
|
700
785
|
|
701
|
-
|
786
|
+
if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) {
|
787
|
+
console.warn('GLTFExporter: Image size is bigger than maxTextureSize', image);
|
788
|
+
}
|
702
789
|
|
703
|
-
|
704
|
-
data = new Uint8ClampedArray(image.height * image.width * 4);
|
790
|
+
const data = new Uint8ClampedArray(image.height * image.width * 4);
|
705
791
|
|
792
|
+
if (image instanceof ImageData) {
|
793
|
+
if (format === RGBAFormat) {
|
794
|
+
for (let i = 0; i < data.length; i += 4) {
|
795
|
+
data[i + 0] = image.data[i + 0];
|
796
|
+
data[i + 1] = image.data[i + 1];
|
797
|
+
data[i + 2] = image.data[i + 2];
|
798
|
+
data[i + 3] = image.data[i + 3];
|
799
|
+
}
|
800
|
+
} else {
|
706
801
|
for (let i = 0, j = 0; i < data.length; i += 4, j += 3) {
|
707
802
|
data[i + 0] = image.data[j + 0];
|
708
803
|
data[i + 1] = image.data[j + 1];
|
@@ -710,550 +805,589 @@ const GLTFExporter = (() => {
|
|
710
805
|
data[i + 3] = 255;
|
711
806
|
}
|
712
807
|
}
|
713
|
-
|
714
|
-
ctx.putImageData(new ImageData(data, image.width, image.height), 0, 0);
|
715
808
|
}
|
716
809
|
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
810
|
+
ctx === null || ctx === void 0 ? void 0 : ctx.putImageData(new ImageData(data, image.width, image.height), 0, 0);
|
811
|
+
}
|
812
|
+
|
813
|
+
if (options.binary) {
|
814
|
+
pending.push(new Promise(function (resolve) {
|
815
|
+
canvas.toBlob(function (blob) {
|
816
|
+
if (blob !== null) {
|
817
|
+
writer.processBufferViewImage(blob).then(function (bufferViewIndex) {
|
818
|
+
imageDef.bufferView = bufferViewIndex; // @ts-expect-error
|
819
|
+
|
722
820
|
resolve();
|
723
821
|
});
|
724
|
-
}
|
725
|
-
})
|
726
|
-
}
|
727
|
-
imageDef.uri = canvas.toDataURL(mimeType);
|
728
|
-
}
|
822
|
+
}
|
823
|
+
}, mimeType);
|
824
|
+
}));
|
729
825
|
} else {
|
730
|
-
imageDef.uri =
|
826
|
+
imageDef.uri = canvas.toDataURL(mimeType);
|
731
827
|
}
|
828
|
+
} else if (image instanceof Image) {
|
829
|
+
imageDef.uri = image.src;
|
830
|
+
}
|
831
|
+
|
832
|
+
const index = json.images.push(imageDef) - 1;
|
833
|
+
if (cachedImages !== undefined) cachedImages[key] = index;
|
834
|
+
return index;
|
835
|
+
}
|
836
|
+
/**
|
837
|
+
* Process sampler
|
838
|
+
* @param {Texture} map Texture to process
|
839
|
+
* @return {Integer} Index of the processed texture in the "samplers" array
|
840
|
+
*/
|
732
841
|
|
733
|
-
const index = json.images.push(imageDef) - 1;
|
734
|
-
cachedImages[key] = index;
|
735
|
-
return index;
|
736
|
-
},
|
737
842
|
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
843
|
+
processSampler(map) {
|
844
|
+
const json = this.json;
|
845
|
+
if (!json.samplers) json.samplers = [];
|
846
|
+
const samplerDef = {
|
847
|
+
magFilter: THREE_TO_WEBGL[map.magFilter],
|
848
|
+
minFilter: THREE_TO_WEBGL[map.minFilter],
|
849
|
+
wrapS: THREE_TO_WEBGL[map.wrapS],
|
850
|
+
wrapT: THREE_TO_WEBGL[map.wrapT]
|
851
|
+
};
|
852
|
+
return json.samplers.push(samplerDef) - 1;
|
853
|
+
}
|
854
|
+
/**
|
855
|
+
* Process texture
|
856
|
+
* @param {Texture} map Map to process
|
857
|
+
* @return {Integer} Index of the processed texture in the "textures" array
|
858
|
+
*/
|
754
859
|
|
755
|
-
/**
|
756
|
-
* Process texture
|
757
|
-
* @param {Texture} map Map to process
|
758
|
-
* @return {Integer} Index of the processed texture in the "textures" array
|
759
|
-
*/
|
760
|
-
processTexture: function (map) {
|
761
|
-
const cache = this.cache;
|
762
|
-
const json = this.json;
|
763
|
-
if (cache.textures.has(map)) return cache.textures.get(map);
|
764
|
-
if (!json.textures) json.textures = [];
|
765
|
-
const textureDef = {
|
766
|
-
sampler: this.processSampler(map),
|
767
|
-
source: this.processImage(map.image, map.format, map.flipY)
|
768
|
-
};
|
769
|
-
if (map.name) textureDef.name = map.name;
|
770
860
|
|
771
|
-
|
772
|
-
|
773
|
-
|
861
|
+
processTexture(map) {
|
862
|
+
const cache = this.cache;
|
863
|
+
const json = this.json;
|
864
|
+
if (cache.textures.has(map)) return cache.textures.get(map);
|
865
|
+
if (!json.textures) json.textures = [];
|
866
|
+
const textureDef = {
|
867
|
+
sampler: this.processSampler(map),
|
868
|
+
source: this.processImage(map.image, map.format, map.flipY)
|
869
|
+
};
|
870
|
+
if (map.name) textureDef.name = map.name;
|
774
871
|
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
},
|
872
|
+
this._invokeAll(function (ext) {
|
873
|
+
ext.writeTexture && ext.writeTexture(map, textureDef);
|
874
|
+
});
|
779
875
|
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
876
|
+
const index = json.textures.push(textureDef) - 1;
|
877
|
+
cache.textures.set(map, index);
|
878
|
+
return index;
|
879
|
+
}
|
880
|
+
/**
|
881
|
+
* Process material
|
882
|
+
* @param {THREE.Material} material Material to process
|
883
|
+
* @return {Integer|null} Index of the processed material in the "materials" array
|
884
|
+
*/
|
789
885
|
|
790
|
-
if (material.isShaderMaterial) {
|
791
|
-
console.warn('GLTFExporter: THREE.ShaderMaterial not supported.');
|
792
|
-
return null;
|
793
|
-
}
|
794
886
|
|
795
|
-
|
887
|
+
processMaterial(material) {
|
888
|
+
const cache = this.cache;
|
889
|
+
const json = this.json;
|
890
|
+
if (cache.materials.has(material)) return cache.materials.get(material);
|
796
891
|
|
797
|
-
|
798
|
-
|
799
|
-
|
892
|
+
if (material instanceof ShaderMaterial && material.isShaderMaterial) {
|
893
|
+
console.warn('GLTFExporter: THREE.ShaderMaterial not supported.');
|
894
|
+
return null;
|
895
|
+
}
|
800
896
|
|
801
|
-
|
802
|
-
console.warn('GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.');
|
803
|
-
} // pbrMetallicRoughness.baseColorFactor
|
897
|
+
if (!json.materials) json.materials = []; // @QUESTION Should we avoid including any attribute that has the default value?
|
804
898
|
|
899
|
+
const materialDef = {
|
900
|
+
pbrMetallicRoughness: {}
|
901
|
+
};
|
902
|
+
|
903
|
+
if (!(material instanceof MeshStandardMaterial && material.isMeshStandardMaterial && material instanceof MeshBasicMaterial && // @ts-expect-error
|
904
|
+
material.isMeshBasicMaterial)) {
|
905
|
+
console.warn('GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.');
|
906
|
+
}
|
805
907
|
|
908
|
+
if (material instanceof MeshStandardMaterial || material instanceof MeshPhysicalMaterial) {
|
909
|
+
// pbrMetallicRoughness.baseColorFactor
|
806
910
|
const color = material.color.toArray().concat([material.opacity]);
|
807
911
|
|
808
|
-
if (!equalArray(color, [1, 1, 1, 1])) {
|
912
|
+
if (!this.equalArray(color, [1, 1, 1, 1])) {
|
809
913
|
materialDef.pbrMetallicRoughness.baseColorFactor = color;
|
810
914
|
}
|
915
|
+
}
|
916
|
+
|
917
|
+
if (material instanceof MeshStandardMaterial && material.isMeshStandardMaterial) {
|
918
|
+
materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
|
919
|
+
materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness;
|
920
|
+
} else {
|
921
|
+
materialDef.pbrMetallicRoughness.metallicFactor = 0.5;
|
922
|
+
materialDef.pbrMetallicRoughness.roughnessFactor = 0.5;
|
923
|
+
} // pbrMetallicRoughness.metallicRoughnessTexture
|
811
924
|
|
812
|
-
|
813
|
-
|
814
|
-
|
925
|
+
|
926
|
+
if (material instanceof MeshStandardMaterial && material.metalnessMap || material instanceof MeshStandardMaterial && material.roughnessMap) {
|
927
|
+
if (material.metalnessMap === material.roughnessMap && material.metalnessMap !== null) {
|
928
|
+
const metalRoughMapDef = {
|
929
|
+
index: this.processTexture(material.metalnessMap)
|
930
|
+
};
|
931
|
+
this.applyTextureTransform(metalRoughMapDef, material.metalnessMap);
|
932
|
+
materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
|
815
933
|
} else {
|
816
|
-
|
817
|
-
|
818
|
-
|
934
|
+
console.warn('THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.');
|
935
|
+
}
|
936
|
+
} // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture
|
819
937
|
|
820
938
|
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
} else {
|
829
|
-
console.warn('THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.');
|
830
|
-
}
|
831
|
-
} // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture
|
939
|
+
if ((material instanceof MeshStandardMaterial || material instanceof MeshPhysicalMaterial) && material.map) {
|
940
|
+
const baseColorMapDef = {
|
941
|
+
index: this.processTexture(material.map)
|
942
|
+
};
|
943
|
+
this.applyTextureTransform(baseColorMapDef, material.map);
|
944
|
+
materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
|
945
|
+
}
|
832
946
|
|
947
|
+
if ((material instanceof MeshStandardMaterial || material instanceof MeshPhysicalMaterial) && material.emissive) {
|
948
|
+
// note: emissive components are limited to stay within the 0 - 1 range to accommodate glTF spec. see #21849 and #22000.
|
949
|
+
const emissive = material.emissive.clone().multiplyScalar(material.emissiveIntensity);
|
950
|
+
const maxEmissiveComponent = Math.max(emissive.r, emissive.g, emissive.b);
|
833
951
|
|
834
|
-
if (
|
835
|
-
|
836
|
-
|
837
|
-
};
|
838
|
-
this.applyTextureTransform(baseColorMapDef, material.map);
|
839
|
-
materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
|
952
|
+
if (maxEmissiveComponent > 1) {
|
953
|
+
emissive.multiplyScalar(1 / maxEmissiveComponent);
|
954
|
+
console.warn('THREE.GLTFExporter: Some emissive components exceed 1; emissive has been limited');
|
840
955
|
}
|
841
956
|
|
842
|
-
if (
|
843
|
-
|
844
|
-
|
957
|
+
if (maxEmissiveComponent > 0) {
|
958
|
+
materialDef.emissiveFactor = emissive.toArray();
|
959
|
+
} // emissiveTexture
|
845
960
|
|
846
|
-
if (!equalArray(emissive, [0, 0, 0])) {
|
847
|
-
materialDef.emissiveFactor = emissive;
|
848
|
-
} // emissiveTexture
|
849
961
|
|
962
|
+
if (material.emissiveMap) {
|
963
|
+
const emissiveMapDef = {
|
964
|
+
index: this.processTexture(material.emissiveMap)
|
965
|
+
};
|
966
|
+
this.applyTextureTransform(emissiveMapDef, material.emissiveMap);
|
967
|
+
materialDef.emissiveTexture = emissiveMapDef;
|
968
|
+
}
|
969
|
+
} // normalTexture
|
850
970
|
|
851
|
-
if (material.emissiveMap) {
|
852
|
-
const emissiveMapDef = {
|
853
|
-
index: this.processTexture(material.emissiveMap)
|
854
|
-
};
|
855
|
-
this.applyTextureTransform(emissiveMapDef, material.emissiveMap);
|
856
|
-
materialDef.emissiveTexture = emissiveMapDef;
|
857
|
-
}
|
858
|
-
} // normalTexture
|
859
971
|
|
972
|
+
if ((material instanceof MeshMatcapMaterial || material instanceof MeshNormalMaterial || material instanceof MeshPhongMaterial || material instanceof MeshStandardMaterial || material instanceof MeshToonMaterial) && material.normalMap) {
|
973
|
+
const normalMapDef = {
|
974
|
+
index: this.processTexture(material.normalMap)
|
975
|
+
};
|
860
976
|
|
861
|
-
if (material.
|
862
|
-
|
863
|
-
|
864
|
-
|
977
|
+
if (material.normalScale && material.normalScale.x !== 1) {
|
978
|
+
// glTF normal scale is univariate. Ignore `y`, which may be flipped.
|
979
|
+
// Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
|
980
|
+
normalMapDef.scale = material.normalScale.x;
|
981
|
+
}
|
865
982
|
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
}
|
983
|
+
this.applyTextureTransform(normalMapDef, material.normalMap);
|
984
|
+
materialDef.normalTexture = normalMapDef;
|
985
|
+
} // occlusionTexture
|
870
986
|
|
871
|
-
normalMapDef.scale = material.normalScale.x;
|
872
|
-
}
|
873
987
|
|
874
|
-
|
875
|
-
|
876
|
-
|
988
|
+
if ((material instanceof MeshBasicMaterial || material instanceof MeshLambertMaterial || material instanceof MeshPhongMaterial || material instanceof MeshStandardMaterial || material instanceof MeshToonMaterial) && material.aoMap) {
|
989
|
+
const occlusionMapDef = {
|
990
|
+
index: this.processTexture(material.aoMap),
|
991
|
+
texCoord: 1
|
992
|
+
};
|
877
993
|
|
994
|
+
if (material.aoMapIntensity !== 1.0) {
|
995
|
+
occlusionMapDef.strength = material.aoMapIntensity;
|
996
|
+
}
|
878
997
|
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
texCoord: 1
|
883
|
-
};
|
998
|
+
this.applyTextureTransform(occlusionMapDef, material.aoMap);
|
999
|
+
materialDef.occlusionTexture = occlusionMapDef;
|
1000
|
+
} // alphaMode
|
884
1001
|
|
885
|
-
if (material.aoMapIntensity !== 1.0) {
|
886
|
-
occlusionMapDef.strength = material.aoMapIntensity;
|
887
|
-
}
|
888
1002
|
|
889
|
-
|
890
|
-
|
891
|
-
|
1003
|
+
if (material.transparent) {
|
1004
|
+
materialDef.alphaMode = 'BLEND';
|
1005
|
+
} else {
|
1006
|
+
if (material.alphaTest > 0.0) {
|
1007
|
+
materialDef.alphaMode = 'MASK';
|
1008
|
+
materialDef.alphaCutoff = material.alphaTest;
|
1009
|
+
}
|
1010
|
+
} // doubleSided
|
892
1011
|
|
893
1012
|
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
if (material.alphaTest > 0.0) {
|
898
|
-
materialDef.alphaMode = 'MASK';
|
899
|
-
materialDef.alphaCutoff = material.alphaTest;
|
900
|
-
}
|
901
|
-
} // doubleSided
|
1013
|
+
if (material.side === DoubleSide) materialDef.doubleSided = true;
|
1014
|
+
if (material.name !== '') materialDef.name = material.name;
|
1015
|
+
this.serializeUserData(material, materialDef);
|
902
1016
|
|
1017
|
+
this._invokeAll(function (ext) {
|
1018
|
+
ext.writeMaterial && ext.writeMaterial(material, materialDef);
|
1019
|
+
});
|
903
1020
|
|
904
|
-
|
905
|
-
|
906
|
-
|
1021
|
+
const index = json.materials.push(materialDef) - 1;
|
1022
|
+
cache.materials.set(material, index);
|
1023
|
+
return index;
|
1024
|
+
}
|
1025
|
+
/**
|
1026
|
+
* Process mesh
|
1027
|
+
* @param {THREE.Mesh} mesh Mesh to process
|
1028
|
+
* @return {Integer|null} Index of the processed mesh in the "meshes" array
|
1029
|
+
*/
|
907
1030
|
|
908
|
-
this._invokeAll(ext => {
|
909
|
-
ext.writeMaterial && ext.writeMaterial(material, materialDef);
|
910
|
-
});
|
911
1031
|
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
1032
|
+
processMesh(mesh) {
|
1033
|
+
const cache = this.cache;
|
1034
|
+
const json = this.json;
|
1035
|
+
const meshCacheKeyParts = [mesh.geometry.uuid];
|
916
1036
|
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
* @return {Integer|null} Index of the processed mesh in the "meshes" array
|
921
|
-
*/
|
922
|
-
processMesh: function (mesh) {
|
923
|
-
const cache = this.cache;
|
924
|
-
const json = this.json;
|
925
|
-
const meshCacheKeyParts = [mesh.geometry.uuid];
|
926
|
-
|
927
|
-
if (Array.isArray(mesh.material)) {
|
928
|
-
for (let i = 0, l = mesh.material.length; i < l; i++) {
|
929
|
-
meshCacheKeyParts.push(mesh.material[i].uuid);
|
930
|
-
}
|
931
|
-
} else {
|
932
|
-
meshCacheKeyParts.push(mesh.material.uuid);
|
1037
|
+
if (Array.isArray(mesh.material)) {
|
1038
|
+
for (let i = 0, l = mesh.material.length; i < l; i++) {
|
1039
|
+
meshCacheKeyParts.push(mesh.material[i].uuid);
|
933
1040
|
}
|
1041
|
+
} else {
|
1042
|
+
meshCacheKeyParts.push(mesh.material.uuid);
|
1043
|
+
}
|
934
1044
|
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
1045
|
+
const meshCacheKey = meshCacheKeyParts.join(':');
|
1046
|
+
if (cache.meshes.has(meshCacheKey)) return cache.meshes.get(meshCacheKey);
|
1047
|
+
const geometry = mesh.geometry;
|
1048
|
+
let mode; // Use the correct mode
|
1049
|
+
|
1050
|
+
if (mesh instanceof LineSegments && mesh.isLineSegments) {
|
1051
|
+
mode = WEBGL_CONSTANTS.LINES;
|
1052
|
+
} else if (mesh instanceof LineLoop && mesh.isLineLoop) {
|
1053
|
+
mode = WEBGL_CONSTANTS.LINE_LOOP;
|
1054
|
+
} else if (mesh instanceof Line && mesh.isLine) {
|
1055
|
+
mode = WEBGL_CONSTANTS.LINE_STRIP;
|
1056
|
+
} else if (mesh instanceof Points && mesh.isPoints) {
|
1057
|
+
mode = WEBGL_CONSTANTS.POINTS;
|
1058
|
+
} else {
|
1059
|
+
mode = (mesh.material instanceof MeshBasicMaterial || mesh.material instanceof MeshDepthMaterial || mesh.material instanceof MeshLambertMaterial || mesh.material instanceof MeshNormalMaterial || mesh.material instanceof MeshPhongMaterial || mesh.material instanceof MeshStandardMaterial || mesh.material instanceof MeshToonMaterial || mesh.material instanceof ShaderMaterial) && mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
|
1060
|
+
}
|
951
1061
|
|
952
|
-
|
953
|
-
|
954
|
-
|
1062
|
+
if (!geometry.isBufferGeometry) {
|
1063
|
+
throw new Error('THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.');
|
1064
|
+
}
|
955
1065
|
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
1066
|
+
const meshDef = {};
|
1067
|
+
const attributes = {};
|
1068
|
+
const primitives = [];
|
1069
|
+
const targets = []; // Conversion between attributes names in threejs and gltf spec
|
1070
|
+
|
1071
|
+
const nameConversion = {
|
1072
|
+
uv: 'TEXCOORD_0',
|
1073
|
+
uv2: 'TEXCOORD_1',
|
1074
|
+
color: 'COLOR_0',
|
1075
|
+
skinWeight: 'WEIGHTS_0',
|
1076
|
+
skinIndex: 'JOINTS_0'
|
1077
|
+
};
|
1078
|
+
const originalNormal = geometry.getAttribute('normal');
|
969
1079
|
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
1080
|
+
if (originalNormal !== undefined && !(originalNormal instanceof InterleavedBufferAttribute) && !this.isNormalizedNormalAttribute(originalNormal)) {
|
1081
|
+
console.warn('THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.');
|
1082
|
+
geometry.setAttribute('normal', this.createNormalizedNormalAttribute(originalNormal));
|
1083
|
+
} // @QUESTION Detect if .vertexColors = true?
|
1084
|
+
// For every attribute create an accessor
|
975
1085
|
|
976
1086
|
|
977
|
-
|
1087
|
+
let modifiedAttribute = null;
|
978
1088
|
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
1089
|
+
for (let attributeName in geometry.attributes) {
|
1090
|
+
// Ignore morph target attributes, which are exported later.
|
1091
|
+
if (attributeName.substr(0, 5) === 'morph') continue;
|
1092
|
+
const attribute = geometry.attributes[attributeName];
|
1093
|
+
attributeName = nameConversion[attributeName] || attributeName.toUpperCase(); // Prefix all geometry attributes except the ones specifically
|
1094
|
+
// listed in the spec; non-spec attributes are considered custom.
|
985
1095
|
|
986
|
-
|
987
|
-
|
1096
|
+
const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
|
1097
|
+
if (!validVertexAttributes.test(attributeName)) attributeName = '_' + attributeName;
|
988
1098
|
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
1099
|
+
if (cache.attributes.has(this.getUID(attribute))) {
|
1100
|
+
attributes[attributeName] = cache.attributes.get(this.getUID(attribute));
|
1101
|
+
continue;
|
1102
|
+
} // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT.
|
993
1103
|
|
994
1104
|
|
995
|
-
|
996
|
-
|
1105
|
+
modifiedAttribute = null;
|
1106
|
+
const array = attribute.array;
|
997
1107
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1108
|
+
if (attributeName === 'JOINTS_0' && !(array instanceof Uint16Array) && !(array instanceof Uint8Array)) {
|
1109
|
+
console.warn('GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.');
|
1110
|
+
modifiedAttribute = new BufferAttribute(new Uint16Array(array), attribute.itemSize, attribute.normalized);
|
1111
|
+
}
|
1002
1112
|
|
1003
|
-
|
1113
|
+
const accessor = modifiedAttribute !== null && this.processAccessor(modifiedAttribute || attribute, geometry);
|
1004
1114
|
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
}
|
1115
|
+
if (accessor) {
|
1116
|
+
attributes[attributeName] = accessor;
|
1117
|
+
cache.attributes.set(this.getUID(attribute), accessor);
|
1009
1118
|
}
|
1119
|
+
}
|
1010
1120
|
|
1011
|
-
|
1121
|
+
if (originalNormal !== undefined) geometry.setAttribute('normal', originalNormal); // Skip if no exportable attributes found
|
1012
1122
|
|
1013
|
-
|
1123
|
+
if (Object.keys(attributes).length === 0) return null; // Morph targets
|
1014
1124
|
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1125
|
+
if (mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0) {
|
1126
|
+
const weights = [];
|
1127
|
+
const targetNames = [];
|
1128
|
+
const reverseDictionary = {};
|
1019
1129
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
}
|
1130
|
+
if (mesh.morphTargetDictionary !== undefined) {
|
1131
|
+
for (const key in mesh.morphTargetDictionary) {
|
1132
|
+
reverseDictionary[mesh.morphTargetDictionary[key]] = key;
|
1024
1133
|
}
|
1134
|
+
}
|
1025
1135
|
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
}
|
1038
|
-
|
1039
|
-
continue;
|
1136
|
+
for (let i = 0; i < mesh.morphTargetInfluences.length; ++i) {
|
1137
|
+
const target = {};
|
1138
|
+
let warned = false;
|
1139
|
+
|
1140
|
+
for (const attributeName in geometry.morphAttributes) {
|
1141
|
+
// glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
|
1142
|
+
// Three.js doesn't support TANGENT yet.
|
1143
|
+
if (attributeName !== 'position' && attributeName !== 'normal') {
|
1144
|
+
if (!warned) {
|
1145
|
+
console.warn('GLTFExporter: Only POSITION and NORMAL morph are supported.');
|
1146
|
+
warned = true;
|
1040
1147
|
}
|
1041
1148
|
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1149
|
+
continue;
|
1150
|
+
}
|
1151
|
+
|
1152
|
+
const attribute = geometry.morphAttributes[attributeName][i];
|
1153
|
+
const gltfAttributeName = attributeName.toUpperCase(); // Three.js morph attribute has absolute values while the one of glTF has relative values.
|
1154
|
+
//
|
1155
|
+
// glTF 2.0 Specification:
|
1156
|
+
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets
|
1047
1157
|
|
1048
|
-
|
1158
|
+
const baseAttribute = geometry.attributes[attributeName];
|
1049
1159
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1160
|
+
if (cache.attributes.has(this.getUID(attribute))) {
|
1161
|
+
target[gltfAttributeName] = cache.attributes.get(this.getUID(attribute));
|
1162
|
+
continue;
|
1163
|
+
} // Clones attribute not to override
|
1054
1164
|
|
1055
1165
|
|
1056
|
-
|
1166
|
+
const relativeAttribute = attribute.clone();
|
1057
1167
|
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
}
|
1168
|
+
if (!geometry.morphTargetsRelative) {
|
1169
|
+
for (let j = 0, jl = attribute.count; j < jl; j++) {
|
1170
|
+
relativeAttribute.setXYZ(j, attribute.getX(j) - baseAttribute.getX(j), attribute.getY(j) - baseAttribute.getY(j), attribute.getZ(j) - baseAttribute.getZ(j));
|
1062
1171
|
}
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
const accessor = this.processAccessor(relativeAttribute, geometry);
|
1063
1175
|
|
1064
|
-
|
1065
|
-
|
1176
|
+
if (accessor != undefined) {
|
1177
|
+
target[gltfAttributeName] = accessor;
|
1066
1178
|
}
|
1067
1179
|
|
1068
|
-
|
1069
|
-
weights.push(mesh.morphTargetInfluences[i]);
|
1070
|
-
if (mesh.morphTargetDictionary !== undefined) targetNames.push(reverseDictionary[i]);
|
1180
|
+
cache.attributes.set(this.getUID(baseAttribute), target[gltfAttributeName]);
|
1071
1181
|
}
|
1072
1182
|
|
1073
|
-
|
1183
|
+
targets.push(target);
|
1184
|
+
weights.push(mesh.morphTargetInfluences[i]);
|
1185
|
+
if (mesh.morphTargetDictionary !== undefined) targetNames.push(reverseDictionary[i]);
|
1186
|
+
}
|
1074
1187
|
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
}
|
1188
|
+
meshDef.weights = weights;
|
1189
|
+
|
1190
|
+
if (targetNames.length > 0) {
|
1191
|
+
meshDef.extras = {};
|
1192
|
+
meshDef.extras.targetNames = targetNames;
|
1079
1193
|
}
|
1194
|
+
}
|
1080
1195
|
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1196
|
+
const isMultiMaterial = Array.isArray(mesh.material);
|
1197
|
+
if (isMultiMaterial && geometry.groups.length === 0) return null;
|
1198
|
+
const materials = isMultiMaterial ? mesh.material : [mesh.material];
|
1199
|
+
const groups = isMultiMaterial ? geometry.groups : [{
|
1200
|
+
materialIndex: 0,
|
1201
|
+
start: undefined,
|
1202
|
+
count: undefined
|
1203
|
+
}];
|
1204
|
+
|
1205
|
+
for (let i = 0, il = groups.length; i < il; i++) {
|
1206
|
+
const primitive = {
|
1207
|
+
mode: mode,
|
1208
|
+
attributes: attributes
|
1209
|
+
};
|
1210
|
+
|
1211
|
+
if (geometry instanceof Object3D || geometry instanceof Material) {
|
1095
1212
|
this.serializeUserData(geometry, primitive);
|
1096
|
-
|
1213
|
+
}
|
1097
1214
|
|
1098
|
-
|
1099
|
-
let cacheKey = this.getUID(geometry.index);
|
1215
|
+
if (targets.length > 0) primitive.targets = targets;
|
1100
1216
|
|
1101
|
-
|
1102
|
-
|
1103
|
-
}
|
1217
|
+
if (geometry.index !== null) {
|
1218
|
+
let cacheKey = this.getUID(geometry.index);
|
1104
1219
|
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
cache.attributes.set(cacheKey, primitive.indices);
|
1110
|
-
}
|
1220
|
+
if (groups[i].start !== undefined || groups[i].count !== undefined) {
|
1221
|
+
// @ts-expect-error
|
1222
|
+
cacheKey += `:${groups[i].start}:${groups[i].count}`;
|
1223
|
+
}
|
1111
1224
|
|
1112
|
-
|
1225
|
+
if (cache.attributes.has(cacheKey)) {
|
1226
|
+
primitive.indices = cache.attributes.get(cacheKey);
|
1227
|
+
} else {
|
1228
|
+
primitive.indices = this.processAccessor(geometry.index, geometry, groups[i].start, groups[i].count);
|
1229
|
+
cache.attributes.set(cacheKey, primitive.indices);
|
1113
1230
|
}
|
1114
1231
|
|
1115
|
-
|
1116
|
-
if (material !== null) primitive.material = material;
|
1117
|
-
primitives.push(primitive);
|
1232
|
+
if (primitive.indices === null) delete primitive.indices;
|
1118
1233
|
}
|
1119
1234
|
|
1120
|
-
|
1121
|
-
if (!json.meshes) json.meshes = [];
|
1235
|
+
const materialIndex = groups[i].materialIndex;
|
1122
1236
|
|
1123
|
-
|
1124
|
-
|
1125
|
-
});
|
1126
|
-
|
1127
|
-
const index = json.meshes.push(meshDef) - 1;
|
1128
|
-
cache.meshes.set(meshCacheKey, index);
|
1129
|
-
return index;
|
1130
|
-
},
|
1237
|
+
if (materialIndex !== undefined && Array.isArray(materials)) {
|
1238
|
+
const targetMaterials = materials[materialIndex];
|
1131
1239
|
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
if (!json.cameras) json.cameras = [];
|
1140
|
-
const isOrtho = camera.isOrthographicCamera;
|
1141
|
-
const cameraDef = {
|
1142
|
-
type: isOrtho ? 'orthographic' : 'perspective'
|
1143
|
-
};
|
1240
|
+
if (!Array.isArray(targetMaterials)) {
|
1241
|
+
const material = this.processMaterial(targetMaterials);
|
1242
|
+
if (material !== null) primitive.material = material;
|
1243
|
+
primitives.push(primitive);
|
1244
|
+
}
|
1245
|
+
}
|
1246
|
+
}
|
1144
1247
|
|
1145
|
-
|
1146
|
-
|
1147
|
-
xmag: camera.right * 2,
|
1148
|
-
ymag: camera.top * 2,
|
1149
|
-
zfar: camera.far <= 0 ? 0.001 : camera.far,
|
1150
|
-
znear: camera.near < 0 ? 0 : camera.near
|
1151
|
-
};
|
1152
|
-
} else {
|
1153
|
-
cameraDef.perspective = {
|
1154
|
-
aspectRatio: camera.aspect,
|
1155
|
-
yfov: MathUtils.degToRad(camera.fov),
|
1156
|
-
zfar: camera.far <= 0 ? 0.001 : camera.far,
|
1157
|
-
znear: camera.near < 0 ? 0 : camera.near
|
1158
|
-
};
|
1159
|
-
} // Question: Is saving "type" as name intentional?
|
1248
|
+
meshDef.primitives = primitives;
|
1249
|
+
if (!json.meshes) json.meshes = [];
|
1160
1250
|
|
1251
|
+
this._invokeAll(function (ext) {
|
1252
|
+
ext.writeMesh && ext.writeMesh(mesh, meshDef);
|
1253
|
+
});
|
1161
1254
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1255
|
+
const index = json.meshes.push(meshDef) - 1;
|
1256
|
+
cache.meshes.set(meshCacheKey, index);
|
1257
|
+
return index;
|
1258
|
+
}
|
1259
|
+
/**
|
1260
|
+
* Process camera
|
1261
|
+
* @param {THREE.Camera} camera Camera to process
|
1262
|
+
* @return {Integer} Index of the processed mesh in the "camera" array
|
1263
|
+
*/
|
1165
1264
|
|
1166
|
-
/**
|
1167
|
-
* Creates glTF animation entry from AnimationClip object.
|
1168
|
-
*
|
1169
|
-
* Status:
|
1170
|
-
* - Only properties listed in PATH_PROPERTIES may be animated.
|
1171
|
-
*
|
1172
|
-
* @param {THREE.AnimationClip} clip
|
1173
|
-
* @param {THREE.Object3D} root
|
1174
|
-
* @return {number|null}
|
1175
|
-
*/
|
1176
|
-
processAnimation: function (clip, root) {
|
1177
|
-
const json = this.json;
|
1178
|
-
const nodeMap = this.nodeMap;
|
1179
|
-
if (!json.animations) json.animations = [];
|
1180
|
-
clip = GLTFExporter.Utils.mergeMorphTargetTracks(clip.clone(), root);
|
1181
|
-
const tracks = clip.tracks;
|
1182
|
-
const channels = [];
|
1183
|
-
const samplers = [];
|
1184
|
-
|
1185
|
-
for (let i = 0; i < tracks.length; ++i) {
|
1186
|
-
const track = tracks[i];
|
1187
|
-
const trackBinding = PropertyBinding.parseTrackName(track.name);
|
1188
|
-
let trackNode = PropertyBinding.findNode(root, trackBinding.nodeName);
|
1189
|
-
const trackProperty = PATH_PROPERTIES[trackBinding.propertyName];
|
1190
|
-
|
1191
|
-
if (trackBinding.objectName === 'bones') {
|
1192
|
-
if (trackNode.isSkinnedMesh === true) {
|
1193
|
-
trackNode = trackNode.skeleton.getBoneByName(trackBinding.objectIndex);
|
1194
|
-
} else {
|
1195
|
-
trackNode = undefined;
|
1196
|
-
}
|
1197
|
-
}
|
1198
1265
|
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1266
|
+
processCamera(camera) {
|
1267
|
+
const json = this.json;
|
1268
|
+
if (!json.cameras) json.cameras = [];
|
1269
|
+
const isOrtho = camera instanceof OrthographicCamera && camera.isOrthographicCamera;
|
1270
|
+
const cameraDef = {
|
1271
|
+
type: isOrtho ? 'orthographic' : 'perspective'
|
1272
|
+
};
|
1203
1273
|
|
1204
|
-
|
1205
|
-
|
1274
|
+
if (camera instanceof OrthographicCamera && isOrtho) {
|
1275
|
+
cameraDef.orthographic = {
|
1276
|
+
xmag: camera.right * 2,
|
1277
|
+
ymag: camera.top * 2,
|
1278
|
+
zfar: camera.far <= 0 ? 0.001 : camera.far,
|
1279
|
+
znear: camera.near < 0 ? 0 : camera.near
|
1280
|
+
};
|
1281
|
+
} else if (camera instanceof PerspectiveCamera) {
|
1282
|
+
cameraDef.perspective = {
|
1283
|
+
aspectRatio: camera.aspect,
|
1284
|
+
yfov: MathUtils.degToRad(camera.fov),
|
1285
|
+
zfar: camera.far <= 0 ? 0.001 : camera.far,
|
1286
|
+
znear: camera.near < 0 ? 0 : camera.near
|
1287
|
+
};
|
1288
|
+
} // Question: Is saving "type" as name intentional?
|
1206
1289
|
|
1207
|
-
if (trackProperty === PATH_PROPERTIES.morphTargetInfluences) {
|
1208
|
-
outputItemSize /= trackNode.morphTargetInfluences.length;
|
1209
|
-
}
|
1210
1290
|
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1291
|
+
if (camera.name !== '') cameraDef.name = camera.type;
|
1292
|
+
return json.cameras.push(cameraDef) - 1;
|
1293
|
+
}
|
1294
|
+
/**
|
1295
|
+
* Creates glTF animation entry from AnimationClip object.
|
1296
|
+
*
|
1297
|
+
* Status:
|
1298
|
+
* - Only properties listed in PATH_PROPERTIES may be animated.
|
1299
|
+
*
|
1300
|
+
* @param {THREE.AnimationClip} clip
|
1301
|
+
* @param {THREE.Object3D} root
|
1302
|
+
* @return {number|null}
|
1303
|
+
*/
|
1215
1304
|
|
1216
|
-
if (track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true) {
|
1217
|
-
interpolation = 'CUBICSPLINE'; // itemSize of CUBICSPLINE keyframe is 9
|
1218
|
-
// (VEC3 * 3: inTangent, splineVertex, and outTangent)
|
1219
|
-
// but needs to be stored as VEC3 so dividing by 3 here.
|
1220
1305
|
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1306
|
+
processAnimation(clip, root) {
|
1307
|
+
const json = this.json;
|
1308
|
+
const nodeMap = this.nodeMap;
|
1309
|
+
if (!json.animations) json.animations = [];
|
1310
|
+
clip = GLTFExporter.Utils.mergeMorphTargetTracks(clip.clone(), root);
|
1311
|
+
const tracks = clip.tracks;
|
1312
|
+
const channels = [];
|
1313
|
+
const samplers = [];
|
1314
|
+
|
1315
|
+
for (let i = 0; i < tracks.length; ++i) {
|
1316
|
+
const track = tracks[i];
|
1317
|
+
const trackBinding = PropertyBinding.parseTrackName(track.name);
|
1318
|
+
let trackNode = PropertyBinding.findNode(root, trackBinding.nodeName);
|
1319
|
+
const trackProperty = PATH_PROPERTIES[trackBinding.propertyName];
|
1320
|
+
|
1321
|
+
if (trackBinding.objectName === 'bones') {
|
1322
|
+
if (trackNode.isSkinnedMesh) {
|
1323
|
+
trackNode = trackNode.skeleton.getBoneByName(trackBinding.objectIndex);
|
1224
1324
|
} else {
|
1225
|
-
|
1325
|
+
trackNode = undefined;
|
1226
1326
|
}
|
1327
|
+
}
|
1227
1328
|
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
}
|
1239
|
-
});
|
1329
|
+
if (!trackNode || !trackProperty) {
|
1330
|
+
console.warn('THREE.GLTFExporter: Could not export animation track "%s".', track.name);
|
1331
|
+
return null;
|
1332
|
+
}
|
1333
|
+
|
1334
|
+
const inputItemSize = 1;
|
1335
|
+
let outputItemSize = track.values.length / track.times.length;
|
1336
|
+
|
1337
|
+
if (trackProperty === PATH_PROPERTIES.morphTargetInfluences) {
|
1338
|
+
outputItemSize /= trackNode.morphTargetInfluences.length;
|
1240
1339
|
}
|
1241
1340
|
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1341
|
+
let interpolation; // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE
|
1342
|
+
// Detecting glTF cubic spline interpolant by checking factory method's special property
|
1343
|
+
// GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
|
1344
|
+
// valid value from .getInterpolation().
|
1345
|
+
// @ts-expect-error
|
1346
|
+
|
1347
|
+
if (track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) {
|
1348
|
+
interpolation = 'CUBICSPLINE'; // itemSize of CUBICSPLINE keyframe is 9
|
1349
|
+
// (VEC3 * 3: inTangent, splineVertex, and outTangent)
|
1350
|
+
// but needs to be stored as VEC3 so dividing by 3 here.
|
1351
|
+
|
1352
|
+
outputItemSize /= 3;
|
1353
|
+
} else if (track.getInterpolation() === InterpolateDiscrete) {
|
1354
|
+
interpolation = 'STEP';
|
1355
|
+
} else {
|
1356
|
+
interpolation = 'LINEAR';
|
1357
|
+
}
|
1358
|
+
|
1359
|
+
samplers.push({
|
1360
|
+
input: this.processAccessor(new BufferAttribute(track.times, inputItemSize)),
|
1361
|
+
output: this.processAccessor(new BufferAttribute(track.values, outputItemSize)),
|
1362
|
+
interpolation: interpolation
|
1363
|
+
});
|
1364
|
+
channels.push({
|
1365
|
+
sampler: samplers.length - 1,
|
1366
|
+
target: {
|
1367
|
+
node: nodeMap.get(trackNode),
|
1368
|
+
path: trackProperty
|
1369
|
+
}
|
1246
1370
|
});
|
1247
|
-
|
1248
|
-
},
|
1371
|
+
}
|
1249
1372
|
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1373
|
+
json.animations.push({
|
1374
|
+
name: clip.name || 'clip_' + json.animations.length,
|
1375
|
+
samplers: samplers,
|
1376
|
+
channels: channels
|
1377
|
+
});
|
1378
|
+
return json.animations.length - 1;
|
1379
|
+
}
|
1380
|
+
/**
|
1381
|
+
* @param {THREE.Object3D} object
|
1382
|
+
* @return {number|null}
|
1383
|
+
*/
|
1384
|
+
|
1385
|
+
|
1386
|
+
processSkin(object) {
|
1387
|
+
const json = this.json;
|
1388
|
+
const nodeMap = this.nodeMap;
|
1389
|
+
|
1390
|
+
if (json.nodes !== undefined && object instanceof SkinnedMesh) {
|
1257
1391
|
const node = json.nodes[nodeMap.get(object)];
|
1258
1392
|
const skeleton = object.skeleton;
|
1259
1393
|
if (skeleton === undefined) return null;
|
@@ -1272,235 +1406,353 @@ const GLTFExporter = (() => {
|
|
1272
1406
|
if (json.skins === undefined) json.skins = [];
|
1273
1407
|
json.skins.push({
|
1274
1408
|
inverseBindMatrices: this.processAccessor(new BufferAttribute(inverseBindMatrices, 16)),
|
1275
|
-
joints,
|
1409
|
+
joints: joints,
|
1276
1410
|
skeleton: nodeMap.get(rootJoint)
|
1277
1411
|
});
|
1278
1412
|
const skinIndex = node.skin = json.skins.length - 1;
|
1279
1413
|
return skinIndex;
|
1280
|
-
}
|
1414
|
+
} else {
|
1415
|
+
return null;
|
1416
|
+
}
|
1417
|
+
}
|
1418
|
+
/**
|
1419
|
+
* Process Object3D node
|
1420
|
+
* @param {THREE.Object3D} node Object3D to processNode
|
1421
|
+
* @return {Integer} Index of the node in the nodes list
|
1422
|
+
*/
|
1281
1423
|
|
1282
|
-
/**
|
1283
|
-
* Process Object3D node
|
1284
|
-
* @param {THREE.Object3D} node Object3D to processNode
|
1285
|
-
* @return {Integer} Index of the node in the nodes list
|
1286
|
-
*/
|
1287
|
-
processNode: function (object) {
|
1288
|
-
const json = this.json;
|
1289
|
-
const options = this.options;
|
1290
|
-
const nodeMap = this.nodeMap;
|
1291
|
-
if (!json.nodes) json.nodes = [];
|
1292
|
-
const nodeDef = {};
|
1293
|
-
|
1294
|
-
if (options.trs) {
|
1295
|
-
const rotation = object.quaternion.toArray();
|
1296
|
-
const position = object.position.toArray();
|
1297
|
-
const scale = object.scale.toArray();
|
1298
|
-
|
1299
|
-
if (!equalArray(rotation, [0, 0, 0, 1])) {
|
1300
|
-
nodeDef.rotation = rotation;
|
1301
|
-
}
|
1302
1424
|
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1425
|
+
processNode(object) {
|
1426
|
+
const json = this.json;
|
1427
|
+
const options = this.options;
|
1428
|
+
const nodeMap = this.nodeMap;
|
1429
|
+
if (!json.nodes) json.nodes = [];
|
1430
|
+
const nodeDef = {};
|
1306
1431
|
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
if (object.matrixAutoUpdate) {
|
1312
|
-
object.updateMatrix();
|
1313
|
-
}
|
1432
|
+
if (options.trs) {
|
1433
|
+
const rotation = object.quaternion.toArray();
|
1434
|
+
const position = object.position.toArray();
|
1435
|
+
const scale = object.scale.toArray();
|
1314
1436
|
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
} // We don't export empty strings name because it represents no-name in Three.js.
|
1437
|
+
if (!this.equalArray(rotation, [0, 0, 0, 1])) {
|
1438
|
+
nodeDef.rotation = rotation;
|
1439
|
+
}
|
1319
1440
|
|
1441
|
+
if (!this.equalArray(position, [0, 0, 0])) {
|
1442
|
+
nodeDef.translation = position;
|
1443
|
+
}
|
1320
1444
|
|
1321
|
-
if (
|
1322
|
-
|
1445
|
+
if (!this.equalArray(scale, [1, 1, 1])) {
|
1446
|
+
nodeDef.scale = scale;
|
1447
|
+
}
|
1448
|
+
} else {
|
1449
|
+
if (object.matrixAutoUpdate) {
|
1450
|
+
object.updateMatrix();
|
1451
|
+
}
|
1323
1452
|
|
1324
|
-
if (
|
1325
|
-
|
1326
|
-
if (meshIndex !== null) nodeDef.mesh = meshIndex;
|
1327
|
-
} else if (object.isCamera) {
|
1328
|
-
nodeDef.camera = this.processCamera(object);
|
1453
|
+
if (!this.isIdentityMatrix(object.matrix)) {
|
1454
|
+
nodeDef.matrix = object.matrix.elements;
|
1329
1455
|
}
|
1456
|
+
} // We don't export empty strings name because it represents no-name in Three.js.
|
1457
|
+
|
1330
1458
|
|
1331
|
-
|
1459
|
+
if (object.name !== '') nodeDef.name = String(object.name);
|
1460
|
+
this.serializeUserData(object, nodeDef);
|
1332
1461
|
|
1333
|
-
|
1334
|
-
|
1462
|
+
if ((object instanceof Mesh && object.isMesh || object instanceof Line && object.isLine || object instanceof Points && object.isPoints) && object instanceof Mesh) {
|
1463
|
+
const meshIndex = this.processMesh(object);
|
1464
|
+
if (meshIndex !== null) nodeDef.mesh = meshIndex;
|
1465
|
+
} else if (object instanceof Camera && object.isCamera) {
|
1466
|
+
nodeDef.camera = this.processCamera(object);
|
1467
|
+
}
|
1335
1468
|
|
1336
|
-
|
1337
|
-
const child = object.children[i];
|
1469
|
+
if (object instanceof SkinnedMesh && object.isSkinnedMesh) this.skins.push(object);
|
1338
1470
|
|
1339
|
-
|
1340
|
-
|
1341
|
-
if (nodeIndex !== null) children.push(nodeIndex);
|
1342
|
-
}
|
1343
|
-
}
|
1471
|
+
if (object.children.length > 0) {
|
1472
|
+
const children = [];
|
1344
1473
|
|
1345
|
-
|
1474
|
+
for (let i = 0, l = object.children.length; i < l; i++) {
|
1475
|
+
const child = object.children[i];
|
1476
|
+
|
1477
|
+
if (child.visible || !options.onlyVisible) {
|
1478
|
+
const nodeIndex = this.processNode(child);
|
1479
|
+
if (nodeIndex !== null) children.push(nodeIndex);
|
1480
|
+
}
|
1346
1481
|
}
|
1347
1482
|
|
1348
|
-
|
1349
|
-
|
1350
|
-
});
|
1483
|
+
if (children.length > 0) nodeDef.children = children;
|
1484
|
+
}
|
1351
1485
|
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
},
|
1486
|
+
this._invokeAll(function (ext) {
|
1487
|
+
ext.writeNode && ext.writeNode(object, nodeDef);
|
1488
|
+
});
|
1356
1489
|
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1490
|
+
const nodeIndex = json.nodes.push(nodeDef) - 1;
|
1491
|
+
nodeMap.set(object, nodeIndex);
|
1492
|
+
return nodeIndex;
|
1493
|
+
}
|
1494
|
+
/**
|
1495
|
+
* Process Scene
|
1496
|
+
* @param {Scene} node Scene to process
|
1497
|
+
*/
|
1364
1498
|
|
1365
|
-
if (!json.scenes) {
|
1366
|
-
json.scenes = [];
|
1367
|
-
json.scene = 0;
|
1368
|
-
}
|
1369
1499
|
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
const nodes = [];
|
1500
|
+
processScene(scene) {
|
1501
|
+
const json = this.json;
|
1502
|
+
const options = this.options;
|
1374
1503
|
|
1375
|
-
|
1376
|
-
|
1504
|
+
if (!json.scenes) {
|
1505
|
+
json.scenes = [];
|
1506
|
+
json.scene = 0;
|
1507
|
+
}
|
1377
1508
|
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
}
|
1509
|
+
const sceneDef = {};
|
1510
|
+
if (scene.name !== '') sceneDef.name = scene.name;
|
1511
|
+
json.scenes.push(sceneDef);
|
1512
|
+
const nodes = [];
|
1383
1513
|
|
1384
|
-
|
1385
|
-
|
1386
|
-
},
|
1514
|
+
for (let i = 0, l = scene.children.length; i < l; i++) {
|
1515
|
+
const child = scene.children[i];
|
1387
1516
|
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
*/
|
1392
|
-
processObjects: function (objects) {
|
1393
|
-
const scene = new Scene();
|
1394
|
-
scene.name = 'AuxScene';
|
1395
|
-
|
1396
|
-
for (let i = 0; i < objects.length; i++) {
|
1397
|
-
// We push directly to children instead of calling `add` to prevent
|
1398
|
-
// modify the .parent and break its original scene and hierarchy
|
1399
|
-
scene.children.push(objects[i]);
|
1517
|
+
if (child.visible || !options.onlyVisible) {
|
1518
|
+
const nodeIndex = this.processNode(child);
|
1519
|
+
if (nodeIndex !== null) nodes.push(nodeIndex);
|
1400
1520
|
}
|
1521
|
+
}
|
1401
1522
|
|
1402
|
-
|
1403
|
-
|
1523
|
+
if (nodes.length > 0) sceneDef.nodes = nodes;
|
1524
|
+
this.serializeUserData(scene, sceneDef);
|
1525
|
+
}
|
1404
1526
|
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
processInput: function (input) {
|
1409
|
-
const options = this.options;
|
1410
|
-
input = input instanceof Array ? input : [input];
|
1527
|
+
processObjects(objects) {
|
1528
|
+
const scene = new Scene();
|
1529
|
+
scene.name = 'AuxScene';
|
1411
1530
|
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1531
|
+
for (let i = 0; i < objects.length; i++) {
|
1532
|
+
// We push directly to children instead of calling `add` to prevent
|
1533
|
+
// modify the .parent and break its original scene and hierarchy
|
1534
|
+
scene.children.push(objects[i]);
|
1535
|
+
}
|
1415
1536
|
|
1416
|
-
|
1537
|
+
this.processScene(scene);
|
1538
|
+
}
|
1539
|
+
/**
|
1540
|
+
* @param {THREE.Object3D|Array<THREE.Object3D>} input
|
1541
|
+
*/
|
1417
1542
|
|
1418
|
-
for (let i = 0; i < input.length; i++) {
|
1419
|
-
if (input[i] instanceof Scene) {
|
1420
|
-
this.processScene(input[i]);
|
1421
|
-
} else {
|
1422
|
-
objectsWithoutScene.push(input[i]);
|
1423
|
-
}
|
1424
|
-
}
|
1425
1543
|
|
1426
|
-
|
1544
|
+
processInput(input) {
|
1545
|
+
const options = this.options;
|
1546
|
+
input = input instanceof Array ? input : [input];
|
1427
1547
|
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1548
|
+
this._invokeAll(function (ext) {
|
1549
|
+
ext.beforeParse && ext.beforeParse(input);
|
1550
|
+
});
|
1551
|
+
|
1552
|
+
const objectsWithoutScene = [];
|
1431
1553
|
|
1432
|
-
|
1433
|
-
|
1554
|
+
for (let i = 0; i < input.length; i++) {
|
1555
|
+
const inputScene = input[i];
|
1556
|
+
|
1557
|
+
if (inputScene instanceof Scene) {
|
1558
|
+
this.processScene(inputScene);
|
1559
|
+
} else {
|
1560
|
+
objectsWithoutScene.push(input[i]);
|
1434
1561
|
}
|
1562
|
+
}
|
1435
1563
|
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1564
|
+
if (objectsWithoutScene.length > 0) this.processObjects(objectsWithoutScene);
|
1565
|
+
|
1566
|
+
for (let i = 0; i < this.skins.length; ++i) {
|
1567
|
+
this.processSkin(this.skins[i]);
|
1568
|
+
}
|
1569
|
+
|
1570
|
+
for (let i = 0; options.animations !== undefined && i < options.animations.length; ++i) {
|
1571
|
+
this.processAnimation(options.animations[i], input[0]);
|
1572
|
+
}
|
1573
|
+
|
1574
|
+
this._invokeAll(function (ext) {
|
1575
|
+
ext.afterParse && ext.afterParse(input);
|
1576
|
+
});
|
1577
|
+
}
|
1578
|
+
|
1579
|
+
_invokeAll(func) {
|
1580
|
+
for (let i = 0, il = this.plugins.length; i < il; i++) {
|
1581
|
+
func(this.plugins[i]);
|
1582
|
+
}
|
1583
|
+
} //------------------------------------------------------------------------------
|
1584
|
+
// Utility functions
|
1585
|
+
//------------------------------------------------------------------------------
|
1586
|
+
|
1587
|
+
/**
|
1588
|
+
* Compare two arrays
|
1589
|
+
* @param {Array} array1 Array 1 to compare
|
1590
|
+
* @param {Array} array2 Array 2 to compare
|
1591
|
+
* @return {Boolean} Returns true if both arrays are equal
|
1592
|
+
*/
|
1593
|
+
|
1594
|
+
|
1595
|
+
equalArray(array1, array2) {
|
1596
|
+
return array1.length === array2.length && array1.every(function (element, index) {
|
1597
|
+
return element === array2[index];
|
1598
|
+
});
|
1599
|
+
}
|
1600
|
+
/**
|
1601
|
+
* Converts a string to an ArrayBuffer.
|
1602
|
+
* @param {string} text
|
1603
|
+
* @return {ArrayBuffer}
|
1604
|
+
*/
|
1605
|
+
|
1606
|
+
|
1607
|
+
stringToArrayBuffer(text) {
|
1608
|
+
if (window.TextEncoder !== undefined) {
|
1609
|
+
return new TextEncoder().encode(text).buffer;
|
1610
|
+
}
|
1611
|
+
|
1612
|
+
const array = new Uint8Array(new ArrayBuffer(text.length));
|
1613
|
+
|
1614
|
+
for (let i = 0, il = text.length; i < il; i++) {
|
1615
|
+
const value = text.charCodeAt(i); // Replacing multi-byte character with space(0x20).
|
1616
|
+
|
1617
|
+
array[i] = value > 0xff ? 0x20 : value;
|
1618
|
+
}
|
1619
|
+
|
1620
|
+
return array.buffer;
|
1621
|
+
}
|
1622
|
+
|
1623
|
+
isIdentityMatrix(matrix) {
|
1624
|
+
return this.equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
1625
|
+
}
|
1626
|
+
|
1627
|
+
getMinMax(attribute, start, count) {
|
1628
|
+
const output = {
|
1629
|
+
min: new Array(attribute.itemSize).fill(Number.POSITIVE_INFINITY),
|
1630
|
+
max: new Array(attribute.itemSize).fill(Number.NEGATIVE_INFINITY)
|
1631
|
+
};
|
1632
|
+
|
1633
|
+
for (let i = start; i < start + count; i++) {
|
1634
|
+
for (let a = 0; a < attribute.itemSize; a++) {
|
1635
|
+
let value;
|
1636
|
+
|
1637
|
+
if (attribute.itemSize > 4) {
|
1638
|
+
// no support for interleaved data for itemSize > 4
|
1639
|
+
value = attribute.array[i * attribute.itemSize + a];
|
1640
|
+
} else {
|
1641
|
+
if (a === 0) value = attribute.getX(i);else if (a === 1) value = attribute.getY(i);else if (a === 2) value = attribute.getZ(i);else if (a === 3) value = attribute.getW(i);
|
1642
|
+
}
|
1643
|
+
|
1644
|
+
if (value !== undefined) {
|
1645
|
+
output.min[a] = Math.min(output.min[a], value);
|
1646
|
+
output.max[a] = Math.max(output.max[a], value);
|
1647
|
+
}
|
1443
1648
|
}
|
1444
1649
|
}
|
1445
|
-
|
1650
|
+
|
1651
|
+
return output;
|
1652
|
+
}
|
1653
|
+
/**
|
1654
|
+
* Get the required size + padding for a buffer, rounded to the next 4-byte boundary.
|
1655
|
+
* https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
|
1656
|
+
*/
|
1657
|
+
|
1658
|
+
|
1659
|
+
getPaddedBufferSize(bufferSize) {
|
1660
|
+
return Math.ceil(bufferSize / 4) * 4;
|
1661
|
+
}
|
1446
1662
|
/**
|
1447
|
-
*
|
1663
|
+
* Returns a buffer aligned to 4-byte boundary.
|
1448
1664
|
*
|
1449
|
-
*
|
1665
|
+
* @param {ArrayBuffer} arrayBuffer Buffer to pad
|
1666
|
+
* @param {Integer} paddingByte (Optional)
|
1667
|
+
* @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
|
1450
1668
|
*/
|
1451
1669
|
|
1452
|
-
|
1670
|
+
|
1671
|
+
getPaddedArrayBuffer(arrayBuffer, paddingByte = 0) {
|
1672
|
+
const paddedLength = this.getPaddedBufferSize(arrayBuffer.byteLength);
|
1673
|
+
|
1674
|
+
if (paddedLength !== arrayBuffer.byteLength) {
|
1675
|
+
const array = new Uint8Array(paddedLength);
|
1676
|
+
array.set(new Uint8Array(arrayBuffer));
|
1677
|
+
|
1678
|
+
if (paddingByte !== 0) {
|
1679
|
+
for (let i = arrayBuffer.byteLength; i < paddedLength; i++) {
|
1680
|
+
array[i] = paddingByte;
|
1681
|
+
}
|
1682
|
+
}
|
1683
|
+
|
1684
|
+
return array.buffer;
|
1685
|
+
}
|
1686
|
+
|
1687
|
+
return arrayBuffer;
|
1688
|
+
}
|
1689
|
+
|
1690
|
+
}
|
1691
|
+
/**
|
1692
|
+
* Punctual Lights Extension
|
1693
|
+
*
|
1694
|
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
|
1695
|
+
*/
|
1696
|
+
|
1697
|
+
|
1698
|
+
class GLTFLightExtension {
|
1699
|
+
constructor(writer) {
|
1700
|
+
_defineProperty(this, "writer", void 0);
|
1701
|
+
|
1702
|
+
_defineProperty(this, "name", void 0);
|
1703
|
+
|
1453
1704
|
this.writer = writer;
|
1454
1705
|
this.name = 'KHR_lights_punctual';
|
1455
1706
|
}
|
1456
1707
|
|
1457
|
-
|
1458
|
-
|
1459
|
-
writeNode: function (light, nodeDef) {
|
1460
|
-
if (!light.isLight) return;
|
1708
|
+
writeNode(light, nodeDef) {
|
1709
|
+
if (!light.isLight) return;
|
1461
1710
|
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1711
|
+
if (!(light instanceof DirectionalLight && light.isDirectionalLight) && // @ts-expect-error
|
1712
|
+
!(light instanceof PointLight && light.isPointLight) && !(light instanceof SpotLight && light.isSpotLight)) {
|
1713
|
+
console.warn('THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light);
|
1714
|
+
return;
|
1715
|
+
}
|
1466
1716
|
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1717
|
+
const writer = this.writer;
|
1718
|
+
const json = writer.json;
|
1719
|
+
const extensionsUsed = writer.extensionsUsed;
|
1720
|
+
const lightDef = {};
|
1721
|
+
if (light.name) lightDef.name = light.name;
|
1722
|
+
lightDef.color = light.color.toArray();
|
1723
|
+
lightDef.intensity = light.intensity;
|
1724
|
+
|
1725
|
+
if (light instanceof DirectionalLight && light.isDirectionalLight) {
|
1726
|
+
lightDef.type = 'directional';
|
1727
|
+
} else if (light instanceof PointLight && // @ts-expect-error
|
1728
|
+
light.isPointLight) {
|
1729
|
+
lightDef.type = 'point';
|
1730
|
+
if (light.distance > 0) lightDef.range = light.distance;
|
1731
|
+
} else if (light instanceof SpotLight && light.isSpotLight) {
|
1732
|
+
lightDef.type = 'spot';
|
1733
|
+
if (light.distance > 0) lightDef.range = light.distance;
|
1734
|
+
lightDef.spot = {};
|
1735
|
+
lightDef.spot.innerConeAngle = (light.penumbra - 1.0) * light.angle * -1.0;
|
1736
|
+
lightDef.spot.outerConeAngle = light.angle;
|
1737
|
+
}
|
1487
1738
|
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1739
|
+
if (!(light instanceof DirectionalLight) && light.decay !== undefined && light.decay !== 2) {
|
1740
|
+
console.warn('THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.');
|
1741
|
+
}
|
1491
1742
|
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1743
|
+
if (!(light instanceof PointLight) && light.target && (light.target.parent !== light || light.target.position.x !== 0 || light.target.position.y !== 0 || light.target.position.z !== -1)) {
|
1744
|
+
console.warn('THREE.GLTFExporter: Light direction may be lost. For best results, ' + 'make light.target a child of the light with position 0,0,-1.');
|
1745
|
+
}
|
1495
1746
|
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1747
|
+
if (!extensionsUsed[this.name]) {
|
1748
|
+
json.extensions = json.extensions || {};
|
1749
|
+
json.extensions[this.name] = {
|
1750
|
+
lights: []
|
1751
|
+
};
|
1752
|
+
extensionsUsed[this.name] = true;
|
1753
|
+
}
|
1503
1754
|
|
1755
|
+
if (json.extensions !== undefined) {
|
1504
1756
|
const lights = json.extensions[this.name].lights;
|
1505
1757
|
lights.push(lightDef);
|
1506
1758
|
nodeDef.extensions = nodeDef.extensions || {};
|
@@ -1508,213 +1760,187 @@ const GLTFExporter = (() => {
|
|
1508
1760
|
light: lights.length - 1
|
1509
1761
|
};
|
1510
1762
|
}
|
1511
|
-
}
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1763
|
+
}
|
1764
|
+
|
1765
|
+
}
|
1766
|
+
/**
|
1767
|
+
* Unlit Materials Extension
|
1768
|
+
*
|
1769
|
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
|
1770
|
+
*/
|
1771
|
+
|
1772
|
+
|
1773
|
+
class GLTFMaterialsUnlitExtension {
|
1774
|
+
constructor(writer) {
|
1775
|
+
_defineProperty(this, "writer", void 0);
|
1776
|
+
|
1777
|
+
_defineProperty(this, "name", void 0);
|
1517
1778
|
|
1518
|
-
function GLTFMaterialsUnlitExtension(writer) {
|
1519
1779
|
this.writer = writer;
|
1520
1780
|
this.name = 'KHR_materials_unlit';
|
1521
1781
|
}
|
1522
1782
|
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
const writer = this.writer;
|
1528
|
-
const extensionsUsed = writer.extensionsUsed;
|
1529
|
-
materialDef.extensions = materialDef.extensions || {};
|
1530
|
-
materialDef.extensions[this.name] = {};
|
1531
|
-
extensionsUsed[this.name] = true;
|
1532
|
-
materialDef.pbrMetallicRoughness.metallicFactor = 0.0;
|
1533
|
-
materialDef.pbrMetallicRoughness.roughnessFactor = 0.9;
|
1783
|
+
writeMaterial(material, materialDef) {
|
1784
|
+
if (!(material instanceof MeshBasicMaterial && // @ts-expect-error
|
1785
|
+
material.isMeshBasicMaterial)) {
|
1786
|
+
return;
|
1534
1787
|
}
|
1535
|
-
};
|
1536
|
-
/**
|
1537
|
-
* Specular-Glossiness Extension
|
1538
|
-
*
|
1539
|
-
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
|
1540
|
-
*/
|
1541
1788
|
|
1542
|
-
|
1789
|
+
const writer = this.writer;
|
1790
|
+
const extensionsUsed = writer.extensionsUsed;
|
1791
|
+
materialDef.extensions = materialDef.extensions || {};
|
1792
|
+
materialDef.extensions[this.name] = {};
|
1793
|
+
extensionsUsed[this.name] = true;
|
1794
|
+
materialDef.pbrMetallicRoughness.metallicFactor = 0.0;
|
1795
|
+
materialDef.pbrMetallicRoughness.roughnessFactor = 0.9;
|
1796
|
+
}
|
1797
|
+
|
1798
|
+
}
|
1799
|
+
/**
|
1800
|
+
* Specular-Glossiness Extension
|
1801
|
+
*
|
1802
|
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
|
1803
|
+
*/
|
1804
|
+
|
1805
|
+
|
1806
|
+
class GLTFMaterialsPBRSpecularGlossiness {
|
1807
|
+
constructor(writer) {
|
1808
|
+
_defineProperty(this, "writer", void 0);
|
1809
|
+
|
1810
|
+
_defineProperty(this, "name", void 0);
|
1811
|
+
|
1543
1812
|
this.writer = writer;
|
1544
1813
|
this.name = 'KHR_materials_pbrSpecularGlossiness';
|
1545
1814
|
}
|
1546
1815
|
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
const extensionDef = {};
|
1816
|
+
writeMaterial(material, materialDef) {
|
1817
|
+
// @ts-expect-error
|
1818
|
+
if (!material.isGLTFSpecularGlossinessMaterial) return;
|
1819
|
+
const writer = this.writer;
|
1820
|
+
const extensionsUsed = writer.extensionsUsed;
|
1821
|
+
const extensionDef = {};
|
1554
1822
|
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1823
|
+
if (materialDef.pbrMetallicRoughness.baseColorFactor) {
|
1824
|
+
extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor;
|
1825
|
+
}
|
1558
1826
|
|
1827
|
+
if (material instanceof MeshPhongMaterial) {
|
1559
1828
|
const specularFactor = [1, 1, 1];
|
1560
1829
|
material.specular.toArray(specularFactor, 0);
|
1561
1830
|
extensionDef.specularFactor = specularFactor;
|
1562
|
-
extensionDef.glossinessFactor =
|
1563
|
-
|
1564
|
-
|
1565
|
-
extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture;
|
1566
|
-
}
|
1831
|
+
extensionDef.glossinessFactor = // @ts-expect-error
|
1832
|
+
material.glossiness;
|
1833
|
+
}
|
1567
1834
|
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
};
|
1572
|
-
writer.applyTextureTransform(specularMapDef, material.specularMap);
|
1573
|
-
extensionDef.specularGlossinessTexture = specularMapDef;
|
1574
|
-
}
|
1835
|
+
if (materialDef.pbrMetallicRoughness.baseColorTexture) {
|
1836
|
+
extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture;
|
1837
|
+
}
|
1575
1838
|
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1839
|
+
if ((material instanceof MeshBasicMaterial || material instanceof MeshLambertMaterial || material instanceof MeshPhongMaterial) && material.specularMap) {
|
1840
|
+
const specularMapDef = {
|
1841
|
+
index: writer.processTexture(material.specularMap)
|
1842
|
+
};
|
1843
|
+
writer.applyTextureTransform(specularMapDef, material.specularMap);
|
1844
|
+
extensionDef.specularGlossinessTexture = specularMapDef;
|
1579
1845
|
}
|
1580
|
-
};
|
1581
|
-
/**
|
1582
|
-
* Static utility functions
|
1583
|
-
*/
|
1584
1846
|
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1847
|
+
materialDef.extensions = materialDef.extensions || {};
|
1848
|
+
materialDef.extensions[this.name] = extensionDef;
|
1849
|
+
extensionsUsed[this.name] = true;
|
1850
|
+
}
|
1588
1851
|
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1852
|
+
}
|
1853
|
+
/**
|
1854
|
+
* Transmission Materials Extension
|
1855
|
+
*
|
1856
|
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission
|
1857
|
+
*/
|
1594
1858
|
|
1595
|
-
if (track.times.length === 0) {
|
1596
|
-
times[0] = time;
|
1597
1859
|
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1860
|
+
class GLTFMaterialsTransmissionExtension {
|
1861
|
+
constructor(writer) {
|
1862
|
+
_defineProperty(this, "writer", void 0);
|
1601
1863
|
|
1602
|
-
|
1603
|
-
} else if (time < track.times[0]) {
|
1604
|
-
if (Math.abs(track.times[0] - time) < tolerance) return 0;
|
1605
|
-
times[0] = time;
|
1606
|
-
times.set(track.times, 1);
|
1607
|
-
values.set(interpolant.evaluate(time), 0);
|
1608
|
-
values.set(track.values, valueSize);
|
1609
|
-
index = 0;
|
1610
|
-
} else if (time > track.times[track.times.length - 1]) {
|
1611
|
-
if (Math.abs(track.times[track.times.length - 1] - time) < tolerance) {
|
1612
|
-
return track.times.length - 1;
|
1613
|
-
}
|
1864
|
+
_defineProperty(this, "name", void 0);
|
1614
1865
|
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
values.set(interpolant.evaluate(time), track.values.length);
|
1619
|
-
index = times.length - 1;
|
1620
|
-
} else {
|
1621
|
-
for (let i = 0; i < track.times.length; i++) {
|
1622
|
-
if (Math.abs(track.times[i] - time) < tolerance) return i;
|
1623
|
-
|
1624
|
-
if (track.times[i] < time && track.times[i + 1] > time) {
|
1625
|
-
times.set(track.times.slice(0, i + 1), 0);
|
1626
|
-
times[i + 1] = time;
|
1627
|
-
times.set(track.times.slice(i + 1), i + 2);
|
1628
|
-
values.set(track.values.slice(0, (i + 1) * valueSize), 0);
|
1629
|
-
values.set(interpolant.evaluate(time), (i + 1) * valueSize);
|
1630
|
-
values.set(track.values.slice((i + 1) * valueSize), (i + 2) * valueSize);
|
1631
|
-
index = i + 1;
|
1632
|
-
break;
|
1633
|
-
}
|
1634
|
-
}
|
1635
|
-
}
|
1636
|
-
|
1637
|
-
track.times = times;
|
1638
|
-
track.values = values;
|
1639
|
-
return index;
|
1640
|
-
},
|
1641
|
-
mergeMorphTargetTracks: function (clip, root) {
|
1642
|
-
const tracks = [];
|
1643
|
-
const mergedTracks = {};
|
1644
|
-
const sourceTracks = clip.tracks;
|
1645
|
-
|
1646
|
-
for (let i = 0; i < sourceTracks.length; ++i) {
|
1647
|
-
let sourceTrack = sourceTracks[i];
|
1648
|
-
const sourceTrackBinding = PropertyBinding.parseTrackName(sourceTrack.name);
|
1649
|
-
const sourceTrackNode = PropertyBinding.findNode(root, sourceTrackBinding.nodeName);
|
1650
|
-
|
1651
|
-
if (sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined) {
|
1652
|
-
// Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is.
|
1653
|
-
tracks.push(sourceTrack);
|
1654
|
-
continue;
|
1655
|
-
}
|
1866
|
+
this.writer = writer;
|
1867
|
+
this.name = 'KHR_materials_transmission';
|
1868
|
+
}
|
1656
1869
|
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
}
|
1870
|
+
writeMaterial(material, materialDef) {
|
1871
|
+
if (!(material instanceof MeshPhysicalMaterial && // @ts-expect-error
|
1872
|
+
material.isMeshPhysicalMaterial) || material.transmission === 0) {
|
1873
|
+
return;
|
1874
|
+
}
|
1663
1875
|
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1876
|
+
const writer = this.writer;
|
1877
|
+
const extensionsUsed = writer.extensionsUsed;
|
1878
|
+
const extensionDef = {};
|
1879
|
+
extensionDef.transmissionFactor = material.transmission;
|
1668
1880
|
|
1669
|
-
|
1670
|
-
|
1881
|
+
if (material.transmissionMap) {
|
1882
|
+
const transmissionMapDef = {
|
1883
|
+
index: writer.processTexture(material.transmissionMap)
|
1884
|
+
};
|
1885
|
+
writer.applyTextureTransform(transmissionMapDef, material.transmissionMap);
|
1886
|
+
extensionDef.transmissionTexture = transmissionMapDef;
|
1887
|
+
}
|
1671
1888
|
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1889
|
+
materialDef.extensions = materialDef.extensions || {};
|
1890
|
+
materialDef.extensions[this.name] = extensionDef;
|
1891
|
+
extensionsUsed[this.name] = true;
|
1892
|
+
}
|
1675
1893
|
|
1676
|
-
|
1677
|
-
|
1894
|
+
}
|
1895
|
+
/**
|
1896
|
+
* Materials Volume Extension
|
1897
|
+
*
|
1898
|
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume
|
1899
|
+
*/
|
1678
1900
|
|
1679
|
-
if (mergedTracks[sourceTrackNode.uuid] === undefined) {
|
1680
|
-
mergedTrack = sourceTrack.clone();
|
1681
|
-
const values = new mergedTrack.ValueBufferType(targetCount * mergedTrack.times.length);
|
1682
1901
|
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
// of our original un-merged morphTarget animation.
|
1902
|
+
class GLTFMaterialsVolumeExtension {
|
1903
|
+
constructor(writer) {
|
1904
|
+
_defineProperty(this, "writer", void 0);
|
1687
1905
|
|
1906
|
+
_defineProperty(this, "name", void 0);
|
1688
1907
|
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
tracks.push(mergedTrack);
|
1693
|
-
continue;
|
1694
|
-
}
|
1908
|
+
this.writer = writer;
|
1909
|
+
this.name = 'KHR_materials_volume';
|
1910
|
+
}
|
1695
1911
|
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1912
|
+
writeMaterial(material, materialDef) {
|
1913
|
+
if (!(material instanceof MeshPhysicalMaterial && // @ts-expect-error
|
1914
|
+
material.isMeshPhysicalMaterial) || // @ts-expect-error
|
1915
|
+
material.thickness === 0) {
|
1916
|
+
return;
|
1917
|
+
}
|
1699
1918
|
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1919
|
+
const writer = this.writer;
|
1920
|
+
const extensionsUsed = writer.extensionsUsed;
|
1921
|
+
const extensionDef = {};
|
1922
|
+
extensionDef.thickness = // @ts-expect-error
|
1923
|
+
material.thickness; // @ts-expect-error
|
1705
1924
|
|
1925
|
+
if (material.thicknessMap) {
|
1926
|
+
const thicknessMapDef = {
|
1927
|
+
index: writer.processTexture( // @ts-expect-error
|
1928
|
+
material.thicknessMap)
|
1929
|
+
};
|
1930
|
+
writer.applyTextureTransform(thicknessMapDef, // @ts-expect-error
|
1931
|
+
material.thicknessMap);
|
1932
|
+
extensionDef.thicknessTexture = thicknessMapDef;
|
1933
|
+
}
|
1706
1934
|
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1935
|
+
extensionDef.attenuationDistance = //@ts-expect-error
|
1936
|
+
material.attenuationDistance;
|
1937
|
+
extensionDef.attenuationColor = //@ts-expect-error
|
1938
|
+
material.attenuationTint.toArray();
|
1939
|
+
materialDef.extensions = materialDef.extensions || {};
|
1940
|
+
materialDef.extensions[this.name] = extensionDef;
|
1941
|
+
extensionsUsed[this.name] = true;
|
1942
|
+
}
|
1712
1943
|
|
1713
|
-
|
1714
|
-
return clip;
|
1715
|
-
}
|
1716
|
-
};
|
1717
|
-
return GLTFExporter;
|
1718
|
-
})();
|
1944
|
+
}
|
1719
1945
|
|
1720
1946
|
export { GLTFExporter };
|