(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { runTimeout(drainQueue); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.prependListener = noop; process.prependOnceListener = noop; process.listeners = function (name) { return [] } process.binding = function (name) { throw new Error('process.binding is not supported'); }; process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; },{}],3:[function(require,module,exports){ (function (setImmediate,clearImmediate){(function (){ var nextTick = require('process/browser.js').nextTick; var apply = Function.prototype.apply; var slice = Array.prototype.slice; var immediateIds = {}; var nextImmediateId = 0; // DOM APIs, for completeness exports.setTimeout = function() { return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); }; exports.setInterval = function() { return new Timeout(apply.call(setInterval, window, arguments), clearInterval); }; exports.clearTimeout = exports.clearInterval = function(timeout) { timeout.close(); }; function Timeout(id, clearFn) { this._id = id; this._clearFn = clearFn; } Timeout.prototype.unref = Timeout.prototype.ref = function() {}; Timeout.prototype.close = function() { this._clearFn.call(window, this._id); }; // Does not start the time, just sets up the members needed. exports.enroll = function(item, msecs) { clearTimeout(item._idleTimeoutId); item._idleTimeout = msecs; }; exports.unenroll = function(item) { clearTimeout(item._idleTimeoutId); item._idleTimeout = -1; }; exports._unrefActive = exports.active = function(item) { clearTimeout(item._idleTimeoutId); var msecs = item._idleTimeout; if (msecs >= 0) { item._idleTimeoutId = setTimeout(function onTimeout() { if (item._onTimeout) item._onTimeout(); }, msecs); } }; // That's not how node.js implements it but the exposed api is the same. exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { var id = nextImmediateId++; var args = arguments.length < 2 ? false : slice.call(arguments, 1); immediateIds[id] = true; nextTick(function onNextTick() { if (immediateIds[id]) { // fn.call() is faster so we optimize for the common use-case // @see http://jsperf.com/call-apply-segu if (args) { fn.apply(null, args); } else { fn.call(null); } // Prevent ids from leaking exports.clearImmediate(id); } }); return id; }; exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { delete immediateIds[id]; }; }).call(this)}).call(this,require("timers").setImmediate,require("timers").clearImmediate) },{"process/browser.js":2,"timers":3}],4:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const THREE = require("./three.js"); const CameraSync = require("./camera/CameraSync.js"); const utils = require("./utils/utils.js"); const SunCalc = require("./utils/suncalc.js"); const ThreeboxConstants = require("./utils/constants.js"); const Objects = require("./objects/objects.js"); const material = require("./utils/material.js"); const sphere = require("./objects/sphere.js"); const extrusion = require("./objects/extrusion.js"); const label = require("./objects/label.js"); const tooltip = require("./objects/tooltip.js"); const loader = require("./objects/loadObj.js"); const Object3D = require("./objects/Object3D.js"); const line = require("./objects/line.js"); const tube = require("./objects/tube.js"); const LabelRenderer = require("./objects/LabelRenderer.js"); const BuildingShadows = require("./objects/effects/BuildingShadows.js"); function Threebox(map, glContext, options){ this.init(map, glContext, options); }; Threebox.prototype = { repaint: function () { this.map.repaint = true; }, /** * Threebox constructor init method * @param {mapboxgl.map} map * @param {WebGLRenderingContext} glContext * @param {defaultOptions} options */ init: function (map, glContext, options) { // apply starter options this.options = utils._validate(options || {}, defaultOptions); this.map = map; this.map.tb = this; //[jscastro] needed if we want to queryRenderedFeatures from map.onload this.objects = new Objects(); this.mapboxVersion = parseFloat(this.map.version); // Set up a THREE.js scene this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: options.preserveDrawingBuffer, canvas: map.getCanvas(), context: glContext }); this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight); this.renderer.outputEncoding = THREE.sRGBEncoding; this.renderer.autoClear = false; // [jscastro] set labelRendered this.labelRenderer = new LabelRenderer(this.map); this.scene = new THREE.Scene(); this.world = new THREE.Group(); this.world.name = "world"; this.scene.add(this.world); this.objectsCache = new Map(); this.zoomLayers = []; this.fov = this.options.fov; this.orthographic = this.options.orthographic || false; //raycaster for mouse events this.raycaster = new THREE.Raycaster(); this.raycaster.layers.set(0); //this.raycaster.params.Points.threshold = 100; this.mapCenter = this.map.getCenter(); this.mapCenterUnits = utils.projectToWorld([this.mapCenter.lng, this.mapCenter.lat]); this.lightDateTime = new Date(); this.lightLng = this.mapCenter.lng; this.lightLat = this.mapCenter.lat; this.sunPosition; this.rotationStep = 5;// degrees step size for rotation this.gridStep = 6;// decimals to adjust the lnglat grid step, 6 = 11.1cm this.altitudeStep = 0.1; // 1px = 0.1m = 10cm this.defaultCursor = 'default'; this.lights = this.initLights; if (this.options.defaultLights) this.defaultLights(); if (this.options.realSunlight) this.realSunlight(this.options.realSunlightHelper); this.skyLayerName = 'sky-layer'; this.terrainSourceName = 'mapbox-dem'; this.terrainExaggeration = 1.0; this.terrainLayerName = ''; this.enableSelectingFeatures = this.options.enableSelectingFeatures || false; this.enableSelectingObjects = this.options.enableSelectingObjects || false; this.enableDraggingObjects = this.options.enableDraggingObjects || false; this.enableRotatingObjects = this.options.enableRotatingObjects || false; this.enableTooltips = this.options.enableTooltips || false; this.multiLayer = this.options.multiLayer || false; this.enableHelpTooltips = this.options.enableHelpTooltips || false; this.map.on('style.load', function () { this.tb.zoomLayers = []; //[jscastro] if multiLayer, create a by default layer in the map, so tb.update won't be needed in client side to avoid duplicating calls to render if (this.tb.options.multiLayer) this.addLayer({ id: "threebox_layer", type: 'custom', renderingMode: '3d', map: this, onAdd: function (map, gl) { }, render: function (gl, matrix) { this.map.tb.update(); } }) this.once('idle', () => { this.tb.setObjectsScale(); }); if (this.tb.options.sky) { this.tb.sky = true; } if (this.tb.options.terrain) { this.tb.terrain = true; } let rasterLayers = ['satellite', 'mapbox-mapbox-satellite', 'satelliteLayer']; rasterLayers.forEach((l) => { if (this.getLayer(l)) this.tb.terrainLayerName = l; }) }); //[jscastro] new event map on load this.map.on('load', function () { //[jscastro] new fields to manage events on map this.selectedObject; //selected object through click this.selectedFeature;//selected state id for extrusion layer features this.draggedObject; //dragged object through mousedown + mousemove let draggedAction; //dragged action to notify frontend this.overedObject; //overed object through mouseover this.overedFeature; //overed state for extrusion layer features let canvas = this.getCanvasContainer(); this.getCanvasContainer().style.cursor = this.tb.defaultCursor; // Variable to hold the starting xy coordinates // when 'mousedown' occured. let start; //when object selected let startCoords = []; let lngDiff; // difference between cursor and model left corner let latDiff; // difference between cursor and model bottom corner let altDiff; // difference between cursor and model height let rotationDiff; // Return the xy coordinates of the mouse position function mousePos(e) { var rect = canvas.getBoundingClientRect(); return { x: e.originalEvent.clientX - rect.left - canvas.clientLeft, y: e.originalEvent.clientY - rect.top - canvas.clientTop }; } this.unselectObject = function () { //deselect, reset and return this.selectedObject.selected = false; this.selectedObject = null; } this.outObject = function () { this.overedObject.over = false; this.overedObject = null; } this.unselectFeature = function (f) { if (typeof f.id == 'undefined') return; this.setFeatureState( { source: f.source, sourceLayer: f.sourceLayer, id: f.id }, { select: false } ); this.removeTooltip(f); f = this.queryRenderedFeatures({ layers: [f.layer.id], filter: ["==", ['id'], f.id] })[0]; // Dispatch new event f for unselected if (f) this.fire('SelectedFeatureChange', { detail: f }); this.selectedFeature = null; } this.selectFeature = function(f) { this.selectedFeature = f; this.setFeatureState( { source: this.selectedFeature.source, sourceLayer: this.selectedFeature.sourceLayer, id: this.selectedFeature.id }, { select: true } ); this.selectedFeature = this.queryRenderedFeatures({ layers: [this.selectedFeature.layer.id], filter: ["==", ['id'], this.selectedFeature.id] })[0]; this.addTooltip(this.selectedFeature); // Dispatch new event SelectedFeature for selected this.fire('SelectedFeatureChange', { detail: this.selectedFeature }); } this.outFeature = function(f) { if (this.overedFeature && typeof this.overedFeature != 'undefined' && this.overedFeature.id != f) { map.setFeatureState( { source: this.overedFeature.source, sourceLayer: this.overedFeature.sourceLayer, id: this.overedFeature.id }, { hover: false } ); this.removeTooltip(this.overedFeature); this.overedFeature = null; } } this.addTooltip = function(f) { if (!this.tb.enableTooltips) return; let coordinates = this.tb.getFeatureCenter(f); let t = this.tb.tooltip({ text: f.properties.name || f.id || f.type, mapboxStyle: true, feature: f }); t.setCoords(coordinates); this.tb.add(t, f.layer.id); f.tooltip = t; f.tooltip.tooltip.visible = true; } this.removeTooltip = function(f) { if (f.tooltip) { f.tooltip.visibility = false; this.tb.remove(f.tooltip); f.tooltip = null; } } map.onContextMenu = function (e) { alert('contextMenu'); //TODO: implement a callback } // onclick function this.onClick = function (e) { let intersectionExists let intersects = []; if (map.tb.enableSelectingObjects) { //raycast only if we are in a custom layer, for other layers go to the else, this avoids duplicated calls to raycaster intersects = this.tb.queryRenderedFeatures(e.point); } intersectionExists = typeof intersects[0] == 'object'; // if intersect exists, highlight it if (intersectionExists) { let nearestObject = Threebox.prototype.findParent3DObject(intersects[0]); if (nearestObject) { //if extrusion object selected, unselect if (this.selectedFeature) { this.unselectFeature(this.selectedFeature); } //if not selected yet, select it if (!this.selectedObject) { this.selectedObject = nearestObject; this.selectedObject.selected = true; } else if (this.selectedObject.uuid != nearestObject.uuid) { //it's a different object, restore the previous and select the new one this.selectedObject.selected = false; nearestObject.selected = true; this.selectedObject = nearestObject; } else if (this.selectedObject.uuid == nearestObject.uuid) { //deselect, reset and return this.unselectObject(); return; } // fire the Wireframed event to notify UI status change this.selectedObject.dispatchEvent({ type: 'Wireframed', detail: this.selectedObject }); this.selectedObject.dispatchEvent({ type: 'IsPlayingChanged', detail: this.selectedObject }); this.repaint = true; e.preventDefault(); } } else { let features = []; if (map.tb.enableSelectingFeatures) { features = this.queryRenderedFeatures(e.point); } //now let's check the extrusion layer objects if (features.length > 0) { if (features[0].layer.type == 'fill-extrusion' && typeof features[0].id != 'undefined') { //if 3D object selected, unselect if (this.selectedObject) { this.unselectObject(); } //if not selected yet, select it if (!this.selectedFeature) { this.selectFeature(features[0]) } else if (this.selectedFeature.id != features[0].id) { //it's a different feature, restore the previous and select the new one this.unselectFeature(this.selectedFeature); this.selectFeature(features[0]) } else if (this.selectedFeature.id == features[0].id) { //deselect, reset and return this.unselectFeature(this.selectedFeature); return; } } } } } this.onMouseMove = function (e) { // Capture the ongoing xy coordinates let current = mousePos(e); this.getCanvasContainer().style.cursor = this.tb.defaultCursor; //check if being rotated if (e.originalEvent.altKey && this.draggedObject) { if (!map.tb.enableRotatingObjects) return; draggedAction = 'rotate'; // Set a UI indicator for dragging. this.getCanvasContainer().style.cursor = 'move'; var minX = Math.min(start.x, current.x), maxX = Math.max(start.x, current.x), minY = Math.min(start.y, current.y), maxY = Math.max(start.y, current.y); //set the movement fluid we rotate only every 10px moved, in steps of 10 degrees up to 360 let rotation = { x: 0, y: 0, z: (Math.round(rotationDiff[2] + (~~((current.x - start.x) / this.tb.rotationStep) % 360 * this.tb.rotationStep) % 360)) }; //now rotate the model depending the axis this.draggedObject.setRotation(rotation); if (map.tb.enableHelpTooltips) this.draggedObject.addHelp("rot: " + rotation.z + "°"); //this.draggedObject.setRotationAxis(rotation); return; } //check if being moved if (e.originalEvent.shiftKey && this.draggedObject) { if (!map.tb.enableDraggingObjects) return; draggedAction = 'translate'; // Set a UI indicator for dragging. this.getCanvasContainer().style.cursor = 'move'; // Capture the first xy coordinates, height must be the same to move on the same plane let coords = e.lngLat; let options = [Number((coords.lng + lngDiff).toFixed(this.tb.gridStep)), Number((coords.lat + latDiff).toFixed(this.tb.gridStep)), this.draggedObject.modelHeight]; this.draggedObject.setCoords(options); if (map.tb.enableHelpTooltips) this.draggedObject.addHelp("lng: " + options[0] + "°, lat: " + options[1] + "°"); return; } //check if being moved on altitude if (e.originalEvent.ctrlKey && this.draggedObject) { if (!map.tb.enableDraggingObjects) return; draggedAction = 'altitude'; // Set a UI indicator for dragging. this.getCanvasContainer().style.cursor = 'move'; // Capture the first xy coordinates, height must be the same to move on the same plane let now = (e.point.y * this.tb.altitudeStep); let options = [this.draggedObject.coordinates[0], this.draggedObject.coordinates[1], Number((- now - altDiff).toFixed(this.tb.gridStep))]; this.draggedObject.setCoords(options); if (map.tb.enableHelpTooltips) this.draggedObject.addHelp("alt: " + options[2] + "m"); return; } let intersectionExists let intersects = []; if (map.tb.enableSelectingObjects) { // calculate objects intersecting the picking ray // intersects = this.tb.queryRenderedFeatures(e.point); } intersectionExists = typeof intersects[0] == 'object'; // if intersect exists, highlight it, if not check the extrusion layer if (intersectionExists) { let nearestObject = Threebox.prototype.findParent3DObject(intersects[0]); if (nearestObject) { this.outFeature(this.overedFeature); this.getCanvasContainer().style.cursor = 'pointer'; if (!this.selectedObject || nearestObject.uuid != this.selectedObject.uuid) { if (this.overedObject && this.overedObject.uuid != nearestObject.uuid) { this.outObject(); } nearestObject.over = true; this.overedObject = nearestObject; } else if (this.selectedObject && nearestObject.uuid == this.selectedObject.uuid) { nearestObject.over = true; this.overedObject = nearestObject; } this.repaint = true; e.preventDefault(); } } else { //clean the object overed if (this.overedObject) { this.outObject(); } //now let's check the extrusion layer objects let features = []; if (map.tb.enableSelectingFeatures) { features = this.queryRenderedFeatures(e.point); } if (features.length > 0) { this.outFeature(features[0]); if (features[0].layer.type == 'fill-extrusion' && typeof features[0].id != 'undefined') { if ((!this.selectedFeature || this.selectedFeature.id != features[0].id)) { this.getCanvasContainer().style.cursor = 'pointer'; this.overedFeature = features[0]; this.setFeatureState( { source: this.overedFeature.source, sourceLayer: this.overedFeature.sourceLayer, id: this.overedFeature.id }, { hover: true } ); this.overedFeature = map.queryRenderedFeatures({ layers: [this.overedFeature.layer.id], filter: ["==", ['id'], this.overedFeature.id] })[0]; this.addTooltip(this.overedFeature); } } } } } this.onMouseDown = function (e) { // Continue the rest of the function shiftkey or altkey are pressed, and if object is selected if (!((e.originalEvent.shiftKey || e.originalEvent.altKey || e.originalEvent.ctrlKey) && e.originalEvent.button === 0 && this.selectedObject)) return; if (!map.tb.enableDraggingObjects && !map.tb.enableRotatingObjects) return; e.preventDefault(); map.getCanvasContainer().style.cursor = 'move'; // Disable default drag zooming when the shift key is held down. //map.dragPan.disable(); // Call functions for the following events map.once('mouseup', this.onMouseUp); //map.once('mouseout', this.onMouseUp); // move the selected object this.draggedObject = this.selectedObject; // Capture the first xy coordinates start = mousePos(e); startCoords = this.draggedObject.coordinates; rotationDiff = utils.degreeify(this.draggedObject.rotation); lngDiff = startCoords[0] - e.lngLat.lng; latDiff = startCoords[1] - e.lngLat.lat; altDiff = -this.draggedObject.modelHeight - (e.point.y * this.tb.altitudeStep); } this.onMouseUp = function (e) { // Set a UI indicator for dragging. this.getCanvasContainer().style.cursor = this.tb.defaultCursor; // Remove these events now that finish has been called. //map.off('mousemove', onMouseMove); this.off('mouseup', this.onMouseUp); this.off('mouseout', this.onMouseUp); this.dragPan.enable(); if (this.draggedObject) { this.draggedObject.dispatchEvent({ type: 'ObjectDragged', detail: { draggedObject: this.draggedObject, draggedAction: draggedAction } }); this.draggedObject.removeHelp(); this.draggedObject = null; draggedAction = null; }; } this.onMouseOut = function (e) { if (this.overedFeature) { let features = this.queryRenderedFeatures(e.point); if (features.length > 0 && this.overedFeature.id != features[0].id) { this.getCanvasContainer().style.cursor = this.tb.defaultCursor; //only unover when new feature is another this.outFeature(features[0]); } } } this.onZoom = function (e) { this.tb.zoomLayers.forEach((l) => { this.tb.toggleLayer(l); }); this.tb.setObjectsScale(); } let ctrlDown = false; let shiftDown = false; let ctrlKey = 17, cmdKey = 91, shiftKey = 16, sK = 83, dK = 68; function onKeyDown(e) { if (e.which === ctrlKey || e.which === cmdKey) ctrlDown = true; if (e.which === shiftKey) shiftDown = true; let obj = this.selectedObject; if (shiftDown && e.which === sK && obj) { //shift + sS let dc = utils.toDecimal; if (!obj.help) { let s = obj.modelSize; let sf = 1; if (obj.userData.units !== 'meters') { //if not meters, calculate scale to the current lat sf = utils.projectedUnitsPerMeter(obj.coordinates[1]); if (!sf) { sf = 1; }; sf = dc(sf, 7); } if (map.tb.enableHelpTooltips) obj.addHelp("size(m): " + dc((s.x / sf), 3) + " W, " + dc((s.y / sf), 3) + " L, " + dc((s.z / sf), 3) + " H"); this.repaint = true; } else { obj.removeHelp(); } return false; } }; function onKeyUp (e) { if (e.which == ctrlKey || e.which == cmdKey) ctrlDown = false; if (e.which === shiftKey) shiftDown = false; } //listener to the events //this.on('contextmenu', map.onContextMenu); this.on('click', this.onClick); this.on('mousemove', this.onMouseMove); this.on('mouseout', this.onMouseOut) this.on('mousedown', this.onMouseDown); this.on('zoom', this.onZoom); this.on('zoomend', this.onZoom); document.addEventListener('keydown', onKeyDown.bind(this), true); document.addEventListener('keyup', onKeyUp.bind(this)); }); }, //[jscastro] added property to manage an athmospheric sky layer get sky() { return this.options.sky; }, set sky(value) { if (value) { this.createSkyLayer(); } else { this.removeLayer(this.skyLayerName); } this.options.sky = value; }, //[jscastro] added property to manage an athmospheric sky layer get terrain() { return this.options.terrain; }, set terrain(value) { this.terrainLayerName = ''; if (value) { this.createTerrainLayer(); } else { if (this.mapboxVersion < 2.0) { console.warn("Terrain layer are only supported by Mapbox-gl-js > v2.0"); return }; if (this.map.getTerrain()) { this.map.setTerrain(null); // this.map.removeSource(this.terrainSourceName); } } this.options.terrain = value; }, //[jscastro] added property to manage FOV for perspective camera get fov() { return this.options.fov;}, set fov(value) { if (this.camera instanceof THREE.PerspectiveCamera && this.options.fov !== value) { this.map.transform.fov = value; this.camera.fov = this.map.transform.fov; this.cameraSync.setupCamera(); this.map.repaint = true; this.options.fov = value; } }, //[jscastro] added property to manage camera type get orthographic() { return this.options.orthographic; }, set orthographic(value) { const h = this.map.getCanvas().clientHeight; const w = this.map.getCanvas().clientWidth; if (value) { this.map.transform.fov = 0; this.camera = new THREE.OrthographicCamera(w / - 2, w / 2, h / 2, h / - 2, 0.1, 1e21); } else { this.map.transform.fov = this.fov; this.camera = new THREE.PerspectiveCamera(this.map.transform.fov, w / h, 0.1, 1e21); } this.camera.layers.enable(0); this.camera.layers.enable(1); // The CameraSync object will keep the Mapbox and THREE.js camera movements in sync. // It requires a world group to scale as we zoom in. Rotation is handled in the camera's // projection matrix itself (as is field of view and near/far clipping) // It automatically registers to listen for move events on the map so we don't need to do that here this.cameraSync = new CameraSync(this.map, this.camera, this.world); this.map.repaint = true; // repaint the map this.options.orthographic = value; }, //[jscastro] method to create an athmospheric sky layer createSkyLayer: function () { if (this.mapboxVersion < 2.0) { console.warn("Sky layer are only supported by Mapbox-gl-js > v2.0"); this.options.sky = false; return }; let layer = this.map.getLayer(this.skyLayerName); if (!layer) { this.map.addLayer({ 'id': this.skyLayerName, 'type': 'sky', 'paint': { 'sky-opacity': [ 'interpolate', ['linear'], ['zoom'], 0, 0, 5, 0.3, 8, 1 ], // set up the sky layer for atmospheric scattering 'sky-type': 'atmosphere', // explicitly set the position of the sun rather than allowing the sun to be attached to the main light source 'sky-atmosphere-sun': this.getSunSky(this.lightDateTime), // set the intensity of the sun as a light source (0-100 with higher values corresponding to brighter skies) 'sky-atmosphere-sun-intensity': 10 } }); this.map.once('idle', () => { this.setSunlight(); this.repaint(); }); } }, //[jscastro] method to create a terrain layer createTerrainLayer: function () { if (this.mapboxVersion < 2.0) { console.warn("Terrain layer are only supported by Mapbox-gl-js > v2.0"); this.options.terrain = false; return }; let layer = this.map.getTerrain(); if (!layer) { // add the DEM source as a terrain layer with exaggerated height this.map.addSource(this.terrainSourceName, { 'type': 'raster-dem', 'url': 'mapbox://mapbox.mapbox-terrain-dem-v1', 'tileSize': 512, 'maxzoom': 14 }); this.map.setTerrain({ 'source': this.terrainSourceName, 'exaggeration': this.terrainExaggeration }); this.map.once('idle', () => { //alert("idle"); this.cameraSync.updateCamera(); this.repaint(); }); } }, // Objects sphere: function (options) { this.setDefaultView(options, this.options); return sphere(options, this.world) }, line: line, label: label, tooltip: tooltip, tube: function (options) { this.setDefaultView(options, this.options); return tube(options, this.world) }, extrusion: function (options) { this.setDefaultView(options, this.options); return extrusion(options); }, Object3D: function (options) { this.setDefaultView(options, this.options); return Object3D(options) }, loadObj: async function loadObj(options, cb) { this.setDefaultView(options, this.options); if (options.clone === false) { return new Promise( async (resolve) => { loader(options, cb, async (obj) => { resolve(obj); }); }); } else { //[jscastro] new added cache for 3D Objects let cache = this.objectsCache.get(options.obj); if (cache) { cache.promise .then(obj => { cb(obj.duplicate(options)); }) .catch(err => { this.objectsCache.delete(options.obj); console.error("Could not load model file: " + options.obj); }); } else { this.objectsCache.set(options.obj, { promise: new Promise( async (resolve, reject) => { loader(options, cb, async (obj) => { if (obj.duplicate) { resolve(obj.duplicate()); } else { reject(obj); } }); }) }); } } }, // Material material: function (o) { return material(o) }, initLights : { ambientLight: null, dirLight: null, dirLightBack: null, dirLightHelper: null, hemiLight: null, pointLight: null }, utils: utils, SunCalc: SunCalc, Constants: ThreeboxConstants, projectToWorld: function (coords) { return this.utils.projectToWorld(coords) }, unprojectFromWorld: function (v3) { return this.utils.unprojectFromWorld(v3) }, projectedUnitsPerMeter: function (lat) { return this.utils.projectedUnitsPerMeter(lat) }, //get the center point of a feature getFeatureCenter: function getFeatureCenter(feature, obj, level) { return utils.getFeatureCenter(feature, obj, level); }, getObjectHeightOnFloor: function (feature, obj, level) { return utils.getObjectHeightOnFloor(feature, obj, level); }, queryRenderedFeatures: function (point) { let mouse = new THREE.Vector2(); // // scale mouse pixel position to a percentage of the screen's width and height mouse.x = (point.x / this.map.transform.width) * 2 - 1; mouse.y = 1 - (point.y / this.map.transform.height) * 2; this.raycaster.setFromCamera(mouse, this.camera); // calculate objects intersecting the picking ray let intersects = this.raycaster.intersectObjects(this.world.children, true); return intersects }, //[jscastro] find 3D object of a mesh. this method is needed to know the object of a raycasted mesh findParent3DObject: function (mesh) { //find the Parent Object3D of the mesh captured by Raytracer var result; mesh.object.traverseAncestors(function (m) { if (m.parent) if (m.parent.type == "Group" && m.userData.obj) { result = m; } }); return result; }, //[jscastro] method to replicate behaviour of map.setLayoutProperty when Threebox are affected setLayoutProperty: function (layerId, name, value) { //first set layout property at the map this.map.setLayoutProperty(layerId, name, value); if (value !== null && value !== undefined) { if (name === 'visibility') { this.world.children.filter(o => (o.layer === layerId)).forEach((o) => { o.visibility = value }); } } }, //[jscastro] Custom Layers doesn't work on minzoom and maxzoom attributes, and if the layer is including labels they don't hide either on minzoom setLayerZoomRange: function (layerId, minZoomLayer, maxZoomLayer) { if (this.map.getLayer(layerId)) { this.map.setLayerZoomRange(layerId, minZoomLayer, maxZoomLayer); if (!this.zoomLayers.includes(layerId)) this.zoomLayers.push(layerId); this.toggleLayer(layerId); } }, //[jscastro] method to set the height of all the objects in a level. this only works if the objects have a geojson feature setLayerHeigthProperty: function (layerId, level) { let layer = this.map.getLayer(layerId); if (!layer) return; if (layer.type == "fill-extrusion") { let data = this.map.getStyle().sources[layer.source].data; let features = data.features; features.forEach(function (f) { f.properties.level = level; }); //we change the level on the source this.map.getSource(layer.source).setData(data); } else if (layer.type == "custom") { this.world.children.forEach(function (obj) { let feature = obj.userData.feature; if (feature && feature.layer === layerId) { //TODO: this could be a multidimensional array let location = this.tb.getFeatureCenter(feature, obj, level); obj.setCoords(location); } }); } }, //[jscastro] method to set globally all the objects that are fixedScale setObjectsScale: function () { this.world.children.filter(o => (o.fixedZoom != null)).forEach((o) => { o.setObjectScale(this.map.transform.scale); }); }, //[jscastro] mapbox setStyle removes all the layers, including custom layers, so tb.world must be cleaned up too setStyle: function (styleId, options) { this.clear().then(() => { this.map.setStyle(styleId, options); }); }, //[jscastro] method to toggle Layer visibility checking zoom range toggleLayer: function (layerId, visible = true) { let l = this.map.getLayer(layerId); if (l) { if (!visible) { this.toggle(l.id, false); return; } let z = this.map.getZoom(); if (l.minzoom && z < l.minzoom) { this.toggle(l.id, false); return; }; if (l.maxzoom && z >= l.maxzoom) { this.toggle(l.id, false); return; }; this.toggle(l.id, true); }; }, //[jscastro] method to toggle Layer visibility toggle: function (layerId, visible) { //call this.setLayoutProperty(layerId, 'visibility', (visible ? 'visible' : 'none')) this.labelRenderer.toggleLabels(layerId, visible); }, update: function () { if (this.map.repaint) this.map.repaint = false var timestamp = Date.now(); // Update any animations this.objects.animationManager.update(timestamp); this.updateLightHelper(); // Render the scene and repaint the map this.renderer.resetState(); //update threejs r126 this.renderer.render(this.scene, this.camera); // [jscastro] Render any label this.labelRenderer.render(this.scene, this.camera); if (this.options.passiveRendering === false) this.map.triggerRepaint(); }, add: function (obj, layerId, sourceId) { //[jscastro] remove the tooltip if not enabled if (!this.enableTooltips && obj.tooltip) { obj.tooltip.visibility = false }; this.world.add(obj); if (layerId) { obj.layer = layerId; obj.source = sourceId; let l = this.map.getLayer(layerId); if (l) { let v = l.visibility; let u = typeof v === 'undefined'; obj.visibility = (u || v === 'visible' ? true : false); } } }, removeByName: function (name) { let obj = this.world.getObjectByName(name); if (obj) this.remove(obj); }, remove: function (obj) { if (this.map.selectedObject && obj.uuid == this.map.selectedObject.uuid) this.map.unselectObject(); if (this.map.draggedObject && obj.uuid == this.map.draggedObject.uuid) this.map.draggedObject = null; if (obj.dispose) obj.dispose(); this.world.remove(obj); obj = null; }, //[jscastro] this clears tb.world in order to dispose properly the resources clear: async function (layerId = null, dispose = false) { return new Promise((resolve, reject) => { let objects = []; this.world.children.forEach(function (object) { objects.push(object); }); for (let i = 0; i < objects.length; i++) { let obj = objects[i]; //if layerId, check the layer to remove, otherwise always remove if (obj.layer === layerId || !layerId) { this.remove(obj); } } if (dispose) { this.objectsCache.forEach((value) => { value.promise.then(obj => { obj.dispose(); obj = null; }) }) } resolve("clear"); }); }, //[jscastro] remove a layer clearing first the 3D objects from this layer in tb.world removeLayer: function (layerId) { this.clear(layerId, true).then( () => { this.map.removeLayer(layerId); }); }, //[jscastro] get the sun position (azimuth, altitude) from a given datetime, lng, lat getSunPosition: function (date, coords) { return SunCalc.getPosition(date || Date.now(), coords[1], coords[0]); }, //[jscastro] get the sun times for sunrise, sunset, etc.. from a given datetime, lng, lat and alt getSunTimes: function (date, coords) { return SunCalc.getTimes(date, coords[1], coords[0], (coords[2] ? coords[2] : 0)); }, //[jscastro] set shadows for fill-extrusion layers setBuildingShadows: function (options) { if (this.map.getLayer(options.buildingsLayerId)) { let layer = new BuildingShadows(options, this); this.map.addLayer(layer, options.buildingsLayerId); } else { console.warn("The layer '" + options.buildingsLayerId + "' does not exist in the map."); } }, //[jscastro] This method set the sun light for a given datetime and lnglat setSunlight: function (newDate = new Date(), coords) { if (!this.lights.dirLight || !this.options.realSunlight) { console.warn("To use setSunlight it's required to set realSunlight : true in Threebox initial options."); return; } var date = new Date(newDate.getTime()); if (coords) { if (coords.lng && coords.lat) this.mapCenter = coords else this.mapCenter = { lng: coords[0], lat: coords[1] }; } else { this.mapCenter = this.map.getCenter(); } if (this.lightDateTime && this.lightDateTime.getTime() === date.getTime() && this.lightLng === this.mapCenter.lng && this.lightLat === this.mapCenter.lat) { return; //setSunLight could be called on render, so due to performance, avoid duplicated calls } this.lightDateTime = date; this.lightLng = this.mapCenter.lng; this.lightLat = this.mapCenter.lat this.sunPosition = this.getSunPosition(date, [this.mapCenter.lng, this.mapCenter.lat]); let altitude = this.sunPosition.altitude; let azimuth = Math.PI + this.sunPosition.azimuth; //console.log("Altitude: " + utils.degreeify(altitude) + ", Azimuth: " + (utils.degreeify(azimuth))); let radius = ThreeboxConstants.WORLD_SIZE / 2; let alt = Math.sin(altitude); let altRadius = Math.cos(altitude); let azCos = Math.cos(azimuth) * altRadius; let azSin = Math.sin(azimuth) * altRadius; this.lights.dirLight.position.set(azSin, azCos, alt); this.lights.dirLight.position.multiplyScalar(radius); this.lights.dirLight.intensity = Math.max(alt, 0); this.lights.hemiLight.intensity = Math.max(alt * 1, 0.1); //console.log("Intensity:" + this.lights.dirLight.intensity); this.lights.dirLight.updateMatrixWorld(); this.updateLightHelper(); if (this.map.loaded()) { this.updateSunGround(this.sunPosition); this.map.setLight({ anchor: 'map', position: [3, 180 + this.sunPosition.azimuth * 180 / Math.PI, 90 - this.sunPosition.altitude * 180 / Math.PI], intensity: Math.cos(this.sunPosition.altitude), //0.4, color: `hsl(40, ${50 * Math.cos(this.sunPosition.altitude)}%, ${Math.max(20, 20 + (96 * Math.sin(this.sunPosition.altitude)))}%)` }, { duration: 0 }); if (this.sky) { this.updateSunSky(this.getSunSky(date, this.sunPosition));} } }, getSunSky: function (date, sunPos) { if (!sunPos) { var center = this.map.getCenter(); sunPos = this.getSunPosition( date || Date.now(), [center.lng, center.lat] ); } var sunAzimuth = 180 + (sunPos.azimuth * 180) / Math.PI; var sunAltitude = 90 - (sunPos.altitude * 180) / Math.PI; return [sunAzimuth, sunAltitude]; }, updateSunSky: function (sunPos) { if (this.sky) { // update the `sky-atmosphere-sun` paint property with the position of the sun based on the selected time this.map.setPaintProperty(this.skyLayerName, 'sky-atmosphere-sun', sunPos); } }, updateSunGround: function (sunPos) { if (this.terrainLayerName != '') { // update the raster layer paint property with the position of the sun based on the selected time this.map.setPaintProperty(this.terrainLayerName, 'raster-opacity', Math.max(Math.min(1, sunPos.altitude * 4), 0.25)); } }, //[jscastro] this updates the directional light helper updateLightHelper: function () { if (this.lights.dirLightHelper) { this.lights.dirLightHelper.position.setFromMatrixPosition(this.lights.dirLight.matrixWorld); this.lights.dirLightHelper.updateMatrix(); this.lights.dirLightHelper.update(); } }, //[jscastro] method to fully dispose the resources, watch out is you call this without navigating to other page dispose: async function () { console.log(this.memory()); //console.log(window.performance.memory); return new Promise((resolve) => { resolve( this.clear(null, true).then((resolve) => { this.map.remove(); this.map = {}; this.scene.remove(this.world); this.world.children = []; this.world = null; this.objectsCache.clear(); this.labelRenderer.dispose(); console.log(this.memory()); this.renderer.dispose(); return resolve; }) ); //console.log(window.performance.memory); }); }, defaultLights: function () { this.lights.ambientLight = new THREE.AmbientLight(new THREE.Color('hsl(0, 0%, 100%)'), 0.75); this.scene.add(this.lights.ambientLight); this.lights.dirLightBack = new THREE.DirectionalLight(new THREE.Color('hsl(0, 0%, 100%)'), 0.25); this.lights.dirLightBack.position.set(30, 100, 100); this.scene.add(this.lights.dirLightBack); this.lights.dirLight = new THREE.DirectionalLight(new THREE.Color('hsl(0, 0%, 100%)'), 0.25); this.lights.dirLight.position.set(-30, 100, -100); this.scene.add(this.lights.dirLight); }, realSunlight: function (helper = false) { this.renderer.shadowMap.enabled = true; //this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; this.lights.dirLight = new THREE.DirectionalLight(0xffffff, 1); this.scene.add(this.lights.dirLight); if (helper) { this.lights.dirLightHelper = new THREE.DirectionalLightHelper(this.lights.dirLight, 5); this.scene.add(this.lights.dirLightHelper); } let d2 = 1000; let r2 = 2; let mapSize2 = 8192; this.lights.dirLight.castShadow = true; this.lights.dirLight.shadow.radius = r2; this.lights.dirLight.shadow.mapSize.width = mapSize2; this.lights.dirLight.shadow.mapSize.height = mapSize2; this.lights.dirLight.shadow.camera.top = this.lights.dirLight.shadow.camera.right = d2; this.lights.dirLight.shadow.camera.bottom = this.lights.dirLight.shadow.camera.left = -d2; this.lights.dirLight.shadow.camera.near = 1; this.lights.dirLight.shadow.camera.visible = true; this.lights.dirLight.shadow.camera.far = 400000000; this.lights.hemiLight = new THREE.HemisphereLight(new THREE.Color(0xffffff), new THREE.Color(0xffffff), 0.6); this.lights.hemiLight.color.setHSL(0.661, 0.96, 0.12); this.lights.hemiLight.groundColor.setHSL(0.11, 0.96, 0.14); this.lights.hemiLight.position.set(0, 0, 50); this.scene.add(this.lights.hemiLight); this.setSunlight(); this.map.once('idle', () => { this.setSunlight(); this.repaint(); }); }, setDefaultView: function (options, defOptions) { options.bbox = (options.bbox || options.bbox == null) && defOptions.enableSelectingObjects; options.tooltip = (options.tooltip || options.tooltip == null) && defOptions.enableTooltips; options.mapScale = this.map.transform.scale; }, memory: function () { return this.renderer.info.memory }, programs: function () { return this.renderer.info.programs.length }, version: '2.2.7', } var defaultOptions = { defaultLights: false, realSunlight: false, realSunlightHelper: false, passiveRendering: true, preserveDrawingBuffer: false, enableSelectingFeatures: false, enableSelectingObjects: false, enableDraggingObjects: false, enableRotatingObjects: false, enableTooltips: false, enableHelpTooltips: false, multiLayer: false, orthographic: false, fov: ThreeboxConstants.FOV_DEGREES, sky: false, terrain: false } module.exports = exports = Threebox; },{"./camera/CameraSync.js":6,"./objects/LabelRenderer.js":8,"./objects/Object3D.js":9,"./objects/effects/BuildingShadows.js":10,"./objects/extrusion.js":11,"./objects/label.js":13,"./objects/line.js":14,"./objects/loadObj.js":15,"./objects/objects.js":21,"./objects/sphere.js":22,"./objects/tooltip.js":23,"./objects/tube.js":24,"./three.js":25,"./utils/constants.js":26,"./utils/material.js":27,"./utils/suncalc.js":28,"./utils/utils.js":29}],5:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const THREE = require('../three.js'); const utils = require("../utils/utils.js"); function AnimationManager(map) { this.map = map this.enrolledObjects = []; this.previousFrameTime; }; AnimationManager.prototype = { unenroll: function (obj) { this.enrolledObjects.splice(this.enrolledObjects.indexOf(obj), 1); }, enroll: function (obj) { //[jscastro] add the object default animations obj.clock = new THREE.Clock(); obj.hasDefaultAnimation = false; obj.defaultAction; obj.actions = []; obj.mixer; //[jscastro] if the object includes animations if (obj.animations && obj.animations.length > 0) { obj.hasDefaultAnimation = true; //check first if a defaultAnimation is defined by options let daIndex = (obj.userData.defaultAnimation ? obj.userData.defaultAnimation : 0); obj.mixer = new THREE.AnimationMixer(obj); setAction(daIndex); } //[jscastro] set the action to play function setAction(animationIndex) { for (let i = 0; i < obj.animations.length; i++) { if (animationIndex > obj.animations.length) console.log("The animation index " + animationIndex + " doesn't exist for this object"); let animation = obj.animations[i]; let action = obj.mixer.clipAction(animation); obj.actions.push(action); //select the default animation and set the weight to 1 if (animationIndex === i) { obj.defaultAction = action; action.setEffectiveWeight(1); } else { action.setEffectiveWeight(0); } action.play(); } } let _isPlaying = false; //[jscastro] added property for isPlaying state Object.defineProperty(obj, 'isPlaying', { get() { return _isPlaying; }, set(value) { if (_isPlaying != value) { _isPlaying = value; // Dispatch new event IsPlayingChanged obj.dispatchEvent({ type: 'IsPlayingChanged', detail: obj}); } } }) /* Extend the provided object with animation-specific properties and track in the animation manager */ this.enrolledObjects.push(obj); // Give this object its own internal animation queue obj.animationQueue = []; obj.set = function (options) { //if duration is set, animate to the new state if (options.duration > 0) { let newParams = { start: Date.now(), expiration: Date.now() + options.duration, endState: {} } utils.extend(options, newParams); let translating = options.coords; let rotating = options.rotation; let scaling = options.scale || options.scaleX || options.scaleY || options.scaleZ; if (rotating) { let r = obj.rotation; options.startRotation = [r.x, r.y, r.z]; options.endState.rotation = utils.types.rotation(options.rotation, options.startRotation); options.rotationPerMs = options.endState.rotation .map(function (angle, index) { return (angle - options.startRotation[index]) / options.duration; }) } if (scaling) { let s = obj.scale; options.startScale = [s.x, s.y, s.z]; options.endState.scale = utils.types.scale(options.scale, options.startScale); options.scalePerMs = options.endState.scale .map(function (scale, index) { return (scale - options.startScale[index]) / options.duration; }) } if (translating) options.pathCurve = new THREE.CatmullRomCurve3(utils.lnglatsToWorld([obj.coordinates, options.coords])); let entry = { type: 'set', parameters: options } this.animationQueue .push(entry); tb.map.repaint = true; } //if no duration set, stop object's existing animations and go to that state immediately else { this.stop(); options.rotation = utils.radify(options.rotation); this._setObject(options); } return this }; //[jscastro] animation method, is set by update method obj.animationMethod = null; //[jscastro] stop animation and the queue obj.stop = function (index) { if (obj.mixer) { obj.isPlaying = false; cancelAnimationFrame(obj.animationMethod); } //TODO: if this is removed, it produces an error in this.animationQueue = []; return this; } obj.followPath = function (options, cb) { let entry = { type: 'followPath', parameters: utils._validate(options, defaults.followPath) }; utils.extend( entry.parameters, { pathCurve: new THREE.CatmullRomCurve3( utils.lnglatsToWorld(options.path) ), start: Date.now(), expiration: Date.now() + entry.parameters.duration, cb: cb } ); this.animationQueue .push(entry); tb.map.repaint = true; return this; }; obj._setObject = function (options) { //default scale always obj.setScale(); let p = options.position; // lnglat let r = options.rotation; // radians let s = options.scale; // custom scale let w = options.worldCoordinates; //Vector3 let q = options.quaternion; // [axis, angle in rads] let t = options.translate; // [jscastro] lnglat + height for 3D objects let wt = options.worldTranslate; // [jscastro] Vector3 translation if (p) { this.coordinates = p; let c = utils.projectToWorld(p); this.position.copy(c) } if (t) { this.coordinates = [this.coordinates[0] + t[0], this.coordinates[1] + t[1], this.coordinates[2] + t[2]]; let c = utils.projectToWorld(t); this.position.copy(c) //this.translateX(c.x); //this.translateY(c.y); //this.translateZ(c.z); options.position = this.coordinates; } if (wt) { this.translateX(wt.x); this.translateY(wt.y); this.translateZ(wt.z); let p = utils.unprojectFromWorld(this.position); this.coordinates = options.position = p; } if (r) { this.rotation.set(r[0], r[1], r[2]); options.rotation = new THREE.Vector3(r[0], r[1], r[2]); } if (s) { this.scale.set(s[0], s[1], s[2]); options.scale = this.scale; } if (q) { this.quaternion.setFromAxisAngle(q[0], q[1]); options.rotation = q[0].multiplyScalar(q[1]); } if (w) { this.position.copy(w); let p = utils.unprojectFromWorld(w); this.coordinates = options.position = p; } //Each time the object is positioned, project the floor and correct shadow plane this.setBoundingBoxShadowFloor(); this.setReceiveShadowFloor(); this.updateMatrixWorld(); tb.map.repaint = true; //const threeTarget = new THREE.EventDispatcher(); //threeTarget.dispatchEvent({ type: 'event', detail: { object: this, action: { position: options.position, rotation: options.rotation, scale: options.scale } } }); // fire the ObjectChanged event to notify UI object change let e = { type: 'ObjectChanged', detail: { object: this, action: { position: options.position, rotation: options.rotation, scale: options.scale } } }; this.dispatchEvent(e); }; //[jscastro] play default animation obj.playDefault = function (options) { if (obj.mixer && obj.hasDefaultAnimation) { let newParams = { start: Date.now(), expiration: Date.now() + options.duration, endState: {} } utils.extend(options, newParams); obj.mixer.timeScale = options.speed || 1; let entry = { type: 'playDefault', parameters: options }; this.animationQueue .push(entry); tb.map.repaint = true return this; } } //[jscastro] play an animation, requires options.animation as an index, if not it will play the default one obj.playAnimation = function (options) { if (obj.mixer) { if (options.animation) { setAction(options.animation) } obj.playDefault(options); } } //[jscastro] pause all actions animation obj.pauseAllActions = function () { if (obj.mixer) { obj.actions.forEach(function (action) { action.paused = true; }); } } //[jscastro] unpause all actions obj.unPauseAllActions = function () { if (obj.mixer) { obj.actions.forEach(function (action) { action.paused = false; }); } } //[jscastro] stop all actions obj.deactivateAllActions = function () { if (obj.mixer) { obj.actions.forEach(function (action) { action.stop(); }); } } //[jscastro] play all actions obj.activateAllActions = function () { if (obj.mixer) { obj.actions.forEach(function (action) { action.play(); }); } } //[jscastro] move the model action one tick just to avoid issues with initial position obj.idle = function () { if (obj.mixer) { // Update the animation mixer and render this frame obj.mixer.update(0.01); } tb.map.repaint = true; return this; } }, update: function (now) { if (this.previousFrameTime === undefined) this.previousFrameTime = now; let dimensions = ['X', 'Y', 'Z']; //[jscastro] when function expires this produces an error if (!this.enrolledObjects) return false; //iterate through objects in queue. count in reverse so we can cull objects without frame shifting for (let a = this.enrolledObjects.length - 1; a >= 0; a--) { let object = this.enrolledObjects[a]; if (!object.animationQueue || object.animationQueue.length === 0) continue; //[jscastro] now multiple animations on a single object is possible for (let i = object.animationQueue.length - 1; i >= 0; i--) { //focus on first item in queue let item = object.animationQueue[i]; if (!item) continue; let options = item.parameters; // if an animation is past its expiration date, cull it if (!options.expiration) { // console.log('culled') object.animationQueue.splice(i, 1); // set the start time of the next animation if (object.animationQueue[i]) object.animationQueue[i].parameters.start = now; return } //if finished, jump to end state and flag animation entry for removal next time around. Execute callback if there is one let expiring = now >= options.expiration; if (expiring) { options.expiration = false; if (item.type === 'playDefault') { object.stop(); } else { if (options.endState) object._setObject(options.endState); if (typeof (options.cb) != 'undefined') options.cb(); } } else { let timeProgress = (now - options.start) / options.duration; if (item.type === 'set') { let objectState = {}; if (options.pathCurve) objectState.worldCoordinates = options.pathCurve.getPoint(timeProgress); if (options.rotationPerMs) { objectState.rotation = options.startRotation.map(function (rad, index) { return rad + options.rotationPerMs[index] * timeProgress * options.duration }) } if (options.scalePerMs) { objectState.scale = options.startScale.map(function (scale, index) { return scale + options.scalePerMs[index] * timeProgress * options.duration }) } object._setObject(objectState); } if (item.type === 'followPath') { let position = options.pathCurve.getPointAt(timeProgress); let objectState = { worldCoordinates: position }; // if we need to track heading if (options.trackHeading) { let tangent = options.pathCurve .getTangentAt(timeProgress) .normalize(); let axis = new THREE.Vector3(0, 0, 0); let up = new THREE.Vector3(0, 1, 0); axis .crossVectors(up, tangent) .normalize(); let radians = Math.acos(up.dot(tangent)); objectState.quaternion = [axis, radians]; } object._setObject(objectState); } //[jscastro] play default animation if (item.type === 'playDefault') { object.activateAllActions(); object.isPlaying = true; object.animationMethod = requestAnimationFrame(this.update); object.mixer.update(object.clock.getDelta()); tb.map.repaint = true; } } } } this.previousFrameTime = now; } } const defaults = { followPath: { path: null, duration: 1000, trackHeading: true } } module.exports = exports = AnimationManager; },{"../three.js":25,"../utils/utils.js":29}],6:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const THREE = require("../three.js"); const utils = require("../utils/utils.js"); const ThreeboxConstants = require("../utils/constants.js"); function CameraSync(map, camera, world) { // console.log("CameraSync constructor"); this.map = map; this.camera = camera; this.active = true; this.camera.matrixAutoUpdate = false; // We're in charge of the camera now! // Postion and configure the world group so we can scale it appropriately when the camera zooms this.world = world || new THREE.Group(); this.world.position.x = this.world.position.y = ThreeboxConstants.WORLD_SIZE / 2 this.world.matrixAutoUpdate = false; // set up basic camera state this.state = { translateCenter: new THREE.Matrix4().makeTranslation(ThreeboxConstants.WORLD_SIZE / 2, -ThreeboxConstants.WORLD_SIZE / 2, 0), worldSizeRatio: ThreeboxConstants.TILE_SIZE / ThreeboxConstants.WORLD_SIZE, worldSize: ThreeboxConstants.TILE_SIZE * this.map.transform.scale }; // Listen for move events from the map and update the Three.js camera let _this = this; // keep the function on _this this.map .on('move', function () { _this.updateCamera(); }) .on('resize', function () { _this.setupCamera(); }) this.setupCamera(); } CameraSync.prototype = { setupCamera: function () { const t = this.map.transform; this.camera.aspect = t.width / t.height; //bug fixed, if aspect is not reset raycast will fail on map resize this.halfFov = t._fov / 2; this.cameraToCenterDistance = 0.5 / Math.tan(this.halfFov) * t.height; const maxPitch = t._maxPitch * Math.PI / 180; this.acuteAngle = Math.PI / 2 - maxPitch; this.updateCamera(); }, updateCamera: function (ev) { if (!this.camera) { console.log('nocamera') return; } const t = this.map.transform; this.camera.aspect = t.width / t.height; //bug fixed, if aspect is not reset raycast will fail on map resize const offset = t.centerOffset || new THREE.Vector3(); //{ x: t.width / 2, y: t.height / 2 }; let farZ = 0; let furthestDistance = 0; this.halfFov = t._fov / 2; const groundAngle = Math.PI / 2 + t._pitch; const pitchAngle = Math.cos((Math.PI / 2) - t._pitch); //pitch seems to influence heavily the depth calculation and cannot be more than 60 = PI/3 < v1 and 85 > v2 this.cameraToCenterDistance = 0.5 / Math.tan(this.halfFov) * t.height; let pixelsPerMeter = 1; const worldSize = this.worldSize(); if (this.map.tb.mapboxVersion >= 2.0) { // mapbox version >= 2.0 pixelsPerMeter = this.mercatorZfromAltitude(1, t.center.lat) * worldSize; const fovAboveCenter = t._fov * (0.5 + t.centerOffset.y / t.height); // Adjust distance to MSL by the minimum possible elevation visible on screen, // this way the far plane is pushed further in the case of negative elevation. const minElevationInPixels = t.elevation ? t.elevation.getMinElevationBelowMSL() * pixelsPerMeter : 0; const cameraToSeaLevelDistance = ((t._camera.position[2] * worldSize) - minElevationInPixels) / Math.cos(t._pitch); const topHalfSurfaceDistance = Math.sin(fovAboveCenter) * cameraToSeaLevelDistance / Math.sin(utils.clamp(Math.PI - groundAngle - fovAboveCenter, 0.01, Math.PI - 0.01)); // Calculate z distance of the farthest fragment that should be rendered. furthestDistance = pitchAngle * topHalfSurfaceDistance + cameraToSeaLevelDistance; // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance` const horizonDistance = cameraToSeaLevelDistance * (1 / t._horizonShift); farZ = Math.min(furthestDistance * 1.01, horizonDistance); } else { // mapbox version < 2.0 or azure maps // Furthest distance optimized by @jscastro76 const topHalfSurfaceDistance = Math.sin(this.halfFov) * this.cameraToCenterDistance / Math.sin(Math.PI - groundAngle - this.halfFov); // Calculate z distance of the farthest fragment that should be rendered. furthestDistance = pitchAngle * topHalfSurfaceDistance + this.cameraToCenterDistance; // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance` farZ = furthestDistance * 1.01; } this.cameraTranslateZ = new THREE.Matrix4().makeTranslation(0, 0, this.cameraToCenterDistance); // someday @ansis set further near plane to fix precision for deckgl,so we should fix it to use mapbox-gl v1.3+ correctly // https://github.com/mapbox/mapbox-gl-js/commit/5cf6e5f523611bea61dae155db19a7cb19eb825c#diff-5dddfe9d7b5b4413ee54284bc1f7966d const nz = (t.height / 50); //min near z as coded by @ansis const nearZ = Math.max(nz * pitchAngle, nz); //on changes in the pitch nz could be too low const h = t.height; const w = t.width; if (this.camera instanceof THREE.OrthographicCamera) { this.camera.projectionMatrix = utils.makeOrthographicMatrix(w / - 2, w / 2, h / 2, h / - 2, nearZ, farZ); } else { this.camera.projectionMatrix = utils.makePerspectiveMatrix(t._fov, w / h, nearZ, farZ); } this.camera.projectionMatrix.elements[8] = -offset.x * 2 / t.width; this.camera.projectionMatrix.elements[9] = offset.y * 2 / t.height; // Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix // If this is applied directly to the projection matrix, it will work OK but break raycasting let cameraWorldMatrix = this.calcCameraMatrix(t._pitch, t.angle); // When terrain layers are included, height of 3D layers must be modified from t_camera.z * worldSize if (t.elevation) cameraWorldMatrix.elements[14] = t._camera.position[2] * worldSize; //this.camera.matrixWorld.elements is equivalent to t._camera._transform this.camera.matrixWorld.copy(cameraWorldMatrix); let zoomPow = t.scale * this.state.worldSizeRatio; // Handle scaling and translation of objects in the map in the world's matrix transform, not the camera let scale = new THREE.Matrix4; let translateMap = new THREE.Matrix4; let rotateMap = new THREE.Matrix4; scale.makeScale(zoomPow, zoomPow, zoomPow); let x = t.x || t.point.x; let y = t.y || t.point.y; translateMap.makeTranslation(-x, y, 0); rotateMap.makeRotationZ(Math.PI); this.world.matrix = new THREE.Matrix4() .premultiply(rotateMap) .premultiply(this.state.translateCenter) .premultiply(scale) .premultiply(translateMap) // utils.prettyPrintMatrix(this.camera.projectionMatrix.elements); this.map.fire('CameraSynced', { detail: { nearZ: nearZ, farZ: farZ, pitch: t._pitch, angle: t.angle, furthestDistance: furthestDistance, cameraToCenterDistance: this.cameraToCenterDistance, t: this.map.transform, tbProjMatrix: this.camera.projectionMatrix.elements, tbWorldMatrix: this.world.matrix.elements, cameraSyn: CameraSync } }); }, worldSize() { let t = this.map.transform; return t.tileSize * t.scale; }, worldSizeFromZoom() { let t = this.map.transform; return Math.pow(2.0, t.zoom) * t.tileSize; }, mercatorZfromAltitude(altitude, lat) { return altitude / this.circumferenceAtLatitude(lat); }, mercatorZfromZoom() { return this.cameraToCenterDistance / this.worldSizeFromZoom(); }, circumferenceAtLatitude(latitude) { return ThreeboxConstants.EARTH_CIRCUMFERENCE * Math.cos(latitude * Math.PI / 180); }, calcCameraMatrix(pitch, angle, trz) { const t = this.map.transform; const _pitch = (pitch === undefined) ? t._pitch : pitch; const _angle = (angle === undefined) ? t.angle : angle; const _trz = (trz === undefined) ? this.cameraTranslateZ : trz; return new THREE.Matrix4() .premultiply(_trz) .premultiply(new THREE.Matrix4().makeRotationX(_pitch)) .premultiply(new THREE.Matrix4().makeRotationZ(_angle)); }, updateCameraState() { let t = this.map.transform; if (!t.height) return; // Set camera orientation and move it to a proper distance from the map //t._camera.setPitchBearing(t._pitch, t.angle); const dir = t._camera.forward(); const distance = t.cameraToCenterDistance; const center = t.point; // Use camera zoom (if terrain is enabled) to maintain constant altitude to sea level const zoom = t._cameraZoom ? t._cameraZoom : t._zoom; const altitude = this.mercatorZfromZoom(t); const height = altitude - this.mercatorZfromAltitude(t._centerAltitude, t.center.lat); // simplified version of: this._worldSizeFromZoom(this._zoomFromMercatorZ(height)) const updatedWorldSize = t.cameraToCenterDistance / height; return [ center.x / this.worldSize() - (dir[0] * distance) / updatedWorldSize, center.y / this.worldSize() - (dir[1] * distance) / updatedWorldSize, this.mercatorZfromAltitude(t._centerAltitude, t._center.lat) + (-dir[2] * distance) / updatedWorldSize ]; }, getWorldToCamera(worldSize, pixelsPerMeter) { // transformation chain from world space to camera space: // 1. Height value (z) of renderables is in meters. Scale z coordinate by pixelsPerMeter // 2. Transform from pixel coordinates to camera space with cameraMatrix^-1 // 3. flip Y if required // worldToCamera: flip * cam^-1 * zScale // cameraToWorld: (flip * cam^-1 * zScale)^-1 => (zScale^-1 * cam * flip^-1) let t = this.map.transform; const matrix = new THREE.Matrix4(); const matrixT = new THREE.Matrix4(); // Compute inverse of camera matrix and post-multiply negated translation const o = t._camera._orientation; const p = t._camera.position; const invPosition = new THREE.Vector3(p[0], p[1], p[2]); const quat = new THREE.Quaternion(); quat.set(o[0], o[1], o[2], o[3]); const invOrientation = quat.conjugate(); invPosition.multiplyScalar(-worldSize); matrixT.makeTranslation(invPosition.x, invPosition.y, invPosition.z); matrix .makeRotationFromQuaternion(invOrientation) .premultiply(matrixT); //this would make the matrix exact to getWorldToCamera but breaks //this.translate(matrix.elements, matrix.elements, invPosition); // Pre-multiply y (2nd row) matrix.elements[1] *= -1.0; matrix.elements[5] *= -1.0; matrix.elements[9] *= -1.0; matrix.elements[13] *= -1.0; // Post-multiply z (3rd column) matrix.elements[8] *= pixelsPerMeter; matrix.elements[9] *= pixelsPerMeter; matrix.elements[10] *= pixelsPerMeter; matrix.elements[11] *= pixelsPerMeter; //console.log(matrix.elements); return matrix; }, translate(out, a, v) { let x = v[0] || v.x, y = v[1] || v.y, z = v[2] || v.z; let a00, a01, a02, a03; let a10, a11, a12, a13; let a20, a21, a22, a23; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; } else { a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; } return out; } } module.exports = exports = CameraSync; },{"../three.js":25,"../utils/constants.js":26,"../utils/utils.js":29}],7:[function(require,module,exports){ /** * @author mrdoob / http://mrdoob.com/ */ const THREE = require('../three.js'); (function () { class CSS2DObject extends THREE.Object3D { constructor(element) { super(); this.element = element || document.createElement('div'); this.element.style.position = 'absolute'; this.element.style.userSelect = 'none'; this.element.setAttribute('draggable', false); //[jscastro] some labels must be always visible this.alwaysVisible = false; //[jscastro] layer is needed to be rendered/hidden based on layer visibility Object.defineProperty(this, 'layer', { get() { return (this.parent && this.parent.parent ? this.parent.parent.layer : null) } }); //[jscastro] implement dispose this.dispose = function () { this.remove(); this.element = null; } //[jscastro] implement explicit method this.remove = function () { if (this.element instanceof Element && this.element.parentNode !== null) { this.element.parentNode.removeChild(this.element); } } this.addEventListener('removed', function () { this.remove(); }); } copy(source, recursive) { super.copy(source, recursive); this.element = source.element.cloneNode(true); return this; } } CSS2DObject.prototype.isCSS2DObject = true; // const _vector = new THREE.Vector3(); const _viewMatrix = new THREE.Matrix4(); const _viewProjectionMatrix = new THREE.Matrix4(); const _a = new THREE.Vector3(); const _b = new THREE.Vector3(); class CSS2DRenderer { constructor() { const _this = this; let _width, _height; let _widthHalf, _heightHalf; const cache = { objects: new WeakMap(), list: new Map() }; this.cacheList = cache.list; const domElement = document.createElement('div'); domElement.style.overflow = 'hidden'; this.domElement = domElement; this.getSize = function () { return { width: _width, height: _height }; }; this.render = function (scene, camera) { if (scene.autoUpdate === true) scene.updateMatrixWorld(); if (camera.parent === null) camera.updateMatrixWorld(); _viewMatrix.copy(camera.matrixWorldInverse); _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix); renderObject(scene, scene, camera); zOrder(scene); }; this.setSize = function (width, height) { _width = width; _height = height; _widthHalf = _width / 2; _heightHalf = _height / 2; domElement.style.width = width + 'px'; domElement.style.height = height + 'px'; }; function renderObject(object, scene, camera) { if (object.isCSS2DObject) { //[jscastro] optimize performance and don't update and remove the labels that are not visible if (!object.visible) { cache.objects.delete({ key: object.uuid }); cache.list.delete(object.uuid); object.remove(); } else { object.onBeforeRender(_this, scene, camera); _vector.setFromMatrixPosition(object.matrixWorld); _vector.applyMatrix4(_viewProjectionMatrix); const element = object.element; var style; if (/apple/i.test(navigator.vendor)) { // https://github.com/mrdoob/three.js/issues/21415 style = 'translate(-50%,-50%) translate(' + Math.round(_vector.x * _widthHalf + _widthHalf) + 'px,' + Math.round(- _vector.y * _heightHalf + _heightHalf) + 'px)'; } else { style = 'translate(-50%,-50%) translate(' + (_vector.x * _widthHalf + _widthHalf) + 'px,' + (- _vector.y * _heightHalf + _heightHalf) + 'px)'; } element.style.WebkitTransform = style; element.style.MozTransform = style; element.style.oTransform = style; element.style.transform = style; element.style.display = object.visible && _vector.z >= - 1 && _vector.z <= 1 ? '' : 'none'; const objectData = { distanceToCameraSquared: getDistanceToSquared(camera, object) }; cache.objects.set({ key: object.uuid }, objectData); cache.list.set(object.uuid, object); if (element.parentNode !== domElement) { domElement.appendChild(element); } object.onAfterRender(_this, scene, camera); } } for (let i = 0, l = object.children.length; i < l; i++) { renderObject(object.children[i], scene, camera); } } function getDistanceToSquared(object1, object2) { _a.setFromMatrixPosition(object1.matrixWorld); _b.setFromMatrixPosition(object2.matrixWorld); return _a.distanceToSquared(_b); } function filterAndFlatten(scene) { const result = []; scene.traverse(function (object) { if (object.isCSS2DObject) result.push(object); }); return result; } function zOrder(scene) { const sorted = filterAndFlatten(scene).sort(function (a, b) { //[jscastro] check the objects already exist in the cache let cacheA = cache.objects.get({ key: a.uuid }); let cacheB = cache.objects.get({ key: b.uuid }); if (cacheA && cacheB) { const distanceA = cacheA.distanceToCameraSquared; const distanceB = cacheB.distanceToCameraSquared; return distanceA - distanceB; } }); const zMax = sorted.length; for (let i = 0, l = sorted.length; i < l; i++) { sorted[i].element.style.zIndex = zMax - i; } } } } THREE.CSS2DObject = CSS2DObject; THREE.CSS2DRenderer = CSS2DRenderer; })(); module.exports = exports = { CSS2DRenderer: THREE.CSS2DRenderer, CSS2DObject: THREE.CSS2DObject }; },{"../three.js":25}],8:[function(require,module,exports){ /** * @author jscastro / https://github.com/jscastro76 */ const THREE = require("./CSS2DRenderer.js"); function LabelRenderer(map) { this.map = map; this.renderer = new THREE.CSS2DRenderer(); this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight); this.renderer.domElement.style.position = 'absolute'; this.renderer.domElement.id = 'labelCanvas'; //TODO: this value must come by parameter this.renderer.domElement.style.top = 0; this.renderer.domElement.style.zIndex = "0"; this.map.getCanvasContainer().appendChild(this.renderer.domElement); this.scene, this.camera; this.dispose = function () { this.map.getCanvasContainer().removeChild(this.renderer.domElement) this.renderer.domElement.remove(); this.renderer = {}; } this.setSize = function (width, height) { this.renderer.setSize(width, height); } this.map.on('resize', function () { this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight); }.bind(this)); this.state = { reset: function () { //TODO: Implement a good state reset, check out what is made in WebGlRenderer } } this.render = async function (scene, camera) { this.scene = scene; this.camera = camera; return new Promise((resolve) => { resolve(this.renderer.render(scene, camera)) }); } //[jscastro] method to toggle Layer visibility this.toggleLabels = async function (layerId, visible) { return new Promise((resolve) => { resolve(this.setVisibility(layerId, visible, this.scene, this.camera, this.renderer)); }) }; //[jscastro] method to set visibility this.setVisibility = function (layerId, visible, scene, camera, renderer) { var cache = this.renderer.cacheList; cache.forEach(function (l) { if (l.visible != visible && l.layer === layerId) { if ((visible && l.alwaysVisible) || !visible) { l.visible = visible; renderer.renderObject(l, scene, camera); } } }); }; } module.exports = exports = LabelRenderer; },{"./CSS2DRenderer.js":7}],9:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const Objects = require('./objects.js'); const utils = require("../utils/utils.js"); function Object3D(opt) { opt = utils._validate(opt, Objects.prototype._defaults.Object3D); // [jscastro] full refactor of Object3D to behave exactly like 3D Models loadObj let obj = opt.obj; // [jscastro] options.rotation was wrongly used const r = utils.types.rotation(opt.rotation, [0, 0, 0]); const s = utils.types.scale(opt.scale, [1, 1, 1]); obj.rotation.set(r[0], r[1], r[2]); obj.scale.set(s[0], s[1], s[2]); obj.name = "model"; let userScaleGroup = Objects.prototype._makeGroup(obj, opt); opt.obj.name = "model"; Objects.prototype._addMethods(userScaleGroup); //[jscastro] calculate automatically the pivotal center of the object userScaleGroup.setAnchor(opt.anchor); //[jscastro] override the center calculated if the object has adjustments userScaleGroup.setCenter(opt.adjustment); //[jscastro] if the object is excluded from raycasting userScaleGroup.raycasted = opt.raycasted; userScaleGroup.visibility = true; return userScaleGroup } module.exports = exports = Object3D; },{"../utils/utils.js":29,"./objects.js":21}],10:[function(require,module,exports){ const SunCalc = require('../../utils/suncalc.js'); class BuildingShadows { constructor(options, threebox) { this.id = options.layerId; this.type = 'custom'; this.renderingMode = '3d'; this.opacity = 0.5; this.buildingsLayerId = options.buildingsLayerId; this.minAltitude = options.minAltitude || 0.10; this.tb = threebox; } onAdd(map, gl) { this.map = map; // find layer source const sourceName = this.map.getLayer(this.buildingsLayerId).source; this.source = (this.map.style.sourceCaches || this.map.style._otherSourceCaches)[sourceName]; if (!this.source) { console.warn(`Can't find layer ${this.buildingsLayerId}'s source.`); } // vertex shader of fill-extrusion layer is different in mapbox v1 and v2. // https://github.com/mapbox/mapbox-gl-js/commit/cef95aa0241e748b396236f1269fbb8270f31565 const vertexSource = this._getVertexSource(); const fragmentSource = ` void main() { gl_FragColor = vec4(0.0, 0.0, 0.0, 0.7); } `; const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); this.program = gl.createProgram(); gl.attachShader(this.program, vertexShader); gl.attachShader(this.program, fragmentShader); gl.linkProgram(this.program); gl.validateProgram(this.program); this.uMatrix = gl.getUniformLocation(this.program, "u_matrix"); this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor"); this.uAltitude = gl.getUniformLocation(this.program, "u_altitude"); this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth"); if (this.tb.mapboxVersion >= 2.0) { this.aPosNormal = gl.getAttribLocation(this.program, "a_pos_normal_ed"); } else { this.aPos = gl.getAttribLocation(this.program, "a_pos"); this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed"); } this.aBase = gl.getAttribLocation(this.program, "a_base"); this.aHeight = gl.getAttribLocation(this.program, "a_height"); } render(gl, matrix) { if (!this.source) return; gl.useProgram(this.program); const coords = this.source.getVisibleCoordinates().reverse(); const buildingsLayer = this.map.getLayer(this.buildingsLayerId); const context = this.map.painter.context; const { lng, lat } = this.map.getCenter(); const pos = this.tb.getSunPosition(this.tb.lightDateTime, [lng, lat]); gl.uniform1f(this.uAltitude, (pos.altitude > this.minAltitude ? pos.altitude : 0)); gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2); //this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.6; gl.enable(gl.BLEND); //gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.DST_ALPHA, gl.SRC_ALPHA); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) var ext = gl.getExtension('EXT_blend_minmax'); //gl.blendEquationSeparate(gl.FUNC_SUBTRACT, ext.MIN_EXT); //gl.blendEquation(gl.FUNC_ADD); gl.disable(gl.DEPTH_TEST); for (const coord of coords) { const tile = this.source.getTile(coord); const bucket = tile.getBucket(buildingsLayer); if (!bucket) continue; const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations[this.buildingsLayerId]._buffers; gl.uniformMatrix4fv(this.uMatrix, false, (coord.posMatrix || coord.projMatrix)); gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8); for (const segment of bucket.segments.get()) { const numPrevAttrib = context.currentNumAttributes || 0; const numNextAttrib = 2; for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i); const vertexOffset = segment.vertexOffset || 0; gl.enableVertexAttribArray(this.aNormal); gl.enableVertexAttribArray(this.aHeight); gl.enableVertexAttribArray(this.aBase); bucket.layoutVertexBuffer.bind(); if (this.tb.mapboxVersion >= 2.0) { gl.enableVertexAttribArray(this.aPosNormal); gl.vertexAttribPointer(this.aPosNormal, 4, gl.SHORT, false, 8, 8 * vertexOffset); } else { gl.enableVertexAttribArray(this.aPos); gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset); gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset); } heightBuffer.bind(); gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset); baseBuffer.bind(); gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset); bucket.indexBuffer.bind(); context.currentNumAttributes = numNextAttrib; gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } } _getVertexSource() { if (this.tb.mapboxVersion >= 2.0) { return ` uniform mat4 u_matrix; uniform float u_height_factor; uniform float u_altitude; uniform float u_azimuth; attribute vec4 a_pos_normal_ed; attribute lowp vec2 a_base; attribute lowp vec2 a_height; void main() { float base = max(0.0, a_base.x); float height = max(0.0, a_height.x); vec3 pos_nx = floor(a_pos_normal_ed.xyz * 0.5); mediump vec3 top_up_ny = a_pos_normal_ed.xyz - 2.0 * pos_nx; float t = top_up_ny.x; vec4 pos = vec4(pos_nx.xy, t > 0.0 ? height : base, 1); float len = pos.z * u_height_factor / tan(u_altitude); pos.x += cos(u_azimuth) * len; pos.y += sin(u_azimuth) * len; pos.z = 0.0; gl_Position = u_matrix * pos; } `; } else { return ` uniform mat4 u_matrix; uniform float u_height_factor; uniform float u_altitude; uniform float u_azimuth; attribute vec2 a_pos; attribute vec4 a_normal_ed; attribute lowp vec2 a_base; attribute lowp vec2 a_height; void main() { float base = max(0.0, a_base.x); float height = max(0.0, a_height.x); float t = mod(a_normal_ed.x, 2.0); vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1); float len = pos.z * u_height_factor / tan(u_altitude); pos.x += cos(u_azimuth) * len; pos.y += sin(u_azimuth) * len; pos.z = 0.0; gl_Position = u_matrix * pos; } `; } } } module.exports = exports = BuildingShadows; },{"../../utils/suncalc.js":28}],11:[function(require,module,exports){ /** * @author jscastro / https://github.com/jscastro76 */ const Objects = require('./objects.js'); const utils = require("../utils/utils.js"); const THREE = require("../three.js"); const Object3D = require('./Object3D.js'); /** * * @param {any} opt must fit the default defined in Objects.prototype._defaults.extrusion * @param {arr} opt.coordinates could receive a feature.geometry.coordinates */ function extrusion(opt) { opt = utils._validate(opt, Objects.prototype._defaults.extrusion); let shape = extrusion.prototype.buildShape(opt.coordinates); let geometry = extrusion.prototype.buildGeometry(shape, opt.geometryOptions); let mesh = new THREE.Mesh(geometry, opt.materials); opt.obj = mesh; //[jscastro] we convert it in Object3D to add methods, bounding box, model, tooltip... return new Object3D(opt); } extrusion.prototype = { buildShape: function (coords) { if (coords[0] instanceof (THREE.Vector2 || THREE.Vector3)) return new THREE.Shape(coords); let shape = new THREE.Shape(); for (let i = 0; i < coords.length; i++) { if (i === 0) { shape = new THREE.Shape(this.buildPoints(coords[0], coords[0])); } else { shape.holes.push(new THREE.Path(this.buildPoints(coords[i], coords[0]))); } } return shape; }, buildPoints: function (coords, initCoords) { const points = []; let init = utils.projectToWorld([initCoords[0][0], initCoords[0][1], 0]); for (let i = 0; i < coords.length; i++) { let pos = utils.projectToWorld([coords[i][0], coords[i][1], 0]); points.push(new THREE.Vector2(utils.toDecimal((pos.x - init.x), 9), utils.toDecimal((pos.y - init.y), 9))); } return points; }, buildGeometry: function (shape, settings) { let geometry = new THREE.ExtrudeBufferGeometry(shape, settings); geometry.computeBoundingBox(); return geometry; } } module.exports = exports = extrusion; },{"../three.js":25,"../utils/utils.js":29,"./Object3D.js":9,"./objects.js":21}],12:[function(require,module,exports){ (function (setImmediate){(function (){ /*! fflate - fast JavaScript compression/decompression Licensed under MIT. https://github.com/101arrowz/fflate/blob/master/LICENSE */ !function(f){typeof module!='undefined'&&typeof exports=='object'?module.exports=f():typeof define!='undefined'&&define.amd?define(['fflate',f]):(typeof self!='undefined'?self:this).fflate=f()}(function(){var _e={};"use strict";_e.__esModule=!0;var t=(typeof module!='undefined'&&typeof exports=='object'?function(_f){"use strict";var e;var r=";var __w=require('worker_threads');__w.parentPort.on('message',function(m){onmessage({data:m})}),postMessage=function(m,t){__w.parentPort.postMessage(m,t)},close=process.exit;self=global";try{e("require('worker_threads')").Worker}catch(e){}exports.default=e?function(t,n,o,s,a){var u=!1,i=new e(t+r,{eval:!0}).on("error",(function(e){return a(e,null)})).on("message",(function(e){return a(null,e)})).on("exit",(function(e){e&&!u&&a(Error("exited with code "+e),null)}));return i.postMessage(o,s),i.terminate=function(){return u=!0,e.prototype.terminate.call(i)},i}:function(e,r,t,n,o){setImmediate((function(){return o(Error("async operations unsupported - update to Node 12+ (or Node 10-11 with the --experimental-worker CLI flag)"),null)}));var s=function(){};return{terminate:s,postMessage:s}};return _f}:function(_f){"use strict";var e=eval;_f.default=function(r,t,n,o,s){var u=e[t]||(e[t]=URL.createObjectURL(new Blob([r],{type:"text/javascript"}))),a=new Worker(u);return a.onerror=function(e){return s(e.error,null)},a.onmessage=function(e){return s(null,e.data)},a.postMessage(n,o),a};return _f})({}),n=Uint8Array,r=Uint16Array,e=Uint32Array,i=new n([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),o=new n([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),a=new n([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),s=function(t,n){for(var i=new r(31),o=0;o<31;++o)i[o]=n+=1<>>1|(21845&d)<<1;v[d]=((65280&(g=(61680&(g=(52428&g)>>>2|(13107&g)<<2))>>>4|(3855&g)<<4))>>>8|(255&g)<<8)>>>1}var w=function(t,n,e){for(var i=t.length,o=0,a=new r(n);o>>u]=h}else for(s=new r(i),o=0;o>>15-t[o]);return s},y=new n(288);for(d=0;d<144;++d)y[d]=8;for(d=144;d<256;++d)y[d]=9;for(d=256;d<280;++d)y[d]=7;for(d=280;d<288;++d)y[d]=8;var m=new n(32);for(d=0;d<32;++d)m[d]=5;var b=w(y,9,0),x=w(y,9,1),z=w(m,5,0),k=w(m,5,1),M=function(t){for(var n=t[0],r=1;rn&&(n=t[r]);return n},A=function(t,n,r){var e=n/8|0;return(t[e]|t[e+1]<<8)>>(7&n)&r},S=function(t,n){var r=n/8|0;return(t[r]|t[r+1]<<8|t[r+2]<<16)>>(7&n)},D=function(t){return(t/8|0)+(7&t&&1)},C=function(t,i,o){(null==i||i<0)&&(i=0),(null==o||o>t.length)&&(o=t.length);var a=new(t instanceof r?r:t instanceof e?e:n)(o-i);return a.set(t.subarray(i,o)),a},U=function(t,r,e){var s=t.length;if(!s||e&&!e.l&&s<5)return r||new n(0);var f=!r||e,h=!e||e.i;e||(e={}),r||(r=new n(3*s));var c=function(t){var e=r.length;if(t>e){var i=new n(Math.max(2*e,t));i.set(r),r=i}},p=e.f||0,v=e.p||0,d=e.b||0,g=e.l,y=e.d,m=e.m,b=e.n,z=8*s;do{if(!g){e.f=p=A(t,v,1);var U=A(t,v+1,3);if(v+=3,!U){var O=t[(H=D(v)+4)-4]|t[H-3]<<8,T=H+O;if(T>s){if(h)throw"unexpected EOF";break}f&&c(d+O),r.set(t.subarray(H,T),d),e.b=d+=O,e.p=v=8*T;continue}if(1==U)g=x,y=k,m=9,b=5;else{if(2!=U)throw"invalid block type";var Z=A(t,v,31)+257,I=A(t,v+10,15)+4,F=Z+A(t,v+5,31)+1;v+=14;for(var E=new n(F),G=new n(19),P=0;Pz)break;var q=w(G,j,1);for(P=0;P>>4)<16)E[P++]=H;else{var B=0,J=0;for(16==H?(J=3+A(t,v,3),v+=2,B=E[P-1]):17==H?(J=3+A(t,v,7),v+=3):18==H&&(J=11+A(t,v,127),v+=7);J--;)E[P++]=B}}var K=E.subarray(0,Z),L=E.subarray(Z);m=M(K),b=M(L),g=w(K,m,1),y=w(L,b,1)}if(v>z)throw"unexpected EOF"}f&&c(d+131072);for(var N=(1<>>4;if((v+=15&B)>z)throw"unexpected EOF";if(!B)throw"invalid length/literal";if(V<256)r[d++]=V;else{if(256==V){g=null;break}var W=V-254;V>264&&(W=A(t,v,(1<<(tt=i[P=V-257]))-1)+u[P],v+=tt);var X=y[S(t,v)&Q],$=X>>>4;if(!X)throw"invalid distance";if(v+=15&X,L=l[$],$>3){var tt=o[$];L+=S(t,v)&(1<z)throw"unexpected EOF";f&&c(d+131072);for(var nt=d+W;d>>8},T=function(t,n,r){var e=n/8|0;t[e]|=r<<=7&n,t[e+1]|=r>>>8,t[e+2]|=r>>>16},Z=function(t,e){for(var i=[],o=0;ov&&(v=s[o].s);var d=new r(v+1),g=I(i[l-1],d,0);if(g>e){o=0;var w=0,y=g-e,m=1<e))break;w+=m-(1<>>=y;w>0;){var x=s[o].s;d[x]=0&&w;--o){var z=s[o].s;d[z]==e&&(--d[z],++w)}g=e}return[new n(d),g]},I=function(t,n,r){return-1==t.s?Math.max(I(t.l,n,r+1),I(t.r,n,r+1)):n[t.s]=r},F=function(t){for(var n=t.length;n&&!t[--n];);for(var e=new r(++n),i=0,o=t[0],a=1,s=function(t){e[i++]=t},f=1;f<=n;++f)if(t[f]==o&&f!=n)++a;else{if(!o&&a>2){for(;a>138;a-=138)s(32754);a>2&&(s(a>10?a-11<<5|28690:a-3<<5|12305),a=0)}else if(a>3){for(s(o),--a;a>6;a-=6)s(8304);a>2&&(s(a-3<<5|8208),a=0)}for(;a--;)s(o);a=1,o=t[f]}return[e.subarray(0,i),n]},E=function(t,n){for(var r=0,e=0;e>>8,t[i+2]=255^t[i],t[i+3]=255^t[i+1];for(var o=0;o4&&!H[a[B-1]];--B);var J,K,L,N,Q=p+5<<3,R=E(f,y)+E(u,m)+h,V=E(f,g)+E(u,M)+h+14+3*B+E(j,H)+(2*j[16]+3*j[17]+7*j[18]);if(Q<=R&&Q<=V)return G(n,v,t.subarray(l,l+p));if(O(n,v,1+(V15&&(O(n,v,tt[_]>>>5&127),v+=tt[_]>>>12)}}else J=b,K=y,L=z,N=m;for(_=0;_255){var nt;T(n,v,J[257+(nt=s[_]>>>18&31)]),v+=K[nt+257],nt>7&&(O(n,v,s[_]>>>23&31),v+=i[nt]);var rt=31&s[_];T(n,v,L[rt]),v+=N[rt],rt>3&&(T(n,v,s[_]>>>5&8191),v+=o[rt])}else T(n,v,J[s[_]]),v+=K[s[_]];return T(n,v,J[256]),v+K[256]},j=new e([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),_=new n(0),q=function(t,a,s,f,u,c){var l=t.length,v=new n(f+l+5*(1+Math.ceil(l/7e3))+u),d=v.subarray(f,v.length-u),g=0;if(!a||l<8)for(var w=0;w<=l;w+=65535){var y=w+65535;y>>13,x=8191&m,z=(1<7e3||E>24576)&&K>423){g=P(t,d,0,O,T,Z,F,E,H,w-H,g),E=I=F=0,H=w;for(var L=0;L<286;++L)T[L]=0;for(L=0;L<30;++L)Z[L]=0}var N=2,Q=0,R=x,V=B-J&32767;if(K>2&&Y==U(w-V))for(var W=Math.min(b,K)-1,X=Math.min(32767,w),$=Math.min(258,K);V<=X&&--R&&B!=J;){if(t[w+N]==t[w+N-V]){for(var tt=0;tt<$&&t[w+tt]==t[w+tt-V];++tt);if(tt>N){if(N=tt,Q=V,tt>W)break;var nt=Math.min(V,tt-2),rt=0;for(L=0;Lrt&&(rt=it,J=et)}}}V+=(B=J)-(J=k[B])+32768&32767}if(Q){O[E++]=268435456|h[N]<<18|p[Q];var ot=31&h[N],at=31&p[Q];F+=i[ot]+o[at],++T[257+ot],++Z[at],q=w+N,++I}else O[E++]=t[w],++T[t[w]]}}g=P(t,d,c,O,T,Z,F,E,H,w-H,g),!c&&7&g&&(g=G(d,g+1,_))}return C(v,0,f+D(g)+u)},H=function(){for(var t=new e(256),n=0;n<256;++n){for(var r=n,i=9;--i;)r=(1&r&&3988292384)^r>>>1;t[n]=r}return t}(),Y=function(){var t=-1;return{p:function(n){for(var r=t,e=0;e>>8;t=r},d:function(){return~t}}},B=function(){var t=1,n=0;return{p:function(r){for(var e=t,i=n,o=r.length,a=0;a!=o;){for(var s=Math.min(a+2655,o);a>16),i=(65535&i)+15*(i>>16)}t=e,n=i},d:function(){return((t%=65521)>>>8<<16|(255&(n%=65521))<<8|n>>>8)+2*((255&t)<<23)}}},J=function(t,n,r,e,i){return q(t,null==n.level?6:n.level,null==n.mem?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(t.length)))):12+n.mem,r,e,!i)},K=function(t,n){var r={};for(var e in t)r[e]=t[e];for(var e in n)r[e]=n[e];return r},L=function(t,n,r){for(var e=t(),i=""+t,o=i.slice(i.indexOf("[")+1,i.lastIndexOf("]")).replace(/ /g,"").split(","),a=0;a>>=8},ct=function(t,n){var r=n.filename;if(t[0]=31,t[1]=139,t[2]=8,t[8]=n.level<2?4:9==n.level?2:0,t[9]=3,0!=n.mtime&&ht(t,4,Math.floor(new Date(n.mtime||Date.now())/1e3)),r){t[3]=8;for(var e=0;e<=r.length;++e)t[e+10]=r.charCodeAt(e)}},lt=function(t){if(31!=t[0]||139!=t[1]||8!=t[2])throw"invalid gzip data";var n=t[3],r=10;4&n&&(r+=t[10]|2+(t[11]<<8));for(var e=(n>>3&1)+(n>>4&1);e>0;e-=!t[r++]);return r+(2&n)},pt=function(t){var n=t.length;return(t[n-4]|t[n-3]<<8|t[n-2]<<16)+2*(t[n-1]<<23)},vt=function(t){return 10+(t.filename&&t.filename.length+1||0)},dt=function(t,n){var r=n.level,e=0==r?0:r<6?1:9==r?3:2;t[0]=120,t[1]=e<<6|(e?32-2*e:1)},gt=function(t){if(8!=(15&t[0])||t[0]>>>4>7||(t[0]<<8|t[1])%31)throw"invalid zlib data";if(32&t[1])throw"invalid zlib data: preset dictionaries not supported"};function wt(t,n){return n||"function"!=typeof t||(n=t,t={}),this.ondata=n,t}var yt=function(){function t(t,n){n||"function"!=typeof t||(n=t,t={}),this.ondata=n,this.o=t||{}}return t.prototype.p=function(t,n){this.ondata(J(t,this.o,0,0,!n),n)},t.prototype.push=function(t,n){if(this.d)throw"stream finished";if(!this.ondata)throw"no stream handler";this.d=n,this.p(t,n||!1)},t}();_e.Deflate=yt;var mt=function(){return function(t,n){at([W,function(){return[ot,yt]}],this,wt.call(this,t,n),(function(t){var n=new yt(t.data);onmessage=ot(n)}),6)}}();function bt(t,n,r){if(r||(r=n,n={}),"function"!=typeof r)throw"no callback";return it(t,n,[W],(function(t){return rt(xt(t.data[0],t.data[1]))}),0,r)}function xt(t,n){return J(t,n||{},0,0)}_e.AsyncDeflate=mt,_e.deflate=bt,_e.deflateSync=xt;var zt=function(){function t(t){this.s={},this.p=new n(0),this.ondata=t}return t.prototype.e=function(t){if(this.d)throw"stream finished";if(!this.ondata)throw"no stream handler";var r=this.p.length,e=new n(r+t.length);e.set(this.p),e.set(t,r),this.p=e},t.prototype.c=function(t){this.d=this.s.i=t||!1;var n=this.s.b,r=U(this.p,this.o,this.s);this.ondata(C(r,n,this.s.b),this.d),this.o=C(r,this.s.b-32768),this.s.b=this.o.length,this.p=C(this.p,this.s.p/8|0),this.s.p&=7},t.prototype.push=function(t,n){this.e(t),this.c(n)},t}();_e.Inflate=zt;var kt=function(){return function(t){this.ondata=t,at([V,function(){return[ot,zt]}],this,0,(function(){var t=new zt;onmessage=ot(t)}),7)}}();function Mt(t,n,r){if(r||(r=n,n={}),"function"!=typeof r)throw"no callback";return it(t,n,[V],(function(t){return rt(At(t.data[0],et(t.data[1])))}),1,r)}function At(t,n){return U(t,n)}_e.AsyncInflate=kt,_e.inflate=Mt,_e.inflateSync=At;var St=function(){function t(t,n){this.c=Y(),this.l=0,this.v=1,yt.call(this,t,n)}return t.prototype.push=function(t,n){yt.prototype.push.call(this,t,n)},t.prototype.p=function(t,n){this.c.p(t),this.l+=t.length;var r=J(t,this.o,this.v&&vt(this.o),n&&8,!n);this.v&&(ct(r,this.o),this.v=0),n&&(ht(r,r.length-8,this.c.d()),ht(r,r.length-4,this.l)),this.ondata(r,n)},t}();_e.Gzip=St,_e.Compress=St;var Dt=function(){return function(t,n){at([W,X,function(){return[ot,yt,St]}],this,wt.call(this,t,n),(function(t){var n=new St(t.data);onmessage=ot(n)}),8)}}();function Ct(t,n,r){if(r||(r=n,n={}),"function"!=typeof r)throw"no callback";return it(t,n,[W,X,function(){return[Ut]}],(function(t){return rt(Ut(t.data[0],t.data[1]))}),2,r)}function Ut(t,n){n||(n={});var r=Y(),e=t.length;r.p(t);var i=J(t,n,vt(n),8),o=i.length;return ct(i,n),ht(i,o-8,r.d()),ht(i,o-4,e),i}_e.AsyncGzip=Dt,_e.AsyncCompress=Dt,_e.gzip=Ct,_e.compress=Ct,_e.gzipSync=Ut,_e.compressSync=Ut;var Ot=function(){function t(t){this.v=1,zt.call(this,t)}return t.prototype.push=function(t,n){if(zt.prototype.e.call(this,t),this.v){var r=this.p.length>3?lt(this.p):4;if(r>=this.p.length&&!n)return;this.p=this.p.subarray(r),this.v=0}if(n){if(this.p.length<8)throw"invalid gzip stream";this.p=this.p.subarray(0,-8)}zt.prototype.c.call(this,n)},t}();_e.Gunzip=Ot;var Tt=function(){return function(t){this.ondata=t,at([V,$,function(){return[ot,zt,Ot]}],this,0,(function(){var t=new Ot;onmessage=ot(t)}),9)}}();function Zt(t,n,r){if(r||(r=n,n={}),"function"!=typeof r)throw"no callback";return it(t,n,[V,$,function(){return[It]}],(function(t){return rt(It(t.data[0]))}),3,r)}function It(t,r){return U(t.subarray(lt(t),-8),r||new n(pt(t)))}_e.AsyncGunzip=Tt,_e.gunzip=Zt,_e.gunzipSync=It;var Ft=function(){function t(t,n){this.c=B(),this.v=1,yt.call(this,t,n)}return t.prototype.push=function(t,n){yt.prototype.push.call(this,t,n)},t.prototype.p=function(t,n){this.c.p(t);var r=J(t,this.o,this.v&&2,n&&4,!n);this.v&&(dt(r,this.o),this.v=0),n&&ht(r,r.length-4,this.c.d()),this.ondata(r,n)},t}();_e.Zlib=Ft;var Et=function(){return function(t,n){at([W,tt,function(){return[ot,yt,Ft]}],this,wt.call(this,t,n),(function(t){var n=new Ft(t.data);onmessage=ot(n)}),10)}}();function Gt(t,n,r){if(r||(r=n,n={}),"function"!=typeof r)throw"no callback";return it(t,n,[W,tt,function(){return[Pt]}],(function(t){return rt(Pt(t.data[0],t.data[1]))}),4,r)}function Pt(t,n){n||(n={});var r=B();r.p(t);var e=J(t,n,2,4);return dt(e,n),ht(e,e.length-4,r.d()),e}_e.AsyncZlib=Et,_e.zlib=Gt,_e.zlibSync=Pt;var jt=function(){function t(t){this.v=1,zt.call(this,t)}return t.prototype.push=function(t,n){if(zt.prototype.e.call(this,t),this.v){if(this.p.length<2&&!n)return;this.p=this.p.subarray(2),this.v=0}if(n){if(this.p.length<4)throw"invalid zlib stream";this.p=this.p.subarray(0,-4)}zt.prototype.c.call(this,n)},t}();_e.Unzlib=jt;var _t=function(){return function(t){this.ondata=t,at([V,nt,function(){return[ot,zt,jt]}],this,0,(function(){var t=new jt;onmessage=ot(t)}),11)}}();function qt(t,n,r){if(r||(r=n,n={}),"function"!=typeof r)throw"no callback";return it(t,n,[V,nt,function(){return[Ht]}],(function(t){return rt(Ht(t.data[0],et(t.data[1])))}),5,r)}function Ht(t,n){return U((gt(t),t.subarray(2,-4)),n)}_e.AsyncUnzlib=_t,_e.unzlib=qt,_e.unzlibSync=Ht;var Yt=function(){function t(t){this.G=Ot,this.I=zt,this.Z=jt,this.ondata=t}return t.prototype.push=function(t,r){if(!this.ondata)throw"no stream handler";if(this.s)this.s.push(t,r);else{if(this.p&&this.p.length){var e=new n(this.p.length+t.length);e.set(this.p),e.set(t,this.p.length)}else this.p=t;if(this.p.length>2){var i=this,o=function(){i.ondata.apply(i,arguments)};this.s=31==this.p[0]&&139==this.p[1]&&8==this.p[2]?new this.G(o):8!=(15&this.p[0])||this.p[0]>>4>7||(this.p[0]<<8|this.p[1])%31?new this.I(o):new this.Z(o),this.s.push(this.p,r),this.p=null}}},t}();_e.Decompress=Yt;var Bt=function(){function t(t){this.G=Tt,this.I=kt,this.Z=_t,this.ondata=t}return t.prototype.push=function(t,n){Yt.prototype.push.call(this,t,n)},t}();function Jt(t,n,r){if(r||(r=n,n={}),"function"!=typeof r)throw"no callback";return 31==t[0]&&139==t[1]&&8==t[2]?Zt(t,n,r):8!=(15&t[0])||t[0]>>4>7||(t[0]<<8|t[1])%31?Mt(t,n,r):qt(t,n,r)}function Kt(t,n){return 31==t[0]&&139==t[1]&&8==t[2]?It(t,n):8!=(15&t[0])||t[0]>>4>7||(t[0]<<8|t[1])%31?At(t,n):Ht(t,n)}_e.AsyncDecompress=Bt,_e.decompress=Jt,_e.decompressSync=Kt;var Lt=function(t,r,e,i){for(var o in t){var a=t[o],s=r+o;a instanceof n?e[s]=[a,i]:Array.isArray(a)?e[s]=[a[0],K(i,a[1])]:Lt(a,s+"/",e,i)}},Nt="undefined"!=typeof TextEncoder&&new TextEncoder,Qt="undefined"!=typeof TextDecoder&&new TextDecoder,Rt=0;try{Qt.decode(_,{stream:!0}),Rt=1}catch(t){}var Vt=function(t){for(var n="",r=0;;){var e=t[r++],i=(e>127)+(e>223)+(e>239);if(r+i>t.length)return[n,C(t,r-1)];i?3==i?(e=((15&e)<<18|(63&t[r++])<<12|(63&t[r++])<<6|63&t[r++])-65536,n+=String.fromCharCode(55296|e>>10,56320|1023&e)):n+=String.fromCharCode(1&i?(31&e)<<6|63&t[r++]:(15&e)<<12|(63&t[r++])<<6|63&t[r++]):n+=String.fromCharCode(e)}},Wt=function(){function t(t){this.ondata=t,Rt?this.t=new TextDecoder:this.p=_}return t.prototype.push=function(t,r){if(!this.ondata)throw"no callback";if(r||(r=!1),this.t)return this.ondata(this.t.decode(t,{stream:!r}),r);var e=new n(this.p.length+t.length);e.set(this.p),e.set(t,this.p.length);var i=Vt(e),o=i[0],a=i[1];if(r&&a.length)throw"invalid utf-8 data";this.p=a,this.ondata(o,r)},t}();_e.DecodeUTF8=Wt;var Xt=function(){function t(t){this.ondata=t}return t.prototype.push=function(t,n){if(!this.ondata)throw"no callback";this.ondata($t(t),n||!1)},t}();function $t(t,r){if(r){for(var e=new n(t.length),i=0;i>1)),s=0,f=function(t){a[s++]=t};for(i=0;ia.length){var u=new n(s+8+(o-i<<1));u.set(a),a=u}var h=t.charCodeAt(i);h<128||r?f(h):h<2048?(f(192|h>>>6),f(128|63&h)):h>55295&&h<57344?(f(240|(h=65536+(1047552&h)|1023&t.charCodeAt(++i))>>>18),f(128|h>>>12&63),f(128|h>>>6&63),f(128|63&h)):(f(224|h>>>12),f(128|h>>>6&63),f(128|63&h))}return C(a,0,s)}function tn(t,n){if(n){for(var r="",e=0;e65535)throw"extra field too long";n+=e+4}return n},sn=function(t,n,r,e,i,o,a,s){var f=e.length,u=r.extra,h=s&&s.length,c=an(u);ht(t,n,null!=a?33639248:67324752),n+=4,null!=a&&(t[n++]=20,t[n++]=r.os),t[n]=20,n+=2,t[n++]=r.flag<<1|(null==o&&8),t[n++]=i&&8,t[n++]=255&r.compression,t[n++]=r.compression>>8;var l=new Date(null==r.mtime?Date.now():r.mtime),p=l.getFullYear()-1980;if(p<0||p>119)throw"date not in range 1980-2099";if(ht(t,n,2*(p<<24)|l.getMonth()+1<<21|l.getDate()<<16|l.getHours()<<11|l.getMinutes()<<5|l.getSeconds()>>>1),n+=4,null!=o&&(ht(t,n,r.crc),ht(t,n+4,o),ht(t,n+8,r.size)),ht(t,n+12,f),ht(t,n+14,c),n+=16,null!=a&&(ht(t,n,h),ht(t,n+6,r.attrs),ht(t,n+10,a),n+=14),t.set(e,n),n+=f,c)for(var v in u){var d=u[v],g=d.length;ht(t,n,+v),ht(t,n+2,g),t.set(d,n+4),n+=4+g}return h&&(t.set(s,n),n+=h),n},fn=function(t,n,r,e,i){ht(t,n,101010256),ht(t,n+8,r),ht(t,n+10,r),ht(t,n+12,e),ht(t,n+16,i)},un=function(){function t(t){this.filename=t,this.c=Y(),this.size=0,this.compression=0}return t.prototype.process=function(t,n){this.ondata(null,t,n)},t.prototype.push=function(t,n){if(!this.ondata)throw"no callback - add to ZIP archive before pushing";this.c.p(t),this.size+=t.length,n&&(this.crc=this.c.d()),this.process(t,n||!1)},t}();_e.ZipPassThrough=un;var hn=function(){function t(t,n){var r=this;n||(n={}),un.call(this,t),this.d=new yt(n,(function(t,n){r.ondata(null,t,n)})),this.compression=8,this.flag=nn(n.level)}return t.prototype.process=function(t,n){try{this.d.push(t,n)}catch(t){this.ondata(t,null,n)}},t.prototype.push=function(t,n){un.prototype.push.call(this,t,n)},t}();_e.ZipDeflate=hn;var cn=function(){function t(t,n){var r=this;n||(n={}),un.call(this,t),this.d=new mt(n,(function(t,n,e){r.ondata(t,n,e)})),this.compression=8,this.flag=nn(n.level),this.terminate=this.d.terminate}return t.prototype.process=function(t,n){this.d.push(t,n)},t.prototype.push=function(t,n){un.prototype.push.call(this,t,n)},t}();_e.AsyncZipDeflate=cn;var ln=function(){function t(t){this.ondata=t,this.u=[],this.d=1}return t.prototype.add=function(t){var r=this;if(2&this.d)throw"stream finished";var e=$t(t.filename),i=e.length,o=t.comment,a=o&&$t(o),s=i!=t.filename.length||a&&o.length!=a.length,f=i+an(t.extra)+30;if(i>65535)throw"filename too long";var u=new n(f);sn(u,0,t,e,s);var h=[u],c=function(){for(var t=0,n=h;t65535&&M("filename too long",null),k)if(g<16e4)try{M(null,xt(u,v))}catch(t){M(t,null)}else c.push(bt(u,v,M));else M(null,u)},d=0;d65535)throw"filename too long";var w=c?xt(u,h):u,y=w.length,m=Y();m.p(u),i.push(K(h,{size:u.length,crc:m.d(),c:w,f:M,m:v,u:l!=s.length||v&&p.length!=d,o:o,compression:c})),o+=30+l+g+y,a+=76+2*(l+g)+(d||0)+y}for(var b=new n(a+22),x=o,z=a-o,k=0;k0){var i=Math.min(this.c,t.length),o=t.subarray(0,i);if(this.c-=i,this.d?this.d.push(o,!this.c):this.k[0].push(o),(t=t.subarray(i)).length)return this.push(t,r)}else{var a=0,s=0,f=void 0,u=void 0;this.p.length?t.length?((u=new n(this.p.length+t.length)).set(this.p),u.set(t,this.p.length)):u=this.p:u=t;for(var h=u.length,c=this.c,l=c&&this.d,p=function(){var t,n=ft(u,s);if(67324752==n){a=1,f=s,v.d=null,v.c=0;var r=st(u,s+6),i=st(u,s+8),o=2048&r,l=8&r,p=st(u,s+26),d=st(u,s+28);if(h>s+30+p+d){var g=[];v.k.unshift(g),a=2;var w=ft(u,s+18),y=ft(u,s+22),m=tn(u.subarray(s+30,s+=30+p),!o);4294967295==w?(t=l?[-2]:on(u,s),w=t[0],y=t[1]):l&&(w=-1),s+=d,v.c=w;var b={name:m,compression:i,start:function(){if(!b.ondata)throw"no callback";if(w){var t=e.o[i];if(!t)throw"unknown compression type "+i;var n=w<0?new t(m):new t(m,w,y);n.ondata=function(t,n,r){b.ondata(t,n,r)};for(var r=0,o=g;r=0&&(b.size=w,b.originalSize=y),v.onfile(b)}return"break"}if(c){if(134695760==n)return f=s+=12+(-2==c&&8),a=2,v.c=0,"break";if(33639248==n)return f=s-=4,a=2,v.c=0,"break"}},v=this;s65558)return void r("invalid zip file",null);var s=st(t,a+8);s||r(null,{});var f=s,u=ft(t,a+16),h=4294967295==u;if(h){if(a=ft(t,a-12),101075792!=ft(t,a))return void r("invalid zip file",null);f=s=ft(t,a+32),u=ft(t,a+48)}for(var c=function(a){var f=en(t,u,h),c=f[0],l=f[1],p=f[2],v=f[3],d=f[4],g=rn(t,f[5]);u=d;var w=function(t,n){t?(i(),r(t,null)):(o[v]=n,--s||r(null,o))};if(c)if(8==c){var y=t.subarray(g,g+l);if(l<32e4)try{w(null,At(y,new n(p)))}catch(t){w(t,null)}else e.push(Mt(y,{size:p},w))}else w("unknown compression type "+c,null);else w(null,C(t,g,g+l))},l=0;l65558)throw"invalid zip file";var i=st(t,e+8);if(!i)return{};var o=ft(t,e+16),a=4294967295==o;if(a){if(e=ft(t,e-12),101075792!=ft(t,e))throw"invalid zip file";i=ft(t,e+32),o=ft(t,e+48)}for(var s=0;s, * linewidth: , * dashed: , * dashScale: , * dashSize: , * gapSize: , * resolution: , // to be set by renderer * } */ (function () { /** * parameters = { * color: , * linewidth: , * dashed: , * dashScale: , * dashSize: , * gapSize: , * resolution: , // to be set by renderer * } */ THREE.UniformsLib.line = { worldUnits: { value: 1 }, linewidth: { value: 1 }, resolution: { value: new THREE.Vector2(1, 1) }, dashScale: { value: 1 }, dashSize: { value: 1 }, gapSize: { value: 1 } // todo FIX - maybe change to totalSize }; THREE.ShaderLib['line'] = { uniforms: THREE.UniformsUtils.merge([THREE.UniformsLib.common, THREE.UniformsLib.fog, THREE.UniformsLib.line]), vertexShader: /* glsl */ ` #include #include #include #include #include uniform float linewidth; uniform vec2 resolution; attribute vec3 instanceStart; attribute vec3 instanceEnd; attribute vec3 instanceColorStart; attribute vec3 instanceColorEnd; varying vec2 vUv; varying vec4 worldPos; varying vec3 worldStart; varying vec3 worldEnd; #ifdef USE_DASH uniform float dashScale; attribute float instanceDistanceStart; attribute float instanceDistanceEnd; varying float vLineDistance; #endif void trimSegment( const in vec4 start, inout vec4 end ) { // trim end segment so it terminates between the camera plane and the near plane // conservative estimate of the near plane float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column float nearEstimate = - 0.5 * b / a; float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); end.xyz = mix( start.xyz, end.xyz, alpha ); } void main() { #ifdef USE_COLOR vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; #endif #ifdef USE_DASH vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; #endif float aspect = resolution.x / resolution.y; vUv = uv; // camera space vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); worldStart = start.xyz; worldEnd = end.xyz; // special case for perspective projection, and segments that terminate either in, or behind, the camera plane // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space // but we need to perform ndc-space calculations in the shader, so we must address this issue directly // perhaps there is a more elegant solution -- WestLangley bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column if ( perspective ) { if ( start.z < 0.0 && end.z >= 0.0 ) { trimSegment( start, end ); } else if ( end.z < 0.0 && start.z >= 0.0 ) { trimSegment( end, start ); } } // clip space vec4 clipStart = projectionMatrix * start; vec4 clipEnd = projectionMatrix * end; // ndc space vec3 ndcStart = clipStart.xyz / clipStart.w; vec3 ndcEnd = clipEnd.xyz / clipEnd.w; // direction vec2 dir = ndcEnd.xy - ndcStart.xy; // account for clip-space aspect ratio dir.x *= aspect; dir = normalize( dir ); #ifdef WORLD_UNITS // get the offset direction as perpendicular to the view vector vec3 worldDir = normalize( end.xyz - start.xyz ); vec3 offset; if ( position.y < 0.5 ) { offset = normalize( cross( start.xyz, worldDir ) ); } else { offset = normalize( cross( end.xyz, worldDir ) ); } // sign flip if ( position.x < 0.0 ) offset *= - 1.0; float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); // don't extend the line if we're rendering dashes because we // won't be rendering the endcaps #ifndef USE_DASH // extend the line bounds to encompass endcaps start.xyz += - worldDir * linewidth * 0.5; end.xyz += worldDir * linewidth * 0.5; // shift the position of the quad so it hugs the forward edge of the line offset.xy -= dir * forwardOffset; offset.z += 0.5; #endif // endcaps if ( position.y > 1.0 || position.y < 0.0 ) { offset.xy += dir * 2.0 * forwardOffset; } // adjust for linewidth offset *= linewidth * 0.5; // set the world position worldPos = ( position.y < 0.5 ) ? start : end; worldPos.xyz += offset; // project the worldpos vec4 clip = projectionMatrix * worldPos; // shift the depth of the projected points so the line // segements overlap neatly vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; clip.z = clipPose.z * clip.w; #else vec2 offset = vec2( dir.y, - dir.x ); // undo aspect ratio adjustment dir.x /= aspect; offset.x /= aspect; // sign flip if ( position.x < 0.0 ) offset *= - 1.0; // endcaps if ( position.y < 0.0 ) { offset += - dir; } else if ( position.y > 1.0 ) { offset += dir; } // adjust for linewidth offset *= linewidth; // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... offset /= resolution.y; // select end vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; // back to clip space offset *= clip.w; clip.xy += offset; #endif gl_Position = clip; vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation #include #include #include } `, fragmentShader: /* glsl */ ` uniform vec3 diffuse; uniform float opacity; uniform float linewidth; #ifdef USE_DASH uniform float dashSize; uniform float gapSize; #endif varying float vLineDistance; varying vec4 worldPos; varying vec3 worldStart; varying vec3 worldEnd; #include #include #include #include #include varying vec2 vUv; vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { float mua; float mub; vec3 p13 = p1 - p3; vec3 p43 = p4 - p3; vec3 p21 = p2 - p1; float d1343 = dot( p13, p43 ); float d4321 = dot( p43, p21 ); float d1321 = dot( p13, p21 ); float d4343 = dot( p43, p43 ); float d2121 = dot( p21, p21 ); float denom = d2121 * d4343 - d4321 * d4321; float numer = d1343 * d4321 - d1321 * d4343; mua = numer / denom; mua = clamp( mua, 0.0, 1.0 ); mub = ( d1343 + d4321 * ( mua ) ) / d4343; mub = clamp( mub, 0.0, 1.0 ); return vec2( mua, mub ); } void main() { #include #ifdef USE_DASH if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX #endif float alpha = opacity; #ifdef WORLD_UNITS // Find the closest points on the view ray and the line segment vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; vec3 lineDir = worldEnd - worldStart; vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); vec3 p1 = worldStart + lineDir * params.x; vec3 p2 = rayEnd * params.y; vec3 delta = p1 - p2; float len = length( delta ); float norm = len / linewidth; #ifndef USE_DASH #ifdef ALPHA_TO_COVERAGE float dnorm = fwidth( norm ); alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); #else if ( norm > 0.5 ) { discard; } #endif #endif #else #ifdef ALPHA_TO_COVERAGE // artifacts appear on some hardware if a derivative is taken within a conditional float a = vUv.x; float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; float len2 = a * a + b * b; float dlen = fwidth( len2 ); if ( abs( vUv.y ) > 1.0 ) { alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); } #else if ( abs( vUv.y ) > 1.0 ) { float a = vUv.x; float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; float len2 = a * a + b * b; if ( len2 > 1.0 ) discard; } #endif #endif vec4 diffuseColor = vec4( diffuse, alpha ); #include #include gl_FragColor = vec4( diffuseColor.rgb, alpha ); #include #include #include #include } ` }; class LineMaterial extends THREE.ShaderMaterial { constructor(parameters) { super({ type: 'LineMaterial', uniforms: THREE.UniformsUtils.clone(THREE.ShaderLib['line'].uniforms), vertexShader: THREE.ShaderLib['line'].vertexShader, fragmentShader: THREE.ShaderLib['line'].fragmentShader, clipping: true // required for clipping support }); Object.defineProperties(this, { color: { enumerable: true, get: function () { return this.uniforms.diffuse.value; }, set: function (value) { this.uniforms.diffuse.value = value; } }, worldUnits: { enumerable: true, get: function () { return 'WORLD_UNITS' in this.defines; }, set: function (value) { if (value === true) { this.defines.WORLD_UNITS = ''; } else { delete this.defines.WORLD_UNITS; } } }, linewidth: { enumerable: true, get: function () { return this.uniforms.linewidth.value; }, set: function (value) { this.uniforms.linewidth.value = value; } }, dashed: { enumerable: true, get: function () { return Boolean('USE_DASH' in this.defines); }, set(value) { if (Boolean(value) !== Boolean('USE_DASH' in this.defines)) { this.needsUpdate = true; } if (value === true) { this.defines.USE_DASH = ''; } else { delete this.defines.USE_DASH; } } }, dashScale: { enumerable: true, get: function () { return this.uniforms.dashScale.value; }, set: function (value) { this.uniforms.dashScale.value = value; } }, dashSize: { enumerable: true, get: function () { return this.uniforms.dashSize.value; }, set: function (value) { this.uniforms.dashSize.value = value; } }, dashOffset: { enumerable: true, get: function () { return this.uniforms.dashOffset.value; }, set: function (value) { this.uniforms.dashOffset.value = value; } }, gapSize: { enumerable: true, get: function () { return this.uniforms.gapSize.value; }, set: function (value) { this.uniforms.gapSize.value = value; } }, opacity: { enumerable: true, get: function () { return this.uniforms.opacity.value; }, set: function (value) { this.uniforms.opacity.value = value; } }, resolution: { enumerable: true, get: function () { return this.uniforms.resolution.value; }, set: function (value) { this.uniforms.resolution.value.copy(value); } }, alphaToCoverage: { enumerable: true, get: function () { return Boolean('ALPHA_TO_COVERAGE' in this.defines); }, set: function (value) { if (Boolean(value) !== Boolean('ALPHA_TO_COVERAGE' in this.defines)) { this.needsUpdate = true; } if (value === true) { this.defines.ALPHA_TO_COVERAGE = ''; this.extensions.derivatives = true; } else { delete this.defines.ALPHA_TO_COVERAGE; this.extensions.derivatives = false; } } } }); this.setValues(parameters); } } LineMaterial.prototype.isLineMaterial = true; THREE.LineMaterial = LineMaterial; })(); /** * @author WestLangley / http://github.com/WestLangley * */ (function () { const _start = new THREE.Vector3(); const _end = new THREE.Vector3(); const _start4 = new THREE.Vector4(); const _end4 = new THREE.Vector4(); const _ssOrigin = new THREE.Vector4(); const _ssOrigin3 = new THREE.Vector3(); const _mvMatrix = new THREE.Matrix4(); const _line = new THREE.Line3(); const _closestPoint = new THREE.Vector3(); const _box = new THREE.Box3(); const _sphere = new THREE.Sphere(); const _clipToWorldVector = new THREE.Vector4(); class LineSegments2 extends THREE.Mesh { constructor(geometry = new THREE.LineSegmentsGeometry(), material = new THREE.LineMaterial({ color: Math.random() * 0xffffff })) { super(geometry, material); this.type = 'LineSegments2'; } // for backwards-compatability, but could be a method of THREE.LineSegmentsGeometry... computeLineDistances() { const geometry = this.geometry; const instanceStart = geometry.attributes.instanceStart; const instanceEnd = geometry.attributes.instanceEnd; const lineDistances = new Float32Array(2 * instanceStart.count); for (let i = 0, j = 0, l = instanceStart.count; i < l; i++, j += 2) { _start.fromBufferAttribute(instanceStart, i); _end.fromBufferAttribute(instanceEnd, i); lineDistances[j] = j === 0 ? 0 : lineDistances[j - 1]; lineDistances[j + 1] = lineDistances[j] + _start.distanceTo(_end); } const instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer(lineDistances, 2, 1); // d0, d1 geometry.setAttribute('instanceDistanceStart', new THREE.InterleavedBufferAttribute(instanceDistanceBuffer, 1, 0)); // d0 geometry.setAttribute('instanceDistanceEnd', new THREE.InterleavedBufferAttribute(instanceDistanceBuffer, 1, 1)); // d1 return this; } raycast(raycaster, intersects) { if (raycaster.camera === null) { console.error('LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.'); } const threshold = raycaster.params.Line2 !== undefined ? raycaster.params.Line2.threshold || 0 : 0; const ray = raycaster.ray; const camera = raycaster.camera; const projectionMatrix = camera.projectionMatrix; const matrixWorld = this.matrixWorld; const geometry = this.geometry; const material = this.material; const resolution = material.resolution; const lineWidth = material.linewidth + threshold; const instanceStart = geometry.attributes.instanceStart; const instanceEnd = geometry.attributes.instanceEnd; // camera forward is negative const near = - camera.near; // clip space is [ - 1, 1 ] so multiply by two to get the full // width in clip space const ssMaxWidth = 2.0 * Math.max(lineWidth / resolution.width, lineWidth / resolution.height); // // check if we intersect the sphere bounds if (geometry.boundingSphere === null) { geometry.computeBoundingSphere(); } _sphere.copy(geometry.boundingSphere).applyMatrix4(matrixWorld); const distanceToSphere = Math.max(camera.near, _sphere.distanceToPoint(ray.origin)); // get the w component to scale the world space line width _clipToWorldVector.set(0, 0, - distanceToSphere, 1.0).applyMatrix4(camera.projectionMatrix); _clipToWorldVector.multiplyScalar(1.0 / _clipToWorldVector.w); _clipToWorldVector.applyMatrix4(camera.projectionMatrixInverse); // increase the sphere bounds by the worst case line screen space width const sphereMargin = Math.abs(ssMaxWidth / _clipToWorldVector.w) * 0.5; _sphere.radius += sphereMargin; if (raycaster.ray.intersectsSphere(_sphere) === false) { return; } // // check if we intersect the box bounds if (geometry.boundingBox === null) { geometry.computeBoundingBox(); } _box.copy(geometry.boundingBox).applyMatrix4(matrixWorld); const distanceToBox = Math.max(camera.near, _box.distanceToPoint(ray.origin)); // get the w component to scale the world space line width _clipToWorldVector.set(0, 0, - distanceToBox, 1.0).applyMatrix4(camera.projectionMatrix); _clipToWorldVector.multiplyScalar(1.0 / _clipToWorldVector.w); _clipToWorldVector.applyMatrix4(camera.projectionMatrixInverse); // increase the sphere bounds by the worst case line screen space width const boxMargin = Math.abs(ssMaxWidth / _clipToWorldVector.w) * 0.5; _box.max.x += boxMargin; _box.max.y += boxMargin; _box.max.z += boxMargin; _box.min.x -= boxMargin; _box.min.y -= boxMargin; _box.min.z -= boxMargin; if (raycaster.ray.intersectsBox(_box) === false) { return; } // // pick a point 1 unit out along the ray to avoid the ray origin // sitting at the camera origin which will cause "w" to be 0 when // applying the projection matrix. ray.at(1, _ssOrigin); // ndc space [ - 1.0, 1.0 ] _ssOrigin.w = 1; _ssOrigin.applyMatrix4(camera.matrixWorldInverse); _ssOrigin.applyMatrix4(projectionMatrix); _ssOrigin.multiplyScalar(1 / _ssOrigin.w); // screen space _ssOrigin.x *= resolution.x / 2; _ssOrigin.y *= resolution.y / 2; _ssOrigin.z = 0; _ssOrigin3.copy(_ssOrigin); _mvMatrix.multiplyMatrices(camera.matrixWorldInverse, matrixWorld); for (let i = 0, l = instanceStart.count; i < l; i++) { _start4.fromBufferAttribute(instanceStart, i); _end4.fromBufferAttribute(instanceEnd, i); _start4.w = 1; _end4.w = 1; // camera space _start4.applyMatrix4(_mvMatrix); _end4.applyMatrix4(_mvMatrix); // skip the segment if it's entirely behind the camera var isBehindCameraNear = _start4.z > near && _end4.z > near; if (isBehindCameraNear) { continue; } // trim the segment if it extends behind camera near if (_start4.z > near) { const deltaDist = _start4.z - _end4.z; const t = (_start4.z - near) / deltaDist; _start4.lerp(_end4, t); } else if (_end4.z > near) { const deltaDist = _end4.z - _start4.z; const t = (_end4.z - near) / deltaDist; _end4.lerp(_start4, t); } // clip space _start4.applyMatrix4(projectionMatrix); _end4.applyMatrix4(projectionMatrix); // ndc space [ - 1.0, 1.0 ] _start4.multiplyScalar(1 / _start4.w); _end4.multiplyScalar(1 / _end4.w); // screen space _start4.x *= resolution.x / 2; _start4.y *= resolution.y / 2; _end4.x *= resolution.x / 2; _end4.y *= resolution.y / 2; // create 2d segment _line.start.copy(_start4); _line.start.z = 0; _line.end.copy(_end4); _line.end.z = 0; // get closest point on ray to segment const param = _line.closestPointToPointParameter(_ssOrigin3, true); _line.at(param, _closestPoint); // check if the intersection point is within clip space const zPos = THREE.MathUtils.lerp(_start4.z, _end4.z, param); const isInClipSpace = zPos >= - 1 && zPos <= 1; const isInside = _ssOrigin3.distanceTo(_closestPoint) < lineWidth * 0.5; if (isInClipSpace && isInside) { _line.start.fromBufferAttribute(instanceStart, i); _line.end.fromBufferAttribute(instanceEnd, i); _line.start.applyMatrix4(matrixWorld); _line.end.applyMatrix4(matrixWorld); const pointOnLine = new THREE.Vector3(); const point = new THREE.Vector3(); ray.distanceSqToSegment(_line.start, _line.end, point, pointOnLine); intersects.push({ point: point, pointOnLine: pointOnLine, distance: ray.origin.distanceTo(point), object: this, face: null, faceIndex: i, uv: null, uv2: null }); } } } } LineSegments2.prototype.LineSegments2 = true; THREE.LineSegments2 = LineSegments2; })(); /** * @author WestLangley / http://github.com/WestLangley * */ (function () { class Line2 extends THREE.LineSegments2 { constructor(geometry = new THREE.LineGeometry(), material = new THREE.LineMaterial({ color: Math.random() * 0xffffff })) { super(geometry, material); this.type = 'Line2'; } } Line2.prototype.isLine2 = true; THREE.Line2 = Line2; })(); /** * @author WestLangley / http://github.com/WestLangley * */ (function () { const _start = new THREE.Vector3(); const _end = new THREE.Vector3(); class Wireframe extends THREE.Mesh { constructor(geometry = new THREE.LineSegmentsGeometry(), material = new THREE.LineMaterial({ color: Math.random() * 0xffffff })) { super(geometry, material); this.type = 'Wireframe'; } // for backwards-compatability, but could be a method of THREE.LineSegmentsGeometry... computeLineDistances() { const geometry = this.geometry; const instanceStart = geometry.attributes.instanceStart; const instanceEnd = geometry.attributes.instanceEnd; const lineDistances = new Float32Array(2 * instanceStart.count); for (let i = 0, j = 0, l = instanceStart.count; i < l; i++, j += 2) { _start.fromBufferAttribute(instanceStart, i); _end.fromBufferAttribute(instanceEnd, i); lineDistances[j] = j === 0 ? 0 : lineDistances[j - 1]; lineDistances[j + 1] = lineDistances[j] + _start.distanceTo(_end); } const instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer(lineDistances, 2, 1); // d0, d1 geometry.setAttribute('instanceDistanceStart', new THREE.InterleavedBufferAttribute(instanceDistanceBuffer, 1, 0)); // d0 geometry.setAttribute('instanceDistanceEnd', new THREE.InterleavedBufferAttribute(instanceDistanceBuffer, 1, 1)); // d1 return this; } } Wireframe.prototype.isWireframe = true; THREE.Wireframe = Wireframe; })(); },{"../three.js":25,"../utils/utils.js":29,"./objects.js":21}],15:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const utils = require("../utils/utils.js"); const Objects = require('./objects.js'); const OBJLoader = require("./loaders/OBJLoader.js"); const MTLLoader = require("./loaders/MTLLoader.js"); const FBXLoader = require("./loaders/FBXLoader.js"); const GLTFLoader = require("./loaders/GLTFLoader.js"); const ColladaLoader = require("./loaders/ColladaLoader.js"); const objLoader = new OBJLoader(); const materialLoader = new MTLLoader(); const gltfLoader = new GLTFLoader(); const fbxLoader = new FBXLoader(); const daeLoader = new ColladaLoader(); function loadObj(options, cb, promise) { if (options === undefined) return console.error("Invalid options provided to loadObj()"); options = utils._validate(options, Objects.prototype._defaults.loadObj); let loader; if (!options.type) { options.type = 'mtl'; }; //[jscastro] support other models switch (options.type) { case "mtl": // TODO: Support formats other than OBJ/MTL loader = objLoader; break; case "gltf": case "glb": // [jscastro] Support for GLTF/GLB loader = gltfLoader; break; case "fbx": loader = fbxLoader; break; case "dae": loader = daeLoader; break; } materialLoader.load(options.mtl, loadObject, () => (null), error => { console.warn("No material file found " + error.stack); }); function loadObject(materials) { if (materials && options.type == "mtl") { materials.preload(); loader.setMaterials(materials); } loader.load(options.obj, obj => { //[jscastro] MTL/GLTF/FBX models have a different structure let animations = []; switch (options.type) { case "mtl": obj = obj.children[0]; break; case "gltf": case "glb": case "dae": animations = obj.animations; obj = obj.scene; break; case "fbx": animations = obj.animations; break; } obj.animations = animations; // [jscastro] options.rotation was wrongly used const r = utils.types.rotation(options.rotation, [0, 0, 0]); const s = utils.types.scale(options.scale, [1, 1, 1]); obj.rotation.set(r[0], r[1], r[2]); obj.scale.set(s[0], s[1], s[2]); // [jscastro] normalize specular/metalness/shininess from meshes in FBX and GLB model as it would need 5 lights to illuminate them properly if (options.normalize) { normalizeSpecular(obj); } obj.name = "model"; let userScaleGroup = Objects.prototype._makeGroup(obj, options); Objects.prototype._addMethods(userScaleGroup); //[jscastro] calculate automatically the pivotal center of the object userScaleGroup.setAnchor(options.anchor); //[jscastro] override the center calculated if the object has adjustments userScaleGroup.setCenter(options.adjustment); //[jscastro] if the object is excluded from raycasting userScaleGroup.raycasted = options.raycasted; //[jscastro] return to cache promise(userScaleGroup); //[jscastro] then return to the client-side callback cb(userScaleGroup); //[jscastro] apply the fixed zoom scale if needed userScaleGroup.setFixedZoom(options.mapScale); //[jscastro] initialize the default animation to avoid issues with skeleton position userScaleGroup.idle(); }, () => (null), error => { console.error("Could not load model file: " + options.obj + " \n " + error.stack); promise("Error loading the model"); }); }; //[jscastro] some FBX/GLTF models have too much specular effects for mapbox function normalizeSpecular(model) { model.traverse(function (c) { if (c.isMesh) { //c.castShadow = true; let specularColor; if (c.material.type == 'MeshStandardMaterial') { if (c.material.metalness) { c.material.metalness *= 0.1; } if (c.material.glossiness) { c.material.glossiness *= 0.25; } specularColor = new THREE.Color(12, 12, 12); } else if (c.material.type == 'MeshPhongMaterial') { c.material.shininess = 0.1; specularColor = new THREE.Color(20, 20, 20); } if (c.material.specular && c.material.specular.isColor) { c.material.specular = specularColor; } //c.material.needsUpdate = true; } }); } } module.exports = exports = loadObj; },{"../utils/utils.js":29,"./loaders/ColladaLoader.js":16,"./loaders/FBXLoader.js":17,"./loaders/GLTFLoader.js":18,"./loaders/MTLLoader.js":19,"./loaders/OBJLoader.js":20,"./objects.js":21}],16:[function(require,module,exports){ const THREE = require('../../three.js'); /** * @author mrdoob / http://mrdoob.com/ * @author Mugen87 / https://github.com/Mugen87 */ (function () { class ColladaLoader extends THREE.Loader { constructor(manager) { super(manager); } load(url, onLoad, onProgress, onError) { const scope = this; const path = scope.path === '' ? THREE.LoaderUtils.extractUrlBase(url) : scope.path; const loader = new THREE.FileLoader(scope.manager); loader.setPath(scope.path); loader.setRequestHeader(scope.requestHeader); loader.setWithCredentials(scope.withCredentials); loader.load(url, function (text) { try { onLoad(scope.parse(text, path)); } catch (e) { if (onError) { onError(e); } else { console.error(e); } scope.manager.itemError(url); } }, onProgress, onError); } parse(text, path) { function getElementsByTagName(xml, name) { // Non recursive xml.getElementsByTagName() ... const array = []; const childNodes = xml.childNodes; for (let i = 0, l = childNodes.length; i < l; i++) { const child = childNodes[i]; if (child.nodeName === name) { array.push(child); } } return array; } function parseStrings(text) { if (text.length === 0) return []; const parts = text.trim().split(/\s+/); const array = new Array(parts.length); for (let i = 0, l = parts.length; i < l; i++) { array[i] = parts[i]; } return array; } function parseFloats(text) { if (text.length === 0) return []; const parts = text.trim().split(/\s+/); const array = new Array(parts.length); for (let i = 0, l = parts.length; i < l; i++) { array[i] = parseFloat(parts[i]); } return array; } function parseInts(text) { if (text.length === 0) return []; const parts = text.trim().split(/\s+/); const array = new Array(parts.length); for (let i = 0, l = parts.length; i < l; i++) { array[i] = parseInt(parts[i]); } return array; } function parseId(text) { return text.substring(1); } function generateId() { return 'three_default_' + count++; } function isEmpty(object) { return Object.keys(object).length === 0; } // asset function parseAsset(xml) { return { unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]), upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0]) }; } function parseAssetUnit(xml) { if (xml !== undefined && xml.hasAttribute('meter') === true) { return parseFloat(xml.getAttribute('meter')); } else { return 1; // default 1 meter } } function parseAssetUpAxis(xml) { return xml !== undefined ? xml.textContent : 'Y_UP'; } // library function parseLibrary(xml, libraryName, nodeName, parser) { const library = getElementsByTagName(xml, libraryName)[0]; if (library !== undefined) { const elements = getElementsByTagName(library, nodeName); for (let i = 0; i < elements.length; i++) { parser(elements[i]); } } } function buildLibrary(data, builder) { for (const name in data) { const object = data[name]; object.build = builder(data[name]); } } // get function getBuild(data, builder) { if (data.build !== undefined) return data.build; data.build = builder(data); return data.build; } // animation function parseAnimation(xml) { const data = { sources: {}, samplers: {}, channels: {} }; let hasChildren = false; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; let id; switch (child.nodeName) { case 'source': id = child.getAttribute('id'); data.sources[id] = parseSource(child); break; case 'sampler': id = child.getAttribute('id'); data.samplers[id] = parseAnimationSampler(child); break; case 'channel': id = child.getAttribute('target'); data.channels[id] = parseAnimationChannel(child); break; case 'animation': // hierarchy of related animations parseAnimation(child); hasChildren = true; break; default: console.log(child); } } if (hasChildren === false) { // since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment library.animations[xml.getAttribute('id') || THREE.MathUtils.generateUUID()] = data; } } function parseAnimationSampler(xml) { const data = { inputs: {} }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'input': const id = parseId(child.getAttribute('source')); const semantic = child.getAttribute('semantic'); data.inputs[semantic] = id; break; } } return data; } function parseAnimationChannel(xml) { const data = {}; const target = xml.getAttribute('target'); // parsing SID Addressing Syntax let parts = target.split('/'); const id = parts.shift(); let sid = parts.shift(); // check selection syntax const arraySyntax = sid.indexOf('(') !== - 1; const memberSyntax = sid.indexOf('.') !== - 1; if (memberSyntax) { // member selection access parts = sid.split('.'); sid = parts.shift(); data.member = parts.shift(); } else if (arraySyntax) { // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. const indices = sid.split('('); sid = indices.shift(); for (let i = 0; i < indices.length; i++) { indices[i] = parseInt(indices[i].replace(/\)/, '')); } data.indices = indices; } data.id = id; data.sid = sid; data.arraySyntax = arraySyntax; data.memberSyntax = memberSyntax; data.sampler = parseId(xml.getAttribute('source')); return data; } function buildAnimation(data) { const tracks = []; const channels = data.channels; const samplers = data.samplers; const sources = data.sources; for (const target in channels) { if (channels.hasOwnProperty(target)) { const channel = channels[target]; const sampler = samplers[channel.sampler]; const inputId = sampler.inputs.INPUT; const outputId = sampler.inputs.OUTPUT; const inputSource = sources[inputId]; const outputSource = sources[outputId]; const animation = buildAnimationChannel(channel, inputSource, outputSource); createKeyframeTracks(animation, tracks); } } return tracks; } function getAnimation(id) { return getBuild(library.animations[id], buildAnimation); } function buildAnimationChannel(channel, inputSource, outputSource) { const node = library.nodes[channel.id]; const object3D = getNode(node.id); const transform = node.transforms[channel.sid]; const defaultMatrix = node.matrix.clone().transpose(); let time, stride; let i, il, j, jl; const data = {}; // the collada spec allows the animation of data in various ways. // depending on the transform type (matrix, translate, rotate, scale), we execute different logic switch (transform) { case 'matrix': for (i = 0, il = inputSource.array.length; i < il; i++) { time = inputSource.array[i]; stride = i * outputSource.stride; if (data[time] === undefined) data[time] = {}; if (channel.arraySyntax === true) { const value = outputSource.array[stride]; const index = channel.indices[0] + 4 * channel.indices[1]; data[time][index] = value; } else { for (j = 0, jl = outputSource.stride; j < jl; j++) { data[time][j] = outputSource.array[stride + j]; } } } break; case 'translate': console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); break; case 'rotate': console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); break; case 'scale': console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); break; } const keyframes = prepareAnimationData(data, defaultMatrix); const animation = { name: object3D.uuid, keyframes: keyframes }; return animation; } function prepareAnimationData(data, defaultMatrix) { const keyframes = []; // transfer data into a sortable array for (const time in data) { keyframes.push({ time: parseFloat(time), value: data[time] }); } // ensure keyframes are sorted by time keyframes.sort(ascending); // now we clean up all animation data, so we can use them for keyframe tracks for (let i = 0; i < 16; i++) { transformAnimationData(keyframes, i, defaultMatrix.elements[i]); } return keyframes; // array sort function function ascending(a, b) { return a.time - b.time; } } const position = new THREE.Vector3(); const scale = new THREE.Vector3(); const quaternion = new THREE.Quaternion(); function createKeyframeTracks(animation, tracks) { const keyframes = animation.keyframes; const name = animation.name; const times = []; const positionData = []; const quaternionData = []; const scaleData = []; for (let i = 0, l = keyframes.length; i < l; i++) { const keyframe = keyframes[i]; const time = keyframe.time; const value = keyframe.value; matrix.fromArray(value).transpose(); matrix.decompose(position, quaternion, scale); times.push(time); positionData.push(position.x, position.y, position.z); quaternionData.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w); scaleData.push(scale.x, scale.y, scale.z); } if (positionData.length > 0) tracks.push(new THREE.VectorKeyframeTrack(name + '.position', times, positionData)); if (quaternionData.length > 0) tracks.push(new THREE.QuaternionKeyframeTrack(name + '.quaternion', times, quaternionData)); if (scaleData.length > 0) tracks.push(new THREE.VectorKeyframeTrack(name + '.scale', times, scaleData)); return tracks; } function transformAnimationData(keyframes, property, defaultValue) { let keyframe; let empty = true; let i, l; // check, if values of a property are missing in our keyframes for (i = 0, l = keyframes.length; i < l; i++) { keyframe = keyframes[i]; if (keyframe.value[property] === undefined) { keyframe.value[property] = null; // mark as missing } else { empty = false; } } if (empty === true) { // no values at all, so we set a default value for (i = 0, l = keyframes.length; i < l; i++) { keyframe = keyframes[i]; keyframe.value[property] = defaultValue; } } else { // filling gaps createMissingKeyframes(keyframes, property); } } function createMissingKeyframes(keyframes, property) { let prev, next; for (let i = 0, l = keyframes.length; i < l; i++) { const keyframe = keyframes[i]; if (keyframe.value[property] === null) { prev = getPrev(keyframes, i, property); next = getNext(keyframes, i, property); if (prev === null) { keyframe.value[property] = next.value[property]; continue; } if (next === null) { keyframe.value[property] = prev.value[property]; continue; } interpolate(keyframe, prev, next, property); } } } function getPrev(keyframes, i, property) { while (i >= 0) { const keyframe = keyframes[i]; if (keyframe.value[property] !== null) return keyframe; i--; } return null; } function getNext(keyframes, i, property) { while (i < keyframes.length) { const keyframe = keyframes[i]; if (keyframe.value[property] !== null) return keyframe; i++; } return null; } function interpolate(key, prev, next, property) { if (next.time - prev.time === 0) { key.value[property] = prev.value[property]; return; } key.value[property] = (key.time - prev.time) * (next.value[property] - prev.value[property]) / (next.time - prev.time) + prev.value[property]; } // animation clips function parseAnimationClip(xml) { const data = { name: xml.getAttribute('id') || 'default', start: parseFloat(xml.getAttribute('start') || 0), end: parseFloat(xml.getAttribute('end') || 0), animations: [] }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'instance_animation': data.animations.push(parseId(child.getAttribute('url'))); break; } } library.clips[xml.getAttribute('id')] = data; } function buildAnimationClip(data) { const tracks = []; const name = data.name; const duration = data.end - data.start || - 1; const animations = data.animations; for (let i = 0, il = animations.length; i < il; i++) { const animationTracks = getAnimation(animations[i]); for (let j = 0, jl = animationTracks.length; j < jl; j++) { tracks.push(animationTracks[j]); } } return new THREE.AnimationClip(name, duration, tracks); } function getAnimationClip(id) { return getBuild(library.clips[id], buildAnimationClip); } // controller function parseController(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'skin': // there is exactly one skin per controller data.id = parseId(child.getAttribute('source')); data.skin = parseSkin(child); break; case 'morph': data.id = parseId(child.getAttribute('source')); console.warn('THREE.ColladaLoader: Morph target animation not supported yet.'); break; } } library.controllers[xml.getAttribute('id')] = data; } function parseSkin(xml) { const data = { sources: {} }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'bind_shape_matrix': data.bindShapeMatrix = parseFloats(child.textContent); break; case 'source': const id = child.getAttribute('id'); data.sources[id] = parseSource(child); break; case 'joints': data.joints = parseJoints(child); break; case 'vertex_weights': data.vertexWeights = parseVertexWeights(child); break; } } return data; } function parseJoints(xml) { const data = { inputs: {} }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'input': const semantic = child.getAttribute('semantic'); const id = parseId(child.getAttribute('source')); data.inputs[semantic] = id; break; } } return data; } function parseVertexWeights(xml) { const data = { inputs: {} }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'input': const semantic = child.getAttribute('semantic'); const id = parseId(child.getAttribute('source')); const offset = parseInt(child.getAttribute('offset')); data.inputs[semantic] = { id: id, offset: offset }; break; case 'vcount': data.vcount = parseInts(child.textContent); break; case 'v': data.v = parseInts(child.textContent); break; } } return data; } function buildController(data) { const build = { id: data.id }; const geometry = library.geometries[build.id]; if (data.skin !== undefined) { build.skin = buildSkin(data.skin); // we enhance the 'sources' property of the corresponding geometry with our skin data geometry.sources.skinIndices = build.skin.indices; geometry.sources.skinWeights = build.skin.weights; } return build; } function buildSkin(data) { const BONE_LIMIT = 4; const build = { joints: [], // this must be an array to preserve the joint order indices: { array: [], stride: BONE_LIMIT }, weights: { array: [], stride: BONE_LIMIT } }; const sources = data.sources; const vertexWeights = data.vertexWeights; const vcount = vertexWeights.vcount; const v = vertexWeights.v; const jointOffset = vertexWeights.inputs.JOINT.offset; const weightOffset = vertexWeights.inputs.WEIGHT.offset; const jointSource = data.sources[data.joints.inputs.JOINT]; const inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX]; const weights = sources[vertexWeights.inputs.WEIGHT.id].array; let stride = 0; let i, j, l; // procces skin data for each vertex for (i = 0, l = vcount.length; i < l; i++) { const jointCount = vcount[i]; // this is the amount of joints that affect a single vertex const vertexSkinData = []; for (j = 0; j < jointCount; j++) { const skinIndex = v[stride + jointOffset]; const weightId = v[stride + weightOffset]; const skinWeight = weights[weightId]; vertexSkinData.push({ index: skinIndex, weight: skinWeight }); stride += 2; } // we sort the joints in descending order based on the weights. // this ensures, we only procced the most important joints of the vertex vertexSkinData.sort(descending); // now we provide for each vertex a set of four index and weight values. // the order of the skin data matches the order of vertices for (j = 0; j < BONE_LIMIT; j++) { const d = vertexSkinData[j]; if (d !== undefined) { build.indices.array.push(d.index); build.weights.array.push(d.weight); } else { build.indices.array.push(0); build.weights.array.push(0); } } } // setup bind matrix if (data.bindShapeMatrix) { build.bindMatrix = new THREE.Matrix4().fromArray(data.bindShapeMatrix).transpose(); } else { build.bindMatrix = new THREE.Matrix4().identity(); } // process bones and inverse bind matrix data for (i = 0, l = jointSource.array.length; i < l; i++) { const name = jointSource.array[i]; const boneInverse = new THREE.Matrix4().fromArray(inverseSource.array, i * inverseSource.stride).transpose(); build.joints.push({ name: name, boneInverse: boneInverse }); } return build; // array sort function function descending(a, b) { return b.weight - a.weight; } } function getController(id) { return getBuild(library.controllers[id], buildController); } // image function parseImage(xml) { const data = { init_from: getElementsByTagName(xml, 'init_from')[0].textContent }; library.images[xml.getAttribute('id')] = data; } function buildImage(data) { if (data.build !== undefined) return data.build; return data.init_from; } function getImage(id) { const data = library.images[id]; if (data !== undefined) { return getBuild(data, buildImage); } console.warn('THREE.ColladaLoader: Couldn\'t find image with ID:', id); return null; } // effect function parseEffect(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'profile_COMMON': data.profile = parseEffectProfileCOMMON(child); break; } } library.effects[xml.getAttribute('id')] = data; } function parseEffectProfileCOMMON(xml) { const data = { surfaces: {}, samplers: {} }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'newparam': parseEffectNewparam(child, data); break; case 'technique': data.technique = parseEffectTechnique(child); break; case 'extra': data.extra = parseEffectExtra(child); break; } } return data; } function parseEffectNewparam(xml, data) { const sid = xml.getAttribute('sid'); for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'surface': data.surfaces[sid] = parseEffectSurface(child); break; case 'sampler2D': data.samplers[sid] = parseEffectSampler(child); break; } } } function parseEffectSurface(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'init_from': data.init_from = child.textContent; break; } } return data; } function parseEffectSampler(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'source': data.source = child.textContent; break; } } return data; } function parseEffectTechnique(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'constant': case 'lambert': case 'blinn': case 'phong': data.type = child.nodeName; data.parameters = parseEffectParameters(child); break; } } return data; } function parseEffectParameters(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'emission': case 'diffuse': case 'specular': case 'bump': case 'ambient': case 'shininess': case 'transparency': data[child.nodeName] = parseEffectParameter(child); break; case 'transparent': data[child.nodeName] = { opaque: child.getAttribute('opaque'), data: parseEffectParameter(child) }; break; } } return data; } function parseEffectParameter(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'color': data[child.nodeName] = parseFloats(child.textContent); break; case 'float': data[child.nodeName] = parseFloat(child.textContent); break; case 'texture': data[child.nodeName] = { id: child.getAttribute('texture'), extra: parseEffectParameterTexture(child) }; break; } } return data; } function parseEffectParameterTexture(xml) { const data = { technique: {} }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'extra': parseEffectParameterTextureExtra(child, data); break; } } return data; } function parseEffectParameterTextureExtra(xml, data) { for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'technique': parseEffectParameterTextureExtraTechnique(child, data); break; } } } function parseEffectParameterTextureExtraTechnique(xml, data) { for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'repeatU': case 'repeatV': case 'offsetU': case 'offsetV': data.technique[child.nodeName] = parseFloat(child.textContent); break; case 'wrapU': case 'wrapV': // some files have values for wrapU/wrapV which become NaN via parseInt if (child.textContent.toUpperCase() === 'TRUE') { data.technique[child.nodeName] = 1; } else if (child.textContent.toUpperCase() === 'FALSE') { data.technique[child.nodeName] = 0; } else { data.technique[child.nodeName] = parseInt(child.textContent); } break; } } } function parseEffectExtra(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'technique': data.technique = parseEffectExtraTechnique(child); break; } } return data; } function parseEffectExtraTechnique(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'double_sided': data[child.nodeName] = parseInt(child.textContent); break; } } return data; } function buildEffect(data) { return data; } function getEffect(id) { return getBuild(library.effects[id], buildEffect); } // material function parseMaterial(xml) { const data = { name: xml.getAttribute('name') }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'instance_effect': data.url = parseId(child.getAttribute('url')); break; } } library.materials[xml.getAttribute('id')] = data; } function getTextureLoader(image) { let loader; let extension = image.slice((image.lastIndexOf('.') - 1 >>> 0) + 2); // http://www.jstips.co/en/javascript/get-file-extension/ extension = extension.toLowerCase(); switch (extension) { case 'tga': loader = tgaLoader; break; default: loader = textureLoader; } return loader; } function buildMaterial(data) { const effect = getEffect(data.url); const technique = effect.profile.technique; const extra = effect.profile.extra; let material; switch (technique.type) { case 'phong': case 'blinn': material = new THREE.MeshPhongMaterial(); break; case 'lambert': material = new THREE.MeshLambertMaterial(); break; default: material = new THREE.MeshBasicMaterial(); break; } material.name = data.name || ''; function getTexture(textureObject) { const sampler = effect.profile.samplers[textureObject.id]; let image = null; // get image if (sampler !== undefined) { const surface = effect.profile.surfaces[sampler.source]; image = getImage(surface.init_from); } else { console.warn('THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).'); image = getImage(textureObject.id); } // create texture if image is avaiable if (image !== null) { const loader = getTextureLoader(image); if (loader !== undefined) { const texture = loader.load(image); const extra = textureObject.extra; if (extra !== undefined && extra.technique !== undefined && isEmpty(extra.technique) === false) { const technique = extra.technique; texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; texture.offset.set(technique.offsetU || 0, technique.offsetV || 0); texture.repeat.set(technique.repeatU || 1, technique.repeatV || 1); } else { texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; } return texture; } else { console.warn('THREE.ColladaLoader: THREE.Loader for texture %s not found.', image); return null; } } else { console.warn('THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id); return null; } } const parameters = technique.parameters; for (const key in parameters) { const parameter = parameters[key]; switch (key) { case 'diffuse': if (parameter.color) material.color.fromArray(parameter.color); if (parameter.texture) material.map = getTexture(parameter.texture); break; case 'specular': if (parameter.color && material.specular) material.specular.fromArray(parameter.color); if (parameter.texture) material.specularMap = getTexture(parameter.texture); break; case 'bump': if (parameter.texture) material.normalMap = getTexture(parameter.texture); break; case 'ambient': if (parameter.texture) material.lightMap = getTexture(parameter.texture); break; case 'shininess': if (parameter.float && material.shininess) material.shininess = parameter.float; break; case 'emission': if (parameter.color && material.emissive) material.emissive.fromArray(parameter.color); if (parameter.texture) material.emissiveMap = getTexture(parameter.texture); break; } } // let transparent = parameters['transparent']; let transparency = parameters['transparency']; // does not exist but if (transparency === undefined && transparent) { transparency = { float: 1 }; } // does not exist but if (transparent === undefined && transparency) { transparent = { opaque: 'A_ONE', data: { color: [1, 1, 1, 1] } }; } if (transparent && transparency) { // handle case if a texture exists but no color if (transparent.data.texture) { // we do not set an alpha map (see #13792) material.transparent = true; } else { const color = transparent.data.color; switch (transparent.opaque) { case 'A_ONE': material.opacity = color[3] * transparency.float; break; case 'RGB_ZERO': material.opacity = 1 - color[0] * transparency.float; break; case 'A_ZERO': material.opacity = 1 - color[3] * transparency.float; break; case 'RGB_ONE': material.opacity = color[0] * transparency.float; break; default: console.warn('THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque); } if (material.opacity < 1) material.transparent = true; } } // if (extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1) { material.side = THREE.DoubleSide; } return material; } function getMaterial(id) { return getBuild(library.materials[id], buildMaterial); } // camera function parseCamera(xml) { const data = { name: xml.getAttribute('name') }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'optics': data.optics = parseCameraOptics(child); break; } } library.cameras[xml.getAttribute('id')] = data; } function parseCameraOptics(xml) { for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; switch (child.nodeName) { case 'technique_common': return parseCameraTechnique(child); } } return {}; } function parseCameraTechnique(xml) { const data = {}; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; switch (child.nodeName) { case 'perspective': case 'orthographic': data.technique = child.nodeName; data.parameters = parseCameraParameters(child); break; } } return data; } function parseCameraParameters(xml) { const data = {}; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; switch (child.nodeName) { case 'xfov': case 'yfov': case 'xmag': case 'ymag': case 'znear': case 'zfar': case 'aspect_ratio': data[child.nodeName] = parseFloat(child.textContent); break; } } return data; } function buildCamera(data) { let camera; switch (data.optics.technique) { case 'perspective': camera = new THREE.PerspectiveCamera(data.optics.parameters.yfov, data.optics.parameters.aspect_ratio, data.optics.parameters.znear, data.optics.parameters.zfar); break; case 'orthographic': let ymag = data.optics.parameters.ymag; let xmag = data.optics.parameters.xmag; const aspectRatio = data.optics.parameters.aspect_ratio; xmag = xmag === undefined ? ymag * aspectRatio : xmag; ymag = ymag === undefined ? xmag / aspectRatio : ymag; xmag *= 0.5; ymag *= 0.5; camera = new THREE.OrthographicCamera(- xmag, xmag, ymag, - ymag, // left, right, top, bottom data.optics.parameters.znear, data.optics.parameters.zfar); break; default: camera = new THREE.PerspectiveCamera(); break; } camera.name = data.name || ''; return camera; } function getCamera(id) { const data = library.cameras[id]; if (data !== undefined) { return getBuild(data, buildCamera); } console.warn('THREE.ColladaLoader: Couldn\'t find camera with ID:', id); return null; } // light function parseLight(xml) { let data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'technique_common': data = parseLightTechnique(child); break; } } library.lights[xml.getAttribute('id')] = data; } function parseLightTechnique(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'directional': case 'point': case 'spot': case 'ambient': data.technique = child.nodeName; data.parameters = parseLightParameters(child); } } return data; } function parseLightParameters(xml) { const data = {}; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'color': const array = parseFloats(child.textContent); data.color = new THREE.Color().fromArray(array); break; case 'falloff_angle': data.falloffAngle = parseFloat(child.textContent); break; case 'quadratic_attenuation': const f = parseFloat(child.textContent); data.distance = f ? Math.sqrt(1 / f) : 0; break; } } return data; } function buildLight(data) { let light; switch (data.technique) { case 'directional': light = new THREE.DirectionalLight(); break; case 'point': light = new THREE.PointLight(); break; case 'spot': light = new THREE.SpotLight(); break; case 'ambient': light = new THREE.AmbientLight(); break; } if (data.parameters.color) light.color.copy(data.parameters.color); if (data.parameters.distance) light.distance = data.parameters.distance; return light; } function getLight(id) { const data = library.lights[id]; if (data !== undefined) { return getBuild(data, buildLight); } console.warn('THREE.ColladaLoader: Couldn\'t find light with ID:', id); return null; } // geometry function parseGeometry(xml) { const data = { name: xml.getAttribute('name'), sources: {}, vertices: {}, primitives: [] }; const mesh = getElementsByTagName(xml, 'mesh')[0]; // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep if (mesh === undefined) return; for (let i = 0; i < mesh.childNodes.length; i++) { const child = mesh.childNodes[i]; if (child.nodeType !== 1) continue; const id = child.getAttribute('id'); switch (child.nodeName) { case 'source': data.sources[id] = parseSource(child); break; case 'vertices': // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; data.vertices = parseGeometryVertices(child); break; case 'polygons': console.warn('THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName); break; case 'lines': case 'linestrips': case 'polylist': case 'triangles': data.primitives.push(parseGeometryPrimitive(child)); break; default: console.log(child); } } library.geometries[xml.getAttribute('id')] = data; } function parseSource(xml) { const data = { array: [], stride: 3 }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'float_array': data.array = parseFloats(child.textContent); break; case 'Name_array': data.array = parseStrings(child.textContent); break; case 'technique_common': const accessor = getElementsByTagName(child, 'accessor')[0]; if (accessor !== undefined) { data.stride = parseInt(accessor.getAttribute('stride')); } break; } } return data; } function parseGeometryVertices(xml) { const data = {}; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; data[child.getAttribute('semantic')] = parseId(child.getAttribute('source')); } return data; } function parseGeometryPrimitive(xml) { const primitive = { type: xml.nodeName, material: xml.getAttribute('material'), count: parseInt(xml.getAttribute('count')), inputs: {}, stride: 0, hasUV: false }; for (let i = 0, l = xml.childNodes.length; i < l; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'input': const id = parseId(child.getAttribute('source')); const semantic = child.getAttribute('semantic'); const offset = parseInt(child.getAttribute('offset')); const set = parseInt(child.getAttribute('set')); const inputname = set > 0 ? semantic + set : semantic; primitive.inputs[inputname] = { id: id, offset: offset }; primitive.stride = Math.max(primitive.stride, offset + 1); if (semantic === 'TEXCOORD') primitive.hasUV = true; break; case 'vcount': primitive.vcount = parseInts(child.textContent); break; case 'p': primitive.p = parseInts(child.textContent); break; } } return primitive; } function groupPrimitives(primitives) { const build = {}; for (let i = 0; i < primitives.length; i++) { const primitive = primitives[i]; if (build[primitive.type] === undefined) build[primitive.type] = []; build[primitive.type].push(primitive); } return build; } function checkUVCoordinates(primitives) { let count = 0; for (let i = 0, l = primitives.length; i < l; i++) { const primitive = primitives[i]; if (primitive.hasUV === true) { count++; } } if (count > 0 && count < primitives.length) { primitives.uvsNeedsFix = true; } } function buildGeometry(data) { const build = {}; const sources = data.sources; const vertices = data.vertices; const primitives = data.primitives; if (primitives.length === 0) return {}; // our goal is to create one buffer geometry for a single type of primitives // first, we group all primitives by their type const groupedPrimitives = groupPrimitives(primitives); for (const type in groupedPrimitives) { const primitiveType = groupedPrimitives[type]; // second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines) checkUVCoordinates(primitiveType); // third, create a buffer geometry for each type of primitives build[type] = buildGeometryType(primitiveType, sources, vertices); } return build; } function buildGeometryType(primitives, sources, vertices) { const build = {}; const position = { array: [], stride: 0 }; const normal = { array: [], stride: 0 }; const uv = { array: [], stride: 0 }; const uv2 = { array: [], stride: 0 }; const color = { array: [], stride: 0 }; const skinIndex = { array: [], stride: 4 }; const skinWeight = { array: [], stride: 4 }; const geometry = new THREE.BufferGeometry(); const materialKeys = []; let start = 0; for (let p = 0; p < primitives.length; p++) { const primitive = primitives[p]; const inputs = primitive.inputs; // groups let count = 0; switch (primitive.type) { case 'lines': case 'linestrips': count = primitive.count * 2; break; case 'triangles': count = primitive.count * 3; break; case 'polylist': for (let g = 0; g < primitive.count; g++) { const vc = primitive.vcount[g]; switch (vc) { case 3: count += 3; // single triangle break; case 4: count += 6; // quad, subdivided into two triangles break; default: count += (vc - 2) * 3; // polylist with more than four vertices break; } } break; default: console.warn('THREE.ColladaLoader: Unknow primitive type:', primitive.type); } geometry.addGroup(start, count, p); start += count; // material if (primitive.material) { materialKeys.push(primitive.material); } // geometry data for (const name in inputs) { const input = inputs[name]; switch (name) { case 'VERTEX': for (const key in vertices) { const id = vertices[key]; switch (key) { case 'POSITION': const prevLength = position.array.length; buildGeometryData(primitive, sources[id], input.offset, position.array); position.stride = sources[id].stride; if (sources.skinWeights && sources.skinIndices) { buildGeometryData(primitive, sources.skinIndices, input.offset, skinIndex.array); buildGeometryData(primitive, sources.skinWeights, input.offset, skinWeight.array); } // see #3803 if (primitive.hasUV === false && primitives.uvsNeedsFix === true) { const count = (position.array.length - prevLength) / position.stride; for (let i = 0; i < count; i++) { // fill missing uv coordinates uv.array.push(0, 0); } } break; case 'NORMAL': buildGeometryData(primitive, sources[id], input.offset, normal.array); normal.stride = sources[id].stride; break; case 'COLOR': buildGeometryData(primitive, sources[id], input.offset, color.array); color.stride = sources[id].stride; break; case 'TEXCOORD': buildGeometryData(primitive, sources[id], input.offset, uv.array); uv.stride = sources[id].stride; break; case 'TEXCOORD1': buildGeometryData(primitive, sources[id], input.offset, uv2.array); uv.stride = sources[id].stride; break; default: console.warn('THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key); } } break; case 'NORMAL': buildGeometryData(primitive, sources[input.id], input.offset, normal.array); normal.stride = sources[input.id].stride; break; case 'COLOR': buildGeometryData(primitive, sources[input.id], input.offset, color.array); color.stride = sources[input.id].stride; break; case 'TEXCOORD': buildGeometryData(primitive, sources[input.id], input.offset, uv.array); uv.stride = sources[input.id].stride; break; case 'TEXCOORD1': buildGeometryData(primitive, sources[input.id], input.offset, uv2.array); uv2.stride = sources[input.id].stride; break; } } } // build geometry if (position.array.length > 0) geometry.setAttribute('position', new THREE.Float32BufferAttribute(position.array, position.stride)); if (normal.array.length > 0) geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normal.array, normal.stride)); if (color.array.length > 0) geometry.setAttribute('color', new THREE.Float32BufferAttribute(color.array, color.stride)); if (uv.array.length > 0) geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv.array, uv.stride)); if (uv2.array.length > 0) geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(uv2.array, uv2.stride)); if (skinIndex.array.length > 0) geometry.setAttribute('skinIndex', new THREE.Float32BufferAttribute(skinIndex.array, skinIndex.stride)); if (skinWeight.array.length > 0) geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(skinWeight.array, skinWeight.stride)); build.data = geometry; build.type = primitives[0].type; build.materialKeys = materialKeys; return build; } function buildGeometryData(primitive, source, offset, array) { const indices = primitive.p; const stride = primitive.stride; const vcount = primitive.vcount; function pushVector(i) { let index = indices[i + offset] * sourceStride; const length = index + sourceStride; for (; index < length; index++) { array.push(sourceArray[index]); } } const sourceArray = source.array; const sourceStride = source.stride; if (primitive.vcount !== undefined) { let index = 0; for (let i = 0, l = vcount.length; i < l; i++) { const count = vcount[i]; if (count === 4) { const a = index + stride * 0; const b = index + stride * 1; const c = index + stride * 2; const d = index + stride * 3; pushVector(a); pushVector(b); pushVector(d); pushVector(b); pushVector(c); pushVector(d); } else if (count === 3) { const a = index + stride * 0; const b = index + stride * 1; const c = index + stride * 2; pushVector(a); pushVector(b); pushVector(c); } else if (count > 4) { for (let k = 1, kl = count - 2; k <= kl; k++) { const a = index + stride * 0; const b = index + stride * k; const c = index + stride * (k + 1); pushVector(a); pushVector(b); pushVector(c); } } index += stride * count; } } else { for (let i = 0, l = indices.length; i < l; i += stride) { pushVector(i); } } } function getGeometry(id) { return getBuild(library.geometries[id], buildGeometry); } // kinematics function parseKinematicsModel(xml) { const data = { name: xml.getAttribute('name') || '', joints: {}, links: [] }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'technique_common': parseKinematicsTechniqueCommon(child, data); break; } } library.kinematicsModels[xml.getAttribute('id')] = data; } function buildKinematicsModel(data) { if (data.build !== undefined) return data.build; return data; } function getKinematicsModel(id) { return getBuild(library.kinematicsModels[id], buildKinematicsModel); } function parseKinematicsTechniqueCommon(xml, data) { for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'joint': data.joints[child.getAttribute('sid')] = parseKinematicsJoint(child); break; case 'link': data.links.push(parseKinematicsLink(child)); break; } } } function parseKinematicsJoint(xml) { let data; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'prismatic': case 'revolute': data = parseKinematicsJointParameter(child); break; } } return data; } function parseKinematicsJointParameter(xml) { const data = { sid: xml.getAttribute('sid'), name: xml.getAttribute('name') || '', axis: new THREE.Vector3(), limits: { min: 0, max: 0 }, type: xml.nodeName, static: false, zeroPosition: 0, middlePosition: 0 }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'axis': const array = parseFloats(child.textContent); data.axis.fromArray(array); break; case 'limits': const max = child.getElementsByTagName('max')[0]; const min = child.getElementsByTagName('min')[0]; data.limits.max = parseFloat(max.textContent); data.limits.min = parseFloat(min.textContent); break; } } // if min is equal to or greater than max, consider the joint static if (data.limits.min >= data.limits.max) { data.static = true; } // calculate middle position data.middlePosition = (data.limits.min + data.limits.max) / 2.0; return data; } function parseKinematicsLink(xml) { const data = { sid: xml.getAttribute('sid'), name: xml.getAttribute('name') || '', attachments: [], transforms: [] }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'attachment_full': data.attachments.push(parseKinematicsAttachment(child)); break; case 'matrix': case 'translate': case 'rotate': data.transforms.push(parseKinematicsTransform(child)); break; } } return data; } function parseKinematicsAttachment(xml) { const data = { joint: xml.getAttribute('joint').split('/').pop(), transforms: [], links: [] }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'link': data.links.push(parseKinematicsLink(child)); break; case 'matrix': case 'translate': case 'rotate': data.transforms.push(parseKinematicsTransform(child)); break; } } return data; } function parseKinematicsTransform(xml) { const data = { type: xml.nodeName }; const array = parseFloats(xml.textContent); switch (data.type) { case 'matrix': data.obj = new THREE.Matrix4(); data.obj.fromArray(array).transpose(); break; case 'translate': data.obj = new THREE.Vector3(); data.obj.fromArray(array); break; case 'rotate': data.obj = new THREE.Vector3(); data.obj.fromArray(array); data.angle = THREE.MathUtils.degToRad(array[3]); break; } return data; } // physics function parsePhysicsModel(xml) { const data = { name: xml.getAttribute('name') || '', rigidBodies: {} }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'rigid_body': data.rigidBodies[child.getAttribute('name')] = {}; parsePhysicsRigidBody(child, data.rigidBodies[child.getAttribute('name')]); break; } } library.physicsModels[xml.getAttribute('id')] = data; } function parsePhysicsRigidBody(xml, data) { for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'technique_common': parsePhysicsTechniqueCommon(child, data); break; } } } function parsePhysicsTechniqueCommon(xml, data) { for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'inertia': data.inertia = parseFloats(child.textContent); break; case 'mass': data.mass = parseFloats(child.textContent)[0]; break; } } } // scene function parseKinematicsScene(xml) { const data = { bindJointAxis: [] }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'bind_joint_axis': data.bindJointAxis.push(parseKinematicsBindJointAxis(child)); break; } } library.kinematicsScenes[parseId(xml.getAttribute('url'))] = data; } function parseKinematicsBindJointAxis(xml) { const data = { target: xml.getAttribute('target').split('/').pop() }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; switch (child.nodeName) { case 'axis': const param = child.getElementsByTagName('param')[0]; data.axis = param.textContent; const tmpJointIndex = data.axis.split('inst_').pop().split('axis')[0]; data.jointIndex = tmpJointIndex.substr(0, tmpJointIndex.length - 1); break; } } return data; } function buildKinematicsScene(data) { if (data.build !== undefined) return data.build; return data; } function getKinematicsScene(id) { return getBuild(library.kinematicsScenes[id], buildKinematicsScene); } function setupKinematics() { const kinematicsModelId = Object.keys(library.kinematicsModels)[0]; const kinematicsSceneId = Object.keys(library.kinematicsScenes)[0]; const visualSceneId = Object.keys(library.visualScenes)[0]; if (kinematicsModelId === undefined || kinematicsSceneId === undefined) return; const kinematicsModel = getKinematicsModel(kinematicsModelId); const kinematicsScene = getKinematicsScene(kinematicsSceneId); const visualScene = getVisualScene(visualSceneId); const bindJointAxis = kinematicsScene.bindJointAxis; const jointMap = {}; for (let i = 0, l = bindJointAxis.length; i < l; i++) { const axis = bindJointAxis[i]; // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' const targetElement = collada.querySelector('[sid="' + axis.target + '"]'); if (targetElement) { // get the parent of the transform element const parentVisualElement = targetElement.parentElement; // connect the joint of the kinematics model with the element in the visual scene connect(axis.jointIndex, parentVisualElement); } } function connect(jointIndex, visualElement) { const visualElementName = visualElement.getAttribute('name'); const joint = kinematicsModel.joints[jointIndex]; visualScene.traverse(function (object) { if (object.name === visualElementName) { jointMap[jointIndex] = { object: object, transforms: buildTransformList(visualElement), joint: joint, position: joint.zeroPosition }; } }); } const m0 = new THREE.Matrix4(); kinematics = { joints: kinematicsModel && kinematicsModel.joints, getJointValue: function (jointIndex) { const jointData = jointMap[jointIndex]; if (jointData) { return jointData.position; } else { console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.'); } }, setJointValue: function (jointIndex, value) { const jointData = jointMap[jointIndex]; if (jointData) { const joint = jointData.joint; if (value > joint.limits.max || value < joint.limits.min) { console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').'); } else if (joint.static) { console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' is static.'); } else { const object = jointData.object; const axis = joint.axis; const transforms = jointData.transforms; matrix.identity(); // each update, we have to apply all transforms in the correct order for (let i = 0; i < transforms.length; i++) { const transform = transforms[i]; // if there is a connection of the transform node with a joint, apply the joint value if (transform.sid && transform.sid.indexOf(jointIndex) !== - 1) { switch (joint.type) { case 'revolute': matrix.multiply(m0.makeRotationAxis(axis, THREE.MathUtils.degToRad(value))); break; case 'prismatic': matrix.multiply(m0.makeTranslation(axis.x * value, axis.y * value, axis.z * value)); break; default: console.warn('THREE.ColladaLoader: Unknown joint type: ' + joint.type); break; } } else { switch (transform.type) { case 'matrix': matrix.multiply(transform.obj); break; case 'translate': matrix.multiply(m0.makeTranslation(transform.obj.x, transform.obj.y, transform.obj.z)); break; case 'scale': matrix.scale(transform.obj); break; case 'rotate': matrix.multiply(m0.makeRotationAxis(transform.obj, transform.angle)); break; } } } object.matrix.copy(matrix); object.matrix.decompose(object.position, object.quaternion, object.scale); jointMap[jointIndex].position = value; } } else { console.log('THREE.ColladaLoader: ' + jointIndex + ' does not exist.'); } } }; } function buildTransformList(node) { const transforms = []; const xml = collada.querySelector('[id="' + node.id + '"]'); for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; let array, vector; switch (child.nodeName) { case 'matrix': array = parseFloats(child.textContent); const matrix = new THREE.Matrix4().fromArray(array).transpose(); transforms.push({ sid: child.getAttribute('sid'), type: child.nodeName, obj: matrix }); break; case 'translate': case 'scale': array = parseFloats(child.textContent); vector = new THREE.Vector3().fromArray(array); transforms.push({ sid: child.getAttribute('sid'), type: child.nodeName, obj: vector }); break; case 'rotate': array = parseFloats(child.textContent); vector = new THREE.Vector3().fromArray(array); const angle = THREE.MathUtils.degToRad(array[3]); transforms.push({ sid: child.getAttribute('sid'), type: child.nodeName, obj: vector, angle: angle }); break; } } return transforms; } // nodes function prepareNodes(xml) { const elements = xml.getElementsByTagName('node'); // ensure all node elements have id attributes for (let i = 0; i < elements.length; i++) { const element = elements[i]; if (element.hasAttribute('id') === false) { element.setAttribute('id', generateId()); } } } const matrix = new THREE.Matrix4(); const vector = new THREE.Vector3(); function parseNode(xml) { const data = { name: xml.getAttribute('name') || '', type: xml.getAttribute('type'), id: xml.getAttribute('id'), sid: xml.getAttribute('sid'), matrix: new THREE.Matrix4(), nodes: [], instanceCameras: [], instanceControllers: [], instanceLights: [], instanceGeometries: [], instanceNodes: [], transforms: {} }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; if (child.nodeType !== 1) continue; let array; switch (child.nodeName) { case 'node': data.nodes.push(child.getAttribute('id')); parseNode(child); break; case 'instance_camera': data.instanceCameras.push(parseId(child.getAttribute('url'))); break; case 'instance_controller': data.instanceControllers.push(parseNodeInstance(child)); break; case 'instance_light': data.instanceLights.push(parseId(child.getAttribute('url'))); break; case 'instance_geometry': data.instanceGeometries.push(parseNodeInstance(child)); break; case 'instance_node': data.instanceNodes.push(parseId(child.getAttribute('url'))); break; case 'matrix': array = parseFloats(child.textContent); data.matrix.multiply(matrix.fromArray(array).transpose()); data.transforms[child.getAttribute('sid')] = child.nodeName; break; case 'translate': array = parseFloats(child.textContent); vector.fromArray(array); data.matrix.multiply(matrix.makeTranslation(vector.x, vector.y, vector.z)); data.transforms[child.getAttribute('sid')] = child.nodeName; break; case 'rotate': array = parseFloats(child.textContent); const angle = THREE.MathUtils.degToRad(array[3]); data.matrix.multiply(matrix.makeRotationAxis(vector.fromArray(array), angle)); data.transforms[child.getAttribute('sid')] = child.nodeName; break; case 'scale': array = parseFloats(child.textContent); data.matrix.scale(vector.fromArray(array)); data.transforms[child.getAttribute('sid')] = child.nodeName; break; case 'extra': break; default: console.log(child); } } if (hasNode(data.id)) { console.warn('THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id); } else { library.nodes[data.id] = data; } return data; } function parseNodeInstance(xml) { const data = { id: parseId(xml.getAttribute('url')), materials: {}, skeletons: [] }; for (let i = 0; i < xml.childNodes.length; i++) { const child = xml.childNodes[i]; switch (child.nodeName) { case 'bind_material': const instances = child.getElementsByTagName('instance_material'); for (let j = 0; j < instances.length; j++) { const instance = instances[j]; const symbol = instance.getAttribute('symbol'); const target = instance.getAttribute('target'); data.materials[symbol] = parseId(target); } break; case 'skeleton': data.skeletons.push(parseId(child.textContent)); break; default: break; } } return data; } function buildSkeleton(skeletons, joints) { const boneData = []; const sortedBoneData = []; let i, j, data; // a skeleton can have multiple root bones. collada expresses this // situtation with multiple "skeleton" tags per controller instance for (i = 0; i < skeletons.length; i++) { const skeleton = skeletons[i]; let root; if (hasNode(skeleton)) { root = getNode(skeleton); buildBoneHierarchy(root, joints, boneData); } else if (hasVisualScene(skeleton)) { // handle case where the skeleton refers to the visual scene (#13335) const visualScene = library.visualScenes[skeleton]; const children = visualScene.children; for (let j = 0; j < children.length; j++) { const child = children[j]; if (child.type === 'JOINT') { const root = getNode(child.id); buildBoneHierarchy(root, joints, boneData); } } } else { console.error('THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton); } } // sort bone data (the order is defined in the corresponding controller) for (i = 0; i < joints.length; i++) { for (j = 0; j < boneData.length; j++) { data = boneData[j]; if (data.bone.name === joints[i].name) { sortedBoneData[i] = data; data.processed = true; break; } } } // add unprocessed bone data at the end of the list for (i = 0; i < boneData.length; i++) { data = boneData[i]; if (data.processed === false) { sortedBoneData.push(data); data.processed = true; } } // setup arrays for skeleton creation const bones = []; const boneInverses = []; for (i = 0; i < sortedBoneData.length; i++) { data = sortedBoneData[i]; bones.push(data.bone); boneInverses.push(data.boneInverse); } return new THREE.Skeleton(bones, boneInverses); } function buildBoneHierarchy(root, joints, boneData) { // setup bone data from visual scene root.traverse(function (object) { if (object.isBone === true) { let boneInverse; // retrieve the boneInverse from the controller data for (let i = 0; i < joints.length; i++) { const joint = joints[i]; if (joint.name === object.name) { boneInverse = joint.boneInverse; break; } } if (boneInverse === undefined) { // Unfortunately, there can be joints in the visual scene that are not part of the // corresponding controller. In this case, we have to create a dummy boneInverse matrix // for the respective bone. This bone won't affect any vertices, because there are no skin indices // and weights defined for it. But we still have to add the bone to the sorted bone list in order to // ensure a correct animation of the model. boneInverse = new THREE.Matrix4(); } boneData.push({ bone: object, boneInverse: boneInverse, processed: false }); } }); } function buildNode(data) { const objects = []; const matrix = data.matrix; const nodes = data.nodes; const type = data.type; const instanceCameras = data.instanceCameras; const instanceControllers = data.instanceControllers; const instanceLights = data.instanceLights; const instanceGeometries = data.instanceGeometries; const instanceNodes = data.instanceNodes; // nodes for (let i = 0, l = nodes.length; i < l; i++) { objects.push(getNode(nodes[i])); } // instance cameras for (let i = 0, l = instanceCameras.length; i < l; i++) { const instanceCamera = getCamera(instanceCameras[i]); if (instanceCamera !== null) { objects.push(instanceCamera.clone()); } } // instance controllers for (let i = 0, l = instanceControllers.length; i < l; i++) { const instance = instanceControllers[i]; const controller = getController(instance.id); const geometries = getGeometry(controller.id); const newObjects = buildObjects(geometries, instance.materials); const skeletons = instance.skeletons; const joints = controller.skin.joints; const skeleton = buildSkeleton(skeletons, joints); for (let j = 0, jl = newObjects.length; j < jl; j++) { const object = newObjects[j]; if (object.isSkinnedMesh) { object.bind(skeleton, controller.skin.bindMatrix); object.normalizeSkinWeights(); } objects.push(object); } } // instance lights for (let i = 0, l = instanceLights.length; i < l; i++) { const instanceLight = getLight(instanceLights[i]); if (instanceLight !== null) { objects.push(instanceLight.clone()); } } // instance geometries for (let i = 0, l = instanceGeometries.length; i < l; i++) { const instance = instanceGeometries[i]; // a single geometry instance in collada can lead to multiple object3Ds. // this is the case when primitives are combined like triangles and lines const geometries = getGeometry(instance.id); const newObjects = buildObjects(geometries, instance.materials); for (let j = 0, jl = newObjects.length; j < jl; j++) { objects.push(newObjects[j]); } } // instance nodes for (let i = 0, l = instanceNodes.length; i < l; i++) { objects.push(getNode(instanceNodes[i]).clone()); } let object; if (nodes.length === 0 && objects.length === 1) { object = objects[0]; } else { object = type === 'JOINT' ? new THREE.Bone() : new THREE.Group(); for (let i = 0; i < objects.length; i++) { object.add(objects[i]); } } object.name = type === 'JOINT' ? data.sid : data.name; object.matrix.copy(matrix); object.matrix.decompose(object.position, object.quaternion, object.scale); return object; } const fallbackMaterial = new THREE.MeshBasicMaterial({ color: 0xff00ff }); function resolveMaterialBinding(keys, instanceMaterials) { const materials = []; for (let i = 0, l = keys.length; i < l; i++) { const id = instanceMaterials[keys[i]]; if (id === undefined) { console.warn('THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[i]); materials.push(fallbackMaterial); } else { materials.push(getMaterial(id)); } } return materials; } function buildObjects(geometries, instanceMaterials) { const objects = []; for (const type in geometries) { const geometry = geometries[type]; const materials = resolveMaterialBinding(geometry.materialKeys, instanceMaterials); // handle case if no materials are defined if (materials.length === 0) { if (type === 'lines' || type === 'linestrips') { materials.push(new THREE.LineBasicMaterial()); } else { materials.push(new THREE.MeshPhongMaterial()); } } // regard skinning const skinning = geometry.data.attributes.skinIndex !== undefined; // choose between a single or multi materials (material array) const material = materials.length === 1 ? materials[0] : materials; // now create a specific 3D object let object; switch (type) { case 'lines': object = new THREE.LineSegments(geometry.data, material); break; case 'linestrips': object = new THREE.Line(geometry.data, material); break; case 'triangles': case 'polylist': if (skinning) { object = new THREE.SkinnedMesh(geometry.data, material); } else { object = new THREE.Mesh(geometry.data, material); } break; } objects.push(object); } return objects; } function hasNode(id) { return library.nodes[id] !== undefined; } function getNode(id) { return getBuild(library.nodes[id], buildNode); } // visual scenes function parseVisualScene(xml) { const data = { name: xml.getAttribute('name'), children: [] }; prepareNodes(xml); const elements = getElementsByTagName(xml, 'node'); for (let i = 0; i < elements.length; i++) { data.children.push(parseNode(elements[i])); } library.visualScenes[xml.getAttribute('id')] = data; } function buildVisualScene(data) { const group = new THREE.Group(); group.name = data.name; const children = data.children; for (let i = 0; i < children.length; i++) { const child = children[i]; group.add(getNode(child.id)); } return group; } function hasVisualScene(id) { return library.visualScenes[id] !== undefined; } function getVisualScene(id) { return getBuild(library.visualScenes[id], buildVisualScene); } // scenes function parseScene(xml) { const instance = getElementsByTagName(xml, 'instance_visual_scene')[0]; return getVisualScene(parseId(instance.getAttribute('url'))); } function setupAnimations() { const clips = library.clips; if (isEmpty(clips) === true) { if (isEmpty(library.animations) === false) { // if there are animations but no clips, we create a default clip for playback const tracks = []; for (const id in library.animations) { const animationTracks = getAnimation(id); for (let i = 0, l = animationTracks.length; i < l; i++) { tracks.push(animationTracks[i]); } } animations.push(new THREE.AnimationClip('default', - 1, tracks)); } } else { for (const id in clips) { animations.push(getAnimationClip(id)); } } } // convert the parser error element into text with each child elements text // separated by new lines. function parserErrorToText(parserError) { let result = ''; const stack = [parserError]; while (stack.length) { const node = stack.shift(); if (node.nodeType === Node.TEXT_NODE) { result += node.textContent; } else { result += '\n'; stack.push.apply(stack, node.childNodes); } } return result.trim(); } if (text.length === 0) { return { scene: new THREE.Scene() }; } const xml = new DOMParser().parseFromString(text, 'application/xml'); const collada = getElementsByTagName(xml, 'COLLADA')[0]; const parserError = xml.getElementsByTagName('parsererror')[0]; if (parserError !== undefined) { // Chrome will return parser error with a div in it const errorElement = getElementsByTagName(parserError, 'div')[0]; let errorText; if (errorElement) { errorText = errorElement.textContent; } else { errorText = parserErrorToText(parserError); } console.error('THREE.ColladaLoader: Failed to parse collada file.\n', errorText); return null; } // metadata const version = collada.getAttribute('version'); console.log('THREE.ColladaLoader: File version', version); const asset = parseAsset(getElementsByTagName(collada, 'asset')[0]); const textureLoader = new THREE.TextureLoader(this.manager); textureLoader.setPath(this.resourcePath || path).setCrossOrigin(this.crossOrigin); let tgaLoader; if (THREE.TGALoader) { tgaLoader = new THREE.TGALoader(this.manager); tgaLoader.setPath(this.resourcePath || path); } // const animations = []; let kinematics = {}; let count = 0; // const library = { animations: {}, clips: {}, controllers: {}, images: {}, effects: {}, materials: {}, cameras: {}, lights: {}, geometries: {}, nodes: {}, visualScenes: {}, kinematicsModels: {}, physicsModels: {}, kinematicsScenes: {} }; parseLibrary(collada, 'library_animations', 'animation', parseAnimation); parseLibrary(collada, 'library_animation_clips', 'animation_clip', parseAnimationClip); parseLibrary(collada, 'library_controllers', 'controller', parseController); parseLibrary(collada, 'library_images', 'image', parseImage); parseLibrary(collada, 'library_effects', 'effect', parseEffect); parseLibrary(collada, 'library_materials', 'material', parseMaterial); parseLibrary(collada, 'library_cameras', 'camera', parseCamera); parseLibrary(collada, 'library_lights', 'light', parseLight); parseLibrary(collada, 'library_geometries', 'geometry', parseGeometry); parseLibrary(collada, 'library_nodes', 'node', parseNode); parseLibrary(collada, 'library_visual_scenes', 'visual_scene', parseVisualScene); parseLibrary(collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel); parseLibrary(collada, 'library_physics_models', 'physics_model', parsePhysicsModel); parseLibrary(collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene); buildLibrary(library.animations, buildAnimation); buildLibrary(library.clips, buildAnimationClip); buildLibrary(library.controllers, buildController); buildLibrary(library.images, buildImage); buildLibrary(library.effects, buildEffect); buildLibrary(library.materials, buildMaterial); buildLibrary(library.cameras, buildCamera); buildLibrary(library.lights, buildLight); buildLibrary(library.geometries, buildGeometry); buildLibrary(library.visualScenes, buildVisualScene); setupAnimations(); setupKinematics(); const scene = parseScene(getElementsByTagName(collada, 'scene')[0]); scene.animations = animations; if (asset.upAxis === 'Z_UP') { scene.quaternion.setFromEuler(new THREE.Euler(- Math.PI / 2, 0, 0)); } scene.scale.multiplyScalar(asset.unit); return { get animations() { console.warn('THREE.ColladaLoader: Please access animations over scene.animations now.'); return animations; }, kinematics: kinematics, library: library, scene: scene }; } } THREE.ColladaLoader = ColladaLoader; })(); module.exports = exports = THREE.ColladaLoader; },{"../../three.js":25}],17:[function(require,module,exports){ const THREE = require('../../three.js'); const fflate = require('../fflate.min.js'); /**co * @author Kyle-Larson https://github.com/Kyle-Larson * @author Takahiro https://github.com/takahirox * @author Lewy Blue https://github.com/looeee * * Loader loads FBX file and generates Group representing FBX scene. * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format * Versions lower than this may load but will probably have errors * * Needs Support: * Morph normals / blend shape normals * * FBX format references: * https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure * http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference) * * Binary format specification: * https://code.blender.org/2013/08/fbx-binary-file-format-specification/ */ /** * Loader loads FBX file and generates Group representing FBX scene. * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format * Versions lower than this may load but will probably have errors * * Needs Support: * Morph normals / blend shape normals * * FBX format references: * https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure * http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference) * * Binary format specification: * https://code.blender.org/2013/08/fbx-binary-file-format-specification/ */ (function () { /** * THREE.Loader loads FBX file and generates THREE.Group representing FBX scene. * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format * Versions lower than this may load but will probably have errors * * Needs Support: * Morph normals / blend shape normals * * FBX format references: * https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure * http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference) * * Binary format specification: * https://code.blender.org/2013/08/fbx-binary-file-format-specification/ */ let fbxTree; let connections; let sceneGraph; class FBXLoader extends THREE.Loader { constructor(manager) { super(manager); } load(url, onLoad, onProgress, onError) { const scope = this; const path = scope.path === '' ? THREE.LoaderUtils.extractUrlBase(url) : scope.path; const loader = new THREE.FileLoader(this.manager); loader.setPath(scope.path); loader.setResponseType('arraybuffer'); loader.setRequestHeader(scope.requestHeader); loader.setWithCredentials(scope.withCredentials); loader.load(url, function (buffer) { try { onLoad(scope.parse(buffer, path)); } catch (e) { if (onError) { onError(e); } else { console.error(e); } scope.manager.itemError(url); } }, onProgress, onError); } parse(FBXBuffer, path) { if (isFbxFormatBinary(FBXBuffer)) { fbxTree = new BinaryParser().parse(FBXBuffer); } else { const FBXText = convertArrayBufferToString(FBXBuffer); if (!isFbxFormatASCII(FBXText)) { throw new Error('THREE.FBXLoader: Unknown format.'); } if (getFbxVersion(FBXText) < 7000) { throw new Error('THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion(FBXText)); } fbxTree = new TextParser().parse(FBXText); } // console.log( fbxTree ); const textureLoader = new THREE.TextureLoader(this.manager).setPath(this.resourcePath || path).setCrossOrigin(this.crossOrigin); return new FBXTreeParser(textureLoader, this.manager).parse(fbxTree); } } // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group class FBXTreeParser { constructor(textureLoader, manager) { this.textureLoader = textureLoader; this.manager = manager; } parse() { connections = this.parseConnections(); const images = this.parseImages(); const textures = this.parseTextures(images); const materials = this.parseMaterials(textures); const deformers = this.parseDeformers(); const geometryMap = new GeometryParser().parse(deformers); this.parseScene(deformers, geometryMap, materials); return sceneGraph; } // Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry ) // and details the connection type parseConnections() { const connectionMap = new Map(); if ('Connections' in fbxTree) { const rawConnections = fbxTree.Connections.connections; rawConnections.forEach(function (rawConnection) { const fromID = rawConnection[0]; const toID = rawConnection[1]; const relationship = rawConnection[2]; if (!connectionMap.has(fromID)) { connectionMap.set(fromID, { parents: [], children: [] }); } const parentRelationship = { ID: toID, relationship: relationship }; connectionMap.get(fromID).parents.push(parentRelationship); if (!connectionMap.has(toID)) { connectionMap.set(toID, { parents: [], children: [] }); } const childRelationship = { ID: fromID, relationship: relationship }; connectionMap.get(toID).children.push(childRelationship); }); } return connectionMap; } // Parse FBXTree.Objects.Video for embedded image data // These images are connected to textures in FBXTree.Objects.Textures // via FBXTree.Connections. parseImages() { const images = {}; const blobs = {}; if ('Video' in fbxTree.Objects) { const videoNodes = fbxTree.Objects.Video; for (const nodeID in videoNodes) { const videoNode = videoNodes[nodeID]; const id = parseInt(nodeID); images[id] = videoNode.RelativeFilename || videoNode.Filename; // raw image data is in videoNode.Content if ('Content' in videoNode) { const arrayBufferContent = videoNode.Content instanceof ArrayBuffer && videoNode.Content.byteLength > 0; const base64Content = typeof videoNode.Content === 'string' && videoNode.Content !== ''; if (arrayBufferContent || base64Content) { const image = this.parseImage(videoNodes[nodeID]); blobs[videoNode.RelativeFilename || videoNode.Filename] = image; } } } } for (const id in images) { const filename = images[id]; if (blobs[filename] !== undefined) images[id] = blobs[filename]; else images[id] = images[id].split('\\').pop(); } return images; } // Parse embedded image data in FBXTree.Video.Content parseImage(videoNode) { const content = videoNode.Content; const fileName = videoNode.RelativeFilename || videoNode.Filename; const extension = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase(); let type; switch (extension) { case 'bmp': type = 'image/bmp'; break; case 'jpg': case 'jpeg': type = 'image/jpeg'; break; case 'png': type = 'image/png'; break; case 'tif': type = 'image/tiff'; break; case 'tga': if (this.manager.getHandler('.tga') === null) { console.warn('FBXLoader: TGA loader not found, skipping ', fileName); } type = 'image/tga'; break; default: console.warn('FBXLoader: Image type "' + extension + '" is not supported.'); return; } if (typeof content === 'string') { // ASCII format return 'data:' + type + ';base64,' + content; } else { // Binary Format const array = new Uint8Array(content); return window.URL.createObjectURL(new Blob([array], { type: type })); } } // Parse nodes in FBXTree.Objects.Texture // These contain details such as UV scaling, cropping, rotation etc and are connected // to images in FBXTree.Objects.Video parseTextures(images) { const textureMap = new Map(); if ('Texture' in fbxTree.Objects) { const textureNodes = fbxTree.Objects.Texture; for (const nodeID in textureNodes) { const texture = this.parseTexture(textureNodes[nodeID], images); textureMap.set(parseInt(nodeID), texture); } } return textureMap; } // Parse individual node in FBXTree.Objects.Texture parseTexture(textureNode, images) { const texture = this.loadTexture(textureNode, images); texture.ID = textureNode.id; texture.name = textureNode.attrName; const wrapModeU = textureNode.WrapModeU; const wrapModeV = textureNode.WrapModeV; const valueU = wrapModeU !== undefined ? wrapModeU.value : 0; const valueV = wrapModeV !== undefined ? wrapModeV.value : 0; // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a // 0: repeat(default), 1: clamp texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; if ('Scaling' in textureNode) { const values = textureNode.Scaling.value; texture.repeat.x = values[0]; texture.repeat.y = values[1]; } return texture; } // load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader loadTexture(textureNode, images) { let fileName; const currentPath = this.textureLoader.path; const children = connections.get(textureNode.id).children; if (children !== undefined && children.length > 0 && images[children[0].ID] !== undefined) { fileName = images[children[0].ID]; if (fileName.indexOf('blob:') === 0 || fileName.indexOf('data:') === 0) { this.textureLoader.setPath(undefined); } } let texture; const extension = textureNode.FileName.slice(- 3).toLowerCase(); if (extension === 'tga') { const loader = this.manager.getHandler('.tga'); if (loader === null) { console.warn('FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename); texture = new THREE.Texture(); } else { loader.setPath(this.textureLoader.path); texture = loader.load(fileName); } } else if (extension === 'psd') { console.warn('FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename); texture = new THREE.Texture(); } else { texture = this.textureLoader.load(fileName); } this.textureLoader.setPath(currentPath); return texture; } // Parse nodes in FBXTree.Objects.Material parseMaterials(textureMap) { const materialMap = new Map(); if ('Material' in fbxTree.Objects) { const materialNodes = fbxTree.Objects.Material; for (const nodeID in materialNodes) { const material = this.parseMaterial(materialNodes[nodeID], textureMap); if (material !== null) materialMap.set(parseInt(nodeID), material); } } return materialMap; } // Parse single node in FBXTree.Objects.Material // Materials are connected to texture maps in FBXTree.Objects.Textures // FBX format currently only supports Lambert and Phong shading models parseMaterial(materialNode, textureMap) { const ID = materialNode.id; const name = materialNode.attrName; let type = materialNode.ShadingModel; // Case where FBX wraps shading model in property object. if (typeof type === 'object') { type = type.value; } // Ignore unused materials which don't have any connections. if (!connections.has(ID)) return null; const parameters = this.parseParameters(materialNode, textureMap, ID); let material; switch (type.toLowerCase()) { case 'phong': material = new THREE.MeshPhongMaterial(); break; case 'lambert': material = new THREE.MeshLambertMaterial(); break; default: console.warn('THREE.FBXLoader: unknown material type "%s". Defaulting to THREE.MeshPhongMaterial.', type); material = new THREE.MeshPhongMaterial(); break; } material.setValues(parameters); material.name = name; return material; } // Parse FBX material and return parameters suitable for a three.js material // Also parse the texture map and return any textures associated with the material parseParameters(materialNode, textureMap, ID) { const parameters = {}; if (materialNode.BumpFactor) { parameters.bumpScale = materialNode.BumpFactor.value; } if (materialNode.Diffuse) { parameters.color = new THREE.Color().fromArray(materialNode.Diffuse.value); } else if (materialNode.DiffuseColor && (materialNode.DiffuseColor.type === 'Color' || materialNode.DiffuseColor.type === 'ColorRGB')) { // The blender exporter exports diffuse here instead of in materialNode.Diffuse parameters.color = new THREE.Color().fromArray(materialNode.DiffuseColor.value); } if (materialNode.DisplacementFactor) { parameters.displacementScale = materialNode.DisplacementFactor.value; } if (materialNode.Emissive) { parameters.emissive = new THREE.Color().fromArray(materialNode.Emissive.value); } else if (materialNode.EmissiveColor && (materialNode.EmissiveColor.type === 'Color' || materialNode.EmissiveColor.type === 'ColorRGB')) { // The blender exporter exports emissive color here instead of in materialNode.Emissive parameters.emissive = new THREE.Color().fromArray(materialNode.EmissiveColor.value); } if (materialNode.EmissiveFactor) { parameters.emissiveIntensity = parseFloat(materialNode.EmissiveFactor.value); } if (materialNode.Opacity) { parameters.opacity = parseFloat(materialNode.Opacity.value); } if (parameters.opacity < 1.0) { parameters.transparent = true; } if (materialNode.ReflectionFactor) { parameters.reflectivity = materialNode.ReflectionFactor.value; } if (materialNode.Shininess) { parameters.shininess = materialNode.Shininess.value; } if (materialNode.Specular) { parameters.specular = new THREE.Color().fromArray(materialNode.Specular.value); } else if (materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color') { // The blender exporter exports specular color here instead of in materialNode.Specular parameters.specular = new THREE.Color().fromArray(materialNode.SpecularColor.value); } const scope = this; connections.get(ID).children.forEach(function (child) { const type = child.relationship; switch (type) { case 'Bump': parameters.bumpMap = scope.getTexture(textureMap, child.ID); break; case 'Maya|TEX_ao_map': parameters.aoMap = scope.getTexture(textureMap, child.ID); break; case 'DiffuseColor': case 'Maya|TEX_color_map': parameters.map = scope.getTexture(textureMap, child.ID); if (parameters.map !== undefined) { parameters.map.encoding = THREE.sRGBEncoding; } break; case 'DisplacementColor': parameters.displacementMap = scope.getTexture(textureMap, child.ID); break; case 'EmissiveColor': parameters.emissiveMap = scope.getTexture(textureMap, child.ID); if (parameters.emissiveMap !== undefined) { parameters.emissiveMap.encoding = THREE.sRGBEncoding; } break; case 'NormalMap': case 'Maya|TEX_normal_map': parameters.normalMap = scope.getTexture(textureMap, child.ID); break; case 'ReflectionColor': parameters.envMap = scope.getTexture(textureMap, child.ID); if (parameters.envMap !== undefined) { parameters.envMap.mapping = THREE.EquirectangularReflectionMapping; parameters.envMap.encoding = THREE.sRGBEncoding; } break; case 'SpecularColor': parameters.specularMap = scope.getTexture(textureMap, child.ID); if (parameters.specularMap !== undefined) { parameters.specularMap.encoding = THREE.sRGBEncoding; } break; case 'TransparentColor': case 'TransparencyFactor': parameters.alphaMap = scope.getTexture(textureMap, child.ID); parameters.transparent = true; break; case 'AmbientColor': case 'ShininessExponent': // AKA glossiness map case 'SpecularFactor': // AKA specularLevel case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor default: console.warn('THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type); break; } }); return parameters; } // get a texture from the textureMap for use by a material. getTexture(textureMap, id) { // if the texture is a layered texture, just use the first layer and issue a warning if ('LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture) { console.warn('THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.'); id = connections.get(id).children[0].ID; } return textureMap.get(id); } // Parse nodes in FBXTree.Objects.Deformer // Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here // Generates map of THREE.Skeleton-like objects for use later when generating and binding skeletons. parseDeformers() { const skeletons = {}; const morphTargets = {}; if ('Deformer' in fbxTree.Objects) { const DeformerNodes = fbxTree.Objects.Deformer; for (const nodeID in DeformerNodes) { const deformerNode = DeformerNodes[nodeID]; const relationships = connections.get(parseInt(nodeID)); if (deformerNode.attrType === 'Skin') { const skeleton = this.parseSkeleton(relationships, DeformerNodes); skeleton.ID = nodeID; if (relationships.parents.length > 1) console.warn('THREE.FBXLoader: skeleton attached to more than one geometry is not supported.'); skeleton.geometryID = relationships.parents[0].ID; skeletons[nodeID] = skeleton; } else if (deformerNode.attrType === 'BlendShape') { const morphTarget = { id: nodeID }; morphTarget.rawTargets = this.parseMorphTargets(relationships, DeformerNodes); morphTarget.id = nodeID; if (relationships.parents.length > 1) console.warn('THREE.FBXLoader: morph target attached to more than one geometry is not supported.'); morphTargets[nodeID] = morphTarget; } } } return { skeletons: skeletons, morphTargets: morphTargets }; } // Parse single nodes in FBXTree.Objects.Deformer // The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster' // Each skin node represents a skeleton and each cluster node represents a bone parseSkeleton(relationships, deformerNodes) { const rawBones = []; relationships.children.forEach(function (child) { const boneNode = deformerNodes[child.ID]; if (boneNode.attrType !== 'Cluster') return; const rawBone = { ID: child.ID, indices: [], weights: [], transformLink: new THREE.Matrix4().fromArray(boneNode.TransformLink.a) // transform: new THREE.Matrix4().fromArray( boneNode.Transform.a ), // linkMode: boneNode.Mode, }; if ('Indexes' in boneNode) { rawBone.indices = boneNode.Indexes.a; rawBone.weights = boneNode.Weights.a; } rawBones.push(rawBone); }); return { rawBones: rawBones, bones: [] }; } // The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel" parseMorphTargets(relationships, deformerNodes) { const rawMorphTargets = []; for (let i = 0; i < relationships.children.length; i++) { const child = relationships.children[i]; const morphTargetNode = deformerNodes[child.ID]; const rawMorphTarget = { name: morphTargetNode.attrName, initialWeight: morphTargetNode.DeformPercent, id: morphTargetNode.id, fullWeights: morphTargetNode.FullWeights.a }; if (morphTargetNode.attrType !== 'BlendShapeChannel') return; rawMorphTarget.geoID = connections.get(parseInt(child.ID)).children.filter(function (child) { return child.relationship === undefined; })[0].ID; rawMorphTargets.push(rawMorphTarget); } return rawMorphTargets; } // create the main THREE.Group() to be returned by the loader parseScene(deformers, geometryMap, materialMap) { sceneGraph = new THREE.Group(); const modelMap = this.parseModels(deformers.skeletons, geometryMap, materialMap); const modelNodes = fbxTree.Objects.Model; const scope = this; modelMap.forEach(function (model) { const modelNode = modelNodes[model.ID]; scope.setLookAtProperties(model, modelNode); const parentConnections = connections.get(model.ID).parents; parentConnections.forEach(function (connection) { const parent = modelMap.get(connection.ID); if (parent !== undefined) parent.add(model); }); if (model.parent === null) { sceneGraph.add(model); } }); this.bindSkeleton(deformers.skeletons, geometryMap, modelMap); this.createAmbientLight(); sceneGraph.traverse(function (node) { if (node.userData.transformData) { if (node.parent) { node.userData.transformData.parentMatrix = node.parent.matrix; node.userData.transformData.parentMatrixWorld = node.parent.matrixWorld; } const transform = generateTransform(node.userData.transformData); node.applyMatrix4(transform); node.updateWorldMatrix(); } }); const animations = new AnimationParser().parse(); // if all the models where already combined in a single group, just return that if (sceneGraph.children.length === 1 && sceneGraph.children[0].isGroup) { sceneGraph.children[0].animations = animations; sceneGraph = sceneGraph.children[0]; } sceneGraph.animations = animations; } // parse nodes in FBXTree.Objects.Model parseModels(skeletons, geometryMap, materialMap) { const modelMap = new Map(); const modelNodes = fbxTree.Objects.Model; for (const nodeID in modelNodes) { const id = parseInt(nodeID); const node = modelNodes[nodeID]; const relationships = connections.get(id); let model = this.buildSkeleton(relationships, skeletons, id, node.attrName); if (!model) { switch (node.attrType) { case 'Camera': model = this.createCamera(relationships); break; case 'Light': model = this.createLight(relationships); break; case 'Mesh': model = this.createMesh(relationships, geometryMap, materialMap); break; case 'NurbsCurve': model = this.createCurve(relationships, geometryMap); break; case 'LimbNode': case 'Root': model = new THREE.Bone(); break; case 'Null': default: model = new THREE.Group(); break; } model.name = node.attrName ? THREE.PropertyBinding.sanitizeNodeName(node.attrName) : ''; model.ID = id; } this.getTransformData(model, node); modelMap.set(id, model); } return modelMap; } buildSkeleton(relationships, skeletons, id, name) { let bone = null; relationships.parents.forEach(function (parent) { for (const ID in skeletons) { const skeleton = skeletons[ID]; skeleton.rawBones.forEach(function (rawBone, i) { if (rawBone.ID === parent.ID) { const subBone = bone; bone = new THREE.Bone(); bone.matrixWorld.copy(rawBone.transformLink); // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id bone.name = name ? THREE.PropertyBinding.sanitizeNodeName(name) : ''; bone.ID = id; skeleton.bones[i] = bone; // In cases where a bone is shared between multiple meshes // duplicate the bone here and and it as a child of the first bone if (subBone !== null) { bone.add(subBone); } } }); } }); return bone; } // create a THREE.PerspectiveCamera or THREE.OrthographicCamera createCamera(relationships) { let model; let cameraAttribute; relationships.children.forEach(function (child) { const attr = fbxTree.Objects.NodeAttribute[child.ID]; if (attr !== undefined) { cameraAttribute = attr; } }); if (cameraAttribute === undefined) { model = new THREE.Object3D(); } else { let type = 0; if (cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1) { type = 1; } let nearClippingPlane = 1; if (cameraAttribute.NearPlane !== undefined) { nearClippingPlane = cameraAttribute.NearPlane.value / 1000; } let farClippingPlane = 1000; if (cameraAttribute.FarPlane !== undefined) { farClippingPlane = cameraAttribute.FarPlane.value / 1000; } let width = window.innerWidth; let height = window.innerHeight; if (cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined) { width = cameraAttribute.AspectWidth.value; height = cameraAttribute.AspectHeight.value; } const aspect = width / height; let fov = 45; if (cameraAttribute.FieldOfView !== undefined) { fov = cameraAttribute.FieldOfView.value; } const focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null; switch (type) { case 0: // Perspective model = new THREE.PerspectiveCamera(fov, aspect, nearClippingPlane, farClippingPlane); if (focalLength !== null) model.setFocalLength(focalLength); break; case 1: // Orthographic model = new THREE.OrthographicCamera(- width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane); break; default: console.warn('THREE.FBXLoader: Unknown camera type ' + type + '.'); model = new THREE.Object3D(); break; } } return model; } // Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight createLight(relationships) { let model; let lightAttribute; relationships.children.forEach(function (child) { const attr = fbxTree.Objects.NodeAttribute[child.ID]; if (attr !== undefined) { lightAttribute = attr; } }); if (lightAttribute === undefined) { model = new THREE.Object3D(); } else { let type; // LightType can be undefined for Point lights if (lightAttribute.LightType === undefined) { type = 0; } else { type = lightAttribute.LightType.value; } let color = 0xffffff; if (lightAttribute.Color !== undefined) { color = new THREE.Color().fromArray(lightAttribute.Color.value); } let intensity = lightAttribute.Intensity === undefined ? 1 : lightAttribute.Intensity.value / 100; // light disabled if (lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0) { intensity = 0; } let distance = 0; if (lightAttribute.FarAttenuationEnd !== undefined) { if (lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0) { distance = 0; } else { distance = lightAttribute.FarAttenuationEnd.value; } } // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd? const decay = 1; switch (type) { case 0: // Point model = new THREE.PointLight(color, intensity, distance, decay); break; case 1: // Directional model = new THREE.DirectionalLight(color, intensity); break; case 2: // Spot let angle = Math.PI / 3; if (lightAttribute.InnerAngle !== undefined) { angle = THREE.MathUtils.degToRad(lightAttribute.InnerAngle.value); } let penumbra = 0; if (lightAttribute.OuterAngle !== undefined) { // TODO: this is not correct - FBX calculates outer and inner angle in degrees // with OuterAngle > InnerAngle && OuterAngle <= Math.PI // while three.js uses a penumbra between (0, 1) to attenuate the inner angle penumbra = THREE.MathUtils.degToRad(lightAttribute.OuterAngle.value); penumbra = Math.max(penumbra, 1); } model = new THREE.SpotLight(color, intensity, distance, angle, penumbra, decay); break; default: console.warn('THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.'); model = new THREE.PointLight(color, intensity); break; } if (lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1) { model.castShadow = true; } } return model; } createMesh(relationships, geometryMap, materialMap) { let model; let geometry = null; let material = null; const materials = []; // get geometry and materials(s) from connections relationships.children.forEach(function (child) { if (geometryMap.has(child.ID)) { geometry = geometryMap.get(child.ID); } if (materialMap.has(child.ID)) { materials.push(materialMap.get(child.ID)); } }); if (materials.length > 1) { material = materials; } else if (materials.length > 0) { material = materials[0]; } else { material = new THREE.MeshPhongMaterial({ color: 0xcccccc }); materials.push(material); } if ('color' in geometry.attributes) { materials.forEach(function (material) { material.vertexColors = true; }); } if (geometry.FBX_Deformer) { model = new THREE.SkinnedMesh(geometry, material); model.normalizeSkinWeights(); } else { model = new THREE.Mesh(geometry, material); } return model; } createCurve(relationships, geometryMap) { const geometry = relationships.children.reduce(function (geo, child) { if (geometryMap.has(child.ID)) geo = geometryMap.get(child.ID); return geo; }, null); // FBX does not list materials for Nurbs lines, so we'll just put our own in here. const material = new THREE.LineBasicMaterial({ color: 0x3300ff, linewidth: 1 }); return new THREE.Line(geometry, material); } // parse the model node for transform data getTransformData(model, modelNode) { const transformData = {}; if ('InheritType' in modelNode) transformData.inheritType = parseInt(modelNode.InheritType.value); if ('RotationOrder' in modelNode) transformData.eulerOrder = getEulerOrder(modelNode.RotationOrder.value); else transformData.eulerOrder = 'ZYX'; if ('Lcl_Translation' in modelNode) transformData.translation = modelNode.Lcl_Translation.value; if ('PreRotation' in modelNode) transformData.preRotation = modelNode.PreRotation.value; if ('Lcl_Rotation' in modelNode) transformData.rotation = modelNode.Lcl_Rotation.value; if ('PostRotation' in modelNode) transformData.postRotation = modelNode.PostRotation.value; if ('Lcl_Scaling' in modelNode) transformData.scale = modelNode.Lcl_Scaling.value; if ('ScalingOffset' in modelNode) transformData.scalingOffset = modelNode.ScalingOffset.value; if ('ScalingPivot' in modelNode) transformData.scalingPivot = modelNode.ScalingPivot.value; if ('RotationOffset' in modelNode) transformData.rotationOffset = modelNode.RotationOffset.value; if ('RotationPivot' in modelNode) transformData.rotationPivot = modelNode.RotationPivot.value; model.userData.transformData = transformData; } setLookAtProperties(model, modelNode) { if ('LookAtProperty' in modelNode) { const children = connections.get(model.ID).children; children.forEach(function (child) { if (child.relationship === 'LookAtProperty') { const lookAtTarget = fbxTree.Objects.Model[child.ID]; if ('Lcl_Translation' in lookAtTarget) { const pos = lookAtTarget.Lcl_Translation.value; // THREE.DirectionalLight, THREE.SpotLight if (model.target !== undefined) { model.target.position.fromArray(pos); sceneGraph.add(model.target); } else { // Cameras and other Object3Ds model.lookAt(new THREE.Vector3().fromArray(pos)); } } } }); } } bindSkeleton(skeletons, geometryMap, modelMap) { const bindMatrices = this.parsePoseNodes(); for (const ID in skeletons) { const skeleton = skeletons[ID]; const parents = connections.get(parseInt(skeleton.ID)).parents; parents.forEach(function (parent) { if (geometryMap.has(parent.ID)) { const geoID = parent.ID; const geoRelationships = connections.get(geoID); geoRelationships.parents.forEach(function (geoConnParent) { if (modelMap.has(geoConnParent.ID)) { const model = modelMap.get(geoConnParent.ID); model.bind(new THREE.Skeleton(skeleton.bones), bindMatrices[geoConnParent.ID]); } }); } }); } } parsePoseNodes() { const bindMatrices = {}; if ('Pose' in fbxTree.Objects) { const BindPoseNode = fbxTree.Objects.Pose; for (const nodeID in BindPoseNode) { if (BindPoseNode[nodeID].attrType === 'BindPose') { const poseNodes = BindPoseNode[nodeID].PoseNode; if (Array.isArray(poseNodes)) { poseNodes.forEach(function (poseNode) { bindMatrices[poseNode.Node] = new THREE.Matrix4().fromArray(poseNode.Matrix.a); }); } else { bindMatrices[poseNodes.Node] = new THREE.Matrix4().fromArray(poseNodes.Matrix.a); } } } } return bindMatrices; } // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light createAmbientLight() { if ('GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings) { const ambientColor = fbxTree.GlobalSettings.AmbientColor.value; const r = ambientColor[0]; const g = ambientColor[1]; const b = ambientColor[2]; if (r !== 0 || g !== 0 || b !== 0) { const color = new THREE.Color(r, g, b); sceneGraph.add(new THREE.AmbientLight(color, 1)); } } } } // parse Geometry data from FBXTree and return map of BufferGeometries class GeometryParser { // Parse nodes in FBXTree.Objects.Geometry parse(deformers) { const geometryMap = new Map(); if ('Geometry' in fbxTree.Objects) { const geoNodes = fbxTree.Objects.Geometry; for (const nodeID in geoNodes) { const relationships = connections.get(parseInt(nodeID)); const geo = this.parseGeometry(relationships, geoNodes[nodeID], deformers); geometryMap.set(parseInt(nodeID), geo); } } return geometryMap; } // Parse single node in FBXTree.Objects.Geometry parseGeometry(relationships, geoNode, deformers) { switch (geoNode.attrType) { case 'Mesh': return this.parseMeshGeometry(relationships, geoNode, deformers); break; case 'NurbsCurve': return this.parseNurbsGeometry(geoNode); break; } } // Parse single node mesh geometry in FBXTree.Objects.Geometry parseMeshGeometry(relationships, geoNode, deformers) { const skeletons = deformers.skeletons; const morphTargets = []; const modelNodes = relationships.parents.map(function (parent) { return fbxTree.Objects.Model[parent.ID]; }); // don't create geometry if it is not associated with any models if (modelNodes.length === 0) return; const skeleton = relationships.children.reduce(function (skeleton, child) { if (skeletons[child.ID] !== undefined) skeleton = skeletons[child.ID]; return skeleton; }, null); relationships.children.forEach(function (child) { if (deformers.morphTargets[child.ID] !== undefined) { morphTargets.push(deformers.morphTargets[child.ID]); } }); // Assume one model and get the preRotation from that // if there is more than one model associated with the geometry this may cause problems const modelNode = modelNodes[0]; const transformData = {}; if ('RotationOrder' in modelNode) transformData.eulerOrder = getEulerOrder(modelNode.RotationOrder.value); if ('InheritType' in modelNode) transformData.inheritType = parseInt(modelNode.InheritType.value); if ('GeometricTranslation' in modelNode) transformData.translation = modelNode.GeometricTranslation.value; if ('GeometricRotation' in modelNode) transformData.rotation = modelNode.GeometricRotation.value; if ('GeometricScaling' in modelNode) transformData.scale = modelNode.GeometricScaling.value; const transform = generateTransform(transformData); return this.genGeometry(geoNode, skeleton, morphTargets, transform); } // Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry genGeometry(geoNode, skeleton, morphTargets, preTransform) { const geo = new THREE.BufferGeometry(); if (geoNode.attrName) geo.name = geoNode.attrName; const geoInfo = this.parseGeoNode(geoNode, skeleton); const buffers = this.genBuffers(geoInfo); const positionAttribute = new THREE.Float32BufferAttribute(buffers.vertex, 3); positionAttribute.applyMatrix4(preTransform); geo.setAttribute('position', positionAttribute); if (buffers.colors.length > 0) { geo.setAttribute('color', new THREE.Float32BufferAttribute(buffers.colors, 3)); } if (skeleton) { geo.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(buffers.weightsIndices, 4)); geo.setAttribute('skinWeight', new THREE.Float32BufferAttribute(buffers.vertexWeights, 4)); // used later to bind the skeleton to the model geo.FBX_Deformer = skeleton; } if (buffers.normal.length > 0) { const normalMatrix = new THREE.Matrix3().getNormalMatrix(preTransform); const normalAttribute = new THREE.Float32BufferAttribute(buffers.normal, 3); normalAttribute.applyNormalMatrix(normalMatrix); geo.setAttribute('normal', normalAttribute); } buffers.uvs.forEach(function (uvBuffer, i) { // subsequent uv buffers are called 'uv1', 'uv2', ... let name = 'uv' + (i + 1).toString(); // the first uv buffer is just called 'uv' if (i === 0) { name = 'uv'; } geo.setAttribute(name, new THREE.Float32BufferAttribute(buffers.uvs[i], 2)); }); if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { // Convert the material indices of each vertex into rendering groups on the geometry. let prevMaterialIndex = buffers.materialIndex[0]; let startIndex = 0; buffers.materialIndex.forEach(function (currentIndex, i) { if (currentIndex !== prevMaterialIndex) { geo.addGroup(startIndex, i - startIndex, prevMaterialIndex); prevMaterialIndex = currentIndex; startIndex = i; } }); // the loop above doesn't add the last group, do that here. if (geo.groups.length > 0) { const lastGroup = geo.groups[geo.groups.length - 1]; const lastIndex = lastGroup.start + lastGroup.count; if (lastIndex !== buffers.materialIndex.length) { geo.addGroup(lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex); } } // case where there are multiple materials but the whole geometry is only // using one of them if (geo.groups.length === 0) { geo.addGroup(0, buffers.materialIndex.length, buffers.materialIndex[0]); } } this.addMorphTargets(geo, geoNode, morphTargets, preTransform); return geo; } parseGeoNode(geoNode, skeleton) { const geoInfo = {}; geoInfo.vertexPositions = geoNode.Vertices !== undefined ? geoNode.Vertices.a : []; geoInfo.vertexIndices = geoNode.PolygonVertexIndex !== undefined ? geoNode.PolygonVertexIndex.a : []; if (geoNode.LayerElementColor) { geoInfo.color = this.parseVertexColors(geoNode.LayerElementColor[0]); } if (geoNode.LayerElementMaterial) { geoInfo.material = this.parseMaterialIndices(geoNode.LayerElementMaterial[0]); } if (geoNode.LayerElementNormal) { geoInfo.normal = this.parseNormals(geoNode.LayerElementNormal[0]); } if (geoNode.LayerElementUV) { geoInfo.uv = []; let i = 0; while (geoNode.LayerElementUV[i]) { if (geoNode.LayerElementUV[i].UV) { geoInfo.uv.push(this.parseUVs(geoNode.LayerElementUV[i])); } i++; } } geoInfo.weightTable = {}; if (skeleton !== null) { geoInfo.skeleton = skeleton; skeleton.rawBones.forEach(function (rawBone, i) { // loop over the bone's vertex indices and weights rawBone.indices.forEach(function (index, j) { if (geoInfo.weightTable[index] === undefined) geoInfo.weightTable[index] = []; geoInfo.weightTable[index].push({ id: i, weight: rawBone.weights[j] }); }); }); } return geoInfo; } genBuffers(geoInfo) { const buffers = { vertex: [], normal: [], colors: [], uvs: [], materialIndex: [], vertexWeights: [], weightsIndices: [] }; let polygonIndex = 0; let faceLength = 0; let displayedWeightsWarning = false; // these will hold data for a single face let facePositionIndexes = []; let faceNormals = []; let faceColors = []; let faceUVs = []; let faceWeights = []; let faceWeightIndices = []; const scope = this; geoInfo.vertexIndices.forEach(function (vertexIndex, polygonVertexIndex) { let materialIndex; let endOfFace = false; // Face index and vertex index arrays are combined in a single array // A cube with quad faces looks like this: // PolygonVertexIndex: *24 { // a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5 // } // Negative numbers mark the end of a face - first face here is 0, 1, 3, -3 // to find index of last vertex bit shift the index: ^ - 1 if (vertexIndex < 0) { vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1 endOfFace = true; } let weightIndices = []; let weights = []; facePositionIndexes.push(vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2); if (geoInfo.color) { const data = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color); faceColors.push(data[0], data[1], data[2]); } if (geoInfo.skeleton) { if (geoInfo.weightTable[vertexIndex] !== undefined) { geoInfo.weightTable[vertexIndex].forEach(function (wt) { weights.push(wt.weight); weightIndices.push(wt.id); }); } if (weights.length > 4) { if (!displayedWeightsWarning) { console.warn('THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.'); displayedWeightsWarning = true; } const wIndex = [0, 0, 0, 0]; const Weight = [0, 0, 0, 0]; weights.forEach(function (weight, weightIndex) { let currentWeight = weight; let currentIndex = weightIndices[weightIndex]; Weight.forEach(function (comparedWeight, comparedWeightIndex, comparedWeightArray) { if (currentWeight > comparedWeight) { comparedWeightArray[comparedWeightIndex] = currentWeight; currentWeight = comparedWeight; const tmp = wIndex[comparedWeightIndex]; wIndex[comparedWeightIndex] = currentIndex; currentIndex = tmp; } }); }); weightIndices = wIndex; weights = Weight; } // if the weight array is shorter than 4 pad with 0s while (weights.length < 4) { weights.push(0); weightIndices.push(0); } for (let i = 0; i < 4; ++i) { faceWeights.push(weights[i]); faceWeightIndices.push(weightIndices[i]); } } if (geoInfo.normal) { const data = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal); faceNormals.push(data[0], data[1], data[2]); } if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { materialIndex = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material)[0]; } if (geoInfo.uv) { geoInfo.uv.forEach(function (uv, i) { const data = getData(polygonVertexIndex, polygonIndex, vertexIndex, uv); if (faceUVs[i] === undefined) { faceUVs[i] = []; } faceUVs[i].push(data[0]); faceUVs[i].push(data[1]); }); } faceLength++; if (endOfFace) { scope.genFace(buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength); polygonIndex++; faceLength = 0; // reset arrays for the next face facePositionIndexes = []; faceNormals = []; faceColors = []; faceUVs = []; faceWeights = []; faceWeightIndices = []; } }); return buffers; } // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris genFace(buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength) { for (let i = 2; i < faceLength; i++) { buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[0]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[1]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[2]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3 + 1]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3 + 2]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3 + 1]]); buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3 + 2]]); if (geoInfo.skeleton) { buffers.vertexWeights.push(faceWeights[0]); buffers.vertexWeights.push(faceWeights[1]); buffers.vertexWeights.push(faceWeights[2]); buffers.vertexWeights.push(faceWeights[3]); buffers.vertexWeights.push(faceWeights[(i - 1) * 4]); buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 1]); buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 2]); buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 3]); buffers.vertexWeights.push(faceWeights[i * 4]); buffers.vertexWeights.push(faceWeights[i * 4 + 1]); buffers.vertexWeights.push(faceWeights[i * 4 + 2]); buffers.vertexWeights.push(faceWeights[i * 4 + 3]); buffers.weightsIndices.push(faceWeightIndices[0]); buffers.weightsIndices.push(faceWeightIndices[1]); buffers.weightsIndices.push(faceWeightIndices[2]); buffers.weightsIndices.push(faceWeightIndices[3]); buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4]); buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 1]); buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 2]); buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 3]); buffers.weightsIndices.push(faceWeightIndices[i * 4]); buffers.weightsIndices.push(faceWeightIndices[i * 4 + 1]); buffers.weightsIndices.push(faceWeightIndices[i * 4 + 2]); buffers.weightsIndices.push(faceWeightIndices[i * 4 + 3]); } if (geoInfo.color) { buffers.colors.push(faceColors[0]); buffers.colors.push(faceColors[1]); buffers.colors.push(faceColors[2]); buffers.colors.push(faceColors[(i - 1) * 3]); buffers.colors.push(faceColors[(i - 1) * 3 + 1]); buffers.colors.push(faceColors[(i - 1) * 3 + 2]); buffers.colors.push(faceColors[i * 3]); buffers.colors.push(faceColors[i * 3 + 1]); buffers.colors.push(faceColors[i * 3 + 2]); } if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { buffers.materialIndex.push(materialIndex); buffers.materialIndex.push(materialIndex); buffers.materialIndex.push(materialIndex); } if (geoInfo.normal) { buffers.normal.push(faceNormals[0]); buffers.normal.push(faceNormals[1]); buffers.normal.push(faceNormals[2]); buffers.normal.push(faceNormals[(i - 1) * 3]); buffers.normal.push(faceNormals[(i - 1) * 3 + 1]); buffers.normal.push(faceNormals[(i - 1) * 3 + 2]); buffers.normal.push(faceNormals[i * 3]); buffers.normal.push(faceNormals[i * 3 + 1]); buffers.normal.push(faceNormals[i * 3 + 2]); } if (geoInfo.uv) { geoInfo.uv.forEach(function (uv, j) { if (buffers.uvs[j] === undefined) buffers.uvs[j] = []; buffers.uvs[j].push(faceUVs[j][0]); buffers.uvs[j].push(faceUVs[j][1]); buffers.uvs[j].push(faceUVs[j][(i - 1) * 2]); buffers.uvs[j].push(faceUVs[j][(i - 1) * 2 + 1]); buffers.uvs[j].push(faceUVs[j][i * 2]); buffers.uvs[j].push(faceUVs[j][i * 2 + 1]); }); } } } addMorphTargets(parentGeo, parentGeoNode, morphTargets, preTransform) { if (morphTargets.length === 0) return; parentGeo.morphTargetsRelative = true; parentGeo.morphAttributes.position = []; // parentGeo.morphAttributes.normal = []; // not implemented const scope = this; morphTargets.forEach(function (morphTarget) { morphTarget.rawTargets.forEach(function (rawTarget) { const morphGeoNode = fbxTree.Objects.Geometry[rawTarget.geoID]; if (morphGeoNode !== undefined) { scope.genMorphGeometry(parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name); } }); }); } // a morph geometry node is similar to a standard node, and the node is also contained // in FBXTree.Objects.Geometry, however it can only have attributes for position, normal // and a special attribute Index defining which vertices of the original geometry are affected // Normal and position attributes only have data for the vertices that are affected by the morph genMorphGeometry(parentGeo, parentGeoNode, morphGeoNode, preTransform, name) { const vertexIndices = parentGeoNode.PolygonVertexIndex !== undefined ? parentGeoNode.PolygonVertexIndex.a : []; const morphPositionsSparse = morphGeoNode.Vertices !== undefined ? morphGeoNode.Vertices.a : []; const indices = morphGeoNode.Indexes !== undefined ? morphGeoNode.Indexes.a : []; const length = parentGeo.attributes.position.count * 3; const morphPositions = new Float32Array(length); for (let i = 0; i < indices.length; i++) { const morphIndex = indices[i] * 3; morphPositions[morphIndex] = morphPositionsSparse[i * 3]; morphPositions[morphIndex + 1] = morphPositionsSparse[i * 3 + 1]; morphPositions[morphIndex + 2] = morphPositionsSparse[i * 3 + 2]; } // TODO: add morph normal support const morphGeoInfo = { vertexIndices: vertexIndices, vertexPositions: morphPositions }; const morphBuffers = this.genBuffers(morphGeoInfo); const positionAttribute = new THREE.Float32BufferAttribute(morphBuffers.vertex, 3); positionAttribute.name = name || morphGeoNode.attrName; positionAttribute.applyMatrix4(preTransform); parentGeo.morphAttributes.position.push(positionAttribute); } // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists parseNormals(NormalNode) { const mappingType = NormalNode.MappingInformationType; const referenceType = NormalNode.ReferenceInformationType; const buffer = NormalNode.Normals.a; let indexBuffer = []; if (referenceType === 'IndexToDirect') { if ('NormalIndex' in NormalNode) { indexBuffer = NormalNode.NormalIndex.a; } else if ('NormalsIndex' in NormalNode) { indexBuffer = NormalNode.NormalsIndex.a; } } return { dataSize: 3, buffer: buffer, indices: indexBuffer, mappingType: mappingType, referenceType: referenceType }; } // Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists parseUVs(UVNode) { const mappingType = UVNode.MappingInformationType; const referenceType = UVNode.ReferenceInformationType; const buffer = UVNode.UV.a; let indexBuffer = []; if (referenceType === 'IndexToDirect') { indexBuffer = UVNode.UVIndex.a; } return { dataSize: 2, buffer: buffer, indices: indexBuffer, mappingType: mappingType, referenceType: referenceType }; } // Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists parseVertexColors(ColorNode) { const mappingType = ColorNode.MappingInformationType; const referenceType = ColorNode.ReferenceInformationType; const buffer = ColorNode.Colors.a; let indexBuffer = []; if (referenceType === 'IndexToDirect') { indexBuffer = ColorNode.ColorIndex.a; } return { dataSize: 4, buffer: buffer, indices: indexBuffer, mappingType: mappingType, referenceType: referenceType }; } // Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists parseMaterialIndices(MaterialNode) { const mappingType = MaterialNode.MappingInformationType; const referenceType = MaterialNode.ReferenceInformationType; if (mappingType === 'NoMappingInformation') { return { dataSize: 1, buffer: [0], indices: [0], mappingType: 'AllSame', referenceType: referenceType }; } const materialIndexBuffer = MaterialNode.Materials.a; // Since materials are stored as indices, there's a bit of a mismatch between FBX and what // we expect.So we create an intermediate buffer that points to the index in the buffer, // for conforming with the other functions we've written for other data. const materialIndices = []; for (let i = 0; i < materialIndexBuffer.length; ++i) { materialIndices.push(i); } return { dataSize: 1, buffer: materialIndexBuffer, indices: materialIndices, mappingType: mappingType, referenceType: referenceType }; } // Generate a NurbGeometry from a node in FBXTree.Objects.Geometry parseNurbsGeometry(geoNode) { if (THREE.NURBSCurve === undefined) { console.error('THREE.FBXLoader: The loader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.'); return new THREE.BufferGeometry(); } const order = parseInt(geoNode.Order); if (isNaN(order)) { console.error('THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id); return new THREE.BufferGeometry(); } const degree = order - 1; const knots = geoNode.KnotVector.a; const controlPoints = []; const pointsValues = geoNode.Points.a; for (let i = 0, l = pointsValues.length; i < l; i += 4) { controlPoints.push(new THREE.Vector4().fromArray(pointsValues, i)); } let startKnot, endKnot; if (geoNode.Form === 'Closed') { controlPoints.push(controlPoints[0]); } else if (geoNode.Form === 'Periodic') { startKnot = degree; endKnot = knots.length - 1 - startKnot; for (let i = 0; i < degree; ++i) { controlPoints.push(controlPoints[i]); } } const curve = new THREE.NURBSCurve(degree, knots, controlPoints, startKnot, endKnot); const points = curve.getPoints(controlPoints.length * 12); return new THREE.BufferGeometry().setFromPoints(points); } } // parse animation data from FBXTree class AnimationParser { // take raw animation clips and turn them into three.js animation clips parse() { const animationClips = []; const rawClips = this.parseClips(); if (rawClips !== undefined) { for (const key in rawClips) { const rawClip = rawClips[key]; const clip = this.addClip(rawClip); animationClips.push(clip); } } return animationClips; } parseClips() { // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve, // if this is undefined we can safely assume there are no animations if (fbxTree.Objects.AnimationCurve === undefined) return undefined; const curveNodesMap = this.parseAnimationCurveNodes(); this.parseAnimationCurves(curveNodesMap); const layersMap = this.parseAnimationLayers(curveNodesMap); const rawClips = this.parseAnimStacks(layersMap); return rawClips; } // parse nodes in FBXTree.Objects.AnimationCurveNode // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation ) // and is referenced by an AnimationLayer parseAnimationCurveNodes() { const rawCurveNodes = fbxTree.Objects.AnimationCurveNode; const curveNodesMap = new Map(); for (const nodeID in rawCurveNodes) { const rawCurveNode = rawCurveNodes[nodeID]; if (rawCurveNode.attrName.match(/S|R|T|DeformPercent/) !== null) { const curveNode = { id: rawCurveNode.id, attr: rawCurveNode.attrName, curves: {} }; curveNodesMap.set(curveNode.id, curveNode); } } return curveNodesMap; } // parse nodes in FBXTree.Objects.AnimationCurve and connect them up to // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated // axis ( e.g. times and values of x rotation) parseAnimationCurves(curveNodesMap) { const rawCurves = fbxTree.Objects.AnimationCurve; // TODO: Many values are identical up to roundoff error, but won't be optimised // e.g. position times: [0, 0.4, 0. 8] // position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809] // clearly, this should be optimised to // times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809] // this shows up in nearly every FBX file, and generally time array is length > 100 for (const nodeID in rawCurves) { const animationCurve = { id: rawCurves[nodeID].id, times: rawCurves[nodeID].KeyTime.a.map(convertFBXTimeToSeconds), values: rawCurves[nodeID].KeyValueFloat.a }; const relationships = connections.get(animationCurve.id); if (relationships !== undefined) { const animationCurveID = relationships.parents[0].ID; const animationCurveRelationship = relationships.parents[0].relationship; if (animationCurveRelationship.match(/X/)) { curveNodesMap.get(animationCurveID).curves['x'] = animationCurve; } else if (animationCurveRelationship.match(/Y/)) { curveNodesMap.get(animationCurveID).curves['y'] = animationCurve; } else if (animationCurveRelationship.match(/Z/)) { curveNodesMap.get(animationCurveID).curves['z'] = animationCurve; } else if (animationCurveRelationship.match(/d|DeformPercent/) && curveNodesMap.has(animationCurveID)) { curveNodesMap.get(animationCurveID).curves['morph'] = animationCurve; } } } } // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references // to various AnimationCurveNodes and is referenced by an AnimationStack node // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack parseAnimationLayers(curveNodesMap) { const rawLayers = fbxTree.Objects.AnimationLayer; const layersMap = new Map(); for (const nodeID in rawLayers) { const layerCurveNodes = []; const connection = connections.get(parseInt(nodeID)); if (connection !== undefined) { // all the animationCurveNodes used in the layer const children = connection.children; children.forEach(function (child, i) { if (curveNodesMap.has(child.ID)) { const curveNode = curveNodesMap.get(child.ID); // check that the curves are defined for at least one axis, otherwise ignore the curveNode if (curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined) { if (layerCurveNodes[i] === undefined) { const modelID = connections.get(child.ID).parents.filter(function (parent) { return parent.relationship !== undefined; })[0].ID; if (modelID !== undefined) { const rawModel = fbxTree.Objects.Model[modelID.toString()]; if (rawModel === undefined) { console.warn('THREE.FBXLoader: Encountered a unused curve.', child); return; } const node = { modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName(rawModel.attrName) : '', ID: rawModel.id, initialPosition: [0, 0, 0], initialRotation: [0, 0, 0], initialScale: [1, 1, 1] }; sceneGraph.traverse(function (child) { if (child.ID === rawModel.id) { node.transform = child.matrix; if (child.userData.transformData) node.eulerOrder = child.userData.transformData.eulerOrder; } }); if (!node.transform) node.transform = new THREE.Matrix4(); // if the animated model is pre rotated, we'll have to apply the pre rotations to every // animation value as well if ('PreRotation' in rawModel) node.preRotation = rawModel.PreRotation.value; if ('PostRotation' in rawModel) node.postRotation = rawModel.PostRotation.value; layerCurveNodes[i] = node; } } if (layerCurveNodes[i]) layerCurveNodes[i][curveNode.attr] = curveNode; } else if (curveNode.curves.morph !== undefined) { if (layerCurveNodes[i] === undefined) { const deformerID = connections.get(child.ID).parents.filter(function (parent) { return parent.relationship !== undefined; })[0].ID; const morpherID = connections.get(deformerID).parents[0].ID; const geoID = connections.get(morpherID).parents[0].ID; // assuming geometry is not used in more than one model const modelID = connections.get(geoID).parents[0].ID; const rawModel = fbxTree.Objects.Model[modelID]; const node = { modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName(rawModel.attrName) : '', morphName: fbxTree.Objects.Deformer[deformerID].attrName }; layerCurveNodes[i] = node; } layerCurveNodes[i][curveNode.attr] = curveNode; } } }); layersMap.set(parseInt(nodeID), layerCurveNodes); } } return layersMap; } // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation // hierarchy. Each Stack node will be used to create a THREE.AnimationClip parseAnimStacks(layersMap) { const rawStacks = fbxTree.Objects.AnimationStack; // connect the stacks (clips) up to the layers const rawClips = {}; for (const nodeID in rawStacks) { const children = connections.get(parseInt(nodeID)).children; if (children.length > 1) { // it seems like stacks will always be associated with a single layer. But just in case there are files // where there are multiple layers per stack, we'll display a warning console.warn('THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.'); } const layer = layersMap.get(children[0].ID); rawClips[nodeID] = { name: rawStacks[nodeID].attrName, layer: layer }; } return rawClips; } addClip(rawClip) { let tracks = []; const scope = this; rawClip.layer.forEach(function (rawTracks) { tracks = tracks.concat(scope.generateTracks(rawTracks)); }); return new THREE.AnimationClip(rawClip.name, - 1, tracks); } generateTracks(rawTracks) { const tracks = []; let initialPosition = new THREE.Vector3(); let initialRotation = new THREE.Quaternion(); let initialScale = new THREE.Vector3(); if (rawTracks.transform) rawTracks.transform.decompose(initialPosition, initialRotation, initialScale); initialPosition = initialPosition.toArray(); initialRotation = new THREE.Euler().setFromQuaternion(initialRotation, rawTracks.eulerOrder).toArray(); initialScale = initialScale.toArray(); if (rawTracks.T !== undefined && Object.keys(rawTracks.T.curves).length > 0) { const positionTrack = this.generateVectorTrack(rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position'); if (positionTrack !== undefined) tracks.push(positionTrack); } if (rawTracks.R !== undefined && Object.keys(rawTracks.R.curves).length > 0) { const rotationTrack = this.generateRotationTrack(rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder); if (rotationTrack !== undefined) tracks.push(rotationTrack); } if (rawTracks.S !== undefined && Object.keys(rawTracks.S.curves).length > 0) { const scaleTrack = this.generateVectorTrack(rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale'); if (scaleTrack !== undefined) tracks.push(scaleTrack); } if (rawTracks.DeformPercent !== undefined) { const morphTrack = this.generateMorphTrack(rawTracks); if (morphTrack !== undefined) tracks.push(morphTrack); } return tracks; } generateVectorTrack(modelName, curves, initialValue, type) { const times = this.getTimesForAllAxes(curves); const values = this.getKeyframeTrackValues(times, curves, initialValue); return new THREE.VectorKeyframeTrack(modelName + '.' + type, times, values); } generateRotationTrack(modelName, curves, initialValue, preRotation, postRotation, eulerOrder) { if (curves.x !== undefined) { this.interpolateRotations(curves.x); curves.x.values = curves.x.values.map(THREE.MathUtils.degToRad); } if (curves.y !== undefined) { this.interpolateRotations(curves.y); curves.y.values = curves.y.values.map(THREE.MathUtils.degToRad); } if (curves.z !== undefined) { this.interpolateRotations(curves.z); curves.z.values = curves.z.values.map(THREE.MathUtils.degToRad); } const times = this.getTimesForAllAxes(curves); const values = this.getKeyframeTrackValues(times, curves, initialValue); if (preRotation !== undefined) { preRotation = preRotation.map(THREE.MathUtils.degToRad); preRotation.push(eulerOrder); preRotation = new THREE.Euler().fromArray(preRotation); preRotation = new THREE.Quaternion().setFromEuler(preRotation); } if (postRotation !== undefined) { postRotation = postRotation.map(THREE.MathUtils.degToRad); postRotation.push(eulerOrder); postRotation = new THREE.Euler().fromArray(postRotation); postRotation = new THREE.Quaternion().setFromEuler(postRotation).invert(); } const quaternion = new THREE.Quaternion(); const euler = new THREE.Euler(); const quaternionValues = []; for (let i = 0; i < values.length; i += 3) { euler.set(values[i], values[i + 1], values[i + 2], eulerOrder); quaternion.setFromEuler(euler); if (preRotation !== undefined) quaternion.premultiply(preRotation); if (postRotation !== undefined) quaternion.multiply(postRotation); quaternion.toArray(quaternionValues, i / 3 * 4); } return new THREE.QuaternionKeyframeTrack(modelName + '.quaternion', times, quaternionValues); } generateMorphTrack(rawTracks) { const curves = rawTracks.DeformPercent.curves.morph; const values = curves.values.map(function (val) { return val / 100; }); const morphNum = sceneGraph.getObjectByName(rawTracks.modelName).morphTargetDictionary[rawTracks.morphName]; return new THREE.NumberKeyframeTrack(rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values); } // For all animated objects, times are defined separately for each axis // Here we'll combine the times into one sorted array without duplicates getTimesForAllAxes(curves) { let times = []; // first join together the times for each axis, if defined if (curves.x !== undefined) times = times.concat(curves.x.times); if (curves.y !== undefined) times = times.concat(curves.y.times); if (curves.z !== undefined) times = times.concat(curves.z.times); // then sort them times = times.sort(function (a, b) { return a - b; }); // and remove duplicates if (times.length > 1) { let targetIndex = 1; let lastValue = times[0]; for (let i = 1; i < times.length; i++) { const currentValue = times[i]; if (currentValue !== lastValue) { times[targetIndex] = currentValue; lastValue = currentValue; targetIndex++; } } times = times.slice(0, targetIndex); } return times; } getKeyframeTrackValues(times, curves, initialValue) { const prevValue = initialValue; const values = []; let xIndex = - 1; let yIndex = - 1; let zIndex = - 1; times.forEach(function (time) { if (curves.x) xIndex = curves.x.times.indexOf(time); if (curves.y) yIndex = curves.y.times.indexOf(time); if (curves.z) zIndex = curves.z.times.indexOf(time); // if there is an x value defined for this frame, use that if (xIndex !== - 1) { const xValue = curves.x.values[xIndex]; values.push(xValue); prevValue[0] = xValue; } else { // otherwise use the x value from the previous frame values.push(prevValue[0]); } if (yIndex !== - 1) { const yValue = curves.y.values[yIndex]; values.push(yValue); prevValue[1] = yValue; } else { values.push(prevValue[1]); } if (zIndex !== - 1) { const zValue = curves.z.values[zIndex]; values.push(zValue); prevValue[2] = zValue; } else { values.push(prevValue[2]); } }); return values; } // Rotations are defined as THREE.Euler angles which can have values of any size // These will be converted to quaternions which don't support values greater than // PI, so we'll interpolate large rotations interpolateRotations(curve) { for (let i = 1; i < curve.values.length; i++) { const initialValue = curve.values[i - 1]; const valuesSpan = curve.values[i] - initialValue; const absoluteSpan = Math.abs(valuesSpan); if (absoluteSpan >= 180) { const numSubIntervals = absoluteSpan / 180; const step = valuesSpan / numSubIntervals; let nextValue = initialValue + step; const initialTime = curve.times[i - 1]; const timeSpan = curve.times[i] - initialTime; const interval = timeSpan / numSubIntervals; let nextTime = initialTime + interval; const interpolatedTimes = []; const interpolatedValues = []; while (nextTime < curve.times[i]) { interpolatedTimes.push(nextTime); nextTime += interval; interpolatedValues.push(nextValue); nextValue += step; } curve.times = inject(curve.times, i, interpolatedTimes); curve.values = inject(curve.values, i, interpolatedValues); } } } } // parse an FBX file in ASCII format class TextParser { getPrevNode() { return this.nodeStack[this.currentIndent - 2]; } getCurrentNode() { return this.nodeStack[this.currentIndent - 1]; } getCurrentProp() { return this.currentProp; } pushStack(node) { this.nodeStack.push(node); this.currentIndent += 1; } popStack() { this.nodeStack.pop(); this.currentIndent -= 1; } setCurrentProp(val, name) { this.currentProp = val; this.currentPropName = name; } parse(text) { this.currentIndent = 0; this.allNodes = new FBXTree(); this.nodeStack = []; this.currentProp = []; this.currentPropName = ''; const scope = this; const split = text.split(/[\r\n]+/); split.forEach(function (line, i) { const matchComment = line.match(/^[\s\t]*;/); const matchEmpty = line.match(/^[\s\t]*$/); if (matchComment || matchEmpty) return; const matchBeginning = line.match('^\\t{' + scope.currentIndent + '}(\\w+):(.*){', ''); const matchProperty = line.match('^\\t{' + scope.currentIndent + '}(\\w+):[\\s\\t\\r\\n](.*)'); const matchEnd = line.match('^\\t{' + (scope.currentIndent - 1) + '}}'); if (matchBeginning) { scope.parseNodeBegin(line, matchBeginning); } else if (matchProperty) { scope.parseNodeProperty(line, matchProperty, split[++i]); } else if (matchEnd) { scope.popStack(); } else if (line.match(/^[^\s\t}]/)) { // large arrays are split over multiple lines terminated with a ',' character // if this is encountered the line needs to be joined to the previous line scope.parseNodePropertyContinued(line); } }); return this.allNodes; } parseNodeBegin(line, property) { const nodeName = property[1].trim().replace(/^"/, '').replace(/"$/, ''); const nodeAttrs = property[2].split(',').map(function (attr) { return attr.trim().replace(/^"/, '').replace(/"$/, ''); }); const node = { name: nodeName }; const attrs = this.parseNodeAttr(nodeAttrs); const currentNode = this.getCurrentNode(); // a top node if (this.currentIndent === 0) { this.allNodes.add(nodeName, node); } else { // a subnode // if the subnode already exists, append it if (nodeName in currentNode) { // special case Pose needs PoseNodes as an array if (nodeName === 'PoseNode') { currentNode.PoseNode.push(node); } else if (currentNode[nodeName].id !== undefined) { currentNode[nodeName] = {}; currentNode[nodeName][currentNode[nodeName].id] = currentNode[nodeName]; } if (attrs.id !== '') currentNode[nodeName][attrs.id] = node; } else if (typeof attrs.id === 'number') { currentNode[nodeName] = {}; currentNode[nodeName][attrs.id] = node; } else if (nodeName !== 'Properties70') { if (nodeName === 'PoseNode') currentNode[nodeName] = [node]; else currentNode[nodeName] = node; } } if (typeof attrs.id === 'number') node.id = attrs.id; if (attrs.name !== '') node.attrName = attrs.name; if (attrs.type !== '') node.attrType = attrs.type; this.pushStack(node); } parseNodeAttr(attrs) { let id = attrs[0]; if (attrs[0] !== '') { id = parseInt(attrs[0]); if (isNaN(id)) { id = attrs[0]; } } let name = '', type = ''; if (attrs.length > 1) { name = attrs[1].replace(/^(\w+)::/, ''); type = attrs[2]; } return { id: id, name: name, type: type }; } parseNodeProperty(line, property, contentLine) { let propName = property[1].replace(/^"/, '').replace(/"$/, '').trim(); let propValue = property[2].replace(/^"/, '').replace(/"$/, '').trim(); // for special case: base64 image data follows "Content: ," line // Content: , // "/9j/4RDaRXhpZgAATU0A..." if (propName === 'Content' && propValue === ',') { propValue = contentLine.replace(/"/g, '').replace(/,$/, '').trim(); } const currentNode = this.getCurrentNode(); const parentName = currentNode.name; if (parentName === 'Properties70') { this.parseNodeSpecialProperty(line, propName, propValue); return; } // Connections if (propName === 'C') { const connProps = propValue.split(',').slice(1); const from = parseInt(connProps[0]); const to = parseInt(connProps[1]); let rest = propValue.split(',').slice(3); rest = rest.map(function (elem) { return elem.trim().replace(/^"/, ''); }); propName = 'connections'; propValue = [from, to]; append(propValue, rest); if (currentNode[propName] === undefined) { currentNode[propName] = []; } } // Node if (propName === 'Node') currentNode.id = propValue; // connections if (propName in currentNode && Array.isArray(currentNode[propName])) { currentNode[propName].push(propValue); } else { if (propName !== 'a') currentNode[propName] = propValue; else currentNode.a = propValue; } this.setCurrentProp(currentNode, propName); // convert string to array, unless it ends in ',' in which case more will be added to it if (propName === 'a' && propValue.slice(- 1) !== ',') { currentNode.a = parseNumberArray(propValue); } } parseNodePropertyContinued(line) { const currentNode = this.getCurrentNode(); currentNode.a += line; // if the line doesn't end in ',' we have reached the end of the property value // so convert the string to an array if (line.slice(- 1) !== ',') { currentNode.a = parseNumberArray(currentNode.a); } } // parse "Property70" parseNodeSpecialProperty(line, propName, propValue) { // split this // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 // into array like below // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ] const props = propValue.split('",').map(function (prop) { return prop.trim().replace(/^\"/, '').replace(/\s/, '_'); }); const innerPropName = props[0]; const innerPropType1 = props[1]; const innerPropType2 = props[2]; const innerPropFlag = props[3]; let innerPropValue = props[4]; // cast values where needed, otherwise leave as strings switch (innerPropType1) { case 'int': case 'enum': case 'bool': case 'ULongLong': case 'double': case 'Number': case 'FieldOfView': innerPropValue = parseFloat(innerPropValue); break; case 'Color': case 'ColorRGB': case 'Vector3D': case 'Lcl_Translation': case 'Lcl_Rotation': case 'Lcl_Scaling': innerPropValue = parseNumberArray(innerPropValue); break; } // CAUTION: these props must append to parent's parent this.getPrevNode()[innerPropName] = { 'type': innerPropType1, 'type2': innerPropType2, 'flag': innerPropFlag, 'value': innerPropValue }; this.setCurrentProp(this.getPrevNode(), innerPropName); } } // Parse an FBX file in Binary format class BinaryParser { parse(buffer) { const reader = new BinaryReader(buffer); reader.skip(23); // skip magic 23 bytes const version = reader.getUint32(); if (version < 6400) { throw new Error('THREE.FBXLoader: FBX version not supported, FileVersion: ' + version); } const allNodes = new FBXTree(); while (!this.endOfContent(reader)) { const node = this.parseNode(reader, version); if (node !== null) allNodes.add(node.name, node); } return allNodes; } // Check if reader has reached the end of content. endOfContent(reader) { // footer size: 160bytes + 16-byte alignment padding // - 16bytes: magic // - padding til 16-byte alignment (at least 1byte?) // (seems like some exporters embed fixed 15 or 16bytes?) // - 4bytes: magic // - 4bytes: version // - 120bytes: zero // - 16bytes: magic if (reader.size() % 16 === 0) { return (reader.getOffset() + 160 + 16 & ~0xf) >= reader.size(); } else { return reader.getOffset() + 160 + 16 >= reader.size(); } } // recursively parse nodes until the end of the file is reached parseNode(reader, version) { const node = {}; // The first three data sizes depends on version. const endOffset = version >= 7500 ? reader.getUint64() : reader.getUint32(); const numProperties = version >= 7500 ? reader.getUint64() : reader.getUint32(); version >= 7500 ? reader.getUint64() : reader.getUint32(); // the returned propertyListLen is not used const nameLen = reader.getUint8(); const name = reader.getString(nameLen); // Regards this node as NULL-record if endOffset is zero if (endOffset === 0) return null; const propertyList = []; for (let i = 0; i < numProperties; i++) { propertyList.push(this.parseProperty(reader)); } // Regards the first three elements in propertyList as id, attrName, and attrType const id = propertyList.length > 0 ? propertyList[0] : ''; const attrName = propertyList.length > 1 ? propertyList[1] : ''; const attrType = propertyList.length > 2 ? propertyList[2] : ''; // check if this node represents just a single property // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]} node.singleProperty = numProperties === 1 && reader.getOffset() === endOffset ? true : false; while (endOffset > reader.getOffset()) { const subNode = this.parseNode(reader, version); if (subNode !== null) this.parseSubNode(name, node, subNode); } node.propertyList = propertyList; // raw property list used by parent if (typeof id === 'number') node.id = id; if (attrName !== '') node.attrName = attrName; if (attrType !== '') node.attrType = attrType; if (name !== '') node.name = name; return node; } parseSubNode(name, node, subNode) { // special case: child node is single property if (subNode.singleProperty === true) { const value = subNode.propertyList[0]; if (Array.isArray(value)) { node[subNode.name] = subNode; subNode.a = value; } else { node[subNode.name] = value; } } else if (name === 'Connections' && subNode.name === 'C') { const array = []; subNode.propertyList.forEach(function (property, i) { // first Connection is FBX type (OO, OP, etc.). We'll discard these if (i !== 0) array.push(property); }); if (node.connections === undefined) { node.connections = []; } node.connections.push(array); } else if (subNode.name === 'Properties70') { const keys = Object.keys(subNode); keys.forEach(function (key) { node[key] = subNode[key]; }); } else if (name === 'Properties70' && subNode.name === 'P') { let innerPropName = subNode.propertyList[0]; let innerPropType1 = subNode.propertyList[1]; const innerPropType2 = subNode.propertyList[2]; const innerPropFlag = subNode.propertyList[3]; let innerPropValue; if (innerPropName.indexOf('Lcl ') === 0) innerPropName = innerPropName.replace('Lcl ', 'Lcl_'); if (innerPropType1.indexOf('Lcl ') === 0) innerPropType1 = innerPropType1.replace('Lcl ', 'Lcl_'); if (innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf('Lcl_') === 0) { innerPropValue = [subNode.propertyList[4], subNode.propertyList[5], subNode.propertyList[6]]; } else { innerPropValue = subNode.propertyList[4]; } // this will be copied to parent, see above node[innerPropName] = { 'type': innerPropType1, 'type2': innerPropType2, 'flag': innerPropFlag, 'value': innerPropValue }; } else if (node[subNode.name] === undefined) { if (typeof subNode.id === 'number') { node[subNode.name] = {}; node[subNode.name][subNode.id] = subNode; } else { node[subNode.name] = subNode; } } else { if (subNode.name === 'PoseNode') { if (!Array.isArray(node[subNode.name])) { node[subNode.name] = [node[subNode.name]]; } node[subNode.name].push(subNode); } else if (node[subNode.name][subNode.id] === undefined) { node[subNode.name][subNode.id] = subNode; } } } parseProperty(reader) { const type = reader.getString(1); let length; switch (type) { case 'C': return reader.getBoolean(); case 'D': return reader.getFloat64(); case 'F': return reader.getFloat32(); case 'I': return reader.getInt32(); case 'L': return reader.getInt64(); case 'R': length = reader.getUint32(); return reader.getArrayBuffer(length); case 'S': length = reader.getUint32(); return reader.getString(length); case 'Y': return reader.getInt16(); case 'b': case 'c': case 'd': case 'f': case 'i': case 'l': const arrayLength = reader.getUint32(); const encoding = reader.getUint32(); // 0: non-compressed, 1: compressed const compressedLength = reader.getUint32(); if (encoding === 0) { switch (type) { case 'b': case 'c': return reader.getBooleanArray(arrayLength); case 'd': return reader.getFloat64Array(arrayLength); case 'f': return reader.getFloat32Array(arrayLength); case 'i': return reader.getInt32Array(arrayLength); case 'l': return reader.getInt64Array(arrayLength); } } if (typeof fflate === 'undefined') { console.error('THREE.FBXLoader: External library fflate.min.js required.'); } const data = fflate.unzlibSync(new Uint8Array(reader.getArrayBuffer(compressedLength))); // eslint-disable-line no-undef const reader2 = new BinaryReader(data.buffer); switch (type) { case 'b': case 'c': return reader2.getBooleanArray(arrayLength); case 'd': return reader2.getFloat64Array(arrayLength); case 'f': return reader2.getFloat32Array(arrayLength); case 'i': return reader2.getInt32Array(arrayLength); case 'l': return reader2.getInt64Array(arrayLength); } default: throw new Error('THREE.FBXLoader: Unknown property type ' + type); } } } class BinaryReader { constructor(buffer, littleEndian) { this.dv = new DataView(buffer); this.offset = 0; this.littleEndian = littleEndian !== undefined ? littleEndian : true; } getOffset() { return this.offset; } size() { return this.dv.buffer.byteLength; } skip(length) { this.offset += length; } // seems like true/false representation depends on exporter. // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54) // then sees LSB. getBoolean() { return (this.getUint8() & 1) === 1; } getBooleanArray(size) { const a = []; for (let i = 0; i < size; i++) { a.push(this.getBoolean()); } return a; } getUint8() { const value = this.dv.getUint8(this.offset); this.offset += 1; return value; } getInt16() { const value = this.dv.getInt16(this.offset, this.littleEndian); this.offset += 2; return value; } getInt32() { const value = this.dv.getInt32(this.offset, this.littleEndian); this.offset += 4; return value; } getInt32Array(size) { const a = []; for (let i = 0; i < size; i++) { a.push(this.getInt32()); } return a; } getUint32() { const value = this.dv.getUint32(this.offset, this.littleEndian); this.offset += 4; return value; } // JavaScript doesn't support 64-bit integer so calculate this here // 1 << 32 will return 1 so using multiply operation instead here. // There's a possibility that this method returns wrong value if the value // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. // TODO: safely handle 64-bit integer getInt64() { let low, high; if (this.littleEndian) { low = this.getUint32(); high = this.getUint32(); } else { high = this.getUint32(); low = this.getUint32(); } // calculate negative value if (high & 0x80000000) { high = ~high & 0xFFFFFFFF; low = ~low & 0xFFFFFFFF; if (low === 0xFFFFFFFF) high = high + 1 & 0xFFFFFFFF; low = low + 1 & 0xFFFFFFFF; return - (high * 0x100000000 + low); } return high * 0x100000000 + low; } getInt64Array(size) { const a = []; for (let i = 0; i < size; i++) { a.push(this.getInt64()); } return a; } // Note: see getInt64() comment getUint64() { let low, high; if (this.littleEndian) { low = this.getUint32(); high = this.getUint32(); } else { high = this.getUint32(); low = this.getUint32(); } return high * 0x100000000 + low; } getFloat32() { const value = this.dv.getFloat32(this.offset, this.littleEndian); this.offset += 4; return value; } getFloat32Array(size) { const a = []; for (let i = 0; i < size; i++) { a.push(this.getFloat32()); } return a; } getFloat64() { const value = this.dv.getFloat64(this.offset, this.littleEndian); this.offset += 8; return value; } getFloat64Array(size) { const a = []; for (let i = 0; i < size; i++) { a.push(this.getFloat64()); } return a; } getArrayBuffer(size) { const value = this.dv.buffer.slice(this.offset, this.offset + size); this.offset += size; return value; } getString(size) { // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead let a = []; for (let i = 0; i < size; i++) { a[i] = this.getUint8(); } const nullByte = a.indexOf(0); if (nullByte >= 0) a = a.slice(0, nullByte); return THREE.LoaderUtils.decodeText(new Uint8Array(a)); } } // FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format) // and BinaryParser( FBX Binary format) class FBXTree { add(key, val) { this[key] = val; } } // ************** UTILITY FUNCTIONS ************** function isFbxFormatBinary(buffer) { const CORRECT = 'Kaydara\u0020FBX\u0020Binary\u0020\u0020\0'; return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString(buffer, 0, CORRECT.length); } function isFbxFormatASCII(text) { const CORRECT = ['K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\']; let cursor = 0; function read(offset) { const result = text[offset - 1]; text = text.slice(cursor + offset); cursor++; return result; } for (let i = 0; i < CORRECT.length; ++i) { const num = read(1); if (num === CORRECT[i]) { return false; } } return true; } function getFbxVersion(text) { const versionRegExp = /FBXVersion: (\d+)/; const match = text.match(versionRegExp); if (match) { const version = parseInt(match[1]); return version; } throw new Error('THREE.FBXLoader: Cannot find the version number for the file given.'); } // Converts FBX ticks into real time seconds. function convertFBXTimeToSeconds(time) { return time / 46186158000; } const dataArray = []; // extracts the data from the correct position in the FBX array based on indexing type function getData(polygonVertexIndex, polygonIndex, vertexIndex, infoObject) { let index; switch (infoObject.mappingType) { case 'ByPolygonVertex': index = polygonVertexIndex; break; case 'ByPolygon': index = polygonIndex; break; case 'ByVertice': index = vertexIndex; break; case 'AllSame': index = infoObject.indices[0]; break; default: console.warn('THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType); } if (infoObject.referenceType === 'IndexToDirect') index = infoObject.indices[index]; const from = index * infoObject.dataSize; const to = from + infoObject.dataSize; return slice(dataArray, infoObject.buffer, from, to); } const tempEuler = new THREE.Euler(); const tempVec = new THREE.Vector3(); // generate transformation from FBX transform data // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm // ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e function generateTransform(transformData) { const lTranslationM = new THREE.Matrix4(); const lPreRotationM = new THREE.Matrix4(); const lRotationM = new THREE.Matrix4(); const lPostRotationM = new THREE.Matrix4(); const lScalingM = new THREE.Matrix4(); const lScalingPivotM = new THREE.Matrix4(); const lScalingOffsetM = new THREE.Matrix4(); const lRotationOffsetM = new THREE.Matrix4(); const lRotationPivotM = new THREE.Matrix4(); const lParentGX = new THREE.Matrix4(); const lParentLX = new THREE.Matrix4(); const lGlobalT = new THREE.Matrix4(); const inheritType = transformData.inheritType ? transformData.inheritType : 0; if (transformData.translation) lTranslationM.setPosition(tempVec.fromArray(transformData.translation)); if (transformData.preRotation) { const array = transformData.preRotation.map(THREE.MathUtils.degToRad); array.push(transformData.eulerOrder); lPreRotationM.makeRotationFromEuler(tempEuler.fromArray(array)); } if (transformData.rotation) { const array = transformData.rotation.map(THREE.MathUtils.degToRad); array.push(transformData.eulerOrder); lRotationM.makeRotationFromEuler(tempEuler.fromArray(array)); } if (transformData.postRotation) { const array = transformData.postRotation.map(THREE.MathUtils.degToRad); array.push(transformData.eulerOrder); lPostRotationM.makeRotationFromEuler(tempEuler.fromArray(array)); lPostRotationM.invert(); } if (transformData.scale) lScalingM.scale(tempVec.fromArray(transformData.scale)); // Pivots and offsets if (transformData.scalingOffset) lScalingOffsetM.setPosition(tempVec.fromArray(transformData.scalingOffset)); if (transformData.scalingPivot) lScalingPivotM.setPosition(tempVec.fromArray(transformData.scalingPivot)); if (transformData.rotationOffset) lRotationOffsetM.setPosition(tempVec.fromArray(transformData.rotationOffset)); if (transformData.rotationPivot) lRotationPivotM.setPosition(tempVec.fromArray(transformData.rotationPivot)); // parent transform if (transformData.parentMatrixWorld) { lParentLX.copy(transformData.parentMatrix); lParentGX.copy(transformData.parentMatrixWorld); } const lLRM = lPreRotationM.clone().multiply(lRotationM).multiply(lPostRotationM); // Global Rotation const lParentGRM = new THREE.Matrix4(); lParentGRM.extractRotation(lParentGX); // Global Shear*Scaling const lParentTM = new THREE.Matrix4(); lParentTM.copyPosition(lParentGX); const lParentGRSM = lParentTM.clone().invert().multiply(lParentGX); const lParentGSM = lParentGRM.clone().invert().multiply(lParentGRSM); const lLSM = lScalingM; const lGlobalRS = new THREE.Matrix4(); if (inheritType === 0) { lGlobalRS.copy(lParentGRM).multiply(lLRM).multiply(lParentGSM).multiply(lLSM); } else if (inheritType === 1) { lGlobalRS.copy(lParentGRM).multiply(lParentGSM).multiply(lLRM).multiply(lLSM); } else { const lParentLSM = new THREE.Matrix4().scale(new THREE.Vector3().setFromMatrixScale(lParentLX)); const lParentLSM_inv = lParentLSM.clone().invert(); const lParentGSM_noLocal = lParentGSM.clone().multiply(lParentLSM_inv); lGlobalRS.copy(lParentGRM).multiply(lLRM).multiply(lParentGSM_noLocal).multiply(lLSM); } const lRotationPivotM_inv = lRotationPivotM.clone().invert(); const lScalingPivotM_inv = lScalingPivotM.clone().invert(); // Calculate the local transform matrix let lTransform = lTranslationM.clone().multiply(lRotationOffsetM).multiply(lRotationPivotM).multiply(lPreRotationM).multiply(lRotationM).multiply(lPostRotationM).multiply(lRotationPivotM_inv).multiply(lScalingOffsetM).multiply(lScalingPivotM).multiply(lScalingM).multiply(lScalingPivotM_inv); const lLocalTWithAllPivotAndOffsetInfo = new THREE.Matrix4().copyPosition(lTransform); const lGlobalTranslation = lParentGX.clone().multiply(lLocalTWithAllPivotAndOffsetInfo); lGlobalT.copyPosition(lGlobalTranslation); lTransform = lGlobalT.clone().multiply(lGlobalRS); // from global to local lTransform.premultiply(lParentGX.invert()); return lTransform; } // Returns the three.js intrinsic THREE.Euler order corresponding to FBX extrinsic THREE.Euler order // ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html function getEulerOrder(order) { order = order || 0; const enums = ['ZYX', // -> XYZ extrinsic 'YZX', // -> XZY extrinsic 'XZY', // -> YZX extrinsic 'ZXY', // -> YXZ extrinsic 'YXZ', // -> ZXY extrinsic 'XYZ' // -> ZYX extrinsic //'SphericXYZ', // not possible to support ]; if (order === 6) { console.warn('THREE.FBXLoader: unsupported THREE.Euler Order: Spherical XYZ. Animations and rotations may be incorrect.'); return enums[0]; } return enums[order]; } // Parses comma separated list of numbers and returns them an array. // Used internally by the TextParser function parseNumberArray(value) { const array = value.split(',').map(function (val) { return parseFloat(val); }); return array; } function convertArrayBufferToString(buffer, from, to) { if (from === undefined) from = 0; if (to === undefined) to = buffer.byteLength; return THREE.LoaderUtils.decodeText(new Uint8Array(buffer, from, to)); } function append(a, b) { for (let i = 0, j = a.length, l = b.length; i < l; i++, j++) { a[j] = b[i]; } } function slice(a, b, from, to) { for (let i = from, j = 0; i < to; i++, j++) { a[j] = b[i]; } return a; } // inject array a2 into array a1 at index function inject(a1, index, a2) { return a1.slice(0, index).concat(a2).concat(a1.slice(index)); } THREE.FBXLoader = FBXLoader; })(); module.exports = exports = THREE.FBXLoader; },{"../../three.js":25,"../fflate.min.js":12}],18:[function(require,module,exports){ const THREE = require('../../three.js'); /** * @author Rich Tibbett / https://github.com/richtr * @author mrdoob / http://mrdoob.com/ * @author Tony Parisi / http://www.tonyparisi.com/ * @author Takahiro / https://github.com/takahirox * @author Don McCurdy / https://www.donmccurdy.com */ (function () { class GLTFLoader extends THREE.Loader { constructor(manager) { super(manager); this.dracoLoader = null; this.ktx2Loader = null; this.meshoptDecoder = null; this.pluginCallbacks = []; this.register(function (parser) { return new GLTFMaterialsClearcoatExtension(parser); }); this.register(function (parser) { return new GLTFTextureBasisUExtension(parser); }); this.register(function (parser) { return new GLTFTextureWebPExtension(parser); }); this.register(function (parser) { return new GLTFMaterialsTransmissionExtension(parser); }); this.register(function (parser) { return new GLTFMaterialsVolumeExtension(parser); }); this.register(function (parser) { return new GLTFMaterialsIorExtension(parser); }); this.register(function (parser) { return new GLTFMaterialsSpecularExtension(parser); }); this.register(function (parser) { return new GLTFLightsExtension(parser); }); this.register(function (parser) { return new GLTFMeshoptCompression(parser); }); } load(url, onLoad, onProgress, onError) { const scope = this; let resourcePath; if (this.resourcePath !== '') { resourcePath = this.resourcePath; } else if (this.path !== '') { resourcePath = this.path; } else { resourcePath = THREE.LoaderUtils.extractUrlBase(url); } // Tells the LoadingManager to track an extra item, which resolves after // the model is fully loaded. This means the count of items loaded will // be incorrect, but ensures manager.onLoad() does not fire early. this.manager.itemStart(url); const _onError = function (e) { if (onError) { onError(e); } else { console.error(e); } scope.manager.itemError(url); scope.manager.itemEnd(url); }; const loader = new THREE.FileLoader(this.manager); loader.setPath(this.path); loader.setResponseType('arraybuffer'); loader.setRequestHeader(this.requestHeader); loader.setWithCredentials(this.withCredentials); loader.load(url, function (data) { try { scope.parse(data, resourcePath, function (gltf) { onLoad(gltf); scope.manager.itemEnd(url); }, _onError); } catch (e) { _onError(e); } }, onProgress, _onError); } setDRACOLoader(dracoLoader) { this.dracoLoader = dracoLoader; return this; } setDDSLoader() { throw new Error('THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".'); } setKTX2Loader(ktx2Loader) { this.ktx2Loader = ktx2Loader; return this; } setMeshoptDecoder(meshoptDecoder) { this.meshoptDecoder = meshoptDecoder; return this; } register(callback) { if (this.pluginCallbacks.indexOf(callback) === - 1) { this.pluginCallbacks.push(callback); } return this; } unregister(callback) { if (this.pluginCallbacks.indexOf(callback) !== - 1) { this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(callback), 1); } return this; } parse(data, path, onLoad, onError) { let content; const extensions = {}; const plugins = {}; if (typeof data === 'string') { content = data; } else { const magic = THREE.LoaderUtils.decodeText(new Uint8Array(data, 0, 4)); if (magic === BINARY_EXTENSION_HEADER_MAGIC) { try { extensions[EXTENSIONS.KHR_BINARY_GLTF] = new GLTFBinaryExtension(data); } catch (error) { if (onError) onError(error); return; } content = extensions[EXTENSIONS.KHR_BINARY_GLTF].content; } else { content = THREE.LoaderUtils.decodeText(new Uint8Array(data)); } } const json = JSON.parse(content); if (json.asset === undefined || json.asset.version[0] < 2) { if (onError) onError(new Error('THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.')); return; } const parser = new GLTFParser(json, { path: path || this.resourcePath || '', crossOrigin: this.crossOrigin, requestHeader: this.requestHeader, manager: this.manager, ktx2Loader: this.ktx2Loader, meshoptDecoder: this.meshoptDecoder }); parser.fileLoader.setRequestHeader(this.requestHeader); for (let i = 0; i < this.pluginCallbacks.length; i++) { const plugin = this.pluginCallbacks[i](parser); plugins[plugin.name] = plugin; // Workaround to avoid determining as unknown extension // in addUnknownExtensionsToUserData(). // Remove this workaround if we move all the existing // extension handlers to plugin system extensions[plugin.name] = true; } if (json.extensionsUsed) { for (let i = 0; i < json.extensionsUsed.length; ++i) { const extensionName = json.extensionsUsed[i]; const extensionsRequired = json.extensionsRequired || []; switch (extensionName) { case EXTENSIONS.KHR_MATERIALS_UNLIT: extensions[extensionName] = new GLTFMaterialsUnlitExtension(); break; case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: extensions[extensionName] = new GLTFMaterialsPbrSpecularGlossinessExtension(); break; case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: extensions[extensionName] = new GLTFDracoMeshCompressionExtension(json, this.dracoLoader); break; case EXTENSIONS.KHR_TEXTURE_TRANSFORM: extensions[extensionName] = new GLTFTextureTransformExtension(); break; case EXTENSIONS.KHR_MESH_QUANTIZATION: extensions[extensionName] = new GLTFMeshQuantizationExtension(); break; default: if (extensionsRequired.indexOf(extensionName) >= 0 && plugins[extensionName] === undefined) { console.warn('THREE.GLTFLoader: Unknown extension "' + extensionName + '".'); } } } } parser.setExtensions(extensions); parser.setPlugins(plugins); parser.parse(onLoad, onError); } } /* GLTFREGISTRY */ function GLTFRegistry() { let objects = {}; return { get: function (key) { return objects[key]; }, add: function (key, object) { objects[key] = object; }, remove: function (key) { delete objects[key]; }, removeAll: function () { objects = {}; } }; } /*********************************/ /********** EXTENSIONS ***********/ /*********************************/ const EXTENSIONS = { KHR_BINARY_GLTF: 'KHR_binary_glTF', KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', KHR_MATERIALS_IOR: 'KHR_materials_ior', KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', KHR_MATERIALS_VOLUME: 'KHR_materials_volume', KHR_TEXTURE_BASISU: 'KHR_texture_basisu', KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', EXT_TEXTURE_WEBP: 'EXT_texture_webp', EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression' }; /** * Punctual Lights Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ class GLTFLightsExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; // THREE.Object3D instance caches this.cache = { refs: {}, uses: {} }; } _markDefs() { const parser = this.parser; const nodeDefs = this.parser.json.nodes || []; for (let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex++) { const nodeDef = nodeDefs[nodeIndex]; if (nodeDef.extensions && nodeDef.extensions[this.name] && nodeDef.extensions[this.name].light !== undefined) { parser._addNodeRef(this.cache, nodeDef.extensions[this.name].light); } } } _loadLight(lightIndex) { const parser = this.parser; const cacheKey = 'light:' + lightIndex; let dependency = parser.cache.get(cacheKey); if (dependency) return dependency; const json = parser.json; const extensions = json.extensions && json.extensions[this.name] || {}; const lightDefs = extensions.lights || []; const lightDef = lightDefs[lightIndex]; let lightNode; const color = new THREE.Color(0xffffff); if (lightDef.color !== undefined) color.fromArray(lightDef.color); const range = lightDef.range !== undefined ? lightDef.range : 0; switch (lightDef.type) { case 'directional': lightNode = new THREE.DirectionalLight(color); lightNode.target.position.set(0, 0, - 1); lightNode.add(lightNode.target); break; case 'point': lightNode = new THREE.PointLight(color); lightNode.distance = range; break; case 'spot': lightNode = new THREE.SpotLight(color); lightNode.distance = range; // Handle spotlight properties. lightDef.spot = lightDef.spot || {}; lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; lightNode.angle = lightDef.spot.outerConeAngle; lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; lightNode.target.position.set(0, 0, - 1); lightNode.add(lightNode.target); break; default: throw new Error('THREE.GLTFLoader: Unexpected light type: ' + lightDef.type); } // Some lights (e.g. spot) default to a position other than the origin. Reset the position // here, because node-level parsing will only override position if explicitly specified. lightNode.position.set(0, 0, 0); lightNode.decay = 2; if (lightDef.intensity !== undefined) lightNode.intensity = lightDef.intensity; lightNode.name = parser.createUniqueName(lightDef.name || 'light_' + lightIndex); dependency = Promise.resolve(lightNode); parser.cache.add(cacheKey, dependency); return dependency; } createNodeAttachment(nodeIndex) { const self = this; const parser = this.parser; const json = parser.json; const nodeDef = json.nodes[nodeIndex]; const lightDef = nodeDef.extensions && nodeDef.extensions[this.name] || {}; const lightIndex = lightDef.light; if (lightIndex === undefined) return null; return this._loadLight(lightIndex).then(function (light) { return parser._getNodeRef(self.cache, lightIndex, light); }); } } /** * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit */ class GLTFMaterialsUnlitExtension { constructor() { this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; } getMaterialType() { return THREE.MeshBasicMaterial; } extendParams(materialParams, materialDef, parser) { const pending = []; materialParams.color = new THREE.Color(1.0, 1.0, 1.0); materialParams.opacity = 1.0; const metallicRoughness = materialDef.pbrMetallicRoughness; if (metallicRoughness) { if (Array.isArray(metallicRoughness.baseColorFactor)) { const array = metallicRoughness.baseColorFactor; materialParams.color.fromArray(array); materialParams.opacity = array[3]; } if (metallicRoughness.baseColorTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'map', metallicRoughness.baseColorTexture)); } } return Promise.all(pending); } } /** * Clearcoat Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat */ class GLTFMaterialsClearcoatExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; } getMaterialType(materialIndex) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) return null; return THREE.MeshPhysicalMaterial; } extendMaterialParams(materialIndex, materialParams) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) { return Promise.resolve(); } const pending = []; const extension = materialDef.extensions[this.name]; if (extension.clearcoatFactor !== undefined) { materialParams.clearcoat = extension.clearcoatFactor; } if (extension.clearcoatTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'clearcoatMap', extension.clearcoatTexture)); } if (extension.clearcoatRoughnessFactor !== undefined) { materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; } if (extension.clearcoatRoughnessTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture)); } if (extension.clearcoatNormalTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture)); if (extension.clearcoatNormalTexture.scale !== undefined) { const scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 materialParams.clearcoatNormalScale = new THREE.Vector2(scale, - scale); } } return Promise.all(pending); } } /** * Transmission Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission * Draft: https://github.com/KhronosGroup/glTF/pull/1698 */ class GLTFMaterialsTransmissionExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; } getMaterialType(materialIndex) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) return null; return THREE.MeshPhysicalMaterial; } extendMaterialParams(materialIndex, materialParams) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) { return Promise.resolve(); } const pending = []; const extension = materialDef.extensions[this.name]; if (extension.transmissionFactor !== undefined) { materialParams.transmission = extension.transmissionFactor; } if (extension.transmissionTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'transmissionMap', extension.transmissionTexture)); } return Promise.all(pending); } } /** * Materials Volume Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume */ class GLTFMaterialsVolumeExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; } getMaterialType(materialIndex) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) return null; return THREE.MeshPhysicalMaterial; } extendMaterialParams(materialIndex, materialParams) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) { return Promise.resolve(); } const pending = []; const extension = materialDef.extensions[this.name]; materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; if (extension.thicknessTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'thicknessMap', extension.thicknessTexture)); } materialParams.attenuationDistance = extension.attenuationDistance || 0; const colorArray = extension.attenuationColor || [1, 1, 1]; materialParams.attenuationTint = new THREE.Color(colorArray[0], colorArray[1], colorArray[2]); return Promise.all(pending); } } /** * Materials ior Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior */ class GLTFMaterialsIorExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_IOR; } getMaterialType(materialIndex) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) return null; return THREE.MeshPhysicalMaterial; } extendMaterialParams(materialIndex, materialParams) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) { return Promise.resolve(); } const extension = materialDef.extensions[this.name]; materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; return Promise.resolve(); } } /** * Materials specular Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular */ class GLTFMaterialsSpecularExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; } getMaterialType(materialIndex) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) return null; return THREE.MeshPhysicalMaterial; } extendMaterialParams(materialIndex, materialParams) { const parser = this.parser; const materialDef = parser.json.materials[materialIndex]; if (!materialDef.extensions || !materialDef.extensions[this.name]) { return Promise.resolve(); } const pending = []; const extension = materialDef.extensions[this.name]; materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; if (extension.specularTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'specularIntensityMap', extension.specularTexture)); } const colorArray = extension.specularColorFactor || [1, 1, 1]; materialParams.specularTint = new THREE.Color(colorArray[0], colorArray[1], colorArray[2]); if (extension.specularColorTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'specularTintMap', extension.specularColorTexture).then(function (texture) { texture.encoding = THREE.sRGBEncoding; })); } return Promise.all(pending); } } /** * BasisU THREE.Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu */ class GLTFTextureBasisUExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.KHR_TEXTURE_BASISU; } loadTexture(textureIndex) { const parser = this.parser; const json = parser.json; const textureDef = json.textures[textureIndex]; if (!textureDef.extensions || !textureDef.extensions[this.name]) { return null; } const extension = textureDef.extensions[this.name]; const source = json.images[extension.source]; const loader = parser.options.ktx2Loader; if (!loader) { if (json.extensionsRequired && json.extensionsRequired.indexOf(this.name) >= 0) { throw new Error('THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures'); } else { // Assumes that the extension is optional and that a fallback texture is present return null; } } return parser.loadTextureImage(textureIndex, source, loader); } } /** * WebP THREE.Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp */ class GLTFTextureWebPExtension { constructor(parser) { this.parser = parser; this.name = EXTENSIONS.EXT_TEXTURE_WEBP; this.isSupported = null; } loadTexture(textureIndex) { const name = this.name; const parser = this.parser; const json = parser.json; const textureDef = json.textures[textureIndex]; if (!textureDef.extensions || !textureDef.extensions[name]) { return null; } const extension = textureDef.extensions[name]; const source = json.images[extension.source]; let loader = parser.textureLoader; if (source.uri) { const handler = parser.options.manager.getHandler(source.uri); if (handler !== null) loader = handler; } return this.detectSupport().then(function (isSupported) { if (isSupported) return parser.loadTextureImage(textureIndex, source, loader); if (json.extensionsRequired && json.extensionsRequired.indexOf(name) >= 0) { throw new Error('THREE.GLTFLoader: WebP required by asset but unsupported.'); } // Fall back to PNG or JPEG. return parser.loadTexture(textureIndex); }); } detectSupport() { if (!this.isSupported) { this.isSupported = new Promise(function (resolve) { const image = new Image(); // Lossy test image. Support for lossy images doesn't guarantee support for all // WebP images, unfortunately. image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; image.onload = image.onerror = function () { resolve(image.height === 1); }; }); } return this.isSupported; } } /** * meshopt BufferView Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression */ class GLTFMeshoptCompression { constructor(parser) { this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; this.parser = parser; } loadBufferView(index) { const json = this.parser.json; const bufferView = json.bufferViews[index]; if (bufferView.extensions && bufferView.extensions[this.name]) { const extensionDef = bufferView.extensions[this.name]; const buffer = this.parser.getDependency('buffer', extensionDef.buffer); const decoder = this.parser.options.meshoptDecoder; if (!decoder || !decoder.supported) { if (json.extensionsRequired && json.extensionsRequired.indexOf(this.name) >= 0) { throw new Error('THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files'); } else { // Assumes that the extension is optional and that fallback buffer data is present return null; } } return Promise.all([buffer, decoder.ready]).then(function (res) { const byteOffset = extensionDef.byteOffset || 0; const byteLength = extensionDef.byteLength || 0; const count = extensionDef.count; const stride = extensionDef.byteStride; const result = new ArrayBuffer(count * stride); const source = new Uint8Array(res[0], byteOffset, byteLength); decoder.decodeGltfBuffer(new Uint8Array(result), count, stride, source, extensionDef.mode, extensionDef.filter); return result; }); } else { return null; } } } /* BINARY EXTENSION */ const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; const BINARY_EXTENSION_HEADER_LENGTH = 12; const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; class GLTFBinaryExtension { constructor(data) { this.name = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; const headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH); this.header = { magic: THREE.LoaderUtils.decodeText(new Uint8Array(data.slice(0, 4))), version: headerView.getUint32(4, true), length: headerView.getUint32(8, true) }; if (this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC) { throw new Error('THREE.GLTFLoader: Unsupported glTF-Binary header.'); } else if (this.header.version < 2.0) { throw new Error('THREE.GLTFLoader: Legacy binary file detected.'); } const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; const chunkView = new DataView(data, BINARY_EXTENSION_HEADER_LENGTH); let chunkIndex = 0; while (chunkIndex < chunkContentsLength) { const chunkLength = chunkView.getUint32(chunkIndex, true); chunkIndex += 4; const chunkType = chunkView.getUint32(chunkIndex, true); chunkIndex += 4; if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON) { const contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength); this.content = THREE.LoaderUtils.decodeText(contentArray); } else if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN) { const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; this.body = data.slice(byteOffset, byteOffset + chunkLength); } // Clients must ignore chunks with unknown types. chunkIndex += chunkLength; } if (this.content === null) { throw new Error('THREE.GLTFLoader: JSON content not found.'); } } } /** * DRACO THREE.Mesh Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ class GLTFDracoMeshCompressionExtension { constructor(json, dracoLoader) { if (!dracoLoader) { throw new Error('THREE.GLTFLoader: No DRACOLoader instance provided.'); } this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; this.json = json; this.dracoLoader = dracoLoader; this.dracoLoader.preload(); } decodePrimitive(primitive, parser) { const json = this.json; const dracoLoader = this.dracoLoader; const bufferViewIndex = primitive.extensions[this.name].bufferView; const gltfAttributeMap = primitive.extensions[this.name].attributes; const threeAttributeMap = {}; const attributeNormalizedMap = {}; const attributeTypeMap = {}; for (const attributeName in gltfAttributeMap) { const threeAttributeName = ATTRIBUTES[attributeName] || attributeName.toLowerCase(); threeAttributeMap[threeAttributeName] = gltfAttributeMap[attributeName]; } for (const attributeName in primitive.attributes) { const threeAttributeName = ATTRIBUTES[attributeName] || attributeName.toLowerCase(); if (gltfAttributeMap[attributeName] !== undefined) { const accessorDef = json.accessors[primitive.attributes[attributeName]]; const componentType = WEBGL_COMPONENT_TYPES[accessorDef.componentType]; attributeTypeMap[threeAttributeName] = componentType; attributeNormalizedMap[threeAttributeName] = accessorDef.normalized === true; } } return parser.getDependency('bufferView', bufferViewIndex).then(function (bufferView) { return new Promise(function (resolve) { dracoLoader.decodeDracoFile(bufferView, function (geometry) { for (const attributeName in geometry.attributes) { const attribute = geometry.attributes[attributeName]; const normalized = attributeNormalizedMap[attributeName]; if (normalized !== undefined) attribute.normalized = normalized; } resolve(geometry); }, threeAttributeMap, attributeTypeMap); }); }); } } /** * THREE.Texture Transform Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform */ class GLTFTextureTransformExtension { constructor() { this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } extendTexture(texture, transform) { if (transform.texCoord !== undefined) { console.warn('THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.'); } if (transform.offset === undefined && transform.rotation === undefined && transform.scale === undefined) { // See https://github.com/mrdoob/three.js/issues/21819. return texture; } texture = texture.clone(); if (transform.offset !== undefined) { texture.offset.fromArray(transform.offset); } if (transform.rotation !== undefined) { texture.rotation = transform.rotation; } if (transform.scale !== undefined) { texture.repeat.fromArray(transform.scale); } texture.needsUpdate = true; return texture; } } /** * Specular-Glossiness Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness */ /** * A sub class of StandardMaterial with some of the functionality * changed via the `onBeforeCompile` callback * @pailhead */ class GLTFMeshStandardSGMaterial extends THREE.MeshStandardMaterial { constructor(params) { super(); this.isGLTFSpecularGlossinessMaterial = true; //various chunks that need replacing const specularMapParsFragmentChunk = ['#ifdef USE_SPECULARMAP', ' uniform sampler2D specularMap;', '#endif'].join('\n'); const glossinessMapParsFragmentChunk = ['#ifdef USE_GLOSSINESSMAP', ' uniform sampler2D glossinessMap;', '#endif'].join('\n'); const specularMapFragmentChunk = ['vec3 specularFactor = specular;', '#ifdef USE_SPECULARMAP', ' vec4 texelSpecular = texture2D( specularMap, vUv );', ' texelSpecular = sRGBToLinear( texelSpecular );', ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', ' specularFactor *= texelSpecular.rgb;', '#endif'].join('\n'); const glossinessMapFragmentChunk = ['float glossinessFactor = glossiness;', '#ifdef USE_GLOSSINESSMAP', ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', ' glossinessFactor *= texelGlossiness.a;', '#endif'].join('\n'); const lightPhysicalFragmentChunk = ['PhysicalMaterial material;', 'material.diffuseColor = diffuseColor.rgb * ( 1. - max( specularFactor.r, max( specularFactor.g, specularFactor.b ) ) );', 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', 'material.roughness = max( 1.0 - glossinessFactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.', 'material.roughness += geometryRoughness;', 'material.roughness = min( material.roughness, 1.0 );', 'material.specularColor = specularFactor;'].join('\n'); const uniforms = { specular: { value: new THREE.Color().setHex(0xffffff) }, glossiness: { value: 1 }, specularMap: { value: null }, glossinessMap: { value: null } }; this._extraUniforms = uniforms; this.onBeforeCompile = function (shader) { for (const uniformName in uniforms) { shader.uniforms[uniformName] = uniforms[uniformName]; } shader.fragmentShader = shader.fragmentShader.replace('uniform float roughness;', 'uniform vec3 specular;').replace('uniform float metalness;', 'uniform float glossiness;').replace('#include ', specularMapParsFragmentChunk).replace('#include ', glossinessMapParsFragmentChunk).replace('#include ', specularMapFragmentChunk).replace('#include ', glossinessMapFragmentChunk).replace('#include ', lightPhysicalFragmentChunk); }; Object.defineProperties(this, { specular: { get: function () { return uniforms.specular.value; }, set: function (v) { uniforms.specular.value = v; } }, specularMap: { get: function () { return uniforms.specularMap.value; }, set: function (v) { uniforms.specularMap.value = v; if (v) { this.defines.USE_SPECULARMAP = ''; // USE_UV is set by the renderer for specular maps } else { delete this.defines.USE_SPECULARMAP; } } }, glossiness: { get: function () { return uniforms.glossiness.value; }, set: function (v) { uniforms.glossiness.value = v; } }, glossinessMap: { get: function () { return uniforms.glossinessMap.value; }, set: function (v) { uniforms.glossinessMap.value = v; if (v) { this.defines.USE_GLOSSINESSMAP = ''; this.defines.USE_UV = ''; } else { delete this.defines.USE_GLOSSINESSMAP; delete this.defines.USE_UV; } } } }); delete this.metalness; delete this.roughness; delete this.metalnessMap; delete this.roughnessMap; this.setValues(params); } copy(source) { super.copy(source); this.specularMap = source.specularMap; this.specular.copy(source.specular); this.glossinessMap = source.glossinessMap; this.glossiness = source.glossiness; delete this.metalness; delete this.roughness; delete this.metalnessMap; delete this.roughnessMap; return this; } } class GLTFMaterialsPbrSpecularGlossinessExtension { constructor() { this.name = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS; this.specularGlossinessParams = ['color', 'map', 'lightMap', 'lightMapIntensity', 'aoMap', 'aoMapIntensity', 'emissive', 'emissiveIntensity', 'emissiveMap', 'bumpMap', 'bumpScale', 'normalMap', 'normalMapType', 'displacementMap', 'displacementScale', 'displacementBias', 'specularMap', 'specular', 'glossinessMap', 'glossiness', 'alphaMap', 'envMap', 'envMapIntensity', 'refractionRatio']; } getMaterialType() { return GLTFMeshStandardSGMaterial; } extendParams(materialParams, materialDef, parser) { const pbrSpecularGlossiness = materialDef.extensions[this.name]; materialParams.color = new THREE.Color(1.0, 1.0, 1.0); materialParams.opacity = 1.0; const pending = []; if (Array.isArray(pbrSpecularGlossiness.diffuseFactor)) { const array = pbrSpecularGlossiness.diffuseFactor; materialParams.color.fromArray(array); materialParams.opacity = array[3]; } if (pbrSpecularGlossiness.diffuseTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'map', pbrSpecularGlossiness.diffuseTexture)); } materialParams.emissive = new THREE.Color(0.0, 0.0, 0.0); materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; materialParams.specular = new THREE.Color(1.0, 1.0, 1.0); if (Array.isArray(pbrSpecularGlossiness.specularFactor)) { materialParams.specular.fromArray(pbrSpecularGlossiness.specularFactor); } if (pbrSpecularGlossiness.specularGlossinessTexture !== undefined) { const specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; pending.push(parser.assignTexture(materialParams, 'glossinessMap', specGlossMapDef)); pending.push(parser.assignTexture(materialParams, 'specularMap', specGlossMapDef)); } return Promise.all(pending); } createMaterial(materialParams) { const material = new GLTFMeshStandardSGMaterial(materialParams); material.fog = true; material.color = materialParams.color; material.map = materialParams.map === undefined ? null : materialParams.map; material.lightMap = null; material.lightMapIntensity = 1.0; material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap; material.aoMapIntensity = 1.0; material.emissive = materialParams.emissive; material.emissiveIntensity = 1.0; material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap; material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap; material.bumpScale = 1; material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap; material.normalMapType = THREE.TangentSpaceNormalMap; if (materialParams.normalScale) material.normalScale = materialParams.normalScale; material.displacementMap = null; material.displacementScale = 1; material.displacementBias = 0; material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap; material.specular = materialParams.specular; material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap; material.glossiness = materialParams.glossiness; material.alphaMap = null; material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap; material.envMapIntensity = 1.0; material.refractionRatio = 0.98; return material; } } /** * THREE.Mesh Quantization Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization */ class GLTFMeshQuantizationExtension { constructor() { this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; } } /*********************************/ /********** INTERPOLATION ********/ /*********************************/ // Spline Interpolation // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation class GLTFCubicSplineInterpolant extends THREE.Interpolant { constructor(parameterPositions, sampleValues, sampleSize, resultBuffer) { super(parameterPositions, sampleValues, sampleSize, resultBuffer); } copySampleValue_(index) { // Copies a sample value to the result buffer. See description of glTF // CUBICSPLINE values layout in interpolate_() function below. const result = this.resultBuffer, values = this.sampleValues, valueSize = this.valueSize, offset = index * valueSize * 3 + valueSize; for (let i = 0; i !== valueSize; i++) { result[i] = values[offset + i]; } return result; } } GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; GLTFCubicSplineInterpolant.prototype.interpolate_ = function (i1, t0, t, t1) { const result = this.resultBuffer; const values = this.sampleValues; const stride = this.valueSize; const stride2 = stride * 2; const stride3 = stride * 3; const td = t1 - t0; const p = (t - t0) / td; const pp = p * p; const ppp = pp * p; const offset1 = i1 * stride3; const offset0 = offset1 - stride3; const s2 = - 2 * ppp + 3 * pp; const s3 = ppp - pp; const s0 = 1 - s2; const s1 = s3 - pp + p; // Layout of keyframe output values for CUBICSPLINE animations: // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] for (let i = 0; i !== stride; i++) { const p0 = values[offset0 + i + stride]; // splineVertex_k const m0 = values[offset0 + i + stride2] * td; // outTangent_k * (t_k+1 - t_k) const p1 = values[offset1 + i + stride]; // splineVertex_k+1 const m1 = values[offset1 + i] * td; // inTangent_k+1 * (t_k+1 - t_k) result[i] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; } return result; }; const _q = new THREE.Quaternion(); class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { interpolate_(i1, t0, t, t1) { const result = super.interpolate_(i1, t0, t, t1); _q.fromArray(result).normalize().toArray(result); return result; } } /*********************************/ /********** INTERNALS ************/ /*********************************/ /* CONSTANTS */ const WEBGL_CONSTANTS = { FLOAT: 5126, //FLOAT_MAT2: 35674, FLOAT_MAT3: 35675, FLOAT_MAT4: 35676, FLOAT_VEC2: 35664, FLOAT_VEC3: 35665, FLOAT_VEC4: 35666, LINEAR: 9729, REPEAT: 10497, SAMPLER_2D: 35678, POINTS: 0, LINES: 1, LINE_LOOP: 2, LINE_STRIP: 3, TRIANGLES: 4, TRIANGLE_STRIP: 5, TRIANGLE_FAN: 6, UNSIGNED_BYTE: 5121, UNSIGNED_SHORT: 5123 }; const WEBGL_COMPONENT_TYPES = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, 5123: Uint16Array, 5125: Uint32Array, 5126: Float32Array }; const WEBGL_FILTERS = { 9728: THREE.NearestFilter, 9729: THREE.LinearFilter, 9984: THREE.NearestMipmapNearestFilter, 9985: THREE.LinearMipmapNearestFilter, 9986: THREE.NearestMipmapLinearFilter, 9987: THREE.LinearMipmapLinearFilter }; const WEBGL_WRAPPINGS = { 33071: THREE.ClampToEdgeWrapping, 33648: THREE.MirroredRepeatWrapping, 10497: THREE.RepeatWrapping }; const WEBGL_TYPE_SIZES = { 'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, 'VEC4': 4, 'MAT2': 4, 'MAT3': 9, 'MAT4': 16 }; const ATTRIBUTES = { POSITION: 'position', NORMAL: 'normal', TANGENT: 'tangent', TEXCOORD_0: 'uv', TEXCOORD_1: 'uv2', COLOR_0: 'color', WEIGHTS_0: 'skinWeight', JOINTS_0: 'skinIndex' }; const PATH_PROPERTIES = { scale: 'scale', translation: 'position', rotation: 'quaternion', weights: 'morphTargetInfluences' }; const INTERPOLATION = { CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each // keyframe track will be initialized with a default interpolation type, then modified. LINEAR: THREE.InterpolateLinear, STEP: THREE.InterpolateDiscrete }; const ALPHA_MODES = { OPAQUE: 'OPAQUE', MASK: 'MASK', BLEND: 'BLEND' }; /* UTILITY FUNCTIONS */ function resolveURL(url, path) { // Invalid URL if (typeof url !== 'string' || url === '') return ''; // Host Relative URL if (/^https?:\/\//i.test(path) && /^\//.test(url)) { path = path.replace(/(^https?:\/\/[^\/]+).*/i, '$1'); } // Absolute URL http://,https://,// if (/^(https?:)?\/\//i.test(url)) return url; // Data URI if (/^data:.*,.*$/i.test(url)) return url; // Blob URL if (/^blob:.*$/i.test(url)) return url; // Relative URL return path + url; } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material */ function createDefaultMaterial(cache) { if (cache['DefaultMaterial'] === undefined) { cache['DefaultMaterial'] = new THREE.MeshStandardMaterial({ color: 0xFFFFFF, emissive: 0x000000, metalness: 1, roughness: 1, transparent: false, depthTest: true, side: THREE.FrontSide }); } return cache['DefaultMaterial']; } function addUnknownExtensionsToUserData(knownExtensions, object, objectDef) { // Add unknown glTF extensions to an object's userData. for (const name in objectDef.extensions) { if (knownExtensions[name] === undefined) { object.userData.gltfExtensions = object.userData.gltfExtensions || {}; object.userData.gltfExtensions[name] = objectDef.extensions[name]; } } } /** * @param {Object3D|Material|BufferGeometry} object * @param {GLTF.definition} gltfDef */ function assignExtrasToUserData(object, gltfDef) { if (gltfDef.extras !== undefined) { if (typeof gltfDef.extras === 'object') { Object.assign(object.userData, gltfDef.extras); } else { console.warn('THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras); } } } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets * * @param {BufferGeometry} geometry * @param {Array} targets * @param {GLTFParser} parser * @return {Promise} */ function addMorphTargets(geometry, targets, parser) { let hasMorphPosition = false; let hasMorphNormal = false; for (let i = 0, il = targets.length; i < il; i++) { const target = targets[i]; if (target.POSITION !== undefined) hasMorphPosition = true; if (target.NORMAL !== undefined) hasMorphNormal = true; if (hasMorphPosition && hasMorphNormal) break; } if (!hasMorphPosition && !hasMorphNormal) return Promise.resolve(geometry); const pendingPositionAccessors = []; const pendingNormalAccessors = []; for (let i = 0, il = targets.length; i < il; i++) { const target = targets[i]; if (hasMorphPosition) { const pendingAccessor = target.POSITION !== undefined ? parser.getDependency('accessor', target.POSITION) : geometry.attributes.position; pendingPositionAccessors.push(pendingAccessor); } if (hasMorphNormal) { const pendingAccessor = target.NORMAL !== undefined ? parser.getDependency('accessor', target.NORMAL) : geometry.attributes.normal; pendingNormalAccessors.push(pendingAccessor); } } return Promise.all([Promise.all(pendingPositionAccessors), Promise.all(pendingNormalAccessors)]).then(function (accessors) { const morphPositions = accessors[0]; const morphNormals = accessors[1]; if (hasMorphPosition) geometry.morphAttributes.position = morphPositions; if (hasMorphNormal) geometry.morphAttributes.normal = morphNormals; geometry.morphTargetsRelative = true; return geometry; }); } /** * @param {Mesh} mesh * @param {GLTF.Mesh} meshDef */ function updateMorphTargets(mesh, meshDef) { mesh.updateMorphTargets(); if (meshDef.weights !== undefined) { for (let i = 0, il = meshDef.weights.length; i < il; i++) { mesh.morphTargetInfluences[i] = meshDef.weights[i]; } } // .extras has user-defined data, so check that .extras.targetNames is an array. if (meshDef.extras && Array.isArray(meshDef.extras.targetNames)) { const targetNames = meshDef.extras.targetNames; if (mesh.morphTargetInfluences.length === targetNames.length) { mesh.morphTargetDictionary = {}; for (let i = 0, il = targetNames.length; i < il; i++) { mesh.morphTargetDictionary[targetNames[i]] = i; } } else { console.warn('THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.'); } } } function createPrimitiveKey(primitiveDef) { const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION]; let geometryKey; if (dracoExtension) { geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey(dracoExtension.attributes); } else { geometryKey = primitiveDef.indices + ':' + createAttributesKey(primitiveDef.attributes) + ':' + primitiveDef.mode; } return geometryKey; } function createAttributesKey(attributes) { let attributesKey = ''; const keys = Object.keys(attributes).sort(); for (let i = 0, il = keys.length; i < il; i++) { attributesKey += keys[i] + ':' + attributes[keys[i]] + ';'; } return attributesKey; } function getNormalizedComponentScale(constructor) { // Reference: // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data switch (constructor) { case Int8Array: return 1 / 127; case Uint8Array: return 1 / 255; case Int16Array: return 1 / 32767; case Uint16Array: return 1 / 65535; default: throw new Error('THREE.GLTFLoader: Unsupported normalized accessor component type.'); } } /* GLTF PARSER */ class GLTFParser { constructor(json = {}, options = {}) { this.json = json; this.extensions = {}; this.plugins = {}; this.options = options; // loader object cache this.cache = new GLTFRegistry(); // associations between Three.js objects and glTF elements this.associations = new Map(); // THREE.BufferGeometry caching this.primitiveCache = {}; // THREE.Object3D instance caches this.meshCache = { refs: {}, uses: {} }; this.cameraCache = { refs: {}, uses: {} }; this.lightCache = { refs: {}, uses: {} }; this.textureCache = {}; // Track node names, to ensure no duplicates this.nodeNamesUsed = {}; // Use an THREE.ImageBitmapLoader if imageBitmaps are supported. Moves much of the // expensive work of uploading a texture to the GPU off the main thread. if (typeof createImageBitmap !== 'undefined' && /Firefox/.test(navigator.userAgent) === false) { this.textureLoader = new THREE.ImageBitmapLoader(this.options.manager); } else { this.textureLoader = new THREE.TextureLoader(this.options.manager); } this.textureLoader.setCrossOrigin(this.options.crossOrigin); this.textureLoader.setRequestHeader(this.options.requestHeader); this.fileLoader = new THREE.FileLoader(this.options.manager); this.fileLoader.setResponseType('arraybuffer'); if (this.options.crossOrigin === 'use-credentials') { this.fileLoader.setWithCredentials(true); } } setExtensions(extensions) { this.extensions = extensions; } setPlugins(plugins) { this.plugins = plugins; } parse(onLoad, onError) { const parser = this; const json = this.json; const extensions = this.extensions; // Clear the loader cache this.cache.removeAll(); // Mark the special nodes/meshes in json for efficient parse this._invokeAll(function (ext) { return ext._markDefs && ext._markDefs(); }); Promise.all(this._invokeAll(function (ext) { return ext.beforeRoot && ext.beforeRoot(); })).then(function () { return Promise.all([parser.getDependencies('scene'), parser.getDependencies('animation'), parser.getDependencies('camera')]); }).then(function (dependencies) { const result = { scene: dependencies[0][json.scene || 0], scenes: dependencies[0], animations: dependencies[1], cameras: dependencies[2], asset: json.asset, parser: parser, userData: {} }; addUnknownExtensionsToUserData(extensions, result, json); assignExtrasToUserData(result, json); Promise.all(parser._invokeAll(function (ext) { return ext.afterRoot && ext.afterRoot(result); })).then(function () { onLoad(result); }); }).catch(onError); } /** * Marks the special nodes/meshes in json for efficient parse. */ _markDefs() { const nodeDefs = this.json.nodes || []; const skinDefs = this.json.skins || []; const meshDefs = this.json.meshes || []; // Nothing in the node definition indicates whether it is a THREE.Bone or an // THREE.Object3D. Use the skins' joint references to mark bones. for (let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex++) { const joints = skinDefs[skinIndex].joints; for (let i = 0, il = joints.length; i < il; i++) { nodeDefs[joints[i]].isBone = true; } } // Iterate over all nodes, marking references to shared resources, // as well as skeleton joints. for (let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex++) { const nodeDef = nodeDefs[nodeIndex]; if (nodeDef.mesh !== undefined) { this._addNodeRef(this.meshCache, nodeDef.mesh); // Nothing in the mesh definition indicates whether it is // a THREE.SkinnedMesh or THREE.Mesh. Use the node's mesh reference // to mark THREE.SkinnedMesh if node has skin. if (nodeDef.skin !== undefined) { meshDefs[nodeDef.mesh].isSkinnedMesh = true; } } if (nodeDef.camera !== undefined) { this._addNodeRef(this.cameraCache, nodeDef.camera); } } } /** * Counts references to shared node / THREE.Object3D resources. These resources * can be reused, or "instantiated", at multiple nodes in the scene * hierarchy. THREE.Mesh, Camera, and Light instances are instantiated and must * be marked. Non-scenegraph resources (like Materials, Geometries, and * Textures) can be reused directly and are not marked here. * * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. */ _addNodeRef(cache, index) { if (index === undefined) return; if (cache.refs[index] === undefined) { cache.refs[index] = cache.uses[index] = 0; } cache.refs[index]++; } /** Returns a reference to a shared resource, cloning it if necessary. */ _getNodeRef(cache, index, object) { if (cache.refs[index] <= 1) return object; const ref = object.clone(); ref.name += '_instance_' + cache.uses[index]++; return ref; } _invokeOne(func) { const extensions = Object.values(this.plugins); extensions.push(this); for (let i = 0; i < extensions.length; i++) { const result = func(extensions[i]); if (result) return result; } return null; } _invokeAll(func) { const extensions = Object.values(this.plugins); extensions.unshift(this); const pending = []; for (let i = 0; i < extensions.length; i++) { const result = func(extensions[i]); if (result) pending.push(result); } return pending; } /** * Requests the specified dependency asynchronously, with caching. * @param {string} type * @param {number} index * @return {Promise} */ getDependency(type, index) { const cacheKey = type + ':' + index; let dependency = this.cache.get(cacheKey); if (!dependency) { switch (type) { case 'scene': dependency = this.loadScene(index); break; case 'node': dependency = this.loadNode(index); break; case 'mesh': dependency = this._invokeOne(function (ext) { return ext.loadMesh && ext.loadMesh(index); }); break; case 'accessor': dependency = this.loadAccessor(index); break; case 'bufferView': dependency = this._invokeOne(function (ext) { return ext.loadBufferView && ext.loadBufferView(index); }); break; case 'buffer': dependency = this.loadBuffer(index); break; case 'material': dependency = this._invokeOne(function (ext) { return ext.loadMaterial && ext.loadMaterial(index); }); break; case 'texture': dependency = this._invokeOne(function (ext) { return ext.loadTexture && ext.loadTexture(index); }); break; case 'skin': dependency = this.loadSkin(index); break; case 'animation': dependency = this.loadAnimation(index); break; case 'camera': dependency = this.loadCamera(index); break; default: throw new Error('Unknown type: ' + type); } this.cache.add(cacheKey, dependency); } return dependency; } /** * Requests all dependencies of the specified type asynchronously, with caching. * @param {string} type * @return {Promise>} */ getDependencies(type) { let dependencies = this.cache.get(type); if (!dependencies) { const parser = this; const defs = this.json[type + (type === 'mesh' ? 'es' : 's')] || []; dependencies = Promise.all(defs.map(function (def, index) { return parser.getDependency(type, index); })); this.cache.add(type, dependencies); } return dependencies; } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views * @param {number} bufferIndex * @return {Promise} */ loadBuffer(bufferIndex) { const bufferDef = this.json.buffers[bufferIndex]; const loader = this.fileLoader; if (bufferDef.type && bufferDef.type !== 'arraybuffer') { throw new Error('THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.'); } // If present, GLB container is required to be the first buffer. if (bufferDef.uri === undefined && bufferIndex === 0) { return Promise.resolve(this.extensions[EXTENSIONS.KHR_BINARY_GLTF].body); } const options = this.options; return new Promise(function (resolve, reject) { loader.load(resolveURL(bufferDef.uri, options.path), resolve, undefined, function () { reject(new Error('THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".')); }); }); } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views * @param {number} bufferViewIndex * @return {Promise} */ loadBufferView(bufferViewIndex) { const bufferViewDef = this.json.bufferViews[bufferViewIndex]; return this.getDependency('buffer', bufferViewDef.buffer).then(function (buffer) { const byteLength = bufferViewDef.byteLength || 0; const byteOffset = bufferViewDef.byteOffset || 0; return buffer.slice(byteOffset, byteOffset + byteLength); }); } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors * @param {number} accessorIndex * @return {Promise} */ loadAccessor(accessorIndex) { const parser = this; const json = this.json; const accessorDef = this.json.accessors[accessorIndex]; if (accessorDef.bufferView === undefined && accessorDef.sparse === undefined) { // Ignore empty accessors, which may be used to declare runtime // information about attributes coming from another source (e.g. Draco // compression extension). return Promise.resolve(null); } const pendingBufferViews = []; if (accessorDef.bufferView !== undefined) { pendingBufferViews.push(this.getDependency('bufferView', accessorDef.bufferView)); } else { pendingBufferViews.push(null); } if (accessorDef.sparse !== undefined) { pendingBufferViews.push(this.getDependency('bufferView', accessorDef.sparse.indices.bufferView)); pendingBufferViews.push(this.getDependency('bufferView', accessorDef.sparse.values.bufferView)); } return Promise.all(pendingBufferViews).then(function (bufferViews) { const bufferView = bufferViews[0]; const itemSize = WEBGL_TYPE_SIZES[accessorDef.type]; const TypedArray = WEBGL_COMPONENT_TYPES[accessorDef.componentType]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. const elementBytes = TypedArray.BYTES_PER_ELEMENT; const itemBytes = elementBytes * itemSize; const byteOffset = accessorDef.byteOffset || 0; const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[accessorDef.bufferView].byteStride : undefined; const normalized = accessorDef.normalized === true; let array, bufferAttribute; // The buffer is not interleaved if the stride is the item size in bytes. if (byteStride && byteStride !== itemBytes) { // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own THREE.InterleavedBuffer // This makes sure that IBA.count reflects accessor.count properly const ibSlice = Math.floor(byteOffset / byteStride); const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; let ib = parser.cache.get(ibCacheKey); if (!ib) { array = new TypedArray(bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes); // Integer parameters to IB/IBA are in array elements, not bytes. ib = new THREE.InterleavedBuffer(array, byteStride / elementBytes); parser.cache.add(ibCacheKey, ib); } bufferAttribute = new THREE.InterleavedBufferAttribute(ib, itemSize, byteOffset % byteStride / elementBytes, normalized); } else { if (bufferView === null) { array = new TypedArray(accessorDef.count * itemSize); } else { array = new TypedArray(bufferView, byteOffset, accessorDef.count * itemSize); } bufferAttribute = new THREE.BufferAttribute(array, itemSize, normalized); } // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors if (accessorDef.sparse !== undefined) { const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; const TypedArrayIndices = WEBGL_COMPONENT_TYPES[accessorDef.sparse.indices.componentType]; const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; const sparseIndices = new TypedArrayIndices(bufferViews[1], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices); const sparseValues = new TypedArray(bufferViews[2], byteOffsetValues, accessorDef.sparse.count * itemSize); if (bufferView !== null) { // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. bufferAttribute = new THREE.BufferAttribute(bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized); } for (let i = 0, il = sparseIndices.length; i < il; i++) { const index = sparseIndices[i]; bufferAttribute.setX(index, sparseValues[i * itemSize]); if (itemSize >= 2) bufferAttribute.setY(index, sparseValues[i * itemSize + 1]); if (itemSize >= 3) bufferAttribute.setZ(index, sparseValues[i * itemSize + 2]); if (itemSize >= 4) bufferAttribute.setW(index, sparseValues[i * itemSize + 3]); if (itemSize >= 5) throw new Error('THREE.GLTFLoader: Unsupported itemSize in sparse THREE.BufferAttribute.'); } } return bufferAttribute; }); } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures * @param {number} textureIndex * @return {Promise} */ loadTexture(textureIndex) { const json = this.json; const options = this.options; const textureDef = json.textures[textureIndex]; const source = json.images[textureDef.source]; let loader = this.textureLoader; if (source.uri) { const handler = options.manager.getHandler(source.uri); if (handler !== null) loader = handler; } return this.loadTextureImage(textureIndex, source, loader); } loadTextureImage(textureIndex, source, loader) { const parser = this; const json = this.json; const options = this.options; const textureDef = json.textures[textureIndex]; const cacheKey = (source.uri || source.bufferView) + ':' + textureDef.sampler; if (this.textureCache[cacheKey]) { // See https://github.com/mrdoob/three.js/issues/21559. return this.textureCache[cacheKey]; } const URL = self.URL || self.webkitURL; let sourceURI = source.uri || ''; let isObjectURL = false; let hasAlpha = true; const isJPEG = sourceURI.search(/\.jpe?g($|\?)/i) > 0 || sourceURI.search(/^data\:image\/jpeg/) === 0; if (source.mimeType === 'image/jpeg' || isJPEG) hasAlpha = false; if (source.bufferView !== undefined) { // Load binary image data from bufferView, if provided. sourceURI = parser.getDependency('bufferView', source.bufferView).then(function (bufferView) { if (source.mimeType === 'image/png') { // Inspect the PNG 'IHDR' chunk to determine whether the image could have an // alpha channel. This check is conservative � the image could have an alpha // channel with all values == 1, and the indexed type (colorType == 3) only // sometimes contains alpha. // // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header const colorType = new DataView(bufferView, 25, 1).getUint8(0, false); hasAlpha = colorType === 6 || colorType === 4 || colorType === 3; } isObjectURL = true; const blob = new Blob([bufferView], { type: source.mimeType }); sourceURI = URL.createObjectURL(blob); return sourceURI; }); } else if (source.uri === undefined) { throw new Error('THREE.GLTFLoader: Image ' + textureIndex + ' is missing URI and bufferView'); } const promise = Promise.resolve(sourceURI).then(function (sourceURI) { return new Promise(function (resolve, reject) { let onLoad = resolve; if (loader.isImageBitmapLoader === true) { onLoad = function (imageBitmap) { const texture = new THREE.Texture(imageBitmap); texture.needsUpdate = true; resolve(texture); }; } loader.load(resolveURL(sourceURI, options.path), onLoad, undefined, reject); }); }).then(function (texture) { // Clean up resources and configure THREE.Texture. if (isObjectURL === true) { URL.revokeObjectURL(sourceURI); } texture.flipY = false; if (textureDef.name) texture.name = textureDef.name; // When there is definitely no alpha channel in the texture, set THREE.RGBFormat to save space. if (!hasAlpha) texture.format = THREE.RGBFormat; const samplers = json.samplers || {}; const sampler = samplers[textureDef.sampler] || {}; texture.magFilter = WEBGL_FILTERS[sampler.magFilter] || THREE.LinearFilter; texture.minFilter = WEBGL_FILTERS[sampler.minFilter] || THREE.LinearMipmapLinearFilter; texture.wrapS = WEBGL_WRAPPINGS[sampler.wrapS] || THREE.RepeatWrapping; texture.wrapT = WEBGL_WRAPPINGS[sampler.wrapT] || THREE.RepeatWrapping; parser.associations.set(texture, { type: 'textures', index: textureIndex }); return texture; }).catch(function () { console.error('THREE.GLTFLoader: Couldn\'t load texture', sourceURI); return null; }); this.textureCache[cacheKey] = promise; return promise; } /** * Asynchronously assigns a texture to the given material parameters. * @param {Object} materialParams * @param {string} mapName * @param {Object} mapDef * @return {Promise} */ assignTexture(materialParams, mapName, mapDef) { const parser = this; return this.getDependency('texture', mapDef.index).then(function (texture) { // Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured // However, we will copy UV set 0 to UV set 1 on demand for aoMap if (mapDef.texCoord !== undefined && mapDef.texCoord != 0 && !(mapName === 'aoMap' && mapDef.texCoord == 1)) { console.warn('THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.'); } if (parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM]) { const transform = mapDef.extensions !== undefined ? mapDef.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM] : undefined; if (transform) { const gltfReference = parser.associations.get(texture); texture = parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM].extendTexture(texture, transform); parser.associations.set(texture, gltfReference); } } materialParams[mapName] = texture; return texture; }); } /** * Assigns final material to a THREE.Mesh, THREE.Line, or THREE.Points instance. The instance * already has a material (generated from the glTF material options alone) * but reuse of the same glTF material may require multiple threejs materials * to accommodate different primitive types, defines, etc. New materials will * be created if necessary, and reused from a cache. * @param {Object3D} mesh THREE.Mesh, THREE.Line, or THREE.Points instance. */ assignFinalMaterial(mesh) { const geometry = mesh.geometry; let material = mesh.material; const useVertexTangents = geometry.attributes.tangent !== undefined; const useVertexColors = geometry.attributes.color !== undefined; const useFlatShading = geometry.attributes.normal === undefined; if (mesh.isPoints) { const cacheKey = 'PointsMaterial:' + material.uuid; let pointsMaterial = this.cache.get(cacheKey); if (!pointsMaterial) { pointsMaterial = new THREE.PointsMaterial(); THREE.Material.prototype.copy.call(pointsMaterial, material); pointsMaterial.color.copy(material.color); pointsMaterial.map = material.map; pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px this.cache.add(cacheKey, pointsMaterial); } material = pointsMaterial; } else if (mesh.isLine) { const cacheKey = 'LineBasicMaterial:' + material.uuid; let lineMaterial = this.cache.get(cacheKey); if (!lineMaterial) { lineMaterial = new THREE.LineBasicMaterial(); THREE.Material.prototype.copy.call(lineMaterial, material); lineMaterial.color.copy(material.color); this.cache.add(cacheKey, lineMaterial); } material = lineMaterial; } // Clone the material if it will be modified if (useVertexTangents || useVertexColors || useFlatShading) { let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; if (material.isGLTFSpecularGlossinessMaterial) cacheKey += 'specular-glossiness:'; if (useVertexTangents) cacheKey += 'vertex-tangents:'; if (useVertexColors) cacheKey += 'vertex-colors:'; if (useFlatShading) cacheKey += 'flat-shading:'; let cachedMaterial = this.cache.get(cacheKey); if (!cachedMaterial) { cachedMaterial = material.clone(); if (useVertexColors) cachedMaterial.vertexColors = true; if (useFlatShading) cachedMaterial.flatShading = true; if (useVertexTangents) { // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 if (cachedMaterial.normalScale) cachedMaterial.normalScale.y *= - 1; if (cachedMaterial.clearcoatNormalScale) cachedMaterial.clearcoatNormalScale.y *= - 1; } this.cache.add(cacheKey, cachedMaterial); this.associations.set(cachedMaterial, this.associations.get(material)); } material = cachedMaterial; } // workarounds for mesh and geometry if (material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined) { geometry.setAttribute('uv2', geometry.attributes.uv); } mesh.material = material; } getMaterialType() { return THREE.MeshStandardMaterial; } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials * @param {number} materialIndex * @return {Promise} */ loadMaterial(materialIndex) { const parser = this; const json = this.json; const extensions = this.extensions; const materialDef = json.materials[materialIndex]; let materialType; const materialParams = {}; const materialExtensions = materialDef.extensions || {}; const pending = []; if (materialExtensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS]) { const sgExtension = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS]; materialType = sgExtension.getMaterialType(); pending.push(sgExtension.extendParams(materialParams, materialDef, parser)); } else if (materialExtensions[EXTENSIONS.KHR_MATERIALS_UNLIT]) { const kmuExtension = extensions[EXTENSIONS.KHR_MATERIALS_UNLIT]; materialType = kmuExtension.getMaterialType(); pending.push(kmuExtension.extendParams(materialParams, materialDef, parser)); } else { // Specification: // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material const metallicRoughness = materialDef.pbrMetallicRoughness || {}; materialParams.color = new THREE.Color(1.0, 1.0, 1.0); materialParams.opacity = 1.0; if (Array.isArray(metallicRoughness.baseColorFactor)) { const array = metallicRoughness.baseColorFactor; materialParams.color.fromArray(array); materialParams.opacity = array[3]; } if (metallicRoughness.baseColorTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'map', metallicRoughness.baseColorTexture)); } materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; if (metallicRoughness.metallicRoughnessTexture !== undefined) { pending.push(parser.assignTexture(materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture)); pending.push(parser.assignTexture(materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture)); } materialType = this._invokeOne(function (ext) { return ext.getMaterialType && ext.getMaterialType(materialIndex); }); pending.push(Promise.all(this._invokeAll(function (ext) { return ext.extendMaterialParams && ext.extendMaterialParams(materialIndex, materialParams); }))); } if (materialDef.doubleSided === true) { materialParams.side = THREE.DoubleSide; } const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; if (alphaMode === ALPHA_MODES.BLEND) { materialParams.transparent = true; // See: https://github.com/mrdoob/three.js/issues/17706 materialParams.depthWrite = false; } else { materialParams.format = THREE.RGBFormat; materialParams.transparent = false; if (alphaMode === ALPHA_MODES.MASK) { materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; } } if (materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial) { pending.push(parser.assignTexture(materialParams, 'normalMap', materialDef.normalTexture)); // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 materialParams.normalScale = new THREE.Vector2(1, - 1); if (materialDef.normalTexture.scale !== undefined) { materialParams.normalScale.set(materialDef.normalTexture.scale, - materialDef.normalTexture.scale); } } if (materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial) { pending.push(parser.assignTexture(materialParams, 'aoMap', materialDef.occlusionTexture)); if (materialDef.occlusionTexture.strength !== undefined) { materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; } } if (materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial) { materialParams.emissive = new THREE.Color().fromArray(materialDef.emissiveFactor); } if (materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial) { pending.push(parser.assignTexture(materialParams, 'emissiveMap', materialDef.emissiveTexture)); } return Promise.all(pending).then(function () { let material; if (materialType === GLTFMeshStandardSGMaterial) { material = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].createMaterial(materialParams); } else { material = new materialType(materialParams); } if (materialDef.name) material.name = materialDef.name; // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding. if (material.map) material.map.encoding = THREE.sRGBEncoding; if (material.emissiveMap) material.emissiveMap.encoding = THREE.sRGBEncoding; assignExtrasToUserData(material, materialDef); parser.associations.set(material, { type: 'materials', index: materialIndex }); if (materialDef.extensions) addUnknownExtensionsToUserData(extensions, material, materialDef); return material; }); } /** When THREE.Object3D instances are targeted by animation, they need unique names. */ createUniqueName(originalName) { const sanitizedName = THREE.PropertyBinding.sanitizeNodeName(originalName || ''); let name = sanitizedName; for (let i = 1; this.nodeNamesUsed[name]; ++i) { name = sanitizedName + '_' + i; } this.nodeNamesUsed[name] = true; return name; } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry * * Creates BufferGeometries from primitives. * * @param {Array} primitives * @return {Promise>} */ loadGeometries(primitives) { const parser = this; const extensions = this.extensions; const cache = this.primitiveCache; function createDracoPrimitive(primitive) { return extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION].decodePrimitive(primitive, parser).then(function (geometry) { return addPrimitiveAttributes(geometry, primitive, parser); }); } const pending = []; for (let i = 0, il = primitives.length; i < il; i++) { const primitive = primitives[i]; const cacheKey = createPrimitiveKey(primitive); // See if we've already created this geometry const cached = cache[cacheKey]; if (cached) { // Use the cached geometry if it exists pending.push(cached.promise); } else { let geometryPromise; if (primitive.extensions && primitive.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION]) { // Use DRACO geometry if available geometryPromise = createDracoPrimitive(primitive); } else { // Otherwise create a new geometry geometryPromise = addPrimitiveAttributes(new THREE.BufferGeometry(), primitive, parser); } // Cache this geometry cache[cacheKey] = { primitive: primitive, promise: geometryPromise }; pending.push(geometryPromise); } } return Promise.all(pending); } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes * @param {number} meshIndex * @return {Promise} */ loadMesh(meshIndex) { const parser = this; const json = this.json; const extensions = this.extensions; const meshDef = json.meshes[meshIndex]; const primitives = meshDef.primitives; const pending = []; for (let i = 0, il = primitives.length; i < il; i++) { const material = primitives[i].material === undefined ? createDefaultMaterial(this.cache) : this.getDependency('material', primitives[i].material); pending.push(material); } pending.push(parser.loadGeometries(primitives)); return Promise.all(pending).then(function (results) { const materials = results.slice(0, results.length - 1); const geometries = results[results.length - 1]; const meshes = []; for (let i = 0, il = geometries.length; i < il; i++) { const geometry = geometries[i]; const primitive = primitives[i]; // 1. create THREE.Mesh let mesh; const material = materials[i]; if (primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || primitive.mode === undefined) { // .isSkinnedMesh isn't in glTF spec. See ._markDefs() mesh = meshDef.isSkinnedMesh === true ? new THREE.SkinnedMesh(geometry, material) : new THREE.Mesh(geometry, material); if (mesh.isSkinnedMesh === true && !mesh.geometry.attributes.skinWeight.normalized) { // we normalize floating point skin weight array to fix malformed assets (see #15319) // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs mesh.normalizeSkinWeights(); } if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP) { mesh.geometry = toTrianglesDrawMode(mesh.geometry, THREE.TriangleStripDrawMode); } else if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN) { mesh.geometry = toTrianglesDrawMode(mesh.geometry, THREE.TriangleFanDrawMode); } } else if (primitive.mode === WEBGL_CONSTANTS.LINES) { mesh = new THREE.LineSegments(geometry, material); } else if (primitive.mode === WEBGL_CONSTANTS.LINE_STRIP) { mesh = new THREE.Line(geometry, material); } else if (primitive.mode === WEBGL_CONSTANTS.LINE_LOOP) { mesh = new THREE.LineLoop(geometry, material); } else if (primitive.mode === WEBGL_CONSTANTS.POINTS) { mesh = new THREE.Points(geometry, material); } else { throw new Error('THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode); } if (Object.keys(mesh.geometry.morphAttributes).length > 0) { updateMorphTargets(mesh, meshDef); } mesh.name = parser.createUniqueName(meshDef.name || 'mesh_' + meshIndex); assignExtrasToUserData(mesh, meshDef); if (primitive.extensions) addUnknownExtensionsToUserData(extensions, mesh, primitive); parser.assignFinalMaterial(mesh); meshes.push(mesh); } if (meshes.length === 1) { return meshes[0]; } const group = new THREE.Group(); for (let i = 0, il = meshes.length; i < il; i++) { group.add(meshes[i]); } return group; }); } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras * @param {number} cameraIndex * @return {Promise} */ loadCamera(cameraIndex) { let camera; const cameraDef = this.json.cameras[cameraIndex]; const params = cameraDef[cameraDef.type]; if (!params) { console.warn('THREE.GLTFLoader: Missing camera parameters.'); return; } if (cameraDef.type === 'perspective') { camera = new THREE.PerspectiveCamera(THREE.MathUtils.radToDeg(params.yfov), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6); } else if (cameraDef.type === 'orthographic') { camera = new THREE.OrthographicCamera(- params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar); } if (cameraDef.name) camera.name = this.createUniqueName(cameraDef.name); assignExtrasToUserData(camera, cameraDef); return Promise.resolve(camera); } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins * @param {number} skinIndex * @return {Promise} */ loadSkin(skinIndex) { const skinDef = this.json.skins[skinIndex]; const skinEntry = { joints: skinDef.joints }; if (skinDef.inverseBindMatrices === undefined) { return Promise.resolve(skinEntry); } return this.getDependency('accessor', skinDef.inverseBindMatrices).then(function (accessor) { skinEntry.inverseBindMatrices = accessor; return skinEntry; }); } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations * @param {number} animationIndex * @return {Promise} */ loadAnimation(animationIndex) { const json = this.json; const animationDef = json.animations[animationIndex]; const pendingNodes = []; const pendingInputAccessors = []; const pendingOutputAccessors = []; const pendingSamplers = []; const pendingTargets = []; for (let i = 0, il = animationDef.channels.length; i < il; i++) { const channel = animationDef.channels[i]; const sampler = animationDef.samplers[channel.sampler]; const target = channel.target; const name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. const input = animationDef.parameters !== undefined ? animationDef.parameters[sampler.input] : sampler.input; const output = animationDef.parameters !== undefined ? animationDef.parameters[sampler.output] : sampler.output; pendingNodes.push(this.getDependency('node', name)); pendingInputAccessors.push(this.getDependency('accessor', input)); pendingOutputAccessors.push(this.getDependency('accessor', output)); pendingSamplers.push(sampler); pendingTargets.push(target); } return Promise.all([Promise.all(pendingNodes), Promise.all(pendingInputAccessors), Promise.all(pendingOutputAccessors), Promise.all(pendingSamplers), Promise.all(pendingTargets)]).then(function (dependencies) { const nodes = dependencies[0]; const inputAccessors = dependencies[1]; const outputAccessors = dependencies[2]; const samplers = dependencies[3]; const targets = dependencies[4]; const tracks = []; for (let i = 0, il = nodes.length; i < il; i++) { const node = nodes[i]; const inputAccessor = inputAccessors[i]; const outputAccessor = outputAccessors[i]; const sampler = samplers[i]; const target = targets[i]; if (node === undefined) continue; node.updateMatrix(); node.matrixAutoUpdate = true; let TypedKeyframeTrack; switch (PATH_PROPERTIES[target.path]) { case PATH_PROPERTIES.weights: TypedKeyframeTrack = THREE.NumberKeyframeTrack; break; case PATH_PROPERTIES.rotation: TypedKeyframeTrack = THREE.QuaternionKeyframeTrack; break; case PATH_PROPERTIES.position: case PATH_PROPERTIES.scale: default: TypedKeyframeTrack = THREE.VectorKeyframeTrack; break; } const targetName = node.name ? node.name : node.uuid; const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[sampler.interpolation] : THREE.InterpolateLinear; const targetNames = []; if (PATH_PROPERTIES[target.path] === PATH_PROPERTIES.weights) { // Node may be a THREE.Group (glTF mesh with several primitives) or a THREE.Mesh. node.traverse(function (object) { if (object.isMesh === true && object.morphTargetInfluences) { targetNames.push(object.name ? object.name : object.uuid); } }); } else { targetNames.push(targetName); } let outputArray = outputAccessor.array; if (outputAccessor.normalized) { const scale = getNormalizedComponentScale(outputArray.constructor); const scaled = new Float32Array(outputArray.length); for (let j = 0, jl = outputArray.length; j < jl; j++) { scaled[j] = outputArray[j] * scale; } outputArray = scaled; } for (let j = 0, jl = targetNames.length; j < jl; j++) { const track = new TypedKeyframeTrack(targetNames[j] + '.' + PATH_PROPERTIES[target.path], inputAccessor.array, outputArray, interpolation); // Override interpolation with custom factory method. if (sampler.interpolation === 'CUBICSPLINE') { track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline(result) { // A CUBICSPLINE keyframe in glTF has three output values for each input value, // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() // must be divided by three to get the interpolant's sampleSize argument. const interpolantType = this instanceof THREE.QuaternionKeyframeTrack ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; return new interpolantType(this.times, this.values, this.getValueSize() / 3, result); }; // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; } tracks.push(track); } } const name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; return new THREE.AnimationClip(name, undefined, tracks); }); } createNodeMesh(nodeIndex) { const json = this.json; const parser = this; const nodeDef = json.nodes[nodeIndex]; if (nodeDef.mesh === undefined) return null; return parser.getDependency('mesh', nodeDef.mesh).then(function (mesh) { const node = parser._getNodeRef(parser.meshCache, nodeDef.mesh, mesh); // if weights are provided on the node, override weights on the mesh. if (nodeDef.weights !== undefined) { node.traverse(function (o) { if (!o.isMesh) return; for (let i = 0, il = nodeDef.weights.length; i < il; i++) { o.morphTargetInfluences[i] = nodeDef.weights[i]; } }); } return node; }); } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy * @param {number} nodeIndex * @return {Promise} */ loadNode(nodeIndex) { const json = this.json; const extensions = this.extensions; const parser = this; const nodeDef = json.nodes[nodeIndex]; // reserve node's name before its dependencies, so the root has the intended name. const nodeName = nodeDef.name ? parser.createUniqueName(nodeDef.name) : ''; return function () { const pending = []; const meshPromise = parser._invokeOne(function (ext) { return ext.createNodeMesh && ext.createNodeMesh(nodeIndex); }); if (meshPromise) { pending.push(meshPromise); } if (nodeDef.camera !== undefined) { pending.push(parser.getDependency('camera', nodeDef.camera).then(function (camera) { return parser._getNodeRef(parser.cameraCache, nodeDef.camera, camera); })); } parser._invokeAll(function (ext) { return ext.createNodeAttachment && ext.createNodeAttachment(nodeIndex); }).forEach(function (promise) { pending.push(promise); }); return Promise.all(pending); }().then(function (objects) { let node; // .isBone isn't in glTF spec. See ._markDefs if (nodeDef.isBone === true) { node = new THREE.Bone(); } else if (objects.length > 1) { node = new THREE.Group(); } else if (objects.length === 1) { node = objects[0]; } else { node = new THREE.Object3D(); } if (node !== objects[0]) { for (let i = 0, il = objects.length; i < il; i++) { node.add(objects[i]); } } if (nodeDef.name) { node.userData.name = nodeDef.name; node.name = nodeName; } assignExtrasToUserData(node, nodeDef); if (nodeDef.extensions) addUnknownExtensionsToUserData(extensions, node, nodeDef); if (nodeDef.matrix !== undefined) { const matrix = new THREE.Matrix4(); matrix.fromArray(nodeDef.matrix); node.applyMatrix4(matrix); } else { if (nodeDef.translation !== undefined) { node.position.fromArray(nodeDef.translation); } if (nodeDef.rotation !== undefined) { node.quaternion.fromArray(nodeDef.rotation); } if (nodeDef.scale !== undefined) { node.scale.fromArray(nodeDef.scale); } } parser.associations.set(node, { type: 'nodes', index: nodeIndex }); return node; }); } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes * @param {number} sceneIndex * @return {Promise} */ loadScene(sceneIndex) { const json = this.json; const extensions = this.extensions; const sceneDef = this.json.scenes[sceneIndex]; const parser = this; // THREE.Loader returns THREE.Group, not Scene. // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 const scene = new THREE.Group(); if (sceneDef.name) scene.name = parser.createUniqueName(sceneDef.name); assignExtrasToUserData(scene, sceneDef); if (sceneDef.extensions) addUnknownExtensionsToUserData(extensions, scene, sceneDef); const nodeIds = sceneDef.nodes || []; const pending = []; for (let i = 0, il = nodeIds.length; i < il; i++) { pending.push(buildNodeHierachy(nodeIds[i], scene, json, parser)); } return Promise.all(pending).then(function () { return scene; }); } } function buildNodeHierachy(nodeId, parentObject, json, parser) { const nodeDef = json.nodes[nodeId]; return parser.getDependency('node', nodeId).then(function (node) { if (nodeDef.skin === undefined) return node; // build skeleton here as well let skinEntry; return parser.getDependency('skin', nodeDef.skin).then(function (skin) { skinEntry = skin; const pendingJoints = []; for (let i = 0, il = skinEntry.joints.length; i < il; i++) { pendingJoints.push(parser.getDependency('node', skinEntry.joints[i])); } return Promise.all(pendingJoints); }).then(function (jointNodes) { node.traverse(function (mesh) { if (!mesh.isMesh) return; const bones = []; const boneInverses = []; for (let j = 0, jl = jointNodes.length; j < jl; j++) { const jointNode = jointNodes[j]; if (jointNode) { bones.push(jointNode); const mat = new THREE.Matrix4(); if (skinEntry.inverseBindMatrices !== undefined) { mat.fromArray(skinEntry.inverseBindMatrices.array, j * 16); } boneInverses.push(mat); } else { console.warn('THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[j]); } } mesh.bind(new THREE.Skeleton(bones, boneInverses), mesh.matrixWorld); }); return node; }); }).then(function (node) { // build node hierachy parentObject.add(node); const pending = []; if (nodeDef.children) { const children = nodeDef.children; for (let i = 0, il = children.length; i < il; i++) { const child = children[i]; pending.push(buildNodeHierachy(child, node, json, parser)); } } return Promise.all(pending); }); } /** * @param {BufferGeometry} geometry * @param {GLTF.Primitive} primitiveDef * @param {GLTFParser} parser */ function computeBounds(geometry, primitiveDef, parser) { const attributes = primitiveDef.attributes; const box = new THREE.Box3(); if (attributes.POSITION !== undefined) { const accessor = parser.json.accessors[attributes.POSITION]; const min = accessor.min; const max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. if (min !== undefined && max !== undefined) { box.set(new THREE.Vector3(min[0], min[1], min[2]), new THREE.Vector3(max[0], max[1], max[2])); if (accessor.normalized) { const boxScale = getNormalizedComponentScale(WEBGL_COMPONENT_TYPES[accessor.componentType]); box.min.multiplyScalar(boxScale); box.max.multiplyScalar(boxScale); } } else { console.warn('THREE.GLTFLoader: Missing min/max properties for accessor POSITION.'); return; } } else { return; } const targets = primitiveDef.targets; if (targets !== undefined) { const maxDisplacement = new THREE.Vector3(); const vector = new THREE.Vector3(); for (let i = 0, il = targets.length; i < il; i++) { const target = targets[i]; if (target.POSITION !== undefined) { const accessor = parser.json.accessors[target.POSITION]; const min = accessor.min; const max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. if (min !== undefined && max !== undefined) { // we need to get max of absolute components because target weight is [-1,1] vector.setX(Math.max(Math.abs(min[0]), Math.abs(max[0]))); vector.setY(Math.max(Math.abs(min[1]), Math.abs(max[1]))); vector.setZ(Math.max(Math.abs(min[2]), Math.abs(max[2]))); if (accessor.normalized) { const boxScale = getNormalizedComponentScale(WEBGL_COMPONENT_TYPES[accessor.componentType]); vector.multiplyScalar(boxScale); } // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets // are used to implement key-frame animations and as such only two are active at a time - this results in very large // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. maxDisplacement.max(vector); } else { console.warn('THREE.GLTFLoader: Missing min/max properties for accessor POSITION.'); } } } // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. box.expandByVector(maxDisplacement); } geometry.boundingBox = box; const sphere = new THREE.Sphere(); box.getCenter(sphere.center); sphere.radius = box.min.distanceTo(box.max) / 2; geometry.boundingSphere = sphere; } /** * @param {BufferGeometry} geometry * @param {GLTF.Primitive} primitiveDef * @param {GLTFParser} parser * @return {Promise} */ function addPrimitiveAttributes(geometry, primitiveDef, parser) { const attributes = primitiveDef.attributes; const pending = []; function assignAttributeAccessor(accessorIndex, attributeName) { return parser.getDependency('accessor', accessorIndex).then(function (accessor) { geometry.setAttribute(attributeName, accessor); }); } for (const gltfAttributeName in attributes) { const threeAttributeName = ATTRIBUTES[gltfAttributeName] || gltfAttributeName.toLowerCase(); // Skip attributes already provided by e.g. Draco extension. if (threeAttributeName in geometry.attributes) continue; pending.push(assignAttributeAccessor(attributes[gltfAttributeName], threeAttributeName)); } if (primitiveDef.indices !== undefined && !geometry.index) { const accessor = parser.getDependency('accessor', primitiveDef.indices).then(function (accessor) { geometry.setIndex(accessor); }); pending.push(accessor); } assignExtrasToUserData(geometry, primitiveDef); computeBounds(geometry, primitiveDef, parser); return Promise.all(pending).then(function () { return primitiveDef.targets !== undefined ? addMorphTargets(geometry, primitiveDef.targets, parser) : geometry; }); } /** * @param {BufferGeometry} geometry * @param {Number} drawMode * @return {BufferGeometry} */ function toTrianglesDrawMode(geometry, drawMode) { let index = geometry.getIndex(); // generate index if not present if (index === null) { const indices = []; const position = geometry.getAttribute('position'); if (position !== undefined) { for (let i = 0; i < position.count; i++) { indices.push(i); } geometry.setIndex(indices); index = geometry.getIndex(); } else { console.error('THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.'); return geometry; } } // const numberOfTriangles = index.count - 2; const newIndices = []; if (drawMode === THREE.TriangleFanDrawMode) { // gl.TRIANGLE_FAN for (let i = 1; i <= numberOfTriangles; i++) { newIndices.push(index.getX(0)); newIndices.push(index.getX(i)); newIndices.push(index.getX(i + 1)); } } else { // gl.TRIANGLE_STRIP for (let i = 0; i < numberOfTriangles; i++) { if (i % 2 === 0) { newIndices.push(index.getX(i)); newIndices.push(index.getX(i + 1)); newIndices.push(index.getX(i + 2)); } else { newIndices.push(index.getX(i + 2)); newIndices.push(index.getX(i + 1)); newIndices.push(index.getX(i)); } } } if (newIndices.length / 3 !== numberOfTriangles) { console.error('THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.'); } // build final geometry const newGeometry = geometry.clone(); newGeometry.setIndex(newIndices); return newGeometry; } THREE.GLTFLoader = GLTFLoader; })(); module.exports = exports = THREE.GLTFLoader; },{"../../three.js":25}],19:[function(require,module,exports){ const THREE = require('../../three.js'); (function () { /** * Loads a Wavefront .mtl file specifying materials */ class MTLLoader extends THREE.Loader { constructor(manager) { super(manager); } /** * Loads and parses a MTL asset from a URL. * * @param {String} url - URL to the MTL file. * @param {Function} [onLoad] - Callback invoked with the loaded object. * @param {Function} [onProgress] - Callback for download progress. * @param {Function} [onError] - Callback for download errors. * * @see setPath setResourcePath * * @note In order for relative texture references to resolve correctly * you must call setResourcePath() explicitly prior to load. */ load(url, onLoad, onProgress, onError) { const scope = this; const path = this.path === '' ? THREE.LoaderUtils.extractUrlBase(url || '') : this.path; const loader = new THREE.FileLoader(this.manager); loader.setPath(this.path); loader.setRequestHeader(this.requestHeader); loader.setWithCredentials(this.withCredentials); loader.load(url, function (text) { try { onLoad(scope.parse(text, path)); } catch (e) { if (onError) { onError(e); } else { console.error(e); } scope.manager.itemError(url); } }, onProgress, onError); } setMaterialOptions(value) { this.materialOptions = value; return this; } /** * Parses a MTL file. * * @param {String} text - Content of MTL file * @return {MaterialCreator} * * @see setPath setResourcePath * * @note In order for relative texture references to resolve correctly * you must call setResourcePath() explicitly prior to parse. */ parse(text, path) { const lines = text.split('\n'); let info = {}; const delimiter_pattern = /\s+/; const materialsInfo = {}; for (let i = 0; i < lines.length; i++) { let line = lines[i]; line = line.trim(); if (line.length === 0 || line.charAt(0) === '#') { // Blank line or comment ignore continue; } const pos = line.indexOf(' '); let key = pos >= 0 ? line.substring(0, pos) : line; key = key.toLowerCase(); let value = pos >= 0 ? line.substring(pos + 1) : ''; value = value.trim(); if (key === 'newmtl') { // New material info = { name: value }; materialsInfo[value] = info; } else { if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') { const ss = value.split(delimiter_pattern, 3); info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])]; } else { info[key] = value; } } } const materialCreator = new MaterialCreator(this.resourcePath || path, this.materialOptions); materialCreator.setCrossOrigin(this.crossOrigin); materialCreator.setManager(this.manager); materialCreator.setMaterials(materialsInfo); return materialCreator; } } /** * Create a new MTLLoader.MaterialCreator * @param baseUrl - Url relative to which textures are loaded * @param options - Set of options on how to construct the materials * side: Which side to apply the material * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide * wrap: What type of wrapping to apply for textures * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 * Default: false, assumed to be already normalized * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's * Default: false * @constructor */ class MaterialCreator { constructor(baseUrl = '', options = {}) { this.baseUrl = baseUrl; this.options = options; this.materialsInfo = {}; this.materials = {}; this.materialsArray = []; this.nameLookup = {}; this.crossOrigin = 'anonymous'; this.side = this.options.side !== undefined ? this.options.side : THREE.FrontSide; this.wrap = this.options.wrap !== undefined ? this.options.wrap : THREE.RepeatWrapping; } setCrossOrigin(value) { this.crossOrigin = value; return this; } setManager(value) { this.manager = value; } setMaterials(materialsInfo) { this.materialsInfo = this.convert(materialsInfo); this.materials = {}; this.materialsArray = []; this.nameLookup = {}; } convert(materialsInfo) { if (!this.options) return materialsInfo; const converted = {}; for (const mn in materialsInfo) { // Convert materials info into normalized form based on options const mat = materialsInfo[mn]; const covmat = {}; converted[mn] = covmat; for (const prop in mat) { let save = true; let value = mat[prop]; const lprop = prop.toLowerCase(); switch (lprop) { case 'kd': case 'ka': case 'ks': // Diffuse color (color under white light) using RGB values if (this.options && this.options.normalizeRGB) { value = [value[0] / 255, value[1] / 255, value[2] / 255]; } if (this.options && this.options.ignoreZeroRGBs) { if (value[0] === 0 && value[1] === 0 && value[2] === 0) { // ignore save = false; } } break; default: break; } if (save) { covmat[lprop] = value; } } } return converted; } preload() { for (const mn in this.materialsInfo) { this.create(mn); } } getIndex(materialName) { return this.nameLookup[materialName]; } getAsArray() { let index = 0; for (const mn in this.materialsInfo) { this.materialsArray[index] = this.create(mn); this.nameLookup[mn] = index; index++; } return this.materialsArray; } create(materialName) { if (this.materials[materialName] === undefined) { this.createMaterial_(materialName); } return this.materials[materialName]; } createMaterial_(materialName) { // Create material const scope = this; const mat = this.materialsInfo[materialName]; const params = { name: materialName, side: this.side }; function resolveURL(baseUrl, url) { if (typeof url !== 'string' || url === '') return ''; // Absolute URL if (/^https?:\/\//i.test(url)) return url; return baseUrl + url; } function setMapForType(mapType, value) { if (params[mapType]) return; // Keep the first encountered texture const texParams = scope.getTextureParams(value, params); const map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url)); map.repeat.copy(texParams.scale); map.offset.copy(texParams.offset); map.wrapS = scope.wrap; map.wrapT = scope.wrap; params[mapType] = map; } for (const prop in mat) { const value = mat[prop]; let n; if (value === '') continue; switch (prop.toLowerCase()) { // Ns is material specular exponent case 'kd': // Diffuse color (color under white light) using RGB values params.color = new THREE.Color().fromArray(value); break; case 'ks': // Specular color (color when light is reflected from shiny surface) using RGB values params.specular = new THREE.Color().fromArray(value); break; case 'ke': // Emissive using RGB values params.emissive = new THREE.Color().fromArray(value); break; case 'map_kd': // Diffuse texture map setMapForType('map', value); break; case 'map_ks': // Specular map setMapForType('specularMap', value); break; case 'map_ke': // Emissive map setMapForType('emissiveMap', value); break; case 'norm': setMapForType('normalMap', value); break; case 'map_bump': case 'bump': // Bump texture map setMapForType('bumpMap', value); break; case 'map_d': // Alpha map setMapForType('alphaMap', value); params.transparent = true; break; case 'ns': // The specular exponent (defines the focus of the specular highlight) // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. params.shininess = parseFloat(value); break; case 'd': n = parseFloat(value); if (n < 1) { params.opacity = n; params.transparent = true; } break; case 'tr': n = parseFloat(value); if (this.options && this.options.invertTrProperty) n = 1 - n; if (n > 0) { params.opacity = 1 - n; params.transparent = true; } break; default: break; } } this.materials[materialName] = new THREE.MeshPhongMaterial(params); return this.materials[materialName]; } getTextureParams(value, matParams) { const texParams = { scale: new THREE.Vector2(1, 1), offset: new THREE.Vector2(0, 0) }; const items = value.split(/\s+/); let pos; pos = items.indexOf('-bm'); if (pos >= 0) { matParams.bumpScale = parseFloat(items[pos + 1]); items.splice(pos, 2); } pos = items.indexOf('-s'); if (pos >= 0) { texParams.scale.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2])); items.splice(pos, 4); // we expect 3 parameters here! } pos = items.indexOf('-o'); if (pos >= 0) { texParams.offset.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2])); items.splice(pos, 4); // we expect 3 parameters here! } texParams.url = items.join(' ').trim(); return texParams; } loadTexture(url, mapping, onLoad, onProgress, onError) { const manager = this.manager !== undefined ? this.manager : THREE.DefaultLoadingManager; let loader = manager.getHandler(url); if (loader === null) { loader = new THREE.TextureLoader(manager); } if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin); const texture = loader.load(url, onLoad, onProgress, onError); if (mapping !== undefined) texture.mapping = mapping; return texture; } } THREE.MTLLoader = MTLLoader; })(); module.exports = exports = THREE.MTLLoader; },{"../../three.js":25}],20:[function(require,module,exports){ /** * @author mrdoob / http://mrdoob.com/ */ const THREE = require('../../three.js'); (function () { const _object_pattern = /^[og]\s*(.+)?/; // mtllib file_reference const _material_library_pattern = /^mtllib /; // usemtl material_name const _material_use_pattern = /^usemtl /; // usemap map_name const _map_use_pattern = /^usemap /; const _vA = new THREE.Vector3(); const _vB = new THREE.Vector3(); const _vC = new THREE.Vector3(); const _ab = new THREE.Vector3(); const _cb = new THREE.Vector3(); function ParserState() { const state = { objects: [], object: {}, vertices: [], normals: [], colors: [], uvs: [], materials: {}, materialLibraries: [], startObject: function (name, fromDeclaration) { // If the current object (initial from reset) is not from a g/o declaration in the parsed // file. We need to use it for the first parsed g/o to keep things in sync. if (this.object && this.object.fromDeclaration === false) { this.object.name = name; this.object.fromDeclaration = fromDeclaration !== false; return; } const previousMaterial = this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined; if (this.object && typeof this.object._finalize === 'function') { this.object._finalize(true); } this.object = { name: name || '', fromDeclaration: fromDeclaration !== false, geometry: { vertices: [], normals: [], colors: [], uvs: [], hasUVIndices: false }, materials: [], smooth: true, startMaterial: function (name, libraries) { const previous = this._finalize(false); // New usemtl declaration overwrites an inherited material, except if faces were declared // after the material, then it must be preserved for proper MultiMaterial continuation. if (previous && (previous.inherited || previous.groupCount <= 0)) { this.materials.splice(previous.index, 1); } const material = { index: this.materials.length, name: name || '', mtllib: Array.isArray(libraries) && libraries.length > 0 ? libraries[libraries.length - 1] : '', smooth: previous !== undefined ? previous.smooth : this.smooth, groupStart: previous !== undefined ? previous.groupEnd : 0, groupEnd: - 1, groupCount: - 1, inherited: false, clone: function (index) { const cloned = { index: typeof index === 'number' ? index : this.index, name: this.name, mtllib: this.mtllib, smooth: this.smooth, groupStart: 0, groupEnd: - 1, groupCount: - 1, inherited: false }; cloned.clone = this.clone.bind(cloned); return cloned; } }; this.materials.push(material); return material; }, currentMaterial: function () { if (this.materials.length > 0) { return this.materials[this.materials.length - 1]; } return undefined; }, _finalize: function (end) { const lastMultiMaterial = this.currentMaterial(); if (lastMultiMaterial && lastMultiMaterial.groupEnd === - 1) { lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; lastMultiMaterial.inherited = false; } // Ignore objects tail materials if no face declarations followed them before a new o/g started. if (end && this.materials.length > 1) { for (let mi = this.materials.length - 1; mi >= 0; mi--) { if (this.materials[mi].groupCount <= 0) { this.materials.splice(mi, 1); } } } // Guarantee at least one empty material, this makes the creation later more straight forward. if (end && this.materials.length === 0) { this.materials.push({ name: '', smooth: this.smooth }); } return lastMultiMaterial; } }; // Inherit previous objects material. // Spec tells us that a declared material must be set to all objects until a new material is declared. // If a usemtl declaration is encountered while this new object is being parsed, it will // overwrite the inherited material. Exception being that there was already face declarations // to the inherited material, then it will be preserved for proper MultiMaterial continuation. if (previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function') { const declared = previousMaterial.clone(0); declared.inherited = true; this.object.materials.push(declared); } this.objects.push(this.object); }, finalize: function () { if (this.object && typeof this.object._finalize === 'function') { this.object._finalize(true); } }, parseVertexIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 3) * 3; }, parseNormalIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 3) * 3; }, parseUVIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 2) * 2; }, addVertex: function (a, b, c) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); dst.push(src[b + 0], src[b + 1], src[b + 2]); dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addVertexPoint: function (a) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); }, addVertexLine: function (a) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); }, addNormal: function (a, b, c) { const src = this.normals; const dst = this.object.geometry.normals; dst.push(src[a + 0], src[a + 1], src[a + 2]); dst.push(src[b + 0], src[b + 1], src[b + 2]); dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addFaceNormal: function (a, b, c) { const src = this.vertices; const dst = this.object.geometry.normals; _vA.fromArray(src, a); _vB.fromArray(src, b); _vC.fromArray(src, c); _cb.subVectors(_vC, _vB); _ab.subVectors(_vA, _vB); _cb.cross(_ab); _cb.normalize(); dst.push(_cb.x, _cb.y, _cb.z); dst.push(_cb.x, _cb.y, _cb.z); dst.push(_cb.x, _cb.y, _cb.z); }, addColor: function (a, b, c) { const src = this.colors; const dst = this.object.geometry.colors; if (src[a] !== undefined) dst.push(src[a + 0], src[a + 1], src[a + 2]); if (src[b] !== undefined) dst.push(src[b + 0], src[b + 1], src[b + 2]); if (src[c] !== undefined) dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addUV: function (a, b, c) { const src = this.uvs; const dst = this.object.geometry.uvs; dst.push(src[a + 0], src[a + 1]); dst.push(src[b + 0], src[b + 1]); dst.push(src[c + 0], src[c + 1]); }, addDefaultUV: function () { const dst = this.object.geometry.uvs; dst.push(0, 0); dst.push(0, 0); dst.push(0, 0); }, addUVLine: function (a) { const src = this.uvs; const dst = this.object.geometry.uvs; dst.push(src[a + 0], src[a + 1]); }, addFace: function (a, b, c, ua, ub, uc, na, nb, nc) { const vLen = this.vertices.length; let ia = this.parseVertexIndex(a, vLen); let ib = this.parseVertexIndex(b, vLen); let ic = this.parseVertexIndex(c, vLen); this.addVertex(ia, ib, ic); this.addColor(ia, ib, ic); // normals if (na !== undefined && na !== '') { const nLen = this.normals.length; ia = this.parseNormalIndex(na, nLen); ib = this.parseNormalIndex(nb, nLen); ic = this.parseNormalIndex(nc, nLen); this.addNormal(ia, ib, ic); } else { this.addFaceNormal(ia, ib, ic); } // uvs if (ua !== undefined && ua !== '') { const uvLen = this.uvs.length; ia = this.parseUVIndex(ua, uvLen); ib = this.parseUVIndex(ub, uvLen); ic = this.parseUVIndex(uc, uvLen); this.addUV(ia, ib, ic); this.object.geometry.hasUVIndices = true; } else { // add placeholder values (for inconsistent face definitions) this.addDefaultUV(); } }, addPointGeometry: function (vertices) { this.object.geometry.type = 'Points'; const vLen = this.vertices.length; for (let vi = 0, l = vertices.length; vi < l; vi++) { const index = this.parseVertexIndex(vertices[vi], vLen); this.addVertexPoint(index); this.addColor(index); } }, addLineGeometry: function (vertices, uvs) { this.object.geometry.type = 'Line'; const vLen = this.vertices.length; const uvLen = this.uvs.length; for (let vi = 0, l = vertices.length; vi < l; vi++) { this.addVertexLine(this.parseVertexIndex(vertices[vi], vLen)); } for (let uvi = 0, l = uvs.length; uvi < l; uvi++) { this.addUVLine(this.parseUVIndex(uvs[uvi], uvLen)); } } }; state.startObject('', false); return state; } // class OBJLoader extends THREE.Loader { constructor(manager) { super(manager); this.materials = null; } load(url, onLoad, onProgress, onError) { const scope = this; const loader = new THREE.FileLoader(this.manager); loader.setPath(this.path); loader.setRequestHeader(this.requestHeader); loader.setWithCredentials(this.withCredentials); loader.load(url, function (text) { try { onLoad(scope.parse(text)); } catch (e) { if (onError) { onError(e); } else { console.error(e); } scope.manager.itemError(url); } }, onProgress, onError); } setMaterials(materials) { this.materials = materials; return this; } parse(text) { const state = new ParserState(); if (text.indexOf('\r\n') !== - 1) { // This is faster than String.split with regex that splits on both text = text.replace(/\r\n/g, '\n'); } if (text.indexOf('\\\n') !== - 1) { // join lines separated by a line continuation character (\) text = text.replace(/\\\n/g, ''); } const lines = text.split('\n'); let line = '', lineFirstChar = ''; let lineLength = 0; let result = []; // Faster to just trim left side of the line. Use if available. const trimLeft = typeof ''.trimLeft === 'function'; for (let i = 0, l = lines.length; i < l; i++) { line = lines[i]; line = trimLeft ? line.trimLeft() : line.trim(); lineLength = line.length; if (lineLength === 0) continue; lineFirstChar = line.charAt(0); // @todo invoke passed in handler if any if (lineFirstChar === '#') continue; if (lineFirstChar === 'v') { const data = line.split(/\s+/); switch (data[0]) { case 'v': state.vertices.push(parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3])); if (data.length >= 7) { state.colors.push(parseFloat(data[4]), parseFloat(data[5]), parseFloat(data[6])); } else { // if no colors are defined, add placeholders so color and vertex indices match state.colors.push(undefined, undefined, undefined); } break; case 'vn': state.normals.push(parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3])); break; case 'vt': state.uvs.push(parseFloat(data[1]), parseFloat(data[2])); break; } } else if (lineFirstChar === 'f') { const lineData = line.substr(1).trim(); const vertexData = lineData.split(/\s+/); const faceVertices = []; // Parse the face vertex data into an easy to work with format for (let j = 0, jl = vertexData.length; j < jl; j++) { const vertex = vertexData[j]; if (vertex.length > 0) { const vertexParts = vertex.split('/'); faceVertices.push(vertexParts); } } // Draw an edge between the first vertex and all subsequent vertices to form an n-gon const v1 = faceVertices[0]; for (let j = 1, jl = faceVertices.length - 1; j < jl; j++) { const v2 = faceVertices[j]; const v3 = faceVertices[j + 1]; state.addFace(v1[0], v2[0], v3[0], v1[1], v2[1], v3[1], v1[2], v2[2], v3[2]); } } else if (lineFirstChar === 'l') { const lineParts = line.substring(1).trim().split(' '); let lineVertices = []; const lineUVs = []; if (line.indexOf('/') === - 1) { lineVertices = lineParts; } else { for (let li = 0, llen = lineParts.length; li < llen; li++) { const parts = lineParts[li].split('/'); if (parts[0] !== '') lineVertices.push(parts[0]); if (parts[1] !== '') lineUVs.push(parts[1]); } } state.addLineGeometry(lineVertices, lineUVs); } else if (lineFirstChar === 'p') { const lineData = line.substr(1).trim(); const pointData = lineData.split(' '); state.addPointGeometry(pointData); } else if ((result = _object_pattern.exec(line)) !== null) { // o object_name // or // g group_name // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 // let name = result[ 0 ].substr( 1 ).trim(); const name = (' ' + result[0].substr(1).trim()).substr(1); state.startObject(name); } else if (_material_use_pattern.test(line)) { // material state.object.startMaterial(line.substring(7).trim(), state.materialLibraries); } else if (_material_library_pattern.test(line)) { // mtl file state.materialLibraries.push(line.substring(7).trim()); } else if (_map_use_pattern.test(line)) { // the line is parsed but ignored since the loader assumes textures are defined MTL files // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) console.warn('THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.'); } else if (lineFirstChar === 's') { result = line.split(' '); // smooth shading // @todo Handle files that have varying smooth values for a set of faces inside one geometry, // but does not define a usemtl for each face set. // This should be detected and a dummy material created (later MultiMaterial and geometry groups). // This requires some care to not create extra material on each smooth value for "normal" obj files. // where explicit usemtl defines geometry groups. // Example asset: examples/models/obj/cerberus/Cerberus.obj /* * http://paulbourke.net/dataformats/obj/ * or * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf * * From chapter "Grouping" Syntax explanation "s group_number": * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form * surfaces, smoothing groups are either turned on or off; there is no difference between values greater * than 0." */ if (result.length > 1) { const value = result[1].trim().toLowerCase(); state.object.smooth = value !== '0' && value !== 'off'; } else { // ZBrush can produce "s" lines #11707 state.object.smooth = true; } const material = state.object.currentMaterial(); if (material) material.smooth = state.object.smooth; } else { // Handle null terminated files without exception if (line === '\0') continue; console.warn('THREE.OBJLoader: Unexpected line: "' + line + '"'); } } state.finalize(); const container = new THREE.Group(); container.materialLibraries = [].concat(state.materialLibraries); const hasPrimitives = !(state.objects.length === 1 && state.objects[0].geometry.vertices.length === 0); if (hasPrimitives === true) { for (let i = 0, l = state.objects.length; i < l; i++) { const object = state.objects[i]; const geometry = object.geometry; const materials = object.materials; const isLine = geometry.type === 'Line'; const isPoints = geometry.type === 'Points'; let hasVertexColors = false; // Skip o/g line declarations that did not follow with any faces if (geometry.vertices.length === 0) continue; const buffergeometry = new THREE.BufferGeometry(); buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(geometry.vertices, 3)); if (geometry.normals.length > 0) { buffergeometry.setAttribute('normal', new THREE.Float32BufferAttribute(geometry.normals, 3)); } if (geometry.colors.length > 0) { hasVertexColors = true; buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(geometry.colors, 3)); } if (geometry.hasUVIndices === true) { buffergeometry.setAttribute('uv', new THREE.Float32BufferAttribute(geometry.uvs, 2)); } // Create materials const createdMaterials = []; for (let mi = 0, miLen = materials.length; mi < miLen; mi++) { const sourceMaterial = materials[mi]; const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; let material = state.materials[materialHash]; if (this.materials !== null) { material = this.materials.create(sourceMaterial.name); // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. if (isLine && material && !(material instanceof THREE.LineBasicMaterial)) { const materialLine = new THREE.LineBasicMaterial(); THREE.Material.prototype.copy.call(materialLine, material); materialLine.color.copy(material.color); material = materialLine; } else if (isPoints && material && !(material instanceof THREE.PointsMaterial)) { const materialPoints = new THREE.PointsMaterial({ size: 10, sizeAttenuation: false }); THREE.Material.prototype.copy.call(materialPoints, material); materialPoints.color.copy(material.color); materialPoints.map = material.map; material = materialPoints; } } if (material === undefined) { if (isLine) { material = new THREE.LineBasicMaterial(); } else if (isPoints) { material = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false }); } else { material = new THREE.MeshPhongMaterial(); } material.name = sourceMaterial.name; material.flatShading = sourceMaterial.smooth ? false : true; material.vertexColors = hasVertexColors; state.materials[materialHash] = material; } createdMaterials.push(material); } // Create mesh let mesh; if (createdMaterials.length > 1) { for (let mi = 0, miLen = materials.length; mi < miLen; mi++) { const sourceMaterial = materials[mi]; buffergeometry.addGroup(sourceMaterial.groupStart, sourceMaterial.groupCount, mi); } if (isLine) { mesh = new THREE.LineSegments(buffergeometry, createdMaterials); } else if (isPoints) { mesh = new THREE.Points(buffergeometry, createdMaterials); } else { mesh = new THREE.Mesh(buffergeometry, createdMaterials); } } else { if (isLine) { mesh = new THREE.LineSegments(buffergeometry, createdMaterials[0]); } else if (isPoints) { mesh = new THREE.Points(buffergeometry, createdMaterials[0]); } else { mesh = new THREE.Mesh(buffergeometry, createdMaterials[0]); } } mesh.name = object.name; container.add(mesh); } } else { // if there is only the default parser state object with no geometry data, interpret data as point cloud if (state.vertices.length > 0) { const material = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false }); const buffergeometry = new THREE.BufferGeometry(); buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(state.vertices, 3)); if (state.colors.length > 0 && state.colors[0] !== undefined) { buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(state.colors, 3)); material.vertexColors = true; } const points = new THREE.Points(buffergeometry, material); container.add(points); } } return container; } } THREE.OBJLoader = OBJLoader; })(); module.exports = exports = THREE.OBJLoader; },{"../../three.js":25}],21:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const utils = require("../utils/utils.js"); const material = require("../utils/material.js"); const THREE = require('../three.js'); const AnimationManager = require("../animation/AnimationManager.js"); const CSS2D = require("./CSS2DRenderer.js"); function Objects(){ } Objects.prototype = { // standard 1px line with gl line: function (obj) { obj = utils._validate(obj, this._defaults.line); //project to world and normalize var straightProject = utils.lnglatsToWorld(obj.geometry); var normalized = utils.normalizeVertices(straightProject); //flatten array for buffergeometry var flattenedArray = utils.flattenVectors(normalized.vertices); var positions = new Float32Array(flattenedArray); // 3 vertices per point var geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); // material var material = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 21 }); var line = new THREE.Line(geometry, material); line.options = options || {}; line.position.copy(normalized.position) return line }, extrusion: function (options) { }, unenroll: function (obj, isStatic) { var root = this; if (isStatic) { } else { // Bestow this mesh with animation superpowers and keeps track of its movements in the global animation queue root.animationManager.unenroll(obj); } }, _addMethods: function (obj, isStatic) { var root = this; const labelName = "label"; const tooltipName = "tooltip"; const helpName = "help"; const shadowPlane = "shadowPlane"; if (isStatic) { } else { if (!obj.coordinates) obj.coordinates = [0, 0, 0]; //[jscastro] added property for the internal 3D model Object.defineProperty(obj, 'model', { get() { return obj.getObjectByName("model"); } }); let _animations; //[jscastro] added property for the internal 3D model Object.defineProperty(obj, 'animations', { get() { const model = obj.model; if (model) { return model.animations } else return null; }, //set(value) { _animations = value} }); // Bestow this mesh with animation superpowers and keeps track of its movements in the global animation queue root.animationManager.enroll(obj); // Place an object on the map at the given lnglat obj.setCoords = function (lnglat) { // CSS2DObjects could bring an specific vertical positioning to correct in units if (obj.userData.topMargin && obj.userData.feature) { lnglat[2] += ((obj.userData.feature.properties.height || 0) - (obj.userData.feature.properties.base_height || obj.userData.feature.properties.min_height || 0)) * (obj.userData.topMargin || 0); } obj.coordinates = lnglat; obj.set({ position: lnglat }); return obj; } obj.setTranslate = function (lnglat) { obj.set({ translate: lnglat }); return obj; } obj.setRotation = function (xyz) { if (typeof xyz === 'number') xyz = { z: xyz } var r = { x: utils.radify(xyz.x) || obj.rotation.x, y: utils.radify(xyz.y) || obj.rotation.y, z: utils.radify(xyz.z) || obj.rotation.z } obj._setObject({ rotation: [r.x, r.y, r.z] }) } //[jscastro] added method to adjust 3D models to their issues with center position for rotation obj.calculateAdjustedPosition = function (lnglat, xyz, inverse) { let location = lnglat.slice(); //we convert the units to Long/Lat/Height let newCoords = utils.unprojectFromWorld(obj.modelSize); if (inverse) { //each model will have different adjustment attributes, we add them for x, y, z location[0] -= (xyz.x != 0 ? (newCoords[0] / xyz.x) : 0); location[1] -= (xyz.y != 0 ? (newCoords[1] / xyz.y) : 0); location[2] -= (xyz.z != 0 ? (newCoords[2] / xyz.z) : 0); } else { //each model will have different adjustment attributes, we add them for x, y, z location[0] += (xyz.x != 0 ? (newCoords[0] / xyz.x) : 0); location[1] += (xyz.y != 0 ? (newCoords[1] / xyz.y) : 0); location[2] += (xyz.z != 0 ? (newCoords[2] / xyz.z) : 0); } return location; } //[jscastro] added method to rotate on objects on an axis instead of centers obj.setRotationAxis = function (xyz) { if (typeof xyz === 'number') xyz = { z: xyz } let bb = obj.modelBox(); let point = new THREE.Vector3(bb.max.x, bb.max.y, bb.min.z); //apply Axis rotation on angle if (xyz.x != 0) _applyAxisAngle(obj, point, new THREE.Vector3(0, 0, 1), xyz.x); if (xyz.y != 0) _applyAxisAngle(obj, point, new THREE.Vector3(0, 0, 1), xyz.y); if (xyz.z != 0) _applyAxisAngle(obj, point, new THREE.Vector3(0, 0, 1), xyz.z); } //[jscastro] Auxiliar method to rotate an object on an axis function _applyAxisAngle(model, point, axis, degrees) { let theta = utils.radify(degrees); model.position.sub(point); // remove the offset model.position.applyAxisAngle(axis, theta); // rotate the POSITION model.position.add(point); // re-add the offset model.rotateOnAxis(axis, theta) tb.map.repaint = true; } //[jscastro] added property for scaled group inside threeboxObject Object.defineProperty(obj, 'scaleGroup', { get() { return obj.getObjectByName("scaleGroup"); } }) //[jscastro] added property for boundingBox group helper Object.defineProperty(obj, 'boxGroup', { get() { return obj.getObjectByName("boxGroup"); } }) //[jscastro] added property for boundingBox helper Object.defineProperty(obj, 'boundingBox', { get() { return obj.getObjectByName("boxModel"); } }) let _boundingBoxShadow; //[jscastro] added property for boundingBox shadow helper Object.defineProperty(obj, 'boundingBoxShadow', { get() { return obj.getObjectByName("boxShadow"); } }) //[jscastro] added method to create a bounding box and a shadow box obj.drawBoundingBox = function () { //let's create 2 wireframes, one for the object and one to project on the floor position let bb = obj.box3(); //create the group to return let boxGroup = new THREE.Group(); boxGroup.name = "boxGroup"; boxGroup.updateMatrixWorld(true); let boxModel = new THREE.Box3Helper(bb, Objects.prototype._defaults.colors.yellow); boxModel.name = "boxModel"; boxGroup.add(boxModel); boxModel.layers.disable(0); // it makes the object invisible for the raycaster //obj.boundingBox = boxModel; //it needs to clone, to avoid changing the object by reference let bb2 = bb.clone(); //we make the second box flat and at the floor height level bb2.max.z = bb2.min.z; let boxShadow = new THREE.Box3Helper(bb2, Objects.prototype._defaults.colors.black); boxShadow.name = "boxShadow"; boxGroup.add(boxShadow); boxShadow.layers.disable(0); // it makes the object invisible for the raycaster //obj.boundingBoxShadow = boxShadow; boxGroup.visible = false; // visibility is managed from the parent obj.scaleGroup.add(boxGroup); obj.setBoundingBoxShadowFloor(); } //[jscastro] added method to position the shadow box on the floor depending the object height obj.setBoundingBoxShadowFloor = function () { if (obj.boundingBoxShadow) { let h = -obj.modelHeight, r = obj.rotation, o = obj.boundingBoxShadow; o.box.max.z = o.box.min.z = h; o.rotation.y = r.y; o.rotation.x = -r.x; } } //[jscastro] Set the positional and pivotal anchor automatically from string param obj.setAnchor = function (anchor) { const b = obj.box3(); //const size = b.getSize(new THREE.Vector3()); const c = b.getCenter(new THREE.Vector3()); obj.none = { x: 0, y: 0, z: 0 }; obj.center = { x: c.x, y: c.y, z: b.min.z }; obj.bottom = { x: c.x, y: b.max.y, z: b.min.z }; obj.bottomLeft = { x: b.max.x, y: b.max.y, z: b.min.z }; obj.bottomRight = { x: b.min.x, y: b.max.y, z: b.min.z }; obj.top = { x: c.x, y: b.min.y, z: b.min.z }; obj.topLeft = { x: b.max.x, y: b.min.y, z: b.min.z }; obj.topRight = { x: b.min.x, y: b.min.y, z: b.min.z }; obj.left = { x: b.max.x, y: c.y, z: b.min.z }; obj.right = { x: b.min.x, y: c.y, z: b.min.z }; switch (anchor) { case 'center': obj.anchor = obj.center; break; case 'top': obj.anchor = obj.top; break; case 'top-left': obj.anchor = obj.topLeft; break; case 'top-right': obj.anchor = obj.topRight; break; case 'left': obj.anchor = obj.left; break; case 'right': obj.anchor = obj.right; break; case 'bottom': obj.anchor = obj.bottom; break; case 'bottom-left': default: obj.anchor = obj.bottomLeft; break; case 'bottom-right': obj.anchor = obj.bottomRight; break; case 'auto': case 'none': obj.anchor = obj.none; } obj.model.position.set(-obj.anchor.x, -obj.anchor.y, -obj.anchor.z); } //[jscastro] Set the positional and pivotal anchor based on (x, y, z) size units obj.setCenter = function (center) { //[jscastro] if the object options have an adjustment to center the 3D Object different to 0 if (center && (center.x != 0 || center.y != 0 || center.z != 0)) { let size = obj.getSize(); obj.anchor = { x: obj.anchor.x - (size.x * center.x), y: obj.anchor.y - (size.y * center.y), z: obj.anchor.z - (size.z * center.z) }; obj.model.position.set(-obj.anchor.x, -obj.anchor.y, -obj.anchor.z) } } //[jscastro] added property for simulated label Object.defineProperty(obj, 'label', { get() { return obj.getObjectByName(labelName); } }); //[jscastro] added property for simulated tooltip Object.defineProperty(obj, 'tooltip', { get() { return obj.getObjectByName(tooltipName); } }); //[jscastro] added property for help Object.defineProperty(obj, 'help', { get() { return obj.getObjectByName(helpName); } }); let _hidden = false; //[jscastro] added property for explicitely hidden object to avoid zoom layer behavior Object.defineProperty(obj, 'hidden', { get() { return _hidden; }, set(value) { if (_hidden != value) { _hidden = value; obj.visibility = !_hidden; } } }); //[jscastro] added property to redefine visible, including the label and tooltip Object.defineProperty(obj, 'visibility', { get() { return obj.visible; }, set(value) { let _value = value; if (value == 'visible' || value == true) { _value = true; if (obj.label) obj.label.visible = _value; } else if (value == 'none' || value == false) { _value = false; if (obj.label && obj.label.alwaysVisible) obj.label.visible = _value; if (obj.tooltip) obj.tooltip.visible = _value; } else return; if (obj.visible != _value) { if (obj.hidden && _value) return; obj.visible = _value; if (obj.model) { obj.model.traverse(function (c) { if (c.type == "Mesh" || c.type == "SkinnedMesh") { if (_value && obj.raycasted) { c.layers.enable(0); //this makes the meshes visible for raycast } else { c.layers.disable(0); //this makes the meshes invisible for raycast } } if (c.type == "LineSegments") { c.layers.disableAll(); } }); } } } }); //[jscastro] add CSS2 label method obj.addLabel = function (HTMLElement, visible, center, height) { if (HTMLElement) { //we add it to the first children to get same boxing and position //obj.children[0].add(obj.drawLabel(text, height)); obj.drawLabelHTML(HTMLElement, visible, center, height); } } //[jscastro] remove CSS2 label method obj.removeLabel = function () { obj.removeCSS2D(labelName); } //[jscastro] draw label method can be invoked separately obj.drawLabelHTML = function (HTMLElement, visible = false, center = obj.anchor, height = 0.5) { let divLabel = root.drawLabelHTML(HTMLElement, Objects.prototype._defaults.label.cssClass); let label = obj.addCSS2D(divLabel, labelName, center, height) //label.position.set(((-size.x * 0.5) - obj.model.position.x - center.x + bottomLeft.x), ((-size.y * 0.5) - obj.model.position.y - center.y + bottomLeft.y), size.z * 0.5); //middle-centered label.alwaysVisible = visible; label.visible = visible; return label; } //[jscastro] add tooltip method obj.addTooltip = function (tooltipText, mapboxStyle, center, custom = true, height = 1) { let t = obj.addHelp(tooltipText, tooltipName, mapboxStyle, center, height); t.visible = false; t.custom = custom; } //[jscastro] remove CSS2 tooltip method obj.removeTooltip = function () { obj.removeCSS2D(tooltipName); } //[jscastro] add tooltip method obj.addHelp = function (helpText, objName = helpName, mapboxStyle = false, center = obj.anchor, height = 0) { let divHelp = root.drawTooltip(helpText, mapboxStyle); let h = obj.addCSS2D(divHelp, objName, center, height); h.visible = true; return h; } //[jscastro] remove CSS2 tooltip method obj.removeHelp = function () { obj.removeCSS2D(helpName); } //[jscastro] add CSS2D help method obj.addCSS2D = function (element, objName, center = obj.anchor, height = 1) { if (element) { const box = obj.box3(); const size = box.getSize(new THREE.Vector3()); let bottomLeft = { x: box.max.x, y: box.max.y, z: box.min.z }; obj.removeCSS2D(objName); let c = new CSS2D.CSS2DObject(element); c.name = objName; c.position.set(((-size.x * 0.5) - obj.model.position.x - center.x + bottomLeft.x), ((-size.y * 0.5) - obj.model.position.y - center.y + bottomLeft.y), size.z * height); c.visible = false; //only visible on mouseover or selected obj.scaleGroup.add(c); return c; } } //[jscastro] remove CSS2 help method obj.removeCSS2D = function (objName) { let css2D = obj.getObjectByName(objName); if (css2D) { css2D.dispose(); let g = obj.scaleGroup.children; g.splice(g.indexOf(css2D), 1); } } //[jscastro] added property for help Object.defineProperty(obj, 'shadowPlane', { get() { return obj.getObjectByName(shadowPlane); } }); let _castShadow = false; //[jscastro] added property for traverse an object to cast a shadow Object.defineProperty(obj, 'castShadow', { get() { return _castShadow; }, set(value) { if (!obj.model || _castShadow === value) return; obj.model.traverse(function (c) { if (c.isMesh) c.castShadow = true; }); if (value) { // we add the shadow plane automatically const s = obj.modelSize; const sz = [s.x, s.y, s.z, obj.modelHeight]; const pSize = Math.max(...sz) * 10; const pGeo = new THREE.PlaneBufferGeometry(pSize, pSize); const pMat = new THREE.ShadowMaterial(); //const pMat = new THREE.MeshStandardMaterial({ color: 0x660000 }); pMat.opacity = 0.5; let p = new THREE.Mesh(pGeo, pMat); p.name = shadowPlane; p.layers.enable(1); p.layers.disable(0); // it makes the object invisible for the raycaster p.receiveShadow = value; obj.add(p); } else { // or we remove it obj.traverse(function (c) { if (c.isMesh && c.material instanceof THREE.ShadowMaterial) obj.remove(c); }); } _castShadow = value; } }) //[jscastro] added method to position the shadow box on the floor depending the object height obj.setReceiveShadowFloor = function () { if (obj.castShadow) { let sp = obj.shadowPlane, p = sp.position, r = sp.rotation; p.z = -obj.modelHeight; r.y = obj.rotation.y; r.x = -obj.rotation.x; if (obj.userData.units === 'meters') { const s = obj.modelSize; const sz = [s.x, s.y, s.z, -p.z]; const ps = Math.max(...sz) * 10; const sc = ps / sp.geometry.parameters.width; sp.scale.set(sc, sc, sc); } } } let _receiveShadow = false; //[jscastro] added property for traverse an object to receive a shadow Object.defineProperty(obj, 'receiveShadow', { get() { return _receiveShadow; }, set(value) { if (!obj.model || _receiveShadow === value) return; obj.model.traverse(function (c) { if (c.isMesh) c.receiveShadow = true; }); _receiveShadow = value; } }) let _wireframe = false; //[jscastro] added property for wireframes state Object.defineProperty(obj, 'wireframe', { get() { return _wireframe; }, set(value) { if (!obj.model || _wireframe === value) return; obj.model.traverse(function (c) { if (c.type == "Mesh" || c.type == "SkinnedMesh") { let materials = []; if (!Array.isArray(c.material)) { materials.push(c.material); } else { materials = c.material; } let m = materials[0]; if (value) { c.userData.materials = m; c.material = m.clone(); c.material.wireframe = c.material.transparent = value; c.material.opacity = 0.3; } else { c.material.dispose(); c.material = c.userData.materials; c.userData.materials.dispose(); c.userData.materials = null; } if (value) { c.layers.disable(0); c.layers.enable(1); } else { c.layers.disable(1); c.layers.enable(0); } } if (c.type == "LineSegments") { c.layers.disableAll(); } }); _wireframe = value; // Dispatch new event WireFramed obj.dispatchEvent({ type: 'Wireframed', detail: obj }); } }) let _color = null; //[jscastro] added property for wireframes state Object.defineProperty(obj, 'color', { get() { return _color; }, set(value) { if (!obj.model || _color === value) return; obj.model.traverse(function (c) { if (c.type == "Mesh" || c.type == "SkinnedMesh") { let materials = []; if (!Array.isArray(c.material)) { materials.push(c.material); } else { materials = c.material; } let m = materials[0]; if (value) { c.userData.materials = m; c.material = new THREE.MeshStandardMaterial(); c.material.color.setHex(value); } else { c.material.dispose(); c.material = c.userData.materials; c.userData.materials.dispose(); c.userData.materials = null; } } }); _color = value; } }) let _selected = false; //[jscastro] added property for selected state Object.defineProperty(obj, 'selected', { get() { return _selected; }, set(value) { if (value) { if (obj.userData.bbox && !obj.boundingBox) obj.drawBoundingBox(); if (obj.boxGroup) { obj.boundingBox.material = Objects.prototype._defaults.materials.boxSelectedMaterial; obj.boundingBox.parent.visible = false; // weilb change obj.boundingBox.layers.enable(1); obj.boundingBoxShadow.layers.enable(1); } if (obj.label && !obj.label.alwaysVisible) obj.label.visible = true; } else { if (obj.boxGroup) { obj.remove(obj.boxGroup); //remove the box group } if (obj.label && !obj.label.alwaysVisible) obj.label.visible = false; obj.removeHelp(); } if (obj.tooltip) obj.tooltip.visible = value; //only fire the event if value is different if (_selected != value) { _selected = value; // Dispatch new event SelectedChange obj.dispatchEvent({ type: 'SelectedChange', detail: obj }); } } }) let _raycasted = true; //[jscastro] added property for including/excluding an object from raycast Object.defineProperty(obj, 'raycasted', { get() { return _raycasted; }, set(value) { if (!obj.model || _raycasted === value) return; obj.model.traverse(function (c) { if (c.type == "Mesh" || c.type == "SkinnedMesh") { if (!value) { c.layers.disable(0); c.layers.enable(1); } else { c.layers.disable(1); c.layers.enable(0); } } }); _raycasted = value; } }); let _over = false; //[jscastro] added property for over state Object.defineProperty(obj, 'over', { get() { return _over; }, set(value) { if (value) { if (!obj.selected) { if (obj.userData.bbox && !obj.boundingBox) obj.drawBoundingBox(); if (obj.userData.tooltip && !obj.tooltip) obj.addTooltip(obj.uuid, true, obj.anchor, false); if (obj.boxGroup) { obj.boundingBox.material = Objects.prototype._defaults.materials.boxOverMaterial; obj.boundingBox.parent.visible = true; obj.boundingBox.layers.enable(1); obj.boundingBoxShadow.layers.enable(1); } } if (obj.label && !obj.label.alwaysVisible) { obj.label.visible = true; } // Dispatch new event ObjectOver obj.dispatchEvent({ type: 'ObjectMouseOver', detail: obj }); } else { if (!obj.selected) { if (obj.boxGroup) { obj.remove(obj.boxGroup); //remove the box group if (obj.tooltip && !obj.tooltip.custom) obj.removeTooltip(); } if (obj.label && !obj.label.alwaysVisible) { obj.label.visible = false; } } // Dispatch new event ObjectOver obj.dispatchEvent({ type: 'ObjectMouseOut', detail: obj }); } if (obj.tooltip) obj.tooltip.visible = value || obj.selected; _over = value; } }) //[jscastro] get the object model Box3 in runtime obj.box3 = function () { //update Matrix and MatrixWorld to avoid issues with transformations not full applied obj.updateMatrix(); obj.updateMatrixWorld(true, true); let bounds; //clone also the model inside it's the one who keeps the real size if (obj.model) { //let's clone the object before manipulate it let dup = obj.clone(true); let model = obj.model.clone(); //get the size of the model because the object is translated and has boundingBoxShadow bounds = new THREE.Box3().setFromObject(model); //if the object has parent it's already in the added to world so it's scaled and it could be rotated if (obj.parent) { //first, we return the object to it's original position of rotation, extract rotation and apply inversed let rm = new THREE.Matrix4(); let rmi = new THREE.Matrix4(); obj.matrix.extractRotation(rm); rmi.copy(rm).invert(); dup.setRotationFromMatrix(rmi); //now the object inside will give us a NAABB Non-Axes Aligned Bounding Box bounds = new THREE.Box3().setFromObject(model); } } return bounds; }; //[jscastro] modelBox obj.modelBox = function () { return obj.box3(); } obj.getSize = function () { return obj.box3().getSize(new THREE.Vector3(0, 0, 0)); } //[jscastro] let _modelSize = false; //[jscastro] added property for wireframes state Object.defineProperty(obj, 'modelSize', { get() { _modelSize = obj.getSize(); return _modelSize; }, set(value) { if (_modelSize != value) { _modelSize = value; } } }) //[jscastro] added property to get modelHeight Object.defineProperty(obj, 'modelHeight', { get() { let h = obj.coordinates[2] || 0; if (obj.userData.units === 'scene') h *= (obj.unitsPerMeter / obj.scale.x); return h; } }); //[jscastro] added property to calculate the units per meter in a given latitude //reduced to 7 decimals to avoid deviations on the size of the same object Object.defineProperty(obj, 'unitsPerMeter', { get() { return Number(utils.projectedUnitsPerMeter(obj.coordinates[1]).toFixed(7)); } }); let _fixedZoom = null; //[jscastro] added property to have a fixed scale for some objects Object.defineProperty(obj, 'fixedZoom', { get() { return obj.userData.fixedZoom; }, set(value) { if (obj.userData.fixedZoom === value) return; obj.userData.fixedZoom = value; obj.userData.units = (value ? 'scene' : 'meters'); } }); //[jscastro] sets the scale of an object based fixedZoom obj.setFixedZoom = function (scale) { if (obj.fixedZoom != null && obj.fixedZoom != 0) { if (!scale) scale = obj.userData.mapScale; let s = zoomScale(obj.fixedZoom); if (s > scale) { let calc = s / scale; obj.scale.set(calc, calc, calc); } else { obj.scale.set(1, 1, 1); } } } //[jscastro] sets the scale of an object based in the scale and fixedZoom obj.setScale = function (scale) { // scale the model so that its units are interpreted as meters at the given latitude if (obj.userData.units != 'scene') { let s = obj.unitsPerMeter; obj.scale.set(s, s, s); } else if (obj.fixedZoom) { if (scale) obj.userData.mapScale = scale; obj.setFixedZoom(obj.userData.mapScale); //apply fixed zoom } else obj.scale.set(1, 1, 1); } function zoomScale(zoom) { return Math.pow(2, zoom); } //[jscastro] sets the scale and shadows position of an object based in the scale obj.setObjectScale = function (scale) { obj.setScale(scale); obj.setBoundingBoxShadowFloor(); obj.setReceiveShadowFloor(); } } obj.add = function (o) { obj.scaleGroup.add(o); o.position.z = (obj.coordinates[2] ? -obj.coordinates[2] : 0); return o; } obj.remove = function (o) { if (!o) return; o.traverse(m => { //console.log('dispose geometry!') if (m.geometry) m.geometry.dispose(); if (m.material) { if (m.material.isMaterial) { cleanMaterial(m.material) } else { // an array of materials for (const material of m.material) cleanMaterial(material) } } if (m.dispose) m.dispose(); }) obj.scaleGroup.remove(o); tb.map.repaint = true; } //[jscastro] clone + assigning all the attributes obj.duplicate = function (options) { let dupe = obj.clone(true); //clone the whole threebox object dupe.getObjectByName("model").animations = obj.animations; //we must set this explicitly before addMethods if (dupe.userData.feature) { if (options && options.feature) dupe.userData.feature = options.feature; dupe.userData.feature.properties.uuid = dupe.uuid; } root._addMethods(dupe); // add methods if (!options || utils.equal(options.scale, obj.userData.scale)) { //no options, no changes, just return the same object dupe.copyAnchor(obj); // copy anchors //[jscastro] we add by default a tooltip that can be overriden later or hide it with threebox `enableTooltips` return dupe; } else { dupe.userData = options; dupe.userData.isGeoGroup = true; dupe.remove(dupe.boxGroup); // [jscastro] rotate and scale the model const r = utils.types.rotation(options.rotation, [0, 0, 0]); const s = utils.types.scale(options.scale, [1, 1, 1]); // [jscastro] reposition to 0,0,0 dupe.model.position.set(0, 0, 0); // rotate and scale dupe.model.rotation.set(r[0], r[1], r[2]); dupe.model.scale.set(s[0], s[1], s[2]); //[jscastro] calculate automatically the pivotal center of the object dupe.setAnchor(options.anchor); //[jscastro] override the center calculated if the object has adjustments dupe.setCenter(options.adjustment); return dupe; } } //[jscastro] copy anchor values obj.copyAnchor = function (o) { obj.anchor = o.anchor; obj.none = { x: 0, y: 0, z: 0 }; obj.center = o.center; obj.bottom = o.bottom; obj.bottomLeft = o.bottomLeft; obj.bottomRight = o.bottomRight; obj.top = o.top; obj.topLeft = o.topLeft; obj.topRight = o.topRight; obj.left = o.left; obj.right = o.right; } obj.dispose = function () { Objects.prototype.unenroll(obj); obj.traverse(o => { //don't dispose th object itself as it will be recursive if (o.parent && o.parent.name == "world") return; if (o.name === "threeboxObject") return; //console.log('dispose geometry!') if (o.geometry) o.geometry.dispose(); if (o.material) { if (o.material.isMaterial) { cleanMaterial(o.material) } else { // an array of materials for (const material of o.material) cleanMaterial(material) } } if (o.dispose) o.dispose(); }) obj.children = []; } const cleanMaterial = material => { //console.log('dispose material!') material.dispose() // dispose textures for (const key of Object.keys(material)) { const value = material[key] if (value && typeof value === 'object' && 'minFilter' in value) { //console.log('dispose texture!') value.dispose() } } let m = material; let md = (m.map || m.alphaMap || m.aoMap || m.bumpMap || m.displacementMap || m.emissiveMap || m.envMap || m.lightMap || m.metalnessMap || m.normalMap || m.roughnessMap) if (md) { if (m.map) m.map.dispose(); if (m.alphaMap) m.alphaMap.dispose(); if (m.aoMap) m.aoMap.dispose(); if (m.bumpMap) m.bumpMap.dispose(); if (m.displacementMap) m.displacementMap.dispose(); if (m.emissiveMap) m.emissiveMap.dispose(); if (m.envMap) m.envMap.dispose(); if (m.lightMap) m.lightMap.dispose(); if (m.metalnessMap) m.metalnessMap.dispose(); if (m.normalMap) m.normalMap.dispose(); if (m.roughnessMap) m.roughnessMap.dispose(); } } return obj }, _makeGroup: function (obj, options) { let projScaleGroup = new THREE.Group(); projScaleGroup.name = "scaleGroup"; projScaleGroup.add(obj) var geoGroup = new THREE.Group(); geoGroup.userData = options || {}; geoGroup.userData.isGeoGroup = true; if (geoGroup.userData.feature) { geoGroup.userData.feature.properties.uuid = geoGroup.uuid; } var isArrayOfObjects = projScaleGroup.length; if (isArrayOfObjects) for (o of projScaleGroup) geoGroup.add(o) else geoGroup.add(projScaleGroup); //utils._flipMaterialSides(projScaleGroup); geoGroup.name = "threeboxObject"; return geoGroup }, animationManager: new AnimationManager, //[jscastro] add tooltip method drawTooltip : function (tooltipText, mapboxStyle = false) { if (tooltipText) { let divToolTip; if (mapboxStyle) { let divContent = document.createElement('div'); divContent.className = 'mapboxgl-popup-content'; let strong = document.createElement('strong'); strong.innerHTML = tooltipText; divContent.appendChild(strong); let tip = document.createElement('div'); tip.className = 'mapboxgl-popup-tip'; let div = document.createElement('div'); div.className = 'marker mapboxgl-popup-anchor-bottom'; div.appendChild(tip); div.appendChild(divContent); divToolTip = document.createElement('div'); divToolTip.className += 'label3D'; divToolTip.appendChild(div); } else { divToolTip = document.createElement('span'); divToolTip.className = this._defaults.tooltip.cssClass; divToolTip.innerHTML = tooltipText; } return divToolTip; } }, //[jscastro] draw label method can be invoked separately drawLabelHTML: function (HTMLElement, cssClass) { let div = document.createElement('div'); div.className += cssClass; // [jscastro] create a div [TODO] analize if must be moved if (typeof (HTMLElement) == 'string') { div.innerHTML = HTMLElement; } else { div.innerHTML = HTMLElement.outerHTML; } return div; }, _defaults: { colors: { red: new THREE.Color(0xff0000), yellow: new THREE.Color(0xffff00), green: new THREE.Color(0x00ff00), black: new THREE.Color(0x000000) }, materials: { boxNormalMaterial: new THREE.LineBasicMaterial({ color: new THREE.Color(0xff0000) }), boxOverMaterial: new THREE.LineBasicMaterial({ color: new THREE.Color(0xffff00) }), boxSelectedMaterial: new THREE.LineBasicMaterial({ color: new THREE.Color(0x00ff00) }) }, line: { geometry: null, color: 'black', width: 1, opacity: 1 }, label: { htmlElement: null, cssClass: " label3D", alwaysVisible: false, topMargin: -0.5 }, tooltip: { text: '', cssClass: 'toolTip text-xs', mapboxStyle: false, topMargin: 0 }, sphere: { position: [0, 0, 0], radius: 1, sides: 20, units: 'scene', material: 'MeshBasicMaterial', anchor: 'bottom-left', bbox: true, tooltip: true, raycasted: true }, tube: { geometry: null, radius: 1, sides: 6, units: 'scene', material: 'MeshBasicMaterial', anchor: 'center', bbox: true, tooltip: true, raycasted: true }, loadObj: { type: null, obj: null, units: 'scene', scale: 1, rotation: 0, defaultAnimation: 0, anchor: 'bottom-left', bbox: true, tooltip: true, raycasted: true, clone: true }, Object3D: { obj: null, units: 'scene', anchor: 'bottom-left', bbox: true, tooltip: true, raycasted: true }, extrusion: { coordinates: [[[]]], geometryOptions: {}, height: 100, materials: new THREE.MeshPhongMaterial({ color: 0x660000, side: THREE.DoubleSide }), scale: 1, rotation: 0, units: 'scene', anchor: 'center', bbox: true, tooltip: true, raycasted: true } }, geometries: { line: ['LineString'], tube: ['LineString'], sphere: ['Point'], } } module.exports = exports = Objects; },{"../animation/AnimationManager.js":5,"../three.js":25,"../utils/material.js":27,"../utils/utils.js":29,"./CSS2DRenderer.js":7}],22:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const utils = require("../utils/utils.js"); const material = require("../utils/material.js"); const THREE = require('../three.js'); const Objects = require('./objects.js'); const Object3D = require('./Object3D.js'); function Sphere(opt) { opt = utils._validate(opt, Objects.prototype._defaults.sphere); let geometry = new THREE.SphereBufferGeometry(opt.radius, opt.sides, opt.sides); let mat = material(opt) let output = new THREE.Mesh(geometry, mat); //[jscastro] we convert it in Object3D to add methods, bounding box, model, tooltip... return new Object3D({ obj: output, units: opt.units, anchor: opt.anchor, adjustment: opt.adjustment, bbox: opt.bbox, tooltip: opt.tooltip, raycasted: opt.raycasted }); } module.exports = exports = Sphere; },{"../three.js":25,"../utils/material.js":27,"../utils/utils.js":29,"./Object3D.js":9,"./objects.js":21}],23:[function(require,module,exports){ const utils = require("../utils/utils.js"); const Objects = require('./objects.js'); const CSS2D = require('./CSS2DRenderer.js'); var THREE = require("../three.js"); function Tooltip(obj) { obj = utils._validate(obj, Objects.prototype._defaults.tooltip); if (obj.text) { let divToolTip = Objects.prototype.drawTooltip(obj.text, obj.mapboxStyle); let tooltip = new CSS2D.CSS2DObject(divToolTip); tooltip.visible = false; tooltip.name = "tooltip"; var userScaleGroup = Objects.prototype._makeGroup(tooltip, obj); Objects.prototype._addMethods(userScaleGroup); return userScaleGroup; } } module.exports = exports = Tooltip; },{"../three.js":25,"../utils/utils.js":29,"./CSS2DRenderer.js":7,"./objects.js":21}],24:[function(require,module,exports){ /** * @author peterqliu / https://github.com/peterqliu * @author jscastro / https://github.com/jscastro76 */ const utils = require("../utils/utils.js"); const material = require("../utils/material.js"); const Objects = require('./objects.js'); const THREE = require("../three.js"); const Object3D = require('./Object3D.js'); function tube(opt, world){ // validate and prep input geometry opt = utils._validate(opt, Objects.prototype._defaults.tube); let points = [] opt.geometry.forEach(p => { points.push(new THREE.Vector3(p[0], p[1], p[2])); }) const curve = new THREE.CatmullRomCurve3(points); let tube = new THREE.TubeGeometry(curve, points.length, opt.radius, opt.sides, false); let mat = material(opt); let obj = new THREE.Mesh(tube, mat); //[jscastro] we convert it in Object3D to add methods, bounding box, model, tooltip... return new Object3D({ obj: obj, units: opt.units, anchor: opt.anchor, adjustment: opt.adjustment, bbox: opt.bbox, tooltip: opt.tooltip, raycasted: opt.raycasted }); } module.exports = exports = tube; },{"../three.js":25,"../utils/material.js":27,"../utils/utils.js":29,"./Object3D.js":9,"./objects.js":21}],25:[function(require,module,exports){ /** * @license * Copyright 2010-2021 Three.js Authors * SPDX-License-Identifier: MIT */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="132",n=100,i=300,r=301,s=302,a=303,o=304,l=306,c=307,h=1e3,u=1001,d=1002,p=1003,m=1004,f=1005,g=1006,v=1007,y=1008,x=1009,_=1012,b=1014,M=1015,w=1016,S=1020,T=1022,E=1023,A=1026,L=1027,R=33776,C=33777,P=33778,I=33779,D=35840,N=35841,z=35842,B=35843,F=37492,O=37496,U=2300,H=2301,G=2302,k=2400,V=2401,W=2402,j=2500,q=2501,X=3e3,Y=3001,J=3007,Z=3002,Q=3004,K=3005,$=3006,tt=7680,et=35044,nt=35048,it="300 es";class rt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e>8&255]+st[t>>16&255]+st[t>>24&255]+"-"+st[255&e]+st[e>>8&255]+"-"+st[e>>16&15|64]+st[e>>24&255]+"-"+st[63&n|128]+st[n>>8&255]+"-"+st[n>>16&255]+st[n>>24&255]+st[255&i]+st[i>>8&255]+st[i>>16&255]+st[i>>24&255]).toUpperCase()}function ht(t,e,n){return Math.max(e,Math.min(n,t))}function ut(t,e){return(t%e+e)%e}function dt(t,e,n){return(1-n)*t+n*e}function pt(t){return 0==(t&t-1)&&0!==t}function mt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function ft(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}var gt=Object.freeze({__proto__:null,DEG2RAD:ot,RAD2DEG:lt,generateUUID:ct,clamp:ht,euclideanModulo:ut,mapLinear:function(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)},inverseLerp:function(t,e,n){return t!==e?(n-t)/(e-t):0},lerp:dt,damp:function(t,e,n,i){return dt(t,e,1-Math.exp(-n*i))},pingpong:function(t,e=1){return e-Math.abs(ut(t,2*e)-e)},smoothstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)},smootherstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){return void 0!==t&&(at=t%2147483647),at=16807*at%2147483647,(at-1)/2147483646},degToRad:function(t){return t*ot},radToDeg:function(t){return t*lt},isPowerOfTwo:pt,ceilPowerOfTwo:mt,floorPowerOfTwo:ft,setQuaternionFromProperEuler:function(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}}});class vt{constructor(t=0,e=0){this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this)}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this)}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e,n){return void 0!==n&&console.warn("THREE.Vector2: offset has been removed from .fromBufferAttribute()."),this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*n-s*i+t.x,this.y=r*i+s*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}}vt.prototype.isVector2=!0;class yt{constructor(){this.elements=[1,0,0,0,1,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.")}set(t,e,n,i,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=n,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[3],o=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],m=i[0],f=i[3],g=i[6],v=i[1],y=i[4],x=i[7],_=i[2],b=i[5],M=i[8];return r[0]=s*m+a*v+o*_,r[3]=s*f+a*y+o*b,r[6]=s*g+a*x+o*M,r[1]=l*m+c*v+h*_,r[4]=l*f+c*y+h*b,r[7]=l*g+c*x+h*M,r[2]=u*m+d*v+p*_,r[5]=u*f+d*y+p*b,r[8]=u*g+d*x+p*M,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-n*r*c+n*a*o+i*r*l-i*s*o}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+n*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(i*l-c*n)*m,t[2]=(a*n-i*s)*m,t[3]=u*m,t[4]=(c*e-i*o)*m,t[5]=(i*r-a*e)*m,t[6]=d*m,t[7]=(n*o-l*e)*m,t[8]=(s*e-n*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(n*o,n*l,-n*(o*s+l*a)+s+t,-i*l,i*o,-i*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){const n=this.elements;return n[0]*=t,n[3]*=t,n[6]*=t,n[1]*=e,n[4]*=e,n[7]*=e,this}rotate(t){const e=Math.cos(t),n=Math.sin(t),i=this.elements,r=i[0],s=i[3],a=i[6],o=i[1],l=i[4],c=i[7];return i[0]=e*r+n*o,i[3]=e*s+n*l,i[6]=e*a+n*c,i[1]=-n*r+e*o,i[4]=-n*s+e*l,i[7]=-n*a+e*c,this}translate(t,e){const n=this.elements;return n[0]+=t*n[2],n[3]+=t*n[5],n[6]+=t*n[8],n[1]+=e*n[2],n[4]+=e*n[5],n[7]+=e*n[8],this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}let xt;yt.prototype.isMatrix3=!0;class _t{static getDataURL(t){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let e;if(t instanceof HTMLCanvasElement)e=t;else{void 0===xt&&(xt=document.createElementNS("http://www.w3.org/1999/xhtml","canvas")),xt.width=t.width,xt.height=t.height;const n=xt.getContext("2d");t instanceof ImageData?n.putImageData(t,0,0):n.drawImage(t,0,0,t.width,t.height),e=xt}return e.width>2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}}let bt=0;class Mt extends rt{constructor(t=Mt.DEFAULT_IMAGE,e=Mt.DEFAULT_MAPPING,n=1001,i=1001,r=1006,s=1008,a=1023,o=1009,l=1,c=3e3){super(),Object.defineProperty(this,"id",{value:bt++}),this.uuid=ct(),this.name="",this.image=t,this.mipmaps=[],this.mapping=e,this.wrapS=n,this.wrapT=i,this.magFilter=r,this.minFilter=s,this.anisotropy=l,this.format=a,this.internalFormat=null,this.type=o,this.offset=new vt(0,0),this.repeat=new vt(1,1),this.center=new vt(0,0),this.rotation=0,this.matrixAutoUpdate=!0,this.matrix=new yt,this.generateMipmaps=!0,this.premultiplyAlpha=!1,this.flipY=!0,this.unpackAlignment=4,this.encoding=c,this.version=0,this.onUpdate=null,this.isRenderTargetTexture=!1}updateMatrix(){this.matrix.setUvTransform(this.offset.x,this.offset.y,this.repeat.x,this.repeat.y,this.rotation,this.center.x,this.center.y)}clone(){return(new this.constructor).copy(this)}copy(t){return this.name=t.name,this.image=t.image,this.mipmaps=t.mipmaps.slice(0),this.mapping=t.mapping,this.wrapS=t.wrapS,this.wrapT=t.wrapT,this.magFilter=t.magFilter,this.minFilter=t.minFilter,this.anisotropy=t.anisotropy,this.format=t.format,this.internalFormat=t.internalFormat,this.type=t.type,this.offset.copy(t.offset),this.repeat.copy(t.repeat),this.center.copy(t.center),this.rotation=t.rotation,this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrix.copy(t.matrix),this.generateMipmaps=t.generateMipmaps,this.premultiplyAlpha=t.premultiplyAlpha,this.flipY=t.flipY,this.unpackAlignment=t.unpackAlignment,this.encoding=t.encoding,this}toJSON(t){const e=void 0===t||"string"==typeof t;if(!e&&void 0!==t.textures[this.uuid])return t.textures[this.uuid];const n={metadata:{version:4.5,type:"Texture",generator:"Texture.toJSON"},uuid:this.uuid,name:this.name,mapping:this.mapping,repeat:[this.repeat.x,this.repeat.y],offset:[this.offset.x,this.offset.y],center:[this.center.x,this.center.y],rotation:this.rotation,wrap:[this.wrapS,this.wrapT],format:this.format,type:this.type,encoding:this.encoding,minFilter:this.minFilter,magFilter:this.magFilter,anisotropy:this.anisotropy,flipY:this.flipY,premultiplyAlpha:this.premultiplyAlpha,unpackAlignment:this.unpackAlignment};if(void 0!==this.image){const i=this.image;if(void 0===i.uuid&&(i.uuid=ct()),!e&&void 0===t.images[i.uuid]){let e;if(Array.isArray(i)){e=[];for(let t=0,n=i.length;t1)switch(this.wrapS){case h:t.x=t.x-Math.floor(t.x);break;case u:t.x=t.x<0?0:1;break;case d:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case h:t.y=t.y-Math.floor(t.y);break;case u:t.y=t.y<0?0:1;break;case d:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&this.version++}}function wt(t){return"undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap?_t.getDataURL(t):t.data?{data:Array.prototype.slice.call(t.data),width:t.width,height:t.height,type:t.data.constructor.name}:(console.warn("THREE.Texture: Unable to serialize Texture."),{})}Mt.DEFAULT_IMAGE=void 0,Mt.DEFAULT_MAPPING=i,Mt.prototype.isTexture=!0;class St{constructor(t=0,e=0,n=0,i=1){this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this)}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this)}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*n+s[8]*i+s[12]*r,this.y=s[1]*e+s[5]*n+s[9]*i+s[13]*r,this.z=s[2]*e+s[6]*n+s[10]*i+s[14]*r,this.w=s[3]*e+s[7]*n+s[11]*i+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),s=Math.atan2(r,e*n);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*n;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,n,i,r,s){const a=n[i],o=n[i+1],l=n[i+2],c=n[i+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e){if(!t||!t.isEuler)throw new Error("THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.");const n=t._x,i=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(n/2),c=a(i/2),h=a(r/2),u=o(n/2),d=o(i/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!1!==e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=n+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-i)*t}else if(n>a&&n>h){const t=2*Math.sqrt(1+n-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(i+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-n-h);this._w=(r-l)/t,this._x=(i+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-n-a);this._w=(s-i)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return nMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(ht(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t,e){return void 0!==e?(console.warn("THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(t,e)):this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=n*c+s*a+i*l-r*o,this._y=i*c+s*o+r*a-n*l,this._z=r*c+s*l+n*o-i*a,this._w=s*c-n*a-i*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,s=this._w;let a=s*t._w+n*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=n,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=n*h+this._x*u,this._y=i*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,n){this.copy(t).slerp(e,n)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}}Lt.prototype.isQuaternion=!0;class Rt{constructor(t=0,e=0,n=0){this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this.z+=t.z,this)}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this.z-=t.z,this)}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t,e){return void 0!==e?(console.warn("THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(t,e)):(this.x*=t.x,this.y*=t.y,this.z*=t.z,this)}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return t&&t.isEuler||console.error("THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order."),this.applyQuaternion(Pt.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(Pt.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,s=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*s,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*s,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*s,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=o*e+s*i-a*n,c=o*n+a*e-r*i,h=o*i+r*n-s*e,u=-r*e-s*n-a*i;return this.x=l*o+u*-r+c*-a-h*-s,this.y=c*o+u*-s+h*-r-l*-a,this.z=h*o+u*-a+l*-s-c*-r,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t,e){return void 0!==e?(console.warn("THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(t,e)):this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*s-n*o,this.z=n*a-i*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return Ct.copy(this).projectOnVector(t),this.sub(Ct)}reflect(t){return this.sub(Ct.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(ht(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e,n){return void 0!==n&&console.warn("THREE.Vector3: offset has been removed from .fromBufferAttribute()."),this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}}Rt.prototype.isVector3=!0;const Ct=new Rt,Pt=new Lt;class It{constructor(t=new Rt(1/0,1/0,1/0),e=new Rt(-1/0,-1/0,-1/0)){this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){let e=1/0,n=1/0,i=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.length;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,n,i),this.max.set(r,s,a),this}setFromBufferAttribute(t){let e=1/0,n=1/0,i=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.count;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,n,i),this.max.set(r,s,a),this}setFromPoints(t){this.makeEmpty();for(let e=0,n=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,Nt),Nt.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(kt),Vt.subVectors(this.max,kt),Bt.subVectors(t.a,kt),Ft.subVectors(t.b,kt),Ot.subVectors(t.c,kt),Ut.subVectors(Ft,Bt),Ht.subVectors(Ot,Ft),Gt.subVectors(Bt,Ot);let e=[0,-Ut.z,Ut.y,0,-Ht.z,Ht.y,0,-Gt.z,Gt.y,Ut.z,0,-Ut.x,Ht.z,0,-Ht.x,Gt.z,0,-Gt.x,-Ut.y,Ut.x,0,-Ht.y,Ht.x,0,-Gt.y,Gt.x,0];return!!qt(e,Bt,Ft,Ot,Vt)&&(e=[1,0,0,0,1,0,0,0,1],!!qt(e,Bt,Ft,Ot,Vt)&&(Wt.crossVectors(Ut,Ht),e=[Wt.x,Wt.y,Wt.z],qt(e,Bt,Ft,Ot,Vt)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return Nt.copy(t).clamp(this.min,this.max).sub(t).length()}getBoundingSphere(t){return this.getCenter(t.center),t.radius=.5*this.getSize(Nt).length(),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Dt[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Dt[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Dt[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Dt[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Dt[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Dt[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Dt[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Dt[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Dt)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}It.prototype.isBox3=!0;const Dt=[new Rt,new Rt,new Rt,new Rt,new Rt,new Rt,new Rt,new Rt],Nt=new Rt,zt=new It,Bt=new Rt,Ft=new Rt,Ot=new Rt,Ut=new Rt,Ht=new Rt,Gt=new Rt,kt=new Rt,Vt=new Rt,Wt=new Rt,jt=new Rt;function qt(t,e,n,i,r){for(let s=0,a=t.length-3;s<=a;s+=3){jt.fromArray(t,s);const a=r.x*Math.abs(jt.x)+r.y*Math.abs(jt.y)+r.z*Math.abs(jt.z),o=e.dot(jt),l=n.dot(jt),c=i.dot(jt);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const Xt=new It,Yt=new Rt,Jt=new Rt,Zt=new Rt;class Qt{constructor(t=new Rt,e=-1){this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):Xt.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){Zt.subVectors(t,this.center);const e=Zt.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.add(Zt.multiplyScalar(n/t)),this.radius+=n}return this}union(t){return Jt.subVectors(t.center,this.center).normalize().multiplyScalar(t.radius),this.expandByPoint(Yt.copy(t.center).add(Jt)),this.expandByPoint(Yt.copy(t.center).sub(Jt)),this}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const Kt=new Rt,$t=new Rt,te=new Rt,ee=new Rt,ne=new Rt,ie=new Rt,re=new Rt;class se{constructor(t=new Rt,e=new Rt(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.direction).multiplyScalar(t).add(this.origin)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Kt)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.direction).multiplyScalar(n).add(this.origin)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Kt.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Kt.copy(this.direction).multiplyScalar(e).add(this.origin),Kt.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){$t.copy(t).add(e).multiplyScalar(.5),te.copy(e).sub(t).normalize(),ee.copy(this.origin).sub($t);const r=.5*t.distanceTo(e),s=-this.direction.dot(te),a=ee.dot(this.direction),o=-ee.dot(te),l=ee.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return n&&n.copy(this.direction).multiplyScalar(h).add(this.origin),i&&i.copy(te).multiplyScalar(u).add($t),d}intersectSphere(t,e){Kt.subVectors(t.center,this.origin);const n=Kt.dot(this.direction),i=Kt.dot(Kt)-n*n,r=t.radius*t.radius;if(i>r)return null;const s=Math.sqrt(r-i),a=n-s,o=n+s;return a<0&&o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(t.min.x-u.x)*l,i=(t.max.x-u.x)*l):(n=(t.max.x-u.x)*l,i=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),n>s||r>i?null:((r>n||n!=n)&&(n=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),n>o||a>i?null:((a>n||n!=n)&&(n=a),(o=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Kt)}intersectTriangle(t,e,n,i,r){ne.subVectors(e,t),ie.subVectors(n,t),re.crossVectors(ne,ie);let s,a=this.direction.dot(re);if(a>0){if(i)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}ee.subVectors(this.origin,t);const o=s*this.direction.dot(ie.crossVectors(ee,ie));if(o<0)return null;const l=s*this.direction.dot(ne.cross(ee));if(l<0)return null;if(o+l>a)return null;const c=-s*ee.dot(re);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class ae{constructor(){this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.")}set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new ae).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/oe.setFromMatrixColumn(t,0).length(),r=1/oe.setFromMatrixColumn(t,1).length(),s=1/oe.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*s,e[9]=n[9]*s,e[10]=n[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){t&&t.isEuler||console.error("THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.");const e=this.elements,n=t.x,i=t.y,r=t.z,s=Math.cos(n),a=Math.sin(n),o=Math.cos(i),l=Math.sin(i),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=i+n*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t+r*a,e[4]=i*a-n,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=n*a-i,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=i+n*a,e[1]=n+i*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=i*h+n,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=n*h+i,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=n*h-i,e[2]=i*h-n,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(ce,t,he)}lookAt(t,e,n){const i=this.elements;return pe.subVectors(t,e),0===pe.lengthSq()&&(pe.z=1),pe.normalize(),ue.crossVectors(n,pe),0===ue.lengthSq()&&(1===Math.abs(n.z)?pe.x+=1e-4:pe.z+=1e-4,pe.normalize(),ue.crossVectors(n,pe)),ue.normalize(),de.crossVectors(pe,ue),i[0]=ue.x,i[4]=de.x,i[8]=pe.x,i[1]=ue.y,i[5]=de.y,i[9]=pe.y,i[2]=ue.z,i[6]=de.z,i[10]=pe.z,this}multiply(t,e){return void 0!==e?(console.warn("THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead."),this.multiplyMatrices(t,e)):this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[4],o=n[8],l=n[12],c=n[1],h=n[5],u=n[9],d=n[13],p=n[2],m=n[6],f=n[10],g=n[14],v=n[3],y=n[7],x=n[11],_=n[15],b=i[0],M=i[4],w=i[8],S=i[12],T=i[1],E=i[5],A=i[9],L=i[13],R=i[2],C=i[6],P=i[10],I=i[14],D=i[3],N=i[7],z=i[11],B=i[15];return r[0]=s*b+a*T+o*R+l*D,r[4]=s*M+a*E+o*C+l*N,r[8]=s*w+a*A+o*P+l*z,r[12]=s*S+a*L+o*I+l*B,r[1]=c*b+h*T+u*R+d*D,r[5]=c*M+h*E+u*C+d*N,r[9]=c*w+h*A+u*P+d*z,r[13]=c*S+h*L+u*I+d*B,r[2]=p*b+m*T+f*R+g*D,r[6]=p*M+m*E+f*C+g*N,r[10]=p*w+m*A+f*P+g*z,r[14]=p*S+m*L+f*I+g*B,r[3]=v*b+y*T+x*R+_*D,r[7]=v*M+y*E+x*C+_*N,r[11]=v*w+y*A+x*P+_*z,r[15]=v*S+y*L+x*I+_*B,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-i*l*h-r*a*u+n*l*u+i*a*d-n*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-i*s*d+i*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+n*s*d+r*a*c-n*l*c)+t[15]*(-i*a*c-e*o*h+e*a*u+i*s*h-n*s*u+n*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,y=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,x=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,_=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,b=e*v+n*y+i*x+r*_;if(0===b)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const M=1/b;return t[0]=v*M,t[1]=(m*u*r-h*f*r-m*i*d+n*f*d+h*i*g-n*u*g)*M,t[2]=(a*f*r-m*o*r+m*i*l-n*f*l-a*i*g+n*o*g)*M,t[3]=(h*o*r-a*u*r-h*i*l+n*u*l+a*i*d-n*o*d)*M,t[4]=y*M,t[5]=(c*f*r-p*u*r+p*i*d-e*f*d-c*i*g+e*u*g)*M,t[6]=(p*o*r-s*f*r-p*i*l+e*f*l+s*i*g-e*o*g)*M,t[7]=(s*u*r-c*o*r+c*i*l-e*u*l-s*i*d+e*o*d)*M,t[8]=x*M,t[9]=(p*h*r-c*m*r-p*n*d+e*m*d+c*n*g-e*h*g)*M,t[10]=(s*m*r-p*a*r+p*n*l-e*m*l-s*n*g+e*a*g)*M,t[11]=(c*a*r-s*h*r-c*n*l+e*h*l+s*n*d-e*a*d)*M,t[12]=_*M,t[13]=(c*m*i-p*h*i+p*n*u-e*m*u-c*n*f+e*h*f)*M,t[14]=(p*a*i-s*m*i-p*n*o+e*m*o+s*n*f-e*a*f)*M,t[15]=(s*h*i-c*a*i+c*n*o-e*h*o-s*n*u+e*a*u)*M,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+n,l*a-i*o,l*o+i*a,0,l*a+i*o,c*a+n,c*o-i*s,0,l*o-i*a,c*o+i*s,r*o*o+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n,i,r,s){return this.set(1,n,r,0,t,1,s,0,e,i,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,y=o*c,x=o*h,_=n.x,b=n.y,M=n.z;return i[0]=(1-(m+g))*_,i[1]=(d+x)*_,i[2]=(p-y)*_,i[3]=0,i[4]=(d-x)*b,i[5]=(1-(u+g))*b,i[6]=(f+v)*b,i[7]=0,i[8]=(p+y)*M,i[9]=(f-v)*M,i[10]=(1-(u+m))*M,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=oe.set(i[0],i[1],i[2]).length();const s=oe.set(i[4],i[5],i[6]).length(),a=oe.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],le.copy(this);const o=1/r,l=1/s,c=1/a;return le.elements[0]*=o,le.elements[1]*=o,le.elements[2]*=o,le.elements[4]*=l,le.elements[5]*=l,le.elements[6]*=l,le.elements[8]*=c,le.elements[9]*=c,le.elements[10]*=c,e.setFromRotationMatrix(le),n.x=r,n.y=s,n.z=a,this}makePerspective(t,e,n,i,r,s){void 0===s&&console.warn("THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.");const a=this.elements,o=2*r/(e-t),l=2*r/(n-i),c=(e+t)/(e-t),h=(n+i)/(n-i),u=-(s+r)/(s-r),d=-2*s*r/(s-r);return a[0]=o,a[4]=0,a[8]=c,a[12]=0,a[1]=0,a[5]=l,a[9]=h,a[13]=0,a[2]=0,a[6]=0,a[10]=u,a[14]=d,a[3]=0,a[7]=0,a[11]=-1,a[15]=0,this}makeOrthographic(t,e,n,i,r,s){const a=this.elements,o=1/(e-t),l=1/(n-i),c=1/(s-r),h=(e+t)*o,u=(n+i)*l,d=(s+r)*c;return a[0]=2*o,a[4]=0,a[8]=0,a[12]=-h,a[1]=0,a[5]=2*l,a[9]=0,a[13]=-u,a[2]=0,a[6]=0,a[10]=-2*c,a[14]=-d,a[3]=0,a[7]=0,a[11]=0,a[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}ae.prototype.isMatrix4=!0;const oe=new Rt,le=new ae,ce=new Rt(0,0,0),he=new Rt(1,1,1),ue=new Rt,de=new Rt,pe=new Rt,me=new ae,fe=new Lt;class ge{constructor(t=0,e=0,n=0,i=ge.DefaultOrder){this._x=t,this._y=e,this._z=n,this._order=i}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,n,i=this._order){return this._x=t,this._y=e,this._z=n,this._order=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,n=!0){const i=t.elements,r=i[0],s=i[4],a=i[8],o=i[1],l=i[5],c=i[9],h=i[2],u=i[6],d=i[10];switch(e){case"XYZ":this._y=Math.asin(ht(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-s,r)):(this._x=Math.atan2(u,l),this._z=0);break;case"YXZ":this._x=Math.asin(-ht(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,l)):(this._y=Math.atan2(-h,r),this._z=0);break;case"ZXY":this._x=Math.asin(ht(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-h,d),this._z=Math.atan2(-s,l)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-ht(h,-1,1)),Math.abs(h)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-s,l));break;case"YZX":this._z=Math.asin(ht(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-h,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-ht(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(u,l),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===n&&this._onChangeCallback(),this}setFromQuaternion(t,e,n){return me.makeRotationFromQuaternion(t),this.setFromRotationMatrix(me,e,n)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return fe.setFromEuler(this),this.setFromQuaternion(fe,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}toVector3(t){return t?t.set(this._x,this._y,this._z):new Rt(this._x,this._y,this._z)}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}}ge.prototype.isEuler=!0,ge.DefaultOrder="XYZ",ge.RotationOrders=["XYZ","YZX","ZXY","XZY","YXZ","ZYX"];class ve{constructor(){this.mask=1}set(t){this.mask=1<1){for(let t=0;t1){for(let t=0;t0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),a.length>0&&(n.images=a),o.length>0&&(n.shapes=o),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c)}return n.object=i,n;function s(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){Ie.subVectors(i,e),De.subVectors(n,e),Ne.subVectors(t,e);const s=Ie.dot(Ie),a=Ie.dot(De),o=Ie.dot(Ne),l=De.dot(De),c=De.dot(Ne),h=s*l-a*a;if(0===h)return r.set(-2,-1,-1);const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return this.getBarycoord(t,e,n,i,ze),ze.x>=0&&ze.y>=0&&ze.x+ze.y<=1}static getUV(t,e,n,i,r,s,a,o){return this.getBarycoord(t,e,n,i,ze),o.set(0,0),o.addScaledVector(r,ze.x),o.addScaledVector(s,ze.y),o.addScaledVector(a,ze.z),o}static isFrontFacing(t,e,n,i){return Ie.subVectors(n,e),De.subVectors(t,e),Ie.cross(De).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Ie.subVectors(this.c,this.b),De.subVectors(this.a,this.b),.5*Ie.cross(De).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return ke.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return ke.getBarycoord(t,this.a,this.b,this.c,e)}getUV(t,e,n,i,r){return ke.getUV(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return ke.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return ke.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const n=this.a,i=this.b,r=this.c;let s,a;Be.subVectors(i,n),Fe.subVectors(r,n),Ue.subVectors(t,n);const o=Be.dot(Ue),l=Fe.dot(Ue);if(o<=0&&l<=0)return e.copy(n);He.subVectors(t,i);const c=Be.dot(He),h=Fe.dot(He);if(c>=0&&h<=c)return e.copy(i);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(n).addScaledVector(Be,s);Ge.subVectors(t,r);const d=Be.dot(Ge),p=Fe.dot(Ge);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(n).addScaledVector(Fe,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return Oe.subVectors(r,i),a=(h-c)/(h-c+(d-p)),e.copy(i).addScaledVector(Oe,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(n).addScaledVector(Be,s).addScaledVector(Fe,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}let Ve=0;class We extends rt{constructor(){super(),Object.defineProperty(this,"id",{value:Ve++}),this.uuid=ct(),this.name="",this.type="Material",this.fog=!0,this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.format=E,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=n,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=tt,this.stencilZFail=tt,this.stencilZPass=tt,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaToCoverage=!1,this.premultipliedAlpha=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0,this._alphaTest=0}get alphaTest(){return this._alphaTest}set alphaTest(t){this._alphaTest>0!=t>0&&this.version++,this._alphaTest=t}onBuild(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn("THREE.Material: '"+e+"' parameter is undefined.");continue}if("shading"===e){console.warn("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead."),this.flatShading=1===n;continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn("THREE."+this.type+": '"+e+"' is not a property of this material.")}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.5,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),this.sheenTint&&this.sheenTint.isColor&&(n.sheenTint=this.sheenTint.getHex()),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(n.specularIntensity=this.specularIntensity),this.specularTint&&this.specularTint.isColor&&(n.specularTint=this.specularTint.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularTintMap&&this.specularTintMap.isTexture&&(n.specularTintMap=this.specularTintMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&(n.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationTint&&(n.attenuationTint=this.attenuationTint.getHex()),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),0!==this.side&&(n.side=this.side),this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),this.format!==E&&(n.format=this.format),!0===this.transparent&&(n.transparent=this.transparent),n.depthFunc=this.depthFunc,n.depthTest=this.depthTest,n.depthWrite=this.depthWrite,n.colorWrite=this.colorWrite,n.stencilWrite=this.stencilWrite,n.stencilWriteMask=this.stencilWriteMask,n.stencilFunc=this.stencilFunc,n.stencilRef=this.stencilRef,n.stencilFuncMask=this.stencilFuncMask,n.stencilFail=this.stencilFail,n.stencilZFail=this.stencilZFail,n.stencilZPass=this.stencilZPass,this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaToCoverage&&(n.alphaToCoverage=this.alphaToCoverage),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=this.premultipliedAlpha),!0===this.wireframe&&(n.wireframe=this.wireframe),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(n.flatShading=this.flatShading),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),"{}"!==JSON.stringify(this.userData)&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.fog=t.fog,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.format=t.format,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}We.prototype.isMaterial=!0;const je={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},qe={h:0,s:0,l:0},Xe={h:0,s:0,l:0};function Ye(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}function Je(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function Ze(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}class Qe{constructor(t,e,n){return void 0===e&&void 0===n?this.set(t):this.setRGB(t,e,n)}set(t){return t&&t.isColor?this.copy(t):"number"==typeof t?this.setHex(t):"string"==typeof t&&this.setStyle(t),this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,this}setRGB(t,e,n){return this.r=t,this.g=e,this.b=n,this}setHSL(t,e,n){if(t=ut(t,1),e=ht(e,0,1),n=ht(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=Ye(r,i,t+1/3),this.g=Ye(r,i,t),this.b=Ye(r,i,t-1/3)}return this}setStyle(t){function e(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let n;if(n=/^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec(t)){let t;const i=n[1],r=n[2];switch(i){case"rgb":case"rgba":if(t=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(r))return this.r=Math.min(255,parseInt(t[1],10))/255,this.g=Math.min(255,parseInt(t[2],10))/255,this.b=Math.min(255,parseInt(t[3],10))/255,e(t[4]),this;if(t=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(r))return this.r=Math.min(100,parseInt(t[1],10))/100,this.g=Math.min(100,parseInt(t[2],10))/100,this.b=Math.min(100,parseInt(t[3],10))/100,e(t[4]),this;break;case"hsl":case"hsla":if(t=/^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(r)){const n=parseFloat(t[1])/360,i=parseInt(t[2],10)/100,r=parseInt(t[3],10)/100;return e(t[4]),this.setHSL(n,i,r)}}}else if(n=/^\#([A-Fa-f\d]+)$/.exec(t)){const t=n[1],e=t.length;if(3===e)return this.r=parseInt(t.charAt(0)+t.charAt(0),16)/255,this.g=parseInt(t.charAt(1)+t.charAt(1),16)/255,this.b=parseInt(t.charAt(2)+t.charAt(2),16)/255,this;if(6===e)return this.r=parseInt(t.charAt(0)+t.charAt(1),16)/255,this.g=parseInt(t.charAt(2)+t.charAt(3),16)/255,this.b=parseInt(t.charAt(4)+t.charAt(5),16)/255,this}return t&&t.length>0?this.setColorName(t):this}setColorName(t){const e=je[t.toLowerCase()];return void 0!==e?this.setHex(e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copyGammaToLinear(t,e=2){return this.r=Math.pow(t.r,e),this.g=Math.pow(t.g,e),this.b=Math.pow(t.b,e),this}copyLinearToGamma(t,e=2){const n=e>0?1/e:1;return this.r=Math.pow(t.r,n),this.g=Math.pow(t.g,n),this.b=Math.pow(t.b,n),this}convertGammaToLinear(t){return this.copyGammaToLinear(this,t),this}convertLinearToGamma(t){return this.copyLinearToGamma(this,t),this}copySRGBToLinear(t){return this.r=Je(t.r),this.g=Je(t.g),this.b=Je(t.b),this}copyLinearToSRGB(t){return this.r=Ze(t.r),this.g=Ze(t.g),this.b=Ze(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(){return 255*this.r<<16^255*this.g<<8^255*this.b<<0}getHexString(){return("000000"+this.getHex().toString(16)).slice(-6)}getHSL(t){const e=this.r,n=this.g,i=this.b,r=Math.max(e,n,i),s=Math.min(e,n,i);let a,o;const l=(s+r)/2;if(s===r)a=0,o=0;else{const t=r-s;switch(o=l<=.5?t/(r+s):t/(2-r-s),r){case e:a=(n-i)/t+(ne&&(e=t[n]);return e}const mn={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function fn(t,e){return new mn[t](e)}let gn=0;const vn=new ae,yn=new Pe,xn=new Rt,_n=new It,bn=new It,Mn=new Rt;class wn extends rt{constructor(){super(),Object.defineProperty(this,"id",{value:gn++}),this.uuid=ct(),this.name="",this.type="BufferGeometry",this.index=null,this.attributes={},this.morphAttributes={},this.morphTargetsRelative=!1,this.groups=[],this.boundingBox=null,this.boundingSphere=null,this.drawRange={start:0,count:1/0},this.userData={}}getIndex(){return this.index}setIndex(t){return Array.isArray(t)?this.index=new(pn(t)>65535?cn:on)(t,1):this.index=t,this}getAttribute(t){return this.attributes[t]}setAttribute(t,e){return this.attributes[t]=e,this}deleteAttribute(t){return delete this.attributes[t],this}hasAttribute(t){return void 0!==this.attributes[t]}addGroup(t,e,n=0){this.groups.push({start:t,count:e,materialIndex:n})}clearGroups(){this.groups=[]}setDrawRange(t,e){this.drawRange.start=t,this.drawRange.count=e}applyMatrix4(t){const e=this.attributes.position;void 0!==e&&(e.applyMatrix4(t),e.needsUpdate=!0);const n=this.attributes.normal;if(void 0!==n){const e=(new yt).getNormalMatrix(t);n.applyNormalMatrix(e),n.needsUpdate=!0}const i=this.attributes.tangent;return void 0!==i&&(i.transformDirection(t),i.needsUpdate=!0),null!==this.boundingBox&&this.computeBoundingBox(),null!==this.boundingSphere&&this.computeBoundingSphere(),this}applyQuaternion(t){return vn.makeRotationFromQuaternion(t),this.applyMatrix4(vn),this}rotateX(t){return vn.makeRotationX(t),this.applyMatrix4(vn),this}rotateY(t){return vn.makeRotationY(t),this.applyMatrix4(vn),this}rotateZ(t){return vn.makeRotationZ(t),this.applyMatrix4(vn),this}translate(t,e,n){return vn.makeTranslation(t,e,n),this.applyMatrix4(vn),this}scale(t,e,n){return vn.makeScale(t,e,n),this.applyMatrix4(vn),this}lookAt(t){return yn.lookAt(t),yn.updateMatrix(),this.applyMatrix4(yn.matrix),this}center(){return this.computeBoundingBox(),this.boundingBox.getCenter(xn).negate(),this.translate(xn.x,xn.y,xn.z),this}setFromPoints(t){const e=[];for(let n=0,i=t.length;n0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],s=[];for(let e=0,i=n.length;e0&&(i[e]=s,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new wn).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone(e));const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0){const t=e[n[0]];if(void 0!==t){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let e=0,n=t.length;e0&&console.error("THREE.Mesh.updateMorphTargets() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.")}}raycast(t,e){const n=this.geometry,i=this.material,r=this.matrixWorld;if(void 0===i)return;if(null===n.boundingSphere&&n.computeBoundingSphere(),En.copy(n.boundingSphere),En.applyMatrix4(r),!1===t.ray.intersectsSphere(En))return;if(Sn.copy(r).invert(),Tn.copy(t.ray).applyMatrix4(Sn),null!==n.boundingBox&&!1===Tn.intersectsBox(n.boundingBox))return;let s;if(n.isBufferGeometry){const r=n.index,a=n.attributes.position,o=n.morphAttributes.position,l=n.morphTargetsRelative,c=n.attributes.uv,h=n.attributes.uv2,u=n.groups,d=n.drawRange;if(null!==r)if(Array.isArray(i))for(let n=0,p=u.length;nn.far?null:{distance:c,point:Hn.clone(),object:t}}(t,e,n,i,An,Ln,Rn,Un);if(p){o&&(Bn.fromBufferAttribute(o,c),Fn.fromBufferAttribute(o,h),On.fromBufferAttribute(o,u),p.uv=ke.getUV(Un,An,Ln,Rn,Bn,Fn,On,new vt)),l&&(Bn.fromBufferAttribute(l,c),Fn.fromBufferAttribute(l,h),On.fromBufferAttribute(l,u),p.uv2=ke.getUV(Un,An,Ln,Rn,Bn,Fn,On,new vt));const t={a:c,b:h,c:u,normal:new Rt,materialIndex:0};ke.getNormal(An,Ln,Rn,t.normal),p.face=t}return p}Gn.prototype.isMesh=!0;class Vn extends wn{constructor(t=1,e=1,n=1,i=1,r=1,s=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:n,widthSegments:i,heightSegments:r,depthSegments:s};const a=this;i=Math.floor(i),r=Math.floor(r),s=Math.floor(s);const o=[],l=[],c=[],h=[];let u=0,d=0;function p(t,e,n,i,r,s,p,m,f,g,v){const y=s/f,x=p/g,_=s/2,b=p/2,M=m/2,w=f+1,S=g+1;let T=0,E=0;const A=new Rt;for(let s=0;s0?1:-1,c.push(A.x,A.y,A.z),h.push(o/f),h.push(1-s/g),T+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}Xn.prototype.isShaderMaterial=!0;class Yn extends Pe{constructor(){super(),this.type="Camera",this.matrixWorldInverse=new ae,this.projectionMatrix=new ae,this.projectionMatrixInverse=new ae}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(-e[8],-e[9],-e[10]).normalize()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}Yn.prototype.isCamera=!0;class Jn extends Yn{constructor(t=50,e=1,n=.1,i=2e3){super(),this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*lt*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*ot*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*lt*Math.atan(Math.tan(.5*ot*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}setViewOffset(t,e,n,i,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*ot*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*i/t,e-=s.offsetY*n/a,i*=s.width/t,n*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}Jn.prototype.isPerspectiveCamera=!0;const Zn=90;class Qn extends Pe{constructor(t,e,n){if(super(),this.type="CubeCamera",!0!==n.isWebGLCubeRenderTarget)return void console.error("THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.");this.renderTarget=n;const i=new Jn(Zn,1,t,e);i.layers=this.layers,i.up.set(0,-1,0),i.lookAt(new Rt(1,0,0)),this.add(i);const r=new Jn(Zn,1,t,e);r.layers=this.layers,r.up.set(0,-1,0),r.lookAt(new Rt(-1,0,0)),this.add(r);const s=new Jn(Zn,1,t,e);s.layers=this.layers,s.up.set(0,0,1),s.lookAt(new Rt(0,1,0)),this.add(s);const a=new Jn(Zn,1,t,e);a.layers=this.layers,a.up.set(0,0,-1),a.lookAt(new Rt(0,-1,0)),this.add(a);const o=new Jn(Zn,1,t,e);o.layers=this.layers,o.up.set(0,-1,0),o.lookAt(new Rt(0,0,1)),this.add(o);const l=new Jn(Zn,1,t,e);l.layers=this.layers,l.up.set(0,-1,0),l.lookAt(new Rt(0,0,-1)),this.add(l)}update(t,e){null===this.parent&&this.updateMatrixWorld();const n=this.renderTarget,[i,r,s,a,o,l]=this.children,c=t.xr.enabled,h=t.getRenderTarget();t.xr.enabled=!1;const u=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0),t.render(e,i),t.setRenderTarget(n,1),t.render(e,r),t.setRenderTarget(n,2),t.render(e,s),t.setRenderTarget(n,3),t.render(e,a),t.setRenderTarget(n,4),t.render(e,o),n.texture.generateMipmaps=u,t.setRenderTarget(n,5),t.render(e,l),t.setRenderTarget(h),t.xr.enabled=c}}class Kn extends Mt{constructor(t,e,n,i,s,a,o,l,c,h){super(t=void 0!==t?t:[],e=void 0!==e?e:r,n,i,s,a,o=void 0!==o?o:T,l,c,h),this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}Kn.prototype.isCubeTexture=!0;class $n extends Tt{constructor(t,e,n){Number.isInteger(e)&&(console.warn("THREE.WebGLCubeRenderTarget: constructor signature is now WebGLCubeRenderTarget( size, options )"),e=n),super(t,t,e),e=e||{},this.texture=new Kn(void 0,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.encoding),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:g,this.texture._needsFlipEnvMap=!1}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.format=E,this.texture.encoding=e.encoding,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new Vn(5,5,5),r=new Xn({name:"CubemapFromEquirect",uniforms:Wn(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const s=new Gn(i,r),a=e.minFilter;e.minFilter===y&&(e.minFilter=g);return new Qn(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,n,i){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}$n.prototype.isWebGLCubeRenderTarget=!0;const ti=new Rt,ei=new Rt,ni=new yt;class ii{constructor(t=new Rt(1,0,0),e=0){this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,n,i){return this.normal.set(t,e,n),this.constant=i,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,n){const i=ti.subVectors(n,e).cross(ei.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(i,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(this.normal).multiplyScalar(-this.distanceToPoint(t)).add(t)}intersectLine(t,e){const n=t.delta(ti),i=this.normal.dot(n);if(0===i)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/i;return r<0||r>1?null:e.copy(n).multiplyScalar(r).add(t.start)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||ni.getNormalMatrix(t),i=this.coplanarPoint(ti).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}ii.prototype.isPlane=!0;const ri=new Qt,si=new Rt;class ai{constructor(t=new ii,e=new ii,n=new ii,i=new ii,r=new ii,s=new ii){this.planes=[t,e,n,i,r,s]}set(t,e,n,i,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(n),a[3].copy(i),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t){const e=this.planes,n=t.elements,i=n[0],r=n[1],s=n[2],a=n[3],o=n[4],l=n[5],c=n[6],h=n[7],u=n[8],d=n[9],p=n[10],m=n[11],f=n[12],g=n[13],v=n[14],y=n[15];return e[0].setComponents(a-i,h-o,m-u,y-f).normalize(),e[1].setComponents(a+i,h+o,m+u,y+f).normalize(),e[2].setComponents(a+r,h+l,m+d,y+g).normalize(),e[3].setComponents(a-r,h-l,m-d,y-g).normalize(),e[4].setComponents(a-s,h-c,m-p,y-v).normalize(),e[5].setComponents(a+s,h+c,m+p,y+v).normalize(),this}intersectsObject(t){const e=t.geometry;return null===e.boundingSphere&&e.computeBoundingSphere(),ri.copy(e.boundingSphere).applyMatrix4(t.matrixWorld),this.intersectsSphere(ri)}intersectsSprite(t){return ri.center.set(0,0,0),ri.radius=.7071067811865476,ri.applyMatrix4(t.matrixWorld),this.intersectsSphere(ri)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,si.y=i.normal.y>0?t.max.y:t.min.y,si.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(si)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function oi(){let t=null,e=!1,n=null,i=null;function r(e,s){n(e,s),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function li(t,e){const n=e.isWebGL2,i=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),i.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const n=i.get(e);n&&(t.deleteBuffer(n.buffer),i.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=i.get(e);return void((!t||t.version 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotVH = saturate( dot( geometry.viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float NoH ) {\n\tfloat invAlpha = 1.0 / roughness;\n\tfloat cos2h = NoH * NoH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float NoV, float NoL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( NoL + NoV - NoL * NoV ) ) );\n}\nvec3 BRDF_Sheen( const in float roughness, const in vec3 L, const in GeometricContext geometry, vec3 specularColor ) {\n\tvec3 N = geometry.normal;\n\tvec3 V = geometry.viewDir;\n\tvec3 H = normalize( V + L );\n\tfloat dotNH = saturate( dot( N, H ) );\n\treturn specularColor * D_Charlie( roughness, dotNH ) * V_Neubelt( dot(N, V), dot(N, L) );\n}\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_maxMipLevel 8.0\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_maxTileSize 256.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\tfloat texelSize = 1.0 / ( 3.0 * cubeUV_maxTileSize );\n\t\tvec2 uv = getUV( direction, face ) * ( faceSize - 1.0 );\n\t\tvec2 f = fract( uv );\n\t\tuv += 0.5 - f;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tif ( mipInt < cubeUV_maxMipLevel ) {\n\t\t\tuv.y += 2.0 * cubeUV_maxTileSize;\n\t\t}\n\t\tuv.y += filterInt * 2.0 * cubeUV_minTileSize;\n\t\tuv.x += 3.0 * max( 0.0, cubeUV_maxTileSize - 2.0 * faceSize );\n\t\tuv *= texelSize;\n\t\tvec3 tl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x += texelSize;\n\t\tvec3 tr = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.y += texelSize;\n\t\tvec3 br = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x -= texelSize;\n\t\tvec3 bl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tvec3 tm = mix( tl, tr, f.x );\n\t\tvec3 bm = mix( bl, br, f.x );\n\t\treturn mix( tm, bm, f.y );\n\t}\n\t#define r0 1.0\n\t#define v0 0.339\n\t#define m0 - 2.0\n\t#define r1 0.8\n\t#define v1 0.276\n\t#define m1 - 1.0\n\t#define r4 0.4\n\t#define v4 0.046\n\t#define m4 2.0\n\t#define r5 0.305\n\t#define v5 0.016\n\t#define m5 3.0\n\t#define r6 0.21\n\t#define v6 0.0038\n\t#define m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= r1 ) {\n\t\t\tmip = ( r0 - roughness ) * ( m1 - m0 ) / ( r0 - r1 ) + m0;\n\t\t} else if ( roughness >= r4 ) {\n\t\t\tmip = ( r1 - roughness ) * ( m4 - m1 ) / ( r1 - r4 ) + m1;\n\t\t} else if ( roughness >= r5 ) {\n\t\t\tmip = ( r4 - roughness ) * ( m5 - m4 ) / ( r4 - r5 ) + m4;\n\t\t} else if ( roughness >= r6 ) {\n\t\t\tmip = ( r5 - roughness ) * ( m6 - m5 ) / ( r5 - r6 ) + m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), m0, cubeUV_maxMipLevel );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * value.a * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = clamp( floor( D ) / 255.0, 0.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = cLogLuvM * value.rgb;\n\tXp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract( Le );\n\tvResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t\tenvColor = envMapTexelToLinear( envColor );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 envColor = textureCubeUV( envMap, reflectVec, 0.0 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#if defined( USE_ENVMAP )\n\t#ifdef ENVMAP_MODE_REFRACTION\n\t\tuniform float refractionRatio;\n\t#endif\n\tvec3 getIBLIrradiance( const in GeometricContext geometry ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 reflectVec;\n\t\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\t\treflectVec = reflect( - viewDir, normal );\n\t\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\t#else\n\t\t\t\treflectVec = refract( - viewDir, normal, refractionRatio );\n\t\t\t#endif\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn texture2D( gradientMap, coord ).rgb;\n\t#else\n\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tlightMapIrradiance *= PI;\n\t#endif\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_vertex:"vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\nvIndirectFront += getAmbientLightIrradiance( ambientLightColor );\nvIndirectFront += getLightProbeIrradiance( lightProbe, geometry );\n#ifdef DOUBLE_SIDED\n\tvIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n\tvIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry );\n#endif\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointLightInfo( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotLightInfo( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalLightInfo( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {\n\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#else\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material )\t(0)",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\t#ifdef SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularTintFactor = specularTint;\n\t\t#ifdef USE_SPECULARINTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vUv ).a;\n\t\t#endif\n\t\t#ifdef USE_SPECULARTINTMAP\n\t\t\tspecularTintFactor *= specularTintMapTexelToLinear( texture2D( specularTintMap, vUv ) ).rgb;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularTintFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( ior - 1.0 ) / ( ior + 1.0 ) ) * specularTintFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenTint = sheenTint;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenTint;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\tvec3 FssEss = specularColor * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3(\t\t0, 1,\t\t0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\treflectedLight.directSpecular += irradiance * BRDF_Sheen( material.roughness, directLight.direction, geometry, material.sheenTint );\n\t#else\n\t\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifndef USE_MORPHNORMALS\n\t\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\t\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * faceDirection;\n\t\t\tbitangent = bitangent * faceDirection;\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;",normal_fragment_maps:"#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( - vViewPosition, normal, mapN, faceDirection );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );\n\t\treturn normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN, faceDirection );\n\t#endif\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif",output_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= transmissionAlpha + 0.1;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), \n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n\t\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\tvec4 shadowWorldPosition;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform highp sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3(\t1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108,\t1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605,\t1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tfloat transmissionAlpha = 1.0;\n\tfloat transmissionFactor = transmission;\n\tfloat thicknessFactor = thickness;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\ttransmissionFactor *= texture2D( transmissionMap, vUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tthicknessFactor *= texture2D( thicknessMap, vUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmission = getIBLVolumeRefraction(\n\t\tn, v, roughnessFactor, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, ior, thicknessFactor,\n\t\tattenuationTint, attenuationDistance );\n\ttotalDiffuse = mix( totalDiffuse, transmission.rgb, transmissionFactor );\n\ttransmissionAlpha = transmission.a;\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationTint;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tvec3 getVolumeTransmissionRay( vec3 n, vec3 v, float thickness, float ior, mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( float roughness, float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( vec2 fragCoord, float roughness, float ior ) {\n\t\tfloat framebufferLod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\treturn texture2DLodEXT( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#else\n\t\t\treturn texture2D( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#endif\n\t}\n\tvec3 applyVolumeAttenuation( vec3 radiance, float transmissionDistance, vec3 attenuationColor, float attenuationDistance ) {\n\t\tif ( attenuationDistance == 0.0 ) {\n\t\t\treturn radiance;\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance * radiance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( vec3 n, vec3 v, float roughness, vec3 diffuseColor, vec3 specularColor, float specularF90,\n\t\tvec3 position, mat4 modelMatrix, mat4 viewMatrix, mat4 projMatrix, float ior, float thickness,\n\t\tvec3 attenuationColor, float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a );\n\t}\n#endif",uv_pars_fragment:"#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif",uv_pars_vertex:"#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif",uv_vertex:"#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif",uv2_pars_fragment:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif",uv2_pars_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif",uv2_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION )\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_frag:"uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",cube_frag:"#include \nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 vReflect = vWorldDirection;\n\t#include \n\tgl_FragColor = envColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;\n\t#else\n\t\treflectedLight.indirectDiffuse += vIndirectFront;\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= BRDF_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t\tmatcapColor = matcapTexelToLinear( matcapColor );\n\t#else\n\t\tvec4 matcapColor = vec4( 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularTint;\n\t#ifdef USE_SPECULARINTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n\t#ifdef USE_SPECULARTINTMAP\n\t\tuniform sampler2D specularTintMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenTint;\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - clearcoat * Fcc ) + clearcoatSpecular * clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"},ui={common:{diffuse:{value:new Qe(16777215)},opacity:{value:1},map:{value:null},uvTransform:{value:new yt},uv2Transform:{value:new yt},alphaMap:{value:null},alphaTest:{value:0}},specularmap:{specularMap:{value:null}},envmap:{envMap:{value:null},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98},maxMipLevel:{value:0}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1}},emissivemap:{emissiveMap:{value:null}},bumpmap:{bumpMap:{value:null},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalScale:{value:new vt(1,1)}},displacementmap:{displacementMap:{value:null},displacementScale:{value:1},displacementBias:{value:0}},roughnessmap:{roughnessMap:{value:null}},metalnessmap:{metalnessMap:{value:null}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new Qe(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotShadowMap:{value:[]},spotShadowMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new Qe(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new yt}},sprite:{diffuse:{value:new Qe(16777215)},opacity:{value:1},center:{value:new vt(.5,.5)},rotation:{value:0},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new yt}}},di={basic:{uniforms:jn([ui.common,ui.specularmap,ui.envmap,ui.aomap,ui.lightmap,ui.fog]),vertexShader:hi.meshbasic_vert,fragmentShader:hi.meshbasic_frag},lambert:{uniforms:jn([ui.common,ui.specularmap,ui.envmap,ui.aomap,ui.lightmap,ui.emissivemap,ui.fog,ui.lights,{emissive:{value:new Qe(0)}}]),vertexShader:hi.meshlambert_vert,fragmentShader:hi.meshlambert_frag},phong:{uniforms:jn([ui.common,ui.specularmap,ui.envmap,ui.aomap,ui.lightmap,ui.emissivemap,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.fog,ui.lights,{emissive:{value:new Qe(0)},specular:{value:new Qe(1118481)},shininess:{value:30}}]),vertexShader:hi.meshphong_vert,fragmentShader:hi.meshphong_frag},standard:{uniforms:jn([ui.common,ui.envmap,ui.aomap,ui.lightmap,ui.emissivemap,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.roughnessmap,ui.metalnessmap,ui.fog,ui.lights,{emissive:{value:new Qe(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:hi.meshphysical_vert,fragmentShader:hi.meshphysical_frag},toon:{uniforms:jn([ui.common,ui.aomap,ui.lightmap,ui.emissivemap,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.gradientmap,ui.fog,ui.lights,{emissive:{value:new Qe(0)}}]),vertexShader:hi.meshtoon_vert,fragmentShader:hi.meshtoon_frag},matcap:{uniforms:jn([ui.common,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.fog,{matcap:{value:null}}]),vertexShader:hi.meshmatcap_vert,fragmentShader:hi.meshmatcap_frag},points:{uniforms:jn([ui.points,ui.fog]),vertexShader:hi.points_vert,fragmentShader:hi.points_frag},dashed:{uniforms:jn([ui.common,ui.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:hi.linedashed_vert,fragmentShader:hi.linedashed_frag},depth:{uniforms:jn([ui.common,ui.displacementmap]),vertexShader:hi.depth_vert,fragmentShader:hi.depth_frag},normal:{uniforms:jn([ui.common,ui.bumpmap,ui.normalmap,ui.displacementmap,{opacity:{value:1}}]),vertexShader:hi.meshnormal_vert,fragmentShader:hi.meshnormal_frag},sprite:{uniforms:jn([ui.sprite,ui.fog]),vertexShader:hi.sprite_vert,fragmentShader:hi.sprite_frag},background:{uniforms:{uvTransform:{value:new yt},t2D:{value:null}},vertexShader:hi.background_vert,fragmentShader:hi.background_frag},cube:{uniforms:jn([ui.envmap,{opacity:{value:1}}]),vertexShader:hi.cube_vert,fragmentShader:hi.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:hi.equirect_vert,fragmentShader:hi.equirect_frag},distanceRGBA:{uniforms:jn([ui.common,ui.displacementmap,{referencePosition:{value:new Rt},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:hi.distanceRGBA_vert,fragmentShader:hi.distanceRGBA_frag},shadow:{uniforms:jn([ui.lights,ui.fog,{color:{value:new Qe(0)},opacity:{value:1}}]),vertexShader:hi.shadow_vert,fragmentShader:hi.shadow_frag}};function pi(t,e,n,i,r){const s=new Qe(0);let a,o,c=0,h=null,u=0,d=null;function p(t,e){n.buffers.color.setClear(t.r,t.g,t.b,e,r)}return{getClearColor:function(){return s},setClearColor:function(t,e=1){s.set(t),c=e,p(s,c)},getClearAlpha:function(){return c},setClearAlpha:function(t){c=t,p(s,c)},render:function(n,r){let m=!1,f=!0===r.isScene?r.background:null;f&&f.isTexture&&(f=e.get(f));const g=t.xr,v=g.getSession&&g.getSession();v&&"additive"===v.environmentBlendMode&&(f=null),null===f?p(s,c):f&&f.isColor&&(p(f,1),m=!0),(t.autoClear||m)&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),f&&(f.isCubeTexture||f.mapping===l)?(void 0===o&&(o=new Gn(new Vn(1,1,1),new Xn({name:"BackgroundCubeMaterial",uniforms:Wn(di.cube.uniforms),vertexShader:di.cube.vertexShader,fragmentShader:di.cube.fragmentShader,side:1,depthTest:!1,depthWrite:!1,fog:!1})),o.geometry.deleteAttribute("normal"),o.geometry.deleteAttribute("uv"),o.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(o.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),i.update(o)),o.material.uniforms.envMap.value=f,o.material.uniforms.flipEnvMap.value=f.isCubeTexture&&!1===f.isRenderTargetTexture?-1:1,h===f&&u===f.version&&d===t.toneMapping||(o.material.needsUpdate=!0,h=f,u=f.version,d=t.toneMapping),n.unshift(o,o.geometry,o.material,0,0,null)):f&&f.isTexture&&(void 0===a&&(a=new Gn(new ci(2,2),new Xn({name:"BackgroundMaterial",uniforms:Wn(di.background.uniforms),vertexShader:di.background.vertexShader,fragmentShader:di.background.fragmentShader,side:0,depthTest:!1,depthWrite:!1,fog:!1})),a.geometry.deleteAttribute("normal"),Object.defineProperty(a.material,"map",{get:function(){return this.uniforms.t2D.value}}),i.update(a)),a.material.uniforms.t2D.value=f,!0===f.matrixAutoUpdate&&f.updateMatrix(),a.material.uniforms.uvTransform.value.copy(f.matrix),h===f&&u===f.version&&d===t.toneMapping||(a.material.needsUpdate=!0,h=f,u=f.version,d=t.toneMapping),n.unshift(a,a.geometry,a.material,0,0,null))}}}function mi(t,e,n,i){const r=t.getParameter(34921),s=i.isWebGL2?null:e.get("OES_vertex_array_object"),a=i.isWebGL2||null!==s,o={},l=d(null);let c=l;function h(e){return i.isWebGL2?t.bindVertexArray(e):s.bindVertexArrayOES(e)}function u(e){return i.isWebGL2?t.deleteVertexArray(e):s.deleteVertexArrayOES(e)}function d(t){const e=[],n=[],i=[];for(let t=0;t=0){let s=l[e];if(void 0===s&&("instanceMatrix"===e&&r.instanceMatrix&&(s=r.instanceMatrix),"instanceColor"===e&&r.instanceColor&&(s=r.instanceColor)),void 0!==s){const e=s.normalized,a=s.itemSize,l=n.get(s);if(void 0===l)continue;const c=l.buffer,h=l.type,u=l.bytesPerElement;if(s.isInterleavedBufferAttribute){const n=s.data,l=n.stride,d=s.offset;if(n&&n.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(35632,36338).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(35633,36337).precision>0&&t.getShaderPrecisionFormat(35632,36337).precision>0?"mediump":"lowp"}const s="undefined"!=typeof WebGL2RenderingContext&&t instanceof WebGL2RenderingContext||"undefined"!=typeof WebGL2ComputeRenderingContext&&t instanceof WebGL2ComputeRenderingContext;let a=void 0!==n.precision?n.precision:"highp";const o=r(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=s||e.has("WEBGL_draw_buffers"),c=!0===n.logarithmicDepthBuffer,h=t.getParameter(34930),u=t.getParameter(35660),d=t.getParameter(3379),p=t.getParameter(34076),m=t.getParameter(34921),f=t.getParameter(36347),g=t.getParameter(36348),v=t.getParameter(36349),y=u>0,x=s||e.has("OES_texture_float");return{isWebGL2:s,drawBuffers:l,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");i=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:r,precision:a,logarithmicDepthBuffer:c,maxTextures:h,maxVertexTextures:u,maxTextureSize:d,maxCubemapSize:p,maxAttributes:m,maxVertexUniforms:f,maxVaryings:g,maxFragmentUniforms:v,vertexTextures:y,floatFragmentTextures:x,floatVertexTextures:y&&x,maxSamples:s?t.getParameter(36183):0}}function vi(t){const e=this;let n=null,i=0,r=!1,s=!1;const a=new ii,o=new yt,l={value:null,needsUpdate:!1};function c(){l.value!==n&&(l.value=n,l.needsUpdate=i>0),e.numPlanes=i,e.numIntersection=0}function h(t,n,i,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=i+4*s,r=n.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0){const a=t.getRenderTarget(),o=new $n(s.height/2);return o.fromEquirectangularTexture(t,r),e.set(r,o),t.setRenderTarget(a),r.addEventListener("dispose",i),n(o.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}di.physical={uniforms:jn([di.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatNormalScale:{value:new vt(1,1)},clearcoatNormalMap:{value:null},sheenTint:{value:new Qe(0)},transmission:{value:0},transmissionMap:{value:null},transmissionSamplerSize:{value:new vt},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},attenuationDistance:{value:0},attenuationTint:{value:new Qe(0)},specularIntensity:{value:0},specularIntensityMap:{value:null},specularTint:{value:new Qe(1,1,1)},specularTintMap:{value:null}}]),vertexShader:hi.meshphysical_vert,fragmentShader:hi.meshphysical_frag};class xi extends Yn{constructor(t=-1,e=1,n=1,i=-1,r=.1,s=2e3){super(),this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=n,this.bottom=i,this.near=r,this.far=s,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,n,i,r,s){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,i=(this.top+this.bottom)/2;let r=n-t,s=n+t,a=i+e,o=i-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,s=r+t*this.view.width,a-=e*this.view.offsetY,o=a-e*this.view.height}this.projectionMatrix.makeOrthographic(r,s,a,o,this.near,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}xi.prototype.isOrthographicCamera=!0;class _i extends Xn{constructor(t){super(t),this.type="RawShaderMaterial"}}_i.prototype.isRawShaderMaterial=!0;const bi=Math.pow(2,8),Mi=[.125,.215,.35,.446,.526,.582],wi=5+Mi.length,Si=20,Ti={[X]:0,[Y]:1,[Z]:2,[Q]:3,[K]:4,[$]:5,[J]:6},Ei=new xi,{_lodPlanes:Ai,_sizeLods:Li,_sigmas:Ri}=Fi(),Ci=new Qe;let Pi=null;const Ii=(1+Math.sqrt(5))/2,Di=1/Ii,Ni=[new Rt(1,1,1),new Rt(-1,1,1),new Rt(1,1,-1),new Rt(-1,1,-1),new Rt(0,Ii,Di),new Rt(0,Ii,-Di),new Rt(Di,0,Ii),new Rt(-Di,0,Ii),new Rt(Ii,Di,0),new Rt(-Ii,Di,0)];class zi{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._blurMaterial=function(t){const e=new Float32Array(t),n=new Rt(0,1,0);return new _i({name:"SphericalGaussianBlur",defines:{n:t},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:e},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:n},inputEncoding:{value:Ti[3e3]},outputEncoding:{value:Ti[3e3]}},vertexShader:ki(),fragmentShader:`\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t${Vi()}\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}(Si),this._equirectShader=null,this._cubemapShader=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100){Pi=this._renderer.getRenderTarget();const r=this._allocateTargets();return this._sceneToCubeUV(t,n,i,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t){return this._fromTexture(t)}fromCubemap(t){return this._fromTexture(t)}compileCubemapShader(){null===this._cubemapShader&&(this._cubemapShader=Gi(),this._compileMaterial(this._cubemapShader))}compileEquirectangularShader(){null===this._equirectShader&&(this._equirectShader=Hi(),this._compileMaterial(this._equirectShader))}dispose(){this._blurMaterial.dispose(),null!==this._cubemapShader&&this._cubemapShader.dispose(),null!==this._equirectShader&&this._equirectShader.dispose();for(let t=0;t2?bi:0,bi,bi),o.setRenderTarget(i),p&&o.render(d,r),o.render(t,r)}d.geometry.dispose(),d.material.dispose(),o.toneMapping=h,o.outputEncoding=c,o.autoClear=l,t.background=m}_textureToCubeUV(t,e){const n=this._renderer;t.isCubeTexture?null==this._cubemapShader&&(this._cubemapShader=Gi()):null==this._equirectShader&&(this._equirectShader=Hi());const i=t.isCubeTexture?this._cubemapShader:this._equirectShader,r=new Gn(Ai[0],i),s=i.uniforms;s.envMap.value=t,t.isCubeTexture||s.texelSize.value.set(1/t.image.width,1/t.image.height),s.inputEncoding.value=Ti[t.encoding],s.outputEncoding.value=Ti[e.texture.encoding],Ui(e,0,0,3*bi,2*bi),n.setRenderTarget(e),n.render(r,Ei)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;for(let e=1;eSi&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${m} samples when the maximum is set to 20`);const f=[];let g=0;for(let t=0;t4?i-8+4:0),3*v,2*v),o.setRenderTarget(e),o.render(c,Ei)}}function Bi(t){return void 0!==t&&t.type===x&&(t.encoding===X||t.encoding===Y||t.encoding===J)}function Fi(){const t=[],e=[],n=[];let i=8;for(let r=0;r4?a=Mi[r-8+4-1]:0==r&&(a=0),n.push(a);const o=1/(s-1),l=-o/2,c=1+o/2,h=[l,l,c,l,c,c,l,l,c,c,l,c],u=6,d=6,p=3,m=2,f=1,g=new Float32Array(p*d*u),v=new Float32Array(m*d*u),y=new Float32Array(f*d*u);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];g.set(i,p*d*t),v.set(h,m*d*t);const r=[t,t,t,t,t,t];y.set(r,f*d*t)}const x=new wn;x.setAttribute("position",new en(g,p)),x.setAttribute("uv",new en(v,m)),x.setAttribute("faceIndex",new en(y,f)),t.push(x),i>4&&i--}return{_lodPlanes:t,_sizeLods:e,_sigmas:n}}function Oi(t){const e=new Tt(3*bi,3*bi,t);return e.texture.mapping=l,e.texture.name="PMREM.cubeUv",e.scissorTest=!0,e}function Ui(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function Hi(){const t=new vt(1,1);return new _i({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null},texelSize:{value:t},inputEncoding:{value:Ti[3e3]},outputEncoding:{value:Ti[3e3]}},vertexShader:ki(),fragmentShader:`\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform vec2 texelSize;\n\n\t\t\t${Vi()}\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tvec2 f = fract( uv / texelSize - 0.5 );\n\t\t\t\tuv -= f * texelSize;\n\t\t\t\tvec3 tl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.x += texelSize.x;\n\t\t\t\tvec3 tr = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.y += texelSize.y;\n\t\t\t\tvec3 br = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.x -= texelSize.x;\n\t\t\t\tvec3 bl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\n\t\t\t\tvec3 tm = mix( tl, tr, f.x );\n\t\t\t\tvec3 bm = mix( bl, br, f.x );\n\t\t\t\tgl_FragColor.rgb = mix( tm, bm, f.y );\n\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}function Gi(){return new _i({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},inputEncoding:{value:Ti[3e3]},outputEncoding:{value:Ti[3e3]}},vertexShader:ki(),fragmentShader:`\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\t${Vi()}\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb = envMapTexelToLinear( textureCube( envMap, vec3( - vOutputDirection.x, vOutputDirection.yz ) ) ).rgb;\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}function ki(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute vec3 position;\n\t\tattribute vec2 uv;\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function Vi(){return"\n\n\t\tuniform int inputEncoding;\n\t\tuniform int outputEncoding;\n\n\t\t#include \n\n\t\tvec4 inputTexelToLinear( vec4 value ) {\n\n\t\t\tif ( inputEncoding == 0 ) {\n\n\t\t\t\treturn value;\n\n\t\t\t} else if ( inputEncoding == 1 ) {\n\n\t\t\t\treturn sRGBToLinear( value );\n\n\t\t\t} else if ( inputEncoding == 2 ) {\n\n\t\t\t\treturn RGBEToLinear( value );\n\n\t\t\t} else if ( inputEncoding == 3 ) {\n\n\t\t\t\treturn RGBMToLinear( value, 7.0 );\n\n\t\t\t} else if ( inputEncoding == 4 ) {\n\n\t\t\t\treturn RGBMToLinear( value, 16.0 );\n\n\t\t\t} else if ( inputEncoding == 5 ) {\n\n\t\t\t\treturn RGBDToLinear( value, 256.0 );\n\n\t\t\t} else {\n\n\t\t\t\treturn GammaToLinear( value, 2.2 );\n\n\t\t\t}\n\n\t\t}\n\n\t\tvec4 linearToOutputTexel( vec4 value ) {\n\n\t\t\tif ( outputEncoding == 0 ) {\n\n\t\t\t\treturn value;\n\n\t\t\t} else if ( outputEncoding == 1 ) {\n\n\t\t\t\treturn LinearTosRGB( value );\n\n\t\t\t} else if ( outputEncoding == 2 ) {\n\n\t\t\t\treturn LinearToRGBE( value );\n\n\t\t\t} else if ( outputEncoding == 3 ) {\n\n\t\t\t\treturn LinearToRGBM( value, 7.0 );\n\n\t\t\t} else if ( outputEncoding == 4 ) {\n\n\t\t\t\treturn LinearToRGBM( value, 16.0 );\n\n\t\t\t} else if ( outputEncoding == 5 ) {\n\n\t\t\t\treturn LinearToRGBD( value, 256.0 );\n\n\t\t\t} else {\n\n\t\t\t\treturn LinearToGamma( value, 2.2 );\n\n\t\t\t}\n\n\t\t}\n\n\t\tvec4 envMapTexelToLinear( vec4 color ) {\n\n\t\t\treturn inputTexelToLinear( color );\n\n\t\t}\n\t"}function Wi(t){let e=new WeakMap,n=null;function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(l){if(l&&l.isTexture&&!1===l.isRenderTargetTexture){const c=l.mapping,h=c===a||c===o,u=c===r||c===s;if(h||u){if(e.has(l))return e.get(l).texture;{const r=l.image;if(h&&r&&r.height>0||u&&r&&function(t){let e=0;const n=6;for(let i=0;i65535?cn:on)(n,1);o.version=a;const l=s.get(t);l&&e.remove(l),s.set(t,o)}return{get:function(t,e){return!0===r[e.id]||(e.addEventListener("dispose",a),r[e.id]=!0,n.memory.geometries++),e},update:function(t){const n=t.attributes;for(const t in n)e.update(n[t],34962);const i=t.morphAttributes;for(const t in i){const n=i[t];for(let t=0,i=n.length;t0)return t;const r=e*n;let s=sr[r];if(void 0===s&&(s=new Float32Array(r),sr[r]=s),0!==e){i.toArray(s,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(s,r)}return s}function ur(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n/gm;function ms(t){return t.replace(ps,fs)}function fs(t,e){const n=hi[e];if(void 0===n)throw new Error("Can not resolve #include <"+e+">");return ms(n)}const gs=/#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g,vs=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function ys(t){return t.replace(vs,_s).replace(gs,xs)}function xs(t,e,n,i){return console.warn("WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead."),_s(t,e,n,i)}function _s(t,e,n,i){let r="";for(let t=parseInt(e);t0?t.gammaFactor:1,v=n.isWebGL2?"":function(t){return[t.extensionDerivatives||t.envMapCubeUV||t.bumpMap||t.tangentSpaceNormalMap||t.clearcoatNormalMap||t.flatShading||"physical"===t.shaderID?"#extension GL_OES_standard_derivatives : enable":"",(t.extensionFragDepth||t.logarithmicDepthBuffer)&&t.rendererExtensionFragDepth?"#extension GL_EXT_frag_depth : enable":"",t.extensionDrawBuffers&&t.rendererExtensionDrawBuffers?"#extension GL_EXT_draw_buffers : require":"",(t.extensionShaderTextureLOD||t.envMap||t.transmission)&&t.rendererExtensionShaderTextureLod?"#extension GL_EXT_shader_texture_lod : enable":""].filter(hs).join("\n")}(n),y=function(t){const e=[];for(const n in t){const i=t[n];!1!==i&&e.push("#define "+n+" "+i)}return e.join("\n")}(o),x=a.createProgram();let _,b,M=n.glslVersion?"#version "+n.glslVersion+"\n":"";n.isRawShaderMaterial?(_=[y].filter(hs).join("\n"),_.length>0&&(_+="\n"),b=[v,y].filter(hs).join("\n"),b.length>0&&(b+="\n")):(_=[bs(n),"#define SHADER_NAME "+n.shaderName,y,n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.supportsVertexTextures?"#define VERTEX_TEXTURES":"","#define GAMMA_FACTOR "+g,"#define MAX_BONES "+n.maxBones,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+m:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.displacementMap&&n.supportsVertexTextures?"#define USE_DISPLACEMENTMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularIntensityMap?"#define USE_SPECULARINTENSITYMAP":"",n.specularTintMap?"#define USE_SPECULARTINTMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.useVertexTexture?"#define BONE_TEXTURE":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+d:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#ifdef USE_MORPHTARGETS","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(hs).join("\n"),b=[v,bs(n),"#define SHADER_NAME "+n.shaderName,y,"#define GAMMA_FACTOR "+g,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+p:"",n.envMap?"#define "+m:"",n.envMap?"#define "+f:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularIntensityMap?"#define USE_SPECULARINTENSITYMAP":"",n.specularTintMap?"#define USE_SPECULARTINTMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.sheenTint?"#define USE_SHEEN":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+d:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.physicallyCorrectLights?"#define PHYSICALLY_CORRECT_LIGHTS":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"",(n.extensionShaderTextureLOD||n.envMap)&&n.rendererExtensionShaderTextureLod?"#define TEXTURE_LOD_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==n.toneMapping?"#define TONE_MAPPING":"",0!==n.toneMapping?hi.tonemapping_pars_fragment:"",0!==n.toneMapping?cs("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.format===T?"#define OPAQUE":"",hi.encodings_pars_fragment,n.map?os("mapTexelToLinear",n.mapEncoding):"",n.matcap?os("matcapTexelToLinear",n.matcapEncoding):"",n.envMap?os("envMapTexelToLinear",n.envMapEncoding):"",n.emissiveMap?os("emissiveMapTexelToLinear",n.emissiveMapEncoding):"",n.specularTintMap?os("specularTintMapTexelToLinear",n.specularTintMapEncoding):"",n.lightMap?os("lightMapTexelToLinear",n.lightMapEncoding):"",ls("linearToOutputTexel",n.outputEncoding),n.depthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(hs).join("\n")),h=ms(h),h=us(h,n),h=ds(h,n),u=ms(u),u=us(u,n),u=ds(u,n),h=ys(h),u=ys(u),n.isWebGL2&&!0!==n.isRawShaderMaterial&&(M="#version 300 es\n",_=["#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+_,b=["#define varying in",n.glslVersion===it?"":"out highp vec4 pc_fragColor;",n.glslVersion===it?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+b);const w=M+b+u,S=is(a,35633,M+_+h),E=is(a,35632,w);if(a.attachShader(x,S),a.attachShader(x,E),void 0!==n.index0AttributeName?a.bindAttribLocation(x,0,n.index0AttributeName):!0===n.morphTargets&&a.bindAttribLocation(x,0,"position"),a.linkProgram(x),t.debug.checkShaderErrors){const t=a.getProgramInfoLog(x).trim(),e=a.getShaderInfoLog(S).trim(),n=a.getShaderInfoLog(E).trim();let i=!0,r=!0;if(!1===a.getProgramParameter(x,35714)){i=!1;const e=as(a,S,"vertex"),n=as(a,E,"fragment");console.error("THREE.WebGLProgram: Shader Error "+a.getError()+" - VALIDATE_STATUS "+a.getProgramParameter(x,35715)+"\n\nProgram Info Log: "+t+"\n"+e+"\n"+n)}else""!==t?console.warn("THREE.WebGLProgram: Program Info Log:",t):""!==e&&""!==n||(r=!1);r&&(this.diagnostics={runnable:i,programLog:t,vertexShader:{log:e,prefix:_},fragmentShader:{log:n,prefix:b}})}let A,L;return a.deleteShader(S),a.deleteShader(E),this.getUniforms=function(){return void 0===A&&(A=new ns(a,x)),A},this.getAttributes=function(){return void 0===L&&(L=function(t,e){const n={},i=t.getProgramParameter(e,35721);for(let r=0;r0,C=s.clearcoat>0;return{isWebGL2:h,shaderID:S,shaderName:s.type,vertexShader:E,fragmentShader:A,defines:s.defines,isRawShaderMaterial:!0===s.isRawShaderMaterial,glslVersion:s.glslVersion,precision:f,instancing:!0===_.isInstancedMesh,instancingColor:!0===_.isInstancedMesh&&null!==_.instanceColor,supportsVertexTextures:m,outputEncoding:null!==L?y(L.texture):t.outputEncoding,map:!!s.map,mapEncoding:y(s.map),matcap:!!s.matcap,matcapEncoding:y(s.matcap),envMap:!!w,envMapMode:w&&w.mapping,envMapEncoding:y(w),envMapCubeUV:!!w&&(w.mapping===l||w.mapping===c),lightMap:!!s.lightMap,lightMapEncoding:y(s.lightMap),aoMap:!!s.aoMap,emissiveMap:!!s.emissiveMap,emissiveMapEncoding:y(s.emissiveMap),bumpMap:!!s.bumpMap,normalMap:!!s.normalMap,objectSpaceNormalMap:1===s.normalMapType,tangentSpaceNormalMap:0===s.normalMapType,clearcoat:C,clearcoatMap:C&&!!s.clearcoatMap,clearcoatRoughnessMap:C&&!!s.clearcoatRoughnessMap,clearcoatNormalMap:C&&!!s.clearcoatNormalMap,displacementMap:!!s.displacementMap,roughnessMap:!!s.roughnessMap,metalnessMap:!!s.metalnessMap,specularMap:!!s.specularMap,specularIntensityMap:!!s.specularIntensityMap,specularTintMap:!!s.specularTintMap,specularTintMapEncoding:y(s.specularTintMap),alphaMap:!!s.alphaMap,alphaTest:R,gradientMap:!!s.gradientMap,sheenTint:!!s.sheenTint&&(s.sheenTint.r>0||s.sheenTint.g>0||s.sheenTint.b>0),transmission:s.transmission>0,transmissionMap:!!s.transmissionMap,thicknessMap:!!s.thicknessMap,combine:s.combine,vertexTangents:!!s.normalMap&&!!_.geometry&&!!_.geometry.attributes.tangent,vertexColors:s.vertexColors,vertexAlphas:!0===s.vertexColors&&!!_.geometry&&!!_.geometry.attributes.color&&4===_.geometry.attributes.color.itemSize,vertexUvs:!!(s.map||s.bumpMap||s.normalMap||s.specularMap||s.alphaMap||s.emissiveMap||s.roughnessMap||s.metalnessMap||s.clearcoatMap||s.clearcoatRoughnessMap||s.clearcoatNormalMap||s.displacementMap||s.transmissionMap||s.thicknessMap||s.specularIntensityMap||s.specularTintMap),uvsVertexOnly:!(s.map||s.bumpMap||s.normalMap||s.specularMap||s.alphaMap||s.emissiveMap||s.roughnessMap||s.metalnessMap||s.clearcoatNormalMap||s.transmission>0||s.transmissionMap||s.thicknessMap||s.specularIntensityMap||s.specularTintMap||!s.displacementMap),fog:!!b,useFog:s.fog,fogExp2:b&&b.isFogExp2,flatShading:!!s.flatShading,sizeAttenuation:s.sizeAttenuation,logarithmicDepthBuffer:u,skinning:!0===_.isSkinnedMesh&&T>0,maxBones:T,useVertexTexture:d,morphTargets:!!_.geometry&&!!_.geometry.morphAttributes.position,morphNormals:!!_.geometry&&!!_.geometry.morphAttributes.normal,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,format:s.format,dithering:s.dithering,shadowMapEnabled:t.shadowMap.enabled&&v.length>0,shadowMapType:t.shadowMap.type,toneMapping:s.toneMapped?t.toneMapping:0,physicallyCorrectLights:t.physicallyCorrectLights,premultipliedAlpha:s.premultipliedAlpha,doubleSided:2===s.side,flipSided:1===s.side,depthPacking:void 0!==s.depthPacking&&s.depthPacking,index0AttributeName:s.index0AttributeName,extensionDerivatives:s.extensions&&s.extensions.derivatives,extensionFragDepth:s.extensions&&s.extensions.fragDepth,extensionDrawBuffers:s.extensions&&s.extensions.drawBuffers,extensionShaderTextureLOD:s.extensions&&s.extensions.shaderTextureLOD,rendererExtensionFragDepth:h||i.has("EXT_frag_depth"),rendererExtensionDrawBuffers:h||i.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:h||i.has("EXT_shader_texture_lod"),customProgramCacheKey:s.customProgramCacheKey()}},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.fragmentShader),n.push(e.vertexShader)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);if(!1===e.isRawShaderMaterial){for(let t=0;t0?r.push(h):!0===n.transparent?s.push(h):i.push(h)},unshift:function(t,e,n,a,l,c){const h=o(t,e,n,a,l,c);n.transmission>0?r.unshift(h):!0===n.transparent?s.unshift(h):i.unshift(h)},finish:function(){for(let t=n,i=e.length;t1&&i.sort(t||Ts),r.length>1&&r.sort(e||Es),s.length>1&&s.sort(e||Es)}}}function Ls(t){let e=new WeakMap;return{get:function(n,i){let r;return!1===e.has(n)?(r=new As(t),e.set(n,[r])):i>=e.get(n).length?(r=new As(t),e.get(n).push(r)):r=e.get(n)[i],r},dispose:function(){e=new WeakMap}}}function Rs(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new Rt,color:new Qe};break;case"SpotLight":n={position:new Rt,direction:new Rt,color:new Qe,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new Rt,color:new Qe,distance:0,decay:0};break;case"HemisphereLight":n={direction:new Rt,skyColor:new Qe,groundColor:new Qe};break;case"RectAreaLight":n={color:new Qe,position:new Rt,halfWidth:new Rt,halfHeight:new Rt}}return t[e.id]=n,n}}}let Cs=0;function Ps(t,e){return(e.castShadow?1:0)-(t.castShadow?1:0)}function Is(t,e){const n=new Rs,i=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new vt};break;case"PointLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new vt,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),r={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotShadow:[],spotShadowMap:[],spotShadowMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[]};for(let t=0;t<9;t++)r.probe.push(new Rt);const s=new Rt,a=new ae,o=new ae;return{setup:function(s,a){let o=0,l=0,c=0;for(let t=0;t<9;t++)r.probe[t].set(0,0,0);let h=0,u=0,d=0,p=0,m=0,f=0,g=0,v=0;s.sort(Ps);const y=!0!==a?Math.PI:1;for(let t=0,e=s.length;t0&&(e.isWebGL2||!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=ui.LTC_FLOAT_1,r.rectAreaLTC2=ui.LTC_FLOAT_2):!0===t.has("OES_texture_half_float_linear")?(r.rectAreaLTC1=ui.LTC_HALF_1,r.rectAreaLTC2=ui.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),r.ambient[0]=o,r.ambient[1]=l,r.ambient[2]=c;const x=r.hash;x.directionalLength===h&&x.pointLength===u&&x.spotLength===d&&x.rectAreaLength===p&&x.hemiLength===m&&x.numDirectionalShadows===f&&x.numPointShadows===g&&x.numSpotShadows===v||(r.directional.length=h,r.spot.length=d,r.rectArea.length=p,r.point.length=u,r.hemi.length=m,r.directionalShadow.length=f,r.directionalShadowMap.length=f,r.pointShadow.length=g,r.pointShadowMap.length=g,r.spotShadow.length=v,r.spotShadowMap.length=v,r.directionalShadowMatrix.length=f,r.pointShadowMatrix.length=g,r.spotShadowMatrix.length=v,x.directionalLength=h,x.pointLength=u,x.spotLength=d,x.rectAreaLength=p,x.hemiLength=m,x.numDirectionalShadows=f,x.numPointShadows=g,x.numSpotShadows=v,r.version=Cs++)},setupView:function(t,e){let n=0,i=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=n.get(i).length?(s=new Ds(t,e),n.get(i).push(s)):s=n.get(i)[r],s},dispose:function(){n=new WeakMap}}}class zs extends We{constructor(t){super(),this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.fog=!1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}zs.prototype.isMeshDepthMaterial=!0;class Bs extends We{constructor(t){super(),this.type="MeshDistanceMaterial",this.referencePosition=new Rt,this.nearDistance=1,this.farDistance=1e3,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.fog=!1,this.setValues(t)}copy(t){return super.copy(t),this.referencePosition.copy(t.referencePosition),this.nearDistance=t.nearDistance,this.farDistance=t.farDistance,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}Bs.prototype.isMeshDistanceMaterial=!0;function Fs(t,e,n){let i=new ai;const r=new vt,s=new vt,a=new St,o=new zs({depthPacking:3201}),l=new Bs,c={},h=n.maxTextureSize,u={0:1,1:0,2:2},d=new Xn({uniforms:{shadow_pass:{value:null},resolution:{value:new vt},radius:{value:4},samples:{value:8}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\nuniform float samples;\n#include \nvoid main() {\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),m=d.clone();m.defines.HORIZONTAL_PASS=1;const f=new wn;f.setAttribute("position",new en(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const v=new Gn(f,d),y=this;function x(n,i){const r=e.update(v);d.uniforms.shadow_pass.value=n.map.texture,d.uniforms.resolution.value=n.mapSize,d.uniforms.radius.value=n.radius,d.uniforms.samples.value=n.blurSamples,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,r,d,v,null),m.uniforms.shadow_pass.value=n.mapPass.texture,m.uniforms.resolution.value=n.mapSize,m.uniforms.radius.value=n.radius,m.uniforms.samples.value=n.blurSamples,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,r,m,v,null)}function _(e,n,i,r,s,a,h){let d=null;const p=!0===r.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(d=void 0!==p?p:!0===r.isPointLight?l:o,t.localClippingEnabled&&!0===i.clipShadows&&0!==i.clippingPlanes.length||i.displacementMap&&0!==i.displacementScale||i.alphaMap&&i.alphaTest>0){const t=d.uuid,e=i.uuid;let n=c[t];void 0===n&&(n={},c[t]=n);let r=n[e];void 0===r&&(r=d.clone(),n[e]=r),d=r}return d.visible=i.visible,d.wireframe=i.wireframe,d.side=3===h?null!==i.shadowSide?i.shadowSide:i.side:null!==i.shadowSide?i.shadowSide:u[i.side],d.alphaMap=i.alphaMap,d.alphaTest=i.alphaTest,d.clipShadows=i.clipShadows,d.clippingPlanes=i.clippingPlanes,d.clipIntersection=i.clipIntersection,d.displacementMap=i.displacementMap,d.displacementScale=i.displacementScale,d.displacementBias=i.displacementBias,d.wireframeLinewidth=i.wireframeLinewidth,d.linewidth=i.linewidth,!0===r.isPointLight&&!0===d.isMeshDistanceMaterial&&(d.referencePosition.setFromMatrixPosition(r.matrixWorld),d.nearDistance=s,d.farDistance=a),d}function b(n,r,s,a,o){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&3===o)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,n.matrixWorld);const i=e.update(n),r=n.material;if(Array.isArray(r)){const e=i.groups;for(let l=0,c=e.length;lh||r.y>h)&&(r.x>h&&(s.x=Math.floor(h/m.x),r.x=s.x*m.x,u.mapSize.x=s.x),r.y>h&&(s.y=Math.floor(h/m.y),r.y=s.y*m.y,u.mapSize.y=s.y)),null===u.map&&!u.isPointLightShadow&&3===this.type){const t={minFilter:g,magFilter:g,format:E};u.map=new Tt(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.mapPass=new Tt(r.x,r.y,t),u.camera.updateProjectionMatrix()}if(null===u.map){const t={minFilter:p,magFilter:p,format:E};u.map=new Tt(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const f=u.getViewportCount();for(let t=0;t=1):-1!==R.indexOf("OpenGL ES")&&(L=parseFloat(/^OpenGL ES (\d)/.exec(R)[1]),A=L>=2);let C=null,P={};const I=t.getParameter(3088),D=t.getParameter(2978),N=(new St).fromArray(I),z=(new St).fromArray(D);function B(e,n,i){const r=new Uint8Array(4),s=t.createTexture();t.bindTexture(e,s),t.texParameteri(e,10241,9728),t.texParameteri(e,10240,9728);for(let e=0;ei||t.height>i)&&(r=i/Math.max(t.width,t.height)),r<1||!0===e){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const i=e?ft:Math.floor,s=i(r*t.width),a=i(r*t.height);void 0===P&&(P=D(s,a));const o=n?D(s,a):P;o.width=s,o.height=a;return o.getContext("2d").drawImage(t,0,0,s,a),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+t.width+"x"+t.height+") to ("+s+"x"+a+")."),o}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+t.width+"x"+t.height+")."),t}return t}function z(t){return pt(t.width)&&pt(t.height)}function B(t,e){return t.generateMipmaps&&e&&t.minFilter!==p&&t.minFilter!==g}function F(e,n,r,s,a=1){t.generateMipmap(e);i.get(n).__maxMipLevel=Math.log2(Math.max(r,s,a))}function O(n,i,r){if(!1===o)return i;if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let s=i;return 6403===i&&(5126===r&&(s=33326),5131===r&&(s=33325),5121===r&&(s=33321)),6407===i&&(5126===r&&(s=34837),5131===r&&(s=34843),5121===r&&(s=32849)),6408===i&&(5126===r&&(s=34836),5131===r&&(s=34842),5121===r&&(s=32856)),33325!==s&&33326!==s&&34842!==s&&34836!==s||e.get("EXT_color_buffer_float"),s}function U(t){return t===p||t===m||t===f?9728:9729}function H(e){const n=e.target;n.removeEventListener("dispose",H),function(e){const n=i.get(e);if(void 0===n.__webglInit)return;t.deleteTexture(n.__webglTexture),i.remove(e)}(n),n.isVideoTexture&&C.delete(n),a.memory.textures--}function G(e){const n=e.target;n.removeEventListener("dispose",G),function(e){const n=e.texture,r=i.get(e),s=i.get(n);if(!e)return;void 0!==s.__webglTexture&&(t.deleteTexture(s.__webglTexture),a.memory.textures--);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++)t.deleteFramebuffer(r.__webglFramebuffer[e]),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer[e]);else t.deleteFramebuffer(r.__webglFramebuffer),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer),r.__webglMultisampledFramebuffer&&t.deleteFramebuffer(r.__webglMultisampledFramebuffer),r.__webglColorRenderbuffer&&t.deleteRenderbuffer(r.__webglColorRenderbuffer),r.__webglDepthRenderbuffer&&t.deleteRenderbuffer(r.__webglDepthRenderbuffer);if(e.isWebGLMultipleRenderTargets)for(let e=0,r=n.length;e0&&r.__version!==t.version){const n=t.image;if(void 0===n)console.warn("THREE.WebGLRenderer: Texture marked for update but image is undefined");else{if(!1!==n.complete)return void J(r,t,e);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.activeTexture(33984+e),n.bindTexture(3553,r.__webglTexture)}function W(e,r){const a=i.get(e);e.version>0&&a.__version!==e.version?function(e,i,r){if(6!==i.image.length)return;Y(e,i),n.activeTexture(33984+r),n.bindTexture(34067,e.__webglTexture),t.pixelStorei(37440,i.flipY),t.pixelStorei(37441,i.premultiplyAlpha),t.pixelStorei(3317,i.unpackAlignment),t.pixelStorei(37443,0);const a=i&&(i.isCompressedTexture||i.image[0].isCompressedTexture),l=i.image[0]&&i.image[0].isDataTexture,h=[];for(let t=0;t<6;t++)h[t]=a||l?l?i.image[t].image:i.image[t]:N(i.image[t],!1,!0,c);const u=h[0],d=z(u)||o,p=s.convert(i.format),m=s.convert(i.type),f=O(i.internalFormat,p,m);let g;if(X(34067,i,d),a){for(let t=0;t<6;t++){g=h[t].mipmaps;for(let e=0;e1||i.get(s).__currentAnisotropy)&&(t.texParameterf(n,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),i.get(s).__currentAnisotropy=s.anisotropy)}}function Y(e,n){void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",H),e.__webglTexture=t.createTexture(),a.memory.textures++)}function J(e,i,r){let a=3553;i.isDataTexture2DArray&&(a=35866),i.isDataTexture3D&&(a=32879),Y(e,i),n.activeTexture(33984+r),n.bindTexture(a,e.__webglTexture),t.pixelStorei(37440,i.flipY),t.pixelStorei(37441,i.premultiplyAlpha),t.pixelStorei(3317,i.unpackAlignment),t.pixelStorei(37443,0);const l=function(t){return!o&&(t.wrapS!==u||t.wrapT!==u||t.minFilter!==p&&t.minFilter!==g)}(i)&&!1===z(i.image),c=N(i.image,l,!1,x),h=z(c)||o,d=s.convert(i.format);let m,f=s.convert(i.type),v=O(i.internalFormat,d,f);X(a,i,h);const y=i.mipmaps;if(i.isDepthTexture)v=6402,o?v=i.type===M?36012:i.type===b?33190:i.type===S?35056:33189:i.type===M&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),i.format===A&&6402===v&&i.type!==_&&i.type!==b&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),i.type=_,f=s.convert(i.type)),i.format===L&&6402===v&&(v=34041,i.type!==S&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),i.type=S,f=s.convert(i.type))),n.texImage2D(3553,0,v,c.width,c.height,0,d,f,null);else if(i.isDataTexture)if(y.length>0&&h){for(let t=0,e=y.length;t0&&h){for(let t=0,e=y.length;t=l&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+l),k+=1,t},this.resetTextureUnits=function(){k=0},this.setTexture2D=V,this.setTexture2DArray=function(t,e){const r=i.get(t);t.version>0&&r.__version!==t.version?J(r,t,e):(n.activeTexture(33984+e),n.bindTexture(35866,r.__webglTexture))},this.setTexture3D=function(t,e){const r=i.get(t);t.version>0&&r.__version!==t.version?J(r,t,e):(n.activeTexture(33984+e),n.bindTexture(32879,r.__webglTexture))},this.setTextureCube=W,this.setupRenderTarget=function(e){const l=e.texture,c=i.get(e),h=i.get(l);e.addEventListener("dispose",G),!0!==e.isWebGLMultipleRenderTargets&&(h.__webglTexture=t.createTexture(),h.__version=l.version,a.memory.textures++);const u=!0===e.isWebGLCubeRenderTarget,d=!0===e.isWebGLMultipleRenderTargets,p=!0===e.isWebGLMultisampleRenderTarget,m=l.isDataTexture3D||l.isDataTexture2DArray,f=z(e)||o;if(!o||l.format!==T||l.type!==M&&l.type!==w||(l.format=E,console.warn("THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.")),u){c.__webglFramebuffer=[];for(let e=0;e<6;e++)c.__webglFramebuffer[e]=t.createFramebuffer()}else if(c.__webglFramebuffer=t.createFramebuffer(),d)if(r.drawBuffers){const n=e.texture;for(let e=0,r=n.length;eo+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}}class js extends rt{constructor(t,e){super();const n=this,i=t.state;let r=null,s=1,a=null,o="local-floor",l=null,c=null,h=null,u=null,d=null,p=!1,m=null,f=null,g=null,v=null,y=null,x=null;const _=[],b=new Map,M=new Jn;M.layers.enable(1),M.viewport=new St;const w=new Jn;w.layers.enable(2),w.viewport=new St;const S=[M,w],T=new Gs;T.layers.enable(1),T.layers.enable(2);let E=null,A=null;function L(t){const e=b.get(t.inputSource);e&&e.dispatchEvent({type:t.type,data:t.inputSource})}function R(){b.forEach((function(t,e){t.disconnect(e)})),b.clear(),E=null,A=null,i.bindXRFramebuffer(null),t.setRenderTarget(t.getRenderTarget()),h&&e.deleteFramebuffer(h),m&&e.deleteFramebuffer(m),f&&e.deleteRenderbuffer(f),g&&e.deleteRenderbuffer(g),h=null,m=null,f=null,g=null,d=null,u=null,c=null,r=null,z.stop(),n.isPresenting=!1,n.dispatchEvent({type:"sessionend"})}function C(t){const e=r.inputSources;for(let t=0;t<_.length;t++)b.set(e[t],_[t]);for(let e=0;e0&&(e.alphaTest.value=n.alphaTest);const i=t.get(n).envMap;if(i){e.envMap.value=i,e.flipEnvMap.value=i.isCubeTexture&&!1===i.isRenderTargetTexture?-1:1,e.reflectivity.value=n.reflectivity,e.ior.value=n.ior,e.refractionRatio.value=n.refractionRatio;const r=t.get(i).__maxMipLevel;void 0!==r&&(e.maxMipLevel.value=r)}let r,s;n.lightMap&&(e.lightMap.value=n.lightMap,e.lightMapIntensity.value=n.lightMapIntensity),n.aoMap&&(e.aoMap.value=n.aoMap,e.aoMapIntensity.value=n.aoMapIntensity),n.map?r=n.map:n.specularMap?r=n.specularMap:n.displacementMap?r=n.displacementMap:n.normalMap?r=n.normalMap:n.bumpMap?r=n.bumpMap:n.roughnessMap?r=n.roughnessMap:n.metalnessMap?r=n.metalnessMap:n.alphaMap?r=n.alphaMap:n.emissiveMap?r=n.emissiveMap:n.clearcoatMap?r=n.clearcoatMap:n.clearcoatNormalMap?r=n.clearcoatNormalMap:n.clearcoatRoughnessMap?r=n.clearcoatRoughnessMap:n.specularIntensityMap?r=n.specularIntensityMap:n.specularTintMap?r=n.specularTintMap:n.transmissionMap?r=n.transmissionMap:n.thicknessMap&&(r=n.thicknessMap),void 0!==r&&(r.isWebGLRenderTarget&&(r=r.texture),!0===r.matrixAutoUpdate&&r.updateMatrix(),e.uvTransform.value.copy(r.matrix)),n.aoMap?s=n.aoMap:n.lightMap&&(s=n.lightMap),void 0!==s&&(s.isWebGLRenderTarget&&(s=s.texture),!0===s.matrixAutoUpdate&&s.updateMatrix(),e.uv2Transform.value.copy(s.matrix))}function n(e,n){e.roughness.value=n.roughness,e.metalness.value=n.metalness,n.roughnessMap&&(e.roughnessMap.value=n.roughnessMap),n.metalnessMap&&(e.metalnessMap.value=n.metalnessMap),n.emissiveMap&&(e.emissiveMap.value=n.emissiveMap),n.bumpMap&&(e.bumpMap.value=n.bumpMap,e.bumpScale.value=n.bumpScale,1===n.side&&(e.bumpScale.value*=-1)),n.normalMap&&(e.normalMap.value=n.normalMap,e.normalScale.value.copy(n.normalScale),1===n.side&&e.normalScale.value.negate()),n.displacementMap&&(e.displacementMap.value=n.displacementMap,e.displacementScale.value=n.displacementScale,e.displacementBias.value=n.displacementBias);t.get(n).envMap&&(e.envMapIntensity.value=n.envMapIntensity)}return{refreshFogUniforms:function(t,e){t.fogColor.value.copy(e.color),e.isFog?(t.fogNear.value=e.near,t.fogFar.value=e.far):e.isFogExp2&&(t.fogDensity.value=e.density)},refreshMaterialUniforms:function(t,i,r,s,a){i.isMeshBasicMaterial?e(t,i):i.isMeshLambertMaterial?(e(t,i),function(t,e){e.emissiveMap&&(t.emissiveMap.value=e.emissiveMap)}(t,i)):i.isMeshToonMaterial?(e(t,i),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap);e.emissiveMap&&(t.emissiveMap.value=e.emissiveMap);e.bumpMap&&(t.bumpMap.value=e.bumpMap,t.bumpScale.value=e.bumpScale,1===e.side&&(t.bumpScale.value*=-1));e.normalMap&&(t.normalMap.value=e.normalMap,t.normalScale.value.copy(e.normalScale),1===e.side&&t.normalScale.value.negate());e.displacementMap&&(t.displacementMap.value=e.displacementMap,t.displacementScale.value=e.displacementScale,t.displacementBias.value=e.displacementBias)}(t,i)):i.isMeshPhongMaterial?(e(t,i),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4),e.emissiveMap&&(t.emissiveMap.value=e.emissiveMap);e.bumpMap&&(t.bumpMap.value=e.bumpMap,t.bumpScale.value=e.bumpScale,1===e.side&&(t.bumpScale.value*=-1));e.normalMap&&(t.normalMap.value=e.normalMap,t.normalScale.value.copy(e.normalScale),1===e.side&&t.normalScale.value.negate());e.displacementMap&&(t.displacementMap.value=e.displacementMap,t.displacementScale.value=e.displacementScale,t.displacementBias.value=e.displacementBias)}(t,i)):i.isMeshStandardMaterial?(e(t,i),i.isMeshPhysicalMaterial?function(t,e,i){n(t,e),t.ior.value=e.ior,e.sheenTint&&t.sheenTint.value.copy(e.sheenTint);e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap),e.clearcoatNormalMap&&(t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),t.clearcoatNormalMap.value=e.clearcoatNormalMap,1===e.side&&t.clearcoatNormalScale.value.negate()));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=i.texture,t.transmissionSamplerSize.value.set(i.width,i.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap),t.attenuationDistance.value=e.attenuationDistance,t.attenuationTint.value.copy(e.attenuationTint));t.specularIntensity.value=e.specularIntensity,t.specularTint.value.copy(e.specularTint),e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap);e.specularTintMap&&(t.specularTintMap.value=e.specularTintMap)}(t,i,a):n(t,i)):i.isMeshMatcapMaterial?(e(t,i),function(t,e){e.matcap&&(t.matcap.value=e.matcap);e.bumpMap&&(t.bumpMap.value=e.bumpMap,t.bumpScale.value=e.bumpScale,1===e.side&&(t.bumpScale.value*=-1));e.normalMap&&(t.normalMap.value=e.normalMap,t.normalScale.value.copy(e.normalScale),1===e.side&&t.normalScale.value.negate());e.displacementMap&&(t.displacementMap.value=e.displacementMap,t.displacementScale.value=e.displacementScale,t.displacementBias.value=e.displacementBias)}(t,i)):i.isMeshDepthMaterial?(e(t,i),function(t,e){e.displacementMap&&(t.displacementMap.value=e.displacementMap,t.displacementScale.value=e.displacementScale,t.displacementBias.value=e.displacementBias)}(t,i)):i.isMeshDistanceMaterial?(e(t,i),function(t,e){e.displacementMap&&(t.displacementMap.value=e.displacementMap,t.displacementScale.value=e.displacementScale,t.displacementBias.value=e.displacementBias);t.referencePosition.value.copy(e.referencePosition),t.nearDistance.value=e.nearDistance,t.farDistance.value=e.farDistance}(t,i)):i.isMeshNormalMaterial?(e(t,i),function(t,e){e.bumpMap&&(t.bumpMap.value=e.bumpMap,t.bumpScale.value=e.bumpScale,1===e.side&&(t.bumpScale.value*=-1));e.normalMap&&(t.normalMap.value=e.normalMap,t.normalScale.value.copy(e.normalScale),1===e.side&&t.normalScale.value.negate());e.displacementMap&&(t.displacementMap.value=e.displacementMap,t.displacementScale.value=e.displacementScale,t.displacementBias.value=e.displacementBias)}(t,i)):i.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity}(t,i),i.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,i)):i.isPointsMaterial?function(t,e,n,i){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*n,t.scale.value=.5*i,e.map&&(t.map.value=e.map);e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest);let r;e.map?r=e.map:e.alphaMap&&(r=e.alphaMap);void 0!==r&&(!0===r.matrixAutoUpdate&&r.updateMatrix(),t.uvTransform.value.copy(r.matrix))}(t,i,r,s):i.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map);e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest);let n;e.map?n=e.map:e.alphaMap&&(n=e.alphaMap);void 0!==n&&(!0===n.matrixAutoUpdate&&n.updateMatrix(),t.uvTransform.value.copy(n.matrix))}(t,i):i.isShadowMaterial?(t.color.value.copy(i.color),t.opacity.value=i.opacity):i.isShaderMaterial&&(i.uniformsNeedUpdate=!1)}}}function Xs(t={}){const e=void 0!==t.canvas?t.canvas:function(){const t=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");return t.style.display="block",t}(),n=void 0!==t.context?t.context:null,i=void 0!==t.alpha&&t.alpha,r=void 0===t.depth||t.depth,s=void 0===t.stencil||t.stencil,a=void 0!==t.antialias&&t.antialias,o=void 0===t.premultipliedAlpha||t.premultipliedAlpha,l=void 0!==t.preserveDrawingBuffer&&t.preserveDrawingBuffer,c=void 0!==t.powerPreference?t.powerPreference:"default",h=void 0!==t.failIfMajorPerformanceCaveat&&t.failIfMajorPerformanceCaveat;let d=null,m=null;const f=[],g=[];this.domElement=e,this.debug={checkShaderErrors:!0},this.autoClear=!0,this.autoClearColor=!0,this.autoClearDepth=!0,this.autoClearStencil=!0,this.sortObjects=!0,this.clippingPlanes=[],this.localClippingEnabled=!1,this.gammaFactor=2,this.outputEncoding=X,this.physicallyCorrectLights=!1,this.toneMapping=0,this.toneMappingExposure=1;const v=this;let _=!1,b=0,S=0,T=null,A=-1,L=null;const R=new St,C=new St;let P=null,I=e.width,D=e.height,N=1,z=null,B=null;const F=new St(0,0,I,D),O=new St(0,0,I,D);let U=!1;const H=[],G=new ai;let k=!1,V=!1,W=null;const j=new ae,q=new Rt,Y={background:null,fog:null,environment:null,overrideMaterial:null,isScene:!0};function J(){return null===T?N:1}let Z,Q,K,$,tt,et,nt,it,rt,st,at,ot,lt,ct,ht,ut,dt,pt,mt,ft,gt,vt,yt,xt=n;function _t(t,n){for(let i=0;i0&&function(t,e,n){if(null===W){const t=!0===a&&!0===Q.isWebGL2;W=new(t?At:Tt)(1024,1024,{generateMipmaps:!0,type:null!==vt.convert(w)?w:x,minFilter:y,magFilter:p,wrapS:u,wrapT:u})}const i=v.getRenderTarget();v.setRenderTarget(W),v.clear();const r=v.toneMapping;v.toneMapping=0,Bt(t,e,n),v.toneMapping=r,et.updateMultisampleRenderTarget(W),et.updateRenderTargetMipmap(W),v.setRenderTarget(i)}(r,e,n),i&&K.viewport(R.copy(i)),r.length>0&&Bt(r,e,n),s.length>0&&Bt(s,e,n),o.length>0&&Bt(o,e,n)}function Bt(t,e,n){const i=!0===e.isScene?e.overrideMaterial:null;for(let r=0,s=t.length;r0?g[g.length-1]:null,f.pop(),d=f.length>0?f[f.length-1]:null},this.getActiveCubeFace=function(){return b},this.getActiveMipmapLevel=function(){return S},this.getRenderTarget=function(){return T},this.setRenderTarget=function(t,e=0,n=0){T=t,b=e,S=n,t&&void 0===tt.get(t).__webglFramebuffer&&et.setupRenderTarget(t);let i=null,r=!1,s=!1;if(t){const n=t.texture;(n.isDataTexture3D||n.isDataTexture2DArray)&&(s=!0);const a=tt.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(i=a[e],r=!0):i=t.isWebGLMultisampleRenderTarget?tt.get(t).__webglMultisampledFramebuffer:a,R.copy(t.viewport),C.copy(t.scissor),P=t.scissorTest}else R.copy(F).multiplyScalar(N).floor(),C.copy(O).multiplyScalar(N).floor(),P=U;if(K.bindFramebuffer(36160,i)&&Q.drawBuffers){let e=!1;if(t)if(t.isWebGLMultipleRenderTargets){const n=t.texture;if(H.length!==n.length||36064!==H[0]){for(let t=0,e=n.length;t=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&xt.readPixels(e,n,i,r,vt.convert(o),vt.convert(l),s):console.error("THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.")}finally{const t=null!==T?tt.get(T).__webglFramebuffer:null;K.bindFramebuffer(36160,t)}}},this.copyFramebufferToTexture=function(t,e,n=0){const i=Math.pow(2,-n),r=Math.floor(e.image.width*i),s=Math.floor(e.image.height*i);let a=vt.convert(e.format);Q.isWebGL2&&(6407===a&&(a=32849),6408===a&&(a=32856)),et.setTexture2D(e,0),xt.copyTexImage2D(3553,n,a,t.x,t.y,r,s,0),K.unbindTexture()},this.copyTextureToTexture=function(t,e,n,i=0){const r=e.image.width,s=e.image.height,a=vt.convert(n.format),o=vt.convert(n.type);et.setTexture2D(n,0),xt.pixelStorei(37440,n.flipY),xt.pixelStorei(37441,n.premultiplyAlpha),xt.pixelStorei(3317,n.unpackAlignment),e.isDataTexture?xt.texSubImage2D(3553,i,t.x,t.y,r,s,a,o,e.image.data):e.isCompressedTexture?xt.compressedTexSubImage2D(3553,i,t.x,t.y,e.mipmaps[0].width,e.mipmaps[0].height,a,e.mipmaps[0].data):xt.texSubImage2D(3553,i,t.x,t.y,a,o,e.image),0===i&&n.generateMipmaps&&xt.generateMipmap(3553),K.unbindTexture()},this.copyTextureToTexture3D=function(t,e,n,i,r=0){if(v.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const s=t.max.x-t.min.x+1,a=t.max.y-t.min.y+1,o=t.max.z-t.min.z+1,l=vt.convert(i.format),c=vt.convert(i.type);let h;if(i.isDataTexture3D)et.setTexture3D(i,0),h=32879;else{if(!i.isDataTexture2DArray)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");et.setTexture2DArray(i,0),h=35866}xt.pixelStorei(37440,i.flipY),xt.pixelStorei(37441,i.premultiplyAlpha),xt.pixelStorei(3317,i.unpackAlignment);const u=xt.getParameter(3314),d=xt.getParameter(32878),p=xt.getParameter(3316),m=xt.getParameter(3315),f=xt.getParameter(32877),g=n.isCompressedTexture?n.mipmaps[0]:n.image;xt.pixelStorei(3314,g.width),xt.pixelStorei(32878,g.height),xt.pixelStorei(3316,t.min.x),xt.pixelStorei(3315,t.min.y),xt.pixelStorei(32877,t.min.z),n.isDataTexture||n.isDataTexture3D?xt.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g.data):n.isCompressedTexture?(console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture."),xt.compressedTexSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,g.data)):xt.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g),xt.pixelStorei(3314,u),xt.pixelStorei(32878,d),xt.pixelStorei(3316,p),xt.pixelStorei(3315,m),xt.pixelStorei(32877,f),0===r&&i.generateMipmaps&&xt.generateMipmap(h),K.unbindTexture()},this.initTexture=function(t){et.setTexture2D(t,0),K.unbindTexture()},this.resetState=function(){b=0,S=0,T=null,K.reset(),yt.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}class Ys extends Xs{}Ys.prototype.isWebGL1Renderer=!0;class Js{constructor(t,e=25e-5){this.name="",this.color=new Qe(t),this.density=e}clone(){return new Js(this.color,this.density)}toJSON(){return{type:"FogExp2",color:this.color.getHex(),density:this.density}}}Js.prototype.isFogExp2=!0;class Zs{constructor(t,e=1,n=1e3){this.name="",this.color=new Qe(t),this.near=e,this.far=n}clone(){return new Zs(this.color,this.near,this.far)}toJSON(){return{type:"Fog",color:this.color.getHex(),near:this.near,far:this.far}}}Zs.prototype.isFog=!0;class Qs extends Pe{constructor(){super(),this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.overrideMaterial=null,this.autoUpdate=!0,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.autoUpdate=t.autoUpdate,this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),e}}Qs.prototype.isScene=!0;class Ks{constructor(t,e){this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=et,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=ct()}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,n){t*=this.stride,n*=e.stride;for(let i=0,r=this.stride;it.far||e.push({distance:o,point:ia.clone(),uv:ke.getUV(ia,ca,ha,ua,da,pa,ma,new vt),face:null,object:this})}copy(t){return super.copy(t),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function ga(t,e,n,i,r,s){aa.subVectors(t,n).addScalar(.5).multiply(i),void 0!==r?(oa.x=s*aa.x-r*aa.y,oa.y=r*aa.x+s*aa.y):oa.copy(aa),t.copy(e),t.x+=oa.x,t.y+=oa.y,t.applyMatrix4(la)}fa.prototype.isSprite=!0;const va=new Rt,ya=new Rt;class xa extends Pe{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,n=e.length;t0){let n,i;for(n=1,i=e.length;n0){va.setFromMatrixPosition(this.matrixWorld);const n=t.ray.origin.distanceTo(va);this.getObjectForDistance(n).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){va.setFromMatrixPosition(t.matrixWorld),ya.setFromMatrixPosition(this.matrixWorld);const n=va.distanceTo(ya)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=e[i].distance;i++)e[i-1].object.visible=!1,e[i].object.visible=!0;for(this._currentLevel=i-1;io)continue;u.applyMatrix4(this.matrixWorld);const d=t.ray.origin.distanceTo(u);dt.far||e.push({distance:d,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}else{for(let n=Math.max(0,s.start),i=Math.min(r.count,s.start+s.count)-1;no)continue;u.applyMatrix4(this.matrixWorld);const i=t.ray.origin.distanceTo(u);it.far||e.push({distance:i,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}}else n.isGeometry&&console.error("THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.")}updateMorphTargets(){const t=this.geometry;if(t.isBufferGeometry){const e=t.morphAttributes,n=Object.keys(e);if(n.length>0){const t=e[n[0]];if(void 0!==t){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let e=0,n=t.length;e0&&console.error("THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.")}}}Va.prototype.isLine=!0;const Wa=new Rt,ja=new Rt;class qa extends Va{constructor(t,e){super(t,e),this.type="LineSegments"}computeLineDistances(){const t=this.geometry;if(t.isBufferGeometry)if(null===t.index){const e=t.attributes.position,n=[];for(let t=0,i=e.count;t0){const t=e[n[0]];if(void 0!==t){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let e=0,n=t.length;e0&&console.error("THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.")}}}function to(t,e,n,i,r,s,a){const o=Za.distanceSqToPoint(t);if(or.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:n,index:e,face:null,object:a})}}$a.prototype.isPoints=!0;class eo extends Mt{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.format=void 0!==a?a:T,this.minFilter=void 0!==s?s:g,this.magFilter=void 0!==r?r:g,this.generateMipmaps=!1;const c=this;"requestVideoFrameCallback"in t&&t.requestVideoFrameCallback((function e(){c.needsUpdate=!0,t.requestVideoFrameCallback(e)}))}clone(){return new this.constructor(this.image).copy(this)}update(){const t=this.image;!1==="requestVideoFrameCallback"in t&&t.readyState>=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}}eo.prototype.isVideoTexture=!0;class no extends Mt{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.image={width:e,height:n},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}no.prototype.isCompressedTexture=!0;class io extends Mt{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.needsUpdate=!0}}io.prototype.isCanvasTexture=!0;class ro extends Mt{constructor(t,e,n,i,r,s,a,o,l,c){if((c=void 0!==c?c:A)!==A&&c!==L)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");void 0===n&&c===A&&(n=_),void 0===n&&c===L&&(n=S),super(null,i,r,s,a,o,c,n,l),this.image={width:t,height:e},this.magFilter=void 0!==a?a:p,this.minFilter=void 0!==o?o:p,this.flipY=!1,this.generateMipmaps=!1}}ro.prototype.isDepthTexture=!0;class so extends wn{constructor(t=1,e=8,n=0,i=2*Math.PI){super(),this.type="CircleGeometry",this.parameters={radius:t,segments:e,thetaStart:n,thetaLength:i},e=Math.max(3,e);const r=[],s=[],a=[],o=[],l=new Rt,c=new vt;s.push(0,0,0),a.push(0,0,1),o.push(.5,.5);for(let r=0,h=3;r<=e;r++,h+=3){const u=n+r/e*i;l.x=t*Math.cos(u),l.y=t*Math.sin(u),s.push(l.x,l.y,l.z),a.push(0,0,1),c.x=(s[h]/t+1)/2,c.y=(s[h+1]/t+1)/2,o.push(c.x,c.y)}for(let t=1;t<=e;t++)r.push(t,t+1,0);this.setIndex(r),this.setAttribute("position",new un(s,3)),this.setAttribute("normal",new un(a,3)),this.setAttribute("uv",new un(o,2))}static fromJSON(t){return new so(t.radius,t.segments,t.thetaStart,t.thetaLength)}}class ao extends wn{constructor(t=1,e=1,n=1,i=8,r=1,s=!1,a=0,o=2*Math.PI){super(),this.type="CylinderGeometry",this.parameters={radiusTop:t,radiusBottom:e,height:n,radialSegments:i,heightSegments:r,openEnded:s,thetaStart:a,thetaLength:o};const l=this;i=Math.floor(i),r=Math.floor(r);const c=[],h=[],u=[],d=[];let p=0;const m=[],f=n/2;let g=0;function v(n){const r=p,s=new vt,m=new Rt;let v=0;const y=!0===n?t:e,x=!0===n?1:-1;for(let t=1;t<=i;t++)h.push(0,f*x,0),u.push(0,x,0),d.push(.5,.5),p++;const _=p;for(let t=0;t<=i;t++){const e=t/i*o+a,n=Math.cos(e),r=Math.sin(e);m.x=y*r,m.y=f*x,m.z=y*n,h.push(m.x,m.y,m.z),u.push(0,x,0),s.x=.5*n+.5,s.y=.5*r*x+.5,d.push(s.x,s.y),p++}for(let t=0;t0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new un(h,3)),this.setAttribute("normal",new un(u,3)),this.setAttribute("uv",new un(d,2))}static fromJSON(t){return new ao(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class oo extends ao{constructor(t=1,e=1,n=8,i=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,n,i,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:s,thetaLength:a}}static fromJSON(t){return new oo(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class lo extends wn{constructor(t,e,n=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:n,detail:i};const r=[],s=[];function a(t,e,n,i){const r=i+1,s=[];for(let i=0;i<=r;i++){s[i]=[];const a=t.clone().lerp(n,i/r),o=e.clone().lerp(n,i/r),l=r-i;for(let t=0;t<=l;t++)s[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),n<.2&&(s[t+2]+=1),i<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new un(r,3)),this.setAttribute("normal",new un(r.slice(),3)),this.setAttribute("uv",new un(s,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}static fromJSON(t){return new lo(t.vertices,t.indices,t.radius,t.details)}}class co extends lo{constructor(t=1,e=0){const n=(1+Math.sqrt(5))/2,i=1/n;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-i,-n,0,-i,n,0,i,-n,0,i,n,-i,-n,0,-i,n,0,i,-n,0,i,n,0,-n,0,-i,n,0,-i,-n,0,i,n,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new co(t.radius,t.detail)}}const ho=new Rt,uo=new Rt,po=new Rt,mo=new ke;class fo extends wn{constructor(t,e){if(super(),this.type="EdgesGeometry",this.parameters={thresholdAngle:e},e=void 0!==e?e:1,!0===t.isGeometry)return void console.error("THREE.EdgesGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.");const n=Math.pow(10,4),i=Math.cos(ot*e),r=t.getIndex(),s=t.getAttribute("position"),a=r?r.count:s.count,o=[0,0,0],l=["a","b","c"],c=new Array(3),h={},u=[];for(let t=0;t0)){l=i;break}l=i-1}if(i=l,n[i]===s)return i/(r-1);const c=n[i];return(i+(s-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const s=this.getPoint(i),a=this.getPoint(r),o=e||(s.isVector2?new vt:new Rt);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e){const n=new Rt,i=[],r=[],s=[],a=new Rt,o=new ae;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new Rt),i[e].normalize()}r[0]=new Rt,s[0]=new Rt;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),h=Math.abs(i[0].y),u=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),h<=l&&(l=h,n.set(0,1,0)),u<=l&&n.set(0,0,1),a.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],a),s[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(ht(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(ht(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(o.makeRotationAxis(i[n],e*n)),s[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.5,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class vo extends go{constructor(t=0,e=0,n=1,i=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e){const n=e||new vt,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=i[(l-1)%r]:(_o.subVectors(i[0],i[1]).add(i[0]),a=_o);const h=i[l%r],u=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:s+1],h=i[s>i.length-3?i.length-1:s+2];return n.set(To(a,o.x,l.x,c.x,h.x),To(a,o.y,l.y,c.y,h.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e80*n){o=c=t[0],l=h=t[1];for(let e=n;ec&&(c=u),d>h&&(h=d);p=Math.max(c-o,h-l),p=0!==p?1/p:0}return Uo(s,a,n,o,l,p),a};function Fo(t,e,n,i,r){let s,a;if(r===function(t,e,n,i){let r=0;for(let s=e,a=n-i;s0)for(s=e;s=e;s-=i)a=rl(s,t[s],t[s+1],a);return a&&Ko(a,a.next)&&(sl(a),a=a.next),a}function Oo(t,e){if(!t)return t;e||(e=t);let n,i=t;do{if(n=!1,i.steiner||!Ko(i,i.next)&&0!==Qo(i.prev,i,i.next))i=i.next;else{if(sl(i),i=e=i.prev,i===i.next)break;n=!0}}while(n||i!==e);return e}function Uo(t,e,n,i,r,s,a){if(!t)return;!a&&s&&function(t,e,n,i){let r=t;do{null===r.z&&(r.z=Xo(r.x,r.y,e,n,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,n,i,r,s,a,o,l,c=1;do{for(n=t,t=null,s=null,a=0;n;){for(a++,i=n,o=0,e=0;e0||l>0&&i;)0!==o&&(0===l||!i||n.z<=i.z)?(r=n,n=n.nextZ,o--):(r=i,i=i.nextZ,l--),s?s.nextZ=r:t=r,r.prevZ=s,s=r;n=i}s.nextZ=null,c*=2}while(a>1)}(r)}(t,i,r,s);let o,l,c=t;for(;t.prev!==t.next;)if(o=t.prev,l=t.next,s?Go(t,i,r,s):Ho(t))e.push(o.i/n),e.push(t.i/n),e.push(l.i/n),sl(t),t=l.next,c=l.next;else if((t=l)===c){a?1===a?Uo(t=ko(Oo(t),e,n),e,n,i,r,s,2):2===a&&Vo(t,e,n,i,r,s):Uo(Oo(t),e,n,i,r,s,1);break}}function Ho(t){const e=t.prev,n=t,i=t.next;if(Qo(e,n,i)>=0)return!1;let r=t.next.next;for(;r!==t.prev;){if(Jo(e.x,e.y,n.x,n.y,i.x,i.y,r.x,r.y)&&Qo(r.prev,r,r.next)>=0)return!1;r=r.next}return!0}function Go(t,e,n,i){const r=t.prev,s=t,a=t.next;if(Qo(r,s,a)>=0)return!1;const o=r.xs.x?r.x>a.x?r.x:a.x:s.x>a.x?s.x:a.x,h=r.y>s.y?r.y>a.y?r.y:a.y:s.y>a.y?s.y:a.y,u=Xo(o,l,e,n,i),d=Xo(c,h,e,n,i);let p=t.prevZ,m=t.nextZ;for(;p&&p.z>=u&&m&&m.z<=d;){if(p!==t.prev&&p!==t.next&&Jo(r.x,r.y,s.x,s.y,a.x,a.y,p.x,p.y)&&Qo(p.prev,p,p.next)>=0)return!1;if(p=p.prevZ,m!==t.prev&&m!==t.next&&Jo(r.x,r.y,s.x,s.y,a.x,a.y,m.x,m.y)&&Qo(m.prev,m,m.next)>=0)return!1;m=m.nextZ}for(;p&&p.z>=u;){if(p!==t.prev&&p!==t.next&&Jo(r.x,r.y,s.x,s.y,a.x,a.y,p.x,p.y)&&Qo(p.prev,p,p.next)>=0)return!1;p=p.prevZ}for(;m&&m.z<=d;){if(m!==t.prev&&m!==t.next&&Jo(r.x,r.y,s.x,s.y,a.x,a.y,m.x,m.y)&&Qo(m.prev,m,m.next)>=0)return!1;m=m.nextZ}return!0}function ko(t,e,n){let i=t;do{const r=i.prev,s=i.next.next;!Ko(r,s)&&$o(r,i,i.next,s)&&nl(r,s)&&nl(s,r)&&(e.push(r.i/n),e.push(i.i/n),e.push(s.i/n),sl(i),sl(i.next),i=t=s),i=i.next}while(i!==t);return Oo(i)}function Vo(t,e,n,i,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&Zo(a,t)){let o=il(a,t);return a=Oo(a,a.next),o=Oo(o,o.next),Uo(a,e,n,i,r,s),void Uo(o,e,n,i,r,s)}t=t.next}a=a.next}while(a!==t)}function Wo(t,e){return t.x-e.x}function jo(t,e){if(e=function(t,e){let n=e;const i=t.x,r=t.y;let s,a=-1/0;do{if(r<=n.y&&r>=n.next.y&&n.next.y!==n.y){const t=n.x+(r-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(t<=i&&t>a){if(a=t,t===i){if(r===n.y)return n;if(r===n.next.y)return n.next}s=n.x=n.x&&n.x>=l&&i!==n.x&&Jo(rs.x||n.x===s.x&&qo(s,n)))&&(s=n,u=h)),n=n.next}while(n!==o);return s}(t,e)){const n=il(e,t);Oo(e,e.next),Oo(n,n.next)}}function qo(t,e){return Qo(t.prev,t,e.prev)<0&&Qo(e.next,t,t.next)<0}function Xo(t,e,n,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=32767*(t-n)*r)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=32767*(e-i)*r)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function Yo(t){let e=t,n=t;do{(e.x=0&&(t-a)*(i-o)-(n-a)*(e-o)>=0&&(n-a)*(s-o)-(r-a)*(i-o)>=0}function Zo(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&$o(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(nl(t,e)&&nl(e,t)&&function(t,e){let n=t,i=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{n.y>s!=n.next.y>s&&n.next.y!==n.y&&r<(n.next.x-n.x)*(s-n.y)/(n.next.y-n.y)+n.x&&(i=!i),n=n.next}while(n!==t);return i}(t,e)&&(Qo(t.prev,t,e.prev)||Qo(t,e.prev,e))||Ko(t,e)&&Qo(t.prev,t,t.next)>0&&Qo(e.prev,e,e.next)>0)}function Qo(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function Ko(t,e){return t.x===e.x&&t.y===e.y}function $o(t,e,n,i){const r=el(Qo(t,e,n)),s=el(Qo(t,e,i)),a=el(Qo(n,i,t)),o=el(Qo(n,i,e));return r!==s&&a!==o||(!(0!==r||!tl(t,n,e))||(!(0!==s||!tl(t,i,e))||(!(0!==a||!tl(n,t,i))||!(0!==o||!tl(n,e,i)))))}function tl(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function el(t){return t>0?1:t<0?-1:0}function nl(t,e){return Qo(t.prev,t,t.next)<0?Qo(t,e,t.next)>=0&&Qo(t,t.prev,e)>=0:Qo(t,e,t.prev)<0||Qo(t,t.next,e)<0}function il(t,e){const n=new al(t.i,t.x,t.y),i=new al(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,n.next=r,r.prev=n,i.next=n,n.prev=i,s.next=i,i.prev=s,i}function rl(t,e,n,i){const r=new al(t,e,n);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function sl(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function al(t,e,n){this.i=t,this.x=e,this.y=n,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}class ol{static area(t){const e=t.length;let n=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function cl(t,e){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,m=e.y+a/u,f=((n.x-c/d-p)*c-(n.y+l/d-m)*l)/(a*c-o*l);i=p+a*f-t.x,r=m+o*f-t.y;const g=i*i+r*r;if(g<=2)return new vt(i,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(i=-o,r=a,s=Math.sqrt(h)):(i=a,r=o,s=Math.sqrt(h/2))}return new vt(i/s,r/s)}const P=[];for(let t=0,e=E.length,n=e-1,i=t+1;t=0;t--){const e=t/p,n=h*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=E.length;t=0;){const i=n;let r=n-1;r<0&&(r=t.length-1);for(let t=0,n=o+2*p;t=0?(t(i-o,p,h),u.subVectors(c,h)):(t(i+o,p,h),u.subVectors(h,c)),p-o>=0?(t(i,p-o,h),d.subVectors(c,h)):(t(i,p+o,h),d.subVectors(h,c)),l.crossVectors(u,d).normalize(),s.push(l.x,l.y,l.z),a.push(i,p)}}for(let t=0;t0)&&d.push(e,r,l),(t!==n-1||o0!=t>0&&this.version++,this._clearcoat=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.ior=t.ior,this.sheenTint.copy(t.sheenTint),this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationTint.copy(t.attenuationTint),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularTint.copy(t.specularTint),this.specularTintMap=t.specularTintMap,this}}Rl.prototype.isMeshPhysicalMaterial=!0;class Cl extends We{constructor(t){super(),this.type="MeshPhongMaterial",this.color=new Qe(16777215),this.specular=new Qe(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Qe(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new vt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this}}Cl.prototype.isMeshPhongMaterial=!0;class Pl extends We{constructor(t){super(),this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new Qe(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Qe(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new vt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this}}Pl.prototype.isMeshToonMaterial=!0;class Il extends We{constructor(t){super(),this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new vt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.fog=!1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}Il.prototype.isMeshNormalMaterial=!0;class Dl extends We{constructor(t){super(),this.type="MeshLambertMaterial",this.color=new Qe(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Qe(0),this.emissiveIntensity=1,this.emissiveMap=null,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this}}Dl.prototype.isMeshLambertMaterial=!0;class Nl extends We{constructor(t){super(),this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new Qe(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new vt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this}}Nl.prototype.isMeshMatcapMaterial=!0;class zl extends Fa{constructor(t){super(),this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}zl.prototype.isLineDashedMaterial=!0;var Bl=Object.freeze({__proto__:null,ShadowMaterial:Al,SpriteMaterial:ea,RawShaderMaterial:_i,ShaderMaterial:Xn,PointsMaterial:Ya,MeshPhysicalMaterial:Rl,MeshStandardMaterial:Ll,MeshPhongMaterial:Cl,MeshToonMaterial:Pl,MeshNormalMaterial:Il,MeshLambertMaterial:Dl,MeshDepthMaterial:zs,MeshDistanceMaterial:Bs,MeshBasicMaterial:Ke,MeshMatcapMaterial:Nl,LineDashedMaterial:zl,LineBasicMaterial:Fa,Material:We});const Fl={arraySlice:function(t,e,n){return Fl.isTypedArray(t)?new t.constructor(t.subarray(e,void 0!==n?n:t.length)):t.slice(e,n)},convertArray:function(t,e,n){return!t||!n&&t.constructor===e?t:"number"==typeof e.BYTES_PER_ELEMENT?new e(t):Array.prototype.slice.call(t)},isTypedArray:function(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)},getKeyframeOrder:function(t){const e=t.length,n=new Array(e);for(let t=0;t!==e;++t)n[t]=t;return n.sort((function(e,n){return t[e]-t[n]})),n},sortedArray:function(t,e,n){const i=t.length,r=new t.constructor(i);for(let s=0,a=0;a!==i;++s){const i=n[s]*e;for(let n=0;n!==e;++n)r[a++]=t[i+n]}return r},flattenJSON:function(t,e,n,i){let r=1,s=t[0];for(;void 0!==s&&void 0===s[i];)s=t[r++];if(void 0===s)return;let a=s[i];if(void 0!==a)if(Array.isArray(a))do{a=s[i],void 0!==a&&(e.push(s.time),n.push.apply(n,a)),s=t[r++]}while(void 0!==s);else if(void 0!==a.toArray)do{a=s[i],void 0!==a&&(e.push(s.time),a.toArray(n,n.length)),s=t[r++]}while(void 0!==s);else do{a=s[i],void 0!==a&&(e.push(s.time),n.push(a)),s=t[r++]}while(void 0!==s)},subclip:function(t,e,n,i,r=30){const s=t.clone();s.name=e;const a=[];for(let t=0;t=i)){l.push(e.times[t]);for(let n=0;ns.tracks[t].times[0]&&(o=s.tracks[t].times[0]);for(let t=0;t=i.times[u]){const t=u*l+o,e=t+l-o;d=Fl.arraySlice(i.values,t,e)}else{const t=i.createInterpolant(),e=o,n=l-o;t.evaluate(s),d=Fl.arraySlice(t.resultBuffer,e,n)}if("quaternion"===r){(new Lt).fromArray(d).normalize().conjugate().toArray(d)}const p=a.times.length;for(let t=0;t=r)break t;{const a=e[1];t=r)break e}s=n,n=0}}for(;n>>1;te;)--s;if(++s,0!==r||s!==i){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=Fl.arraySlice(n,r,s),this.values=Fl.arraySlice(this.values,r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const n=this.times,i=this.values,r=n.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const i=n[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==s&&s>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,s),t=!1;break}s=i}if(void 0!==i&&Fl.isTypedArray(i))for(let e=0,n=i.length;e!==n;++e){const n=i[e];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,n),t=!1;break}}return t}optimize(){const t=Fl.arraySlice(this.times),e=Fl.arraySlice(this.values),n=this.getValueSize(),i=this.getInterpolation()===G,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*n,i=s*n,a=0;a!==n;++a)e[i+a]=e[t+a];++s}return s!==t.length?(this.times=Fl.arraySlice(t,0,s),this.values=Fl.arraySlice(e,0,s*n)):(this.times=t,this.values=e),this}clone(){const t=Fl.arraySlice(this.times,0),e=Fl.arraySlice(this.values,0),n=new(0,this.constructor)(this.name,t,e);return n.createInterpolant=this.createInterpolant,n}}kl.prototype.TimeBufferType=Float32Array,kl.prototype.ValueBufferType=Float32Array,kl.prototype.DefaultInterpolation=H;class Vl extends kl{}Vl.prototype.ValueTypeName="bool",Vl.prototype.ValueBufferType=Array,Vl.prototype.DefaultInterpolation=U,Vl.prototype.InterpolantFactoryMethodLinear=void 0,Vl.prototype.InterpolantFactoryMethodSmooth=void 0;class Wl extends kl{}Wl.prototype.ValueTypeName="color";class jl extends kl{}jl.prototype.ValueTypeName="number";class ql extends Ol{constructor(t,e,n,i){super(t,e,n,i)}interpolate_(t,e,n,i){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(n-e)/(i-e);let l=t*a;for(let t=l+a;l!==t;l+=4)Lt.slerpFlat(r,0,s,l-a,s,l,o);return r}}class Xl extends kl{InterpolantFactoryMethodLinear(t){return new ql(this.times,this.values,this.getValueSize(),t)}}Xl.prototype.ValueTypeName="quaternion",Xl.prototype.DefaultInterpolation=H,Xl.prototype.InterpolantFactoryMethodSmooth=void 0;class Yl extends kl{}Yl.prototype.ValueTypeName="string",Yl.prototype.ValueBufferType=Array,Yl.prototype.DefaultInterpolation=U,Yl.prototype.InterpolantFactoryMethodLinear=void 0,Yl.prototype.InterpolantFactoryMethodSmooth=void 0;class Jl extends kl{}Jl.prototype.ValueTypeName="vector";class Zl{constructor(t,e=-1,n,i=2500){this.name=t,this.tracks=n,this.duration=e,this.blendMode=i,this.uuid=ct(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],n=t.tracks,i=1/(t.fps||1);for(let t=0,r=n.length;t!==r;++t)e.push(Ql(n[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],n=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,i=n.length;t!==i;++t)e.push(kl.toJSON(n[t]));return i}static CreateFromMorphTargetSequence(t,e,n,i){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=i[t];e||(i[t]=e=[]),e.push(n)}}const s=[];for(const t in i)s.push(this.CreateFromMorphTargetSequence(t,i[t],e,n));return s}static parseAnimation(t,e){if(!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(t,e,n,i,r){if(0!==n.length){const s=[],a=[];Fl.flattenJSON(n,s,a,i),0!==s.length&&r.push(new t(e,s,a))}},i=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t0||0===t.search(/^data\:image\/jpeg/);r.format=i?T:E,r.needsUpdate=!0,void 0!==e&&e(r)}),n,i),r}}class lc extends go{constructor(){super(),this.type="CurvePath",this.curves=[],this.autoClose=!1}add(t){this.curves.push(t)}closePath(){const t=this.curves[0].getPoint(0),e=this.curves[this.curves.length-1].getPoint(1);t.equals(e)||this.curves.push(new Co(e,t))}getPoint(t){const e=t*this.getLength(),n=this.getCurveLengths();let i=0;for(;i=e){const t=n[i]-e,r=this.curves[i],s=r.getLength(),a=0===s?0:1-t/s;return r.getPointAt(a)}i++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let n=0,i=this.curves.length;n1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,n=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class hc extends cc{constructor(t){super(t),this.uuid=ct(),this.type="Shape",this.holes=[]}getPointsHoles(t){const e=[];for(let n=0,i=this.holes.length;n0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=n(r.value);break;case"c":i.uniforms[e].value=(new Qe).setHex(r.value);break;case"v2":i.uniforms[e].value=(new vt).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new Rt).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new St).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new yt).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new ae).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.shading&&(i.flatShading=1===t.shading),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=n(t.map)),void 0!==t.matcap&&(i.matcap=n(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=n(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=n(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=n(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new vt).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=n(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=n(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=n(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=n(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=n(t.specularMap)),void 0!==t.specularIntensityMap&&(i.specularIntensityMap=n(t.specularIntensityMap)),void 0!==t.specularTintMap&&(i.specularTintMap=n(t.specularTintMap)),void 0!==t.envMap&&(i.envMap=n(t.envMap)),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=n(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=n(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=n(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=n(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=n(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=n(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new vt).fromArray(t.clearcoatNormalScale)),void 0!==t.transmissionMap&&(i.transmissionMap=n(t.transmissionMap)),void 0!==t.thicknessMap&&(i.thicknessMap=n(t.thicknessMap)),i}setTextures(t){return this.textures=t,this}}class Pc{static decodeText(t){if("undefined"!=typeof TextDecoder)return(new TextDecoder).decode(t);let e="";for(let n=0,i=t.length;nNumber.EPSILON){if(l<0&&(n=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===n.y){if(t.x===n.x)return!0}else{const e=l*(t.x-n.x)-o*(t.y-n.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==n.y)continue;if(a.x<=t.x&&t.x<=n.x||n.x<=t.x&&t.x<=a.x)return!0}}return i}const r=ol.isClockWise,s=this.subPaths;if(0===s.length)return[];if(!0===e)return n(s);let a,o,l;const c=[];if(1===s.length)return o=s[0],l=new hc,l.curves=o.curves,c.push(l),c;let h=!r(s[0].getPoints());h=t?!h:h;const u=[],d=[];let p,m,f=[],g=0;d[g]=void 0,f[g]=[];for(let e=0,n=s.length;e1){let t=!1;const e=[];for(let t=0,e=d.length;t0&&(t||(f=u))}for(let t=0,e=d.length;t0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(n,i,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(n[t]!==n[t+e]){a.setValue(n,i);break}}saveOriginalState(){const t=this.binding,e=this.buffer,n=this.valueSize,i=n*this._origIndex;t.getValue(e,i);for(let t=n,r=i;t!==r;++t)e[t]=e[i+t%n];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let n=t;n=.5)for(let i=0;i!==r;++i)t[e+i]=t[n+i]}_slerp(t,e,n,i){Lt.slerpFlat(t,e,t,e,t,n,i)}_slerpAdditive(t,e,n,i,r){const s=this._workIndex*r;Lt.multiplyQuaternionsFlat(t,s,t,e,t,n),Lt.slerpFlat(t,e,t,e,t,s,i)}_lerp(t,e,n,i,r){const s=1-i;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*s+t[n+a]*i}}_lerpAdditive(t,e,n,i,r){for(let s=0;s!==r;++s){const r=e+s;t[r]=t[r]+t[n+s]*i}}}const oh="\\[\\]\\.:\\/",lh=new RegExp("[\\[\\]\\.:\\/]","g"),ch="[^\\[\\]\\.:\\/]",hh="[^"+oh.replace("\\.","")+"]",uh=/((?:WC+[\/:])*)/.source.replace("WC",ch),dh=/(WCOD+)?/.source.replace("WCOD",hh),ph=/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",ch),mh=/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",ch),fh=new RegExp("^"+uh+dh+ph+mh+"$"),gh=["material","materials","bones"];class vh{constructor(t,e,n){this.path=e,this.parsedPath=n||vh.parseTrackName(e),this.node=vh.findNode(t,this.parsedPath.nodeName)||t,this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,n){return t&&t.isAnimationObjectGroup?new vh.Composite(t,e,n):new vh(t,e,n)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(lh,"")}static parseTrackName(t){const e=fh.exec(t);if(!e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const n={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},i=n.nodeName&&n.nodeName.lastIndexOf(".");if(void 0!==i&&-1!==i){const t=n.nodeName.substring(i+1);-1!==gh.indexOf(t)&&(n.nodeName=n.nodeName.substring(0,i),n.objectName=t)}if(null===n.propertyName||0===n.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return n}static findNode(t,e){if(!e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const n=t.skeleton.getBoneByName(e);if(void 0!==n)return n}if(t.children){const n=function(t){for(let i=0;i=r){const s=r++,c=t[s];e[c.uuid]=l,t[l]=c,e[o]=s,t[s]=a;for(let t=0,e=i;t!==e;++t){const e=n[t],i=e[s],r=e[l];e[l]=i,e[s]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,n=this._bindings,i=n.length;let r=this.nCachedObjects_,s=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,l=e[o];if(void 0!==l)if(delete e[o],l0&&(e[a.uuid]=l),t[l]=a,t.pop();for(let t=0,e=i;t!==e;++t){const e=n[t];e[l]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const n=this._bindingsIndicesByPath;let i=n[t];const r=this._bindings;if(void 0!==i)return r[i];const s=this._paths,a=this._parsedPaths,o=this._objects,l=o.length,c=this.nCachedObjects_,h=new Array(l);i=r.length,n[t]=i,s.push(t),a.push(e),r.push(h);for(let n=c,i=o.length;n!==i;++n){const i=o[n];h[n]=new vh(i,t,e)}return h}unsubscribe_(t){const e=this._bindingsIndicesByPath,n=e[t];if(void 0!==n){const i=this._paths,r=this._parsedPaths,s=this._bindings,a=s.length-1,o=s[a];e[t[a]]=n,s[n]=o,s.pop(),r[n]=r[a],r.pop(),i[n]=i[a],i.pop()}}}yh.prototype.isAnimationObjectGroup=!0;class xh{constructor(t,e,n=null,i=e.blendMode){this._mixer=t,this._clip=e,this._localRoot=n,this.blendMode=i;const r=e.tracks,s=r.length,a=new Array(s),o={endingStart:k,endingEnd:k};for(let t=0;t!==s;++t){const e=r[t].createInterpolant(null);a[t]=e,e.settings=o}this._interpolantSettings=o,this._interpolants=a,this._propertyBindings=new Array(s),this._cacheIndex=null,this._byClipCacheIndex=null,this._timeScaleInterpolant=null,this._weightInterpolant=null,this.loop=2201,this._loopCount=-1,this._startTime=null,this.time=0,this.timeScale=1,this._effectiveTimeScale=1,this.weight=1,this._effectiveWeight=1,this.repetitions=1/0,this.paused=!1,this.enabled=!0,this.clampWhenFinished=!1,this.zeroSlopeAtStart=!0,this.zeroSlopeAtEnd=!0}play(){return this._mixer._activateAction(this),this}stop(){return this._mixer._deactivateAction(this),this.reset()}reset(){return this.paused=!1,this.enabled=!0,this.time=0,this._loopCount=-1,this._startTime=null,this.stopFading().stopWarping()}isRunning(){return this.enabled&&!this.paused&&0!==this.timeScale&&null===this._startTime&&this._mixer._isActiveAction(this)}isScheduled(){return this._mixer._isActiveAction(this)}startAt(t){return this._startTime=t,this}setLoop(t,e){return this.loop=t,this.repetitions=e,this}setEffectiveWeight(t){return this.weight=t,this._effectiveWeight=this.enabled?t:0,this.stopFading()}getEffectiveWeight(){return this._effectiveWeight}fadeIn(t){return this._scheduleFading(t,0,1)}fadeOut(t){return this._scheduleFading(t,1,0)}crossFadeFrom(t,e,n){if(t.fadeOut(e),this.fadeIn(e),n){const n=this._clip.duration,i=t._clip.duration,r=i/n,s=n/i;t.warp(1,r,e),this.warp(s,1,e)}return this}crossFadeTo(t,e,n){return t.crossFadeFrom(this,e,n)}stopFading(){const t=this._weightInterpolant;return null!==t&&(this._weightInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}setEffectiveTimeScale(t){return this.timeScale=t,this._effectiveTimeScale=this.paused?0:t,this.stopWarping()}getEffectiveTimeScale(){return this._effectiveTimeScale}setDuration(t){return this.timeScale=this._clip.duration/t,this.stopWarping()}syncWith(t){return this.time=t.time,this.timeScale=t.timeScale,this.stopWarping()}halt(t){return this.warp(this._effectiveTimeScale,0,t)}warp(t,e,n){const i=this._mixer,r=i.time,s=this.timeScale;let a=this._timeScaleInterpolant;null===a&&(a=i._lendControlInterpolant(),this._timeScaleInterpolant=a);const o=a.parameterPositions,l=a.sampleValues;return o[0]=r,o[1]=r+n,l[0]=t/s,l[1]=e/s,this}stopWarping(){const t=this._timeScaleInterpolant;return null!==t&&(this._timeScaleInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}getMixer(){return this._mixer}getClip(){return this._clip}getRoot(){return this._localRoot||this._mixer._root}_update(t,e,n,i){if(!this.enabled)return void this._updateWeight(t);const r=this._startTime;if(null!==r){const i=(t-r)*n;if(i<0||0===n)return;this._startTime=null,e=n*i}e*=this._updateTimeScale(t);const s=this._updateTime(e),a=this._updateWeight(t);if(a>0){const t=this._interpolants,e=this._propertyBindings;switch(this.blendMode){case q:for(let n=0,i=t.length;n!==i;++n)t[n].evaluate(s),e[n].accumulateAdditive(a);break;case j:default:for(let n=0,r=t.length;n!==r;++n)t[n].evaluate(s),e[n].accumulate(i,a)}}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const n=this._weightInterpolant;if(null!==n){const i=n.evaluate(t)[0];e*=i,t>n.parameterPositions[1]&&(this.stopFading(),0===i&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const n=this._timeScaleInterpolant;if(null!==n){e*=n.evaluate(t)[0],t>n.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,n=this.loop;let i=this.time+t,r=this._loopCount;const s=2202===n;if(0===t)return-1===r?i:s&&1==(1&r)?e-i:i;if(2200===n){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(i>=e)i=e;else{if(!(i<0)){this.time=i;break t}i=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,s)):this._setEndings(0===this.repetitions,!0,s)),i>=e||i<0){const n=Math.floor(i/e);i-=e*n,r+=Math.abs(n);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,i=t>0?e:0,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,s)}else this._setEndings(!1,!1,s);this._loopCount=r,this.time=i,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:n})}}else this.time=i;if(s&&1==(1&r))return e-i}return i}_setEndings(t,e,n){const i=this._interpolantSettings;n?(i.endingStart=V,i.endingEnd=V):(i.endingStart=t?this.zeroSlopeAtStart?V:k:W,i.endingEnd=e?this.zeroSlopeAtEnd?V:k:W)}_scheduleFading(t,e,n){const i=this._mixer,r=i.time;let s=this._weightInterpolant;null===s&&(s=i._lendControlInterpolant(),this._weightInterpolant=s);const a=s.parameterPositions,o=s.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=n,this}}class _h extends rt{constructor(t){super(),this._root=t,this._initMemoryManager(),this._accuIndex=0,this.time=0,this.timeScale=1}_bindAction(t,e){const n=t._localRoot||this._root,i=t._clip.tracks,r=i.length,s=t._propertyBindings,a=t._interpolants,o=n.uuid,l=this._bindingsByRootAndName;let c=l[o];void 0===c&&(c={},l[o]=c);for(let t=0;t!==r;++t){const r=i[t],l=r.name;let h=c[l];if(void 0!==h)s[t]=h;else{if(h=s[t],void 0!==h){null===h._cacheIndex&&(++h.referenceCount,this._addInactiveBinding(h,o,l));continue}const i=e&&e._propertyBindings[t].binding.parsedPath;h=new ah(vh.create(n,l,i),r.ValueTypeName,r.getValueSize()),++h.referenceCount,this._addInactiveBinding(h,o,l),s[t]=h}a[t].resultBuffer=h.buffer}}_activateAction(t){if(!this._isActiveAction(t)){if(null===t._cacheIndex){const e=(t._localRoot||this._root).uuid,n=t._clip.uuid,i=this._actionsByClip[n];this._bindAction(t,i&&i.knownActions[0]),this._addInactiveAction(t,n,e)}const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==n.useCount++&&(this._lendBinding(n),n.saveOriginalState())}this._lendAction(t)}}_deactivateAction(t){if(this._isActiveAction(t)){const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==--n.useCount&&(n.restoreOriginalState(),this._takeBackBinding(n))}this._takeBackAction(t)}}_initMemoryManager(){this._actions=[],this._nActiveActions=0,this._actionsByClip={},this._bindings=[],this._nActiveBindings=0,this._bindingsByRootAndName={},this._controlInterpolants=[],this._nActiveControlInterpolants=0;const t=this;this.stats={actions:{get total(){return t._actions.length},get inUse(){return t._nActiveActions}},bindings:{get total(){return t._bindings.length},get inUse(){return t._nActiveBindings}},controlInterpolants:{get total(){return t._controlInterpolants.length},get inUse(){return t._nActiveControlInterpolants}}}}_isActiveAction(t){const e=t._cacheIndex;return null!==e&&e=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,n=this._nActiveActions,i=this.time+=t,r=Math.sign(t),s=this._accuIndex^=1;for(let a=0;a!==n;++a){e[a]._update(i,t,r,s)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(s);return this}setTime(t){this.time=0;for(let t=0;tthis.max.x||t.ythis.max.y)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y)}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return Eh.copy(t).clamp(this.min,this.max).sub(t).length()}intersect(t){return this.min.max(t.min),this.max.min(t.max),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}Ah.prototype.isBox2=!0;const Lh=new Rt,Rh=new Rt;class Ch{constructor(t=new Rt,e=new Rt){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){Lh.subVectors(t,this.start),Rh.subVectors(this.end,this.start);const n=Rh.dot(Rh);let i=Rh.dot(Lh)/n;return e&&(i=ht(i,0,1)),i}closestPointToPoint(t,e,n){const i=this.closestPointToPointParameter(t,e);return this.delta(n).multiplyScalar(i).add(this.start)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}class Ph extends Pe{constructor(t){super(),this.material=t,this.render=function(){},this.hasPositions=!1,this.hasNormals=!1,this.hasColors=!1,this.hasUvs=!1,this.positionArray=null,this.normalArray=null,this.colorArray=null,this.uvArray=null,this.count=0}}Ph.prototype.isImmediateRenderObject=!0;const Ih=new Rt;const Dh=new Rt,Nh=new ae,zh=new ae;class Bh extends qa{constructor(t){const e=Fh(t),n=new wn,i=[],r=[],s=new Qe(0,0,1),a=new Qe(0,1,0);for(let t=0;t.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{Zh.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(Zh,e)}}setLength(t,e=.2*t,n=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(n,e,n),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}},t.Audio=th,t.AudioAnalyser=sh,t.AudioContext=kc,t.AudioListener=class extends Pe{constructor(){super(),this.type="AudioListener",this.context=kc.getContext(),this.gain=this.context.createGain(),this.gain.connect(this.context.destination),this.filter=null,this.timeDelta=0,this._clock=new Yc}getInput(){return this.gain}removeFilter(){return null!==this.filter&&(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination),this.gain.connect(this.context.destination),this.filter=null),this}getFilter(){return this.filter}setFilter(t){return null!==this.filter?(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination)):this.gain.disconnect(this.context.destination),this.filter=t,this.gain.connect(this.filter),this.filter.connect(this.context.destination),this}getMasterVolume(){return this.gain.gain.value}setMasterVolume(t){return this.gain.gain.setTargetAtTime(t,this.context.currentTime,.01),this}updateMatrixWorld(t){super.updateMatrixWorld(t);const e=this.context.listener,n=this.up;if(this.timeDelta=this._clock.getDelta(),this.matrixWorld.decompose(Zc,Qc,Kc),$c.set(0,0,-1).applyQuaternion(Qc),e.positionX){const t=this.context.currentTime+this.timeDelta;e.positionX.linearRampToValueAtTime(Zc.x,t),e.positionY.linearRampToValueAtTime(Zc.y,t),e.positionZ.linearRampToValueAtTime(Zc.z,t),e.forwardX.linearRampToValueAtTime($c.x,t),e.forwardY.linearRampToValueAtTime($c.y,t),e.forwardZ.linearRampToValueAtTime($c.z,t),e.upX.linearRampToValueAtTime(n.x,t),e.upY.linearRampToValueAtTime(n.y,t),e.upZ.linearRampToValueAtTime(n.z,t)}else e.setPosition(Zc.x,Zc.y,Zc.z),e.setOrientation($c.x,$c.y,$c.z,n.x,n.y,n.z)}},t.AudioLoader=Vc,t.AxesHelper=$h,t.AxisHelper=function(t){return console.warn("THREE.AxisHelper has been renamed to THREE.AxesHelper."),new $h(t)},t.BackSide=1,t.BasicDepthPacking=3200,t.BasicShadowMap=0,t.BinaryTextureLoader=function(t){return console.warn("THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader."),new ac(t)},t.Bone=Ea,t.BooleanKeyframeTrack=Vl,t.BoundingBoxHelper=function(t,e){return console.warn("THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead."),new Jh(t,e)},t.Box2=Ah,t.Box3=It,t.Box3Helper=class extends qa{constructor(t,e=16776960){const n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),i=new wn;i.setIndex(new en(n,1)),i.setAttribute("position",new un([1,1,1,-1,1,1,-1,-1,1,1,-1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1],3)),super(i,new Fa({color:e,toneMapped:!1})),this.box=t,this.type="Box3Helper",this.geometry.computeBoundingSphere()}updateMatrixWorld(t){const e=this.box;e.isEmpty()||(e.getCenter(this.position),e.getSize(this.scale),this.scale.multiplyScalar(.5),super.updateMatrixWorld(t))}},t.BoxBufferGeometry=Vn,t.BoxGeometry=Vn,t.BoxHelper=Jh,t.BufferAttribute=en,t.BufferGeometry=wn,t.BufferGeometryLoader=Dc,t.ByteType=1010,t.Cache=Kl,t.Camera=Yn,t.CameraHelper=class extends qa{constructor(t){const e=new wn,n=new Fa({color:16777215,vertexColors:!0,toneMapped:!1}),i=[],r=[],s={},a=new Qe(16755200),o=new Qe(16711680),l=new Qe(43775),c=new Qe(16777215),h=new Qe(3355443);function u(t,e,n){d(t,n),d(e,n)}function d(t,e){i.push(0,0,0),r.push(e.r,e.g,e.b),void 0===s[t]&&(s[t]=[]),s[t].push(i.length/3-1)}u("n1","n2",a),u("n2","n4",a),u("n4","n3",a),u("n3","n1",a),u("f1","f2",a),u("f2","f4",a),u("f4","f3",a),u("f3","f1",a),u("n1","f1",a),u("n2","f2",a),u("n3","f3",a),u("n4","f4",a),u("p","n1",o),u("p","n2",o),u("p","n3",o),u("p","n4",o),u("u1","u2",l),u("u2","u3",l),u("u3","u1",l),u("c","t",c),u("p","c",h),u("cn1","cn2",h),u("cn3","cn4",h),u("cf1","cf2",h),u("cf3","cf4",h),e.setAttribute("position",new un(i,3)),e.setAttribute("color",new un(r,3)),super(e,n),this.type="CameraHelper",this.camera=t,this.camera.updateProjectionMatrix&&this.camera.updateProjectionMatrix(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.pointMap=s,this.update()}update(){const t=this.geometry,e=this.pointMap;qh.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse),Xh("c",e,t,qh,0,0,-1),Xh("t",e,t,qh,0,0,1),Xh("n1",e,t,qh,-1,-1,-1),Xh("n2",e,t,qh,1,-1,-1),Xh("n3",e,t,qh,-1,1,-1),Xh("n4",e,t,qh,1,1,-1),Xh("f1",e,t,qh,-1,-1,1),Xh("f2",e,t,qh,1,-1,1),Xh("f3",e,t,qh,-1,1,1),Xh("f4",e,t,qh,1,1,1),Xh("u1",e,t,qh,.7,1.1,-1),Xh("u2",e,t,qh,-.7,1.1,-1),Xh("u3",e,t,qh,0,2,-1),Xh("cf1",e,t,qh,-1,0,1),Xh("cf2",e,t,qh,1,0,1),Xh("cf3",e,t,qh,0,-1,1),Xh("cf4",e,t,qh,0,1,1),Xh("cn1",e,t,qh,-1,0,-1),Xh("cn2",e,t,qh,1,0,-1),Xh("cn3",e,t,qh,0,-1,-1),Xh("cn4",e,t,qh,0,1,-1),t.getAttribute("position").needsUpdate=!0}dispose(){this.geometry.dispose(),this.material.dispose()}},t.CanvasRenderer=function(){console.error("THREE.CanvasRenderer has been removed")},t.CanvasTexture=io,t.CatmullRomCurve3=So,t.CineonToneMapping=3,t.CircleBufferGeometry=so,t.CircleGeometry=so,t.ClampToEdgeWrapping=u,t.Clock=Yc,t.Color=Qe,t.ColorKeyframeTrack=Wl,t.CompressedTexture=no,t.CompressedTextureLoader=class extends ec{constructor(t){super(t)}load(t,e,n,i){const r=this,s=[],a=new no,o=new ic(this.manager);o.setPath(this.path),o.setResponseType("arraybuffer"),o.setRequestHeader(this.requestHeader),o.setWithCredentials(r.withCredentials);let l=0;function c(c){o.load(t[c],(function(t){const n=r.parse(t,!0);s[c]={width:n.width,height:n.height,format:n.format,mipmaps:n.mipmaps},l+=1,6===l&&(1===n.mipmapCount&&(a.minFilter=g),a.image=s,a.format=n.format,a.needsUpdate=!0,e&&e(a))}),n,i)}if(Array.isArray(t))for(let e=0,n=t.length;e>16&32768,i=e>>12&2047;const r=e>>23&255;return r<103?n:r>142?(n|=31744,n|=(255==r?0:1)&&8388607&e,n):r<113?(i|=2048,n|=(i>>114-r)+(i>>113-r&1),n):(n|=r-112<<10|i>>1,n+=1&i,n)}},t.DecrementStencilOp=7683,t.DecrementWrapStencilOp=34056,t.DefaultLoadingManager=tc,t.DepthFormat=A,t.DepthStencilFormat=L,t.DepthTexture=ro,t.DirectionalLight=Tc,t.DirectionalLightHelper=class extends Pe{constructor(t,e,n){super(),this.light=t,this.light.updateMatrixWorld(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.color=n,void 0===e&&(e=1);let i=new wn;i.setAttribute("position",new un([-e,e,0,e,e,0,e,-e,0,-e,-e,0,-e,e,0],3));const r=new Fa({fog:!1,toneMapped:!1});this.lightPlane=new Va(i,r),this.add(this.lightPlane),i=new wn,i.setAttribute("position",new un([0,0,0,0,0,1],3)),this.targetLine=new Va(i,r),this.add(this.targetLine),this.update()}dispose(){this.lightPlane.geometry.dispose(),this.lightPlane.material.dispose(),this.targetLine.geometry.dispose(),this.targetLine.material.dispose()}update(){kh.setFromMatrixPosition(this.light.matrixWorld),Vh.setFromMatrixPosition(this.light.target.matrixWorld),Wh.subVectors(Vh,kh),this.lightPlane.lookAt(Vh),void 0!==this.color?(this.lightPlane.material.color.set(this.color),this.targetLine.material.color.set(this.color)):(this.lightPlane.material.color.copy(this.light.color),this.targetLine.material.color.copy(this.light.color)),this.targetLine.lookAt(Vh),this.targetLine.scale.z=Wh.length()}},t.DiscreteInterpolant=Gl,t.DodecahedronBufferGeometry=co,t.DodecahedronGeometry=co,t.DoubleSide=2,t.DstAlphaFactor=206,t.DstColorFactor=208,t.DynamicBufferAttribute=function(t,e){return console.warn("THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setUsage( THREE.DynamicDrawUsage ) instead."),new en(t,e).setUsage(nt)},t.DynamicCopyUsage=35050,t.DynamicDrawUsage=nt,t.DynamicReadUsage=35049,t.EdgesGeometry=fo,t.EdgesHelper=function(t,e){return console.warn("THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead."),new qa(new fo(t.geometry),new Fa({color:void 0!==e?e:16777215}))},t.EllipseCurve=vo,t.EqualDepth=4,t.EqualStencilFunc=514,t.EquirectangularReflectionMapping=a,t.EquirectangularRefractionMapping=o,t.Euler=ge,t.EventDispatcher=rt,t.ExtrudeBufferGeometry=hl,t.ExtrudeGeometry=hl,t.FaceColors=1,t.FileLoader=ic,t.FlatShading=1,t.Float16BufferAttribute=hn,t.Float32Attribute=function(t,e){return console.warn("THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead."),new un(t,e)},t.Float32BufferAttribute=un,t.Float64Attribute=function(t,e){return console.warn("THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead."),new dn(t,e)},t.Float64BufferAttribute=dn,t.FloatType=M,t.Fog=Zs,t.FogExp2=Js,t.Font=Uc,t.FontLoader=class extends ec{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new ic(this.manager);s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(r.withCredentials),s.load(t,(function(t){let n;try{n=JSON.parse(t)}catch(e){console.warn("THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead."),n=JSON.parse(t.substring(65,t.length-2))}const i=r.parse(n);e&&e(i)}),n,i)}parse(t){return new Uc(t)}},t.FrontSide=0,t.Frustum=ai,t.GLBufferAttribute=wh,t.GLSL1="100",t.GLSL3=it,t.GammaEncoding=J,t.GreaterDepth=6,t.GreaterEqualDepth=5,t.GreaterEqualStencilFunc=518,t.GreaterStencilFunc=516,t.GridHelper=Gh,t.Group=ks,t.HalfFloatType=w,t.HemisphereLight=dc,t.HemisphereLightHelper=class extends Pe{constructor(t,e,n){super(),this.light=t,this.light.updateMatrixWorld(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.color=n;const i=new ml(e);i.rotateY(.5*Math.PI),this.material=new Ke({wireframe:!0,fog:!1,toneMapped:!1}),void 0===this.color&&(this.material.vertexColors=!0);const r=i.getAttribute("position"),s=new Float32Array(3*r.count);i.setAttribute("color",new en(s,3)),this.add(new Gn(i,this.material)),this.update()}dispose(){this.children[0].geometry.dispose(),this.children[0].material.dispose()}update(){const t=this.children[0];if(void 0!==this.color)this.material.color.set(this.color);else{const e=t.geometry.getAttribute("color");Uh.copy(this.light.color),Hh.copy(this.light.groundColor);for(let t=0,n=e.count;t0){const n=new $l(e);r=new rc(n),r.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e0){i=new rc(this.manager),i.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e converted to rad: return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); } // general sun calculations function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } function eclipticLongitude(M) { var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center P = rad * 102.9372; // perihelion of the Earth return M + C + P + PI; } function sunCoords(d) { var M = solarMeanAnomaly(d), L = eclipticLongitude(M); return { dec: declination(L, 0), ra: rightAscension(L, 0) }; } var SunCalc = {}; // calculates sun position for a given date and latitude/longitude SunCalc.getPosition = function (date, lat, lng) { var lw = rad * -lng, phi = rad * lat, d = toDays(date), c = sunCoords(d), H = siderealTime(d, lw) - c.ra; return { azimuth: azimuth(H, phi, c.dec), altitude: altitude(H, phi, c.dec) }; }; SunCalc.toJulian = function (date) { return toJulian(date); }; // sun times configuration (angle, morning name, evening name) var times = SunCalc.times = [ [-0.833, 'sunrise', 'sunset'], [-0.3, 'sunriseEnd', 'sunsetStart'], [-6, 'dawn', 'dusk'], [-12, 'nauticalDawn', 'nauticalDusk'], [-18, 'nightEnd', 'night'], [6, 'goldenHourEnd', 'goldenHour'] ]; // adds a custom time to the times config SunCalc.addTime = function (angle, riseName, setName) { times.push([angle, riseName, setName]); }; // calculations for sun times var J0 = 0.0009; function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } // returns set time for the given sun altitude function getSetJ(h, lw, phi, dec, n, M, L) { var w = hourAngle(h, phi, dec), a = approxTransit(w, lw, n); return solarTransitJ(a, M, L); } // calculates sun times for a given date, latitude/longitude, and, optionally, // the observer height (in meters) relative to the horizon SunCalc.getTimes = function (date, lat, lng, height) { height = height || 0; var lw = rad * -lng, phi = rad * lat, dh = observerAngle(height), d = toDays(date), n = julianCycle(d, lw), ds = approxTransit(0, lw, n), M = solarMeanAnomaly(ds), L = eclipticLongitude(M), dec = declination(L, 0), Jnoon = solarTransitJ(ds, M, L), i, len, time, h0, Jset, Jrise; var result = { solarNoon: fromJulian(Jnoon), nadir: fromJulian(Jnoon - 0.5) }; for (i = 0, len = times.length; i < len; i += 1) { time = times[i]; h0 = (time[0] + dh) * rad; Jset = getSetJ(h0, lw, phi, dec, n, M, L); Jrise = Jnoon - (Jset - Jnoon); result[time[1]] = fromJulian(Jrise); result[time[2]] = fromJulian(Jset); } return result; }; // moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas function moonCoords(d) { // geocentric ecliptic coordinates of the moon var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude M = rad * (134.963 + 13.064993 * d), // mean anomaly F = rad * (93.272 + 13.229350 * d), // mean distance l = L + rad * 6.289 * sin(M), // longitude b = rad * 5.128 * sin(F), // latitude dt = 385001 - 20905 * cos(M); // distance to the moon in km return { ra: rightAscension(l, b), dec: declination(l, b), dist: dt }; } SunCalc.getMoonPosition = function (date, lat, lng) { var lw = rad * -lng, phi = rad * lat, d = toDays(date), c = moonCoords(d), H = siderealTime(d, lw) - c.ra, h = altitude(H, phi, c.dec), // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); h = h + astroRefraction(h); // altitude correction for refraction return { azimuth: azimuth(H, phi, c.dec), altitude: h, distance: c.dist, parallacticAngle: pa }; }; // calculations for illumination parameters of the moon, // based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and // Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. SunCalc.getMoonIllumination = function (date) { var d = toDays(date || new Date()), s = sunCoords(d), m = moonCoords(d), sdist = 149598000, // distance from Earth to Sun in km phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)), inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)), angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra)); return { fraction: (1 + cos(inc)) / 2, phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI, angle: angle }; }; function hoursLater(date, h) { return new Date(date.valueOf() + h * dayMs / 24); } // calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article SunCalc.getMoonTimes = function (date, lat, lng, inUTC) { var t = new Date(date); if (inUTC) t.setUTCHours(0, 0, 0, 0); else t.setHours(0, 0, 0, 0); var hc = 0.133 * rad, h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) for (var i = 1; i <= 24; i += 2) { h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; a = (h0 + h2) / 2 - h1; b = (h2 - h0) / 2; xe = -b / (2 * a); ye = (a * xe + b) * xe + h1; d = b * b - 4 * a * h1; roots = 0; if (d >= 0) { dx = Math.sqrt(d) / (Math.abs(a) * 2); x1 = xe - dx; x2 = xe + dx; if (Math.abs(x1) <= 1) roots++; if (Math.abs(x2) <= 1) roots++; if (x1 < -1) x1 = x2; } if (roots === 1) { if (h0 < 0) rise = i + x1; else set = i + x1; } else if (roots === 2) { rise = i + (ye < 0 ? x2 : x1); set = i + (ye < 0 ? x1 : x2); } if (rise && set) break; h0 = h2; } var result = {}; if (rise) result.rise = hoursLater(t, rise); if (set) result.set = hoursLater(t, set); if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; return result; }; //// export as Node module / AMD module / browser variable //if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; //else if (typeof define === 'function' && define.amd) define(SunCalc); //else window.SunCalc = SunCalc; module.exports = exports = SunCalc }()); },{}],29:[function(require,module,exports){ var THREE = require("../three.js"); var Constants = require("./constants.js"); var validate = require("./validate.js"); var utils = { prettyPrintMatrix: function (uglymatrix) { for (var s = 0; s < 4; s++) { var quartet = [uglymatrix[s], uglymatrix[s + 4], uglymatrix[s + 8], uglymatrix[s + 12]]; console.log(quartet.map(function (num) { return num.toFixed(4) })) } }, makePerspectiveMatrix: function (fovy, aspect, near, far) { var out = new THREE.Matrix4(); var f = 1.0 / Math.tan(fovy / 2), nf = 1 / (near - far); var newMatrix = [ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) * nf, -1, 0, 0, (2 * far * near) * nf, 0 ] out.elements = newMatrix return out; }, //[jscastro] new orthographic matrix calculations https://en.wikipedia.org/wiki/Orthographic_projection and validated with https://bit.ly/3rPvB9Y makeOrthographicMatrix: function (left, right, top, bottom, near, far) { var out = new THREE.Matrix4(); const w = 1.0 / (right - left); const h = 1.0 / (top - bottom); const p = 1.0 / (far - near); const x = (right + left) * w; const y = (top + bottom) * h; const z = near * p; var newMatrix = [ 2 * w, 0, 0, 0, 0, 2 * h, 0, 0, 0, 0, - 1 * p, 0, - x, -y, -z, 1 ] out.elements = newMatrix return out; }, //gimme radians radify: function (deg) { function convert(degrees) { degrees = degrees || 0; return Math.PI * 2 * degrees / 360 } if (typeof deg === 'object') { //if [x,y,z] array of rotations if (deg.length > 0) { return deg.map(function (degree) { return convert(degree) }) } // if {x: y: z:} rotation object else { return [convert(deg.x), convert(deg.y), convert(deg.z)] } } //if just a number else return convert(deg) }, //gimme degrees degreeify: function (rad) { function convert(radians) { radians = radians || 0; return radians * 360 / (Math.PI * 2) } if (typeof rad === 'object') { return [convert(rad.x), convert(rad.y), convert(rad.z)] } else return convert(rad) }, projectToWorld: function (coords) { // Spherical mercator forward projection, re-scaling to WORLD_SIZE var projected = [ -Constants.MERCATOR_A * Constants.DEG2RAD * coords[0] * Constants.PROJECTION_WORLD_SIZE, -Constants.MERCATOR_A * Math.log(Math.tan((Math.PI * 0.25) + (0.5 * Constants.DEG2RAD * coords[1]))) * Constants.PROJECTION_WORLD_SIZE ]; //z dimension, defaulting to 0 if not provided if (!coords[2]) projected.push(0) else { var pixelsPerMeter = this.projectedUnitsPerMeter(coords[1]); projected.push(coords[2] * pixelsPerMeter); } var result = new THREE.Vector3(projected[0], projected[1], projected[2]); return result; }, projectedUnitsPerMeter: function (latitude) { return Math.abs(Constants.WORLD_SIZE / Math.cos(Constants.DEG2RAD * latitude) / Constants.EARTH_CIRCUMFERENCE); }, _circumferenceAtLatitude: function (latitude) { return Constants.EARTH_CIRCUMFERENCE * Math.cos(latitude * Math.PI / 180); }, mercatorZfromAltitude: function (altitude, lat) { return altitude / this._circumferenceAtLatitude(lat); }, _scaleVerticesToMeters: function (centerLatLng, vertices) { var pixelsPerMeter = this.projectedUnitsPerMeter(centerLatLng[1]); var centerProjected = this.projectToWorld(centerLatLng); for (var i = 0; i < vertices.length; i++) { vertices[i].multiplyScalar(pixelsPerMeter); } return vertices; }, projectToScreen: function (coords) { console.log("WARNING: Projecting to screen coordinates is not yet implemented"); }, unprojectFromScreen: function (pixel) { console.log("WARNING: unproject is not yet implemented"); }, //world units to lnglat unprojectFromWorld: function (worldUnits) { var unprojected = [ -worldUnits.x / (Constants.MERCATOR_A * Constants.DEG2RAD * Constants.PROJECTION_WORLD_SIZE), 2 * (Math.atan(Math.exp(worldUnits.y / (Constants.PROJECTION_WORLD_SIZE * (-Constants.MERCATOR_A)))) - Math.PI / 4) / Constants.DEG2RAD ]; var pixelsPerMeter = this.projectedUnitsPerMeter(unprojected[1]); //z dimension var height = worldUnits.z || 0; unprojected.push(height / pixelsPerMeter); return unprojected; }, toScreenPosition: function (obj, camera) { var vector = new THREE.Vector3(); var widthHalf = 0.5 * renderer.context.canvas.width; var heightHalf = 0.5 * renderer.context.canvas.height; obj.updateMatrixWorld(); vector.setFromMatrixPosition(obj.matrixWorld); vector.project(camera); vector.x = (vector.x * widthHalf) + widthHalf; vector.y = - (vector.y * heightHalf) + heightHalf; return { x: vector.x, y: vector.y }; }, //get the center point of a feature getFeatureCenter: function getFeatureCenter(feature, model, level) { let center = []; let latitude = 0; let longitude = 0; let height = 0; //deep copy to avoid modifying the original array let coordinates = [...feature.geometry.coordinates[0]]; if (feature.geometry.type === "Point") { center = [...coordinates[0]];//deep copy } else { //features in mapbox repeat the first coordinates at the end. We remove it. if (feature.geometry.type === "MultiPolygon") coordinates = coordinates[0]; coordinates.splice(-1, 1); coordinates.forEach(function (c) { latitude += c[0]; longitude += c[1]; }); center = [latitude / coordinates.length, longitude / coordinates.length]; } height = this.getObjectHeightOnFloor(feature, model, level); (center.length < 3 ? center.push(height) : center[2] = height); return center; }, getObjectHeightOnFloor: function (feature, obj, level = feature.properties.level || 0) { let floorHeightMin = (level * (feature.properties.levelHeight || 0)); //object height is modelSize.z + base_height or min_height configured for this object let base = (feature.properties.base_height || feature.properties.min_height || 0); //let height = ((obj && obj.model) ? obj.modelSize.z : (feature.properties.height - base)); let height = ((obj && obj.model) ? 0 : (feature.properties.height - base)); let objectHeight = height + base; let modelHeightFloor = floorHeightMin + objectHeight; return modelHeightFloor; }, _flipMaterialSides: function (obj) { }, // to improve precision, normalize a series of vector3's to their collective center, and move the resultant mesh to that center normalizeVertices(vertices) { let geometry = new THREE.BufferGeometry(); let positions = []; for (var j = 0; j < vertices.length; j++) { let p = vertices[j]; positions.push(p.x, p.y, p.z); positions.push(p.x, p.y, p.z); } geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3)); geometry.computeBoundingSphere(); var center = geometry.boundingSphere.center; var scaled = vertices.map(function (v3) { var normalized = v3.sub(center); return normalized; }); return { vertices: scaled, position: center } }, //flatten an array of Vector3's into a shallow array of values in x-y-z order, for bufferGeometry flattenVectors(vectors) { var flattenedArray = []; for (let vertex of vectors) { flattenedArray.push(vertex.x, vertex.y, vertex.z); } return flattenedArray }, //convert a line/polygon to Vector3's lnglatsToWorld: function (coords) { var vector3 = coords.map( function (pt) { var p = utils.projectToWorld(pt); var v3 = new THREE.Vector3(p.x, p.y, p.z); return v3 } ); return vector3 }, extend: function (original, addition) { for (let key in addition) original[key] = addition[key]; }, clone: function (original) { var clone = {}; for (let key in original) clone[key] = original[key]; return clone; }, clamp: function(n, min, max) { return Math.min(max, Math.max(min, n)); }, // retrieve object parameters from an options object types: { rotation: function (r, currentRotation) { //[jscastro] rotation default 0 if (!r) { r = 0; }; // if number provided, rotate only in Z by that amount if (typeof r === 'number') r = { z: r }; var degrees = this.applyDefault([r.x, r.y, r.z], currentRotation); var radians = utils.radify(degrees); return radians; }, scale: function (s, currentScale) { //[jscastro] scale default 1 if (!s) { s = 1; }; if (typeof s === 'number') return s = [s, s, s]; else return this.applyDefault([s.x, s.y, s.z], currentScale); }, applyDefault: function (array, current) { var output = array.map(function (item, index) { item = item || current[index]; return item }) return output }, }, toDecimal: function (n, d) { return Number(n.toFixed(d)); }, equal: function (obj1, obj2) { const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) { return false; } if (keys1.length == 0 && keys2.length == 0 && keys1 !== keys2) { return false; } for (const key of keys1) { const val1 = obj1[key]; const val2 = obj2[key]; const areObjects = this.isObject(val1) && this.isObject(val2); if ( areObjects && !equal(val1, val2) || !areObjects && val1 !== val2 ) { return false; } } return true; }, isObject: function (object) { return object != null && typeof object === 'object'; }, curveToLine: (curve, params) => { let { width, color } = params; let geometry = new THREE.BufferGeometry().setFromPoints( curve.getPoints(100) ); let material = new THREE.LineBasicMaterial({ color: color, linewidth: width, }); let line = new THREE.Line(geometry, material); return line; }, curvesToLines: (curves) => { var colors = [0xff0000, 0x1eff00, 0x2600ff]; var lines = curves.map((curve, i) => { let params = { width: 3, color: colors[i] || 'purple', }; let curveline = curveToLine(curve, params); return curveline; }); return lines; }, _validate: function (userInputs, defaults) { userInputs = userInputs || {}; var validatedOutput = {}; utils.extend(validatedOutput, userInputs); for (let key of Object.keys(defaults)) { if (userInputs[key] === undefined) { //make sure required params are present if (defaults[key] === null) { console.error(key + ' is required') return; } else validatedOutput[key] = defaults[key] } else validatedOutput[key] = userInputs[key] } return validatedOutput }, Validator: new validate(), exposedMethods: ['projectToWorld', 'projectedUnitsPerMeter', 'extend', 'unprojectFromWorld'] } module.exports = exports = utils },{"../three.js":25,"./constants.js":26,"./validate.js":30}],30:[function(require,module,exports){ // Type validator function Validate(){ }; Validate.prototype = { Coords: function(input) { if (input.constructor !== Array) { console.error("Coords must be an array") return } if (input.length < 2) { console.error("Coords length must be at least 2") return } for (const member of input) { if (member.constructor !== Number) { console.error("Coords values must be numbers") return } } if (Math.abs(input[1]) > 90) { console.error("Latitude must be between -90 and 90") return } return input }, Line: function(input) { var scope = this; if (input.constructor !== Array) { console.error("Line must be an array") return } for (const coord of input){ if (!scope.Coords(coord)) { console.error("Each coordinate in a line must be a valid Coords type") return } } return input }, Rotation: function(input) { if (input.constructor === Number) input = {z: input} else if (input.constructor === Object) { for (const key of Object.keys(input)){ if (!['x', 'y', 'z'].includes(key)) { console.error('Rotation parameters must be x, y, or z') return } if (input[key].constructor !== Number) { console.error('Individual rotation values must be numbers') return } } } else { console.error('Rotation must be an object or a number') return } return input }, Scale: function(input) { if (input.constructor === Number) { input = {x:input, y:input, z: input} } else if (input.constructor === Object) { for (const key of Object.keys(input)){ if (!['x', 'y', 'z'].includes(key)) { console.error('Scale parameters must be x, y, or z') return } if (input[key].constructor !== Number) { console.error('Individual scale values must be numbers') return } } } else { console.error('Scale must be an object or a number') return } return input } } module.exports = exports = Validate; },{}]},{},[1]);