Переглянути джерело

Merge branch 'dev' of https://github.com/mrdoob/three.js into dev

Mr.doob 10 роки тому
батько
коміт
3a6f4fdeda

+ 1 - 1
docs/api/core/BufferAttribute.html

@@ -17,7 +17,7 @@
 		<h2>Constructor</h2>
 		<h3>[name]([page:Array array], [page:Integer itemSize])</h3>
 		<div>
-		Instantiates this attibute with data from the associated buffer. The array can either be a regular Array or a Typed Array.
+		Instantiates this attribute with data from the associated buffer. The array can either be a regular Array or a Typed Array.
 		itemSize gives the number of values of the array that should be associated with a particular vertex.
 		</div>
 

+ 2 - 0
examples/index.html

@@ -225,6 +225,7 @@
 				"webgl_geometry_text",
 				"webgl_geometry_text2",
 				"webgl_gpgpu_birds",
+				"webgl_gpu_particle_system",
 				"webgl_hdr",
 				"webgl_helpers",
 				"webgl_interactive_buffergeometry",
@@ -257,6 +258,7 @@
 				"webgl_loader_ctm_materials",
 				"webgl_loader_gltf",
 				"webgl_loader_json_blender",
+				"webgl_loader_json_claraio",
 				"webgl_loader_json_objconverter",
 				"webgl_loader_md2",
 				"webgl_loader_md2_control",

+ 515 - 0
examples/js/GPUParticleSystem.js

