├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .npmrc
├── LICENSE
├── README.md
├── build
└── webpack.config.js
├── example
├── app.js
├── app.json
├── app.wxss
├── index
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── loaders
│ └── gltf-loader.js
├── miniprogram_npm
│ └── threejs-miniprogram
│ │ └── index.js
├── package.json
├── project.config.json
├── sitemap.json
└── test-cases
│ ├── cube.js
│ ├── cubes.js
│ ├── model.js
│ ├── orbit.js
│ └── sphere.js
├── package-lock.json
├── package.json
└── src
├── EventTarget.js
├── Node.js
├── XMLHttpRequest.js
├── copyProperties.js
└── index.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: "babel-eslint",
4 | parserOptions: {
5 | ecmaVersion: 2018,
6 | sourceType: 'module'
7 | },
8 | env: {
9 | es6: true,
10 | node: true
11 | },
12 | extends: [
13 | "eslint:recommended"
14 | ],
15 | globals: {
16 | 'XMLHttpRequest': true,
17 | 'window': true,
18 | 'document': true,
19 | 'navigator': true,
20 | 'wx': true
21 | },
22 | rules: {
23 | 'no-console': process.env.NODE_ENV !== 'production' ? 0 : 2,
24 | 'no-useless-escape': 0,
25 | 'no-empty': 0
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 |
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 |
9 | node_modules
10 | coverage
11 | dist
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | build
2 | example
3 | src
4 | .eslintrc.js
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 wechat-miniprogram
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # threejs-miniprogram
2 | Three.js 小程序 WebGL 的适配版本。
3 |
4 | ## 使用
5 |
6 | 可参考 example 目录下的示例项目或参照以下流程:
7 |
8 | 1. 通过 npm 安装
9 |
10 | ```
11 | npm install --save threejs-miniprogram
12 | ```
13 | 安装完成之后在微信开发者工具中点击构建 npm。
14 |
15 | 2. 导入小程序适配版本的 Three.js
16 |
17 | ```javascript
18 | import {createScopedThreejs} from 'threejs-miniprogram'
19 |
20 | Page({
21 | onReady() {
22 | wx.createSelectorQuery()
23 | .select('#webgl')
24 | .node()
25 | .exec((res) => {
26 | const canvas = res[0].node
27 | // 创建一个与 canvas 绑定的 three.js
28 | const THREE = createScopedThreejs(canvas)
29 | // 传递并使用 THREE 变量
30 | })
31 | }
32 | })
33 | ```
34 |
35 | ## 说明
36 |
37 | - 本项目当前使用的 Three.js 版本号为 0.108.0,如要更新 threejs 版本可发 PR 修改或 fork 后自行修改。
38 | - 该适配版本的 THREE 不在全局环境中,如使用 Three.js 的其他配套类库,需要自行传入 THREE 到类库中。
39 | - 如在使用过程中发现有适配问题,可通过 issue 反馈或发 PR 修复。
40 |
--------------------------------------------------------------------------------
/build/webpack.config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const StringReplacePlugin = require("string-replace-webpack-plugin");
4 |
5 | function resolveThreeModule() {
6 | const threePath = require.resolve('three')
7 | const code = fs.readFileSync(path.resolve(threePath), 'utf8')
8 | return code
9 | }
10 |
11 | module.exports = {
12 | entry: path.join(__dirname, '../src/index'),
13 | target: 'web',
14 | output: {
15 | path: path.join(__dirname, '../dist'),
16 | filename: 'index.js',
17 | libraryTarget: 'commonjs',
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.js$/,
23 | exclude: /node_modules/,
24 | loader: 'babel-loader',
25 | options: {
26 | presets: [
27 | "@babel/preset-env",
28 | ],
29 | plugins: ["@babel/plugin-proposal-class-properties"]
30 | }
31 | },
32 | {
33 | test: /\index.js$/,
34 | loader: StringReplacePlugin.replace({
35 | replacements: [
36 | {
37 | pattern: /__INJECT_THREE__/ig,
38 | replacement: () => {
39 | return resolveThreeModule()
40 | }
41 | }
42 | ]
43 | })
44 | }
45 | ]
46 | },
47 | plugins: [
48 | // an instance of the plugin must be present
49 | new StringReplacePlugin()
50 | ],
51 | optimization:{
52 | minimize: true,
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/example/app.js:
--------------------------------------------------------------------------------
1 | App({
2 | onLaunch: function () {
3 |
4 | }
5 | })
6 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "index/index"
4 | ],
5 | "window": {
6 | "backgroundTextStyle": "light",
7 | "navigationBarBackgroundColor": "#fff",
8 | "navigationBarTitleText": "WeChat",
9 | "navigationBarTextStyle": "black"
10 | },
11 | "sitemapLocation": "sitemap.json"
12 | }
--------------------------------------------------------------------------------
/example/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | page {
3 | width: 100%;
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .container {
10 | height: 100%;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: space-between;
15 | padding: 200rpx 0;
16 | box-sizing: border-box;
17 | }
18 |
--------------------------------------------------------------------------------
/example/index/index.js:
--------------------------------------------------------------------------------
1 | // const { createScopedThreejs } = require('threejs-miniprogram')
2 | import { createScopedThreejs } from 'threejs-miniprogram'
3 |
4 | const { renderCube } = require('../test-cases/cube')
5 | const { renderCubes } = require('../test-cases/cubes')
6 | const { renderSphere } = require('../test-cases/sphere')
7 | const { renderModel } = require('../test-cases/model')
8 |
9 | const app = getApp()
10 |
11 | Page({
12 | data: {},
13 | onLoad: function () {
14 | wx.createSelectorQuery()
15 | .select('#webgl')
16 | .node()
17 | .exec((res) => {
18 | const canvas = res[0].node
19 | this.canvas = canvas
20 | const THREE = createScopedThreejs(canvas)
21 |
22 | // renderSphere(canvas, THREE)
23 | // renderCube(canvas, THREE)
24 | // renderCubes(canvas, THREE)
25 | renderModel(canvas, THREE)
26 | })
27 | },
28 | touchStart(e) {
29 | this.canvas.dispatchTouchEvent({...e, type:'touchstart'})
30 | },
31 | touchMove(e) {
32 | this.canvas.dispatchTouchEvent({...e, type:'touchmove'})
33 | },
34 | touchEnd(e) {
35 | this.canvas.dispatchTouchEvent({...e, type:'touchend'})
36 | }
37 | })
38 |
--------------------------------------------------------------------------------
/example/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/example/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
15 |
--------------------------------------------------------------------------------
/example/index/index.wxss:
--------------------------------------------------------------------------------
1 | .intro {
2 | margin: 30px;
3 | text-align: center;
4 | }
--------------------------------------------------------------------------------
/example/loaders/gltf-loader.js:
--------------------------------------------------------------------------------
1 | export function registerGLTFLoader(THREE) {
2 |
3 | /**
4 | * @author Rich Tibbett / https://github.com/richtr
5 | * @author mrdoob / http://mrdoob.com/
6 | * @author Tony Parisi / http://www.tonyparisi.com/
7 | * @author Takahiro / https://github.com/takahirox
8 | * @author Don McCurdy / https://www.donmccurdy.com
9 | */
10 |
11 | THREE.GLTFLoader = (function () {
12 |
13 | function GLTFLoader(manager) {
14 |
15 | this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
16 | this.dracoLoader = null;
17 | this.ddsLoader = null;
18 |
19 | }
20 |
21 | GLTFLoader.prototype = {
22 |
23 | constructor: GLTFLoader,
24 |
25 | crossOrigin: 'anonymous',
26 |
27 | load: function (url, onLoad, onProgress, onError) {
28 |
29 | var scope = this;
30 |
31 | var resourcePath;
32 |
33 | if (this.resourcePath !== undefined) {
34 |
35 | resourcePath = this.resourcePath;
36 |
37 | } else if (this.path !== undefined) {
38 |
39 | resourcePath = this.path;
40 |
41 | } else {
42 |
43 | resourcePath = THREE.LoaderUtils.extractUrlBase(url);
44 |
45 | }
46 |
47 | // Tells the LoadingManager to track an extra item, which resolves after
48 | // the model is fully loaded. This means the count of items loaded will
49 | // be incorrect, but ensures manager.onLoad() does not fire early.
50 | scope.manager.itemStart(url);
51 |
52 | var _onError = function (e) {
53 |
54 | if (onError) {
55 |
56 | onError(e);
57 |
58 | } else {
59 |
60 | console.error(e);
61 |
62 | }
63 |
64 | scope.manager.itemError(url);
65 | scope.manager.itemEnd(url);
66 |
67 | };
68 |
69 | var loader = new THREE.FileLoader(scope.manager);
70 |
71 | loader.setPath(this.path);
72 | loader.setResponseType('arraybuffer');
73 |
74 | if (scope.crossOrigin === 'use-credentials') {
75 |
76 | loader.setWithCredentials(true);
77 |
78 | }
79 |
80 | loader.load(url, function (data) {
81 |
82 | try {
83 |
84 | scope.parse(data, resourcePath, function (gltf) {
85 |
86 | onLoad(gltf);
87 |
88 | scope.manager.itemEnd(url);
89 |
90 | }, _onError);
91 |
92 | } catch (e) {
93 |
94 | _onError(e);
95 |
96 | }
97 |
98 | }, onProgress, _onError);
99 |
100 | },
101 |
102 | setCrossOrigin: function (value) {
103 |
104 | this.crossOrigin = value;
105 | return this;
106 |
107 | },
108 |
109 | setPath: function (value) {
110 |
111 | this.path = value;
112 | return this;
113 |
114 | },
115 |
116 | setResourcePath: function (value) {
117 |
118 | this.resourcePath = value;
119 | return this;
120 |
121 | },
122 |
123 | setDRACOLoader: function (dracoLoader) {
124 |
125 | this.dracoLoader = dracoLoader;
126 | return this;
127 |
128 | },
129 |
130 | setDDSLoader: function (ddsLoader) {
131 |
132 | this.ddsLoader = ddsLoader;
133 | return this;
134 |
135 | },
136 |
137 | parse: function (data, path, onLoad, onError) {
138 |
139 | var content;
140 | var extensions = {};
141 |
142 | if (typeof data === 'string') {
143 |
144 | content = data;
145 |
146 | } else {
147 |
148 | var magic = THREE.LoaderUtils.decodeText(new Uint8Array(data, 0, 4));
149 |
150 | if (magic === BINARY_EXTENSION_HEADER_MAGIC) {
151 |
152 | try {
153 |
154 | extensions[EXTENSIONS.KHR_BINARY_GLTF] = new GLTFBinaryExtension(data);
155 |
156 | } catch (error) {
157 |
158 | if (onError) onError(error);
159 | return;
160 |
161 | }
162 |
163 | content = extensions[EXTENSIONS.KHR_BINARY_GLTF].content;
164 |
165 | } else {
166 |
167 | content = THREE.LoaderUtils.decodeText(new Uint8Array(data));
168 |
169 | }
170 |
171 | }
172 |
173 | var json = JSON.parse(content);
174 |
175 | if (json.asset === undefined || json.asset.version[0] < 2) {
176 |
177 | if (onError) onError(new Error('THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.'));
178 | return;
179 |
180 | }
181 |
182 | if (json.extensionsUsed) {
183 |
184 | for (var i = 0; i < json.extensionsUsed.length; ++i) {
185 |
186 | var extensionName = json.extensionsUsed[i];
187 | var extensionsRequired = json.extensionsRequired || [];
188 |
189 | switch (extensionName) {
190 |
191 | case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
192 | extensions[extensionName] = new GLTFLightsExtension(json);
193 | break;
194 |
195 | case EXTENSIONS.KHR_MATERIALS_UNLIT:
196 | extensions[extensionName] = new GLTFMaterialsUnlitExtension();
197 | break;
198 |
199 | case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
200 | extensions[extensionName] = new GLTFMaterialsPbrSpecularGlossinessExtension();
201 | break;
202 |
203 | case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
204 | extensions[extensionName] = new GLTFDracoMeshCompressionExtension(json, this.dracoLoader);
205 | break;
206 |
207 | case EXTENSIONS.MSFT_TEXTURE_DDS:
208 | extensions[EXTENSIONS.MSFT_TEXTURE_DDS] = new GLTFTextureDDSExtension(this.ddsLoader);
209 | break;
210 |
211 | case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
212 | extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM] = new GLTFTextureTransformExtension();
213 | break;
214 |
215 | default:
216 |
217 | if (extensionsRequired.indexOf(extensionName) >= 0) {
218 |
219 | console.warn('THREE.GLTFLoader: Unknown extension "' + extensionName + '".');
220 |
221 | }
222 |
223 | }
224 |
225 | }
226 |
227 | }
228 |
229 | var parser = new GLTFParser(json, extensions, {
230 |
231 | path: path || this.resourcePath || '',
232 | crossOrigin: this.crossOrigin,
233 | manager: this.manager
234 |
235 | });
236 |
237 | parser.parse(onLoad, onError);
238 |
239 | }
240 |
241 | };
242 |
243 | /* GLTFREGISTRY */
244 |
245 | function GLTFRegistry() {
246 |
247 | var objects = {};
248 |
249 | return {
250 |
251 | get: function (key) {
252 |
253 | return objects[key];
254 |
255 | },
256 |
257 | add: function (key, object) {
258 |
259 | objects[key] = object;
260 |
261 | },
262 |
263 | remove: function (key) {
264 |
265 | delete objects[key];
266 |
267 | },
268 |
269 | removeAll: function () {
270 |
271 | objects = {};
272 |
273 | }
274 |
275 | };
276 |
277 | }
278 |
279 | /*********************************/
280 | /********** EXTENSIONS ***********/
281 | /*********************************/
282 |
283 | var EXTENSIONS = {
284 | KHR_BINARY_GLTF: 'KHR_binary_glTF',
285 | KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
286 | KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
287 | KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
288 | KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
289 | KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
290 | MSFT_TEXTURE_DDS: 'MSFT_texture_dds'
291 | };
292 |
293 | /**
294 | * DDS Texture Extension
295 | *
296 | * Specification:
297 | * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds
298 | *
299 | */
300 | function GLTFTextureDDSExtension(ddsLoader) {
301 |
302 | if (!ddsLoader) {
303 |
304 | throw new Error('THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader');
305 |
306 | }
307 |
308 | this.name = EXTENSIONS.MSFT_TEXTURE_DDS;
309 | this.ddsLoader = ddsLoader;
310 |
311 | }
312 |
313 | /**
314 | * Lights Extension
315 | *
316 | * Specification: PENDING
317 | */
318 | function GLTFLightsExtension(json) {
319 |
320 | this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
321 |
322 | var extension = (json.extensions && json.extensions[EXTENSIONS.KHR_LIGHTS_PUNCTUAL]) || {};
323 | this.lightDefs = extension.lights || [];
324 |
325 | }
326 |
327 | GLTFLightsExtension.prototype.loadLight = function (lightIndex) {
328 |
329 | var lightDef = this.lightDefs[lightIndex];
330 | var lightNode;
331 |
332 | var color = new THREE.Color(0xffffff);
333 | if (lightDef.color !== undefined) color.fromArray(lightDef.color);
334 |
335 | var range = lightDef.range !== undefined ? lightDef.range : 0;
336 |
337 | switch (lightDef.type) {
338 |
339 | case 'directional':
340 | lightNode = new THREE.DirectionalLight(color);
341 | lightNode.target.position.set(0, 0, - 1);
342 | lightNode.add(lightNode.target);
343 | break;
344 |
345 | case 'point':
346 | lightNode = new THREE.PointLight(color);
347 | lightNode.distance = range;
348 | break;
349 |
350 | case 'spot':
351 | lightNode = new THREE.SpotLight(color);
352 | lightNode.distance = range;
353 | // Handle spotlight properties.
354 | lightDef.spot = lightDef.spot || {};
355 | lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
356 | lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
357 | lightNode.angle = lightDef.spot.outerConeAngle;
358 | lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
359 | lightNode.target.position.set(0, 0, - 1);
360 | lightNode.add(lightNode.target);
361 | break;
362 |
363 | default:
364 | throw new Error('THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".');
365 |
366 | }
367 |
368 | // Some lights (e.g. spot) default to a position other than the origin. Reset the position
369 | // here, because node-level parsing will only override position if explicitly specified.
370 | lightNode.position.set(0, 0, 0);
371 |
372 | lightNode.decay = 2;
373 |
374 | if (lightDef.intensity !== undefined) lightNode.intensity = lightDef.intensity;
375 |
376 | lightNode.name = lightDef.name || ('light_' + lightIndex);
377 |
378 | return Promise.resolve(lightNode);
379 |
380 | };
381 |
382 | /**
383 | * Unlit Materials Extension (pending)
384 | *
385 | * PR: https://github.com/KhronosGroup/glTF/pull/1163
386 | */
387 | function GLTFMaterialsUnlitExtension() {
388 |
389 | this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
390 |
391 | }
392 |
393 | GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () {
394 |
395 | return THREE.MeshBasicMaterial;
396 |
397 | };
398 |
399 | GLTFMaterialsUnlitExtension.prototype.extendParams = function (materialParams, materialDef, parser) {
400 |
401 | var pending = [];
402 |
403 | materialParams.color = new THREE.Color(1.0, 1.0, 1.0);
404 | materialParams.opacity = 1.0;
405 |
406 | var metallicRoughness = materialDef.pbrMetallicRoughness;
407 |
408 | if (metallicRoughness) {
409 |
410 | if (Array.isArray(metallicRoughness.baseColorFactor)) {
411 |
412 | var array = metallicRoughness.baseColorFactor;
413 |
414 | materialParams.color.fromArray(array);
415 | materialParams.opacity = array[3];
416 |
417 | }
418 |
419 | if (metallicRoughness.baseColorTexture !== undefined) {
420 |
421 | pending.push(parser.assignTexture(materialParams, 'map', metallicRoughness.baseColorTexture));
422 |
423 | }
424 |
425 | }
426 |
427 | return Promise.all(pending);
428 |
429 | };
430 |
431 | /* BINARY EXTENSION */
432 | var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
433 | var BINARY_EXTENSION_HEADER_LENGTH = 12;
434 | var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };
435 |
436 | function GLTFBinaryExtension(data) {
437 |
438 | this.name = EXTENSIONS.KHR_BINARY_GLTF;
439 | this.content = null;
440 | this.body = null;
441 |
442 | var headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH);
443 |
444 | this.header = {
445 | magic: THREE.LoaderUtils.decodeText(new Uint8Array(data.slice(0, 4))),
446 | version: headerView.getUint32(4, true),
447 | length: headerView.getUint32(8, true)
448 | };
449 |
450 | if (this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC) {
451 |
452 | throw new Error('THREE.GLTFLoader: Unsupported glTF-Binary header.');
453 |
454 | } else if (this.header.version < 2.0) {
455 |
456 | throw new Error('THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.');
457 |
458 | }
459 |
460 | var chunkView = new DataView(data, BINARY_EXTENSION_HEADER_LENGTH);
461 | var chunkIndex = 0;
462 |
463 | while (chunkIndex < chunkView.byteLength) {
464 |
465 | var chunkLength = chunkView.getUint32(chunkIndex, true);
466 | chunkIndex += 4;
467 |
468 | var chunkType = chunkView.getUint32(chunkIndex, true);
469 | chunkIndex += 4;
470 |
471 | if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON) {
472 |
473 | var contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength);
474 | this.content = THREE.LoaderUtils.decodeText(contentArray);
475 |
476 | } else if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN) {
477 |
478 | var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
479 | this.body = data.slice(byteOffset, byteOffset + chunkLength);
480 |
481 | }
482 |
483 | // Clients must ignore chunks with unknown types.
484 |
485 | chunkIndex += chunkLength;
486 |
487 | }
488 |
489 | if (this.content === null) {
490 |
491 | throw new Error('THREE.GLTFLoader: JSON content not found.');
492 |
493 | }
494 |
495 | }
496 |
497 | /**
498 | * DRACO Mesh Compression Extension
499 | *
500 | * Specification: https://github.com/KhronosGroup/glTF/pull/874
501 | */
502 | function GLTFDracoMeshCompressionExtension(json, dracoLoader) {
503 |
504 | if (!dracoLoader) {
505 |
506 | throw new Error('THREE.GLTFLoader: No DRACOLoader instance provided.');
507 |
508 | }
509 |
510 | this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
511 | this.json = json;
512 | this.dracoLoader = dracoLoader;
513 |
514 | }
515 |
516 | GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function (primitive, parser) {
517 |
518 | var json = this.json;
519 | var dracoLoader = this.dracoLoader;
520 | var bufferViewIndex = primitive.extensions[this.name].bufferView;
521 | var gltfAttributeMap = primitive.extensions[this.name].attributes;
522 | var threeAttributeMap = {};
523 | var attributeNormalizedMap = {};
524 | var attributeTypeMap = {};
525 |
526 | for (var attributeName in gltfAttributeMap) {
527 |
528 | var threeAttributeName = ATTRIBUTES[attributeName] || attributeName.toLowerCase();
529 |
530 | threeAttributeMap[threeAttributeName] = gltfAttributeMap[attributeName];
531 |
532 | }
533 |
534 | for (attributeName in primitive.attributes) {
535 |
536 | var threeAttributeName = ATTRIBUTES[attributeName] || attributeName.toLowerCase();
537 |
538 | if (gltfAttributeMap[attributeName] !== undefined) {
539 |
540 | var accessorDef = json.accessors[primitive.attributes[attributeName]];
541 | var componentType = WEBGL_COMPONENT_TYPES[accessorDef.componentType];
542 |
543 | attributeTypeMap[threeAttributeName] = componentType;
544 | attributeNormalizedMap[threeAttributeName] = accessorDef.normalized === true;
545 |
546 | }
547 |
548 | }
549 |
550 | return parser.getDependency('bufferView', bufferViewIndex).then(function (bufferView) {
551 |
552 | return new Promise(function (resolve) {
553 |
554 | dracoLoader.decodeDracoFile(bufferView, function (geometry) {
555 |
556 | for (var attributeName in geometry.attributes) {
557 |
558 | var attribute = geometry.attributes[attributeName];
559 | var normalized = attributeNormalizedMap[attributeName];
560 |
561 | if (normalized !== undefined) attribute.normalized = normalized;
562 |
563 | }
564 |
565 | resolve(geometry);
566 |
567 | }, threeAttributeMap, attributeTypeMap);
568 |
569 | });
570 |
571 | });
572 |
573 | };
574 |
575 | /**
576 | * Texture Transform Extension
577 | *
578 | * Specification:
579 | */
580 | function GLTFTextureTransformExtension() {
581 |
582 | this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
583 |
584 | }
585 |
586 | GLTFTextureTransformExtension.prototype.extendTexture = function (texture, transform) {
587 |
588 | texture = texture.clone();
589 |
590 | if (transform.offset !== undefined) {
591 |
592 | texture.offset.fromArray(transform.offset);
593 |
594 | }
595 |
596 | if (transform.rotation !== undefined) {
597 |
598 | texture.rotation = transform.rotation;
599 |
600 | }
601 |
602 | if (transform.scale !== undefined) {
603 |
604 | texture.repeat.fromArray(transform.scale);
605 |
606 | }
607 |
608 | if (transform.texCoord !== undefined) {
609 |
610 | console.warn('THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.');
611 |
612 | }
613 |
614 | texture.needsUpdate = true;
615 |
616 | return texture;
617 |
618 | };
619 |
620 | /**
621 | * Specular-Glossiness Extension
622 | *
623 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
624 | */
625 | function GLTFMaterialsPbrSpecularGlossinessExtension() {
626 |
627 | return {
628 |
629 | name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
630 |
631 | specularGlossinessParams: [
632 | 'color',
633 | 'map',
634 | 'lightMap',
635 | 'lightMapIntensity',
636 | 'aoMap',
637 | 'aoMapIntensity',
638 | 'emissive',
639 | 'emissiveIntensity',
640 | 'emissiveMap',
641 | 'bumpMap',
642 | 'bumpScale',
643 | 'normalMap',
644 | 'displacementMap',
645 | 'displacementScale',
646 | 'displacementBias',
647 | 'specularMap',
648 | 'specular',
649 | 'glossinessMap',
650 | 'glossiness',
651 | 'alphaMap',
652 | 'envMap',
653 | 'envMapIntensity',
654 | 'refractionRatio',
655 | ],
656 |
657 | getMaterialType: function () {
658 |
659 | return THREE.ShaderMaterial;
660 |
661 | },
662 |
663 | extendParams: function (materialParams, materialDef, parser) {
664 |
665 | var pbrSpecularGlossiness = materialDef.extensions[this.name];
666 |
667 | var shader = THREE.ShaderLib['standard'];
668 |
669 | var uniforms = THREE.UniformsUtils.clone(shader.uniforms);
670 |
671 | var specularMapParsFragmentChunk = [
672 | '#ifdef USE_SPECULARMAP',
673 | ' uniform sampler2D specularMap;',
674 | '#endif'
675 | ].join('\n');
676 |
677 | var glossinessMapParsFragmentChunk = [
678 | '#ifdef USE_GLOSSINESSMAP',
679 | ' uniform sampler2D glossinessMap;',
680 | '#endif'
681 | ].join('\n');
682 |
683 | var specularMapFragmentChunk = [
684 | 'vec3 specularFactor = specular;',
685 | '#ifdef USE_SPECULARMAP',
686 | ' vec4 texelSpecular = texture2D( specularMap, vUv );',
687 | ' texelSpecular = sRGBToLinear( texelSpecular );',
688 | ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture',
689 | ' specularFactor *= texelSpecular.rgb;',
690 | '#endif'
691 | ].join('\n');
692 |
693 | var glossinessMapFragmentChunk = [
694 | 'float glossinessFactor = glossiness;',
695 | '#ifdef USE_GLOSSINESSMAP',
696 | ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );',
697 | ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture',
698 | ' glossinessFactor *= texelGlossiness.a;',
699 | '#endif'
700 | ].join('\n');
701 |
702 | var lightPhysicalFragmentChunk = [
703 | 'PhysicalMaterial material;',
704 | 'material.diffuseColor = diffuseColor.rgb;',
705 | 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );',
706 | 'material.specularColor = specularFactor.rgb;',
707 | ].join('\n');
708 |
709 | var fragmentShader = shader.fragmentShader
710 | .replace('uniform float roughness;', 'uniform vec3 specular;')
711 | .replace('uniform float metalness;', 'uniform float glossiness;')
712 | .replace('#include ', specularMapParsFragmentChunk)
713 | .replace('#include ', glossinessMapParsFragmentChunk)
714 | .replace('#include ', specularMapFragmentChunk)
715 | .replace('#include ', glossinessMapFragmentChunk)
716 | .replace('#include ', lightPhysicalFragmentChunk);
717 |
718 | delete uniforms.roughness;
719 | delete uniforms.metalness;
720 | delete uniforms.roughnessMap;
721 | delete uniforms.metalnessMap;
722 |
723 | uniforms.specular = { value: new THREE.Color().setHex(0x111111) };
724 | uniforms.glossiness = { value: 0.5 };
725 | uniforms.specularMap = { value: null };
726 | uniforms.glossinessMap = { value: null };
727 |
728 | materialParams.vertexShader = shader.vertexShader;
729 | materialParams.fragmentShader = fragmentShader;
730 | materialParams.uniforms = uniforms;
731 | materialParams.defines = { 'STANDARD': '' }
732 |
733 | materialParams.color = new THREE.Color(1.0, 1.0, 1.0);
734 | materialParams.opacity = 1.0;
735 |
736 | var pending = [];
737 |
738 | if (Array.isArray(pbrSpecularGlossiness.diffuseFactor)) {
739 |
740 | var array = pbrSpecularGlossiness.diffuseFactor;
741 |
742 | materialParams.color.fromArray(array);
743 | materialParams.opacity = array[3];
744 |
745 | }
746 |
747 | if (pbrSpecularGlossiness.diffuseTexture !== undefined) {
748 |
749 | pending.push(parser.assignTexture(materialParams, 'map', pbrSpecularGlossiness.diffuseTexture));
750 |
751 | }
752 |
753 | materialParams.emissive = new THREE.Color(0.0, 0.0, 0.0);
754 | materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
755 | materialParams.specular = new THREE.Color(1.0, 1.0, 1.0);
756 |
757 | if (Array.isArray(pbrSpecularGlossiness.specularFactor)) {
758 |
759 | materialParams.specular.fromArray(pbrSpecularGlossiness.specularFactor);
760 |
761 | }
762 |
763 | if (pbrSpecularGlossiness.specularGlossinessTexture !== undefined) {
764 |
765 | var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
766 | pending.push(parser.assignTexture(materialParams, 'glossinessMap', specGlossMapDef));
767 | pending.push(parser.assignTexture(materialParams, 'specularMap', specGlossMapDef));
768 |
769 | }
770 |
771 | return Promise.all(pending);
772 |
773 | },
774 |
775 | createMaterial: function (params) {
776 |
777 | // setup material properties based on MeshStandardMaterial for Specular-Glossiness
778 |
779 | var material = new THREE.ShaderMaterial({
780 | defines: params.defines,
781 | vertexShader: params.vertexShader,
782 | fragmentShader: params.fragmentShader,
783 | uniforms: params.uniforms,
784 | fog: true,
785 | lights: true,
786 | opacity: params.opacity,
787 | transparent: params.transparent
788 | });
789 |
790 | material.isGLTFSpecularGlossinessMaterial = true;
791 |
792 | material.color = params.color;
793 |
794 | material.map = params.map === undefined ? null : params.map;
795 |
796 | material.lightMap = null;
797 | material.lightMapIntensity = 1.0;
798 |
799 | material.aoMap = params.aoMap === undefined ? null : params.aoMap;
800 | material.aoMapIntensity = 1.0;
801 |
802 | material.emissive = params.emissive;
803 | material.emissiveIntensity = 1.0;
804 | material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap;
805 |
806 | material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap;
807 | material.bumpScale = 1;
808 |
809 | material.normalMap = params.normalMap === undefined ? null : params.normalMap;
810 |
811 | if (params.normalScale) material.normalScale = params.normalScale;
812 |
813 | material.displacementMap = null;
814 | material.displacementScale = 1;
815 | material.displacementBias = 0;
816 |
817 | material.specularMap = params.specularMap === undefined ? null : params.specularMap;
818 | material.specular = params.specular;
819 |
820 | material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap;
821 | material.glossiness = params.glossiness;
822 |
823 | material.alphaMap = null;
824 |
825 | material.envMap = params.envMap === undefined ? null : params.envMap;
826 | material.envMapIntensity = 1.0;
827 |
828 | material.refractionRatio = 0.98;
829 |
830 | material.extensions.derivatives = true;
831 |
832 | return material;
833 |
834 | },
835 |
836 | /**
837 | * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can
838 | * copy only properties it knows about or inherits, and misses many properties that would
839 | * normally be defined by MeshStandardMaterial.
840 | *
841 | * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of
842 | * loading a glTF model, but cloning later (e.g. by the user) would require these changes
843 | * AND also updating `.onBeforeRender` on the parent mesh.
844 | *
845 | * @param {THREE.ShaderMaterial} source
846 | * @return {THREE.ShaderMaterial}
847 | */
848 | cloneMaterial: function (source) {
849 |
850 | var target = source.clone();
851 |
852 | target.isGLTFSpecularGlossinessMaterial = true;
853 |
854 | var params = this.specularGlossinessParams;
855 |
856 | for (var i = 0, il = params.length; i < il; i++) {
857 |
858 | var value = source[params[i]];
859 | target[params[i]] = (value && value.isColor) ? value.clone() : value;
860 |
861 | }
862 |
863 | return target;
864 |
865 | },
866 |
867 | // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer.
868 | refreshUniforms: function (renderer, scene, camera, geometry, material) {
869 |
870 | if (material.isGLTFSpecularGlossinessMaterial !== true) {
871 |
872 | return;
873 |
874 | }
875 |
876 | var uniforms = material.uniforms;
877 | var defines = material.defines;
878 |
879 | uniforms.opacity.value = material.opacity;
880 |
881 | uniforms.diffuse.value.copy(material.color);
882 | uniforms.emissive.value.copy(material.emissive).multiplyScalar(material.emissiveIntensity);
883 |
884 | uniforms.map.value = material.map;
885 | uniforms.specularMap.value = material.specularMap;
886 | uniforms.alphaMap.value = material.alphaMap;
887 |
888 | uniforms.lightMap.value = material.lightMap;
889 | uniforms.lightMapIntensity.value = material.lightMapIntensity;
890 |
891 | uniforms.aoMap.value = material.aoMap;
892 | uniforms.aoMapIntensity.value = material.aoMapIntensity;
893 |
894 | // uv repeat and offset setting priorities
895 | // 1. color map
896 | // 2. specular map
897 | // 3. normal map
898 | // 4. bump map
899 | // 5. alpha map
900 | // 6. emissive map
901 |
902 | var uvScaleMap;
903 |
904 | if (material.map) {
905 |
906 | uvScaleMap = material.map;
907 |
908 | } else if (material.specularMap) {
909 |
910 | uvScaleMap = material.specularMap;
911 |
912 | } else if (material.displacementMap) {
913 |
914 | uvScaleMap = material.displacementMap;
915 |
916 | } else if (material.normalMap) {
917 |
918 | uvScaleMap = material.normalMap;
919 |
920 | } else if (material.bumpMap) {
921 |
922 | uvScaleMap = material.bumpMap;
923 |
924 | } else if (material.glossinessMap) {
925 |
926 | uvScaleMap = material.glossinessMap;
927 |
928 | } else if (material.alphaMap) {
929 |
930 | uvScaleMap = material.alphaMap;
931 |
932 | } else if (material.emissiveMap) {
933 |
934 | uvScaleMap = material.emissiveMap;
935 |
936 | }
937 |
938 | if (uvScaleMap !== undefined) {
939 |
940 | // backwards compatibility
941 | if (uvScaleMap.isWebGLRenderTarget) {
942 |
943 | uvScaleMap = uvScaleMap.texture;
944 |
945 | }
946 |
947 | if (uvScaleMap.matrixAutoUpdate === true) {
948 |
949 | uvScaleMap.updateMatrix();
950 |
951 | }
952 |
953 | uniforms.uvTransform.value.copy(uvScaleMap.matrix);
954 |
955 | }
956 |
957 | if (material.envMap) {
958 |
959 | uniforms.envMap.value = material.envMap;
960 | uniforms.envMapIntensity.value = material.envMapIntensity;
961 |
962 | // don't flip CubeTexture envMaps, flip everything else:
963 | // WebGLRenderTargetCube will be flipped for backwards compatibility
964 | // WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture
965 | // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future
966 | uniforms.flipEnvMap.value = material.envMap.isCubeTexture ? - 1 : 1;
967 |
968 | uniforms.reflectivity.value = material.reflectivity;
969 | uniforms.refractionRatio.value = material.refractionRatio;
970 |
971 | uniforms.maxMipLevel.value = renderer.properties.get(material.envMap).__maxMipLevel;
972 |
973 | }
974 |
975 | uniforms.specular.value.copy(material.specular);
976 | uniforms.glossiness.value = material.glossiness;
977 |
978 | uniforms.glossinessMap.value = material.glossinessMap;
979 |
980 | uniforms.emissiveMap.value = material.emissiveMap;
981 | uniforms.bumpMap.value = material.bumpMap;
982 | uniforms.normalMap.value = material.normalMap;
983 |
984 | uniforms.displacementMap.value = material.displacementMap;
985 | uniforms.displacementScale.value = material.displacementScale;
986 | uniforms.displacementBias.value = material.displacementBias;
987 |
988 | if (uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined) {
989 |
990 | defines.USE_GLOSSINESSMAP = '';
991 | // set USE_ROUGHNESSMAP to enable vUv
992 | defines.USE_ROUGHNESSMAP = '';
993 |
994 | }
995 |
996 | if (uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined) {
997 |
998 | delete defines.USE_GLOSSINESSMAP;
999 | delete defines.USE_ROUGHNESSMAP;
1000 |
1001 | }
1002 |
1003 | }
1004 |
1005 | };
1006 |
1007 | }
1008 |
1009 | /*********************************/
1010 | /********** INTERPOLATION ********/
1011 | /*********************************/
1012 |
1013 | // Spline Interpolation
1014 | // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
1015 | function GLTFCubicSplineInterpolant(parameterPositions, sampleValues, sampleSize, resultBuffer) {
1016 |
1017 | THREE.Interpolant.call(this, parameterPositions, sampleValues, sampleSize, resultBuffer);
1018 |
1019 | }
1020 |
1021 | GLTFCubicSplineInterpolant.prototype = Object.create(THREE.Interpolant.prototype);
1022 | GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
1023 |
1024 | GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function (index) {
1025 |
1026 | // Copies a sample value to the result buffer. See description of glTF
1027 | // CUBICSPLINE values layout in interpolate_() function below.
1028 |
1029 | var result = this.resultBuffer,
1030 | values = this.sampleValues,
1031 | valueSize = this.valueSize,
1032 | offset = index * valueSize * 3 + valueSize;
1033 |
1034 | for (var i = 0; i !== valueSize; i++) {
1035 |
1036 | result[i] = values[offset + i];
1037 |
1038 | }
1039 |
1040 | return result;
1041 |
1042 | };
1043 |
1044 | GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
1045 |
1046 | GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
1047 |
1048 | GLTFCubicSplineInterpolant.prototype.interpolate_ = function (i1, t0, t, t1) {
1049 |
1050 | var result = this.resultBuffer;
1051 | var values = this.sampleValues;
1052 | var stride = this.valueSize;
1053 |
1054 | var stride2 = stride * 2;
1055 | var stride3 = stride * 3;
1056 |
1057 | var td = t1 - t0;
1058 |
1059 | var p = (t - t0) / td;
1060 | var pp = p * p;
1061 | var ppp = pp * p;
1062 |
1063 | var offset1 = i1 * stride3;
1064 | var offset0 = offset1 - stride3;
1065 |
1066 | var s2 = - 2 * ppp + 3 * pp;
1067 | var s3 = ppp - pp;
1068 | var s0 = 1 - s2;
1069 | var s1 = s3 - pp + p;
1070 |
1071 | // Layout of keyframe output values for CUBICSPLINE animations:
1072 | // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
1073 | for (var i = 0; i !== stride; i++) {
1074 |
1075 | var p0 = values[offset0 + i + stride]; // splineVertex_k
1076 | var m0 = values[offset0 + i + stride2] * td; // outTangent_k * (t_k+1 - t_k)
1077 | var p1 = values[offset1 + i + stride]; // splineVertex_k+1
1078 | var m1 = values[offset1 + i] * td; // inTangent_k+1 * (t_k+1 - t_k)
1079 |
1080 | result[i] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
1081 |
1082 | }
1083 |
1084 | return result;
1085 |
1086 | };
1087 |
1088 | /*********************************/
1089 | /********** INTERNALS ************/
1090 | /*********************************/
1091 |
1092 | /* CONSTANTS */
1093 |
1094 | var WEBGL_CONSTANTS = {
1095 | FLOAT: 5126,
1096 | //FLOAT_MAT2: 35674,
1097 | FLOAT_MAT3: 35675,
1098 | FLOAT_MAT4: 35676,
1099 | FLOAT_VEC2: 35664,
1100 | FLOAT_VEC3: 35665,
1101 | FLOAT_VEC4: 35666,
1102 | LINEAR: 9729,
1103 | REPEAT: 10497,
1104 | SAMPLER_2D: 35678,
1105 | POINTS: 0,
1106 | LINES: 1,
1107 | LINE_LOOP: 2,
1108 | LINE_STRIP: 3,
1109 | TRIANGLES: 4,
1110 | TRIANGLE_STRIP: 5,
1111 | TRIANGLE_FAN: 6,
1112 | UNSIGNED_BYTE: 5121,
1113 | UNSIGNED_SHORT: 5123
1114 | };
1115 |
1116 | var WEBGL_COMPONENT_TYPES = {
1117 | 5120: Int8Array,
1118 | 5121: Uint8Array,
1119 | 5122: Int16Array,
1120 | 5123: Uint16Array,
1121 | 5125: Uint32Array,
1122 | 5126: Float32Array
1123 | };
1124 |
1125 | var WEBGL_FILTERS = {
1126 | 9728: THREE.NearestFilter,
1127 | 9729: THREE.LinearFilter,
1128 | 9984: THREE.NearestMipmapNearestFilter,
1129 | 9985: THREE.LinearMipmapNearestFilter,
1130 | 9986: THREE.NearestMipmapLinearFilter,
1131 | 9987: THREE.LinearMipmapLinearFilter
1132 | };
1133 |
1134 | var WEBGL_WRAPPINGS = {
1135 | 33071: THREE.ClampToEdgeWrapping,
1136 | 33648: THREE.MirroredRepeatWrapping,
1137 | 10497: THREE.RepeatWrapping
1138 | };
1139 |
1140 | var WEBGL_TYPE_SIZES = {
1141 | 'SCALAR': 1,
1142 | 'VEC2': 2,
1143 | 'VEC3': 3,
1144 | 'VEC4': 4,
1145 | 'MAT2': 4,
1146 | 'MAT3': 9,
1147 | 'MAT4': 16
1148 | };
1149 |
1150 | var ATTRIBUTES = {
1151 | POSITION: 'position',
1152 | NORMAL: 'normal',
1153 | TANGENT: 'tangent',
1154 | TEXCOORD_0: 'uv',
1155 | TEXCOORD_1: 'uv2',
1156 | COLOR_0: 'color',
1157 | WEIGHTS_0: 'skinWeight',
1158 | JOINTS_0: 'skinIndex',
1159 | };
1160 |
1161 | var PATH_PROPERTIES = {
1162 | scale: 'scale',
1163 | translation: 'position',
1164 | rotation: 'quaternion',
1165 | weights: 'morphTargetInfluences'
1166 | };
1167 |
1168 | var INTERPOLATION = {
1169 | CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
1170 | // keyframe track will be initialized with a default interpolation type, then modified.
1171 | LINEAR: THREE.InterpolateLinear,
1172 | STEP: THREE.InterpolateDiscrete
1173 | };
1174 |
1175 | var ALPHA_MODES = {
1176 | OPAQUE: 'OPAQUE',
1177 | MASK: 'MASK',
1178 | BLEND: 'BLEND'
1179 | };
1180 |
1181 | var MIME_TYPE_FORMATS = {
1182 | 'image/png': THREE.RGBAFormat,
1183 | 'image/jpeg': THREE.RGBFormat
1184 | };
1185 |
1186 | /* UTILITY FUNCTIONS */
1187 |
1188 | function resolveURL(url, path) {
1189 |
1190 | // Invalid URL
1191 | if (typeof url !== 'string' || url === '') return '';
1192 |
1193 | // Host Relative URL
1194 | if (/^https?:\/\//i.test(path) && /^\//.test(url)) {
1195 |
1196 | path = path.replace(/(^https?:\/\/[^\/]+).*/i, '$1');
1197 |
1198 | }
1199 |
1200 | // Absolute URL http://,https://,//
1201 | if (/^(https?:)?\/\//i.test(url)) return url;
1202 |
1203 | // Data URI
1204 | if (/^data:.*,.*$/i.test(url)) return url;
1205 |
1206 | // Blob URL
1207 | if (/^blob:.*$/i.test(url)) return url;
1208 |
1209 | // Relative URL
1210 | return path + url;
1211 |
1212 | }
1213 |
1214 | var defaultMaterial;
1215 |
1216 | /**
1217 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
1218 | */
1219 | function createDefaultMaterial() {
1220 |
1221 | defaultMaterial = defaultMaterial || new THREE.MeshStandardMaterial({
1222 | color: 0xFFFFFF,
1223 | emissive: 0x000000,
1224 | metalness: 1,
1225 | roughness: 1,
1226 | transparent: false,
1227 | depthTest: true,
1228 | side: THREE.FrontSide
1229 | });
1230 |
1231 | return defaultMaterial;
1232 |
1233 | }
1234 |
1235 | function addUnknownExtensionsToUserData(knownExtensions, object, objectDef) {
1236 |
1237 | // Add unknown glTF extensions to an object's userData.
1238 |
1239 | for (var name in objectDef.extensions) {
1240 |
1241 | if (knownExtensions[name] === undefined) {
1242 |
1243 | object.userData.gltfExtensions = object.userData.gltfExtensions || {};
1244 | object.userData.gltfExtensions[name] = objectDef.extensions[name];
1245 |
1246 | }
1247 |
1248 | }
1249 |
1250 | }
1251 |
1252 | /**
1253 | * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object
1254 | * @param {GLTF.definition} gltfDef
1255 | */
1256 | function assignExtrasToUserData(object, gltfDef) {
1257 |
1258 | if (gltfDef.extras !== undefined) {
1259 |
1260 | if (typeof gltfDef.extras === 'object') {
1261 |
1262 | Object.assign(object.userData, gltfDef.extras);
1263 |
1264 | } else {
1265 |
1266 | console.warn('THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras);
1267 |
1268 | }
1269 |
1270 | }
1271 |
1272 | }
1273 |
1274 | /**
1275 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
1276 | *
1277 | * @param {THREE.BufferGeometry} geometry
1278 | * @param {Array} targets
1279 | * @param {GLTFParser} parser
1280 | * @return {Promise}
1281 | */
1282 | function addMorphTargets(geometry, targets, parser) {
1283 |
1284 | var hasMorphPosition = false;
1285 | var hasMorphNormal = false;
1286 |
1287 | for (var i = 0, il = targets.length; i < il; i++) {
1288 |
1289 | var target = targets[i];
1290 |
1291 | if (target.POSITION !== undefined) hasMorphPosition = true;
1292 | if (target.NORMAL !== undefined) hasMorphNormal = true;
1293 |
1294 | if (hasMorphPosition && hasMorphNormal) break;
1295 |
1296 | }
1297 |
1298 | if (!hasMorphPosition && !hasMorphNormal) return Promise.resolve(geometry);
1299 |
1300 | var pendingPositionAccessors = [];
1301 | var pendingNormalAccessors = [];
1302 |
1303 | for (var i = 0, il = targets.length; i < il; i++) {
1304 |
1305 | var target = targets[i];
1306 |
1307 | if (hasMorphPosition) {
1308 |
1309 | var pendingAccessor = target.POSITION !== undefined
1310 | ? parser.getDependency('accessor', target.POSITION)
1311 | : geometry.attributes.position;
1312 |
1313 | pendingPositionAccessors.push(pendingAccessor);
1314 |
1315 | }
1316 |
1317 | if (hasMorphNormal) {
1318 |
1319 | var pendingAccessor = target.NORMAL !== undefined
1320 | ? parser.getDependency('accessor', target.NORMAL)
1321 | : geometry.attributes.normal;
1322 |
1323 | pendingNormalAccessors.push(pendingAccessor);
1324 |
1325 | }
1326 |
1327 | }
1328 |
1329 | return Promise.all([
1330 | Promise.all(pendingPositionAccessors),
1331 | Promise.all(pendingNormalAccessors)
1332 | ]).then(function (accessors) {
1333 |
1334 | var morphPositions = accessors[0];
1335 | var morphNormals = accessors[1];
1336 |
1337 | // Clone morph target accessors before modifying them.
1338 |
1339 | for (var i = 0, il = morphPositions.length; i < il; i++) {
1340 |
1341 | if (geometry.attributes.position === morphPositions[i]) continue;
1342 |
1343 | morphPositions[i] = cloneBufferAttribute(morphPositions[i]);
1344 |
1345 | }
1346 |
1347 | for (var i = 0, il = morphNormals.length; i < il; i++) {
1348 |
1349 | if (geometry.attributes.normal === morphNormals[i]) continue;
1350 |
1351 | morphNormals[i] = cloneBufferAttribute(morphNormals[i]);
1352 |
1353 | }
1354 |
1355 | for (var i = 0, il = targets.length; i < il; i++) {
1356 |
1357 | var target = targets[i];
1358 | var attributeName = 'morphTarget' + i;
1359 |
1360 | if (hasMorphPosition) {
1361 |
1362 | // Three.js morph position is absolute value. The formula is
1363 | // basePosition
1364 | // + weight0 * ( morphPosition0 - basePosition )
1365 | // + weight1 * ( morphPosition1 - basePosition )
1366 | // ...
1367 | // while the glTF one is relative
1368 | // basePosition
1369 | // + weight0 * glTFmorphPosition0
1370 | // + weight1 * glTFmorphPosition1
1371 | // ...
1372 | // then we need to convert from relative to absolute here.
1373 |
1374 | if (target.POSITION !== undefined) {
1375 |
1376 | var positionAttribute = morphPositions[i];
1377 | positionAttribute.name = attributeName;
1378 |
1379 | var position = geometry.attributes.position;
1380 |
1381 | for (var j = 0, jl = positionAttribute.count; j < jl; j++) {
1382 |
1383 | positionAttribute.setXYZ(
1384 | j,
1385 | positionAttribute.getX(j) + position.getX(j),
1386 | positionAttribute.getY(j) + position.getY(j),
1387 | positionAttribute.getZ(j) + position.getZ(j)
1388 | );
1389 |
1390 | }
1391 |
1392 | }
1393 |
1394 | }
1395 |
1396 | if (hasMorphNormal) {
1397 |
1398 | // see target.POSITION's comment
1399 |
1400 | if (target.NORMAL !== undefined) {
1401 |
1402 | var normalAttribute = morphNormals[i];
1403 | normalAttribute.name = attributeName;
1404 |
1405 | var normal = geometry.attributes.normal;
1406 |
1407 | for (var j = 0, jl = normalAttribute.count; j < jl; j++) {
1408 |
1409 | normalAttribute.setXYZ(
1410 | j,
1411 | normalAttribute.getX(j) + normal.getX(j),
1412 | normalAttribute.getY(j) + normal.getY(j),
1413 | normalAttribute.getZ(j) + normal.getZ(j)
1414 | );
1415 |
1416 | }
1417 |
1418 | }
1419 |
1420 | }
1421 |
1422 | }
1423 |
1424 | if (hasMorphPosition) geometry.morphAttributes.position = morphPositions;
1425 | if (hasMorphNormal) geometry.morphAttributes.normal = morphNormals;
1426 |
1427 | return geometry;
1428 |
1429 | });
1430 |
1431 | }
1432 |
1433 | /**
1434 | * @param {THREE.Mesh} mesh
1435 | * @param {GLTF.Mesh} meshDef
1436 | */
1437 | function updateMorphTargets(mesh, meshDef) {
1438 |
1439 | mesh.updateMorphTargets();
1440 |
1441 | if (meshDef.weights !== undefined) {
1442 |
1443 | for (var i = 0, il = meshDef.weights.length; i < il; i++) {
1444 |
1445 | mesh.morphTargetInfluences[i] = meshDef.weights[i];
1446 |
1447 | }
1448 |
1449 | }
1450 |
1451 | // .extras has user-defined data, so check that .extras.targetNames is an array.
1452 | if (meshDef.extras && Array.isArray(meshDef.extras.targetNames)) {
1453 |
1454 | var targetNames = meshDef.extras.targetNames;
1455 |
1456 | if (mesh.morphTargetInfluences.length === targetNames.length) {
1457 |
1458 | mesh.morphTargetDictionary = {};
1459 |
1460 | for (var i = 0, il = targetNames.length; i < il; i++) {
1461 |
1462 | mesh.morphTargetDictionary[targetNames[i]] = i;
1463 |
1464 | }
1465 |
1466 | } else {
1467 |
1468 | console.warn('THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.');
1469 |
1470 | }
1471 |
1472 | }
1473 |
1474 | }
1475 |
1476 | function createPrimitiveKey(primitiveDef) {
1477 |
1478 | var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION];
1479 | var geometryKey;
1480 |
1481 | if (dracoExtension) {
1482 |
1483 | geometryKey = 'draco:' + dracoExtension.bufferView
1484 | + ':' + dracoExtension.indices
1485 | + ':' + createAttributesKey(dracoExtension.attributes);
1486 |
1487 | } else {
1488 |
1489 | geometryKey = primitiveDef.indices + ':' + createAttributesKey(primitiveDef.attributes) + ':' + primitiveDef.mode;
1490 |
1491 | }
1492 |
1493 | return geometryKey;
1494 |
1495 | }
1496 |
1497 | function createAttributesKey(attributes) {
1498 |
1499 | var attributesKey = '';
1500 |
1501 | var keys = Object.keys(attributes).sort();
1502 |
1503 | for (var i = 0, il = keys.length; i < il; i++) {
1504 |
1505 | attributesKey += keys[i] + ':' + attributes[keys[i]] + ';';
1506 |
1507 | }
1508 |
1509 | return attributesKey;
1510 |
1511 | }
1512 |
1513 | function cloneBufferAttribute(attribute) {
1514 |
1515 | if (attribute.isInterleavedBufferAttribute) {
1516 |
1517 | var count = attribute.count;
1518 | var itemSize = attribute.itemSize;
1519 | var array = attribute.array.slice(0, count * itemSize);
1520 |
1521 | for (var i = 0, j = 0; i < count; ++i) {
1522 |
1523 | array[j++] = attribute.getX(i);
1524 | if (itemSize >= 2) array[j++] = attribute.getY(i);
1525 | if (itemSize >= 3) array[j++] = attribute.getZ(i);
1526 | if (itemSize >= 4) array[j++] = attribute.getW(i);
1527 |
1528 | }
1529 |
1530 | return new THREE.BufferAttribute(array, itemSize, attribute.normalized);
1531 |
1532 | }
1533 |
1534 | return attribute.clone();
1535 |
1536 | }
1537 |
1538 | /* GLTF PARSER */
1539 |
1540 | function GLTFParser(json, extensions, options) {
1541 |
1542 | this.json = json || {};
1543 | this.extensions = extensions || {};
1544 | this.options = options || {};
1545 |
1546 | // loader object cache
1547 | this.cache = new GLTFRegistry();
1548 |
1549 | // BufferGeometry caching
1550 | this.primitiveCache = {};
1551 |
1552 | this.textureLoader = new THREE.TextureLoader(this.options.manager);
1553 | this.textureLoader.setCrossOrigin(this.options.crossOrigin);
1554 |
1555 | this.fileLoader = new THREE.FileLoader(this.options.manager);
1556 | this.fileLoader.setResponseType('arraybuffer');
1557 |
1558 | if (this.options.crossOrigin === 'use-credentials') {
1559 |
1560 | this.fileLoader.setWithCredentials(true);
1561 |
1562 | }
1563 |
1564 | }
1565 |
1566 | GLTFParser.prototype.parse = function (onLoad, onError) {
1567 |
1568 | var parser = this;
1569 | var json = this.json;
1570 | var extensions = this.extensions;
1571 |
1572 | // Clear the loader cache
1573 | this.cache.removeAll();
1574 |
1575 | // Mark the special nodes/meshes in json for efficient parse
1576 | this.markDefs();
1577 |
1578 | Promise.all([
1579 |
1580 | this.getDependencies('scene'),
1581 | this.getDependencies('animation'),
1582 | this.getDependencies('camera'),
1583 |
1584 | ]).then(function (dependencies) {
1585 |
1586 | var result = {
1587 | scene: dependencies[0][json.scene || 0],
1588 | scenes: dependencies[0],
1589 | animations: dependencies[1],
1590 | cameras: dependencies[2],
1591 | asset: json.asset,
1592 | parser: parser,
1593 | userData: {}
1594 | };
1595 |
1596 | addUnknownExtensionsToUserData(extensions, result, json);
1597 |
1598 | assignExtrasToUserData(result, json);
1599 |
1600 | onLoad(result);
1601 |
1602 | }).catch(onError);
1603 |
1604 | };
1605 |
1606 | /**
1607 | * Marks the special nodes/meshes in json for efficient parse.
1608 | */
1609 | GLTFParser.prototype.markDefs = function () {
1610 |
1611 | var nodeDefs = this.json.nodes || [];
1612 | var skinDefs = this.json.skins || [];
1613 | var meshDefs = this.json.meshes || [];
1614 |
1615 | var meshReferences = {};
1616 | var meshUses = {};
1617 |
1618 | // Nothing in the node definition indicates whether it is a Bone or an
1619 | // Object3D. Use the skins' joint references to mark bones.
1620 | for (var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex++) {
1621 |
1622 | var joints = skinDefs[skinIndex].joints;
1623 |
1624 | for (var i = 0, il = joints.length; i < il; i++) {
1625 |
1626 | nodeDefs[joints[i]].isBone = true;
1627 |
1628 | }
1629 |
1630 | }
1631 |
1632 | // Meshes can (and should) be reused by multiple nodes in a glTF asset. To
1633 | // avoid having more than one THREE.Mesh with the same name, count
1634 | // references and rename instances below.
1635 | //
1636 | // Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
1637 | for (var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex++) {
1638 |
1639 | var nodeDef = nodeDefs[nodeIndex];
1640 |
1641 | if (nodeDef.mesh !== undefined) {
1642 |
1643 | if (meshReferences[nodeDef.mesh] === undefined) {
1644 |
1645 | meshReferences[nodeDef.mesh] = meshUses[nodeDef.mesh] = 0;
1646 |
1647 | }
1648 |
1649 | meshReferences[nodeDef.mesh]++;
1650 |
1651 | // Nothing in the mesh definition indicates whether it is
1652 | // a SkinnedMesh or Mesh. Use the node's mesh reference
1653 | // to mark SkinnedMesh if node has skin.
1654 | if (nodeDef.skin !== undefined) {
1655 |
1656 | meshDefs[nodeDef.mesh].isSkinnedMesh = true;
1657 |
1658 | }
1659 |
1660 | }
1661 |
1662 | }
1663 |
1664 | this.json.meshReferences = meshReferences;
1665 | this.json.meshUses = meshUses;
1666 |
1667 | };
1668 |
1669 | /**
1670 | * Requests the specified dependency asynchronously, with caching.
1671 | * @param {string} type
1672 | * @param {number} index
1673 | * @return {Promise}
1674 | */
1675 | GLTFParser.prototype.getDependency = function (type, index) {
1676 |
1677 | var cacheKey = type + ':' + index;
1678 | var dependency = this.cache.get(cacheKey);
1679 |
1680 | if (!dependency) {
1681 |
1682 | switch (type) {
1683 |
1684 | case 'scene':
1685 | dependency = this.loadScene(index);
1686 | break;
1687 |
1688 | case 'node':
1689 | dependency = this.loadNode(index);
1690 | break;
1691 |
1692 | case 'mesh':
1693 | dependency = this.loadMesh(index);
1694 | break;
1695 |
1696 | case 'accessor':
1697 | dependency = this.loadAccessor(index);
1698 | break;
1699 |
1700 | case 'bufferView':
1701 | dependency = this.loadBufferView(index);
1702 | break;
1703 |
1704 | case 'buffer':
1705 | dependency = this.loadBuffer(index);
1706 | break;
1707 |
1708 | case 'material':
1709 | dependency = this.loadMaterial(index);
1710 | break;
1711 |
1712 | case 'texture':
1713 | dependency = this.loadTexture(index);
1714 | break;
1715 |
1716 | case 'skin':
1717 | dependency = this.loadSkin(index);
1718 | break;
1719 |
1720 | case 'animation':
1721 | dependency = this.loadAnimation(index);
1722 | break;
1723 |
1724 | case 'camera':
1725 | dependency = this.loadCamera(index);
1726 | break;
1727 |
1728 | case 'light':
1729 | dependency = this.extensions[EXTENSIONS.KHR_LIGHTS_PUNCTUAL].loadLight(index);
1730 | break;
1731 |
1732 | default:
1733 | throw new Error('Unknown type: ' + type);
1734 |
1735 | }
1736 |
1737 | this.cache.add(cacheKey, dependency);
1738 |
1739 | }
1740 |
1741 | return dependency;
1742 |
1743 | };
1744 |
1745 | /**
1746 | * Requests all dependencies of the specified type asynchronously, with caching.
1747 | * @param {string} type
1748 | * @return {Promise>}
1749 | */
1750 | GLTFParser.prototype.getDependencies = function (type) {
1751 |
1752 | var dependencies = this.cache.get(type);
1753 |
1754 | if (!dependencies) {
1755 |
1756 | var parser = this;
1757 | var defs = this.json[type + (type === 'mesh' ? 'es' : 's')] || [];
1758 |
1759 | dependencies = Promise.all(defs.map(function (def, index) {
1760 |
1761 | return parser.getDependency(type, index);
1762 |
1763 | }));
1764 |
1765 | this.cache.add(type, dependencies);
1766 |
1767 | }
1768 |
1769 | return dependencies;
1770 |
1771 | };
1772 |
1773 | /**
1774 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1775 | * @param {number} bufferIndex
1776 | * @return {Promise}
1777 | */
1778 | GLTFParser.prototype.loadBuffer = function (bufferIndex) {
1779 |
1780 | var bufferDef = this.json.buffers[bufferIndex];
1781 | var loader = this.fileLoader;
1782 |
1783 | if (bufferDef.type && bufferDef.type !== 'arraybuffer') {
1784 |
1785 | throw new Error('THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.');
1786 |
1787 | }
1788 |
1789 | // If present, GLB container is required to be the first buffer.
1790 | if (bufferDef.uri === undefined && bufferIndex === 0) {
1791 |
1792 | return Promise.resolve(this.extensions[EXTENSIONS.KHR_BINARY_GLTF].body);
1793 |
1794 | }
1795 |
1796 | var options = this.options;
1797 |
1798 | return new Promise(function (resolve, reject) {
1799 |
1800 | loader.load(resolveURL(bufferDef.uri, options.path), resolve, undefined, function () {
1801 |
1802 | reject(new Error('THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".'));
1803 |
1804 | });
1805 |
1806 | });
1807 |
1808 | };
1809 |
1810 | /**
1811 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1812 | * @param {number} bufferViewIndex
1813 | * @return {Promise}
1814 | */
1815 | GLTFParser.prototype.loadBufferView = function (bufferViewIndex) {
1816 |
1817 | var bufferViewDef = this.json.bufferViews[bufferViewIndex];
1818 |
1819 | return this.getDependency('buffer', bufferViewDef.buffer).then(function (buffer) {
1820 |
1821 | var byteLength = bufferViewDef.byteLength || 0;
1822 | var byteOffset = bufferViewDef.byteOffset || 0;
1823 | return buffer.slice(byteOffset, byteOffset + byteLength);
1824 |
1825 | });
1826 |
1827 | };
1828 |
1829 | /**
1830 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
1831 | * @param {number} accessorIndex
1832 | * @return {Promise}
1833 | */
1834 | GLTFParser.prototype.loadAccessor = function (accessorIndex) {
1835 |
1836 | var parser = this;
1837 | var json = this.json;
1838 |
1839 | var accessorDef = this.json.accessors[accessorIndex];
1840 |
1841 | if (accessorDef.bufferView === undefined && accessorDef.sparse === undefined) {
1842 |
1843 | // Ignore empty accessors, which may be used to declare runtime
1844 | // information about attributes coming from another source (e.g. Draco
1845 | // compression extension).
1846 | return Promise.resolve(null);
1847 |
1848 | }
1849 |
1850 | var pendingBufferViews = [];
1851 |
1852 | if (accessorDef.bufferView !== undefined) {
1853 |
1854 | pendingBufferViews.push(this.getDependency('bufferView', accessorDef.bufferView));
1855 |
1856 | } else {
1857 |
1858 | pendingBufferViews.push(null);
1859 |
1860 | }
1861 |
1862 | if (accessorDef.sparse !== undefined) {
1863 |
1864 | pendingBufferViews.push(this.getDependency('bufferView', accessorDef.sparse.indices.bufferView));
1865 | pendingBufferViews.push(this.getDependency('bufferView', accessorDef.sparse.values.bufferView));
1866 |
1867 | }
1868 |
1869 | return Promise.all(pendingBufferViews).then(function (bufferViews) {
1870 |
1871 | var bufferView = bufferViews[0];
1872 |
1873 | var itemSize = WEBGL_TYPE_SIZES[accessorDef.type];
1874 | var TypedArray = WEBGL_COMPONENT_TYPES[accessorDef.componentType];
1875 |
1876 | // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
1877 | var elementBytes = TypedArray.BYTES_PER_ELEMENT;
1878 | var itemBytes = elementBytes * itemSize;
1879 | var byteOffset = accessorDef.byteOffset || 0;
1880 | var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[accessorDef.bufferView].byteStride : undefined;
1881 | var normalized = accessorDef.normalized === true;
1882 | var array, bufferAttribute;
1883 |
1884 | // The buffer is not interleaved if the stride is the item size in bytes.
1885 | if (byteStride && byteStride !== itemBytes) {
1886 |
1887 | // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer
1888 | // This makes sure that IBA.count reflects accessor.count properly
1889 | var ibSlice = Math.floor(byteOffset / byteStride);
1890 | var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
1891 | var ib = parser.cache.get(ibCacheKey);
1892 |
1893 | if (!ib) {
1894 |
1895 | array = new TypedArray(bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes);
1896 |
1897 | // Integer parameters to IB/IBA are in array elements, not bytes.
1898 | ib = new THREE.InterleavedBuffer(array, byteStride / elementBytes);
1899 |
1900 | parser.cache.add(ibCacheKey, ib);
1901 |
1902 | }
1903 |
1904 | bufferAttribute = new THREE.InterleavedBufferAttribute(ib, itemSize, (byteOffset % byteStride) / elementBytes, normalized);
1905 |
1906 | } else {
1907 |
1908 | if (bufferView === null) {
1909 |
1910 | array = new TypedArray(accessorDef.count * itemSize);
1911 |
1912 | } else {
1913 |
1914 | array = new TypedArray(bufferView, byteOffset, accessorDef.count * itemSize);
1915 |
1916 | }
1917 |
1918 | bufferAttribute = new THREE.BufferAttribute(array, itemSize, normalized);
1919 |
1920 | }
1921 |
1922 | // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
1923 | if (accessorDef.sparse !== undefined) {
1924 |
1925 | var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
1926 | var TypedArrayIndices = WEBGL_COMPONENT_TYPES[accessorDef.sparse.indices.componentType];
1927 |
1928 | var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
1929 | var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
1930 |
1931 | var sparseIndices = new TypedArrayIndices(bufferViews[1], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices);
1932 | var sparseValues = new TypedArray(bufferViews[2], byteOffsetValues, accessorDef.sparse.count * itemSize);
1933 |
1934 | if (bufferView !== null) {
1935 |
1936 | // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
1937 | bufferAttribute.setArray(bufferAttribute.array.slice());
1938 |
1939 | }
1940 |
1941 | for (var i = 0, il = sparseIndices.length; i < il; i++) {
1942 |
1943 | var index = sparseIndices[i];
1944 |
1945 | bufferAttribute.setX(index, sparseValues[i * itemSize]);
1946 | if (itemSize >= 2) bufferAttribute.setY(index, sparseValues[i * itemSize + 1]);
1947 | if (itemSize >= 3) bufferAttribute.setZ(index, sparseValues[i * itemSize + 2]);
1948 | if (itemSize >= 4) bufferAttribute.setW(index, sparseValues[i * itemSize + 3]);
1949 | if (itemSize >= 5) throw new Error('THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.');
1950 |
1951 | }
1952 |
1953 | }
1954 |
1955 | return bufferAttribute;
1956 |
1957 | });
1958 |
1959 | };
1960 |
1961 | /**
1962 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
1963 | * @param {number} textureIndex
1964 | * @return {Promise}
1965 | */
1966 | GLTFParser.prototype.loadTexture = function (textureIndex) {
1967 |
1968 | var parser = this;
1969 | var json = this.json;
1970 | var options = this.options;
1971 | var textureLoader = this.textureLoader;
1972 |
1973 | var URL = window.URL || window.webkitURL;
1974 |
1975 | var textureDef = json.textures[textureIndex];
1976 |
1977 | var textureExtensions = textureDef.extensions || {};
1978 |
1979 | var source;
1980 |
1981 | if (textureExtensions[EXTENSIONS.MSFT_TEXTURE_DDS]) {
1982 |
1983 | source = json.images[textureExtensions[EXTENSIONS.MSFT_TEXTURE_DDS].source];
1984 |
1985 | } else {
1986 |
1987 | source = json.images[textureDef.source];
1988 |
1989 | }
1990 |
1991 | var sourceURI = source.uri;
1992 | var isObjectURL = false;
1993 |
1994 | if (source.bufferView !== undefined) {
1995 |
1996 | // Load binary image data from bufferView, if provided.
1997 |
1998 | sourceURI = parser.getDependency('bufferView', source.bufferView).then(function (bufferView) {
1999 |
2000 | isObjectURL = true;
2001 | var blob = new Blob([bufferView], { type: source.mimeType });
2002 | sourceURI = URL.createObjectURL(blob);
2003 | return sourceURI;
2004 |
2005 | });
2006 |
2007 | }
2008 |
2009 | return Promise.resolve(sourceURI).then(function (sourceURI) {
2010 |
2011 | // Load Texture resource.
2012 |
2013 | var loader = THREE.Loader.Handlers.get(sourceURI);
2014 |
2015 | if (!loader) {
2016 |
2017 | loader = textureExtensions[EXTENSIONS.MSFT_TEXTURE_DDS]
2018 | ? parser.extensions[EXTENSIONS.MSFT_TEXTURE_DDS].ddsLoader
2019 | : textureLoader;
2020 |
2021 | }
2022 |
2023 | return new Promise(function (resolve, reject) {
2024 |
2025 | loader.load(resolveURL(sourceURI, options.path), resolve, undefined, reject);
2026 |
2027 | });
2028 |
2029 | }).then(function (texture) {
2030 |
2031 | // Clean up resources and configure Texture.
2032 |
2033 | if (isObjectURL === true) {
2034 |
2035 | URL.revokeObjectURL(sourceURI);
2036 |
2037 | }
2038 |
2039 | texture.flipY = false;
2040 |
2041 | if (textureDef.name !== undefined) texture.name = textureDef.name;
2042 |
2043 | // Ignore unknown mime types, like DDS files.
2044 | if (source.mimeType in MIME_TYPE_FORMATS) {
2045 |
2046 | texture.format = MIME_TYPE_FORMATS[source.mimeType];
2047 |
2048 | }
2049 |
2050 | var samplers = json.samplers || {};
2051 | var sampler = samplers[textureDef.sampler] || {};
2052 |
2053 | texture.magFilter = WEBGL_FILTERS[sampler.magFilter] || THREE.LinearFilter;
2054 | texture.minFilter = WEBGL_FILTERS[sampler.minFilter] || THREE.LinearMipmapLinearFilter;
2055 | texture.wrapS = WEBGL_WRAPPINGS[sampler.wrapS] || THREE.RepeatWrapping;
2056 | texture.wrapT = WEBGL_WRAPPINGS[sampler.wrapT] || THREE.RepeatWrapping;
2057 |
2058 | return texture;
2059 |
2060 | });
2061 |
2062 | };
2063 |
2064 | /**
2065 | * Asynchronously assigns a texture to the given material parameters.
2066 | * @param {Object} materialParams
2067 | * @param {string} mapName
2068 | * @param {Object} mapDef
2069 | * @return {Promise}
2070 | */
2071 | GLTFParser.prototype.assignTexture = function (materialParams, mapName, mapDef) {
2072 |
2073 | var parser = this;
2074 |
2075 | return this.getDependency('texture', mapDef.index).then(function (texture) {
2076 |
2077 | if (!texture.isCompressedTexture) {
2078 |
2079 | switch (mapName) {
2080 |
2081 | case 'aoMap':
2082 | case 'emissiveMap':
2083 | case 'metalnessMap':
2084 | case 'normalMap':
2085 | case 'roughnessMap':
2086 | texture.format = THREE.RGBFormat;
2087 | break;
2088 |
2089 | }
2090 |
2091 | }
2092 |
2093 | if (parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM]) {
2094 |
2095 | var transform = mapDef.extensions !== undefined ? mapDef.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM] : undefined;
2096 |
2097 | if (transform) {
2098 |
2099 | texture = parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM].extendTexture(texture, transform);
2100 |
2101 | }
2102 |
2103 | }
2104 |
2105 | materialParams[mapName] = texture;
2106 |
2107 | });
2108 |
2109 | };
2110 |
2111 | /**
2112 | * Assigns final material to a Mesh, Line, or Points instance. The instance
2113 | * already has a material (generated from the glTF material options alone)
2114 | * but reuse of the same glTF material may require multiple threejs materials
2115 | * to accomodate different primitive types, defines, etc. New materials will
2116 | * be created if necessary, and reused from a cache.
2117 | * @param {THREE.Object3D} mesh Mesh, Line, or Points instance.
2118 | */
2119 | GLTFParser.prototype.assignFinalMaterial = function (mesh) {
2120 |
2121 | var geometry = mesh.geometry;
2122 | var material = mesh.material;
2123 | var extensions = this.extensions;
2124 |
2125 | var useVertexTangents = geometry.attributes.tangent !== undefined;
2126 | var useVertexColors = geometry.attributes.color !== undefined;
2127 | var useFlatShading = geometry.attributes.normal === undefined;
2128 | var useSkinning = mesh.isSkinnedMesh === true;
2129 | var useMorphTargets = Object.keys(geometry.morphAttributes).length > 0;
2130 | var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
2131 |
2132 | if (mesh.isPoints) {
2133 |
2134 | var cacheKey = 'PointsMaterial:' + material.uuid;
2135 |
2136 | var pointsMaterial = this.cache.get(cacheKey);
2137 |
2138 | if (!pointsMaterial) {
2139 |
2140 | pointsMaterial = new THREE.PointsMaterial();
2141 | THREE.Material.prototype.copy.call(pointsMaterial, material);
2142 | pointsMaterial.color.copy(material.color);
2143 | pointsMaterial.map = material.map;
2144 | pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet
2145 | pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
2146 |
2147 | this.cache.add(cacheKey, pointsMaterial);
2148 |
2149 | }
2150 |
2151 | material = pointsMaterial;
2152 |
2153 | } else if (mesh.isLine) {
2154 |
2155 | var cacheKey = 'LineBasicMaterial:' + material.uuid;
2156 |
2157 | var lineMaterial = this.cache.get(cacheKey);
2158 |
2159 | if (!lineMaterial) {
2160 |
2161 | lineMaterial = new THREE.LineBasicMaterial();
2162 | THREE.Material.prototype.copy.call(lineMaterial, material);
2163 | lineMaterial.color.copy(material.color);
2164 | lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet
2165 |
2166 | this.cache.add(cacheKey, lineMaterial);
2167 |
2168 | }
2169 |
2170 | material = lineMaterial;
2171 |
2172 | }
2173 |
2174 | // Clone the material if it will be modified
2175 | if (useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets) {
2176 |
2177 | var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
2178 |
2179 | if (material.isGLTFSpecularGlossinessMaterial) cacheKey += 'specular-glossiness:';
2180 | if (useSkinning) cacheKey += 'skinning:';
2181 | if (useVertexTangents) cacheKey += 'vertex-tangents:';
2182 | if (useVertexColors) cacheKey += 'vertex-colors:';
2183 | if (useFlatShading) cacheKey += 'flat-shading:';
2184 | if (useMorphTargets) cacheKey += 'morph-targets:';
2185 | if (useMorphNormals) cacheKey += 'morph-normals:';
2186 |
2187 | var cachedMaterial = this.cache.get(cacheKey);
2188 |
2189 | if (!cachedMaterial) {
2190 |
2191 | cachedMaterial = material.isGLTFSpecularGlossinessMaterial
2192 | ? extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].cloneMaterial(material)
2193 | : material.clone();
2194 |
2195 | if (useSkinning) cachedMaterial.skinning = true;
2196 | if (useVertexTangents) cachedMaterial.vertexTangents = true;
2197 | if (useVertexColors) cachedMaterial.vertexColors = THREE.VertexColors;
2198 | if (useFlatShading) cachedMaterial.flatShading = true;
2199 | if (useMorphTargets) cachedMaterial.morphTargets = true;
2200 | if (useMorphNormals) cachedMaterial.morphNormals = true;
2201 |
2202 | this.cache.add(cacheKey, cachedMaterial);
2203 |
2204 | }
2205 |
2206 | material = cachedMaterial;
2207 |
2208 | }
2209 |
2210 | // workarounds for mesh and geometry
2211 |
2212 | if (material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined) {
2213 |
2214 | console.log('THREE.GLTFLoader: Duplicating UVs to support aoMap.');
2215 | geometry.addAttribute('uv2', new THREE.BufferAttribute(geometry.attributes.uv.array, 2));
2216 |
2217 | }
2218 |
2219 | if (material.isGLTFSpecularGlossinessMaterial) {
2220 |
2221 | // for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update
2222 | mesh.onBeforeRender = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].refreshUniforms;
2223 |
2224 | }
2225 |
2226 | mesh.material = material;
2227 |
2228 | };
2229 |
2230 | /**
2231 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
2232 | * @param {number} materialIndex
2233 | * @return {Promise}
2234 | */
2235 | GLTFParser.prototype.loadMaterial = function (materialIndex) {
2236 |
2237 | var parser = this;
2238 | var json = this.json;
2239 | var extensions = this.extensions;
2240 | var materialDef = json.materials[materialIndex];
2241 |
2242 | var materialType;
2243 | var materialParams = {};
2244 | var materialExtensions = materialDef.extensions || {};
2245 |
2246 | var pending = [];
2247 |
2248 | if (materialExtensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS]) {
2249 |
2250 | var sgExtension = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS];
2251 | materialType = sgExtension.getMaterialType();
2252 | pending.push(sgExtension.extendParams(materialParams, materialDef, parser));
2253 |
2254 | } else if (materialExtensions[EXTENSIONS.KHR_MATERIALS_UNLIT]) {
2255 |
2256 | var kmuExtension = extensions[EXTENSIONS.KHR_MATERIALS_UNLIT];
2257 | materialType = kmuExtension.getMaterialType();
2258 | pending.push(kmuExtension.extendParams(materialParams, materialDef, parser));
2259 |
2260 | } else {
2261 |
2262 | // Specification:
2263 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
2264 |
2265 | materialType = THREE.MeshStandardMaterial;
2266 |
2267 | var metallicRoughness = materialDef.pbrMetallicRoughness || {};
2268 |
2269 | materialParams.color = new THREE.Color(1.0, 1.0, 1.0);
2270 | materialParams.opacity = 1.0;
2271 |
2272 | if (Array.isArray(metallicRoughness.baseColorFactor)) {
2273 |
2274 | var array = metallicRoughness.baseColorFactor;
2275 |
2276 | materialParams.color.fromArray(array);
2277 | materialParams.opacity = array[3];
2278 |
2279 | }
2280 |
2281 | if (metallicRoughness.baseColorTexture !== undefined) {
2282 |
2283 | pending.push(parser.assignTexture(materialParams, 'map', metallicRoughness.baseColorTexture));
2284 |
2285 | }
2286 |
2287 | materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
2288 | materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
2289 |
2290 | if (metallicRoughness.metallicRoughnessTexture !== undefined) {
2291 |
2292 | pending.push(parser.assignTexture(materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture));
2293 | pending.push(parser.assignTexture(materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture));
2294 |
2295 | }
2296 |
2297 | }
2298 |
2299 | if (materialDef.doubleSided === true) {
2300 |
2301 | materialParams.side = THREE.DoubleSide;
2302 |
2303 | }
2304 |
2305 | var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE;
2306 |
2307 | if (alphaMode === ALPHA_MODES.BLEND) {
2308 |
2309 | materialParams.transparent = true;
2310 |
2311 | } else {
2312 |
2313 | materialParams.transparent = false;
2314 |
2315 | if (alphaMode === ALPHA_MODES.MASK) {
2316 |
2317 | materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
2318 |
2319 | }
2320 |
2321 | }
2322 |
2323 | if (materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial) {
2324 |
2325 | pending.push(parser.assignTexture(materialParams, 'normalMap', materialDef.normalTexture));
2326 |
2327 | materialParams.normalScale = new THREE.Vector2(1, 1);
2328 |
2329 | if (materialDef.normalTexture.scale !== undefined) {
2330 |
2331 | materialParams.normalScale.set(materialDef.normalTexture.scale, materialDef.normalTexture.scale);
2332 |
2333 | }
2334 |
2335 | }
2336 |
2337 | if (materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial) {
2338 |
2339 | pending.push(parser.assignTexture(materialParams, 'aoMap', materialDef.occlusionTexture));
2340 |
2341 | if (materialDef.occlusionTexture.strength !== undefined) {
2342 |
2343 | materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
2344 |
2345 | }
2346 |
2347 | }
2348 |
2349 | if (materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial) {
2350 |
2351 | materialParams.emissive = new THREE.Color().fromArray(materialDef.emissiveFactor);
2352 |
2353 | }
2354 |
2355 | if (materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial) {
2356 |
2357 | pending.push(parser.assignTexture(materialParams, 'emissiveMap', materialDef.emissiveTexture));
2358 |
2359 | }
2360 |
2361 | return Promise.all(pending).then(function () {
2362 |
2363 | var material;
2364 |
2365 | if (materialType === THREE.ShaderMaterial) {
2366 |
2367 | material = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].createMaterial(materialParams);
2368 |
2369 | } else {
2370 |
2371 | material = new materialType(materialParams);
2372 |
2373 | }
2374 |
2375 | if (materialDef.name !== undefined) material.name = materialDef.name;
2376 |
2377 | // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
2378 | if (material.map) material.map.encoding = THREE.sRGBEncoding;
2379 | if (material.emissiveMap) material.emissiveMap.encoding = THREE.sRGBEncoding;
2380 | if (material.specularMap) material.specularMap.encoding = THREE.sRGBEncoding;
2381 |
2382 | assignExtrasToUserData(material, materialDef);
2383 |
2384 | if (materialDef.extensions) addUnknownExtensionsToUserData(extensions, material, materialDef);
2385 |
2386 | return material;
2387 |
2388 | });
2389 |
2390 | };
2391 |
2392 | /**
2393 | * @param {THREE.BufferGeometry} geometry
2394 | * @param {GLTF.Primitive} primitiveDef
2395 | * @param {GLTFParser} parser
2396 | * @return {Promise}
2397 | */
2398 | function addPrimitiveAttributes(geometry, primitiveDef, parser) {
2399 |
2400 | var attributes = primitiveDef.attributes;
2401 |
2402 | var pending = [];
2403 |
2404 | function assignAttributeAccessor(accessorIndex, attributeName) {
2405 |
2406 | return parser.getDependency('accessor', accessorIndex)
2407 | .then(function (accessor) {
2408 |
2409 | geometry.addAttribute(attributeName, accessor);
2410 |
2411 | });
2412 |
2413 | }
2414 |
2415 | for (var gltfAttributeName in attributes) {
2416 |
2417 | var threeAttributeName = ATTRIBUTES[gltfAttributeName] || gltfAttributeName.toLowerCase();
2418 |
2419 | // Skip attributes already provided by e.g. Draco extension.
2420 | if (threeAttributeName in geometry.attributes) continue;
2421 |
2422 | pending.push(assignAttributeAccessor(attributes[gltfAttributeName], threeAttributeName));
2423 |
2424 | }
2425 |
2426 | if (primitiveDef.indices !== undefined && !geometry.index) {
2427 |
2428 | var accessor = parser.getDependency('accessor', primitiveDef.indices).then(function (accessor) {
2429 |
2430 | geometry.setIndex(accessor);
2431 |
2432 | });
2433 |
2434 | pending.push(accessor);
2435 |
2436 | }
2437 |
2438 | assignExtrasToUserData(geometry, primitiveDef);
2439 |
2440 | return Promise.all(pending).then(function () {
2441 |
2442 | return primitiveDef.targets !== undefined
2443 | ? addMorphTargets(geometry, primitiveDef.targets, parser)
2444 | : geometry;
2445 |
2446 | });
2447 |
2448 | }
2449 |
2450 | /**
2451 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
2452 | *
2453 | * Creates BufferGeometries from primitives.
2454 | *
2455 | * @param {Array} primitives
2456 | * @return {Promise>}
2457 | */
2458 | GLTFParser.prototype.loadGeometries = function (primitives) {
2459 |
2460 | var parser = this;
2461 | var extensions = this.extensions;
2462 | var cache = this.primitiveCache;
2463 |
2464 | function createDracoPrimitive(primitive) {
2465 |
2466 | return extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION]
2467 | .decodePrimitive(primitive, parser)
2468 | .then(function (geometry) {
2469 |
2470 | return addPrimitiveAttributes(geometry, primitive, parser);
2471 |
2472 | });
2473 |
2474 | }
2475 |
2476 | var pending = [];
2477 |
2478 | for (var i = 0, il = primitives.length; i < il; i++) {
2479 |
2480 | var primitive = primitives[i];
2481 | var cacheKey = createPrimitiveKey(primitive);
2482 |
2483 | // See if we've already created this geometry
2484 | var cached = cache[cacheKey];
2485 |
2486 | if (cached) {
2487 |
2488 | // Use the cached geometry if it exists
2489 | pending.push(cached.promise);
2490 |
2491 | } else {
2492 |
2493 | var geometryPromise;
2494 |
2495 | if (primitive.extensions && primitive.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION]) {
2496 |
2497 | // Use DRACO geometry if available
2498 | geometryPromise = createDracoPrimitive(primitive);
2499 |
2500 | } else {
2501 |
2502 | // Otherwise create a new geometry
2503 | geometryPromise = addPrimitiveAttributes(new THREE.BufferGeometry(), primitive, parser);
2504 |
2505 | }
2506 |
2507 | // Cache this geometry
2508 | cache[cacheKey] = { primitive: primitive, promise: geometryPromise };
2509 |
2510 | pending.push(geometryPromise);
2511 |
2512 | }
2513 |
2514 | }
2515 |
2516 | return Promise.all(pending);
2517 |
2518 | };
2519 |
2520 | /**
2521 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
2522 | * @param {number} meshIndex
2523 | * @return {Promise}
2524 | */
2525 | GLTFParser.prototype.loadMesh = function (meshIndex) {
2526 |
2527 | var parser = this;
2528 | var json = this.json;
2529 |
2530 | var meshDef = json.meshes[meshIndex];
2531 | var primitives = meshDef.primitives;
2532 |
2533 | var pending = [];
2534 |
2535 | for (var i = 0, il = primitives.length; i < il; i++) {
2536 |
2537 | var material = primitives[i].material === undefined
2538 | ? createDefaultMaterial()
2539 | : this.getDependency('material', primitives[i].material);
2540 |
2541 | pending.push(material);
2542 |
2543 | }
2544 |
2545 | return Promise.all(pending).then(function (originalMaterials) {
2546 |
2547 | return parser.loadGeometries(primitives).then(function (geometries) {
2548 |
2549 | var meshes = [];
2550 |
2551 | for (var i = 0, il = geometries.length; i < il; i++) {
2552 |
2553 | var geometry = geometries[i];
2554 | var primitive = primitives[i];
2555 |
2556 | // 1. create Mesh
2557 |
2558 | var mesh;
2559 |
2560 | var material = originalMaterials[i];
2561 |
2562 | if (primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
2563 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
2564 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
2565 | primitive.mode === undefined) {
2566 |
2567 | // .isSkinnedMesh isn't in glTF spec. See .markDefs()
2568 | mesh = meshDef.isSkinnedMesh === true
2569 | ? new THREE.SkinnedMesh(geometry, material)
2570 | : new THREE.Mesh(geometry, material);
2571 |
2572 | if (mesh.isSkinnedMesh === true && !mesh.geometry.attributes.skinWeight.normalized) {
2573 |
2574 | // we normalize floating point skin weight array to fix malformed assets (see #15319)
2575 | // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
2576 | mesh.normalizeSkinWeights();
2577 |
2578 | }
2579 |
2580 | if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP) {
2581 |
2582 | mesh.drawMode = THREE.TriangleStripDrawMode;
2583 |
2584 | } else if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN) {
2585 |
2586 | mesh.drawMode = THREE.TriangleFanDrawMode;
2587 |
2588 | }
2589 |
2590 | } else if (primitive.mode === WEBGL_CONSTANTS.LINES) {
2591 |
2592 | mesh = new THREE.LineSegments(geometry, material);
2593 |
2594 | } else if (primitive.mode === WEBGL_CONSTANTS.LINE_STRIP) {
2595 |
2596 | mesh = new THREE.Line(geometry, material);
2597 |
2598 | } else if (primitive.mode === WEBGL_CONSTANTS.LINE_LOOP) {
2599 |
2600 | mesh = new THREE.LineLoop(geometry, material);
2601 |
2602 | } else if (primitive.mode === WEBGL_CONSTANTS.POINTS) {
2603 |
2604 | mesh = new THREE.Points(geometry, material);
2605 |
2606 | } else {
2607 |
2608 | throw new Error('THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode);
2609 |
2610 | }
2611 |
2612 | if (Object.keys(mesh.geometry.morphAttributes).length > 0) {
2613 |
2614 | updateMorphTargets(mesh, meshDef);
2615 |
2616 | }
2617 |
2618 | mesh.name = meshDef.name || ('mesh_' + meshIndex);
2619 |
2620 | if (geometries.length > 1) mesh.name += '_' + i;
2621 |
2622 | assignExtrasToUserData(mesh, meshDef);
2623 |
2624 | parser.assignFinalMaterial(mesh);
2625 |
2626 | meshes.push(mesh);
2627 |
2628 | }
2629 |
2630 | if (meshes.length === 1) {
2631 |
2632 | return meshes[0];
2633 |
2634 | }
2635 |
2636 | var group = new THREE.Group();
2637 |
2638 | for (var i = 0, il = meshes.length; i < il; i++) {
2639 |
2640 | group.add(meshes[i]);
2641 |
2642 | }
2643 |
2644 | return group;
2645 |
2646 | });
2647 |
2648 | });
2649 |
2650 | };
2651 |
2652 | /**
2653 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
2654 | * @param {number} cameraIndex
2655 | * @return {Promise}
2656 | */
2657 | GLTFParser.prototype.loadCamera = function (cameraIndex) {
2658 |
2659 | var camera;
2660 | var cameraDef = this.json.cameras[cameraIndex];
2661 | var params = cameraDef[cameraDef.type];
2662 |
2663 | if (!params) {
2664 |
2665 | console.warn('THREE.GLTFLoader: Missing camera parameters.');
2666 | return;
2667 |
2668 | }
2669 |
2670 | if (cameraDef.type === 'perspective') {
2671 |
2672 | camera = new THREE.PerspectiveCamera(THREE.Math.radToDeg(params.yfov), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6);
2673 |
2674 | } else if (cameraDef.type === 'orthographic') {
2675 |
2676 | camera = new THREE.OrthographicCamera(params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar);
2677 |
2678 | }
2679 |
2680 | if (cameraDef.name !== undefined) camera.name = cameraDef.name;
2681 |
2682 | assignExtrasToUserData(camera, cameraDef);
2683 |
2684 | return Promise.resolve(camera);
2685 |
2686 | };
2687 |
2688 | /**
2689 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
2690 | * @param {number} skinIndex
2691 | * @return {Promise