浏览代码

Merge branch 'geo-clone' of https://github.com/dubejf/three.js into dev

Conflicts:
	examples/js/wip/ProxyGeometry.js
Mr.doob 10 年之前
父节点
当前提交
6f15df93dc
共有 88 个文件被更改,包括 1557 次插入262 次删除
  1. 1 2
      examples/js/renderers/CanvasRenderer.js
  2. 2 1
      package.json
  3. 12 6
      src/cameras/Camera.js
  4. 1 1
      src/cameras/OrthographicCamera.js
  5. 1 1
      src/cameras/PerspectiveCamera.js
  6. 12 33
      src/core/BufferGeometry.js
  7. 19 10
      src/core/Geometry.js
  8. 28 22
      src/core/Object3D.js
  9. 15 0
      src/extras/geometries/BoxGeometry.js
  10. 15 0
      src/extras/geometries/CircleBufferGeometry.js
  11. 13 0
      src/extras/geometries/CircleGeometry.js
  12. 17 0
      src/extras/geometries/CylinderGeometry.js
  13. 21 6
      src/extras/geometries/DodecahedronGeometry.js
  14. 14 1
      src/extras/geometries/IcosahedronGeometry.js
  15. 14 6
      src/extras/geometries/OctahedronGeometry.js
  16. 15 0
      src/extras/geometries/PlaneBufferGeometry.js
  17. 13 0
      src/extras/geometries/PlaneGeometry.js
  18. 20 0
      src/extras/geometries/PolyhedronGeometry.js
  19. 14 0
      src/extras/geometries/RingGeometry.js
  20. 18 0
      src/extras/geometries/SphereBufferGeometry.js
  21. 16 0
      src/extras/geometries/SphereGeometry.js
  22. 14 1
      src/extras/geometries/TetrahedronGeometry.js
  23. 14 0
      src/extras/geometries/TorusGeometry.js
  24. 17 1
      src/extras/geometries/TorusKnotGeometry.js
  25. 1 1
      src/extras/geometries/WireframeGeometry.js
  26. 1 1
      src/lights/AmbientLight.js
  27. 2 2
      src/lights/AreaLight.js
  28. 1 1
      src/lights/DirectionalLight.js
  29. 1 1
      src/lights/HemisphereLight.js
  30. 10 6
      src/lights/Light.js
  31. 1 1
      src/lights/PointLight.js
  32. 1 1
      src/lights/SpotLight.js
  33. 71 2
      src/loaders/ObjectLoader.js
  34. 1 1
      src/materials/LineBasicMaterial.js
  35. 1 1
      src/materials/LineDashedMaterial.js
  36. 28 23
      src/materials/Material.js
  37. 1 1
      src/materials/MeshBasicMaterial.js
  38. 1 1
      src/materials/MeshDepthMaterial.js
  39. 1 1
      src/materials/MeshLambertMaterial.js
  40. 1 1
      src/materials/MeshNormalMaterial.js
  41. 1 1
      src/materials/MeshPhongMaterial.js
  42. 1 1
      src/materials/PointCloudMaterial.js
  43. 1 1
      src/materials/RawShaderMaterial.js
  44. 23 18
      src/materials/ShaderMaterial.js
  45. 1 1
      src/materials/SpriteMaterial.js
  46. 11 6
      src/objects/Bone.js
  47. 9 5
      src/objects/Group.js
  48. 12 6
      src/objects/LOD.js
  49. 13 8
      src/objects/LensFlare.js
  50. 8 4
      src/objects/Line.js
  51. 4 4
      src/objects/LineSegments.js
  52. 8 4
      src/objects/Mesh.js
  53. 16 11
      src/objects/MorphAnimMesh.js
  54. 8 4
      src/objects/PointCloud.js
  55. 7 7
      src/objects/SkinnedMesh.js
  56. 8 4
      src/objects/Sprite.js
  57. 13 8
      src/scenes/Scene.js
  58. 1 1
      src/textures/CompressedTexture.js
  59. 3 3
      src/textures/CubeTexture.js
  60. 1 1
      src/textures/DataTexture.js
  61. 24 19
      src/textures/Texture.js
  62. 177 0
      test/unit/SmartComparer.js
  63. 4 4
      test/unit/extras/ImageUtils.test.js
  64. 38 0
      test/unit/extras/geometries/BoxGeometry.tests.js
  65. 38 0
      test/unit/extras/geometries/CircleBufferGeometry.tests.js
  66. 38 0
      test/unit/extras/geometries/CircleGeometry.tests.js
  67. 46 0
      test/unit/extras/geometries/CylinderGeometry.tests.js
  68. 34 0
      test/unit/extras/geometries/DodecahedronGeometry.tests.js
  69. 1 0
      test/unit/extras/geometries/ExtrudeGeometry.tests.js
  70. 34 0
      test/unit/extras/geometries/IcosahedronGeometry.tests.js
  71. 1 0
      test/unit/extras/geometries/LatheGeometry.tests.js
  72. 34 0
      test/unit/extras/geometries/OctahedronGeometry.tests.js
  73. 1 0
      test/unit/extras/geometries/ParametricGeometry.tests.js
  74. 38 0
      test/unit/extras/geometries/PlaneBufferGeometry.tests.js
  75. 38 0
      test/unit/extras/geometries/PlaneGeometry.tests.js
  76. 1 0
      test/unit/extras/geometries/PolyhedronGeometry.tests.js
  77. 42 0
      test/unit/extras/geometries/RingGeometry.tests.js
  78. 1 0
      test/unit/extras/geometries/ShapeGeometry.tests.js
  79. 44 0
      test/unit/extras/geometries/SphereBufferGeometry.tests.js
  80. 44 0
      test/unit/extras/geometries/SphereGeometry.tests.js
  81. 34 0
      test/unit/extras/geometries/TetrahedronGeometry.tests.js
  82. 1 0
      test/unit/extras/geometries/TextGeometry.tests.js
  83. 40 0
      test/unit/extras/geometries/TorusGeometry.tests.js
  84. 42 0
      test/unit/extras/geometries/TorusKnotGeometry.tests.js
  85. 1 0
      test/unit/extras/geometries/TubeGeometry.tests.js
  86. 1 0
      test/unit/extras/geometries/WireframeGeometry.tests.js
  87. 130 2
      test/unit/qunit-utils.js
  88. 32 3
      test/unit/unittests_three.html

+ 1 - 2
examples/js/renderers/CanvasRenderer.js

@@ -22,8 +22,7 @@ THREE.SpriteCanvasMaterial.prototype.clone = function () {
 
 	var material = new THREE.SpriteCanvasMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
-
+	material.copy( this );
 	material.color.copy( this.color );
 	material.program = this.program;
 

+ 2 - 1
package.json

@@ -28,6 +28,7 @@
   },
   "homepage": "http://threejs.org/",
   "devDependencies": {
-    "jscs": "^1.13.1"
+    "jscs": "^1.13.1",
+    "lodash": "^3.10.0"
   }
 }

+ 12 - 6
src/cameras/Camera.js

@@ -50,14 +50,20 @@ THREE.Camera.prototype.lookAt = function () {
 
 }();
 
-THREE.Camera.prototype.clone = function ( camera ) {
+THREE.Camera.prototype.clone = function () {
 
-	if ( camera === undefined ) camera = new THREE.Camera();
+	var camera = new THREE.Camera();
+	return camera.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, camera );
+};
+
+THREE.Camera.prototype.copy = function ( source ) {
+
+	THREE.Object3D.prototype.copy.call( this, source );
+
+	this.matrixWorldInverse.copy( source.matrixWorldInverse );
+	this.projectionMatrix.copy( source.projectionMatrix );
 
-	camera.matrixWorldInverse.copy( this.matrixWorldInverse );
-	camera.projectionMatrix.copy( this.projectionMatrix );
+	return this;
 
-	return camera;
 };

+ 1 - 1
src/cameras/OrthographicCamera.js