@@ -0,0 +1,515 @@
+/*
+ * GPU Particle System
+ * @author flimshaw - Charlie Hoey - http://charliehoey.com
+ *
+ * A simple to use, general purpose GPU system.  Particles are spawn-and-forget with
+ * several options available, and do not require monitoring or cleanup after spawning.
+ * Because the paths of all particles are completely deterministic once spawned, the scale
+ * and direction of time is also variable.
+ *
+ * Currently uses a static wrapping perlin noise texture for turbulence, and a small png texture for
+ * particles, but adding support for a particle texture atlas or changing to a different type of turbulence
+ * would be a fairly light day's work.
+ *
+ * Shader and javascript packing code derrived from several Stack Overflow examples.
+ *
+ */
+
+THREE.GPUParticleSystem = function(options) {
+
+  var self = this;
+  var options = options || {};
+
+  // parse options and use defaults
+  self.PARTICLE_COUNT = options.maxParticles || 1000000;
+  self.PARTICLE_CONTAINERS = options.containerCount || 1;
+  self.PARTICLES_PER_CONTAINER = Math.ceil(self.PARTICLE_COUNT / self.PARTICLE_CONTAINERS);
+  self.PARTICLE_CURSOR = 0;
+  self.time = 0;
+
+
+  // Custom vertex and fragement shader
+  var GPUParticleShader = {
+
+    vertexShader: [
+
+      'precision highp float;',
+      'const vec4 bitSh = vec4(256. * 256. * 256., 256. * 256., 256., 1.);',
+      'const vec4 bitMsk = vec4(0.,vec3(1./256.0));',
+      'const vec4 bitShifts = vec4(1.) / bitSh;',
+
+      '#define FLOAT_MAX  1.70141184e38',
+      '#define FLOAT_MIN  1.17549435e-38',
+
+      'lowp vec4 encode_float(highp float v) {',
+      'highp float av = abs(v);',
+
+      '//Handle special cases',
+      'if(av < FLOAT_MIN) {',
+      'return vec4(0.0, 0.0, 0.0, 0.0);',
+      '} else if(v > FLOAT_MAX) {',
+      'return vec4(127.0, 128.0, 0.0, 0.0) / 255.0;',
+      '} else if(v < -FLOAT_MAX) {',
+      'return vec4(255.0, 128.0, 0.0, 0.0) / 255.0;',
+      '}',
+
+      'highp vec4 c = vec4(0,0,0,0);',
+
+      '//Compute exponent and mantissa',
+      'highp float e = floor(log2(av));',
+      'highp float m = av * pow(2.0, -e) - 1.0;',
+
+      //Unpack mantissa
+      'c[1] = floor(128.0 * m);',
+      'm -= c[1] / 128.0;',
+      'c[2] = floor(32768.0 * m);',
+      'm -= c[2] / 32768.0;',
+      'c[3] = floor(8388608.0 * m);',
+
+      '//Unpack exponent',
+      'highp float ebias = e + 127.0;',
+      'c[0] = floor(ebias / 2.0);',
+      'ebias -= c[0] * 2.0;',
+      'c[1] += floor(ebias) * 128.0;',
+
+      '//Unpack sign bit',
+      'c[0] += 128.0 * step(0.0, -v);',
+
+      '//Scale back to range',
+      'return c / 255.0;',
+      '}',
+
+      'vec4 pack(const in float depth)',
+      '{',
+      'const vec4 bit_shift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);',
+      'const vec4 bit_mask  = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);',
+      'vec4 res = fract(depth * bit_shift);',
+      'res -= res.xxyz * bit_mask;',
+      'return res;',
+      '}',
+
+      'float unpack(const in vec4 rgba_depth)',
+      '{',
+      'const vec4 bit_shift = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0);',
+      'float depth = dot(rgba_depth, bit_shift);',
+      'return depth;',
+      '}',
+
+      'uniform float uTime;',
+      'uniform float uScale;',
+      'uniform sampler2D tNoise;',
+
+      'attribute vec4 particlePositionsStartTime;',
+      'attribute vec4 particleVelColSizeLife;',
+
+      'varying vec4 vColor;',
+      'varying float lifeLeft;',
+
+      'void main() {',
+
+      '// unpack things from our attributes',
+      'vColor = encode_float( particleVelColSizeLife.y );',
+
+      '// convert our velocity back into a value we can use',
+      'vec4 velTurb = encode_float( particleVelColSizeLife.x );',
+      'vec3 velocity = vec3( velTurb.xyz );',
+      'float turbulence = velTurb.w;',
+
+      'vec3 newPosition;',
+
+      'float timeElapsed = uTime - particlePositionsStartTime.a;',
+
+      'lifeLeft = 1. - (timeElapsed / particleVelColSizeLife.w);',
+
+      'gl_PointSize = ( uScale * particleVelColSizeLife.z ) * lifeLeft;',
+
+      'velocity.x = ( velocity.x - .5 ) * 3.;',
+      'velocity.y = ( velocity.y - .5 ) * 3.;',
+      'velocity.z = ( velocity.z - .5 ) * 3.;',
+
+      'newPosition = particlePositionsStartTime.xyz + ( velocity * 10. ) * ( uTime - particlePositionsStartTime.a );',
+
+      'vec3 noise = texture2D( tNoise, vec2( newPosition.x * .015 + (uTime * .05), newPosition.y * .02 + (uTime * .015) )).rgb;',
+      'vec3 noiseVel = ( noise.rgb - .5 ) * 30.;',
+
+      'newPosition = mix(newPosition, newPosition + vec3(noiseVel * ( turbulence * 5. ) ), (timeElapsed / particleVelColSizeLife.a) );',
+
+      'if( velocity.y > 0. && velocity.y < .05 ) {',
+      'lifeLeft = 0.;',
+      '}',
+
+      'if( velocity.x < -1.45 ) {',
+      'lifeLeft = 0.;',
+      '}',
+
+      'if( timeElapsed > 0. ) {',
+      'gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );',
+      '} else {',
+      'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+      'lifeLeft = 0.;',
+      'gl_PointSize = 0.;',
+      '}',
+      '}'
+
+    ].join("\n"),
+
+    fragmentShader: [
+
+      'float scaleLinear(float value, vec2 valueDomain) {',
+      'return (value - valueDomain.x) / (valueDomain.y - valueDomain.x);',
+      '}',
+
+      'float scaleLinear(float value, vec2 valueDomain, vec2 valueRange) {',
+      'return mix(valueRange.x, valueRange.y, scaleLinear(value, valueDomain));',
+      '}',
+
+      'varying vec4 vColor;',
+      'varying float lifeLeft;',
+
+      'uniform sampler2D tSprite;',
+
+      'void main() {',
+
+      'float alpha = 0.;',
+
+      'if( lifeLeft > .995 ) {',
+      'alpha = scaleLinear( lifeLeft, vec2(1., .995), vec2(0., 1.));//mix( 0., 1., ( lifeLeft - .95 ) * 100. ) * .75;',
+      '} else {',
+      'alpha = lifeLeft * .75;',
+      '}',
+
+      'vec4 tex = texture2D( tSprite, gl_PointCoord );',
+
+      'gl_FragColor = vec4( vColor.rgb * tex.a, alpha * tex.a );',
+      '}'
+
+    ].join("\n")
+
+  };
+
+  // preload a million random numbers
+  self.rand = [];
+
+  for (var i = 1e5; i > 0; i--) {
+    self.rand.push(Math.random() - .5);
+  }
+
+  self.random = function() {
+    return ++i >= self.rand.length ? self.rand[i = 1] : self.rand[i];
+  }
+
+  self.particleNoiseTex = THREE.ImageUtils.loadTexture("textures/perlin-512.png");
+  self.particleNoiseTex.wrapS = self.particleNoiseTex.wrapT = THREE.RepeatWrapping;
+
+  self.particleSpriteTex = THREE.ImageUtils.loadTexture("textures/particle2.png");
+  self.particleSpriteTex.wrapS = self.particleSpriteTex.wrapT = THREE.RepeatWrapping;
+
+  self.particleShaderMat = new THREE.ShaderMaterial({
+    transparent: true,
+    depthWrite: false,
+    uniforms: {
+      "uTime": {
+        type: "f",
+        value: 0.0
+      },
+      "uScale": {
+        type: "f",
+        value: 1.0
+      },
+      "tNoise": {
+        type: "t",
+        value: self.particleNoiseTex
+      },
+      "tSprite": {
+        type: "t",
+        value: self.particleSpriteTex
+      }
+    },
+    attributes: {
+      "particlePositionsStartTime": {
+        type: "v4",
+        value: []
+      },
+      "particleVelColSizeLife": {
+        type: "v4",
+        value: []
+      }
+    },
+    blending: THREE.AdditiveBlending,
+    vertexShader: GPUParticleShader.vertexShader,
+    fragmentShader: GPUParticleShader.fragmentShader
+  });
+
+  // define defaults for all values
+  self.particleShaderMat.defaultAttributeValues.particlePositionsStartTime = [0, 0, 0, 0];
+  self.particleShaderMat.defaultAttributeValues.particleVelColSizeLife = [0, 0, 0, 0];
+
+  self.particleContainers = [];
+
+
+  // extend Object3D
+  THREE.Object3D.apply(this, arguments);
+
+  this.init = function() {
+
+    for (var i = 0; i < self.PARTICLE_CONTAINERS; i++) {
+
+      var c = new THREE.GPUParticleContainer(self.PARTICLES_PER_CONTAINER, self);
+      self.particleContainers.push(c);
+      self.add(c);
+
+    }
+
+  }
+
+  this.spawnParticle = function(options) {
+
+    self.PARTICLE_CURSOR++;
+    if (self.PARTICLE_CURSOR >= self.PARTICLE_COUNT) {
+      self.PARTICLE_CURSOR = 1;
+    }
+
+    var currentContainer = self.particleContainers[Math.floor(self.PARTICLE_CURSOR / self.PARTICLES_PER_CONTAINER)];
+
+    currentContainer.spawnParticle(options);
+
+  }
+
+  this.update = function(time) {
+    for (var i = 0; i < self.PARTICLE_CONTAINERS; i++) {
+
+      self.particleContainers[i].update(time);
+
+    }
+  };
+
+  this.init();
+
+}
+
+THREE.GPUParticleSystem.prototype = Object.create(THREE.Object3D.prototype);
+THREE.GPUParticleSystem.prototype.constructor = THREE.GPUParticleSystem;
+
+
+// Subclass for particle containers, allows for very large arrays to be spread out
+THREE.GPUParticleContainer = function(maxParticles, particleSystem) {
+
+  var self = this;
+  self.PARTICLE_COUNT = maxParticles || 100000;
+  self.PARTICLE_CURSOR = 0;
+  self.time = 0;
+  self.DPR = window.devicePixelRatio;
+  self.GPUParticleSystem = particleSystem;
+
+  var particlesPerArray = Math.floor(self.PARTICLE_COUNT / self.MAX_ATTRIBUTES);
+
+  // extend Object3D
+  THREE.Object3D.apply(this, arguments);
+
+  // construct a couple small arrays used for packing variables into floats etc
+  var UINT8_VIEW = new Uint8Array(4)
+  var FLOAT_VIEW = new Float32Array(UINT8_VIEW.buffer)
+
+  function decodeFloat(x, y, z, w) {
+    UINT8_VIEW[0] = Math.floor(w)
+    UINT8_VIEW[1] = Math.floor(z)
+    UINT8_VIEW[2] = Math.floor(y)
+    UINT8_VIEW[3] = Math.floor(x)
+    return FLOAT_VIEW[0]
+  }
+
+  function componentToHex(c) {
+    var hex = c.toString(16);
+    return hex.length == 1 ? "0" + hex : hex;
+  }
+
+  function rgbToHex(r, g, b) {
+    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
+  }
+
+  function hexToRgb(hex) {
+    var r = hex >> 16;
+    var g = (hex & 0x00FF00) >> 8;
+    var b = hex & 0x0000FF;
+
+    if (r > 0) r--;
+    if (g > 0) g--;
+    if (b > 0) b--;
+
+    return [r, g, b];
+  };
+
+  self.particles = [];
+  self.deadParticles = [];
+  self.particlesAvailableSlot = [];
+
+  // create a container for particles
+  self.particleUpdate = false;
+
+  // Shader Based Particle System
+  self.particleShaderGeo = new THREE.BufferGeometry();
+
+  // new hyper compressed attributes
+  self.particleVertices = new Float32Array(self.PARTICLE_COUNT * 3); // position
+  self.particlePositionsStartTime = new Float32Array(self.PARTICLE_COUNT * 4); // position
+  self.particleVelColSizeLife = new Float32Array(self.PARTICLE_COUNT * 4);
+
+  for (var i = 0; i < self.PARTICLE_COUNT; i++) {
+    self.particlePositionsStartTime[i * 4 + 0] = 100; //x
+    self.particlePositionsStartTime[i * 4 + 1] = 0; //y
+    self.particlePositionsStartTime[i * 4 + 2] = 0.0; //z
+    self.particlePositionsStartTime[i * 4 + 3] = 0.0; //startTime
+
+    self.particleVertices[i * 3 + 0] = 0; //x
+    self.particleVertices[i * 3 + 1] = 0; //y
+    self.particleVertices[i * 3 + 2] = 0.0; //z
+
+    self.particleVelColSizeLife[i * 4 + 0] = decodeFloat(128, 128, 0, 0); //vel
+    self.particleVelColSizeLife[i * 4 + 1] = decodeFloat(0, 254, 0, 254); //color
+    self.particleVelColSizeLife[i * 4 + 2] = 1.0; //size
+    self.particleVelColSizeLife[i * 4 + 3] = 0.0; //lifespan
+  }
+
+  self.particleShaderGeo.addAttribute('position', new THREE.BufferAttribute(self.particleVertices, 3));
+  self.particleShaderGeo.addAttribute('particlePositionsStartTime', new THREE.DynamicBufferAttribute(self.particlePositionsStartTime, 4));
+  self.particleShaderGeo.addAttribute('particleVelColSizeLife', new THREE.DynamicBufferAttribute(self.particleVelColSizeLife, 4));
+
+  self.posStart = self.particleShaderGeo.getAttribute('particlePositionsStartTime')
+  self.velCol = self.particleShaderGeo.getAttribute('particleVelColSizeLife');
+
+  self.particleShaderMat = self.GPUParticleSystem.particleShaderMat;
+
+  this.init = function() {
+    self.particleSystem = new THREE.PointCloud(self.particleShaderGeo, self.particleShaderMat);
+    self.particleSystem.frustumCulled = false;
+    this.add(self.particleSystem);
+  };
+
+  var options = {},
+    position = new THREE.Vector3(),
+    velocity = new THREE.Vector3(),
+    positionRandomness = 0.,
+    velocityRandomness = 0.,
+    color = 0xffffff,
+    colorRandomness = 0.,
+    turbulence = 0.,
+    lifetime = 0.,
+    size = 0.,
+    sizeRandomness = 0.,
+    i;
+
+  var maxVel = 2;
+  var maxSource = 250;
+  this.offset = 0;
+  this.count = 0;
+
+  this.spawnParticle = function(options) {
+
+    options = options || {};
+
+    // setup reasonable default values for all arguments
+    position = options.position !== undefined ? position.copy(options.position) : position.set(0., 0., 0.);
+    velocity = options.velocity !== undefined ? velocity.copy(options.velocity) : velocity.set(0., 0., 0.);
+    positionRandomness = options.positionRandomness !== undefined ? options.positionRandomness : 0.0;
+    velocityRandomness = options.velocityRandomness !== undefined ? options.velocityRandomness : 0.0;
+    color = options.color !== undefined ? options.color : 0xffffff;
+    colorRandomness = options.colorRandomness !== undefined ? options.colorRandomness : 1.0;
+    turbulence = options.turbulence !== undefined ? options.turbulence : 1.0;
+    lifetime = options.lifetime !== undefined ? options.lifetime : 5.0;
+    size = options.size !== undefined ? options.size : 10;
+    sizeRandomness = options.sizeRandomness !== undefined ? options.sizeRandomness : 0.0,
+      smoothPosition = options.smoothPosition !== undefined ? options.smoothPosition : false;
+
+    if (self.DPR !== undefined) size *= self.DPR;
+
+    i = self.PARTICLE_CURSOR;
+
+    self.posStart.array[i * 4 + 0] = position.x + ((particleSystem.random()) * positionRandomness); // - ( velocity.x * particleSystem.random() ); //x
+    self.posStart.array[i * 4 + 1] = position.y + ((particleSystem.random()) * positionRandomness); // - ( velocity.y * particleSystem.random() ); //y
+    self.posStart.array[i * 4 + 2] = position.z + ((particleSystem.random()) * positionRandomness); // - ( velocity.z * particleSystem.random() ); //z
+    self.posStart.array[i * 4 + 3] = self.time + (particleSystem.random() * 2e-2); //startTime
+
+    if (smoothPosition === true) {
+      self.posStart.array[i * 4 + 0] += -(velocity.x * particleSystem.random()); //x
+      self.posStart.array[i * 4 + 1] += -(velocity.y * particleSystem.random()); //y
+      self.posStart.array[i * 4 + 2] += -(velocity.z * particleSystem.random()); //z
+    }
+
+    var velX = velocity.x + (particleSystem.random()) * velocityRandomness;
+    var velY = velocity.y + (particleSystem.random()) * velocityRandomness;
+    var velZ = velocity.z + (particleSystem.random()) * velocityRandomness;
+
+    // convert turbulence rating to something we can pack into a vec4
+    var turbulence = Math.floor(turbulence * 254);
+
+    // clamp our value to between 0. and 1.
+    velX = Math.floor(maxSource * ((velX - -maxVel) / (maxVel - -maxVel)));
+    velY = Math.floor(maxSource * ((velY - -maxVel) / (maxVel - -maxVel)));
+    velZ = Math.floor(maxSource * ((velZ - -maxVel) / (maxVel - -maxVel)));
+
+    self.velCol.array[i * 4 + 0] = decodeFloat(velX, velY, velZ, turbulence); //vel
+
+    var rgb = hexToRgb(color);
+
+    for (var c = 0; c < rgb.length; c++) {
+      rgb[c] = Math.floor(rgb[c] + ((particleSystem.random()) * colorRandomness) * 254);
+      if (rgb[c] > 254) rgb[c] = 254;
+      if (rgb[c] < 0) rgb[c] = 0;
+    }
+
+    self.velCol.array[i * 4 + 1] = decodeFloat(rgb[0], rgb[1], rgb[2], 254); //color
+    self.velCol.array[i * 4 + 2] = size + (particleSystem.random()) * sizeRandomness; //size
+    self.velCol.array[i * 4 + 3] = lifetime; //lifespan
+
+    if (this.offset == 0) {
+      this.offset = self.PARTICLE_CURSOR;
+    }
+
+    self.count++;
+
+    self.PARTICLE_CURSOR++;
+
+    if (self.PARTICLE_CURSOR >= self.PARTICLE_COUNT) {
+      self.PARTICLE_CURSOR = 0;
+    }
+
+    self.particleUpdate = true;
+
+  }
+
+  this.update = function(time) {
+
+    self.time = time;
+    self.particleShaderMat.uniforms['uTime'].value = time;
+
+    this.geometryUpdate();
+
+  };
+
+  this.geometryUpdate = function() {
+    if (self.particleUpdate == true) {
+      self.particleUpdate = false;
+
+      // if we can get away with a partial buffer update, do so
+      if (self.offset + self.count < self.PARTICLE_COUNT) {
+        self.posStart.updateRange.offset = self.velCol.updateRange.offset = self.offset * 4;
+        self.posStart.updateRange.count = self.velCol.updateRange.count = self.count * 4;
+      } else {
+        self.posStart.updateRange.offset = 0;
+        self.posStart.updateRange.count = self.velCol.updateRange.count = (self.PARTICLE_COUNT * 4);
+      }
+
+      self.posStart.needsUpdate = true;
+      self.velCol.needsUpdate = true;
+
+      self.offset = 0;
+      self.count = 0;
+    }
+  }
+
+  this.init();
+
+}
+
+THREE.GPUParticleContainer.prototype = Object.create(THREE.Object3D.prototype);
+THREE.GPUParticleContainer.prototype.constructor = THREE.GPUParticleContainer;

