Browse Source

Merge pull request #12300 from moraxy/expand_unit_tests

Tests: Added remaining unit tests for src/math/*.js
Mr.doob 7 years ago
parent
commit
0a529ac093

+ 102 - 0
test/unit/src/math/Box3.js

@@ -311,3 +311,105 @@ QUnit.test( "translate" , function( assert ) {
 	assert.ok( d.clone().translate( one3 ).equals( b ), "Passed!" );
 	assert.ok( b.clone().translate( one3.clone().negate() ).equals( d ), "Passed!" );
 });
+
+QUnit.test( "setFromCenterAndSize", function ( assert ) {
+
+	var a = new THREE.Box3( zero3.clone(), one3.clone() );
+	var b = a.clone();
+	var newCenter = one3;
+	var newSize = two3;
+
+	a.setFromCenterAndSize( a.getCenter(), a.getSize() );
+	assert.ok( a.equals( b ), "Same values: no changes" );
+
+	a.setFromCenterAndSize( newCenter, a.getSize() );
+	assert.ok( a.getCenter().equals( newCenter ), "Move center: correct new center" );
+	assert.ok( a.getSize().equals( b.getSize() ), "Move center: no change in size" );
+	assert.notOk( a.equals( b ), "Move center: no longer equal to old values" );
+
+	a.setFromCenterAndSize( a.getCenter(), newSize );
+	assert.ok( a.getCenter().equals( newCenter ), "Resize: no change to center" );
+	assert.ok( a.getSize().equals( newSize ), "Resize: correct new size" );
+	assert.notOk( a.equals( b ), "Resize: no longer equal to old values" );
+
+} );
+
+QUnit.test( "setFromBufferAttribute", function ( assert ) {
+
+	var a = new THREE.Box3( zero3.clone(), one3.clone() );
+	var bigger = new THREE.BufferAttribute( new Float32Array( [
+		 - 2, - 2, - 2, 2, 2, 2, 1.5, 1.5, 1.5, 0, 0, 0
+	] ), 3 );
+	var smaller = new THREE.BufferAttribute( new Float32Array( [
+		 - 0.5, - 0.5, - 0.5, 0.5, 0.5, 0.5, 0, 0, 0
+	] ), 3 );
+	var newMin = new THREE.Vector3( - 2, - 2, - 2 );
+	var newMax = new THREE.Vector3( 2, 2, 2 );
+
+	a.setFromBufferAttribute( bigger );
+	assert.ok( a.min.equals( newMin ), "Bigger box: correct new minimum" );
+	assert.ok( a.max.equals( newMax ), "Bigger box: correct new maximum" );
+
+	newMin.set( - 0.5, - 0.5, - 0.5 );
+	newMax.set( 0.5, 0.5, 0.5 );
+
+	a.setFromBufferAttribute( smaller );
+	assert.ok( a.min.equals( newMin ), "Smaller box: correct new minimum" );
+	assert.ok( a.max.equals( newMax ), "Smaller box: correct new maximum" );
+
+} );
+
+QUnit.test( "expandByObject", function ( assert ) {
+
+	var a = new THREE.Box3( zero3.clone(), one3.clone() );
+	var b = a.clone();
+	var bigger = new THREE.Mesh( new THREE.BoxGeometry( 2, 2, 2 ) );
+	var smaller = new THREE.Mesh( new THREE.BoxGeometry( 0.5, 0.5, 0.5 ) );
+	var child = new THREE.Mesh( new THREE.BoxGeometry( 1, 1, 1 ) );
+
+	// just a bigger box to begin with
+	a.expandByObject( bigger );
+	assert.ok( a.min.equals( new THREE.Vector3( - 1, - 1, - 1 ) ), "Bigger box: correct new minimum" );
+	assert.ok( a.max.equals( new THREE.Vector3( 1, 1, 1 ) ), "Bigger box: correct new maximum" );
+
+	// a translated, bigger box
+	a.copy( b );
+	bigger.translateX( 2 );
+	a.expandByObject( bigger );
+	assert.ok( a.min.equals( new THREE.Vector3( 0, - 1, - 1 ) ), "Translated, bigger box: correct new minimum" );
+	assert.ok( a.max.equals( new THREE.Vector3( 3, 1, 1 ) ), "Translated, bigger box: correct new maximum" );
+
+	// a translated, bigger box with child
+	a.copy( b );
+	bigger.add( child );
+	a.expandByObject( bigger );
+	assert.ok( a.min.equals( new THREE.Vector3( 0, - 1, - 1 ) ), "Translated, bigger box with child: correct new minimum" );
+	assert.ok( a.max.equals( new THREE.Vector3( 3, 1, 1 ) ), "Translated, bigger box with child: correct new maximum" );
+
+	// a translated, bigger box with a translated child
+	a.copy( b );
+	child.translateX( 2 );
+	a.expandByObject( bigger );
+	assert.ok( a.min.equals( new THREE.Vector3( 0, - 1, - 1 ) ), "Translated, bigger box with translated child: correct new minimum" );
+	assert.ok( a.max.equals( new THREE.Vector3( 4.5, 1, 1 ) ), "Translated, bigger box with translated child: correct new maximum" );
+
+	// a smaller box
+	a.copy( b );
+	a.expandByObject( smaller );
+	assert.ok( a.min.equals( new THREE.Vector3( - 0.25, - 0.25, - 0.25 ) ), "Smaller box: correct new minimum" );
+	assert.ok( a.max.equals( new THREE.Vector3( 1, 1, 1 ) ), "Smaller box: correct new maximum" );
+
+} );
+
+QUnit.test( "setFromObject/BufferGeometry", function ( assert ) {
+
+	var a = new THREE.Box3( zero3.clone(), one3.clone() );
+	var object = new THREE.Mesh( new THREE.BoxBufferGeometry( 2, 2, 2 ) );
+	var child = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 1 ) );
+	object.add( child );
+
+	a.setFromObject( object );
+	assert.ok( a.min.equals( new THREE.Vector3( - 1, - 1, - 1 ) ), "Correct new minimum" );
+	assert.ok( a.max.equals( new THREE.Vector3( 1, 1, 1 ) ), "Correct new maximum" );
+
+} );

+ 167 - 0
test/unit/src/math/Color.js

@@ -278,3 +278,170 @@ QUnit.test( "setHSL", function ( assert ) {
     assert.ok( hsl.s == 1.00, "saturation: " + hsl.s );
     assert.ok( hsl.l == 0.25, "lightness: " + hsl.l );
 });
+
+QUnit.test( "set", function ( assert ) {
+
+	var a = new THREE.Color();
+	var b = new THREE.Color( 0.5, 0, 0 );
+	var c = new THREE.Color( 0xFF0000 );
+	var d = new THREE.Color( 0, 1.0, 0 );
+
+	a.set( b );
+	assert.ok( a.equals( b ), "Set with THREE.Color instance" );
+
+	a.set( 0xFF0000 );
+	assert.ok( a.equals( c ), "Set with number" );
+
+	a.set( "rgb(0,255,0)" );
+	assert.ok( a.equals( d ), "Set with style" );
+
+} );
+
+QUnit.test( "offsetHSL", function ( assert ) {
+
+	var a = new THREE.Color( "hsl(120,50%,50%)" );
+	var b = new THREE.Color( 0.36, 0.84, 0.648 );
+
+	a.offsetHSL( 0.1, 0.1, 0.1 );
+
+	assert.ok( Math.abs( a.r - b.r ) <= eps, "Check r" );
+	assert.ok( Math.abs( a.g - b.g ) <= eps, "Check g" );
+	assert.ok( Math.abs( a.b - b.b ) <= eps, "Check b" );
+
+} );
+
+QUnit.test( "add", function ( assert ) {
+
+	var a = new THREE.Color( 0x0000FF );
+	var b = new THREE.Color( 0xFF0000 );
+	var c = new THREE.Color( 0xFF00FF );
+
+	a.add( b );
+
+	assert.ok( a.equals( c ), "Check new value" );
+
+} );
+
+QUnit.test( "addScalar", function ( assert ) {
+
+	var a = new THREE.Color( 0.1, 0.0, 0.0 );
+	var b = new THREE.Color( 0.6, 0.5, 0.5 );
+
+	a.addScalar( 0.5 );
+
+	assert.ok( a.equals( b ), "Check new value" );
+
+} );
+
+QUnit.test( "sub", function ( assert ) {
+
+	var a = new THREE.Color( 0x0000CC );
+	var b = new THREE.Color( 0xFF0000 );
+	var c = new THREE.Color( 0x0000AA );
+
+	a.sub( b );
+	assert.strictEqual( a.getHex(), 0xCC, "Difference too large" );
+
+	a.sub( c );
+	assert.strictEqual( a.getHex(), 0x22, "Difference fine" );
+
+} );
+
+QUnit.test( "multiply", function ( assert ) {
+
+	var a = new THREE.Color( 1, 0, 0.5 );
+	var b = new THREE.Color( 0.5, 1, 0.5 );
+	var c = new THREE.Color( 0.5, 0, 0.25 );
+
+	a.multiply( b );
+	assert.ok( a.equals( c ), "Check new value" );
+
+} );
+
+QUnit.test( "multiplyScalar", function ( assert ) {
+
+	var a = new THREE.Color( 0.25, 0, 0.5 );
+	var b = new THREE.Color( 0.5, 0, 1 );
+
+	a.multiplyScalar( 2 );
+	assert.ok( a.equals( b ), "Check new value" );
+
+} );
+
+QUnit.test( "equals", function ( assert ) {
+
+	var a = new THREE.Color( 0.5, 0.0, 1.0 );
+	var b = new THREE.Color( 0.5, 1.0, 0.0 );
+
+	assert.strictEqual( a.r, b.r, "Components: r is equal" );
+	assert.notStrictEqual( a.g, b.g, "Components: g is not equal" );
+	assert.notStrictEqual( a.b, b.b, "Components: b is not equal" );
+
+	assert.notOk( a.equals( b ), "equals(): a not equal b" );
+	assert.notOk( b.equals( a ), "equals(): b not equal a" );
+
+	a.copy( b );
+	assert.strictEqual( a.r, b.r, "Components after copy(): r is equal" );
+	assert.strictEqual( a.g, b.g, "Components after copy(): g is equal" );
+	assert.strictEqual( a.b, b.b, "Components after copy(): b is equal" );
+
+	assert.ok( a.equals( b ), "equals() after copy(): a equals b" );
+	assert.ok( b.equals( a ), "equals() after copy(): b equals a" );
+
+} );
+
+QUnit.test( "fromArray", function ( assert ) {
+
+	var a = new THREE.Color();
+	var array = [ 0.5, 0.6, 0.7, 0, 1, 0 ];
+
+	a.fromArray( array );
+	assert.strictEqual( a.r, 0.5, "No offset: check r" );
+	assert.strictEqual( a.g, 0.6, "No offset: check g" );
+	assert.strictEqual( a.b, 0.7, "No offset: check b" );
+
+	a.fromArray( array, 3 );
+	assert.strictEqual( a.r, 0, "With offset: check r" );
+	assert.strictEqual( a.g, 1, "With offset: check g" );
+	assert.strictEqual( a.b, 0, "With offset: check b" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var r = 0.5, g = 1.0, b = 0.0;
+	var a = new THREE.Color( r, g, b );
+
+	var array = a.toArray();
+	assert.strictEqual( array[ 0 ], r, "No array, no offset: check r" );
+	assert.strictEqual( array[ 1 ], g, "No array, no offset: check g" );
+	assert.strictEqual( array[ 2 ], b, "No array, no offset: check b" );
+
+	array = [];
+	a.toArray( array );
+	assert.strictEqual( array[ 0 ], r, "With array, no offset: check r" );
+	assert.strictEqual( array[ 1 ], g, "With array, no offset: check g" );
+	assert.strictEqual( array[ 2 ], b, "With array, no offset: check b" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.strictEqual( array[ 0 ], undefined, "With array and offset: check [0]" );
+	assert.strictEqual( array[ 1 ], r, "With array and offset: check r" );
+	assert.strictEqual( array[ 2 ], g, "With array and offset: check g" );
+	assert.strictEqual( array[ 3 ], b, "With array and offset: check b" );
+
+} );
+
+QUnit.test( "toJSON", function ( assert ) {
+
+	var a = new THREE.Color( 0.0, 0.0, 0.0 );
+	var b = new THREE.Color( 0.0, 0.5, 0.0 );
+	var c = new THREE.Color( 1.0, 0.0, 0.0 );
+	var d = new THREE.Color( 1.0, 1.0, 1.0 );
+
+	assert.strictEqual( a.toJSON(), 0x000000, "Check black" );
+	assert.strictEqual( b.toJSON(), 0x007F00, "Check half-blue" );
+	assert.strictEqual( c.toJSON(), 0xFF0000, "Check red" );
+	assert.strictEqual( d.toJSON(), 0xFFFFFF, "Check white" );
+
+} );

+ 2 - 0
test/unit/src/math/Constants.js

@@ -20,3 +20,5 @@ var posInf3 = new THREE.Vector3( Infinity, Infinity, Infinity );
 var zero3 = new THREE.Vector3();
 var one3 = new THREE.Vector3( 1, 1, 1 );
 var two3 = new THREE.Vector3( 2, 2, 2 );
+
+var eps = 0.0001;

+ 83 - 3
test/unit/src/math/Cylindrical.js

@@ -1,6 +1,86 @@
 /**
- * @author TristanVALCKE / https://github.com/TristanVALCKE
+ * @author moraxy / https://github.com/moraxy
  */
 