@@ -40,7 +40,7 @@ THREE.OrthographicCamera.prototype.clone = function () {
 
 	var camera = new THREE.OrthographicCamera();
 
-	THREE.Camera.prototype.clone.call( this, camera );
+	camera.copy( this );
 
 	camera.zoom = this.zoom;
 

+ 1 - 1
src/cameras/PerspectiveCamera.js

@@ -126,7 +126,7 @@ THREE.PerspectiveCamera.prototype.clone = function () {
 
 	var camera = new THREE.PerspectiveCamera();
 
-	THREE.Camera.prototype.clone.call( this, camera );
+	camera.copy( this );
 
 	camera.zoom = this.zoom;
 

+ 12 - 33
src/core/BufferGeometry.js

@@ -110,32 +110,6 @@ THREE.BufferGeometry.prototype = {
 
 	},
 
-	copy: function ( geometry ) {
-
-		// TODO Clear attributes? Clear drawcalls? Copy morphTargets?
-
-		var attributes = geometry.attributes;
-		var offsets = geometry.drawcalls;
-
-		for ( var name in attributes ) {
-
-			var attribute = attributes[ name ];
-
-			this.addAttribute( name, attribute.clone() );
-
-		}
-
-		for ( var i = 0, il = offsets.length; i < il; i ++ ) {
-
-			var offset = offsets[ i ];
-			this.addDrawCall( offset.start, offset.count, offset.index );
-
-		}
-
-		return this;
-
-	},
-
 	center: function () {
 
 		this.computeBoundingBox();
@@ -1108,23 +1082,28 @@ THREE.BufferGeometry.prototype = {
 	clone: function () {
 
 		var geometry = new THREE.BufferGeometry();
+		return geometry.copy( this );
 
-		for ( var attr in this.attributes ) {
+	},
+
+	copy: function ( source ) {
+
+		for ( var attr in source.attributes ) {
 
-			var sourceAttr = this.attributes[ attr ];
-			geometry.addAttribute( attr, sourceAttr.clone() );
+			var sourceAttr = source.attributes[ attr ];
+			this.addAttribute( attr, sourceAttr.clone() );
 
 		}
 
-		for ( var i = 0, il = this.drawcalls.length; i < il; i ++ ) {
+		for ( var i = 0, il = source.drawcalls.length; i < il; i ++ ) {
 
-			var offset = this.drawcalls[ i ];
+			var offset = source.drawcalls[ i ];
 
-			geometry.addDrawCall( offset.start, offset.count, offset.index );
+			this.addDrawCall( offset.start, offset.count, offset.index );
 
 		}
 
-		return geometry;
+		return this;
 
 	},
 

+ 19 - 10
src/core/Geometry.js

@@ -1049,30 +1049,39 @@ THREE.Geometry.prototype = {
 	clone: function () {
 
 		var geometry = new THREE.Geometry();
+		return geometry.copy( this );
 
-		var vertices = this.vertices;
+	},
+
+	copy: function ( source ) {
+
+		this.vertices = [];
+		this.faces = [];
+		this.faceVertexUvs = [ [] ];
+
+		var vertices = source.vertices;
 
 		for ( var i = 0, il = vertices.length; i < il; i ++ ) {
 
-			geometry.vertices.push( vertices[ i ].clone() );
+			this.vertices.push( vertices[ i ].clone() );
 
 		}
 
-		var faces = this.faces;
+		var faces = source.faces;
 
 		for ( var i = 0, il = faces.length; i < il; i ++ ) {
 
-			geometry.faces.push( faces[ i ].clone() );
+			this.faces.push( faces[ i ].clone() );
 
 		}
 
-		for ( var i = 0, il = this.faceVertexUvs.length; i < il; i ++ ) {
+		for ( var i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) {
 
-			var faceVertexUvs = this.faceVertexUvs[ i ];
+			var faceVertexUvs = source.faceVertexUvs[ i ];
 
-			if ( geometry.faceVertexUvs[ i ] === undefined ) {
+			if ( this.faceVertexUvs[ i ] === undefined ) {
 
-				geometry.faceVertexUvs[ i ] = [];
+				this.faceVertexUvs[ i ] = [];
 
 			}
 
@@ -1088,13 +1097,13 @@ THREE.Geometry.prototype = {
 
 				}
 
-				geometry.faceVertexUvs[ i ].push( uvsCopy );
+				this.faceVertexUvs[ i ].push( uvsCopy );
 
 			}
 
 		}
 
-		return geometry;
+		return this;
 
 	},
 

+ 28 - 22
src/core/Object3D.js

@@ -659,49 +659,55 @@ THREE.Object3D.prototype = {
 
 	},
 
-	clone: function ( object, recursive ) {
+	clone: function ( recursive ) {
+
+		var object = new THREE.Object3D();
+		return object.copy( this, recursive );
+
+	},
+
+	copy: function ( source, recursive ) {
 
-		if ( object === undefined ) object = new THREE.Object3D();
 		if ( recursive === undefined ) recursive = true;
 
-		object.name = this.name;
+		this.name = source.name;
 
-		object.up.copy( this.up );
+		this.up.copy( source.up );
 
-		object.position.copy( this.position );
-		object.quaternion.copy( this.quaternion );
-		object.scale.copy( this.scale );
+		this.position.copy( source.position );
+		this.quaternion.copy( source.quaternion );
+		this.scale.copy( source.scale );
 
-		object.rotationAutoUpdate = this.rotationAutoUpdate;
+		this.rotationAutoUpdate = source.rotationAutoUpdate;
 
-		object.matrix.copy( this.matrix );
-		object.matrixWorld.copy( this.matrixWorld );
+		this.matrix.copy( source.matrix );
+		this.matrixWorld.copy( source.matrixWorld );
 
-		object.matrixAutoUpdate = this.matrixAutoUpdate;
-		object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate;
+		this.matrixAutoUpdate = source.matrixAutoUpdate;
+		this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;
 
-		object.visible = this.visible;
+		this.visible = source.visible;
 
-		object.castShadow = this.castShadow;
-		object.receiveShadow = this.receiveShadow;
+		this.castShadow = source.castShadow;
+		this.receiveShadow = source.receiveShadow;
 
-		object.frustumCulled = this.frustumCulled;
-		object.renderOrder = this.renderOrder;
+		this.frustumCulled = source.frustumCulled;
+		this.renderOrder = source.renderOrder;
 
-		object.userData = JSON.parse( JSON.stringify( this.userData ) );
+		this.userData = JSON.parse( JSON.stringify( source.userData ) );
 
 		if ( recursive === true ) {
 
-			for ( var i = 0; i < this.children.length; i ++ ) {
+			for ( var i = 0; i < source.children.length; i ++ ) {
 
-				var child = this.children[ i ];
-				object.add( child.clone() );
+				var child = source.children[ i ];
+				this.add( child.clone() );
 
 			}
 
 		}
 
-		return object;
+		return this;
 
 	}
 

+ 15 - 0
src/extras/geometries/BoxGeometry.js

@@ -124,4 +124,19 @@ THREE.BoxGeometry = function ( width, height, depth, widthSegments, heightSegmen
 THREE.BoxGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.BoxGeometry.prototype.constructor = THREE.BoxGeometry;
 
+THREE.BoxGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.BoxGeometry(
+		this.parameters.width,
+		this.parameters.height,
+		this.parameters.depth,
+		this.parameters.widthSegments,
+		this.parameters.heightSegments,
+		this.parameters.depthSegments
+	);
+
+	return geometry;
+
+};
+
 THREE.CubeGeometry = THREE.BoxGeometry; // backwards compatibility

+ 15 - 0
src/extras/geometries/CircleBufferGeometry.js

@@ -67,3 +67,18 @@ THREE.CircleBufferGeometry = function ( radius, segments, thetaStart, thetaLengt
 
 THREE.CircleBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
 THREE.CircleBufferGeometry.prototype.constructor = THREE.CircleBufferGeometry;
+
+THREE.CircleBufferGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.CircleBufferGeometry(
+		this.parameters.radius,
+		this.parameters.segments,
+		this.parameters.thetaStart,
+		this.parameters.thetaLength
+	);
+
+	geometry.copy( this );
+
+	return geometry;
+
+};

+ 13 - 0
src/extras/geometries/CircleGeometry.js

@@ -57,3 +57,16 @@ THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) {
 
 THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.CircleGeometry.prototype.constructor = THREE.CircleGeometry;
+
+THREE.CircleGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.CircleGeometry(
+		this.parameters.radius,
+		this.parameters.segments,
+		this.parameters.thetaStart,
+		this.parameters.thetaLength
+	);
+
+	return geometry;
+
+};

+ 17 - 0
src/extras/geometries/CylinderGeometry.js

@@ -170,3 +170,20 @@ THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radialSegme
 
 THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.CylinderGeometry.prototype.constructor = THREE.CylinderGeometry;
+
+THREE.CylinderGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.CylinderGeometry(
+		this.parameters.radiusTop,
+		this.parameters.radiusBottom,
+		this.parameters.height,
+		this.parameters.radialSegments,
+		this.parameters.heightSegments,
+		this.parameters.openEnded,
+		this.parameters.thetaStart,
+		this.parameters.thetaLength
+	);
+
+	return geometry;
+
+};

+ 21 - 6
src/extras/geometries/DodecahedronGeometry.js

@@ -4,11 +4,6 @@
 
 THREE.DodecahedronGeometry = function ( radius, detail ) {
 
-	this.parameters = {
-		radius: radius,
-		detail: detail
-	};
-
 	var t = ( 1 + Math.sqrt( 5 ) ) / 2;
 	var r = 1 / t;
 
@@ -50,7 +45,27 @@ THREE.DodecahedronGeometry = function ( radius, detail ) {
 
 	THREE.PolyhedronGeometry.call( this, vertices, indices, radius, detail );
 
+	this.type = 'DodecahedronGeometry';
+
+	this.parameters = {
+		radius: radius,
+		detail: detail
+	};
+
 };
 
-THREE.DodecahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+THREE.DodecahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype );
 THREE.DodecahedronGeometry.prototype.constructor = THREE.DodecahedronGeometry;
+
+THREE.DodecahedronGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.DodecahedronGeometry(
+		this.parameters.radius,
+		this.parameters.detail
+	);
+
+	geometry.copy( this );
+
+	return geometry;
+
+};

+ 14 - 1
src/extras/geometries/IcosahedronGeometry.js

@@ -29,5 +29,18 @@ THREE.IcosahedronGeometry = function ( radius, detail ) {
 	};
 };
 
-THREE.IcosahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+THREE.IcosahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype );
 THREE.IcosahedronGeometry.prototype.constructor = THREE.IcosahedronGeometry;
+
+THREE.IcosahedronGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.IcosahedronGeometry(
+		this.parameters.radius,
+		this.parameters.detail
+	);
+
+	geometry.copy( this );
+
+	return geometry;
+
+};

+ 14 - 6
src/extras/geometries/OctahedronGeometry.js

@@ -4,11 +4,6 @@
 
 THREE.OctahedronGeometry = function ( radius, detail ) {
 
-	this.parameters = {
-		radius: radius,
-		detail: detail
-	};
-
 	var vertices = [
 		1, 0, 0,   - 1, 0, 0,    0, 1, 0,    0,- 1, 0,    0, 0, 1,    0, 0,- 1
 	];
@@ -27,5 +22,18 @@ THREE.OctahedronGeometry = function ( radius, detail ) {
 	};
 };
 
-THREE.OctahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+THREE.OctahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype );
 THREE.OctahedronGeometry.prototype.constructor = THREE.OctahedronGeometry;
+
+THREE.OctahedronGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.OctahedronGeometry(
+		this.parameters.radius,
+		this.parameters.detail
+	);
+
+	geometry.copy( this );
+
+	return geometry;
+
+};

+ 15 - 0
src/extras/geometries/PlaneBufferGeometry.js

@@ -94,3 +94,18 @@ THREE.PlaneBufferGeometry = function ( width, height, widthSegments, heightSegme
 
 THREE.PlaneBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
 THREE.PlaneBufferGeometry.prototype.constructor = THREE.PlaneBufferGeometry;
+
+THREE.PlaneBufferGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.PlaneBufferGeometry(
+		this.parameters.width,
+		this.parameters.height,
+		this.parameters.widthSegments,
+		this.parameters.heightSegments
+	);
+
+	geometry.copy( this );
+
+	return geometry;
+
+};

+ 13 - 0
src/extras/geometries/PlaneGeometry.js

@@ -22,3 +22,16 @@ THREE.PlaneGeometry = function ( width, height, widthSegments, heightSegments )
 
 THREE.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.PlaneGeometry.prototype.constructor = THREE.PlaneGeometry;
+
+THREE.PlaneGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.PlaneGeometry(
+		this.parameters.width,
+		this.parameters.height,
+		this.parameters.widthSegments,
+		this.parameters.heightSegments
+	);
+
+	return geometry;
+
+};

+ 20 - 0
src/extras/geometries/PolyhedronGeometry.js

@@ -233,3 +233,23 @@ THREE.PolyhedronGeometry = function ( vertices, indices, radius, detail ) {
 
 THREE.PolyhedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.PolyhedronGeometry.prototype.constructor = THREE.PolyhedronGeometry;
+
+THREE.PolyhedronGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.PolyhedronGeometry(
+		this.parameters.vertices,
+		this.parameters.indices,
+		this.parameters.radius,
+		this.parameters.detail
+	);
+
+	return geometry.copy( this );
+
+};
+
+THREE.PolyhedronGeometry.prototype.copy = function ( source ) {
+
+	THREE.Geometry.prototype.copy.call( this, source );
+	return this;
+
+};

+ 14 - 0
src/extras/geometries/RingGeometry.js

@@ -81,3 +81,17 @@ THREE.RingGeometry = function ( innerRadius, outerRadius, thetaSegments, phiSegm
 THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.RingGeometry.prototype.constructor = THREE.RingGeometry;
 
+THREE.RingGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.RingGeometry(
+		this.parameters.innerRadius,
+		this.parameters.outerRadius,
+		this.parameters.thetaSegments,
+		this.parameters.phiSegments,
+		this.parameters.thetaStart,
+		this.parameters.thetaLength
+	);
+
+	return geometry;
+
+};

+ 18 - 0
src/extras/geometries/SphereBufferGeometry.js

@@ -99,3 +99,21 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 
 THREE.SphereBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
 THREE.SphereBufferGeometry.prototype.constructor = THREE.SphereBufferGeometry;
+
+THREE.SphereBufferGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.SphereBufferGeometry(
+		this.parameters.radius,
+		this.parameters.widthSegments,
+		this.parameters.heightSegments,
+		this.parameters.phiStart,
+		this.parameters.phiLength,
+		this.parameters.thetaStart,
+		this.parameters.thetaLength
+	);
+
+	geometry.copy( this );
+
+	return geometry;
+
+};