+ 6 - 0
examples/js/controls/DeviceOrientationControls.js

@@ -88,6 +88,12 @@ THREE.DeviceOrientationControls = function ( object ) {
 
 	};
 
+	this.dispose = function () {
+
+		this.disconnect();
+
+	};
+
 	this.connect();
 
 };

+ 6 - 0
examples/js/controls/DragControls.js

@@ -96,6 +96,12 @@ THREE.DragControls = function( _camera, _objects, _domElement ) {
 	
 	};
 
+	this.dispose = function() {
+
+		me.deactivate();
+
+	};
+
 	this.activate();
 
 	function onDocumentMouseMove( event ) {

+ 21 - 2
examples/js/controls/EditorControls.js

@@ -203,11 +203,30 @@ THREE.EditorControls = function ( object, domElement ) {
 
 	}
 
-	domElement.addEventListener( 'contextmenu', function ( event ) {
+	function contextmenu( event ) {
 
 		event.preventDefault();
 
-	}, false );
+	}
+
+	this.dispose = function() {
+
+		domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		domElement.removeEventListener( 'mousedown', onMouseDown, false );
+		domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
+		domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
+
+		domElement.removeEventListener( 'mousemove', onMouseMove, false );
+		domElement.removeEventListener( 'mouseup', onMouseUp, false );
+		domElement.removeEventListener( 'mouseout', onMouseUp, false );
+		domElement.removeEventListener( 'dblclick', onMouseUp, false );
+
+		domElement.removeEventListener( 'touchstart', touchStart, false );
+		domElement.removeEventListener( 'touchmove', touchMove, false );
+
+	}
+
+	domElement.addEventListener( 'contextmenu', contextmenu, false );
 	domElement.addEventListener( 'mousedown', onMouseDown, false );
 	domElement.addEventListener( 'mousewheel', onMouseWheel, false );
 	domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox

+ 26 - 8
examples/js/controls/FirstPersonControls.js

@@ -253,19 +253,37 @@ THREE.FirstPersonControls = function ( object, domElement ) {
 
 	};
 
-
-	this.domElement.addEventListener( 'contextmenu', function ( event ) {
+	function contextmenu( event ) {
 
 		event.preventDefault();
 
-	}, false );
+	}
+
+	this.dispose = function() {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', _onMouseDown, false );
+		this.domElement.removeEventListener( 'mousemove', _onMouseMove, false );
+		this.domElement.removeEventListener( 'mouseup', _onMouseUp, false );
+
+		window.removeEventListener( 'keydown', _onKeyDown, false );
+		window.removeEventListener( 'keyup', _onKeyUp, false );
+
+	}
+
+	var _onMouseMove = bind( this, this.onMouseMove );
+	var _onMouseDown = bind( this, this.onMouseDown );
+	var _onMouseUp = bind( this, this.onMouseUp );
+	var _onKeyDown = bind( this, this.onKeyDown );
+	var _onKeyUp = bind( this, this.onKeyUp );
 
-	this.domElement.addEventListener( 'mousemove', bind( this, this.onMouseMove ), false );
-	this.domElement.addEventListener( 'mousedown', bind( this, this.onMouseDown ), false );
-	this.domElement.addEventListener( 'mouseup', bind( this, this.onMouseUp ), false );
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+	this.domElement.addEventListener( 'mousemove', _onMouseMove, false );
+	this.domElement.addEventListener( 'mousedown', _onMouseDown, false );
+	this.domElement.addEventListener( 'mouseup', _onMouseUp, false );
 
-	window.addEventListener( 'keydown', bind( this, this.onKeyDown ), false );
-	window.addEventListener( 'keyup', bind( this, this.onKeyUp ), false );
+	window.addEventListener( 'keydown', _onKeyDown, false );
+	window.addEventListener( 'keyup', _onKeyUp, false );
 
 	function bind( scope, fn ) {
 

+ 27 - 7
examples/js/controls/FlyControls.js

@@ -254,18 +254,38 @@ THREE.FlyControls = function ( object, domElement ) {
 
 	}
 
-	this.domElement.addEventListener( 'contextmenu', function ( event ) {
+	function contextmenu( event ) {
 
 		event.preventDefault();
 
-	}, false );
+	}
+
+	this.dispose = function() {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', _mousedown, false );
+		this.domElement.removeEventListener( 'mousemove', _mousemove, false );
+		this.domElement.removeEventListener( 'mouseup', _mouseup, false );
+
+		window.removeEventListener( 'keydown', _keydown, false );
+		window.removeEventListener( 'keyup', _keyup, false );
+
+	}
+
+	var _mousemove = bind( this, this.mousemove );
+	var _mousedown = bind( this, this.mousedown );
+	var _mouseup = bind( this, this.mouseup );
+	var _keydown = bind( this, this.keydown );
+	var _keyup = bind( this, this.keyup );
+
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
 
-	this.domElement.addEventListener( 'mousemove', bind( this, this.mousemove ), false );
-	this.domElement.addEventListener( 'mousedown', bind( this, this.mousedown ), false );
-	this.domElement.addEventListener( 'mouseup',   bind( this, this.mouseup ), false );
+	this.domElement.addEventListener( 'mousemove', _mousemove, false );
+	this.domElement.addEventListener( 'mousedown', _mousedown, false );
+	this.domElement.addEventListener( 'mouseup',   _mouseup, false );
 
-	window.addEventListener( 'keydown', bind( this, this.keydown ), false );
-	window.addEventListener( 'keyup',   bind( this, this.keyup ), false );
+	window.addEventListener( 'keydown', _keydown, false );
+	window.addEventListener( 'keyup',   _keyup, false );
 
 	this.updateMovementVector();
 	this.updateRotationVector();

+ 6 - 0
examples/js/controls/MouseControls.js

@@ -50,6 +50,12 @@ THREE.MouseControls = function ( object ) {
 
 	};
 
+	this.dispose = function() {
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+
+	}
+
 	document.addEventListener( 'mousemove', onMouseMove, false );
 
 };

+ 22 - 2
examples/js/controls/OrbitControls.js

@@ -750,11 +750,31 @@
 
 		}
 