-//Todo
-console.warn("Todo: Unit tests of Cylindrical")
+QUnit.module( "Cylindrical" );
+
+QUnit.test( "constructor", function ( assert ) {
+
+	var a = new THREE.Cylindrical();
+	var radius = 10.0;
+	var theta = Math.PI;
+	var y = 5;
+
+	assert.strictEqual( a.radius, 1.0, "Default values: check radius" );
+	assert.strictEqual( a.theta, 0, "Default values: check theta" );
+	assert.strictEqual( a.y, 0, "Default values: check y" );
+
+	a = new THREE.Cylindrical( radius, theta, y );
+	assert.strictEqual( a.radius, radius, "Custom values: check radius" );
+	assert.strictEqual( a.theta, theta, "Custom values: check theta" );
+	assert.strictEqual( a.y, y, "Custom values: check y" );
+
+} );
+
+QUnit.test( "set", function ( assert ) {
+
+	var a = new THREE.Cylindrical();
+	var radius = 10.0;
+	var theta = Math.PI;
+	var y = 5;
+
+	a.set( radius, theta, y );
+	assert.strictEqual( a.radius, radius, "Check radius" );
+	assert.strictEqual( a.theta, theta, "Check theta" );
+	assert.strictEqual( a.y, y, "Check y" );
+
+} );
+
+QUnit.test( "clone", function ( assert ) {
+
+	var radius = 10.0;
+	var theta = Math.PI;
+	var y = 5;
+	var a = new THREE.Cylindrical( radius, theta, y );
+	var b = a.clone();
+
+	assert.propEqual( a, b, "Check a and b are equal after clone()" );
+
+	a.radius = 1;
+	assert.notPropEqual( a, b, "Check a and b are not equal after modification" );
+
+} );
+
+QUnit.test( "copy", function ( assert ) {
+
+	var radius = 10.0;
+	var theta = Math.PI;
+	var y = 5;
+	var a = new THREE.Cylindrical( radius, theta, y );
+	var b = new THREE.Cylindrical().copy( a );
+
+	assert.propEqual( a, b, "Check a and b are equal after copy()" );
+
+	a.radius = 1;
+	assert.notPropEqual( a, b, "Check a and b are not equal after modification" );
+
+} );
+
+QUnit.test( "setFromVector3", function ( assert ) {
+
+	var a = new THREE.Cylindrical( 1, 1, 1 );
+	var b = new THREE.Vector3( 0, 0, 0 );
+	var c = new THREE.Vector3( 3, - 1, - 3 );
+	var expected = new THREE.Cylindrical( Math.sqrt( 9 + 9 ), Math.atan2( 3, - 3 ), - 1 );
+
+	a.setFromVector3( b );
+	assert.strictEqual( a.radius, 0, "Zero-length vector: check radius" );
+	assert.strictEqual( a.theta, 0, "Zero-length vector: check theta" );
+	assert.strictEqual( a.y, 0, "Zero-length vector: check y" );
+
+	a.setFromVector3( c );
+	assert.ok( Math.abs( a.radius - expected.radius ) <= eps, "Normal vector: check radius" );
+	assert.ok( Math.abs( a.theta - expected.theta ) <= eps, "Normal vector: check theta" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Normal vector: check y" );
+
+} );

+ 108 - 0
test/unit/src/math/Euler.js

@@ -132,3 +132,111 @@ QUnit.test( "gimbalLocalQuat" , function( assert ) {
 	assert.ok( eulerEquals( eViaQ1, eViaMViaQ1 ), "Passed!" );  // this result is correct
 
 });
+
+QUnit.test( "set/get properties, check callbacks", function ( assert ) {
+
+	var a = new THREE.Euler();
+	a.onChange( function () {
+
+		assert.step( "set: onChange called" );
+
+	} );
+
+	assert.expect( 8 );
+
+	// should be 4 calls to onChangeCallback
+	a.x = 1;
+	a.y = 2;
+	a.z = 3;
+	a.order = "ZYX";
+
+	assert.strictEqual( a.x, 1, "get: check x" );
+	assert.strictEqual( a.y, 2, "get: check y" );
+	assert.strictEqual( a.z, 3, "get: check z" );
+	assert.strictEqual( a.order, "ZYX", "get: check order" );
+
+} );
+
+QUnit.test( "clone/copy, check callbacks", function ( assert ) {
+
+	assert.expect( 3 );
+
+	var a = new THREE.Euler( 1, 2, 3, "ZXY" );
+	var b = new THREE.Euler( 4, 5, 6, "XZY" );
+	var cb = function () {
+
+		assert.step( "onChange called" );
+
+	};
+	a.onChange( cb );
+	b.onChange( cb );
+
+	// clone doesn't trigger onChange
+	a = b.clone();
+	assert.ok( a.equals( b ), "clone: check if a equals b" );
+
+	// copy triggers onChange once
+	a = new THREE.Euler( 1, 2, 3, "ZXY" );
+	a.onChange( cb );
+	a.copy( b );
+	assert.ok( a.equals( b ), "copy: check if a equals b" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var order = "YXZ";
+	var a = new THREE.Euler( x, y, z, order );
+
+	var array = a.toArray();
+	assert.strictEqual( array[ 0 ], x, "No array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "No array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "No array, no offset: check z" );
+	assert.strictEqual( array[ 3 ], order, "No array, no offset: check order" );
+
+	array = [];
+	a.toArray( array );
+	assert.strictEqual( array[ 0 ], x, "With array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "With array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "With array, no offset: check z" );
+	assert.strictEqual( array[ 3 ], order, "With array, no offset: check order" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.strictEqual( array[ 0 ], undefined, "With array and offset: check [0]" );
+	assert.strictEqual( array[ 1 ], x, "With array and offset: check x" );
+	assert.strictEqual( array[ 2 ], y, "With array and offset: check y" );
+	assert.strictEqual( array[ 3 ], z, "With array and offset: check z" );
+	assert.strictEqual( array[ 4 ], order, "With array and offset: check order" );
+
+} );
+
+QUnit.test( "fromArray", function ( assert ) {
+
+	assert.expect( 10 );
+
+	var a = new THREE.Euler();
+	var array = [ x, y, z ];
+	var cb = function () {
+
+		assert.step( "onChange called" );
+
+	};
+	a.onChange( cb );
+
+	a.fromArray( array );
+	assert.strictEqual( a.x, x, "No order: check x" );
+	assert.strictEqual( a.y, y, "No order: check y" );
+	assert.strictEqual( a.z, z, "No order: check z" );
+	assert.strictEqual( a.order, "XYZ", "No order: check order" );
+
+	a = new THREE.Euler();
+	a.onChange( cb );
+	array = [ x, y, z, "ZXY" ];
+	a.fromArray( array );
+	assert.strictEqual( a.x, x, "With order: check x" );
+	assert.strictEqual( a.y, y, "With order: check y" );
+	assert.strictEqual( a.z, z, "With order: check z" );
+	assert.strictEqual( a.order, "ZXY", "With order: check order" );
+
+} );

+ 80 - 0
test/unit/src/math/Frustum.js

@@ -154,3 +154,83 @@ QUnit.test( "clone" , function( assert ) {
 	a.planes[0].copy( p1 );
 	assert.ok( b.planes[0].equals( p0 ), "Passed!" );
 });
+
+QUnit.test( "set", function ( assert ) {
+
+	var a = new THREE.Frustum();
+	var p0 = new THREE.Plane( unit3, - 1 );
+	var p1 = new THREE.Plane( unit3, 1 );
+	var p2 = new THREE.Plane( unit3, 2 );
+	var p3 = new THREE.Plane( unit3, 3 );
+	var p4 = new THREE.Plane( unit3, 4 );
+	var p5 = new THREE.Plane( unit3, 5 );
+
+	a.set( p0, p1, p2, p3, p4, p5 );
+
+	assert.ok( a.planes[ 0 ].equals( p0 ), "Check plane #0" );
+	assert.ok( a.planes[ 1 ].equals( p1 ), "Check plane #1" );
+	assert.ok( a.planes[ 2 ].equals( p2 ), "Check plane #2" );
+	assert.ok( a.planes[ 3 ].equals( p3 ), "Check plane #3" );
+	assert.ok( a.planes[ 4 ].equals( p4 ), "Check plane #4" );
+	assert.ok( a.planes[ 5 ].equals( p5 ), "Check plane #5" );
+
+} );
+
+QUnit.test( "intersectsObject", function ( assert ) {
+
+	var m = new THREE.Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+	var a = new THREE.Frustum().setFromMatrix( m );
+	var object = new THREE.Mesh( new THREE.BoxGeometry( 1, 1, 1 ) );
+	var intersects;
+
+	intersects = a.intersectsObject( object );
+	assert.notOk( intersects, "No intersection" );
+
+	object.position.set( - 1, - 1, - 1 );
+	object.updateMatrixWorld();
+
+	intersects = a.intersectsObject( object );
+	assert.ok( intersects, "Successful intersection" );
+
+	object.position.set( 1, 1, 1 );
+	object.updateMatrixWorld();
+
+	intersects = a.intersectsObject( object );
+	assert.notOk( intersects, "No intersection" );
+
+} );
+
+QUnit.test( "intersectsSprite", function ( assert ) {
+
+	var m = new THREE.Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+	var a = new THREE.Frustum().setFromMatrix( m );
+	var sprite = new THREE.Sprite();
+	var intersects;
+
+	intersects = a.intersectsSprite( sprite );
+	assert.notOk( intersects, "No intersection" );
+
+	sprite.position.set( - 1, - 1, - 1 );
+	sprite.updateMatrixWorld();
+
+	intersects = a.intersectsSprite( sprite );
+	assert.ok( intersects, "Successful intersection" );
+
+} );
+
+QUnit.test( "intersectsBox", function ( assert ) {
+
+	var m = new THREE.Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+	var a = new THREE.Frustum().setFromMatrix( m );
+	var box = new THREE.Box3( zero3.clone(), one3.clone() );
+	var intersects;
+
+	intersects = a.intersectsBox( box );
+	assert.notOk( intersects, "No intersection" );
+
+	box.translate( - 1, - 1, - 1 );
+
+	intersects = a.intersectsBox( box );
+	assert.ok( intersects, "Successful intersection" );
+
+} );

+ 71 - 0
test/unit/src/math/Line3.js

@@ -67,3 +67,74 @@ QUnit.test( "closestPointToPoint/closestPointToPointParameter", function( assert
 	var c = a.closestPointToPoint( one3.clone(), true );
 	assert.ok( c.distanceTo( one3.clone() ) < 0.0001, "Passed!" );
 });
+
+QUnit.test( "clone/equal", function ( assert ) {
+
+	var a = new THREE.Line3();
+	var b = new THREE.Line3( zero3, new THREE.Vector3( 1, 1, 1 ) );
+	var c = new THREE.Line3( zero3, new THREE.Vector3( 1, 1, 0 ) );
+
+	assert.notOk( a.equals( b ), "Check a and b aren't equal" );
+	assert.notOk( a.equals( c ), "Check a and c aren't equal" );
+	assert.notOk( b.equals( c ), "Check b and c aren't equal" );
+
+	a = b.clone();
+	assert.ok( a.equals( b ), "Check a and b are equal after clone()" );
+	assert.notOk( a.equals( c ), "Check a and c aren't equal after clone()" );
+
+	a.set( zero3, zero3 );
+	assert.notOk( a.equals( b ), "Check a and b are not equal after modification" );
+
+} );
+
+QUnit.test( "distance", function ( assert ) {
+
+	var a = new THREE.Line3( zero3, zero3 );
+	var b = new THREE.Line3( zero3, one3 );
+	var c = new THREE.Line3( one3.clone().negate(), one3 );
+	var d = new THREE.Line3( two3.clone().multiplyScalar( - 2 ), two3.clone().negate() );
+
+	assert.numEqual( a.distance(), 0, "Check distance for zero-length line" );
+	assert.numEqual( b.distance(), Math.sqrt( 3 ), "Check distance for simple line" );
+	assert.numEqual( c.distance(), Math.sqrt( 12 ), "Check distance for negative to positive endpoints" );
+	assert.numEqual( d.distance(), Math.sqrt( 12 ), "Check distance for negative to negative endpoints" );
+
+} );
+
+QUnit.test( "applyMatrix4", function ( assert ) {
+
+	var a = new THREE.Line3( zero3.clone(), two3.clone() );
+	var b = new THREE.Vector4( two3.x, two3.y, two3.z, 1 );
+	var m = new THREE.Matrix4().makeTranslation( x, y, z );
+	var v = new THREE.Vector3( x, y, z );
+
+	a.applyMatrix4( m );
+	assert.ok( a.start.equals( v ), "Translation: check start" );
+	assert.ok( a.end.equals( new THREE.Vector3( 2 + x, 2 + y, 2 + z ) ), "Translation: check start" );
+
+	// reset starting conditions
+	a.set( zero3.clone(), two3.clone() );
+	m.makeRotationX( Math.PI );
+
+	a.applyMatrix4( m );
+	b.applyMatrix4( m );
+
+	assert.ok( a.start.equals( zero3 ), "Rotation: check start" );
+	assert.numEqual( a.end.x, b.x / b.w, "Rotation: check end.x" );
+	assert.numEqual( a.end.y, b.y / b.w, "Rotation: check end.y" );
+	assert.numEqual( a.end.z, b.z / b.w, "Rotation: check end.z" );
+
+	// reset starting conditions
+	a.set( zero3.clone(), two3.clone() );
+	b.set( two3.x, two3.y, two3.z, 1 );
+	m.setPosition( v );
+
+	a.applyMatrix4( m );
+	b.applyMatrix4( m );
+
+	assert.ok( a.start.equals( v ), "Both: check start" );
+	assert.numEqual( a.end.x, b.x / b.w, "Both: check end.x" );
+	assert.numEqual( a.end.y, b.y / b.w, "Both: check end.y" );
+	assert.numEqual( a.end.z, b.z / b.w, "Both: check end.z" );
+
+} );

+ 123 - 0
test/unit/src/math/Math.js

@@ -42,3 +42,126 @@ QUnit.test( "Math.sign/polyfill", function( assert ) {
 	}
 
 });
+
+QUnit.test( "generateUUID", function ( assert ) {
+
+	var a = THREE.Math.generateUUID();
+	var regex = /[A-Z0-9]{8}-[A-Z0-9]{4}-4[A-Z0-9]{3}-[A-Z0-9]{4}-[A-Z0-9]{12}/i;
+	// note the fixed '4' here ----------^
+
+	assert.ok( regex.test( a ), "Generated UUID matches the expected pattern" );
+
+} );
+
+QUnit.test( "clamp", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.clamp( 0.5, 0, 1 ), 0.5, "Value already within limits" );
+	assert.strictEqual( THREE.Math.clamp( 0, 0, 1 ), 0, "Value equal to one limit" );
+	assert.strictEqual( THREE.Math.clamp( - 0.1, 0, 1 ), 0, "Value too low" );
+	assert.strictEqual( THREE.Math.clamp( 1.1, 0, 1 ), 1, "Value too high" );
+
+} );
+
+QUnit.test( "euclideanModulo", function ( assert ) {
+
+	assert.ok( isNaN( THREE.Math.euclideanModulo( 6, 0 ) ), "Division by zero returns NaN" );
+	assert.strictEqual( THREE.Math.euclideanModulo( 6, 1 ), 0, "Divison by trivial divisor" );
+	assert.strictEqual( THREE.Math.euclideanModulo( 6, 2 ), 0, "Divison by non-trivial divisor" );
+	assert.strictEqual( THREE.Math.euclideanModulo( 6, 5 ), 1, "Divison by itself - 1" );
+	assert.strictEqual( THREE.Math.euclideanModulo( 6, 6 ), 0, "Divison by itself" );
+	assert.strictEqual( THREE.Math.euclideanModulo( 6, 7 ), 6, "Divison by itself + 1" );
+
+} );
+
+QUnit.test( "mapLinear", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.mapLinear( 0.5, 0, 1, 0, 10 ), 5, "Value within range" );
+	assert.strictEqual( THREE.Math.mapLinear( 0.0, 0, 1, 0, 10 ), 0, "Value equal to lower boundary" );
+	assert.strictEqual( THREE.Math.mapLinear( 1.0, 0, 1, 0, 10 ), 10, "Value equal to upper boundary" );
+
+} );
+
+QUnit.test( "smoothstep", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.smoothstep( - 1, 0, 2 ), 0, "Value lower than minimum" );
+	assert.strictEqual( THREE.Math.smoothstep( 0, 0, 2 ), 0, "Value equal to minimum" );
+	assert.strictEqual( THREE.Math.smoothstep( 0.5, 0, 2 ), 0.15625, "Value within limits" );
+	assert.strictEqual( THREE.Math.smoothstep( 1, 0, 2 ), 0.5, "Value within limits" );
+	assert.strictEqual( THREE.Math.smoothstep( 1.5, 0, 2 ), 0.84375, "Value within limits" );
+	assert.strictEqual( THREE.Math.smoothstep( 2, 0, 2 ), 1, "Value equal to maximum" );
+	assert.strictEqual( THREE.Math.smoothstep( 3, 0, 2 ), 1, "Value highter than maximum" );
+
+} );
+
+QUnit.test( "randInt", function ( assert ) {
+
+	var low = 1, high = 3;
+	var a = THREE.Math.randInt( low, high );
+
+	assert.ok( a >= low, "Value equal to or higher than lower limit" );
+	assert.ok( a <= high, "Value equal to or lower than upper limit" );
+
+} );
+
+QUnit.test( "randFloat", function ( assert ) {
+
+	var low = 1, high = 3;
+	var a = THREE.Math.randFloat( low, high );
+
+	assert.ok( a >= low, "Value equal to or higher than lower limit" );
+	assert.ok( a <= high, "Value equal to or lower than upper limit" );
+
+} );
+
+QUnit.test( "randFloatSpread", function ( assert ) {
+
+	var a = THREE.Math.randFloatSpread( 3 );
+
+	assert.ok( a > - 3 / 2, "Value higher than lower limit" );
+	assert.ok( a < 3 / 2, "Value lower than upper limit" );
+
+} );
+
+QUnit.test( "degToRad", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.degToRad( 0 ), 0, "0 degrees" );
+	assert.strictEqual( THREE.Math.degToRad( 90 ), Math.PI / 2, "90 degrees" );
+	assert.strictEqual( THREE.Math.degToRad( 180 ), Math.PI, "180 degrees" );
+	assert.strictEqual( THREE.Math.degToRad( 360 ), Math.PI * 2, "360 degrees" );
+
+} );
+
+QUnit.test( "radToDeg", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.radToDeg( 0 ), 0, "0 radians" );
+	assert.strictEqual( THREE.Math.radToDeg( Math.PI / 2 ), 90, "Math.PI / 2 radians" );
+	assert.strictEqual( THREE.Math.radToDeg( Math.PI ), 180, "Math.PI radians" );
+	assert.strictEqual( THREE.Math.radToDeg( Math.PI * 2 ), 360, "Math.PI * 2 radians" );
+
+} );
+
+QUnit.test( "isPowerOfTwo", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.isPowerOfTwo( 0 ), false, "0 is not a PoT" );
+	assert.strictEqual( THREE.Math.isPowerOfTwo( 1 ), true, "1 is a PoT" );
+	assert.strictEqual( THREE.Math.isPowerOfTwo( 2 ), true, "2 is a PoT" );
+	assert.strictEqual( THREE.Math.isPowerOfTwo( 3 ), false, "3 is not a PoT" );
+	assert.strictEqual( THREE.Math.isPowerOfTwo( 4 ), true, "4 is a PoT" );
+
+} );
+
+QUnit.test( "ceilPowerOfTwo", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.ceilPowerOfTwo( 1 ), 1, "Closest higher PoT to 1 is 1" );
+	assert.strictEqual( THREE.Math.ceilPowerOfTwo( 3 ), 4, "Closest higher PoT to 3 is 4" );
+	assert.strictEqual( THREE.Math.ceilPowerOfTwo( 4 ), 4, "Closest higher PoT to 4 is 4" );
+
+} );
+
+QUnit.test( "floorPowerOfTwo", function ( assert ) {
+
+	assert.strictEqual( THREE.Math.floorPowerOfTwo( 1 ), 1, "Closest lower PoT to 1 is 1" );
+	assert.strictEqual( THREE.Math.floorPowerOfTwo( 3 ), 2, "Closest lower PoT to 3 is 2" );
+	assert.strictEqual( THREE.Math.floorPowerOfTwo( 4 ), 4, "Closest lower PoT to 4 is 4" );
+
+} );