+ 16 - 0
src/extras/geometries/SphereGeometry.js

@@ -113,3 +113,19 @@ THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStar
 
 THREE.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.SphereGeometry.prototype.constructor = THREE.SphereGeometry;
+
+THREE.SphereGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.SphereGeometry(
+		this.parameters.radius,
+		this.parameters.widthSegments,
+		this.parameters.heightSegments,
+		this.parameters.phiStart,
+		this.parameters.phiLength,
+		this.parameters.thetaStart,
+		this.parameters.thetaLength
+	);
+
+	return geometry;
+
+};

+ 14 - 1
src/extras/geometries/TetrahedronGeometry.js

@@ -23,5 +23,18 @@ THREE.TetrahedronGeometry = function ( radius, detail ) {
 
 };
 
-THREE.TetrahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+THREE.TetrahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype );
 THREE.TetrahedronGeometry.prototype.constructor = THREE.TetrahedronGeometry;
+
+THREE.TetrahedronGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.TetrahedronGeometry(
+		this.parameters.radius,
+		this.parameters.detail
+	);
+
+	geometry.copy( this );
+
+	return geometry;
+
+};

+ 14 - 0
src/extras/geometries/TorusGeometry.js

@@ -77,3 +77,17 @@ THREE.TorusGeometry = function ( radius, tube, radialSegments, tubularSegments,
 
 THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.TorusGeometry.prototype.constructor = THREE.TorusGeometry;
+
+THREE.TorusGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.TorusGeometry(
+		this.parameters.radius,
+		this.parameters.tube,
+		this.parameters.radialSegments,
+		this.parameters.tubularSegments,
+		this.parameters.arc
+	);
+
+	return geometry;
+
+};

+ 17 - 1
src/extras/geometries/TorusKnotGeometry.js