-		this.domElement.addEventListener( 'contextmenu', function ( event ) {
+		function contextmenu( event ) {
 
 			event.preventDefault();
 
-		}, false );
+		}
+
+		this.dispose = function() {
+
+			this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+			this.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+			this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
+			this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
+
+			this.domElement.removeEventListener( 'touchstart', touchstart, false );
+			this.domElement.removeEventListener( 'touchend', touchend, false );
+			this.domElement.removeEventListener( 'touchmove', touchmove, false );
+
+			document.removeEventListener( 'mousemove', onMouseMove, false );
+			document.removeEventListener( 'mouseup', onMouseUp, false );
+
+			window.removeEventListener( 'keydown', onKeyDown, false );
+
+		}
+
+		this.domElement.addEventListener( 'contextmenu', contextmenu, false );
 
 		this.domElement.addEventListener( 'mousedown', onMouseDown, false );
 		this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );

+ 23 - 3
examples/js/controls/OrthographicTrackballControls.js

@@ -606,14 +606,34 @@ THREE.OrthographicTrackballControls = function ( object, domElement ) {
 
 	}
 
-	this.domElement.addEventListener( 'contextmenu', function ( event ) {
+	function contextmenu( event ) {
 
 		event.preventDefault();
 
-	}, false );
+	}
 
