├── server.bat ├── js ├── .DS_Store ├── main.js ├── GPGPU.js ├── ui.js ├── GeometryUtils.js └── gpgpu │ └── SimulationShader.js ├── Image ├── 50__.PNG ├── Pins.PNG ├── pic.PNG ├── pic1.PNG ├── 100___.PNG ├── 100_ks.PNG ├── PinPong.jpg ├── Points.PNG ├── Rigid1.PNG ├── Rigid2.PNG ├── Transf.jpg ├── WebGL1.PNG ├── WebGL2.PNG ├── com100.PNG ├── com150.PNG ├── com20.PNG ├── com50.PNG ├── image.PNG ├── pinEdge.PNG ├── Overview.PNG ├── Perf_Can1.PNG ├── Settings.PNG ├── Triangles.PNG ├── 100deltT001.PNG ├── 100deltT003.PNG ├── RigidControl.PNG ├── SettingPoint.PNG ├── SettingTri.PNG ├── com150_com.PNG └── AnimationControl.PNG ├── media ├── Back.png ├── Side.png ├── Ceiling.png └── Floor.png ├── Presentation ├── MileStone 2.pdf ├── Milestone 1.pdf └── WebGL Cloth Simulation.pdf ├── server.py ├── lib ├── stats.min.js ├── seedrandom.min.js ├── Points.js ├── OBJLoader.js ├── OBJMTLLoader.js ├── MTLLoader.js ├── es6-promise.min.js ├── OrbitControls.js └── webgl-debug.js ├── springVP.cpp ├── README.md ├── glMatrix-0.9.5.min.js └── index.html /server.bat: -------------------------------------------------------------------------------- 1 | python server.py -------------------------------------------------------------------------------- /js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/js/.DS_Store -------------------------------------------------------------------------------- /Image/50__.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/50__.PNG -------------------------------------------------------------------------------- /Image/Pins.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Pins.PNG -------------------------------------------------------------------------------- /Image/pic.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/pic.PNG -------------------------------------------------------------------------------- /Image/pic1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/pic1.PNG -------------------------------------------------------------------------------- /media/Back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/media/Back.png -------------------------------------------------------------------------------- /media/Side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/media/Side.png -------------------------------------------------------------------------------- /Image/100___.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/100___.PNG -------------------------------------------------------------------------------- /Image/100_ks.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/100_ks.PNG -------------------------------------------------------------------------------- /Image/PinPong.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/PinPong.jpg -------------------------------------------------------------------------------- /Image/Points.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Points.PNG -------------------------------------------------------------------------------- /Image/Rigid1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Rigid1.PNG -------------------------------------------------------------------------------- /Image/Rigid2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Rigid2.PNG -------------------------------------------------------------------------------- /Image/Transf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Transf.jpg -------------------------------------------------------------------------------- /Image/WebGL1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/WebGL1.PNG -------------------------------------------------------------------------------- /Image/WebGL2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/WebGL2.PNG -------------------------------------------------------------------------------- /Image/com100.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/com100.PNG -------------------------------------------------------------------------------- /Image/com150.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/com150.PNG -------------------------------------------------------------------------------- /Image/com20.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/com20.PNG -------------------------------------------------------------------------------- /Image/com50.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/com50.PNG -------------------------------------------------------------------------------- /Image/image.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/image.PNG -------------------------------------------------------------------------------- /Image/pinEdge.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/pinEdge.PNG -------------------------------------------------------------------------------- /media/Ceiling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/media/Ceiling.png -------------------------------------------------------------------------------- /media/Floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/media/Floor.png -------------------------------------------------------------------------------- /Image/Overview.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Overview.PNG -------------------------------------------------------------------------------- /Image/Perf_Can1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Perf_Can1.PNG -------------------------------------------------------------------------------- /Image/Settings.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Settings.PNG -------------------------------------------------------------------------------- /Image/Triangles.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/Triangles.PNG -------------------------------------------------------------------------------- /Image/100deltT001.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/100deltT001.PNG -------------------------------------------------------------------------------- /Image/100deltT003.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/100deltT003.PNG -------------------------------------------------------------------------------- /Image/RigidControl.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/RigidControl.PNG -------------------------------------------------------------------------------- /Image/SettingPoint.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/SettingPoint.PNG -------------------------------------------------------------------------------- /Image/SettingTri.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/SettingTri.PNG -------------------------------------------------------------------------------- /Image/com150_com.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/com150_com.PNG -------------------------------------------------------------------------------- /Image/AnimationControl.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Image/AnimationControl.PNG -------------------------------------------------------------------------------- /Presentation/MileStone 2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Presentation/MileStone 2.pdf -------------------------------------------------------------------------------- /Presentation/Milestone 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Presentation/Milestone 1.pdf -------------------------------------------------------------------------------- /Presentation/WebGL Cloth Simulation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zammiez/CIS565-Final-WebGL-Cloth-Simulation/HEAD/Presentation/WebGL Cloth Simulation.pdf -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | 2 | var handle_load = []; 3 | 4 | (function() { 5 | 'use strict'; 6 | 7 | window.onload = function() { 8 | for (var i = 0; i < handle_load.length; i++) { 9 | handle_load[i](); 10 | } 11 | }; 12 | })(); -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | if sys.version_info.major == 2: 5 | from SimpleHTTPServer import SimpleHTTPRequestHandler 6 | from SocketServer import TCPServer 7 | elif sys.version_info.major == 3: 8 | from http.server import SimpleHTTPRequestHandler 9 | from socketserver import TCPServer 10 | 11 | PORT = 10252 12 | 13 | Handler = SimpleHTTPRequestHandler 14 | 15 | httpd = TCPServer(("", PORT), Handler) 16 | 17 | print("serving at port {}".format(PORT)) 18 | httpd.serve_forever() 19 | -------------------------------------------------------------------------------- /lib/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function f(a,e,b){a=document.createElement(a);a.id=e;a.style.cssText=b;return a}function l(a,e,b){var c=f("div",a,"padding:0 0 3px 3px;text-align:left;background:"+b),d=f("div",a+"Text","font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px;color:"+e);d.innerHTML=a.toUpperCase();c.appendChild(d);a=f("div",a+"Graph","width:74px;height:30px;background:"+e);c.appendChild(a);for(e=0;74>e;e++)a.appendChild(f("span","","width:1px;height:30px;float:left;opacity:0.9;background:"+ 3 | b));return c}function m(a){for(var b=c.children,d=0;dr+1E3&&(d=Math.round(1E3* 5 | t/(a-r)),u=Math.min(u,d),v=Math.max(v,d),A.textContent=d+" FPS ("+u+"-"+v+")",p(B,d/100),r=a,t=0,void 0!==h)){var b=performance.memory.usedJSHeapSize,c=performance.memory.jsHeapSizeLimit;h=Math.round(9.54E-7*b);y=Math.min(y,h);z=Math.max(z,h);E.textContent=h+" MB ("+y+"-"+z+")";p(F,b/c)}return a},update:function(){k=this.end()}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /springVP.cpp: -------------------------------------------------------------------------------- 1 | //float ksStr, ksShr, ksBnd, kdStr, kdShr, kdBnd;//uniforms 2 | 3 | vec2 getNeighbor(int n, out float ks, out float kd) 4 | { 5 | //structural springs (adjacent neighbors) 6 | if (n < 4){ ks = 0.1; kd = 0.1; } //ksStr, kdStr 7 | if (n == 0) return vec2(1.0, 0.0); 8 | if (n == 1) return vec2(0.0, -1.0); 9 | if (n == 2) return vec2(-1.0, 0.0); 10 | if (n == 3) return vec2(0.0, 1.0); 11 | //shear springs (diagonal neighbors) 12 | if (n<8) {ks = 0.1;kd = 0.1;} //ksShr,kdShr 13 | if (n == 4) return vec2(1.0, -1.0); 14 | if (n == 5) return vec2(-1.0, -1.0); 15 | if (n == 6) return vec2(-1.0, 1.0); 16 | if (n == 7) return vec2(1.0, 1.0); 17 | //bend spring (adjacent neighbors 1 node away) 18 | if (n<12) { ks = ksBnd;kd = kdBnd;} //ksBnd,kdBnd 19 | if (n == 8) return vec2(2.0, 0.0); 20 | if (n == 9) return vec2(0.0, -2.0); 21 | if (n == 10) return vec2(-2.0, 0.0); 22 | if (n == 11) return vec2(0.0, 2.0); 23 | } 24 | 25 | float ks, kd; 26 | 27 | for (int k = 0; k < 12; k++) 28 | { 29 | vec2 nCoord = getNeighbor(k, ks, kd); 30 | 31 | float inv_cloth_size = 1.0 / (100.0);//size of a single patch in world space 32 | float rest_length = length(nCoord*inv_cloth_size); 33 | 34 | float nxid = xid + nCoord.x; 35 | float nyid = yid + nCoord.y; 36 | if (nxid < 0.0 || nxid>99.0 || nyid<0.0 || nyid>99.0) continue; 37 | 38 | nCoord = vec2(nyid,nxid) / 100.0; 39 | vec3 posNP = texture2D(u_texPos, nCoord); 40 | vec3 prevNP = texture2D(u_texPrevPos, nCoord); 41 | 42 | vec3 v2 = (posNP - prevNP) / timestep; 43 | vec3 deltaP = pos.xyz - posNP; 44 | vec3 deltaV = vel - prevNP; 45 | float dist = length(deltaP); 46 | 47 | float leftTerm = -ks * (dist - rest_length); 48 | float rightTerm = kd * (dot(deltaV, deltaP) / dist); 49 | vec3 springForce = (leftTerm + rightTerm)* normalize(deltaP); 50 | F += springForce; 51 | } 52 | 53 | 54 | 55 | 56 | 57 | '//shear springs (diagonal neighbors)', 58 | 'if (n<8) { ks = 0.1; kd = 0.1; } //ksShr,kdShr', 59 | 'if (n == 4) return vec2(1.0, -1.0);', 60 | 'if (n == 5) return vec2(-1.0, -1.0);', 61 | 'if (n == 6) return vec2(-1.0, 1.0);', 62 | 'if (n == 7) return vec2(1.0, 1.0);', 63 | 64 | //bend spring (adjacent neighbors 1 node away) 65 | 'if (n<12) { ks = ksBnd; kd = kdBnd; }', //ksBnd,kdBnd 66 | 'if (n == 8) return vec2(2.0, 0.0);', 67 | 'if (n == 9) return vec2(0.0, -2.0);', 68 | 'if (n == 10) return vec2(-2.0, 0.0);', 69 | 'if (n == 11) return vec2(0.0, 2.0);', -------------------------------------------------------------------------------- /lib/seedrandom.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/davidbau/seedrandom 3 | * 4 | * Copyright 2015 David Bau. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | !function(a,b){function c(c,j,k){var n=[];j=1==j?{entropy:!0}:j||{};var s=g(f(j.entropy?[c,i(a)]:null==c?h():c,3),n),t=new d(n),u=function(){for(var a=t.g(m),b=p,c=0;q>a;)a=(a+c)*l,b*=l,c=t.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b};return u.int32=function(){return 0|t.g(4)},u.quick=function(){return t.g(4)/(4*(1<<30))},u["double"]=u,g(i(t.S),a),(j.pass||k||function(a,c,d,f){return f&&(f.S&&e(f,t),a.state=function(){return e(t,{})}),d?(b[o]=a,c):a})(u,s,"global"in j?j.global:this==b,j.state)}function d(a){var b,c=a.length,d=this,e=0,f=d.i=d.j=0,g=d.S=[];for(c||(a=[c++]);l>e;)g[e]=e++;for(e=0;l>e;e++)g[e]=g[f=s&f+a[e%c]+(b=g[e])],g[f]=b;(d.g=function(a){for(var b,c=0,e=d.i,f=d.j,g=d.S;a--;)b=g[e=s&e+1],c=c*l+g[s&(g[e]=g[f=s&f+b])+(g[f]=b)];return d.i=e,d.j=f,c})(l)}function e(a,b){return b.i=a.i,b.j=a.j,b.S=a.S.slice(),b}function f(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(f(a[c],b-1))}catch(g){}return d.length?d:"string"==e?a:a+"\0"}function g(a,b){for(var c,d=a+"",e=0;e raycaster.far) return; 59 | 60 | intersects.push({ 61 | 62 | distance: distance, 63 | distanceToRay: Math.sqrt(rayPointDistanceSq), 64 | point: intersectPoint.clone(), 65 | index: index, 66 | face: null, 67 | object: object 68 | 69 | }); 70 | 71 | } 72 | 73 | } 74 | 75 | if (geometry instanceof THREE.BufferGeometry) { 76 | 77 | var index = geometry.index; 78 | var attributes = geometry.attributes; 79 | var positions = attributes.position.array; 80 | 81 | if (index !== null) { 82 | 83 | var indices = index.array; 84 | 85 | for (var i = 0, il = indices.length; i < il; i++) { 86 | 87 | var a = indices[i]; 88 | 89 | position.fromArray(positions, a * 3); 90 | 91 | testPoint(position, a); 92 | 93 | } 94 | 95 | } else { 96 | 97 | for (var i = 0, l = positions.length / 3; i < l; i++) { 98 | 99 | position.fromArray(positions, i * 3); 100 | 101 | testPoint(position, i); 102 | 103 | } 104 | 105 | } 106 | 107 | } else { 108 | 109 | var vertices = geometry.vertices; 110 | 111 | for (var i = 0, l = vertices.length; i < l; i++) { 112 | 113 | testPoint(vertices[i], i); 114 | 115 | } 116 | 117 | } 118 | 119 | }; 120 | 121 | }()); 122 | 123 | THREE.Points.prototype.clone = function () { 124 | 125 | return new this.constructor(this.geometry, this.material).copy(this); 126 | 127 | }; 128 | 129 | // Backwards compatibility 130 | 131 | THREE.PointCloud = function (geometry, material) { 132 | 133 | console.warn('THREE.PointCloud has been renamed to THREE.Points.'); 134 | return new THREE.Points(geometry, material); 135 | 136 | }; 137 | 138 | THREE.ParticleSystem = function (geometry, material) { 139 | 140 | console.warn('THREE.ParticleSystem has been renamed to THREE.Points.'); 141 | return new THREE.Points(geometry, material); 142 | 143 | }; -------------------------------------------------------------------------------- /js/GPGPU.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://www.mrdoob.com 3 | */ 4 | 5 | 6 | var GPGPU2 = function (renderer,cloth_w,cloth_h) { 7 | var gl = renderer.context; 8 | var transformFeedback = gl.createTransformFeedback(); 9 | var arrBuffer = new ArrayBuffer(cloth_w * cloth_h * 4 * 4); 10 | var posData = new Float32Array(cloth_w * cloth_h * 4); 11 | var prevposData = new Float32Array(cloth_w * cloth_h * 4); 12 | this.init = function (data) 13 | { 14 | posData = new Float32Array(data); 15 | prevposData = new Float32Array(data); 16 | } 17 | this.pass = function (shader, source, target, cfg, usrCtrl) { 18 | 19 | var sourceAttrib = source.attributes['position']; 20 | if (target.attributes['position'].buffer && sourceAttrib.buffer) { 21 | 22 | shader.bind(posData, prevposData, cfg, usrCtrl); 23 | prevposData = new Float32Array(posData); 24 | 25 | gl.enableVertexAttribArray(shader.attributes.a_trytry); 26 | gl.bindBuffer(gl.ARRAY_BUFFER, source.attributes['trytry'].buffer); 27 | gl.vertexAttribPointer(shader.attributes.a_trytry, 4, gl.FLOAT, false, 16, 0); 28 | 29 | gl.enableVertexAttribArray(shader.attributes.a_position); 30 | gl.bindBuffer(gl.ARRAY_BUFFER, sourceAttrib.buffer); 31 | gl.vertexAttribPointer(shader.attributes.a_position, 4, gl.FLOAT, false, 16, 0); 32 | 33 | gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback); 34 | gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, target.attributes['position'].buffer); 35 | 36 | gl.enable(gl.RASTERIZER_DISCARD); 37 | gl.beginTransformFeedback(gl.POINTS); 38 | 39 | gl.drawArrays(gl.POINTS, 0, sourceAttrib.length / sourceAttrib.itemSize); 40 | 41 | gl.endTransformFeedback(); 42 | gl.disable(gl.RASTERIZER_DISCARD); 43 | 44 | // Unbind the transform feedback buffer so subsequent attempts 45 | // to bind it to ARRAY_BUFFER work. 46 | gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null); 47 | 48 | gl.bindBuffer(gl.ARRAY_BUFFER, target.attributes['position'].buffer); 49 | gl.getBufferSubData(gl.ARRAY_BUFFER, 0, arrBuffer); 50 | posData = new Float32Array(arrBuffer); 51 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 52 | 53 | //gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null); 54 | //gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, 0); 55 | } 56 | }; 57 | }; 58 | 59 | var GPGPU = function (renderer) { 60 | 61 | var camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0, 1); 62 | 63 | var scene = new THREE.Scene(); 64 | 65 | var mesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(1, 1)); 66 | scene.add(mesh); 67 | 68 | var velTexture = new THREE.WebGLRenderTarget(cloth_w, cloth_h, { 69 | wrapS: THREE.RepeatWrapping, 70 | wrapT: THREE.RepeatWrapping, 71 | minFilter: THREE.NearestFilter, 72 | magFilter: THREE.NearestFilter, 73 | format: THREE.RGBAFormat, 74 | type: THREE.FloatType, 75 | stencilBuffer: false 76 | }); 77 | 78 | var prevVelTexture = velTexture.clone(); 79 | 80 | /* 81 | this.render = function (_scene, _camera, target) { 82 | renderer.render(_scene, _camera, target, true); 83 | }; 84 | */ 85 | 86 | this.initVel = function (shader) { 87 | //Initialze Velocity 88 | mesh.material = shader.initVelMat; 89 | renderer.render(scene, camera, velTexture, false); 90 | renderer.render(scene, camera, prevVelTexture, false); 91 | }; 92 | 93 | this.pass = function (shader, target,cfg, usrCtrl) { 94 | 95 | shader.setCfgSettings(cfg); 96 | shader.setPrevVelocityTexture( prevVelTexture); 97 | mesh.material = shader.updateVelMat; 98 | renderer.render(scene, camera, velTexture, false); 99 | 100 | shader.setVelocityTexture(velTexture); 101 | mesh.material = shader.material; 102 | renderer.render(scene, camera, target, false); 103 | 104 | var a = velTexture; 105 | velTexture = prevVelTexture; 106 | prevVelTexture = a; 107 | 108 | }; 109 | /* 110 | this.out = function (shader) { 111 | mesh.material = shader.material; 112 | renderer.render(scene, camera); 113 | }; 114 | */ 115 | }; 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebGL Cloth Simulation 2 | -------------------------------------------- 3 | 4 | by: [Ziwei Zong](https://www.ziweizong.com) 5 | 6 | * LinkedIn: [Ziwei Zong](https://www.linkedin.com/in/ziweizong) 7 | * Twitter: [@zzammie_zz](https://twitter.com/zammie_zz) 8 | 9 | 0. Overview 10 | -------------------------------------------- 11 | 12 | Cloth Simulation using transform feedback for WebGL 2.0 supported browsers and ping-ponging texture method for browsers only supporting WebGL 1.0. 13 | 14 | **Using [Brandon Jones](https://github.com/toji)'s [WebGL 2 Particle Simulation](https://github.com/toji/webgl2-particles) as framework.** 15 | 16 | * [Live Demo](http://zammiez.github.io/CIS565-Final-WebGL-Cloth-Simulation/) 17 | * [Video Demo](https://www.youtube.com/watch?v=4LcZuiO5xKU&feature=youtu.be) 18 | * [Final Presentation Slides](https://docs.google.com/a/seas.upenn.edu/presentation/d/1azaTshN9id6mygkEYDAcntXlHf8WixpvsSFgzbcu8_0/edit?usp=sharing) 19 | 20 | WebGL 2.0 version tested successfully on Chrome(Windows), Chrome Canary (Windows), FireFox(Mac) 21 | 22 | WebGL 1.0 version tested successfully on Chrome(Windows,Mac), Chrome Canary (Windows), FireFox (Windows) 23 | 24 | ![](Image/Overview.PNG) 25 | 26 | 1. Instructions 27 | -------------------------------------------- 28 | 29 | ### 1.1 How to use 30 | 31 | #### 1.1.1 Enable WebGL 2.0 32 | 33 | Follow the instruction from [WebGL Wiki](https://www.khronos.org/webgl/wiki/Getting_a_WebGL_Implementation) to enable your browser with WebGL2. 34 | 35 | #### 1.1.2 Features 36 | 37 | * **WebGL 1.0 & WebGL 2.0** 38 | 39 | If the project is running on browser that supports only WebGL 1.0, there would be a "WebGL Cloth Simulaiton" object in the scene. 40 | Otherwise, if the browser supports WebGL 2.0, there would be "WebGL 2 Cloth Simulation" instead and the simulation is using transform feedback. 41 | 42 | |WebGL 1.0 ScreenShot |WebGL 2.0 ScreenShot 43 | |:-------------------------:|:-------------------: 44 | |![](Image/WebGL1.PNG) |![](Image/WebGL2.PNG) 45 | 46 | * **Settings and Basic Animation Control** 47 | 48 | ![](Image/Settings.PNG)|![](Image/AnimationControl.PNG) 49 | 50 | * **Render Mode** 51 | 52 | |Points |Triangle 53 | |:-------------------------:|:-------------------: 54 | |![](Image/SettingPoint.PNG)|![](Image/SettingTri.PNG) 55 | |![](Image/Points.PNG) |![](Image/Triangles.PNG) 56 | 57 | * **Interactions** 58 | 59 | * **RigidBody** 60 | 61 | ![](Image/RigidControl.PNG) 62 | 63 | Cloth-Rigidbody collision is more stable in WebGL2 implementation than in WebGL1. 64 | 65 | |WebGL 1.0 Rigidbody |WebGL 2.0 Rigidbody 66 | |:-------------------------:|:-------------------: 67 | |![](Image/Rigid1.PNG) |![](Image/Rigid2.PNG) 68 | 69 | * **Wind** 70 | 71 | TODO: add pic here 72 | 73 | * **Pins** 74 | 75 | There are two kinds of pins: pin points and pin edges. Select them under Interaction_Folder->Edges or ->Pins 76 | 77 | ![](Image/pinEdge.PNG) 78 | 79 | WebGL 2 version is supporting movable pin. 80 | 81 | TODO: movable pin gif. 82 | 83 | ### 1.2 Implementation 84 | 85 | #### Ping-ponging Texture 86 | 87 | ![](Image/PinPong.jpg) 88 | 89 | #### Transform Feedback 90 | 91 | ![](Image/Transf.jpg) 92 | 93 | #### Mass-Spring Cloth Simulation 94 | 95 | #### Unsolved 96 | 97 | * **Different Browsers** 98 | 99 | * **Uniform Buffer Object** 100 | 101 | 2. Performance Analysis 102 | -------------------------------------------- 103 | 104 | ### 2.1 Cloth Dimension (Particle Number) 105 | 106 | #### Performance 107 | 108 | ![](Image/Perf_Can1.PNG) 109 | 110 | #### Simulation Result 111 | 112 | Cloth Dimension could also effect simulation results. 113 | 114 | When the mass of single cloth particle is constant. Same set of string parameters could turn into different cloth behavior with differenct particle counts. 115 | Below are clothes with 20*20, 50*50, 100*100 and 150*150 particles respectively and they are simulated with the same set of settings. 116 | 117 | |20*20 |50*50 |100*100 |150*150 118 | |:-------------------:|:---------------------:|:---------------------:|:---------------------: 119 | |![](Image/com20.PNG) |![](Image/com50.PNG) |![](Image/com100.PNG) |![](Image/com150.PNG) 120 | 121 | To get the same simulation result as cloth with lower particle count, we need to decrease the value of single particle mass, or increase the value of string parameters. 122 | Either way, a smaller timestep is always needed to ensure a stable system. 123 | 124 | |timestep:0.001 |timestep:0.003 125 | |:-------------------------:|:-------------------------: 126 | |![](Image/100deltT001.PNG) |![](Image/100deltT003.PNG) 127 | 128 | ### 2.2 Code Quality 129 | 130 | * WebGL1 version is simulating within fragment shader. Most of the code are through Three.js shaderMaterial. 131 | WebGL2 version is using transform feedback and doing the simulation in the Vertex Shader. Thus, there are more OpenGL codes and operations. 132 | This makes the WebGL1 version's code more readable and compact. 133 | 134 | 3.References 135 | -------------------------------------------- 136 | 137 | [WebGL 2.0 Particles](https://github.com/toji/webgl2-particles) 138 | 139 | [OpenGL Insights.Chap 17](http://openglinsights.com/bendingthepipeline.html#RealTimePhysicallyBasedDeformationUsingTransformFeedback) 140 | 141 | [Game Engine Gems 2.Chap 22](http://www.crcnetbase.com/doi/abs/10.1201/b11333-25) 142 | 143 | -------------------------------------------------------------------------------- /js/ui.js: -------------------------------------------------------------------------------- 1 | 2 | var UI_cfg = function () { 3 | 4 | var controls = { 5 | gui: null, 6 | "Speed Up": 1, 7 | "Render": 0, 8 | "Material":0, 9 | 10 | "Cloth Dimension": 50, 11 | "Particle Mass": 0.1, 12 | "Time Step": 0.003, 13 | "Ks String": 850.0, 14 | "Ks Shear": 850.0, 15 | "Ks Bend": 2050.0, 16 | "Kd String": 0.25, 17 | "Kd Shear": 0.25, 18 | "Kd Bend":0.25, 19 | 20 | "pause": false, 21 | 22 | "Rigidbody":-1, 23 | "Wind": false, 24 | "Wind Force": 0.5, 25 | "edge 1": false, 26 | "edge 2": false, 27 | "edge 3": false, 28 | "edge 4": false, 29 | "pin 1": true, 30 | "pin 2": true, 31 | "pin 3": false, 32 | "pin 4": false, 33 | }; 34 | 35 | controls.start = function () { 36 | var startEvent = new CustomEvent('start-simulation', null); 37 | window.dispatchEvent(startEvent); 38 | } 39 | 40 | controls.step = function () { 41 | var stepEvent = new CustomEvent('step-simulation', null); 42 | window.dispatchEvent(stepEvent); 43 | } 44 | 45 | this.getRenderMode = function () { 46 | return controls['Render']; 47 | }; 48 | 49 | this.getRenderMaterial = function () { 50 | return controls['Material']; 51 | }; 52 | 53 | this.getRigid = function () { 54 | return controls['Rigidbody']; 55 | }; 56 | 57 | this.getClothDim = function () { 58 | return controls['Cloth Dimension']; 59 | }; 60 | 61 | this.getParticleMass = function () { 62 | //return 0.1; 63 | return controls['Particle Mass']; 64 | }; 65 | 66 | this.getTimeStep = function () { 67 | return controls['Time Step']; 68 | }; 69 | 70 | this.getKsString = function () { 71 | return controls['Ks String']; 72 | }; 73 | this.getKsShear = function () { 74 | return controls['Ks Shear']; 75 | }; 76 | this.getKsBend = function () { 77 | return controls['Ks Bend']; 78 | }; 79 | 80 | this.getKdString = function () { 81 | return controls['Kd String']; 82 | }; 83 | this.getKdShear = function () { 84 | return controls['Kd Shear']; 85 | }; 86 | this.getKdBend = function () { 87 | return controls['Kd Bend']; 88 | }; 89 | this.getWindForce = function () { 90 | if (controls['Wind']) return controls['Wind Force']; 91 | return 0.0; 92 | } 93 | 94 | this.getEdge = function () { 95 | var result = 0; 96 | if (controls['edge 1']) result += 8; 97 | if (controls['edge 2']) result += 4; 98 | if (controls['edge 3']) result += 2; 99 | if (controls['edge 4']) result += 1; 100 | return result; 101 | }; 102 | this.getPin1 = function () { 103 | if (controls['pin 1']) 104 | return 1.0; 105 | return -1.0; 106 | }; 107 | this.getPin2 = function () { 108 | if (controls['pin 2']) 109 | return 1.0; 110 | return -1.0; 111 | }; 112 | this.getPin3 = function () { 113 | if (controls['pin 3']) 114 | return 1.0; 115 | return -1.0; 116 | }; 117 | this.getPin4 = function () { 118 | if (controls['pin 4']) 119 | return 1.0; 120 | return -1.0; 121 | }; 122 | 123 | this.getPause = function () { 124 | return controls['pause']; 125 | } 126 | this.setPause = function (value) { 127 | controls['pause'] = value; 128 | } 129 | 130 | this.getSpeedUp = function () { 131 | return controls['Speed Up']; 132 | } 133 | 134 | this.init = function () { 135 | //cfg = new Cfg(); 136 | 137 | controls.gui = new dat.GUI(); 138 | 139 | var Simulation_Settings = controls.gui.addFolder('Simulation_Settings'); 140 | 141 | Simulation_Settings.add(controls, "Render", { 142 | 'Points': 0, 143 | 'Triangle':1, 144 | }); 145 | Simulation_Settings.add(controls, "Material", { 146 | 'Blinn-Phong': 0, 147 | 'Debug:Position': 1, 148 | 'Debug:Normal': 1, 149 | }); 150 | Simulation_Settings.add(controls, "Cloth Dimension"); 151 | Simulation_Settings.add(controls, "Particle Mass"); 152 | Simulation_Settings.add(controls, "Time Step"); 153 | 154 | Simulation_Settings.add(controls, "Ks String"); 155 | Simulation_Settings.add(controls, "Ks Shear"); 156 | Simulation_Settings.add(controls, "Ks Bend"); 157 | 158 | Simulation_Settings.add(controls, "Kd String"); 159 | Simulation_Settings.add(controls, "Kd Shear"); 160 | Simulation_Settings.add(controls, "Kd Bend"); 161 | 162 | 163 | var Interaction_Folder = controls.gui.addFolder('Interaction_Folder'); 164 | Interaction_Folder.add(controls, "Rigidbody", { 165 | 'None': -1, 166 | 'Sphere': 0, 167 | }) 168 | Interaction_Folder.add(controls, "Wind"); 169 | Interaction_Folder.add(controls, "Wind Force"); 170 | 171 | var EdgeFolder = Interaction_Folder.addFolder('Edges'); 172 | EdgeFolder.add(controls, "edge 1"); 173 | EdgeFolder.add(controls, "edge 2"); 174 | EdgeFolder.add(controls, "edge 3"); 175 | EdgeFolder.add(controls, "edge 4"); 176 | var PinFolder = Interaction_Folder.addFolder('Pins'); 177 | PinFolder.add(controls, "pin 1"); 178 | PinFolder.add(controls, "pin 2"); 179 | PinFolder.add(controls, "pin 3"); 180 | PinFolder.add(controls, "pin 4"); 181 | 182 | var Action_Folder = controls.gui.addFolder('Action_Folder'); 183 | Action_Folder.add(controls, "start"); 184 | Action_Folder.add(controls, "pause"); 185 | Action_Folder.add(controls, "step"); 186 | Action_Folder.add(controls, "Speed Up").step(1); 187 | 188 | Action_Folder.open(); 189 | 190 | }; 191 | } 192 | -------------------------------------------------------------------------------- /js/GeometryUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author alteredq / http://alteredqualia.com/ 4 | */ 5 | 6 | THREE.GeometryUtils = { 7 | 8 | // Merge two geometries or geometry and geometry from object (using object's transform) 9 | 10 | merge: function ( geometry1, object2 /* mesh | geometry */, materialIndexOffset ) { 11 | 12 | var matrix, normalMatrix, 13 | vertexOffset = geometry1.vertices.length, 14 | uvPosition = geometry1.faceVertexUvs[ 0 ].length, 15 | geometry2 = object2 instanceof THREE.Mesh ? object2.geometry : object2, 16 | vertices1 = geometry1.vertices, 17 | vertices2 = geometry2.vertices, 18 | faces1 = geometry1.faces, 19 | faces2 = geometry2.faces, 20 | uvs1 = geometry1.faceVertexUvs[ 0 ], 21 | uvs2 = geometry2.faceVertexUvs[ 0 ]; 22 | 23 | if ( materialIndexOffset === undefined ) materialIndexOffset = 0; 24 | 25 | if ( object2 instanceof THREE.Mesh ) { 26 | 27 | object2.matrixAutoUpdate && object2.updateMatrix(); 28 | 29 | matrix = object2.matrix; 30 | 31 | normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); 32 | 33 | } 34 | 35 | // vertices 36 | 37 | for ( var i = 0, il = vertices2.length; i < il; i ++ ) { 38 | 39 | var vertex = vertices2[ i ]; 40 | 41 | var vertexCopy = vertex.clone(); 42 | 43 | if ( matrix ) vertexCopy.applyMatrix4( matrix ); 44 | 45 | vertices1.push( vertexCopy ); 46 | 47 | } 48 | 49 | // faces 50 | 51 | for ( i = 0, il = faces2.length; i < il; i ++ ) { 52 | 53 | var face = faces2[ i ], faceCopy, normal, color, 54 | faceVertexNormals = face.vertexNormals, 55 | faceVertexColors = face.vertexColors; 56 | 57 | faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); 58 | faceCopy.normal.copy( face.normal ); 59 | 60 | if ( normalMatrix ) { 61 | 62 | faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); 63 | 64 | } 65 | 66 | for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { 67 | 68 | normal = faceVertexNormals[ j ].clone(); 69 | 70 | if ( normalMatrix ) { 71 | 72 | normal.applyMatrix3( normalMatrix ).normalize(); 73 | 74 | } 75 | 76 | faceCopy.vertexNormals.push( normal ); 77 | 78 | } 79 | 80 | faceCopy.color.copy( face.color ); 81 | 82 | for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { 83 | 84 | color = faceVertexColors[ j ]; 85 | faceCopy.vertexColors.push( color.clone() ); 86 | 87 | } 88 | 89 | faceCopy.materialIndex = face.materialIndex + materialIndexOffset; 90 | 91 | faceCopy.centroid.copy( face.centroid ); 92 | 93 | if ( matrix ) { 94 | 95 | faceCopy.centroid.applyMatrix4( matrix ); 96 | 97 | } 98 | 99 | faces1.push( faceCopy ); 100 | 101 | } 102 | 103 | // uvs 104 | 105 | for ( i = 0, il = uvs2.length; i < il; i ++ ) { 106 | 107 | var uv = uvs2[ i ], uvCopy = []; 108 | 109 | for ( var j = 0, jl = uv.length; j < jl; j ++ ) { 110 | 111 | uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) ); 112 | 113 | } 114 | 115 | uvs1.push( uvCopy ); 116 | 117 | } 118 | 119 | }, 120 | 121 | // Get random point in triangle (via barycentric coordinates) 122 | // (uniform distribution) 123 | // http://www.cgafaq.info/wiki/Random_Point_In_Triangle 124 | 125 | randomPointInTriangle: function () { 126 | 127 | var vector = new THREE.Vector3(); 128 | 129 | return function ( vectorA, vectorB, vectorC ) { 130 | 131 | var point = new THREE.Vector3(); 132 | 133 | var a = THREE.Math.random16(); 134 | var b = THREE.Math.random16(); 135 | 136 | if ( ( a + b ) > 1 ) { 137 | 138 | a = 1 - a; 139 | b = 1 - b; 140 | 141 | } 142 | 143 | var c = 1 - a - b; 144 | 145 | point.copy( vectorA ); 146 | point.multiplyScalar( a ); 147 | 148 | vector.copy( vectorB ); 149 | vector.multiplyScalar( b ); 150 | 151 | point.add( vector ); 152 | 153 | vector.copy( vectorC ); 154 | vector.multiplyScalar( c ); 155 | 156 | point.add( vector ); 157 | 158 | return point; 159 | 160 | }; 161 | 162 | }(), 163 | 164 | // Get random point in face (triangle / quad) 165 | // (uniform distribution) 166 | 167 | randomPointInFace: function ( face, geometry, useCachedAreas ) { 168 | 169 | var vA, vB, vC, vD; 170 | 171 | vA = geometry.vertices[ face.a ]; 172 | vB = geometry.vertices[ face.b ]; 173 | vC = geometry.vertices[ face.c ]; 174 | 175 | return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vC ); 176 | 177 | }, 178 | 179 | // Get uniformly distributed random points in mesh 180 | // - create array with cumulative sums of face areas 181 | // - pick random number from 0 to total area 182 | // - find corresponding place in area array by binary search 183 | // - get random point in face 184 | 185 | randomPointsInGeometry: function ( geometry, n ) { 186 | 187 | var face, i, 188 | faces = geometry.faces, 189 | vertices = geometry.vertices, 190 | il = faces.length, 191 | totalArea = 0, 192 | cumulativeAreas = [], 193 | vA, vB, vC, vD; 194 | 195 | // precompute face areas 196 | 197 | for ( i = 0; i < il; i ++ ) { 198 | 199 | face = faces[ i ]; 200 | 201 | vA = vertices[ face.a ]; 202 | vB = vertices[ face.b ]; 203 | vC = vertices[ face.c ]; 204 | 205 | face._area = THREE.GeometryUtils.triangleArea( vA, vB, vC ); 206 | 207 | totalArea += face._area; 208 | 209 | cumulativeAreas[ i ] = totalArea; 210 | 211 | } 212 | 213 | // binary search cumulative areas array 214 | 215 | function binarySearchIndices( value ) { 216 | 217 | function binarySearch( start, end ) { 218 | 219 | // return closest larger index 220 | // if exact number is not found 221 | 222 | if ( end < start ) 223 | return start; 224 | 225 | var mid = start + Math.floor( ( end - start ) / 2 ); 226 | 227 | if ( cumulativeAreas[ mid ] > value ) { 228 | 229 | return binarySearch( start, mid - 1 ); 230 | 231 | } else if ( cumulativeAreas[ mid ] < value ) { 232 | 233 | return binarySearch( mid + 1, end ); 234 | 235 | } else { 236 | 237 | return mid; 238 | 239 | } 240 | 241 | } 242 | 243 | var result = binarySearch( 0, cumulativeAreas.length - 1 ) 244 | return result; 245 | 246 | } 247 | 248 | // pick random face weighted by face area 249 | 250 | var r, index, 251 | result = []; 252 | 253 | var stats = {}; 254 | 255 | for ( i = 0; i < n; i ++ ) { 256 | 257 | r = THREE.Math.random16() * totalArea; 258 | 259 | index = binarySearchIndices( r ); 260 | 261 | result[ i ] = THREE.GeometryUtils.randomPointInFace( faces[ index ], geometry, true ); 262 | 263 | if ( ! stats[ index ] ) { 264 | 265 | stats[ index ] = 1; 266 | 267 | } else { 268 | 269 | stats[ index ] += 1; 270 | 271 | } 272 | 273 | } 274 | 275 | return result; 276 | 277 | }, 278 | 279 | // Get triangle area (half of parallelogram) 280 | // http://mathworld.wolfram.com/TriangleArea.html 281 | 282 | triangleArea: function () { 283 | 284 | var vector1 = new THREE.Vector3(); 285 | var vector2 = new THREE.Vector3(); 286 | 287 | return function ( vectorA, vectorB, vectorC ) { 288 | 289 | vector1.subVectors( vectorB, vectorA ); 290 | vector2.subVectors( vectorC, vectorA ); 291 | vector1.cross( vector2 ); 292 | 293 | return 0.5 * vector1.length(); 294 | 295 | }; 296 | 297 | }(), 298 | 299 | // Center geometry so that 0,0,0 is in center of bounding box 300 | 301 | center: function ( geometry ) { 302 | 303 | geometry.computeBoundingBox(); 304 | 305 | var bb = geometry.boundingBox; 306 | 307 | var offset = new THREE.Vector3(); 308 | 309 | offset.addVectors( bb.min, bb.max ); 310 | offset.multiplyScalar( -0.5 ); 311 | 312 | geometry.applyMatrix( new THREE.Matrix4().makeTranslation( offset.x, offset.y, offset.z ) ); 313 | geometry.computeBoundingBox(); 314 | 315 | return offset; 316 | 317 | }, 318 | 319 | triangulateQuads: function ( geometry ) { 320 | 321 | var i, il, j, jl; 322 | 323 | var faces = []; 324 | var faceVertexUvs = []; 325 | 326 | for ( i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) { 327 | 328 | faceVertexUvs[ i ] = []; 329 | 330 | } 331 | 332 | for ( i = 0, il = geometry.faces.length; i < il; i ++ ) { 333 | 334 | var face = geometry.faces[ i ]; 335 | 336 | faces.push( face ); 337 | 338 | for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) { 339 | 340 | faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] ); 341 | 342 | } 343 | 344 | } 345 | 346 | geometry.faces = faces; 347 | geometry.faceVertexUvs = faceVertexUvs; 348 | 349 | geometry.computeCentroids(); 350 | geometry.computeFaceNormals(); 351 | geometry.computeVertexNormals(); 352 | 353 | if ( geometry.hasTangents ) geometry.computeTangents(); 354 | 355 | } 356 | 357 | }; 358 | -------------------------------------------------------------------------------- /lib/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | }; 10 | 11 | THREE.OBJLoader.prototype = { 12 | 13 | constructor: THREE.OBJLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.setCrossOrigin( this.crossOrigin ); 21 | loader.load( url, function ( text ) { 22 | 23 | onLoad( scope.parse( text ) ); 24 | 25 | }, onProgress, onError ); 26 | 27 | }, 28 | 29 | setCrossOrigin: function ( value ) { 30 | 31 | this.crossOrigin = value; 32 | 33 | }, 34 | 35 | parse: function ( text ) { 36 | 37 | console.time( 'OBJLoader' ); 38 | 39 | var object, objects = []; 40 | var geometry, material; 41 | 42 | function parseVertexIndex( value ) { 43 | 44 | var index = parseInt( value ); 45 | 46 | return ( index >= 0 ? index - 1 : index + vertices.length / 3 ) * 3; 47 | 48 | } 49 | 50 | function parseNormalIndex( value ) { 51 | 52 | var index = parseInt( value ); 53 | 54 | return ( index >= 0 ? index - 1 : index + normals.length / 3 ) * 3; 55 | 56 | } 57 | 58 | function parseUVIndex( value ) { 59 | 60 | var index = parseInt( value ); 61 | 62 | return ( index >= 0 ? index - 1 : index + uvs.length / 2 ) * 2; 63 | 64 | } 65 | 66 | function addVertex( a, b, c ) { 67 | 68 | geometry.vertices.push( 69 | vertices[ a ], vertices[ a + 1 ], vertices[ a + 2 ], 70 | vertices[ b ], vertices[ b + 1 ], vertices[ b + 2 ], 71 | vertices[ c ], vertices[ c + 1 ], vertices[ c + 2 ] 72 | ); 73 | 74 | } 75 | 76 | function addNormal( a, b, c ) { 77 | 78 | geometry.normals.push( 79 | normals[ a ], normals[ a + 1 ], normals[ a + 2 ], 80 | normals[ b ], normals[ b + 1 ], normals[ b + 2 ], 81 | normals[ c ], normals[ c + 1 ], normals[ c + 2 ] 82 | ); 83 | 84 | } 85 | 86 | function addUV( a, b, c ) { 87 | 88 | geometry.uvs.push( 89 | uvs[ a ], uvs[ a + 1 ], 90 | uvs[ b ], uvs[ b + 1 ], 91 | uvs[ c ], uvs[ c + 1 ] 92 | ); 93 | 94 | } 95 | 96 | function addFace( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 97 | 98 | var ia = parseVertexIndex( a ); 99 | var ib = parseVertexIndex( b ); 100 | var ic = parseVertexIndex( c ); 101 | var id; 102 | 103 | if ( d === undefined ) { 104 | 105 | addVertex( ia, ib, ic ); 106 | 107 | } else { 108 | 109 | id = parseVertexIndex( d ); 110 | 111 | addVertex( ia, ib, id ); 112 | addVertex( ib, ic, id ); 113 | 114 | } 115 | 116 | if ( ua !== undefined ) { 117 | 118 | ia = parseUVIndex( ua ); 119 | ib = parseUVIndex( ub ); 120 | ic = parseUVIndex( uc ); 121 | 122 | if ( d === undefined ) { 123 | 124 | addUV( ia, ib, ic ); 125 | 126 | } else { 127 | 128 | id = parseUVIndex( ud ); 129 | 130 | addUV( ia, ib, id ); 131 | addUV( ib, ic, id ); 132 | 133 | } 134 | 135 | } 136 | 137 | if ( na !== undefined ) { 138 | 139 | ia = parseNormalIndex( na ); 140 | ib = parseNormalIndex( nb ); 141 | ic = parseNormalIndex( nc ); 142 | 143 | if ( d === undefined ) { 144 | 145 | addNormal( ia, ib, ic ); 146 | 147 | } else { 148 | 149 | id = parseNormalIndex( nd ); 150 | 151 | addNormal( ia, ib, id ); 152 | addNormal( ib, ic, id ); 153 | 154 | } 155 | 156 | } 157 | 158 | } 159 | 160 | // create mesh if no objects in text 161 | 162 | if ( /^o /gm.test( text ) === false ) { 163 | 164 | geometry = { 165 | vertices: [], 166 | normals: [], 167 | uvs: [] 168 | }; 169 | 170 | material = { 171 | name: '' 172 | }; 173 | 174 | object = { 175 | name: '', 176 | geometry: geometry, 177 | material: material 178 | }; 179 | 180 | objects.push( object ); 181 | 182 | } 183 | 184 | var vertices = []; 185 | var normals = []; 186 | var uvs = []; 187 | 188 | // v float float float 189 | 190 | var vertex_pattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; 191 | 192 | // vn float float float 193 | 194 | var normal_pattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; 195 | 196 | // vt float float 197 | 198 | var uv_pattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; 199 | 200 | // f vertex vertex vertex ... 201 | 202 | var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/; 203 | 204 | // f vertex/uv vertex/uv vertex/uv ... 205 | 206 | var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/; 207 | 208 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... 209 | 210 | var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; 211 | 212 | // f vertex//normal vertex//normal vertex//normal ... 213 | 214 | var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; 215 | 216 | // 217 | 218 | var lines = text.split( '\n' ); 219 | 220 | for ( var i = 0; i < lines.length; i ++ ) { 221 | 222 | var line = lines[ i ]; 223 | line = line.trim(); 224 | 225 | var result; 226 | 227 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 228 | 229 | continue; 230 | 231 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { 232 | 233 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 234 | 235 | vertices.push( 236 | parseFloat( result[ 1 ] ), 237 | parseFloat( result[ 2 ] ), 238 | parseFloat( result[ 3 ] ) 239 | ); 240 | 241 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { 242 | 243 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 244 | 245 | normals.push( 246 | parseFloat( result[ 1 ] ), 247 | parseFloat( result[ 2 ] ), 248 | parseFloat( result[ 3 ] ) 249 | ); 250 | 251 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { 252 | 253 | // ["vt 0.1 0.2", "0.1", "0.2"] 254 | 255 | uvs.push( 256 | parseFloat( result[ 1 ] ), 257 | parseFloat( result[ 2 ] ) 258 | ); 259 | 260 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { 261 | 262 | // ["f 1 2 3", "1", "2", "3", undefined] 263 | 264 | addFace( 265 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 266 | ); 267 | 268 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { 269 | 270 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] 271 | 272 | addFace( 273 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 274 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 275 | ); 276 | 277 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { 278 | 279 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] 280 | 281 | addFace( 282 | result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ], 283 | result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ], 284 | result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] 285 | ); 286 | 287 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { 288 | 289 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] 290 | 291 | addFace( 292 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 293 | undefined, undefined, undefined, undefined, 294 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 295 | ); 296 | 297 | } else if ( /^o /.test( line ) ) { 298 | 299 | geometry = { 300 | vertices: [], 301 | normals: [], 302 | uvs: [] 303 | }; 304 | 305 | material = { 306 | name: '' 307 | }; 308 | 309 | object = { 310 | name: line.substring( 2 ).trim(), 311 | geometry: geometry, 312 | material: material 313 | }; 314 | 315 | objects.push( object ) 316 | 317 | } else if ( /^g /.test( line ) ) { 318 | 319 | // group 320 | 321 | } else if ( /^usemtl /.test( line ) ) { 322 | 323 | // material 324 | 325 | material.name = line.substring( 7 ).trim(); 326 | 327 | } else if ( /^mtllib /.test( line ) ) { 328 | 329 | // mtl file 330 | 331 | } else if ( /^s /.test( line ) ) { 332 | 333 | // smooth shading 334 | 335 | } else { 336 | 337 | // console.log( "THREE.OBJLoader: Unhandled line " + line ); 338 | 339 | } 340 | 341 | } 342 | 343 | var container = new THREE.Object3D(); 344 | 345 | for ( var i = 0, l = objects.length; i < l; i ++ ) { 346 | 347 | object = objects[ i ]; 348 | geometry = object.geometry; 349 | 350 | var buffergeometry = new THREE.BufferGeometry(); 351 | 352 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 353 | 354 | if ( geometry.normals.length > 0 ) { 355 | 356 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 357 | 358 | } 359 | 360 | if ( geometry.uvs.length > 0 ) { 361 | 362 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 363 | 364 | } 365 | 366 | material = new THREE.MeshLambertMaterial(); 367 | material.name = object.material.name; 368 | 369 | var mesh = new THREE.Mesh( buffergeometry, material ); 370 | mesh.name = object.name; 371 | 372 | container.add( mesh ); 373 | 374 | } 375 | 376 | console.timeEnd( 'OBJLoader' ); 377 | 378 | return container; 379 | 380 | } 381 | 382 | }; 383 | -------------------------------------------------------------------------------- /lib/OBJMTLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .obj file with materials 3 | * 4 | * @author mrdoob / http://mrdoob.com/ 5 | * @author angelxuanchang 6 | */ 7 | 8 | THREE.OBJMTLLoader = function ( manager ) { 9 | 10 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 11 | 12 | }; 13 | 14 | THREE.OBJMTLLoader.prototype = { 15 | 16 | constructor: THREE.OBJMTLLoader, 17 | 18 | load: function ( url, mtlurl, onLoad, onProgress, onError ) { 19 | 20 | var scope = this; 21 | 22 | var mtlLoader = new THREE.MTLLoader( this.manager ); 23 | mtlLoader.setBaseUrl( url.substr( 0, url.lastIndexOf( "/" ) + 1 ) ); 24 | mtlLoader.setCrossOrigin( this.crossOrigin ); 25 | mtlLoader.load( mtlurl, function ( materials ) { 26 | 27 | var materialsCreator = materials; 28 | materialsCreator.preload(); 29 | 30 | var loader = new THREE.XHRLoader( scope.manager ); 31 | loader.setCrossOrigin( scope.crossOrigin ); 32 | loader.load( url, function ( text ) { 33 | 34 | var object = scope.parse( text ); 35 | 36 | object.traverse( function ( object ) { 37 | 38 | if ( object instanceof THREE.Mesh ) { 39 | 40 | if ( object.material.name ) { 41 | 42 | var material = materialsCreator.create( object.material.name ); 43 | 44 | if ( material ) object.material = material; 45 | 46 | } 47 | 48 | } 49 | 50 | } ); 51 | 52 | onLoad( object ); 53 | 54 | }, onProgress, onError ); 55 | 56 | }, onProgress, onError ); 57 | 58 | }, 59 | 60 | setCrossOrigin: function ( value ) { 61 | 62 | this.crossOrigin = value; 63 | 64 | }, 65 | 66 | /** 67 | * Parses loaded .obj file 68 | * @param data - content of .obj file 69 | * @param mtllibCallback - callback to handle mtllib declaration (optional) 70 | * @return {THREE.Object3D} - Object3D (with default material) 71 | */ 72 | 73 | parse: function ( data, mtllibCallback ) { 74 | 75 | function vector( x, y, z ) { 76 | 77 | return new THREE.Vector3( x, y, z ); 78 | 79 | } 80 | 81 | function uv( u, v ) { 82 | 83 | return new THREE.Vector2( u, v ); 84 | 85 | } 86 | 87 | function face3( a, b, c, normals ) { 88 | 89 | return new THREE.Face3( a, b, c, normals ); 90 | 91 | } 92 | 93 | var face_offset = 0; 94 | 95 | function meshN( meshName, materialName ) { 96 | 97 | if ( vertices.length > 0 ) { 98 | 99 | geometry.vertices = vertices; 100 | 101 | geometry.mergeVertices(); 102 | geometry.computeFaceNormals(); 103 | geometry.computeBoundingSphere(); 104 | 105 | object.add( mesh ); 106 | 107 | geometry = new THREE.Geometry(); 108 | mesh = new THREE.Mesh( geometry, material ); 109 | 110 | } 111 | 112 | if ( meshName !== undefined ) mesh.name = meshName; 113 | 114 | if ( materialName !== undefined ) { 115 | 116 | material = new THREE.MeshLambertMaterial(); 117 | material.name = materialName; 118 | 119 | mesh.material = material; 120 | 121 | } 122 | 123 | } 124 | 125 | var group = new THREE.Group(); 126 | var object = group; 127 | 128 | var geometry = new THREE.Geometry(); 129 | var material = new THREE.MeshLambertMaterial(); 130 | var mesh = new THREE.Mesh( geometry, material ); 131 | 132 | var vertices = []; 133 | var normals = []; 134 | var uvs = []; 135 | 136 | function add_face( a, b, c, normals_inds ) { 137 | 138 | if ( normals_inds === undefined ) { 139 | 140 | geometry.faces.push( face3( 141 | parseInt( a ) - ( face_offset + 1 ), 142 | parseInt( b ) - ( face_offset + 1 ), 143 | parseInt( c ) - ( face_offset + 1 ) 144 | ) ); 145 | 146 | } else { 147 | 148 | geometry.faces.push( face3( 149 | parseInt( a ) - ( face_offset + 1 ), 150 | parseInt( b ) - ( face_offset + 1 ), 151 | parseInt( c ) - ( face_offset + 1 ), 152 | [ 153 | normals[ parseInt( normals_inds[ 0 ] ) - 1 ].clone(), 154 | normals[ parseInt( normals_inds[ 1 ] ) - 1 ].clone(), 155 | normals[ parseInt( normals_inds[ 2 ] ) - 1 ].clone() 156 | ] 157 | ) ); 158 | 159 | } 160 | 161 | } 162 | 163 | function add_uvs( a, b, c ) { 164 | 165 | geometry.faceVertexUvs[ 0 ].push( [ 166 | uvs[ parseInt( a ) - 1 ].clone(), 167 | uvs[ parseInt( b ) - 1 ].clone(), 168 | uvs[ parseInt( c ) - 1 ].clone() 169 | ] ); 170 | 171 | } 172 | 173 | function handle_face_line( faces, uvs, normals_inds ) { 174 | 175 | if ( faces[ 3 ] === undefined ) { 176 | 177 | add_face( faces[ 0 ], faces[ 1 ], faces[ 2 ], normals_inds ); 178 | 179 | if ( ! ( uvs === undefined ) && uvs.length > 0 ) { 180 | 181 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ); 182 | 183 | } 184 | 185 | } else { 186 | 187 | if ( ! ( normals_inds === undefined ) && normals_inds.length > 0 ) { 188 | 189 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ], [ normals_inds[ 0 ], normals_inds[ 1 ], normals_inds[ 3 ] ] ); 190 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ], [ normals_inds[ 1 ], normals_inds[ 2 ], normals_inds[ 3 ] ] ); 191 | 192 | } else { 193 | 194 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ] ); 195 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ] ); 196 | 197 | } 198 | 199 | if ( ! ( uvs === undefined ) && uvs.length > 0 ) { 200 | 201 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ); 202 | add_uvs( uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ); 203 | 204 | } 205 | 206 | } 207 | 208 | } 209 | 210 | 211 | // v float float float 212 | 213 | var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 214 | 215 | // vn float float float 216 | 217 | var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 218 | 219 | // vt float float 220 | 221 | var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 222 | 223 | // f vertex vertex vertex ... 224 | 225 | var face_pattern1 = /f( +\d+)( +\d+)( +\d+)( +\d+)?/; 226 | 227 | // f vertex/uv vertex/uv vertex/uv ... 228 | 229 | var face_pattern2 = /f( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))?/; 230 | 231 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... 232 | 233 | var face_pattern3 = /f( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))?/; 234 | 235 | // f vertex//normal vertex//normal vertex//normal ... 236 | 237 | var face_pattern4 = /f( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))?/; 238 | 239 | // 240 | 241 | var lines = data.split( "\n" ); 242 | 243 | for ( var i = 0; i < lines.length; i ++ ) { 244 | 245 | var line = lines[ i ]; 246 | line = line.trim(); 247 | 248 | var result; 249 | 250 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 251 | 252 | continue; 253 | 254 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { 255 | 256 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 257 | 258 | vertices.push( vector( 259 | parseFloat( result[ 1 ] ), 260 | parseFloat( result[ 2 ] ), 261 | parseFloat( result[ 3 ] ) 262 | ) ); 263 | 264 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { 265 | 266 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 267 | 268 | normals.push( vector( 269 | parseFloat( result[ 1 ] ), 270 | parseFloat( result[ 2 ] ), 271 | parseFloat( result[ 3 ] ) 272 | ) ); 273 | 274 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { 275 | 276 | // ["vt 0.1 0.2", "0.1", "0.2"] 277 | 278 | uvs.push( uv( 279 | parseFloat( result[ 1 ] ), 280 | parseFloat( result[ 2 ] ) 281 | ) ); 282 | 283 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { 284 | 285 | // ["f 1 2 3", "1", "2", "3", undefined] 286 | 287 | handle_face_line( [ result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] ] ); 288 | 289 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { 290 | 291 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] 292 | 293 | handle_face_line( 294 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 295 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //uv 296 | ); 297 | 298 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { 299 | 300 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] 301 | 302 | handle_face_line( 303 | [ result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ] ], //faces 304 | [ result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ] ], //uv 305 | [ result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] ] //normal 306 | ); 307 | 308 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { 309 | 310 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] 311 | 312 | handle_face_line( 313 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 314 | [ ], //uv 315 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //normal 316 | ); 317 | 318 | } else if ( /^o /.test( line ) ) { 319 | 320 | // object 321 | 322 | meshN(); 323 | face_offset = face_offset + vertices.length; 324 | vertices = []; 325 | object = new THREE.Object3D(); 326 | object.name = line.substring( 2 ).trim(); 327 | group.add( object ); 328 | 329 | } else if ( /^g /.test( line ) ) { 330 | 331 | // group 332 | 333 | meshN( line.substring( 2 ).trim(), undefined ); 334 | 335 | } else if ( /^usemtl /.test( line ) ) { 336 | 337 | // material 338 | 339 | meshN( undefined, line.substring( 7 ).trim() ); 340 | 341 | } else if ( /^mtllib /.test( line ) ) { 342 | 343 | // mtl file 344 | 345 | if ( mtllibCallback ) { 346 | 347 | var mtlfile = line.substring( 7 ); 348 | mtlfile = mtlfile.trim(); 349 | mtllibCallback( mtlfile ); 350 | 351 | } 352 | 353 | } else if ( /^s /.test( line ) ) { 354 | 355 | // Smooth shading 356 | 357 | } else { 358 | 359 | console.log( "THREE.OBJMTLLoader: Unhandled line " + line ); 360 | 361 | } 362 | 363 | } 364 | 365 | //Add last object 366 | meshN( undefined, undefined ); 367 | 368 | return group; 369 | 370 | } 371 | 372 | }; 373 | 374 | THREE.EventDispatcher.prototype.apply( THREE.OBJMTLLoader.prototype ); 375 | -------------------------------------------------------------------------------- /lib/MTLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .mtl file specifying materials 3 | * 4 | * @author angelxuanchang 5 | */ 6 | 7 | THREE.MTLLoader = function( manager ) { 8 | 9 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 10 | 11 | }; 12 | 13 | THREE.MTLLoader.prototype = { 14 | 15 | constructor: THREE.MTLLoader, 16 | 17 | load: function ( url, onLoad, onProgress, onError ) { 18 | 19 | var scope = this; 20 | 21 | var loader = new THREE.XHRLoader( this.manager ); 22 | loader.setCrossOrigin( this.crossOrigin ); 23 | loader.load( url, function ( text ) { 24 | 25 | onLoad( scope.parse( text ) ); 26 | 27 | }, onProgress, onError ); 28 | 29 | }, 30 | 31 | setBaseUrl: function( value ) { 32 | 33 | this.baseUrl = value; 34 | 35 | }, 36 | 37 | setCrossOrigin: function ( value ) { 38 | 39 | this.crossOrigin = value; 40 | 41 | }, 42 | 43 | setMaterialOptions: function ( value ) { 44 | 45 | this.materialOptions = value; 46 | 47 | }, 48 | 49 | /** 50 | * Parses loaded MTL file 51 | * @param text - Content of MTL file 52 | * @return {THREE.MTLLoader.MaterialCreator} 53 | */ 54 | parse: function ( text ) { 55 | 56 | var lines = text.split( "\n" ); 57 | var info = {}; 58 | var delimiter_pattern = /\s+/; 59 | var materialsInfo = {}; 60 | 61 | for ( var i = 0; i < lines.length; i ++ ) { 62 | 63 | var line = lines[ i ]; 64 | line = line.trim(); 65 | 66 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 67 | 68 | // Blank line or comment ignore 69 | continue; 70 | 71 | } 72 | 73 | var pos = line.indexOf( ' ' ); 74 | 75 | var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; 76 | key = key.toLowerCase(); 77 | 78 | var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ""; 79 | value = value.trim(); 80 | 81 | if ( key === "newmtl" ) { 82 | 83 | // New material 84 | 85 | info = { name: value }; 86 | materialsInfo[ value ] = info; 87 | 88 | } else if ( info ) { 89 | 90 | if ( key === "ka" || key === "kd" || key === "ks" ) { 91 | 92 | var ss = value.split( delimiter_pattern, 3 ); 93 | info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; 94 | 95 | } else { 96 | 97 | info[ key ] = value; 98 | 99 | } 100 | 101 | } 102 | 103 | } 104 | 105 | var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.materialOptions ); 106 | materialCreator.setCrossOrigin( this.crossOrigin ); 107 | materialCreator.setManager( this.manager ); 108 | materialCreator.setMaterials( materialsInfo ); 109 | return materialCreator; 110 | 111 | } 112 | 113 | }; 114 | 115 | /** 116 | * Create a new THREE-MTLLoader.MaterialCreator 117 | * @param baseUrl - Url relative to which textures are loaded 118 | * @param options - Set of options on how to construct the materials 119 | * side: Which side to apply the material 120 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide 121 | * wrap: What type of wrapping to apply for textures 122 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping 123 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 124 | * Default: false, assumed to be already normalized 125 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's 126 | * Default: false 127 | * invertTransparency: If transparency need to be inverted (inversion is needed if d = 0 is fully opaque) 128 | * Default: false (d = 1 is fully opaque) 129 | * @constructor 130 | */ 131 | 132 | THREE.MTLLoader.MaterialCreator = function( baseUrl, options ) { 133 | 134 | this.baseUrl = baseUrl; 135 | this.options = options; 136 | this.materialsInfo = {}; 137 | this.materials = {}; 138 | this.materialsArray = []; 139 | this.nameLookup = {}; 140 | 141 | this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; 142 | this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; 143 | 144 | }; 145 | 146 | THREE.MTLLoader.MaterialCreator.prototype = { 147 | 148 | constructor: THREE.MTLLoader.MaterialCreator, 149 | 150 | setCrossOrigin: function ( value ) { 151 | 152 | this.crossOrigin = value; 153 | 154 | }, 155 | 156 | setManager: function ( value ) { 157 | 158 | this.manager = value; 159 | 160 | }, 161 | 162 | setMaterials: function( materialsInfo ) { 163 | 164 | this.materialsInfo = this.convert( materialsInfo ); 165 | this.materials = {}; 166 | this.materialsArray = []; 167 | this.nameLookup = {}; 168 | 169 | }, 170 | 171 | convert: function( materialsInfo ) { 172 | 173 | if ( ! this.options ) return materialsInfo; 174 | 175 | var converted = {}; 176 | 177 | for ( var mn in materialsInfo ) { 178 | 179 | // Convert materials info into normalized form based on options 180 | 181 | var mat = materialsInfo[ mn ]; 182 | 183 | var covmat = {}; 184 | 185 | converted[ mn ] = covmat; 186 | 187 | for ( var prop in mat ) { 188 | 189 | var save = true; 190 | var value = mat[ prop ]; 191 | var lprop = prop.toLowerCase(); 192 | 193 | switch ( lprop ) { 194 | 195 | case 'kd': 196 | case 'ka': 197 | case 'ks': 198 | 199 | // Diffuse color (color under white light) using RGB values 200 | 201 | if ( this.options && this.options.normalizeRGB ) { 202 | 203 | value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; 204 | 205 | } 206 | 207 | if ( this.options && this.options.ignoreZeroRGBs ) { 208 | 209 | if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 1 ] === 0 ) { 210 | 211 | // ignore 212 | 213 | save = false; 214 | 215 | } 216 | 217 | } 218 | 219 | break; 220 | 221 | case 'd': 222 | 223 | // According to MTL format (http://paulbourke.net/dataformats/mtl/): 224 | // d is dissolve for current material 225 | // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) 226 | 227 | if ( this.options && this.options.invertTransparency ) { 228 | 229 | value = 1 - value; 230 | 231 | } 232 | 233 | break; 234 | 235 | default: 236 | 237 | break; 238 | } 239 | 240 | if ( save ) { 241 | 242 | covmat[ lprop ] = value; 243 | 244 | } 245 | 246 | } 247 | 248 | } 249 | 250 | return converted; 251 | 252 | }, 253 | 254 | preload: function () { 255 | 256 | for ( var mn in this.materialsInfo ) { 257 | 258 | this.create( mn ); 259 | 260 | } 261 | 262 | }, 263 | 264 | getIndex: function( materialName ) { 265 | 266 | return this.nameLookup[ materialName ]; 267 | 268 | }, 269 | 270 | getAsArray: function() { 271 | 272 | var index = 0; 273 | 274 | for ( var mn in this.materialsInfo ) { 275 | 276 | this.materialsArray[ index ] = this.create( mn ); 277 | this.nameLookup[ mn ] = index; 278 | index ++; 279 | 280 | } 281 | 282 | return this.materialsArray; 283 | 284 | }, 285 | 286 | create: function ( materialName ) { 287 | 288 | if ( this.materials[ materialName ] === undefined ) { 289 | 290 | this.createMaterial_( materialName ); 291 | 292 | } 293 | 294 | return this.materials[ materialName ]; 295 | 296 | }, 297 | 298 | createMaterial_: function ( materialName ) { 299 | 300 | // Create material 301 | 302 | var mat = this.materialsInfo[ materialName ]; 303 | var params = { 304 | 305 | name: materialName, 306 | side: this.side 307 | 308 | }; 309 | 310 | for ( var prop in mat ) { 311 | 312 | var value = mat[ prop ]; 313 | 314 | switch ( prop.toLowerCase() ) { 315 | 316 | // Ns is material specular exponent 317 | 318 | case 'kd': 319 | 320 | // Diffuse color (color under white light) using RGB values 321 | 322 | params[ 'diffuse' ] = new THREE.Color().fromArray( value ); 323 | 324 | break; 325 | 326 | case 'ka': 327 | 328 | // Ambient color (color under shadow) using RGB values 329 | 330 | break; 331 | 332 | case 'ks': 333 | 334 | // Specular color (color when light is reflected from shiny surface) using RGB values 335 | params[ 'specular' ] = new THREE.Color().fromArray( value ); 336 | 337 | break; 338 | 339 | case 'map_kd': 340 | 341 | // Diffuse texture map 342 | 343 | params[ 'map' ] = this.loadTexture( this.baseUrl + value ); 344 | params[ 'map' ].wrapS = this.wrap; 345 | params[ 'map' ].wrapT = this.wrap; 346 | 347 | break; 348 | 349 | case 'ns': 350 | 351 | // The specular exponent (defines the focus of the specular highlight) 352 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. 353 | 354 | params[ 'shininess' ] = value; 355 | 356 | break; 357 | 358 | case 'd': 359 | 360 | // According to MTL format (http://paulbourke.net/dataformats/mtl/): 361 | // d is dissolve for current material 362 | // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) 363 | 364 | if ( value < 1 ) { 365 | 366 | params[ 'transparent' ] = true; 367 | params[ 'opacity' ] = value; 368 | 369 | } 370 | 371 | break; 372 | 373 | case 'map_bump': 374 | case 'bump': 375 | 376 | // Bump texture map 377 | 378 | if ( params[ 'bumpMap' ] ) break; // Avoid loading twice. 379 | 380 | params[ 'bumpMap' ] = this.loadTexture( this.baseUrl + value ); 381 | params[ 'bumpMap' ].wrapS = this.wrap; 382 | params[ 'bumpMap' ].wrapT = this.wrap; 383 | 384 | break; 385 | 386 | default: 387 | break; 388 | 389 | } 390 | 391 | } 392 | 393 | if ( params[ 'diffuse' ] ) { 394 | 395 | params[ 'color' ] = params[ 'diffuse' ]; 396 | 397 | } 398 | 399 | this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); 400 | return this.materials[ materialName ]; 401 | 402 | }, 403 | 404 | 405 | loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { 406 | 407 | var texture; 408 | var loader = THREE.Loader.Handlers.get( url ); 409 | var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager; 410 | 411 | if ( loader !== null ) { 412 | 413 | texture = loader.load( url, onLoad ); 414 | 415 | } else { 416 | 417 | texture = new THREE.Texture(); 418 | 419 | loader = new THREE.ImageLoader( manager ); 420 | loader.setCrossOrigin( this.crossOrigin ); 421 | loader.load( url, function ( image ) { 422 | 423 | texture.image = THREE.MTLLoader.ensurePowerOfTwo_( image ); 424 | texture.needsUpdate = true; 425 | 426 | if ( onLoad ) onLoad( texture ); 427 | 428 | }, onProgress, onError ); 429 | 430 | } 431 | 432 | if ( mapping !== undefined ) texture.mapping = mapping; 433 | 434 | return texture; 435 | 436 | } 437 | 438 | }; 439 | 440 | THREE.MTLLoader.ensurePowerOfTwo_ = function ( image ) { 441 | 442 | if ( ! THREE.Math.isPowerOfTwo( image.width ) || ! THREE.Math.isPowerOfTwo( image.height ) ) { 443 | 444 | var canvas = document.createElement( "canvas" ); 445 | canvas.width = THREE.MTLLoader.nextHighestPowerOfTwo_( image.width ); 446 | canvas.height = THREE.MTLLoader.nextHighestPowerOfTwo_( image.height ); 447 | 448 | var ctx = canvas.getContext( "2d" ); 449 | ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); 450 | return canvas; 451 | 452 | } 453 | 454 | return image; 455 | 456 | }; 457 | 458 | THREE.MTLLoader.nextHighestPowerOfTwo_ = function( x ) { 459 | 460 | -- x; 461 | 462 | for ( var i = 1; i < 32; i <<= 1 ) { 463 | 464 | x = x | x >> i; 465 | 466 | } 467 | 468 | return x + 1; 469 | 470 | }; 471 | 472 | THREE.EventDispatcher.prototype.apply( THREE.MTLLoader.prototype ); 473 | -------------------------------------------------------------------------------- /glMatrix-0.9.5.min.js: -------------------------------------------------------------------------------- 1 | // glMatrix v0.9.5 2 | glMatrixArrayType=typeof Float32Array!="undefined"?Float32Array:typeof WebGLFloatArray!="undefined"?WebGLFloatArray:Array;var vec3={};vec3.create=function(a){var b=new glMatrixArrayType(3);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2]}return b};vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,c){if(!c||a==c){a[0]+=b[0];a[1]+=b[1];a[2]+=b[2];return a}c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];return c}; 3 | vec3.subtract=function(a,b,c){if(!c||a==c){a[0]-=b[0];a[1]-=b[1];a[2]-=b[2];return a}c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];return c};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};vec3.scale=function(a,b,c){if(!c||a==c){a[0]*=b;a[1]*=b;a[2]*=b;return a}c[0]=a[0]*b;c[1]=a[1]*b;c[2]=a[2]*b;return c}; 4 | vec3.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=Math.sqrt(c*c+d*d+e*e);if(g){if(g==1){b[0]=c;b[1]=d;b[2]=e;return b}}else{b[0]=0;b[1]=0;b[2]=0;return b}g=1/g;b[0]=c*g;b[1]=d*g;b[2]=e*g;return b};vec3.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1];a=a[2];var g=b[0],f=b[1];b=b[2];c[0]=e*b-a*f;c[1]=a*g-d*b;c[2]=d*f-e*g;return c};vec3.length=function(a){var b=a[0],c=a[1];a=a[2];return Math.sqrt(b*b+c*c+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]}; 5 | vec3.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1];a=a[2]-b[2];b=Math.sqrt(d*d+e*e+a*a);if(!b){c[0]=0;c[1]=0;c[2]=0;return c}b=1/b;c[0]=d*b;c[1]=e*b;c[2]=a*b;return c};vec3.lerp=function(a,b,c,d){d||(d=a);d[0]=a[0]+c*(b[0]-a[0]);d[1]=a[1]+c*(b[1]-a[1]);d[2]=a[2]+c*(b[2]-a[2]);return d};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};var mat3={}; 6 | mat3.create=function(a){var b=new glMatrixArrayType(9);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9]}return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a}; 7 | mat3.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[5];a[1]=a[3];a[2]=a[6];a[3]=c;a[5]=a[7];a[6]=d;a[7]=e;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=0;b[4]=a[3];b[5]=a[4];b[6]=a[5];b[7]=0;b[8]=a[6];b[9]=a[7];b[10]=a[8];b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b}; 8 | mat3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};var mat4={};mat4.create=function(a){var b=new glMatrixArrayType(16);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15]}return b}; 9 | mat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a}; 10 | mat4.transpose=function(a,b){if(!b||a==b){var c=a[1],d=a[2],e=a[3],g=a[6],f=a[7],h=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=c;a[6]=a[9];a[7]=a[13];a[8]=d;a[9]=g;a[11]=a[14];a[12]=e;a[13]=f;a[14]=h;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b}; 11 | mat4.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],g=a[4],f=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],o=a[11],m=a[12],n=a[13],p=a[14];a=a[15];return m*k*h*e-j*n*h*e-m*f*l*e+g*n*l*e+j*f*p*e-g*k*p*e-m*k*d*i+j*n*d*i+m*c*l*i-b*n*l*i-j*c*p*i+b*k*p*i+m*f*d*o-g*n*d*o-m*c*h*o+b*n*h*o+g*c*p*o-b*f*p*o-j*f*d*a+g*k*d*a+j*c*h*a-b*k*h*a-g*c*l*a+b*f*l*a}; 12 | mat4.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],o=a[10],m=a[11],n=a[12],p=a[13],r=a[14],s=a[15],A=c*h-d*f,B=c*i-e*f,t=c*j-g*f,u=d*i-e*h,v=d*j-g*h,w=e*j-g*i,x=k*p-l*n,y=k*r-o*n,z=k*s-m*n,C=l*r-o*p,D=l*s-m*p,E=o*s-m*r,q=1/(A*E-B*D+t*C+u*z-v*y+w*x);b[0]=(h*E-i*D+j*C)*q;b[1]=(-d*E+e*D-g*C)*q;b[2]=(p*w-r*v+s*u)*q;b[3]=(-l*w+o*v-m*u)*q;b[4]=(-f*E+i*z-j*y)*q;b[5]=(c*E-e*z+g*y)*q;b[6]=(-n*w+r*t-s*B)*q;b[7]=(k*w-o*t+m*B)*q;b[8]=(f*D-h*z+j*x)*q; 13 | b[9]=(-c*D+d*z-g*x)*q;b[10]=(n*v-p*t+s*A)*q;b[11]=(-k*v+l*t-m*A)*q;b[12]=(-f*C+h*y-i*x)*q;b[13]=(c*C-d*y+e*x)*q;b[14]=(-n*u+p*B-r*A)*q;b[15]=(k*u-l*B+o*A)*q;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b}; 14 | mat4.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],f=a[5],h=a[6],i=a[8],j=a[9],k=a[10],l=k*f-h*j,o=-k*g+h*i,m=j*g-f*i,n=c*l+d*o+e*m;if(!n)return null;n=1/n;b||(b=mat3.create());b[0]=l*n;b[1]=(-k*d+e*j)*n;b[2]=(h*d-e*f)*n;b[3]=o*n;b[4]=(k*c-e*i)*n;b[5]=(-h*c+e*g)*n;b[6]=m*n;b[7]=(-j*c+d*i)*n;b[8]=(f*c-d*g)*n;return b}; 15 | mat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],f=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],o=a[9],m=a[10],n=a[11],p=a[12],r=a[13],s=a[14];a=a[15];var A=b[0],B=b[1],t=b[2],u=b[3],v=b[4],w=b[5],x=b[6],y=b[7],z=b[8],C=b[9],D=b[10],E=b[11],q=b[12],F=b[13],G=b[14];b=b[15];c[0]=A*d+B*h+t*l+u*p;c[1]=A*e+B*i+t*o+u*r;c[2]=A*g+B*j+t*m+u*s;c[3]=A*f+B*k+t*n+u*a;c[4]=v*d+w*h+x*l+y*p;c[5]=v*e+w*i+x*o+y*r;c[6]=v*g+w*j+x*m+y*s;c[7]=v*f+w*k+x*n+y*a;c[8]=z*d+C*h+D*l+E*p;c[9]=z*e+C*i+D*o+E*r;c[10]=z* 16 | g+C*j+D*m+E*s;c[11]=z*f+C*k+D*n+E*a;c[12]=q*d+F*h+G*l+b*p;c[13]=q*e+F*i+G*o+b*r;c[14]=q*g+F*j+G*m+b*s;c[15]=q*f+F*k+G*n+b*a;return c};mat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1];b=b[2];c[0]=a[0]*d+a[4]*e+a[8]*b+a[12];c[1]=a[1]*d+a[5]*e+a[9]*b+a[13];c[2]=a[2]*d+a[6]*e+a[10]*b+a[14];return c}; 17 | mat4.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2];b=b[3];c[0]=a[0]*d+a[4]*e+a[8]*g+a[12]*b;c[1]=a[1]*d+a[5]*e+a[9]*g+a[13]*b;c[2]=a[2]*d+a[6]*e+a[10]*g+a[14]*b;c[3]=a[3]*d+a[7]*e+a[11]*g+a[15]*b;return c}; 18 | mat4.translate=function(a,b,c){var d=b[0],e=b[1];b=b[2];if(!c||a==c){a[12]=a[0]*d+a[4]*e+a[8]*b+a[12];a[13]=a[1]*d+a[5]*e+a[9]*b+a[13];a[14]=a[2]*d+a[6]*e+a[10]*b+a[14];a[15]=a[3]*d+a[7]*e+a[11]*b+a[15];return a}var g=a[0],f=a[1],h=a[2],i=a[3],j=a[4],k=a[5],l=a[6],o=a[7],m=a[8],n=a[9],p=a[10],r=a[11];c[0]=g;c[1]=f;c[2]=h;c[3]=i;c[4]=j;c[5]=k;c[6]=l;c[7]=o;c[8]=m;c[9]=n;c[10]=p;c[11]=r;c[12]=g*d+j*e+m*b+a[12];c[13]=f*d+k*e+n*b+a[13];c[14]=h*d+l*e+p*b+a[14];c[15]=i*d+o*e+r*b+a[15];return c}; 19 | mat4.scale=function(a,b,c){var d=b[0],e=b[1];b=b[2];if(!c||a==c){a[0]*=d;a[1]*=d;a[2]*=d;a[3]*=d;a[4]*=e;a[5]*=e;a[6]*=e;a[7]*=e;a[8]*=b;a[9]*=b;a[10]*=b;a[11]*=b;return a}c[0]=a[0]*d;c[1]=a[1]*d;c[2]=a[2]*d;c[3]=a[3]*d;c[4]=a[4]*e;c[5]=a[5]*e;c[6]=a[6]*e;c[7]=a[7]*e;c[8]=a[8]*b;c[9]=a[9]*b;c[10]=a[10]*b;c[11]=a[11]*b;c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15];return c}; 20 | mat4.rotate=function(a,b,c,d){var e=c[0],g=c[1];c=c[2];var f=Math.sqrt(e*e+g*g+c*c);if(!f)return null;if(f!=1){f=1/f;e*=f;g*=f;c*=f}var h=Math.sin(b),i=Math.cos(b),j=1-i;b=a[0];f=a[1];var k=a[2],l=a[3],o=a[4],m=a[5],n=a[6],p=a[7],r=a[8],s=a[9],A=a[10],B=a[11],t=e*e*j+i,u=g*e*j+c*h,v=c*e*j-g*h,w=e*g*j-c*h,x=g*g*j+i,y=c*g*j+e*h,z=e*c*j+g*h;e=g*c*j-e*h;g=c*c*j+i;if(d){if(a!=d){d[12]=a[12];d[13]=a[13];d[14]=a[14];d[15]=a[15]}}else d=a;d[0]=b*t+o*u+r*v;d[1]=f*t+m*u+s*v;d[2]=k*t+n*u+A*v;d[3]=l*t+p*u+B* 21 | v;d[4]=b*w+o*x+r*y;d[5]=f*w+m*x+s*y;d[6]=k*w+n*x+A*y;d[7]=l*w+p*x+B*y;d[8]=b*z+o*e+r*g;d[9]=f*z+m*e+s*g;d[10]=k*z+n*e+A*g;d[11]=l*z+p*e+B*g;return d};mat4.rotateX=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[4],g=a[5],f=a[6],h=a[7],i=a[8],j=a[9],k=a[10],l=a[11];if(c){if(a!=c){c[0]=a[0];c[1]=a[1];c[2]=a[2];c[3]=a[3];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[4]=e*b+i*d;c[5]=g*b+j*d;c[6]=f*b+k*d;c[7]=h*b+l*d;c[8]=e*-d+i*b;c[9]=g*-d+j*b;c[10]=f*-d+k*b;c[11]=h*-d+l*b;return c}; 22 | mat4.rotateY=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[0],g=a[1],f=a[2],h=a[3],i=a[8],j=a[9],k=a[10],l=a[11];if(c){if(a!=c){c[4]=a[4];c[5]=a[5];c[6]=a[6];c[7]=a[7];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[0]=e*b+i*-d;c[1]=g*b+j*-d;c[2]=f*b+k*-d;c[3]=h*b+l*-d;c[8]=e*d+i*b;c[9]=g*d+j*b;c[10]=f*d+k*b;c[11]=h*d+l*b;return c}; 23 | mat4.rotateZ=function(a,b,c){var d=Math.sin(b);b=Math.cos(b);var e=a[0],g=a[1],f=a[2],h=a[3],i=a[4],j=a[5],k=a[6],l=a[7];if(c){if(a!=c){c[8]=a[8];c[9]=a[9];c[10]=a[10];c[11]=a[11];c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15]}}else c=a;c[0]=e*b+i*d;c[1]=g*b+j*d;c[2]=f*b+k*d;c[3]=h*b+l*d;c[4]=e*-d+i*b;c[5]=g*-d+j*b;c[6]=f*-d+k*b;c[7]=h*-d+l*b;return c}; 24 | mat4.frustum=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=e*2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=e*2/i;f[6]=0;f[7]=0;f[8]=(b+a)/h;f[9]=(d+c)/i;f[10]=-(g+e)/j;f[11]=-1;f[12]=0;f[13]=0;f[14]=-(g*e*2)/j;f[15]=0;return f};mat4.perspective=function(a,b,c,d,e){a=c*Math.tan(a*Math.PI/360);b=a*b;return mat4.frustum(-b,b,-a,a,c,d,e)}; 25 | mat4.ortho=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=2/i;f[6]=0;f[7]=0;f[8]=0;f[9]=0;f[10]=-2/j;f[11]=0;f[12]=-(a+b)/h;f[13]=-(d+c)/i;f[14]=-(g+e)/j;f[15]=1;return f}; 26 | mat4.lookAt=function(a,b,c,d){d||(d=mat4.create());var e=a[0],g=a[1];a=a[2];var f=c[0],h=c[1],i=c[2];c=b[1];var j=b[2];if(e==b[0]&&g==c&&a==j)return mat4.identity(d);var k,l,o,m;c=e-b[0];j=g-b[1];b=a-b[2];m=1/Math.sqrt(c*c+j*j+b*b);c*=m;j*=m;b*=m;k=h*b-i*j;i=i*c-f*b;f=f*j-h*c;if(m=Math.sqrt(k*k+i*i+f*f)){m=1/m;k*=m;i*=m;f*=m}else f=i=k=0;h=j*f-b*i;l=b*k-c*f;o=c*i-j*k;if(m=Math.sqrt(h*h+l*l+o*o)){m=1/m;h*=m;l*=m;o*=m}else o=l=h=0;d[0]=k;d[1]=h;d[2]=c;d[3]=0;d[4]=i;d[5]=l;d[6]=j;d[7]=0;d[8]=f;d[9]= 27 | o;d[10]=b;d[11]=0;d[12]=-(k*e+i*g+f*a);d[13]=-(h*e+l*g+o*a);d[14]=-(c*e+j*g+b*a);d[15]=1;return d};mat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4={};quat4.create=function(a){var b=new glMatrixArrayType(4);if(a){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3]}return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b}; 28 | quat4.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];if(!b||a==b){a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return a}b[0]=c;b[1]=d;b[2]=e;b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return b};quat4.inverse=function(a,b){if(!b||a==b){a[0]*=1;a[1]*=1;a[2]*=1;return a}b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],c=a[1],d=a[2];a=a[3];return Math.sqrt(b*b+c*c+d*d+a*a)}; 29 | quat4.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=Math.sqrt(c*c+d*d+e*e+g*g);if(f==0){b[0]=0;b[1]=0;b[2]=0;b[3]=0;return b}f=1/f;b[0]=c*f;b[1]=d*f;b[2]=e*f;b[3]=g*f;return b};quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2];a=a[3];var f=b[0],h=b[1],i=b[2];b=b[3];c[0]=d*b+a*f+e*i-g*h;c[1]=e*b+a*h+g*f-d*i;c[2]=g*b+a*i+d*h-e*f;c[3]=a*b-d*f-e*h-g*i;return c}; 30 | quat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2];b=a[0];var f=a[1],h=a[2];a=a[3];var i=a*d+f*g-h*e,j=a*e+h*d-b*g,k=a*g+b*e-f*d;d=-b*d-f*e-h*g;c[0]=i*a+d*-b+j*-h-k*-f;c[1]=j*a+d*-f+k*-b-i*-h;c[2]=k*a+d*-h+i*-f-j*-b;return c};quat4.toMat3=function(a,b){b||(b=mat3.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c=c*i;var l=d*h;d=d*i;e=e*i;f=g*f;h=g*h;g=g*i;b[0]=1-(l+e);b[1]=k-g;b[2]=c+h;b[3]=k+g;b[4]=1-(j+e);b[5]=d-f;b[6]=c-h;b[7]=d+f;b[8]=1-(j+l);return b}; 31 | quat4.toMat4=function(a,b){b||(b=mat4.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c=c*i;var l=d*h;d=d*i;e=e*i;f=g*f;h=g*h;g=g*i;b[0]=1-(l+e);b[1]=k-g;b[2]=c+h;b[3]=0;b[4]=k+g;b[5]=1-(j+e);b[6]=d-f;b[7]=0;b[8]=c-h;b[9]=d+f;b[10]=1-(j+l);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};quat4.slerp=function(a,b,c,d){d||(d=a);var e=c;if(a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]<0)e=-1*c;d[0]=1-c*a[0]+e*b[0];d[1]=1-c*a[1]+e*b[1];d[2]=1-c*a[2]+e*b[2];d[3]=1-c*a[3]+e*b[3];return d}; 32 | quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"}; 33 | -------------------------------------------------------------------------------- /lib/es6-promise.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @overview es6-promise - a tiny implementation of Promises/A+. 3 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 4 | * @license Licensed under MIT license 5 | * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE 6 | * @version 3.0.2 7 | */ 8 | 9 | (function(){"use strict";function lib$es6$promise$utils$$objectOrFunction(x){return typeof x==="function"||typeof x==="object"&&x!==null}function lib$es6$promise$utils$$isFunction(x){return typeof x==="function"}function lib$es6$promise$utils$$isMaybeThenable(x){return typeof x==="object"&&x!==null}var lib$es6$promise$utils$$_isArray;if(!Array.isArray){lib$es6$promise$utils$$_isArray=function(x){return Object.prototype.toString.call(x)==="[object Array]"}}else{lib$es6$promise$utils$$_isArray=Array.isArray}var lib$es6$promise$utils$$isArray=lib$es6$promise$utils$$_isArray;var lib$es6$promise$asap$$len=0;var lib$es6$promise$asap$$toString={}.toString;var lib$es6$promise$asap$$vertxNext;var lib$es6$promise$asap$$customSchedulerFn;var lib$es6$promise$asap$$asap=function asap(callback,arg){lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len]=callback;lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len+1]=arg;lib$es6$promise$asap$$len+=2;if(lib$es6$promise$asap$$len===2){if(lib$es6$promise$asap$$customSchedulerFn){lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush)}else{lib$es6$promise$asap$$scheduleFlush()}}};function lib$es6$promise$asap$$setScheduler(scheduleFn){lib$es6$promise$asap$$customSchedulerFn=scheduleFn}function lib$es6$promise$asap$$setAsap(asapFn){lib$es6$promise$asap$$asap=asapFn}var lib$es6$promise$asap$$browserWindow=typeof window!=="undefined"?window:undefined;var lib$es6$promise$asap$$browserGlobal=lib$es6$promise$asap$$browserWindow||{};var lib$es6$promise$asap$$BrowserMutationObserver=lib$es6$promise$asap$$browserGlobal.MutationObserver||lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;var lib$es6$promise$asap$$isNode=typeof process!=="undefined"&&{}.toString.call(process)==="[object process]";var lib$es6$promise$asap$$isWorker=typeof Uint8ClampedArray!=="undefined"&&typeof importScripts!=="undefined"&&typeof MessageChannel!=="undefined";function lib$es6$promise$asap$$useNextTick(){return function(){process.nextTick(lib$es6$promise$asap$$flush)}}function lib$es6$promise$asap$$useVertxTimer(){return function(){lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush)}}function lib$es6$promise$asap$$useMutationObserver(){var iterations=0;var observer=new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);var node=document.createTextNode("");observer.observe(node,{characterData:true});return function(){node.data=iterations=++iterations%2}}function lib$es6$promise$asap$$useMessageChannel(){var channel=new MessageChannel;channel.port1.onmessage=lib$es6$promise$asap$$flush;return function(){channel.port2.postMessage(0)}}function lib$es6$promise$asap$$useSetTimeout(){return function(){setTimeout(lib$es6$promise$asap$$flush,1)}}var lib$es6$promise$asap$$queue=new Array(1e3);function lib$es6$promise$asap$$flush(){for(var i=0;i(u_clothWidth-1.0) || nyid<0.0 || nyid>(u_clothWidth-1.0)) continue;', 102 | ' nCoord = vec2(nyid,u_clothWidth-1.0-nxid) / u_clothWidth;', 103 | ' vec3 posNP = texture(u_texPos, nCoord).xyz;', 104 | ' vec3 prevNP = texture(u_texPrevPos, nCoord).xyz;', 105 | 106 | ' vec3 v2 = (posNP - prevNP) / timestep;', 107 | ' vec3 deltaP = pos.xyz - posNP;', 108 | ' vec3 deltaV = vel - v2;', 109 | ' float dist = length(deltaP);', 110 | ' float leftTerm = -ks * (dist - rest_length);', 111 | ' float rightTerm = kd * (dot(deltaV, deltaP) / dist);', 112 | ' vec3 springForce = (leftTerm + rightTerm)* normalize(deltaP);', 113 | ' F += springForce;', 114 | '};', 115 | 116 | 'vec3 acc = F/pos.w;', // acc = F/m 117 | 'vel = vel+ acc*timestep;',//v = v0+a*t 118 | ].join('\n'); 119 | } 120 | function simulationCommon() { 121 | //UBO: 122 | //http://www.opentk.com/node/2926 123 | return [ 124 | //'layout(std140) uniform u_tryUBO{', 125 | //' vec4 uboTry1;', 126 | //' vec4 uboTry2;', 127 | //'};', 128 | 'uniform float u_timer;', 129 | 'uniform float u_clothWidth;', 130 | 'uniform float u_clothHeight;', 131 | 'uniform vec4 u_newPinPos;', 132 | //'uniform float mass;', 133 | commonUniforms(), 134 | 135 | 'uniform sampler2D u_texPos;', 136 | 'uniform sampler2D u_texPrevPos;', 137 | 'float DAMPING = -0.0125;', 138 | 139 | sphereCollision(), 140 | getNeighbor(), 141 | 142 | 'vec4 runSimulation(vec4 pos,float v_id) {', 143 | 144 | 'float xid = float( int(v_id)/int(u_clothWidth));', 145 | 'float yid = v_id - u_clothWidth*xid;', 146 | 'bool pinBoolean = (pos.w<=0.0);',//Pin1 147 | 'if(!pinBoolean) {', 148 | ' pinBoolean = (xid<=1.0)&&(yid<=1.0)&&(u_pins.x>0.0);', 149 | ' if(u_newPinPos.w==1.0&&pinBoolean) pos.xyz = u_newPinPos.xyz;', 150 | '}', 151 | 'if(!pinBoolean) pinBoolean = (xid>=u_clothWidth-2.0)&&(yid<=1.0)&&(u_pins.y>0.0);',//Pin2 152 | 'if(!pinBoolean) pinBoolean = (xid<=1.0)&&(yid>=u_clothWidth-2.0)&&(u_pins.z>0.0);',//Pin3 153 | 'if(!pinBoolean) pinBoolean = (xid>=u_clothWidth-2.0)&&(yid>=u_clothWidth-2.0)&&(u_pins.w>0.0);',//Pin4 154 | 'if(pinBoolean) return pos;', 155 | 'vec2 coord;', 156 | 'coord = vec2(yid,u_clothWidth-1.0-xid)*(1.0/u_clothWidth);', 157 | 'float timestep = u_timer;', 158 | ' vec4 texPos = texture(u_texPos,coord);', 159 | ' vec4 texPrevPos = texture(u_texPrevPos,coord);', 160 | 161 | simLoop2(), 162 | 163 | 'if(pinBoolean); else pos.xyz += vel*timestep;', 164 | 'if(u_rigid == 0) sphereCollision(pos.xyz,vec3(0.5,0.45,0.4),0.3);', 165 | ' return pos;', 166 | '}', 167 | ].join('\n'); 168 | } 169 | GPGPU2.SimulationShader2 = function (renderer,c_w,c_h) { 170 | var gl = renderer.context; 171 | 172 | var attributes = { 173 | a_position: 0, 174 | a_trytry: 1, 175 | }; 176 | 177 | function createProgram () { 178 | 179 | //WebGL doesn't support gl_VertexID 180 | //http://max-limper.de/tech/batchedrendering.html 181 | //TODO:uniform block 182 | //https://www.opengl.org/wiki/Interface_Block_(GLSL) 183 | var vertexShader = gl.createShader( gl.VERTEX_SHADER ); 184 | var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER ); 185 | 186 | gl.shaderSource(vertexShader, [ 187 | '#version 300 es', 188 | 'precision ' + renderer.getPrecision() + ' float;', 189 | 'in vec4 a_position;', 190 | 'in vec4 a_trytry;', 191 | 'out vec4 v_prevpos;', 192 | simulationCommon(), 193 | 'void main() {', 194 | ' vec4 pos = a_position;', 195 | ' v_prevpos = pos;', 196 | ' pos = runSimulation(pos,a_trytry.x);', 197 | ' gl_Position =vec4(pos.xyz,pos.w);', 198 | '}' 199 | ].join('\n')); 200 | 201 | gl.shaderSource(fragmentShader, [ 202 | '#version 300 es', 203 | 'precision ' + renderer.getPrecision() + ' float;', 204 | 'in vec4 v_prevpos;', 205 | 'void main() {', 206 | //'gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);'//TODO: error 207 | '}' 208 | ].join('\n')); 209 | 210 | gl.compileShader( vertexShader ); 211 | if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { 212 | console.error("Shader failed to compile", gl.getShaderInfoLog( vertexShader )); 213 | return null; 214 | } 215 | 216 | gl.compileShader( fragmentShader ); 217 | if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { 218 | console.error("Shader failed to compile", gl.getShaderInfoLog( fragmentShader )); 219 | return null; 220 | } 221 | 222 | var program = gl.createProgram(); 223 | 224 | gl.attachShader( program, vertexShader ); 225 | gl.attachShader( program, fragmentShader ); 226 | 227 | gl.deleteShader( vertexShader ); 228 | gl.deleteShader( fragmentShader ); 229 | 230 | for (var i in attributes) { 231 | gl.bindAttribLocation( program, attributes[i], i ); 232 | } 233 | var maxSepTrans = gl.getParameter(gl.MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS); 234 | 235 | gl.transformFeedbackVaryings(program, ["gl_Position"], gl.SEPARATE_ATTRIBS); 236 | 237 | gl.linkProgram( program ); 238 | 239 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 240 | console.error("Shader program failed to link", gl.getProgramInfoLog( program )); 241 | gl.deleteProgram(program); 242 | return null; 243 | } 244 | 245 | return program; 246 | }; 247 | 248 | var program = createProgram(); 249 | 250 | if (!program) { 251 | return null; 252 | } 253 | 254 | var uniforms = {}; 255 | var count = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 256 | for (var i = 0; i < count; i++) { 257 | uniform = gl.getActiveUniform(program, i); 258 | name = uniform.name.replace("[0]", ""); 259 | uniforms[name] = gl.getUniformLocation(program, name); 260 | } 261 | 262 | var timerValue = 0; 263 | var cWidth = c_w; 264 | var cHeight = c_h; 265 | 266 | return { 267 | program: program, 268 | 269 | attributes: attributes, 270 | 271 | bind: function (tempData, prevData, cfg, usrCtrl) { 272 | //TODO: VBO -> Texture 273 | //http://stackoverflow.com/questions/17262574/packing-vertex-data-into-a-webgl-texture 274 | //TODO: don't need to re-create texture every frame.....put those into init 275 | gl.useProgram(program); 276 | 277 | var tempTexture = gl.createTexture(); 278 | gl.bindTexture(gl.TEXTURE_2D, tempTexture); 279 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 280 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 281 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 282 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 283 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, cWidth, cHeight, 0, gl.RGBA, gl.FLOAT, new Float32Array(tempData)); 284 | gl.bindTexture(gl.TEXTURE_2D, null); 285 | 286 | var tempPrevTexture = gl.createTexture(); 287 | gl.bindTexture(gl.TEXTURE_2D, tempPrevTexture); 288 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 289 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 290 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 291 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 292 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, cWidth, cHeight, 0, gl.RGBA, gl.FLOAT, new Float32Array(prevData)); 293 | gl.bindTexture(gl.TEXTURE_2D, null); 294 | 295 | gl.activeTexture(gl.TEXTURE0); 296 | gl.bindTexture(gl.TEXTURE_2D, tempTexture); 297 | 298 | gl.activeTexture(gl.TEXTURE1); 299 | gl.bindTexture(gl.TEXTURE_2D, tempPrevTexture); 300 | 301 | gl.uniform1i(uniforms.u_texPos, 0); 302 | gl.uniform1i(uniforms.u_texPrevPos, 1); 303 | 304 | gl.uniform1i(uniforms.u_rigid, cfg.getRigid()); 305 | gl.uniform1f(uniforms.u_timer, cfg.getTimeStep()); 306 | gl.uniform1f(uniforms.u_clothWidth, cWidth); 307 | gl.uniform1f(uniforms.u_clothHeight, cHeight); 308 | gl.uniform1f(uniforms.u_wind, cfg.getWindForce()); 309 | 310 | //gl.uniform1f(uniforms.mass, 0.1); 311 | 312 | gl.uniform2f(uniforms.Str, cfg.getKsString(), -cfg.getKdString()); 313 | gl.uniform2f(uniforms.Shr, cfg.getKsShear(), -cfg.getKdShear()); 314 | gl.uniform2f(uniforms.Bnd, cfg.getKsBend(), -cfg.getKdBend()); 315 | 316 | gl.uniform1i(uniforms.u_pinEdges, cfg.getEdge()); 317 | gl.uniform4f(uniforms.u_pins, cfg.getPin1(), cfg.getPin2(), cfg.getPin3(), cfg.getPin4());//TODO: one int would be enough..change later 0000~1111 318 | gl.uniform4f(uniforms.u_newPinPos, usrCtrl.uniformPins[0], usrCtrl.uniformPins[1], usrCtrl.uniformPins[2], usrCtrl.uniformPins[3]); 319 | }, 320 | 321 | setTimer: function ( timer ) { 322 | timerValue = timer; 323 | }, 324 | 325 | 326 | } 327 | 328 | }; 329 | 330 | /********************** 331 | ** WebGL1 ** 332 | **********************/ 333 | 334 | function isPin() { 335 | return [ 336 | 'int pinEdge = u_pinEdges;', 337 | 'if((pinEdge-8)>=0){pinEdge-=8; pinBoolean = (vUv.x<0.1);}',//Edge Up 338 | 339 | 'if(!pinBoolean)', 340 | ' if((pinEdge-4)>=0){', 341 | ' pinEdge-=4;', 342 | ' pinBoolean = (vUv.y<0.1);', 343 | ' }',//Edge Right 344 | 345 | 'if(!pinBoolean)', 346 | ' if((pinEdge-2)>=0){', 347 | ' pinEdge-=2;', 348 | ' pinBoolean = (vUv.x>(1.0-0.1));', 349 | ' }',//Edge Bottom 350 | 351 | 'if(!pinBoolean)', 352 | ' if((pinEdge)>0){', 353 | ' pinBoolean = (vUv.y >(1.0-0.1));', 354 | ' }',//Edge Left 355 | 356 | 357 | 'if(!pinBoolean) pinBoolean = ((vUv.y>(1.0-0.1)) && vUv.x<0.1) &&(u_pins.x>0.0);',//Pin1 358 | 'if(!pinBoolean) pinBoolean = (vUv.y <0.1&& vUv.x<0.1 && u_pins.y>0.0);',//Pin2 359 | 'if(!pinBoolean) pinBoolean = (vUv.y >(1.0-0.1) && vUv.x>(1.0-0.1) && u_pins.z>0.0);',//Pin3 360 | 'if(!pinBoolean) pinBoolean = (vUv.y <(2.0/cloth_w) && vUv.x>(1.0-2.0/cloth_w) && u_pins.w>0.0);',//Pin4 361 | 362 | ].join('\n'); 363 | } 364 | 365 | function boarderCondition() { 366 | return ['newCoord.x<=0.0 || newCoord.x>=1.0 || newCoord.y<=0.0 || newCoord.y>=1.0'].join(''); 367 | } 368 | 369 | GPGPU.SimulationShader = function () { 370 | 371 | // if (!maxColliders) maxColliders = 8; 372 | var initVelMat = new THREE.ShaderMaterial({ 373 | 374 | vertexShader: [ 375 | 376 | 'void main() {', 377 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 378 | '}', 379 | ].join('\n'), 380 | 381 | fragmentShader: [ 382 | 383 | 'void main() {', 384 | ' gl_FragColor = vec4(0.0,0.0,0.0,1.0);', 385 | '}', 386 | ].join('\n'), 387 | }); 388 | 389 | var updateVelMat = new THREE.ShaderMaterial({ 390 | 391 | uniforms: { 392 | u_rigid: { type: "i", value: -1 }, 393 | cloth_w: { type: "f", value: 50.0 }, 394 | tVelocity: { type: "t", value: texture }, 395 | tPositions: { type: "t", value: texture }, 396 | timestep: { type: "f", value: 0.003 }, 397 | u_wind: { type: "f", value: 0.0 }, 398 | Str: { type: "v2", value: new THREE.Vector2(850.0, -0.25) }, 399 | Shr: { type: "v2", value: new THREE.Vector2(850.0, -0.25) }, 400 | Bnd: { type: "v2", value: new THREE.Vector2(2550.0, -0.25) }, 401 | u_pins: { type: "v4", value: new THREE.Vector4(1.0, 1.0, 0.0, 0.0) }, 402 | u_pinEdges: { type: "i", value: 0 }, 403 | }, 404 | 405 | vertexShader: [ 406 | 'varying vec2 vUv;', 407 | 'void main() {', 408 | ' vUv = uv.xy;', 409 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 410 | '}', 411 | ].join('\n'), 412 | 413 | fragmentShader: [ 414 | 'varying vec2 vUv;', 415 | 'uniform float cloth_w;', 416 | 'uniform sampler2D tVelocity;', 417 | 'uniform sampler2D tPositions;', 418 | 'uniform float timestep;', 419 | commonUniforms(), 420 | 'float DAMPING = -0.0125;', 421 | getNeighbor(), 422 | sphereCollision(), 423 | 'void main() {', 424 | ' vec4 pos = texture2D( tPositions, vUv );', 425 | ' vec3 F = vec3(0.0,-9.8*0.1,0.0);',//TODO:mass 426 | addWind(), 427 | ' vec4 vel = texture2D( tVelocity, vUv );', 428 | 'F+=DAMPING*vel.xyz;', 429 | 430 | /**************** 431 | ** SIMULATION ** 432 | *****************/ 433 | 434 | 'for (int k = 0; k < 12; k++)', 435 | '{', 436 | ' vec3 tempVel = vel.xyz;', 437 | ' float ks, kd;', 438 | ' vec2 nCoord = getNeighbor(k, ks, kd);', 439 | 440 | ' float inv_cloth_size = 1.0 / cloth_w;', 441 | ' float rest_length = length(nCoord*inv_cloth_size);', 442 | 443 | ' nCoord *=(1.0/cloth_w);', 444 | ' vec2 newCoord = vUv+nCoord;', 445 | ' if( '+ boarderCondition() +') continue;', 446 | 447 | ' vec3 posNP = texture2D( tPositions, newCoord).xyz;', 448 | ' vec3 v2 = texture2D( tVelocity, newCoord ).xyz;', 449 | ' vec3 deltaP = pos.xyz - posNP;', 450 | 451 | 'tempVel += deltaP;', 452 | 453 | ' vec3 deltaV = tempVel - v2;', 454 | ' float dist = length(deltaP);', 455 | ' float leftTerm = -ks * (dist - rest_length);', 456 | ' float rightTerm = kd * (dot(deltaV, deltaP) / dist);', 457 | ' vec3 springForce = (leftTerm + rightTerm)* normalize(deltaP);', 458 | ' F += springForce;', 459 | '};', 460 | /**************** 461 | *****************/ 462 | ' vec3 acc = F/0.1;',//TODO:mass 463 | 'bool sphereCol = false;', 464 | 'if(u_rigid==0) sphereCol = sphereCollision(pos.xyz,vec3(0.5,0.45,0.4),0.28);', 465 | 'bool pinBoolean = false;', 466 | isPin(), 467 | 'if(pinBoolean) vel.xyz = vec3(0.0);else vel.xyz += acc*timestep;', 468 | //'if(sphereCol) vel.xyz*=0.95;',//TODO: direction 469 | ' gl_FragColor = vec4(vel.xyz,1.0);', 470 | '}', 471 | ].join('\n'), 472 | }); 473 | 474 | var material = new THREE.ShaderMaterial({ 475 | uniforms: { 476 | u_rigid: { type: "i", value: -1 }, 477 | cloth_w: { type: "f", value: 50.0 }, 478 | tVelocity: { type: "t", value: texture }, 479 | tPositions: { type: "t", value: texture }, 480 | origin: { type: "t", value: texture }, 481 | timer: { type: "f", value: 0.003 }, 482 | isStart: { type: "i", value: 1 }, 483 | u_pins: { type: "v4", value: new THREE.Vector4(1.0, 1.0, 0.0, 0.0) }, 484 | u_newPinPos: { type: "v4", value: new THREE.Vector4(0.0, 0.0, 0.0, 0.0) }, 485 | u_pinEdges: { type: "i", value: 0 }, 486 | }, 487 | 488 | vertexShader: [ 489 | 'varying vec2 vUv;', 490 | 491 | 'void main() {', 492 | ' vUv = uv.xy;', 493 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 494 | '}', 495 | ].join('\n'), 496 | 497 | fragmentShader: [ 498 | 'varying vec2 vUv;', 499 | 500 | 'uniform float cloth_w;', 501 | 'uniform int u_rigid;', 502 | 'uniform sampler2D tVelocity;', 503 | 'uniform sampler2D tPositions;', 504 | 505 | 'uniform sampler2D origin;', 506 | 'uniform float timer;', 507 | 'uniform int isStart;', 508 | 509 | 'uniform vec4 u_pins;', 510 | 'uniform vec4 u_newPinPos;', 511 | 'uniform int u_pinEdges;', 512 | sphereCollision(), 513 | //getNeighbor(), 514 | 'void main() {', 515 | ' vec4 pos = texture2D( tPositions, vUv );', 516 | ' vec4 vel = texture2D( tVelocity, vUv );', 517 | //* 518 | 'if(isStart==1) {', 519 | ' pos = vec4(texture2D( origin, vUv ).xyz, 0.1);', 520 | '}', 521 | 'else{', 522 | ' bool pinBoolean = false;', 523 | isPin(), 524 | ' if( pinBoolean) ; else pos.xyz+=vel.xyz*timer;', 525 | ' if(u_rigid==0) sphereCollision(pos.xyz,vec3(0.5,0.45,0.4),0.3);', 526 | '}', 527 | 528 | ' gl_FragColor = pos;', 529 | '}', 530 | ].join('\n'), 531 | }); 532 | var ss = 1; 533 | return { 534 | 535 | initVelMat: initVelMat, 536 | 537 | updateVelMat: updateVelMat, 538 | 539 | material: material, 540 | 541 | setCfgSettings: function (cfg) { 542 | material.uniforms.u_rigid.value = cfg.getRigid(); 543 | material.uniforms.u_pins.value = new THREE.Vector4(cfg.getPin1(), cfg.getPin2(), cfg.getPin3(), cfg.getPin4());//TODO: same as above 544 | material.uniforms.u_pinEdges.value = cfg.getEdge(); 545 | updateVelMat.uniforms.u_rigid.value = cfg.getRigid(); 546 | updateVelMat.uniforms.timestep.value = cfg.getTimeStep(); 547 | updateVelMat.uniforms.Str.value = new THREE.Vector2(cfg.getKsString(), -cfg.getKdString()); 548 | updateVelMat.uniforms.Shr.value = new THREE.Vector2(cfg.getKsShear(), -cfg.getKdShear()); 549 | updateVelMat.uniforms.Bnd.value = new THREE.Vector2(cfg.getKsBend(), -cfg.getKdBend()); 550 | updateVelMat.uniforms.u_wind.value = cfg.getWindForce(); 551 | updateVelMat.uniforms.u_pins.value = new THREE.Vector4(cfg.getPin1(), cfg.getPin2(), cfg.getPin3(), cfg.getPin4());//TODO: same as above 552 | updateVelMat.uniforms.u_pinEdges.value = cfg.getEdge(); 553 | 554 | }, 555 | 556 | setPositionsTexture: function (positions) { 557 | material.uniforms.tPositions.value = positions; 558 | updateVelMat.uniforms.tPositions.value = positions; 559 | return this; 560 | }, 561 | 562 | setVelocityTexture: function (velocities) { 563 | material.uniforms.tVelocity.value = velocities; 564 | return this; 565 | }, 566 | 567 | setOriginsTexture: function (origins) { 568 | 569 | material.uniforms.origin.value = origins; 570 | return this; 571 | }, 572 | 573 | setTimer: function (timer) { 574 | 575 | material.uniforms.timer.value = timer; 576 | 577 | return this; 578 | 579 | }, 580 | 581 | setClothDim: function (clothDim) { 582 | 583 | updateVelMat.uniforms.cloth_w.value = clothDim; 584 | material.uniforms.cloth_w.value = clothDim; 585 | 586 | return this; 587 | 588 | }, 589 | 590 | setStart: function (isStart) { 591 | 592 | material.uniforms.isStart.value = isStart; 593 | 594 | return this; 595 | 596 | }, 597 | 598 | setPrevVelocityTexture: function (velocities) { 599 | updateVelMat.uniforms.tVelocity.value = velocities; 600 | return this; 601 | }, 602 | 603 | 604 | } 605 | 606 | }; 607 | -------------------------------------------------------------------------------- /lib/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | /*global THREE, console */ 9 | 10 | ( function () { 11 | 12 | function OrbitConstraint ( object ) { 13 | 14 | this.object = object; 15 | 16 | // "target" sets the location of focus, where the object orbits around 17 | // and where it pans with respect to. 18 | this.target = new THREE.Vector3(); 19 | 20 | // Limits to how far you can dolly in and out ( PerspectiveCamera only ) 21 | this.minDistance = 0; 22 | this.maxDistance = Infinity; 23 | 24 | // Limits to how far you can zoom in and out ( OrthographicCamera only ) 25 | this.minZoom = 0; 26 | this.maxZoom = Infinity; 27 | 28 | // How far you can orbit vertically, upper and lower limits. 29 | // Range is 0 to Math.PI radians. 30 | this.minPolarAngle = 0; // radians 31 | this.maxPolarAngle = Math.PI; // radians 32 | 33 | // How far you can orbit horizontally, upper and lower limits. 34 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 35 | this.minAzimuthAngle = - Infinity; // radians 36 | this.maxAzimuthAngle = Infinity; // radians 37 | 38 | // Set to true to enable damping (inertia) 39 | // If damping is enabled, you must call controls.update() in your animation loop 40 | this.enableDamping = false; 41 | this.dampingFactor = 0.25; 42 | 43 | //////////// 44 | // internals 45 | 46 | var scope = this; 47 | 48 | var EPS = 0.000001; 49 | 50 | // Current position in spherical coordinate system. 51 | var theta; 52 | var phi; 53 | 54 | // Pending changes 55 | var phiDelta = 0; 56 | var thetaDelta = 0; 57 | var scale = 1; 58 | var panOffset = new THREE.Vector3(); 59 | var zoomChanged = false; 60 | 61 | // API 62 | 63 | this.getPolarAngle = function () { 64 | 65 | return phi; 66 | 67 | }; 68 | 69 | this.getAzimuthalAngle = function () { 70 | 71 | return theta; 72 | 73 | }; 74 | 75 | this.rotateLeft = function ( angle ) { 76 | 77 | thetaDelta -= angle; 78 | 79 | }; 80 | 81 | this.rotateUp = function ( angle ) { 82 | 83 | phiDelta -= angle; 84 | 85 | }; 86 | 87 | // pass in distance in world space to move left 88 | this.panLeft = function() { 89 | 90 | var v = new THREE.Vector3(); 91 | 92 | return function panLeft ( distance ) { 93 | 94 | var te = this.object.matrix.elements; 95 | 96 | // get X column of matrix 97 | v.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 98 | v.multiplyScalar( - distance ); 99 | 100 | panOffset.add( v ); 101 | 102 | }; 103 | 104 | }(); 105 | 106 | // pass in distance in world space to move up 107 | this.panUp = function() { 108 | 109 | var v = new THREE.Vector3(); 110 | 111 | return function panUp ( distance ) { 112 | 113 | var te = this.object.matrix.elements; 114 | 115 | // get Y column of matrix 116 | v.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 117 | v.multiplyScalar( distance ); 118 | 119 | panOffset.add( v ); 120 | 121 | }; 122 | 123 | }(); 124 | 125 | // pass in x,y of change desired in pixel space, 126 | // right and down are positive 127 | this.pan = function ( deltaX, deltaY, screenWidth, screenHeight ) { 128 | 129 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 130 | 131 | // perspective 132 | var position = scope.object.position; 133 | var offset = position.clone().sub( scope.target ); 134 | var targetDistance = offset.length(); 135 | 136 | // half of the fov is center to top of screen 137 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 138 | 139 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 140 | scope.panLeft( 2 * deltaX * targetDistance / screenHeight ); 141 | scope.panUp( 2 * deltaY * targetDistance / screenHeight ); 142 | 143 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 144 | 145 | // orthographic 146 | scope.panLeft( deltaX * ( scope.object.right - scope.object.left ) / screenWidth ); 147 | scope.panUp( deltaY * ( scope.object.top - scope.object.bottom ) / screenHeight ); 148 | 149 | } else { 150 | 151 | // camera neither orthographic or perspective 152 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 153 | 154 | } 155 | 156 | }; 157 | 158 | this.dollyIn = function ( dollyScale ) { 159 | 160 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 161 | 162 | scale /= dollyScale; 163 | 164 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 165 | 166 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); 167 | scope.object.updateProjectionMatrix(); 168 | zoomChanged = true; 169 | 170 | } else { 171 | 172 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 173 | 174 | } 175 | 176 | }; 177 | 178 | this.dollyOut = function ( dollyScale ) { 179 | 180 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 181 | 182 | scale *= dollyScale; 183 | 184 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 185 | 186 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); 187 | scope.object.updateProjectionMatrix(); 188 | zoomChanged = true; 189 | 190 | } else { 191 | 192 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 193 | 194 | } 195 | 196 | }; 197 | 198 | this.update = function() { 199 | 200 | var offset = new THREE.Vector3(); 201 | 202 | // so camera.up is the orbit axis 203 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 204 | var quatInverse = quat.clone().inverse(); 205 | 206 | var lastPosition = new THREE.Vector3(); 207 | var lastQuaternion = new THREE.Quaternion(); 208 | 209 | return function () { 210 | 211 | var position = this.object.position; 212 | 213 | offset.copy( position ).sub( this.target ); 214 | 215 | // rotate offset to "y-axis-is-up" space 216 | offset.applyQuaternion( quat ); 217 | 218 | // angle from z-axis around y-axis 219 | 220 | theta = Math.atan2( offset.x, offset.z ); 221 | 222 | // angle from y-axis 223 | 224 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 225 | 226 | theta += thetaDelta; 227 | phi += phiDelta; 228 | 229 | // restrict theta to be between desired limits 230 | theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); 231 | 232 | // restrict phi to be between desired limits 233 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 234 | 235 | // restrict phi to be betwee EPS and PI-EPS 236 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 237 | 238 | var radius = offset.length() * scale; 239 | 240 | // restrict radius to be between desired limits 241 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 242 | 243 | // move target to panned location 244 | this.target.add( panOffset ); 245 | 246 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 247 | offset.y = radius * Math.cos( phi ); 248 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 249 | 250 | // rotate offset back to "camera-up-vector-is-up" space 251 | offset.applyQuaternion( quatInverse ); 252 | 253 | position.copy( this.target ).add( offset ); 254 | 255 | this.object.lookAt( this.target ); 256 | 257 | if ( this.enableDamping === true ) { 258 | 259 | thetaDelta *= ( 1 - this.dampingFactor ); 260 | phiDelta *= ( 1 - this.dampingFactor ); 261 | 262 | } else { 263 | 264 | thetaDelta = 0; 265 | phiDelta = 0; 266 | 267 | } 268 | 269 | scale = 1; 270 | panOffset.set( 0, 0, 0 ); 271 | 272 | // update condition is: 273 | // min(camera displacement, camera rotation in radians)^2 > EPS 274 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 275 | 276 | if ( zoomChanged || 277 | lastPosition.distanceToSquared( this.object.position ) > EPS || 278 | 8 * ( 1 - lastQuaternion.dot( this.object.quaternion ) ) > EPS ) { 279 | 280 | lastPosition.copy( this.object.position ); 281 | lastQuaternion.copy( this.object.quaternion ); 282 | zoomChanged = false; 283 | 284 | return true; 285 | 286 | } 287 | 288 | return false; 289 | 290 | }; 291 | 292 | }(); 293 | 294 | }; 295 | 296 | 297 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 298 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 299 | // supported. 300 | // 301 | // Orbit - left mouse / touch: one finger move 302 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 303 | // Pan - right mouse, or arrow keys / touch: three finter swipe 304 | 305 | THREE.OrbitControls = function ( object, domElement ) { 306 | 307 | var constraint = new OrbitConstraint( object ); 308 | 309 | this.domElement = ( domElement !== undefined ) ? domElement : document; 310 | 311 | // API 312 | 313 | Object.defineProperty( this, 'constraint', { 314 | 315 | get: function() { 316 | 317 | return constraint; 318 | 319 | } 320 | 321 | } ); 322 | 323 | this.getPolarAngle = function () { 324 | 325 | return constraint.getPolarAngle(); 326 | 327 | }; 328 | 329 | this.getAzimuthalAngle = function () { 330 | 331 | return constraint.getAzimuthalAngle(); 332 | 333 | }; 334 | 335 | // Set to false to disable this control 336 | this.enabled = true; 337 | 338 | // center is old, deprecated; use "target" instead 339 | this.center = this.target; 340 | 341 | // This option actually enables dollying in and out; left as "zoom" for 342 | // backwards compatibility. 343 | // Set to false to disable zooming 344 | this.enableZoom = true; 345 | this.zoomSpeed = 1.0; 346 | 347 | // Set to false to disable rotating 348 | this.enableRotate = true; 349 | this.rotateSpeed = 1.0; 350 | 351 | // Set to false to disable panning 352 | this.enablePan = true; 353 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 354 | 355 | // Set to true to automatically rotate around the target 356 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 357 | this.autoRotate = false; 358 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 359 | 360 | // Set to false to disable use of the keys 361 | this.enableKeys = true; 362 | 363 | // The four arrow keys 364 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 365 | 366 | // Mouse buttons 367 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 368 | 369 | //////////// 370 | // internals 371 | 372 | var scope = this; 373 | 374 | var rotateStart = new THREE.Vector2(); 375 | var rotateEnd = new THREE.Vector2(); 376 | var rotateDelta = new THREE.Vector2(); 377 | 378 | var panStart = new THREE.Vector2(); 379 | var panEnd = new THREE.Vector2(); 380 | var panDelta = new THREE.Vector2(); 381 | 382 | var dollyStart = new THREE.Vector2(); 383 | var dollyEnd = new THREE.Vector2(); 384 | var dollyDelta = new THREE.Vector2(); 385 | 386 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 387 | 388 | var state = STATE.NONE; 389 | 390 | // for reset 391 | 392 | this.target0 = this.target.clone(); 393 | this.position0 = this.object.position.clone(); 394 | this.zoom0 = this.object.zoom; 395 | 396 | // events 397 | 398 | var changeEvent = { type: 'change' }; 399 | var startEvent = { type: 'start' }; 400 | var endEvent = { type: 'end' }; 401 | 402 | // pass in x,y of change desired in pixel space, 403 | // right and down are positive 404 | function pan( deltaX, deltaY ) { 405 | 406 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 407 | 408 | constraint.pan( deltaX, deltaY, element.clientWidth, element.clientHeight ); 409 | 410 | } 411 | 412 | this.update = function () { 413 | 414 | if ( this.autoRotate && state === STATE.NONE ) { 415 | 416 | constraint.rotateLeft( getAutoRotationAngle() ); 417 | 418 | } 419 | 420 | if ( constraint.update() === true ) { 421 | 422 | this.dispatchEvent( changeEvent ); 423 | 424 | } 425 | 426 | }; 427 | 428 | this.reset = function () { 429 | 430 | state = STATE.NONE; 431 | 432 | this.target.copy( this.target0 ); 433 | this.object.position.copy( this.position0 ); 434 | this.object.zoom = this.zoom0; 435 | 436 | this.object.updateProjectionMatrix(); 437 | this.dispatchEvent( changeEvent ); 438 | 439 | this.update(); 440 | 441 | }; 442 | 443 | function getAutoRotationAngle() { 444 | 445 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 446 | 447 | } 448 | 449 | function getZoomScale() { 450 | 451 | return Math.pow( 0.95, scope.zoomSpeed ); 452 | 453 | } 454 | 455 | function onMouseDown( event ) { 456 | 457 | if ( scope.enabled === false ) return; 458 | 459 | event.preventDefault(); 460 | 461 | if ( event.button === scope.mouseButtons.ORBIT ) { 462 | 463 | if ( scope.enableRotate === false ) return; 464 | 465 | state = STATE.ROTATE; 466 | 467 | rotateStart.set( event.clientX, event.clientY ); 468 | 469 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 470 | 471 | if ( scope.enableZoom === false ) return; 472 | 473 | state = STATE.DOLLY; 474 | 475 | dollyStart.set( event.clientX, event.clientY ); 476 | 477 | } else if ( event.button === scope.mouseButtons.PAN ) { 478 | 479 | if ( scope.enablePan === false ) return; 480 | 481 | state = STATE.PAN; 482 | 483 | panStart.set( event.clientX, event.clientY ); 484 | 485 | } 486 | 487 | if ( state !== STATE.NONE ) { 488 | 489 | document.addEventListener( 'mousemove', onMouseMove, false ); 490 | document.addEventListener( 'mouseup', onMouseUp, false ); 491 | scope.dispatchEvent( startEvent ); 492 | 493 | } 494 | 495 | } 496 | 497 | function onMouseMove( event ) { 498 | 499 | if ( scope.enabled === false ) return; 500 | 501 | event.preventDefault(); 502 | 503 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 504 | 505 | if ( state === STATE.ROTATE ) { 506 | 507 | if ( scope.enableRotate === false ) return; 508 | 509 | rotateEnd.set( event.clientX, event.clientY ); 510 | rotateDelta.subVectors( rotateEnd, rotateStart ); 511 | 512 | // rotating across whole screen goes 360 degrees around 513 | constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 514 | 515 | // rotating up and down along whole screen attempts to go 360, but limited to 180 516 | constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 517 | 518 | rotateStart.copy( rotateEnd ); 519 | 520 | } else if ( state === STATE.DOLLY ) { 521 | 522 | if ( scope.enableZoom === false ) return; 523 | 524 | dollyEnd.set( event.clientX, event.clientY ); 525 | dollyDelta.subVectors( dollyEnd, dollyStart ); 526 | 527 | if ( dollyDelta.y > 0 ) { 528 | 529 | constraint.dollyIn( getZoomScale() ); 530 | 531 | } else if ( dollyDelta.y < 0 ) { 532 | 533 | constraint.dollyOut( getZoomScale() ); 534 | 535 | } 536 | 537 | dollyStart.copy( dollyEnd ); 538 | 539 | } else if ( state === STATE.PAN ) { 540 | 541 | if ( scope.enablePan === false ) return; 542 | 543 | panEnd.set( event.clientX, event.clientY ); 544 | panDelta.subVectors( panEnd, panStart ); 545 | 546 | pan( panDelta.x, panDelta.y ); 547 | 548 | panStart.copy( panEnd ); 549 | 550 | } 551 | 552 | if ( state !== STATE.NONE ) scope.update(); 553 | 554 | } 555 | 556 | function onMouseUp( /* event */ ) { 557 | 558 | if ( scope.enabled === false ) return; 559 | 560 | document.removeEventListener( 'mousemove', onMouseMove, false ); 561 | document.removeEventListener( 'mouseup', onMouseUp, false ); 562 | scope.dispatchEvent( endEvent ); 563 | state = STATE.NONE; 564 | 565 | } 566 | 567 | function onMouseWheel( event ) { 568 | 569 | if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; 570 | 571 | event.preventDefault(); 572 | event.stopPropagation(); 573 | 574 | var delta = 0; 575 | 576 | if ( event.wheelDelta !== undefined ) { 577 | 578 | // WebKit / Opera / Explorer 9 579 | 580 | delta = event.wheelDelta; 581 | 582 | } else if ( event.detail !== undefined ) { 583 | 584 | // Firefox 585 | 586 | delta = - event.detail; 587 | 588 | } 589 | 590 | if ( delta > 0 ) { 591 | 592 | constraint.dollyOut( getZoomScale() ); 593 | 594 | } else if ( delta < 0 ) { 595 | 596 | constraint.dollyIn( getZoomScale() ); 597 | 598 | } 599 | 600 | scope.update(); 601 | scope.dispatchEvent( startEvent ); 602 | scope.dispatchEvent( endEvent ); 603 | 604 | } 605 | 606 | function onKeyDown( event ) { 607 | 608 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 609 | 610 | switch ( event.keyCode ) { 611 | 612 | case scope.keys.UP: 613 | pan( 0, scope.keyPanSpeed ); 614 | scope.update(); 615 | break; 616 | 617 | case scope.keys.BOTTOM: 618 | pan( 0, - scope.keyPanSpeed ); 619 | scope.update(); 620 | break; 621 | 622 | case scope.keys.LEFT: 623 | pan( scope.keyPanSpeed, 0 ); 624 | scope.update(); 625 | break; 626 | 627 | case scope.keys.RIGHT: 628 | pan( - scope.keyPanSpeed, 0 ); 629 | scope.update(); 630 | break; 631 | 632 | } 633 | 634 | } 635 | 636 | function touchstart( event ) { 637 | 638 | if ( scope.enabled === false ) return; 639 | 640 | switch ( event.touches.length ) { 641 | 642 | case 1: // one-fingered touch: rotate 643 | 644 | if ( scope.enableRotate === false ) return; 645 | 646 | state = STATE.TOUCH_ROTATE; 647 | 648 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 649 | break; 650 | 651 | case 2: // two-fingered touch: dolly 652 | 653 | if ( scope.enableZoom === false ) return; 654 | 655 | state = STATE.TOUCH_DOLLY; 656 | 657 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 658 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 659 | var distance = Math.sqrt( dx * dx + dy * dy ); 660 | dollyStart.set( 0, distance ); 661 | break; 662 | 663 | case 3: // three-fingered touch: pan 664 | 665 | if ( scope.enablePan === false ) return; 666 | 667 | state = STATE.TOUCH_PAN; 668 | 669 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 670 | break; 671 | 672 | default: 673 | 674 | state = STATE.NONE; 675 | 676 | } 677 | 678 | if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); 679 | 680 | } 681 | 682 | function touchmove( event ) { 683 | 684 | if ( scope.enabled === false ) return; 685 | 686 | event.preventDefault(); 687 | event.stopPropagation(); 688 | 689 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 690 | 691 | switch ( event.touches.length ) { 692 | 693 | case 1: // one-fingered touch: rotate 694 | 695 | if ( scope.enableRotate === false ) return; 696 | if ( state !== STATE.TOUCH_ROTATE ) return; 697 | 698 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 699 | rotateDelta.subVectors( rotateEnd, rotateStart ); 700 | 701 | // rotating across whole screen goes 360 degrees around 702 | constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 703 | // rotating up and down along whole screen attempts to go 360, but limited to 180 704 | constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 705 | 706 | rotateStart.copy( rotateEnd ); 707 | 708 | scope.update(); 709 | break; 710 | 711 | case 2: // two-fingered touch: dolly 712 | 713 | if ( scope.enableZoom === false ) return; 714 | if ( state !== STATE.TOUCH_DOLLY ) return; 715 | 716 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 717 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 718 | var distance = Math.sqrt( dx * dx + dy * dy ); 719 | 720 | dollyEnd.set( 0, distance ); 721 | dollyDelta.subVectors( dollyEnd, dollyStart ); 722 | 723 | if ( dollyDelta.y > 0 ) { 724 | 725 | constraint.dollyOut( getZoomScale() ); 726 | 727 | } else if ( dollyDelta.y < 0 ) { 728 | 729 | constraint.dollyIn( getZoomScale() ); 730 | 731 | } 732 | 733 | dollyStart.copy( dollyEnd ); 734 | 735 | scope.update(); 736 | break; 737 | 738 | case 3: // three-fingered touch: pan 739 | 740 | if ( scope.enablePan === false ) return; 741 | if ( state !== STATE.TOUCH_PAN ) return; 742 | 743 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 744 | panDelta.subVectors( panEnd, panStart ); 745 | 746 | pan( panDelta.x, panDelta.y ); 747 | 748 | panStart.copy( panEnd ); 749 | 750 | scope.update(); 751 | break; 752 | 753 | default: 754 | 755 | state = STATE.NONE; 756 | 757 | } 758 | 759 | } 760 | 761 | function touchend( /* event */ ) { 762 | 763 | if ( scope.enabled === false ) return; 764 | 765 | scope.dispatchEvent( endEvent ); 766 | state = STATE.NONE; 767 | 768 | } 769 | 770 | function contextmenu( event ) { 771 | 772 | event.preventDefault(); 773 | 774 | } 775 | 776 | this.dispose = function() { 777 | 778 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 779 | this.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 780 | this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false ); 781 | this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 782 | 783 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 784 | this.domElement.removeEventListener( 'touchend', touchend, false ); 785 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 786 | 787 | document.removeEventListener( 'mousemove', onMouseMove, false ); 788 | document.removeEventListener( 'mouseup', onMouseUp, false ); 789 | 790 | window.removeEventListener( 'keydown', onKeyDown, false ); 791 | 792 | } 793 | 794 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 795 | 796 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 797 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 798 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 799 | 800 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 801 | this.domElement.addEventListener( 'touchend', touchend, false ); 802 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 803 | 804 | window.addEventListener( 'keydown', onKeyDown, false ); 805 | 806 | // force an update at start 807 | this.update(); 808 | 809 | }; 810 | 811 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 812 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 813 | 814 | Object.defineProperties( THREE.OrbitControls.prototype, { 815 | 816 | object: { 817 | 818 | get: function () { 819 | 820 | return this.constraint.object; 821 | 822 | } 823 | 824 | }, 825 | 826 | target: { 827 | 828 | get: function () { 829 | 830 | return this.constraint.target; 831 | 832 | }, 833 | 834 | set: function ( value ) { 835 | 836 | console.warn( 'THREE.OrbitControls: target is now immutable. Use target.set() instead.' ); 837 | this.constraint.target.copy( value ); 838 | 839 | } 840 | 841 | }, 842 | 843 | minDistance : { 844 | 845 | get: function () { 846 | 847 | return this.constraint.minDistance; 848 | 849 | }, 850 | 851 | set: function ( value ) { 852 | 853 | this.constraint.minDistance = value; 854 | 855 | } 856 | 857 | }, 858 | 859 | maxDistance : { 860 | 861 | get: function () { 862 | 863 | return this.constraint.maxDistance; 864 | 865 | }, 866 | 867 | set: function ( value ) { 868 | 869 | this.constraint.maxDistance = value; 870 | 871 | } 872 | 873 | }, 874 | 875 | minZoom : { 876 | 877 | get: function () { 878 | 879 | return this.constraint.minZoom; 880 | 881 | }, 882 | 883 | set: function ( value ) { 884 | 885 | this.constraint.minZoom = value; 886 | 887 | } 888 | 889 | }, 890 | 891 | maxZoom : { 892 | 893 | get: function () { 894 | 895 | return this.constraint.maxZoom; 896 | 897 | }, 898 | 899 | set: function ( value ) { 900 | 901 | this.constraint.maxZoom = value; 902 | 903 | } 904 | 905 | }, 906 | 907 | minPolarAngle : { 908 | 909 | get: function () { 910 | 911 | return this.constraint.minPolarAngle; 912 | 913 | }, 914 | 915 | set: function ( value ) { 916 | 917 | this.constraint.minPolarAngle = value; 918 | 919 | } 920 | 921 | }, 922 | 923 | maxPolarAngle : { 924 | 925 | get: function () { 926 | 927 | return this.constraint.maxPolarAngle; 928 | 929 | }, 930 | 931 | set: function ( value ) { 932 | 933 | this.constraint.maxPolarAngle = value; 934 | 935 | } 936 | 937 | }, 938 | 939 | minAzimuthAngle : { 940 | 941 | get: function () { 942 | 943 | return this.constraint.minAzimuthAngle; 944 | 945 | }, 946 | 947 | set: function ( value ) { 948 | 949 | this.constraint.minAzimuthAngle = value; 950 | 951 | } 952 | 953 | }, 954 | 955 | maxAzimuthAngle : { 956 | 957 | get: function () { 958 | 959 | return this.constraint.maxAzimuthAngle; 960 | 961 | }, 962 | 963 | set: function ( value ) { 964 | 965 | this.constraint.maxAzimuthAngle = value; 966 | 967 | } 968 | 969 | }, 970 | 971 | enableDamping : { 972 | 973 | get: function () { 974 | 975 | return this.constraint.enableDamping; 976 | 977 | }, 978 | 979 | set: function ( value ) { 980 | 981 | this.constraint.enableDamping = value; 982 | 983 | } 984 | 985 | }, 986 | 987 | dampingFactor : { 988 | 989 | get: function () { 990 | 991 | return this.constraint.dampingFactor; 992 | 993 | }, 994 | 995 | set: function ( value ) { 996 | 997 | this.constraint.dampingFactor = value; 998 | 999 | } 1000 | 1001 | }, 1002 | 1003 | // backward compatibility 1004 | 1005 | noZoom: { 1006 | 1007 | get: function () { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1010 | return ! this.enableZoom; 1011 | 1012 | }, 1013 | 1014 | set: function ( value ) { 1015 | 1016 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1017 | this.enableZoom = ! value; 1018 | 1019 | } 1020 | 1021 | }, 1022 | 1023 | noRotate: { 1024 | 1025 | get: function () { 1026 | 1027 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1028 | return ! this.enableRotate; 1029 | 1030 | }, 1031 | 1032 | set: function ( value ) { 1033 | 1034 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1035 | this.enableRotate = ! value; 1036 | 1037 | } 1038 | 1039 | }, 1040 | 1041 | noPan: { 1042 | 1043 | get: function () { 1044 | 1045 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1046 | return ! this.enablePan; 1047 | 1048 | }, 1049 | 1050 | set: function ( value ) { 1051 | 1052 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1053 | this.enablePan = ! value; 1054 | 1055 | } 1056 | 1057 | }, 1058 | 1059 | noKeys: { 1060 | 1061 | get: function () { 1062 | 1063 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1064 | return ! this.enableKeys; 1065 | 1066 | }, 1067 | 1068 | set: function ( value ) { 1069 | 1070 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1071 | this.enableKeys = ! value; 1072 | 1073 | } 1074 | 1075 | }, 1076 | 1077 | staticMoving : { 1078 | 1079 | get: function () { 1080 | 1081 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1082 | return ! this.constraint.enableDamping; 1083 | 1084 | }, 1085 | 1086 | set: function ( value ) { 1087 | 1088 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1089 | this.constraint.enableDamping = ! value; 1090 | 1091 | } 1092 | 1093 | }, 1094 | 1095 | dynamicDampingFactor : { 1096 | 1097 | get: function () { 1098 | 1099 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1100 | return this.constraint.dampingFactor; 1101 | 1102 | }, 1103 | 1104 | set: function ( value ) { 1105 | 1106 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1107 | this.constraint.dampingFactor = value; 1108 | 1109 | } 1110 | 1111 | } 1112 | 1113 | } ); 1114 | 1115 | }() ); 1116 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 110 | 145 | 146 | 157 | 166 | 754 | 755 | 756 | -------------------------------------------------------------------------------- /lib/webgl-debug.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2012 The Khronos Group Inc. 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a 5 | ** copy of this software and/or associated documentation files (the 6 | ** "Materials"), to deal in the Materials without restriction, including 7 | ** without limitation the rights to use, copy, modify, merge, publish, 8 | ** distribute, sublicense, and/or sell copies of the Materials, and to 9 | ** permit persons to whom the Materials are furnished to do so, subject to 10 | ** the following conditions: 11 | ** 12 | ** The above copyright notice and this permission notice shall be included 13 | ** in all copies or substantial portions of the Materials. 14 | ** 15 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 22 | */ 23 | 24 | // Various functions for helping debug WebGL apps. 25 | 26 | WebGLDebugUtils = function() { 27 | 28 | /** 29 | * Wrapped logging function. 30 | * @param {string} msg Message to log. 31 | */ 32 | var log = function(msg) { 33 | if (window.console && window.console.log) { 34 | window.console.log(msg); 35 | } 36 | }; 37 | 38 | /** 39 | * Wrapped error logging function. 40 | * @param {string} msg Message to log. 41 | */ 42 | var error = function(msg) { 43 | if (window.console && window.console.error) { 44 | window.console.error(msg); 45 | } else { 46 | log(msg); 47 | } 48 | }; 49 | 50 | 51 | /** 52 | * Which arguments are enums based on the number of arguments to the function. 53 | * So 54 | * 'texImage2D': { 55 | * 9: { 0:true, 2:true, 6:true, 7:true }, 56 | * 6: { 0:true, 2:true, 3:true, 4:true }, 57 | * }, 58 | * 59 | * means if there are 9 arguments then 6 and 7 are enums, if there are 6 60 | * arguments 3 and 4 are enums 61 | * 62 | * @type {!Object.} 63 | */ 64 | var glValidEnumContexts = { 65 | // Generic setters and getters 66 | 67 | 'enable': {1: { 0:true }}, 68 | 'disable': {1: { 0:true }}, 69 | 'getParameter': {1: { 0:true }}, 70 | 71 | // Rendering 72 | 73 | 'drawArrays': {3:{ 0:true }}, 74 | 'drawElements': {4:{ 0:true, 2:true }}, 75 | 76 | // Shaders 77 | 78 | 'createShader': {1: { 0:true }}, 79 | 'getShaderParameter': {2: { 1:true }}, 80 | 'getProgramParameter': {2: { 1:true }}, 81 | 'getShaderPrecisionFormat': {2: { 0: true, 1:true }}, 82 | 83 | // Vertex attributes 84 | 85 | 'getVertexAttrib': {2: { 1:true }}, 86 | 'vertexAttribPointer': {6: { 2:true }}, 87 | 88 | // Textures 89 | 90 | 'bindTexture': {2: { 0:true }}, 91 | 'activeTexture': {1: { 0:true }}, 92 | 'getTexParameter': {2: { 0:true, 1:true }}, 93 | 'texParameterf': {3: { 0:true, 1:true }}, 94 | 'texParameteri': {3: { 0:true, 1:true, 2:true }}, 95 | 'texImage2D': { 96 | 9: { 0:true, 2:true, 6:true, 7:true }, 97 | 6: { 0:true, 2:true, 3:true, 4:true } 98 | }, 99 | 'texSubImage2D': { 100 | 9: { 0:true, 6:true, 7:true }, 101 | 7: { 0:true, 4:true, 5:true } 102 | }, 103 | 'copyTexImage2D': {8: { 0:true, 2:true }}, 104 | 'copyTexSubImage2D': {8: { 0:true }}, 105 | 'generateMipmap': {1: { 0:true }}, 106 | 'compressedTexImage2D': {7: { 0: true, 2:true }}, 107 | 'compressedTexSubImage2D': {8: { 0: true, 6:true }}, 108 | 109 | // Buffer objects 110 | 111 | 'bindBuffer': {2: { 0:true }}, 112 | 'bufferData': {3: { 0:true, 2:true }}, 113 | 'bufferSubData': {3: { 0:true }}, 114 | 'getBufferParameter': {2: { 0:true, 1:true }}, 115 | 116 | // Renderbuffers and framebuffers 117 | 118 | 'pixelStorei': {2: { 0:true, 1:true }}, 119 | 'readPixels': {7: { 4:true, 5:true }}, 120 | 'bindRenderbuffer': {2: { 0:true }}, 121 | 'bindFramebuffer': {2: { 0:true }}, 122 | 'checkFramebufferStatus': {1: { 0:true }}, 123 | 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }}, 124 | 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }}, 125 | 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }}, 126 | 'getRenderbufferParameter': {2: { 0:true, 1:true }}, 127 | 'renderbufferStorage': {4: { 0:true, 1:true }}, 128 | 129 | // Frame buffer operations (clear, blend, depth test, stencil) 130 | 131 | 'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}}, 132 | 'depthFunc': {1: { 0:true }}, 133 | 'blendFunc': {2: { 0:true, 1:true }}, 134 | 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 135 | 'blendEquation': {1: { 0:true }}, 136 | 'blendEquationSeparate': {2: { 0:true, 1:true }}, 137 | 'stencilFunc': {3: { 0:true }}, 138 | 'stencilFuncSeparate': {4: { 0:true, 1:true }}, 139 | 'stencilMaskSeparate': {2: { 0:true }}, 140 | 'stencilOp': {3: { 0:true, 1:true, 2:true }}, 141 | 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 142 | 143 | // Culling 144 | 145 | 'cullFace': {1: { 0:true }}, 146 | 'frontFace': {1: { 0:true }}, 147 | 148 | // ANGLE_instanced_arrays extension 149 | 150 | 'drawArraysInstancedANGLE': {4: { 0:true }}, 151 | 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }}, 152 | 153 | // EXT_blend_minmax extension 154 | 155 | 'blendEquationEXT': {1: { 0:true }} 156 | }; 157 | 158 | /** 159 | * Map of numbers to names. 160 | * @type {Object} 161 | */ 162 | var glEnums = null; 163 | 164 | /** 165 | * Map of names to numbers. 166 | * @type {Object} 167 | */ 168 | var enumStringToValue = null; 169 | 170 | /** 171 | * Initializes this module. Safe to call more than once. 172 | * @param {!WebGLRenderingContext} ctx A WebGL context. If 173 | * you have more than one context it doesn't matter which one 174 | * you pass in, it is only used to pull out constants. 175 | */ 176 | function init(ctx) { 177 | if (glEnums == null) { 178 | glEnums = { }; 179 | enumStringToValue = { }; 180 | for (var propertyName in ctx) { 181 | if (typeof ctx[propertyName] == 'number') { 182 | glEnums[ctx[propertyName]] = propertyName; 183 | enumStringToValue[propertyName] = ctx[propertyName]; 184 | } 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Checks the utils have been initialized. 191 | */ 192 | function checkInit() { 193 | if (glEnums == null) { 194 | throw 'WebGLDebugUtils.init(ctx) not called'; 195 | } 196 | } 197 | 198 | /** 199 | * Returns true or false if value matches any WebGL enum 200 | * @param {*} value Value to check if it might be an enum. 201 | * @return {boolean} True if value matches one of the WebGL defined enums 202 | */ 203 | function mightBeEnum(value) { 204 | checkInit(); 205 | return (glEnums[value] !== undefined); 206 | } 207 | 208 | /** 209 | * Gets an string version of an WebGL enum. 210 | * 211 | * Example: 212 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 213 | * 214 | * @param {number} value Value to return an enum for 215 | * @return {string} The string version of the enum. 216 | */ 217 | function glEnumToString(value) { 218 | checkInit(); 219 | var name = glEnums[value]; 220 | return (name !== undefined) ? ("gl." + name) : 221 | ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + ""); 222 | } 223 | 224 | /** 225 | * Returns the string version of a WebGL argument. 226 | * Attempts to convert enum arguments to strings. 227 | * @param {string} functionName the name of the WebGL function. 228 | * @param {number} numArgs the number of arguments passed to the function. 229 | * @param {number} argumentIndx the index of the argument. 230 | * @param {*} value The value of the argument. 231 | * @return {string} The value as a string. 232 | */ 233 | function glFunctionArgToString(functionName, numArgs, argumentIndex, value) { 234 | var funcInfo = glValidEnumContexts[functionName]; 235 | if (funcInfo !== undefined) { 236 | var funcInfo = funcInfo[numArgs]; 237 | if (funcInfo !== undefined) { 238 | if (funcInfo[argumentIndex]) { 239 | if (typeof funcInfo[argumentIndex] === 'object' && 240 | funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) { 241 | var enums = funcInfo[argumentIndex]['enumBitwiseOr']; 242 | var orResult = 0; 243 | var orEnums = []; 244 | for (var i = 0; i < enums.length; ++i) { 245 | var enumValue = enumStringToValue[enums[i]]; 246 | if ((value & enumValue) !== 0) { 247 | orResult |= enumValue; 248 | orEnums.push(glEnumToString(enumValue)); 249 | } 250 | } 251 | if (orResult === value) { 252 | return orEnums.join(' | '); 253 | } else { 254 | return glEnumToString(value); 255 | } 256 | } else { 257 | return glEnumToString(value); 258 | } 259 | } 260 | } 261 | } 262 | if (value === null) { 263 | return "null"; 264 | } else if (value === undefined) { 265 | return "undefined"; 266 | } else { 267 | return value.toString(); 268 | } 269 | } 270 | 271 | /** 272 | * Converts the arguments of a WebGL function to a string. 273 | * Attempts to convert enum arguments to strings. 274 | * 275 | * @param {string} functionName the name of the WebGL function. 276 | * @param {number} args The arguments. 277 | * @return {string} The arguments as a string. 278 | */ 279 | function glFunctionArgsToString(functionName, args) { 280 | // apparently we can't do args.join(","); 281 | var argStr = ""; 282 | var numArgs = args.length; 283 | for (var ii = 0; ii < numArgs; ++ii) { 284 | argStr += ((ii == 0) ? '' : ', ') + 285 | glFunctionArgToString(functionName, numArgs, ii, args[ii]); 286 | } 287 | return argStr; 288 | }; 289 | 290 | 291 | function makePropertyWrapper(wrapper, original, propertyName) { 292 | //log("wrap prop: " + propertyName); 293 | wrapper.__defineGetter__(propertyName, function() { 294 | return original[propertyName]; 295 | }); 296 | // TODO(gmane): this needs to handle properties that take more than 297 | // one value? 298 | wrapper.__defineSetter__(propertyName, function(value) { 299 | //log("set: " + propertyName); 300 | original[propertyName] = value; 301 | }); 302 | } 303 | 304 | // Makes a function that calls a function on another object. 305 | function makeFunctionWrapper(original, functionName) { 306 | //log("wrap fn: " + functionName); 307 | var f = original[functionName]; 308 | return function() { 309 | //log("call: " + functionName); 310 | var result = f.apply(original, arguments); 311 | return result; 312 | }; 313 | } 314 | 315 | /** 316 | * Given a WebGL context returns a wrapped context that calls 317 | * gl.getError after every command and calls a function if the 318 | * result is not gl.NO_ERROR. 319 | * 320 | * @param {!WebGLRenderingContext} ctx The webgl context to 321 | * wrap. 322 | * @param {!function(err, funcName, args): void} opt_onErrorFunc 323 | * The function to call when gl.getError returns an 324 | * error. If not specified the default function calls 325 | * console.log with a message. 326 | * @param {!function(funcName, args): void} opt_onFunc The 327 | * function to call when each webgl function is called. 328 | * You can use this to log all calls for example. 329 | * @param {!WebGLRenderingContext} opt_err_ctx The webgl context 330 | * to call getError on if different than ctx. 331 | */ 332 | function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc, opt_err_ctx) { 333 | opt_err_ctx = opt_err_ctx || ctx; 334 | init(ctx); 335 | opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { 336 | // apparently we can't do args.join(","); 337 | var argStr = ""; 338 | var numArgs = args.length; 339 | for (var ii = 0; ii < numArgs; ++ii) { 340 | argStr += ((ii == 0) ? '' : ', ') + 341 | glFunctionArgToString(functionName, numArgs, ii, args[ii]); 342 | } 343 | error("WebGL error "+ glEnumToString(err) + " in "+ functionName + 344 | "(" + argStr + ")"); 345 | }; 346 | 347 | // Holds booleans for each GL error so after we get the error ourselves 348 | // we can still return it to the client app. 349 | var glErrorShadow = { }; 350 | 351 | // Makes a function that calls a WebGL function and then calls getError. 352 | function makeErrorWrapper(ctx, functionName) { 353 | return function() { 354 | if (opt_onFunc) { 355 | opt_onFunc(functionName, arguments); 356 | } 357 | var result = ctx[functionName].apply(ctx, arguments); 358 | var err = opt_err_ctx.getError(); 359 | if (err != 0) { 360 | glErrorShadow[err] = true; 361 | opt_onErrorFunc(err, functionName, arguments); 362 | } 363 | return result; 364 | }; 365 | } 366 | 367 | // Make a an object that has a copy of every property of the WebGL context 368 | // but wraps all functions. 369 | var wrapper = {}; 370 | for (var propertyName in ctx) { 371 | if (typeof ctx[propertyName] == 'function') { 372 | if (propertyName != 'getExtension') { 373 | wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); 374 | } else { 375 | var wrapped = makeErrorWrapper(ctx, propertyName); 376 | wrapper[propertyName] = function () { 377 | var result = wrapped.apply(ctx, arguments); 378 | return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx); 379 | }; 380 | } 381 | } else { 382 | makePropertyWrapper(wrapper, ctx, propertyName); 383 | } 384 | } 385 | 386 | // Override the getError function with one that returns our saved results. 387 | wrapper.getError = function() { 388 | for (var err in glErrorShadow) { 389 | if (glErrorShadow.hasOwnProperty(err)) { 390 | if (glErrorShadow[err]) { 391 | glErrorShadow[err] = false; 392 | return err; 393 | } 394 | } 395 | } 396 | return ctx.NO_ERROR; 397 | }; 398 | 399 | return wrapper; 400 | } 401 | 402 | function resetToInitialState(ctx) { 403 | var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); 404 | var tmp = ctx.createBuffer(); 405 | ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); 406 | for (var ii = 0; ii < numAttribs; ++ii) { 407 | ctx.disableVertexAttribArray(ii); 408 | ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); 409 | ctx.vertexAttrib1f(ii, 0); 410 | } 411 | ctx.deleteBuffer(tmp); 412 | 413 | var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); 414 | for (var ii = 0; ii < numTextureUnits; ++ii) { 415 | ctx.activeTexture(ctx.TEXTURE0 + ii); 416 | ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); 417 | ctx.bindTexture(ctx.TEXTURE_2D, null); 418 | } 419 | 420 | ctx.activeTexture(ctx.TEXTURE0); 421 | ctx.useProgram(null); 422 | ctx.bindBuffer(ctx.ARRAY_BUFFER, null); 423 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); 424 | ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); 425 | ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); 426 | ctx.disable(ctx.BLEND); 427 | ctx.disable(ctx.CULL_FACE); 428 | ctx.disable(ctx.DEPTH_TEST); 429 | ctx.disable(ctx.DITHER); 430 | ctx.disable(ctx.SCISSOR_TEST); 431 | ctx.blendColor(0, 0, 0, 0); 432 | ctx.blendEquation(ctx.FUNC_ADD); 433 | ctx.blendFunc(ctx.ONE, ctx.ZERO); 434 | ctx.clearColor(0, 0, 0, 0); 435 | ctx.clearDepth(1); 436 | ctx.clearStencil(-1); 437 | ctx.colorMask(true, true, true, true); 438 | ctx.cullFace(ctx.BACK); 439 | ctx.depthFunc(ctx.LESS); 440 | ctx.depthMask(true); 441 | ctx.depthRange(0, 1); 442 | ctx.frontFace(ctx.CCW); 443 | ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); 444 | ctx.lineWidth(1); 445 | ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); 446 | ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); 447 | ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); 448 | ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); 449 | // TODO: Delete this IF. 450 | if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { 451 | ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); 452 | } 453 | ctx.polygonOffset(0, 0); 454 | ctx.sampleCoverage(1, false); 455 | ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); 456 | ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); 457 | ctx.stencilMask(0xFFFFFFFF); 458 | ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); 459 | ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); 460 | ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); 461 | 462 | // TODO: This should NOT be needed but Firefox fails with 'hint' 463 | while(ctx.getError()); 464 | } 465 | 466 | function makeLostContextSimulatingCanvas(canvas) { 467 | var unwrappedContext_; 468 | var wrappedContext_; 469 | var onLost_ = []; 470 | var onRestored_ = []; 471 | var wrappedContext_ = {}; 472 | var contextId_ = 1; 473 | var contextLost_ = false; 474 | var resourceId_ = 0; 475 | var resourceDb_ = []; 476 | var numCallsToLoseContext_ = 0; 477 | var numCalls_ = 0; 478 | var canRestore_ = false; 479 | var restoreTimeout_ = 0; 480 | 481 | // Holds booleans for each GL error so can simulate errors. 482 | var glErrorShadow_ = { }; 483 | 484 | canvas.getContext = function(f) { 485 | return function() { 486 | var ctx = f.apply(canvas, arguments); 487 | // Did we get a context and is it a WebGL context? 488 | if (ctx instanceof WebGLRenderingContext) { 489 | if (ctx != unwrappedContext_) { 490 | if (unwrappedContext_) { 491 | throw "got different context" 492 | } 493 | unwrappedContext_ = ctx; 494 | wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); 495 | } 496 | return wrappedContext_; 497 | } 498 | return ctx; 499 | } 500 | }(canvas.getContext); 501 | 502 | function wrapEvent(listener) { 503 | if (typeof(listener) == "function") { 504 | return listener; 505 | } else { 506 | return function(info) { 507 | listener.handleEvent(info); 508 | } 509 | } 510 | } 511 | 512 | var addOnContextLostListener = function(listener) { 513 | onLost_.push(wrapEvent(listener)); 514 | }; 515 | 516 | var addOnContextRestoredListener = function(listener) { 517 | onRestored_.push(wrapEvent(listener)); 518 | }; 519 | 520 | 521 | function wrapAddEventListener(canvas) { 522 | var f = canvas.addEventListener; 523 | canvas.addEventListener = function(type, listener, bubble) { 524 | switch (type) { 525 | case 'webglcontextlost': 526 | addOnContextLostListener(listener); 527 | break; 528 | case 'webglcontextrestored': 529 | addOnContextRestoredListener(listener); 530 | break; 531 | default: 532 | f.apply(canvas, arguments); 533 | } 534 | }; 535 | } 536 | 537 | wrapAddEventListener(canvas); 538 | 539 | canvas.loseContext = function() { 540 | if (!contextLost_) { 541 | contextLost_ = true; 542 | numCallsToLoseContext_ = 0; 543 | ++contextId_; 544 | while (unwrappedContext_.getError()); 545 | clearErrors(); 546 | glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; 547 | var event = makeWebGLContextEvent("context lost"); 548 | var callbacks = onLost_.slice(); 549 | setTimeout(function() { 550 | //log("numCallbacks:" + callbacks.length); 551 | for (var ii = 0; ii < callbacks.length; ++ii) { 552 | //log("calling callback:" + ii); 553 | callbacks[ii](event); 554 | } 555 | if (restoreTimeout_ >= 0) { 556 | setTimeout(function() { 557 | canvas.restoreContext(); 558 | }, restoreTimeout_); 559 | } 560 | }, 0); 561 | } 562 | }; 563 | 564 | canvas.restoreContext = function() { 565 | if (contextLost_) { 566 | if (onRestored_.length) { 567 | setTimeout(function() { 568 | if (!canRestore_) { 569 | throw "can not restore. webglcontestlost listener did not call event.preventDefault"; 570 | } 571 | freeResources(); 572 | resetToInitialState(unwrappedContext_); 573 | contextLost_ = false; 574 | numCalls_ = 0; 575 | canRestore_ = false; 576 | var callbacks = onRestored_.slice(); 577 | var event = makeWebGLContextEvent("context restored"); 578 | for (var ii = 0; ii < callbacks.length; ++ii) { 579 | callbacks[ii](event); 580 | } 581 | }, 0); 582 | } 583 | } 584 | }; 585 | 586 | canvas.loseContextInNCalls = function(numCalls) { 587 | if (contextLost_) { 588 | throw "You can not ask a lost contet to be lost"; 589 | } 590 | numCallsToLoseContext_ = numCalls_ + numCalls; 591 | }; 592 | 593 | canvas.getNumCalls = function() { 594 | return numCalls_; 595 | }; 596 | 597 | canvas.setRestoreTimeout = function(timeout) { 598 | restoreTimeout_ = timeout; 599 | }; 600 | 601 | function isWebGLObject(obj) { 602 | //return false; 603 | return (obj instanceof WebGLBuffer || 604 | obj instanceof WebGLFramebuffer || 605 | obj instanceof WebGLProgram || 606 | obj instanceof WebGLRenderbuffer || 607 | obj instanceof WebGLShader || 608 | obj instanceof WebGLTexture); 609 | } 610 | 611 | function checkResources(args) { 612 | for (var ii = 0; ii < args.length; ++ii) { 613 | var arg = args[ii]; 614 | if (isWebGLObject(arg)) { 615 | return arg.__webglDebugContextLostId__ == contextId_; 616 | } 617 | } 618 | return true; 619 | } 620 | 621 | function clearErrors() { 622 | var k = Object.keys(glErrorShadow_); 623 | for (var ii = 0; ii < k.length; ++ii) { 624 | delete glErrorShadow_[k]; 625 | } 626 | } 627 | 628 | function loseContextIfTime() { 629 | ++numCalls_; 630 | if (!contextLost_) { 631 | if (numCallsToLoseContext_ == numCalls_) { 632 | canvas.loseContext(); 633 | } 634 | } 635 | } 636 | 637 | // Makes a function that simulates WebGL when out of context. 638 | function makeLostContextFunctionWrapper(ctx, functionName) { 639 | var f = ctx[functionName]; 640 | return function() { 641 | // log("calling:" + functionName); 642 | // Only call the functions if the context is not lost. 643 | loseContextIfTime(); 644 | if (!contextLost_) { 645 | //if (!checkResources(arguments)) { 646 | // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; 647 | // return; 648 | //} 649 | var result = f.apply(ctx, arguments); 650 | return result; 651 | } 652 | }; 653 | } 654 | 655 | function freeResources() { 656 | for (var ii = 0; ii < resourceDb_.length; ++ii) { 657 | var resource = resourceDb_[ii]; 658 | if (resource instanceof WebGLBuffer) { 659 | unwrappedContext_.deleteBuffer(resource); 660 | } else if (resource instanceof WebGLFramebuffer) { 661 | unwrappedContext_.deleteFramebuffer(resource); 662 | } else if (resource instanceof WebGLProgram) { 663 | unwrappedContext_.deleteProgram(resource); 664 | } else if (resource instanceof WebGLRenderbuffer) { 665 | unwrappedContext_.deleteRenderbuffer(resource); 666 | } else if (resource instanceof WebGLShader) { 667 | unwrappedContext_.deleteShader(resource); 668 | } else if (resource instanceof WebGLTexture) { 669 | unwrappedContext_.deleteTexture(resource); 670 | } 671 | } 672 | } 673 | 674 | function makeWebGLContextEvent(statusMessage) { 675 | return { 676 | statusMessage: statusMessage, 677 | preventDefault: function() { 678 | canRestore_ = true; 679 | } 680 | }; 681 | } 682 | 683 | return canvas; 684 | 685 | function makeLostContextSimulatingContext(ctx) { 686 | // copy all functions and properties to wrapper 687 | for (var propertyName in ctx) { 688 | if (typeof ctx[propertyName] == 'function') { 689 | wrappedContext_[propertyName] = makeLostContextFunctionWrapper( 690 | ctx, propertyName); 691 | } else { 692 | makePropertyWrapper(wrappedContext_, ctx, propertyName); 693 | } 694 | } 695 | 696 | // Wrap a few functions specially. 697 | wrappedContext_.getError = function() { 698 | loseContextIfTime(); 699 | if (!contextLost_) { 700 | var err; 701 | while (err = unwrappedContext_.getError()) { 702 | glErrorShadow_[err] = true; 703 | } 704 | } 705 | for (var err in glErrorShadow_) { 706 | if (glErrorShadow_[err]) { 707 | delete glErrorShadow_[err]; 708 | return err; 709 | } 710 | } 711 | return wrappedContext_.NO_ERROR; 712 | }; 713 | 714 | var creationFunctions = [ 715 | "createBuffer", 716 | "createFramebuffer", 717 | "createProgram", 718 | "createRenderbuffer", 719 | "createShader", 720 | "createTexture" 721 | ]; 722 | for (var ii = 0; ii < creationFunctions.length; ++ii) { 723 | var functionName = creationFunctions[ii]; 724 | wrappedContext_[functionName] = function(f) { 725 | return function() { 726 | loseContextIfTime(); 727 | if (contextLost_) { 728 | return null; 729 | } 730 | var obj = f.apply(ctx, arguments); 731 | obj.__webglDebugContextLostId__ = contextId_; 732 | resourceDb_.push(obj); 733 | return obj; 734 | }; 735 | }(ctx[functionName]); 736 | } 737 | 738 | var functionsThatShouldReturnNull = [ 739 | "getActiveAttrib", 740 | "getActiveUniform", 741 | "getBufferParameter", 742 | "getContextAttributes", 743 | "getAttachedShaders", 744 | "getFramebufferAttachmentParameter", 745 | "getParameter", 746 | "getProgramParameter", 747 | "getProgramInfoLog", 748 | "getRenderbufferParameter", 749 | "getShaderParameter", 750 | "getShaderInfoLog", 751 | "getShaderSource", 752 | "getTexParameter", 753 | "getUniform", 754 | "getUniformLocation", 755 | "getVertexAttrib" 756 | ]; 757 | for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { 758 | var functionName = functionsThatShouldReturnNull[ii]; 759 | wrappedContext_[functionName] = function(f) { 760 | return function() { 761 | loseContextIfTime(); 762 | if (contextLost_) { 763 | return null; 764 | } 765 | return f.apply(ctx, arguments); 766 | } 767 | }(wrappedContext_[functionName]); 768 | } 769 | 770 | var isFunctions = [ 771 | "isBuffer", 772 | "isEnabled", 773 | "isFramebuffer", 774 | "isProgram", 775 | "isRenderbuffer", 776 | "isShader", 777 | "isTexture" 778 | ]; 779 | for (var ii = 0; ii < isFunctions.length; ++ii) { 780 | var functionName = isFunctions[ii]; 781 | wrappedContext_[functionName] = function(f) { 782 | return function() { 783 | loseContextIfTime(); 784 | if (contextLost_) { 785 | return false; 786 | } 787 | return f.apply(ctx, arguments); 788 | } 789 | }(wrappedContext_[functionName]); 790 | } 791 | 792 | wrappedContext_.checkFramebufferStatus = function(f) { 793 | return function() { 794 | loseContextIfTime(); 795 | if (contextLost_) { 796 | return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; 797 | } 798 | return f.apply(ctx, arguments); 799 | }; 800 | }(wrappedContext_.checkFramebufferStatus); 801 | 802 | wrappedContext_.getAttribLocation = function(f) { 803 | return function() { 804 | loseContextIfTime(); 805 | if (contextLost_) { 806 | return -1; 807 | } 808 | return f.apply(ctx, arguments); 809 | }; 810 | }(wrappedContext_.getAttribLocation); 811 | 812 | wrappedContext_.getVertexAttribOffset = function(f) { 813 | return function() { 814 | loseContextIfTime(); 815 | if (contextLost_) { 816 | return 0; 817 | } 818 | return f.apply(ctx, arguments); 819 | }; 820 | }(wrappedContext_.getVertexAttribOffset); 821 | 822 | wrappedContext_.isContextLost = function() { 823 | return contextLost_; 824 | }; 825 | 826 | return wrappedContext_; 827 | } 828 | } 829 | 830 | return { 831 | /** 832 | * Initializes this module. Safe to call more than once. 833 | * @param {!WebGLRenderingContext} ctx A WebGL context. If 834 | * you have more than one context it doesn't matter which one 835 | * you pass in, it is only used to pull out constants. 836 | */ 837 | 'init': init, 838 | 839 | /** 840 | * Returns true or false if value matches any WebGL enum 841 | * @param {*} value Value to check if it might be an enum. 842 | * @return {boolean} True if value matches one of the WebGL defined enums 843 | */ 844 | 'mightBeEnum': mightBeEnum, 845 | 846 | /** 847 | * Gets an string version of an WebGL enum. 848 | * 849 | * Example: 850 | * WebGLDebugUtil.init(ctx); 851 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 852 | * 853 | * @param {number} value Value to return an enum for 854 | * @return {string} The string version of the enum. 855 | */ 856 | 'glEnumToString': glEnumToString, 857 | 858 | /** 859 | * Converts the argument of a WebGL function to a string. 860 | * Attempts to convert enum arguments to strings. 861 | * 862 | * Example: 863 | * WebGLDebugUtil.init(ctx); 864 | * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D); 865 | * 866 | * would return 'TEXTURE_2D' 867 | * 868 | * @param {string} functionName the name of the WebGL function. 869 | * @param {number} numArgs The number of arguments 870 | * @param {number} argumentIndx the index of the argument. 871 | * @param {*} value The value of the argument. 872 | * @return {string} The value as a string. 873 | */ 874 | 'glFunctionArgToString': glFunctionArgToString, 875 | 876 | /** 877 | * Converts the arguments of a WebGL function to a string. 878 | * Attempts to convert enum arguments to strings. 879 | * 880 | * @param {string} functionName the name of the WebGL function. 881 | * @param {number} args The arguments. 882 | * @return {string} The arguments as a string. 883 | */ 884 | 'glFunctionArgsToString': glFunctionArgsToString, 885 | 886 | /** 887 | * Given a WebGL context returns a wrapped context that calls 888 | * gl.getError after every command and calls a function if the 889 | * result is not NO_ERROR. 890 | * 891 | * You can supply your own function if you want. For example, if you'd like 892 | * an exception thrown on any GL error you could do this 893 | * 894 | * function throwOnGLError(err, funcName, args) { 895 | * throw WebGLDebugUtils.glEnumToString(err) + 896 | * " was caused by call to " + funcName; 897 | * }; 898 | * 899 | * ctx = WebGLDebugUtils.makeDebugContext( 900 | * canvas.getContext("webgl"), throwOnGLError); 901 | * 902 | * @param {!WebGLRenderingContext} ctx The webgl context to wrap. 903 | * @param {!function(err, funcName, args): void} opt_onErrorFunc The function 904 | * to call when gl.getError returns an error. If not specified the default 905 | * function calls console.log with a message. 906 | * @param {!function(funcName, args): void} opt_onFunc The 907 | * function to call when each webgl function is called. You 908 | * can use this to log all calls for example. 909 | */ 910 | 'makeDebugContext': makeDebugContext, 911 | 912 | /** 913 | * Given a canvas element returns a wrapped canvas element that will 914 | * simulate lost context. The canvas returned adds the following functions. 915 | * 916 | * loseContext: 917 | * simulates a lost context event. 918 | * 919 | * restoreContext: 920 | * simulates the context being restored. 921 | * 922 | * lostContextInNCalls: 923 | * loses the context after N gl calls. 924 | * 925 | * getNumCalls: 926 | * tells you how many gl calls there have been so far. 927 | * 928 | * setRestoreTimeout: 929 | * sets the number of milliseconds until the context is restored 930 | * after it has been lost. Defaults to 0. Pass -1 to prevent 931 | * automatic restoring. 932 | * 933 | * @param {!Canvas} canvas The canvas element to wrap. 934 | */ 935 | 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, 936 | 937 | /** 938 | * Resets a context to the initial state. 939 | * @param {!WebGLRenderingContext} ctx The webgl context to 940 | * reset. 941 | */ 942 | 'resetToInitialState': resetToInitialState 943 | }; 944 | 945 | }(); 946 | 947 | --------------------------------------------------------------------------------