diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9af75f3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/node_modules/
+/uploads/
\ No newline at end of file
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..0865085
--- /dev/null
+++ b/app.js
@@ -0,0 +1,24 @@
+var express = require('express');
+var multer = require('multer');
+var upload = multer({ dest: 'uploads/' });
+var app = express();
+
+app.use(express.static('static'));
+
+//主页加载
+app.get('/', function (req, res) {
+ res.sendFile(__dirname + "/views/createScheme.html");
+});
+
+//接受文件上传,并且返回文件名
+app.post('/upload', upload.single('file'), function (req, res) {
+ //console.info(req.file)
+ res.send(req.file);
+});
+
+app.get('/files/:filename', function (req, res) {
+ var filename = req.params['filename'];
+ res.sendFile(__dirname + "/uploads/" + filename);
+})
+
+app.listen(80);
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..48e341a
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3 @@
+{
+ "lockfileVersion": 1
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6278ef2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "3dmodelos",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.16.4",
+ "multer": "^1.4.1"
+ }
+}
diff --git a/static/img/skybox/nx.jpg b/static/img/skybox/nx.jpg
new file mode 100644
index 0000000..d6fed4c
Binary files /dev/null and b/static/img/skybox/nx.jpg differ
diff --git a/static/img/skybox/ny.jpg b/static/img/skybox/ny.jpg
new file mode 100644
index 0000000..3fa44ff
Binary files /dev/null and b/static/img/skybox/ny.jpg differ
diff --git a/static/img/skybox/nz.jpg b/static/img/skybox/nz.jpg
new file mode 100644
index 0000000..3d65f80
Binary files /dev/null and b/static/img/skybox/nz.jpg differ
diff --git a/static/img/skybox/px.jpg b/static/img/skybox/px.jpg
new file mode 100644
index 0000000..cdc9c26
Binary files /dev/null and b/static/img/skybox/px.jpg differ
diff --git a/static/img/skybox/py.jpg b/static/img/skybox/py.jpg
new file mode 100644
index 0000000..8cec709
Binary files /dev/null and b/static/img/skybox/py.jpg differ
diff --git a/static/img/skybox/pz.jpg b/static/img/skybox/pz.jpg
new file mode 100644
index 0000000..75ed830
Binary files /dev/null and b/static/img/skybox/pz.jpg differ
diff --git a/static/img/texture/001.jpg b/static/img/texture/001.jpg
new file mode 100644
index 0000000..b04703d
Binary files /dev/null and b/static/img/texture/001.jpg differ
diff --git a/static/js/createScheme.js b/static/js/createScheme.js
new file mode 100644
index 0000000..5d4cd7b
--- /dev/null
+++ b/static/js/createScheme.js
@@ -0,0 +1,266 @@
+var renderer, scene, camera;
+
+//整个页面维护的数据
+var data = { name: '默认方案', components: [] };
+
+//当前选择的部件
+var componentIndex = -1;
+
+$().ready(function () {
+ initData();
+ initUi();
+ initThree();
+ loadmodel();
+ initEvent();
+});
+//初始化Threejs
+function initThree() {
+ initScene();
+ initCamera();
+ initRenderer();
+ render();
+}
+//获取数据
+function initData() {
+ //todo ajax请求数据
+}
+//刷新主Ui
+function initUi() {
+ //方案名
+ $('#schemeName').val(data.name);
+ //组件列表
+ freshComponentItem();
+}
+
+function freshComponentItem() {
+ //清空原有列表
+ $('.componentItem').remove();
+ for (var index in data.components) {
+ //添加一个item并注册了click监听
+ $('#componentList').prepend("
" + data.components[index].name + "");
+ }
+}
+
+//选择部件
+function selectComponent(index) {
+ componentIndex = index;
+ $('#componentTitle').text(data.components[componentIndex].name);
+ $('#delComponent').show();
+ $('#upload').removeClass("disabled");
+ //加载模型列表
+ freshmodelList();
+}
+
+//选择模型列表
+function freshmodelList() {
+ $('.list-group-item').remove();
+ if (data.components[componentIndex].models.length == 0) {
+ $('#modelList').append('暂无模型')
+ } else {
+ // if (data.components[componentIndex].modelIndex == -1)
+ // data.components[componentIndex].modelIndex = 0;
+ for (var index in data.components[componentIndex].models) {
+ if (index == data.components[componentIndex].modelIndex) {
+ data.components[componentIndex].models[index].modelObj.visible = true;
+ $('#modelList').append("" + data.components[componentIndex].models[index].name + "")
+ } else {
+ data.components[componentIndex].models[index].modelObj.visible = false;
+ $('#modelList').append("" + data.components[componentIndex].models[index].name + "")
+ }
+ }
+ }
+}
+
+//选择模型
+function selectModel(index) {
+ data.components[componentIndex].modelIndex = index;
+ freshmodelList();
+}
+
+//初始化模型
+function loadmodel() {
+
+}
+
+function initScene() {
+ //场景设置
+ scene = new THREE.Scene();
+ //设置天空盒
+ scene.background = new THREE.CubeTextureLoader()
+ .setPath('/img/skybox/')
+ .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
+ //场景灯光
+ //环境光
+ var light = new THREE.HemisphereLight(0xffffff, 0x444444);
+ light.position.set(0, 2, 0);
+ scene.add(light);
+ //直射光
+ light = new THREE.DirectionalLight(0xffffff);
+ light.position.set(0, 2, 1);
+ light.castShadow = true;
+ light.shadow.camera.top = 180;
+ light.shadow.camera.bottom = - 100;
+ light.shadow.camera.left = - 120;
+ light.shadow.camera.right = 120;
+ scene.add(light);
+ //grid
+ var grid = new THREE.GridHelper(20, 20, 0x0000ff, 0xff0000);
+ grid.material.opacity = 0.2;
+ grid.material.transparent = true;
+ scene.add(grid);
+
+}
+
+function initCamera() {
+ //相机设置
+ camera = new THREE.PerspectiveCamera(45, $('#viewField').innerWidth() / $('#viewField').innerHeight());
+ camera.position.set(0, 5, 10);
+ //让相机对着场景中央
+ camera.lookAt(scene.position);
+ //相机控制,控制的相机和监听的dom
+ controls = new THREE.OrbitControls(camera, $('#viewField')[0]);
+ controls.target.set(0, 0, 0);
+ controls.update();
+
+}
+
+function initRenderer() {
+ //初始化渲染器
+ renderer = new THREE.WebGLRenderer();
+ //设置像素值
+ renderer.setPixelRatio(window.devicePixelRatio);
+ //设置渲染范围为屏幕的大小
+ renderer.setSize($('#viewField').innerWidth(), $('#viewField').innerHeight());
+ //将渲染结果放到页面中
+ $('#viewField').append(renderer.domElement);
+}
+
+function render() {
+ requestAnimationFrame(render);
+ renderer.render(scene, camera);
+}
+
+//初始化所有事件
+function initEvent() {
+ //方案名变动监听
+ $('#schemeName').change(function () {
+ data.name = $('#schemeName').val();
+ });
+
+ //将fileinput事件注册到uploadbtn上
+ $("#upload").click(function () {
+ $("#file").click();
+ });
+
+ //只要file发生改变就上传文件
+ $("#file").change(function () {
+ if ($(this).val().length > 0) {
+ var formData = new FormData($('#uploadForm')[0]);
+ $.ajax({
+ type: 'post',
+ url: "/upload",
+ data: formData,
+ cache: false,
+ processData: false,
+ contentType: false,
+ success: function (fileData) {
+ //上传成功后加载模型
+ //加载是异步的
+ var addModelItem = function (modelObj) {
+ data.components[componentIndex].models.push({
+ name: fileData.originalname,
+ fileId: fileData.filename,
+ modelObj: modelObj
+ });
+ selectModel(data.components[componentIndex].models.length - 1);
+ }
+ addModel('/files/' + fileData.filename, addModelItem);
+ },
+ error: function () {
+ alert("上传失败")
+ }
+ });
+ }
+ });
+
+ //当浏览器大小变化时
+ $(window).resize(function () {
+ camera.aspect = $('#viewField').innerWidth() / $('#viewField').innerHeight();
+ camera.updateProjectionMatrix();
+ renderer.setSize($('#viewField').innerWidth(), $('#viewField').innerHeight());
+ });
+
+ //当模态框消失的时候清空text
+ $('#addComponentModal').on('hidden.zui.modal', function () {
+ $("#componentName").val("")
+ });
+
+ //添加一个部件
+ $('#addComponent').click(function () {
+ var componentName = $("#componentName").val().trim();
+ if (componentName.length > 0) {
+ var component = {
+ name: componentName,
+ models: [],
+ modelIndex: -1
+ }
+ data.components.push(component);
+ freshComponentItem();
+ $('#addComponentModal').modal('hide');
+ }
+ });
+
+ //删除部件
+ $('#delComponent').click(function () {
+ new $.zui.Messager('你确定要删除该部件吗,该部件的所有模型将会被清空!', {
+ type: 'danger',
+ close: false,
+ actions: [{
+ icon: 'ok-sign',
+ text: '确定',
+ action: function () { // 点击该操作按钮的回调函数
+ if (componentIndex >= 0) {
+ for (var index in data.components[componentIndex].models)
+ scene.remove(data.components[componentIndex].models[index].modelObj);
+ data.components.splice(componentIndex, 1);
+ freshComponentItem();
+ componentIndex = -1;
+ $('#componentTitle').text('请先选择部件');
+ $('#delComponent').hide();
+ $('#upload').addClass("disabled");
+ $('.list-group-item').remove();
+ //todo 还需要发送ajax清空模型文件
+ }
+ }
+ }]
+ }).show();
+ });
+}
+
+function addModel(url, callBack) {
+ var map = new THREE.TextureLoader().load('/img/texture/001.jpg');
+ //加载obj模型
+ // loader = new THREE.OBJLoader();
+ // loader.load(url, function (object) {
+ // object.position.set(0, -1, 0);
+ // object.scale.set(0.01, 0.01, 0.01);
+ // scene.add(object);
+ // });
+ //加载fbx模型
+ var loader = new THREE.FBXLoader();
+ loader.load(url, function (object) {
+ object.traverse(function (child) {
+ if (child instanceof THREE.Mesh) {
+ //child.material.map = map;
+ child.material.castShadow = true;
+ child.material.receiveShadow = true;
+ child.material.needsUpdate = true;
+ }
+ });
+ scene.add(object);
+ callBack(object);
+ });
+
+
+}
+
diff --git a/static/js/three/FBXLoader.js b/static/js/three/FBXLoader.js
new file mode 100644
index 0000000..aad8917
--- /dev/null
+++ b/static/js/three/FBXLoader.js
@@ -0,0 +1,4119 @@
+/**
+ * @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/
+ */
+
+
+THREE.FBXLoader = ( function () {
+
+ var fbxTree;
+ var connections;
+ var sceneGraph;
+
+ function FBXLoader( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+ }
+
+ FBXLoader.prototype = {
+
+ constructor: FBXLoader,
+
+ crossOrigin: 'anonymous',
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var self = this;
+
+ var path = ( self.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : self.path;
+
+ var loader = new THREE.FileLoader( this.manager );
+ loader.setResponseType( 'arraybuffer' );
+
+ loader.load( url, function ( buffer ) {
+
+ try {
+
+ onLoad( self.parse( buffer, path ) );
+
+ } catch ( error ) {
+
+ setTimeout( function () {
+
+ if ( onError ) onError( error );
+
+ self.manager.itemError( url );
+
+ }, 0 );
+
+ }
+
+ }, onProgress, onError );
+
+ },
+
+ setPath: function ( value ) {
+
+ this.path = value;
+ return this;
+
+ },
+
+ setResourcePath: function ( value ) {
+
+ this.resourcePath = value;
+ return this;
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+ return this;
+
+ },
+
+ parse: function ( FBXBuffer, path ) {
+
+ if ( isFbxFormatBinary( FBXBuffer ) ) {
+
+ fbxTree = new BinaryParser().parse( FBXBuffer );
+
+ } else {
+
+ var 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 );
+
+ var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+
+ return new FBXTreeParser( textureLoader ).parse( fbxTree );
+
+ }
+
+ };
+
+ // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group
+ function FBXTreeParser( textureLoader ) {
+
+ this.textureLoader = textureLoader;
+
+ }
+
+ FBXTreeParser.prototype = {
+
+ constructor: FBXTreeParser,
+
+ parse: function () {
+
+ connections = this.parseConnections();
+
+ var images = this.parseImages();
+ var textures = this.parseTextures( images );
+ var materials = this.parseMaterials( textures );
+ var deformers = this.parseDeformers();
+ var 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: function () {
+
+ var connectionMap = new Map();
+
+ if ( 'Connections' in fbxTree ) {
+
+ var rawConnections = fbxTree.Connections.connections;
+
+ rawConnections.forEach( function ( rawConnection ) {
+
+ var fromID = rawConnection[ 0 ];
+ var toID = rawConnection[ 1 ];
+ var relationship = rawConnection[ 2 ];
+
+ if ( ! connectionMap.has( fromID ) ) {
+
+ connectionMap.set( fromID, {
+ parents: [],
+ children: []
+ } );
+
+ }
+
+ var parentRelationship = { ID: toID, relationship: relationship };
+ connectionMap.get( fromID ).parents.push( parentRelationship );
+
+ if ( ! connectionMap.has( toID ) ) {
+
+ connectionMap.set( toID, {
+ parents: [],
+ children: []
+ } );
+
+ }
+
+ var 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: function () {
+
+ var images = {};
+ var blobs = {};
+
+ if ( 'Video' in fbxTree.Objects ) {
+
+ var videoNodes = fbxTree.Objects.Video;
+
+ for ( var nodeID in videoNodes ) {
+
+ var videoNode = videoNodes[ nodeID ];
+
+ var id = parseInt( nodeID );
+
+ images[ id ] = videoNode.RelativeFilename || videoNode.Filename;
+
+ // raw image data is in videoNode.Content
+ if ( 'Content' in videoNode ) {
+
+ var arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 );
+ var base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' );
+
+ if ( arrayBufferContent || base64Content ) {
+
+ var image = this.parseImage( videoNodes[ nodeID ] );
+
+ blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ for ( var id in images ) {
+
+ var 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: function ( videoNode ) {
+
+ var content = videoNode.Content;
+ var fileName = videoNode.RelativeFilename || videoNode.Filename;
+ var extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase();
+
+ var 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 ( typeof THREE.TGALoader !== 'function' ) {
+
+ console.warn( 'FBXLoader: THREE.TGALoader is required to load TGA textures' );
+ return;
+
+ } else {
+
+ if ( THREE.Loader.Handlers.get( '.tga' ) === null ) {
+
+ var tgaLoader = new THREE.TGALoader();
+ tgaLoader.setPath( this.textureLoader.path );
+
+ THREE.Loader.Handlers.add( /\.tga$/i, tgaLoader );
+
+ }
+
+ 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
+
+ var 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: function ( images ) {
+
+ var textureMap = new Map();
+
+ if ( 'Texture' in fbxTree.Objects ) {
+
+ var textureNodes = fbxTree.Objects.Texture;
+ for ( var nodeID in textureNodes ) {
+
+ var texture = this.parseTexture( textureNodes[ nodeID ], images );
+ textureMap.set( parseInt( nodeID ), texture );
+
+ }
+
+ }
+
+ return textureMap;
+
+ },
+
+ // Parse individual node in FBXTree.Objects.Texture
+ parseTexture: function ( textureNode, images ) {
+
+ var texture = this.loadTexture( textureNode, images );
+
+ texture.ID = textureNode.id;
+
+ texture.name = textureNode.attrName;
+
+ var wrapModeU = textureNode.WrapModeU;
+ var wrapModeV = textureNode.WrapModeV;
+
+ var valueU = wrapModeU !== undefined ? wrapModeU.value : 0;
+ var 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 ) {
+
+ var 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: function ( textureNode, images ) {
+
+ var fileName;
+
+ var currentPath = this.textureLoader.path;
+
+ var 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 );
+
+ }
+
+ }
+
+ var texture;
+
+ var extension = textureNode.FileName.slice( - 3 ).toLowerCase();
+
+ if ( extension === 'tga' ) {
+
+ var loader = THREE.Loader.Handlers.get( '.tga' );
+
+ if ( loader === null ) {
+
+ console.warn( 'FBXLoader: TGALoader not found, creating empty placeholder texture for', fileName );
+ texture = new THREE.Texture();
+
+ } else {
+
+ texture = loader.load( fileName );
+
+ }
+
+ } else if ( extension === 'psd' ) {
+
+ console.warn( 'FBXLoader: PSD textures are not supported, creating empty placeholder texture for', fileName );
+ texture = new THREE.Texture();
+
+ } else {
+
+ texture = this.textureLoader.load( fileName );
+
+ }
+
+ this.textureLoader.setPath( currentPath );
+
+ return texture;
+
+ },
+
+ // Parse nodes in FBXTree.Objects.Material
+ parseMaterials: function ( textureMap ) {
+
+ var materialMap = new Map();
+
+ if ( 'Material' in fbxTree.Objects ) {
+
+ var materialNodes = fbxTree.Objects.Material;
+
+ for ( var nodeID in materialNodes ) {
+
+ var 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: function ( materialNode, textureMap ) {
+
+ var ID = materialNode.id;
+ var name = materialNode.attrName;
+ var 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;
+
+ var parameters = this.parseParameters( materialNode, textureMap, ID );
+
+ var 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 MeshPhongMaterial.', type );
+ material = new THREE.MeshPhongMaterial( { color: 0x3300ff } );
+ 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: function ( materialNode, textureMap, ID ) {
+
+ var 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' ) {
+
+ // 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' ) {
+
+ // 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 );
+
+ }
+
+ var self = this;
+ connections.get( ID ).children.forEach( function ( child ) {
+
+ var type = child.relationship;
+
+ switch ( type ) {
+
+ case 'Bump':
+ parameters.bumpMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'DiffuseColor':
+ parameters.map = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'DisplacementColor':
+ parameters.displacementMap = self.getTexture( textureMap, child.ID );
+ break;
+
+
+ case 'EmissiveColor':
+ parameters.emissiveMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'NormalMap':
+ parameters.normalMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'ReflectionColor':
+ parameters.envMap = self.getTexture( textureMap, child.ID );
+ parameters.envMap.mapping = THREE.EquirectangularReflectionMapping;
+ break;
+
+ case 'SpecularColor':
+ parameters.specularMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'TransparentColor':
+ parameters.alphaMap = self.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: function ( 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 Skeleton-like objects for use later when generating and binding skeletons.
+ parseDeformers: function () {
+
+ var skeletons = {};
+ var morphTargets = {};
+
+ if ( 'Deformer' in fbxTree.Objects ) {
+
+ var DeformerNodes = fbxTree.Objects.Deformer;
+
+ for ( var nodeID in DeformerNodes ) {
+
+ var deformerNode = DeformerNodes[ nodeID ];
+
+ var relationships = connections.get( parseInt( nodeID ) );
+
+ if ( deformerNode.attrType === 'Skin' ) {
+
+ var 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' ) {
+
+ var 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: function ( relationships, deformerNodes ) {
+
+ var rawBones = [];
+
+ relationships.children.forEach( function ( child ) {
+
+ var boneNode = deformerNodes[ child.ID ];
+
+ if ( boneNode.attrType !== 'Cluster' ) return;
+
+ var 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: function ( relationships, deformerNodes ) {
+
+ var rawMorphTargets = [];
+
+ for ( var i = 0; i < relationships.children.length; i ++ ) {
+
+ var child = relationships.children[ i ];
+
+ var morphTargetNode = deformerNodes[ child.ID ];
+
+ var 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: function ( deformers, geometryMap, materialMap ) {
+
+ sceneGraph = new THREE.Group();
+
+ var modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap );
+
+ var modelNodes = fbxTree.Objects.Model;
+
+ var self = this;
+ modelMap.forEach( function ( model ) {
+
+ var modelNode = modelNodes[ model.ID ];
+ self.setLookAtProperties( model, modelNode );
+
+ var parentConnections = connections.get( model.ID ).parents;
+
+ parentConnections.forEach( function ( connection ) {
+
+ var 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();
+
+ this.setupMorphMaterials();
+
+ sceneGraph.traverse( function ( node ) {
+
+ if ( node.userData.transformData ) {
+
+ if ( node.parent ) node.userData.transformData.parentMatrixWorld = node.parent.matrix;
+
+ var transform = generateTransform( node.userData.transformData );
+
+ node.applyMatrix( transform );
+
+ }
+
+ } );
+
+ var 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: function ( skeletons, geometryMap, materialMap ) {
+
+ var modelMap = new Map();
+ var modelNodes = fbxTree.Objects.Model;
+
+ for ( var nodeID in modelNodes ) {
+
+ var id = parseInt( nodeID );
+ var node = modelNodes[ nodeID ];
+ var relationships = connections.get( id );
+
+ var 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 = THREE.PropertyBinding.sanitizeNodeName( node.attrName );
+ model.ID = id;
+
+ }
+
+ this.getTransformData( model, node );
+ modelMap.set( id, model );
+
+ }
+
+ return modelMap;
+
+ },
+
+ buildSkeleton: function ( relationships, skeletons, id, name ) {
+
+ var bone = null;
+
+ relationships.parents.forEach( function ( parent ) {
+
+ for ( var ID in skeletons ) {
+
+ var skeleton = skeletons[ ID ];
+
+ skeleton.rawBones.forEach( function ( rawBone, i ) {
+
+ if ( rawBone.ID === parent.ID ) {
+
+ var 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 = 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: function ( relationships ) {
+
+ var model;
+ var cameraAttribute;
+
+ relationships.children.forEach( function ( child ) {
+
+ var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
+
+ if ( attr !== undefined ) {
+
+ cameraAttribute = attr;
+
+ }
+
+ } );
+
+ if ( cameraAttribute === undefined ) {
+
+ model = new THREE.Object3D();
+
+ } else {
+
+ var type = 0;
+ if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) {
+
+ type = 1;
+
+ }
+
+ var nearClippingPlane = 1;
+ if ( cameraAttribute.NearPlane !== undefined ) {
+
+ nearClippingPlane = cameraAttribute.NearPlane.value / 1000;
+
+ }
+
+ var farClippingPlane = 1000;
+ if ( cameraAttribute.FarPlane !== undefined ) {
+
+ farClippingPlane = cameraAttribute.FarPlane.value / 1000;
+
+ }
+
+
+ var width = window.innerWidth;
+ var height = window.innerHeight;
+
+ if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) {
+
+ width = cameraAttribute.AspectWidth.value;
+ height = cameraAttribute.AspectHeight.value;
+
+ }
+
+ var aspect = width / height;
+
+ var fov = 45;
+ if ( cameraAttribute.FieldOfView !== undefined ) {
+
+ fov = cameraAttribute.FieldOfView.value;
+
+ }
+
+ var 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: function ( relationships ) {
+
+ var model;
+ var lightAttribute;
+
+ relationships.children.forEach( function ( child ) {
+
+ var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
+
+ if ( attr !== undefined ) {
+
+ lightAttribute = attr;
+
+ }
+
+ } );
+
+ if ( lightAttribute === undefined ) {
+
+ model = new THREE.Object3D();
+
+ } else {
+
+ var type;
+
+ // LightType can be undefined for Point lights
+ if ( lightAttribute.LightType === undefined ) {
+
+ type = 0;
+
+ } else {
+
+ type = lightAttribute.LightType.value;
+
+ }
+
+ var color = 0xffffff;
+
+ if ( lightAttribute.Color !== undefined ) {
+
+ color = new THREE.Color().fromArray( lightAttribute.Color.value );
+
+ }
+
+ var intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100;
+
+ // light disabled
+ if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) {
+
+ intensity = 0;
+
+ }
+
+ var 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?
+ var 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
+ var angle = Math.PI / 3;
+
+ if ( lightAttribute.InnerAngle !== undefined ) {
+
+ angle = THREE.Math.degToRad( lightAttribute.InnerAngle.value );
+
+ }
+
+ var 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.Math.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: function ( relationships, geometryMap, materialMap ) {
+
+ var model;
+ var geometry = null;
+ var material = null;
+ var 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 = THREE.VertexColors;
+
+ } );
+
+ }
+
+ if ( geometry.FBX_Deformer ) {
+
+ materials.forEach( function ( material ) {
+
+ material.skinning = true;
+
+ } );
+
+ model = new THREE.SkinnedMesh( geometry, material );
+
+ } else {
+
+ model = new THREE.Mesh( geometry, material );
+
+ }
+
+ return model;
+
+ },
+
+ createCurve: function ( relationships, geometryMap ) {
+
+ var 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.
+ var material = new THREE.LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } );
+ return new THREE.Line( geometry, material );
+
+ },
+
+ // parse the model node for transform data
+ getTransformData: function ( model, modelNode ) {
+
+ var 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: function ( model, modelNode ) {
+
+ if ( 'LookAtProperty' in modelNode ) {
+
+ var children = connections.get( model.ID ).children;
+
+ children.forEach( function ( child ) {
+
+ if ( child.relationship === 'LookAtProperty' ) {
+
+ var lookAtTarget = fbxTree.Objects.Model[ child.ID ];
+
+ if ( 'Lcl_Translation' in lookAtTarget ) {
+
+ var pos = lookAtTarget.Lcl_Translation.value;
+
+ // DirectionalLight, 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: function ( skeletons, geometryMap, modelMap ) {
+
+ var bindMatrices = this.parsePoseNodes();
+
+ for ( var ID in skeletons ) {
+
+ var skeleton = skeletons[ ID ];
+
+ var parents = connections.get( parseInt( skeleton.ID ) ).parents;
+
+ parents.forEach( function ( parent ) {
+
+ if ( geometryMap.has( parent.ID ) ) {
+
+ var geoID = parent.ID;
+ var geoRelationships = connections.get( geoID );
+
+ geoRelationships.parents.forEach( function ( geoConnParent ) {
+
+ if ( modelMap.has( geoConnParent.ID ) ) {
+
+ var model = modelMap.get( geoConnParent.ID );
+
+ model.bind( new THREE.Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
+
+ }
+
+ } );
+
+ }
+
+ } );
+
+ }
+
+ },
+
+ parsePoseNodes: function () {
+
+ var bindMatrices = {};
+
+ if ( 'Pose' in fbxTree.Objects ) {
+
+ var BindPoseNode = fbxTree.Objects.Pose;
+
+ for ( var nodeID in BindPoseNode ) {
+
+ if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) {
+
+ var 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: function () {
+
+ if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) {
+
+ var ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
+ var r = ambientColor[ 0 ];
+ var g = ambientColor[ 1 ];
+ var b = ambientColor[ 2 ];
+
+ if ( r !== 0 || g !== 0 || b !== 0 ) {
+
+ var color = new THREE.Color( r, g, b );
+ sceneGraph.add( new THREE.AmbientLight( color, 1 ) );
+
+ }
+
+ }
+
+ },
+
+ setupMorphMaterials: function () {
+
+ var self = this;
+ sceneGraph.traverse( function ( child ) {
+
+ if ( child.isMesh ) {
+
+ if ( child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length ) {
+
+ if ( Array.isArray( child.material ) ) {
+
+ child.material.forEach( function ( material, i ) {
+
+ self.setupMorphMaterial( child, material, i );
+
+ } );
+
+ } else {
+
+ self.setupMorphMaterial( child, child.material );
+
+ }
+
+ }
+
+ }
+
+ } );
+
+ },
+
+ setupMorphMaterial: function ( child, material, index ) {
+
+ var uuid = child.uuid;
+ var matUuid = material.uuid;
+
+ // if a geometry has morph targets, it cannot share the material with other geometries
+ var sharedMat = false;
+
+ sceneGraph.traverse( function ( node ) {
+
+ if ( node.isMesh ) {
+
+ if ( Array.isArray( node.material ) ) {
+
+ node.material.forEach( function ( mat ) {
+
+ if ( mat.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+
+ } );
+
+ } else if ( node.material.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+
+ }
+
+ } );
+
+ if ( sharedMat === true ) {
+
+ var clonedMat = material.clone();
+ clonedMat.morphTargets = true;
+
+ if ( index === undefined ) child.material = clonedMat;
+ else child.material[ index ] = clonedMat;
+
+ } else material.morphTargets = true;
+
+ }
+
+ };
+
+ // parse Geometry data from FBXTree and return map of BufferGeometries
+ function GeometryParser() {}
+
+ GeometryParser.prototype = {
+
+ constructor: GeometryParser,
+
+ // Parse nodes in FBXTree.Objects.Geometry
+ parse: function ( deformers ) {
+
+ var geometryMap = new Map();
+
+ if ( 'Geometry' in fbxTree.Objects ) {
+
+ var geoNodes = fbxTree.Objects.Geometry;
+
+ for ( var nodeID in geoNodes ) {
+
+ var relationships = connections.get( parseInt( nodeID ) );
+ var geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers );
+
+ geometryMap.set( parseInt( nodeID ), geo );
+
+ }
+
+ }
+
+ return geometryMap;
+
+ },
+
+ // Parse single node in FBXTree.Objects.Geometry
+ parseGeometry: function ( 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: function ( relationships, geoNode, deformers ) {
+
+ var skeletons = deformers.skeletons;
+ var morphTargets = deformers.morphTargets;
+
+ var 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;
+
+ var skeleton = relationships.children.reduce( function ( skeleton, child ) {
+
+ if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ];
+
+ return skeleton;
+
+ }, null );
+
+ var morphTarget = relationships.children.reduce( function ( morphTarget, child ) {
+
+ if ( morphTargets[ child.ID ] !== undefined ) morphTarget = morphTargets[ child.ID ];
+
+ return morphTarget;
+
+ }, null );
+
+ // Assume one model and get the preRotation from that
+ // if there is more than one model associated with the geometry this may cause problems
+ var modelNode = modelNodes[ 0 ];
+
+ var 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;
+
+ var transform = generateTransform( transformData );
+
+ return this.genGeometry( geoNode, skeleton, morphTarget, transform );
+
+ },
+
+ // Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry
+ genGeometry: function ( geoNode, skeleton, morphTarget, preTransform ) {
+
+ var geo = new THREE.BufferGeometry();
+ if ( geoNode.attrName ) geo.name = geoNode.attrName;
+
+ var geoInfo = this.parseGeoNode( geoNode, skeleton );
+ var buffers = this.genBuffers( geoInfo );
+
+ var positionAttribute = new THREE.Float32BufferAttribute( buffers.vertex, 3 );
+
+ preTransform.applyToBufferAttribute( positionAttribute );
+
+ geo.addAttribute( 'position', positionAttribute );
+
+ if ( buffers.colors.length > 0 ) {
+
+ geo.addAttribute( 'color', new THREE.Float32BufferAttribute( buffers.colors, 3 ) );
+
+ }
+
+ if ( skeleton ) {
+
+ geo.addAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( buffers.weightsIndices, 4 ) );
+
+ geo.addAttribute( '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 ) {
+
+ var normalAttribute = new THREE.Float32BufferAttribute( buffers.normal, 3 );
+
+ var normalMatrix = new THREE.Matrix3().getNormalMatrix( preTransform );
+ normalMatrix.applyToBufferAttribute( normalAttribute );
+
+ geo.addAttribute( 'normal', normalAttribute );
+
+ }
+
+ buffers.uvs.forEach( function ( uvBuffer, i ) {
+
+ // subsequent uv buffers are called 'uv1', 'uv2', ...
+ var name = 'uv' + ( i + 1 ).toString();
+
+ // the first uv buffer is just called 'uv'
+ if ( i === 0 ) {
+
+ name = 'uv';
+
+ }
+
+ geo.addAttribute( 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.
+ var prevMaterialIndex = buffers.materialIndex[ 0 ];
+ var 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 ) {
+
+ var lastGroup = geo.groups[ geo.groups.length - 1 ];
+ var 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, morphTarget, preTransform );
+
+ return geo;
+
+ },
+
+ parseGeoNode: function ( geoNode, skeleton ) {
+
+ var 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 = [];
+
+ var i = 0;
+ while ( geoNode.LayerElementUV[ i ] ) {
+
+ 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: function ( geoInfo ) {
+
+ var buffers = {
+ vertex: [],
+ normal: [],
+ colors: [],
+ uvs: [],
+ materialIndex: [],
+ vertexWeights: [],
+ weightsIndices: [],
+ };
+
+ var polygonIndex = 0;
+ var faceLength = 0;
+ var displayedWeightsWarning = false;
+
+ // these will hold data for a single face
+ var facePositionIndexes = [];
+ var faceNormals = [];
+ var faceColors = [];
+ var faceUVs = [];
+ var faceWeights = [];
+ var faceWeightIndices = [];
+
+ var self = this;
+ geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
+
+ var 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;
+
+ }
+
+ var weightIndices = [];
+ var weights = [];
+
+ facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
+
+ if ( geoInfo.color ) {
+
+ var 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;
+
+ }
+
+ var wIndex = [ 0, 0, 0, 0 ];
+ var Weight = [ 0, 0, 0, 0 ];
+
+ weights.forEach( function ( weight, weightIndex ) {
+
+ var currentWeight = weight;
+ var currentIndex = weightIndices[ weightIndex ];
+
+ Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) {
+
+ if ( currentWeight > comparedWeight ) {
+
+ comparedWeightArray[ comparedWeightIndex ] = currentWeight;
+ currentWeight = comparedWeight;
+
+ var 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 ( var i = 0; i < 4; ++ i ) {
+
+ faceWeights.push( weights[ i ] );
+ faceWeightIndices.push( weightIndices[ i ] );
+
+ }
+
+ }
+
+ if ( geoInfo.normal ) {
+
+ var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal );
+
+ faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] );
+
+ }
+
+ if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+ var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ];
+
+ }
+
+ if ( geoInfo.uv ) {
+
+ geoInfo.uv.forEach( function ( uv, i ) {
+
+ var 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 ) {
+
+ self.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: function ( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
+
+ for ( var 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: function ( parentGeo, parentGeoNode, morphTarget, preTransform ) {
+
+ if ( morphTarget === null ) return;
+
+ parentGeo.morphAttributes.position = [];
+ // parentGeo.morphAttributes.normal = []; // not implemented
+
+ var self = this;
+ morphTarget.rawTargets.forEach( function ( rawTarget ) {
+
+ var morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ];
+
+ if ( morphGeoNode !== undefined ) {
+
+ self.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: function ( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) {
+
+ var morphGeo = new THREE.BufferGeometry();
+ if ( morphGeoNode.attrName ) morphGeo.name = morphGeoNode.attrName;
+
+ var vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : [];
+
+ // make a copy of the parent's vertex positions
+ var vertexPositions = ( parentGeoNode.Vertices !== undefined ) ? parentGeoNode.Vertices.a.slice() : [];
+
+ var morphPositions = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : [];
+ var indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : [];
+
+ for ( var i = 0; i < indices.length; i ++ ) {
+
+ var morphIndex = indices[ i ] * 3;
+
+ // FBX format uses blend shapes rather than morph targets. This can be converted
+ // by additively combining the blend shape positions with the original geometry's positions
+ vertexPositions[ morphIndex ] += morphPositions[ i * 3 ];
+ vertexPositions[ morphIndex + 1 ] += morphPositions[ i * 3 + 1 ];
+ vertexPositions[ morphIndex + 2 ] += morphPositions[ i * 3 + 2 ];
+
+ }
+
+ // TODO: add morph normal support
+ var morphGeoInfo = {
+ vertexIndices: vertexIndices,
+ vertexPositions: vertexPositions,
+ };
+
+ var morphBuffers = this.genBuffers( morphGeoInfo );
+
+ var positionAttribute = new THREE.Float32BufferAttribute( morphBuffers.vertex, 3 );
+ positionAttribute.name = name || morphGeoNode.attrName;
+
+ preTransform.applyToBufferAttribute( positionAttribute );
+
+ parentGeo.morphAttributes.position.push( positionAttribute );
+
+ },
+
+ // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists
+ parseNormals: function ( NormalNode ) {
+
+ var mappingType = NormalNode.MappingInformationType;
+ var referenceType = NormalNode.ReferenceInformationType;
+ var buffer = NormalNode.Normals.a;
+ var 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: function ( UVNode ) {
+
+ var mappingType = UVNode.MappingInformationType;
+ var referenceType = UVNode.ReferenceInformationType;
+ var buffer = UVNode.UV.a;
+ var 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: function ( ColorNode ) {
+
+ var mappingType = ColorNode.MappingInformationType;
+ var referenceType = ColorNode.ReferenceInformationType;
+ var buffer = ColorNode.Colors.a;
+ var 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: function ( MaterialNode ) {
+
+ var mappingType = MaterialNode.MappingInformationType;
+ var referenceType = MaterialNode.ReferenceInformationType;
+
+ if ( mappingType === 'NoMappingInformation' ) {
+
+ return {
+ dataSize: 1,
+ buffer: [ 0 ],
+ indices: [ 0 ],
+ mappingType: 'AllSame',
+ referenceType: referenceType
+ };
+
+ }
+
+ var 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.
+ var materialIndices = [];
+
+ for ( var 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: function ( 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();
+
+ }
+
+ var 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();
+
+ }
+
+ var degree = order - 1;
+
+ var knots = geoNode.KnotVector.a;
+ var controlPoints = [];
+ var pointsValues = geoNode.Points.a;
+
+ for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) {
+
+ controlPoints.push( new THREE.Vector4().fromArray( pointsValues, i ) );
+
+ }
+
+ var startKnot, endKnot;
+
+ if ( geoNode.Form === 'Closed' ) {
+
+ controlPoints.push( controlPoints[ 0 ] );
+
+ } else if ( geoNode.Form === 'Periodic' ) {
+
+ startKnot = degree;
+ endKnot = knots.length - 1 - startKnot;
+
+ for ( var i = 0; i < degree; ++ i ) {
+
+ controlPoints.push( controlPoints[ i ] );
+
+ }
+
+ }
+
+ var curve = new THREE.NURBSCurve( degree, knots, controlPoints, startKnot, endKnot );
+ var vertices = curve.getPoints( controlPoints.length * 7 );
+
+ var positions = new Float32Array( vertices.length * 3 );
+
+ vertices.forEach( function ( vertex, i ) {
+
+ vertex.toArray( positions, i * 3 );
+
+ } );
+
+ var geometry = new THREE.BufferGeometry();
+ geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+
+ return geometry;
+
+ },
+
+ };
+
+ // parse animation data from FBXTree
+ function AnimationParser() {}
+
+ AnimationParser.prototype = {
+
+ constructor: AnimationParser,
+
+ // take raw animation clips and turn them into three.js animation clips
+ parse: function () {
+
+ var animationClips = [];
+
+ var rawClips = this.parseClips();
+
+ if ( rawClips === undefined ) return;
+
+ for ( var key in rawClips ) {
+
+ var rawClip = rawClips[ key ];
+
+ var clip = this.addClip( rawClip );
+
+ animationClips.push( clip );
+
+ }
+
+ return animationClips;
+
+ },
+
+ parseClips: function () {
+
+ // 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;
+
+ var curveNodesMap = this.parseAnimationCurveNodes();
+
+ this.parseAnimationCurves( curveNodesMap );
+
+ var layersMap = this.parseAnimationLayers( curveNodesMap );
+ var 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: function () {
+
+ var rawCurveNodes = fbxTree.Objects.AnimationCurveNode;
+
+ var curveNodesMap = new Map();
+
+ for ( var nodeID in rawCurveNodes ) {
+
+ var rawCurveNode = rawCurveNodes[ nodeID ];
+
+ if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
+
+ var 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: function ( curveNodesMap ) {
+
+ var 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 ( var nodeID in rawCurves ) {
+
+ var animationCurve = {
+
+ id: rawCurves[ nodeID ].id,
+ times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
+ values: rawCurves[ nodeID ].KeyValueFloat.a,
+
+ };
+
+ var relationships = connections.get( animationCurve.id );
+
+ if ( relationships !== undefined ) {
+
+ var animationCurveID = relationships.parents[ 0 ].ID;
+ var 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: function ( curveNodesMap ) {
+
+ var rawLayers = fbxTree.Objects.AnimationLayer;
+
+ var layersMap = new Map();
+
+ for ( var nodeID in rawLayers ) {
+
+ var layerCurveNodes = [];
+
+ var connection = connections.get( parseInt( nodeID ) );
+
+ if ( connection !== undefined ) {
+
+ // all the animationCurveNodes used in the layer
+ var children = connection.children;
+
+ children.forEach( function ( child, i ) {
+
+ if ( curveNodesMap.has( child.ID ) ) {
+
+ var 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 ) {
+
+ var modelID = connections.get( child.ID ).parents.filter( function ( parent ) {
+
+ return parent.relationship !== undefined;
+
+ } )[ 0 ].ID;
+
+ if ( modelID !== undefined ) {
+
+ var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
+
+ var node = {
+
+ modelName: 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 ) {
+
+ var deformerID = connections.get( child.ID ).parents.filter( function ( parent ) {
+
+ return parent.relationship !== undefined;
+
+ } )[ 0 ].ID;
+
+ var morpherID = connections.get( deformerID ).parents[ 0 ].ID;
+ var geoID = connections.get( morpherID ).parents[ 0 ].ID;
+
+ // assuming geometry is not used in more than one model
+ var modelID = connections.get( geoID ).parents[ 0 ].ID;
+
+ var rawModel = fbxTree.Objects.Model[ modelID ];
+
+ var node = {
+
+ modelName: 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: function ( layersMap ) {
+
+ var rawStacks = fbxTree.Objects.AnimationStack;
+
+ // connect the stacks (clips) up to the layers
+ var rawClips = {};
+
+ for ( var nodeID in rawStacks ) {
+
+ var 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.' );
+
+ }
+
+ var layer = layersMap.get( children[ 0 ].ID );
+
+ rawClips[ nodeID ] = {
+
+ name: rawStacks[ nodeID ].attrName,
+ layer: layer,
+
+ };
+
+ }
+
+ return rawClips;
+
+ },
+
+ addClip: function ( rawClip ) {
+
+ var tracks = [];
+
+ var self = this;
+ rawClip.layer.forEach( function ( rawTracks ) {
+
+ tracks = tracks.concat( self.generateTracks( rawTracks ) );
+
+ } );
+
+ return new THREE.AnimationClip( rawClip.name, - 1, tracks );
+
+ },
+
+ generateTracks: function ( rawTracks ) {
+
+ var tracks = [];
+
+ var initialPosition = new THREE.Vector3();
+ var initialRotation = new THREE.Quaternion();
+ var 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 ) {
+
+ var 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 ) {
+
+ var 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 ) {
+
+ var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
+ if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
+
+ }
+
+ if ( rawTracks.DeformPercent !== undefined ) {
+
+ var morphTrack = this.generateMorphTrack( rawTracks );
+ if ( morphTrack !== undefined ) tracks.push( morphTrack );
+
+ }
+
+ return tracks;
+
+ },
+
+ generateVectorTrack: function ( modelName, curves, initialValue, type ) {
+
+ var times = this.getTimesForAllAxes( curves );
+ var values = this.getKeyframeTrackValues( times, curves, initialValue );
+
+ return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values );
+
+ },
+
+ generateRotationTrack: function ( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
+
+ if ( curves.x !== undefined ) {
+
+ this.interpolateRotations( curves.x );
+ curves.x.values = curves.x.values.map( THREE.Math.degToRad );
+
+ }
+ if ( curves.y !== undefined ) {
+
+ this.interpolateRotations( curves.y );
+ curves.y.values = curves.y.values.map( THREE.Math.degToRad );
+
+ }
+ if ( curves.z !== undefined ) {
+
+ this.interpolateRotations( curves.z );
+ curves.z.values = curves.z.values.map( THREE.Math.degToRad );
+
+ }
+
+ var times = this.getTimesForAllAxes( curves );
+ var values = this.getKeyframeTrackValues( times, curves, initialValue );
+
+ if ( preRotation !== undefined ) {
+
+ preRotation = preRotation.map( THREE.Math.degToRad );
+ preRotation.push( eulerOrder );
+
+ preRotation = new THREE.Euler().fromArray( preRotation );
+ preRotation = new THREE.Quaternion().setFromEuler( preRotation );
+
+ }
+
+ if ( postRotation !== undefined ) {
+
+ postRotation = postRotation.map( THREE.Math.degToRad );
+ postRotation.push( eulerOrder );
+
+ postRotation = new THREE.Euler().fromArray( postRotation );
+ postRotation = new THREE.Quaternion().setFromEuler( postRotation ).inverse();
+
+ }
+
+ var quaternion = new THREE.Quaternion();
+ var euler = new THREE.Euler();
+
+ var quaternionValues = [];
+
+ for ( var 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: function ( rawTracks ) {
+
+ var curves = rawTracks.DeformPercent.curves.morph;
+ var values = curves.values.map( function ( val ) {
+
+ return val / 100;
+
+ } );
+
+ var 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: function ( curves ) {
+
+ var 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 and remove duplicates
+ times = times.sort( function ( a, b ) {
+
+ return a - b;
+
+ } ).filter( function ( elem, index, array ) {
+
+ return array.indexOf( elem ) == index;
+
+ } );
+
+ return times;
+
+ },
+
+ getKeyframeTrackValues: function ( times, curves, initialValue ) {
+
+ var prevValue = initialValue;
+
+ var values = [];
+
+ var xIndex = - 1;
+ var yIndex = - 1;
+ var 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 ) {
+
+ var 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 ) {
+
+ var yValue = curves.y.values[ yIndex ];
+ values.push( yValue );
+ prevValue[ 1 ] = yValue;
+
+ } else {
+
+ values.push( prevValue[ 1 ] );
+
+ }
+
+ if ( zIndex !== - 1 ) {
+
+ var zValue = curves.z.values[ zIndex ];
+ values.push( zValue );
+ prevValue[ 2 ] = zValue;
+
+ } else {
+
+ values.push( prevValue[ 2 ] );
+
+ }
+
+ } );
+
+ return values;
+
+ },
+
+ // Rotations are defined as 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: function ( curve ) {
+
+ for ( var i = 1; i < curve.values.length; i ++ ) {
+
+ var initialValue = curve.values[ i - 1 ];
+ var valuesSpan = curve.values[ i ] - initialValue;
+
+ var absoluteSpan = Math.abs( valuesSpan );
+
+ if ( absoluteSpan >= 180 ) {
+
+ var numSubIntervals = absoluteSpan / 180;
+
+ var step = valuesSpan / numSubIntervals;
+ var nextValue = initialValue + step;
+
+ var initialTime = curve.times[ i - 1 ];
+ var timeSpan = curve.times[ i ] - initialTime;
+ var interval = timeSpan / numSubIntervals;
+ var nextTime = initialTime + interval;
+
+ var interpolatedTimes = [];
+ var 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
+ function TextParser() {}
+
+ TextParser.prototype = {
+
+ constructor: TextParser,
+
+ getPrevNode: function () {
+
+ return this.nodeStack[ this.currentIndent - 2 ];
+
+ },
+
+ getCurrentNode: function () {
+
+ return this.nodeStack[ this.currentIndent - 1 ];
+
+ },
+
+ getCurrentProp: function () {
+
+ return this.currentProp;
+
+ },
+
+ pushStack: function ( node ) {
+
+ this.nodeStack.push( node );
+ this.currentIndent += 1;
+
+ },
+
+ popStack: function () {
+
+ this.nodeStack.pop();
+ this.currentIndent -= 1;
+
+ },
+
+ setCurrentProp: function ( val, name ) {
+
+ this.currentProp = val;
+ this.currentPropName = name;
+
+ },
+
+ parse: function ( text ) {
+
+ this.currentIndent = 0;
+
+ this.allNodes = new FBXTree();
+ this.nodeStack = [];
+ this.currentProp = [];
+ this.currentPropName = '';
+
+ var self = this;
+
+ var split = text.split( /[\r\n]+/ );
+
+ split.forEach( function ( line, i ) {
+
+ var matchComment = line.match( /^[\s\t]*;/ );
+ var matchEmpty = line.match( /^[\s\t]*$/ );
+
+ if ( matchComment || matchEmpty ) return;
+
+ var matchBeginning = line.match( '^\\t{' + self.currentIndent + '}(\\w+):(.*){', '' );
+ var matchProperty = line.match( '^\\t{' + ( self.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' );
+ var matchEnd = line.match( '^\\t{' + ( self.currentIndent - 1 ) + '}}' );
+
+ if ( matchBeginning ) {
+
+ self.parseNodeBegin( line, matchBeginning );
+
+ } else if ( matchProperty ) {
+
+ self.parseNodeProperty( line, matchProperty, split[ ++ i ] );
+
+ } else if ( matchEnd ) {
+
+ self.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
+ self.parseNodePropertyContinued( line );
+
+ }
+
+ } );
+
+ return this.allNodes;
+
+ },
+
+ parseNodeBegin: function ( line, property ) {
+
+ var nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' );
+
+ var nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) {
+
+ return attr.trim().replace( /^"/, '' ).replace( /"$/, '' );
+
+ } );
+
+ var node = { name: nodeName };
+ var attrs = this.parseNodeAttr( nodeAttrs );
+
+ var 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: function ( attrs ) {
+
+ var id = attrs[ 0 ];
+
+ if ( attrs[ 0 ] !== '' ) {
+
+ id = parseInt( attrs[ 0 ] );
+
+ if ( isNaN( id ) ) {
+
+ id = attrs[ 0 ];
+
+ }
+
+ }
+
+ var name = '', type = '';
+
+ if ( attrs.length > 1 ) {
+
+ name = attrs[ 1 ].replace( /^(\w+)::/, '' );
+ type = attrs[ 2 ];
+
+ }
+
+ return { id: id, name: name, type: type };
+
+ },
+
+ parseNodeProperty: function ( line, property, contentLine ) {
+
+ var propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
+ var 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();
+
+ }
+
+ var currentNode = this.getCurrentNode();
+ var parentName = currentNode.name;
+
+ if ( parentName === 'Properties70' ) {
+
+ this.parseNodeSpecialProperty( line, propName, propValue );
+ return;
+
+ }
+
+ // Connections
+ if ( propName === 'C' ) {
+
+ var connProps = propValue.split( ',' ).slice( 1 );
+ var from = parseInt( connProps[ 0 ] );
+ var to = parseInt( connProps[ 1 ] );
+
+ var 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: function ( line ) {
+
+ var 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: function ( 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" ]
+ var props = propValue.split( '",' ).map( function ( prop ) {
+
+ return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' );
+
+ } );
+
+ var innerPropName = props[ 0 ];
+ var innerPropType1 = props[ 1 ];
+ var innerPropType2 = props[ 2 ];
+ var innerPropFlag = props[ 3 ];
+ var 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
+ function BinaryParser() {}
+
+ BinaryParser.prototype = {
+
+ constructor: BinaryParser,
+
+ parse: function ( buffer ) {
+
+ var reader = new BinaryReader( buffer );
+ reader.skip( 23 ); // skip magic 23 bytes
+
+ var version = reader.getUint32();
+
+ console.log( 'THREE.FBXLoader: FBX binary version: ' + version );
+
+ var allNodes = new FBXTree();
+
+ while ( ! this.endOfContent( reader ) ) {
+
+ var 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: function ( 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: function ( reader, version ) {
+
+ var node = {};
+
+ // The first three data sizes depends on version.
+ var endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+ var numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+
+ // note: do not remove this even if you get a linter warning as it moves the buffer forward
+ var propertyListLen = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+
+ var nameLen = reader.getUint8();
+ var name = reader.getString( nameLen );
+
+ // Regards this node as NULL-record if endOffset is zero
+ if ( endOffset === 0 ) return null;
+
+ var propertyList = [];
+
+ for ( var i = 0; i < numProperties; i ++ ) {
+
+ propertyList.push( this.parseProperty( reader ) );
+
+ }
+
+ // Regards the first three elements in propertyList as id, attrName, and attrType
+ var id = propertyList.length > 0 ? propertyList[ 0 ] : '';
+ var attrName = propertyList.length > 1 ? propertyList[ 1 ] : '';
+ var 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() ) {
+
+ var 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: function ( name, node, subNode ) {
+
+ // special case: child node is single property
+ if ( subNode.singleProperty === true ) {
+
+ var 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' ) {
+
+ var 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' ) {
+
+ var keys = Object.keys( subNode );
+
+ keys.forEach( function ( key ) {
+
+ node[ key ] = subNode[ key ];
+
+ } );
+
+ } else if ( name === 'Properties70' && subNode.name === 'P' ) {
+
+ var innerPropName = subNode.propertyList[ 0 ];
+ var innerPropType1 = subNode.propertyList[ 1 ];
+ var innerPropType2 = subNode.propertyList[ 2 ];
+ var innerPropFlag = subNode.propertyList[ 3 ];
+ var 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: function ( reader ) {
+
+ var type = reader.getString( 1 );
+
+ 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':
+ var length = reader.getUint32();
+ return reader.getArrayBuffer( length );
+
+ case 'S':
+ var length = reader.getUint32();
+ return reader.getString( length );
+
+ case 'Y':
+ return reader.getInt16();
+
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'f':
+ case 'i':
+ case 'l':
+
+ var arrayLength = reader.getUint32();
+ var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed
+ var 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 Zlib === 'undefined' ) {
+
+ console.error( 'THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js' );
+
+ }
+
+ var inflate = new Zlib.Inflate( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef
+ var reader2 = new BinaryReader( inflate.decompress().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 );
+
+ }
+
+ }
+
+ };
+
+ function BinaryReader( buffer, littleEndian ) {
+
+ this.dv = new DataView( buffer );
+ this.offset = 0;
+ this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true;
+
+ }
+
+ BinaryReader.prototype = {
+
+ constructor: BinaryReader,
+
+ getOffset: function () {
+
+ return this.offset;
+
+ },
+
+ size: function () {
+
+ return this.dv.buffer.byteLength;
+
+ },
+
+ skip: function ( 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: function () {
+
+ return ( this.getUint8() & 1 ) === 1;
+
+ },
+
+ getBooleanArray: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getBoolean() );
+
+ }
+
+ return a;
+
+ },
+
+ getUint8: function () {
+
+ var value = this.dv.getUint8( this.offset );
+ this.offset += 1;
+ return value;
+
+ },
+
+ getInt16: function () {
+
+ var value = this.dv.getInt16( this.offset, this.littleEndian );
+ this.offset += 2;
+ return value;
+
+ },
+
+ getInt32: function () {
+
+ var value = this.dv.getInt32( this.offset, this.littleEndian );
+ this.offset += 4;
+ return value;
+
+ },
+
+ getInt32Array: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getInt32() );
+
+ }
+
+ return a;
+
+ },
+
+ getUint32: function () {
+
+ var 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: function () {
+
+ var 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: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getInt64() );
+
+ }
+
+ return a;
+
+ },
+
+ // Note: see getInt64() comment
+ getUint64: function () {
+
+ var low, high;
+
+ if ( this.littleEndian ) {
+
+ low = this.getUint32();
+ high = this.getUint32();
+
+ } else {
+
+ high = this.getUint32();
+ low = this.getUint32();
+
+ }
+
+ return high * 0x100000000 + low;
+
+ },
+
+ getFloat32: function () {
+
+ var value = this.dv.getFloat32( this.offset, this.littleEndian );
+ this.offset += 4;
+ return value;
+
+ },
+
+ getFloat32Array: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getFloat32() );
+
+ }
+
+ return a;
+
+ },
+
+ getFloat64: function () {
+
+ var value = this.dv.getFloat64( this.offset, this.littleEndian );
+ this.offset += 8;
+ return value;
+
+ },
+
+ getFloat64Array: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getFloat64() );
+
+ }
+
+ return a;
+
+ },
+
+ getArrayBuffer: function ( size ) {
+
+ var value = this.dv.buffer.slice( this.offset, this.offset + size );
+ this.offset += size;
+ return value;
+
+ },
+
+ getString: function ( size ) {
+
+ // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a[ i ] = this.getUint8();
+
+ }
+
+ var 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)
+ function FBXTree() {}
+
+ FBXTree.prototype = {
+
+ constructor: FBXTree,
+
+ add: function ( key, val ) {
+
+ this[ key ] = val;
+
+ },
+
+ };
+
+ // ************** UTILITY FUNCTIONS **************
+
+ function isFbxFormatBinary( buffer ) {
+
+ var CORRECT = 'Kaydara FBX Binary \0';
+
+ return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length );
+
+ }
+
+ function isFbxFormatASCII( text ) {
+
+ var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ];
+
+ var cursor = 0;
+
+ function read( offset ) {
+
+ var result = text[ offset - 1 ];
+ text = text.slice( cursor + offset );
+ cursor ++;
+ return result;
+
+ }
+
+ for ( var i = 0; i < CORRECT.length; ++ i ) {
+
+ var num = read( 1 );
+ if ( num === CORRECT[ i ] ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ function getFbxVersion( text ) {
+
+ var versionRegExp = /FBXVersion: (\d+)/;
+ var match = text.match( versionRegExp );
+ if ( match ) {
+
+ var 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;
+
+ }
+
+ var dataArray = [];
+
+ // extracts the data from the correct position in the FBX array based on indexing type
+ function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ var 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 ];
+
+ var from = index * infoObject.dataSize;
+ var to = from + infoObject.dataSize;
+
+ return slice( dataArray, infoObject.buffer, from, to );
+
+ }
+
+ var tempEuler = new THREE.Euler();
+ var 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 ) {
+
+ var lTranslationM = new THREE.Matrix4();
+ var lPreRotationM = new THREE.Matrix4();
+ var lRotationM = new THREE.Matrix4();
+ var lPostRotationM = new THREE.Matrix4();
+
+ var lScalingM = new THREE.Matrix4();
+ var lScalingPivotM = new THREE.Matrix4();
+ var lScalingOffsetM = new THREE.Matrix4();
+ var lRotationOffsetM = new THREE.Matrix4();
+ var lRotationPivotM = new THREE.Matrix4();
+
+ var lParentGX = new THREE.Matrix4();
+ var lGlobalT = new THREE.Matrix4();
+
+ var inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0;
+
+ if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) );
+
+ if ( transformData.preRotation ) {
+
+ var array = transformData.preRotation.map( THREE.Math.degToRad );
+ array.push( transformData.eulerOrder );
+ lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+ }
+
+ if ( transformData.rotation ) {
+
+ var array = transformData.rotation.map( THREE.Math.degToRad );
+ array.push( transformData.eulerOrder );
+ lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+ }
+
+ if ( transformData.postRotation ) {
+
+ var array = transformData.postRotation.map( THREE.Math.degToRad );
+ array.push( transformData.eulerOrder );
+ lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+ }
+
+ 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 ) lParentGX = transformData.parentMatrixWorld;
+
+ // Global Rotation
+ var lLRM = lPreRotationM.multiply( lRotationM ).multiply( lPostRotationM );
+ var lParentGRM = new THREE.Matrix4();
+ lParentGX.extractRotation( lParentGRM );
+
+ // Global Shear*Scaling
+ var lParentTM = new THREE.Matrix4();
+ var lLSM;
+ var lParentGSM;
+ var lParentGRSM;
+
+ lParentTM.copyPosition( lParentGX );
+ lParentGRSM = lParentTM.getInverse( lParentTM ).multiply( lParentGX );
+ lParentGSM = lParentGRM.getInverse( lParentGRM ).multiply( lParentGRSM );
+ lLSM = lScalingM;
+
+ var lGlobalRS;
+ if ( inheritType === 0 ) {
+
+ lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM );
+
+ } else if ( inheritType === 1 ) {
+
+ lGlobalRS = lParentGRM.multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM );
+
+ } else {
+
+ var lParentLSM = new THREE.Matrix4().copy( lScalingM );
+
+ var lParentGSM_noLocal = lParentGSM.multiply( lParentLSM.getInverse( lParentLSM ) );
+
+ lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM );
+
+ }
+
+ // Calculate the local transform matrix
+ var lTransform = lTranslationM.multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM.getInverse( lRotationPivotM ) ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM.getInverse( lScalingPivotM ) );
+
+ var lLocalTWithAllPivotAndOffsetInfo = new THREE.Matrix4().copyPosition( lTransform );
+
+ var lGlobalTranslation = lParentGX.multiply( lLocalTWithAllPivotAndOffsetInfo );
+ lGlobalT.copyPosition( lGlobalTranslation );
+
+ lTransform = lGlobalT.multiply( lGlobalRS );
+
+ return lTransform;
+
+ }
+
+ // Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order
+ // ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
+ function getEulerOrder( order ) {
+
+ order = order || 0;
+
+ var 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 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 ) {
+
+ var 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 ( var i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) {
+
+ a[ j ] = b[ i ];
+
+ }
+
+ }
+
+ function slice( a, b, from, to ) {
+
+ for ( var 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 ) );
+
+ }
+
+ return FBXLoader;
+
+} )();
diff --git a/static/js/three/OBJLoader.js b/static/js/three/OBJLoader.js
new file mode 100644
index 0000000..dddc7a5
--- /dev/null
+++ b/static/js/three/OBJLoader.js
@@ -0,0 +1,797 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.OBJLoader = ( function () {
+
+ // o object_name | g group_name
+ var object_pattern = /^[og]\s*(.+)?/;
+ // mtllib file_reference
+ var material_library_pattern = /^mtllib /;
+ // usemtl material_name
+ var material_use_pattern = /^usemtl /;
+
+ function ParserState() {
+
+ var state = {
+ objects: [],
+ object: {},
+
+ vertices: [],
+ normals: [],
+ colors: [],
+ uvs: [],
+
+ 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;
+
+ }
+
+ var 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: []
+ },
+ materials: [],
+ smooth: true,
+
+ startMaterial: function ( name, libraries ) {
+
+ var 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 );
+
+ }
+
+ var 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 ) {
+
+ var 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 ) {
+
+ var 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 ( var 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' ) {
+
+ var 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 ) {
+
+ var index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+
+ parseNormalIndex: function ( value, len ) {
+
+ var index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+
+ parseUVIndex: function ( value, len ) {
+
+ var index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+ },
+
+ addVertex: function ( a, b, c ) {
+
+ var src = this.vertices;
+ var 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 ) {
+
+ var src = this.vertices;
+ var dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+
+ addVertexLine: function ( a ) {
+
+ var src = this.vertices;
+ var dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+
+ addNormal: function ( a, b, c ) {
+
+ var src = this.normals;
+ var 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 ] );
+
+ },
+
+ addColor: function ( a, b, c ) {
+
+ var src = this.colors;
+ var dst = this.object.geometry.colors;
+
+ 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 ] );
+
+ },
+
+ addUV: function ( a, b, c ) {
+
+ var src = this.uvs;
+ var 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 ] );
+
+ },
+
+ addUVLine: function ( a ) {
+
+ var src = this.uvs;
+ var dst = this.object.geometry.uvs;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+ },
+
+ addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+ var vLen = this.vertices.length;
+
+ var ia = this.parseVertexIndex( a, vLen );
+ var ib = this.parseVertexIndex( b, vLen );
+ var ic = this.parseVertexIndex( c, vLen );
+
+ this.addVertex( ia, ib, ic );
+
+ if ( ua !== undefined && ua !== '' ) {
+
+ var uvLen = this.uvs.length;
+ ia = this.parseUVIndex( ua, uvLen );
+ ib = this.parseUVIndex( ub, uvLen );
+ ic = this.parseUVIndex( uc, uvLen );
+ this.addUV( ia, ib, ic );
+
+ }
+
+ if ( na !== undefined && na !== '' ) {
+
+ // Normals are many times the same. If so, skip function call and parseInt.
+ var nLen = this.normals.length;
+ ia = this.parseNormalIndex( na, nLen );
+
+ ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
+ ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
+
+ this.addNormal( ia, ib, ic );
+
+ }
+
+ if ( this.colors.length > 0 ) {
+
+ this.addColor( ia, ib, ic );
+
+ }
+
+ },
+
+ addPointGeometry: function ( vertices ) {
+
+ this.object.geometry.type = 'Points';
+
+ var vLen = this.vertices.length;
+
+ for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+ }
+
+ },
+
+ addLineGeometry: function ( vertices, uvs ) {
+
+ this.object.geometry.type = 'Line';
+
+ var vLen = this.vertices.length;
+ var uvLen = this.uvs.length;
+
+ for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+ }
+
+ for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+ this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+ }
+
+ }
+
+ };
+
+ state.startObject( '', false );
+
+ return state;
+
+ }
+
+ //
+
+ function OBJLoader( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+ this.materials = null;
+
+ }
+
+ OBJLoader.prototype = {
+
+ constructor: OBJLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.FileLoader( scope.manager );
+ loader.setPath( this.path );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( text ) );
+
+ }, onProgress, onError );
+
+ },
+
+ setPath: function ( value ) {
+
+ this.path = value;
+
+ return this;
+
+ },
+
+ setMaterials: function ( materials ) {
+
+ this.materials = materials;
+
+ return this;
+
+ },
+
+ parse: function ( text ) {
+
+ console.time( 'OBJLoader' );
+
+ var 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, '' );
+
+ }
+
+ var lines = text.split( '\n' );
+ var line = '', lineFirstChar = '';
+ var lineLength = 0;
+ var result = [];
+
+ // Faster to just trim left side of the line. Use if available.
+ var trimLeft = ( typeof ''.trimLeft === 'function' );
+
+ for ( var 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' ) {
+
+ var data = line.split( /\s+/ );
+
+ switch ( data[ 0 ] ) {
+
+ case 'v':
+ state.vertices.push(
+ parseFloat( data[ 1 ] ),
+ parseFloat( data[ 2 ] ),
+ parseFloat( data[ 3 ] )
+ );
+ if ( data.length === 8 ) {
+
+ state.colors.push(
+ parseFloat( data[ 4 ] ),
+ parseFloat( data[ 5 ] ),
+ parseFloat( data[ 6 ] )
+
+ );
+
+ }
+ 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' ) {
+
+ var lineData = line.substr( 1 ).trim();
+ var vertexData = lineData.split( /\s+/ );
+ var faceVertices = [];
+
+ // Parse the face vertex data into an easy to work with format
+
+ for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+ var vertex = vertexData[ j ];
+
+ if ( vertex.length > 0 ) {
+
+ var vertexParts = vertex.split( '/' );
+ faceVertices.push( vertexParts );
+
+ }
+
+ }
+
+ // Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+ var v1 = faceVertices[ 0 ];
+
+ for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+ var v2 = faceVertices[ j ];
+ var 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' ) {
+
+ var lineParts = line.substring( 1 ).trim().split( " " );
+ var lineVertices = [], lineUVs = [];
+
+ if ( line.indexOf( "/" ) === - 1 ) {
+
+ lineVertices = lineParts;
+
+ } else {
+
+ for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+ var 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' ) {
+
+ var lineData = line.substr( 1 ).trim();
+ var 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
+ // var name = result[ 0 ].substr( 1 ).trim();
+ var 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 ( 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 ) {
+
+ var value = result[ 1 ].trim().toLowerCase();
+ state.object.smooth = ( value !== '0' && value !== 'off' );
+
+ } else {
+
+ // ZBrush can produce "s" lines #11707
+ state.object.smooth = true;
+
+ }
+ var material = state.object.currentMaterial();
+ if ( material ) material.smooth = state.object.smooth;
+
+ } else {
+
+ // Handle null terminated files without exception
+ if ( line === '\0' ) continue;
+
+ throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+ }
+
+ }
+
+ state.finalize();
+
+ var container = new THREE.Group();
+ container.materialLibraries = [].concat( state.materialLibraries );
+
+ for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
+
+ var object = state.objects[ i ];
+ var geometry = object.geometry;
+ var materials = object.materials;
+ var isLine = ( geometry.type === 'Line' );
+ var isPoints = ( geometry.type === 'Points' );
+ var hasVertexColors = false;
+
+ // Skip o/g line declarations that did not follow with any faces
+ if ( geometry.vertices.length === 0 ) continue;
+
+ var buffergeometry = new THREE.BufferGeometry();
+
+ buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) );
+
+ if ( geometry.normals.length > 0 ) {
+
+ buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );
+
+ } else {
+
+ buffergeometry.computeVertexNormals();
+
+ }
+
+ if ( geometry.colors.length > 0 ) {
+
+ hasVertexColors = true;
+ buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) );
+
+ }
+
+ if ( geometry.uvs.length > 0 ) {
+
+ buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );
+
+ }
+
+ // Create materials
+
+ var createdMaterials = [];
+
+ for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+ var sourceMaterial = materials[ mi ];
+ var material = undefined;
+
+ 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 ) ) {
+
+ var materialLine = new THREE.LineBasicMaterial();
+ THREE.Material.prototype.copy.call( materialLine, material );
+ materialLine.color.copy( material.color );
+ materialLine.lights = false;
+ material = materialLine;
+
+ } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) {
+
+ var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } );
+ THREE.Material.prototype.copy.call( materialPoints, material );
+ materialPoints.color.copy( material.color );
+ materialPoints.map = material.map;
+ materialPoints.lights = false;
+ material = materialPoints;
+
+ }
+
+ }
+
+ if ( ! material ) {
+
+ 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 ? THREE.VertexColors : THREE.NoColors;
+
+ createdMaterials.push( material );
+
+ }
+
+ // Create mesh
+
+ var mesh;
+
+ if ( createdMaterials.length > 1 ) {
+
+ for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+ var 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 );
+
+ }
+
+ console.timeEnd( 'OBJLoader' );
+
+ return container;
+
+ }
+
+ };
+
+ return OBJLoader;
+
+} )();
diff --git a/static/js/three/OrbitControls.js b/static/js/three/OrbitControls.js
new file mode 100644
index 0000000..001dc53
--- /dev/null
+++ b/static/js/three/OrbitControls.js
@@ -0,0 +1,1051 @@
+/**
+ * @author qiao / https://github.com/qiao
+ * @author mrdoob / http://mrdoob.com
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author erich666 / http://erichaines.com
+ */
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+// Orbit - left mouse / touch: one-finger move
+// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+THREE.OrbitControls = function ( object, domElement ) {
+
+ this.object = object;
+
+ this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+ // Set to false to disable this control
+ this.enabled = true;
+
+ // "target" sets the location of focus, where the object orbits around
+ this.target = new THREE.Vector3();
+
+ // How far you can dolly in and out ( PerspectiveCamera only )
+ this.minDistance = 0;
+ this.maxDistance = Infinity;
+
+ // How far you can zoom in and out ( OrthographicCamera only )
+ this.minZoom = 0;
+ this.maxZoom = Infinity;
+
+ // How far you can orbit vertically, upper and lower limits.
+ // Range is 0 to Math.PI radians.
+ this.minPolarAngle = 0; // radians
+ this.maxPolarAngle = Math.PI; // radians
+
+ // How far you can orbit horizontally, upper and lower limits.
+ // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
+ this.minAzimuthAngle = - Infinity; // radians
+ this.maxAzimuthAngle = Infinity; // radians
+
+ // Set to true to enable damping (inertia)
+ // If damping is enabled, you must call controls.update() in your animation loop
+ this.enableDamping = false;
+ this.dampingFactor = 0.25;
+
+ // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+ // Set to false to disable zooming
+ this.enableZoom = true;
+ this.zoomSpeed = 1.0;
+
+ // Set to false to disable rotating
+ this.enableRotate = true;
+ this.rotateSpeed = 1.0;
+
+ // Set to false to disable panning
+ this.enablePan = true;
+ this.panSpeed = 1.0;
+ this.screenSpacePanning = false; // if true, pan in screen-space
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+
+ // Set to true to automatically rotate around the target
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
+ this.autoRotate = false;
+ this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
+
+ // Set to false to disable use of the keys
+ this.enableKeys = true;
+
+ // The four arrow keys
+ this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
+
+ // Mouse buttons
+ this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT };
+
+ // for reset
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.zoom0 = this.object.zoom;
+
+ //
+ // public methods
+ //
+
+ this.getPolarAngle = function () {
+
+ return spherical.phi;
+
+ };
+
+ this.getAzimuthalAngle = function () {
+
+ return spherical.theta;
+
+ };
+
+ this.saveState = function () {
+
+ scope.target0.copy( scope.target );
+ scope.position0.copy( scope.object.position );
+ scope.zoom0 = scope.object.zoom;
+
+ };
+
+ this.reset = function () {
+
+ scope.target.copy( scope.target0 );
+ scope.object.position.copy( scope.position0 );
+ scope.object.zoom = scope.zoom0;
+
+ scope.object.updateProjectionMatrix();
+ scope.dispatchEvent( changeEvent );
+
+ scope.update();
+
+ state = STATE.NONE;
+
+ };
+
+ // this method is exposed, but perhaps it would be better if we can make it private...
+ this.update = function () {
+
+ var offset = new THREE.Vector3();
+
+ // so camera.up is the orbit axis
+ var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+ var quatInverse = quat.clone().inverse();
+
+ var lastPosition = new THREE.Vector3();
+ var lastQuaternion = new THREE.Quaternion();
+
+ return function update() {
+
+ var position = scope.object.position;
+
+ offset.copy( position ).sub( scope.target );
+
+ // rotate offset to "y-axis-is-up" space
+ offset.applyQuaternion( quat );
+
+ // angle from z-axis around y-axis
+ spherical.setFromVector3( offset );
+
+ if ( scope.autoRotate && state === STATE.NONE ) {
+
+ rotateLeft( getAutoRotationAngle() );
+
+ }
+
+ spherical.theta += sphericalDelta.theta;
+ spherical.phi += sphericalDelta.phi;
+
+ // restrict theta to be between desired limits
+ spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
+
+ // restrict phi to be between desired limits
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+
+ spherical.makeSafe();
+
+
+ spherical.radius *= scale;
+
+ // restrict radius to be between desired limits
+ spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+
+ // move target to panned location
+ scope.target.add( panOffset );
+
+ offset.setFromSpherical( spherical );
+
+ // rotate offset back to "camera-up-vector-is-up" space
+ offset.applyQuaternion( quatInverse );
+
+ position.copy( scope.target ).add( offset );
+
+ scope.object.lookAt( scope.target );
+
+ if ( scope.enableDamping === true ) {
+
+ sphericalDelta.theta *= ( 1 - scope.dampingFactor );
+ sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
+
+ } else {
+
+ sphericalDelta.set( 0, 0, 0 );
+
+ panOffset.set( 0, 0, 0 );
+
+ }
+
+ scale = 1;
+
+ // update condition is:
+ // min(camera displacement, camera rotation in radians)^2 > EPS
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+ if ( zoomChanged ||
+ lastPosition.distanceToSquared( scope.object.position ) > EPS ||
+ 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+ scope.dispatchEvent( changeEvent );
+
+ lastPosition.copy( scope.object.position );
+ lastQuaternion.copy( scope.object.quaternion );
+ zoomChanged = false;
+
+ return true;
+
+ }
+
+ return false;
+
+ };
+
+ }();
+
+ this.dispose = function () {
+
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
+ scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
+
+ scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
+ scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
+ scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ window.removeEventListener( 'keydown', onKeyDown, false );
+
+ //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+ };
+
+ //
+ // internals
+ //
+
+ var scope = this;
+
+ var changeEvent = { type: 'change' };
+ var startEvent = { type: 'start' };
+ var endEvent = { type: 'end' };
+
+ var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 };
+
+ var state = STATE.NONE;
+
+ var EPS = 0.000001;
+
+ // current position in spherical coordinates
+ var spherical = new THREE.Spherical();
+ var sphericalDelta = new THREE.Spherical();
+
+ var scale = 1;
+ var panOffset = new THREE.Vector3();
+ var zoomChanged = false;
+
+ var rotateStart = new THREE.Vector2();
+ var rotateEnd = new THREE.Vector2();
+ var rotateDelta = new THREE.Vector2();
+
+ var panStart = new THREE.Vector2();
+ var panEnd = new THREE.Vector2();
+ var panDelta = new THREE.Vector2();
+
+ var dollyStart = new THREE.Vector2();
+ var dollyEnd = new THREE.Vector2();
+ var dollyDelta = new THREE.Vector2();
+
+ function getAutoRotationAngle() {
+
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+ }
+
+ function getZoomScale() {
+
+ return Math.pow( 0.95, scope.zoomSpeed );
+
+ }
+
+ function rotateLeft( angle ) {
+
+ sphericalDelta.theta -= angle;
+
+ }
+
+ function rotateUp( angle ) {
+
+ sphericalDelta.phi -= angle;
+
+ }
+
+ var panLeft = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panLeft( distance, objectMatrix ) {
+
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+ v.multiplyScalar( - distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ var panUp = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panUp( distance, objectMatrix ) {
+
+ if ( scope.screenSpacePanning === true ) {
+
+ v.setFromMatrixColumn( objectMatrix, 1 );
+
+ } else {
+
+ v.setFromMatrixColumn( objectMatrix, 0 );
+ v.crossVectors( scope.object.up, v );
+
+ }
+
+ v.multiplyScalar( distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ // deltaX and deltaY are in pixels; right and down are positive
+ var pan = function () {
+
+ var offset = new THREE.Vector3();
+
+ return function pan( deltaX, deltaY ) {
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ // perspective
+ var position = scope.object.position;
+ offset.copy( position ).sub( scope.target );
+ var targetDistance = offset.length();
+
+ // half of the fov is center to top of screen
+ targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
+
+ // we use only clientHeight here so aspect ratio does not distort speed
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ // orthographic
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+ } else {
+
+ // camera neither orthographic nor perspective
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+ scope.enablePan = false;
+
+ }
+
+ };
+
+ }();
+
+ function dollyIn( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale /= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ function dollyOut( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale *= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ //
+ // event callbacks - update the object state
+ //
+
+ function handleMouseDownRotate( event ) {
+
+ //console.log( 'handleMouseDownRotate' );
+
+ rotateStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownDolly( event ) {
+
+ //console.log( 'handleMouseDownDolly' );
+
+ dollyStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownPan( event ) {
+
+ //console.log( 'handleMouseDownPan' );
+
+ panStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseMoveRotate( event ) {
+
+ //console.log( 'handleMouseMoveRotate' );
+
+ rotateEnd.set( event.clientX, event.clientY );
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMoveDolly( event ) {
+
+ //console.log( 'handleMouseMoveDolly' );
+
+ dollyEnd.set( event.clientX, event.clientY );
+
+ dollyDelta.subVectors( dollyEnd, dollyStart );
+
+ if ( dollyDelta.y > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ } else if ( dollyDelta.y < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ }
+
+ dollyStart.copy( dollyEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMovePan( event ) {
+
+ //console.log( 'handleMouseMovePan' );
+
+ panEnd.set( event.clientX, event.clientY );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseUp( event ) {
+
+ // console.log( 'handleMouseUp' );
+
+ }
+
+ function handleMouseWheel( event ) {
+
+ // console.log( 'handleMouseWheel' );
+
+ if ( event.deltaY < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ } else if ( event.deltaY > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleKeyDown( event ) {
+
+ //console.log( 'handleKeyDown' );
+
+ switch ( event.keyCode ) {
+
+ case scope.keys.UP:
+ pan( 0, scope.keyPanSpeed );
+ scope.update();
+ break;
+
+ case scope.keys.BOTTOM:
+ pan( 0, - scope.keyPanSpeed );
+ scope.update();
+ break;
+
+ case scope.keys.LEFT:
+ pan( scope.keyPanSpeed, 0 );
+ scope.update();
+ break;
+
+ case scope.keys.RIGHT:
+ pan( - scope.keyPanSpeed, 0 );
+ scope.update();
+ break;
+
+ }
+
+ }
+
+ function handleTouchStartRotate( event ) {
+
+ //console.log( 'handleTouchStartRotate' );
+
+ rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ }
+
+ function handleTouchStartDollyPan( event ) {
+
+ //console.log( 'handleTouchStartDollyPan' );
+
+ if ( scope.enableZoom ) {
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyStart.set( 0, distance );
+
+ }
+
+ if ( scope.enablePan ) {
+
+ var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+ var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+
+ panStart.set( x, y );
+
+ }
+
+ }
+
+ function handleTouchMoveRotate( event ) {
+
+ //console.log( 'handleTouchMoveRotate' );
+
+ rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleTouchMoveDollyPan( event ) {
+
+ //console.log( 'handleTouchMoveDollyPan' );
+
+ if ( scope.enableZoom ) {
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyEnd.set( 0, distance );
+
+ dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+
+ dollyIn( dollyDelta.y );
+
+ dollyStart.copy( dollyEnd );
+
+ }
+
+ if ( scope.enablePan ) {
+
+ var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+ var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+
+ panEnd.set( x, y );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleTouchEnd( event ) {
+
+ //console.log( 'handleTouchEnd' );
+
+ }
+
+ //
+ // event handlers - FSM: listen for events and reset state
+ //
+
+ function onMouseDown( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ switch ( event.button ) {
+
+ case scope.mouseButtons.LEFT:
+
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ } else {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ }
+
+ break;
+
+ case scope.mouseButtons.MIDDLE:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseDownDolly( event );
+
+ state = STATE.DOLLY;
+
+ break;
+
+ case scope.mouseButtons.RIGHT:
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ break;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ document.addEventListener( 'mousemove', onMouseMove, false );
+ document.addEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onMouseMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ switch ( state ) {
+
+ case STATE.ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseMoveRotate( event );
+
+ break;
+
+ case STATE.DOLLY:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseMoveDolly( event );
+
+ break;
+
+ case STATE.PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseMovePan( event );
+
+ break;
+
+ }
+
+ }
+
+ function onMouseUp( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleMouseUp( event );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onMouseWheel( event ) {
+
+ if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ scope.dispatchEvent( startEvent );
+
+ handleMouseWheel( event );
+
+ scope.dispatchEvent( endEvent );
+
+ }
+
+ function onKeyDown( event ) {
+
+ if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
+
+ handleKeyDown( event );
+
+ }
+
+ function onTouchStart( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchStartRotate( event );
+
+ state = STATE.TOUCH_ROTATE;
+
+ break;
+
+ case 2: // two-fingered touch: dolly-pan
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+ handleTouchStartDollyPan( event );
+
+ state = STATE.TOUCH_DOLLY_PAN;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onTouchMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+ if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?
+
+ handleTouchMoveRotate( event );
+
+ break;
+
+ case 2: // two-fingered touch: dolly-pan
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+ if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed?
+
+ handleTouchMoveDollyPan( event );
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ }
+
+ function onTouchEnd( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleTouchEnd( event );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onContextMenu( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ }
+
+ //
+
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
+
+ scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
+
+ scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
+ scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
+ scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
+
+ window.addEventListener( 'keydown', onKeyDown, false );
+
+ // force an update at start
+
+ this.update();
+
+};
+
+THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
+
+Object.defineProperties( THREE.OrbitControls.prototype, {
+
+ center: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
+ return this.target;
+
+ }
+
+ },
+
+ // backward compatibility
+
+ noZoom: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+ return ! this.enableZoom;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+ this.enableZoom = ! value;
+
+ }
+
+ },
+
+ noRotate: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+ return ! this.enableRotate;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+ this.enableRotate = ! value;
+
+ }
+
+ },
+
+ noPan: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+ return ! this.enablePan;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+ this.enablePan = ! value;
+
+ }
+
+ },
+
+ noKeys: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+ return ! this.enableKeys;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+ this.enableKeys = ! value;
+
+ }
+
+ },
+
+ staticMoving: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ return ! this.enableDamping;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ this.enableDamping = ! value;
+
+ }
+
+ },
+
+ dynamicDampingFactor: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ return this.dampingFactor;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ this.dampingFactor = value;
+
+ }
+
+ }
+
+} );
diff --git a/static/js/three/TGALoader.js b/static/js/three/TGALoader.js
new file mode 100644
index 0000000..94937dd
--- /dev/null
+++ b/static/js/three/TGALoader.js
@@ -0,0 +1,548 @@
+/*
+ * @author Daosheng Mu / https://github.com/DaoshengMu/
+ * @author mrdoob / http://mrdoob.com/
+ * @author takahirox / https://github.com/takahirox/
+ */
+
+THREE.TGALoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.TGALoader.prototype = {
+
+ constructor: THREE.TGALoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var texture = new THREE.Texture();
+
+ var loader = new THREE.FileLoader( this.manager );
+ loader.setResponseType( 'arraybuffer' );
+ loader.setPath( this.path );
+
+ loader.load( url, function ( buffer ) {
+
+ texture.image = scope.parse( buffer );
+ texture.needsUpdate = true;
+
+ if ( onLoad !== undefined ) {
+
+ onLoad( texture );
+
+ }
+
+ }, onProgress, onError );
+
+ return texture;
+
+ },
+
+ parse: function ( buffer ) {
+
+ // reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js
+
+ function tgaCheckHeader( header ) {
+
+ switch ( header.image_type ) {
+
+ // check indexed type
+
+ case TGA_TYPE_INDEXED:
+ case TGA_TYPE_RLE_INDEXED:
+ if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) {
+
+ console.error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' );
+
+ }
+ break;
+
+ // check colormap type
+
+ case TGA_TYPE_RGB:
+ case TGA_TYPE_GREY:
+ case TGA_TYPE_RLE_RGB:
+ case TGA_TYPE_RLE_GREY:
+ if ( header.colormap_type ) {
+
+ console.error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
+
+ }
+ break;
+
+ // What the need of a file without data ?
+
+ case TGA_TYPE_NO_DATA:
+ console.error( 'THREE.TGALoader: No data.' );
+
+ // Invalid type ?
+
+ default:
+ console.error( 'THREE.TGALoader: Invalid type "%s".', header.image_type );
+
+ }
+
+ // check image width and height
+
+ if ( header.width <= 0 || header.height <= 0 ) {
+
+ console.error( 'THREE.TGALoader: Invalid image size.' );
+
+ }
+
+ // check image pixel size
+
+ if ( header.pixel_size !== 8 && header.pixel_size !== 16 &&
+ header.pixel_size !== 24 && header.pixel_size !== 32 ) {
+
+ console.error( 'THREE.TGALoader: Invalid pixel size "%s".', header.pixel_size );
+
+ }
+
+ }
+
+ // parse tga image buffer
+
+ function tgaParse( use_rle, use_pal, header, offset, data ) {
+
+ var pixel_data,
+ pixel_size,
+ pixel_total,
+ palettes;
+
+ pixel_size = header.pixel_size >> 3;
+ pixel_total = header.width * header.height * pixel_size;
+
+ // read palettes
+
+ if ( use_pal ) {
+
+ palettes = data.subarray( offset, offset += header.colormap_length * ( header.colormap_size >> 3 ) );
+
+ }
+
+ // read RLE
+
+ if ( use_rle ) {
+
+ pixel_data = new Uint8Array( pixel_total );
+
+ var c, count, i;
+ var shift = 0;
+ var pixels = new Uint8Array( pixel_size );
+
+ while ( shift < pixel_total ) {
+
+ c = data[ offset ++ ];
+ count = ( c & 0x7f ) + 1;
+
+ // RLE pixels
+
+ if ( c & 0x80 ) {
+
+ // bind pixel tmp array
+
+ for ( i = 0; i < pixel_size; ++ i ) {
+
+ pixels[ i ] = data[ offset ++ ];
+
+ }
+
+ // copy pixel array
+
+ for ( i = 0; i < count; ++ i ) {
+
+ pixel_data.set( pixels, shift + i * pixel_size );
+
+ }
+
+ shift += pixel_size * count;
+
+ } else {
+
+ // raw pixels
+
+ count *= pixel_size;
+ for ( i = 0; i < count; ++ i ) {
+
+ pixel_data[ shift + i ] = data[ offset ++ ];
+
+ }
+ shift += count;
+
+ }
+
+ }
+
+ } else {
+
+ // raw pixels
+
+ pixel_data = data.subarray(
+ offset, offset += ( use_pal ? header.width * header.height : pixel_total )
+ );
+
+ }
+
+ return {
+ pixel_data: pixel_data,
+ palettes: palettes
+ };
+
+ }
+
+ function tgaGetImageData8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image, palettes ) {
+
+ var colormap = palettes;
+ var color, i = 0, x, y;
+ var width = header.width;
+
+ for ( y = y_start; y !== y_end; y += y_step ) {
+
+ for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+ color = image[ i ];
+ imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+ imageData[ ( x + width * y ) * 4 + 2 ] = colormap[ ( color * 3 ) + 0 ];
+ imageData[ ( x + width * y ) * 4 + 1 ] = colormap[ ( color * 3 ) + 1 ];
+ imageData[ ( x + width * y ) * 4 + 0 ] = colormap[ ( color * 3 ) + 2 ];
+
+ }
+
+ }
+
+ return imageData;
+
+ }
+
+ function tgaGetImageData16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+ var color, i = 0, x, y;
+ var width = header.width;
+
+ for ( y = y_start; y !== y_end; y += y_step ) {
+
+ for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+ color = image[ i + 0 ] + ( image[ i + 1 ] << 8 ); // Inversed ?
+ imageData[ ( x + width * y ) * 4 + 0 ] = ( color & 0x7C00 ) >> 7;
+ imageData[ ( x + width * y ) * 4 + 1 ] = ( color & 0x03E0 ) >> 2;
+ imageData[ ( x + width * y ) * 4 + 2 ] = ( color & 0x001F ) >> 3;
+ imageData[ ( x + width * y ) * 4 + 3 ] = ( color & 0x8000 ) ? 0 : 255;
+
+ }
+
+ }
+
+ return imageData;
+
+ }
+
+ function tgaGetImageData24bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+ var i = 0, x, y;
+ var width = header.width;
+
+ for ( y = y_start; y !== y_end; y += y_step ) {
+
+ for ( x = x_start; x !== x_end; x += x_step, i += 3 ) {
+
+ imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+ imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+ imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+ imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+
+ }
+
+ }
+
+ return imageData;
+
+ }
+
+ function tgaGetImageData32bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+ var i = 0, x, y;
+ var width = header.width;
+
+ for ( y = y_start; y !== y_end; y += y_step ) {
+
+ for ( x = x_start; x !== x_end; x += x_step, i += 4 ) {
+
+ imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+ imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+ imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+ imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 3 ];
+
+ }
+
+ }
+
+ return imageData;
+
+ }
+
+ function tgaGetImageDataGrey8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+ var color, i = 0, x, y;
+ var width = header.width;
+
+ for ( y = y_start; y !== y_end; y += y_step ) {
+
+ for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+ color = image[ i ];
+ imageData[ ( x + width * y ) * 4 + 0 ] = color;
+ imageData[ ( x + width * y ) * 4 + 1 ] = color;
+ imageData[ ( x + width * y ) * 4 + 2 ] = color;
+ imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+
+ }
+
+ }
+
+ return imageData;
+
+ }
+
+ function tgaGetImageDataGrey16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+ var i = 0, x, y;
+ var width = header.width;
+
+ for ( y = y_start; y !== y_end; y += y_step ) {
+
+ for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+ imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 0 ];
+ imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 0 ];
+ imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+ imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 1 ];
+
+ }
+
+ }
+
+ return imageData;
+
+ }
+
+ function getTgaRGBA( data, width, height, image, palette ) {
+
+ var x_start,
+ y_start,
+ x_step,
+ y_step,
+ x_end,
+ y_end;
+
+ switch ( ( header.flags & TGA_ORIGIN_MASK ) >> TGA_ORIGIN_SHIFT ) {
+
+ default:
+ case TGA_ORIGIN_UL:
+ x_start = 0;
+ x_step = 1;
+ x_end = width;
+ y_start = 0;
+ y_step = 1;
+ y_end = height;
+ break;
+
+ case TGA_ORIGIN_BL:
+ x_start = 0;
+ x_step = 1;
+ x_end = width;
+ y_start = height - 1;
+ y_step = - 1;
+ y_end = - 1;
+ break;
+
+ case TGA_ORIGIN_UR:
+ x_start = width - 1;
+ x_step = - 1;
+ x_end = - 1;
+ y_start = 0;
+ y_step = 1;
+ y_end = height;
+ break;
+
+ case TGA_ORIGIN_BR:
+ x_start = width - 1;
+ x_step = - 1;
+ x_end = - 1;
+ y_start = height - 1;
+ y_step = - 1;
+ y_end = - 1;
+ break;
+
+ }
+
+ if ( use_grey ) {
+
+ switch ( header.pixel_size ) {
+
+ case 8:
+ tgaGetImageDataGrey8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+ break;
+
+ case 16:
+ tgaGetImageDataGrey16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+ break;
+
+ default:
+ console.error( 'THREE.TGALoader: Format not supported.' );
+ break;
+
+ }
+
+ } else {
+
+ switch ( header.pixel_size ) {
+
+ case 8:
+ tgaGetImageData8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image, palette );
+ break;
+
+ case 16:
+ tgaGetImageData16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+ break;
+
+ case 24:
+ tgaGetImageData24bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+ break;
+
+ case 32:
+ tgaGetImageData32bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+ break;
+
+ default:
+ console.error( 'THREE.TGALoader: Format not supported.' );
+ break;
+
+ }
+
+ }
+
+ // Load image data according to specific method
+ // var func = 'tgaGetImageData' + (use_grey ? 'Grey' : '') + (header.pixel_size) + 'bits';
+ // func(data, y_start, y_step, y_end, x_start, x_step, x_end, width, image, palette );
+ return data;
+
+ }
+
+ // TGA constants
+
+ var TGA_TYPE_NO_DATA = 0,
+ TGA_TYPE_INDEXED = 1,
+ TGA_TYPE_RGB = 2,
+ TGA_TYPE_GREY = 3,
+ TGA_TYPE_RLE_INDEXED = 9,
+ TGA_TYPE_RLE_RGB = 10,
+ TGA_TYPE_RLE_GREY = 11,
+
+ TGA_ORIGIN_MASK = 0x30,
+ TGA_ORIGIN_SHIFT = 0x04,
+ TGA_ORIGIN_BL = 0x00,
+ TGA_ORIGIN_BR = 0x01,
+ TGA_ORIGIN_UL = 0x02,
+ TGA_ORIGIN_UR = 0x03;
+
+ if ( buffer.length < 19 ) console.error( 'THREE.TGALoader: Not enough data to contain header.' );
+
+ var content = new Uint8Array( buffer ),
+ offset = 0,
+ header = {
+ id_length: content[ offset ++ ],
+ colormap_type: content[ offset ++ ],
+ image_type: content[ offset ++ ],
+ colormap_index: content[ offset ++ ] | content[ offset ++ ] << 8,
+ colormap_length: content[ offset ++ ] | content[ offset ++ ] << 8,
+ colormap_size: content[ offset ++ ],
+ origin: [
+ content[ offset ++ ] | content[ offset ++ ] << 8,
+ content[ offset ++ ] | content[ offset ++ ] << 8
+ ],
+ width: content[ offset ++ ] | content[ offset ++ ] << 8,
+ height: content[ offset ++ ] | content[ offset ++ ] << 8,
+ pixel_size: content[ offset ++ ],
+ flags: content[ offset ++ ]
+ };
+
+ // check tga if it is valid format
+
+ tgaCheckHeader( header );
+
+ if ( header.id_length + offset > buffer.length ) {
+
+ console.error( 'THREE.TGALoader: No data.' );
+
+ }
+
+ // skip the needn't data
+
+ offset += header.id_length;
+
+ // get targa information about RLE compression and palette
+
+ var use_rle = false,
+ use_pal = false,
+ use_grey = false;
+
+ switch ( header.image_type ) {
+
+ case TGA_TYPE_RLE_INDEXED:
+ use_rle = true;
+ use_pal = true;
+ break;
+
+ case TGA_TYPE_INDEXED:
+ use_pal = true;
+ break;
+
+ case TGA_TYPE_RLE_RGB:
+ use_rle = true;
+ break;
+
+ case TGA_TYPE_RGB:
+ break;
+
+ case TGA_TYPE_RLE_GREY:
+ use_rle = true;
+ use_grey = true;
+ break;
+
+ case TGA_TYPE_GREY:
+ use_grey = true;
+ break;
+
+ }
+
+ //
+
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = header.width;
+ canvas.height = header.height;
+
+ var context = canvas.getContext( '2d' );
+ var imageData = context.createImageData( header.width, header.height );
+
+ var result = tgaParse( use_rle, use_pal, header, offset, content );
+ var rgbaData = getTgaRGBA( imageData.data, header.width, header.height, result.pixel_data, result.palettes );
+
+ context.putImageData( imageData, 0, 0 );
+
+ return canvas;
+
+ },
+
+ setPath: function ( value ) {
+
+ this.path = value;
+ return this;
+
+ }
+
+};
diff --git a/static/js/three/inflate.min.js b/static/js/three/inflate.min.js
new file mode 100644
index 0000000..312b077
--- /dev/null
+++ b/static/js/three/inflate.min.js
@@ -0,0 +1,15 @@
+/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';var l=void 0,aa=this;function r(c,d){var a=c.split("."),b=aa;!(a[0]in b)&&b.execScript&&b.execScript("var "+a[0]);for(var e;a.length&&(e=a.shift());)!a.length&&d!==l?b[e]=d:b=b[e]?b[e]:b[e]={}};var t="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function v(c){var d=c.length,a=0,b=Number.POSITIVE_INFINITY,e,f,g,h,k,m,n,p,s,x;for(p=0;pa&&(a=c[p]),c[p]>=1;x=g<<16|p;for(s=m;s>>=1;switch(c){case 0:var d=this.input,a=this.a,b=this.c,e=this.b,f=d.length,g=l,h=l,k=b.length,m=l;this.d=this.f=0;if(a+1>=f)throw Error("invalid uncompressed block header: LEN");g=d[a++]|d[a++]<<8;if(a+1>=f)throw Error("invalid uncompressed block header: NLEN");h=d[a++]|d[a++]<<8;if(g===~h)throw Error("invalid uncompressed block header: length verify");if(a+g>d.length)throw Error("input buffer is broken");switch(this.i){case A:for(;e+
+g>b.length;){m=k-e;g-=m;if(t)b.set(d.subarray(a,a+m),e),e+=m,a+=m;else for(;m--;)b[e++]=d[a++];this.b=e;b=this.e();e=this.b}break;case y:for(;e+g>b.length;)b=this.e({p:2});break;default:throw Error("invalid inflate mode");}if(t)b.set(d.subarray(a,a+g),e),e+=g,a+=g;else for(;g--;)b[e++]=d[a++];this.a=a;this.b=e;this.c=b;break;case 1:this.j(ba,ca);break;case 2:for(var n=C(this,5)+257,p=C(this,5)+1,s=C(this,4)+4,x=new (t?Uint8Array:Array)(D.length),S=l,T=l,U=l,u=l,M=l,F=l,z=l,q=l,V=l,q=0;q=P?8:255>=P?9:279>=P?7:8;var ba=v(O),Q=new (t?Uint8Array:Array)(30),R,ga;R=0;for(ga=Q.length;R=g)throw Error("input buffer is broken");a|=e[f++]<>>d;c.d=b-d;c.a=f;return h}
+function E(c,d){for(var a=c.f,b=c.d,e=c.input,f=c.a,g=e.length,h=d[0],k=d[1],m,n;b=g);)a|=e[f++]<>>16;if(n>b)throw Error("invalid code length: "+n);c.f=a>>n;c.d=b-n;c.a=f;return m&65535}
+w.prototype.j=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length-258,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(this.b=b,a=this.e(),b=this.b),a[b++]=f;else{g=f-257;k=I[g];0=e&&(this.b=b,a=this.e(),b=this.b);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
+w.prototype.w=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(a=this.e(),e=a.length),a[b++]=f;else{g=f-257;k=I[g];0e&&(a=this.e(),e=a.length);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
+w.prototype.e=function(){var c=new (t?Uint8Array:Array)(this.b-32768),d=this.b-32768,a,b,e=this.c;if(t)c.set(e.subarray(32768,c.length));else{a=0;for(b=c.length;aa;++a)e[a]=e[d+a];this.b=32768;return e};
+w.prototype.z=function(c){var d,a=this.input.length/this.a+1|0,b,e,f,g=this.input,h=this.c;c&&("number"===typeof c.p&&(a=c.p),"number"===typeof c.u&&(a+=c.u));2>a?(b=(g.length-this.a)/this.o[2],f=258*(b/2)|0,e=fd&&(this.c.length=d),c=this.c);return this.buffer=c};function W(c,d){var a,b;this.input=c;this.a=0;if(d||!(d={}))d.index&&(this.a=d.index),d.verify&&(this.A=d.verify);a=c[this.a++];b=c[this.a++];switch(a&15){case ha:this.method=ha;break;default:throw Error("unsupported compression method");}if(0!==((a<<8)+b)%31)throw Error("invalid fcheck flag:"+((a<<8)+b)%31);if(b&32)throw Error("fdict flag is not supported");this.q=new w(c,{index:this.a,bufferSize:d.bufferSize,bufferType:d.bufferType,resize:d.resize})}
+W.prototype.k=function(){var c=this.input,d,a;d=this.q.k();this.a=this.q.a;if(this.A){a=(c[this.a++]<<24|c[this.a++]<<16|c[this.a++]<<8|c[this.a++])>>>0;var b=d;if("string"===typeof b){var e=b.split(""),f,g;f=0;for(g=e.length;f>>0;b=e}for(var h=1,k=0,m=b.length,n,p=0;0>>0)throw Error("invalid adler-32 checksum");}return d};var ha=8;r("Zlib.Inflate",W);r("Zlib.Inflate.prototype.decompress",W.prototype.k);var X={ADAPTIVE:B.s,BLOCK:B.t},Y,Z,$,ia;if(Object.keys)Y=Object.keys(X);else for(Z in Y=[],$=0,X)Y[$++]=Z;$=0;for(ia=Y.length;$ 0 ) ? 1 : + x;
+
+ };
+
+ }
+
+ if ( 'name' in Function.prototype === false ) {
+
+ // Missing in IE
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
+
+ Object.defineProperty( Function.prototype, 'name', {
+
+ get: function () {
+
+ return this.toString().match( /^\s*function\s*([^\(\s]*)/ )[ 1 ];
+
+ }
+
+ } );
+
+ }
+
+ if ( Object.assign === undefined ) {
+
+ // Missing in IE
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
+
+ ( function () {
+
+ Object.assign = function ( target ) {
+
+ if ( target === undefined || target === null ) {
+
+ throw new TypeError( 'Cannot convert undefined or null to object' );
+
+ }
+
+ var output = Object( target );
+
+ for ( var index = 1; index < arguments.length; index ++ ) {
+
+ var source = arguments[ index ];
+
+ if ( source !== undefined && source !== null ) {
+
+ for ( var nextKey in source ) {
+
+ if ( Object.prototype.hasOwnProperty.call( source, nextKey ) ) {
+
+ output[ nextKey ] = source[ nextKey ];
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return output;
+
+ };
+
+ } )();
+
+ }
+
+ /**
+ * https://github.com/mrdoob/eventdispatcher.js/
+ */
+
+ function EventDispatcher() {}
+
+ Object.assign( EventDispatcher.prototype, {
+
+ addEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) this._listeners = {};
+
+ var listeners = this._listeners;
+
+ if ( listeners[ type ] === undefined ) {
+
+ listeners[ type ] = [];
+
+ }
+
+ if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+ listeners[ type ].push( listener );
+
+ }
+
+ },
+
+ hasEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return false;
+
+ var listeners = this._listeners;
+
+ return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
+
+ },
+
+ removeEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return;
+
+ var listeners = this._listeners;
+ var listenerArray = listeners[ type ];
+
+ if ( listenerArray !== undefined ) {
+
+ var index = listenerArray.indexOf( listener );
+
+ if ( index !== - 1 ) {
+
+ listenerArray.splice( index, 1 );
+
+ }
+
+ }
+
+ },
+
+ dispatchEvent: function ( event ) {
+
+ if ( this._listeners === undefined ) return;
+
+ var listeners = this._listeners;
+ var listenerArray = listeners[ event.type ];
+
+ if ( listenerArray !== undefined ) {
+
+ event.target = this;
+
+ var array = listenerArray.slice( 0 );
+
+ for ( var i = 0, l = array.length; i < l; i ++ ) {
+
+ array[ i ].call( this, event );
+
+ }
+
+ }
+
+ }
+
+ } );
+
+ var REVISION = '99';
+ var MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
+ var CullFaceNone = 0;
+ var CullFaceBack = 1;
+ var CullFaceFront = 2;
+ var CullFaceFrontBack = 3;
+ var FrontFaceDirectionCW = 0;
+ var FrontFaceDirectionCCW = 1;
+ var BasicShadowMap = 0;
+ var PCFShadowMap = 1;
+ var PCFSoftShadowMap = 2;
+ var FrontSide = 0;
+ var BackSide = 1;
+ var DoubleSide = 2;
+ var FlatShading = 1;
+ var SmoothShading = 2;
+ var NoColors = 0;
+ var FaceColors = 1;
+ var VertexColors = 2;
+ var NoBlending = 0;
+ var NormalBlending = 1;
+ var AdditiveBlending = 2;
+ var SubtractiveBlending = 3;
+ var MultiplyBlending = 4;
+ var CustomBlending = 5;
+ var AddEquation = 100;
+ var SubtractEquation = 101;
+ var ReverseSubtractEquation = 102;
+ var MinEquation = 103;
+ var MaxEquation = 104;
+ var ZeroFactor = 200;
+ var OneFactor = 201;
+ var SrcColorFactor = 202;
+ var OneMinusSrcColorFactor = 203;
+ var SrcAlphaFactor = 204;
+ var OneMinusSrcAlphaFactor = 205;
+ var DstAlphaFactor = 206;
+ var OneMinusDstAlphaFactor = 207;
+ var DstColorFactor = 208;
+ var OneMinusDstColorFactor = 209;
+ var SrcAlphaSaturateFactor = 210;
+ var NeverDepth = 0;
+ var AlwaysDepth = 1;
+ var LessDepth = 2;
+ var LessEqualDepth = 3;
+ var EqualDepth = 4;
+ var GreaterEqualDepth = 5;
+ var GreaterDepth = 6;
+ var NotEqualDepth = 7;
+ var MultiplyOperation = 0;
+ var MixOperation = 1;
+ var AddOperation = 2;
+ var NoToneMapping = 0;
+ var LinearToneMapping = 1;
+ var ReinhardToneMapping = 2;
+ var Uncharted2ToneMapping = 3;
+ var CineonToneMapping = 4;
+ var ACESFilmicToneMapping = 5;
+
+ var UVMapping = 300;
+ var CubeReflectionMapping = 301;
+ var CubeRefractionMapping = 302;
+ var EquirectangularReflectionMapping = 303;
+ var EquirectangularRefractionMapping = 304;
+ var SphericalReflectionMapping = 305;
+ var CubeUVReflectionMapping = 306;
+ var CubeUVRefractionMapping = 307;
+ var RepeatWrapping = 1000;
+ var ClampToEdgeWrapping = 1001;
+ var MirroredRepeatWrapping = 1002;
+ var NearestFilter = 1003;
+ var NearestMipMapNearestFilter = 1004;
+ var NearestMipMapLinearFilter = 1005;
+ var LinearFilter = 1006;
+ var LinearMipMapNearestFilter = 1007;
+ var LinearMipMapLinearFilter = 1008;
+ var UnsignedByteType = 1009;
+ var ByteType = 1010;
+ var ShortType = 1011;
+ var UnsignedShortType = 1012;
+ var IntType = 1013;
+ var UnsignedIntType = 1014;
+ var FloatType = 1015;
+ var HalfFloatType = 1016;
+ var UnsignedShort4444Type = 1017;
+ var UnsignedShort5551Type = 1018;
+ var UnsignedShort565Type = 1019;
+ var UnsignedInt248Type = 1020;
+ var AlphaFormat = 1021;
+ var RGBFormat = 1022;
+ var RGBAFormat = 1023;
+ var LuminanceFormat = 1024;
+ var LuminanceAlphaFormat = 1025;
+ var RGBEFormat = RGBAFormat;
+ var DepthFormat = 1026;
+ var DepthStencilFormat = 1027;
+ var RedFormat = 1028;
+ var RGB_S3TC_DXT1_Format = 33776;
+ var RGBA_S3TC_DXT1_Format = 33777;
+ var RGBA_S3TC_DXT3_Format = 33778;
+ var RGBA_S3TC_DXT5_Format = 33779;
+ var RGB_PVRTC_4BPPV1_Format = 35840;
+ var RGB_PVRTC_2BPPV1_Format = 35841;
+ var RGBA_PVRTC_4BPPV1_Format = 35842;
+ var RGBA_PVRTC_2BPPV1_Format = 35843;
+ var RGB_ETC1_Format = 36196;
+ var RGBA_ASTC_4x4_Format = 37808;
+ var RGBA_ASTC_5x4_Format = 37809;
+ var RGBA_ASTC_5x5_Format = 37810;
+ var RGBA_ASTC_6x5_Format = 37811;
+ var RGBA_ASTC_6x6_Format = 37812;
+ var RGBA_ASTC_8x5_Format = 37813;
+ var RGBA_ASTC_8x6_Format = 37814;
+ var RGBA_ASTC_8x8_Format = 37815;
+ var RGBA_ASTC_10x5_Format = 37816;
+ var RGBA_ASTC_10x6_Format = 37817;
+ var RGBA_ASTC_10x8_Format = 37818;
+ var RGBA_ASTC_10x10_Format = 37819;
+ var RGBA_ASTC_12x10_Format = 37820;
+ var RGBA_ASTC_12x12_Format = 37821;
+ var LoopOnce = 2200;
+ var LoopRepeat = 2201;
+ var LoopPingPong = 2202;
+ var InterpolateDiscrete = 2300;
+ var InterpolateLinear = 2301;
+ var InterpolateSmooth = 2302;
+ var ZeroCurvatureEnding = 2400;
+ var ZeroSlopeEnding = 2401;
+ var WrapAroundEnding = 2402;
+ var TrianglesDrawMode = 0;
+ var TriangleStripDrawMode = 1;
+ var TriangleFanDrawMode = 2;
+ var LinearEncoding = 3000;
+ var sRGBEncoding = 3001;
+ var GammaEncoding = 3007;
+ var RGBEEncoding = 3002;
+ var LogLuvEncoding = 3003;
+ var RGBM7Encoding = 3004;
+ var RGBM16Encoding = 3005;
+ var RGBDEncoding = 3006;
+ var BasicDepthPacking = 3200;
+ var RGBADepthPacking = 3201;
+ var TangentSpaceNormalMap = 0;
+ var ObjectSpaceNormalMap = 1;
+
+ /**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+ var _Math = {
+
+ DEG2RAD: Math.PI / 180,
+ RAD2DEG: 180 / Math.PI,
+
+ generateUUID: ( function () {
+
+ // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
+
+ var lut = [];
+
+ for ( var i = 0; i < 256; i ++ ) {
+
+ lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
+
+ }
+
+ return function generateUUID() {
+
+ var d0 = Math.random() * 0xffffffff | 0;
+ var d1 = Math.random() * 0xffffffff | 0;
+ var d2 = Math.random() * 0xffffffff | 0;
+ var d3 = Math.random() * 0xffffffff | 0;
+ var uuid = lut[ d0 & 0xff ] + lut[ d0 >> 8 & 0xff ] + lut[ d0 >> 16 & 0xff ] + lut[ d0 >> 24 & 0xff ] + '-' +
+ lut[ d1 & 0xff ] + lut[ d1 >> 8 & 0xff ] + '-' + lut[ d1 >> 16 & 0x0f | 0x40 ] + lut[ d1 >> 24 & 0xff ] + '-' +
+ lut[ d2 & 0x3f | 0x80 ] + lut[ d2 >> 8 & 0xff ] + '-' + lut[ d2 >> 16 & 0xff ] + lut[ d2 >> 24 & 0xff ] +
+ lut[ d3 & 0xff ] + lut[ d3 >> 8 & 0xff ] + lut[ d3 >> 16 & 0xff ] + lut[ d3 >> 24 & 0xff ];
+
+ // .toUpperCase() here flattens concatenated strings to save heap memory space.
+ return uuid.toUpperCase();
+
+ };
+
+ } )(),
+
+ clamp: function ( value, min, max ) {
+
+ return Math.max( min, Math.min( max, value ) );
+
+ },
+
+ // compute euclidian modulo of m % n
+ // https://en.wikipedia.org/wiki/Modulo_operation
+
+ euclideanModulo: function ( n, m ) {
+
+ return ( ( n % m ) + m ) % m;
+
+ },
+
+ // Linear mapping from range to range
+
+ mapLinear: function ( x, a1, a2, b1, b2 ) {
+
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+
+ },
+
+ // https://en.wikipedia.org/wiki/Linear_interpolation
+
+ lerp: function ( x, y, t ) {
+
+ return ( 1 - t ) * x + t * y;
+
+ },
+
+ // http://en.wikipedia.org/wiki/Smoothstep
+
+ smoothstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min ) / ( max - min );
+
+ return x * x * ( 3 - 2 * x );
+
+ },
+
+ smootherstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min ) / ( max - min );
+
+ return x * x * x * ( x * ( x * 6 - 15 ) + 10 );
+
+ },
+
+ // Random integer from interval
+
+ randInt: function ( low, high ) {
+
+ return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+ },
+
+ // Random float from interval
+
+ randFloat: function ( low, high ) {
+
+ return low + Math.random() * ( high - low );
+
+ },
+
+ // Random float from <-range/2, range/2> interval
+
+ randFloatSpread: function ( range ) {
+
+ return range * ( 0.5 - Math.random() );
+
+ },
+
+ degToRad: function ( degrees ) {
+
+ return degrees * _Math.DEG2RAD;
+
+ },
+
+ radToDeg: function ( radians ) {
+
+ return radians * _Math.RAD2DEG;
+
+ },
+
+ isPowerOfTwo: function ( value ) {
+
+ return ( value & ( value - 1 ) ) === 0 && value !== 0;
+
+ },
+
+ ceilPowerOfTwo: function ( value ) {
+
+ return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );
+
+ },
+
+ floorPowerOfTwo: function ( value ) {
+
+ return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) );
+
+ }
+
+ };
+
+ /**
+ * @author mrdoob / http://mrdoob.com/
+ * @author philogb / http://blog.thejit.org/
+ * @author egraether / http://egraether.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ */
+
+ function Vector2( x, y ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+
+ }
+
+ Object.defineProperties( Vector2.prototype, {
+
+ "width": {
+
+ get: function () {
+
+ return this.x;
+
+ },
+
+ set: function ( value ) {
+
+ this.x = value;
+
+ }
+
+ },
+
+ "height": {
+
+ get: function () {
+
+ return this.y;
+
+ },
+
+ set: function ( value ) {
+
+ this.y = value;
+
+ }
+
+ }
+
+ } );
+
+ Object.assign( Vector2.prototype, {
+
+ isVector2: true,
+
+ set: function ( x, y ) {
+
+ this.x = x;
+ this.y = y;
+
+ return this;
+
+ },
+
+ setScalar: function ( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ },
+
+ clone: function () {
+
+ return new this.constructor( this.x, this.y );
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+
+ return this;
+
+ },
+
+ addScaledVector: function ( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+
+ return this;
+
+ },
+
+ subScalar: function ( s ) {
+
+ this.x -= s;
+ this.y -= s;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+
+ return this;
+
+ },
+
+ multiply: function ( v ) {
+
+ this.x *= v.x;
+ this.y *= v.y;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+
+ return this;
+
+ },
+
+ divide: function ( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ },
+
+ applyMatrix3: function ( m ) {
+
+ var x = this.x, y = this.y;
+ var e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];
+ this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+ this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+
+ return this;
+
+ },
+
+ clampScalar: function () {
+
+ var min = new Vector2();
+ var max = new Vector2();
+
+ return function clampScalar( minVal, maxVal ) {
+
+ min.set( minVal, minVal );
+ max.set( maxVal, maxVal );
+
+ return this.clamp( min, max );
+
+ };
+
+ }(),
+
+ clampLength: function ( min, max ) {
+
+ var length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+ },
+
+ floor: function () {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+
+ return this;
+
+ },
+
+ ceil: function () {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+
+ return this;
+
+ },
+
+ round: function () {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+
+ return this;
+
+ },
+
+ roundToZero: function () {
+
+ 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 );
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ this.x = - this.x;
+ this.y = - this.y;
+
+ return this;
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y;
+
+ },
+
+ cross: function ( v ) {
+
+ return this.x * v.y - this.y * v.x;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y );
+
+ },
+
+ manhattanLength: function () {
+
+ return Math.abs( this.x ) + Math.abs( this.y );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() || 1 );
+
+ },
+
+ angle: function () {
+
+ // computes the angle in radians with respect to the positive x-axis
+
+ var angle = Math.atan2( this.y, this.x );
+
+ if ( angle < 0 ) angle += 2 * Math.PI;
+
+ return angle;
+
+ },
+
+ distanceTo: function ( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ },
+
+ distanceToSquared: function ( v ) {
+
+ var dx = this.x - v.x, dy = this.y - v.y;
+ return dx * dx + dy * dy;
+
+ },
+
+ manhattanDistanceTo: function ( v ) {
+
+ return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y );
+
+ },
+
+ setLength: function ( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+
+ return this;
+
+ },
+
+ lerpVectors: function ( v1, v2, alpha ) {
+
+ return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );
+
+ },
+
+ equals: function ( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) );
+
+ },
+
+ fromArray: function ( array, offset ) {
+
+ if ( offset === undefined ) offset = 0;
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+
+ return this;
+
+ },
+
+ toArray: function ( array, offset ) {
+
+ if ( array === undefined ) array = [];
+ if ( offset === undefined ) offset = 0;
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+
+ return array;
+
+ },
+
+ fromBufferAttribute: function ( attribute, index, offset ) {
+
+ if ( offset !== undefined ) {
+
+ console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' );
+
+ }
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+
+ return this;
+
+ },
+
+ rotateAround: function ( center, angle ) {
+
+ var c = Math.cos( angle ), s = Math.sin( angle );
+
+ var x = this.x - center.x;
+ var y = this.y - center.y;
+
+ this.x = x * c - y * s + center.x;
+ this.y = x * s + y * c + center.y;
+
+ return this;
+
+ }
+
+ } );
+
+ /**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author jordi_ros / http://plattsoft.com
+ * @author D1plo1d / http://github.com/D1plo1d
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author timknip / http://www.floorplanner.com/
+ * @author bhouston / http://clara.io
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+ function Matrix4() {
+
+ this.elements = [
+
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ ];
+
+ if ( arguments.length > 0 ) {
+
+ console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' );
+
+ }
+
+ }
+
+ Object.assign( Matrix4.prototype, {
+
+ isMatrix4: true,
+
+ set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+ var te = this.elements;
+
+ te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;
+ te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;
+ te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;
+ te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;
+
+ return this;
+
+ },
+
+ identity: function () {
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ return new Matrix4().fromArray( this.elements );
+
+ },
+
+ copy: function ( m ) {
+
+ var te = this.elements;
+ var me = m.elements;
+
+ te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];
+ te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];
+ te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];
+ te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];
+
+ return this;
+
+ },
+
+ copyPosition: function ( m ) {
+
+ var te = this.elements, me = m.elements;
+
+ te[ 12 ] = me[ 12 ];
+ te[ 13 ] = me[ 13 ];
+ te[ 14 ] = me[ 14 ];
+
+ return this;
+
+ },
+
+ extractBasis: function ( xAxis, yAxis, zAxis ) {
+
+ xAxis.setFromMatrixColumn( this, 0 );
+ yAxis.setFromMatrixColumn( this, 1 );
+ zAxis.setFromMatrixColumn( this, 2 );
+
+ return this;
+
+ },
+
+ makeBasis: function ( xAxis, yAxis, zAxis ) {
+
+ this.set(
+ xAxis.x, yAxis.x, zAxis.x, 0,
+ xAxis.y, yAxis.y, zAxis.y, 0,
+ xAxis.z, yAxis.z, zAxis.z, 0,
+ 0, 0, 0, 1
+ );
+
+ return this;
+
+ },
+
+ extractRotation: function () {
+
+ var v1 = new Vector3();
+
+ return function extractRotation( m ) {
+
+ // this method does not support reflection matrices
+
+ var te = this.elements;
+ var me = m.elements;
+
+ var scaleX = 1 / v1.setFromMatrixColumn( m, 0 ).length();
+ var scaleY = 1 / v1.setFromMatrixColumn( m, 1 ).length();
+ var scaleZ = 1 / v1.setFromMatrixColumn( m, 2 ).length();
+
+ te[ 0 ] = me[ 0 ] * scaleX;
+ te[ 1 ] = me[ 1 ] * scaleX;
+ te[ 2 ] = me[ 2 ] * scaleX;
+ te[ 3 ] = 0;
+
+ te[ 4 ] = me[ 4 ] * scaleY;
+ te[ 5 ] = me[ 5 ] * scaleY;
+ te[ 6 ] = me[ 6 ] * scaleY;
+ te[ 7 ] = 0;
+
+ te[ 8 ] = me[ 8 ] * scaleZ;
+ te[ 9 ] = me[ 9 ] * scaleZ;
+ te[ 10 ] = me[ 10 ] * scaleZ;
+ te[ 11 ] = 0;
+
+ te[ 12 ] = 0;
+ te[ 13 ] = 0;
+ te[ 14 ] = 0;
+ te[ 15 ] = 1;
+
+ return this;
+
+ };
+
+ }(),
+
+ makeRotationFromEuler: function ( euler ) {
+
+ if ( ! ( euler && euler.isEuler ) ) {
+
+ console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );
+
+ }
+
+ var te = this.elements;
+
+ var x = euler.x, y = euler.y, z = euler.z;
+ var a = Math.cos( x ), b = Math.sin( x );
+ var c = Math.cos( y ), d = Math.sin( y );
+ var e = Math.cos( z ), f = Math.sin( z );
+
+ if ( euler.order === 'XYZ' ) {
+
+ var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = - c * f;
+ te[ 8 ] = d;
+
+ te[ 1 ] = af + be * d;
+ te[ 5 ] = ae - bf * d;
+ te[ 9 ] = - b * c;
+
+ te[ 2 ] = bf - ae * d;
+ te[ 6 ] = be + af * d;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'YXZ' ) {
+
+ var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[ 0 ] = ce + df * b;
+ te[ 4 ] = de * b - cf;
+ te[ 8 ] = a * d;
+
+ te[ 1 ] = a * f;
+ te[ 5 ] = a * e;
+ te[ 9 ] = - b;
+
+ te[ 2 ] = cf * b - de;
+ te[ 6 ] = df + ce * b;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'ZXY' ) {
+
+ var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[ 0 ] = ce - df * b;
+ te[ 4 ] = - a * f;
+ te[ 8 ] = de + cf * b;
+
+ te[ 1 ] = cf + de * b;
+ te[ 5 ] = a * e;
+ te[ 9 ] = df - ce * b;
+
+ te[ 2 ] = - a * d;
+ te[ 6 ] = b;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'ZYX' ) {
+
+ var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = be * d - af;
+ te[ 8 ] = ae * d + bf;
+
+ te[ 1 ] = c * f;
+ te[ 5 ] = bf * d + ae;
+ te[ 9 ] = af * d - be;
+
+ te[ 2 ] = - d;
+ te[ 6 ] = b * c;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'YZX' ) {
+
+ var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = bd - ac * f;
+ te[ 8 ] = bc * f + ad;
+
+ te[ 1 ] = f;
+ te[ 5 ] = a * e;
+ te[ 9 ] = - b * e;
+
+ te[ 2 ] = - d * e;
+ te[ 6 ] = ad * f + bc;
+ te[ 10 ] = ac - bd * f;
+
+ } else if ( euler.order === 'XZY' ) {
+
+ var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = - f;
+ te[ 8 ] = d * e;
+
+ te[ 1 ] = ac * f + bd;
+ te[ 5 ] = a * e;
+ te[ 9 ] = ad * f - bc;
+
+ te[ 2 ] = bc * f - ad;
+ te[ 6 ] = b * e;
+ te[ 10 ] = bd * f + ac;
+
+ }
+
+ // bottom row
+ te[ 3 ] = 0;
+ te[ 7 ] = 0;
+ te[ 11 ] = 0;
+
+ // last column
+ te[ 12 ] = 0;
+ te[ 13 ] = 0;
+ te[ 14 ] = 0;
+ te[ 15 ] = 1;
+
+ return this;
+
+ },
+
+ makeRotationFromQuaternion: function () {
+
+ var zero = new Vector3( 0, 0, 0 );
+ var one = new Vector3( 1, 1, 1 );
+
+ return function makeRotationFromQuaternion( q ) {
+
+ return this.compose( zero, q, one );
+
+ };
+
+ }(),
+
+ lookAt: function () {
+
+ var x = new Vector3();
+ var y = new Vector3();
+ var z = new Vector3();
+
+ return function lookAt( eye, target, up ) {
+
+ var te = this.elements;
+
+ z.subVectors( eye, target );
+
+ if ( z.lengthSq() === 0 ) {
+
+ // eye and target are in the same position
+
+ z.z = 1;
+
+ }
+
+ z.normalize();
+ x.crossVectors( up, z );
+
+ if ( x.lengthSq() === 0 ) {
+
+ // up and z are parallel
+
+ if ( Math.abs( up.z ) === 1 ) {
+
+ z.x += 0.0001;
+
+ } else {
+
+ z.z += 0.0001;
+
+ }
+
+ z.normalize();
+ x.crossVectors( up, z );
+
+ }
+
+ x.normalize();
+ y.crossVectors( z, x );
+
+ te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x;
+ te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y;
+ te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z;
+
+ return this;
+
+ };
+
+ }(),
+
+ multiply: function ( m, n ) {
+
+ if ( n !== undefined ) {
+
+ console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
+ return this.multiplyMatrices( m, n );
+
+ }
+
+ return this.multiplyMatrices( this, m );
+
+ },
+
+ premultiply: function ( m ) {
+
+ return this.multiplyMatrices( m, this );
+
+ },
+
+ multiplyMatrices: function ( a, b ) {
+
+ var ae = a.elements;
+ var be = b.elements;
+ var te = this.elements;
+
+ var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];
+ var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];
+ var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];
+ var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];
+
+ var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];
+ var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];
+ var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];
+ var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];
+
+ te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
+ te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
+ te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
+ te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
+
+ te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
+ te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
+ te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
+ te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
+
+ te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
+ te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
+ te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
+ te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
+
+ te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
+ te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
+ te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
+ te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ var te = this.elements;
+
+ te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;
+ te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;
+ te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;
+ te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;
+
+ return this;
+
+ },
+
+ applyToBufferAttribute: function () {
+
+ var v1 = new Vector3();
+
+ return function applyToBufferAttribute( attribute ) {
+
+ for ( var i = 0, l = attribute.count; i < l; i ++ ) {
+
+ v1.x = attribute.getX( i );
+ v1.y = attribute.getY( i );
+ v1.z = attribute.getZ( i );
+
+ v1.applyMatrix4( this );
+
+ attribute.setXYZ( i, v1.x, v1.y, v1.z );
+
+ }
+
+ return attribute;
+
+ };
+
+ }(),
+
+ determinant: function () {
+
+ var te = this.elements;
+
+ var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];
+ var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];
+ var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];
+ var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];
+
+ //TODO: make this more efficient
+ //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+
+ return (
+ n41 * (
+ + n14 * n23 * n32
+ - n13 * n24 * n32
+ - n14 * n22 * n33
+ + n12 * n24 * n33
+ + n13 * n22 * n34
+ - n12 * n23 * n34
+ ) +
+ n42 * (
+ + n11 * n23 * n34
+ - n11 * n24 * n33
+ + n14 * n21 * n33
+ - n13 * n21 * n34
+ + n13 * n24 * n31
+ - n14 * n23 * n31
+ ) +
+ n43 * (
+ + n11 * n24 * n32
+ - n11 * n22 * n34
+ - n14 * n21 * n32
+ + n12 * n21 * n34
+ + n14 * n22 * n31
+ - n12 * n24 * n31
+ ) +
+ n44 * (
+ - n13 * n22 * n31
+ - n11 * n23 * n32
+ + n11 * n22 * n33
+ + n13 * n21 * n32
+ - n12 * n21 * n33
+ + n12 * n23 * n31
+ )
+
+ );
+
+ },
+
+ transpose: function () {
+
+ var te = this.elements;
+ var tmp;
+
+ tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;
+ tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;
+ tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;
+
+ tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;
+ tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;
+ tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;
+
+ return this;
+
+ },
+
+ setPosition: function ( v ) {
+
+ var te = this.elements;
+
+ te[ 12 ] = v.x;
+ te[ 13 ] = v.y;
+ te[ 14 ] = v.z;
+
+ return this;
+
+ },
+
+ getInverse: function ( m, throwOnDegenerate ) {
+
+ // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
+ var te = this.elements,
+ me = m.elements,
+
+ n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ],
+ n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ],
+ n13 = me[ 8 ], n23 = me[ 9 ], n33 = me[ 10 ], n43 = me[ 11 ],
+ n14 = me[ 12 ], n24 = me[ 13 ], n34 = me[ 14 ], n44 = me[ 15 ],
+
+ t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
+ t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
+ t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
+ t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
+
+ var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
+
+ if ( det === 0 ) {
+
+ var msg = "THREE.Matrix4: .getInverse() can't invert matrix, determinant is 0";
+
+ if ( throwOnDegenerate === true ) {
+
+ throw new Error( msg );
+
+ } else {
+
+ console.warn( msg );
+
+ }
+
+ return this.identity();
+
+ }
+
+ var detInv = 1 / det;
+
+ te[ 0 ] = t11 * detInv;
+ te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;
+ te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;
+ te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;
+
+ te[ 4 ] = t12 * detInv;
+ te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;
+ te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;
+ te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;
+
+ te[ 8 ] = t13 * detInv;
+ te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;
+ te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;
+ te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;
+
+ te[ 12 ] = t14 * detInv;
+ te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;
+ te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;
+ te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;
+
+ return this;
+
+ },
+
+ scale: function ( v ) {
+
+ var te = this.elements;
+ var x = v.x, y = v.y, z = v.z;
+
+ te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;
+ te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;
+ te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;
+ te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;
+
+ return this;
+
+ },
+
+ getMaxScaleOnAxis: function () {
+
+ var te = this.elements;
+
+ var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];
+ var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];
+ var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];
+
+ return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );
+
+ },
+
+ makeTranslation: function ( x, y, z ) {
+
+ this.set(
+
+ 1, 0, 0, x,
+ 0, 1, 0, y,
+ 0, 0, 1, z,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationX: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, c, - s, 0,
+ 0, s, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationY: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, 0, s, 0,
+ 0, 1, 0, 0,
+ - s, 0, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationZ: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, - s, 0, 0,
+ s, c, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationAxis: function ( axis, angle ) {
+
+ // Based on http://www.gamedev.net/reference/articles/article1199.asp
+
+ var c = Math.cos( angle );
+ var s = Math.sin( angle );
+ var t = 1 - c;
+ var x = axis.x, y = axis.y, z = axis.z;
+ var tx = t * x, ty = t * y;
+
+ this.set(
+
+ tx * x + c, tx * y - s * z, tx * z + s * y, 0,
+ tx * y + s * z, ty * y + c, ty * z - s * x, 0,
+ tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeScale: function ( x, y, z ) {
+
+ this.set(
+
+ x, 0, 0, 0,
+ 0, y, 0, 0,
+ 0, 0, z, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeShear: function ( x, y, z ) {
+
+ this.set(
+
+ 1, y, z, 0,
+ x, 1, z, 0,
+ x, y, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ compose: function ( position, quaternion, scale ) {
+
+ var te = this.elements;
+
+ var x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w;
+ var x2 = x + x, y2 = y + y, z2 = z + z;
+ var xx = x * x2, xy = x * y2, xz = x * z2;
+ var yy = y * y2, yz = y * z2, zz = z * z2;
+ var wx = w * x2, wy = w * y2, wz = w * z2;
+
+ var sx = scale.x, sy = scale.y, sz = scale.z;
+
+ te[ 0 ] = ( 1 - ( yy + zz ) ) * sx;
+ te[ 1 ] = ( xy + wz ) * sx;
+ te[ 2 ] = ( xz - wy ) * sx;
+ te[ 3 ] = 0;
+
+ te[ 4 ] = ( xy - wz ) * sy;
+ te[ 5 ] = ( 1 - ( xx + zz ) ) * sy;
+ te[ 6 ] = ( yz + wx ) * sy;
+ te[ 7 ] = 0;
+
+ te[ 8 ] = ( xz + wy ) * sz;
+ te[ 9 ] = ( yz - wx ) * sz;
+ te[ 10 ] = ( 1 - ( xx + yy ) ) * sz;
+ te[ 11 ] = 0;
+
+ te[ 12 ] = position.x;
+ te[ 13 ] = position.y;
+ te[ 14 ] = position.z;
+ te[ 15 ] = 1;
+
+ return this;
+
+ },
+
+ decompose: function () {
+
+ var vector = new Vector3();
+ var matrix = new Matrix4();
+
+ return function decompose( position, quaternion, scale ) {
+
+ var te = this.elements;
+
+ var sx = vector.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
+ var sy = vector.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
+ var sz = vector.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
+
+ // if determine is negative, we need to invert one scale
+ var det = this.determinant();
+ if ( det < 0 ) sx = - sx;
+
+ position.x = te[ 12 ];
+ position.y = te[ 13 ];
+ position.z = te[ 14 ];
+
+ // scale the rotation part
+ matrix.copy( this );
+
+ var invSX = 1 / sx;
+ var invSY = 1 / sy;
+ var invSZ = 1 / sz;
+
+ matrix.elements[ 0 ] *= invSX;
+ matrix.elements[ 1 ] *= invSX;
+ matrix.elements[ 2 ] *= invSX;
+
+ matrix.elements[ 4 ] *= invSY;
+ matrix.elements[ 5 ] *= invSY;
+ matrix.elements[ 6 ] *= invSY;
+
+ matrix.elements[ 8 ] *= invSZ;
+ matrix.elements[ 9 ] *= invSZ;
+ matrix.elements[ 10 ] *= invSZ;
+
+ quaternion.setFromRotationMatrix( matrix );
+
+ scale.x = sx;
+ scale.y = sy;
+ scale.z = sz;
+
+ return this;
+
+ };
+
+ }(),
+
+ makePerspective: function ( left, right, top, bottom, near, far ) {
+
+ if ( far === undefined ) {
+
+ console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' );
+
+ }
+
+ var te = this.elements;
+ var x = 2 * near / ( right - left );
+ var y = 2 * near / ( top - bottom );
+
+ var a = ( right + left ) / ( right - left );
+ var b = ( top + bottom ) / ( top - bottom );
+ var c = - ( far + near ) / ( far - near );
+ var d = - 2 * far * near / ( far - near );
+
+ te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0;
+ te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0;
+ te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d;
+ te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0;
+
+ return this;
+
+ },
+
+ makeOrthographic: function ( left, right, top, bottom, near, far ) {
+
+ var te = this.elements;
+ var w = 1.0 / ( right - left );
+ var h = 1.0 / ( top - bottom );
+ var p = 1.0 / ( far - near );
+
+ var x = ( right + left ) * w;
+ var y = ( top + bottom ) * h;
+ var z = ( far + near ) * p;
+
+ te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x;
+ te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y;
+ te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z;
+ te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1;
+
+ return this;
+
+ },
+
+ equals: function ( matrix ) {
+
+ var te = this.elements;
+ var me = matrix.elements;
+
+ for ( var i = 0; i < 16; i ++ ) {
+
+ if ( te[ i ] !== me[ i ] ) return false;
+
+ }
+
+ return true;
+
+ },
+
+ fromArray: function ( array, offset ) {
+
+ if ( offset === undefined ) offset = 0;
+
+ for ( var i = 0; i < 16; i ++ ) {
+
+ this.elements[ i ] = array[ i + offset ];
+
+ }
+
+ return this;
+
+ },
+
+ toArray: function ( array, offset ) {
+
+ if ( array === undefined ) array = [];
+ if ( offset === undefined ) offset = 0;
+
+ var te = this.elements;
+
+ array[ offset ] = te[ 0 ];
+ array[ offset + 1 ] = te[ 1 ];
+ array[ offset + 2 ] = te[ 2 ];
+ array[ offset + 3 ] = te[ 3 ];
+
+ array[ offset + 4 ] = te[ 4 ];
+ array[ offset + 5 ] = te[ 5 ];
+ array[ offset + 6 ] = te[ 6 ];
+ array[ offset + 7 ] = te[ 7 ];
+
+ array[ offset + 8 ] = te[ 8 ];
+ array[ offset + 9 ] = te[ 9 ];
+ array[ offset + 10 ] = te[ 10 ];
+ array[ offset + 11 ] = te[ 11 ];
+
+ array[ offset + 12 ] = te[ 12 ];
+ array[ offset + 13 ] = te[ 13 ];
+ array[ offset + 14 ] = te[ 14 ];
+ array[ offset + 15 ] = te[ 15 ];
+
+ return array;
+
+ }
+
+ } );
+
+ /**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://clara.io
+ */
+
+ function Quaternion( x, y, z, w ) {
+
+ this._x = x || 0;
+ this._y = y || 0;
+ this._z = z || 0;
+ this._w = ( w !== undefined ) ? w : 1;
+
+ }
+
+ Object.assign( Quaternion, {
+
+ slerp: function ( qa, qb, qm, t ) {
+
+ return qm.copy( qa ).slerp( qb, t );
+
+ },
+
+ slerpFlat: function ( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {
+
+ // fuzz-free, array-based Quaternion SLERP operation
+
+ var x0 = src0[ srcOffset0 + 0 ],
+ y0 = src0[ srcOffset0 + 1 ],
+ z0 = src0[ srcOffset0 + 2 ],
+ w0 = src0[ srcOffset0 + 3 ],
+
+ x1 = src1[ srcOffset1 + 0 ],
+ y1 = src1[ srcOffset1 + 1 ],
+ z1 = src1[ srcOffset1 + 2 ],
+ w1 = src1[ srcOffset1 + 3 ];
+
+ if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
+
+ var s = 1 - t,
+
+ cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
+
+ dir = ( cos >= 0 ? 1 : - 1 ),
+ sqrSin = 1 - cos * cos;
+
+ // Skip the Slerp for tiny steps to avoid numeric problems:
+ if ( sqrSin > Number.EPSILON ) {
+
+ var sin = Math.sqrt( sqrSin ),
+ len = Math.atan2( sin, cos * dir );
+
+ s = Math.sin( s * len ) / sin;
+ t = Math.sin( t * len ) / sin;
+
+ }
+
+ var tDir = t * dir;
+
+ x0 = x0 * s + x1 * tDir;
+ y0 = y0 * s + y1 * tDir;
+ z0 = z0 * s + z1 * tDir;
+ w0 = w0 * s + w1 * tDir;
+
+ // Normalize in case we just did a lerp:
+ if ( s === 1 - t ) {
+
+ var f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );
+
+ x0 *= f;
+ y0 *= f;
+ z0 *= f;
+ w0 *= f;
+
+ }
+
+ }
+
+ dst[ dstOffset ] = x0;
+ dst[ dstOffset + 1 ] = y0;
+ dst[ dstOffset + 2 ] = z0;
+ dst[ dstOffset + 3 ] = w0;
+
+ }
+
+ } );
+
+ Object.defineProperties( Quaternion.prototype, {
+
+ x: {
+
+ get: function () {
+
+ return this._x;
+
+ },
+
+ set: function ( value ) {
+
+ this._x = value;
+ this.onChangeCallback();
+
+ }
+
+ },
+
+ y: {
+
+ get: function () {
+
+ return this._y;
+
+ },
+
+ set: function ( value ) {
+
+ this._y = value;
+ this.onChangeCallback();
+
+ }
+
+ },
+
+ z: {
+
+ get: function () {
+
+ return this._z;
+
+ },
+
+ set: function ( value ) {
+
+ this._z = value;
+ this.onChangeCallback();
+
+ }
+
+ },
+
+ w: {
+
+ get: function () {
+
+ return this._w;
+
+ },
+
+ set: function ( value ) {
+
+ this._w = value;
+ this.onChangeCallback();
+
+ }
+
+ }
+
+ } );
+
+ Object.assign( Quaternion.prototype, {
+
+ isQuaternion: true,
+
+ set: function ( x, y, z, w ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ return new this.constructor( this._x, this._y, this._z, this._w );
+
+ },
+
+ copy: function ( quaternion ) {
+
+ this._x = quaternion.x;
+ this._y = quaternion.y;
+ this._z = quaternion.z;
+ this._w = quaternion.w;
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ setFromEuler: function ( euler, update ) {
+
+ if ( ! ( euler && euler.isEuler ) ) {
+
+ throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );
+
+ }
+
+ var x = euler._x, y = euler._y, z = euler._z, order = euler.order;
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/
+ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+ // content/SpinCalc.m
+
+ var cos = Math.cos;
+ var sin = Math.sin;
+
+ var c1 = cos( x / 2 );
+ var c2 = cos( y / 2 );
+ var c3 = cos( z / 2 );
+
+ var s1 = sin( x / 2 );
+ var s2 = sin( y / 2 );
+ var s3 = sin( z / 2 );
+
+ if ( order === 'XYZ' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( order === 'YXZ' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ } else if ( order === 'ZXY' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( order === 'ZYX' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ } else if ( order === 'YZX' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( order === 'XZY' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ }
+
+ if ( update !== false ) this.onChangeCallback();
+
+ return this;
+
+ },
+
+ setFromAxisAngle: function ( axis, angle ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+
+ // assumes axis is normalized
+
+ var halfAngle = angle / 2, s = Math.sin( halfAngle );
+
+ this._x = axis.x * s;
+ this._y = axis.y * s;
+ this._z = axis.z * s;
+ this._w = Math.cos( halfAngle );
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ setFromRotationMatrix: function ( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ var te = m.elements,
+
+ m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
+ m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
+ m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
+
+ trace = m11 + m22 + m33,
+ s;
+
+ if ( trace > 0 ) {
+
+ s = 0.5 / Math.sqrt( trace + 1.0 );
+
+ this._w = 0.25 / s;
+ this._x = ( m32 - m23 ) * s;
+ this._y = ( m13 - m31 ) * s;
+ this._z = ( m21 - m12 ) * s;
+
+ } else if ( m11 > m22 && m11 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+ this._w = ( m32 - m23 ) / s;
+ this._x = 0.25 * s;
+ this._y = ( m12 + m21 ) / s;
+ this._z = ( m13 + m31 ) / s;
+
+ } else if ( m22 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+ this._w = ( m13 - m31 ) / s;
+ this._x = ( m12 + m21 ) / s;
+ this._y = 0.25 * s;
+ this._z = ( m23 + m32 ) / s;
+
+ } else {
+
+ s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+ this._w = ( m21 - m12 ) / s;
+ this._x = ( m13 + m31 ) / s;
+ this._y = ( m23 + m32 ) / s;
+ this._z = 0.25 * s;
+
+ }
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ setFromUnitVectors: function () {
+
+ // assumes direction vectors vFrom and vTo are normalized
+
+ var v1 = new Vector3();
+ var r;
+
+ var EPS = 0.000001;
+
+ return function setFromUnitVectors( vFrom, vTo ) {
+
+ if ( v1 === undefined ) v1 = new Vector3();
+
+ r = vFrom.dot( vTo ) + 1;
+
+ if ( r < EPS ) {
+
+ r = 0;
+
+ if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {
+
+ v1.set( - vFrom.y, vFrom.x, 0 );
+
+ } else {
+
+ v1.set( 0, - vFrom.z, vFrom.y );
+
+ }
+
+ } else {
+
+ v1.crossVectors( vFrom, vTo );
+
+ }
+
+ this._x = v1.x;
+ this._y = v1.y;
+ this._z = v1.z;
+ this._w = r;
+
+ return this.normalize();
+
+ };
+
+ }(),
+
+ angleTo: function ( q ) {
+
+ return 2 * Math.acos( Math.abs( _Math.clamp( this.dot( q ), - 1, 1 ) ) );
+
+ },
+
+ rotateTowards: function ( q, step ) {
+
+ var angle = this.angleTo( q );
+
+ if ( angle === 0 ) return this;
+
+ var t = Math.min( 1, step / angle );
+
+ this.slerp( q, t );
+
+ return this;
+
+ },
+
+ inverse: function () {
+
+ // quaternion is assumed to have unit length
+
+ return this.conjugate();
+
+ },
+
+ conjugate: function () {
+
+ this._x *= - 1;
+ this._y *= - 1;
+ this._z *= - 1;
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ dot: function ( v ) {
+
+ return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
+
+ },
+
+ lengthSq: function () {
+
+ return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+ },
+
+ normalize: function () {
+
+ var l = this.length();
+
+ if ( l === 0 ) {
+
+ this._x = 0;
+ this._y = 0;
+ this._z = 0;
+ this._w = 1;
+
+ } else {
+
+ l = 1 / l;
+
+ this._x = this._x * l;
+ this._y = this._y * l;
+ this._z = this._z * l;
+ this._w = this._w * l;
+
+ }
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ multiply: function ( q, p ) {
+
+ if ( p !== undefined ) {
+
+ console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
+ return this.multiplyQuaternions( q, p );
+
+ }
+
+ return this.multiplyQuaternions( this, q );
+
+ },
+
+ premultiply: function ( q ) {
+
+ return this.multiplyQuaternions( q, this );
+
+ },
+
+ multiplyQuaternions: function ( a, b ) {
+
+ // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+ var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
+ var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
+
+ this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+ this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+ this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+ this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ slerp: function ( qb, t ) {
+
+ if ( t === 0 ) return this;
+ if ( t === 1 ) return this.copy( qb );
+
+ var x = this._x, y = this._y, z = this._z, w = this._w;
+
+ // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+ var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
+
+ if ( cosHalfTheta < 0 ) {
+
+ this._w = - qb._w;
+ this._x = - qb._x;
+ this._y = - qb._y;
+ this._z = - qb._z;
+
+ cosHalfTheta = - cosHalfTheta;
+
+ } else {
+
+ this.copy( qb );
+
+ }
+
+ if ( cosHalfTheta >= 1.0 ) {
+
+ this._w = w;
+ this._x = x;
+ this._y = y;
+ this._z = z;
+
+ return this;
+
+ }
+
+ var sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
+
+ if ( sqrSinHalfTheta <= Number.EPSILON ) {
+
+ var s = 1 - t;
+ this._w = s * w + t * this._w;
+ this._x = s * x + t * this._x;
+ this._y = s * y + t * this._y;
+ this._z = s * z + t * this._z;
+
+ return this.normalize();
+
+ }
+
+ var sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
+ var halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
+ var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
+ ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
+
+ this._w = ( w * ratioA + this._w * ratioB );
+ this._x = ( x * ratioA + this._x * ratioB );
+ this._y = ( y * ratioA + this._y * ratioB );
+ this._z = ( z * ratioA + this._z * ratioB );
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ equals: function ( quaternion ) {
+
+ return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
+
+ },
+
+ fromArray: function ( array, offset ) {
+
+ if ( offset === undefined ) offset = 0;
+
+ this._x = array[ offset ];
+ this._y = array[ offset + 1 ];
+ this._z = array[ offset + 2 ];
+ this._w = array[ offset + 3 ];
+
+ this.onChangeCallback();
+
+ return this;
+
+ },
+
+ toArray: function ( array, offset ) {
+
+ if ( array === undefined ) array = [];
+ if ( offset === undefined ) offset = 0;
+
+ array[ offset ] = this._x;
+ array[ offset + 1 ] = this._y;
+ array[ offset + 2 ] = this._z;
+ array[ offset + 3 ] = this._w;
+
+ return array;
+
+ },
+
+ onChange: function ( callback ) {
+
+ this.onChangeCallback = callback;
+
+ return this;
+
+ },
+
+ onChangeCallback: function () {}
+
+ } );
+
+ /**
+ * @author mrdoob / http://mrdoob.com/
+ * @author kile / http://kile.stravaganza.org/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+ function Vector3( x, y, z ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+ this.z = z || 0;
+
+ }
+
+ Object.assign( Vector3.prototype, {
+
+ isVector3: true,
+
+ set: function ( x, y, z ) {
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ return this;
+
+ },
+
+ setScalar: function ( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+ this.z = scalar;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+ setZ: function ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ },
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ },
+
+ clone: function () {
+
+ return new this.constructor( this.x, this.y, this.z );
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+
+ return this;
+
+ },
+
+ addScaledVector: function ( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+ this.z += v.z * s;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+
+ return this;
+
+ },
+
+ subScalar: function ( s ) {
+
+ this.x -= s;
+ this.y -= s;
+ this.z -= s;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+
+ return this;
+
+ },
+
+ multiply: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
+ return this.multiplyVectors( v, w );
+
+ }
+
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+
+ return this;
+
+ },
+
+ multiplyVectors: function ( a, b ) {
+
+ this.x = a.x * b.x;
+ this.y = a.y * b.y;
+ this.z = a.z * b.z;
+
+ return this;
+
+ },
+
+ applyEuler: function () {
+
+ var quaternion = new Quaternion();
+
+ return function applyEuler( euler ) {
+
+ if ( ! ( euler && euler.isEuler ) ) {
+
+ console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );
+
+ }
+
+ return this.applyQuaternion( quaternion.setFromEuler( euler ) );
+
+ };
+
+ }(),
+
+ applyAxisAngle: function () {
+
+ var quaternion = new Quaternion();
+
+ return function applyAxisAngle( axis, angle ) {
+
+ return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );
+
+ };
+
+ }(),
+
+ applyMatrix3: function ( m ) {
+
+ var x = this.x, y = this.y, z = this.z;
+ var e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
+ this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
+ this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;
+
+ return this;
+
+ },
+
+ applyMatrix4: function ( m ) {
+
+ var x = this.x, y = this.y, z = this.z;
+ var e = m.elements;
+
+ var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );
+
+ this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;
+ this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;
+ this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;
+
+ return this;
+
+ },
+
+ applyQuaternion: function ( q ) {
+
+ var x = this.x, y = this.y, z = this.z;
+ var qx = q.x, qy = q.y, qz = q.z, qw = q.w;
+
+ // calculate quat * vector
+
+ var ix = qw * x + qy * z - qz * y;
+ var iy = qw * y + qz * x - qx * z;
+ var iz = qw * z + qx * y - qy * x;
+ var iw = - qx * x - qy * y - qz * z;
+
+ // calculate result * inverse quat
+
+ this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;
+ this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;
+ this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;
+
+ return this;
+
+ },
+
+ project: function ( camera ) {
+
+ return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix );
+
+ },
+
+ unproject: function () {
+
+ var matrix = new Matrix4();
+
+ return function unproject( camera ) {
+
+ return this.applyMatrix4( matrix.getInverse( camera.projectionMatrix ) ).applyMatrix4( camera.matrixWorld );
+
+ };
+
+ }(),
+
+ transformDirection: function ( m ) {
+
+ // input: THREE.Matrix4 affine matrix
+ // vector interpreted as a direction
+
+ var x = this.x, y = this.y, z = this.z;
+ var e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
+ this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
+ this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;
+
+ return this.normalize();
+
+ },
+
+ divide: function ( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+ this.z /= v.z;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ },
+
+ min: function ( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+ this.z = Math.min( this.z, v.z );
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+ this.z = Math.max( this.z, v.z );
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+ this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+ this.z = Math.max( min.z, Math.min( max.z, this.z ) );
+
+ return this;
+
+ },
+
+ clampScalar: function () {
+
+ var min = new Vector3();
+ var max = new Vector3();
+
+ return function clampScalar( minVal, maxVal ) {
+
+ min.set( minVal, minVal, minVal );
+ max.set( maxVal, maxVal, maxVal );
+
+ return this.clamp( min, max );
+
+ };
+
+ }(),
+
+ clampLength: function ( min, max ) {
+
+ var length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+ },
+
+ floor: function () {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+ this.z = Math.floor( this.z );
+
+ return this;
+
+ },
+
+ ceil: function () {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+ this.z = Math.ceil( this.z );
+
+ return this;
+
+ },
+
+ round: function () {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+ this.z = Math.round( this.z );
+
+ return this;
+
+ },
+
+ roundToZero: function () {
+
+ 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 );
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ this.x = - this.x;
+ this.y = - this.y;
+ this.z = - this.z;
+
+ return this;
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+
+ },
+
+ // TODO lengthSquared?
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+ },
+
+ manhattanLength: function () {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() || 1 );
+
+ },
+
+ setLength: function ( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+
+ return this;
+
+ },
+
+ lerpVectors: function ( v1, v2, alpha ) {
+
+ return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );
+
+ },
+
+ cross: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
+ return this.crossVectors( v, w );
+
+ }
+
+ return this.crossVectors( this, v );
+
+ },
+
+ crossVectors: function ( a, b ) {
+
+ var ax = a.x, ay = a.y, az = a.z;
+ var bx = b.x, by = b.y, bz = b.z;
+
+ this.x = ay * bz - az * by;
+ this.y = az * bx - ax * bz;
+ this.z = ax * by - ay * bx;
+
+ return this;
+
+ },
+
+ projectOnVector: function ( vector ) {
+
+ var scalar = vector.dot( this ) / vector.lengthSq();
+
+ return this.copy( vector ).multiplyScalar( scalar );
+
+ },
+
+ projectOnPlane: function () {
+
+ var v1 = new Vector3();
+
+ return function projectOnPlane( planeNormal ) {
+
+ v1.copy( this ).projectOnVector( planeNormal );
+
+ return this.sub( v1 );
+
+ };
+
+ }(),
+
+ reflect: function () {
+
+ // reflect incident vector off plane orthogonal to normal
+ // normal is assumed to have unit length
+
+ var v1 = new Vector3();
+
+ return function reflect( normal ) {
+
+ return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );
+
+ };
+
+ }(),
+
+ angleTo: function ( v ) {
+
+ var theta = this.dot( v ) / ( Math.sqrt( this.lengthSq() * v.lengthSq() ) );
+
+ // clamp, to handle numerical problems
+
+ return Math.acos( _Math.clamp( theta, - 1, 1 ) );
+
+ },
+
+ distanceTo: function ( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ },
+
+ distanceToSquared: function ( v ) {
+
+ var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
+
+ return dx * dx + dy * dy + dz * dz;
+
+ },
+
+ manhattanDistanceTo: function ( v ) {
+
+ return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );
+
+ },
+
+ setFromSpherical: function ( s ) {
+
+ return this.setFromSphericalCoords( s.radius, s.phi, s.theta );
+
+ },
+
+ setFromSphericalCoords: function ( radius, phi, theta ) {
+
+ var sinPhiRadius = Math.sin( phi ) * radius;
+
+ this.x = sinPhiRadius * Math.sin( theta );
+ this.y = Math.cos( phi ) * radius;
+ this.z = sinPhiRadius * Math.cos( theta );
+
+ return this;
+
+ },
+
+ setFromCylindrical: function ( c ) {
+
+ return this.setFromCylindricalCoords( c.radius, c.theta, c.y );
+
+ },
+
+ setFromCylindricalCoords: function ( radius, theta, y ) {
+
+ this.x = radius * Math.sin( theta );
+ this.y = y;
+ this.z = radius * Math.cos( theta );
+
+ return this;
+
+ },
+
+ setFromMatrixPosition: function ( m ) {
+
+ var e = m.elements;
+
+ this.x = e[ 12 ];
+ this.y = e[ 13 ];
+ this.z = e[ 14 ];
+
+ return this;
+
+ },
+
+ setFromMatrixScale: function ( m ) {
+
+ var sx = this.setFromMatrixColumn( m, 0 ).length();
+ var sy = this.setFromMatrixColumn( m, 1 ).length();
+ var sz = this.setFromMatrixColumn( m, 2 ).length();
+
+ this.x = sx;
+ this.y = sy;
+ this.z = sz;
+
+ return this;
+
+ },
+
+ setFromMatrixColumn: function ( m, index ) {
+
+ return this.fromArray( m.elements, index * 4 );
+
+ },
+
+ equals: function ( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
+
+ },
+
+ fromArray: function ( array, offset ) {
+
+ if ( offset === undefined ) offset = 0;
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+ this.z = array[ offset + 2 ];
+
+ return this;
+
+ },
+
+ toArray: function ( array, offset ) {
+
+ if ( array === undefined ) array = [];
+ if ( offset === undefined ) offset = 0;
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+ array[ offset + 2 ] = this.z;
+
+ return array;
+
+ },
+
+ fromBufferAttribute: function ( attribute, index, offset ) {
+
+ if ( offset !== undefined ) {
+
+ console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' );
+
+ }
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+ this.z = attribute.getZ( index );
+
+ return this;
+
+ }
+
+ } );
+
+ /**
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://clara.io
+ * @author tschw
+ */
+
+ function Matrix3() {
+
+ this.elements = [
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ ];
+
+ if ( arguments.length > 0 ) {
+
+ console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' );
+
+ }
+
+ }
+
+ Object.assign( Matrix3.prototype, {
+
+ isMatrix3: true,
+
+ set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ var te = this.elements;
+
+ te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;
+ te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;
+ te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;
+
+ return this;
+
+ },
+
+ identity: function () {
+
+ this.set(
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().fromArray( this.elements );
+
+ },
+
+ copy: function ( m ) {
+
+ var te = this.elements;
+ var me = m.elements;
+
+ te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];
+ te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];
+ te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];
+
+ return this;
+
+ },
+
+ setFromMatrix4: function ( m ) {
+
+ var me = m.elements;
+
+ this.set(
+
+ me[ 0 ], me[ 4 ], me[ 8 ],
+ me[ 1 ], me[ 5 ], me[ 9 ],
+ me[ 2 ], me[ 6 ], me[ 10 ]
+
+ );
+
+ return this;
+
+ },
+
+ applyToBufferAttribute: function () {
+
+ var v1 = new Vector3();
+
+ return function applyToBufferAttribute( attribute ) {
+
+ for ( var i = 0, l = attribute.count; i < l; i ++ ) {
+
+ v1.x = attribute.getX( i );
+ v1.y = attribute.getY( i );
+ v1.z = attribute.getZ( i );
+
+ v1.applyMatrix3( this );
+
+ attribute.setXYZ( i, v1.x, v1.y, v1.z );
+
+ }
+
+ return attribute;
+
+ };
+
+ }(),
+
+ multiply: function ( m ) {
+
+ return this.multiplyMatrices( this, m );
+
+ },
+
+ premultiply: function ( m ) {
+
+ return this.multiplyMatrices( m, this );
+
+ },
+
+ multiplyMatrices: function ( a, b ) {
+
+ var ae = a.elements;
+ var be = b.elements;
+ var te = this.elements;
+
+ var a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];
+ var a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];
+ var a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];
+
+ var b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];
+ var b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];
+ var b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];
+
+ te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;
+ te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;
+ te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;
+
+ te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;
+ te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;
+ te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;
+
+ te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;
+ te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;
+ te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ var te = this.elements;
+
+ te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s;
+ te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s;
+ te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s;
+
+ return this;
+
+ },
+
+ determinant: function () {
+
+ var te = this.elements;
+
+ var a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],
+ d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],
+ g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];
+
+ return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
+
+ },
+
+ getInverse: function ( matrix, throwOnDegenerate ) {
+
+ if ( matrix && matrix.isMatrix4 ) {
+
+ console.error( "THREE.Matrix3: .getInverse() no longer takes a Matrix4 argument." );
+
+ }
+
+ var me = matrix.elements,
+ te = this.elements,
+
+ n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ],
+ n12 = me[ 3 ], n22 = me[ 4 ], n32 = me[ 5 ],
+ n13 = me[ 6 ], n23 = me[ 7 ], n33 = me[ 8 ],
+
+ t11 = n33 * n22 - n32 * n23,
+ t12 = n32 * n13 - n33 * n12,
+ t13 = n23 * n12 - n22 * n13,
+
+ det = n11 * t11 + n21 * t12 + n31 * t13;
+
+ if ( det === 0 ) {
+
+ var msg = "THREE.Matrix3: .getInverse() can't invert matrix, determinant is 0";
+
+ if ( throwOnDegenerate === true ) {
+
+ throw new Error( msg );
+
+ } else {
+
+ console.warn( msg );
+
+ }
+
+ return this.identity();
+
+ }
+
+ var detInv = 1 / det;
+
+ te[ 0 ] = t11 * detInv;
+ te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;
+ te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;
+
+ te[ 3 ] = t12 * detInv;
+ te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;
+ te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;
+
+ te[ 6 ] = t13 * detInv;
+ te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;
+ te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;
+
+ return this;
+
+ },
+
+ transpose: function () {
+
+ var tmp, m = this.elements;
+
+ tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;
+ tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;
+ tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;
+
+ return this;
+
+ },
+
+ getNormalMatrix: function ( matrix4 ) {
+
+ return this.setFromMatrix4( matrix4 ).getInverse( this ).transpose();
+
+ },
+
+ transposeIntoArray: function ( r ) {
+
+ var m = this.elements;
+
+ r[ 0 ] = m[ 0 ];
+ r[ 1 ] = m[ 3 ];
+ r[ 2 ] = m[ 6 ];
+ r[ 3 ] = m[ 1 ];
+ r[ 4 ] = m[ 4 ];
+ r[ 5 ] = m[ 7 ];
+ r[ 6 ] = m[ 2 ];
+ r[ 7 ] = m[ 5 ];
+ r[ 8 ] = m[ 8 ];
+
+ return this;
+
+ },
+
+ setUvTransform: function ( tx, ty, sx, sy, rotation, cx, cy ) {
+
+ var c = Math.cos( rotation );
+ var s = Math.sin( rotation );
+
+ this.set(
+ sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,
+ - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,
+ 0, 0, 1
+ );
+
+ },
+
+ scale: function ( sx, sy ) {
+
+ var te = this.elements;
+
+ te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx;
+ te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy;
+
+ return this;
+
+ },
+
+ rotate: function ( theta ) {
+
+ var c = Math.cos( theta );
+ var s = Math.sin( theta );
+
+ var te = this.elements;
+
+ var a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ];
+ var a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ];
+
+ te[ 0 ] = c * a11 + s * a21;
+ te[ 3 ] = c * a12 + s * a22;
+ te[ 6 ] = c * a13 + s * a23;
+
+ te[ 1 ] = - s * a11 + c * a21;
+ te[ 4 ] = - s * a12 + c * a22;
+ te[ 7 ] = - s * a13 + c * a23;
+
+ return this;
+
+ },
+
+ translate: function ( tx, ty ) {
+
+ var te = this.elements;
+
+ te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ];
+ te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ];
+
+ return this;
+
+ },
+
+ equals: function ( matrix ) {
+
+ var te = this.elements;
+ var me = matrix.elements;
+
+ for ( var i = 0; i < 9; i ++ ) {
+
+ if ( te[ i ] !== me[ i ] ) return false;
+
+ }
+
+ return true;
+
+ },
+
+ fromArray: function ( array, offset ) {
+
+ if ( offset === undefined ) offset = 0;
+
+ for ( var i = 0; i < 9; i ++ ) {
+
+ this.elements[ i ] = array[ i + offset ];
+
+ }
+
+ return this;
+
+ },
+
+ toArray: function ( array, offset ) {
+
+ if ( array === undefined ) array = [];
+ if ( offset === undefined ) offset = 0;
+
+ var te = this.elements;
+
+ array[ offset ] = te[ 0 ];
+ array[ offset + 1 ] = te[ 1 ];
+ array[ offset + 2 ] = te[ 2 ];
+
+ array[ offset + 3 ] = te[ 3 ];
+ array[ offset + 4 ] = te[ 4 ];
+ array[ offset + 5 ] = te[ 5 ];
+
+ array[ offset + 6 ] = te[ 6 ];
+ array[ offset + 7 ] = te[ 7 ];
+ array[ offset + 8 ] = te[ 8 ];
+
+ return array;
+
+ }
+
+ } );
+
+ /**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+ var _canvas;
+
+ var ImageUtils = {
+
+ getDataURL: function ( image ) {
+
+ var canvas;
+
+ if ( typeof HTMLCanvasElement == 'undefined' ) {
+
+ return image.src;
+
+ } else if ( image instanceof HTMLCanvasElement ) {
+
+ canvas = image;
+
+ } else {
+
+ if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+
+ _canvas.width = image.width;
+ _canvas.height = image.height;
+
+ var context = _canvas.getContext( '2d' );
+
+ if ( image instanceof ImageData ) {
+
+ context.putImageData( image, 0, 0 );
+
+ } else {
+
+ context.drawImage( image, 0, 0, image.width, image.height );
+
+ }
+
+ canvas = _canvas;
+
+ }
+
+ if ( canvas.width > 2048 || canvas.height > 2048 ) {
+
+ return canvas.toDataURL( 'image/jpeg', 0.6 );
+
+ } else {
+
+ return canvas.toDataURL( 'image/png' );
+
+ }
+
+ }
+
+ };
+
+ /**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+ var textureId = 0;
+
+ function Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {
+
+ Object.defineProperty( this, 'id', { value: textureId ++ } );
+
+ this.uuid = _Math.generateUUID();
+
+ this.name = '';
+
+ this.image = image !== undefined ? image : Texture.DEFAULT_IMAGE;
+ this.mipmaps = [];
+
+ this.mapping = mapping !== undefined ? mapping : Texture.DEFAULT_MAPPING;
+
+ this.wrapS = wrapS !== undefined ? wrapS : ClampToEdgeWrapping;
+ this.wrapT = wrapT !== undefined ? wrapT : ClampToEdgeWrapping;
+
+ this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
+ this.minFilter = minFilter !== undefined ? minFilter : LinearMipMapLinearFilter;
+
+ this.anisotropy = anisotropy !== undefined ? anisotropy : 1;
+
+ this.format = format !== undefined ? format : RGBAFormat;
+ this.type = type !== undefined ? type : UnsignedByteType;
+
+ this.offset = new Vector2( 0, 0 );
+ this.repeat = new Vector2( 1, 1 );
+ this.center = new Vector2( 0, 0 );
+ this.rotation = 0;
+
+ this.matrixAutoUpdate = true;
+ this.matrix = new Matrix3();
+
+ this.generateMipmaps = true;
+ this.premultiplyAlpha = false;
+ this.flipY = true;
+ this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
+
+ // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap.
+ //
+ // Also changing the encoding after already used by a Material will not automatically make the Material
+ // update. You need to explicitly call Material.needsUpdate to trigger it to recompile.
+ this.encoding = encoding !== undefined ? encoding : LinearEncoding;
+
+ this.version = 0;
+ this.onUpdate = null;
+
+ }
+
+ Texture.DEFAULT_IMAGE = undefined;
+ Texture.DEFAULT_MAPPING = UVMapping;
+
+ Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+ constructor: Texture,
+
+ isTexture: true,
+
+ updateMatrix: function () {
+
+ this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y );
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( source ) {
+
+ this.name = source.name;
+
+ this.image = source.image;
+ this.mipmaps = source.mipmaps.slice( 0 );
+
+ this.mapping = source.mapping;
+
+ this.wrapS = source.wrapS;
+ this.wrapT = source.wrapT;
+
+ this.magFilter = source.magFilter;
+ this.minFilter = source.minFilter;
+
+ this.anisotropy = source.anisotropy;
+
+ this.format = source.format;
+ this.type = source.type;
+
+ this.offset.copy( source.offset );
+ this.repeat.copy( source.repeat );
+ this.center.copy( source.center );
+ this.rotation = source.rotation;
+
+ this.matrixAutoUpdate = source.matrixAutoUpdate;
+ this.matrix.copy( source.matrix );
+
+ this.generateMipmaps = source.generateMipmaps;
+ this.premultiplyAlpha = source.premultiplyAlpha;
+ this.flipY = source.flipY;
+ this.unpackAlignment = source.unpackAlignment;
+ this.encoding = source.encoding;
+
+ return this;
+
+ },
+
+ toJSON: function ( meta ) {
+
+ var isRootObject = ( meta === undefined || typeof meta === 'string' );
+
+ if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) {
+
+ return meta.textures[ this.uuid ];
+
+ }
+
+ var output = {
+
+ 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,
+ minFilter: this.minFilter,
+ magFilter: this.magFilter,
+ anisotropy: this.anisotropy,
+
+ flipY: this.flipY
+
+ };
+
+ if ( this.image !== undefined ) {
+
+ // TODO: Move to THREE.Image
+
+ var image = this.image;
+
+ if ( image.uuid === undefined ) {
+
+ image.uuid = _Math.generateUUID(); // UGH
+
+ }
+
+ if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) {
+
+ var url;
+
+ if ( Array.isArray( image ) ) {
+
+ // process array of images e.g. CubeTexture
+
+ url = [];
+
+ for ( var i = 0, l = image.length; i < l; i ++ ) {
+
+ url.push( ImageUtils.getDataURL( image[ i ] ) );
+
+ }
+
+ } else {
+
+ // process single image
+
+ url = ImageUtils.getDataURL( image );
+
+ }
+
+ meta.images[ image.uuid ] = {
+ uuid: image.uuid,
+ url: url
+ };
+
+ }
+
+ output.image = image.uuid;
+
+ }
+
+ if ( ! isRootObject ) {
+
+ meta.textures[ this.uuid ] = output;
+
+ }
+
+ return output;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ },
+
+ transformUv: function ( uv ) {
+
+ if ( this.mapping !== UVMapping ) return uv;
+
+ uv.applyMatrix3( this.matrix );
+
+ if ( uv.x < 0 || uv.x > 1 ) {
+
+ switch ( this.wrapS ) {
+
+ case RepeatWrapping:
+
+ uv.x = uv.x - Math.floor( uv.x );
+ break;
+
+ case ClampToEdgeWrapping:
+
+ uv.x = uv.x < 0 ? 0 : 1;
+ break;
+
+ case MirroredRepeatWrapping:
+
+ if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) {
+
+ uv.x = Math.ceil( uv.x ) - uv.x;
+
+ } else {
+
+ uv.x = uv.x - Math.floor( uv.x );
+
+ }
+ break;
+
+ }
+
+ }
+
+ if ( uv.y < 0 || uv.y > 1 ) {
+
+ switch ( this.wrapT ) {
+
+ case RepeatWrapping:
+
+ uv.y = uv.y - Math.floor( uv.y );
+ break;
+
+ case ClampToEdgeWrapping:
+
+ uv.y = uv.y < 0 ? 0 : 1;
+ break;
+
+ case MirroredRepeatWrapping:
+
+ if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) {
+
+ uv.y = Math.ceil( uv.y ) - uv.y;
+
+ } else {
+
+ uv.y = uv.y - Math.floor( uv.y );
+
+ }
+ break;
+
+ }
+
+ }
+
+ if ( this.flipY ) {
+
+ uv.y = 1 - uv.y;
+
+ }
+
+ return uv;
+
+ }
+
+ } );
+
+ Object.defineProperty( Texture.prototype, "needsUpdate", {
+
+ set: function ( value ) {
+
+ if ( value === true ) this.version ++;
+
+ }
+
+ } );
+
+ /**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+ function Vector4( x, y, z, w ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+ this.z = z || 0;
+ this.w = ( w !== undefined ) ? w : 1;
+
+ }
+
+ Object.assign( Vector4.prototype, {
+
+ isVector4: true,
+
+ set: function ( x, y, z, w ) {
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+
+ return this;
+
+ },
+
+ setScalar: function ( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+ this.z = scalar;
+ this.w = scalar;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+ setZ: function ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ },
+
+ setW: function ( w ) {
+
+ this.w = w;
+
+ return this;
+
+ },
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ case 3: this.w = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ 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: ' + index );
+
+ }
+
+ },
+
+ clone: function () {
+
+ return new this.constructor( this.x, this.y, this.z, this.w );
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ this.w = ( v.w !== undefined ) ? v.w : 1;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+ this.w += v.w;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+ this.w += s;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+ this.w = a.w + b.w;
+
+ return this;
+
+ },
+
+ addScaledVector: function ( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+ this.z += v.z * s;
+ this.w += v.w * s;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+ this.w -= v.w;
+
+ return this;
+
+ },
+
+ subScalar: function ( s ) {
+
+ this.x -= s;
+ this.y -= s;
+ this.z -= s;
+ this.w -= s;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+ this.w = a.w - b.w;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ this.w *= scalar;
+
+ return this;
+
+ },
+
+ applyMatrix4: function ( m ) {
+
+ var x = this.x, y = this.y, z = this.z, w = this.w;
+ var e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w;
+ this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w;
+ this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w;
+ this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ },
+
+ setAxisAngleFromQuaternion: function ( q ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
+
+ // q is assumed to be normalized
+
+ this.w = 2 * Math.acos( q.w );
+
+ var s = Math.sqrt( 1 - q.w * q.w );
+
+ if ( s < 0.0001 ) {
+
+ this.x = 1;
+ this.y = 0;
+ this.z = 0;
+
+ } else {
+
+ this.x = q.x / s;
+ this.y = q.y / s;
+ this.z = q.z / s;
+
+ }
+
+ return this;
+
+ },
+
+ setAxisAngleFromRotationMatrix: function ( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ var angle, x, y, z, // variables for result
+ epsilon = 0.01, // margin to allow for rounding errors
+ epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees
+
+ te = m.elements,
+
+ m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
+ m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
+ m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
+
+ if ( ( Math.abs( m12 - m21 ) < epsilon ) &&
+ ( Math.abs( m13 - m31 ) < epsilon ) &&
+ ( Math.abs( m23 - m32 ) < epsilon ) ) {
+
+ // singularity found
+ // first check for identity matrix which must have +1 for all terms
+ // in leading diagonal and zero in other terms
+
+ if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&
+ ( Math.abs( m13 + m31 ) < epsilon2 ) &&
+ ( Math.abs( m23 + m32 ) < epsilon2 ) &&
+ ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
+
+ // this singularity is identity matrix so angle = 0
+
+ this.set( 1, 0, 0, 0 );
+
+ return this; // zero angle, arbitrary axis
+
+ }
+
+ // otherwise this singularity is angle = 180
+
+ angle = Math.PI;
+
+ var xx = ( m11 + 1 ) / 2;
+ var yy = ( m22 + 1 ) / 2;
+ var zz = ( m33 + 1 ) / 2;
+ var xy = ( m12 + m21 ) / 4;
+ var xz = ( m13 + m31 ) / 4;
+ var yz = ( m23 + m32 ) / 4;
+
+ if ( ( xx > yy ) && ( xx > zz ) ) {
+
+ // m11 is the largest diagonal term
+
+ if ( xx < epsilon ) {
+
+ x = 0;
+ y = 0.707106781;
+ z = 0.707106781;
+
+ } else {
+
+ x = Math.sqrt( xx );
+ y = xy / x;
+ z = xz / x;
+
+ }
+
+ } else if ( yy > zz ) {
+
+ // m22 is the largest diagonal term
+
+ if ( yy < epsilon ) {
+
+ x = 0.707106781;
+ y = 0;
+ z = 0.707106781;
+
+ } else {
+
+ y = Math.sqrt( yy );
+ x = xy / y;
+ z = yz / y;
+
+ }
+
+ } else {
+
+ // m33 is the largest diagonal term so base result on this
+
+ if ( zz < epsilon ) {
+
+ x = 0.707106781;
+ y = 0.707106781;
+ z = 0;
+
+ } else {
+
+ z = Math.sqrt( zz );
+ x = xz / z;
+ y = yz / z;
+
+ }
+
+ }
+
+ this.set( x, y, z, angle );
+
+ return this; // return 180 deg rotation
+
+ }
+
+ // as we have reached here there are no singularities so we can handle normally
+
+ var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +
+ ( m13 - m31 ) * ( m13 - m31 ) +
+ ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize
+
+ if ( Math.abs( s ) < 0.001 ) s = 1;
+
+ // prevent divide by zero, should not happen if matrix is orthogonal and should be
+ // caught by singularity test above, but I've left it in just in case
+
+ this.x = ( m32 - m23 ) / s;
+ this.y = ( m13 - m31 ) / s;
+ this.z = ( m21 - m12 ) / s;
+ this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+ this.z = Math.min( this.z, v.z );
+ this.w = Math.min( this.w, v.w );
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+ this.z = Math.max( this.z, v.z );
+ this.w = Math.max( this.w, v.w );
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+ this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+ this.z = Math.max( min.z, Math.min( max.z, this.z ) );
+ this.w = Math.max( min.w, Math.min( max.w, this.w ) );
+
+ return this;
+
+ },
+
+ clampScalar: function () {
+
+ var min, max;
+
+ return function clampScalar( minVal, maxVal ) {
+
+ if ( min === undefined ) {
+
+ min = new Vector4();
+ max = new Vector4();
+
+ }
+
+ min.set( minVal, minVal, minVal, minVal );
+ max.set( maxVal, maxVal, maxVal, maxVal );
+
+ return this.clamp( min, max );
+
+ };
+
+ }(),
+
+ clampLength: function ( min, max ) {
+
+ var length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+ },
+
+ floor: function () {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+ this.z = Math.floor( this.z );
+ this.w = Math.floor( this.w );
+
+ return this;
+
+ },
+
+ ceil: function () {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+ this.z = Math.ceil( this.z );
+ this.w = Math.ceil( this.w );
+
+ return this;
+
+ },
+
+ round: function () {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+ this.z = Math.round( this.z );
+ this.w = Math.round( this.w );
+
+ return this;
+
+ },
+
+ roundToZero: function () {
+
+ 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.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w );
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ this.x = - this.x;
+ this.y = - this.y;
+ this.z = - this.z;
+ this.w = - this.w;
+
+ return this;
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+
+ },
+
+ manhattanLength: function () {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() || 1 );
+
+ },
+
+ setLength: function ( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+ this.w += ( v.w - this.w ) * alpha;
+
+ return this;
+
+ },
+
+ lerpVectors: function ( v1, v2, alpha ) {
+
+ return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );
+
+ },
+
+ equals: function ( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
+
+ },
+
+ fromArray: function ( array, offset ) {
+
+ if ( offset === undefined ) offset = 0;
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+ this.z = array[ offset + 2 ];
+ this.w = array[ offset + 3 ];
+
+ return this;
+
+ },
+
+ toArray: function ( array, offset ) {
+
+ if ( array === undefined ) array = [];
+ if ( offset === undefined ) offset = 0;
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+ array[ offset + 2 ] = this.z;
+ array[ offset + 3 ] = this.w;
+
+ return array;
+
+ },
+
+ fromBufferAttribute: function ( attribute, index, offset ) {
+
+ if ( offset !== undefined ) {
+
+ console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' );
+
+ }
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+ this.z = attribute.getZ( index );
+ this.w = attribute.getW( index );
+
+ return this;
+
+ }
+
+ } );
+
+ /**
+ * @author szimek / https://github.com/szimek/
+ * @author alteredq / http://alteredqualia.com/
+ * @author Marius Kintel / https://github.com/kintel
+ */
+
+ /*
+ In options, we can specify:
+ * Texture parameters for an auto-generated target texture
+ * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers
+ */
+ function WebGLRenderTarget( width, height, options ) {
+
+ this.width = width;
+ this.height = height;
+
+ this.scissor = new Vector4( 0, 0, width, height );
+ this.scissorTest = false;
+
+ this.viewport = new Vector4( 0, 0, width, height );
+
+ options = options || {};
+
+ this.texture = new Texture( undefined, undefined, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );
+
+ this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
+ this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;
+
+ this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
+ this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;
+ this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null;
+
+ }
+
+ WebGLRenderTarget.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+ constructor: WebGLRenderTarget,
+
+ isWebGLRenderTarget: true,
+
+ setSize: function ( width, height ) {
+
+ if ( this.width !== width || this.height !== height ) {
+
+ this.width = width;
+ this.height = height;
+
+ this.dispose();
+
+ }
+
+ this.viewport.set( 0, 0, width, height );
+ this.scissor.set( 0, 0, width, height );
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( source ) {
+
+ this.width = source.width;
+ this.height = source.height;
+
+ this.viewport.copy( source.viewport );
+
+ this.texture = source.texture.clone();
+
+ this.depthBuffer = source.depthBuffer;
+ this.stencilBuffer = source.stencilBuffer;
+ this.depthTexture = source.depthTexture;
+
+ return this;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+ } );
+
+ /**
+ * @author alteredq / http://alteredqualia.com
+ */
+
+ function WebGLRenderTargetCube( width, height, options ) {
+
+ WebGLRenderTarget.call( this, width, height, options );
+
+ this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5
+ this.activeMipMapLevel = 0;
+
+ }
+
+ WebGLRenderTargetCube.prototype = Object.create( WebGLRenderTarget.prototype );
+ WebGLRenderTargetCube.prototype.constructor = WebGLRenderTargetCube;
+
+ WebGLRenderTargetCube.prototype.isWebGLRenderTargetCube = true;
+
+ /**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+ function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+
+ Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+
+ this.image = { data: data, width: width, height: height };
+
+ this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
+ this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+
+ this.generateMipmaps = false;
+ this.flipY = false;
+ this.unpackAlignment = 1;
+
+ }
+
+ DataTexture.prototype = Object.create( Texture.prototype );
+ DataTexture.prototype.constructor = DataTexture;
+
+ DataTexture.prototype.isDataTexture = true;
+
+ /**
+ * @author bhouston / http://clara.io
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+ function Box3( min, max ) {
+
+ this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity );
+ this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity );
+
+ }
+
+ Object.assign( Box3.prototype, {
+
+ isBox3: true,
+
+ set: function ( min, max ) {
+
+ this.min.copy( min );
+ this.max.copy( max );
+
+ return this;
+
+ },
+
+ setFromArray: function ( array ) {
+
+ var minX = + Infinity;
+ var minY = + Infinity;
+ var minZ = + Infinity;
+
+ var maxX = - Infinity;
+ var maxY = - Infinity;
+ var maxZ = - Infinity;
+
+ for ( var i = 0, l = array.length; i < l; i += 3 ) {
+
+ var x = array[ i ];
+ var y = array[ i + 1 ];
+ var z = array[ i + 2 ];
+
+ if ( x < minX ) minX = x;
+ if ( y < minY ) minY = y;
+ if ( z < minZ ) minZ = z;
+
+ if ( x > maxX ) maxX = x;
+ if ( y > maxY ) maxY = y;
+ if ( z > maxZ ) maxZ = z;
+
+ }
+
+ this.min.set( minX, minY, minZ );
+ this.max.set( maxX, maxY, maxZ );
+
+ return this;
+
+ },
+
+ setFromBufferAttribute: function ( attribute ) {
+
+ var minX = + Infinity;
+ var minY = + Infinity;
+ var minZ = + Infinity;
+
+ var maxX = - Infinity;
+ var maxY = - Infinity;
+ var maxZ = - Infinity;
+
+ for ( var i = 0, l = attribute.count; i < l; i ++ ) {
+
+ var x = attribute.getX( i );
+ var y = attribute.getY( i );
+ var z = attribute.getZ( i );
+
+ if ( x < minX ) minX = x;
+ if ( y < minY ) minY = y;
+ if ( z < minZ ) minZ = z;
+
+ if ( x > maxX ) maxX = x;
+ if ( y > maxY ) maxY = y;
+ if ( z > maxZ ) maxZ = z;
+
+ }
+
+ this.min.set( minX, minY, minZ );
+ this.max.set( maxX, maxY, maxZ );
+
+ return this;
+
+ },
+
+ setFromPoints: function ( points ) {
+
+ this.makeEmpty();
+
+ for ( var i = 0, il = points.length; i < il; i ++ ) {
+
+ this.expandByPoint( points[ i ] );
+
+ }
+
+ return this;
+
+ },
+
+ setFromCenterAndSize: function () {
+
+ var v1 = new Vector3();
+
+ return function setFromCenterAndSize( center, size ) {
+
+ var halfSize = v1.copy( size ).multiplyScalar( 0.5 );
+
+ this.min.copy( center ).sub( halfSize );
+ this.max.copy( center ).add( halfSize );
+
+ return this;
+
+ };
+
+ }(),
+
+ setFromObject: function ( object ) {
+
+ this.makeEmpty();
+
+ return this.expandByObject( object );
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( box ) {
+
+ this.min.copy( box.min );
+ this.max.copy( box.max );
+
+ return this;
+
+ },
+
+ makeEmpty: function () {
+
+ this.min.x = this.min.y = this.min.z = + Infinity;
+ this.max.x = this.max.y = this.max.z = - Infinity;
+
+ return this;
+
+ },
+
+ isEmpty: function () {
+
+ // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+ return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );
+
+ },
+
+ getCenter: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .getCenter() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+ },
+
+ getSize: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .getSize() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min );
+
+ },
+
+ expandByPoint: function ( point ) {
+
+ this.min.min( point );
+ this.max.max( point );
+
+ return this;
+
+ },
+
+ expandByVector: function ( vector ) {
+
+ this.min.sub( vector );
+ this.max.add( vector );
+
+ return this;
+
+ },
+
+ expandByScalar: function ( scalar ) {
+
+ this.min.addScalar( - scalar );
+ this.max.addScalar( scalar );
+
+ return this;
+
+ },
+
+ expandByObject: function () {
+
+ // Computes the world-axis-aligned bounding box of an object (including its children),
+ // accounting for both the object's, and children's, world transforms
+
+ var scope, i, l;
+
+ var v1 = new Vector3();
+
+ function traverse( node ) {
+
+ var geometry = node.geometry;
+
+ if ( geometry !== undefined ) {
+
+ if ( geometry.isGeometry ) {
+
+ var vertices = geometry.vertices;
+
+ for ( i = 0, l = vertices.length; i < l; i ++ ) {
+
+ v1.copy( vertices[ i ] );
+ v1.applyMatrix4( node.matrixWorld );
+
+ scope.expandByPoint( v1 );
+
+ }
+
+ } else if ( geometry.isBufferGeometry ) {
+
+ var attribute = geometry.attributes.position;
+
+ if ( attribute !== undefined ) {
+
+ for ( i = 0, l = attribute.count; i < l; i ++ ) {
+
+ v1.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld );
+
+ scope.expandByPoint( v1 );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return function expandByObject( object ) {
+
+ scope = this;
+
+ object.updateMatrixWorld( true );
+
+ object.traverse( traverse );
+
+ return this;
+
+ };
+
+ }(),
+
+ containsPoint: function ( point ) {
+
+ return point.x < this.min.x || point.x > this.max.x ||
+ point.y < this.min.y || point.y > this.max.y ||
+ point.z < this.min.z || point.z > this.max.z ? false : true;
+
+ },
+
+ containsBox: function ( box ) {
+
+ return this.min.x <= box.min.x && box.max.x <= this.max.x &&
+ this.min.y <= box.min.y && box.max.y <= this.max.y &&
+ this.min.z <= box.min.z && box.max.z <= this.max.z;
+
+ },
+
+ getParameter: function ( point, target ) {
+
+ // This can potentially have a divide by zero if the box
+ // has a size dimension of 0.
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .getParameter() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.set(
+ ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+ ( point.y - this.min.y ) / ( this.max.y - this.min.y ),
+ ( point.z - this.min.z ) / ( this.max.z - this.min.z )
+ );
+
+ },
+
+ intersectsBox: function ( box ) {
+
+ // using 6 splitting planes to rule out intersections.
+ return box.max.x < this.min.x || box.min.x > this.max.x ||
+ box.max.y < this.min.y || box.min.y > this.max.y ||
+ box.max.z < this.min.z || box.min.z > this.max.z ? false : true;
+
+ },
+
+ intersectsSphere: ( function () {
+
+ var closestPoint = new Vector3();
+
+ return function intersectsSphere( sphere ) {
+
+ // Find the point on the AABB closest to the sphere center.
+ this.clampPoint( sphere.center, closestPoint );
+
+ // If that point is inside the sphere, the AABB and sphere intersect.
+ return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
+
+ };
+
+ } )(),
+
+ intersectsPlane: function ( plane ) {
+
+ // We compute the minimum and maximum dot product values. If those values
+ // are on the same side (back or front) of the plane, then there is no intersection.
+
+ var min, max;
+
+ if ( plane.normal.x > 0 ) {
+
+ min = plane.normal.x * this.min.x;
+ max = plane.normal.x * this.max.x;
+
+ } else {
+
+ min = plane.normal.x * this.max.x;
+ max = plane.normal.x * this.min.x;
+
+ }
+
+ if ( plane.normal.y > 0 ) {
+
+ min += plane.normal.y * this.min.y;
+ max += plane.normal.y * this.max.y;
+
+ } else {
+
+ min += plane.normal.y * this.max.y;
+ max += plane.normal.y * this.min.y;
+
+ }
+
+ if ( plane.normal.z > 0 ) {
+
+ min += plane.normal.z * this.min.z;
+ max += plane.normal.z * this.max.z;
+
+ } else {
+
+ min += plane.normal.z * this.max.z;
+ max += plane.normal.z * this.min.z;
+
+ }
+
+ return ( min <= - plane.constant && max >= - plane.constant );
+
+ },
+
+ intersectsTriangle: ( function () {
+
+ // triangle centered vertices
+ var v0 = new Vector3();
+ var v1 = new Vector3();
+ var v2 = new Vector3();
+
+ // triangle edge vectors
+ var f0 = new Vector3();
+ var f1 = new Vector3();
+ var f2 = new Vector3();
+
+ var testAxis = new Vector3();
+
+ var center = new Vector3();
+ var extents = new Vector3();
+
+ var triangleNormal = new Vector3();
+
+ function satForAxes( axes ) {
+
+ var i, j;
+
+ for ( i = 0, j = axes.length - 3; i <= j; i += 3 ) {
+
+ testAxis.fromArray( axes, i );
+ // project the aabb onto the seperating axis
+ var r = extents.x * Math.abs( testAxis.x ) + extents.y * Math.abs( testAxis.y ) + extents.z * Math.abs( testAxis.z );
+ // project all 3 vertices of the triangle onto the seperating axis
+ var p0 = v0.dot( testAxis );
+ var p1 = v1.dot( testAxis );
+ var p2 = v2.dot( testAxis );
+ // actual test, basically see if either of the most extreme of the triangle points intersects r
+ if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) {
+
+ // points of the projected triangle are outside the projected half-length of the aabb
+ // the axis is seperating and we can exit
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ return function intersectsTriangle( triangle ) {
+
+ if ( this.isEmpty() ) {
+
+ return false;
+
+ }
+
+ // compute box center and extents
+ this.getCenter( center );
+ extents.subVectors( this.max, center );
+
+ // translate triangle to aabb origin
+ v0.subVectors( triangle.a, center );
+ v1.subVectors( triangle.b, center );
+ v2.subVectors( triangle.c, center );
+
+ // compute edge vectors for triangle
+ f0.subVectors( v1, v0 );
+ f1.subVectors( v2, v1 );
+ f2.subVectors( v0, v2 );
+
+ // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb
+ // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation
+ // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned)
+ var axes = [
+ 0, - f0.z, f0.y, 0, - f1.z, f1.y, 0, - f2.z, f2.y,
+ f0.z, 0, - f0.x, f1.z, 0, - f1.x, f2.z, 0, - f2.x,
+ - f0.y, f0.x, 0, - f1.y, f1.x, 0, - f2.y, f2.x, 0
+ ];
+ if ( ! satForAxes( axes ) ) {
+
+ return false;
+
+ }
+
+ // test 3 face normals from the aabb
+ axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
+ if ( ! satForAxes( axes ) ) {
+
+ return false;
+
+ }
+
+ // finally testing the face normal of the triangle
+ // use already existing triangle edge vectors here
+ triangleNormal.crossVectors( f0, f1 );
+ axes = [ triangleNormal.x, triangleNormal.y, triangleNormal.z ];
+ return satForAxes( axes );
+
+ };
+
+ } )(),
+
+ clampPoint: function ( point, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .clampPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.copy( point ).clamp( this.min, this.max );
+
+ },
+
+ distanceToPoint: function () {
+
+ var v1 = new Vector3();
+
+ return function distanceToPoint( point ) {
+
+ var clampedPoint = v1.copy( point ).clamp( this.min, this.max );
+ return clampedPoint.sub( point ).length();
+
+ };
+
+ }(),
+
+ getBoundingSphere: function () {
+
+ var v1 = new Vector3();
+
+ return function getBoundingSphere( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .getBoundingSphere() target is now required' );
+ target = new Sphere();
+
+ }
+
+ this.getCenter( target.center );
+
+ target.radius = this.getSize( v1 ).length() * 0.5;
+
+ return target;
+
+ };
+
+ }(),
+
+ intersect: function ( box ) {
+
+ this.min.max( box.min );
+ this.max.min( box.max );
+
+ // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values.
+ if ( this.isEmpty() ) this.makeEmpty();
+
+ return this;
+
+ },
+
+ union: function ( box ) {
+
+ this.min.min( box.min );
+ this.max.max( box.max );
+
+ return this;
+
+ },
+
+ applyMatrix4: function () {
+
+ var points = [
+ new Vector3(),
+ new Vector3(),
+ new Vector3(),
+ new Vector3(),
+ new Vector3(),
+ new Vector3(),
+ new Vector3(),
+ new Vector3()
+ ];
+
+ return function applyMatrix4( matrix ) {
+
+ // transform of empty box is an empty box.
+ if ( this.isEmpty() ) return this;
+
+ // NOTE: I am using a binary pattern to specify all 2^3 combinations below
+ points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000
+ points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001
+ points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010
+ points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011
+ points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100
+ points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101
+ points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110
+ points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111
+
+ this.setFromPoints( points );
+
+ return this;
+
+ };
+
+ }(),
+
+ translate: function ( offset ) {
+
+ this.min.add( offset );
+ this.max.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( box ) {
+
+ return box.min.equals( this.min ) && box.max.equals( this.max );
+
+ }
+
+ } );
+
+ /**
+ * @author bhouston / http://clara.io
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+ function Sphere( center, radius ) {
+
+ this.center = ( center !== undefined ) ? center : new Vector3();
+ this.radius = ( radius !== undefined ) ? radius : 0;
+
+ }
+
+ Object.assign( Sphere.prototype, {
+
+ set: function ( center, radius ) {
+
+ this.center.copy( center );
+ this.radius = radius;
+
+ return this;
+
+ },
+
+ setFromPoints: function () {
+
+ var box = new Box3();
+
+ return function setFromPoints( points, optionalCenter ) {
+
+ var center = this.center;
+
+ if ( optionalCenter !== undefined ) {
+
+ center.copy( optionalCenter );
+
+ } else {
+
+ box.setFromPoints( points ).getCenter( center );
+
+ }
+
+ var maxRadiusSq = 0;
+
+ for ( var i = 0, il = points.length; i < il; i ++ ) {
+
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );
+
+ }
+
+ this.radius = Math.sqrt( maxRadiusSq );
+
+ return this;
+
+ };
+
+ }(),
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( sphere ) {
+
+ this.center.copy( sphere.center );
+ this.radius = sphere.radius;
+
+ return this;
+
+ },
+
+ empty: function () {
+
+ return ( this.radius <= 0 );
+
+ },
+
+ containsPoint: function ( point ) {
+
+ return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );
+
+ },
+
+ distanceToPoint: function ( point ) {
+
+ return ( point.distanceTo( this.center ) - this.radius );
+
+ },
+
+ intersectsSphere: function ( sphere ) {
+
+ var radiusSum = this.radius + sphere.radius;
+
+ return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );
+
+ },
+
+ intersectsBox: function ( box ) {
+
+ return box.intersectsSphere( this );
+
+ },
+
+ intersectsPlane: function ( plane ) {
+
+ return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius;
+
+ },
+
+ clampPoint: function ( point, target ) {
+
+ var deltaLengthSq = this.center.distanceToSquared( point );
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Sphere: .clampPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ target.copy( point );
+
+ if ( deltaLengthSq > ( this.radius * this.radius ) ) {
+
+ target.sub( this.center ).normalize();
+ target.multiplyScalar( this.radius ).add( this.center );
+
+ }
+
+ return target;
+
+ },
+
+ getBoundingBox: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Sphere: .getBoundingBox() target is now required' );
+ target = new Box3();
+
+ }
+
+ target.set( this.center, this.center );
+ target.expandByScalar( this.radius );
+
+ return target;
+
+ },
+
+ applyMatrix4: function ( matrix ) {
+
+ this.center.applyMatrix4( matrix );
+ this.radius = this.radius * matrix.getMaxScaleOnAxis();
+
+ return this;
+
+ },
+
+ translate: function ( offset ) {
+
+ this.center.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( sphere ) {
+
+ return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
+
+ }
+
+ } );
+
+ /**
+ * @author bhouston / http://clara.io
+ */
+
+ function Plane( normal, constant ) {
+
+ // normal is assumed to be normalized
+
+ this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 );
+ this.constant = ( constant !== undefined ) ? constant : 0;
+
+ }
+
+ Object.assign( Plane.prototype, {
+
+ set: function ( normal, constant ) {
+
+ this.normal.copy( normal );
+ this.constant = constant;
+
+ return this;
+
+ },
+
+ setComponents: function ( x, y, z, w ) {
+
+ this.normal.set( x, y, z );
+ this.constant = w;
+
+ return this;
+
+ },
+
+ setFromNormalAndCoplanarPoint: function ( normal, point ) {
+
+ this.normal.copy( normal );
+ this.constant = - point.dot( this.normal );
+
+ return this;
+
+ },
+
+ setFromCoplanarPoints: function () {
+
+ var v1 = new Vector3();
+ var v2 = new Vector3();
+
+ return function setFromCoplanarPoints( a, b, c ) {
+
+ var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize();
+
+ // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
+
+ this.setFromNormalAndCoplanarPoint( normal, a );
+
+ return this;
+
+ };
+
+ }(),
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( plane ) {
+
+ this.normal.copy( plane.normal );
+ this.constant = plane.constant;
+
+ return this;
+
+ },
+
+ normalize: function () {
+
+ // Note: will lead to a divide by zero if the plane is invalid.
+
+ var inverseNormalLength = 1.0 / this.normal.length();
+ this.normal.multiplyScalar( inverseNormalLength );
+ this.constant *= inverseNormalLength;
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ this.constant *= - 1;
+ this.normal.negate();
+
+ return this;
+
+ },
+
+ distanceToPoint: function ( point ) {
+
+ return this.normal.dot( point ) + this.constant;
+
+ },
+
+ distanceToSphere: function ( sphere ) {
+
+ return this.distanceToPoint( sphere.center ) - sphere.radius;
+
+ },
+
+ projectPoint: function ( point, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Plane: .projectPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point );
+
+ },
+
+ intersectLine: function () {
+
+ var v1 = new Vector3();
+
+ return function intersectLine( line, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Plane: .intersectLine() target is now required' );
+ target = new Vector3();
+
+ }
+
+ var direction = line.delta( v1 );
+
+ var denominator = this.normal.dot( direction );
+
+ if ( denominator === 0 ) {
+
+ // line is coplanar, return origin
+ if ( this.distanceToPoint( line.start ) === 0 ) {
+
+ return target.copy( line.start );
+
+ }
+
+ // Unsure if this is the correct method to handle this case.
+ return undefined;
+
+ }
+
+ var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
+
+ if ( t < 0 || t > 1 ) {
+
+ return undefined;
+
+ }
+
+ return target.copy( direction ).multiplyScalar( t ).add( line.start );
+
+ };
+
+ }(),
+
+ intersectsLine: function ( line ) {
+
+ // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
+
+ var startSign = this.distanceToPoint( line.start );
+ var endSign = this.distanceToPoint( line.end );
+
+ return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );
+
+ },
+
+ intersectsBox: function ( box ) {
+
+ return box.intersectsPlane( this );
+
+ },
+
+ intersectsSphere: function ( sphere ) {
+
+ return sphere.intersectsPlane( this );
+
+ },
+
+ coplanarPoint: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Plane: .coplanarPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.copy( this.normal ).multiplyScalar( - this.constant );
+
+ },
+
+ applyMatrix4: function () {
+
+ var v1 = new Vector3();
+ var m1 = new Matrix3();
+
+ return function applyMatrix4( matrix, optionalNormalMatrix ) {
+
+ var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix );
+
+ var referencePoint = this.coplanarPoint( v1 ).applyMatrix4( matrix );
+
+ var normal = this.normal.applyMatrix3( normalMatrix ).normalize();
+
+ this.constant = - referencePoint.dot( normal );
+
+ return this;
+
+ };
+
+ }(),
+
+ translate: function ( offset ) {
+
+ this.constant -= offset.dot( this.normal );
+
+ return this;
+
+ },
+
+ equals: function ( plane ) {
+
+ return plane.normal.equals( this.normal ) && ( plane.constant === this.constant );
+
+ }
+
+ } );
+
+ /**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author bhouston / http://clara.io
+ */
+
+ function Frustum( p0, p1, p2, p3, p4, p5 ) {
+
+ this.planes = [
+
+ ( p0 !== undefined ) ? p0 : new Plane(),
+ ( p1 !== undefined ) ? p1 : new Plane(),
+ ( p2 !== undefined ) ? p2 : new Plane(),
+ ( p3 !== undefined ) ? p3 : new Plane(),
+ ( p4 !== undefined ) ? p4 : new Plane(),
+ ( p5 !== undefined ) ? p5 : new Plane()
+
+ ];
+
+ }
+
+ Object.assign( Frustum.prototype, {
+
+ set: function ( p0, p1, p2, p3, p4, p5 ) {
+
+ var planes = this.planes;
+
+ planes[ 0 ].copy( p0 );
+ planes[ 1 ].copy( p1 );
+ planes[ 2 ].copy( p2 );
+ planes[ 3 ].copy( p3 );
+ planes[ 4 ].copy( p4 );
+ planes[ 5 ].copy( p5 );
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( frustum ) {
+
+ var planes = this.planes;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ planes[ i ].copy( frustum.planes[ i ] );
+
+ }
+
+ return this;
+
+ },
+
+ setFromMatrix: function ( m ) {
+
+ var planes = this.planes;
+ var me = m.elements;
+ var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];
+ var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];
+ var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];
+ var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];
+
+ planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
+ planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
+ planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
+ planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
+ planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
+ planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
+
+ return this;
+
+ },
+
+ intersectsObject: function () {
+
+ var sphere = new Sphere();
+
+ return function intersectsObject( object ) {
+
+ var geometry = object.geometry;
+
+ if ( geometry.boundingSphere === null )
+ geometry.computeBoundingSphere();
+
+ sphere.copy( geometry.boundingSphere )
+ .applyMatrix4( object.matrixWorld );
+
+ return this.intersectsSphere( sphere );
+
+ };
+
+ }(),
+
+ intersectsSprite: function () {
+
+ var sphere = new Sphere();
+
+ return function intersectsSprite( sprite ) {
+
+ sphere.center.set( 0, 0, 0 );
+ sphere.radius = 0.7071067811865476;
+ sphere.applyMatrix4( sprite.matrixWorld );
+
+ return this.intersectsSphere( sphere );
+
+ };
+
+ }(),
+
+ intersectsSphere: function ( sphere ) {
+
+ var planes = this.planes;
+ var center = sphere.center;
+ var negRadius = - sphere.radius;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ var distance = planes[ i ].distanceToPoint( center );
+
+ if ( distance < negRadius ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ },
+
+ intersectsBox: function () {
+
+ var p = new Vector3();
+
+ return function intersectsBox( box ) {
+
+ var planes = this.planes;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ var plane = planes[ i ];
+
+ // corner at max distance
+
+ p.x = plane.normal.x > 0 ? box.max.x : box.min.x;
+ p.y = plane.normal.y > 0 ? box.max.y : box.min.y;
+ p.z = plane.normal.z > 0 ? box.max.z : box.min.z;
+
+ if ( plane.distanceToPoint( p ) < 0 ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ };
+
+ }(),
+
+ containsPoint: function ( point ) {
+
+ var planes = this.planes;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ if ( planes[ i ].distanceToPoint( point ) < 0 ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ } );
+
+ var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif";
+
+ var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";
+
+ var alphatest_fragment = "#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif";
+
+ var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif";
+
+ var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif";
+
+ var begin_vertex = "vec3 transformed = vec3( position );";
+
+ var beginnormal_vertex = "vec3 objectNormal = vec3( normal );";
+
+ var bsdfs = "float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n#else\n\tif( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t}\n\treturn 1.0;\n#endif\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 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}\nvec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.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 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;\n\treturn specularColor * AB.x + AB.y;\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_Specular_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 dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}";
+
+ var 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 ) {\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 );\n\t\tfDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\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";
+
+ var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vViewPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vViewPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\tif ( clipped ) discard;\n\t#endif\n#endif";
+
+ var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\t#if ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( MATCAP )\n\t\tvarying vec3 vViewPosition;\n\t#endif\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif";
+
+ var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( MATCAP )\n\tvarying vec3 vViewPosition;\n#endif";
+
+ var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( MATCAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif";
+
+ var color_fragment = "#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif";
+
+ var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";
+
+ var color_pars_vertex = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";
+
+ var color_vertex = "#ifdef USE_COLOR\n\tvColor.xyz = color.xyz;\n#endif";
+
+ var common = "#define PI 3.14159265359\n#define PI2 6.28318530718\n#define PI_HALF 1.5707963267949\n#define RECIPROCAL_PI 0.31830988618\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#define whiteCompliment(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 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}\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};\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}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\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}";
+
+ var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n#define cubeUV_textureSize (1024.0)\nint getFaceFromDirection(vec3 direction) {\n\tvec3 absDirection = abs(direction);\n\tint face = -1;\n\tif( absDirection.x > absDirection.z ) {\n\t\tif(absDirection.x > absDirection.y )\n\t\t\tface = direction.x > 0.0 ? 0 : 3;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\telse {\n\t\tif(absDirection.z > absDirection.y )\n\t\t\tface = direction.z > 0.0 ? 2 : 5;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\treturn face;\n}\n#define cubeUV_maxLods1 (log2(cubeUV_textureSize*0.25) - 1.0)\n#define cubeUV_rangeClamp (exp2((6.0 - 1.0) * 2.0))\nvec2 MipLevelInfo( vec3 vec, float roughnessLevel, float roughness ) {\n\tfloat scale = exp2(cubeUV_maxLods1 - roughnessLevel);\n\tfloat dxRoughness = dFdx(roughness);\n\tfloat dyRoughness = dFdy(roughness);\n\tvec3 dx = dFdx( vec * scale * dxRoughness );\n\tvec3 dy = dFdy( vec * scale * dyRoughness );\n\tfloat d = max( dot( dx, dx ), dot( dy, dy ) );\n\td = clamp(d, 1.0, cubeUV_rangeClamp);\n\tfloat mipLevel = 0.5 * log2(d);\n\treturn vec2(floor(mipLevel), fract(mipLevel));\n}\n#define cubeUV_maxLods2 (log2(cubeUV_textureSize*0.25) - 2.0)\n#define cubeUV_rcpTextureSize (1.0 / cubeUV_textureSize)\nvec2 getCubeUV(vec3 direction, float roughnessLevel, float mipLevel) {\n\tmipLevel = roughnessLevel > cubeUV_maxLods2 - 3.0 ? 0.0 : mipLevel;\n\tfloat a = 16.0 * cubeUV_rcpTextureSize;\n\tvec2 exp2_packed = exp2( vec2( roughnessLevel, mipLevel ) );\n\tvec2 rcp_exp2_packed = vec2( 1.0 ) / exp2_packed;\n\tfloat powScale = exp2_packed.x * exp2_packed.y;\n\tfloat scale = rcp_exp2_packed.x * rcp_exp2_packed.y * 0.25;\n\tfloat mipOffset = 0.75*(1.0 - rcp_exp2_packed.y) * rcp_exp2_packed.x;\n\tbool bRes = mipLevel == 0.0;\n\tscale = bRes && (scale < a) ? a : scale;\n\tvec3 r;\n\tvec2 offset;\n\tint face = getFaceFromDirection(direction);\n\tfloat rcpPowScale = 1.0 / powScale;\n\tif( face == 0) {\n\t\tr = vec3(direction.x, -direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 1) {\n\t\tr = vec3(direction.y, direction.x, direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 2) {\n\t\tr = vec3(direction.z, direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 3) {\n\t\tr = vec3(direction.x, direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse if( face == 4) {\n\t\tr = vec3(direction.y, direction.x, -direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse {\n\t\tr = vec3(direction.z, -direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\tr = normalize(r);\n\tfloat texelOffset = 0.5 * cubeUV_rcpTextureSize;\n\tvec2 s = ( r.yz / abs( r.x ) + vec2( 1.0 ) ) * 0.5;\n\tvec2 base = offset + vec2( texelOffset );\n\treturn base + s * ( scale - 2.0 * texelOffset );\n}\n#define cubeUV_maxLods3 (log2(cubeUV_textureSize*0.25) - 3.0)\nvec4 textureCubeUV( sampler2D envMap, vec3 reflectedDirection, float roughness ) {\n\tfloat roughnessVal = roughness* cubeUV_maxLods3;\n\tfloat r1 = floor(roughnessVal);\n\tfloat r2 = r1 + 1.0;\n\tfloat t = fract(roughnessVal);\n\tvec2 mipInfo = MipLevelInfo(reflectedDirection, r1, roughness);\n\tfloat s = mipInfo.y;\n\tfloat level0 = mipInfo.x;\n\tfloat level1 = level0 + 1.0;\n\tlevel1 = level1 > 5.0 ? 5.0 : level1;\n\tlevel0 += min( floor( s + 0.5 ), 5.0 );\n\tvec2 uv_10 = getCubeUV(reflectedDirection, r1, level0);\n\tvec4 color10 = envMapTexelToLinear(texture2D(envMap, uv_10));\n\tvec2 uv_20 = getCubeUV(reflectedDirection, r2, level0);\n\tvec4 color20 = envMapTexelToLinear(texture2D(envMap, uv_20));\n\tvec4 result = mix(color10, color20, t);\n\treturn vec4(result.rgb, 1.0);\n}\n#endif";
+
+ var defaultnormal_vertex = "vec3 transformedNormal = normalMatrix * objectNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif";
+
+ var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif";
+
+ var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\n#endif";
+
+ var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif";
+
+ var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif";
+
+ var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );";
+
+ var 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 = min( floor( D ) / 255.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 = value.rgb * cLogLuvM;\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 = Xp_Y_XYZp.rgb * cLogLuvInverseM;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}";
+
+ var envmap_fragment = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToVertex, 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#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\tvec2 sampleUV;\n\t\treflectVec = normalize( reflectVec );\n\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\tvec4 envColor = texture2D( envMap, sampleUV );\n\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\treflectVec = normalize( reflectVec );\n\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\n\t\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\tenvColor = envMapTexelToLinear( envColor );\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";
+
+ var envmap_pars_fragment = "#if defined( USE_ENVMAP ) || defined( PHYSICAL )\n\tuniform float reflectivity;\n\tuniform float envMapIntensity;\n#endif\n#ifdef USE_ENVMAP\n\t#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )\n\t\tvarying vec3 vWorldPosition;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif";
+
+ var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif";
+
+ var envmap_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\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";
+
+ var fog_vertex = "#ifdef USE_FOG\n\tfogDepth = -mvPosition.z;\n#endif";
+
+ var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif";
+
+ var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif";
+
+ var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\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";
+
+ var gradientmap_pars_fragment = "#ifdef TOON\n\tuniform sampler2D gradientMap;\n\tvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\t\tfloat dotNL = dot( normal, lightDirection );\n\t\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t\t#ifdef USE_GRADIENTMAP\n\t\t\treturn texture2D( gradientMap, coord ).rgb;\n\t\t#else\n\t\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t\t#endif\n\t}\n#endif";
+
+ var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\treflectedLight.indirectDiffuse += PI * texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n#endif";
+
+ var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif";
+
+ var lights_lambert_vertex = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * 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#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * 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#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * 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#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvLightFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n#endif";
+
+ var lights_pars_begin = "uniform vec3 ambientLightColor;\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.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\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t\tfloat shadowCameraNear;\n\t\tfloat shadowCameraFar;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.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\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.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\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif";
+
+ var envmap_physical_pars_fragment = "#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, queryVec, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent ));\n\t\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\t\tvec2 sampleUV;\n\t\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif";
+
+ var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";
+
+ var lights_phong_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifdef TOON\n\t\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#else\n\t\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\t\tvec3 irradiance = dotNL * directLight.color;\n\t#endif\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_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_Diffuse_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)";
+
+ var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nmaterial.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );\n#ifdef STANDARD\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.clearCoat = saturate( clearCoat );\tmaterial.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );\n#endif";
+
+ var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3\tdiffuseColor;\n\tfloat\tspecularRoughness;\n\tvec3\tspecularColor;\n\t#ifndef STANDARD\n\t\tfloat clearCoat;\n\t\tfloat clearCoatRoughness;\n\t#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearCoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\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.specularRoughness;\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( 0, 1, 0 ),\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#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifndef STANDARD\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );\n\treflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\t#ifndef STANDARD\n\t\treflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\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_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifndef STANDARD\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\tfloat dotNL = dotNV;\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.indirectSpecular += ( 1.0 - clearCoatDHR ) * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );\n\t#ifndef STANDARD\n\t\treflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\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\n#define Material_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.specularRoughness )\n#define Material_ClearCoat_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.clearCoatRoughness )\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}";
+
+ var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = normalize( vViewPosition );\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( pointLight.shadow, directLight.visible ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( spotLight.shadow, directLight.visible ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop\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#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearCoatRadiance = vec3( 0.0 );\n#endif";
+
+ var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec3 lightMapIrradiance = texture2D( lightMap, vUv2 ).xyz * 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( PHYSICAL ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tirradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), maxMipLevel );\n\t#ifndef STANDARD\n\t\tclearCoatRadiance += getLightProbeIndirectRadiance( geometry, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );\n\t#endif\n#endif";
+
+ var 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, clearCoatRadiance, geometry, material, reflectedLight );\n#endif";
+
+ var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif";
+
+ var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n#endif";
+
+ var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif";
+
+ var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t#else\n\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\tgl_Position.z *= gl_Position.w;\n\t#endif\n#endif";
+
+ var map_fragment = "#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif";
+
+ var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif";
+
+ var map_particle_fragment = "#ifdef USE_MAP\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif";
+
+ var map_particle_pars_fragment = "#ifdef USE_MAP\n\tuniform mat3 uvTransform;\n\tuniform sampler2D map;\n#endif";
+
+ var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif";
+
+ var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif";
+
+ var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n\tobjectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n\tobjectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n\tobjectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n#endif";
+
+ var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_MORPHNORMALS\n\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif";
+
+ var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n\ttransformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n\ttransformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n\ttransformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\ttransformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n\ttransformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n\ttransformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n\ttransformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\t#endif\n#endif";
+
+ var normal_fragment_begin = "#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 * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n#endif";
+
+ var normal_fragment_maps = "#ifdef USE_NORMALMAP\n\t#ifdef OBJECTSPACE_NORMALMAP\n\t\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\t#ifdef FLIP_SIDED\n\t\t\tnormal = - normal;\n\t\t#endif\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t\tnormal = normalize( normalMatrix * normal );\n\t#else\n\t\tnormal = perturbNormal2Arb( -vViewPosition, normal );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif";
+
+ var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n\t#ifdef OBJECTSPACE_NORMALMAP\n\t\tuniform mat3 normalMatrix;\n\t#else\n\t\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\t\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\t\tvec2 st0 = dFdx( vUv.st );\n\t\t\tvec2 st1 = dFdy( vUv.st );\n\t\t\tfloat scale = sign( st1.t * st0.s - st0.t * st1.s );\n\t\t\tvec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );\n\t\t\tvec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );\n\t\t\tvec3 N = normalize( surf_norm );\n\t\t\tmat3 tsn = mat3( S, T, N );\n\t\t\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\t\tmapN.xy *= normalScale;\n\t\t\tmapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\treturn normalize( tsn * mapN );\n\t\t}\n\t#endif\n#endif";
+
+ var 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}\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}";
+
+ var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif";
+
+ var project_vertex = "vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\ngl_Position = projectionMatrix * mvPosition;";
+
+ var dithering_fragment = "#if defined( DITHERING )\n gl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif";
+
+ var dithering_pars_fragment = "#if defined( 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";
+
+ var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif";
+
+ var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif";
+
+ var shadowmap_pars_fragment = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tfloat texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {\n\t\tconst vec2 offset = vec2( 0.0, 1.0 );\n\t\tvec2 texelSize = vec2( 1.0 ) / size;\n\t\tvec2 centroidUV = floor( uv * size + 0.5 ) / size;\n\t\tfloat lb = texture2DCompare( depths, centroidUV + texelSize * offset.xx, compare );\n\t\tfloat lt = texture2DCompare( depths, centroidUV + texelSize * offset.xy, compare );\n\t\tfloat rb = texture2DCompare( depths, centroidUV + texelSize * offset.yx, compare );\n\t\tfloat rt = texture2DCompare( depths, centroidUV + texelSize * offset.yy, compare );\n\t\tvec2 f = fract( uv * size + 0.5 );\n\t\tfloat a = mix( lb, lt, f.y );\n\t\tfloat b = mix( rb, rt, f.y );\n\t\tfloat c = mix( a, b, f.x );\n\t\treturn c;\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\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( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, 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( 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 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\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\tshadow = (\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\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 )\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";
+
+ var shadowmap_pars_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n#endif";
+
+ var shadowmap_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n#endif";
+
+ var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tshadow *= bool( directionalLight.shadow ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tshadow *= bool( spotLight.shadow ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tshadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#endif\n\t#endif\n\treturn shadow;\n}";
+
+ var 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";
+
+ var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform 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";
+
+ var 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";
+
+ var 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#endif";
+
+ var 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";
+
+ var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif";
+
+ var tonemapping_fragment = "#if defined( TONE_MAPPING )\n gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif";
+
+ var tonemapping_pars_fragment = "#ifndef saturate\n\t#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nuniform float toneMappingWhitePoint;\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}\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\nvec3 Uncharted2ToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\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 ACESFilmicToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( ( color * ( 2.51 * color + 0.03 ) ) / ( color * ( 2.43 * color + 0.59 ) + 0.14 ) );\n}";
+
+ var uv_pars_fragment = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n#endif";
+
+ var uv_pars_vertex = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif";
+
+ var uv_vertex = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif";
+
+ var uv2_pars_fragment = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif";
+
+ var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n#endif";
+
+ var uv2_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = uv2;\n#endif";
+
+ var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\n#endif";
+
+ var 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}";
+
+ var 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}";
+
+ var cube_frag = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}";
+
+ var 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}";
+
+ var 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 \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\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - gl_FragCoord.z ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( gl_FragCoord.z );\n\t#endif\n}";
+
+ var depth_vert = "#include \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}";
+
+ var 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 \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}";
+
+ var 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}";
+
+ var equirect_frag = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV;\n\tsampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\tsampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;\n\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}";
+
+ var equirect_vert = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}";
+
+ var 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\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}";
+
+ var linedashed_vert = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvLineDistance = scale * lineDistance;\n\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}";
+
+ var 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 \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\treflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * 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\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}";
+
+ var 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#include \n\t#ifdef USE_ENVMAP\n\t#include \n\t#include \n\t#include \n\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}";
+
+ var meshlambert_frag = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\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 \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\treflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\n\t#include \n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_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_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}";
+
+ var meshlambert_vert = "#define LAMBERT\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\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}";
+
+ var meshmatcap_frag = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\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 \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\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\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}";
+
+ var meshmatcap_vert = "#define MATCAP\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include