-	this.domElement.addEventListener( 'mousedown', mousedown, false );
+	this.dispose = function() {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', mousedown, false );
+		this.domElement.removeEventListener( 'mousewheel', mousewheel, false );
+		this.domElement.removeEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
+
+		this.domElement.removeEventListener( 'touchstart', touchstart, false );
+		this.domElement.removeEventListener( 'touchend', touchend, false );
+		this.domElement.removeEventListener( 'touchmove', touchmove, false );
+
+		document.removeEventListener( 'mousemove', mousemove, false );
+		document.removeEventListener( 'mouseup', mouseup, false );
 
+		window.removeEventListener( 'keydown', keydown, false );
+		window.removeEventListener( 'keyup', keyup, false );
+
+	}
+
+
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+	this.domElement.addEventListener( 'mousedown', mousedown, false );
 	this.domElement.addEventListener( 'mousewheel', mousewheel, false );
 	this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
 

+ 6 - 0
examples/js/controls/PointerLockControls.js

@@ -31,6 +31,12 @@ THREE.PointerLockControls = function ( camera ) {
 
 	};
 
+	this.dispose = function() {
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+
+	}
+
 	document.addEventListener( 'mousemove', onMouseMove, false );
 
 	this.enabled = false;

+ 22 - 3
examples/js/controls/TrackballControls.js