+ 159 - 0
test/unit/src/math/Matrix3.js

@@ -243,3 +243,162 @@ QUnit.test( "clone" , function( assert ) {
 	a.elements[0] = 2;
 	assert.ok( ! matrixEquals3( a, b ), "Passed!" );
 });
+
+QUnit.test( "applyToBufferAttribute", function ( assert ) {
+
+	var a = new THREE.Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+	var attr = new THREE.Float32BufferAttribute( [ 1, 2, 1, 3, 0, 3 ], 3 );
+	var expected = new Float32Array( [ 8, 20, 32, 12, 30, 48 ] );
+
+	var applied = a.applyToBufferAttribute( attr );
+
+	assert.deepEqual( applied.array, expected, "Check resulting buffer" );
+
+} );
+
+QUnit.test( "multiply/premultiply", function ( assert ) {
+
+	// both simply just wrap multiplyMatrices
+	var a = new THREE.Matrix3().set( 2, 3, 5, 7, 11, 13, 17, 19, 23 );
+	var b = new THREE.Matrix3().set( 29, 31, 37, 41, 43, 47, 53, 59, 61 );
+	var expectedMultiply = [ 446, 1343, 2491, 486, 1457, 2701, 520, 1569, 2925 ];
+	var expectedPremultiply = [ 904, 1182, 1556, 1131, 1489, 1967, 1399, 1845, 2435 ];
+
+	a.multiply( b );
+	assert.deepEqual( a.elements, expectedMultiply, "multiply: check result" );
+
+	a.set( 2, 3, 5, 7, 11, 13, 17, 19, 23 );
+	a.premultiply( b );
+	assert.deepEqual( a.elements, expectedPremultiply, "premultiply: check result" );
+
+} );
+
+QUnit.test( "getNormalMatrix", function ( assert ) {
+
+	var a = new THREE.Matrix3();
+	var b = new THREE.Matrix4().set(
+		2, 3, 5, 7,
+		11, 13, 17, 19,
+		23, 29, 31, 37,
+		41, 43, 47, 57
+	);
+	var expected = new THREE.Matrix3().set(
+		- 1.2857142857142856, 0.7142857142857143, 0.2857142857142857,
+		0.7428571428571429, - 0.7571428571428571, 0.15714285714285714,
+		- 0.19999999999999998, 0.3, - 0.09999999999999999
+	);
+
+	a.getNormalMatrix( b );
+	assert.ok( matrixEquals3( a, expected ), "Check resulting Matrix3" );
+
+} );
+
+QUnit.test( "equals", function ( assert ) {
+
+	var a = new THREE.Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	var b = new THREE.Matrix3().set( 0, - 1, 2, 3, 4, 5, 6, 7, 8 );
+
+	assert.notOk( a.equals( b ), "Check that a does not equal b" );
+	assert.notOk( b.equals( a ), "Check that b does not equal a" );
+
+	a.copy( b );
+	assert.ok( a.equals( b ), "Check that a equals b after copy()" );
+	assert.ok( b.equals( a ), "Check that b equals a after copy()" );
+
+} );
+
+QUnit.test( "setUvTransform", function ( assert ) {
+
+	var a = new THREE.Matrix3().set(
+		0.1767766952966369, 0.17677669529663687, 0.32322330470336313,
+		- 0.17677669529663687, 0.1767766952966369, 0.5,
+		0, 0, 1
+	);
+	var b = new THREE.Matrix3();
+	var params = {
+		centerX: 0.5, centerY: 0.5,
+		offsetX: 0, offsetY: 0,
+		repeatX: 0.25, repeatY: 0.25,
+		rotation: 0.7753981633974483
+	};
+	var expected = new THREE.Matrix3().set(
+		0.1785355940258599, 0.17500011904519763, 0.32323214346447127,
+		- 0.17500011904519763, 0.1785355940258599, 0.4982322625096689,
+		0, 0, 1
+	);
+
+	a.setUvTransform(
+		params.offsetX, params.offsetY,
+		params.repeatX, params.repeatY,
+		params.rotation,
+		params.centerX, params.centerY
+	);
+
+	b.identity()
+	 .translate( - params.centerX, - params.centerY )
+	 .rotate( params.rotation )
+	 .scale( params.repeatX, params.repeatY )
+	 .translate( params.centerX, params.centerY )
+	 .translate( params.offsetX, params.offsetY );
+
+	assert.ok( matrixEquals3( a, expected ), "Check direct method" );
+	assert.ok( matrixEquals3( b, expected ), "Check indirect method" );
+
+} );
+
+QUnit.test( "rotate", function ( assert ) {
+
+	var a = new THREE.Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+	var expected = new THREE.Matrix3().set(
+		3.5355339059327373, 4.949747468305833, 6.363961030678928,
+		2.121320343559643, 2.121320343559643, 2.1213203435596433,
+		7, 8, 9
+	);
+
+	a.rotate( Math.PI / 4 );
+	assert.ok( matrixEquals3( a, expected ), "Check rotated result" );
+
+} );
+
+QUnit.test( "scale", function ( assert ) {
+
+	var a = new THREE.Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+	var expected = new THREE.Matrix3().set(
+		0.25, 0.5, 0.75,
+		1, 1.25, 1.5,
+		7, 8, 9
+	);
+
+	a.scale( 0.25, 0.25 );
+	assert.ok( matrixEquals3( a, expected ), "Check scaling result" );
+
+} );
+
+QUnit.test( "translate", function ( assert ) {
+
+	var a = new THREE.Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+	var expected = new THREE.Matrix3().set( 22, 26, 30, 53, 61, 69, 7, 8, 9 );
+
+	a.translate( 3, 7 );
+	assert.ok( matrixEquals3( a, expected ), "Check translation result" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var a = new THREE.Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+	var noOffset = [ 1, 4, 7, 2, 5, 8, 3, 6, 9 ];
+	var withOffset = [ undefined, 1, 4, 7, 2, 5, 8, 3, 6, 9 ];
+
+	var array = a.toArray();
+	assert.deepEqual( array, noOffset, "No array, no offset" );
+
+	array = [];
+	a.toArray( array );
+	assert.deepEqual( array, noOffset, "With array, no offset" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.deepEqual( array, withOffset, "With array, with offset" );
+
+} );

+ 180 - 0
test/unit/src/math/Matrix4.js

@@ -18,6 +18,15 @@ var matrixEquals4 = function( a, b, tolerance ) {
 	return true;
 };
 
+// from Euler.js
+var eulerEquals = function ( a, b, tolerance ) {
+
+	tolerance = tolerance || 0.0001;
+	var diff = Math.abs( a.x - b.x ) + Math.abs( a.y - b.y ) + Math.abs( a.z - b.z );
+	return ( diff < tolerance );
+
+};
+
 QUnit.test( "constructor" , function( assert ) {
 	var a = new THREE.Matrix4();
 	assert.ok( a.determinant() == 1, "Passed!" );
@@ -367,3 +376,174 @@ QUnit.test( "compose/decompose", function( assert ) {
 		}
 	}
 });
+
+QUnit.test( "copyPosition", function ( assert ) {
+
+	var a = new THREE.Matrix4().set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+	var b = new THREE.Matrix4().set( 1, 2, 3, 0, 5, 6, 7, 0, 9, 10, 11, 0, 13, 14, 15, 16 );
+
+	assert.notOk( matrixEquals4( a, b ), "a and b initially not equal" );
+
+	b.copyPosition( a );
+	assert.ok( matrixEquals4( a, b ), "a and b equal after copyPosition()" );
+
+} );
+
+QUnit.test( "makeRotationFromEuler/extractRotation", function ( assert ) {
+
+	var testValues = [
+		new THREE.Euler( 0, 0, 0, "XYZ" ),
+		new THREE.Euler( 1, 0, 0, "XYZ" ),
+		new THREE.Euler( 0, 1, 0, "ZYX" ),
+		new THREE.Euler( 0, 0, 0.5, "YZX" ),
+		new THREE.Euler( 0, 0, - 0.5, "YZX" )
+	];
+
+	for ( var i = 0; i < testValues.length; i ++ ) {
+
+		var v = testValues[ i ];
+
+		var m = new THREE.Matrix4().makeRotationFromEuler( v );
+
+		var v2 = new THREE.Euler().setFromRotationMatrix( m, v.order );
+		var m2 = new THREE.Matrix4().makeRotationFromEuler( v2 );
+
+		assert.ok( matrixEquals4( m, m2, eps ), "makeRotationFromEuler #" + i + ": original and Euler-derived matrices are equal" );
+		assert.ok( eulerEquals( v, v2, eps ), "makeRotationFromEuler #" + i + ": original and matrix-derived Eulers are equal" );
+
+		var m3 = new THREE.Matrix4().extractRotation( m2 );
+		var v3 = new THREE.Euler().setFromRotationMatrix( m3, v.order );
+
+		assert.ok( matrixEquals4( m, m3, eps ), "extractRotation #" + i + ": original and extracted matrices are equal" );
+		assert.ok( eulerEquals( v, v3, eps ), "extractRotation #" + i + ": original and extracted Eulers are equal" );
+
+	}
+
+} );
+
+QUnit.test( "applyToBufferAttribute", function ( assert ) {
+
+	var a = new THREE.Matrix4().set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+	var attr = new THREE.Float32BufferAttribute( [ 1, 2, 1, 3, 0, 3 ], 3 );
+	var expected = new THREE.Float32BufferAttribute( [
+		0.1666666716337204,	0.4444444477558136, 0.7222222089767456,
+		0.1599999964237213, 0.4399999976158142, 0.7200000286102295
+	], 3 );
+
+	var applied = a.applyToBufferAttribute( attr );
+
+	assert.strictEqual( expected.count, applied.count, "Applied buffer and expected buffer have the same number of entries" );
+
+	for ( var i = 0, l = expected.count; i < l; i ++ ) {
+
+		assert.ok( Math.abs( applied.getX( i ) - expected.getX( i ) ) <= eps, "Check x" );
+		assert.ok( Math.abs( applied.getY( i ) - expected.getY( i ) ) <= eps, "Check y" );
+		assert.ok( Math.abs( applied.getZ( i ) - expected.getZ( i ) ) <= eps, "Check z" );
+
+	}
+
+} );
+
+QUnit.test( "equals", function ( assert ) {
+
+	var a = new THREE.Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+	var b = new THREE.Matrix4().set( 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+
+	assert.notOk( a.equals( b ), "Check that a does not equal b" );
+	assert.notOk( b.equals( a ), "Check that b does not equal a" );
+
+	a.copy( b );
+	assert.ok( a.equals( b ), "Check that a equals b after copy()" );
+	assert.ok( b.equals( a ), "Check that b equals a after copy()" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var a = new THREE.Matrix4().set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+	var noOffset = [ 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16 ];
+	var withOffset = [ undefined, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16 ];
+
+	var array = a.toArray();
+	assert.deepEqual( array, noOffset, "No array, no offset" );
+
+	array = [];
+	a.toArray( array );
+	assert.deepEqual( array, noOffset, "With array, no offset" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.deepEqual( array, withOffset, "With array, with offset" );
+
+} );
+
+QUnit.test( "getMaxScaleOnAxis", function ( assert ) {
+
+	var a = new THREE.Matrix4().set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+	var expected = Math.sqrt( 3 * 3 + 7 * 7 + 11 * 11 );
+
+	assert.ok( Math.abs( a.getMaxScaleOnAxis() - expected ) <= eps, "Check result" );
+
+} );
+
+QUnit.test( "makeOrthographic", function ( assert ) {
+
+	var a = new THREE.Matrix4().makeOrthographic( - 1, 1, - 1, 1, 1, 100 );
+	var expected = new THREE.Matrix4().set(
+		1, 0, 0, 0,
+		0, - 1, 0, 0,
+		0, 0, - 2 / 99, - 101 / 99,
+		0, 0, 0, 1
+	);
+
+	assert.ok( matrixEquals4( a, expected ), "Check result" );
+
+} );
+
+QUnit.test( "makeRotationAxis", function ( assert ) {
+
+	var axis = new THREE.Vector3( 1.5, 0.0, 1.0 ).normalize();
+	var radians = THREE.Math.degToRad( 45 );
+	var a = new THREE.Matrix4().makeRotationAxis( axis, radians );
+
+	var expected = new THREE.Matrix4().set(
+		0.9098790095958609, - 0.39223227027636803, 0.13518148560620882, 0,
+		0.39223227027636803, 0.7071067811865476, - 0.588348405414552, 0,
+		0.13518148560620882, 0.588348405414552, 0.7972277715906868, 0,
+		0, 0, 0, 1
+	);
+
+	assert.ok( matrixEquals4( a, expected ), "Check numeric result" );
+
+} );
+
+QUnit.test( "lookAt", function ( assert ) {
+
+	var a = new THREE.Matrix4();
+	var expected = new THREE.Matrix4().identity();
+	var eye = new THREE.Vector3( 0, 0, 0 );
+	var target = new THREE.Vector3( 0, 1, - 1 );
+	var up = new THREE.Vector3( 0, 1, 0 );
+
+	a.lookAt( eye, target, up );
+	var rotation = new THREE.Euler().setFromRotationMatrix( a );
+	assert.numEqual( rotation.x * ( 180 / Math.PI ), 45, "Check the rotation" );
+
+	// eye and target are in the same position
+	eye.copy( target );
+	a.lookAt( eye, target, up );
+	assert.ok( matrixEquals4( a, expected ), "Check the result for eye == target" );
+
+	// up and z are parallel
+	eye.set( 0, 1, 0 );
+	target.set( 0, 0, 0 );
+	a.lookAt( eye, target, up );
+	expected.set(
+		1, 0, 0, 0,
+		0, 0.0001, 1, 0,
+		0, - 1, 0.0001, 0,
+		0, 0, 0, 1
+	);
+	assert.ok( matrixEquals4( a, expected ), "Check the result for when up and z are parallel" );
+
+} );

+ 38 - 0
test/unit/src/math/Plane.js

@@ -188,3 +188,41 @@ QUnit.test( "applyMatrix4/translate", function( assert ) {
 	m.makeTranslation( 1, 1, 1 );
 	assert.ok( comparePlane( a.clone().applyMatrix4( m ), a.clone().translate( new THREE.Vector3( 1, 1, 1 ) ) ), "Passed!" );
 });
+
+QUnit.test( "equals", function ( assert ) {
+
+	var a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 );
+	var b = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 1 );
+	var c = new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), 0 );
+
+	assert.ok( a.normal.equals( b.normal ), "Normals: equal" );
+	assert.notOk( a.normal.equals( c.normal ), "Normals: not equal" );
+
+	assert.notStrictEqual( a.constant, b.constant, "Constants: not equal" );
+	assert.strictEqual( a.constant, c.constant, "Constants: equal" );
+
+	assert.notOk( a.equals( b ), "Planes: not equal" );
+	assert.notOk( a.equals( c ), "Planes: not equal" );
+
+	a.copy( b );
+	assert.ok( a.normal.equals( b.normal ), "Normals after copy(): equal" );
+	assert.strictEqual( a.constant, b.constant, "Constants after copy(): equal" );
+	assert.ok( a.equals( b ), "Planes after copy(): equal" );
+
+} );
+
+QUnit.test( "setFromCoplanarPoints", function ( assert ) {
+
+	var a = new THREE.Plane();
+	var v1 = new THREE.Vector3( 2.0, 0.5, 0.25 );
+	var v2 = new THREE.Vector3( 2.0, - 0.5, 1.25 );
+	var v3 = new THREE.Vector3( 2.0, - 3.5, 2.2 );
+	var normal = new THREE.Vector3( 1, 0, 0 );
+	var constant = - 2;
+
+	a.setFromCoplanarPoints( v1, v2, v3 );
+
+	assert.ok( a.normal.equals( normal ), "Check normal" );
+	assert.strictEqual( a.constant, constant, "Check constant" );
+
+} );

+ 127 - 0
test/unit/src/math/Quaternion.js

@@ -348,3 +348,130 @@ QUnit.test( "slerpFlat" , function( assert ) {
 	slerpTestSkeleton( doSlerpArray, Number.EPSILON, assert );
 
 } );
+
+QUnit.test( "properties", function ( assert ) {
+
+	assert.expect( 8 );
+
+	var a = new THREE.Quaternion();
+	a.onChange( function () {
+
+		assert.ok( true, "onChange called" );
+
+	} );
+
+	a.x = x;
+	a.y = y;
+	a.z = z;
+	a.w = w;
+
+	assert.strictEqual( a.x, x, "Check x" );
+	assert.strictEqual( a.y, y, "Check y" );
+	assert.strictEqual( a.z, z, "Check z" );
+	assert.strictEqual( a.w, w, "Check w" );
+
+} );
+
+QUnit.test( "inverse", function ( assert ) {
+
+	assert.expect( 6 );
+
+	var a = new THREE.Quaternion( x, y, z, w );
+	var inverted = new THREE.Quaternion( - 0.2721655269759087, - 0.408248290463863, - 0.5443310539518174, 0.6804138174397717 );
+	a.onChange( function () {
+
+		assert.ok( true, "onChange called" );
+
+	} );
+
+	a.inverse();
+	assert.ok( Math.abs( a.x - inverted.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - inverted.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - inverted.z ) <= eps, "Check z" );
+	assert.ok( Math.abs( a.w - inverted.w ) <= eps, "Check w" );
+
+} );
+
+QUnit.test( "premultiply", function ( assert ) {
+
+	var a = new THREE.Quaternion( x, y, z, w );
+	var b = new THREE.Quaternion( 2 * x, - y, - 2 * z, w );
+	var expected = new THREE.Quaternion( 42, - 32, - 2, 58 );
+
+	a.premultiply( b );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "Check w" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var a = new THREE.Quaternion( x, y, z, w );
+
+	var array = a.toArray();
+	assert.strictEqual( array[ 0 ], x, "No array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "No array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "No array, no offset: check z" );
+	assert.strictEqual( array[ 3 ], w, "No array, no offset: check w" );
+
+	array = [];
+	a.toArray( array );
+	assert.strictEqual( array[ 0 ], x, "With array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "With array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "With array, no offset: check z" );
+	assert.strictEqual( array[ 3 ], w, "With array, no offset: check w" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.strictEqual( array[ 0 ], undefined, "With array and offset: check [0]" );
+	assert.strictEqual( array[ 1 ], x, "With array and offset: check x" );
+	assert.strictEqual( array[ 2 ], y, "With array and offset: check y" );
+	assert.strictEqual( array[ 3 ], z, "With array and offset: check z" );
+	assert.strictEqual( array[ 4 ], w, "With array and offset: check w" );
+
+} );
+
+QUnit.test( "setFromUnitVectors", function ( assert ) {
+
+	var a = new THREE.Quaternion();
+	var b = new THREE.Vector3( 1, 0, 0 );
+	var c = new THREE.Vector3( 0, 1, 0 );
+	var expected = new THREE.Quaternion( 0, 0, Math.sqrt( 2 ) / 2, Math.sqrt( 2 ) / 2 );
+
+	a.setFromUnitVectors( b, c );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "Check w" );
+
+} );
+
+QUnit.test( "setFromRotationMatrix", function ( assert ) {
+
+	// contrived examples purely to please the god of code coverage...
+	// match conditions in various 'else [if]' blocks
+
+	var a = new THREE.Quaternion();
+	var q = new THREE.Quaternion( - 9, - 2, 3, - 4 ).normalize();
+	var m = new THREE.Matrix4().makeRotationFromQuaternion( q );
+	var expected = new THREE.Vector4( 0.8581163303210332, 0.19069251784911848, - 0.2860387767736777, 0.38138503569823695 );
+
+	a.setFromRotationMatrix( m );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "m11 > m22 && m11 > m33: check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "m11 > m22 && m11 > m33: check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "m11 > m22 && m11 > m33: check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "m11 > m22 && m11 > m33: check w" );
+
+	q = new THREE.Quaternion( - 1, - 2, 1, - 1 ).normalize();
+	m.makeRotationFromQuaternion( q );
+	expected = new THREE.Vector4( 0.37796447300922714, 0.7559289460184544, - 0.37796447300922714, 0.37796447300922714 );
+
+	a.setFromRotationMatrix( m );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "m22 > m33: check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "m22 > m33: check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "m22 > m33: check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "m22 > m33: check w" );
+
+} );

+ 60 - 0
test/unit/src/math/Ray.js

@@ -329,3 +329,63 @@ QUnit.test( "intersectBox" , function( assert ) {
 	assert.ok( f.intersectBox(box) == null, "Passed!" );
 
 });
+
+QUnit.test( "lookAt", function ( assert ) {
+
+	var a = new THREE.Ray( two3.clone(), one3.clone() );
+	var target = one3.clone();
+	var expected = target.sub( two3 ).normalize();
+
+	a.lookAt( target );
+	assert.ok( a.direction.equals( expected ), "Check if we're looking in the right direction" );
+
+} );
+
+QUnit.test( "intersectTriangle", function ( assert ) {
+
+	var ray = new THREE.Ray();
+	var a = new THREE.Vector3( 1, 1, 0 );
+	var b = new THREE.Vector3( 0, 1, 1 );
+	var c = new THREE.Vector3( 1, 0, 1 );
+	var intersect;
+
+	// DdN == 0
+	ray.set( ray.origin, zero3.clone() );
+	intersect = ray.intersectTriangle( a, b, c, false );
+	assert.strictEqual( intersect, null, "No intersection if direction == zero" );
+
+	// DdN > 0, backfaceCulling = true
+	ray.set( ray.origin, one3.clone() );
+	intersect = ray.intersectTriangle( a, b, c, true );
+	assert.strictEqual( intersect, null, "No intersection with backside faces if backfaceCulling is true" );
+
+	// DdN > 0
+	ray.set( ray.origin, one3.clone() );
+	intersect = ray.intersectTriangle( a, b, c, false );
+	assert.ok( Math.abs( intersect.x - 2 / 3 ) <= eps, "Successful intersection: check x" );
+	assert.ok( Math.abs( intersect.y - 2 / 3 ) <= eps, "Successful intersection: check y" );
+	assert.ok( Math.abs( intersect.z - 2 / 3 ) <= eps, "Successful intersection: check z" );
+
+	// DdN > 0, DdQxE2 < 0
+	b.multiplyScalar( - 1 );
+	intersect = ray.intersectTriangle( a, b, c, false );
+	assert.strictEqual( intersect, null, "No intersection" );
+
+	// DdN > 0, DdE1xQ < 0
+	a.multiplyScalar( - 1 );
+	intersect = ray.intersectTriangle( a, b, c, false );
+	assert.strictEqual( intersect, null, "No intersection" );
+
+	// DdN > 0, DdQxE2 + DdE1xQ > DdN
+	b.multiplyScalar( - 1 );
+	intersect = ray.intersectTriangle( a, b, c, false );
+	assert.strictEqual( intersect, null, "No intersection" );
+
+	// DdN < 0, QdN < 0
+	a.multiplyScalar( - 1 );
+	b.multiplyScalar( - 1 );
+	ray.direction.multiplyScalar( - 1 );
+	intersect = ray.intersectTriangle( a, b, c, false );
+	assert.strictEqual( intersect, null, "No intersection when looking in the wrong direction" );
+
+} );

+ 60 - 0
test/unit/src/math/Sphere.js

@@ -110,3 +110,63 @@ QUnit.test( "translate" , function( assert ) {
 	a.translate( one3.clone().negate() );
 	assert.ok( a.center.equals( zero3 ), "Passed!" );
 });
+
+QUnit.test( "setFromPoints", function ( assert ) {
+
+	var a = new THREE.Sphere();
+	var expectedCenter = new THREE.Vector3( 0.9330126941204071, 0, 0 );
+	var expectedRadius = 1.3676668773461689;
+	var optionalCenter = new THREE.Vector3( 1, 1, 1 );
+	var points = [
+		new THREE.Vector3( 1, 1, 0 ), new THREE.Vector3( 1, 1, 0 ),
+		new THREE.Vector3( 1, 1, 0 ), new THREE.Vector3( 1, 1, 0 ),
+		new THREE.Vector3( 1, 1, 0 ), new THREE.Vector3( 0.8660253882408142, 0.5, 0 ),
+		new THREE.Vector3( - 0, 0.5, 0.8660253882408142 ), new THREE.Vector3( 1.8660253882408142, 0.5, 0 ),
+		new THREE.Vector3( 0, 0.5, - 0.8660253882408142 ), new THREE.Vector3( 0.8660253882408142, 0.5, - 0 ),
+		new THREE.Vector3( 0.8660253882408142, - 0.5, 0 ), new THREE.Vector3( - 0, - 0.5, 0.8660253882408142 ),
+		new THREE.Vector3( 1.8660253882408142, - 0.5, 0 ), new THREE.Vector3( 0, - 0.5, - 0.8660253882408142 ),
+		new THREE.Vector3( 0.8660253882408142, - 0.5, - 0 ), new THREE.Vector3( - 0, - 1, 0 ),
+		new THREE.Vector3( - 0, - 1, 0 ), new THREE.Vector3( 0, - 1, 0 ),
+		new THREE.Vector3( 0, - 1, - 0 ), new THREE.Vector3( - 0, - 1, - 0 ),
+	];
+
+	a.setFromPoints( points );
+	assert.ok( Math.abs( a.center.x - expectedCenter.x ) <= eps, "Default center: check center.x" );
+	assert.ok( Math.abs( a.center.y - expectedCenter.y ) <= eps, "Default center: check center.y" );
+	assert.ok( Math.abs( a.center.z - expectedCenter.z ) <= eps, "Default center: check center.z" );
+	assert.ok( Math.abs( a.radius - expectedRadius ) <= eps, "Default center: check radius" );
+
+	expectedRadius = 2.5946195770400102;
+	a.setFromPoints( points, optionalCenter );
+	assert.ok( Math.abs( a.center.x - optionalCenter.x ) <= eps, "Optional center: check center.x" );
+	assert.ok( Math.abs( a.center.y - optionalCenter.y ) <= eps, "Optional center: check center.y" );
+	assert.ok( Math.abs( a.center.z - optionalCenter.z ) <= eps, "Optional center: check center.z" );
+	assert.ok( Math.abs( a.radius - expectedRadius ) <= eps, "Optional center: check radius" );
+
+} );
+
+QUnit.test( "intersectsBox", function ( assert ) {
+
+	var a = new THREE.Sphere();
+	var b = new THREE.Sphere( new THREE.Vector3( - 5, - 5, - 5 ) );
+	var box = new THREE.Box3( zero3, one3 );
+
+	assert.strictEqual( a.intersectsBox( box ), true, "Check default sphere" );
+	assert.strictEqual( b.intersectsBox( box ), false, "Check shifted sphere" );
+
+} );
+
+QUnit.test( "equals", function ( assert ) {
+
+	var a = new THREE.Sphere();
+	var b = new THREE.Sphere( new THREE.Vector3( 1, 0, 0 ) );
+	var c = new THREE.Sphere( new THREE.Vector3( 1, 0, 0 ), 1.0 );
+
+	assert.strictEqual( a.equals( b ), false, "a does not equal b" );
+	assert.strictEqual( a.equals( c ), false, "a does not equal c" );
+	assert.strictEqual( b.equals( c ), false, "b does not equal c" );
+
+	a.copy( b );
+	assert.strictEqual( a.equals( b ), true, "a equals b after copy()" );
+
+} );

+ 104 - 3
test/unit/src/math/Spherical.js

@@ -1,6 +1,107 @@
 /**
- * @author TristanVALCKE / https://github.com/TristanVALCKE
+ * @author moraxy / https://github.com/moraxy
  */
 
-//Todo
-console.warn("Todo: Unit tests of Spherical")
+QUnit.module( "Spherical" );
+
+QUnit.test( "constructor", function ( assert ) {
+
+	var a = new THREE.Spherical();
+	var radius = 10.0;
+	var phi = Math.acos( - 0.5 );
+	var theta = Math.sqrt( Math.PI ) * phi;
+
+	assert.strictEqual( a.radius, 1.0, "Default values: check radius" );
+	assert.strictEqual( a.phi, 0, "Default values: check phi" );
+	assert.strictEqual( a.theta, 0, "Default values: check theta" );
+
+	a = new THREE.Spherical( radius, phi, theta );
+	assert.strictEqual( a.radius, radius, "Custom values: check radius" );
+	assert.strictEqual( a.phi, phi, "Custom values: check phi" );
+	assert.strictEqual( a.theta, theta, "Custom values: check theta" );
+
+} );
+
+QUnit.test( "set", function ( assert ) {
+
+	var a = new THREE.Spherical();
+	var radius = 10.0;
+	var phi = Math.acos( - 0.5 );
+	var theta = Math.sqrt( Math.PI ) * phi;
+
+	a.set( radius, phi, theta );
+	assert.strictEqual( a.radius, radius, "Check radius" );
+	assert.strictEqual( a.phi, phi, "Check phi" );
+	assert.strictEqual( a.theta, theta, "Check theta" );
+
+} );
+
+QUnit.test( "clone", function ( assert ) {
+
+	var radius = 10.0;
+	var phi = Math.acos( - 0.5 );
+	var theta = Math.sqrt( Math.PI ) * phi;
+	var a = new THREE.Spherical( radius, phi, theta );
+	var b = a.clone();
+
+	assert.propEqual( a, b, "Check a and b are equal after clone()" );
+
+	a.radius = 2.0;
+	assert.notPropEqual( a, b, "Check a and b are not equal after modification" );
+
+} );
+
+QUnit.test( "copy", function ( assert ) {
+
+	var radius = 10.0;
+	var phi = Math.acos( - 0.5 );
+	var theta = Math.sqrt( Math.PI ) * phi;
+	var a = new THREE.Spherical( radius, phi, theta );
+	var b = new THREE.Spherical().copy( a );
+
+	assert.propEqual( a, b, "Check a and b are equal after copy()" );
+
+	a.radius = 2.0;
+	assert.notPropEqual( a, b, "Check a and b are not equal after modification" );
+
+} );
+
+QUnit.test( "makeSafe", function ( assert ) {
+
+	var EPS = 0.000001; // from source
+	var tooLow = 0.0;
+	var tooHigh = Math.PI;
+	var justRight = 1.5;
+	var a = new THREE.Spherical( 1, tooLow, 0 );
+
+	a.makeSafe();
+	assert.strictEqual( a.phi, EPS, "Check if small values are set to EPS" );
+
+	a.set( 1, tooHigh, 0 );
+	a.makeSafe();
+	assert.strictEqual( a.phi, Math.PI - EPS, "Check if high values are set to (Math.PI - EPS)" );
+
+	a.set( 1, justRight, 0 );
+	a.makeSafe();
+	assert.strictEqual( a.phi, justRight, "Check that valid values don't get changed" );
+
+} );
+
+QUnit.test( "setFromVector3", function ( assert ) {
+
+	var a = new THREE.Spherical( 1, 1, 1 );
+	var b = new THREE.Vector3( 0, 0, 0 );
+	var c = new THREE.Vector3( Math.PI, 1, - Math.PI );
+	var expected = new THREE.Spherical( 4.554032147688322, 1.3494066171539107, 2.356194490192345 );
+
+	a.setFromVector3( b );
+	assert.strictEqual( a.radius, 0, "Zero-length vector: check radius" );
+	assert.strictEqual( a.phi, 0, "Zero-length vector: check phi" );
+	assert.strictEqual( a.theta, 0, "Zero-length vector: check theta" );
+
+	a.setFromVector3( c );
+	assert.ok( Math.abs( a.radius - expected.radius ) <= eps, "Normal vector: check radius" );
+	assert.ok( Math.abs( a.phi - expected.phi ) <= eps, "Normal vector: check phi" );
+	assert.ok( Math.abs( a.theta - expected.theta ) <= eps, "Normal vector: check theta" );
+
+} );

+ 28 - 0
test/unit/src/math/Triangle.js

@@ -188,3 +188,31 @@ QUnit.test( "closestPointToPoint" , function( assert ) {
 	b0 = a.closestPointToPoint( new THREE.Vector3( 0, -2, 0 ) );
 	assert.ok( b0.equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
 });
+
+QUnit.test( "equals", function ( assert ) {
+
+	var a = new THREE.Triangle(
+		new THREE.Vector3( 1, 0, 0 ),
+		new THREE.Vector3( 0, 1, 0 ),
+		new THREE.Vector3( 0, 0, 1 )
+	);
+	var b = new THREE.Triangle(
+		new THREE.Vector3( 0, 0, 1 ),
+		new THREE.Vector3( 0, 1, 0 ),
+		new THREE.Vector3( 1, 0, 0 )
+	);
+	var c = new THREE.Triangle(
+		new THREE.Vector3( - 1, 0, 0 ),
+		new THREE.Vector3( 0, 1, 0 ),
+		new THREE.Vector3( 0, 0, 1 )
+	);
+
+	assert.ok( a.equals( a ), "a equals a" );
+	assert.notOk( a.equals( b ), "a does not equal b" );
+	assert.notOk( a.equals( c ), "a does not equal c" );
+	assert.notOk( b.equals( c ), "b does not equal c" );
+
+	a.copy( b );
+	assert.ok( a.equals( a ), "a equals b after copy()" );
+
+} );

+ 164 - 0
test/unit/src/math/Vector2.js

@@ -262,3 +262,167 @@ QUnit.test( "equals" , function( assert ) {
 	assert.ok( a.equals( b ), "Passed!" );
 	assert.ok( b.equals( a ), "Passed!" );
 });
+
+QUnit.test( "setComponent/getComponent exceptions", function ( assert ) {
+
+	var a = new THREE.Vector2( 0, 0 );
+
+	assert.throws(
+		function () {
+
+			a.setComponent( 2, 0 );
+
+		},
+		/index is out of range/,
+		"setComponent with an out of range index throws Error"
+	);
+	assert.throws(
+		function () {
+
+			a.getComponent( 2 );
+
+		},
+		/index is out of range/,
+		"getComponent with an out of range index throws Error"
+	);
+
+} );
+
+QUnit.test( "lengthManhattan", function ( assert ) {
+
+	var a = new THREE.Vector2( x, 0 );
+	var b = new THREE.Vector2( 0, - y );
+	var c = new THREE.Vector2();
+
+	assert.strictEqual( a.lengthManhattan(), x, "Positive component" );
+	assert.strictEqual( b.lengthManhattan(), y, "Negative component" );
+	assert.strictEqual( c.lengthManhattan(), 0, "Empty component" );
+
+	a.set( x, y );
+	assert.strictEqual( a.lengthManhattan(), Math.abs( x ) + Math.abs( y ), "Two components" );
+
+} );
+
+QUnit.test( "properties", function ( assert ) {
+
+	var a = new THREE.Vector2( 0, 0 );
+	var width = 100;
+	var height = 200;
+
+	assert.ok( a.width = width, "Set width" );
+	assert.ok( a.height = height, "Set height" );
+
+	a.set( width, height );
+	assert.strictEqual( a.width, width, "Get width" );
+	assert.strictEqual( a.height, height, "Get height" );
+
+} );
+
+QUnit.test( "setScalar/addScalar/subScalar", function ( assert ) {
+
+	var a = new THREE.Vector2( 1, 1 );
+	var s = 3;
+
+	a.setScalar( s );
+	assert.strictEqual( a.x, s, "setScalar: check x" );
+	assert.strictEqual( a.y, s, "setScalar: check y" );
+
+	a.addScalar( s );
+	assert.strictEqual( a.x, 2 * s, "addScalar: check x" );
+	assert.strictEqual( a.y, 2 * s, "addScalar: check y" );
+
+	a.subScalar( 2 * s );
+	assert.strictEqual( a.x, 0, "subScalar: check x" );
+	assert.strictEqual( a.y, 0, "subScalar: check y" );
+
+} );
+
+QUnit.test( "addScaledVector", function ( assert ) {
+
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2( 2, 3 );
+	var s = 3;
+
+	a.addScaledVector( b, s );
+	assert.strictEqual( a.x, x + b.x * s, "Check x" );
+	assert.strictEqual( a.y, y + b.y * s, "Check y" );
+
+} );
+
+QUnit.test( "multiply/divide", function ( assert ) {
+
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2( 2 * x, 2 * y );
+	var c = new THREE.Vector2( 4 * x, 4 * y );
+
+	a.multiply( b );
+	assert.strictEqual( a.x, x * b.x, "multiply: check x" );
+	assert.strictEqual( a.y, y * b.y, "multiply: check y" );
+
+	b.divide( c );
+	assert.strictEqual( b.x, 0.5, "divide: check x" );
+	assert.strictEqual( b.y, 0.5, "divide: check y" );
+
+} );
+
+QUnit.test( "applyMatrix3", function ( assert ) {
+
+	var a = new THREE.Vector2( x, y );
+	var m = new THREE.Matrix3().set( 2, 3, 5, 7, 11, 13, 17, 19, 23 );
+
+	a.applyMatrix3( m );
+	assert.strictEqual( a.x, 18, "Check x" );
+	assert.strictEqual( a.y, 60, "Check y" );
+
+} );
+
+QUnit.test( "fromArray", function ( assert ) {
+
+	var a = new THREE.Vector2();
+	var array = [ 1, 2, 3, 4 ];
+
+	a.fromArray( array );
+	assert.strictEqual( a.x, 1, "No offset: check x" );
+	assert.strictEqual( a.y, 2, "No offset: check y" );
+
+	a.fromArray( array, 2 );
+	assert.strictEqual( a.x, 3, "With offset: check x" );
+	assert.strictEqual( a.y, 4, "With offset: check y" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var a = new THREE.Vector2( x, y );
+
+	var array = a.toArray();
+	assert.strictEqual( array[ 0 ], x, "No array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "No array, no offset: check y" );
+
+	array = [];
+	a.toArray( array );
+	assert.strictEqual( array[ 0 ], x, "With array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "With array, no offset: check y" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.strictEqual( array[ 0 ], undefined, "With array and offset: check [0]" );
+	assert.strictEqual( array[ 1 ], x, "With array and offset: check x" );
+	assert.strictEqual( array[ 2 ], y, "With array and offset: check y" );
+
+} );
+
+QUnit.test( "fromBufferAttribute", function ( assert ) {
+
+	var a = new THREE.Vector2();
+	var attr = new THREE.BufferAttribute( new Float32Array( [ 1, 2, 3, 4 ] ), 2 );
+
+	a.fromBufferAttribute( attr, 0 );
+	assert.strictEqual( a.x, 1, "Offset 0: check x" );
+	assert.strictEqual( a.y, 2, "Offset 0: check y" );
+
+	a.fromBufferAttribute( attr, 1 );
+	assert.strictEqual( a.x, 3, "Offset 1: check x" );
+	assert.strictEqual( a.y, 4, "Offset 1: check y" );
+
+} );

+ 360 - 0
test/unit/src/math/Vector3.js

@@ -379,3 +379,363 @@ QUnit.test( "applyMatrix4" , function( assert ) {
 	assert.ok( a.z == b.z / b.w, "Passed!" );
 
 });
+
+QUnit.test( "setComponent/getComponent exceptions", function ( assert ) {
+
+	var a = new THREE.Vector3();
+
+	assert.throws(
+		function () {
+
+			a.setComponent( 3, 0 );
+
+		},
+		/index is out of range/,
+		"setComponent with an out of range index throws Error"
+	);
+	assert.throws(
+		function () {
+
+			a.getComponent( 3 );
+
+		},
+		/index is out of range/,
+		"getComponent with an out of range index throws Error"
+	);
+
+} );
+
+QUnit.test( "lengthManhattan", function ( assert ) {
+
+	var a = new THREE.Vector3( x, 0, 0 );
+	var b = new THREE.Vector3( 0, - y, 0 );
+	var c = new THREE.Vector3( 0, 0, z );
+	var d = new THREE.Vector3();
+
+	assert.ok( a.lengthManhattan() == x, "Positive x" );
+	assert.ok( b.lengthManhattan() == y, "Negative y" );
+	assert.ok( c.lengthManhattan() == z, "Positive z" );
+	assert.ok( d.lengthManhattan() == 0, "Empty initialization" );
+
+	a.set( x, y, z );
+	assert.ok( a.lengthManhattan() == Math.abs( x ) + Math.abs( y ) + Math.abs( z ), "All components" );
+
+} );
+
+QUnit.test( "setScalar/addScalar/subScalar", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var s = 3;
+
+	a.setScalar( s );
+	assert.strictEqual( a.x, s, "setScalar: check x" );
+	assert.strictEqual( a.y, s, "setScalar: check y" );
+	assert.strictEqual( a.z, s, "setScalar: check z" );
+
+	a.addScalar( s );
+	assert.strictEqual( a.x, 2 * s, "addScalar: check x" );
+	assert.strictEqual( a.y, 2 * s, "addScalar: check y" );
+	assert.strictEqual( a.z, 2 * s, "addScalar: check z" );
+
+	a.subScalar( 2 * s );
+	assert.strictEqual( a.x, 0, "subScalar: check x" );
+	assert.strictEqual( a.y, 0, "subScalar: check y" );
+	assert.strictEqual( a.z, 0, "subScalar: check z" );
+
+} );
+
+QUnit.test( "addScaledVector", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( 2, 3, 4 );
+	var s = 3;
+
+	a.addScaledVector( b, s );
+	assert.strictEqual( a.x, x + b.x * s, "Check x" );
+	assert.strictEqual( a.y, y + b.y * s, "Check y" );
+	assert.strictEqual( a.z, z + b.z * s, "Check z" );
+
+} );
+
+QUnit.test( "multiply/divide", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( 2 * x, 2 * y, 2 * z );
+	var c = new THREE.Vector3( 4 * x, 4 * y, 4 * z );
+
+	a.multiply( b );
+	assert.strictEqual( a.x, x * b.x, "multiply: check x" );
+	assert.strictEqual( a.y, y * b.y, "multiply: check y" );
+	assert.strictEqual( a.z, z * b.z, "multiply: check z" );
+
+	b.divide( c );
+	assert.ok( Math.abs( b.x - 0.5 ) <= eps, "divide: check z" );
+	assert.ok( Math.abs( b.y - 0.5 ) <= eps, "divide: check z" );
+	assert.ok( Math.abs( b.z - 0.5 ) <= eps, "divide: check z" );
+
+} );
+
+QUnit.test( "multiplyVectors", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( 2, 3, - 5 );
+
+	var c = new THREE.Vector3().multiplyVectors( a, b );
+	assert.strictEqual( c.x, x * 2, "Check x" );
+	assert.strictEqual( c.y, y * 3, "Check y" );
+	assert.strictEqual( c.z, z * - 5, "Check z" );
+
+} );
+
+QUnit.test( "applyMatrix3", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var m = new THREE.Matrix3().set( 2, 3, 5, 7, 11, 13, 17, 19, 23 );
+
+	a.applyMatrix3( m );
+	assert.strictEqual( a.x, 33, "Check x" );
+	assert.strictEqual( a.y, 99, "Check y" );
+	assert.strictEqual( a.z, 183, "Check z" );
+
+} );
+
+QUnit.test( "applyQuaternion", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+
+	a.applyQuaternion( new THREE.Quaternion() );
+	assert.strictEqual( a.x, x, "Identity rotation: check x" );
+	assert.strictEqual( a.y, y, "Identity rotation: check y" );
+	assert.strictEqual( a.z, z, "Identity rotation: check z" );
+
+	a.applyQuaternion( new THREE.Quaternion( x, y, z, w ) );
+	assert.strictEqual( a.x, 108, "Normal rotation: check x" );
+	assert.strictEqual( a.y, 162, "Normal rotation: check y" );
+	assert.strictEqual( a.z, 216, "Normal rotation: check z" );
+
+} );
+
+QUnit.test( "project/unproject", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var camera = new THREE.PerspectiveCamera( 75, 16 / 9, 0.1, 300.0 );
+	var projected = new THREE.Vector3( - 0.36653213611158914, - 0.9774190296309043, 1.0506835611870624 );
+
+	a.project( camera );
+	assert.ok( Math.abs( a.x - projected.x ) <= eps, "project: check x" );
+	assert.ok( Math.abs( a.y - projected.y ) <= eps, "project: check y" );
+	assert.ok( Math.abs( a.z - projected.z ) <= eps, "project: check z" );
+
+	a.unproject( camera );
+	assert.ok( Math.abs( a.x - x ) <= eps, "unproject: check x" );
+	assert.ok( Math.abs( a.y - y ) <= eps, "unproject: check y" );
+	assert.ok( Math.abs( a.z - z ) <= eps, "unproject: check z" );
+
+} );
+
+QUnit.test( "transformDirection", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var m = new THREE.Matrix4();
+	var transformed = new THREE.Vector3( 0.3713906763541037, 0.5570860145311556, 0.7427813527082074 );
+
+	a.transformDirection( m );
+	assert.ok( Math.abs( a.x - transformed.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - transformed.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - transformed.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "applyEuler", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var euler = new THREE.Euler( 90, - 45, 0 );
+	var expected = new THREE.Vector3( - 2.352970120501014, - 4.7441750936226645, 0.9779234597246458 );
+
+	a.applyEuler( euler );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "applyAxisAngle", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var axis = new THREE.Vector3( 0, 1, 0 );
+	var angle = Math.PI / 4.0;
+	var expected = new THREE.Vector3( 3 * Math.sqrt( 2 ), 3, Math.sqrt( 2 ) );
+
+	a.applyAxisAngle( axis, angle );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "clampScalar", function ( assert ) {
+
+	var a = new THREE.Vector3( - 0.01, 0.5, 1.5 );
+	var clamped = new THREE.Vector3( 0.1, 0.5, 1.0 );
+
+	a.clampScalar( 0.1, 1.0 );
+	assert.ok( Math.abs( a.x - clamped.x ) <= 0.001, "Check x" );
+	assert.ok( Math.abs( a.y - clamped.y ) <= 0.001, "Check y" );
+	assert.ok( Math.abs( a.z - clamped.z ) <= 0.001, "Check z" );
+
+} );
+
+QUnit.test( "cross", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( 2 * x, - y, 0.5 * z );
+	var crossed = new THREE.Vector3( 18, 12, - 18 );
+
+	a.cross( b );
+	assert.ok( Math.abs( a.x - crossed.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - crossed.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - crossed.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "crossVectors", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( x, - y, z );
+	var c = new THREE.Vector3();
+	var crossed = new THREE.Vector3( 24, 0, - 12 );
+
+	c.crossVectors( a, b );
+	assert.ok( Math.abs( c.x - crossed.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( c.y - crossed.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( c.z - crossed.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "setFromSpherical", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var phi = Math.acos( - 0.5 );
+	var theta = Math.sqrt( Math.PI ) * phi;
+	var sph = new THREE.Spherical( 10, phi, theta );
+	var expected = new THREE.Vector3( - 4.677914006701843, - 5, - 7.288149322420796 );
+
+	a.setFromSpherical( sph );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "setFromCylindrical", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var cyl = new THREE.Cylindrical( 10, Math.PI * 0.125, 20 );
+	var expected = new THREE.Vector3( 3.826834323650898, 20, 9.238795325112868 );
+
+	a.setFromCylindrical( cyl );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "setFromMatrixPosition", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var m = new THREE.Matrix4().set( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 );
+
+	a.setFromMatrixPosition( m );
+	assert.strictEqual( a.x, 7, "Check x" );
+	assert.strictEqual( a.y, 19, "Check y" );
+	assert.strictEqual( a.z, 37, "Check z" );
+
+} );
+
+QUnit.test( "setFromMatrixScale", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var m = new THREE.Matrix4().set( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 );
+	var expected = new THREE.Vector3( 25.573423705088842, 31.921779399024736, 35.70714214271425 );
+
+	a.setFromMatrixScale( m );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Check z" );
+
+} );
+
+QUnit.test( "setFromMatrixColumn", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var m = new THREE.Matrix4().set( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 );
+
+	a.setFromMatrixColumn( m, 0 );
+	assert.strictEqual( a.x, 2, "Index 0: check x" );
+	assert.strictEqual( a.y, 11, "Index 0: check y" );
+	assert.strictEqual( a.z, 23, "Index 0: check z" );
+
+	a.setFromMatrixColumn( m, 2 );
+	assert.strictEqual( a.x, 5, "Index 2: check x" );
+	assert.strictEqual( a.y, 17, "Index 2: check y" );
+	assert.strictEqual( a.z, 31, "Index 2: check z" );
+
+} );
+
+QUnit.test( "fromArray", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var array = [ 1, 2, 3, 4, 5, 6 ];
+
+	a.fromArray( array );
+	assert.strictEqual( a.x, 1, "No offset: check x" );
+	assert.strictEqual( a.y, 2, "No offset: check y" );
+	assert.strictEqual( a.z, 3, "No offset: check z" );
+
+	a.fromArray( array, 3 );
+	assert.strictEqual( a.x, 4, "With offset: check x" );
+	assert.strictEqual( a.y, 5, "With offset: check y" );
+	assert.strictEqual( a.z, 6, "With offset: check z" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var a = new THREE.Vector3( x, y, z );
+
+	var array = a.toArray();
+	assert.strictEqual( array[ 0 ], x, "No array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "No array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "No array, no offset: check z" );
+
+	array = [];
+	a.toArray( array );
+	assert.strictEqual( array[ 0 ], x, "With array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "With array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "With array, no offset: check z" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.strictEqual( array[ 0 ], undefined, "With array and offset: check [0]" );
+	assert.strictEqual( array[ 1 ], x, "With array and offset: check x" );
+	assert.strictEqual( array[ 2 ], y, "With array and offset: check y" );
+	assert.strictEqual( array[ 3 ], z, "With array and offset: check z" );
+
+} );
+
+QUnit.test( "fromBufferAttribute", function ( assert ) {
+
+	var a = new THREE.Vector3();
+	var attr = new THREE.BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6 ] ), 3 );
+
+	a.fromBufferAttribute( attr, 0 );
+	assert.strictEqual( a.x, 1, "Offset 0: check x" );
+	assert.strictEqual( a.y, 2, "Offset 0: check y" );
+	assert.strictEqual( a.z, 3, "Offset 0: check z" );
+
+	a.fromBufferAttribute( attr, 1 );
+	assert.strictEqual( a.x, 4, "Offset 1: check x" );
+	assert.strictEqual( a.y, 5, "Offset 1: check y" );
+	assert.strictEqual( a.z, 6, "Offset 1: check z" );
+
+} );

+ 208 - 0
test/unit/src/math/Vector4.js

@@ -318,3 +318,211 @@ QUnit.test( "equals" , function( assert ) {
 	assert.ok( a.equals( b ), "Passed!" );
 	assert.ok( b.equals( a ), "Passed!" );
 });
+
+QUnit.test( "setComponent/getComponent exceptions", function ( assert ) {
+
+	var a = new THREE.Vector4();
+
+	assert.throws(
+		function () {
+
+			a.setComponent( 4, 0 );
+
+		},
+		/index is out of range/,
+		"setComponent with an out of range index throws Error"
+	);
+	assert.throws(
+		function () {
+
+			a.getComponent( 4 );
+
+		},
+		/index is out of range/,
+		"getComponent with an out of range index throws Error"
+	);
+
+} );
+
+QUnit.test( "lengthManhattan", function ( assert ) {
+
+	var a = new THREE.Vector4( x, 0, 0, 0 );
+	var b = new THREE.Vector4( 0, - y, 0, 0 );
+	var c = new THREE.Vector4( 0, 0, z, 0 );
+	var d = new THREE.Vector4( 0, 0, 0, w );
+	var e = new THREE.Vector4( 0, 0, 0, 0 );
+
+	assert.ok( a.lengthManhattan() == x, "Positive x" );
+	assert.ok( b.lengthManhattan() == y, "Negative y" );
+	assert.ok( c.lengthManhattan() == z, "Positive z" );
+	assert.ok( d.lengthManhattan() == w, "Positive w" );
+	assert.ok( e.lengthManhattan() == 0, "Empty initialization" );
+
+	a.set( x, y, z, w );
+	assert.ok(
+		a.lengthManhattan() == Math.abs( x ) + Math.abs( y ) + Math.abs( z ) + Math.abs( w ),
+		"All components"
+	);
+
+} );
+
+QUnit.test( "setScalar/addScalar/subScalar", function ( assert ) {
+
+	var a = new THREE.Vector4();
+	var s = 3;
+
+	a.setScalar( s );
+	assert.strictEqual( a.x, s, "setScalar: check x" );
+	assert.strictEqual( a.y, s, "setScalar: check y" );
+	assert.strictEqual( a.z, s, "setScalar: check z" );
+	assert.strictEqual( a.w, s, "setScalar: check w" );
+
+	a.addScalar( s );
+	assert.strictEqual( a.x, 2 * s, "addScalar: check x" );
+	assert.strictEqual( a.y, 2 * s, "addScalar: check y" );
+	assert.strictEqual( a.z, 2 * s, "addScalar: check z" );
+	assert.strictEqual( a.w, 2 * s, "addScalar: check w" );
+
+	a.subScalar( 2 * s );
+	assert.strictEqual( a.x, 0, "subScalar: check x" );
+	assert.strictEqual( a.y, 0, "subScalar: check y" );
+	assert.strictEqual( a.z, 0, "subScalar: check z" );
+	assert.strictEqual( a.w, 0, "subScalar: check w" );
+
+} );
+
+QUnit.test( "addScaledVector", function ( assert ) {
+
+	var a = new THREE.Vector4( x, y, z, w );
+	var b = new THREE.Vector4( 6, 7, 8, 9 );
+	var s = 3;
+
+	a.addScaledVector( b, s );
+	assert.strictEqual( a.x, x + b.x * s, "Check x" );
+	assert.strictEqual( a.y, y + b.y * s, "Check y" );
+	assert.strictEqual( a.z, z + b.z * s, "Check z" );
+	assert.strictEqual( a.w, w + b.w * s, "Check w" );
+
+} );
+
+QUnit.test( "applyMatrix4", function ( assert ) {
+
+	var a = new THREE.Vector4( x, y, z, w );
+	var m = new THREE.Matrix4().makeRotationX( Math.PI );
+	var expected = new THREE.Vector4( 2, - 3, - 4, 5 );
+
+	a.applyMatrix4( m );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Rotation matrix: check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Rotation matrix: check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Rotation matrix: check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "Rotation matrix: check w" );
+
+	a.set( x, y, z, w );
+	m.makeTranslation( 5, 7, 11 );
+	expected.set( 27, 38, 59, 5 );
+
+	a.applyMatrix4( m );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Translation matrix: check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Translation matrix: check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Translation matrix: check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "Translation matrix: check w" );
+
+	a.set( x, y, z, w );
+	m.set( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0 );
+	expected.set( 2, 3, 4, 4 );
+
+	a.applyMatrix4( m );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Custom matrix: check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Custom matrix: check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Custom matrix: check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "Custom matrix: check w" );
+
+	a.set( x, y, z, w );
+	m.set( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 );
+	expected.set( 68, 224, 442, 664 );
+
+	a.applyMatrix4( m );
+	assert.ok( Math.abs( a.x - expected.x ) <= eps, "Bogus matrix: check x" );
+	assert.ok( Math.abs( a.y - expected.y ) <= eps, "Bogus matrix: check y" );
+	assert.ok( Math.abs( a.z - expected.z ) <= eps, "Bogus matrix: check z" );
+	assert.ok( Math.abs( a.w - expected.w ) <= eps, "Bogus matrix: check w" );
+
+} );
+
+QUnit.test( "clampScalar", function ( assert ) {
+
+	var a = new THREE.Vector4( - 0.1, 0.01, 0.5, 1.5 );
+	var clamped = new THREE.Vector4( 0.1, 0.1, 0.5, 1.0 );
+
+	a.clampScalar( 0.1, 1.0 );
+	assert.ok( Math.abs( a.x - clamped.x ) <= eps, "Check x" );
+	assert.ok( Math.abs( a.y - clamped.y ) <= eps, "Check y" );
+	assert.ok( Math.abs( a.z - clamped.z ) <= eps, "Check z" );
+	assert.ok( Math.abs( a.w - clamped.w ) <= eps, "Check w" );
+
+} );
+
+QUnit.test( "fromArray", function ( assert ) {
+
+	var a = new THREE.Vector4();
+	var array = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
+
+	a.fromArray( array );
+	assert.strictEqual( a.x, 1, "No offset: check x" );
+	assert.strictEqual( a.y, 2, "No offset: check y" );
+	assert.strictEqual( a.z, 3, "No offset: check z" );
+	assert.strictEqual( a.w, 4, "No offset: check w" );
+
+	a.fromArray( array, 4 );
+	assert.strictEqual( a.x, 5, "With offset: check x" );
+	assert.strictEqual( a.y, 6, "With offset: check y" );
+	assert.strictEqual( a.z, 7, "With offset: check z" );
+	assert.strictEqual( a.w, 8, "With offset: check w" );
+
+} );
+
+QUnit.test( "toArray", function ( assert ) {
+
+	var a = new THREE.Vector4( x, y, z, w );
+
+	var array = a.toArray();
+	assert.strictEqual( array[ 0 ], x, "No array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "No array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "No array, no offset: check z" );
+	assert.strictEqual( array[ 3 ], w, "No array, no offset: check w" );
+
+	array = [];
+	a.toArray( array );
+	assert.strictEqual( array[ 0 ], x, "With array, no offset: check x" );
+	assert.strictEqual( array[ 1 ], y, "With array, no offset: check y" );
+	assert.strictEqual( array[ 2 ], z, "With array, no offset: check z" );
+	assert.strictEqual( array[ 3 ], w, "With array, no offset: check w" );
+
+	array = [];
+	a.toArray( array, 1 );
+	assert.strictEqual( array[ 0 ], undefined, "With array and offset: check [0]" );
+	assert.strictEqual( array[ 1 ], x, "With array and offset: check x" );
+	assert.strictEqual( array[ 2 ], y, "With array and offset: check y" );
+	assert.strictEqual( array[ 3 ], z, "With array and offset: check z" );
+	assert.strictEqual( array[ 4 ], w, "With array and offset: check w" );
+
+} );
+
+QUnit.test( "fromBufferAttribute", function ( assert ) {
+
+	var a = new THREE.Vector4();
+	var attr = new THREE.BufferAttribute( new Float32Array( [ 1, 2, 3, 4, 5, 6, 7, 8 ] ), 4 );
+
+	a.fromBufferAttribute( attr, 0 );
+	assert.strictEqual( a.x, 1, "Offset 0: check x" );
+	assert.strictEqual( a.y, 2, "Offset 0: check y" );
+	assert.strictEqual( a.z, 3, "Offset 0: check z" );
+	assert.strictEqual( a.w, 4, "Offset 0: check w" );
+
+	a.fromBufferAttribute( attr, 1 );
+	assert.strictEqual( a.x, 5, "Offset 1: check x" );
+	assert.strictEqual( a.y, 6, "Offset 1: check y" );
+	assert.strictEqual( a.z, 7, "Offset 1: check z" );
+	assert.strictEqual( a.w, 8, "Offset 1: check w" );
+
+} );