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.
@@ -1,232 +1,296 @@
1
- import { PropertyBinding, InterpolateLinear, Vector3, RGBAFormat, RGBFormat, DoubleSide, BufferAttribute, MathUtils, InterpolateDiscrete, Matrix4, Scene, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping } from 'three';
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(writer => new GLTFLightExtension(writer));
7
- this.register(writer => new GLTFMaterialsUnlitExtension(writer));
8
- this.register(writer => new GLTFMaterialsPBRSpecularGlossiness(writer));
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
- GLTFExporter.prototype = {
12
- constructor: GLTFExporter,
13
- register: function (callback) {
14
- if (this.pluginCallbacks.indexOf(callback) === -1) {
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
- return this;
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
- return this;
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
- * Parse scenes and generate GLTF output
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
- for (let i = 0, il = this.pluginCallbacks.length; i < il; i++) {
39
- plugins.push(this.pluginCallbacks[i](writer));
40
- }
42
+ parse(input, onDone, options) {
43
+ const writer = new GLTFWriter();
44
+ const plugins = [];
41
45
 
42
- writer.setPlugins(plugins);
43
- writer.write(input, onDone, options);
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
- function equalArray(array1, array2) {
108
- return array1.length === array2.length && array1.every((element, index) => element === array2[index]);
50
+ writer.setPlugins(plugins);
51
+ writer.write(input, onDone, options);
109
52
  }
110
53
  /**
111
- * Converts a string to an ArrayBuffer.
112
- * @param {string} text
113
- * @return {ArrayBuffer}
54
+ * Static utility functions
114
55
  */
115
56
 
116
57
 
117
- function stringToArrayBuffer(text) {
118
- if (window.TextEncoder !== undefined) {
119
- return new TextEncoder().encode(text).buffer;
120
- }
58
+ } //------------------------------------------------------------------------------
59
+ // Constants
60
+ //------------------------------------------------------------------------------
121
61
 
122
- const array = new Uint8Array(new ArrayBuffer(text.length));
123
62
 
124
- for (let i = 0, il = text.length; i < il; i++) {
125
- const value = text.charCodeAt(i); // Replacing multi-byte character with space(0x20).
63
+ _defineProperty(GLTFExporter, "Utils", {
64
+ insertKeyframe: function (track, time) {
65
+ const tolerance = 0.001; // 1ms
126
66
 
127
- array[i] = value > 0xff ? 0x20 : value;
128
- }
67
+ const valueSize = track.getValueSize(); // @ts-expect-error
129
68
 
130
- return array.buffer;
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
- function isIdentityMatrix(matrix) {
141
- return equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
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
- function getMinMax(attribute, start, count) {
153
- const output = {
154
- min: new Array(attribute.itemSize).fill(Number.POSITIVE_INFINITY),
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
- for (let i = start; i < start + count; i++) {
159
- for (let a = 0; a < attribute.itemSize; a++) {
160
- let value;
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
- if (attribute.itemSize > 4) {
163
- // no support for interleaved data for itemSize > 4
164
- value = attribute.array[i * attribute.itemSize + a];
165
- } else {
166
- 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);
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
- return output;
175
- }
176
- /**
177
- * Get the required size + padding for a buffer, rounded to the next 4-byte boundary.
178
- * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
179
- *
180
- * @param {Integer} bufferSize The size the original buffer.
181
- * @returns {Integer} new buffer size with required padding.
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
- function getPaddedBufferSize(bufferSize) {
187
- return Math.ceil(bufferSize / 4) * 4;
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
- function getPaddedArrayBuffer(arrayBuffer, paddingByte) {
199
- paddingByte = paddingByte || 0;
200
- const paddedLength = getPaddedBufferSize(arrayBuffer.byteLength);
161
+ if (targetIndex === undefined) {
162
+ throw new Error('THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex);
163
+ }
201
164
 
202
- if (paddedLength !== arrayBuffer.byteLength) {
203
- const array = new Uint8Array(paddedLength);
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 (paddingByte !== 0) {
207
- for (let i = arrayBuffer.byteLength; i < paddedLength; i++) {
208
- array[i] = paddingByte;
209
- }
210
- }
168
+ if (mergedTracks[sourceTrackNode.uuid] === undefined) {
169
+ mergedTrack = sourceTrack.clone(); // @ts-expect-error
211
170
 
212
- return array.buffer;
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
- return arrayBuffer;
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
- * Parse scenes and generate GLTF output
259
- * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes
260
- * @param {Function} onDone Callback on completed
261
- * @param {Object} options options
262
- */
263
- write: function (input, onDone, options) {
264
- this.options = Object.assign({}, {
265
- // default options
266
- binary: false,
267
- trs: false,
268
- onlyVisible: true,
269
- truncateDrawRange: true,
270
- embedImages: true,
271
- maxTextureSize: Infinity,
272
- animations: [],
273
- includeCustomExtensions: false
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
- this.processInput(input);
282
- const writer = this;
283
- Promise.all(this.pending).then(() => {
284
- const buffers = writer.buffers;
285
- const json = writer.json;
286
- const options = writer.options;
287
- const extensionsUsed = writer.extensionsUsed; // Merge buffers.
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
- const blob = new Blob(buffers, {
290
- type: 'application/octet-stream'
291
- }); // Declare extensions.
353
+ const blob = new Blob(buffers, {
354
+ type: 'application/octet-stream'
355
+ }); // Declare extensions.
292
356
 
293
- const extensionsUsedList = Object.keys(extensionsUsed);
294
- if (extensionsUsedList.length > 0) json.extensionsUsed = extensionsUsedList; // Update bytelength of the single buffer.
357
+ const extensionsUsedList = Object.keys(extensionsUsed);
358
+ if (extensionsUsedList.length > 0) json.extensionsUsed = extensionsUsedList; // Update bytelength of the single buffer.
295
359
 
296
- if (json.buffers && json.buffers.length > 0) json.buffers[0].byteLength = blob.size;
360
+ if (json.buffers && json.buffers.length > 0) json.buffers[0].byteLength = blob.size;
297
361
 
298
- if (options.binary === true) {
299
- // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
300
- var reader = new window.FileReader();
301
- reader.readAsArrayBuffer(blob);
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
- reader.onloadend = () => {
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
- onDone(glbReader.result);
392
+ glbReader.onloadend = function () {
393
+ if (glbReader.result !== null && typeof glbReader.result !== 'string') {
394
+ onDone(glbReader.result);
395
+ }
329
396
  };
330
- };
331
- } else {
332
- if (json.buffers && json.buffers.length > 0) {
333
- var reader = new window.FileReader();
334
- reader.readAsDataURL(blob);
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
- reader.onloadend = () => {
337
- const base64data = reader.result;
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
- } else {
342
- onDone(json);
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
- try {
360
- const json = JSON.parse(JSON.stringify(object.userData));
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
- if (options.includeCustomExtensions && json.gltfExtensions) {
363
- if (objectDef.extensions === undefined) objectDef.extensions = {};
431
+ try {
432
+ const json = JSON.parse(JSON.stringify(object.userData));
364
433
 
365
- for (let extensionName in json.gltfExtensions) {
366
- objectDef.extensions[extensionName] = json.gltfExtensions[extensionName];
367
- extensionsUsed[extensionName] = true;
368
- }
434
+ if (options.includeCustomExtensions && json.gltfExtensions) {
435
+ if (objectDef.extensions === undefined) objectDef.extensions = {};
369
436
 
370
- delete json.gltfExtensions;
437
+ for (const extensionName in json.gltfExtensions) {
438
+ objectDef.extensions[extensionName] = json.gltfExtensions[extensionName];
439
+ extensionsUsed[extensionName] = true;
371
440
  }
372
441
 
373
- if (Object.keys(json).length > 0) objectDef.extras = json;
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
- * Checks if normal attribute values are normalized.
392
- *
393
- * @param {BufferAttribute} normal
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
- * Creates normalized normal buffer attribute.
411
- *
412
- * @param {BufferAttribute} normal
413
- * @returns {BufferAttribute}
414
- *
415
- */
416
- createNormalizedNormalAttribute: function (normal) {
417
- const cache = this.cache;
418
- if (cache.attributesNormalized.has(normal)) return cache.attributesNormalized.get(normal);
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
- cache.attributesNormalized.set(normal, attribute);
436
- return attribute;
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
- * Applies a texture transform, if present, to the map definition. Requires
441
- * the KHR_texture_transform extension.
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
- if (texture.offset.x !== 0 || texture.offset.y !== 0) {
451
- transformDef.offset = texture.offset.toArray();
452
- didTransform = true;
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
- if (texture.repeat.x !== 1 || texture.repeat.y !== 1) {
461
- transformDef.scale = texture.repeat.toArray();
462
- didTransform = true;
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 (didTransform) {
466
- mapDef.extensions = mapDef.extensions || {};
467
- mapDef.extensions['KHR_texture_transform'] = transformDef;
468
- this.extensionsUsed['KHR_texture_transform'] = true;
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
- * Process a buffer to append to the default one.
474
- * @param {ArrayBuffer} buffer
475
- * @return {Integer}
476
- */
477
- processBuffer: function (buffer) {
478
- const json = this.json;
479
- const buffers = this.buffers;
480
- if (!json.buffers) json.buffers = [{
481
- byteLength: 0
482
- }]; // All buffers are merged before export.
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
- * Process and generate a BufferView
490
- * @param {BufferAttribute} attribute
491
- * @param {number} componentType
492
- * @param {number} start
493
- * @param {number} count
494
- * @param {number} target (Optional) Target usage of the BufferView
495
- * @return {Object}
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
- let componentSize;
533
+ if (texture.rotation !== 0) {
534
+ transformDef.rotation = texture.rotation;
535
+ didTransform = true;
536
+ }
502
537
 
503
- if (componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE) {
504
- componentSize = 1;
505
- } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT) {
506
- componentSize = 2;
507
- } else {
508
- componentSize = 4;
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
- for (let i = start; i < start + count; i++) {
516
- for (let a = 0; a < attribute.itemSize; a++) {
517
- let value;
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
- if (attribute.itemSize > 4) {
520
- // no support for interleaved data for itemSize > 4
521
- value = attribute.array[i * attribute.itemSize + a];
522
- } else {
523
- 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);
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
- const bufferViewDef = {
541
- buffer: this.processBuffer(dataView.buffer),
542
- byteOffset: this.byteOffset,
543
- byteLength
544
- };
545
- if (target !== undefined) bufferViewDef.target = target;
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
- if (target === WEBGL_CONSTANTS.ARRAY_BUFFER) {
548
- // Only define byteStride for vertex attributes.
549
- bufferViewDef.byteStride = attribute.itemSize * componentSize;
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
- this.byteOffset += byteLength;
553
- json.bufferViews.push(bufferViewDef); // @TODO Merge bufferViews where possible.
634
+ this.byteOffset += byteLength;
635
+ json.bufferViews.push(bufferViewDef); // @TODO Merge bufferViews where possible.
554
636
 
555
- const output = {
556
- id: json.bufferViews.length - 1,
557
- byteLength: 0
558
- };
559
- return output;
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
- reader.onloadend = () => {
576
- const buffer = getPaddedArrayBuffer(reader.result);
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
- let componentType; // Detect the component type of the attribute array (float, uint or ushort)
607
-
608
- if (attribute.array.constructor === Float32Array) {
609
- componentType = WEBGL_CONSTANTS.FLOAT;
610
- } else if (attribute.array.constructor === Uint32Array) {
611
- componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
612
- } else if (attribute.array.constructor === Uint16Array) {
613
- componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
614
- } else if (attribute.array.constructor === Uint8Array) {
615
- componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;
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
- if (options.truncateDrawRange && geometry !== undefined && geometry.index === null) {
624
- const end = start + count;
625
- const end2 = geometry.drawRange.count === Infinity ? attribute.count : geometry.drawRange.start + geometry.drawRange.count;
626
- start = Math.max(start, geometry.drawRange.start);
627
- count = Math.min(end, end2) - start;
628
- if (count < 0) count = 0;
629
- } // Skip creating an accessor if the attribute doesn't have data to export
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
- if (count === 0) return null;
633
- const minMax = getMinMax(attribute, start, count);
634
- let bufferViewTarget; // If geometry isn't provided, don't infer the target usage of the bufferView. For
635
- // animation samplers, target must not be set.
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 === true) accessorDef.normalized = true;
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
- if (options.embedImages) {
680
- const canvas = cachedCanvas = cachedCanvas || document.createElement('canvas');
681
- canvas.width = Math.min(image.width, options.maxTextureSize);
682
- canvas.height = Math.min(image.height, options.maxTextureSize);
683
- const ctx = canvas.getContext('2d');
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
- if (flipY === true) {
686
- ctx.translate(0, canvas.height);
687
- ctx.scale(1, -1);
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
- 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) {
691
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
692
- } else {
693
- if (format !== RGBAFormat && format !== RGBFormat) {
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
- if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) {
698
- console.warn('GLTFExporter: Image size is bigger than maxTextureSize', image);
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
- let data = image.data;
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
- if (format === RGBFormat) {
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
- if (options.binary === true) {
718
- pending.push(new Promise(resolve => {
719
- canvas.toBlob(blob => {
720
- writer.processBufferViewImage(blob).then(bufferViewIndex => {
721
- imageDef.bufferView = bufferViewIndex;
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
- }, mimeType);
725
- }));
726
- } else {
727
- imageDef.uri = canvas.toDataURL(mimeType);
728
- }
822
+ }
823
+ }, mimeType);
824
+ }));
729
825
  } else {
730
- imageDef.uri = image.src;
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
- * Process sampler
740
- * @param {Texture} map Texture to process
741
- * @return {Integer} Index of the processed texture in the "samplers" array
742
- */
743
- processSampler: function (map) {
744
- const json = this.json;
745
- if (!json.samplers) json.samplers = [];
746
- const samplerDef = {
747
- magFilter: THREE_TO_WEBGL[map.magFilter],
748
- minFilter: THREE_TO_WEBGL[map.minFilter],
749
- wrapS: THREE_TO_WEBGL[map.wrapS],
750
- wrapT: THREE_TO_WEBGL[map.wrapT]
751
- };
752
- return json.samplers.push(samplerDef) - 1;
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
- this._invokeAll(ext => {
772
- ext.writeTexture && ext.writeTexture(map, textureDef);
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
- const index = json.textures.push(textureDef) - 1;
776
- cache.textures.set(map, index);
777
- return index;
778
- },
872
+ this._invokeAll(function (ext) {
873
+ ext.writeTexture && ext.writeTexture(map, textureDef);
874
+ });
779
875
 
780
- /**
781
- * Process material
782
- * @param {THREE.Material} material Material to process
783
- * @return {Integer|null} Index of the processed material in the "materials" array
784
- */
785
- processMaterial: function (material) {
786
- const cache = this.cache;
787
- const json = this.json;
788
- if (cache.materials.has(material)) return cache.materials.get(material);
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
- if (!json.materials) json.materials = []; // @QUESTION Should we avoid including any attribute that has the default value?
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
- const materialDef = {
798
- pbrMetallicRoughness: {}
799
- };
892
+ if (material instanceof ShaderMaterial && material.isShaderMaterial) {
893
+ console.warn('GLTFExporter: THREE.ShaderMaterial not supported.');
894
+ return null;
895
+ }
800
896
 
801
- if (material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true) {
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
- if (material.isMeshStandardMaterial) {
813
- materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
814
- materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness;
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
- materialDef.pbrMetallicRoughness.metallicFactor = 0.5;
817
- materialDef.pbrMetallicRoughness.roughnessFactor = 0.5;
818
- } // pbrMetallicRoughness.metallicRoughnessTexture
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
- if (material.metalnessMap || material.roughnessMap) {
822
- if (material.metalnessMap === material.roughnessMap) {
823
- const metalRoughMapDef = {
824
- index: this.processTexture(material.metalnessMap)
825
- };
826
- this.applyTextureTransform(metalRoughMapDef, material.metalnessMap);
827
- materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
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 (material.map) {
835
- const baseColorMapDef = {
836
- index: this.processTexture(material.map)
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 (material.emissive) {
843
- // emissiveFactor
844
- const emissive = material.emissive.clone().multiplyScalar(material.emissiveIntensity).toArray();
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.normalMap) {
862
- const normalMapDef = {
863
- index: this.processTexture(material.normalMap)
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
- if (material.normalScale && material.normalScale.x !== -1) {
867
- if (material.normalScale.x !== material.normalScale.y) {
868
- console.warn('THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.');
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
- this.applyTextureTransform(normalMapDef, material.normalMap);
875
- materialDef.normalTexture = normalMapDef;
876
- } // occlusionTexture
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
- if (material.aoMap) {
880
- const occlusionMapDef = {
881
- index: this.processTexture(material.aoMap),
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
- this.applyTextureTransform(occlusionMapDef, material.aoMap);
890
- materialDef.occlusionTexture = occlusionMapDef;
891
- } // alphaMode
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
- if (material.transparent) {
895
- materialDef.alphaMode = 'BLEND';
896
- } else {
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
- if (material.side === DoubleSide) materialDef.doubleSided = true;
905
- if (material.name !== '') materialDef.name = material.name;
906
- this.serializeUserData(material, materialDef);
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
- const index = json.materials.push(materialDef) - 1;
913
- cache.materials.set(material, index);
914
- return index;
915
- },
1032
+ processMesh(mesh) {
1033
+ const cache = this.cache;
1034
+ const json = this.json;
1035
+ const meshCacheKeyParts = [mesh.geometry.uuid];
916
1036
 
917
- /**
918
- * Process mesh
919
- * @param {THREE.Mesh} mesh Mesh to process
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
- const meshCacheKey = meshCacheKeyParts.join(':');
936
- if (cache.meshes.has(meshCacheKey)) return cache.meshes.get(meshCacheKey);
937
- const geometry = mesh.geometry;
938
- let mode; // Use the correct mode
939
-
940
- if (mesh.isLineSegments) {
941
- mode = WEBGL_CONSTANTS.LINES;
942
- } else if (mesh.isLineLoop) {
943
- mode = WEBGL_CONSTANTS.LINE_LOOP;
944
- } else if (mesh.isLine) {
945
- mode = WEBGL_CONSTANTS.LINE_STRIP;
946
- } else if (mesh.isPoints) {
947
- mode = WEBGL_CONSTANTS.POINTS;
948
- } else {
949
- mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
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
- if (geometry.isBufferGeometry !== true) {
953
- throw new Error('THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.');
954
- }
1062
+ if (!geometry.isBufferGeometry) {
1063
+ throw new Error('THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.');
1064
+ }
955
1065
 
956
- const meshDef = {};
957
- const attributes = {};
958
- const primitives = [];
959
- const targets = []; // Conversion between attributes names in threejs and gltf spec
960
-
961
- const nameConversion = {
962
- uv: 'TEXCOORD_0',
963
- uv2: 'TEXCOORD_1',
964
- color: 'COLOR_0',
965
- skinWeight: 'WEIGHTS_0',
966
- skinIndex: 'JOINTS_0'
967
- };
968
- const originalNormal = geometry.getAttribute('normal');
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
- if (originalNormal !== undefined && !this.isNormalizedNormalAttribute(originalNormal)) {
971
- console.warn('THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.');
972
- geometry.setAttribute('normal', this.createNormalizedNormalAttribute(originalNormal));
973
- } // @QUESTION Detect if .vertexColors = true?
974
- // For every attribute create an accessor
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
- let modifiedAttribute = null;
1087
+ let modifiedAttribute = null;
978
1088
 
979
- for (let attributeName in geometry.attributes) {
980
- // Ignore morph target attributes, which are exported later.
981
- if (attributeName.substr(0, 5) === 'morph') continue;
982
- var attribute = geometry.attributes[attributeName];
983
- attributeName = nameConversion[attributeName] || attributeName.toUpperCase(); // Prefix all geometry attributes except the ones specifically
984
- // listed in the spec; non-spec attributes are considered custom.
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
- const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
987
- if (!validVertexAttributes.test(attributeName)) attributeName = `_${attributeName}`;
1096
+ const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
1097
+ if (!validVertexAttributes.test(attributeName)) attributeName = '_' + attributeName;
988
1098
 
989
- if (cache.attributes.has(this.getUID(attribute))) {
990
- attributes[attributeName] = cache.attributes.get(this.getUID(attribute));
991
- continue;
992
- } // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT.
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
- modifiedAttribute = null;
996
- const array = attribute.array;
1105
+ modifiedAttribute = null;
1106
+ const array = attribute.array;
997
1107
 
998
- if (attributeName === 'JOINTS_0' && !(array instanceof Uint16Array) && !(array instanceof Uint8Array)) {
999
- console.warn('GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.');
1000
- modifiedAttribute = new BufferAttribute(new Uint16Array(array), attribute.itemSize, attribute.normalized);
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
- const accessor = this.processAccessor(modifiedAttribute || attribute, geometry);
1113
+ const accessor = modifiedAttribute !== null && this.processAccessor(modifiedAttribute || attribute, geometry);
1004
1114
 
1005
- if (accessor !== null) {
1006
- attributes[attributeName] = accessor;
1007
- cache.attributes.set(this.getUID(attribute), accessor);
1008
- }
1115
+ if (accessor) {
1116
+ attributes[attributeName] = accessor;
1117
+ cache.attributes.set(this.getUID(attribute), accessor);
1009
1118
  }
1119
+ }
1010
1120
 
1011
- if (originalNormal !== undefined) geometry.setAttribute('normal', originalNormal); // Skip if no exportable attributes found
1121
+ if (originalNormal !== undefined) geometry.setAttribute('normal', originalNormal); // Skip if no exportable attributes found
1012
1122
 
1013
- if (Object.keys(attributes).length === 0) return null; // Morph targets
1123
+ if (Object.keys(attributes).length === 0) return null; // Morph targets
1014
1124
 
1015
- if (mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0) {
1016
- const weights = [];
1017
- const targetNames = [];
1018
- const reverseDictionary = {};
1125
+ if (mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0) {
1126
+ const weights = [];
1127
+ const targetNames = [];
1128
+ const reverseDictionary = {};
1019
1129
 
1020
- if (mesh.morphTargetDictionary !== undefined) {
1021
- for (let key in mesh.morphTargetDictionary) {
1022
- reverseDictionary[mesh.morphTargetDictionary[key]] = key;
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
- for (let i = 0; i < mesh.morphTargetInfluences.length; ++i) {
1027
- const target = {};
1028
- let warned = false;
1029
-
1030
- for (let attributeName in geometry.morphAttributes) {
1031
- // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
1032
- // Three.js doesn't support TANGENT yet.
1033
- if (attributeName !== 'position' && attributeName !== 'normal') {
1034
- if (!warned) {
1035
- console.warn('GLTFExporter: Only POSITION and NORMAL morph are supported.');
1036
- warned = true;
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
- var attribute = geometry.morphAttributes[attributeName][i];
1043
- const gltfAttributeName = attributeName.toUpperCase(); // Three.js morph attribute has absolute values while the one of glTF has relative values.
1044
- //
1045
- // glTF 2.0 Specification:
1046
- // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets
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
- const baseAttribute = geometry.attributes[attributeName];
1158
+ const baseAttribute = geometry.attributes[attributeName];
1049
1159
 
1050
- if (cache.attributes.has(this.getUID(attribute))) {
1051
- target[gltfAttributeName] = cache.attributes.get(this.getUID(attribute));
1052
- continue;
1053
- } // Clones attribute not to override
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
- const relativeAttribute = attribute.clone();
1166
+ const relativeAttribute = attribute.clone();
1057
1167
 
1058
- if (!geometry.morphTargetsRelative) {
1059
- for (let j = 0, jl = attribute.count; j < jl; j++) {
1060
- relativeAttribute.setXYZ(j, attribute.getX(j) - baseAttribute.getX(j), attribute.getY(j) - baseAttribute.getY(j), attribute.getZ(j) - baseAttribute.getZ(j));
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
- target[gltfAttributeName] = this.processAccessor(relativeAttribute, geometry);
1065
- cache.attributes.set(this.getUID(baseAttribute), target[gltfAttributeName]);
1176
+ if (accessor != undefined) {
1177
+ target[gltfAttributeName] = accessor;
1066
1178
  }
1067
1179
 
1068
- targets.push(target);
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
- meshDef.weights = weights;
1183
+ targets.push(target);
1184
+ weights.push(mesh.morphTargetInfluences[i]);
1185
+ if (mesh.morphTargetDictionary !== undefined) targetNames.push(reverseDictionary[i]);
1186
+ }
1074
1187
 
1075
- if (targetNames.length > 0) {
1076
- meshDef.extras = {};
1077
- meshDef.extras.targetNames = targetNames;
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
- const isMultiMaterial = Array.isArray(mesh.material);
1082
- if (isMultiMaterial && geometry.groups.length === 0) return null;
1083
- const materials = isMultiMaterial ? mesh.material : [mesh.material];
1084
- const groups = isMultiMaterial ? geometry.groups : [{
1085
- materialIndex: 0,
1086
- start: undefined,
1087
- count: undefined
1088
- }];
1089
-
1090
- for (let i = 0, il = groups.length; i < il; i++) {
1091
- const primitive = {
1092
- mode,
1093
- attributes
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
- if (targets.length > 0) primitive.targets = targets;
1213
+ }
1097
1214
 
1098
- if (geometry.index !== null) {
1099
- let cacheKey = this.getUID(geometry.index);
1215
+ if (targets.length > 0) primitive.targets = targets;
1100
1216
 
1101
- if (groups[i].start !== undefined || groups[i].count !== undefined) {
1102
- cacheKey += `:${groups[i].start}:${groups[i].count}`;
1103
- }
1217
+ if (geometry.index !== null) {
1218
+ let cacheKey = this.getUID(geometry.index);
1104
1219
 
1105
- if (cache.attributes.has(cacheKey)) {
1106
- primitive.indices = cache.attributes.get(cacheKey);
1107
- } else {
1108
- primitive.indices = this.processAccessor(geometry.index, geometry, groups[i].start, groups[i].count);
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
- if (primitive.indices === null) delete primitive.indices;
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
- const material = this.processMaterial(materials[groups[i].materialIndex]);
1116
- if (material !== null) primitive.material = material;
1117
- primitives.push(primitive);
1232
+ if (primitive.indices === null) delete primitive.indices;
1118
1233
  }
1119
1234
 
1120
- meshDef.primitives = primitives;
1121
- if (!json.meshes) json.meshes = [];
1235
+ const materialIndex = groups[i].materialIndex;
1122
1236
 
1123
- this._invokeAll(ext => {
1124
- ext.writeMesh && ext.writeMesh(mesh, meshDef);
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
- * Process camera
1134
- * @param {THREE.Camera} camera Camera to process
1135
- * @return {Integer} Index of the processed mesh in the "camera" array
1136
- */
1137
- processCamera: function (camera) {
1138
- const json = this.json;
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
- if (isOrtho) {
1146
- cameraDef.orthographic = {
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
- if (camera.name !== '') cameraDef.name = camera.type;
1163
- return json.cameras.push(cameraDef) - 1;
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
- if (!trackNode || !trackProperty) {
1200
- console.warn('THREE.GLTFExporter: Could not export animation track "%s".', track.name);
1201
- return null;
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
- const inputItemSize = 1;
1205
- let outputItemSize = track.values.length / track.times.length;
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
- let interpolation; // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE
1212
- // Detecting glTF cubic spline interpolant by checking factory method's special property
1213
- // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
1214
- // valid value from .getInterpolation().
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
- outputItemSize /= 3;
1222
- } else if (track.getInterpolation() === InterpolateDiscrete) {
1223
- interpolation = 'STEP';
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
- interpolation = 'LINEAR';
1325
+ trackNode = undefined;
1226
1326
  }
1327
+ }
1227
1328
 
1228
- samplers.push({
1229
- input: this.processAccessor(new BufferAttribute(track.times, inputItemSize)),
1230
- output: this.processAccessor(new BufferAttribute(track.values, outputItemSize)),
1231
- interpolation
1232
- });
1233
- channels.push({
1234
- sampler: samplers.length - 1,
1235
- target: {
1236
- node: nodeMap.get(trackNode),
1237
- path: trackProperty
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
- json.animations.push({
1243
- name: clip.name || `clip_${json.animations.length}`,
1244
- samplers,
1245
- channels
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
- return json.animations.length - 1;
1248
- },
1371
+ }
1249
1372
 
1250
- /**
1251
- * @param {THREE.Object3D} object
1252
- * @return {number|null}
1253
- */
1254
- processSkin: function (object) {
1255
- const json = this.json;
1256
- const nodeMap = this.nodeMap;
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
- if (!equalArray(position, [0, 0, 0])) {
1304
- nodeDef.translation = position;
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
- if (!equalArray(scale, [1, 1, 1])) {
1308
- nodeDef.scale = scale;
1309
- }
1310
- } else {
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
- if (isIdentityMatrix(object.matrix) === false) {
1316
- nodeDef.matrix = object.matrix.elements;
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 (object.name !== '') nodeDef.name = String(object.name);
1322
- this.serializeUserData(object, nodeDef);
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 (object.isMesh || object.isLine || object.isPoints) {
1325
- const meshIndex = this.processMesh(object);
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
- if (object.isSkinnedMesh) this.skins.push(object);
1459
+ if (object.name !== '') nodeDef.name = String(object.name);
1460
+ this.serializeUserData(object, nodeDef);
1332
1461
 
1333
- if (object.children.length > 0) {
1334
- const children = [];
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
- for (let i = 0, l = object.children.length; i < l; i++) {
1337
- const child = object.children[i];
1469
+ if (object instanceof SkinnedMesh && object.isSkinnedMesh) this.skins.push(object);
1338
1470
 
1339
- if (child.visible || options.onlyVisible === false) {
1340
- var nodeIndex = this.processNode(child);
1341
- if (nodeIndex !== null) children.push(nodeIndex);
1342
- }
1343
- }
1471
+ if (object.children.length > 0) {
1472
+ const children = [];
1344
1473
 
1345
- if (children.length > 0) nodeDef.children = children;
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
- this._invokeAll(ext => {
1349
- ext.writeNode && ext.writeNode(object, nodeDef);
1350
- });
1483
+ if (children.length > 0) nodeDef.children = children;
1484
+ }
1351
1485
 
1352
- var nodeIndex = json.nodes.push(nodeDef) - 1;
1353
- nodeMap.set(object, nodeIndex);
1354
- return nodeIndex;
1355
- },
1486
+ this._invokeAll(function (ext) {
1487
+ ext.writeNode && ext.writeNode(object, nodeDef);
1488
+ });
1356
1489
 
1357
- /**
1358
- * Process Scene
1359
- * @param {Scene} node Scene to process
1360
- */
1361
- processScene: function (scene) {
1362
- const json = this.json;
1363
- const options = this.options;
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
- const sceneDef = {};
1371
- if (scene.name !== '') sceneDef.name = scene.name;
1372
- json.scenes.push(sceneDef);
1373
- const nodes = [];
1500
+ processScene(scene) {
1501
+ const json = this.json;
1502
+ const options = this.options;
1374
1503
 
1375
- for (let i = 0, l = scene.children.length; i < l; i++) {
1376
- const child = scene.children[i];
1504
+ if (!json.scenes) {
1505
+ json.scenes = [];
1506
+ json.scene = 0;
1507
+ }
1377
1508
 
1378
- if (child.visible || options.onlyVisible === false) {
1379
- const nodeIndex = this.processNode(child);
1380
- if (nodeIndex !== null) nodes.push(nodeIndex);
1381
- }
1382
- }
1509
+ const sceneDef = {};
1510
+ if (scene.name !== '') sceneDef.name = scene.name;
1511
+ json.scenes.push(sceneDef);
1512
+ const nodes = [];
1383
1513
 
1384
- if (nodes.length > 0) sceneDef.nodes = nodes;
1385
- this.serializeUserData(scene, sceneDef);
1386
- },
1514
+ for (let i = 0, l = scene.children.length; i < l; i++) {
1515
+ const child = scene.children[i];
1387
1516
 
1388
- /**
1389
- * Creates a Scene to hold a list of objects and parse it
1390
- * @param {Array} objects List of objects to process
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
- this.processScene(scene);
1403
- },
1523
+ if (nodes.length > 0) sceneDef.nodes = nodes;
1524
+ this.serializeUserData(scene, sceneDef);
1525
+ }
1404
1526
 
1405
- /**
1406
- * @param {THREE.Object3D|Array<THREE.Object3D>} input
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
- this._invokeAll(ext => {
1413
- ext.beforeParse && ext.beforeParse(input);
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
- const objectsWithoutScene = [];
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
- if (objectsWithoutScene.length > 0) this.processObjects(objectsWithoutScene);
1544
+ processInput(input) {
1545
+ const options = this.options;
1546
+ input = input instanceof Array ? input : [input];
1427
1547
 
1428
- for (let i = 0; i < this.skins.length; ++i) {
1429
- this.processSkin(this.skins[i]);
1430
- }
1548
+ this._invokeAll(function (ext) {
1549
+ ext.beforeParse && ext.beforeParse(input);
1550
+ });
1551
+
1552
+ const objectsWithoutScene = [];
1431
1553
 
1432
- for (let i = 0; i < options.animations.length; ++i) {
1433
- this.processAnimation(options.animations[i], input[0]);
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
- this._invokeAll(ext => {
1437
- ext.afterParse && ext.afterParse(input);
1438
- });
1439
- },
1440
- _invokeAll: function (func) {
1441
- for (let i = 0, il = this.plugins.length; i < il; i++) {
1442
- func(this.plugins[i]);
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
- * Punctual Lights Extension
1663
+ * Returns a buffer aligned to 4-byte boundary.
1448
1664
  *
1449
- * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
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
- function GLTFLightExtension(writer) {
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
- GLTFLightExtension.prototype = {
1458
- constructor: GLTFLightExtension,
1459
- writeNode: function (light, nodeDef) {
1460
- if (!light.isLight) return;
1708
+ writeNode(light, nodeDef) {
1709
+ if (!light.isLight) return;
1461
1710
 
1462
- if (!light.isDirectionalLight && !light.isPointLight && !light.isSpotLight) {
1463
- console.warn('THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light);
1464
- return;
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
- const writer = this.writer;
1468
- const json = writer.json;
1469
- const extensionsUsed = writer.extensionsUsed;
1470
- const lightDef = {};
1471
- if (light.name) lightDef.name = light.name;
1472
- lightDef.color = light.color.toArray();
1473
- lightDef.intensity = light.intensity;
1474
-
1475
- if (light.isDirectionalLight) {
1476
- lightDef.type = 'directional';
1477
- } else if (light.isPointLight) {
1478
- lightDef.type = 'point';
1479
- if (light.distance > 0) lightDef.range = light.distance;
1480
- } else if (light.isSpotLight) {
1481
- lightDef.type = 'spot';
1482
- if (light.distance > 0) lightDef.range = light.distance;
1483
- lightDef.spot = {};
1484
- lightDef.spot.innerConeAngle = (light.penumbra - 1.0) * light.angle * -1.0;
1485
- lightDef.spot.outerConeAngle = light.angle;
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
- if (light.decay !== undefined && light.decay !== 2) {
1489
- console.warn('THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.');
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
- if (light.target && (light.target.parent !== light || light.target.position.x !== 0 || light.target.position.y !== 0 || light.target.position.z !== -1)) {
1493
- 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.');
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
- if (!extensionsUsed[this.name]) {
1497
- json.extensions = json.extensions || {};
1498
- json.extensions[this.name] = {
1499
- lights: []
1500
- };
1501
- extensionsUsed[this.name] = true;
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
- * Unlit Materials Extension
1514
- *
1515
- * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
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
- GLTFMaterialsUnlitExtension.prototype = {
1524
- constructor: GLTFMaterialsUnlitExtension,
1525
- writeMaterial: function (material, materialDef) {
1526
- if (!material.isMeshBasicMaterial) return;
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
- function GLTFMaterialsPBRSpecularGlossiness(writer) {
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
- GLTFMaterialsPBRSpecularGlossiness.prototype = {
1548
- constructor: GLTFMaterialsPBRSpecularGlossiness,
1549
- writeMaterial: function (material, materialDef) {
1550
- if (!material.isGLTFSpecularGlossinessMaterial) return;
1551
- const writer = this.writer;
1552
- const extensionsUsed = writer.extensionsUsed;
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
- if (materialDef.pbrMetallicRoughness.baseColorFactor) {
1556
- extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor;
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 = material.glossiness;
1563
-
1564
- if (materialDef.pbrMetallicRoughness.baseColorTexture) {
1565
- extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture;
1566
- }
1831
+ extensionDef.glossinessFactor = // @ts-expect-error
1832
+ material.glossiness;
1833
+ }
1567
1834
 
1568
- if (material.specularMap) {
1569
- const specularMapDef = {
1570
- index: writer.processTexture(material.specularMap)
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
- materialDef.extensions = materialDef.extensions || {};
1577
- materialDef.extensions[this.name] = extensionDef;
1578
- extensionsUsed[this.name] = true;
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
- GLTFExporter.Utils = {
1586
- insertKeyframe: function (track, time) {
1587
- const tolerance = 0.001; // 1ms
1847
+ materialDef.extensions = materialDef.extensions || {};
1848
+ materialDef.extensions[this.name] = extensionDef;
1849
+ extensionsUsed[this.name] = true;
1850
+ }
1588
1851
 
1589
- const valueSize = track.getValueSize();
1590
- const times = new track.TimeBufferType(track.times.length + 1);
1591
- const values = new track.ValueBufferType(track.values.length + valueSize);
1592
- const interpolant = track.createInterpolant(new track.ValueBufferType(valueSize));
1593
- let index;
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
- for (let i = 0; i < valueSize; i++) {
1599
- values[i] = 0;
1600
- }
1860
+ class GLTFMaterialsTransmissionExtension {
1861
+ constructor(writer) {
1862
+ _defineProperty(this, "writer", void 0);
1601
1863
 
1602
- index = 0;
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
- times[times.length - 1] = time;
1616
- times.set(track.times, 0);
1617
- values.set(track.values, 0);
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
- if (sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear) {
1658
- if (sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) {
1659
- // This should never happen, because glTF morph target animations
1660
- // affect all targets already.
1661
- throw new Error('THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.');
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
- console.warn('THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.');
1665
- sourceTrack = sourceTrack.clone();
1666
- sourceTrack.setInterpolation(InterpolateLinear);
1667
- }
1876
+ const writer = this.writer;
1877
+ const extensionsUsed = writer.extensionsUsed;
1878
+ const extensionDef = {};
1879
+ extensionDef.transmissionFactor = material.transmission;
1668
1880
 
1669
- const targetCount = sourceTrackNode.morphTargetInfluences.length;
1670
- const targetIndex = sourceTrackNode.morphTargetDictionary[sourceTrackBinding.propertyIndex];
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
- if (targetIndex === undefined) {
1673
- throw new Error(`THREE.GLTFExporter: Morph target name not found: ${sourceTrackBinding.propertyIndex}`);
1674
- }
1889
+ materialDef.extensions = materialDef.extensions || {};
1890
+ materialDef.extensions[this.name] = extensionDef;
1891
+ extensionsUsed[this.name] = true;
1892
+ }
1675
1893
 
1676
- let mergedTrack; // If this is the first time we've seen this object, create a new
1677
- // track to store merged keyframe data for each morph target.
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
- for (let j = 0; j < mergedTrack.times.length; j++) {
1684
- values[j * targetCount + targetIndex] = mergedTrack.values[j];
1685
- } // We need to take into consideration the intended target node
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
- mergedTrack.name = `${sourceTrackBinding.nodeName || ''}.morphTargetInfluences`;
1690
- mergedTrack.values = values;
1691
- mergedTracks[sourceTrackNode.uuid] = mergedTrack;
1692
- tracks.push(mergedTrack);
1693
- continue;
1694
- }
1908
+ this.writer = writer;
1909
+ this.name = 'KHR_materials_volume';
1910
+ }
1695
1911
 
1696
- const sourceInterpolant = sourceTrack.createInterpolant(new sourceTrack.ValueBufferType(1));
1697
- mergedTrack = mergedTracks[sourceTrackNode.uuid]; // For every existing keyframe of the merged track, write a (possibly
1698
- // interpolated) value from the source track.
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
- for (let j = 0; j < mergedTrack.times.length; j++) {
1701
- mergedTrack.values[j * targetCount + targetIndex] = sourceInterpolant.evaluate(mergedTrack.times[j]);
1702
- } // For every existing keyframe of the source track, write a (possibly
1703
- // new) keyframe to the merged track. Values from the previous loop may
1704
- // be written again, but keyframes are de-duplicated.
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
- for (let j = 0; j < sourceTrack.times.length; j++) {
1708
- const keyframeIndex = this.insertKeyframe(mergedTrack, sourceTrack.times[j]);
1709
- mergedTrack.values[keyframeIndex * targetCount + targetIndex] = sourceTrack.values[j];
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
- clip.tracks = tracks;
1714
- return clip;
1715
- }
1716
- };
1717
- return GLTFExporter;
1718
- })();
1944
+ }
1719
1945
 
1720
1946
  export { GLTFExporter };