@@ -586,14 +586,33 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 	}
 
-	this.domElement.addEventListener( 'contextmenu', function ( event ) {
+	function contextmenu( event ) {
 
 		event.preventDefault();
 
-	}, false );
+	}
 
-	this.domElement.addEventListener( 'mousedown', mousedown, false );
+	this.dispose = function() {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', mousedown, false );
+		this.domElement.removeEventListener( 'mousewheel', mousewheel, false );
+		this.domElement.removeEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
+
+		this.domElement.removeEventListener( 'touchstart', touchstart, false );
+		this.domElement.removeEventListener( 'touchend', touchend, false );
+		this.domElement.removeEventListener( 'touchmove', touchmove, false );
+
+		document.removeEventListener( 'mousemove', mousemove, false );
+		document.removeEventListener( 'mouseup', mouseup, false );
 
+		window.removeEventListener( 'keydown', keydown, false );
+		window.removeEventListener( 'keyup', keyup, false );
+
+	}
+
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+	this.domElement.addEventListener( 'mousedown', mousedown, false );
 	this.domElement.addEventListener( 'mousewheel', mousewheel, false );
 	this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
 

+ 6 - 0
examples/js/controls/VRControls.js

@@ -116,4 +116,10 @@ THREE.VRControls = function ( object, onError ) {
 
 	};
 