@@ -26,7 +26,7 @@ THREE.TorusKnotGeometry = function ( radius, tube, radialSegments, tubularSegmen
 	p = p || 2;
 	q = q || 3;
 	heightScale = heightScale || 1;
-	
+
 	var grid = new Array( radialSegments );
 	var tang = new THREE.Vector3();
 	var n = new THREE.Vector3();
@@ -111,3 +111,19 @@ THREE.TorusKnotGeometry = function ( radius, tube, radialSegments, tubularSegmen
 
 THREE.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype );
 THREE.TorusKnotGeometry.prototype.constructor = THREE.TorusKnotGeometry;
+
+THREE.TorusKnotGeometry.prototype.clone = function () {
+
+	var geometry = new THREE.TorusKnotGeometry(
+		this.parameters.radius,
+		this.parameters.tube,
+		this.parameters.radialSegments,
+		this.parameters.tubularSegments,
+		this.parameters.p,
+		this.parameters.q,
+		this.parameters.heightScale
+	);
+
+	return geometry;
+
+};

+ 1 - 1
src/extras/geometries/WireframeGeometry.js

@@ -7,7 +7,7 @@ THREE.WireframeGeometry = function ( geometry ) {
 	THREE.BufferGeometry.call( this );
 
 	var edge = [ 0, 0 ], hash = {};
-	var sortFunction = function ( a, b ) { return a - b };
+	var sortFunction = function ( a, b ) { return a - b; };
 
 	var keys = [ 'a', 'b', 'c' ];
 

+ 1 - 1
src/lights/AmbientLight.js

@@ -17,7 +17,7 @@ THREE.AmbientLight.prototype.clone = function () {
 
 	var light = new THREE.AmbientLight();
 
-	THREE.Light.prototype.clone.call( this, light );
+	light.copy( this );
 
 	return light;
 

+ 2 - 2
src/lights/AreaLight.js

@@ -31,7 +31,7 @@ THREE.AreaLight.prototype.clone = function () {
 
 	var light = new THREE.AreaLight();
 
-	THREE.Light.prototype.clone.call( this, light );
+	light.copy( this );
 
 	light.normal.copy(this.normal);
 	light.right.copy(this.right);
@@ -40,7 +40,7 @@ THREE.AreaLight.prototype.clone = function () {
 	light.height = this.height;
 	light.constantAttenuation = this.constantAttenuation;
 	light.linearAttenuation = this.linearAttenuation;
-	light.quadraticAttenuation = this.quadraticAttenuation
+	light.quadraticAttenuation = this.quadraticAttenuation;
 
 	return light;
 

+ 1 - 1
src/lights/DirectionalLight.js

@@ -67,7 +67,7 @@ THREE.DirectionalLight.prototype.clone = function () {
 
 	var light = new THREE.DirectionalLight();
 
-	THREE.Light.prototype.clone.call( this, light );
+	light.copy( this );
 
 	light.target = this.target.clone();
 

+ 1 - 1
src/lights/HemisphereLight.js

@@ -22,7 +22,7 @@ THREE.HemisphereLight.prototype.clone = function () {
 
 	var light = new THREE.HemisphereLight();
 
-	THREE.Light.prototype.clone.call( this, light );
+	light.copy( this );
 
 	light.groundColor.copy( this.groundColor );
 	light.intensity = this.intensity;

+ 10 - 6
src/lights/Light.js

@@ -8,7 +8,7 @@ THREE.Light = function ( color ) {
 	THREE.Object3D.call( this );
 
 	this.type = 'Light';
-	
+
 	this.color = new THREE.Color( color );
 
 };
@@ -16,14 +16,18 @@ THREE.Light = function ( color ) {
 THREE.Light.prototype = Object.create( THREE.Object3D.prototype );
 THREE.Light.prototype.constructor = THREE.Light;
 
-THREE.Light.prototype.clone = function ( light ) {
+THREE.Light.prototype.clone = function () {
+
+	var light = new THREE.Light();
+	return light.copy( this );
 
-	if ( light === undefined ) light = new THREE.Light();
+};
 
-	THREE.Object3D.prototype.clone.call( this, light );
+THREE.Light.prototype.copy = function ( source ) {
 
-	light.color.copy( this.color );
+	THREE.Object3D.prototype.copy.call( this, source );
+	this.color.copy( source.color );
 
-	return light;
+	return this;
 
 };

+ 1 - 1
src/lights/PointLight.js

@@ -21,7 +21,7 @@ THREE.PointLight.prototype.clone = function () {
 
 	var light = new THREE.PointLight();
 
-	THREE.Light.prototype.clone.call( this, light );
+	light.copy( this );
 
 	light.intensity = this.intensity;
 	light.distance = this.distance;

+ 1 - 1
src/lights/SpotLight.js

@@ -50,7 +50,7 @@ THREE.SpotLight.prototype.clone = function () {
 
 	var light = new THREE.SpotLight();
 
-	THREE.Light.prototype.clone.call( this, light );
+	light.copy( this );
 
 	light.target = this.target.clone();
 

+ 71 - 2
src/loaders/ObjectLoader.js

@@ -111,11 +111,24 @@ THREE.ObjectLoader.prototype = {
 
 						break;
 
+					case 'CircleBufferGeometry':
+
+						geometry = new THREE.CircleBufferGeometry(
+							data.radius,
+							data.segments,
+							data.thetaStart,
+							data.thetaLength
+						);
+
+						break;
+
 					case 'CircleGeometry':
 
 						geometry = new THREE.CircleGeometry(
 							data.radius,
-							data.segments
+							data.segments,
+							data.thetaStart,
+							data.thetaLength
 						);
 
 						break;
@@ -128,7 +141,9 @@ THREE.ObjectLoader.prototype = {
 							data.height,
 							data.radialSegments,
 							data.heightSegments,
-							data.openEnded
+							data.openEnded,
+							data.thetaStart,
+							data.thetaLength
 						);
 
 						break;
@@ -147,6 +162,29 @@ THREE.ObjectLoader.prototype = {
 
 						break;
 
+					case 'SphereBufferGeometry':
+
+						geometry = new THREE.SphereBufferGeometry(
+							data.radius,
+							data.widthSegments,
+							data.heightSegments,
+							data.phiStart,
+							data.phiLength,
+							data.thetaStart,
+							data.thetaLength
+						);
+
+						break;
+
+					case 'DodecahedronGeometry':
+
+						geometry = new THREE.DodecahedronGeometry(
+							data.radius,
+							data.detail
+						);
+
+						break;
+
 					case 'IcosahedronGeometry':
 
 						geometry = new THREE.IcosahedronGeometry(
@@ -156,6 +194,37 @@ THREE.ObjectLoader.prototype = {
 
 						break;
 
+					case 'OctahedronGeometry':
+
+						geometry = new THREE.OctahedronGeometry(
+							data.radius,
+							data.detail
+						);
+
+						break;
+
+					case 'TetrahedronGeometry':
+
+						geometry = new THREE.TetrahedronGeometry(
+							data.radius,
+							data.detail
+						);
+
+						break;
+
+					case 'RingGeometry':
+
+						geometry = new THREE.RingGeometry(
+							data.innerRadius,
+							data.outerRadius,
+							data.thetaSegments,
+							data.phiSegments,
+							data.thetaStart,
+							data.thetaLength
+						);
+
+						break;
+
 					case 'TorusGeometry':
 
 						geometry = new THREE.TorusGeometry(

+ 1 - 1
src/materials/LineBasicMaterial.js

@@ -47,7 +47,7 @@ THREE.LineBasicMaterial.prototype.clone = function () {
 
 	var material = new THREE.LineBasicMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.color.copy( this.color );
 

+ 1 - 1
src/materials/LineDashedMaterial.js

@@ -50,7 +50,7 @@ THREE.LineDashedMaterial.prototype.clone = function () {
 
 	var material = new THREE.LineDashedMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.color.copy( this.color );
 

+ 28 - 23
src/materials/Material.js

@@ -161,41 +161,46 @@ THREE.Material.prototype = {
 
 	},
 
-	clone: function ( material ) {
+	clone: function () {
 
-		if ( material === undefined ) material = new THREE.Material();
+		var material = new THREE.Material();
+		return material.copy( this );
 
-		material.name = this.name;
+	},
+
+	copy: function ( source ) {
+
+		this.name = source.name;
 
-		material.side = this.side;
+		this.side = source.side;
 
-		material.opacity = this.opacity;
-		material.transparent = this.transparent;
+		this.opacity = source.opacity;
+		this.transparent = source.transparent;
 
-		material.blending = this.blending;
+		this.blending = source.blending;
 
-		material.blendSrc = this.blendSrc;
-		material.blendDst = this.blendDst;
-		material.blendEquation = this.blendEquation;
-		material.blendSrcAlpha = this.blendSrcAlpha;
-		material.blendDstAlpha = this.blendDstAlpha;
-		material.blendEquationAlpha = this.blendEquationAlpha;
+		this.blendSrc = source.blendSrc;
+		this.blendDst = source.blendDst;
+		this.blendEquation = source.blendEquation;
+		this.blendSrcAlpha = source.blendSrcAlpha;
+		this.blendDstAlpha = source.blendDstAlpha;
+		this.blendEquationAlpha = source.blendEquationAlpha;
 
-		material.depthFunc = this.depthFunc;
-		material.depthTest = this.depthTest;
-		material.depthWrite = this.depthWrite;
+		this.depthFunc = source.depthFunc;
+		this.depthTest = source.depthTest;
+		this.depthWrite = source.depthWrite;
 
-		material.polygonOffset = this.polygonOffset;
-		material.polygonOffsetFactor = this.polygonOffsetFactor;
-		material.polygonOffsetUnits = this.polygonOffsetUnits;
+		this.polygonOffset = source.polygonOffset;
+		this.polygonOffsetFactor = source.polygonOffsetFactor;
+		this.polygonOffsetUnits = source.polygonOffsetUnits;
 
-		material.alphaTest = this.alphaTest;
+		this.alphaTest = source.alphaTest;
 
-		material.overdraw = this.overdraw;
+		this.overdraw = source.overdraw;
 
-		material.visible = this.visible;
+		this.visible = source.visible;
 
-		return material;
+		return this;
 
 	},
 

+ 1 - 1
src/materials/MeshBasicMaterial.js

@@ -83,7 +83,7 @@ THREE.MeshBasicMaterial.prototype.clone = function () {
 
 	var material = new THREE.MeshBasicMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.color.copy( this.color );
 

+ 1 - 1
src/materials/MeshDepthMaterial.js

@@ -35,7 +35,7 @@ THREE.MeshDepthMaterial.prototype.clone = function () {
 
 	var material = new THREE.MeshDepthMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.wireframe = this.wireframe;
 	material.wireframeLinewidth = this.wireframeLinewidth;

+ 1 - 1
src/materials/MeshLambertMaterial.js

@@ -82,7 +82,7 @@ THREE.MeshLambertMaterial.prototype.clone = function () {
 
 	var material = new THREE.MeshLambertMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.color.copy( this.color );
 	material.emissive.copy( this.emissive );

+ 1 - 1
src/materials/MeshNormalMaterial.js

@@ -36,7 +36,7 @@ THREE.MeshNormalMaterial.prototype.clone = function () {
 
 	var material = new THREE.MeshNormalMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.wireframe = this.wireframe;
 	material.wireframeLinewidth = this.wireframeLinewidth;

+ 1 - 1
src/materials/MeshPhongMaterial.js

@@ -116,7 +116,7 @@ THREE.MeshPhongMaterial.prototype.clone = function () {
 
 	var material = new THREE.MeshPhongMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.color.copy( this.color );
 	material.emissive.copy( this.emissive );

+ 1 - 1
src/materials/PointCloudMaterial.js

@@ -48,7 +48,7 @@ THREE.PointCloudMaterial.prototype.clone = function () {
 
 	var material = new THREE.PointCloudMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.color.copy( this.color );
 

+ 1 - 1
src/materials/RawShaderMaterial.js

@@ -17,7 +17,7 @@ THREE.RawShaderMaterial.prototype.clone = function () {
 
 	var material = new THREE.RawShaderMaterial();
 
-	THREE.ShaderMaterial.prototype.clone.call( this, material );
+	material.copy( this );
 
 	return material;
 

+ 23 - 18
src/materials/ShaderMaterial.js

@@ -89,37 +89,42 @@ THREE.ShaderMaterial = function ( parameters ) {
 THREE.ShaderMaterial.prototype = Object.create( THREE.Material.prototype );
 THREE.ShaderMaterial.prototype.constructor = THREE.ShaderMaterial;
 
-THREE.ShaderMaterial.prototype.clone = function ( material ) {
+THREE.ShaderMaterial.prototype.clone = function () {
 
-	if ( material === undefined ) material = new THREE.ShaderMaterial();
+	var material = new THREE.ShaderMaterial();
+	return material.copy( this );
 
-	THREE.Material.prototype.clone.call( this, material );
+};
+
+THREE.ShaderMaterial.prototype.copy = function ( source ) {
+
+	THREE.Material.prototype.copy.call( this, source );
 
-	material.fragmentShader = this.fragmentShader;
-	material.vertexShader = this.vertexShader;
+	this.fragmentShader = source.fragmentShader;
+	this.vertexShader = source.vertexShader;
 
-	material.uniforms = THREE.UniformsUtils.clone( this.uniforms );
+	this.uniforms = THREE.UniformsUtils.clone( source.uniforms );
 
-	material.attributes = this.attributes;
-	material.defines = this.defines;
+	this.attributes = source.attributes;
+	this.defines = source.defines;
 
-	material.shading = this.shading;
+	this.shading = source.shading;
 
-	material.wireframe = this.wireframe;
-	material.wireframeLinewidth = this.wireframeLinewidth;
+	this.wireframe = source.wireframe;
+	this.wireframeLinewidth = source.wireframeLinewidth;
 
-	material.fog = this.fog;
+	this.fog = source.fog;
 
-	material.lights = this.lights;
+	this.lights = source.lights;
 
-	material.vertexColors = this.vertexColors;
+	this.vertexColors = source.vertexColors;
 
-	material.skinning = this.skinning;
+	this.skinning = source.skinning;
 
-	material.morphTargets = this.morphTargets;
-	material.morphNormals = this.morphNormals;
+	this.morphTargets = source.morphTargets;
+	this.morphNormals = source.morphNormals;
 
-	return material;
+	return this;
 
 };
 

+ 1 - 1
src/materials/SpriteMaterial.js

@@ -43,7 +43,7 @@ THREE.SpriteMaterial.prototype.clone = function () {
 
 	var material = new THREE.SpriteMaterial();
 
-	THREE.Material.prototype.clone.call( this, material );
+	material.copy( this );
 
 	material.color.copy( this.color );
 	material.map = this.map;

+ 11 - 6
src/objects/Bone.js

@@ -17,13 +17,18 @@ THREE.Bone = function ( skin ) {
 THREE.Bone.prototype = Object.create( THREE.Object3D.prototype );
 THREE.Bone.prototype.constructor = THREE.Bone;
 
-THREE.Bone.prototype.clone = function ( object ) {
+THREE.Bone.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.Bone( this.skin );
+	var bone = new THREE.Bone( this.skin );
+	return bone.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object );
-	object.skin = this.skin; 
+};
+
+THREE.Bone.prototype.copy = function ( source ) {
 
-	return object;
+	THREE.Object3D.prototype.copy.call( this, source );
+	this.skin = source.skin;
 
-};
+	return this;
+
+};

+ 9 - 5
src/objects/Group.js

@@ -13,12 +13,16 @@ THREE.Group = function () {
 THREE.Group.prototype = Object.create( THREE.Object3D.prototype );
 THREE.Group.prototype.constructor = THREE.Group;
 
-THREE.Group.prototype.clone = function ( object ) {
+THREE.Group.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.Group();
+	var group = new THREE.Group();
+	return group.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object );
+};
+
+THREE.Group.prototype.copy = function ( source ) {
 
-	return object;
+	THREE.Object3D.prototype.copy.call( this, source );
+	return this;
 
-};
+};

+ 12 - 6
src/objects/LOD.js

@@ -130,22 +130,28 @@ THREE.LOD.prototype.update = function () {
 
 }();
 
-THREE.LOD.prototype.clone = function ( object ) {
+THREE.LOD.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.LOD();
+	var lod = new THREE.LOD();
+	return lod.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object, false );
+};
 
-	var levels = this.levels;
+
+THREE.LOD.prototype.copy = function ( source ) {
+
+	THREE.Object3D.prototype.copy.call( this, source, false );
+
+	var levels = source.levels;
 
 	for ( var i = 0, l = levels.length; i < l; i ++ ) {
 
 		var level = levels[ i ];
 
-		object.addLevel( level.object.clone(), level.distance );
+		this.addLevel( level.object.clone(), level.distance );
 
 	}
 
-	return object;
+	return this;
 
 };

+ 13 - 8
src/objects/LensFlare.js

@@ -78,21 +78,26 @@ THREE.LensFlare.prototype.updateLensFlares = function () {
 
 };
 
-THREE.LensFlare.prototype.clone = function ( object ) {
+THREE.LensFlare.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.LensFlare();
+	var flare = new THREE.LensFlare();
+	return flare.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object );
+};
+
+THREE.LensFlare.prototype.copy = function ( source ) {
+
+	THREE.Object3D.prototype.copy.call( this, source );
 
-	object.positionScreen.copy( this.positionScreen );
-	object.customUpdateCallback = this.customUpdateCallback;
+	this.positionScreen.copy( source.positionScreen );
+	this.customUpdateCallback = source.customUpdateCallback;
 
-	for ( var i = 0, l = this.lensFlares.length; i < l; i ++ ) {
+	for ( var i = 0, l = source.lensFlares.length; i < l; i ++ ) {
 
-		object.lensFlares.push( this.lensFlares[ i ] );
+		this.lensFlares.push( source.lensFlares[ i ] );
 
 	}
 
-	return object;
+	return this;
 
 };

+ 8 - 4
src/objects/Line.js

@@ -183,13 +183,17 @@ THREE.Line.prototype.raycast = ( function () {
 
 }() );
 
-THREE.Line.prototype.clone = function ( object ) {
+THREE.Line.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE[ this.type ]( this.geometry, this.material );
+	var line = new THREE.Line( this.geometry, this.material );
+	return line.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object );
+};
+
+THREE.Line.prototype.copy = function ( source ) {
 
-	return object;
+	THREE.Object3D.prototype.copy.call( this, source );
+	return this;
 
 };
 

+ 4 - 4
src/objects/LineSegments.js

@@ -13,12 +13,12 @@ THREE.LineSegments = function ( geometry, material ) {
 THREE.LineSegments.prototype = Object.create( THREE.Line.prototype );
 THREE.LineSegments.prototype.constructor = THREE.LineSegments;
 
-THREE.LineSegments.prototype.clone = function ( object ) {
+THREE.LineSegments.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.LineSegments( this.geometry, this.material );
+	var line = new THREE.LineSegments( this.geometry, this.material );
 
-	THREE.Line.prototype.clone.call( this, object );
+	line.copy( this );
 
-	return object;
+	return line;
 
 };

+ 8 - 4
src/objects/Mesh.js

@@ -305,13 +305,17 @@ THREE.Mesh.prototype.raycast = ( function () {
 
 }() );
 
-THREE.Mesh.prototype.clone = function ( object, recursive ) {
+THREE.Mesh.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material );
+	var mesh = new THREE.Mesh( this.geometry, this.material );
+	return mesh.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object, recursive );
+};
+
+THREE.Mesh.prototype.copy = function ( source ) {
 
-	return object;
+	THREE.Object3D.prototype.copy.call( this, source );
+	return this;
 
 };
 

+ 16 - 11
src/objects/MorphAnimMesh.js

@@ -192,22 +192,27 @@ THREE.MorphAnimMesh.prototype.interpolateTargets = function ( a, b, t ) {
 
 };
 
-THREE.MorphAnimMesh.prototype.clone = function ( object ) {
+THREE.MorphAnimMesh.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.MorphAnimMesh( this.geometry, this.material );
+	var morph = new THREE.MorphAnimMesh( this.geometry, this.material );
+	return morph.copy( this );
 
-	object.duration = this.duration;
-	object.mirroredLoop = this.mirroredLoop;
-	object.time = this.time;
+};
+
+THREE.MorphAnimMesh.prototype.copy = function ( source ) {
+
+	this.duration = source.duration;
+	this.mirroredLoop = source.mirroredLoop;
+	this.time = source.time;
 
-	object.lastKeyframe = this.lastKeyframe;
-	object.currentKeyframe = this.currentKeyframe;
+	this.lastKeyframe = source.lastKeyframe;
+	this.currentKeyframe = source.currentKeyframe;
 
-	object.direction = this.direction;
-	object.directionBackwards = this.directionBackwards;
+	this.direction = source.direction;
+	this.directionBackwards = source.directionBackwards;
 
-	THREE.Mesh.prototype.clone.call( this, object );
+	THREE.Mesh.prototype.copy.call( this, source );
 
-	return object;
+	return this;
 
 };

+ 8 - 4
src/objects/PointCloud.js

@@ -136,13 +136,17 @@ THREE.PointCloud.prototype.raycast = ( function () {
 
 }() );
 
-THREE.PointCloud.prototype.clone = function ( object ) {
+THREE.PointCloud.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.PointCloud( this.geometry, this.material );
+	var pointCloud = new THREE.PointCloud( this.geometry, this.material );
+	return pointCloud.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object );
+};
+
+THREE.PointCloud.prototype.copy = function ( source ) {
 
-	return object;
+	THREE.Object3D.prototype.copy.call( this, source );
+	return this;
 
 };
 

+ 7 - 7
src/objects/SkinnedMesh.js

@@ -141,16 +141,16 @@ THREE.SkinnedMesh.prototype.updateMatrixWorld = function( force ) {
 
 };
 
-THREE.SkinnedMesh.prototype.clone = function( object ) {
+THREE.SkinnedMesh.prototype.clone = function() {
 
-	if ( object === undefined ) {
+	var skinMesh = new THREE.SkinnedMesh( this.geometry, this.material, this.useVertexTexture );
+	return skinMesh.copy( this );
 
-		object = new THREE.SkinnedMesh( this.geometry, this.material, this.useVertexTexture );
-
-	}
+};
 
-	THREE.Mesh.prototype.clone.call( this, object );
+THREE.SkinnedMesh.prototype.copy = function( source ) {
 
-	return object;
+	THREE.Mesh.prototype.copy.call( this, source );
+	return this;
 
 };

+ 8 - 4
src/objects/Sprite.js

@@ -60,13 +60,17 @@ THREE.Sprite.prototype.raycast = ( function () {
 
 }() );
 
-THREE.Sprite.prototype.clone = function ( object ) {
+THREE.Sprite.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.Sprite( this.material );
+	var sprite = new THREE.Sprite( this.material );
+	return sprite.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object );
+};
+
+THREE.Sprite.prototype.copy = function ( source ) {
 
-	return object;
+	THREE.Object3D.prototype.copy.call( this, source );
+	return this;
 
 };
 

+ 13 - 8
src/scenes/Scene.js

@@ -18,18 +18,23 @@ THREE.Scene = function () {
 THREE.Scene.prototype = Object.create( THREE.Object3D.prototype );
 THREE.Scene.prototype.constructor = THREE.Scene;
 
-THREE.Scene.prototype.clone = function ( object ) {
+THREE.Scene.prototype.clone = function () {
 
-	if ( object === undefined ) object = new THREE.Scene();
+	var scene = new THREE.Scene();
+	return scene.copy( this );
 
-	THREE.Object3D.prototype.clone.call( this, object );
+};
+
+THREE.Scene.prototype.copy = function ( source ) {
+
+	THREE.Object3D.prototype.copy.call( this, source );
 
-	if ( this.fog !== null ) object.fog = this.fog.clone();
-	if ( this.overrideMaterial !== null ) object.overrideMaterial = this.overrideMaterial.clone();
+	if ( source.fog !== null ) this.fog = source.fog.clone();
+	if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();
 
-	object.autoUpdate = this.autoUpdate;
-	object.matrixAutoUpdate = this.matrixAutoUpdate;
+	this.autoUpdate = source.autoUpdate;
+	this.matrixAutoUpdate = source.matrixAutoUpdate;
 
-	return object;
+	return this;
 
 };

+ 1 - 1
src/textures/CompressedTexture.js

@@ -28,7 +28,7 @@ THREE.CompressedTexture.prototype.clone = function () {
 
 	var texture = new THREE.CompressedTexture();
 
-	THREE.Texture.prototype.clone.call( this, texture );
+	texture.copy( this );
 
 	return texture;
 

+ 3 - 3
src/textures/CubeTexture.js

@@ -16,11 +16,11 @@ THREE.CubeTexture = function ( images, mapping, wrapS, wrapT, magFilter, minFilt
 THREE.CubeTexture.prototype = Object.create( THREE.Texture.prototype );
 THREE.CubeTexture.prototype.constructor = THREE.CubeTexture;
 
-THREE.CubeTexture.clone = function ( texture ) {
+THREE.CubeTexture.clone = function () {
 
-	if ( texture === undefined ) texture = new THREE.CubeTexture();
+	var texture = new THREE.CubeTexture();
 
-	THREE.Texture.prototype.clone.call( this, texture );
+	texture.copy( this );
 
 	texture.images = this.images;
 

+ 1 - 1
src/textures/DataTexture.js

@@ -17,7 +17,7 @@ THREE.DataTexture.prototype.clone = function () {
 
 	var texture = new THREE.DataTexture();
 
-	THREE.Texture.prototype.clone.call( this, texture );
+	texture.copy( this );
 
 	return texture;
 

+ 24 - 19
src/textures/Texture.js

@@ -55,35 +55,40 @@ THREE.Texture.prototype = {
 
 	},
 
-	clone: function ( texture ) {
+	clone: function () {
 
-		if ( texture === undefined ) texture = new THREE.Texture();
+		var texture = new THREE.Texture();
+		return texture.copy( this );
 
-		texture.image = this.image;
-		texture.mipmaps = this.mipmaps.slice( 0 );
+	},
+
+	copy: function ( source ) {
+
+		this.image = source.image;
+		this.mipmaps = source.mipmaps.slice( 0 );
 
-		texture.mapping = this.mapping;
+		this.mapping = source.mapping;
 
-		texture.wrapS = this.wrapS;
-		texture.wrapT = this.wrapT;
+		this.wrapS = source.wrapS;
+		this.wrapT = source.wrapT;
 
-		texture.magFilter = this.magFilter;
-		texture.minFilter = this.minFilter;
+		this.magFilter = source.magFilter;
+		this.minFilter = source.minFilter;
 
-		texture.anisotropy = this.anisotropy;
+		this.anisotropy = source.anisotropy;
 
-		texture.format = this.format;
-		texture.type = this.type;
+		this.format = source.format;
+		this.type = source.type;
 
-		texture.offset.copy( this.offset );
-		texture.repeat.copy( this.repeat );
+		this.offset.copy( source.offset );
+		this.repeat.copy( source.repeat );
 
-		texture.generateMipmaps = this.generateMipmaps;
-		texture.premultiplyAlpha = this.premultiplyAlpha;
-		texture.flipY = this.flipY;
-		texture.unpackAlignment = this.unpackAlignment;
+		this.generateMipmaps = source.generateMipmaps;
+		this.premultiplyAlpha = source.premultiplyAlpha;
+		this.flipY = source.flipY;
+		this.unpackAlignment = source.unpackAlignment;
 
-		return texture;
+		return this;
 
 	},
 

+ 177 - 0
test/unit/SmartComparer.js

@@ -0,0 +1,177 @@
+// Smart comparison of three.js objects.
+// Identifies significant differences between two objects.
+// Performs deep comparison.
+// Comparison stops after the first difference is found.
+// Provides an explanation for the failure.
+function SmartComparer() {
+	'use strict';
+
+	// Diagnostic message, when comparison fails.
+	var message;
+
+	// Keys to skip during object comparison.
+	var omitKeys = [ 'id', 'uuid' ];
+
+	return {
+
+		areEqual: areEqual,
+
+		getDiagnostic: function() {
+
+			return message;
+
+		}
+
+	};
+
+
+	// val1 - first value to compare (typically the actual value)
+	// val2 - other value to compare (typically the expected value)
+	function areEqual( val1, val2 ) {
+
+		// Values are strictly equal.
+		if ( val1 === val2 ) return true;
+
+		// Null or undefined values.
+		/* jshint eqnull:true */
+		if ( val1 == null || val2 == null ) {
+
+			if ( val1 != val2 ) {
+
+				return makeFail( 'One value is undefined or null', val1, val2 );
+
+			}
+
+			// Both null / undefined.
+			return true;
+		}
+
+		// Don't compare functions.
+		if ( _.isFunction( val1 ) && _.isFunction( val2 ) ) return true;
+
+		// Array comparison.
+		var arrCmp = compareArrays( val1, val2 );
+		if ( arrCmp !== undefined ) return arrCmp;
+
+		// Has custom equality comparer.
+		if ( val1.equals ) {
+
+			if ( val1.equals( val2 ) ) return true;
+
+			return makeFail( 'Comparison with .equals method returned false' );
+
+		}
+
+		// Object comparison.
+		var objCmp = compareObjects( val1, val2 );
+		if ( objCmp !== undefined ) return objCmp;
+
+		// if (JSON.stringify( val1 ) == JSON.stringify( val2 ) ) return true;
+
+		// Continue with default comparison.
+		if ( _.isEqual( val1, val2 ) ) return true;
+
+		// Object differs (unknown reason).
+		return makeFail( 'Values differ', val1, val2 );
+	}
+
+
+	function compareArrays( val1, val2 ) {
+
+		var isArr1 = Array.isArray( val1 );
+		var isArr2 = Array.isArray( val2 );
+
+		// Compare type.
+		if ( isArr1 !== isArr2 ) return makeFail( 'Values are not both arrays' );
+
+		// Not arrays. Continue.
+		if ( !isArr1 ) return undefined;
+
+		// Compare length.
+		var N1 = val1.length;
+		var N2 = val2.length;
+		if ( N1 !== val2.length ) return makeFail( 'Array length differs', N1, N2 );
+
+		// Compare content at each index.
+		for ( var i = 0; i < N1; i ++ ) {
+
+			var cmp = areEqual( val1[ i ], val2[ i ] );
+			if ( !cmp )	return addContext( 'array index "' + i + '"' );
+
+		}
+
+		// Arrays are equal.
+		return true;
+	}
+
+
+	function compareObjects( val1, val2 ) {
+
+		var isObj1 = _.isObject( val1 );
+		var isObj2 = _.isObject( val2 );
+
+		// Compare type.
+		if ( isObj1 !== isObj2 ) return makeFail( 'Values are not both objects' );
+
+		// Not objects. Continue.
+		if ( !isObj1 ) return undefined;
+
+		// Compare keys.
+		var keys1 = _( val1 ).keys().difference( omitKeys ).value();
+		var keys2 = _( val2 ).keys().difference( omitKeys ).value();
+
+		var missingActual = _.difference( keys1, keys2 );
+		if ( missingActual.length !== 0 ) {
+
+			return makeFail( 'Property "' + missingActual[0] + '" is unexpected.' );
+
+		}
+
+		var missingExpected = _.difference( keys2, keys1 );
+		if ( missingExpected.length !== 0 ) {
+
+			return makeFail( 'Property "' + missingExpected[0] + '" is missing.' );
+
+		}
+
+		// Keys are the same. For each key, compare content until a difference is found.
+		var hadDifference = _.any( keys1, function ( key ) {
+
+			var prop1 = val1[key];
+			var prop2 = val2[key];
+
+			// Compare property content.
+			var eq = areEqual( prop1, prop2 );
+
+			// In case of failure, an message should already be set.
+			// Add context to low level message.
+			if ( !eq ) addContext( 'property "' + key + '"' );
+			return !eq;
+
+		});
+
+		return ! hadDifference;
+
+	}
+
+
+	function makeFail( msg, val1, val2 ) {
+
+		message = msg;
+		if ( arguments.length > 1) message += " (" + val1 + " vs " + val2 + ")";
+
+		return false;
+
+	}
+
+	function addContext( msg ) {
+
+		// There should already be a validation message. Add more context to it.
+		message = message || "Error";
+		message += ", at " + msg;
+
+		return false;
+
+	}
+
+}

+ 4 - 4
test/unit/extras/ImageUtils.test.js

@@ -63,13 +63,13 @@ QUnit.test( "test cached texture", function( assert ) {
 
 		assert.ok( rtex1.image !== undefined, "texture 1 image is loaded" );
 		assert.equal( rtex1, tex1, "texture 1 callback is equal to return" );
-		
+
 		var rtex2 = THREE.ImageUtils.loadTexture( good_url, undefined, function ( tex2 ) {
-			
+
 			assert.ok( rtex2 !== undefined, "cached callback is async" );
 			assert.ok( rtex2.image !== undefined, "texture 2 image is loaded" );
 			assert.equal( rtex2, tex2, "texture 2 callback is equal to return" );
-			
+
 			done();
 
 		});
@@ -77,7 +77,7 @@ QUnit.test( "test cached texture", function( assert ) {
 		assert.ok( rtex2, "texture 2 return is defined" );
 
 	});
-	
+
 	assert.ok( rtex1, "texture 1 return is defined" );
 	assert.ok( rtex1.image === undefined, "texture 1 image is not loaded" );
 

+ 38 - 0
test/unit/extras/geometries/BoxGeometry.tests.js

@@ -0,0 +1,38 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		width: 10,
+		height: 20,
+		depth: 30,
+		widthSegments: 2,
+		heightSegments: 3,
+		depthSegments: 4,
+	};
+
+	var geometries;
+	var box, cube, boxWithSegments;
+
+	QUnit.module( "Extras - Geometries - BoxGeometry", {
+
+		beforeEach: function() {
+
+			box = new THREE.BoxGeometry( parameters.width, parameters.height, parameters.depth );
+			cube = new THREE.CubeGeometry( parameters.width, parameters.height, parameters.depth );
+			boxWithSegments = new THREE.BoxGeometry( parameters.width, parameters.height, parameters.depth,
+													 parameters.widthSegments, parameters.heightSegments, parameters.depthSegments );
+
+			geometries = [ box, cube, boxWithSegments ];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 38 - 0
test/unit/extras/geometries/CircleBufferGeometry.tests.js

@@ -0,0 +1,38 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		segments: 20,
+		thetaStart: 0.1,
+		thetaLength: 0.2
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - CircleBufferGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.CircleBufferGeometry(),
+				new THREE.CircleBufferGeometry( parameters.radius ),
+				new THREE.CircleBufferGeometry( parameters.radius, parameters.segments ),
+				new THREE.CircleBufferGeometry( parameters.radius, parameters.segments, parameters.thetaStart ),
+				new THREE.CircleBufferGeometry( parameters.radius, parameters.segments, parameters.thetaStart, parameters.thetaLength ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 38 - 0
test/unit/extras/geometries/CircleGeometry.tests.js

@@ -0,0 +1,38 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		segments: 20,
+		thetaStart: 0.1,
+		thetaLength: 0.2
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - CircleGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.CircleGeometry(),
+				new THREE.CircleGeometry( parameters.radius ),
+				new THREE.CircleGeometry( parameters.radius, parameters.segments ),
+				new THREE.CircleGeometry( parameters.radius, parameters.segments, parameters.thetaStart ),
+				new THREE.CircleGeometry( parameters.radius, parameters.segments, parameters.thetaStart, parameters.thetaLength ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 46 - 0
test/unit/extras/geometries/CylinderGeometry.tests.js

@@ -0,0 +1,46 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radiusTop: 10,
+		radiusBottom: 20,
+		height: 30,
+		radialSegments: 20,
+		heightSegments: 30,
+		openEnded: true,
+		thetaStart: 0.1,
+		thetaLength: 2.0,
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - CylinderGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.CylinderGeometry(),
+				new THREE.CylinderGeometry( parameters.radiusTop ),
+				new THREE.CylinderGeometry( parameters.radiusTop, parameters.radiusBottom ),
+				new THREE.CylinderGeometry( parameters.radiusTop, parameters.radiusBottom, parameters.height ),
+				new THREE.CylinderGeometry( parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments ),
+				new THREE.CylinderGeometry( parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments, parameters.heightSegments ),
+				new THREE.CylinderGeometry( parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments, parameters.heightSegments, parameters.openEnded ),
+				new THREE.CylinderGeometry( parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments, parameters.heightSegments, parameters.openEnded, parameters.thetaStart ),
+				new THREE.CylinderGeometry( parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments, parameters.heightSegments, parameters.openEnded, parameters.thetaStart, parameters.thetaLength ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 34 - 0
test/unit/extras/geometries/DodecahedronGeometry.tests.js

@@ -0,0 +1,34 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		detail: undefined
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - DodecahedronGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.DodecahedronGeometry(),
+				new THREE.DodecahedronGeometry( parameters.radius ),
+				new THREE.DodecahedronGeometry( parameters.radius, parameters.detail ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 1 - 0
test/unit/extras/geometries/ExtrudeGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 34 - 0
test/unit/extras/geometries/IcosahedronGeometry.tests.js

@@ -0,0 +1,34 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		detail: undefined
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - IcosahedronGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.IcosahedronGeometry(),
+				new THREE.IcosahedronGeometry( parameters.radius ),
+				new THREE.IcosahedronGeometry( parameters.radius, parameters.detail ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 1 - 0
test/unit/extras/geometries/LatheGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 34 - 0
test/unit/extras/geometries/OctahedronGeometry.tests.js

@@ -0,0 +1,34 @@
+(function() {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		detail: undefined
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - OctahedronGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.OctahedronGeometry(),
+				new THREE.OctahedronGeometry( parameters.radius ),
+				new THREE.OctahedronGeometry( parameters.radius, parameters.detail ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 1 - 0
test/unit/extras/geometries/ParametricGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 38 - 0
test/unit/extras/geometries/PlaneBufferGeometry.tests.js

@@ -0,0 +1,38 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		width: 10,
+		height: 30,
+		widthSegments: 3,
+		heightSegments: 5
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - PlaneBufferGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.PlaneBufferGeometry(),
+				new THREE.PlaneBufferGeometry( parameters.width ),
+				new THREE.PlaneBufferGeometry( parameters.width, parameters.height ),
+				new THREE.PlaneBufferGeometry( parameters.width, parameters.height, parameters.widthSegments ),
+				new THREE.PlaneBufferGeometry( parameters.width, parameters.height, parameters.widthSegments, parameters.heightSegments ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 38 - 0
test/unit/extras/geometries/PlaneGeometry.tests.js

@@ -0,0 +1,38 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		width: 10,
+		height: 30,
+		widthSegments: 3,
+		heightSegments: 5
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - PlaneGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.PlaneGeometry(),
+				new THREE.PlaneGeometry( parameters.width ),
+				new THREE.PlaneGeometry( parameters.width, parameters.height ),
+				new THREE.PlaneGeometry( parameters.width, parameters.height, parameters.widthSegments ),
+				new THREE.PlaneGeometry( parameters.width, parameters.height, parameters.widthSegments, parameters.heightSegments ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 1 - 0
test/unit/extras/geometries/PolyhedronGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 42 - 0
test/unit/extras/geometries/RingGeometry.tests.js

@@ -0,0 +1,42 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		innerRadius: 10,
+		outerRadius: 60,
+		thetaSegments: 12,
+		phiSegments: 14,
+		thetaStart: 0.1,
+		thetaLength: 2.0
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - RingGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.RingGeometry(),
+				new THREE.RingGeometry( parameters.innerRadius ),
+				new THREE.RingGeometry( parameters.innerRadius, parameters.outerRadius ),
+				new THREE.RingGeometry( parameters.innerRadius, parameters.outerRadius, parameters.thetaSegments ),
+				new THREE.RingGeometry( parameters.innerRadius, parameters.outerRadius, parameters.thetaSegments, parameters.phiSegments ),
+				new THREE.RingGeometry( parameters.innerRadius, parameters.outerRadius, parameters.thetaSegments, parameters.phiSegments, parameters.thetaStart ),
+				new THREE.RingGeometry( parameters.innerRadius, parameters.outerRadius, parameters.thetaSegments, parameters.phiSegments, parameters.thetaStart, parameters.thetaLength ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 1 - 0
test/unit/extras/geometries/ShapeGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 44 - 0
test/unit/extras/geometries/SphereBufferGeometry.tests.js

@@ -0,0 +1,44 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		widthSegments: 20,
+		heightSegments: 30,
+		phiStart: 0.5,
+		phiLength: 1.0,
+		thetaStart: 0.4,
+		thetaLength: 2.0,
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - SphereBufferGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.SphereBufferGeometry(),
+				new THREE.SphereBufferGeometry( parameters.radius ),
+				new THREE.SphereBufferGeometry( parameters.radius, parameters.widthSegments ),
+				new THREE.SphereBufferGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments ),
+				new THREE.SphereBufferGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart ),
+				new THREE.SphereBufferGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart, parameters.phiLength ),
+				new THREE.SphereBufferGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart, parameters.phiLength, parameters.thetaStart ),
+				new THREE.SphereBufferGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart, parameters.phiLength, parameters.thetaStart, parameters.thetaLength ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 44 - 0
test/unit/extras/geometries/SphereGeometry.tests.js

@@ -0,0 +1,44 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		widthSegments: 20,
+		heightSegments: 30,
+		phiStart: 0.5,
+		phiLength: 1.0,
+		thetaStart: 0.4,
+		thetaLength: 2.0,
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - SphereGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.SphereGeometry(),
+				new THREE.SphereGeometry( parameters.radius ),
+				new THREE.SphereGeometry( parameters.radius, parameters.widthSegments ),
+				new THREE.SphereGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments ),
+				new THREE.SphereGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart ),
+				new THREE.SphereGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart, parameters.phiLength ),
+				new THREE.SphereGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart, parameters.phiLength, parameters.thetaStart ),
+				new THREE.SphereGeometry( parameters.radius, parameters.widthSegments, parameters.heightSegments, parameters.phiStart, parameters.phiLength, parameters.thetaStart, parameters.thetaLength ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 34 - 0
test/unit/extras/geometries/TetrahedronGeometry.tests.js

@@ -0,0 +1,34 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		detail: undefined
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - TetrahedronGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.TetrahedronGeometry(),
+				new THREE.TetrahedronGeometry( parameters.radius ),
+				new THREE.TetrahedronGeometry( parameters.radius, parameters.detail ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 1 - 0
test/unit/extras/geometries/TextGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 40 - 0
test/unit/extras/geometries/TorusGeometry.tests.js

@@ -0,0 +1,40 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		tube: 20,
+		radialSegments: 30,
+		tubularSegments: 10,
+		arc: 2.0,
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - TorusGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.TorusGeometry(),
+				new THREE.TorusGeometry( parameters.radius ),
+				new THREE.TorusGeometry( parameters.radius, parameters.tube ),
+				new THREE.TorusGeometry( parameters.radius, parameters.tube, parameters.radialSegments ),
+				new THREE.TorusGeometry( parameters.radius, parameters.tube, parameters.radialSegments, parameters.tubularSegments ),
+				new THREE.TorusGeometry( parameters.radius, parameters.tube, parameters.radialSegments, parameters.tubularSegments, parameters.arc ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 42 - 0
test/unit/extras/geometries/TorusKnotGeometry.tests.js

@@ -0,0 +1,42 @@
+(function () {
+
+	'use strict';
+
+	var parameters = {
+		radius: 10,
+		tube: 20,
+		radialSegments: 30,
+		tubularSegments: 10,
+		p: 3,
+		q: 2,
+		heightScale: 2.0
+	};
+
+	var geometries;
+
+	QUnit.module( "Extras - Geometries - TorusKnotGeometry", {
+
+		beforeEach: function() {
+
+			geometries = [
+
+				new THREE.TorusKnotGeometry(),
+				new THREE.TorusKnotGeometry( parameters.radius ),
+				new THREE.TorusKnotGeometry( parameters.radius, parameters.tube ),
+				new THREE.TorusKnotGeometry( parameters.radius, parameters.tube, parameters.radialSegments ),
+				new THREE.TorusKnotGeometry( parameters.radius, parameters.tube, parameters.radialSegments, parameters.tubularSegments ),
+				new THREE.TorusKnotGeometry( parameters.radius, parameters.tube, parameters.radialSegments, parameters.tubularSegments, parameters.p, parameters.q, parameters.heightScale ),
+
+			];
+
+		}
+
+	});
+
+	QUnit.test( "standard geometry tests", function( assert ) {
+
+		runStdGeometryTests( assert, geometries );
+
+	});
+
+})();

+ 1 - 0
test/unit/extras/geometries/TubeGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 1 - 0
test/unit/extras/geometries/WireframeGeometry.tests.js

@@ -0,0 +1 @@
+// TODO

+ 130 - 2
test/unit/qunit-utils.js

@@ -5,13 +5,141 @@
 QUnit.assert.success = function( message ) {
 
 	// Equivalent to assert( true, message );
-	QUnit.assert.push( true, undefined, undefined, message ); 
+	QUnit.assert.push( true, undefined, undefined, message );
 
 };
 
 QUnit.assert.fail = function( message ) {
 
 	// Equivalent to assert( false, message );
-	QUnit.assert.push( false, undefined, undefined, message ); 
+	QUnit.assert.push( false, undefined, undefined, message );
 
 };
+
+QUnit.assert.numEqual = function( actual, expected, message ) {
+
+	var diff = Math.abs(actual - expected);
+	message = message || ( actual + " should be equal to " + expected );
+	QUnit.assert.push( diff < 0.1, actual, expected, message );
+
+};
+
+QUnit.assert.equalKey = function( obj, ref, key ) {
+
+	var actual = obj[key];
+	var expected = ref[key];
+	var message = actual + ' should be equal to ' + expected + ' for key "' + key + '"';
+	QUnit.assert.push( actual == expected, actual, expected, message );
+
+};
+
+QUnit.assert.smartEqual = function( actual, expected, message ) {
+
+	var cmp = new SmartComparer();
+
+	var same = cmp.areEqual(actual, expected);
+	var msg = cmp.getDiagnostic() || message;
+
+	QUnit.assert.push( same, actual, expected, msg );
+
+};
+
+
+
+//
+//	GEOMETRY TEST HELPERS
+//
+
+function checkGeometryClone( geom ) {
+
+	// Clone
+	var copy = geom.clone();
+	QUnit.assert.notEqual( copy.uuid, geom.uuid, "clone uuid should differ from original" );
+	QUnit.assert.notEqual( copy.id, geom.id, "clone id should differ from original" );
+	QUnit.assert.smartEqual( copy, geom, "clone is equal to original" );
+
+
+	// json round trip with clone
+	checkGeometryJsonRoundtrip( copy );
+
+}
+
+// Compare json file with its source geometry.
+function checkGeometryJsonWriting( geom, json ) {
+
+	QUnit.assert.equal( json.metadata.version, "4.4", "check metadata version" );
+	QUnit.assert.equalKey( geom, json, 'type' );
+	QUnit.assert.equalKey( geom, json, 'uuid' );
+	QUnit.assert.equal( json.id, undefined, "should not persist id" );
+
+	// All parameters from geometry should be persisted.
+	_.forOwn( geom.parameters, function ( val, key ) {
+
+		QUnit.assert.equalKey( geom.parameters, json, key );
+
+	});
+
+	// All parameters from json should be transfered to the geometry.
+	// json is flat. Ignore first level json properties that are not parameters.
+	var notParameters = [ "metadata", "uuid", "type" ];
+	_.forOwn( json, function ( val, key ) {
+
+		if ( notParameters.indexOf( key) === -1 ) QUnit.assert.equalKey( geom.parameters, json, key );
+
+	});
+
+}
+
+// Check parsing and reconstruction of json geometry
+function checkGeometryJsonReading( json, geom ) {
+
+	var wrap = [ json ];
+
+	var loader = new THREE.ObjectLoader();
+	var output = loader.parseGeometries( wrap );
+
+	QUnit.assert.ok( output[geom.uuid], 'geometry matching source uuid not in output' );
+	QUnit.assert.smartEqual( output[geom.uuid], geom, 'Reconstruct geometry from ObjectLoader' );
+
+}
+
+// Verify geom -> json -> geom
+function checkGeometryJsonRoundtrip( geom ) {
+
+	var json = geom.toJSON();
+	checkGeometryJsonWriting( geom, json );
+	checkGeometryJsonReading( json, geom );
+
+}
+
+// Look for undefined and NaN values in numerical fieds.
+function checkFinite( geom ) {
+
+	var isNotFinite = _.any( geom.vertices, function ( v ) {
+
+		return ! ( _.isFinite( v.x ) || _.isFinite( v.y ) || _.isFinite( v.z ) );
+
+	});
+
+	// TODO Buffers, normal, etc.
+
+	QUnit.assert.ok( isNotFinite === false, "contains non-finite coordinates" );
+
+}
+
+// Run common geometry tests.
+function runStdGeometryTests( assert, geometries ) {
+
+	_.each( geometries, function( geom ) {
+
+		checkFinite( geom );
+
+		// Clone
+		checkGeometryClone( geom );
+
+		// json round trip
+		checkGeometryJsonRoundtrip( geom );
+
+	});
+
+}

+ 32 - 3
test/unit/unittests_three.html

@@ -7,9 +7,11 @@
 </head>
 <body>
   <div id="qunit"></div>
+  <script src="../../node_modules/lodash/index.js"></script>
   <script src="qunit-1.18.0.js"></script>
   <script src="qunit-utils.js"></script>
-  
+  <script src="SmartComparer.js"></script>
+
   <!-- add sources to test below -->
 
   <script src="../../build/three.js"></script>
@@ -33,11 +35,38 @@
   <script src="math/Matrix3.js"></script>
   <script src="math/Matrix4.js"></script>
   <script src="math/Frustum.js"></script>
-  
+
   <script src="geometry/EdgesGeometry.js"></script>
   <script src="extras/ImageUtils.test.js"></script>
-   
+
+  <script src="extras/geometries/BoxGeometry.tests.js"></script>
+  <script src="extras/geometries/CircleBufferGeometry.tests.js"></script>
+  <script src="extras/geometries/CircleGeometry.tests.js"></script>
+  <script src="extras/geometries/CylinderGeometry.tests.js"></script>
+  <script src="extras/geometries/DodecahedronGeometry.tests.js"></script>
+  <script src="extras/geometries/ExtrudeGeometry.tests.js"></script>
+  <script src="extras/geometries/IcosahedronGeometry.tests.js"></script>
+  <script src="extras/geometries/LatheGeometry.tests.js"></script>
+  <script src="extras/geometries/OctahedronGeometry.tests.js"></script>
+  <script src="extras/geometries/ParametricGeometry.tests.js"></script>
+  <script src="extras/geometries/PlaneBufferGeometry.tests.js"></script>
+  <script src="extras/geometries/PlaneGeometry.tests.js"></script>
+  <script src="extras/geometries/PolyhedronGeometry.tests.js"></script>
+  <script src="extras/geometries/RingGeometry.tests.js"></script>
+  <script src="extras/geometries/ShapeGeometry.tests.js"></script>
+  <script src="extras/geometries/SphereBufferGeometry.tests.js"></script>
+  <script src="extras/geometries/SphereGeometry.tests.js"></script>
+  <script src="extras/geometries/TetrahedronGeometry.tests.js"></script>
+  <script src="extras/geometries/TextGeometry.tests.js"></script>
+  <script src="extras/geometries/TorusGeometry.tests.js"></script>
+  <script src="extras/geometries/TorusKnotGeometry.tests.js"></script>
+  <script src="extras/geometries/TubeGeometry.tests.js"></script>
+  <script src="extras/geometries/WireframeGeometry.tests.js"></script>
+
+
   <!-- for debug output -->
+  <!--
   <script src="../../examples/js/controls/OrbitControls.js"></script>
+  -->
 </body>
 </html>