├── models
└── pistol.glb
├── README.md
├── index.html
├── viewer.css
├── vendor
├── OrbitControls.js
└── GLTFLoader.js
└── viewer.js
/models/pistol.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytezeroseven/GLB-Viewer/HEAD/models/pistol.glb
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GLB-Viewer
2 | 3D Viewer Web application for viewing glb files inspired by Autodesk Forge Viewer
3 |
4 | ## Features
5 | 1. Importing glb files
6 | 2. Measure distance b/w two points
7 | 3. Explode faces or separate objects of the model
8 | 4. Fully responsive with mobile support
9 | 5. Standard 3D views with orientation cube
10 |
11 | ## Demo
12 | https://glb-viewer.blogspot.com/
13 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Viewer3D
4 |
5 |
6 |
7 |
8 |
19 |
20 |
21 |
22 |
45 |
46 |
47 |
56 |
75 |
79 |
82 |
83 |
Loading...
84 |
85 |
86 |
87 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
127 |
128 |
--------------------------------------------------------------------------------
/viewer.css:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Copyright Abhinav Singh Chauhan (@xclkvj)
4 | Copying the contents of this file by any means is prohibited.
5 | */
6 |
7 | #viewerCanvasWrapper, #viewerCanvasWrapper canvas {
8 | position: absolute;
9 | width: 100%;
10 | height: 100%;
11 | outline: 0;
12 | }
13 |
14 | #orientCubeWrapper canvas {
15 | outline: 0;
16 | }
17 |
18 | #modelBrowser {
19 | z-index: 15;
20 | }
21 |
22 | .wrapper {
23 | display: flex;
24 | flex-direction: column;
25 | height: 100vh;
26 | color: #666;
27 | overflow: hidden;
28 | -webkit-tap-highlight-color: transparent;
29 | }
30 |
31 | .viewer-wrapper {
32 | position: relative;
33 | background: #eee;
34 | flex: 1;
35 | }
36 |
37 | .viewer-loader {
38 | position: relative;
39 | width: 100%;
40 | height: 100%;
41 | z-index: 12;
42 | }
43 |
44 | .header, .footer {
45 | display: flex;
46 | padding: 0.55em;
47 | background: #fff;
48 | border: 1px solid #ddd;
49 | border-left: none;
50 | border-right: none;
51 | z-index: 10;
52 | }
53 |
54 | .header {
55 | justify-content: space-between;
56 | }
57 |
58 | .footer {
59 | justify-content: center;
60 | }
61 |
62 | .header-item, .footer-item {
63 | display: flex;
64 | border-radius: 1.55em;
65 | padding: 0.45em 0.85em;
66 | margin: 0 5px;
67 | color: #666;
68 | font-weight: medium;
69 | transition: all 0.2s;
70 | cursor: pointer;
71 | }
72 |
73 | .header-item:hover, .footer-item:hover {
74 | box-shadow: 0 0.11em 0.11em #bbb;
75 | color: #111;
76 | }
77 |
78 | .header-item:active, .footer-item:active, .item-selected, .item-selected:hover {
79 | box-shadow: 0 0.11em 0.11em #ccc inset;
80 | color: #03a9f4;
81 | }
82 |
83 | .header-item {
84 | align-items: center;
85 | }
86 |
87 | .header-icon {
88 | font-size: 1.25em;
89 | padding: 0 0.4em;
90 | }
91 |
92 | .footer-item {
93 | flex-direction: column;
94 | align-items: center;
95 | padding: 0.65em 1.55em;
96 | }
97 |
98 | .footer-icon {
99 | font-size: 2em;
100 | }
101 |
102 | .header-left, .header-right {
103 | display: flex;
104 | }
105 |
106 | .slider-container {
107 | position: absolute;
108 | bottom: 0;
109 | left: 0;
110 | background: #fff;
111 | border-top: 1px solid #ccc;
112 | width: 100%;
113 | text-align: center;
114 | }
115 |
116 | .slider {
117 | appearance: none;
118 | -webkit-appearance: none;
119 | height: 10px;
120 | border-radius: 4px;
121 | outline: 0;
122 | background: #d3d3d3;
123 | opacity: 0.7;
124 | transition: opacity 0.2s;
125 | -webkit-transition: 0.2s;
126 | margin: 20px 0;
127 | width: 100%;
128 | }
129 |
130 | .slider:hover {
131 | opacity: 1;
132 | }
133 |
134 | .slider::-webkit-slider-thumb {
135 | -webkit-appearance: none;
136 | appearance: none;
137 | width: 25px;
138 | height: 25px;
139 | background: #03a9f4;
140 | border-radius: 50%;
141 | cursor: pointer;
142 | }
143 |
144 | .slider::-moz-range-thumb {
145 | width: 25px;
146 | height: 25px;
147 | background: #03a9f4;
148 | border-radius: 50%;
149 | cursor: pointer;
150 | }
151 |
152 | .share-sidebar {
153 | position: absolute;
154 | right: 0;
155 | top: 0;
156 | background: #fff;
157 | border-left: 1px solid #ccc;
158 | height: 100%;
159 | padding: 20px;
160 | }
161 |
162 | .sidebar-title {
163 | font-weight: lighter;
164 | text-align: center;
165 | }
166 |
167 | .loader {
168 | position: absolute;
169 | left: 0;
170 | top: 0;
171 | background: #fff;
172 | width: 100%;
173 | height: 100%;
174 | z-index: 20;
175 | }
176 |
177 | .loader-spinner {
178 | position: absolute;
179 | left: 50%;
180 | top: 50%;
181 | margin-left: -60px;
182 | margin-top: -60px;
183 | width: 120px;
184 | height: 120px;
185 | border-radius: 50%;
186 | border: 10px solid #f3f3f3;
187 | border-top: 10px solid #3498db;
188 | animation: spin 2s linear infinite;
189 | }
190 |
191 | .loader-text {
192 | position: absolute;
193 | left: 50%;
194 | bottom: 10px;
195 | transform: translate(-50%, 0);
196 | }
197 |
198 | @keyframes spin {
199 | from { transform: rotate(0deg); }
200 | to { transform: rotate(360deg); }
201 | }
202 |
203 | #explodeSlider {
204 | width: 200px;
205 | }
206 |
207 | .right-container {
208 | position: absolute;
209 | right: 10px;
210 | top: 10px;
211 | display: flex;
212 | }
213 |
214 | .left-sidebar {
215 | position: absolute;
216 | left: 0;
217 | top: 0;
218 | height: 100%;
219 | background: #fff;
220 | border-right: 1px solid #bbb;
221 | min-width: 300px;
222 | display: flex;
223 | flex-direction: column;
224 | }
225 |
226 | .sidebar-header {
227 | border-bottom: 1px solid #bbb;
228 | padding: 15px 10px;
229 | font-size: 1.25em;
230 | display: flex;
231 | justify-content: space-between;
232 | align-items: center;
233 | }
234 |
235 | .sidebar-content {
236 | height: 100%;
237 | overflow-y: auto;
238 | }
239 |
240 | #backToHome {
241 | color: #aaa;
242 | opacity: 0.5;
243 | cursor: pointer;
244 | font-size: 1.5em;
245 | }
246 |
247 | #backToHome:hover {
248 | opacity: 1;
249 | }
250 |
251 | #orientCubeWrapper {
252 | width: 130px;
253 | height: 130px;
254 | opacity: 0.7;
255 | transition: opacity 0.3s;
256 | -webkit-transition: 0.3s;
257 | }
258 |
259 | #orientCubeWrapper:hover {
260 | opacity: 1;
261 | }
262 |
263 | .graph-item-wrapper {
264 | padding: 0.5em 0.75em;
265 | }
266 |
267 | .graph-item-wrapper:hover {
268 | background: rgba(0, 191, 255, 0.2) !important;
269 | }
270 |
271 | .graph-item-wrapper:nth-child(even) {
272 | background: #f5f8fb;
273 | }
274 |
275 | .graph-item-wrapper:nth-child(odd) {
276 | background: #fff;
277 | }
278 |
279 | .graph-item {
280 | display: flex;
281 | justify-content: space-between;
282 | }
283 |
284 | .graph-left, .graph-right {
285 | display: flex;
286 | align-items: center;
287 | }
288 |
289 | .graph-folder {
290 | margin-right: 10px;
291 | }
292 |
293 | .graph-name {
294 | overflow: hidden;
295 | }
296 |
297 | .graph-visible {
298 | margin-left: 10px;
299 | }
300 |
301 | .graph-visible, .graph-folder, .graph-name {
302 | cursor: pointer;
303 | }
304 |
305 | @media only screen and (max-width: 600px) {
306 | #modelBrowser, .share-sidebar {
307 | width: 100%;
308 | }
309 |
310 | .footer-title {
311 | display: none;
312 | }
313 | }
314 |
315 | @media only screen and (max-width: 500px) {
316 | .header-title {
317 | display: none;
318 | }
319 | .footer {
320 | justify-content: space-between;
321 | overflow-x: scroll;
322 | }
323 | }
324 |
325 | .explode-content {
326 | display: flex;
327 | flex-direction: column;
328 | align-items: center;
329 | justify-content: center;
330 | }
331 |
332 | .cb-container {
333 | position: relative;
334 | padding-left: 35px;
335 | margin-bottom: 12px;
336 | cursor: pointer;
337 | -webkit-user-select: none;
338 | -moz-user-select: none;
339 | -ms-user-select: none;
340 | user-select: none;
341 | display: flex;
342 | align-items: center;
343 | }
344 |
345 | .cb-container input {
346 | position: absolute;
347 | opacity: 0;
348 | cursor: pointer;
349 | height: 0;
350 | width: 0;
351 | margin: 10px;
352 | }
353 |
354 | .checkmark {
355 | position: absolute;
356 | top: 0;
357 | left: 0;
358 | height: 25px;
359 | width: 25px;
360 | border-radius: 50%;
361 | background-color: #eee;
362 | }
363 |
364 | .cb-container:hover input ~ .checkmark {
365 | background-color: #ccc;
366 | }
367 |
368 | .cb-container input:checked ~ .checkmark {
369 | background-color: #03a9f4;
370 | }
371 |
372 | .cb-checkmark:after {
373 | content: "";
374 | position: absolute;
375 | display: none;
376 | }
377 |
378 | .cb-container input:checked ~ .checkmark:after {
379 | display: block;
380 | }
381 |
382 | .cb-container .checkmark:after {
383 | left: 9px;
384 | top: 5px;
385 | width: 5px;
386 | height: 10px;
387 | border: solid white;
388 | border-width: 0 3px 3px 0;
389 | -webkit-transform: rotate(45deg);
390 | -ms-transform: rotate(45deg);
391 | transform: rotate(45deg);
392 | }
--------------------------------------------------------------------------------
/vendor/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | * @author ScieCode / http://github.com/sciecode
8 | */
9 |
10 | // This set of controls performs orbiting, dollying (zooming), and panning.
11 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
12 | //
13 | // Orbit - left mouse / touch: one-finger move
14 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
15 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
16 |
17 | THREE.OrbitControls = function ( object, domElement ) {
18 |
19 | if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
20 | if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
21 |
22 | this.object = object;
23 | this.domElement = domElement;
24 |
25 | // Set to false to disable this control
26 | this.enabled = true;
27 |
28 | // "target" sets the location of focus, where the object orbits around
29 | this.target = new THREE.Vector3();
30 |
31 | // How far you can dolly in and out ( PerspectiveCamera only )
32 | this.minDistance = 0;
33 | this.maxDistance = Infinity;
34 |
35 | // How far you can zoom in and out ( OrthographicCamera only )
36 | this.minZoom = 0;
37 | this.maxZoom = Infinity;
38 |
39 | // How far you can orbit vertically, upper and lower limits.
40 | // Range is 0 to Math.PI radians.
41 | this.minPolarAngle = 0; // radians
42 | this.maxPolarAngle = Math.PI; // radians
43 |
44 | // How far you can orbit horizontally, upper and lower limits.
45 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
46 | this.minAzimuthAngle = - Infinity; // radians
47 | this.maxAzimuthAngle = Infinity; // radians
48 |
49 | // Set to true to enable damping (inertia)
50 | // If damping is enabled, you must call controls.update() in your animation loop
51 | this.enableDamping = false;
52 | this.dampingFactor = 0.05;
53 |
54 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
55 | // Set to false to disable zooming
56 | this.enableZoom = true;
57 | this.zoomSpeed = 1.0;
58 |
59 | // Set to false to disable rotating
60 | this.enableRotate = true;
61 | this.rotateSpeed = 1.0;
62 |
63 | // Set to false to disable panning
64 | this.enablePan = true;
65 | this.panSpeed = 1.0;
66 | this.screenSpacePanning = false; // if true, pan in screen-space
67 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
68 |
69 | // Set to true to automatically rotate around the target
70 | // If auto-rotate is enabled, you must call controls.update() in your animation loop
71 | this.autoRotate = false;
72 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
73 |
74 | // Set to false to disable use of the keys
75 | this.enableKeys = true;
76 |
77 | // The four arrow keys
78 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
79 |
80 | // Mouse buttons
81 | this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN };
82 |
83 | // Touch fingers
84 | this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN };
85 |
86 | // for reset
87 | this.target0 = this.target.clone();
88 | this.position0 = this.object.position.clone();
89 | this.zoom0 = this.object.zoom;
90 |
91 | //
92 | // public methods
93 | //
94 |
95 | this.getPolarAngle = function () {
96 |
97 | return spherical.phi;
98 |
99 | };
100 |
101 | this.getAzimuthalAngle = function () {
102 |
103 | return spherical.theta;
104 |
105 | };
106 |
107 | this.saveState = function () {
108 |
109 | scope.target0.copy( scope.target );
110 | scope.position0.copy( scope.object.position );
111 | scope.zoom0 = scope.object.zoom;
112 |
113 | };
114 |
115 | this.reset = function () {
116 |
117 | scope.target.copy( scope.target0 );
118 | scope.object.position.copy( scope.position0 );
119 | scope.object.zoom = scope.zoom0;
120 |
121 | scope.object.updateProjectionMatrix();
122 | scope.dispatchEvent( changeEvent );
123 |
124 | scope.update();
125 |
126 | state = STATE.NONE;
127 |
128 | };
129 |
130 | // this method is exposed, but perhaps it would be better if we can make it private...
131 | this.update = function () {
132 |
133 | var offset = new THREE.Vector3();
134 |
135 | // so camera.up is the orbit axis
136 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
137 | var quatInverse = quat.clone().inverse();
138 |
139 | var lastPosition = new THREE.Vector3();
140 | var lastQuaternion = new THREE.Quaternion();
141 |
142 | return function update() {
143 |
144 | var position = scope.object.position;
145 |
146 | offset.copy( position ).sub( scope.target );
147 |
148 | // rotate offset to "y-axis-is-up" space
149 | offset.applyQuaternion( quat );
150 |
151 | // angle from z-axis around y-axis
152 | spherical.setFromVector3( offset );
153 |
154 | if ( scope.autoRotate && state === STATE.NONE ) {
155 |
156 | rotateLeft( getAutoRotationAngle() );
157 |
158 | }
159 |
160 | if ( scope.enableDamping ) {
161 |
162 | spherical.theta += sphericalDelta.theta * scope.dampingFactor;
163 | spherical.phi += sphericalDelta.phi * scope.dampingFactor;
164 |
165 | } else {
166 |
167 | spherical.theta += sphericalDelta.theta;
168 | spherical.phi += sphericalDelta.phi;
169 |
170 | }
171 |
172 | // restrict theta to be between desired limits
173 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
174 |
175 | // restrict phi to be between desired limits
176 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
177 |
178 | spherical.makeSafe();
179 |
180 |
181 | spherical.radius *= scale;
182 |
183 | // restrict radius to be between desired limits
184 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
185 |
186 | // move target to panned location
187 |
188 | if ( scope.enableDamping === true ) {
189 |
190 | scope.target.addScaledVector( panOffset, scope.dampingFactor );
191 |
192 | } else {
193 |
194 | scope.target.add( panOffset );
195 |
196 | }
197 |
198 | offset.setFromSpherical( spherical );
199 |
200 | // rotate offset back to "camera-up-vector-is-up" space
201 | offset.applyQuaternion( quatInverse );
202 |
203 | position.copy( scope.target ).add( offset );
204 |
205 | scope.object.lookAt( scope.target );
206 |
207 | if ( scope.enableDamping === true ) {
208 |
209 | sphericalDelta.theta *= ( 1 - scope.dampingFactor );
210 | sphericalDelta.phi *= ( 1 - scope.dampingFactor );
211 |
212 | panOffset.multiplyScalar( 1 - scope.dampingFactor );
213 |
214 | } else {
215 |
216 | sphericalDelta.set( 0, 0, 0 );
217 |
218 | panOffset.set( 0, 0, 0 );
219 |
220 | }
221 |
222 | scale = 1;
223 |
224 | // update condition is:
225 | // min(camera displacement, camera rotation in radians)^2 > EPS
226 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
227 |
228 | if ( zoomChanged ||
229 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
230 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
231 |
232 | scope.dispatchEvent( changeEvent );
233 |
234 | lastPosition.copy( scope.object.position );
235 | lastQuaternion.copy( scope.object.quaternion );
236 | zoomChanged = false;
237 |
238 | return true;
239 |
240 | }
241 |
242 | return false;
243 |
244 | };
245 |
246 | }();
247 |
248 | this.dispose = function () {
249 |
250 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
251 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
252 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
253 |
254 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
255 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
256 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
257 |
258 | document.removeEventListener( 'mousemove', onMouseMove, false );
259 | document.removeEventListener( 'mouseup', onMouseUp, false );
260 |
261 | scope.domElement.removeEventListener( 'keydown', onKeyDown, false );
262 |
263 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
264 |
265 | };
266 |
267 | //
268 | // internals
269 | //
270 |
271 | var scope = this;
272 |
273 | var changeEvent = { type: 'change' };
274 | var startEvent = { type: 'start' };
275 | var endEvent = { type: 'end' };
276 |
277 | var STATE = {
278 | NONE: - 1,
279 | ROTATE: 0,
280 | DOLLY: 1,
281 | PAN: 2,
282 | TOUCH_ROTATE: 3,
283 | TOUCH_PAN: 4,
284 | TOUCH_DOLLY_PAN: 5,
285 | TOUCH_DOLLY_ROTATE: 6
286 | };
287 |
288 | var state = STATE.NONE;
289 |
290 | var EPS = 0.000001;
291 |
292 | // current position in spherical coordinates
293 | var spherical = new THREE.Spherical();
294 | var sphericalDelta = new THREE.Spherical();
295 |
296 | var scale = 1;
297 | var panOffset = new THREE.Vector3();
298 | this.panOffset = panOffset;
299 | var zoomChanged = false;
300 |
301 | var rotateStart = new THREE.Vector2();
302 | var rotateEnd = new THREE.Vector2();
303 | var rotateDelta = new THREE.Vector2();
304 |
305 | var panStart = new THREE.Vector2();
306 | var panEnd = new THREE.Vector2();
307 | var panDelta = new THREE.Vector2();
308 |
309 | var dollyStart = new THREE.Vector2();
310 | var dollyEnd = new THREE.Vector2();
311 | var dollyDelta = new THREE.Vector2();
312 |
313 | function getAutoRotationAngle() {
314 |
315 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
316 |
317 | }
318 |
319 | function getZoomScale() {
320 |
321 | return Math.pow( 0.95, scope.zoomSpeed );
322 |
323 | }
324 |
325 | function rotateLeft( angle ) {
326 |
327 | sphericalDelta.theta -= angle;
328 |
329 | }
330 |
331 | function rotateUp( angle ) {
332 |
333 | sphericalDelta.phi -= angle;
334 |
335 | }
336 |
337 | var panLeft = function () {
338 |
339 | var v = new THREE.Vector3();
340 |
341 | return function panLeft( distance, objectMatrix ) {
342 |
343 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
344 | v.multiplyScalar( - distance );
345 |
346 | panOffset.add( v );
347 |
348 | };
349 |
350 | }();
351 |
352 | var panUp = function () {
353 |
354 | var v = new THREE.Vector3();
355 |
356 | return function panUp( distance, objectMatrix ) {
357 |
358 | if ( scope.screenSpacePanning === true ) {
359 |
360 | v.setFromMatrixColumn( objectMatrix, 1 );
361 |
362 | } else {
363 |
364 | v.setFromMatrixColumn( objectMatrix, 0 );
365 | v.crossVectors( scope.object.up, v );
366 |
367 | }
368 |
369 | v.multiplyScalar( distance );
370 |
371 | panOffset.add( v );
372 |
373 | };
374 |
375 | }();
376 |
377 | // deltaX and deltaY are in pixels; right and down are positive
378 | var pan = function () {
379 |
380 | var offset = new THREE.Vector3();
381 |
382 | return function pan( deltaX, deltaY ) {
383 |
384 | var element = scope.domElement;
385 |
386 | if ( scope.object.isPerspectiveCamera ) {
387 |
388 | // perspective
389 | var position = scope.object.position;
390 | offset.copy( position ).sub( scope.target );
391 | var targetDistance = offset.length();
392 |
393 | // half of the fov is center to top of screen
394 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
395 |
396 | // we use only clientHeight here so aspect ratio does not distort speed
397 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
398 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
399 |
400 | } else if ( scope.object.isOrthographicCamera ) {
401 |
402 | // orthographic
403 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
404 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
405 |
406 | } else {
407 |
408 | // camera neither orthographic nor perspective
409 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
410 | scope.enablePan = false;
411 |
412 | }
413 |
414 | };
415 |
416 | }();
417 |
418 | function dollyIn( dollyScale ) {
419 |
420 | if ( scope.object.isPerspectiveCamera ) {
421 |
422 | scale /= dollyScale;
423 |
424 | } else if ( scope.object.isOrthographicCamera ) {
425 |
426 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
427 | scope.object.updateProjectionMatrix();
428 | zoomChanged = true;
429 |
430 | } else {
431 |
432 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
433 | scope.enableZoom = false;
434 |
435 | }
436 |
437 | }
438 |
439 | function dollyOut( dollyScale ) {
440 |
441 | if ( scope.object.isPerspectiveCamera ) {
442 |
443 | scale *= dollyScale;
444 |
445 | } else if ( scope.object.isOrthographicCamera ) {
446 |
447 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
448 | scope.object.updateProjectionMatrix();
449 | zoomChanged = true;
450 |
451 | } else {
452 |
453 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
454 | scope.enableZoom = false;
455 |
456 | }
457 |
458 | }
459 |
460 | //
461 | // event callbacks - update the object state
462 | //
463 |
464 | function handleMouseDownRotate( event ) {
465 |
466 | rotateStart.set( event.clientX, event.clientY );
467 |
468 | }
469 |
470 | function handleMouseDownDolly( event ) {
471 |
472 | dollyStart.set( event.clientX, event.clientY );
473 |
474 | }
475 |
476 | function handleMouseDownPan( event ) {
477 |
478 | panStart.set( event.clientX, event.clientY );
479 |
480 | }
481 |
482 | function handleMouseMoveRotate( event ) {
483 |
484 | rotateEnd.set( event.clientX, event.clientY );
485 |
486 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
487 |
488 | var element = scope.domElement;
489 |
490 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
491 |
492 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
493 |
494 | rotateStart.copy( rotateEnd );
495 |
496 | scope.update();
497 |
498 | }
499 |
500 | function handleMouseMoveDolly( event ) {
501 |
502 | dollyEnd.set( event.clientX, event.clientY );
503 |
504 | dollyDelta.subVectors( dollyEnd, dollyStart );
505 |
506 | if ( dollyDelta.y > 0 ) {
507 |
508 | dollyIn( getZoomScale() );
509 |
510 | } else if ( dollyDelta.y < 0 ) {
511 |
512 | dollyOut( getZoomScale() );
513 |
514 | }
515 |
516 | dollyStart.copy( dollyEnd );
517 |
518 | scope.update();
519 |
520 | }
521 |
522 | function handleMouseMovePan( event ) {
523 |
524 | panEnd.set( event.clientX, event.clientY );
525 |
526 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
527 |
528 | pan( panDelta.x, panDelta.y );
529 |
530 | panStart.copy( panEnd );
531 |
532 | scope.update();
533 |
534 | }
535 |
536 | function handleMouseUp( /*event*/ ) {
537 |
538 | // no-op
539 |
540 | }
541 |
542 | function handleMouseWheel( event ) {
543 |
544 | if ( event.deltaY < 0 ) {
545 |
546 | dollyOut( getZoomScale() );
547 |
548 | } else if ( event.deltaY > 0 ) {
549 |
550 | dollyIn( getZoomScale() );
551 |
552 | }
553 |
554 | scope.update();
555 |
556 | }
557 |
558 | function handleKeyDown( event ) {
559 |
560 | var needsUpdate = false;
561 |
562 | switch ( event.keyCode ) {
563 |
564 | case scope.keys.UP:
565 | pan( 0, scope.keyPanSpeed );
566 | needsUpdate = true;
567 | break;
568 |
569 | case scope.keys.BOTTOM:
570 | pan( 0, - scope.keyPanSpeed );
571 | needsUpdate = true;
572 | break;
573 |
574 | case scope.keys.LEFT:
575 | pan( scope.keyPanSpeed, 0 );
576 | needsUpdate = true;
577 | break;
578 |
579 | case scope.keys.RIGHT:
580 | pan( - scope.keyPanSpeed, 0 );
581 | needsUpdate = true;
582 | break;
583 |
584 | }
585 |
586 | if ( needsUpdate ) {
587 |
588 | // prevent the browser from scrolling on cursor keys
589 | event.preventDefault();
590 |
591 | scope.update();
592 |
593 | }
594 |
595 |
596 | }
597 |
598 | function handleTouchStartRotate( event ) {
599 |
600 | if ( event.touches.length == 1 ) {
601 |
602 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
603 |
604 | } else {
605 |
606 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
607 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
608 |
609 | rotateStart.set( x, y );
610 |
611 | }
612 |
613 | }
614 |
615 | function handleTouchStartPan( event ) {
616 |
617 | if ( event.touches.length == 1 ) {
618 |
619 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
620 |
621 | } else {
622 |
623 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
624 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
625 |
626 | panStart.set( x, y );
627 |
628 | }
629 |
630 | }
631 |
632 | function handleTouchStartDolly( event ) {
633 |
634 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
635 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
636 |
637 | var distance = Math.sqrt( dx * dx + dy * dy );
638 |
639 | dollyStart.set( 0, distance );
640 |
641 | }
642 |
643 | function handleTouchStartDollyPan( event ) {
644 |
645 | if ( scope.enableZoom ) handleTouchStartDolly( event );
646 |
647 | if ( scope.enablePan ) handleTouchStartPan( event );
648 |
649 | }
650 |
651 | function handleTouchStartDollyRotate( event ) {
652 |
653 | if ( scope.enableZoom ) handleTouchStartDolly( event );
654 |
655 | if ( scope.enableRotate ) handleTouchStartRotate( event );
656 |
657 | }
658 |
659 | function handleTouchMoveRotate( event ) {
660 |
661 | if ( event.touches.length == 1 ) {
662 |
663 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
664 |
665 | } else {
666 |
667 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
668 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
669 |
670 | rotateEnd.set( x, y );
671 |
672 | }
673 |
674 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
675 |
676 | var element = scope.domElement;
677 |
678 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
679 |
680 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
681 |
682 | rotateStart.copy( rotateEnd );
683 |
684 | }
685 |
686 | function handleTouchMovePan( event ) {
687 |
688 | if ( event.touches.length == 1 ) {
689 |
690 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
691 |
692 | } else {
693 |
694 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
695 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
696 |
697 | panEnd.set( x, y );
698 |
699 | }
700 |
701 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
702 |
703 | pan( panDelta.x, panDelta.y );
704 |
705 | panStart.copy( panEnd );
706 |
707 | }
708 |
709 | function handleTouchMoveDolly( event ) {
710 |
711 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
712 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
713 |
714 | var distance = Math.sqrt( dx * dx + dy * dy );
715 |
716 | dollyEnd.set( 0, distance );
717 |
718 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
719 |
720 | dollyIn( dollyDelta.y );
721 |
722 | dollyStart.copy( dollyEnd );
723 |
724 | }
725 |
726 | function handleTouchMoveDollyPan( event ) {
727 |
728 | if ( scope.enableZoom ) handleTouchMoveDolly( event );
729 |
730 | if ( scope.enablePan ) handleTouchMovePan( event );
731 |
732 | }
733 |
734 | function handleTouchMoveDollyRotate( event ) {
735 |
736 | if ( scope.enableZoom ) handleTouchMoveDolly( event );
737 |
738 | if ( scope.enableRotate ) handleTouchMoveRotate( event );
739 |
740 | }
741 |
742 | function handleTouchEnd( /*event*/ ) {
743 |
744 | // no-op
745 |
746 | }
747 |
748 | //
749 | // event handlers - FSM: listen for events and reset state
750 | //
751 |
752 | function onMouseDown( event ) {
753 |
754 | if ( scope.enabled === false ) return;
755 |
756 | // Prevent the browser from scrolling.
757 | event.preventDefault();
758 |
759 | // Manually set the focus since calling preventDefault above
760 | // prevents the browser from setting it automatically.
761 |
762 | scope.domElement.focus ? scope.domElement.focus() : window.focus();
763 |
764 | var mouseAction;
765 |
766 | switch ( event.button ) {
767 |
768 | case 0:
769 |
770 | mouseAction = scope.mouseButtons.LEFT;
771 | break;
772 |
773 | case 1:
774 |
775 | mouseAction = scope.mouseButtons.MIDDLE;
776 | break;
777 |
778 | case 2:
779 |
780 | mouseAction = scope.mouseButtons.RIGHT;
781 | break;
782 |
783 | default:
784 |
785 | mouseAction = - 1;
786 |
787 | }
788 |
789 | switch ( mouseAction ) {
790 |
791 | case THREE.MOUSE.DOLLY:
792 |
793 | if ( scope.enableZoom === false ) return;
794 |
795 | handleMouseDownDolly( event );
796 |
797 | state = STATE.DOLLY;
798 |
799 | break;
800 |
801 | case THREE.MOUSE.ROTATE:
802 |
803 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
804 |
805 | if ( scope.enablePan === false ) return;
806 |
807 | handleMouseDownPan( event );
808 |
809 | state = STATE.PAN;
810 |
811 | } else {
812 |
813 | if ( scope.enableRotate === false ) return;
814 |
815 | handleMouseDownRotate( event );
816 |
817 | state = STATE.ROTATE;
818 |
819 | }
820 |
821 | break;
822 |
823 | case THREE.MOUSE.PAN:
824 |
825 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
826 |
827 | if ( scope.enableRotate === false ) return;
828 |
829 | handleMouseDownRotate( event );
830 |
831 | state = STATE.ROTATE;
832 |
833 | } else {
834 |
835 | if ( scope.enablePan === false ) return;
836 |
837 | handleMouseDownPan( event );
838 |
839 | state = STATE.PAN;
840 |
841 | }
842 |
843 | break;
844 |
845 | default:
846 |
847 | state = STATE.NONE;
848 |
849 | }
850 |
851 | if ( state !== STATE.NONE ) {
852 |
853 | document.addEventListener( 'mousemove', onMouseMove, false );
854 | document.addEventListener( 'mouseup', onMouseUp, false );
855 |
856 | scope.dispatchEvent( startEvent );
857 |
858 | }
859 |
860 | }
861 |
862 | function onMouseMove( event ) {
863 |
864 | if ( scope.enabled === false ) return;
865 |
866 | event.preventDefault();
867 |
868 | switch ( state ) {
869 |
870 | case STATE.ROTATE:
871 |
872 | if ( scope.enableRotate === false ) return;
873 |
874 | handleMouseMoveRotate( event );
875 |
876 | break;
877 |
878 | case STATE.DOLLY:
879 |
880 | if ( scope.enableZoom === false ) return;
881 |
882 | handleMouseMoveDolly( event );
883 |
884 | break;
885 |
886 | case STATE.PAN:
887 |
888 | if ( scope.enablePan === false ) return;
889 |
890 | handleMouseMovePan( event );
891 |
892 | break;
893 |
894 | }
895 |
896 | }
897 |
898 | function onMouseUp( event ) {
899 |
900 | if ( scope.enabled === false ) return;
901 |
902 | handleMouseUp( event );
903 |
904 | document.removeEventListener( 'mousemove', onMouseMove, false );
905 | document.removeEventListener( 'mouseup', onMouseUp, false );
906 |
907 | scope.dispatchEvent( endEvent );
908 |
909 | state = STATE.NONE;
910 |
911 | }
912 |
913 | function onMouseWheel( event ) {
914 |
915 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
916 |
917 | event.preventDefault();
918 | event.stopPropagation();
919 |
920 | scope.dispatchEvent( startEvent );
921 |
922 | handleMouseWheel( event );
923 |
924 | scope.dispatchEvent( endEvent );
925 |
926 | }
927 |
928 | function onKeyDown( event ) {
929 |
930 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
931 |
932 | handleKeyDown( event );
933 |
934 | }
935 |
936 | function onTouchStart( event ) {
937 |
938 | if ( scope.enabled === false ) return;
939 |
940 | event.preventDefault();
941 |
942 | switch ( event.touches.length ) {
943 |
944 | case 1:
945 |
946 | switch ( scope.touches.ONE ) {
947 |
948 | case THREE.TOUCH.ROTATE:
949 |
950 | if ( scope.enableRotate === false ) return;
951 |
952 | handleTouchStartRotate( event );
953 |
954 | state = STATE.TOUCH_ROTATE;
955 |
956 | break;
957 |
958 | case THREE.TOUCH.PAN:
959 |
960 | if ( scope.enablePan === false ) return;
961 |
962 | handleTouchStartPan( event );
963 |
964 | state = STATE.TOUCH_PAN;
965 |
966 | break;
967 |
968 | default:
969 |
970 | state = STATE.NONE;
971 |
972 | }
973 |
974 | break;
975 |
976 | case 2:
977 |
978 | switch ( scope.touches.TWO ) {
979 |
980 | case THREE.TOUCH.DOLLY_PAN:
981 |
982 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
983 |
984 | handleTouchStartDollyPan( event );
985 |
986 | state = STATE.TOUCH_DOLLY_PAN;
987 |
988 | break;
989 |
990 | case THREE.TOUCH.DOLLY_ROTATE:
991 |
992 | if ( scope.enableZoom === false && scope.enableRotate === false ) return;
993 |
994 | handleTouchStartDollyRotate( event );
995 |
996 | state = STATE.TOUCH_DOLLY_ROTATE;
997 |
998 | break;
999 |
1000 | default:
1001 |
1002 | state = STATE.NONE;
1003 |
1004 | }
1005 |
1006 | break;
1007 |
1008 | default:
1009 |
1010 | state = STATE.NONE;
1011 |
1012 | }
1013 |
1014 | if ( state !== STATE.NONE ) {
1015 |
1016 | scope.dispatchEvent( startEvent );
1017 |
1018 | }
1019 |
1020 | }
1021 |
1022 | function onTouchMove( event ) {
1023 |
1024 | if ( scope.enabled === false ) return;
1025 |
1026 | event.preventDefault();
1027 | event.stopPropagation();
1028 |
1029 | switch ( state ) {
1030 |
1031 | case STATE.TOUCH_ROTATE:
1032 |
1033 | if ( scope.enableRotate === false ) return;
1034 |
1035 | handleTouchMoveRotate( event );
1036 |
1037 | scope.update();
1038 |
1039 | break;
1040 |
1041 | case STATE.TOUCH_PAN:
1042 |
1043 | if ( scope.enablePan === false ) return;
1044 |
1045 | handleTouchMovePan( event );
1046 |
1047 | scope.update();
1048 |
1049 | break;
1050 |
1051 | case STATE.TOUCH_DOLLY_PAN:
1052 |
1053 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
1054 |
1055 | handleTouchMoveDollyPan( event );
1056 |
1057 | scope.update();
1058 |
1059 | break;
1060 |
1061 | case STATE.TOUCH_DOLLY_ROTATE:
1062 |
1063 | if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1064 |
1065 | handleTouchMoveDollyRotate( event );
1066 |
1067 | scope.update();
1068 |
1069 | break;
1070 |
1071 | default:
1072 |
1073 | state = STATE.NONE;
1074 |
1075 | }
1076 |
1077 | }
1078 |
1079 | function onTouchEnd( event ) {
1080 |
1081 | if ( scope.enabled === false ) return;
1082 |
1083 | handleTouchEnd( event );
1084 |
1085 | scope.dispatchEvent( endEvent );
1086 |
1087 | state = STATE.NONE;
1088 |
1089 | }
1090 |
1091 | function onContextMenu( event ) {
1092 |
1093 | if ( scope.enabled === false ) return;
1094 |
1095 | event.preventDefault();
1096 |
1097 | }
1098 |
1099 | //
1100 |
1101 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
1102 |
1103 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
1104 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
1105 |
1106 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
1107 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
1108 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
1109 |
1110 | scope.domElement.addEventListener( 'keydown', onKeyDown, false );
1111 |
1112 | // make sure element can receive keys.
1113 |
1114 | if ( scope.domElement.tabIndex === - 1 ) {
1115 |
1116 | scope.domElement.tabIndex = 0;
1117 |
1118 | }
1119 |
1120 | // force an update at start
1121 |
1122 | this.update();
1123 |
1124 | };
1125 |
1126 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
1127 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
1128 |
1129 |
1130 | // This set of controls performs orbiting, dollying (zooming), and panning.
1131 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
1132 | // This is very similar to OrbitControls, another set of touch behavior
1133 | //
1134 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
1135 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
1136 | // Pan - left mouse, or arrow keys / touch: one-finger move
1137 |
1138 | THREE.MapControls = function ( object, domElement ) {
1139 |
1140 | THREE.OrbitControls.call( this, object, domElement );
1141 |
1142 | this.mouseButtons.LEFT = THREE.MOUSE.PAN;
1143 | this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
1144 |
1145 | this.touches.ONE = THREE.TOUCH.PAN;
1146 | this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
1147 |
1148 | };
1149 |
1150 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype );
1151 | THREE.MapControls.prototype.constructor = THREE.MapControls;
1152 |
--------------------------------------------------------------------------------
/viewer.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | /*
4 | Copyright Abhinav Singh Chauhan (@xclkvj)
5 | Copying the contents of this file by any means is prohibited.
6 | */
7 |
8 | const ViewerBG = '#eee';
9 | const ViewerUI = {
10 | canvasWrapper: document.getElementById('viewerCanvasWrapper'),
11 | cubeWrapper: document.getElementById('orientCubeWrapper'),
12 | toggleZoom: document.getElementById('toggleZoom'),
13 | togglePan: document.getElementById('togglePan'),
14 | toggleOrbit: document.getElementById('toggleOrbit'),
15 | resetBtn: document.getElementById('resetBtn'),
16 | toggleModelBrowser: document.getElementById('toggleModelBrowser'),
17 | modelBrowser: document.getElementById('modelBrowser'),
18 | modelBrowserContent: document.getElementById('modelBrowserContent'),
19 | fileInput: document.getElementById('fileInput'),
20 | explodeSliderWrapper: document.getElementById('explodeSliderWrapper'),
21 | explodeSlider: document.getElementById('explodeSlider'),
22 | toggleExplode: document.getElementById('toggleExplode'),
23 | toggleShare: document.getElementById('toggleShare'),
24 | shareSidebar: document.getElementById('shareSidebar'),
25 | loader: document.getElementById('loader'),
26 | toggleMeasure: document.getElementById('toggleMeasure'),
27 | loaderInfo: document.getElementById('loaderInfo'),
28 | backToHome: document.getElementById('backToHome'),
29 | webglContainer: document.getElementById('webglContainer'),
30 | downloadScreen: document.getElementById('downloadScreen'),
31 | explodeFace: document.getElementById('explodeFace')
32 | };
33 |
34 | function setItemSelected(ele, bool) {
35 | if (bool) {
36 | ele.classList.add('item-selected');
37 | } else {
38 | ele.classList.remove('item-selected');
39 | }
40 | }
41 |
42 | function toggle(ele) {
43 | if (ele.getBoundingClientRect().height > 0) {
44 | ele.style.display = 'none';
45 | return false;
46 | } else {
47 | ele.style.display = 'block';
48 | return true;
49 | }
50 | }
51 |
52 | function toggleThrough(ele, through, cb, selected=true) {
53 | through.onclick = () => {
54 | let bool = toggle(ele);
55 | selected && setItemSelected(through, bool);
56 | cb && cb(bool);
57 | }
58 | }
59 |
60 | function show(ele) {
61 | ele.style.display = 'block';
62 | }
63 |
64 | function hide(ele) {
65 | ele.style.display = 'none';
66 | }
67 |
68 | function Viewer() {
69 |
70 | ViewerUI.downloadScreen.onclick = function() {
71 | const canvas = renderer.domElement;
72 | renderAll();
73 | const image = canvas.toDataURL("image/png");
74 | const a = document.createElement("a");
75 | a.href = image.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
76 | a.download = "image.png"
77 | a.click();
78 | }
79 |
80 | ViewerUI.explodeFace.onclick = function() {
81 | explodeFace = this.checked;
82 | resetExplode();
83 | explode();
84 | }
85 |
86 | let cubeCameraDistance = 1.75;
87 |
88 | let cubeWrapper = ViewerUI.cubeWrapper;
89 | let cubeScene = new THREE.Scene();
90 | let cubeCamera = new THREE.PerspectiveCamera(70, cubeWrapper.offsetWidth / cubeWrapper.offsetHeight, 0.1, 100);
91 | let cubeRenderer = new THREE.WebGLRenderer({
92 | alpha: true,
93 | antialias: true,
94 | preserveDrawingBuffer: true
95 | });
96 |
97 | cubeRenderer.setSize(cubeWrapper.offsetWidth, cubeWrapper.offsetHeight);
98 | cubeRenderer.setPixelRatio(window.deivicePixelRatio);
99 |
100 | cubeWrapper.appendChild(cubeRenderer.domElement);
101 |
102 | let materials = [];
103 | let texts = ['RIGHT', 'LEFT', 'TOP', 'BOTTOM', 'FRONT', 'BACK'];
104 |
105 | let textureLoader = new THREE.TextureLoader();
106 | let canvas = document.createElement('canvas');
107 | let ctx = canvas.getContext('2d');
108 |
109 | let size = 64;
110 | canvas.width = size;
111 | canvas.height = size;
112 |
113 | ctx.font = 'bolder 12px "Open sans", Arial';
114 | ctx.textBaseline = 'middle';
115 | ctx.textAlign = 'center';
116 |
117 | let mainColor = '#fff';
118 | let otherColor = '#ccc';
119 |
120 | let bg = ctx.createLinearGradient(0, 0, 0, size);
121 | bg.addColorStop(0, mainColor);
122 | bg.addColorStop(1, otherColor);
123 |
124 | for (let i = 0; i < 6; i++) {
125 | if (texts[i] == 'TOP') {
126 | ctx.fillStyle = mainColor;
127 | } else if (texts[i] == 'BOTTOM') {
128 | ctx.fillStyle = otherColor;
129 | } else {
130 | ctx.fillStyle = bg;
131 | }
132 | ctx.fillRect(0, 0, size, size);
133 | ctx.strokeStyle = '#aaa';
134 | ctx.setLineDash([8, 8]);
135 | ctx.lineWidth = 4;
136 | ctx.strokeRect(0, 0, size, size);
137 | ctx.fillStyle = '#999';
138 | ctx.fillText(texts[i], size / 2, size / 2);
139 | materials[i] = new THREE.MeshBasicMaterial({
140 | map: textureLoader.load(canvas.toDataURL())
141 | });
142 | }
143 |
144 | let planes = [];
145 |
146 | let planeMaterial = new THREE.MeshBasicMaterial({
147 | side: THREE.DoubleSide,
148 | color: 0x00c0ff,
149 | transparent: true,
150 | opacity: 0,
151 | depthTest: false
152 | });
153 | let planeSize = 0.7;
154 | let planeGeometry = new THREE.PlaneGeometry(planeSize, planeSize);
155 |
156 | let a = 0.51;
157 |
158 | let plane1 = new THREE.Mesh(planeGeometry, planeMaterial.clone());
159 | plane1.position.z = a;
160 | cubeScene.add(plane1);
161 | planes.push(plane1);
162 |
163 | let plane2 = new THREE.Mesh(planeGeometry, planeMaterial.clone());
164 | plane2.position.z = -a;
165 | cubeScene.add(plane2);
166 | planes.push(plane2);
167 |
168 | let plane3 = new THREE.Mesh(planeGeometry, planeMaterial.clone());
169 | plane3.rotation.y = Math.PI / 2;
170 | plane3.position.x = a;
171 | cubeScene.add(plane3);
172 | planes.push(plane3);
173 |
174 | let plane4 = new THREE.Mesh(planeGeometry, planeMaterial.clone());
175 | plane4.rotation.y = Math.PI / 2;
176 | plane4.position.x = -a;
177 | cubeScene.add(plane4);
178 | planes.push(plane4);
179 |
180 | let plane5 = new THREE.Mesh(planeGeometry, planeMaterial.clone());
181 | plane5.rotation.x = Math.PI / 2;
182 | plane5.position.y = a;
183 | cubeScene.add(plane5);
184 | planes.push(plane5);
185 |
186 | let plane6 = new THREE.Mesh(planeGeometry, planeMaterial.clone());
187 | plane6.rotation.x = Math.PI / 2;
188 | plane6.position.y = -a;
189 | cubeScene.add(plane6);
190 | planes.push(plane6);
191 |
192 | let groundMaterial = new THREE.MeshBasicMaterial({
193 | color: 0xaaaaaa
194 | });
195 | let groundGeometry = new THREE.PlaneGeometry(1, 1);
196 | let groundPlane = new THREE.Mesh(groundGeometry, groundMaterial);
197 | groundPlane.rotation.x = -Math.PI / 2;
198 | groundPlane.position.y = -0.6;
199 |
200 | cubeScene.add(groundPlane);
201 |
202 | let cube = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), materials);
203 | cubeScene.add(cube);
204 |
205 | function updateCubeCamera() {
206 | cubeCamera.rotation.copy(camera.rotation);
207 | let dir = camera.position.clone().sub(controller.target).normalize();
208 | cubeCamera.position.copy(dir.multiplyScalar(cubeCameraDistance));
209 | }
210 |
211 | let activePlane = null;
212 |
213 | cubeRenderer.domElement.onmousemove = function(evt) {
214 |
215 | if (activePlane) {
216 | activePlane.material.opacity = 0;
217 | activePlane.material.needsUpdate = true;
218 | activePlane = null;
219 | }
220 |
221 | let x = evt.offsetX;
222 | let y = evt.offsetY;
223 | let size = cubeRenderer.getSize(new THREE.Vector2());
224 | let mouse = new THREE.Vector2(x / size.width * 2 - 1, -y / size.height * 2 + 1);
225 |
226 | let raycaster = new THREE.Raycaster();
227 | raycaster.setFromCamera(mouse, cubeCamera);
228 | let intersects = raycaster.intersectObjects(planes.concat(cube));
229 |
230 | if (intersects.length > 0 && intersects[0].object != cube) {
231 | activePlane = intersects[0].object;
232 | activePlane.material.opacity = 0.2;
233 | activePlane.material.needsUpdate = true;
234 | }
235 | }
236 |
237 | let startTime = 0;
238 | let duration = 500;
239 | let oldPosition = new THREE.Vector3();
240 | let newPosition = new THREE.Vector3();
241 | let play = false;
242 |
243 | cubeRenderer.domElement.onclick = function(evt) {
244 |
245 | cubeRenderer.domElement.onmousemove(evt);
246 |
247 | if (!activePlane || hasMoved) {
248 | return false;
249 | }
250 |
251 | oldPosition.copy(camera.position);
252 |
253 | let distance = camera.position.clone().sub(controller.target).length();
254 | newPosition.copy(controller.target);
255 |
256 | if (activePlane.position.x !== 0) {
257 | newPosition.x += activePlane.position.x < 0 ? -distance : distance;
258 | } else if (activePlane.position.y !== 0) {
259 | newPosition.y += activePlane.position.y < 0 ? -distance : distance;
260 | } else if (activePlane.position.z !== 0) {
261 | newPosition.z += activePlane.position.z < 0 ? -distance : distance;
262 | }
263 |
264 | //play = true;
265 | //startTime = Date.now();
266 | camera.position.copy(newPosition);
267 | }
268 |
269 | cubeRenderer.domElement.ontouchmove = function(e) {
270 | let rect = e.target.getBoundingClientRect();
271 | let x = e.targetTouches[0].pageX - rect.left;
272 | let y = e.targetTouches[0].pageY - rect.top;
273 | cubeRenderer.domElement.onmousemove({
274 | offsetX: x,
275 | offsetY: y
276 | });
277 | }
278 |
279 | cubeRenderer.domElement.ontouchstart = function(e) {
280 | let rect = e.target.getBoundingClientRect();
281 | let x = e.targetTouches[0].pageX - rect.left;
282 | let y = e.targetTouches[0].pageY - rect.top;
283 | cubeRenderer.domElement.onclick({
284 | offsetX: x,
285 | offsetY: y
286 | });
287 | }
288 |
289 | ViewerUI.fileInput.addEventListener('input', function(evt) {
290 | let file = evt.target.files[0];
291 | if (file) {
292 | show(ViewerUI.loader);
293 | ViewerUI.loaderInfo.innerHTML = 'Reading file...';
294 | let reader = new FileReader();
295 | reader.onload = function(e) {
296 | loadModel(e.target.result);
297 | }
298 | reader.onerror = function(err) {
299 | ViewerUI.loaderInfo.innerHTML = 'Error reading file! See console for more info.';
300 | console.error(err);
301 | }
302 | reader.readAsDataURL(file);
303 | }
304 | });
305 |
306 | hide(ViewerUI.loader);
307 |
308 | let hasMoved = false;
309 |
310 | function antiMoveOnDown(e) {
311 | hasMoved = false;
312 | }
313 | function antiMoveOnMove(e) {
314 | hasMoved = true;
315 | }
316 |
317 | window.addEventListener('mousedown', antiMoveOnDown, false);
318 | window.addEventListener('mousemove', antiMoveOnMove, false);
319 | window.addEventListener('touchstart', antiMoveOnDown, false);
320 | window.addEventListener('touchmove', antiMoveOnMove, true);
321 |
322 | let showExploded = false;
323 | let explodeFactor = 0;
324 | let explodeFace = !true;
325 |
326 | toggleThrough(ViewerUI.explodeSliderWrapper, ViewerUI.toggleExplode, (bool) => {
327 | if (!bool) {
328 | resetExplode();
329 | } else {
330 | explodeFactor = ViewerUI.explodeSlider.value;
331 | explode();
332 | }
333 | });
334 |
335 | function resetExplode() {
336 | let temp = explodeFactor;
337 | let temp2 = explodeFace;
338 | explodeFace = true;
339 | explodeFactor = 0;
340 | explode();
341 | explodeFace = false;
342 | explode();
343 | explodeFactor = temp;
344 | explodeFace = temp2;
345 | }
346 |
347 | toggleThrough(ViewerUI.shareSidebar, ViewerUI.toggleShare);
348 | toggleThrough(ViewerUI.modelBrowser, ViewerUI.toggleModelBrowser);
349 |
350 | ViewerUI.explodeSlider.oninput = function() {
351 | explodeFactor = this.value;
352 | explode();
353 | }
354 |
355 | function explode() {
356 | for (let i = 0; i < loadedMeshes.length; i++) {
357 |
358 | let node = loadedMeshes[i];
359 |
360 | if (explodeFace) {
361 | let defaultPositionArray = node.defaultPositionArray;
362 | let positionArray = node.geometry.attributes.position.array;
363 | let normalArray = node.geometry.attributes.normal.array;
364 | let indexArray = node.geometry.index.array;
365 |
366 | for (let j = 0; j < indexArray.length; j++) {
367 |
368 | let index = indexArray[j]
369 | let position = new THREE.Vector3(defaultPositionArray[index * 3], defaultPositionArray[index * 3 + 1], defaultPositionArray[index * 3 + 2]);
370 | let normal = new THREE.Vector3(normalArray[index * 3], normalArray[index * 3 + 1], normalArray[index * 3 + 2]);
371 |
372 | position.add(normal.multiplyScalar(explodeFactor));
373 | positionArray[index * 3] = position.x;
374 | positionArray[index * 3 + 1] = position.y;
375 | positionArray[index * 3 + 2] = position.z;
376 |
377 | }
378 |
379 | node.geometry.attributes.position.needsUpdate = true;
380 | node.geometry.computeBoundingBox();
381 | node.geometry.computeBoundingSphere();
382 | } else {
383 | node.position.copy(node.defaultPosition).add(node.defaultPosition.clone().normalize().multiplyScalar(explodeFactor));
384 | }
385 | }
386 | }
387 |
388 | ViewerUI.toggleZoom.onclick = function() {
389 | setZoomMode();
390 | setItemSelected(selectedModeElement, false);
391 | selectedModeElement = this;
392 | setItemSelected(this, true);
393 | }
394 |
395 | ViewerUI.togglePan.onclick = function() {
396 | setPanMode();
397 | setItemSelected(selectedModeElement, false);
398 | selectedModeElement = this;
399 | setItemSelected(this, true);
400 | }
401 |
402 | ViewerUI.toggleOrbit.onclick = function() {
403 | setOrbitMode();
404 | setItemSelected(selectedModeElement, false);
405 | selectedModeElement = this;
406 | setItemSelected(this, true);
407 | }
408 |
409 | ViewerUI.toggleMeasure.onclick = function() {
410 | isInMeasureMode = !isInMeasureMode;
411 | if (!isInMeasureMode) {
412 | lineScene.remove.apply(lineScene, lineScene.children);
413 | spriteScene.remove.apply(spriteScene, spriteScene.children);
414 | }
415 | setItemSelected(this, isInMeasureMode);
416 | }
417 |
418 | ViewerUI.resetBtn.onclick = ViewerUI.backToHome.onclick =function() {
419 | resetAll();
420 | }
421 |
422 | function resetAll() {
423 | controller.reset();
424 | lineScene.remove.apply(lineScene, lineScene.children);
425 | spriteScene.remove.apply(spriteScene, spriteScene.children);
426 | isInMeasureMode = false;
427 | setItemSelected(ViewerUI.toggleMeasure, false);
428 | ViewerUI.explodeSliderWrapper.style.display = 'none';
429 | ViewerUI.explodeFace.checked = false;
430 | explodeFace = false;
431 | setItemSelected(ViewerUI.toggleExplode, false);
432 | resetExplode();
433 | resetSelect();
434 | }
435 |
436 | function updateSelectDom(child) {
437 | if (child.itemWrapper) {
438 | if (child.isSelected) {
439 | child.itemWrapper.querySelector('.graph-name').style.color = '#03a9f4';
440 | } else {
441 | child.itemWrapper.querySelector('.graph-name').style.color = 'inherit';
442 | }
443 | }
444 | }
445 |
446 | function setOrbitMode() {
447 | controller.enableZoom = true;
448 | controller.enablePan = true;
449 | controller.enableRotate = true;
450 | controller.mouseButtons = {
451 | LEFT: THREE.MOUSE.ROTATE,
452 | MIDDLE: THREE.MOUSE.DOLLY,
453 | RIGHT: THREE.MOUSE.PAN
454 | };
455 | }
456 |
457 | function setPanMode() {
458 | controller.enableZoom = false;
459 | controller.enablePan = true;
460 | controller.enableRotate = false;
461 | controller.mouseButtons = {
462 | LEFT: THREE.MOUSE.PAN,
463 | MIDDLE: THREE.MOUSE.PAN,
464 | RIGHT: THREE.MOUSE.PAN
465 | };
466 | }
467 |
468 | function setZoomMode() {
469 | controller.enableZoom = true;
470 | controller.enablePan = false;
471 | controller.enableRotate = false;
472 | controller.mouseButtons = {
473 | LEFT: THREE.MOUSE.DOLLY,
474 | MIDDLE: THREE.MOUSE.DOLLY,
475 | RIGHT: THREE.MOUSE.DOLLY
476 | };
477 | }
478 |
479 | let wrapper = ViewerUI.canvasWrapper;
480 | let scene = new THREE.Scene();
481 | let camera = new THREE.PerspectiveCamera(70, wrapper.offsetWidth / wrapper.offsetHeight, 0.1, 1000);
482 |
483 | let renderer = new THREE.WebGLRenderer({
484 | antialias: true,
485 | alpha: false
486 | });
487 |
488 | renderer.setClearColor(new THREE.Color(ViewerBG));
489 | renderer.autoClear = false;
490 | renderer.setPixelRatio(window.deivicePixelRatio);
491 |
492 | let isInMeasureMode = false;
493 | let lineScene = new THREE.Scene();
494 | let spriteScene = new THREE.Scene();
495 |
496 | function makeCircleImage() {
497 | let canvas = document.createElement('canvas');
498 | let ctx = canvas.getContext('2d');
499 | let size = 32;
500 | canvas.width = size;
501 | canvas.height = size;
502 |
503 | let r = size * 0.8 / 2;
504 | let blur = size - r;
505 |
506 | ctx.shadowBlur = 5;
507 | ctx.shadowColor = '#555';
508 |
509 | ctx.fillStyle = '#fff';
510 | ctx.beginPath();
511 | ctx.arc(size / 2, size / 2, r, 0, Math.PI * 2);
512 | ctx.closePath();
513 | ctx.fill();
514 |
515 | ctx.shadowBlur = 0;
516 | ctx.fillStyle = '#009bff';
517 | ctx.beginPath();
518 | ctx.arc(size / 2, size / 2, r * 0.5, 0, Math.PI * 2);
519 | ctx.closePath();
520 | ctx.fill();
521 |
522 | return canvas;
523 | }
524 |
525 | let circleTexture = new THREE.CanvasTexture(makeCircleImage())
526 | let circleMaterial = new THREE.SpriteMaterial({
527 | map: circleTexture,
528 | sizeAttenuation: false
529 | });
530 | let circleSprite = new THREE.Sprite(circleMaterial);
531 | circleSprite.scale.setScalar(0.08);
532 |
533 | let lineMaterial = new THREE.LineBasicMaterial({
534 | color: 0x009bff,
535 | linewidth: 10
536 | });
537 |
538 | let activeLine = null;
539 |
540 | renderer.domElement.onclick = function(evt) {
541 | if (hasMoved) {
542 | return false;
543 | }
544 |
545 | evt = evt || window.event;
546 |
547 | let x = evt.offsetX;
548 | let y = evt.offsetY;
549 | let size = renderer.getSize(new THREE.Vector2());
550 | let mouse = new THREE.Vector2(x / size.width * 2 - 1, -y / size.height * 2 + 1);
551 |
552 | let raycaster = new THREE.Raycaster();
553 | raycaster.setFromCamera(mouse, camera);
554 | let intersects = raycaster.intersectObjects(loadedMeshes);
555 |
556 | if (!isInMeasureMode) {
557 | resetSelect();
558 | }
559 |
560 | if (intersects.length > 0) {
561 | if (isInMeasureMode) {
562 | let point = intersects[0].point;
563 | if (!activeLine) {
564 | let sprite1 = circleSprite.clone();
565 | let sprite2 = circleSprite.clone();
566 | sprite1.position.copy(point.clone());
567 | sprite2.position.copy(point.clone());
568 | spriteScene.add(sprite1);
569 | spriteScene.add(sprite2);
570 | let lineGeometry = new THREE.Geometry();
571 | lineGeometry.vertices.push(sprite1.position, sprite2.position);
572 | let line = new THREE.Line(lineGeometry, lineMaterial);
573 | line.sprite1 = sprite1;
574 | line.sprite2 = sprite2;
575 | lineScene.add(line);
576 | activeLine = line;
577 | } else {
578 | activeLine.geometry.vertices[1].copy(point);
579 | activeLine.geometry.verticesNeedUpdate = true;
580 | makeDistanceSprite();
581 | activeLine = null;
582 | }
583 | } else {
584 | let mesh = intersects[0].object;
585 | mesh.isSelected = true;
586 | updateMeshInteractionMaterial(mesh);
587 | }
588 | } else {
589 | if (isInMeasureMode) {
590 | if (activeLine) {
591 | lineScene.remove(activeLine);
592 | spriteScene.remove(activeLine.sprite1);
593 | spriteScene.remove(activeLine.sprite2);
594 | activeLine = null;
595 | }
596 | }
597 | }
598 | }
599 |
600 | function resetSelect() {
601 | scene.traverse(child => {
602 | child.isSelected = false;
603 | if (child.isMesh && child.material) {
604 | updateMeshInteractionMaterial(child);
605 | }
606 | updateSelectDom(child);
607 | });
608 | }
609 |
610 |
611 | renderer.domElement.onmousemove = function(evt) {
612 | evt = evt || window.event;
613 |
614 | if (!isInMeasureMode) {
615 | return;
616 | }
617 |
618 | let x = evt.offsetX;
619 | let y = evt.offsetY;
620 | let size = renderer.getSize(new THREE.Vector2());
621 | let mouse = new THREE.Vector2(x / size.width * 2 - 1, -y / size.height * 2 + 1);
622 |
623 | let raycaster = new THREE.Raycaster();
624 | raycaster.setFromCamera(mouse, camera);
625 | let intersects = raycaster.intersectObjects(loadedMeshes);
626 |
627 | if (isInMeasureMode && activeLine) {
628 | if (intersects.length > 0) {
629 | let point = intersects[0].point;
630 | activeLine.geometry.vertices[1].copy(point);
631 | activeLine.geometry.verticesNeedUpdate = true;
632 | } else {
633 | activeLine.geometry.vertices[1].copy(activeLine.geometry.vertices[0]);
634 | activeLine.geometry.verticesNeedUpdate = true;
635 | }
636 |
637 | }
638 | }
639 |
640 | /*renderer.domElement.ontouchmove = function(e) {
641 | let rect = e.target.getBoundingClientRect();
642 | let x = e.targetTouches[0].pageX - rect.left;
643 | let y = e.targetTouches[0].pageY - rect.top;
644 | renderer.domElement.onmousemove({
645 | offsetX: x,
646 | offsetY: y
647 | });
648 | }*/
649 |
650 | renderer.domElement.ontouchstart = function(e) {
651 | let rect = e.target.getBoundingClientRect();
652 | let x = e.targetTouches[0].pageX - rect.left;
653 | let y = e.targetTouches[0].pageY - rect.top;
654 | renderer.domElement.onclick({
655 | offsetX: x,
656 | offsetY: y
657 | });
658 | }
659 |
660 | function makeDistanceSprite() {
661 |
662 | let canvas = document.createElement('canvas');
663 | let ctx = canvas.getContext('2d');
664 | let fontsize = 32;
665 |
666 | ctx.font = 'bolder ' + fontsize + 'px "Open Sans", Arial';
667 | let v = activeLine.geometry.vertices;
668 | let length = v[0].clone().sub(v[1]).length().toFixed(1);
669 | let text = '~ ' + length;
670 | let size = ctx.measureText(text);
671 | let paddingLeft = 20;
672 | let paddingTop = 10;
673 | let margin = 10;
674 | canvas.width = size.width + paddingLeft * 2 + margin * 2;
675 | canvas.height = fontsize + paddingTop * 2 + margin * 2;
676 |
677 | ctx.shadowBlur = 10;
678 | ctx.shadowColor = '#555';
679 | ctx.fillStyle = '#009bff';
680 | roundRect(ctx, margin, margin, canvas.width - margin * 2, canvas.height - margin * 2, 10);
681 |
682 | ctx.shadowBlur = 0;
683 | ctx.fillStyle = '#fff';
684 | ctx.textAlign = 'left';
685 | ctx.textBaseline = 'top';
686 | ctx.font = 'bolder ' + fontsize + 'px "Open Sans", Arial';
687 | ctx.fillText(text, paddingLeft + margin, paddingTop + margin);
688 |
689 | let texture = new THREE.CanvasTexture(canvas);
690 | let sprite = new THREE.Sprite(new THREE.SpriteMaterial({
691 | map: texture,
692 | sizeAttenuation: false
693 | }));
694 |
695 | let h = 0.7;
696 | sprite.scale.set(0.002 * canvas.width, 0.0025 * canvas.height).multiplyScalar(h);
697 |
698 | sprite.position.copy(v[0].clone().add(v[1]).multiplyScalar(0.5));
699 | spriteScene.add(sprite);
700 |
701 | }
702 |
703 | function roundRect(ctx, x, y, w, h, r) {
704 | ctx.beginPath();
705 | ctx.moveTo(x + r, y);
706 | ctx.lineTo(x + w - r, y);
707 | ctx.quadraticCurveTo(x + w, y, x + w, y + r);
708 | ctx.lineTo(x + w, y + h - r);
709 | ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
710 | ctx.lineTo(x + r, y + h);
711 | ctx.quadraticCurveTo(x, y + h, x, y + h - r);
712 | ctx.lineTo(x, y + r);
713 | ctx.quadraticCurveTo(x, y, x + r, y);
714 | ctx.closePath();
715 | ctx.fill();
716 | }
717 |
718 |
719 | function updateMeshInteractionMaterial(mesh) {
720 | if (mesh.isHidden) {
721 | mesh.interactionMaterial.color = hiddenColor;
722 | mesh.interactionMaterial.opacity = hiddenAlpha;
723 | } else {
724 | mesh.interactionMaterial.opacity = 1;
725 | }
726 | if (mesh.isSelected) {
727 | mesh.interactionMaterial.color = selectColor;
728 | mesh.itemWrapper.querySelector('.graph-name').style.color = '#03a9f4';
729 | } else {
730 | mesh.itemWrapper.querySelector('.graph-name').style.color = 'inherit';
731 | }
732 | mesh.interactionMaterial.needsUpdate = true;
733 | if (!mesh.isSelected && !mesh.isHidden) {
734 | mesh.material = mesh.defaultMaterial;
735 | } else {
736 | mesh.material = mesh.interactionMaterial;
737 | }
738 | }
739 |
740 | function onResize() {
741 | let width = wrapper.offsetWidth;
742 | let height = wrapper.offsetHeight;
743 | renderer.setSize(width, height, false);
744 | camera.aspect = width / height;
745 | camera.updateProjectionMatrix();
746 | }
747 |
748 | onResize();
749 |
750 | wrapper.appendChild(renderer.domElement);
751 | window.addEventListener('resize', onResize, false);
752 |
753 | let gltfLoader = new THREE.GLTFLoader();
754 | let loadedScene = null;
755 | let loadedMeshes = [];
756 |
757 | let d = 5;
758 |
759 | let selectColor = new THREE.Color('#42006b');
760 | let hiddenColor = new THREE.Color('#555');
761 | let hiddenAlpha = 0.3;
762 |
763 | let interactionMaterial = new THREE.MeshPhongMaterial({
764 | transparent: true,
765 | color: selectColor,
766 | side: THREE.DoubleSide,
767 | precision: 'mediump'
768 | });
769 |
770 | function loadModel(url) {
771 |
772 | resetAll();
773 | if (loadedScene) {
774 | scene.remove(loadedScene);
775 | loadedScene = null;
776 | loadedMeshes.length = 0;
777 | }
778 |
779 | show(ViewerUI.loader);
780 | ViewerUI.modelBrowserContent.innerHTML = '';
781 | ViewerUI.loaderInfo.innerHTML = 'Loading model...';
782 |
783 | gltfLoader.load(
784 | url,
785 | function onLoad(gltf) {
786 |
787 | loadedScene = gltf.scene;
788 | scene.add(gltf.scene);
789 |
790 | gltf.scene = gltf.scene || gltf.scenes[0];
791 |
792 | let object = gltf.scene;
793 |
794 | const box = new THREE.Box3().setFromObject(object);
795 | const size = box.getSize(new THREE.Vector3()).length();
796 | const center = box.getCenter(new THREE.Vector3());
797 |
798 | controller.reset();
799 |
800 | object.position.x += (object.position.x - center.x);
801 | object.position.y += (object.position.y - center.y);
802 | object.position.z += (object.position.z - center.z);
803 | controller.maxDistance = size * 10;
804 | camera.near = size / 100;
805 | camera.far = size * 100;
806 | camera.updateProjectionMatrix();
807 |
808 | camera.position.copy(center);
809 | camera.position.x += size / 2.0;
810 | camera.position.y += size / 5.0;
811 | camera.position.z += size / 2.0;
812 |
813 | directionalLight.position.setScalar(size);
814 |
815 | camera.lookAt(center);
816 |
817 | controller.saveState();
818 |
819 | gltf.scene.traverse((node) => {
820 | if (node.isMesh && node.material) {
821 | node.geometry.computeBoundingBox();
822 | node.material.side = THREE.DoubleSide;
823 | node.material.precision = 'mediump';
824 | node.material.needsUpdate = true;
825 | node.interactionMaterial = interactionMaterial.clone();
826 | node.defaultMaterial = node.material;
827 | node.defaultPositionArray = Array.from(node.geometry.attributes.position.array);
828 | node.defaultPosition = node.position.clone();
829 | loadedMeshes.push(node);
830 | }
831 | });
832 |
833 | let content = ViewerUI.modelBrowserContent;
834 | let counter = 0;
835 | let parentLevel = 0;
836 |
837 | function makeSceneGraph(obj) {
838 |
839 | if (obj.children.length === 0 && !obj.isMesh) {
840 | return;
841 | }
842 |
843 | let itemWrapper = document.createElement('div');
844 | itemWrapper.classList.add('graph-item-wrapper');
845 |
846 | let item = document.createElement('div');
847 | item.classList.add('graph-item');
848 |
849 | itemWrapper.appendChild(item);
850 |
851 | content.appendChild(itemWrapper);
852 | let n = 0;
853 | let obj2 = obj;
854 | while (obj2 != gltf.scene) {
855 | obj2 = obj2.parent;
856 | n++;
857 | }
858 |
859 | item.style.paddingLeft = n * 1.5 + 'em';
860 | obj.itemWrapper = itemWrapper;
861 |
862 | let left = document.createElement('div');
863 | left.classList.add('graph-left');
864 | let right = document.createElement('div');
865 | right.classList.add('graph-right');
866 | item.appendChild(left);
867 | item.appendChild(right);
868 |
869 | if (obj.children.length > 0) {
870 |
871 | parentLevel++;
872 | let folder = document.createElement('div');
873 |
874 | folder.style.marginRight = '10px';
875 | folder.classList.add('graph-folder');
876 | folder.innerHTML = '';
877 | left.appendChild(folder);
878 |
879 | obj.isFolderOpen = true;
880 | obj.openFolder = function() {
881 | folder.innerHTML = obj.isFolderOpen ? '' : '';
882 | obj.traverse(child => {
883 | if (obj === child) {
884 | return;
885 | }
886 | if (child.itemWrapper) {
887 | if (child.parent.isFolderOpen && obj.isFolderOpen) {
888 | child.itemWrapper.style.display = 'block';
889 | }
890 | if (!obj.isFolderOpen) {
891 | child.itemWrapper.style.display = 'none';
892 | }
893 | }
894 | });
895 | }
896 |
897 | folder.onclick = () => {
898 | obj.isFolderOpen = !obj.isFolderOpen;
899 | obj.openFolder();
900 | }
901 |
902 | for (let i = 0; i < obj.children.length; i++) {
903 | makeSceneGraph(obj.children[i]);
904 | }
905 |
906 | }
907 |
908 | let name = document.createElement('div');
909 | name.classList.add('graph-name');
910 | name.innerHTML = obj.name || 'None';
911 | left.appendChild(name);
912 |
913 | name.onclick = function() {
914 | resetSelect();
915 | obj.traverse(child => {
916 | child.isSelected = true;
917 | if (child.isMesh && child.material) {
918 | updateMeshInteractionMaterial(child);
919 | }
920 | updateSelectDom(child)
921 | });
922 | }
923 |
924 | let visible = document.createElement('div');
925 | visible.classList.add('graph-visible');
926 | visible.innerHTML = '';
927 |
928 | obj.showMesh = function() {
929 | visible.innerHTML = obj.isMeshVisible ? '' : '';
930 | obj.traverse(child => {
931 | if (child.itemWrapper) {
932 | let eye = child.itemWrapper.querySelector('.graph-visible');
933 | eye.innerHTML = obj.isMeshVisible ? '' : '';
934 | eye.style.color = obj.isMeshVisible ? 'inherit' : 'rgba(0, 0, 0, 0.3)';
935 | }
936 | if (child.isMesh && child.material) {
937 | child.isHidden = !obj.isMeshVisible;
938 | updateMeshInteractionMaterial(child);
939 | }
940 | });
941 | }
942 |
943 | obj.isHidden = false;
944 | obj.isSelected = false;
945 | obj.isMeshVisible = true;
946 | visible.onclick = function() {
947 | obj.isMeshVisible = !obj.isMeshVisible;
948 | obj.showMesh();
949 | }
950 |
951 | right.appendChild(visible)
952 |
953 | }
954 |
955 | makeSceneGraph(gltf.scene)
956 |
957 | hide(ViewerUI.loader);
958 |
959 | },
960 | function onProgress(xhr) {
961 | ViewerUI.loaderInfo.innerHTML = Math.round(xhr.loaded / xhr.total * 100) + '% loaded';
962 | },
963 | function onError(err) {
964 | ViewerUI.loaderInfo.innerHTML = 'Error loading model! See console for more info.';
965 | console.error('Error loading model!', err);
966 | }
967 | );
968 |
969 | }
970 |
971 |
972 |
973 | let controller = new THREE.OrbitControls(camera, renderer.domElement);
974 | controller.enabled = true;
975 | controller.enableDamping = true;
976 | controller.dampingFactor = 0.5;
977 | controller.screenSpacePanning = true;
978 |
979 | let cubeController = new THREE.OrbitControls(camera, cubeRenderer.domElement);
980 | cubeController.enablePan = false;
981 | cubeController.enableZoom = false;
982 | cubeController.rotateSpeed = 0.125;
983 |
984 | let selectedModeElement = ViewerUI.toggleOrbit;
985 | setOrbitMode();
986 |
987 | camera.position.z = d;
988 | camera.lookAt(scene.position);
989 | controller.update();
990 | controller.saveState();
991 |
992 | let ambientLight = new THREE.AmbientLight();
993 | ambientLight.intensity = 1;
994 | scene.add(ambientLight);
995 |
996 | let directionalLight = new THREE.DirectionalLight();
997 | directionalLight.position.set(200, 200, 200)
998 | directionalLight.intensity = 0.5;
999 | scene.add(directionalLight);
1000 |
1001 | /*let light1 = new THREE.PointLight(0xffffff);
1002 | light1.position.set(100, 100, 100);
1003 | scene.add(light1);
1004 |
1005 | let light2 = new THREE.PointLight(0xffffff);
1006 | light2.position.set(100, 100, -100);
1007 | scene.add(light2);
1008 |
1009 | let light3 = new THREE.PointLight(0xffffff);
1010 | light3.position.set(-100, 100, 100);
1011 | scene.add(light3);
1012 |
1013 | let light4 = new THREE.PointLight(0xffffff);
1014 | light4.position.set(-100, 100, -100);
1015 | scene.add(light4);
1016 |
1017 | light1.intensity = light2.intensity = light3.intensity = light4.intensity = 0.3;*/
1018 |
1019 | let stop = false;
1020 |
1021 | function renderAll() {
1022 |
1023 | renderer.clear();
1024 | renderer.render(scene, camera);
1025 | updateCubeCamera();
1026 | cubeRenderer.render(cubeScene, cubeCamera);
1027 |
1028 | renderer.clearDepth();
1029 |
1030 | if (isInMeasureMode) {
1031 | renderer.clearDepth();
1032 | renderer.render(lineScene, camera);
1033 | renderer.clearDepth();
1034 | renderer.render(spriteScene, camera);
1035 | }
1036 | }
1037 |
1038 | function animate(time) {
1039 |
1040 | if (stop) {
1041 | return;
1042 | }
1043 |
1044 | if (play) {
1045 | let now = Date.now();
1046 | let x = Math.min(1, (now - startTime) / duration);
1047 | camera.position.copy(oldPosition).lerp(newPosition, x)
1048 | if (x === 1) {
1049 | play = false;
1050 | }
1051 | }
1052 |
1053 | requestAnimationFrame(animate);
1054 | controller.update();
1055 | renderAll();
1056 | }
1057 |
1058 | requestAnimationFrame(animate);
1059 |
1060 | return {
1061 | loadModel: loadModel
1062 | };
1063 |
1064 | }
1065 |
1066 | function draggable(ele, toggleEle) {
1067 |
1068 | let startX = 0;
1069 | let startY = 0;
1070 |
1071 | function onMouseDown(evt) {
1072 | evt = evt || window.event;
1073 | startDrag(evt.clientX, evt.clientY);
1074 | window.addEventListener('mousemove', onMouseMove, true);
1075 | }
1076 |
1077 | function onMouseMove(evt) {
1078 | evt = evt || window.event;
1079 | let newX = evt.clientX;
1080 | let newY = evt.clientY;
1081 | moveDrag(newX, newY);
1082 | }
1083 |
1084 | function onMouseUp() {
1085 | window.removeEventListener('mousemove', onMouseMove, true);
1086 | }
1087 |
1088 | function startDrag(x, y) {
1089 | startX = x;
1090 | startY = y;
1091 | }
1092 |
1093 | function moveDrag(newX, newY) {
1094 |
1095 | let deltaX = newX - startX;
1096 | let deltaY = newY - startY;
1097 |
1098 | startX = newX;
1099 | startY = newY;
1100 |
1101 | let x = ele.offsetLeft + deltaX;
1102 | let y = ele.offsetTop + deltaY;
1103 | x < 0 && (x = 0);
1104 | y < 0 && (y = 0);
1105 | let w = ele.parentNode.offsetWidth - ele.offsetWidth;
1106 | let h = ele.parentNode.offsetHeight - ele.offsetHeight;
1107 | x > w && (x = w);
1108 | y > h && (y = h);
1109 |
1110 | ele.style.left = x + 'px';
1111 | ele.style.top = y + 'px';
1112 |
1113 | }
1114 |
1115 | toggleEle.addEventListener('mousedown', onMouseDown, true);
1116 | window.addEventListener('mouseup', onMouseUp, true);
1117 |
1118 | }
1119 |
1120 |
--------------------------------------------------------------------------------
/vendor/GLTFLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Rich Tibbett / https://github.com/richtr
3 | * @author mrdoob / http://mrdoob.com/
4 | * @author Tony Parisi / http://www.tonyparisi.com/
5 | * @author Takahiro / https://github.com/takahirox
6 | * @author Don McCurdy / https://www.donmccurdy.com
7 | */
8 |
9 | THREE.GLTFLoader = ( function () {
10 |
11 | function GLTFLoader( manager ) {
12 |
13 | THREE.Loader.call( this, manager );
14 |
15 | this.dracoLoader = null;
16 | this.ddsLoader = null;
17 |
18 | }
19 |
20 | GLTFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
21 |
22 | constructor: GLTFLoader,
23 |
24 | load: function ( url, onLoad, onProgress, onError ) {
25 |
26 | var scope = this;
27 |
28 | var resourcePath;
29 |
30 | if ( this.resourcePath !== '' ) {
31 |
32 | resourcePath = this.resourcePath;
33 |
34 | } else if ( this.path !== '' ) {
35 |
36 | resourcePath = this.path;
37 |
38 | } else {
39 |
40 | resourcePath = THREE.LoaderUtils.extractUrlBase( url );
41 |
42 | }
43 |
44 | // Tells the LoadingManager to track an extra item, which resolves after
45 | // the model is fully loaded. This means the count of items loaded will
46 | // be incorrect, but ensures manager.onLoad() does not fire early.
47 | scope.manager.itemStart( url );
48 |
49 | var _onError = function ( e ) {
50 |
51 | if ( onError ) {
52 |
53 | onError( e );
54 |
55 | } else {
56 |
57 | console.error( e );
58 |
59 | }
60 |
61 | scope.manager.itemError( url );
62 | scope.manager.itemEnd( url );
63 |
64 | };
65 |
66 | var loader = new THREE.FileLoader( scope.manager );
67 |
68 | loader.setPath( this.path );
69 | loader.setResponseType( 'arraybuffer' );
70 |
71 | if ( scope.crossOrigin === 'use-credentials' ) {
72 |
73 | loader.setWithCredentials( true );
74 |
75 | }
76 |
77 | loader.load( url, function ( data ) {
78 |
79 | try {
80 |
81 | scope.parse( data, resourcePath, function ( gltf ) {
82 |
83 | onLoad( gltf );
84 |
85 | scope.manager.itemEnd( url );
86 |
87 | }, _onError );
88 |
89 | } catch ( e ) {
90 |
91 | _onError( e );
92 |
93 | }
94 |
95 | }, onProgress, _onError );
96 |
97 | },
98 |
99 | setDRACOLoader: function ( dracoLoader ) {
100 |
101 | this.dracoLoader = dracoLoader;
102 | return this;
103 |
104 | },
105 |
106 | setDDSLoader: function ( ddsLoader ) {
107 |
108 | this.ddsLoader = ddsLoader;
109 | return this;
110 |
111 | },
112 |
113 | parse: function ( data, path, onLoad, onError ) {
114 |
115 | var content;
116 | var extensions = {};
117 |
118 | if ( typeof data === 'string' ) {
119 |
120 | content = data;
121 |
122 | } else {
123 |
124 | var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) );
125 |
126 | if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
127 |
128 | try {
129 |
130 | extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
131 |
132 | } catch ( error ) {
133 |
134 | if ( onError ) onError( error );
135 | return;
136 |
137 | }
138 |
139 | content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
140 |
141 | } else {
142 |
143 | content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
144 |
145 | }
146 |
147 | }
148 |
149 | var json = JSON.parse( content );
150 |
151 | if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
152 |
153 | if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) );
154 | return;
155 |
156 | }
157 |
158 | if ( json.extensionsUsed ) {
159 |
160 | for ( var i = 0; i < json.extensionsUsed.length; ++ i ) {
161 |
162 | var extensionName = json.extensionsUsed[ i ];
163 | var extensionsRequired = json.extensionsRequired || [];
164 |
165 | switch ( extensionName ) {
166 |
167 | case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
168 | extensions[ extensionName ] = new GLTFLightsExtension( json );
169 | break;
170 |
171 | case EXTENSIONS.KHR_MATERIALS_UNLIT:
172 | extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
173 | break;
174 |
175 | case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
176 | extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension();
177 | break;
178 |
179 | case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
180 | extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
181 | break;
182 |
183 | case EXTENSIONS.MSFT_TEXTURE_DDS:
184 | extensions[ extensionName ] = new GLTFTextureDDSExtension( this.ddsLoader );
185 | break;
186 |
187 | case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
188 | extensions[ extensionName ] = new GLTFTextureTransformExtension();
189 | break;
190 |
191 | case EXTENSIONS.KHR_MESH_QUANTIZATION:
192 | extensions[ extensionName ] = new GLTFMeshQuantizationExtension();
193 | break;
194 |
195 | default:
196 |
197 | if ( extensionsRequired.indexOf( extensionName ) >= 0 ) {
198 |
199 | console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' );
200 |
201 | }
202 |
203 | }
204 |
205 | }
206 |
207 | }
208 |
209 | var parser = new GLTFParser( json, extensions, {
210 |
211 | path: path || this.resourcePath || '',
212 | crossOrigin: this.crossOrigin,
213 | manager: this.manager
214 |
215 | } );
216 |
217 | parser.parse( onLoad, onError );
218 |
219 | }
220 |
221 | } );
222 |
223 | /* GLTFREGISTRY */
224 |
225 | function GLTFRegistry() {
226 |
227 | var objects = {};
228 |
229 | return {
230 |
231 | get: function ( key ) {
232 |
233 | return objects[ key ];
234 |
235 | },
236 |
237 | add: function ( key, object ) {
238 |
239 | objects[ key ] = object;
240 |
241 | },
242 |
243 | remove: function ( key ) {
244 |
245 | delete objects[ key ];
246 |
247 | },
248 |
249 | removeAll: function () {
250 |
251 | objects = {};
252 |
253 | }
254 |
255 | };
256 |
257 | }
258 |
259 | /*********************************/
260 | /********** EXTENSIONS ***********/
261 | /*********************************/
262 |
263 | var EXTENSIONS = {
264 | KHR_BINARY_GLTF: 'KHR_binary_glTF',
265 | KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
266 | KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
267 | KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
268 | KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
269 | KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
270 | KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
271 | MSFT_TEXTURE_DDS: 'MSFT_texture_dds'
272 | };
273 |
274 | /**
275 | * DDS Texture Extension
276 | *
277 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds
278 | *
279 | */
280 | function GLTFTextureDDSExtension( ddsLoader ) {
281 |
282 | if ( ! ddsLoader ) {
283 |
284 | throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' );
285 |
286 | }
287 |
288 | this.name = EXTENSIONS.MSFT_TEXTURE_DDS;
289 | this.ddsLoader = ddsLoader;
290 |
291 | }
292 |
293 | /**
294 | * Punctual Lights Extension
295 | *
296 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
297 | */
298 | function GLTFLightsExtension( json ) {
299 |
300 | this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
301 |
302 | var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {};
303 | this.lightDefs = extension.lights || [];
304 |
305 | }
306 |
307 | GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) {
308 |
309 | var lightDef = this.lightDefs[ lightIndex ];
310 | var lightNode;
311 |
312 | var color = new THREE.Color( 0xffffff );
313 | if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
314 |
315 | var range = lightDef.range !== undefined ? lightDef.range : 0;
316 |
317 | switch ( lightDef.type ) {
318 |
319 | case 'directional':
320 | lightNode = new THREE.DirectionalLight( color );
321 | lightNode.target.position.set( 0, 0, - 1 );
322 | lightNode.add( lightNode.target );
323 | break;
324 |
325 | case 'point':
326 | lightNode = new THREE.PointLight( color );
327 | lightNode.distance = range;
328 | break;
329 |
330 | case 'spot':
331 | lightNode = new THREE.SpotLight( color );
332 | lightNode.distance = range;
333 | // Handle spotlight properties.
334 | lightDef.spot = lightDef.spot || {};
335 | lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
336 | lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
337 | lightNode.angle = lightDef.spot.outerConeAngle;
338 | lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
339 | lightNode.target.position.set( 0, 0, - 1 );
340 | lightNode.add( lightNode.target );
341 | break;
342 |
343 | default:
344 | throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' );
345 |
346 | }
347 |
348 | // Some lights (e.g. spot) default to a position other than the origin. Reset the position
349 | // here, because node-level parsing will only override position if explicitly specified.
350 | lightNode.position.set( 0, 0, 0 );
351 |
352 | lightNode.decay = 2;
353 |
354 | if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
355 |
356 | lightNode.name = lightDef.name || ( 'light_' + lightIndex );
357 |
358 | return Promise.resolve( lightNode );
359 |
360 | };
361 |
362 | /**
363 | * Unlit Materials Extension
364 | *
365 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
366 | */
367 | function GLTFMaterialsUnlitExtension() {
368 |
369 | this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
370 |
371 | }
372 |
373 | GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () {
374 |
375 | return THREE.MeshBasicMaterial;
376 |
377 | };
378 |
379 | GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) {
380 |
381 | var pending = [];
382 |
383 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
384 | materialParams.opacity = 1.0;
385 |
386 | var metallicRoughness = materialDef.pbrMetallicRoughness;
387 |
388 | if ( metallicRoughness ) {
389 |
390 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
391 |
392 | var array = metallicRoughness.baseColorFactor;
393 |
394 | materialParams.color.fromArray( array );
395 | materialParams.opacity = array[ 3 ];
396 |
397 | }
398 |
399 | if ( metallicRoughness.baseColorTexture !== undefined ) {
400 |
401 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
402 |
403 | }
404 |
405 | }
406 |
407 | return Promise.all( pending );
408 |
409 | };
410 |
411 | /* BINARY EXTENSION */
412 | var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
413 | var BINARY_EXTENSION_HEADER_LENGTH = 12;
414 | var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };
415 |
416 | function GLTFBinaryExtension( data ) {
417 |
418 | this.name = EXTENSIONS.KHR_BINARY_GLTF;
419 | this.content = null;
420 | this.body = null;
421 |
422 | var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
423 |
424 | this.header = {
425 | magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ),
426 | version: headerView.getUint32( 4, true ),
427 | length: headerView.getUint32( 8, true )
428 | };
429 |
430 | if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) {
431 |
432 | throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' );
433 |
434 | } else if ( this.header.version < 2.0 ) {
435 |
436 | throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' );
437 |
438 | }
439 |
440 | var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
441 | var chunkIndex = 0;
442 |
443 | while ( chunkIndex < chunkView.byteLength ) {
444 |
445 | var chunkLength = chunkView.getUint32( chunkIndex, true );
446 | chunkIndex += 4;
447 |
448 | var chunkType = chunkView.getUint32( chunkIndex, true );
449 | chunkIndex += 4;
450 |
451 | if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {
452 |
453 | var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
454 | this.content = THREE.LoaderUtils.decodeText( contentArray );
455 |
456 | } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {
457 |
458 | var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
459 | this.body = data.slice( byteOffset, byteOffset + chunkLength );
460 |
461 | }
462 |
463 | // Clients must ignore chunks with unknown types.
464 |
465 | chunkIndex += chunkLength;
466 |
467 | }
468 |
469 | if ( this.content === null ) {
470 |
471 | throw new Error( 'THREE.GLTFLoader: JSON content not found.' );
472 |
473 | }
474 |
475 | }
476 |
477 | /**
478 | * DRACO Mesh Compression Extension
479 | *
480 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
481 | */
482 | function GLTFDracoMeshCompressionExtension( json, dracoLoader ) {
483 |
484 | if ( ! dracoLoader ) {
485 |
486 | throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );
487 |
488 | }
489 |
490 | this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
491 | this.json = json;
492 | this.dracoLoader = dracoLoader;
493 | this.dracoLoader.preload();
494 |
495 | }
496 |
497 | GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) {
498 |
499 | var json = this.json;
500 | var dracoLoader = this.dracoLoader;
501 | var bufferViewIndex = primitive.extensions[ this.name ].bufferView;
502 | var gltfAttributeMap = primitive.extensions[ this.name ].attributes;
503 | var threeAttributeMap = {};
504 | var attributeNormalizedMap = {};
505 | var attributeTypeMap = {};
506 |
507 | for ( var attributeName in gltfAttributeMap ) {
508 |
509 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
510 |
511 | threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];
512 |
513 | }
514 |
515 | for ( attributeName in primitive.attributes ) {
516 |
517 | var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
518 |
519 | if ( gltfAttributeMap[ attributeName ] !== undefined ) {
520 |
521 | var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
522 | var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
523 |
524 | attributeTypeMap[ threeAttributeName ] = componentType;
525 | attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;
526 |
527 | }
528 |
529 | }
530 |
531 | return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
532 |
533 | return new Promise( function ( resolve ) {
534 |
535 | dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
536 |
537 | for ( var attributeName in geometry.attributes ) {
538 |
539 | var attribute = geometry.attributes[ attributeName ];
540 | var normalized = attributeNormalizedMap[ attributeName ];
541 |
542 | if ( normalized !== undefined ) attribute.normalized = normalized;
543 |
544 | }
545 |
546 | resolve( geometry );
547 |
548 | }, threeAttributeMap, attributeTypeMap );
549 |
550 | } );
551 |
552 | } );
553 |
554 | };
555 |
556 | /**
557 | * Texture Transform Extension
558 | *
559 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform
560 | */
561 | function GLTFTextureTransformExtension() {
562 |
563 | this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
564 |
565 | }
566 |
567 | GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) {
568 |
569 | texture = texture.clone();
570 |
571 | if ( transform.offset !== undefined ) {
572 |
573 | texture.offset.fromArray( transform.offset );
574 |
575 | }
576 |
577 | if ( transform.rotation !== undefined ) {
578 |
579 | texture.rotation = transform.rotation;
580 |
581 | }
582 |
583 | if ( transform.scale !== undefined ) {
584 |
585 | texture.repeat.fromArray( transform.scale );
586 |
587 | }
588 |
589 | if ( transform.texCoord !== undefined ) {
590 |
591 | console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' );
592 |
593 | }
594 |
595 | texture.needsUpdate = true;
596 |
597 | return texture;
598 |
599 | };
600 |
601 | /**
602 | * Specular-Glossiness Extension
603 | *
604 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
605 | */
606 |
607 | /**
608 | * A sub class of THREE.StandardMaterial with some of the functionality
609 | * changed via the `onBeforeCompile` callback
610 | * @pailhead
611 | */
612 |
613 | function GLTFMeshStandardSGMaterial( params ) {
614 |
615 | THREE.MeshStandardMaterial.call( this );
616 |
617 | this.isGLTFSpecularGlossinessMaterial = true;
618 |
619 | //various chunks that need replacing
620 | var specularMapParsFragmentChunk = [
621 | '#ifdef USE_SPECULARMAP',
622 | ' uniform sampler2D specularMap;',
623 | '#endif'
624 | ].join( '\n' );
625 |
626 | var glossinessMapParsFragmentChunk = [
627 | '#ifdef USE_GLOSSINESSMAP',
628 | ' uniform sampler2D glossinessMap;',
629 | '#endif'
630 | ].join( '\n' );
631 |
632 | var specularMapFragmentChunk = [
633 | 'vec3 specularFactor = specular;',
634 | '#ifdef USE_SPECULARMAP',
635 | ' vec4 texelSpecular = texture2D( specularMap, vUv );',
636 | ' texelSpecular = sRGBToLinear( texelSpecular );',
637 | ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture',
638 | ' specularFactor *= texelSpecular.rgb;',
639 | '#endif'
640 | ].join( '\n' );
641 |
642 | var glossinessMapFragmentChunk = [
643 | 'float glossinessFactor = glossiness;',
644 | '#ifdef USE_GLOSSINESSMAP',
645 | ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );',
646 | ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture',
647 | ' glossinessFactor *= texelGlossiness.a;',
648 | '#endif'
649 | ].join( '\n' );
650 |
651 | var lightPhysicalFragmentChunk = [
652 | 'PhysicalMaterial material;',
653 | 'material.diffuseColor = diffuseColor.rgb;',
654 | 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );',
655 | 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );',
656 | 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 );// 0.0525 corresponds to the base mip of a 256 cubemap.',
657 | 'material.specularRoughness += geometryRoughness;',
658 | 'material.specularRoughness = min( material.specularRoughness, 1.0 );',
659 | 'material.specularColor = specularFactor.rgb;',
660 | ].join( '\n' );
661 |
662 | var uniforms = {
663 | specular: { value: new THREE.Color().setHex( 0xffffff ) },
664 | glossiness: { value: 1 },
665 | specularMap: { value: null },
666 | glossinessMap: { value: null }
667 | };
668 |
669 | this._extraUniforms = uniforms;
670 |
671 | // please see #14031 or #13198 for an alternate approach
672 | this.onBeforeCompile = function ( shader ) {
673 |
674 | for ( var uniformName in uniforms ) {
675 |
676 | shader.uniforms[ uniformName ] = uniforms[ uniformName ];
677 |
678 | }
679 |
680 | shader.fragmentShader = shader.fragmentShader.replace( 'uniform float roughness;', 'uniform vec3 specular;' );
681 | shader.fragmentShader = shader.fragmentShader.replace( 'uniform float metalness;', 'uniform float glossiness;' );
682 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapParsFragmentChunk );
683 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapParsFragmentChunk );
684 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapFragmentChunk );
685 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapFragmentChunk );
686 | shader.fragmentShader = shader.fragmentShader.replace( '#include ', lightPhysicalFragmentChunk );
687 |
688 | };
689 |
690 | /*eslint-disable*/
691 | Object.defineProperties(
692 | this,
693 | {
694 | specular: {
695 | get: function () { return uniforms.specular.value; },
696 | set: function ( v ) { uniforms.specular.value = v; }
697 | },
698 | specularMap: {
699 | get: function () { return uniforms.specularMap.value; },
700 | set: function ( v ) { uniforms.specularMap.value = v; }
701 | },
702 | glossiness: {
703 | get: function () { return uniforms.glossiness.value; },
704 | set: function ( v ) { uniforms.glossiness.value = v; }
705 | },
706 | glossinessMap: {
707 | get: function () { return uniforms.glossinessMap.value; },
708 | set: function ( v ) {
709 |
710 | uniforms.glossinessMap.value = v;
711 | //how about something like this - @pailhead
712 | if ( v ) {
713 |
714 | this.defines.USE_GLOSSINESSMAP = '';
715 | // set USE_ROUGHNESSMAP to enable vUv
716 | this.defines.USE_ROUGHNESSMAP = '';
717 |
718 | } else {
719 |
720 | delete this.defines.USE_ROUGHNESSMAP;
721 | delete this.defines.USE_GLOSSINESSMAP;
722 |
723 | }
724 |
725 | }
726 | }
727 | }
728 | );
729 |
730 | /*eslint-enable*/
731 | delete this.metalness;
732 | delete this.roughness;
733 | delete this.metalnessMap;
734 | delete this.roughnessMap;
735 |
736 | this.setValues( params );
737 |
738 | }
739 |
740 | GLTFMeshStandardSGMaterial.prototype = Object.create( THREE.MeshStandardMaterial.prototype );
741 | GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial;
742 |
743 | GLTFMeshStandardSGMaterial.prototype.copy = function ( source ) {
744 |
745 | THREE.MeshStandardMaterial.prototype.copy.call( this, source );
746 | this.specularMap = source.specularMap;
747 | this.specular.copy( source.specular );
748 | this.glossinessMap = source.glossinessMap;
749 | this.glossiness = source.glossiness;
750 | delete this.metalness;
751 | delete this.roughness;
752 | delete this.metalnessMap;
753 | delete this.roughnessMap;
754 | return this;
755 |
756 | };
757 |
758 | function GLTFMaterialsPbrSpecularGlossinessExtension() {
759 |
760 | return {
761 |
762 | name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
763 |
764 | specularGlossinessParams: [
765 | 'color',
766 | 'map',
767 | 'lightMap',
768 | 'lightMapIntensity',
769 | 'aoMap',
770 | 'aoMapIntensity',
771 | 'emissive',
772 | 'emissiveIntensity',
773 | 'emissiveMap',
774 | 'bumpMap',
775 | 'bumpScale',
776 | 'normalMap',
777 | 'normalMapType',
778 | 'displacementMap',
779 | 'displacementScale',
780 | 'displacementBias',
781 | 'specularMap',
782 | 'specular',
783 | 'glossinessMap',
784 | 'glossiness',
785 | 'alphaMap',
786 | 'envMap',
787 | 'envMapIntensity',
788 | 'refractionRatio',
789 | ],
790 |
791 | getMaterialType: function () {
792 |
793 | return GLTFMeshStandardSGMaterial;
794 |
795 | },
796 |
797 | extendParams: function ( materialParams, materialDef, parser ) {
798 |
799 | var pbrSpecularGlossiness = materialDef.extensions[ this.name ];
800 |
801 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
802 | materialParams.opacity = 1.0;
803 |
804 | var pending = [];
805 |
806 | if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) {
807 |
808 | var array = pbrSpecularGlossiness.diffuseFactor;
809 |
810 | materialParams.color.fromArray( array );
811 | materialParams.opacity = array[ 3 ];
812 |
813 | }
814 |
815 | if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) {
816 |
817 | pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) );
818 |
819 | }
820 |
821 | materialParams.emissive = new THREE.Color( 0.0, 0.0, 0.0 );
822 | materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
823 | materialParams.specular = new THREE.Color( 1.0, 1.0, 1.0 );
824 |
825 | if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) {
826 |
827 | materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor );
828 |
829 | }
830 |
831 | if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) {
832 |
833 | var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
834 | pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) );
835 | pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) );
836 |
837 | }
838 |
839 | return Promise.all( pending );
840 |
841 | },
842 |
843 | createMaterial: function ( materialParams ) {
844 |
845 | var material = new GLTFMeshStandardSGMaterial( materialParams );
846 | material.fog = true;
847 |
848 | material.color = materialParams.color;
849 |
850 | material.map = materialParams.map === undefined ? null : materialParams.map;
851 |
852 | material.lightMap = null;
853 | material.lightMapIntensity = 1.0;
854 |
855 | material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap;
856 | material.aoMapIntensity = 1.0;
857 |
858 | material.emissive = materialParams.emissive;
859 | material.emissiveIntensity = 1.0;
860 | material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap;
861 |
862 | material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap;
863 | material.bumpScale = 1;
864 |
865 | material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap;
866 | material.normalMapType = THREE.TangentSpaceNormalMap;
867 |
868 | if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale;
869 |
870 | material.displacementMap = null;
871 | material.displacementScale = 1;
872 | material.displacementBias = 0;
873 |
874 | material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap;
875 | material.specular = materialParams.specular;
876 |
877 | material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap;
878 | material.glossiness = materialParams.glossiness;
879 |
880 | material.alphaMap = null;
881 |
882 | material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap;
883 | material.envMapIntensity = 1.0;
884 |
885 | material.refractionRatio = 0.98;
886 |
887 | return material;
888 |
889 | },
890 |
891 | };
892 |
893 | }
894 |
895 | /**
896 | * Mesh Quantization Extension
897 | *
898 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization
899 | */
900 | function GLTFMeshQuantizationExtension() {
901 |
902 | this.name = EXTENSIONS.KHR_MESH_QUANTIZATION;
903 |
904 | }
905 |
906 | /*********************************/
907 | /********** INTERPOLATION ********/
908 | /*********************************/
909 |
910 | // Spline Interpolation
911 | // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
912 | function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
913 |
914 | THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
915 |
916 | }
917 |
918 | GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype );
919 | GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
920 |
921 | GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) {
922 |
923 | // Copies a sample value to the result buffer. See description of glTF
924 | // CUBICSPLINE values layout in interpolate_() function below.
925 |
926 | var result = this.resultBuffer,
927 | values = this.sampleValues,
928 | valueSize = this.valueSize,
929 | offset = index * valueSize * 3 + valueSize;
930 |
931 | for ( var i = 0; i !== valueSize; i ++ ) {
932 |
933 | result[ i ] = values[ offset + i ];
934 |
935 | }
936 |
937 | return result;
938 |
939 | };
940 |
941 | GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
942 |
943 | GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
944 |
945 | GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
946 |
947 | var result = this.resultBuffer;
948 | var values = this.sampleValues;
949 | var stride = this.valueSize;
950 |
951 | var stride2 = stride * 2;
952 | var stride3 = stride * 3;
953 |
954 | var td = t1 - t0;
955 |
956 | var p = ( t - t0 ) / td;
957 | var pp = p * p;
958 | var ppp = pp * p;
959 |
960 | var offset1 = i1 * stride3;
961 | var offset0 = offset1 - stride3;
962 |
963 | var s2 = - 2 * ppp + 3 * pp;
964 | var s3 = ppp - pp;
965 | var s0 = 1 - s2;
966 | var s1 = s3 - pp + p;
967 |
968 | // Layout of keyframe output values for CUBICSPLINE animations:
969 | // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
970 | for ( var i = 0; i !== stride; i ++ ) {
971 |
972 | var p0 = values[ offset0 + i + stride ]; // splineVertex_k
973 | var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
974 | var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
975 | var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
976 |
977 | result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
978 |
979 | }
980 |
981 | return result;
982 |
983 | };
984 |
985 | /*********************************/
986 | /********** INTERNALS ************/
987 | /*********************************/
988 |
989 | /* CONSTANTS */
990 |
991 | var WEBGL_CONSTANTS = {
992 | FLOAT: 5126,
993 | //FLOAT_MAT2: 35674,
994 | FLOAT_MAT3: 35675,
995 | FLOAT_MAT4: 35676,
996 | FLOAT_VEC2: 35664,
997 | FLOAT_VEC3: 35665,
998 | FLOAT_VEC4: 35666,
999 | LINEAR: 9729,
1000 | REPEAT: 10497,
1001 | SAMPLER_2D: 35678,
1002 | POINTS: 0,
1003 | LINES: 1,
1004 | LINE_LOOP: 2,
1005 | LINE_STRIP: 3,
1006 | TRIANGLES: 4,
1007 | TRIANGLE_STRIP: 5,
1008 | TRIANGLE_FAN: 6,
1009 | UNSIGNED_BYTE: 5121,
1010 | UNSIGNED_SHORT: 5123
1011 | };
1012 |
1013 | var WEBGL_COMPONENT_TYPES = {
1014 | 5120: Int8Array,
1015 | 5121: Uint8Array,
1016 | 5122: Int16Array,
1017 | 5123: Uint16Array,
1018 | 5125: Uint32Array,
1019 | 5126: Float32Array
1020 | };
1021 |
1022 | var WEBGL_FILTERS = {
1023 | 9728: THREE.NearestFilter,
1024 | 9729: THREE.LinearFilter,
1025 | 9984: THREE.NearestMipmapNearestFilter,
1026 | 9985: THREE.LinearMipmapNearestFilter,
1027 | 9986: THREE.NearestMipmapLinearFilter,
1028 | 9987: THREE.LinearMipmapLinearFilter
1029 | };
1030 |
1031 | var WEBGL_WRAPPINGS = {
1032 | 33071: THREE.ClampToEdgeWrapping,
1033 | 33648: THREE.MirroredRepeatWrapping,
1034 | 10497: THREE.RepeatWrapping
1035 | };
1036 |
1037 | var WEBGL_TYPE_SIZES = {
1038 | 'SCALAR': 1,
1039 | 'VEC2': 2,
1040 | 'VEC3': 3,
1041 | 'VEC4': 4,
1042 | 'MAT2': 4,
1043 | 'MAT3': 9,
1044 | 'MAT4': 16
1045 | };
1046 |
1047 | var ATTRIBUTES = {
1048 | POSITION: 'position',
1049 | NORMAL: 'normal',
1050 | TANGENT: 'tangent',
1051 | TEXCOORD_0: 'uv',
1052 | TEXCOORD_1: 'uv2',
1053 | COLOR_0: 'color',
1054 | WEIGHTS_0: 'skinWeight',
1055 | JOINTS_0: 'skinIndex',
1056 | };
1057 |
1058 | var PATH_PROPERTIES = {
1059 | scale: 'scale',
1060 | translation: 'position',
1061 | rotation: 'quaternion',
1062 | weights: 'morphTargetInfluences'
1063 | };
1064 |
1065 | var INTERPOLATION = {
1066 | CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
1067 | // keyframe track will be initialized with a default interpolation type, then modified.
1068 | LINEAR: THREE.InterpolateLinear,
1069 | STEP: THREE.InterpolateDiscrete
1070 | };
1071 |
1072 | var ALPHA_MODES = {
1073 | OPAQUE: 'OPAQUE',
1074 | MASK: 'MASK',
1075 | BLEND: 'BLEND'
1076 | };
1077 |
1078 | var MIME_TYPE_FORMATS = {
1079 | 'image/png': THREE.RGBAFormat,
1080 | 'image/jpeg': THREE.RGBFormat
1081 | };
1082 |
1083 | /* UTILITY FUNCTIONS */
1084 |
1085 | function resolveURL( url, path ) {
1086 |
1087 | // Invalid URL
1088 | if ( typeof url !== 'string' || url === '' ) return '';
1089 |
1090 | // Host Relative URL
1091 | if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) {
1092 |
1093 | path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' );
1094 |
1095 | }
1096 |
1097 | // Absolute URL http://,https://,//
1098 | if ( /^(https?:)?\/\//i.test( url ) ) return url;
1099 |
1100 | // Data URI
1101 | if ( /^data:.*,.*$/i.test( url ) ) return url;
1102 |
1103 | // Blob URL
1104 | if ( /^blob:.*$/i.test( url ) ) return url;
1105 |
1106 | // Relative URL
1107 | return path + url;
1108 |
1109 | }
1110 |
1111 | /**
1112 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
1113 | */
1114 | function createDefaultMaterial( cache ) {
1115 |
1116 | if ( cache[ 'DefaultMaterial' ] === undefined ) {
1117 |
1118 | cache[ 'DefaultMaterial' ] = new THREE.MeshStandardMaterial( {
1119 | color: 0xFFFFFF,
1120 | emissive: 0x000000,
1121 | metalness: 1,
1122 | roughness: 1,
1123 | transparent: false,
1124 | depthTest: true,
1125 | side: THREE.FrontSide
1126 | } );
1127 |
1128 | }
1129 |
1130 | return cache[ 'DefaultMaterial' ];
1131 |
1132 | }
1133 |
1134 | function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
1135 |
1136 | // Add unknown glTF extensions to an object's userData.
1137 |
1138 | for ( var name in objectDef.extensions ) {
1139 |
1140 | if ( knownExtensions[ name ] === undefined ) {
1141 |
1142 | object.userData.gltfExtensions = object.userData.gltfExtensions || {};
1143 | object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];
1144 |
1145 | }
1146 |
1147 | }
1148 |
1149 | }
1150 |
1151 | /**
1152 | * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object
1153 | * @param {GLTF.definition} gltfDef
1154 | */
1155 | function assignExtrasToUserData( object, gltfDef ) {
1156 |
1157 | if ( gltfDef.extras !== undefined ) {
1158 |
1159 | if ( typeof gltfDef.extras === 'object' ) {
1160 |
1161 | Object.assign( object.userData, gltfDef.extras );
1162 |
1163 | } else {
1164 |
1165 | console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );
1166 |
1167 | }
1168 |
1169 | }
1170 |
1171 | }
1172 |
1173 | /**
1174 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
1175 | *
1176 | * @param {THREE.BufferGeometry} geometry
1177 | * @param {Array} targets
1178 | * @param {GLTFParser} parser
1179 | * @return {Promise}
1180 | */
1181 | function addMorphTargets( geometry, targets, parser ) {
1182 |
1183 | var hasMorphPosition = false;
1184 | var hasMorphNormal = false;
1185 |
1186 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1187 |
1188 | var target = targets[ i ];
1189 |
1190 | if ( target.POSITION !== undefined ) hasMorphPosition = true;
1191 | if ( target.NORMAL !== undefined ) hasMorphNormal = true;
1192 |
1193 | if ( hasMorphPosition && hasMorphNormal ) break;
1194 |
1195 | }
1196 |
1197 | if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry );
1198 |
1199 | var pendingPositionAccessors = [];
1200 | var pendingNormalAccessors = [];
1201 |
1202 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
1203 |
1204 | var target = targets[ i ];
1205 |
1206 | if ( hasMorphPosition ) {
1207 |
1208 | var pendingAccessor = target.POSITION !== undefined
1209 | ? parser.getDependency( 'accessor', target.POSITION )
1210 | : geometry.attributes.position;
1211 |
1212 | pendingPositionAccessors.push( pendingAccessor );
1213 |
1214 | }
1215 |
1216 | if ( hasMorphNormal ) {
1217 |
1218 | var pendingAccessor = target.NORMAL !== undefined
1219 | ? parser.getDependency( 'accessor', target.NORMAL )
1220 | : geometry.attributes.normal;
1221 |
1222 | pendingNormalAccessors.push( pendingAccessor );
1223 |
1224 | }
1225 |
1226 | }
1227 |
1228 | return Promise.all( [
1229 | Promise.all( pendingPositionAccessors ),
1230 | Promise.all( pendingNormalAccessors )
1231 | ] ).then( function ( accessors ) {
1232 |
1233 | var morphPositions = accessors[ 0 ];
1234 | var morphNormals = accessors[ 1 ];
1235 |
1236 | if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
1237 | if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
1238 | geometry.morphTargetsRelative = true;
1239 |
1240 | return geometry;
1241 |
1242 | } );
1243 |
1244 | }
1245 |
1246 | /**
1247 | * @param {THREE.Mesh} mesh
1248 | * @param {GLTF.Mesh} meshDef
1249 | */
1250 | function updateMorphTargets( mesh, meshDef ) {
1251 |
1252 | mesh.updateMorphTargets();
1253 |
1254 | if ( meshDef.weights !== undefined ) {
1255 |
1256 | for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) {
1257 |
1258 | mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];
1259 |
1260 | }
1261 |
1262 | }
1263 |
1264 | // .extras has user-defined data, so check that .extras.targetNames is an array.
1265 | if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {
1266 |
1267 | var targetNames = meshDef.extras.targetNames;
1268 |
1269 | if ( mesh.morphTargetInfluences.length === targetNames.length ) {
1270 |
1271 | mesh.morphTargetDictionary = {};
1272 |
1273 | for ( var i = 0, il = targetNames.length; i < il; i ++ ) {
1274 |
1275 | mesh.morphTargetDictionary[ targetNames[ i ] ] = i;
1276 |
1277 | }
1278 |
1279 | } else {
1280 |
1281 | console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );
1282 |
1283 | }
1284 |
1285 | }
1286 |
1287 | }
1288 |
1289 | function createPrimitiveKey( primitiveDef ) {
1290 |
1291 | var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
1292 | var geometryKey;
1293 |
1294 | if ( dracoExtension ) {
1295 |
1296 | geometryKey = 'draco:' + dracoExtension.bufferView
1297 | + ':' + dracoExtension.indices
1298 | + ':' + createAttributesKey( dracoExtension.attributes );
1299 |
1300 | } else {
1301 |
1302 | geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;
1303 |
1304 | }
1305 |
1306 | return geometryKey;
1307 |
1308 | }
1309 |
1310 | function createAttributesKey( attributes ) {
1311 |
1312 | var attributesKey = '';
1313 |
1314 | var keys = Object.keys( attributes ).sort();
1315 |
1316 | for ( var i = 0, il = keys.length; i < il; i ++ ) {
1317 |
1318 | attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';
1319 |
1320 | }
1321 |
1322 | return attributesKey;
1323 |
1324 | }
1325 |
1326 | /* GLTF PARSER */
1327 |
1328 | function GLTFParser( json, extensions, options ) {
1329 |
1330 | this.json = json || {};
1331 | this.extensions = extensions || {};
1332 | this.options = options || {};
1333 |
1334 | // loader object cache
1335 | this.cache = new GLTFRegistry();
1336 |
1337 | // BufferGeometry caching
1338 | this.primitiveCache = {};
1339 |
1340 | this.textureLoader = new THREE.TextureLoader( this.options.manager );
1341 | this.textureLoader.setCrossOrigin( this.options.crossOrigin );
1342 |
1343 | this.fileLoader = new THREE.FileLoader( this.options.manager );
1344 | this.fileLoader.setResponseType( 'arraybuffer' );
1345 |
1346 | if ( this.options.crossOrigin === 'use-credentials' ) {
1347 |
1348 | this.fileLoader.setWithCredentials( true );
1349 |
1350 | }
1351 |
1352 | }
1353 |
1354 | GLTFParser.prototype.parse = function ( onLoad, onError ) {
1355 |
1356 | var parser = this;
1357 | var json = this.json;
1358 | var extensions = this.extensions;
1359 |
1360 | // Clear the loader cache
1361 | this.cache.removeAll();
1362 |
1363 | // Mark the special nodes/meshes in json for efficient parse
1364 | this.markDefs();
1365 |
1366 | Promise.all( [
1367 |
1368 | this.getDependencies( 'scene' ),
1369 | this.getDependencies( 'animation' ),
1370 | this.getDependencies( 'camera' ),
1371 |
1372 | ] ).then( function ( dependencies ) {
1373 |
1374 | var result = {
1375 | scene: dependencies[ 0 ][ json.scene || 0 ],
1376 | scenes: dependencies[ 0 ],
1377 | animations: dependencies[ 1 ],
1378 | cameras: dependencies[ 2 ],
1379 | asset: json.asset,
1380 | parser: parser,
1381 | userData: {}
1382 | };
1383 |
1384 | addUnknownExtensionsToUserData( extensions, result, json );
1385 |
1386 | assignExtrasToUserData( result, json );
1387 |
1388 | onLoad( result );
1389 |
1390 | } ).catch( onError );
1391 |
1392 | };
1393 |
1394 | /**
1395 | * Marks the special nodes/meshes in json for efficient parse.
1396 | */
1397 | GLTFParser.prototype.markDefs = function () {
1398 |
1399 | var nodeDefs = this.json.nodes || [];
1400 | var skinDefs = this.json.skins || [];
1401 | var meshDefs = this.json.meshes || [];
1402 |
1403 | var meshReferences = {};
1404 | var meshUses = {};
1405 |
1406 | // Nothing in the node definition indicates whether it is a Bone or an
1407 | // Object3D. Use the skins' joint references to mark bones.
1408 | for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {
1409 |
1410 | var joints = skinDefs[ skinIndex ].joints;
1411 |
1412 | for ( var i = 0, il = joints.length; i < il; i ++ ) {
1413 |
1414 | nodeDefs[ joints[ i ] ].isBone = true;
1415 |
1416 | }
1417 |
1418 | }
1419 |
1420 | // Meshes can (and should) be reused by multiple nodes in a glTF asset. To
1421 | // avoid having more than one THREE.Mesh with the same name, count
1422 | // references and rename instances below.
1423 | //
1424 | // Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
1425 | for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
1426 |
1427 | var nodeDef = nodeDefs[ nodeIndex ];
1428 |
1429 | if ( nodeDef.mesh !== undefined ) {
1430 |
1431 | if ( meshReferences[ nodeDef.mesh ] === undefined ) {
1432 |
1433 | meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0;
1434 |
1435 | }
1436 |
1437 | meshReferences[ nodeDef.mesh ] ++;
1438 |
1439 | // Nothing in the mesh definition indicates whether it is
1440 | // a SkinnedMesh or Mesh. Use the node's mesh reference
1441 | // to mark SkinnedMesh if node has skin.
1442 | if ( nodeDef.skin !== undefined ) {
1443 |
1444 | meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;
1445 |
1446 | }
1447 |
1448 | }
1449 |
1450 | }
1451 |
1452 | this.json.meshReferences = meshReferences;
1453 | this.json.meshUses = meshUses;
1454 |
1455 | };
1456 |
1457 | /**
1458 | * Requests the specified dependency asynchronously, with caching.
1459 | * @param {string} type
1460 | * @param {number} index
1461 | * @return {Promise}
1462 | */
1463 | GLTFParser.prototype.getDependency = function ( type, index ) {
1464 |
1465 | var cacheKey = type + ':' + index;
1466 | var dependency = this.cache.get( cacheKey );
1467 |
1468 | if ( ! dependency ) {
1469 |
1470 | switch ( type ) {
1471 |
1472 | case 'scene':
1473 | dependency = this.loadScene( index );
1474 | break;
1475 |
1476 | case 'node':
1477 | dependency = this.loadNode( index );
1478 | break;
1479 |
1480 | case 'mesh':
1481 | dependency = this.loadMesh( index );
1482 | break;
1483 |
1484 | case 'accessor':
1485 | dependency = this.loadAccessor( index );
1486 | break;
1487 |
1488 | case 'bufferView':
1489 | dependency = this.loadBufferView( index );
1490 | break;
1491 |
1492 | case 'buffer':
1493 | dependency = this.loadBuffer( index );
1494 | break;
1495 |
1496 | case 'material':
1497 | dependency = this.loadMaterial( index );
1498 | break;
1499 |
1500 | case 'texture':
1501 | dependency = this.loadTexture( index );
1502 | break;
1503 |
1504 | case 'skin':
1505 | dependency = this.loadSkin( index );
1506 | break;
1507 |
1508 | case 'animation':
1509 | dependency = this.loadAnimation( index );
1510 | break;
1511 |
1512 | case 'camera':
1513 | dependency = this.loadCamera( index );
1514 | break;
1515 |
1516 | case 'light':
1517 | dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index );
1518 | break;
1519 |
1520 | default:
1521 | throw new Error( 'Unknown type: ' + type );
1522 |
1523 | }
1524 |
1525 | this.cache.add( cacheKey, dependency );
1526 |
1527 | }
1528 |
1529 | return dependency;
1530 |
1531 | };
1532 |
1533 | /**
1534 | * Requests all dependencies of the specified type asynchronously, with caching.
1535 | * @param {string} type
1536 | * @return {Promise>}
1537 | */
1538 | GLTFParser.prototype.getDependencies = function ( type ) {
1539 |
1540 | var dependencies = this.cache.get( type );
1541 |
1542 | if ( ! dependencies ) {
1543 |
1544 | var parser = this;
1545 | var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];
1546 |
1547 | dependencies = Promise.all( defs.map( function ( def, index ) {
1548 |
1549 | return parser.getDependency( type, index );
1550 |
1551 | } ) );
1552 |
1553 | this.cache.add( type, dependencies );
1554 |
1555 | }
1556 |
1557 | return dependencies;
1558 |
1559 | };
1560 |
1561 | /**
1562 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1563 | * @param {number} bufferIndex
1564 | * @return {Promise}
1565 | */
1566 | GLTFParser.prototype.loadBuffer = function ( bufferIndex ) {
1567 |
1568 | var bufferDef = this.json.buffers[ bufferIndex ];
1569 | var loader = this.fileLoader;
1570 |
1571 | if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) {
1572 |
1573 | throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' );
1574 |
1575 | }
1576 |
1577 | // If present, GLB container is required to be the first buffer.
1578 | if ( bufferDef.uri === undefined && bufferIndex === 0 ) {
1579 |
1580 | return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body );
1581 |
1582 | }
1583 |
1584 | var options = this.options;
1585 |
1586 | return new Promise( function ( resolve, reject ) {
1587 |
1588 | loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () {
1589 |
1590 | reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) );
1591 |
1592 | } );
1593 |
1594 | } );
1595 |
1596 | };
1597 |
1598 | /**
1599 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
1600 | * @param {number} bufferViewIndex
1601 | * @return {Promise}
1602 | */
1603 | GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) {
1604 |
1605 | var bufferViewDef = this.json.bufferViews[ bufferViewIndex ];
1606 |
1607 | return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) {
1608 |
1609 | var byteLength = bufferViewDef.byteLength || 0;
1610 | var byteOffset = bufferViewDef.byteOffset || 0;
1611 | return buffer.slice( byteOffset, byteOffset + byteLength );
1612 |
1613 | } );
1614 |
1615 | };
1616 |
1617 | /**
1618 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
1619 | * @param {number} accessorIndex
1620 | * @return {Promise}
1621 | */
1622 | GLTFParser.prototype.loadAccessor = function ( accessorIndex ) {
1623 |
1624 | var parser = this;
1625 | var json = this.json;
1626 |
1627 | var accessorDef = this.json.accessors[ accessorIndex ];
1628 |
1629 | if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) {
1630 |
1631 | // Ignore empty accessors, which may be used to declare runtime
1632 | // information about attributes coming from another source (e.g. Draco
1633 | // compression extension).
1634 | return Promise.resolve( null );
1635 |
1636 | }
1637 |
1638 | var pendingBufferViews = [];
1639 |
1640 | if ( accessorDef.bufferView !== undefined ) {
1641 |
1642 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) );
1643 |
1644 | } else {
1645 |
1646 | pendingBufferViews.push( null );
1647 |
1648 | }
1649 |
1650 | if ( accessorDef.sparse !== undefined ) {
1651 |
1652 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) );
1653 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) );
1654 |
1655 | }
1656 |
1657 | return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {
1658 |
1659 | var bufferView = bufferViews[ 0 ];
1660 |
1661 | var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
1662 | var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
1663 |
1664 | // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
1665 | var elementBytes = TypedArray.BYTES_PER_ELEMENT;
1666 | var itemBytes = elementBytes * itemSize;
1667 | var byteOffset = accessorDef.byteOffset || 0;
1668 | var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined;
1669 | var normalized = accessorDef.normalized === true;
1670 | var array, bufferAttribute;
1671 |
1672 | // The buffer is not interleaved if the stride is the item size in bytes.
1673 | if ( byteStride && byteStride !== itemBytes ) {
1674 |
1675 | // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer
1676 | // This makes sure that IBA.count reflects accessor.count properly
1677 | var ibSlice = Math.floor( byteOffset / byteStride );
1678 | var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
1679 | var ib = parser.cache.get( ibCacheKey );
1680 |
1681 | if ( ! ib ) {
1682 |
1683 | array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes );
1684 |
1685 | // Integer parameters to IB/IBA are in array elements, not bytes.
1686 | ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes );
1687 |
1688 | parser.cache.add( ibCacheKey, ib );
1689 |
1690 | }
1691 |
1692 | bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized );
1693 |
1694 | } else {
1695 |
1696 | if ( bufferView === null ) {
1697 |
1698 | array = new TypedArray( accessorDef.count * itemSize );
1699 |
1700 | } else {
1701 |
1702 | array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize );
1703 |
1704 | }
1705 |
1706 | bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized );
1707 |
1708 | }
1709 |
1710 | // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
1711 | if ( accessorDef.sparse !== undefined ) {
1712 |
1713 | var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
1714 | var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ];
1715 |
1716 | var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
1717 | var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
1718 |
1719 | var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices );
1720 | var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize );
1721 |
1722 | if ( bufferView !== null ) {
1723 |
1724 | // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
1725 | bufferAttribute = new THREE.BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized );
1726 |
1727 | }
1728 |
1729 | for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) {
1730 |
1731 | var index = sparseIndices[ i ];
1732 |
1733 | bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
1734 | if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
1735 | if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
1736 | if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
1737 | if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' );
1738 |
1739 | }
1740 |
1741 | }
1742 |
1743 | return bufferAttribute;
1744 |
1745 | } );
1746 |
1747 | };
1748 |
1749 | /**
1750 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
1751 | * @param {number} textureIndex
1752 | * @return {Promise}
1753 | */
1754 | GLTFParser.prototype.loadTexture = function ( textureIndex ) {
1755 |
1756 | var parser = this;
1757 | var json = this.json;
1758 | var options = this.options;
1759 | var textureLoader = this.textureLoader;
1760 |
1761 | var URL = window.URL || window.webkitURL;
1762 |
1763 | var textureDef = json.textures[ textureIndex ];
1764 |
1765 | var textureExtensions = textureDef.extensions || {};
1766 |
1767 | var source;
1768 |
1769 | if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) {
1770 |
1771 | source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ];
1772 |
1773 | } else {
1774 |
1775 | source = json.images[ textureDef.source ];
1776 |
1777 | }
1778 |
1779 | var sourceURI = source.uri;
1780 | var isObjectURL = false;
1781 |
1782 | if ( source.bufferView !== undefined ) {
1783 |
1784 | // Load binary image data from bufferView, if provided.
1785 |
1786 | sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) {
1787 |
1788 | isObjectURL = true;
1789 | var blob = new Blob( [ bufferView ], { type: source.mimeType } );
1790 | sourceURI = URL.createObjectURL( blob );
1791 | return sourceURI;
1792 |
1793 | } );
1794 |
1795 | }
1796 |
1797 | return Promise.resolve( sourceURI ).then( function ( sourceURI ) {
1798 |
1799 | // Load Texture resource.
1800 |
1801 | var loader = options.manager.getHandler( sourceURI );
1802 |
1803 | if ( ! loader ) {
1804 |
1805 | loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ]
1806 | ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader
1807 | : textureLoader;
1808 |
1809 | }
1810 |
1811 | return new Promise( function ( resolve, reject ) {
1812 |
1813 | loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject );
1814 |
1815 | } );
1816 |
1817 | } ).then( function ( texture ) {
1818 |
1819 | // Clean up resources and configure Texture.
1820 |
1821 | if ( isObjectURL === true ) {
1822 |
1823 | URL.revokeObjectURL( sourceURI );
1824 |
1825 | }
1826 |
1827 | texture.flipY = false;
1828 |
1829 | if ( textureDef.name ) texture.name = textureDef.name;
1830 |
1831 | // Ignore unknown mime types, like DDS files.
1832 | if ( source.mimeType in MIME_TYPE_FORMATS ) {
1833 |
1834 | texture.format = MIME_TYPE_FORMATS[ source.mimeType ];
1835 |
1836 | }
1837 |
1838 | var samplers = json.samplers || {};
1839 | var sampler = samplers[ textureDef.sampler ] || {};
1840 |
1841 | texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter;
1842 | texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipmapLinearFilter;
1843 | texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
1844 | texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
1845 |
1846 | return texture;
1847 |
1848 | } );
1849 |
1850 | };
1851 |
1852 | /**
1853 | * Asynchronously assigns a texture to the given material parameters.
1854 | * @param {Object} materialParams
1855 | * @param {string} mapName
1856 | * @param {Object} mapDef
1857 | * @return {Promise}
1858 | */
1859 | GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) {
1860 |
1861 | var parser = this;
1862 |
1863 | return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {
1864 |
1865 | if ( ! texture.isCompressedTexture ) {
1866 |
1867 | switch ( mapName ) {
1868 |
1869 | case 'aoMap':
1870 | case 'emissiveMap':
1871 | case 'metalnessMap':
1872 | case 'normalMap':
1873 | case 'roughnessMap':
1874 | texture.format = THREE.RGBFormat;
1875 | break;
1876 |
1877 | }
1878 |
1879 | }
1880 |
1881 | // Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured
1882 | // However, we will copy UV set 0 to UV set 1 on demand for aoMap
1883 | if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) {
1884 |
1885 | console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' );
1886 |
1887 | }
1888 |
1889 | if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {
1890 |
1891 | var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;
1892 |
1893 | if ( transform ) {
1894 |
1895 | texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
1896 |
1897 | }
1898 |
1899 | }
1900 |
1901 | materialParams[ mapName ] = texture;
1902 |
1903 | } );
1904 |
1905 | };
1906 |
1907 | /**
1908 | * Assigns final material to a Mesh, Line, or Points instance. The instance
1909 | * already has a material (generated from the glTF material options alone)
1910 | * but reuse of the same glTF material may require multiple threejs materials
1911 | * to accomodate different primitive types, defines, etc. New materials will
1912 | * be created if necessary, and reused from a cache.
1913 | * @param {THREE.Object3D} mesh Mesh, Line, or Points instance.
1914 | */
1915 | GLTFParser.prototype.assignFinalMaterial = function ( mesh ) {
1916 |
1917 | var geometry = mesh.geometry;
1918 | var material = mesh.material;
1919 | var extensions = this.extensions;
1920 |
1921 | var useVertexTangents = geometry.attributes.tangent !== undefined;
1922 | var useVertexColors = geometry.attributes.color !== undefined;
1923 | var useFlatShading = geometry.attributes.normal === undefined;
1924 | var useSkinning = mesh.isSkinnedMesh === true;
1925 | var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
1926 | var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
1927 |
1928 | if ( mesh.isPoints ) {
1929 |
1930 | var cacheKey = 'PointsMaterial:' + material.uuid;
1931 |
1932 | var pointsMaterial = this.cache.get( cacheKey );
1933 |
1934 | if ( ! pointsMaterial ) {
1935 |
1936 | pointsMaterial = new THREE.PointsMaterial();
1937 | THREE.Material.prototype.copy.call( pointsMaterial, material );
1938 | pointsMaterial.color.copy( material.color );
1939 | pointsMaterial.map = material.map;
1940 | pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
1941 |
1942 | this.cache.add( cacheKey, pointsMaterial );
1943 |
1944 | }
1945 |
1946 | material = pointsMaterial;
1947 |
1948 | } else if ( mesh.isLine ) {
1949 |
1950 | var cacheKey = 'LineBasicMaterial:' + material.uuid;
1951 |
1952 | var lineMaterial = this.cache.get( cacheKey );
1953 |
1954 | if ( ! lineMaterial ) {
1955 |
1956 | lineMaterial = new THREE.LineBasicMaterial();
1957 | THREE.Material.prototype.copy.call( lineMaterial, material );
1958 | lineMaterial.color.copy( material.color );
1959 |
1960 | this.cache.add( cacheKey, lineMaterial );
1961 |
1962 | }
1963 |
1964 | material = lineMaterial;
1965 |
1966 | }
1967 |
1968 | // Clone the material if it will be modified
1969 | if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
1970 |
1971 | var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
1972 |
1973 | if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
1974 | if ( useSkinning ) cacheKey += 'skinning:';
1975 | if ( useVertexTangents ) cacheKey += 'vertex-tangents:';
1976 | if ( useVertexColors ) cacheKey += 'vertex-colors:';
1977 | if ( useFlatShading ) cacheKey += 'flat-shading:';
1978 | if ( useMorphTargets ) cacheKey += 'morph-targets:';
1979 | if ( useMorphNormals ) cacheKey += 'morph-normals:';
1980 |
1981 | var cachedMaterial = this.cache.get( cacheKey );
1982 |
1983 | if ( ! cachedMaterial ) {
1984 |
1985 | cachedMaterial = material.clone();
1986 |
1987 | if ( useSkinning ) cachedMaterial.skinning = true;
1988 | if ( useVertexTangents ) cachedMaterial.vertexTangents = true;
1989 | if ( useVertexColors ) cachedMaterial.vertexColors = true;
1990 | if ( useFlatShading ) cachedMaterial.flatShading = true;
1991 | if ( useMorphTargets ) cachedMaterial.morphTargets = true;
1992 | if ( useMorphNormals ) cachedMaterial.morphNormals = true;
1993 |
1994 | this.cache.add( cacheKey, cachedMaterial );
1995 |
1996 | }
1997 |
1998 | material = cachedMaterial;
1999 |
2000 | }
2001 |
2002 | // workarounds for mesh and geometry
2003 |
2004 | if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
2005 |
2006 | geometry.setAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
2007 |
2008 | }
2009 |
2010 | // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
2011 | if ( material.normalScale && ! useVertexTangents ) {
2012 |
2013 | material.normalScale.y = - material.normalScale.y;
2014 |
2015 | }
2016 |
2017 | mesh.material = material;
2018 |
2019 | };
2020 |
2021 | /**
2022 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
2023 | * @param {number} materialIndex
2024 | * @return {Promise}
2025 | */
2026 | GLTFParser.prototype.loadMaterial = function ( materialIndex ) {
2027 |
2028 | var parser = this;
2029 | var json = this.json;
2030 | var extensions = this.extensions;
2031 | var materialDef = json.materials[ materialIndex ];
2032 |
2033 | var materialType;
2034 | var materialParams = {};
2035 | var materialExtensions = materialDef.extensions || {};
2036 |
2037 | var pending = [];
2038 |
2039 | if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) {
2040 |
2041 | var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ];
2042 | materialType = sgExtension.getMaterialType();
2043 | pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) );
2044 |
2045 | } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) {
2046 |
2047 | var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ];
2048 | materialType = kmuExtension.getMaterialType();
2049 | pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) );
2050 |
2051 | } else {
2052 |
2053 | // Specification:
2054 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
2055 |
2056 | materialType = THREE.MeshStandardMaterial;
2057 |
2058 | var metallicRoughness = materialDef.pbrMetallicRoughness || {};
2059 |
2060 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
2061 | materialParams.opacity = 1.0;
2062 |
2063 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
2064 |
2065 | var array = metallicRoughness.baseColorFactor;
2066 |
2067 | materialParams.color.fromArray( array );
2068 | materialParams.opacity = array[ 3 ];
2069 |
2070 | }
2071 |
2072 | if ( metallicRoughness.baseColorTexture !== undefined ) {
2073 |
2074 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
2075 |
2076 | }
2077 |
2078 | materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
2079 | materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
2080 |
2081 | if ( metallicRoughness.metallicRoughnessTexture !== undefined ) {
2082 |
2083 | pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) );
2084 | pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) );
2085 |
2086 | }
2087 |
2088 | }
2089 |
2090 | if ( materialDef.doubleSided === true ) {
2091 |
2092 | materialParams.side = THREE.DoubleSide;
2093 |
2094 | }
2095 |
2096 | var alphaMode = materialDef.alphaMode;
2097 |
2098 | if ( alphaMode === ALPHA_MODES.BLEND ) {
2099 |
2100 | materialParams.transparent = true;
2101 |
2102 | } else if ( alphaMode === ALPHA_MODES.MASK ) {
2103 |
2104 | materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
2105 |
2106 | }
2107 |
2108 | if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2109 |
2110 | pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) );
2111 |
2112 | materialParams.normalScale = new THREE.Vector2( 1, 1 );
2113 |
2114 | if ( materialDef.normalTexture.scale !== undefined ) {
2115 |
2116 | materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale );
2117 |
2118 | }
2119 |
2120 | }
2121 |
2122 | if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2123 |
2124 | pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) );
2125 |
2126 | if ( materialDef.occlusionTexture.strength !== undefined ) {
2127 |
2128 | materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
2129 |
2130 | }
2131 |
2132 | }
2133 |
2134 | if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2135 |
2136 | materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor );
2137 |
2138 | }
2139 |
2140 | if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
2141 |
2142 | pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) );
2143 |
2144 | }
2145 |
2146 | return Promise.all( pending ).then( function () {
2147 |
2148 | var material;
2149 |
2150 | if ( materialType === GLTFMeshStandardSGMaterial ) {
2151 |
2152 | material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams );
2153 |
2154 | } else {
2155 |
2156 | material = new materialType( materialParams );
2157 |
2158 | }
2159 |
2160 | if ( materialDef.name ) material.name = materialDef.name;
2161 |
2162 | // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
2163 | if ( material.map ) material.map.encoding = THREE.sRGBEncoding;
2164 | if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding;
2165 |
2166 | assignExtrasToUserData( material, materialDef );
2167 |
2168 | if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
2169 |
2170 | return material;
2171 |
2172 | } );
2173 |
2174 | };
2175 |
2176 | /**
2177 | * @param {THREE.BufferGeometry} geometry
2178 | * @param {GLTF.Primitive} primitiveDef
2179 | * @param {GLTFParser} parser
2180 | */
2181 | function computeBounds( geometry, primitiveDef, parser ) {
2182 |
2183 | var attributes = primitiveDef.attributes;
2184 |
2185 | var box = new THREE.Box3();
2186 |
2187 | if ( attributes.POSITION !== undefined ) {
2188 |
2189 | var accessor = parser.json.accessors[ attributes.POSITION ];
2190 |
2191 | var min = accessor.min;
2192 | var max = accessor.max;
2193 |
2194 | // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
2195 |
2196 | if ( min !== undefined && max !== undefined ) {
2197 |
2198 | box.set(
2199 | new THREE.Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ),
2200 | new THREE.Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) );
2201 |
2202 | } else {
2203 |
2204 | console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
2205 |
2206 | return;
2207 |
2208 | }
2209 |
2210 | } else {
2211 |
2212 | return;
2213 |
2214 | }
2215 |
2216 | var targets = primitiveDef.targets;
2217 |
2218 | if ( targets !== undefined ) {
2219 |
2220 | var vector = new THREE.Vector3();
2221 |
2222 | for ( var i = 0, il = targets.length; i < il; i ++ ) {
2223 |
2224 | var target = targets[ i ];
2225 |
2226 | if ( target.POSITION !== undefined ) {
2227 |
2228 | var accessor = parser.json.accessors[ target.POSITION ];
2229 | var min = accessor.min;
2230 | var max = accessor.max;
2231 |
2232 | // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
2233 |
2234 | if ( min !== undefined && max !== undefined ) {
2235 |
2236 | // we need to get max of absolute components because target weight is [-1,1]
2237 | vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) );
2238 | vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) );
2239 | vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) );
2240 |
2241 | box.expandByVector( vector );
2242 |
2243 | } else {
2244 |
2245 | console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
2246 |
2247 | }
2248 |
2249 | }
2250 |
2251 | }
2252 |
2253 | }
2254 |
2255 | geometry.boundingBox = box;
2256 |
2257 | var sphere = new THREE.Sphere();
2258 |
2259 | box.getCenter( sphere.center );
2260 | sphere.radius = box.min.distanceTo( box.max ) / 2;
2261 |
2262 | geometry.boundingSphere = sphere;
2263 |
2264 | }
2265 |
2266 | /**
2267 | * @param {THREE.BufferGeometry} geometry
2268 | * @param {GLTF.Primitive} primitiveDef
2269 | * @param {GLTFParser} parser
2270 | * @return {Promise}
2271 | */
2272 | function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
2273 |
2274 | var attributes = primitiveDef.attributes;
2275 |
2276 | var pending = [];
2277 |
2278 | function assignAttributeAccessor( accessorIndex, attributeName ) {
2279 |
2280 | return parser.getDependency( 'accessor', accessorIndex )
2281 | .then( function ( accessor ) {
2282 |
2283 | geometry.setAttribute( attributeName, accessor );
2284 |
2285 | } );
2286 |
2287 | }
2288 |
2289 | for ( var gltfAttributeName in attributes ) {
2290 |
2291 | var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
2292 |
2293 | // Skip attributes already provided by e.g. Draco extension.
2294 | if ( threeAttributeName in geometry.attributes ) continue;
2295 |
2296 | pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
2297 |
2298 | }
2299 |
2300 | if ( primitiveDef.indices !== undefined && ! geometry.index ) {
2301 |
2302 | var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
2303 |
2304 | geometry.setIndex( accessor );
2305 |
2306 | } );
2307 |
2308 | pending.push( accessor );
2309 |
2310 | }
2311 |
2312 | assignExtrasToUserData( geometry, primitiveDef );
2313 |
2314 | computeBounds( geometry, primitiveDef, parser );
2315 |
2316 | return Promise.all( pending ).then( function () {
2317 |
2318 | return primitiveDef.targets !== undefined
2319 | ? addMorphTargets( geometry, primitiveDef.targets, parser )
2320 | : geometry;
2321 |
2322 | } );
2323 |
2324 | }
2325 |
2326 | /**
2327 | * @param {THREE.BufferGeometry} geometry
2328 | * @param {Number} drawMode
2329 | * @return {THREE.BufferGeometry}
2330 | */
2331 | function toTrianglesDrawMode( geometry, drawMode ) {
2332 |
2333 | var index = geometry.getIndex();
2334 |
2335 | // generate index if not present
2336 |
2337 | if ( index === null ) {
2338 |
2339 | var indices = [];
2340 |
2341 | var position = geometry.getAttribute( 'position' );
2342 |
2343 | if ( position !== undefined ) {
2344 |
2345 | for ( var i = 0; i < position.count; i ++ ) {
2346 |
2347 | indices.push( i );
2348 |
2349 | }
2350 |
2351 | geometry.setIndex( indices );
2352 | index = geometry.getIndex();
2353 |
2354 | } else {
2355 |
2356 | console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
2357 | return geometry;
2358 |
2359 | }
2360 |
2361 | }
2362 |
2363 | //
2364 |
2365 | var numberOfTriangles = index.count - 2;
2366 | var newIndices = [];
2367 |
2368 | if ( drawMode === THREE.TriangleFanDrawMode ) {
2369 |
2370 | // gl.TRIANGLE_FAN
2371 |
2372 | for ( var i = 1; i <= numberOfTriangles; i ++ ) {
2373 |
2374 | newIndices.push( index.getX( 0 ) );
2375 | newIndices.push( index.getX( i ) );
2376 | newIndices.push( index.getX( i + 1 ) );
2377 |
2378 | }
2379 |
2380 | } else {
2381 |
2382 | // gl.TRIANGLE_STRIP
2383 |
2384 | for ( var i = 0; i < numberOfTriangles; i ++ ) {
2385 |
2386 | if ( i % 2 === 0 ) {
2387 |
2388 | newIndices.push( index.getX( i ) );
2389 | newIndices.push( index.getX( i + 1 ) );
2390 | newIndices.push( index.getX( i + 2 ) );
2391 |
2392 |
2393 | } else {
2394 |
2395 | newIndices.push( index.getX( i + 2 ) );
2396 | newIndices.push( index.getX( i + 1 ) );
2397 | newIndices.push( index.getX( i ) );
2398 |
2399 | }
2400 |
2401 | }
2402 |
2403 | }
2404 |
2405 | if ( ( newIndices.length / 3 ) !== numberOfTriangles ) {
2406 |
2407 | console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
2408 |
2409 | }
2410 |
2411 | // build final geometry
2412 |
2413 | var newGeometry = geometry.clone();
2414 | newGeometry.setIndex( newIndices );
2415 |
2416 | return newGeometry;
2417 |
2418 | }
2419 |
2420 | /**
2421 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
2422 | *
2423 | * Creates BufferGeometries from primitives.
2424 | *
2425 | * @param {Array} primitives
2426 | * @return {Promise>}
2427 | */
2428 | GLTFParser.prototype.loadGeometries = function ( primitives ) {
2429 |
2430 | var parser = this;
2431 | var extensions = this.extensions;
2432 | var cache = this.primitiveCache;
2433 |
2434 | function createDracoPrimitive( primitive ) {
2435 |
2436 | return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]
2437 | .decodePrimitive( primitive, parser )
2438 | .then( function ( geometry ) {
2439 |
2440 | return addPrimitiveAttributes( geometry, primitive, parser );
2441 |
2442 | } );
2443 |
2444 | }
2445 |
2446 | var pending = [];
2447 |
2448 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2449 |
2450 | var primitive = primitives[ i ];
2451 | var cacheKey = createPrimitiveKey( primitive );
2452 |
2453 | // See if we've already created this geometry
2454 | var cached = cache[ cacheKey ];
2455 |
2456 | if ( cached ) {
2457 |
2458 | // Use the cached geometry if it exists
2459 | pending.push( cached.promise );
2460 |
2461 | } else {
2462 |
2463 | var geometryPromise;
2464 |
2465 | if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) {
2466 |
2467 | // Use DRACO geometry if available
2468 | geometryPromise = createDracoPrimitive( primitive );
2469 |
2470 | } else {
2471 |
2472 | // Otherwise create a new geometry
2473 | geometryPromise = addPrimitiveAttributes( new THREE.BufferGeometry(), primitive, parser );
2474 |
2475 | }
2476 |
2477 | // Cache this geometry
2478 | cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise };
2479 |
2480 | pending.push( geometryPromise );
2481 |
2482 | }
2483 |
2484 | }
2485 |
2486 | return Promise.all( pending );
2487 |
2488 | };
2489 |
2490 | /**
2491 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
2492 | * @param {number} meshIndex
2493 | * @return {Promise}
2494 | */
2495 | GLTFParser.prototype.loadMesh = function ( meshIndex ) {
2496 |
2497 | var parser = this;
2498 | var json = this.json;
2499 |
2500 | var meshDef = json.meshes[ meshIndex ];
2501 | var primitives = meshDef.primitives;
2502 |
2503 | var pending = [];
2504 |
2505 | for ( var i = 0, il = primitives.length; i < il; i ++ ) {
2506 |
2507 | var material = primitives[ i ].material === undefined
2508 | ? createDefaultMaterial( this.cache )
2509 | : this.getDependency( 'material', primitives[ i ].material );
2510 |
2511 | pending.push( material );
2512 |
2513 | }
2514 |
2515 | pending.push( parser.loadGeometries( primitives ) );
2516 |
2517 | return Promise.all( pending ).then( function ( results ) {
2518 |
2519 | var materials = results.slice( 0, results.length - 1 );
2520 | var geometries = results[ results.length - 1 ];
2521 |
2522 | var meshes = [];
2523 |
2524 | for ( var i = 0, il = geometries.length; i < il; i ++ ) {
2525 |
2526 | var geometry = geometries[ i ];
2527 | var primitive = primitives[ i ];
2528 |
2529 | // 1. create Mesh
2530 |
2531 | var mesh;
2532 |
2533 | var material = materials[ i ];
2534 |
2535 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
2536 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
2537 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
2538 | primitive.mode === undefined ) {
2539 |
2540 | // .isSkinnedMesh isn't in glTF spec. See .markDefs()
2541 | mesh = meshDef.isSkinnedMesh === true
2542 | ? new THREE.SkinnedMesh( geometry, material )
2543 | : new THREE.Mesh( geometry, material );
2544 |
2545 | if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
2546 |
2547 | // we normalize floating point skin weight array to fix malformed assets (see #15319)
2548 | // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
2549 | mesh.normalizeSkinWeights();
2550 |
2551 | }
2552 |
2553 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
2554 |
2555 | mesh.geometry = toTrianglesDrawMode( mesh.geometry, THREE.TriangleStripDrawMode );
2556 |
2557 | } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
2558 |
2559 | mesh.geometry = toTrianglesDrawMode( mesh.geometry, THREE.TriangleFanDrawMode );
2560 |
2561 | }
2562 |
2563 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
2564 |
2565 | mesh = new THREE.LineSegments( geometry, material );
2566 |
2567 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
2568 |
2569 | mesh = new THREE.Line( geometry, material );
2570 |
2571 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
2572 |
2573 | mesh = new THREE.LineLoop( geometry, material );
2574 |
2575 | } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
2576 |
2577 | mesh = new THREE.Points( geometry, material );
2578 |
2579 | } else {
2580 |
2581 | throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
2582 |
2583 | }
2584 |
2585 | if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
2586 |
2587 | updateMorphTargets( mesh, meshDef );
2588 |
2589 | }
2590 |
2591 | mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
2592 |
2593 | if ( geometries.length > 1 ) mesh.name += '_' + i;
2594 |
2595 | assignExtrasToUserData( mesh, meshDef );
2596 |
2597 | parser.assignFinalMaterial( mesh );
2598 |
2599 | meshes.push( mesh );
2600 |
2601 | }
2602 |
2603 | if ( meshes.length === 1 ) {
2604 |
2605 | return meshes[ 0 ];
2606 |
2607 | }
2608 |
2609 | var group = new THREE.Group();
2610 |
2611 | for ( var i = 0, il = meshes.length; i < il; i ++ ) {
2612 |
2613 | group.add( meshes[ i ] );
2614 |
2615 | }
2616 |
2617 | return group;
2618 |
2619 | } );
2620 |
2621 | };
2622 |
2623 | /**
2624 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
2625 | * @param {number} cameraIndex
2626 | * @return {Promise}
2627 | */
2628 | GLTFParser.prototype.loadCamera = function ( cameraIndex ) {
2629 |
2630 | var camera;
2631 | var cameraDef = this.json.cameras[ cameraIndex ];
2632 | var params = cameraDef[ cameraDef.type ];
2633 |
2634 | if ( ! params ) {
2635 |
2636 | console.warn( 'THREE.GLTFLoader: Missing camera parameters.' );
2637 | return;
2638 |
2639 | }
2640 |
2641 | if ( cameraDef.type === 'perspective' ) {
2642 |
2643 | camera = new THREE.PerspectiveCamera( THREE.MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 );
2644 |
2645 | } else if ( cameraDef.type === 'orthographic' ) {
2646 |
2647 | camera = new THREE.OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar );
2648 |
2649 | }
2650 |
2651 | if ( cameraDef.name ) camera.name = cameraDef.name;
2652 |
2653 | assignExtrasToUserData( camera, cameraDef );
2654 |
2655 | return Promise.resolve( camera );
2656 |
2657 | };
2658 |
2659 | /**
2660 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
2661 | * @param {number} skinIndex
2662 | * @return {Promise