+	this.dispose = function () {
+
+		vrInputs = [];
+
+	};
+
 };

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
examples/models/json/teapot-claraio.json


BIN
examples/textures/particle2.png


BIN
examples/textures/perlin-512.png


+ 159 - 0
examples/webgl_gpu_particle_system.html

@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <title>three.js - gpu particle system</title>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+  <style>
+    body {
+      background-color: #000000;
+      margin: 0px;
+      overflow: hidden;
+    }
+
+    a {
+      color: #0078ff;
+    }
+
+    .dg {
+      right: auto!important;
+      left: 20px!important;
+    }
+  </style>
+</head>
+
+<body>
+  <div style="position: absolute; top: 10px; width: 100%; text-align: center; color:#eee">
+    <a href="http://threejs.org" target="_blank">three.js</a> - GPU particle system plugin by <a href="http://charliehoey.com">Charlie Hoey</a>.</div>
+
+  <script src="../build/three.min.js"></script>
+  <script src="./js/controls/TrackballControls.js"></script>
+  <script src="./js/libs/dat.gui.min.js"></script>
+  <script src="./js/GPUParticleSystem.js" charset="utf-8"></script>
+
+  <script>
+    var camera, tick = 0,
+      scene, renderer, clock = new THREE.Clock(true),
+      controls, container, gui = new dat.GUI(),
+      options, spawnerOptions, particleSystem;
+
+    init();
+    animate();
+
+    function init() {
+
+
+      container = document.createElement('div');
+      document.body.appendChild(container);
+
+      camera = new THREE.PerspectiveCamera(28, window.innerWidth / window.innerHeight, 1, 10000);
+      camera.position.z = 100;
+
+      scene = new THREE.Scene();
+
+      // The GPU Particle system extends THREE.Object3D, and so you can use it
+      // as you would any other scene graph component.  Particle positions will be
+      // relative to the position of the particle system, but you will probably only need one
+      // system for your whole scene
+      particleSystem = new THREE.GPUParticleSystem({
+        maxParticles: 250000
+      });
+      scene.add( particleSystem);
+
+
+      // options passed during each spawned
+      options = {
+        position: new THREE.Vector3(),
+        positionRandomness: .3,
+        velocity: new THREE.Vector3(),
+        velocityRandomness: .5,
+        color: 0xaa88ff,
+        colorRandomness: .2,
+        turbulence: .5,
+        lifetime: 2,
+        size: 5,
+        sizeRandomness: 1
+      };
+
+      spawnerOptions = {
+        spawnRate: 15000,
+        horizontalSpeed: 1.5,
+        verticalSpeed: 1.33,
+        timeScale: 1
+      }
+
+      gui.add(options, "velocityRandomness", 0, 3);
+      gui.add(options, "positionRandomness", 0, 3);
+      gui.add(options, "size", 1, 20);
+      gui.add(options, "sizeRandomness", 0, 25);
+      gui.add(options, "colorRandomness", 0, 1);
+      gui.add(options, "lifetime", .1, 10);
+      gui.add(options, "turbulence", 0, 1);
+
+      gui.add(spawnerOptions, "spawnRate", 10, 30000);
+      gui.add(spawnerOptions, "timeScale", -1, 1);
+
+      renderer = new THREE.WebGLRenderer();
+      renderer.setPixelRatio(window.devicePixelRatio);
+      renderer.setSize(window.innerWidth, window.innerHeight);
+      container.appendChild(renderer.domElement);
+
+      // setup controls
+      controls = new THREE.TrackballControls(camera, renderer.domElement);
+      controls.rotateSpeed = 5.0;
+      controls.zoomSpeed = 2.2;
+      controls.panSpeed = 1;
+      controls.dynamicDampingFactor = 0.3;
+
+      window.addEventListener('resize', onWindowResize, false);
+
+    }
+
+    function onWindowResize() {
+
+      camera.aspect = window.innerWidth / window.innerHeight;
+      camera.updateProjectionMatrix();
+
+      renderer.setSize(window.innerWidth, window.innerHeight);
+
+    }
+
+    function animate() {
+
+      requestAnimationFrame(animate);
+
+      controls.update();
+
+      var delta = clock.getDelta() * spawnerOptions.timeScale;
+      tick += delta;
+
+      if (tick < 0) tick = 0;
+
+      if (delta > 0) {
+        options.position.x = Math.sin(tick * spawnerOptions.horizontalSpeed) * 20;
+        options.position.y = Math.sin(tick * spawnerOptions.verticalSpeed) * 10;
+        options.position.z = Math.sin(tick * spawnerOptions.horizontalSpeed + spawnerOptions.verticalSpeed) * 5;
+
+        for (var x = 0; x < spawnerOptions.spawnRate * delta; x++) {
+          // Yep, that's really it.  Spawning particles is super cheap, and once you spawn them, the rest of
+          // their lifecycle is handled entirely on the GPU, driven by a time uniform updated below
+          particleSystem.spawnParticle(options);
+        }
+      }
+
+      particleSystem.update(tick);
+
+      render();
+
+    }
+
+    function render() {
+
+      renderer.render(scene, camera);
+
+    }
+  </script>
+</body>
+
+</html>

+ 135 - 0
examples/webgl_loader_json_claraio.html

@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - loaders - Clara.io JSON loader</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #000;
+				color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				z-index: 100;
+				display:block;
+			}
+			#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
+		</style>
+	</head>
+
+	<body>
+		<div id="info">
+		<a href="http://threejs.org" target="_blank">three.js</a> - <a href="https://clara.io" target="_blank">Clara.io</a> JSON Scene/Object Load Test<br/>
+		More Information: <a href="https://clara.io/learn/user-guide/data_exchange/threejs_export" target="_blank">Clara.io ThreeJS Export Documentation</a>
+		</div>
+
+		<script src="../build/three.min.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+
+			var container, stats;
+
+			var camera, scene, renderer;
+
+			var mouseX = 0, mouseY = 0;
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+
+
+			init();
+			animate();
+
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.z = 4;
+
+				// scene
+
+				scene = new THREE.Scene();
+
+				var ambient = new THREE.AmbientLight( 0x444444 );
+				scene.add( ambient );
+
+				var directionalLight = new THREE.DirectionalLight( 0xffeedd );
+				directionalLight.position.set( 0, 0, 1 ).normalize();
+				scene.add( directionalLight );
+
+				// BEGIN Clara.io JSON loader code
+				var objectLoader = new THREE.ObjectLoader();
+				objectLoader.load("models/json/teapot-claraio.json", function ( obj ) {
+				 	scene.add( obj );
+				} );
+				// END Clara.io JSON loader code
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = ( event.clientX - windowHalfX ) / 2;
+				mouseY = ( event.clientY - windowHalfY ) / 2;
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+				render();
+
+			}
+
+			function render() {
+
+				camera.position.x += ( mouseX - camera.position.x ) * .05;
+				camera.position.y += ( - mouseY - camera.position.y ) * .05;
+
+				camera.lookAt( scene.position );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
src/lights/HemisphereLight.js

@@ -37,6 +37,7 @@ THREE.HemisphereLight.prototype.toJSON = function ( meta ) {
 
 	data.object.color = this.color.getHex();
 	data.object.groundColor = this.groundColor.getHex();
+	data.object.intensity = this.intensity;
 
 	return data;
 

Деякі файли не було показано, через те що забагато файлів було змінено