Przeglądaj źródła

Add math es6 unit tests

Tristan VALCKE 7 lat temu
rodzic
commit
def4c7b8c3

+ 327 - 0
test/unit/src/math/Box2.tests.js

@@ -0,0 +1,327 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Box2 } from '../../../../src/math/Box2';
+import { Vector2 } from '../../../../src/math/Vector2';
+import {
+	negInf2,
+	posInf2,
+	zero2,
+	one2,
+	two2
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Box2', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Box2();
+			assert.ok( a.min.equals( posInf2 ), "Passed!" );
+			assert.ok( a.max.equals( negInf2 ), "Passed!" );
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			assert.ok( a.min.equals( zero2 ), "Passed!" );
+			assert.ok( a.max.equals( zero2 ), "Passed!" );
+
+			var a = new Box2( zero2.clone(), one2.clone() );
+			assert.ok( a.min.equals( zero2 ), "Passed!" );
+			assert.ok( a.max.equals( one2 ), "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Box2();
+
+			a.set( zero2, one2 );
+			assert.ok( a.min.equals( zero2 ), "Passed!" );
+			assert.ok( a.max.equals( one2 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromPoints", ( assert ) => {
+
+			var a = new Box2();
+
+			a.setFromPoints( [ zero2, one2, two2 ] );
+			assert.ok( a.min.equals( zero2 ), "Passed!" );
+			assert.ok( a.max.equals( two2 ), "Passed!" );
+
+			a.setFromPoints( [ one2 ] );
+			assert.ok( a.min.equals( one2 ), "Passed!" );
+			assert.ok( a.max.equals( one2 ), "Passed!" );
+
+			a.setFromPoints( [] );
+			assert.ok( a.isEmpty(), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromCenterAndSize", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), one2.clone() );
+			var b = new Box2().copy( a );
+			assert.ok( b.min.equals( zero2 ), "Passed!" );
+			assert.ok( b.max.equals( one2 ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.min = zero2;
+			a.max = one2;
+			assert.ok( b.min.equals( zero2 ), "Passed!" );
+			assert.ok( b.max.equals( one2 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "empty/makeEmpty", ( assert ) => {
+
+			var a = new Box2();
+
+			assert.ok( a.isEmpty(), "Passed!" );
+
+			var a = new Box2( zero2.clone(), one2.clone() );
+			assert.ok( ! a.isEmpty(), "Passed!" );
+
+			a.makeEmpty();
+			assert.ok( a.isEmpty(), "Passed!" );
+
+		} );
+
+		QUnit.test( "isEmpty", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getCenter", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+
+			assert.ok( a.getCenter().equals( zero2 ), "Passed!" );
+
+			var a = new Box2( zero2, one2 );
+			var midpoint = one2.clone().multiplyScalar( 0.5 );
+			assert.ok( a.getCenter().equals( midpoint ), "Passed!" );
+
+		} );
+
+		QUnit.test( "getSize", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+
+			assert.ok( a.getSize().equals( zero2 ), "Passed!" );
+
+			var a = new Box2( zero2.clone(), one2.clone() );
+			assert.ok( a.getSize().equals( one2 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "expandByPoint", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+
+			a.expandByPoint( zero2 );
+			assert.ok( a.getSize().equals( zero2 ), "Passed!" );
+
+			a.expandByPoint( one2 );
+			assert.ok( a.getSize().equals( one2 ), "Passed!" );
+
+			a.expandByPoint( one2.clone().negate() );
+			assert.ok( a.getSize().equals( one2.clone().multiplyScalar( 2 ) ), "Passed!" );
+			assert.ok( a.getCenter().equals( zero2 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "expandByVector", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+
+			a.expandByVector( zero2 );
+			assert.ok( a.getSize().equals( zero2 ), "Passed!" );
+
+			a.expandByVector( one2 );
+			assert.ok( a.getSize().equals( one2.clone().multiplyScalar( 2 ) ), "Passed!" );
+			assert.ok( a.getCenter().equals( zero2 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "expandByScalar", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+
+			a.expandByScalar( 0 );
+			assert.ok( a.getSize().equals( zero2 ), "Passed!" );
+
+			a.expandByScalar( 1 );
+			assert.ok( a.getSize().equals( one2.clone().multiplyScalar( 2 ) ), "Passed!" );
+			assert.ok( a.getCenter().equals( zero2 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "containsPoint", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+
+			assert.ok( a.containsPoint( zero2 ), "Passed!" );
+			assert.ok( ! a.containsPoint( one2 ), "Passed!" );
+
+			a.expandByScalar( 1 );
+			assert.ok( a.containsPoint( zero2 ), "Passed!" );
+			assert.ok( a.containsPoint( one2 ), "Passed!" );
+			assert.ok( a.containsPoint( one2.clone().negate() ), "Passed!" );
+
+		} );
+
+		QUnit.test( "containsBox", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			var b = new Box2( zero2.clone(), one2.clone() );
+			var c = new Box2( one2.clone().negate(), one2.clone() );
+
+			assert.ok( a.containsBox( a ), "Passed!" );
+			assert.ok( ! a.containsBox( b ), "Passed!" );
+			assert.ok( ! a.containsBox( c ), "Passed!" );
+
+			assert.ok( b.containsBox( a ), "Passed!" );
+			assert.ok( c.containsBox( a ), "Passed!" );
+			assert.ok( ! b.containsBox( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "getParameter", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), one2.clone() );
+			var b = new Box2( one2.clone().negate(), one2.clone() );
+
+			assert.ok( a.getParameter( new Vector2( 0, 0 ) ).equals( new Vector2( 0, 0 ) ), "Passed!" );
+			assert.ok( a.getParameter( new Vector2( 1, 1 ) ).equals( new Vector2( 1, 1 ) ), "Passed!" );
+
+			assert.ok( b.getParameter( new Vector2( - 1, - 1 ) ).equals( new Vector2( 0, 0 ) ), "Passed!" );
+			assert.ok( b.getParameter( new Vector2( 0, 0 ) ).equals( new Vector2( 0.5, 0.5 ) ), "Passed!" );
+			assert.ok( b.getParameter( new Vector2( 1, 1 ) ).equals( new Vector2( 1, 1 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsBox", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			var b = new Box2( zero2.clone(), one2.clone() );
+			var c = new Box2( one2.clone().negate(), one2.clone() );
+
+			assert.ok( a.intersectsBox( a ), "Passed!" );
+			assert.ok( a.intersectsBox( b ), "Passed!" );
+			assert.ok( a.intersectsBox( c ), "Passed!" );
+
+			assert.ok( b.intersectsBox( a ), "Passed!" );
+			assert.ok( c.intersectsBox( a ), "Passed!" );
+			assert.ok( b.intersectsBox( c ), "Passed!" );
+
+			b.translate( new Vector2( 2, 2 ) );
+			assert.ok( ! a.intersectsBox( b ), "Passed!" );
+			assert.ok( ! b.intersectsBox( a ), "Passed!" );
+			assert.ok( ! b.intersectsBox( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clampPoint", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			var b = new Box2( one2.clone().negate(), one2.clone() );
+
+			assert.ok( a.clampPoint( new Vector2( 0, 0 ) ).equals( new Vector2( 0, 0 ) ), "Passed!" );
+			assert.ok( a.clampPoint( new Vector2( 1, 1 ) ).equals( new Vector2( 0, 0 ) ), "Passed!" );
+			assert.ok( a.clampPoint( new Vector2( - 1, - 1 ) ).equals( new Vector2( 0, 0 ) ), "Passed!" );
+
+			assert.ok( b.clampPoint( new Vector2( 2, 2 ) ).equals( new Vector2( 1, 1 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector2( 1, 1 ) ).equals( new Vector2( 1, 1 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector2( 0, 0 ) ).equals( new Vector2( 0, 0 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector2( - 1, - 1 ) ).equals( new Vector2( - 1, - 1 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector2( - 2, - 2 ) ).equals( new Vector2( - 1, - 1 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceToPoint", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			var b = new Box2( one2.clone().negate(), one2.clone() );
+
+			assert.ok( a.distanceToPoint( new Vector2( 0, 0 ) ) == 0, "Passed!" );
+			assert.ok( a.distanceToPoint( new Vector2( 1, 1 ) ) == Math.sqrt( 2 ), "Passed!" );
+			assert.ok( a.distanceToPoint( new Vector2( - 1, - 1 ) ) == Math.sqrt( 2 ), "Passed!" );
+
+			assert.ok( b.distanceToPoint( new Vector2( 2, 2 ) ) == Math.sqrt( 2 ), "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector2( 1, 1 ) ) == 0, "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector2( 0, 0 ) ) == 0, "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector2( - 1, - 1 ) ) == 0, "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector2( - 2, - 2 ) ) == Math.sqrt( 2 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersect", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			var b = new Box2( zero2.clone(), one2.clone() );
+			var c = new Box2( one2.clone().negate(), one2.clone() );
+
+			assert.ok( a.clone().intersect( a ).equals( a ), "Passed!" );
+			assert.ok( a.clone().intersect( b ).equals( a ), "Passed!" );
+			assert.ok( b.clone().intersect( b ).equals( b ), "Passed!" );
+			assert.ok( a.clone().intersect( c ).equals( a ), "Passed!" );
+			assert.ok( b.clone().intersect( c ).equals( b ), "Passed!" );
+			assert.ok( c.clone().intersect( c ).equals( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "union", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			var b = new Box2( zero2.clone(), one2.clone() );
+			var c = new Box2( one2.clone().negate(), one2.clone() );
+
+			assert.ok( a.clone().union( a ).equals( a ), "Passed!" );
+			assert.ok( a.clone().union( b ).equals( b ), "Passed!" );
+			assert.ok( a.clone().union( c ).equals( c ), "Passed!" );
+			assert.ok( b.clone().union( c ).equals( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "translate", ( assert ) => {
+
+			var a = new Box2( zero2.clone(), zero2.clone() );
+			var b = new Box2( zero2.clone(), one2.clone() );
+			var c = new Box2( one2.clone().negate(), one2.clone() );
+			var d = new Box2( one2.clone().negate(), zero2.clone() );
+
+			assert.ok( a.clone().translate( one2 ).equals( new Box2( one2, one2 ) ), "Passed!" );
+			assert.ok( a.clone().translate( one2 ).translate( one2.clone().negate() ).equals( a ), "Passed!" );
+			assert.ok( d.clone().translate( one2 ).equals( b ), "Passed!" );
+			assert.ok( b.clone().translate( one2.clone().negate() ).equals( d ), "Passed!" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 506 - 0
test/unit/src/math/Box3.tests.js

@@ -0,0 +1,506 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Box3 } from '../../../../src/math/Box3';
+import { Sphere } from '../../../../src/math/Sphere';
+import { Plane } from '../../../../src/math/Plane';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Mesh } from '../../../../src/objects/Mesh';
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+import {
+	BoxGeometry,
+	BoxBufferGeometry
+} from '../../../../src/geometries/BoxGeometry';
+import {
+	negInf3,
+	posInf3,
+	zero3,
+	one3,
+	two3
+} from './Constants.tests';
+
+function compareBox( a, b, threshold ) {
+
+	threshold = threshold || 0.0001;
+	return ( a.min.distanceTo( b.min ) < threshold &&
+	a.max.distanceTo( b.max ) < threshold );
+
+}
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Box3', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Box3();
+			assert.ok( a.min.equals( posInf3 ), "Passed!" );
+			assert.ok( a.max.equals( negInf3 ), "Passed!" );
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			assert.ok( a.min.equals( zero3 ), "Passed!" );
+			assert.ok( a.max.equals( zero3 ), "Passed!" );
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			assert.ok( a.min.equals( zero3 ), "Passed!" );
+			assert.ok( a.max.equals( one3 ), "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isBox3", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Box3();
+
+			a.set( zero3, one3 );
+			assert.ok( a.min.equals( zero3 ), "Passed!" );
+			assert.ok( a.max.equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromArray", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setFromBufferAttribute", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var bigger = new BufferAttribute( new Float32Array( [
+				- 2, - 2, - 2, 2, 2, 2, 1.5, 1.5, 1.5, 0, 0, 0
+			] ), 3 );
+			var smaller = new BufferAttribute( new Float32Array( [
+				- 0.5, - 0.5, - 0.5, 0.5, 0.5, 0.5, 0, 0, 0
+			] ), 3 );
+			var newMin = new Vector3( - 2, - 2, - 2 );
+			var newMax = new 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( "setFromPoints", ( assert ) => {
+
+			var a = new Box3();
+
+			a.setFromPoints( [ zero3, one3, two3 ] );
+			assert.ok( a.min.equals( zero3 ), "Passed!" );
+			assert.ok( a.max.equals( two3 ), "Passed!" );
+
+			a.setFromPoints( [ one3 ] );
+			assert.ok( a.min.equals( one3 ), "Passed!" );
+			assert.ok( a.max.equals( one3 ), "Passed!" );
+
+			a.setFromPoints( [] );
+			assert.ok( a.isEmpty(), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromCenterAndSize", ( assert ) => {
+
+			var a = new 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( "setFromObject/BufferGeometry", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var object = new Mesh( new BoxBufferGeometry( 2, 2, 2 ) );
+			var child = new Mesh( new BoxBufferGeometry( 1, 1, 1 ) );
+			object.add( child );
+
+			a.setFromObject( object );
+			assert.ok( a.min.equals( new Vector3( - 1, - 1, - 1 ) ), "Correct new minimum" );
+			assert.ok( a.max.equals( new Vector3( 1, 1, 1 ) ), "Correct new maximum" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var b = new Box3().copy( a );
+			assert.ok( b.min.equals( zero3 ), "Passed!" );
+			assert.ok( b.max.equals( one3 ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.min = zero3;
+			a.max = one3;
+			assert.ok( b.min.equals( zero3 ), "Passed!" );
+			assert.ok( b.max.equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "empty/makeEmpty", ( assert ) => {
+
+			var a = new Box3();
+
+			assert.ok( a.isEmpty(), "Passed!" );
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			assert.ok( ! a.isEmpty(), "Passed!" );
+
+			a.makeEmpty();
+			assert.ok( a.isEmpty(), "Passed!" );
+
+		} );
+
+		QUnit.test( "isEmpty", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getCenter", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+
+			assert.ok( a.getCenter().equals( zero3 ), "Passed!" );
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var midpoint = one3.clone().multiplyScalar( 0.5 );
+			assert.ok( a.getCenter().equals( midpoint ), "Passed!" );
+
+		} );
+
+		QUnit.test( "getSize", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+
+			assert.ok( a.getSize().equals( zero3 ), "Passed!" );
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			assert.ok( a.getSize().equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "expandByPoint", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+
+			a.expandByPoint( zero3 );
+			assert.ok( a.getSize().equals( zero3 ), "Passed!" );
+
+			a.expandByPoint( one3 );
+			assert.ok( a.getSize().equals( one3 ), "Passed!" );
+
+			a.expandByPoint( one3.clone().negate() );
+			assert.ok( a.getSize().equals( one3.clone().multiplyScalar( 2 ) ), "Passed!" );
+			assert.ok( a.getCenter().equals( zero3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "expandByVector", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+
+			a.expandByVector( zero3 );
+			assert.ok( a.getSize().equals( zero3 ), "Passed!" );
+
+			a.expandByVector( one3 );
+			assert.ok( a.getSize().equals( one3.clone().multiplyScalar( 2 ) ), "Passed!" );
+			assert.ok( a.getCenter().equals( zero3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "expandByScalar", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+
+			a.expandByScalar( 0 );
+			assert.ok( a.getSize().equals( zero3 ), "Passed!" );
+
+			a.expandByScalar( 1 );
+			assert.ok( a.getSize().equals( one3.clone().multiplyScalar( 2 ) ), "Passed!" );
+			assert.ok( a.getCenter().equals( zero3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "expandByObject", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var b = a.clone();
+			var bigger = new Mesh( new BoxGeometry( 2, 2, 2 ) );
+			var smaller = new Mesh( new BoxGeometry( 0.5, 0.5, 0.5 ) );
+			var child = new Mesh( new BoxGeometry( 1, 1, 1 ) );
+
+			// just a bigger box to begin with
+			a.expandByObject( bigger );
+			assert.ok( a.min.equals( new Vector3( - 1, - 1, - 1 ) ), "Bigger box: correct new minimum" );
+			assert.ok( a.max.equals( new 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 Vector3( 0, - 1, - 1 ) ), "Translated, bigger box: correct new minimum" );
+			assert.ok( a.max.equals( new 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 Vector3( 0, - 1, - 1 ) ), "Translated, bigger box with child: correct new minimum" );
+			assert.ok( a.max.equals( new 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 Vector3( 0, - 1, - 1 ) ), "Translated, bigger box with translated child: correct new minimum" );
+			assert.ok( a.max.equals( new 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 Vector3( - 0.25, - 0.25, - 0.25 ) ), "Smaller box: correct new minimum" );
+			assert.ok( a.max.equals( new Vector3( 1, 1, 1 ) ), "Smaller box: correct new maximum" );
+
+		} );
+
+		QUnit.test( "containsPoint", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+
+			assert.ok( a.containsPoint( zero3 ), "Passed!" );
+			assert.ok( ! a.containsPoint( one3 ), "Passed!" );
+
+			a.expandByScalar( 1 );
+			assert.ok( a.containsPoint( zero3 ), "Passed!" );
+			assert.ok( a.containsPoint( one3 ), "Passed!" );
+			assert.ok( a.containsPoint( one3.clone().negate() ), "Passed!" );
+
+		} );
+
+		QUnit.test( "containsBox", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( zero3.clone(), one3.clone() );
+			var c = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.containsBox( a ), "Passed!" );
+			assert.ok( ! a.containsBox( b ), "Passed!" );
+			assert.ok( ! a.containsBox( c ), "Passed!" );
+
+			assert.ok( b.containsBox( a ), "Passed!" );
+			assert.ok( c.containsBox( a ), "Passed!" );
+			assert.ok( ! b.containsBox( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "getParameter", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var b = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.getParameter( new Vector3( 0, 0, 0 ) ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+			assert.ok( a.getParameter( new Vector3( 1, 1, 1 ) ).equals( new Vector3( 1, 1, 1 ) ), "Passed!" );
+
+			assert.ok( b.getParameter( new Vector3( - 1, - 1, - 1 ) ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+			assert.ok( b.getParameter( new Vector3( 0, 0, 0 ) ).equals( new Vector3( 0.5, 0.5, 0.5 ) ), "Passed!" );
+			assert.ok( b.getParameter( new Vector3( 1, 1, 1 ) ).equals( new Vector3( 1, 1, 1 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsBox", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( zero3.clone(), one3.clone() );
+			var c = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.intersectsBox( a ), "Passed!" );
+			assert.ok( a.intersectsBox( b ), "Passed!" );
+			assert.ok( a.intersectsBox( c ), "Passed!" );
+
+			assert.ok( b.intersectsBox( a ), "Passed!" );
+			assert.ok( c.intersectsBox( a ), "Passed!" );
+			assert.ok( b.intersectsBox( c ), "Passed!" );
+
+			b.translate( new Vector3( 2, 2, 2 ) );
+			assert.ok( ! a.intersectsBox( b ), "Passed!" );
+			assert.ok( ! b.intersectsBox( a ), "Passed!" );
+			assert.ok( ! b.intersectsBox( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsSphere", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var b = new Sphere( zero3.clone(), 1 );
+
+			assert.ok( a.intersectsSphere( b ), "Passed!" );
+
+			b.translate( new Vector3( 2, 2, 2 ) );
+			assert.ok( ! a.intersectsSphere( b ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsPlane", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), one3.clone() );
+			var b = new Plane( new Vector3( 0, 1, 0 ), 1 );
+			var c = new Plane( new Vector3( 0, 1, 0 ), 1.25 );
+			var d = new Plane( new Vector3( 0, - 1, 0 ), 1.25 );
+
+			assert.ok( a.intersectsPlane( b ), "Passed!" );
+			assert.ok( ! a.intersectsPlane( c ), "Passed!" );
+			assert.ok( ! a.intersectsPlane( d ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clampPoint", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.clampPoint( new Vector3( 0, 0, 0 ) ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+			assert.ok( a.clampPoint( new Vector3( 1, 1, 1 ) ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+			assert.ok( a.clampPoint( new Vector3( - 1, - 1, - 1 ) ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+			assert.ok( b.clampPoint( new Vector3( 2, 2, 2 ) ).equals( new Vector3( 1, 1, 1 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector3( 1, 1, 1 ) ).equals( new Vector3( 1, 1, 1 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector3( 0, 0, 0 ) ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector3( - 1, - 1, - 1 ) ).equals( new Vector3( - 1, - 1, - 1 ) ), "Passed!" );
+			assert.ok( b.clampPoint( new Vector3( - 2, - 2, - 2 ) ).equals( new Vector3( - 1, - 1, - 1 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceToPoint", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.distanceToPoint( new Vector3( 0, 0, 0 ) ) == 0, "Passed!" );
+			assert.ok( a.distanceToPoint( new Vector3( 1, 1, 1 ) ) == Math.sqrt( 3 ), "Passed!" );
+			assert.ok( a.distanceToPoint( new Vector3( - 1, - 1, - 1 ) ) == Math.sqrt( 3 ), "Passed!" );
+
+			assert.ok( b.distanceToPoint( new Vector3( 2, 2, 2 ) ) == Math.sqrt( 3 ), "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector3( 1, 1, 1 ) ) == 0, "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector3( 0, 0, 0 ) ) == 0, "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector3( - 1, - 1, - 1 ) ) == 0, "Passed!" );
+			assert.ok( b.distanceToPoint( new Vector3( - 2, - 2, - 2 ) ) == Math.sqrt( 3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "getBoundingSphere", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( zero3.clone(), one3.clone() );
+			var c = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.getBoundingSphere().equals( new Sphere( zero3, 0 ) ), "Passed!" );
+			assert.ok( b.getBoundingSphere().equals( new Sphere( one3.clone().multiplyScalar( 0.5 ), Math.sqrt( 3 ) * 0.5 ) ), "Passed!" );
+			assert.ok( c.getBoundingSphere().equals( new Sphere( zero3, Math.sqrt( 12 ) * 0.5 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersect", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( zero3.clone(), one3.clone() );
+			var c = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.clone().intersect( a ).equals( a ), "Passed!" );
+			assert.ok( a.clone().intersect( b ).equals( a ), "Passed!" );
+			assert.ok( b.clone().intersect( b ).equals( b ), "Passed!" );
+			assert.ok( a.clone().intersect( c ).equals( a ), "Passed!" );
+			assert.ok( b.clone().intersect( c ).equals( b ), "Passed!" );
+			assert.ok( c.clone().intersect( c ).equals( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "union", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( zero3.clone(), one3.clone() );
+			var c = new Box3( one3.clone().negate(), one3.clone() );
+
+			assert.ok( a.clone().union( a ).equals( a ), "Passed!" );
+			assert.ok( a.clone().union( b ).equals( b ), "Passed!" );
+			assert.ok( a.clone().union( c ).equals( c ), "Passed!" );
+			assert.ok( b.clone().union( c ).equals( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "applyMatrix4", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( zero3.clone(), one3.clone() );
+			var c = new Box3( one3.clone().negate(), one3.clone() );
+			var d = new Box3( one3.clone().negate(), zero3.clone() );
+
+			var m = new Matrix4().makeTranslation( 1, - 2, 1 );
+			var t1 = new Vector3( 1, - 2, 1 );
+
+			assert.ok( compareBox( a.clone().applyMatrix4( m ), a.clone().translate( t1 ) ), "Passed!" );
+			assert.ok( compareBox( b.clone().applyMatrix4( m ), b.clone().translate( t1 ) ), "Passed!" );
+			assert.ok( compareBox( c.clone().applyMatrix4( m ), c.clone().translate( t1 ) ), "Passed!" );
+			assert.ok( compareBox( d.clone().applyMatrix4( m ), d.clone().translate( t1 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "translate", ( assert ) => {
+
+			var a = new Box3( zero3.clone(), zero3.clone() );
+			var b = new Box3( zero3.clone(), one3.clone() );
+			var c = new Box3( one3.clone().negate(), one3.clone() );
+			var d = new Box3( one3.clone().negate(), zero3.clone() );
+
+			assert.ok( a.clone().translate( one3 ).equals( new Box3( one3, one3 ) ), "Passed!" );
+			assert.ok( a.clone().translate( one3 ).translate( one3.clone().negate() ).equals( a ), "Passed!" );
+			assert.ok( d.clone().translate( one3 ).equals( b ), "Passed!" );
+			assert.ok( b.clone().translate( one3.clone().negate() ).equals( d ), "Passed!" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 566 - 0
test/unit/src/math/Color.tests.js

@@ -0,0 +1,566 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Color } from '../../../../src/math/Color';
+import { eps } from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Color', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			// default ctor
+			var c = new Color();
+			assert.ok( c.r, "Red: " + c.r );
+			assert.ok( c.g, "Green: " + c.g );
+			assert.ok( c.b, "Blue: " + c.b );
+
+			// rgb ctor
+			var c = new Color( 1, 1, 1 );
+			assert.ok( c.r == 1, "Passed" );
+			assert.ok( c.g == 1, "Passed" );
+			assert.ok( c.b == 1, "Passed" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isColor", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Color();
+			var b = new Color( 0.5, 0, 0 );
+			var c = new Color( 0xFF0000 );
+			var d = new Color( 0, 1.0, 0 );
+
+			a.set( b );
+			assert.ok( a.equals( b ), "Set with 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( "setScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setHex", ( assert ) => {
+
+			var c = new Color();
+			c.setHex( 0xFA8072 );
+			assert.ok( c.getHex() == 0xFA8072, "Hex: " + c.getHex() );
+
+		} );
+
+		QUnit.test( "setRGB", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setHSL", ( assert ) => {
+
+			var c = new Color();
+			c.setHSL( 0.75, 1.0, 0.25 );
+			var hsl = c.getHSL();
+
+			assert.ok( hsl.h == 0.75, "hue: " + hsl.h );
+			assert.ok( hsl.s == 1.00, "saturation: " + hsl.s );
+			assert.ok( hsl.l == 0.25, "lightness: " + hsl.l );
+
+		} );
+
+		QUnit.test( "setStyle", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var c = new Color( 'teal' );
+			var c2 = c.clone();
+			assert.ok( c2.getHex() == 0x008080, "Hex c2: " + c2.getHex() );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copyGammaToLinear", ( assert ) => {
+
+			var c = new Color();
+			var c2 = new Color();
+			c2.setRGB( 0.3, 0.5, 0.9 );
+			c.copyGammaToLinear( c2 );
+			assert.ok( c.r == 0.09, "Red c: " + c.r + " Red c2: " + c2.r );
+			assert.ok( c.g == 0.25, "Green c: " + c.g + " Green c2: " + c2.g );
+			assert.ok( c.b == 0.81, "Blue c: " + c.b + " Blue c2: " + c2.b );
+
+		} );
+
+		QUnit.test( "copyLinearToGamma", ( assert ) => {
+
+			var c = new Color();
+			var c2 = new Color();
+			c2.setRGB( 0.09, 0.25, 0.81 );
+			c.copyLinearToGamma( c2 );
+			assert.ok( c.r == 0.3, "Red c: " + c.r + " Red c2: " + c2.r );
+			assert.ok( c.g == 0.5, "Green c: " + c.g + " Green c2: " + c2.g );
+			assert.ok( c.b == 0.9, "Blue c: " + c.b + " Blue c2: " + c2.b );
+
+		} );
+
+		QUnit.test( "convertGammaToLinear", ( assert ) => {
+
+			var c = new Color();
+			c.setRGB( 0.3, 0.5, 0.9 );
+			c.convertGammaToLinear();
+			assert.ok( c.r == 0.09, "Red: " + c.r );
+			assert.ok( c.g == 0.25, "Green: " + c.g );
+			assert.ok( c.b == 0.81, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "convertLinearToGamma", ( assert ) => {
+
+			var c = new Color();
+			c.setRGB( 4, 9, 16 );
+			c.convertLinearToGamma();
+			assert.ok( c.r == 2, "Red: " + c.r );
+			assert.ok( c.g == 3, "Green: " + c.g );
+			assert.ok( c.b == 4, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "getHex", ( assert ) => {
+
+			var c = new Color( 'red' );
+			var res = c.getHex();
+			assert.ok( res == 0xFF0000, "Hex: " + res );
+
+		} );
+
+		QUnit.test( "getHexString", ( assert ) => {
+
+			var c = new Color( 'tomato' );
+			var res = c.getHexString();
+			assert.ok( res == 'ff6347', "Hex: " + res );
+
+		} );
+
+		QUnit.test( "getHSL", ( assert ) => {
+
+			var c = new Color( 0x80ffff );
+			var hsl = c.getHSL();
+
+			assert.ok( hsl.h == 0.5, "hue: " + hsl.h );
+			assert.ok( hsl.s == 1.0, "saturation: " + hsl.s );
+			assert.ok( ( Math.round( parseFloat( hsl.l ) * 100 ) / 100 ) == 0.75, "lightness: " + hsl.l );
+
+		} );
+
+		QUnit.test( "getStyle", ( assert ) => {
+
+			var c = new Color( 'plum' );
+			var res = c.getStyle();
+			assert.ok( res == 'rgb(221,160,221)', "style: " + res );
+
+		} );
+
+		QUnit.test( "offsetHSL", ( assert ) => {
+
+			var a = new Color( "hsl(120,50%,50%)" );
+			var b = new 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", ( assert ) => {
+
+			var a = new Color( 0x0000FF );
+			var b = new Color( 0xFF0000 );
+			var c = new Color( 0xFF00FF );
+
+			a.add( b );
+
+			assert.ok( a.equals( c ), "Check new value" );
+
+		} );
+
+		QUnit.test( "addColors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "addScalar", ( assert ) => {
+
+			var a = new Color( 0.1, 0.0, 0.0 );
+			var b = new Color( 0.6, 0.5, 0.5 );
+
+			a.addScalar( 0.5 );
+
+			assert.ok( a.equals( b ), "Check new value" );
+
+		} );
+
+		QUnit.test( "sub", ( assert ) => {
+
+			var a = new Color( 0x0000CC );
+			var b = new Color( 0xFF0000 );
+			var c = new 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", ( assert ) => {
+
+			var a = new Color( 1, 0, 0.5 );
+			var b = new Color( 0.5, 1, 0.5 );
+			var c = new Color( 0.5, 0, 0.25 );
+
+			a.multiply( b );
+			assert.ok( a.equals( c ), "Check new value" );
+
+		} );
+
+		QUnit.test( "multiplyScalar", ( assert ) => {
+
+			var a = new Color( 0.25, 0, 0.5 );
+			var b = new Color( 0.5, 0, 1 );
+
+			a.multiplyScalar( 2 );
+			assert.ok( a.equals( b ), "Check new value" );
+
+		} );
+
+		QUnit.test( "copyHex", ( assert ) => {
+
+			var c = new Color();
+			var c2 = new Color( 0xF5FFFA );
+			c.copy( c2 );
+			assert.ok( c.getHex() == c2.getHex(), "Hex c: " + c.getHex() + " Hex c2: " + c2.getHex() );
+
+		} );
+
+		QUnit.test( "copyColorString", ( assert ) => {
+
+			var c = new Color();
+			var c2 = new Color( 'ivory' );
+			c.copy( c2 );
+			assert.ok( c.getHex() == c2.getHex(), "Hex c: " + c.getHex() + " Hex c2: " + c2.getHex() );
+
+		} );
+
+		QUnit.test( "setRGB", ( assert ) => {
+
+			var c = new Color();
+			c.setRGB( 1, 0.2, 0.1 );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g == 0.2, "Green: " + c.g );
+			assert.ok( c.b == 0.1, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "lerp", ( assert ) => {
+
+			var c = new Color();
+			var c2 = new Color();
+			c.setRGB( 0, 0, 0 );
+			c.lerp( c2, 0.2 );
+			assert.ok( c.r == 0.2, "Red: " + c.r );
+			assert.ok( c.g == 0.2, "Green: " + c.g );
+			assert.ok( c.b == 0.2, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			var a = new Color( 0.5, 0.0, 1.0 );
+			var b = new 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", ( assert ) => {
+
+			var a = new 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", ( assert ) => {
+
+			var r = 0.5, g = 1.0, b = 0.0;
+			var a = new 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" );
+
+			var 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" );
+
+			var 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", ( assert ) => {
+
+			var a = new Color( 0.0, 0.0, 0.0 );
+			var b = new Color( 0.0, 0.5, 0.0 );
+			var c = new Color( 1.0, 0.0, 0.0 );
+			var d = new 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" );
+
+		} );
+
+		// OTHERS
+		QUnit.test( "setWithNum", ( assert ) => {
+
+			var c = new Color();
+			c.set( 0xFF0000 );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setWithString", ( assert ) => {
+
+			var c = new Color();
+			c.set( 'silver' );
+			assert.ok( c.getHex() == 0xC0C0C0, "Hex c: " + c.getHex() );
+
+		} );
+
+		QUnit.test( "setStyleRGBRed", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgb(255,0,0)' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleRGBARed", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgba(255,0,0,0.5)' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleRGBRedWithSpaces", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgb( 255 , 0,   0 )' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleRGBARedWithSpaces", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgba( 255,  0,  0  , 1 )' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleRGBPercent", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgb(100%,50%,10%)' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g == 0.5, "Green: " + c.g );
+			assert.ok( c.b == 0.1, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleRGBAPercent", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgba(100%,50%,10%, 0.5)' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g == 0.5, "Green: " + c.g );
+			assert.ok( c.b == 0.1, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleRGBPercentWithSpaces", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgb( 100% ,50%  , 10% )' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g == 0.5, "Green: " + c.g );
+			assert.ok( c.b == 0.1, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleRGBAPercentWithSpaces", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'rgba( 100% ,50%  ,  10%, 0.5 )' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g == 0.5, "Green: " + c.g );
+			assert.ok( c.b == 0.1, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleHSLRed", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'hsl(360,100%,50%)' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleHSLARed", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'hsla(360,100%,50%,0.5)' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleHSLRedWithSpaces", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'hsl(360,  100% , 50% )' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleHSLARedWithSpaces", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'hsla( 360,  100% , 50%,  0.5 )' );
+			assert.ok( c.r == 1, "Red: " + c.r );
+			assert.ok( c.g === 0, "Green: " + c.g );
+			assert.ok( c.b === 0, "Blue: " + c.b );
+
+		} );
+
+		QUnit.test( "setStyleHexSkyBlue", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( '#87CEEB' );
+			assert.ok( c.getHex() == 0x87CEEB, "Hex c: " + c.getHex() );
+
+		} );
+
+		QUnit.test( "setStyleHexSkyBlueMixed", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( '#87cEeB' );
+			assert.ok( c.getHex() == 0x87CEEB, "Hex c: " + c.getHex() );
+
+		} );
+
+		QUnit.test( "setStyleHex2Olive", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( '#F00' );
+			assert.ok( c.getHex() == 0xFF0000, "Hex c: " + c.getHex() );
+
+		} );
+
+		QUnit.test( "setStyleHex2OliveMixed", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( '#f00' );
+			assert.ok( c.getHex() == 0xFF0000, "Hex c: " + c.getHex() );
+
+		} );
+
+		QUnit.test( "setStyleColorName", ( assert ) => {
+
+			var c = new Color();
+			c.setStyle( 'powderblue' );
+			assert.ok( c.getHex() == 0xB0E0E6, "Hex c: " + c.getHex() );
+
+		} );
+
+
+	} );
+
+} );

+ 27 - 0
test/unit/src/math/Constants.tests.js

@@ -0,0 +1,27 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+import { Vector2 } from '../../../../src/math/Vector2';
+import { Vector3 } from '../../../../src/math/Vector3';
+
+export const x = 2;
+export const y = 3;
+export const z = 4;
+export const w = 5;
+
+export const negInf2 = new Vector2( - Infinity, - Infinity );
+export const posInf2 = new Vector2( Infinity, Infinity );
+
+export const zero2 = new Vector2();
+export const one2 = new Vector2( 1, 1 );
+export const two2 = new Vector2( 2, 2 );
+
+export const negInf3 = new Vector3( - Infinity, - Infinity, - Infinity );
+export const posInf3 = new Vector3( Infinity, Infinity, Infinity );
+
+export const zero3 = new Vector3();
+export const one3 = new Vector3( 1, 1, 1 );
+export const two3 = new Vector3( 2, 2, 2 );
+
+export const eps = 0.0001;

+ 100 - 0
test/unit/src/math/Cylindrical.tests.js

@@ -0,0 +1,100 @@
+/**
+ * @author moraxy / https://github.com/moraxy
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Cylindrical } from '../../../../src/math/Cylindrical';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { eps } from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Cylindrical', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new 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" );
+
+			var a = new 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" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new 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", ( assert ) => {
+
+			var radius = 10.0;
+			var theta = Math.PI;
+			var y = 5;
+			var a = new 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", ( assert ) => {
+
+			var radius = 10.0;
+			var theta = Math.PI;
+			var y = 5;
+			var a = new Cylindrical( radius, theta, y );
+			var b = new 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", ( assert ) => {
+
+			var a = new Cylindrical( 1, 1, 1 );
+			var b = new Vector3( 0, 0, 0 );
+			var c = new Vector3( 3, - 1, - 3 );
+			var expected = new 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" );
+
+		} );
+
+	} );
+
+} );

+ 345 - 0
test/unit/src/math/Euler.tests.js

@@ -0,0 +1,345 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+import { Euler } from '../../../../src/math/Euler';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Quaternion } from '../../../../src/math/Quaternion';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { x, y, z } from './Constants.tests';
+
+const eulerZero = new Euler( 0, 0, 0, "XYZ" );
+const eulerAxyz = new Euler( 1, 0, 0, "XYZ" );
+const eulerAzyx = new Euler( 0, 1, 0, "ZYX" );
+
+function matrixEquals4( a, b, tolerance ) {
+
+	tolerance = tolerance || 0.0001;
+	if ( a.elements.length != b.elements.length ) {
+
+		return false;
+
+	}
+
+	for ( var i = 0, il = a.elements.length; i < il; i ++ ) {
+
+		var delta = a.elements[ i ] - b.elements[ i ];
+		if ( delta > tolerance ) {
+
+			return false;
+
+		}
+
+	}
+
+	return true;
+
+}
+
+function eulerEquals( 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 );
+
+}
+
+function quatEquals( 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 ) + Math.abs( a.w - b.w );
+
+	return ( diff < tolerance );
+
+}
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Euler', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Euler();
+			assert.ok( a.equals( eulerZero ), "Passed!" );
+			assert.ok( ! a.equals( eulerAxyz ), "Passed!" );
+			assert.ok( ! a.equals( eulerAzyx ), "Passed!" );
+
+		} );
+
+		// STATIC STUFF
+		QUnit.test( "RotationOrders", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "DefaultOrder", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PROPERTIES STUFF
+		QUnit.test( "x", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "y", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "z", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "order", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isEuler", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set/setFromVector3/toVector3", ( assert ) => {
+
+			var a = new Euler();
+
+			a.set( 0, 1, 0, "ZYX" );
+			assert.ok( a.equals( eulerAzyx ), "Passed!" );
+			assert.ok( ! a.equals( eulerAxyz ), "Passed!" );
+			assert.ok( ! a.equals( eulerZero ), "Passed!" );
+
+			var vec = new Vector3( 0, 1, 0 );
+
+			var b = new Euler().setFromVector3( vec, "ZYX" );
+			assert.ok( a.equals( b ), "Passed!" );
+
+			var c = b.toVector3();
+			assert.ok( c.equals( vec ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clone/copy/equals", ( assert ) => {
+
+			var a = eulerAxyz.clone();
+			assert.ok( a.equals( eulerAxyz ), "Passed!" );
+			assert.ok( ! a.equals( eulerZero ), "Passed!" );
+			assert.ok( ! a.equals( eulerAzyx ), "Passed!" );
+
+			a.copy( eulerAzyx );
+			assert.ok( a.equals( eulerAzyx ), "Passed!" );
+			assert.ok( ! a.equals( eulerAxyz ), "Passed!" );
+			assert.ok( ! a.equals( eulerZero ), "Passed!" );
+
+		} );
+
+		QUnit.test( "Quaternion.setFromEuler/Euler.fromQuaternion", ( assert ) => {
+
+			var testValues = [ eulerZero, eulerAxyz, eulerAzyx ];
+			for ( var i = 0; i < testValues.length; i ++ ) {
+
+				var v = testValues[ i ];
+				var q = new Quaternion().setFromEuler( v );
+
+				var v2 = new Euler().setFromQuaternion( q, v.order );
+				var q2 = new Quaternion().setFromEuler( v2 );
+				assert.ok( quatEquals( q, q2 ), "Passed!" );
+
+			}
+
+		} );
+
+		QUnit.test( "Matrix4.setFromEuler/Euler.fromRotationMatrix", ( assert ) => {
+
+			var testValues = [ eulerZero, eulerAxyz, eulerAzyx ];
+			for ( var i = 0; i < testValues.length; i ++ ) {
+
+				var v = testValues[ i ];
+				var m = new Matrix4().makeRotationFromEuler( v );
+
+				var v2 = new Euler().setFromRotationMatrix( m, v.order );
+				var m2 = new Matrix4().makeRotationFromEuler( v2 );
+				assert.ok( matrixEquals4( m, m2, 0.0001 ), "Passed!" );
+
+			}
+
+		} );
+
+		QUnit.test( "reorder", ( assert ) => {
+
+			var testValues = [ eulerZero, eulerAxyz, eulerAzyx ];
+			for ( var i = 0; i < testValues.length; i ++ ) {
+
+				var v = testValues[ i ];
+				var q = new Quaternion().setFromEuler( v );
+
+				v.reorder( 'YZX' );
+				var q2 = new Quaternion().setFromEuler( v );
+				assert.ok( quatEquals( q, q2 ), "Passed!" );
+
+				v.reorder( 'ZXY' );
+				var q3 = new Quaternion().setFromEuler( v );
+				assert.ok( quatEquals( q, q3 ), "Passed!" );
+
+			}
+
+		} );
+
+		QUnit.test( "set/get properties, check callbacks", ( assert ) => {
+
+			var a = new 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", ( assert ) => {
+
+			assert.expect( 3 );
+
+			var a = new Euler( 1, 2, 3, "ZXY" );
+			var b = new Euler( 4, 5, 6, "XZY" );
+			var cb = function () {
+
+				assert.step( "onChange called" );
+
+			};
+			a.onChange( cb );
+			b.onChange( cb );
+
+			// clone doesn't trigger onChange
+			var a = b.clone();
+			assert.ok( a.equals( b ), "clone: check if a equals b" );
+
+			// copy triggers onChange once
+			var a = new 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", ( assert ) => {
+
+			var order = "YXZ";
+			var a = new 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" );
+
+			var 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" );
+
+			var 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", ( assert ) => {
+
+			assert.expect( 10 );
+
+			var a = new 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" );
+
+			var a = new Euler();
+			var array = [ x, y, z, "ZXY" ];
+			a.onChange( cb );
+			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" );
+
+		} );
+
+		QUnit.test( "onChange", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "onChangeCallback", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// OTHERS
+		QUnit.test( "gimbalLocalQuat", ( assert ) => {
+
+			// known problematic quaternions
+			var q1 = new Quaternion( 0.5207769385244341, - 0.4783214164122354, 0.520776938524434, 0.47832141641223547 );
+			var q2 = new Quaternion( 0.11284905712620674, 0.6980437630368944, - 0.11284905712620674, 0.6980437630368944 );
+
+			var eulerOrder = "ZYX";
+
+			// create Euler directly from a Quaternion
+			var eViaQ1 = new Euler().setFromQuaternion( q1, eulerOrder ); // there is likely a bug here
+
+			// create Euler from Quaternion via an intermediate Matrix4
+			var mViaQ1 = new Matrix4().makeRotationFromQuaternion( q1 );
+			var eViaMViaQ1 = new Euler().setFromRotationMatrix( mViaQ1, eulerOrder );
+
+			// the results here are different
+			assert.ok( eulerEquals( eViaQ1, eViaMViaQ1 ), "Passed!" ); // this result is correct
+
+		} );
+
+	} );
+
+} );

+ 280 - 0
test/unit/src/math/Frustum.tests.js

@@ -0,0 +1,280 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Frustum } from '../../../../src/math/Frustum';
+import { Sphere } from '../../../../src/math/Sphere';
+import { Plane } from '../../../../src/math/Plane';
+import { Sprite } from '../../../../src/objects/Sprite';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Box3 } from '../../../../src/math/Box3';
+import { Mesh } from '../../../../src/objects/Mesh';
+import { BoxGeometry } from '../../../../src/geometries/BoxGeometry';
+import { zero3, one3 } from './Constants.tests';
+
+const unit3 = new Vector3( 1, 0, 0 );
+
+function planeEquals( a, b, tolerance ) {
+
+	tolerance = tolerance || 0.0001;
+
+	if ( a.normal.distanceTo( b.normal ) > tolerance ) return false;
+	if ( Math.abs( a.constant - b.constant ) > tolerance ) return false;
+
+	return true;
+
+}
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Frustum', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Frustum();
+
+			assert.ok( a.planes !== undefined, "Passed!" );
+			assert.ok( a.planes.length === 6, "Passed!" );
+
+			var pDefault = new Plane();
+			for ( var i = 0; i < 6; i ++ ) {
+
+				assert.ok( a.planes[ i ].equals( pDefault ), "Passed!" );
+
+			}
+
+			var p0 = new Plane( unit3, - 1 );
+			var p1 = new Plane( unit3, 1 );
+			var p2 = new Plane( unit3, 2 );
+			var p3 = new Plane( unit3, 3 );
+			var p4 = new Plane( unit3, 4 );
+			var p5 = new Plane( unit3, 5 );
+
+			var a = new Frustum( p0, p1, p2, p3, p4, p5 );
+			assert.ok( a.planes[ 0 ].equals( p0 ), "Passed!" );
+			assert.ok( a.planes[ 1 ].equals( p1 ), "Passed!" );
+			assert.ok( a.planes[ 2 ].equals( p2 ), "Passed!" );
+			assert.ok( a.planes[ 3 ].equals( p3 ), "Passed!" );
+			assert.ok( a.planes[ 4 ].equals( p4 ), "Passed!" );
+			assert.ok( a.planes[ 5 ].equals( p5 ), "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Frustum();
+			var p0 = new Plane( unit3, - 1 );
+			var p1 = new Plane( unit3, 1 );
+			var p2 = new Plane( unit3, 2 );
+			var p3 = new Plane( unit3, 3 );
+			var p4 = new Plane( unit3, 4 );
+			var p5 = new 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( "clone", ( assert ) => {
+
+			var p0 = new Plane( unit3, - 1 );
+			var p1 = new Plane( unit3, 1 );
+			var p2 = new Plane( unit3, 2 );
+			var p3 = new Plane( unit3, 3 );
+			var p4 = new Plane( unit3, 4 );
+			var p5 = new Plane( unit3, 5 );
+
+			var b = new Frustum( p0, p1, p2, p3, p4, p5 );
+			var a = b.clone();
+			assert.ok( a.planes[ 0 ].equals( p0 ), "Passed!" );
+			assert.ok( a.planes[ 1 ].equals( p1 ), "Passed!" );
+			assert.ok( a.planes[ 2 ].equals( p2 ), "Passed!" );
+			assert.ok( a.planes[ 3 ].equals( p3 ), "Passed!" );
+			assert.ok( a.planes[ 4 ].equals( p4 ), "Passed!" );
+			assert.ok( a.planes[ 5 ].equals( p5 ), "Passed!" );
+
+			// ensure it is a true copy by modifying source
+			a.planes[ 0 ].copy( p1 );
+			assert.ok( b.planes[ 0 ].equals( p0 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var p0 = new Plane( unit3, - 1 );
+			var p1 = new Plane( unit3, 1 );
+			var p2 = new Plane( unit3, 2 );
+			var p3 = new Plane( unit3, 3 );
+			var p4 = new Plane( unit3, 4 );
+			var p5 = new Plane( unit3, 5 );
+
+			var b = new Frustum( p0, p1, p2, p3, p4, p5 );
+			var a = new Frustum().copy( b );
+			assert.ok( a.planes[ 0 ].equals( p0 ), "Passed!" );
+			assert.ok( a.planes[ 1 ].equals( p1 ), "Passed!" );
+			assert.ok( a.planes[ 2 ].equals( p2 ), "Passed!" );
+			assert.ok( a.planes[ 3 ].equals( p3 ), "Passed!" );
+			assert.ok( a.planes[ 4 ].equals( p4 ), "Passed!" );
+			assert.ok( a.planes[ 5 ].equals( p5 ), "Passed!" );
+
+			// ensure it is a true copy by modifying source
+			b.planes[ 0 ] = p1;
+			assert.ok( a.planes[ 0 ].equals( p0 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromMatrix/makeOrthographic/containsPoint", ( assert ) => {
+
+			var m = new Matrix4().makeOrthographic( - 1, 1, - 1, 1, 1, 100 );
+			var a = new Frustum().setFromMatrix( m );
+
+			assert.ok( ! a.containsPoint( new Vector3( 0, 0, 0 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 0, 0, - 50 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 0, 0, - 1.001 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( - 1, - 1, - 1.001 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( - 1.1, - 1.1, - 1.001 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 1, 1, - 1.001 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( 1.1, 1.1, - 1.001 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 0, 0, - 100 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( - 1, - 1, - 100 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( - 1.1, - 1.1, - 100.1 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 1, 1, - 100 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( 1.1, 1.1, - 100.1 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( 0, 0, - 101 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromMatrix/makePerspective/containsPoint", ( assert ) => {
+
+			var m = new Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+			var a = new Frustum().setFromMatrix( m );
+
+			assert.ok( ! a.containsPoint( new Vector3( 0, 0, 0 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 0, 0, - 50 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 0, 0, - 1.001 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( - 1, - 1, - 1.001 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( - 1.1, - 1.1, - 1.001 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 1, 1, - 1.001 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( 1.1, 1.1, - 1.001 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 0, 0, - 99.999 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( - 99.999, - 99.999, - 99.999 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( - 100.1, - 100.1, - 100.1 ) ), "Passed!" );
+			assert.ok( a.containsPoint( new Vector3( 99.999, 99.999, - 99.999 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( 100.1, 100.1, - 100.1 ) ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( 0, 0, - 101 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromMatrix/makePerspective/intersectsSphere", ( assert ) => {
+
+			var m = new Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+			var a = new Frustum().setFromMatrix( m );
+
+			assert.ok( ! a.intersectsSphere( new Sphere( new Vector3( 0, 0, 0 ), 0 ) ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( new Sphere( new Vector3( 0, 0, 0 ), 0.9 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 0, 0, 0 ), 1.1 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 0, 0, - 50 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 0, 0, - 1.001 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( - 1, - 1, - 1.001 ), 0 ) ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( new Sphere( new Vector3( - 1.1, - 1.1, - 1.001 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( - 1.1, - 1.1, - 1.001 ), 0.5 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 1, 1, - 1.001 ), 0 ) ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( new Sphere( new Vector3( 1.1, 1.1, - 1.001 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 1.1, 1.1, - 1.001 ), 0.5 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 0, 0, - 99.999 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( - 99.999, - 99.999, - 99.999 ), 0 ) ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( new Sphere( new Vector3( - 100.1, - 100.1, - 100.1 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( - 100.1, - 100.1, - 100.1 ), 0.5 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 99.999, 99.999, - 99.999 ), 0 ) ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( new Sphere( new Vector3( 100.1, 100.1, - 100.1 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 100.1, 100.1, - 100.1 ), 0.2 ) ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( new Sphere( new Vector3( 0, 0, - 101 ), 0 ) ), "Passed!" );
+			assert.ok( a.intersectsSphere( new Sphere( new Vector3( 0, 0, - 101 ), 1.1 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsObject", ( assert ) => {
+
+			var m = new Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+			var a = new Frustum().setFromMatrix( m );
+			var object = new Mesh( new 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", ( assert ) => {
+
+			var m = new Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+			var a = new Frustum().setFromMatrix( m );
+			var sprite = new 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( "intersectsSphere", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "intersectsBox", ( assert ) => {
+
+			var m = new Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 100 );
+			var a = new Frustum().setFromMatrix( m );
+			var box = new 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" );
+
+		} );
+
+		QUnit.test( "containsPoint", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 534 - 0
test/unit/src/math/Interpolant.tests.js

@@ -0,0 +1,534 @@
+/**
+ * @author tschw
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Interpolant } from '../../../../src/math/Interpolant';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Interpolant', () => {
+
+		// Since this is an abstract base class, we have to make it concrete in order
+		// to QUnit.test its functionality...
+
+		function Mock( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+			Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+		}
+
+		Mock.prototype = Object.create( Interpolant.prototype );
+
+		Mock.prototype.intervalChanged_ = function intervalChanged( i1, t0, t1 ) {
+
+			Mock.captureCall( arguments );
+
+		};
+
+		Mock.prototype.interpolate_ = function interpolate( i1, t0, t, t1 ) {
+
+			Mock.captureCall( arguments );
+			return this.copySampleValue_( i1 - 1 );
+
+		};
+
+		Mock.prototype.beforeStart_ = function beforeStart( i, t, t0 ) {
+
+			Mock.captureCall( arguments );
+			return this.copySampleValue_( i );
+
+		};
+
+		Mock.prototype.afterEnd_ = function afterEnd( i, tN, t ) {
+
+			Mock.captureCall( arguments );
+			return this.copySampleValue_( i );
+
+		};
+
+		// Call capturing facility
+
+		Mock.calls = null;
+
+		Mock.captureCall = function ( args ) {
+
+			if ( Mock.calls !== null ) {
+
+				Mock.calls.push( {
+					func: Mock.captureCall.caller.name,
+					args: Array.prototype.slice.call( args )
+				} );
+
+			}
+
+		};
+
+		// Tests
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "evaluate", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PRIVATE STUFF
+		QUnit.test( "DefaultSettings_", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getSettings_", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copySampleValue_", ( assert ) => {
+
+			var interpolant = new Mock( null, [ 1, 11, 2, 22, 3, 33 ], 2, [] );
+
+			assert.deepEqual( interpolant.copySampleValue_( 0 ), [ 1, 11 ], "sample fetch (0)" );
+			assert.deepEqual( interpolant.copySampleValue_( 1 ), [ 2, 22 ], "sample fetch (1)" );
+			assert.deepEqual( interpolant.copySampleValue_( 2 ), [ 3, 33 ], "first sample (2)" );
+
+		} );
+
+		QUnit.test( "evaluate -> intervalChanged_ / interpolate_", ( assert ) => {
+
+			var actual, expect;
+
+			var interpolant = new Mock( [ 11, 22, 33, 44, 55, 66, 77, 88, 99 ], null, 0, null );
+
+			Mock.calls = [];
+			interpolant.evaluate( 11 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 1, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 12 ); // same interval
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 12, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 22 ); // step forward
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 2, 22, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 2, 22, 22, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2 );
+
+			Mock.calls = [];
+			interpolant.evaluate( 21 ); // step back
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 1, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 21, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 20 ); // same interval
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 20, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 43 ); // two steps forward
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 3, 33, 44 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 3, 33, 43, 44 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 12 ); // two steps back
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 1, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 12, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 77 ); // random access
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 7, 77, 88 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 7, 77, 77, 88 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 80 ); // same interval
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 7, 77, 80, 88 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 36 ); // random access
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 3, 33, 44 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 3, 33, 36, 44 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 24 ); // fast reset / loop (2nd)
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 2, 22, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 2, 22, 24, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			Mock.calls = [];
+			interpolant.evaluate( 16 ); // fast reset / loop (2nd)
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 1, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 16, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+		} );
+
+		QUnit.test( "evaulate -> beforeStart_ [once]", ( assert ) => {
+
+			var actual, expect;
+
+			var interpolant = new Mock( [ 11, 22, 33 ], null, 0, null );
+
+			Mock.calls = [];
+			interpolant.evaluate( 10 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'beforeStart',
+				args: [ 0, 10, 11 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			// Check operation resumes normally and intervalChanged gets called
+			Mock.calls = [];
+			interpolant.evaluate( 11 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 1, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			// Back off-bounds
+			Mock.calls = [];
+			interpolant.evaluate( 10 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'beforeStart',
+				args: [ 0, 10, 11 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+		} );
+
+		QUnit.test( "evaluate -> beforeStart_ [twice]", ( assert ) => {
+
+			var actual, expect;
+
+			var interpolant = new Mock( [ 11, 22, 33 ], null, 0, null );
+
+			Mock.calls = [];
+			interpolant.evaluate( 10 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'beforeStart',
+				args: [ 0, 10, 11 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			Mock.calls = []; // again - consider changed state
+			interpolant.evaluate( 10 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'beforeStart',
+				args: [ 0, 10, 11 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			// Check operation resumes normally and intervalChanged gets called
+			Mock.calls = [];
+			interpolant.evaluate( 11 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 1, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 1, 11, 11, 22 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+		} );
+
+		QUnit.test( "evaluate -> afterEnd_ [once]", ( assert ) => {
+
+			var actual, expect;
+
+			var interpolant = new Mock( [ 11, 22, 33 ], null, 0, null );
+
+			Mock.calls = [];
+			interpolant.evaluate( 33 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'afterEnd',
+				args: [ 2, 33, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			// Check operation resumes normally and intervalChanged gets called
+			Mock.calls = [];
+			interpolant.evaluate( 32 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 2, 22, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 2, 22, 32, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+			// Back off-bounds
+			Mock.calls = [];
+			interpolant.evaluate( 33 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'afterEnd',
+				args: [ 2, 33, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+		} );
+
+		QUnit.test( "evaluate -> afterEnd_ [twice]", ( assert ) => {
+
+			var actual, expect;
+
+			var interpolant = new Mock( [ 11, 22, 33 ], null, 0, null );
+
+			Mock.calls = [];
+			interpolant.evaluate( 33 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'afterEnd',
+				args: [ 2, 33, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			Mock.calls = []; // again - consider changed state
+			interpolant.evaluate( 33 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'afterEnd',
+				args: [ 2, 33, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 1, "no further calls" );
+
+			// Check operation resumes normally and intervalChanged gets called
+			Mock.calls = [];
+			interpolant.evaluate( 32 );
+
+			actual = Mock.calls[ 0 ];
+			expect = {
+				func: 'intervalChanged',
+				args: [ 2, 22, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			actual = Mock.calls[ 1 ];
+			expect = {
+				func: 'interpolate',
+				args: [ 2, 22, 32, 33 ]
+			};
+			assert.deepEqual( actual, expect, JSON.stringify( expect ) );
+
+			assert.ok( Mock.calls.length === 2, "no further calls" );
+
+		} );
+
+	} );
+
+} );

+ 197 - 0
test/unit/src/math/Line3.tests.js

@@ -0,0 +1,197 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Line3 } from '../../../../src/math/Line3';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Vector4 } from '../../../../src/math/Vector4';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import {
+	x,
+	y,
+	z,
+	zero3,
+	one3,
+	two3
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Line3', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Line3();
+			assert.ok( a.start.equals( zero3 ), "Passed!" );
+			assert.ok( a.end.equals( zero3 ), "Passed!" );
+
+			var a = new Line3( two3.clone(), one3.clone() );
+			assert.ok( a.start.equals( two3 ), "Passed!" );
+			assert.ok( a.end.equals( one3 ), "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Line3();
+
+			a.set( one3, one3 );
+			assert.ok( a.start.equals( one3 ), "Passed!" );
+			assert.ok( a.end.equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "copy/equals", ( assert ) => {
+
+			var a = new Line3( zero3.clone(), one3.clone() );
+			var b = new Line3().copy( a );
+			assert.ok( b.start.equals( zero3 ), "Passed!" );
+			assert.ok( b.end.equals( one3 ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.start = zero3;
+			a.end = one3;
+			assert.ok( b.start.equals( zero3 ), "Passed!" );
+			assert.ok( b.end.equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clone/equal", ( assert ) => {
+
+			var a = new Line3();
+			var b = new Line3( zero3, new Vector3( 1, 1, 1 ) );
+			var c = new Line3( zero3, new 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" );
+
+			var 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( "getCenter", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "delta", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "distanceSq", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "distance", ( assert ) => {
+
+			var a = new Line3( zero3, zero3 );
+			var b = new Line3( zero3, one3 );
+			var c = new Line3( one3.clone().negate(), one3 );
+			var d = new 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( "at", ( assert ) => {
+
+			var a = new Line3( one3.clone(), new Vector3( 1, 1, 2 ) );
+
+			assert.ok( a.at( - 1 ).distanceTo( new Vector3( 1, 1, 0 ) ) < 0.0001, "Passed!" );
+			assert.ok( a.at( 0 ).distanceTo( one3.clone() ) < 0.0001, "Passed!" );
+			assert.ok( a.at( 1 ).distanceTo( new Vector3( 1, 1, 2 ) ) < 0.0001, "Passed!" );
+			assert.ok( a.at( 2 ).distanceTo( new Vector3( 1, 1, 3 ) ) < 0.0001, "Passed!" );
+
+		} );
+
+		QUnit.test( "closestPointToPoint/closestPointToPointParameter", ( assert ) => {
+
+			var a = new Line3( one3.clone(), new Vector3( 1, 1, 2 ) );
+
+			// nearby the ray
+			assert.ok( a.closestPointToPointParameter( zero3.clone(), true ) == 0, "Passed!" );
+			var b1 = a.closestPointToPoint( zero3.clone(), true );
+			assert.ok( b1.distanceTo( new Vector3( 1, 1, 1 ) ) < 0.0001, "Passed!" );
+
+			// nearby the ray
+			assert.ok( a.closestPointToPointParameter( zero3.clone(), false ) == - 1, "Passed!" );
+			var b2 = a.closestPointToPoint( zero3.clone(), false );
+			assert.ok( b2.distanceTo( new Vector3( 1, 1, 0 ) ) < 0.0001, "Passed!" );
+
+			// nearby the ray
+			assert.ok( a.closestPointToPointParameter( new Vector3( 1, 1, 5 ), true ) == 1, "Passed!" );
+			var b = a.closestPointToPoint( new Vector3( 1, 1, 5 ), true );
+			assert.ok( b.distanceTo( new Vector3( 1, 1, 2 ) ) < 0.0001, "Passed!" );
+
+			// exactly on the ray
+			assert.ok( a.closestPointToPointParameter( one3.clone(), true ) == 0, "Passed!" );
+			var c = a.closestPointToPoint( one3.clone(), true );
+			assert.ok( c.distanceTo( one3.clone() ) < 0.0001, "Passed!" );
+
+		} );
+
+		QUnit.test( "applyMatrix4", ( assert ) => {
+
+			var a = new Line3( zero3.clone(), two3.clone() );
+			var b = new Vector4( two3.x, two3.y, two3.z, 1 );
+			var m = new Matrix4().makeTranslation( x, y, z );
+			var v = new Vector3( x, y, z );
+
+			a.applyMatrix4( m );
+			assert.ok( a.start.equals( v ), "Translation: check start" );
+			assert.ok( a.end.equals( new 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" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 151 - 0
test/unit/src/math/Math.tests.js

@@ -0,0 +1,151 @@
+/**
+ * @author humbletim / https://github.com/humbletim
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { _Math as ThreeMath } from '../../../../src/math/Math';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Math', () => {
+
+		// PUBLIC STUFF
+		QUnit.test( "generateUUID", ( assert ) => {
+
+			var a = ThreeMath.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", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.clamp( 0.5, 0, 1 ), 0.5, "Value already within limits" );
+			assert.strictEqual( ThreeMath.clamp( 0, 0, 1 ), 0, "Value equal to one limit" );
+			assert.strictEqual( ThreeMath.clamp( - 0.1, 0, 1 ), 0, "Value too low" );
+			assert.strictEqual( ThreeMath.clamp( 1.1, 0, 1 ), 1, "Value too high" );
+
+		} );
+
+		QUnit.test( "euclideanModulo", ( assert ) => {
+
+			assert.ok( isNaN( ThreeMath.euclideanModulo( 6, 0 ) ), "Division by zero returns NaN" );
+			assert.strictEqual( ThreeMath.euclideanModulo( 6, 1 ), 0, "Divison by trivial divisor" );
+			assert.strictEqual( ThreeMath.euclideanModulo( 6, 2 ), 0, "Divison by non-trivial divisor" );
+			assert.strictEqual( ThreeMath.euclideanModulo( 6, 5 ), 1, "Divison by itself - 1" );
+			assert.strictEqual( ThreeMath.euclideanModulo( 6, 6 ), 0, "Divison by itself" );
+			assert.strictEqual( ThreeMath.euclideanModulo( 6, 7 ), 6, "Divison by itself + 1" );
+
+		} );
+
+		QUnit.test( "mapLinear", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.mapLinear( 0.5, 0, 1, 0, 10 ), 5, "Value within range" );
+			assert.strictEqual( ThreeMath.mapLinear( 0.0, 0, 1, 0, 10 ), 0, "Value equal to lower boundary" );
+			assert.strictEqual( ThreeMath.mapLinear( 1.0, 0, 1, 0, 10 ), 10, "Value equal to upper boundary" );
+
+		} );
+
+		QUnit.test( "lerp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "smoothstep", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.smoothstep( - 1, 0, 2 ), 0, "Value lower than minimum" );
+			assert.strictEqual( ThreeMath.smoothstep( 0, 0, 2 ), 0, "Value equal to minimum" );
+			assert.strictEqual( ThreeMath.smoothstep( 0.5, 0, 2 ), 0.15625, "Value within limits" );
+			assert.strictEqual( ThreeMath.smoothstep( 1, 0, 2 ), 0.5, "Value within limits" );
+			assert.strictEqual( ThreeMath.smoothstep( 1.5, 0, 2 ), 0.84375, "Value within limits" );
+			assert.strictEqual( ThreeMath.smoothstep( 2, 0, 2 ), 1, "Value equal to maximum" );
+			assert.strictEqual( ThreeMath.smoothstep( 3, 0, 2 ), 1, "Value highter than maximum" );
+
+		} );
+
+		QUnit.test( "smootherstep", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "randInt", ( assert ) => {
+
+			var low = 1, high = 3;
+			var a = ThreeMath.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", ( assert ) => {
+
+			var low = 1, high = 3;
+			var a = ThreeMath.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", ( assert ) => {
+
+			var a = ThreeMath.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", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.degToRad( 0 ), 0, "0 degrees" );
+			assert.strictEqual( ThreeMath.degToRad( 90 ), Math.PI / 2, "90 degrees" );
+			assert.strictEqual( ThreeMath.degToRad( 180 ), Math.PI, "180 degrees" );
+			assert.strictEqual( ThreeMath.degToRad( 360 ), Math.PI * 2, "360 degrees" );
+
+		} );
+
+		QUnit.test( "radToDeg", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.radToDeg( 0 ), 0, "0 radians" );
+			assert.strictEqual( ThreeMath.radToDeg( Math.PI / 2 ), 90, "Math.PI / 2 radians" );
+			assert.strictEqual( ThreeMath.radToDeg( Math.PI ), 180, "Math.PI radians" );
+			assert.strictEqual( ThreeMath.radToDeg( Math.PI * 2 ), 360, "Math.PI * 2 radians" );
+
+		} );
+
+		QUnit.test( "isPowerOfTwo", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.isPowerOfTwo( 0 ), false, "0 is not a PoT" );
+			assert.strictEqual( ThreeMath.isPowerOfTwo( 1 ), true, "1 is a PoT" );
+			assert.strictEqual( ThreeMath.isPowerOfTwo( 2 ), true, "2 is a PoT" );
+			assert.strictEqual( ThreeMath.isPowerOfTwo( 3 ), false, "3 is not a PoT" );
+			assert.strictEqual( ThreeMath.isPowerOfTwo( 4 ), true, "4 is a PoT" );
+
+		} );
+
+		QUnit.test( "ceilPowerOfTwo", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.ceilPowerOfTwo( 1 ), 1, "Closest higher PoT to 1 is 1" );
+			assert.strictEqual( ThreeMath.ceilPowerOfTwo( 3 ), 4, "Closest higher PoT to 3 is 4" );
+			assert.strictEqual( ThreeMath.ceilPowerOfTwo( 4 ), 4, "Closest higher PoT to 4 is 4" );
+
+		} );
+
+		QUnit.test( "floorPowerOfTwo", ( assert ) => {
+
+			assert.strictEqual( ThreeMath.floorPowerOfTwo( 1 ), 1, "Closest lower PoT to 1 is 1" );
+			assert.strictEqual( ThreeMath.floorPowerOfTwo( 3 ), 2, "Closest lower PoT to 3 is 2" );
+			assert.strictEqual( ThreeMath.floorPowerOfTwo( 4 ), 4, "Closest lower PoT to 4 is 4" );
+
+		} );
+
+	} );
+
+} );

+ 479 - 0
test/unit/src/math/Matrix3.tests.js

@@ -0,0 +1,479 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Matrix3 } from '../../../../src/math/Matrix3';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Float32BufferAttribute } from '../../../../src/core/BufferAttribute';
+
+function matrixEquals3( a, b, tolerance ) {
+
+	tolerance = tolerance || 0.0001;
+	if ( a.elements.length != b.elements.length ) {
+
+		return false;
+
+	}
+
+	for ( var i = 0, il = a.elements.length; i < il; i ++ ) {
+
+		var delta = a.elements[ i ] - b.elements[ i ];
+		if ( delta > tolerance ) {
+
+			return false;
+
+		}
+
+	}
+
+	return true;
+
+}
+
+function toMatrix4( m3 ) {
+
+	var result = new Matrix4();
+	var re = result.elements;
+	var me = m3.elements;
+	re[ 0 ] = me[ 0 ];
+	re[ 1 ] = me[ 1 ];
+	re[ 2 ] = me[ 2 ];
+	re[ 4 ] = me[ 3 ];
+	re[ 5 ] = me[ 4 ];
+	re[ 6 ] = me[ 5 ];
+	re[ 8 ] = me[ 6 ];
+	re[ 9 ] = me[ 7 ];
+	re[ 10 ] = me[ 8 ];
+
+	return result;
+
+}
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Matrix3', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Matrix3();
+			assert.ok( a.determinant() == 1, "Passed!" );
+
+			var b = new Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 3 );
+			assert.ok( b.elements[ 2 ] == 6 );
+			assert.ok( b.elements[ 3 ] == 1 );
+			assert.ok( b.elements[ 4 ] == 4 );
+			assert.ok( b.elements[ 5 ] == 7 );
+			assert.ok( b.elements[ 6 ] == 2 );
+			assert.ok( b.elements[ 7 ] == 5 );
+			assert.ok( b.elements[ 8 ] == 8 );
+
+			assert.ok( ! matrixEquals3( a, b ), "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isMatrix3", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var b = new Matrix3();
+			assert.ok( b.determinant() == 1, "Passed!" );
+
+			b.set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 3 );
+			assert.ok( b.elements[ 2 ] == 6 );
+			assert.ok( b.elements[ 3 ] == 1 );
+			assert.ok( b.elements[ 4 ] == 4 );
+			assert.ok( b.elements[ 5 ] == 7 );
+			assert.ok( b.elements[ 6 ] == 2 );
+			assert.ok( b.elements[ 7 ] == 5 );
+			assert.ok( b.elements[ 8 ] == 8 );
+
+		} );
+
+		QUnit.test( "identity", ( assert ) => {
+
+			var b = new Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 3 );
+			assert.ok( b.elements[ 2 ] == 6 );
+			assert.ok( b.elements[ 3 ] == 1 );
+			assert.ok( b.elements[ 4 ] == 4 );
+			assert.ok( b.elements[ 5 ] == 7 );
+			assert.ok( b.elements[ 6 ] == 2 );
+			assert.ok( b.elements[ 7 ] == 5 );
+			assert.ok( b.elements[ 8 ] == 8 );
+
+			var a = new Matrix3();
+			assert.ok( ! matrixEquals3( a, b ), "Passed!" );
+
+			b.identity();
+			assert.ok( matrixEquals3( a, b ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var a = new Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			var b = a.clone();
+
+			assert.ok( matrixEquals3( a, b ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.elements[ 0 ] = 2;
+			assert.ok( ! matrixEquals3( a, b ), "Passed!" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			var b = new Matrix3().copy( a );
+
+			assert.ok( matrixEquals3( a, b ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.elements[ 0 ] = 2;
+			assert.ok( ! matrixEquals3( a, b ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromMatrix4", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "applyToBufferAttribute", ( assert ) => {
+
+			var a = new Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+			var attr = new 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", ( assert ) => {
+
+			// both simply just wrap multiplyMatrices
+			var a = new Matrix3().set( 2, 3, 5, 7, 11, 13, 17, 19, 23 );
+			var b = new 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( "multiplyMatrices", ( assert ) => {
+
+			// Reference:
+			//
+			// #!/usr/bin/env python
+			// from __future__ import print_function
+			// import numpy as np
+			// print(
+			//     np.dot(
+			//         np.reshape([2, 3, 5, 7, 11, 13, 17, 19, 23], (3, 3)),
+			//         np.reshape([29, 31, 37, 41, 43, 47, 53, 59, 61], (3, 3))
+			//     )
+			// )
+			//
+			// [[ 446  486  520]
+			//  [1343 1457 1569]
+			//  [2491 2701 2925]]
+			var lhs = new Matrix3().set( 2, 3, 5, 7, 11, 13, 17, 19, 23 );
+			var rhs = new Matrix3().set( 29, 31, 37, 41, 43, 47, 53, 59, 61 );
+			var ans = new Matrix3();
+
+			ans.multiplyMatrices( lhs, rhs );
+
+			assert.ok( ans.elements[ 0 ] == 446 );
+			assert.ok( ans.elements[ 1 ] == 1343 );
+			assert.ok( ans.elements[ 2 ] == 2491 );
+			assert.ok( ans.elements[ 3 ] == 486 );
+			assert.ok( ans.elements[ 4 ] == 1457 );
+			assert.ok( ans.elements[ 5 ] == 2701 );
+			assert.ok( ans.elements[ 6 ] == 520 );
+			assert.ok( ans.elements[ 7 ] == 1569 );
+			assert.ok( ans.elements[ 8 ] == 2925 );
+
+		} );
+
+		QUnit.test( "multiplyScalar", ( assert ) => {
+
+			var b = new Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 3 );
+			assert.ok( b.elements[ 2 ] == 6 );
+			assert.ok( b.elements[ 3 ] == 1 );
+			assert.ok( b.elements[ 4 ] == 4 );
+			assert.ok( b.elements[ 5 ] == 7 );
+			assert.ok( b.elements[ 6 ] == 2 );
+			assert.ok( b.elements[ 7 ] == 5 );
+			assert.ok( b.elements[ 8 ] == 8 );
+
+			b.multiplyScalar( 2 );
+			assert.ok( b.elements[ 0 ] == 0 * 2 );
+			assert.ok( b.elements[ 1 ] == 3 * 2 );
+			assert.ok( b.elements[ 2 ] == 6 * 2 );
+			assert.ok( b.elements[ 3 ] == 1 * 2 );
+			assert.ok( b.elements[ 4 ] == 4 * 2 );
+			assert.ok( b.elements[ 5 ] == 7 * 2 );
+			assert.ok( b.elements[ 6 ] == 2 * 2 );
+			assert.ok( b.elements[ 7 ] == 5 * 2 );
+			assert.ok( b.elements[ 8 ] == 8 * 2 );
+
+		} );
+
+		QUnit.test( "determinant", ( assert ) => {
+
+			var a = new Matrix3();
+			assert.ok( a.determinant() == 1, "Passed!" );
+
+			a.elements[ 0 ] = 2;
+			assert.ok( a.determinant() == 2, "Passed!" );
+
+			a.elements[ 0 ] = 0;
+			assert.ok( a.determinant() == 0, "Passed!" );
+
+			// calculated via http://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/threeD/index.htm
+			a.set( 2, 3, 4, 5, 13, 7, 8, 9, 11 );
+			assert.ok( a.determinant() == - 73, "Passed!" );
+
+		} );
+
+		QUnit.test( "getInverse", ( assert ) => {
+
+			var identity = new Matrix3();
+			var identity4 = new Matrix4();
+			var a = new Matrix3();
+			var b = new Matrix3().set( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+			var c = new Matrix3().set( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+			b.getInverse( a, false );
+			assert.ok( matrixEquals3( a, identity ), "Matrix a is identity matrix" );
+
+			try {
+
+				b.getInverse( c, true );
+				assert.ok( false, "Should never get here !" ); // should never get here.
+
+			} catch ( err ) {
+
+				assert.ok( true, "Passed: " + err );
+
+			}
+
+			var testMatrices = [
+				new Matrix4().makeRotationX( 0.3 ),
+				new Matrix4().makeRotationX( - 0.3 ),
+				new Matrix4().makeRotationY( 0.3 ),
+				new Matrix4().makeRotationY( - 0.3 ),
+				new Matrix4().makeRotationZ( 0.3 ),
+				new Matrix4().makeRotationZ( - 0.3 ),
+				new Matrix4().makeScale( 1, 2, 3 ),
+				new Matrix4().makeScale( 1 / 8, 1 / 2, 1 / 3 )
+			];
+
+			for ( var i = 0, il = testMatrices.length; i < il; i ++ ) {
+
+				var m = testMatrices[ i ];
+
+				a.setFromMatrix4( m );
+				var mInverse3 = b.getInverse( a );
+
+				var mInverse = toMatrix4( mInverse3 );
+
+				// the determinant of the inverse should be the reciprocal
+				assert.ok( Math.abs( a.determinant() * mInverse3.determinant() - 1 ) < 0.0001, "Passed!" );
+				assert.ok( Math.abs( m.determinant() * mInverse.determinant() - 1 ) < 0.0001, "Passed!" );
+
+				var mProduct = new Matrix4().multiplyMatrices( m, mInverse );
+				assert.ok( Math.abs( mProduct.determinant() - 1 ) < 0.0001, "Passed!" );
+				assert.ok( matrixEquals3( mProduct, identity4 ), "Passed!" );
+
+			}
+
+		} );
+
+		QUnit.test( "transpose", ( assert ) => {
+
+			var a = new Matrix3();
+			var b = a.clone().transpose();
+			assert.ok( matrixEquals3( a, b ), "Passed!" );
+
+			var b = new Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			var c = b.clone().transpose();
+			assert.ok( ! matrixEquals3( b, c ), "Passed!" );
+			c.transpose();
+			assert.ok( matrixEquals3( b, c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "getNormalMatrix", ( assert ) => {
+
+			var a = new Matrix3();
+			var b = new Matrix4().set(
+				2, 3, 5, 7,
+				11, 13, 17, 19,
+				23, 29, 31, 37,
+				41, 43, 47, 57
+			);
+			var expected = new 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( "transposeIntoArray", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setUvTransform", ( assert ) => {
+
+			var a = new Matrix3().set(
+				0.1767766952966369, 0.17677669529663687, 0.32322330470336313,
+				- 0.17677669529663687, 0.1767766952966369, 0.5,
+				0, 0, 1
+			);
+			var b = new 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 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( "scale", ( assert ) => {
+
+			var a = new Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+			var expected = new 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( "rotate", ( assert ) => {
+
+			var a = new Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+			var expected = new 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( "translate", ( assert ) => {
+
+			var a = new Matrix3().set( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
+			var expected = new 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( "equals", ( assert ) => {
+
+			var a = new Matrix3().set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+			var b = new 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( "fromArray", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "toArray", ( assert ) => {
+
+			var a = new 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" );
+
+			var array = [];
+			a.toArray( array );
+			assert.deepEqual( array, noOffset, "With array, no offset" );
+
+			var array = [];
+			a.toArray( array, 1 );
+			assert.deepEqual( array, withOffset, "With array, with offset" );
+
+		} );
+
+	} );
+
+} );

+ 706 - 0
test/unit/src/math/Matrix4.tests.js

@@ -0,0 +1,706 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Euler } from '../../../../src/math/Euler';
+import { Quaternion } from '../../../../src/math/Quaternion';
+import { Float32BufferAttribute } from '../../../../src/core/BufferAttribute';
+import { _Math } from '../../../../src/math/Math';
+import { eps } from './Constants.tests';
+
+
+function matrixEquals4( a, b, tolerance ) {
+
+	tolerance = tolerance || 0.0001;
+	if ( a.elements.length != b.elements.length ) {
+
+		return false;
+
+	}
+
+	for ( var i = 0, il = a.elements.length; i < il; i ++ ) {
+
+		var delta = a.elements[ i ] - b.elements[ i ];
+		if ( delta > tolerance ) {
+
+			return false;
+
+		}
+
+	}
+
+	return true;
+
+}
+
+// from Euler.js
+function eulerEquals( 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 );
+
+}
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Matrix4', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Matrix4();
+			assert.ok( a.determinant() == 1, "Passed!" );
+
+			var b = new Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 4 );
+			assert.ok( b.elements[ 2 ] == 8 );
+			assert.ok( b.elements[ 3 ] == 12 );
+			assert.ok( b.elements[ 4 ] == 1 );
+			assert.ok( b.elements[ 5 ] == 5 );
+			assert.ok( b.elements[ 6 ] == 9 );
+			assert.ok( b.elements[ 7 ] == 13 );
+			assert.ok( b.elements[ 8 ] == 2 );
+			assert.ok( b.elements[ 9 ] == 6 );
+			assert.ok( b.elements[ 10 ] == 10 );
+			assert.ok( b.elements[ 11 ] == 14 );
+			assert.ok( b.elements[ 12 ] == 3 );
+			assert.ok( b.elements[ 13 ] == 7 );
+			assert.ok( b.elements[ 14 ] == 11 );
+			assert.ok( b.elements[ 15 ] == 15 );
+
+			assert.ok( ! matrixEquals4( a, b ), "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isMatrix4", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var b = new Matrix4();
+			assert.ok( b.determinant() == 1, "Passed!" );
+
+			b.set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 4 );
+			assert.ok( b.elements[ 2 ] == 8 );
+			assert.ok( b.elements[ 3 ] == 12 );
+			assert.ok( b.elements[ 4 ] == 1 );
+			assert.ok( b.elements[ 5 ] == 5 );
+			assert.ok( b.elements[ 6 ] == 9 );
+			assert.ok( b.elements[ 7 ] == 13 );
+			assert.ok( b.elements[ 8 ] == 2 );
+			assert.ok( b.elements[ 9 ] == 6 );
+			assert.ok( b.elements[ 10 ] == 10 );
+			assert.ok( b.elements[ 11 ] == 14 );
+			assert.ok( b.elements[ 12 ] == 3 );
+			assert.ok( b.elements[ 13 ] == 7 );
+			assert.ok( b.elements[ 14 ] == 11 );
+			assert.ok( b.elements[ 15 ] == 15 );
+
+		} );
+
+		QUnit.test( "identity", ( assert ) => {
+
+			var b = new Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 4 );
+			assert.ok( b.elements[ 2 ] == 8 );
+			assert.ok( b.elements[ 3 ] == 12 );
+			assert.ok( b.elements[ 4 ] == 1 );
+			assert.ok( b.elements[ 5 ] == 5 );
+			assert.ok( b.elements[ 6 ] == 9 );
+			assert.ok( b.elements[ 7 ] == 13 );
+			assert.ok( b.elements[ 8 ] == 2 );
+			assert.ok( b.elements[ 9 ] == 6 );
+			assert.ok( b.elements[ 10 ] == 10 );
+			assert.ok( b.elements[ 11 ] == 14 );
+			assert.ok( b.elements[ 12 ] == 3 );
+			assert.ok( b.elements[ 13 ] == 7 );
+			assert.ok( b.elements[ 14 ] == 11 );
+			assert.ok( b.elements[ 15 ] == 15 );
+
+			var a = new Matrix4();
+			assert.ok( ! matrixEquals4( a, b ), "Passed!" );
+
+			b.identity();
+			assert.ok( matrixEquals4( a, b ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			var a = new Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+			var b = a.clone();
+
+			assert.ok( matrixEquals4( a, b ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.elements[ 0 ] = 2;
+			assert.ok( ! matrixEquals4( a, b ), "Passed!" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+			var b = new Matrix4().copy( a );
+
+			assert.ok( matrixEquals4( a, b ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.elements[ 0 ] = 2;
+			assert.ok( ! matrixEquals4( a, b ), "Passed!" );
+
+		} );
+
+		QUnit.test( "copyPosition", ( assert ) => {
+
+			var a = new Matrix4().set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+			var b = new 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( "makeBasis/extractBasis", ( assert ) => {
+
+			var identityBasis = [ new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ) ];
+			var a = new Matrix4().makeBasis( identityBasis[ 0 ], identityBasis[ 1 ], identityBasis[ 2 ] );
+			var identity = new Matrix4();
+			assert.ok( matrixEquals4( a, identity ), "Passed!" );
+
+			var testBases = [[ new Vector3( 0, 1, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ) ]];
+			for ( var i = 0; i < testBases.length; i ++ ) {
+
+				var testBasis = testBases[ i ];
+				var b = new Matrix4().makeBasis( testBasis[ 0 ], testBasis[ 1 ], testBasis[ 2 ] );
+				var outBasis = [ new Vector3(), new Vector3(), new Vector3() ];
+				b.extractBasis( outBasis[ 0 ], outBasis[ 1 ], outBasis[ 2 ] );
+				// check what goes in, is what comes out.
+				for ( var j = 0; j < outBasis.length; j ++ ) {
+
+					assert.ok( outBasis[ j ].equals( testBasis[ j ] ), "Passed!" );
+
+				}
+
+				// get the basis out the hard war
+				for ( var j = 0; j < identityBasis.length; j ++ ) {
+
+					outBasis[ j ].copy( identityBasis[ j ] );
+					outBasis[ j ].applyMatrix4( b );
+
+				}
+				// did the multiply method of basis extraction work?
+				for ( var j = 0; j < outBasis.length; j ++ ) {
+
+					assert.ok( outBasis[ j ].equals( testBasis[ j ] ), "Passed!" );
+
+				}
+
+			}
+
+		} );
+
+		QUnit.test( "extractRotation", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "makeRotationFromEuler/extractRotation", ( assert ) => {
+
+			var testValues = [
+				new Euler( 0, 0, 0, "XYZ" ),
+				new Euler( 1, 0, 0, "XYZ" ),
+				new Euler( 0, 1, 0, "ZYX" ),
+				new Euler( 0, 0, 0.5, "YZX" ),
+				new Euler( 0, 0, - 0.5, "YZX" )
+			];
+
+			for ( var i = 0; i < testValues.length; i ++ ) {
+
+				var v = testValues[ i ];
+
+				var m = new Matrix4().makeRotationFromEuler( v );
+
+				var v2 = new Euler().setFromRotationMatrix( m, v.order );
+				var m2 = new 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 Matrix4().extractRotation( m2 );
+				var v3 = new 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( "lookAt", ( assert ) => {
+
+			var a = new Matrix4();
+			var expected = new Matrix4().identity();
+			var eye = new Vector3( 0, 0, 0 );
+			var target = new Vector3( 0, 1, - 1 );
+			var up = new Vector3( 0, 1, 0 );
+
+			a.lookAt( eye, target, up );
+			var rotation = new 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" );
+
+		} );
+
+		QUnit.test( "multiply", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "premultiply", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "multiplyMatrices", ( assert ) => {
+
+			// Reference:
+			//
+			// #!/usr/bin/env python
+			// from __future__ import print_function
+			// import numpy as np
+			// print(
+			//     np.dot(
+			//         np.reshape([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53], (4, 4)),
+			//         np.reshape([59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131], (4, 4))
+			//     )
+			// )
+			//
+			// [[ 1585  1655  1787  1861]
+			//  [ 5318  5562  5980  6246]
+			//  [10514 11006 11840 12378]
+			//  [15894 16634 17888 18710]]
+			var lhs = new Matrix4().set( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 );
+			var rhs = new Matrix4().set( 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131 );
+			var ans = new Matrix4();
+
+			ans.multiplyMatrices( lhs, rhs );
+
+			assert.ok( ans.elements[ 0 ] == 1585 );
+			assert.ok( ans.elements[ 1 ] == 5318 );
+			assert.ok( ans.elements[ 2 ] == 10514 );
+			assert.ok( ans.elements[ 3 ] == 15894 );
+			assert.ok( ans.elements[ 4 ] == 1655 );
+			assert.ok( ans.elements[ 5 ] == 5562 );
+			assert.ok( ans.elements[ 6 ] == 11006 );
+			assert.ok( ans.elements[ 7 ] == 16634 );
+			assert.ok( ans.elements[ 8 ] == 1787 );
+			assert.ok( ans.elements[ 9 ] == 5980 );
+			assert.ok( ans.elements[ 10 ] == 11840 );
+			assert.ok( ans.elements[ 11 ] == 17888 );
+			assert.ok( ans.elements[ 12 ] == 1861 );
+			assert.ok( ans.elements[ 13 ] == 6246 );
+			assert.ok( ans.elements[ 14 ] == 12378 );
+			assert.ok( ans.elements[ 15 ] == 18710 );
+
+		} );
+
+		QUnit.test( "multiplyScalar", ( assert ) => {
+
+			var b = new Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+			assert.ok( b.elements[ 0 ] == 0 );
+			assert.ok( b.elements[ 1 ] == 4 );
+			assert.ok( b.elements[ 2 ] == 8 );
+			assert.ok( b.elements[ 3 ] == 12 );
+			assert.ok( b.elements[ 4 ] == 1 );
+			assert.ok( b.elements[ 5 ] == 5 );
+			assert.ok( b.elements[ 6 ] == 9 );
+			assert.ok( b.elements[ 7 ] == 13 );
+			assert.ok( b.elements[ 8 ] == 2 );
+			assert.ok( b.elements[ 9 ] == 6 );
+			assert.ok( b.elements[ 10 ] == 10 );
+			assert.ok( b.elements[ 11 ] == 14 );
+			assert.ok( b.elements[ 12 ] == 3 );
+			assert.ok( b.elements[ 13 ] == 7 );
+			assert.ok( b.elements[ 14 ] == 11 );
+			assert.ok( b.elements[ 15 ] == 15 );
+
+			b.multiplyScalar( 2 );
+			assert.ok( b.elements[ 0 ] == 0 * 2 );
+			assert.ok( b.elements[ 1 ] == 4 * 2 );
+			assert.ok( b.elements[ 2 ] == 8 * 2 );
+			assert.ok( b.elements[ 3 ] == 12 * 2 );
+			assert.ok( b.elements[ 4 ] == 1 * 2 );
+			assert.ok( b.elements[ 5 ] == 5 * 2 );
+			assert.ok( b.elements[ 6 ] == 9 * 2 );
+			assert.ok( b.elements[ 7 ] == 13 * 2 );
+			assert.ok( b.elements[ 8 ] == 2 * 2 );
+			assert.ok( b.elements[ 9 ] == 6 * 2 );
+			assert.ok( b.elements[ 10 ] == 10 * 2 );
+			assert.ok( b.elements[ 11 ] == 14 * 2 );
+			assert.ok( b.elements[ 12 ] == 3 * 2 );
+			assert.ok( b.elements[ 13 ] == 7 * 2 );
+			assert.ok( b.elements[ 14 ] == 11 * 2 );
+			assert.ok( b.elements[ 15 ] == 15 * 2 );
+
+		} );
+
+		QUnit.test( "applyToBufferAttribute", ( assert ) => {
+
+			var a = new Matrix4().set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+			var attr = new Float32BufferAttribute( [ 1, 2, 1, 3, 0, 3 ], 3 );
+			var expected = new 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( "determinant", ( assert ) => {
+
+			var a = new Matrix4();
+			assert.ok( a.determinant() == 1, "Passed!" );
+
+			a.elements[ 0 ] = 2;
+			assert.ok( a.determinant() == 2, "Passed!" );
+
+			a.elements[ 0 ] = 0;
+			assert.ok( a.determinant() == 0, "Passed!" );
+
+			// calculated via http://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm
+			a.set( 2, 3, 4, 5, - 1, - 21, - 3, - 4, 6, 7, 8, 10, - 8, - 9, - 10, - 12 );
+			assert.ok( a.determinant() == 76, "Passed!" );
+
+		} );
+
+		QUnit.test( "transpose", ( assert ) => {
+
+			var a = new Matrix4();
+			var b = a.clone().transpose();
+			assert.ok( matrixEquals4( a, b ), "Passed!" );
+
+			var b = new Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+			var c = b.clone().transpose();
+			assert.ok( ! matrixEquals4( b, c ), "Passed!" );
+			c.transpose();
+			assert.ok( matrixEquals4( b, c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setPosition", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getInverse", ( assert ) => {
+
+			var identity = new Matrix4();
+
+			var a = new Matrix4();
+			var b = new Matrix4().set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+			var c = new Matrix4().set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+			assert.ok( ! matrixEquals4( a, b ), "Passed!" );
+			b.getInverse( a, false );
+			assert.ok( matrixEquals4( b, new Matrix4() ), "Passed!" );
+
+			try {
+
+				b.getInverse( c, true );
+				assert.ok( false, "Passed!" ); // should never get here.
+
+			} catch ( err ) {
+
+				assert.ok( true, "Passed!" );
+
+			}
+
+			var testMatrices = [
+				new Matrix4().makeRotationX( 0.3 ),
+				new Matrix4().makeRotationX( - 0.3 ),
+				new Matrix4().makeRotationY( 0.3 ),
+				new Matrix4().makeRotationY( - 0.3 ),
+				new Matrix4().makeRotationZ( 0.3 ),
+				new Matrix4().makeRotationZ( - 0.3 ),
+				new Matrix4().makeScale( 1, 2, 3 ),
+				new Matrix4().makeScale( 1 / 8, 1 / 2, 1 / 3 ),
+				new Matrix4().makePerspective( - 1, 1, 1, - 1, 1, 1000 ),
+				new Matrix4().makePerspective( - 16, 16, 9, - 9, 0.1, 10000 ),
+				new Matrix4().makeTranslation( 1, 2, 3 )
+			];
+
+			for ( var i = 0, il = testMatrices.length; i < il; i ++ ) {
+
+				var m = testMatrices[ i ];
+
+				var mInverse = new Matrix4().getInverse( m );
+				var mSelfInverse = m.clone();
+				mSelfInverse.getInverse( mSelfInverse );
+
+				// self-inverse should the same as inverse
+				assert.ok( matrixEquals4( mSelfInverse, mInverse ), "Passed!" );
+
+				// the determinant of the inverse should be the reciprocal
+				assert.ok( Math.abs( m.determinant() * mInverse.determinant() - 1 ) < 0.0001, "Passed!" );
+
+				var mProduct = new Matrix4().multiplyMatrices( m, mInverse );
+
+				// the determinant of the identity matrix is 1
+				assert.ok( Math.abs( mProduct.determinant() - 1 ) < 0.0001, "Passed!" );
+				assert.ok( matrixEquals4( mProduct, identity ), "Passed!" );
+
+			}
+
+		} );
+
+		QUnit.test( "scale", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getMaxScaleOnAxis", ( assert ) => {
+
+			var a = new 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( "makeTranslation", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "makeRotationX", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "makeRotationY", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "makeRotationZ", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "makeRotationAxis", ( assert ) => {
+
+			var axis = new Vector3( 1.5, 0.0, 1.0 ).normalize();
+			var radians = _Math.degToRad( 45 );
+			var a = new Matrix4().makeRotationAxis( axis, radians );
+
+			var expected = new 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( "makeScale", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "makeShear", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "compose/decompose", ( assert ) => {
+
+			var tValues = [
+				new Vector3(),
+				new Vector3( 3, 0, 0 ),
+				new Vector3( 0, 4, 0 ),
+				new Vector3( 0, 0, 5 ),
+				new Vector3( - 6, 0, 0 ),
+				new Vector3( 0, - 7, 0 ),
+				new Vector3( 0, 0, - 8 ),
+				new Vector3( - 2, 5, - 9 ),
+				new Vector3( - 2, - 5, - 9 )
+			];
+
+			var sValues = [
+				new Vector3( 1, 1, 1 ),
+				new Vector3( 2, 2, 2 ),
+				new Vector3( 1, - 1, 1 ),
+				new Vector3( - 1, 1, 1 ),
+				new Vector3( 1, 1, - 1 ),
+				new Vector3( 2, - 2, 1 ),
+				new Vector3( - 1, 2, - 2 ),
+				new Vector3( - 1, - 1, - 1 ),
+				new Vector3( - 2, - 2, - 2 )
+			];
+
+			var rValues = [
+				new Quaternion(),
+				new Quaternion().setFromEuler( new Euler( 1, 1, 0 ) ),
+				new Quaternion().setFromEuler( new Euler( 1, - 1, 1 ) ),
+				new Quaternion( 0, 0.9238795292366128, 0, 0.38268342717215614 )
+			];
+
+			for ( var ti = 0; ti < tValues.length; ti ++ ) {
+
+				for ( var si = 0; si < sValues.length; si ++ ) {
+
+					for ( var ri = 0; ri < rValues.length; ri ++ ) {
+
+						var t = tValues[ ti ];
+						var s = sValues[ si ];
+						var r = rValues[ ri ];
+
+						var m = new Matrix4().compose( t, r, s );
+						var t2 = new Vector3();
+						var r2 = new Quaternion();
+						var s2 = new Vector3();
+
+						m.decompose( t2, r2, s2 );
+
+						var m2 = new Matrix4().compose( t2, r2, s2 );
+
+						/*
+						// debug code
+						var matrixIsSame = matrixEquals4( m, m2 );
+						if ( ! matrixIsSame ) {
+
+							console.log( t, s, r );
+							console.log( t2, s2, r2 );
+							console.log( m, m2 );
+
+						}
+						*/
+
+						assert.ok( matrixEquals4( m, m2 ), "Passed!" );
+
+					}
+
+				}
+
+			}
+
+		} );
+
+		QUnit.test( "makePerspective", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "makeOrthographic", ( assert ) => {
+
+			var a = new Matrix4().makeOrthographic( - 1, 1, - 1, 1, 1, 100 );
+			var expected = new 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( "equals", ( assert ) => {
+
+			var a = new Matrix4().set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+			var b = new 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( "fromArray", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "toArray", ( assert ) => {
+
+			var a = new 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" );
+
+			var array = [];
+			a.toArray( array );
+			assert.deepEqual( array, noOffset, "With array, no offset" );
+
+			var array = [];
+			a.toArray( array, 1 );
+			assert.deepEqual( array, withOffset, "With array, with offset" );
+
+		} );
+
+	} );
+
+} );

+ 301 - 0
test/unit/src/math/Plane.tests.js

@@ -0,0 +1,301 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Plane } from '../../../../src/math/Plane';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Line3 } from '../../../../src/math/Line3';
+import { Sphere } from '../../../../src/math/Sphere';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import {
+	x,
+	y,
+	z,
+	w,
+	zero3,
+	one3
+} from './Constants.tests';
+
+function comparePlane( a, b, threshold ) {
+
+	threshold = threshold || 0.0001;
+	return ( a.normal.distanceTo( b.normal ) < threshold &&
+	Math.abs( a.constant - b.constant ) < threshold );
+
+}
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Plane', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Plane();
+			assert.ok( a.normal.x == 1, "Passed!" );
+			assert.ok( a.normal.y == 0, "Passed!" );
+			assert.ok( a.normal.z == 0, "Passed!" );
+			assert.ok( a.constant == 0, "Passed!" );
+
+			var a = new Plane( one3.clone(), 0 );
+			assert.ok( a.normal.x == 1, "Passed!" );
+			assert.ok( a.normal.y == 1, "Passed!" );
+			assert.ok( a.normal.z == 1, "Passed!" );
+			assert.ok( a.constant == 0, "Passed!" );
+
+			var a = new Plane( one3.clone(), 1 );
+			assert.ok( a.normal.x == 1, "Passed!" );
+			assert.ok( a.normal.y == 1, "Passed!" );
+			assert.ok( a.normal.z == 1, "Passed!" );
+			assert.ok( a.constant == 1, "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isPlane", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Plane();
+			assert.ok( a.normal.x == 1, "Passed!" );
+			assert.ok( a.normal.y == 0, "Passed!" );
+			assert.ok( a.normal.z == 0, "Passed!" );
+			assert.ok( a.constant == 0, "Passed!" );
+
+			var b = a.clone().set( new Vector3( x, y, z ), w );
+			assert.ok( b.normal.x == x, "Passed!" );
+			assert.ok( b.normal.y == y, "Passed!" );
+			assert.ok( b.normal.z == z, "Passed!" );
+			assert.ok( b.constant == w, "Passed!" );
+
+		} );
+
+		QUnit.test( "setComponents", ( assert ) => {
+
+			var a = new Plane();
+			assert.ok( a.normal.x == 1, "Passed!" );
+			assert.ok( a.normal.y == 0, "Passed!" );
+			assert.ok( a.normal.z == 0, "Passed!" );
+			assert.ok( a.constant == 0, "Passed!" );
+
+			var b = a.clone().setComponents( x, y, z, w );
+			assert.ok( b.normal.x == x, "Passed!" );
+			assert.ok( b.normal.y == y, "Passed!" );
+			assert.ok( b.normal.z == z, "Passed!" );
+			assert.ok( b.constant == w, "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromNormalAndCoplanarPoint", ( assert ) => {
+
+			var normal = one3.clone().normalize();
+			var a = new Plane().setFromNormalAndCoplanarPoint( normal, zero3 );
+
+			assert.ok( a.normal.equals( normal ), "Passed!" );
+			assert.ok( a.constant == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromCoplanarPoints", ( assert ) => {
+
+			var a = new Plane();
+			var v1 = new Vector3( 2.0, 0.5, 0.25 );
+			var v2 = new Vector3( 2.0, - 0.5, 1.25 );
+			var v3 = new Vector3( 2.0, - 3.5, 2.2 );
+			var normal = new 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" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Plane( new Vector3( x, y, z ), w );
+			var b = new Plane().copy( a );
+			assert.ok( b.normal.x == x, "Passed!" );
+			assert.ok( b.normal.y == y, "Passed!" );
+			assert.ok( b.normal.z == z, "Passed!" );
+			assert.ok( b.constant == w, "Passed!" );
+
+			// ensure that it is a true copy
+			a.normal.x = 0;
+			a.normal.y = - 1;
+			a.normal.z = - 2;
+			a.constant = - 3;
+			assert.ok( b.normal.x == x, "Passed!" );
+			assert.ok( b.normal.y == y, "Passed!" );
+			assert.ok( b.normal.z == z, "Passed!" );
+			assert.ok( b.constant == w, "Passed!" );
+
+		} );
+
+		QUnit.test( "normalize", ( assert ) => {
+
+			var a = new Plane( new Vector3( 2, 0, 0 ), 2 );
+
+			a.normalize();
+			assert.ok( a.normal.length() == 1, "Passed!" );
+			assert.ok( a.normal.equals( new Vector3( 1, 0, 0 ) ), "Passed!" );
+			assert.ok( a.constant == 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "negate/distanceToPoint", ( assert ) => {
+
+			var a = new Plane( new Vector3( 2, 0, 0 ), - 2 );
+
+			a.normalize();
+			assert.ok( a.distanceToPoint( new Vector3( 4, 0, 0 ) ) === 3, "Passed!" );
+			assert.ok( a.distanceToPoint( new Vector3( 1, 0, 0 ) ) === 0, "Passed!" );
+
+			a.negate();
+			assert.ok( a.distanceToPoint( new Vector3( 4, 0, 0 ) ) === - 3, "Passed!" );
+			assert.ok( a.distanceToPoint( new Vector3( 1, 0, 0 ) ) === 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceToPoint", ( assert ) => {
+
+			var a = new Plane( new Vector3( 2, 0, 0 ), - 2 );
+
+			a.normalize();
+			assert.ok( a.distanceToPoint( a.projectPoint( zero3.clone() ) ) === 0, "Passed!" );
+			assert.ok( a.distanceToPoint( new Vector3( 4, 0, 0 ) ) === 3, "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceToSphere", ( assert ) => {
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), 0 );
+
+			var b = new Sphere( new Vector3( 2, 0, 0 ), 1 );
+
+			assert.ok( a.distanceToSphere( b ) === 1, "Passed!" );
+
+			a.set( new Vector3( 1, 0, 0 ), 2 );
+			assert.ok( a.distanceToSphere( b ) === 3, "Passed!" );
+			a.set( new Vector3( 1, 0, 0 ), - 2 );
+			assert.ok( a.distanceToSphere( b ) === - 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "projectPoint", ( assert ) => {
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), 0 );
+
+			assert.ok( a.projectPoint( new Vector3( 10, 0, 0 ) ).equals( zero3 ), "Passed!" );
+			assert.ok( a.projectPoint( new Vector3( - 10, 0, 0 ) ).equals( zero3 ), "Passed!" );
+
+			var a = new Plane( new Vector3( 0, 1, 0 ), - 1 );
+			assert.ok( a.projectPoint( new Vector3( 0, 0, 0 ) ).equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+			assert.ok( a.projectPoint( new Vector3( 0, 1, 0 ) ).equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "isInterestionLine/intersectLine", ( assert ) => {
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), 0 );
+
+			var l1 = new Line3( new Vector3( - 10, 0, 0 ), new Vector3( 10, 0, 0 ) );
+			assert.ok( a.intersectsLine( l1 ), "Passed!" );
+			assert.ok( a.intersectLine( l1 ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), - 3 );
+
+			assert.ok( a.intersectsLine( l1 ), "Passed!" );
+			assert.ok( a.intersectLine( l1 ).equals( new Vector3( 3, 0, 0 ) ), "Passed!" );
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), - 11 );
+
+			assert.ok( ! a.intersectsLine( l1 ), "Passed!" );
+			assert.ok( a.intersectLine( l1 ) === undefined, "Passed!" );
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), 11 );
+
+			assert.ok( ! a.intersectsLine( l1 ), "Passed!" );
+			assert.ok( a.intersectLine( l1 ) === undefined, "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsBox", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "intersectsSphere", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "coplanarPoint", ( assert ) => {
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), 0 );
+			assert.ok( a.distanceToPoint( a.coplanarPoint() ) === 0, "Passed!" );
+
+			var a = new Plane( new Vector3( 0, 1, 0 ), - 1 );
+			assert.ok( a.distanceToPoint( a.coplanarPoint() ) === 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "applyMatrix4/translate", ( assert ) => {
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), 0 );
+
+			var m = new Matrix4();
+			m.makeRotationZ( Math.PI * 0.5 );
+
+			assert.ok( comparePlane( a.clone().applyMatrix4( m ), new Plane( new Vector3( 0, 1, 0 ), 0 ) ), "Passed!" );
+
+			var a = new Plane( new Vector3( 0, 1, 0 ), - 1 );
+			assert.ok( comparePlane( a.clone().applyMatrix4( m ), new Plane( new Vector3( - 1, 0, 0 ), - 1 ) ), "Passed!" );
+
+			m.makeTranslation( 1, 1, 1 );
+			assert.ok( comparePlane( a.clone().applyMatrix4( m ), a.clone().translate( new Vector3( 1, 1, 1 ) ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			var a = new Plane( new Vector3( 1, 0, 0 ), 0 );
+			var b = new Plane( new Vector3( 1, 0, 0 ), 1 );
+			var c = new Plane( new 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.module( "Plane" );

+ 585 - 0
test/unit/src/math/Quaternion.tests.js

@@ -0,0 +1,585 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author tschw
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Quaternion } from '../../../../src/math/Quaternion';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Vector4 } from '../../../../src/math/Vector4';
+import { Euler } from '../../../../src/math/Euler';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import {
+	x,
+	y,
+	z,
+	w,
+	eps
+} from './Constants.tests';
+
+const orders = [ 'XYZ', 'YXZ', 'ZXY', 'ZYX', 'YZX', 'XZY' ];
+const eulerAngles = new Euler( 0.1, - 0.3, 0.25 );
+
+function qSub( a, b ) {
+
+	var result = new Quaternion();
+	result.copy( a );
+
+	result.x -= b.x;
+	result.y -= b.y;
+	result.z -= b.z;
+	result.w -= b.w;
+
+	return result;
+
+}
+
+function doSlerpObject( aArr, bArr, t ) {
+
+	var a = new Quaternion().fromArray( aArr ),
+		b = new Quaternion().fromArray( bArr ),
+		c = new Quaternion().fromArray( aArr );
+
+	c.slerp( b, t );
+
+	return {
+
+		equals: function ( x, y, z, w, maxError ) {
+
+			if ( maxError === undefined ) maxError = Number.EPSILON;
+
+			return Math.abs( x - c.x ) <= maxError &&
+				Math.abs( y - c.y ) <= maxError &&
+				Math.abs( z - c.z ) <= maxError &&
+				Math.abs( w - c.w ) <= maxError;
+
+		},
+
+		length: c.length(),
+
+		dotA: c.dot( a ),
+		dotB: c.dot( b )
+
+	};
+
+}
+
+function doSlerpArray( a, b, t ) {
+
+	var result = [ 0, 0, 0, 0 ];
+
+	Quaternion.slerpFlat( result, 0, a, 0, b, 0, t );
+
+	function arrDot( a, b ) {
+
+		return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] +
+			a[ 2 ] * b[ 2 ] + a[ 3 ] * b[ 3 ];
+
+	}
+
+	return {
+
+		equals: function ( x, y, z, w, maxError ) {
+
+			if ( maxError === undefined ) maxError = Number.EPSILON;
+
+			return Math.abs( x - result[ 0 ] ) <= maxError &&
+				Math.abs( y - result[ 1 ] ) <= maxError &&
+				Math.abs( z - result[ 2 ] ) <= maxError &&
+				Math.abs( w - result[ 3 ] ) <= maxError;
+
+		},
+
+		length: Math.sqrt( arrDot( result, result ) ),
+
+		dotA: arrDot( result, a ),
+		dotB: arrDot( result, b )
+
+	};
+
+}
+
+function slerpTestSkeleton( doSlerp, maxError, assert ) {
+
+	var a, b, result;
+
+	a = [
+		0.6753410084407496,
+		0.4087830051091744,
+		0.32856700410659473,
+		0.5185120064806223
+	];
+
+	b = [
+		0.6602792107657797,
+		0.43647413932562285,
+		0.35119011210236006,
+		0.5001871596632682
+	];
+
+	var maxNormError = 0;
+
+	function isNormal( result ) {
+
+		var normError = Math.abs( 1 - result.length );
+		maxNormError = Math.max( maxNormError, normError );
+		return normError <= maxError;
+
+	}
+
+	result = doSlerp( a, b, 0 );
+	assert.ok( result.equals(
+		a[ 0 ], a[ 1 ], a[ 2 ], a[ 3 ], 0 ), "Exactly A @ t = 0" );
+
+	result = doSlerp( a, b, 1 );
+	assert.ok( result.equals(
+		b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ], 0 ), "Exactly B @ t = 1" );
+
+	result = doSlerp( a, b, 0.5 );
+	assert.ok( Math.abs( result.dotA - result.dotB ) <= Number.EPSILON, "Symmetry at 0.5" );
+	assert.ok( isNormal( result ), "Approximately normal (at 0.5)" );
+
+	result = doSlerp( a, b, 0.25 );
+	assert.ok( result.dotA > result.dotB, "Interpolating at 0.25" );
+	assert.ok( isNormal( result ), "Approximately normal (at 0.25)" );
+
+	result = doSlerp( a, b, 0.75 );
+	assert.ok( result.dotA < result.dotB, "Interpolating at 0.75" );
+	assert.ok( isNormal( result ), "Approximately normal (at 0.75)" );
+
+	var D = Math.SQRT1_2;
+
+	result = doSlerp( [ 1, 0, 0, 0 ], [ 0, 0, 1, 0 ], 0.5 );
+	assert.ok( result.equals( D, 0, D, 0 ), "X/Z diagonal from axes" );
+	assert.ok( isNormal( result ), "Approximately normal (X/Z diagonal)" );
+
+	result = doSlerp( [ 0, D, 0, D ], [ 0, - D, 0, D ], 0.5 );
+	assert.ok( result.equals( 0, 0, 0, 1 ), "W-Unit from diagonals" );
+	assert.ok( isNormal( result ), "Approximately normal (W-Unit)" );
+
+}
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Quaternion', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Quaternion();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+			assert.ok( a.w == 1, "Passed!" );
+
+			var a = new Quaternion( x, y, z, w );
+			assert.ok( a.x === x, "Passed!" );
+			assert.ok( a.y === y, "Passed!" );
+			assert.ok( a.z === z, "Passed!" );
+			assert.ok( a.w === w, "Passed!" );
+
+		} );
+
+		// STATIC STUFF
+		QUnit.test( "slerp", ( assert ) => {
+
+			slerpTestSkeleton( doSlerpObject, Number.EPSILON, assert );
+
+		} );
+
+		QUnit.test( "slerpFlat", ( assert ) => {
+
+			slerpTestSkeleton( doSlerpArray, Number.EPSILON, assert );
+
+		} );
+
+		// PROPERTIES
+		QUnit.test( "properties", ( assert ) => {
+
+			assert.expect( 8 );
+
+			var a = new 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( "x", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "y", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "z", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "w", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Quaternion();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+			assert.ok( a.w == 1, "Passed!" );
+
+			a.set( x, y, z, w );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+			assert.ok( a.z === z, "Passed!" );
+			assert.ok( a.w === w, "Passed!" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Quaternion( x, y, z, w );
+			var b = new Quaternion().copy( a );
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+			assert.ok( b.z == z, "Passed!" );
+			assert.ok( b.w == w, "Passed!" );
+
+			// ensure that it is a true copy
+			a.x = 0;
+			a.y = - 1;
+			a.z = 0;
+			a.w = - 1;
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromEuler/setFromQuaternion", ( assert ) => {
+
+			var angles = [ new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ) ];
+
+			// ensure euler conversion to/from Quaternion matches.
+			for ( var i = 0; i < orders.length; i ++ ) {
+
+				for ( var j = 0; j < angles.length; j ++ ) {
+
+					var eulers2 = new Euler().setFromQuaternion( new Quaternion().setFromEuler( new Euler( angles[ j ].x, angles[ j ].y, angles[ j ].z, orders[ i ] ) ), orders[ i ] );
+					var newAngle = new Vector3( eulers2.x, eulers2.y, eulers2.z );
+					assert.ok( newAngle.distanceTo( angles[ j ] ) < 0.001, "Passed!" );
+
+				}
+
+			}
+
+		} );
+
+		QUnit.test( "setFromAxisAngle", ( assert ) => {
+
+			// TODO: find cases to validate.
+			// assert.ok( true, "Passed!" );
+
+			var zero = new Quaternion();
+
+			var a = new Quaternion().setFromAxisAngle( new Vector3( 1, 0, 0 ), 0 );
+			assert.ok( a.equals( zero ), "Passed!" );
+			a = new Quaternion().setFromAxisAngle( new Vector3( 0, 1, 0 ), 0 );
+			assert.ok( a.equals( zero ), "Passed!" );
+			a = new Quaternion().setFromAxisAngle( new Vector3( 0, 0, 1 ), 0 );
+			assert.ok( a.equals( zero ), "Passed!" );
+
+			var b1 = new Quaternion().setFromAxisAngle( new Vector3( 1, 0, 0 ), Math.PI );
+			assert.ok( ! a.equals( b1 ), "Passed!" );
+			var b2 = new Quaternion().setFromAxisAngle( new Vector3( 1, 0, 0 ), - Math.PI );
+			assert.ok( ! a.equals( b2 ), "Passed!" );
+
+			b1.multiply( b2 );
+			assert.ok( a.equals( b1 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromEuler/setFromRotationMatrix", ( assert ) => {
+
+			// ensure euler conversion for Quaternion matches that of Matrix4
+			for ( var i = 0; i < orders.length; i ++ ) {
+
+				var q = new Quaternion().setFromEuler( eulerAngles, orders[ i ] );
+				var m = new Matrix4().makeRotationFromEuler( eulerAngles, orders[ i ] );
+				var q2 = new Quaternion().setFromRotationMatrix( m );
+
+				assert.ok( qSub( q, q2 ).length() < 0.001, "Passed!" );
+
+			}
+
+		} );
+
+		QUnit.test( "setFromRotationMatrix", ( assert ) => {
+
+			// contrived examples purely to please the god of code coverage...
+			// match conditions in various 'else [if]' blocks
+
+			var a = new Quaternion();
+			var q = new Quaternion( - 9, - 2, 3, - 4 ).normalize();
+			var m = new Matrix4().makeRotationFromQuaternion( q );
+			var expected = new 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" );
+
+			var q = new Quaternion( - 1, - 2, 1, - 1 ).normalize();
+			m.makeRotationFromQuaternion( q );
+			var expected = new 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" );
+
+		} );
+
+		QUnit.test( "setFromUnitVectors", ( assert ) => {
+
+			var a = new Quaternion();
+			var b = new Vector3( 1, 0, 0 );
+			var c = new Vector3( 0, 1, 0 );
+			var expected = new 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( "inverse/conjugate", ( assert ) => {
+
+			var a = new Quaternion( x, y, z, w );
+
+			// TODO: add better validation here.
+
+			var b = a.clone().conjugate();
+
+			assert.ok( a.x == - b.x, "Passed!" );
+			assert.ok( a.y == - b.y, "Passed!" );
+			assert.ok( a.z == - b.z, "Passed!" );
+			assert.ok( a.w == b.w, "Passed!" );
+
+		} );
+
+		QUnit.test( "inverse", ( assert ) => {
+
+			assert.expect( 6 );
+
+			var a = new Quaternion( x, y, z, w );
+			var inverted = new 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( "dot", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "normalize/length/lengthSq", ( assert ) => {
+
+			var a = new Quaternion( x, y, z, w );
+			var b = new Quaternion( - x, - y, - z, - w );
+
+			assert.ok( a.length() != 1, "Passed!" );
+			assert.ok( a.lengthSq() != 1, "Passed!" );
+			a.normalize();
+			assert.ok( a.length() == 1, "Passed!" );
+			assert.ok( a.lengthSq() == 1, "Passed!" );
+
+			a.set( 0, 0, 0, 0 );
+			assert.ok( a.lengthSq() == 0, "Passed!" );
+			assert.ok( a.length() == 0, "Passed!" );
+			a.normalize();
+			assert.ok( a.lengthSq() == 1, "Passed!" );
+			assert.ok( a.length() == 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "multiplyQuaternions/multiply", ( assert ) => {
+
+			var angles = [ new Euler( 1, 0, 0 ), new Euler( 0, 1, 0 ), new Euler( 0, 0, 1 ) ];
+
+			var q1 = new Quaternion().setFromEuler( angles[ 0 ], "XYZ" );
+			var q2 = new Quaternion().setFromEuler( angles[ 1 ], "XYZ" );
+			var q3 = new Quaternion().setFromEuler( angles[ 2 ], "XYZ" );
+
+			var q = new Quaternion().multiplyQuaternions( q1, q2 ).multiply( q3 );
+
+			var m1 = new Matrix4().makeRotationFromEuler( angles[ 0 ], "XYZ" );
+			var m2 = new Matrix4().makeRotationFromEuler( angles[ 1 ], "XYZ" );
+			var m3 = new Matrix4().makeRotationFromEuler( angles[ 2 ], "XYZ" );
+
+			var m = new Matrix4().multiplyMatrices( m1, m2 ).multiply( m3 );
+
+			var qFromM = new Quaternion().setFromRotationMatrix( m );
+
+			assert.ok( qSub( q, qFromM ).length() < 0.001, "Passed!" );
+
+		} );
+
+		QUnit.test( "premultiply", ( assert ) => {
+
+			var a = new Quaternion( x, y, z, w );
+			var b = new Quaternion( 2 * x, - y, - 2 * z, w );
+			var expected = new 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( "slerp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			var a = new Quaternion( x, y, z, w );
+			var b = new Quaternion( - x, - y, - z, - w );
+
+			assert.ok( a.x != b.x, "Passed!" );
+			assert.ok( a.y != b.y, "Passed!" );
+
+			assert.ok( ! a.equals( b ), "Passed!" );
+			assert.ok( ! b.equals( a ), "Passed!" );
+
+			a.copy( b );
+			assert.ok( a.x == b.x, "Passed!" );
+			assert.ok( a.y == b.y, "Passed!" );
+
+			assert.ok( a.equals( b ), "Passed!" );
+			assert.ok( b.equals( a ), "Passed!" );
+
+		} );
+
+		QUnit.test( "fromArray", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "toArray", ( assert ) => {
+
+			var a = new 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" );
+
+			var 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" );
+
+			var 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( "onChange", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "onChangeCallback", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// OTHERS
+		QUnit.test( "multiplyVector3", ( assert ) => {
+
+			var angles = [ new Euler( 1, 0, 0 ), new Euler( 0, 1, 0 ), new Euler( 0, 0, 1 ) ];
+
+			// ensure euler conversion for Quaternion matches that of Matrix4
+			for ( var i = 0; i < orders.length; i ++ ) {
+
+				for ( var j = 0; j < angles.length; j ++ ) {
+
+					var q = new Quaternion().setFromEuler( angles[ j ], orders[ i ] );
+					var m = new Matrix4().makeRotationFromEuler( angles[ j ], orders[ i ] );
+
+					var v0 = new Vector3( 1, 0, 0 );
+					var qv = v0.clone().applyQuaternion( q );
+					var mv = v0.clone().applyMatrix4( m );
+
+					assert.ok( qv.distanceTo( mv ) < 0.001, "Passed!" );
+
+				}
+
+			}
+
+		} );
+
+	} );
+
+} );
+
+QUnit.module( "Quaternion" );

+ 462 - 0
test/unit/src/math/Ray.tests.js

@@ -0,0 +1,462 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Ray } from '../../../../src/math/Ray';
+import { Box3 } from '../../../../src/math/Box3';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Sphere } from '../../../../src/math/Sphere';
+import { Plane } from '../../../../src/math/Plane';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import {
+	zero3,
+	one3,
+	two3,
+	eps
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Ray', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Ray();
+			assert.ok( a.origin.equals( zero3 ), "Passed!" );
+			assert.ok( a.direction.equals( zero3 ), "Passed!" );
+
+			var a = new Ray( two3.clone(), one3.clone() );
+			assert.ok( a.origin.equals( two3 ), "Passed!" );
+			assert.ok( a.direction.equals( one3 ), "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isRay", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Ray();
+
+			a.set( one3, one3 );
+			assert.ok( a.origin.equals( one3 ), "Passed!" );
+			assert.ok( a.direction.equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "recast/clone", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+
+			assert.ok( a.recast( 0 ).equals( a ), "Passed!" );
+
+			var b = a.clone();
+			assert.ok( b.recast( - 1 ).equals( new Ray( new Vector3( 1, 1, 0 ), new Vector3( 0, 0, 1 ) ) ), "Passed!" );
+
+			var c = a.clone();
+			assert.ok( c.recast( 1 ).equals( new Ray( new Vector3( 1, 1, 2 ), new Vector3( 0, 0, 1 ) ) ), "Passed!" );
+
+			var d = a.clone();
+			var e = d.clone().recast( 1 );
+			assert.ok( d.equals( a ), "Passed!" );
+			assert.ok( ! e.equals( d ), "Passed!" );
+			assert.ok( e.equals( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "copy/equals", ( assert ) => {
+
+			var a = new Ray( zero3.clone(), one3.clone() );
+			var b = new Ray().copy( a );
+			assert.ok( b.origin.equals( zero3 ), "Passed!" );
+			assert.ok( b.direction.equals( one3 ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.origin = zero3;
+			a.direction = one3;
+			assert.ok( b.origin.equals( zero3 ), "Passed!" );
+			assert.ok( b.direction.equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "at", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+
+			assert.ok( a.at( 0 ).equals( one3 ), "Passed!" );
+			assert.ok( a.at( - 1 ).equals( new Vector3( 1, 1, 0 ) ), "Passed!" );
+			assert.ok( a.at( 1 ).equals( new Vector3( 1, 1, 2 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "lookAt", ( assert ) => {
+
+			var a = new 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( "closestPointToPoint", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+
+			// behind the ray
+			var b = a.closestPointToPoint( zero3 );
+			assert.ok( b.equals( one3 ), "Passed!" );
+
+			// front of the ray
+			var c = a.closestPointToPoint( new Vector3( 0, 0, 50 ) );
+			assert.ok( c.equals( new Vector3( 1, 1, 50 ) ), "Passed!" );
+
+			// exactly on the ray
+			var d = a.closestPointToPoint( one3 );
+			assert.ok( d.equals( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceToPoint", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+
+			// behind the ray
+			var b = a.distanceToPoint( zero3 );
+			assert.ok( b === Math.sqrt( 3 ), "Passed!" );
+
+			// front of the ray
+			var c = a.distanceToPoint( new Vector3( 0, 0, 50 ) );
+			assert.ok( c === Math.sqrt( 2 ), "Passed!" );
+
+			// exactly on the ray
+			var d = a.distanceToPoint( one3 );
+			assert.ok( d === 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceSqToPoint", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+
+			// behind the ray
+			var b = a.distanceSqToPoint( zero3 );
+			assert.ok( b === 3, "Passed!" );
+
+			// front of the ray
+			var c = a.distanceSqToPoint( new Vector3( 0, 0, 50 ) );
+			assert.ok( c === 2, "Passed!" );
+
+			// exactly on the ray
+			var d = a.distanceSqToPoint( one3 );
+			assert.ok( d === 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceSqToSegment", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+			var ptOnLine = new Vector3();
+			var ptOnSegment = new Vector3();
+
+			//segment in front of the ray
+			var v0 = new Vector3( 3, 5, 50 );
+			var v1 = new Vector3( 50, 50, 50 ); // just a far away point
+			var distSqr = a.distanceSqToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+			assert.ok( ptOnSegment.distanceTo( v0 ) < 0.0001, "Passed!" );
+			assert.ok( ptOnLine.distanceTo( new Vector3( 1, 1, 50 ) ) < 0.0001, "Passed!" );
+			// ((3-1) * (3-1) + (5-1) * (5-1) = 4 + 16 = 20
+			assert.ok( Math.abs( distSqr - 20 ) < 0.0001, "Passed!" );
+
+			//segment behind the ray
+			var v0 = new Vector3( - 50, - 50, - 50 ); // just a far away point
+			var v1 = new Vector3( - 3, - 5, - 4 );
+			var distSqr = a.distanceSqToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+			assert.ok( ptOnSegment.distanceTo( v1 ) < 0.0001, "Passed!" );
+			assert.ok( ptOnLine.distanceTo( one3 ) < 0.0001, "Passed!" );
+			// ((-3-1) * (-3-1) + (-5-1) * (-5-1) + (-4-1) + (-4-1) = 16 + 36 + 25 = 77
+			assert.ok( Math.abs( distSqr - 77 ) < 0.0001, "Passed!" );
+
+			//exact intersection between the ray and the segment
+			var v0 = new Vector3( - 50, - 50, - 50 );
+			var v1 = new Vector3( 50, 50, 50 );
+			var distSqr = a.distanceSqToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+			assert.ok( ptOnSegment.distanceTo( one3 ) < 0.0001, "Passed!" );
+			assert.ok( ptOnLine.distanceTo( one3 ) < 0.0001, "Passed!" );
+			assert.ok( distSqr < 0.0001, "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectSphere", ( assert ) => {
+
+			var TOL = 0.0001;
+
+			// ray a0 origin located at ( 0, 0, 0 ) and points outward in negative-z direction
+			var a0 = new Ray( zero3.clone(), new Vector3( 0, 0, - 1 ) );
+			// ray a1 origin located at ( 1, 1, 1 ) and points left in negative-x direction
+			var a1 = new Ray( one3.clone(), new Vector3( - 1, 0, 0 ) );
+
+			// sphere (radius of 2) located behind ray a0, should result in null
+			var b = new Sphere( new Vector3( 0, 0, 3 ), 2 );
+			assert.ok( a0.intersectSphere( b ) === null, "Passed!" );
+
+			// sphere (radius of 2) located in front of, but too far right of ray a0, should result in null
+			var b = new Sphere( new Vector3( 3, 0, - 1 ), 2 );
+			assert.ok( a0.intersectSphere( b ) === null, "Passed!" );
+
+			// sphere (radius of 2) located below ray a1, should result in null
+			var b = new Sphere( new Vector3( 1, - 2, 1 ), 2 );
+			assert.ok( a1.intersectSphere( b ) === null, "Passed!" );
+
+			// sphere (radius of 1) located to the left of ray a1, should result in intersection at 0, 1, 1
+			var b = new Sphere( new Vector3( - 1, 1, 1 ), 1 );
+			assert.ok( a1.intersectSphere( b ).distanceTo( new Vector3( 0, 1, 1 ) ) < TOL, "Passed!" );
+
+			// sphere (radius of 1) located in front of ray a0, should result in intersection at 0, 0, -1
+			var b = new Sphere( new Vector3( 0, 0, - 2 ), 1 );
+			assert.ok( a0.intersectSphere( b ).distanceTo( new Vector3( 0, 0, - 1 ) ) < TOL, "Passed!" );
+
+			// sphere (radius of 2) located in front & right of ray a0, should result in intersection at 0, 0, -1, or left-most edge of sphere
+			var b = new Sphere( new Vector3( 2, 0, - 1 ), 2 );
+			assert.ok( a0.intersectSphere( b ).distanceTo( new Vector3( 0, 0, - 1 ) ) < TOL, "Passed!" );
+
+			// same situation as above, but move the sphere a fraction more to the right, and ray a0 should now just miss
+			var b = new Sphere( new Vector3( 2.01, 0, - 1 ), 2 );
+			assert.ok( a0.intersectSphere( b ) === null, "Passed!" );
+
+			// following QUnit.tests are for situations where the ray origin is inside the sphere
+
+			// sphere (radius of 1) center located at ray a0 origin / sphere surrounds the ray origin, so the first intersect point 0, 0, 1,
+			// is behind ray a0.  Therefore, second exit point on back of sphere will be returned: 0, 0, -1
+			// thus keeping the intersection point always in front of the ray.
+			var b = new Sphere( zero3.clone(), 1 );
+			assert.ok( a0.intersectSphere( b ).distanceTo( new Vector3( 0, 0, - 1 ) ) < TOL, "Passed!" );
+
+			// sphere (radius of 4) center located behind ray a0 origin / sphere surrounds the ray origin, so the first intersect point 0, 0, 5,
+			// is behind ray a0.  Therefore, second exit point on back of sphere will be returned: 0, 0, -3
+			// thus keeping the intersection point always in front of the ray.
+			var b = new Sphere( new Vector3( 0, 0, 1 ), 4 );
+			assert.ok( a0.intersectSphere( b ).distanceTo( new Vector3( 0, 0, - 3 ) ) < TOL, "Passed!" );
+
+			// sphere (radius of 4) center located in front of ray a0 origin / sphere surrounds the ray origin, so the first intersect point 0, 0, 3,
+			// is behind ray a0.  Therefore, second exit point on back of sphere will be returned: 0, 0, -5
+			// thus keeping the intersection point always in front of the ray.
+			var b = new Sphere( new Vector3( 0, 0, - 1 ), 4 );
+			assert.ok( a0.intersectSphere( b ).distanceTo( new Vector3( 0, 0, - 5 ) ) < TOL, "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsSphere", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+			var b = new Sphere( zero3, 0.5 );
+			var c = new Sphere( zero3, 1.5 );
+			var d = new Sphere( one3, 0.1 );
+			var e = new Sphere( two3, 0.1 );
+			var f = new Sphere( two3, 1 );
+
+			assert.ok( ! a.intersectsSphere( b ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( c ), "Passed!" );
+			assert.ok( a.intersectsSphere( d ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( e ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( f ), "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceToPlane", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "intersectPlane", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+
+			// parallel plane behind
+			var b = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 0, 0, 1 ), new Vector3( 1, 1, - 1 ) );
+			assert.ok( a.intersectPlane( b ) === null, "Passed!" );
+
+			// parallel plane coincident with origin
+			var c = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 0, 0, 1 ), new Vector3( 1, 1, 0 ) );
+			assert.ok( a.intersectPlane( c ) === null, "Passed!" );
+
+			// parallel plane infront
+			var d = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 0, 0, 1 ), new Vector3( 1, 1, 1 ) );
+			assert.ok( a.intersectPlane( d ).equals( a.origin ), "Passed!" );
+
+			// perpendical ray that overlaps exactly
+			var e = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 1, 0, 0 ), one3 );
+			assert.ok( a.intersectPlane( e ).equals( a.origin ), "Passed!" );
+
+			// perpendical ray that doesn't overlap
+			var f = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 1, 0, 0 ), zero3 );
+			assert.ok( a.intersectPlane( f ) === null, "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsPlane", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+
+			// parallel plane in front of the ray
+			var b = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 0, 0, 1 ), one3.clone().sub( new Vector3( 0, 0, - 1 ) ) );
+			assert.ok( a.intersectsPlane( b ), "Passed!" );
+
+			// parallel plane coincident with origin
+			var c = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 0, 0, 1 ), one3.clone().sub( new Vector3( 0, 0, 0 ) ) );
+			assert.ok( a.intersectsPlane( c ), "Passed!" );
+
+			// parallel plane behind the ray
+			var d = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 0, 0, 1 ), one3.clone().sub( new Vector3( 0, 0, 1 ) ) );
+			assert.ok( ! a.intersectsPlane( d ), "Passed!" );
+
+			// perpendical ray that overlaps exactly
+			var e = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 1, 0, 0 ), one3 );
+			assert.ok( a.intersectsPlane( e ), "Passed!" );
+
+			// perpendical ray that doesn't overlap
+			var f = new Plane().setFromNormalAndCoplanarPoint( new Vector3( 1, 0, 0 ), zero3 );
+			assert.ok( ! a.intersectsPlane( f ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectBox", ( assert ) => {
+
+			var TOL = 0.0001;
+
+			var box = new Box3( new Vector3( - 1, - 1, - 1 ), new Vector3( 1, 1, 1 ) );
+
+			var a = new Ray( new Vector3( - 2, 0, 0 ), new Vector3( 1, 0, 0 ) );
+			//ray should intersect box at -1,0,0
+			assert.ok( a.intersectsBox( box ) === true, "Passed!" );
+			assert.ok( a.intersectBox( box ).distanceTo( new Vector3( - 1, 0, 0 ) ) < TOL, "Passed!" );
+
+			var b = new Ray( new Vector3( - 2, 0, 0 ), new Vector3( - 1, 0, 0 ) );
+			//ray is point away from box, it should not intersect
+			assert.ok( b.intersectsBox( box ) === false, "Passed!" );
+			assert.ok( b.intersectBox( box ) === null, "Passed!" );
+
+			var c = new Ray( new Vector3( 0, 0, 0 ), new Vector3( 1, 0, 0 ) );
+			// ray is inside box, should return exit point
+			assert.ok( c.intersectsBox( box ) === true, "Passed!" );
+			assert.ok( c.intersectBox( box ).distanceTo( new Vector3( 1, 0, 0 ) ) < TOL, "Passed!" );
+
+			var d = new Ray( new Vector3( 0, 2, 1 ), new Vector3( 0, - 1, - 1 ).normalize() );
+			//tilted ray should intersect box at 0,1,0
+			assert.ok( d.intersectsBox( box ) === true, "Passed!" );
+			assert.ok( d.intersectBox( box ).distanceTo( new Vector3( 0, 1, 0 ) ) < TOL, "Passed!" );
+
+			var e = new Ray( new Vector3( 1, - 2, 1 ), new Vector3( 0, 1, 0 ).normalize() );
+			//handle case where ray is coplanar with one of the boxes side - box in front of ray
+			assert.ok( e.intersectsBox( box ) === true, "Passed!" );
+			assert.ok( e.intersectBox( box ).distanceTo( new Vector3( 1, - 1, 1 ) ) < TOL, "Passed!" );
+
+			var f = new Ray( new Vector3( 1, - 2, 0 ), new Vector3( 0, - 1, 0 ).normalize() );
+			//handle case where ray is coplanar with one of the boxes side - box behind ray
+			assert.ok( f.intersectsBox( box ) === false, "Passed!" );
+			assert.ok( f.intersectBox( box ) == null, "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsBox", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "intersectTriangle", ( assert ) => {
+
+			var ray = new Ray();
+			var a = new Vector3( 1, 1, 0 );
+			var b = new Vector3( 0, 1, 1 );
+			var c = new 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" );
+
+		} );
+
+		QUnit.test( "applyMatrix4", ( assert ) => {
+
+			var a = new Ray( one3.clone(), new Vector3( 0, 0, 1 ) );
+			var m = new Matrix4();
+
+			assert.ok( a.clone().applyMatrix4( m ).equals( a ), "Passed!" );
+
+			var a = new Ray( zero3.clone(), new Vector3( 0, 0, 1 ) );
+			m.makeRotationZ( Math.PI );
+			assert.ok( a.clone().applyMatrix4( m ).equals( a ), "Passed!" );
+
+			m.makeRotationX( Math.PI );
+			var b = a.clone();
+			b.direction.negate();
+			var a2 = a.clone().applyMatrix4( m );
+			assert.ok( a2.origin.distanceTo( b.origin ) < 0.0001, "Passed!" );
+			assert.ok( a2.direction.distanceTo( b.direction ) < 0.0001, "Passed!" );
+
+			a.origin = new Vector3( 0, 0, 1 );
+			b.origin = new Vector3( 0, 0, - 1 );
+			var a2 = a.clone().applyMatrix4( m );
+			assert.ok( a2.origin.distanceTo( b.origin ) < 0.0001, "Passed!" );
+			assert.ok( a2.direction.distanceTo( b.direction ) < 0.0001, "Passed!" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+	} );
+
+} );

+ 230 - 0
test/unit/src/math/Sphere.tests.js

@@ -0,0 +1,230 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Ray } from '../../../../src/math/Ray';
+import { Box3 } from '../../../../src/math/Box3';
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Sphere } from '../../../../src/math/Sphere';
+import { Plane } from '../../../../src/math/Plane';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import {
+	zero3,
+	one3,
+	two3,
+	eps
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Sphere', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Sphere();
+			assert.ok( a.center.equals( zero3 ), "Passed!" );
+			assert.ok( a.radius == 0, "Passed!" );
+
+			var a = new Sphere( one3.clone(), 1 );
+			assert.ok( a.center.equals( one3 ), "Passed!" );
+			assert.ok( a.radius == 1, "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isSphere", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Sphere();
+			assert.ok( a.center.equals( zero3 ), "Passed!" );
+			assert.ok( a.radius == 0, "Passed!" );
+
+			a.set( one3, 1 );
+			assert.ok( a.center.equals( one3 ), "Passed!" );
+			assert.ok( a.radius == 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromPoints", ( assert ) => {
+
+			var a = new Sphere();
+			var expectedCenter = new Vector3( 0.9330126941204071, 0, 0 );
+			var expectedRadius = 1.3676668773461689;
+			var optionalCenter = new Vector3( 1, 1, 1 );
+			var points = [
+				new Vector3( 1, 1, 0 ), new Vector3( 1, 1, 0 ),
+				new Vector3( 1, 1, 0 ), new Vector3( 1, 1, 0 ),
+				new Vector3( 1, 1, 0 ), new Vector3( 0.8660253882408142, 0.5, 0 ),
+				new Vector3( - 0, 0.5, 0.8660253882408142 ), new Vector3( 1.8660253882408142, 0.5, 0 ),
+				new Vector3( 0, 0.5, - 0.8660253882408142 ), new Vector3( 0.8660253882408142, 0.5, - 0 ),
+				new Vector3( 0.8660253882408142, - 0.5, 0 ), new Vector3( - 0, - 0.5, 0.8660253882408142 ),
+				new Vector3( 1.8660253882408142, - 0.5, 0 ), new Vector3( 0, - 0.5, - 0.8660253882408142 ),
+				new Vector3( 0.8660253882408142, - 0.5, - 0 ), new Vector3( - 0, - 1, 0 ),
+				new Vector3( - 0, - 1, 0 ), new Vector3( 0, - 1, 0 ),
+				new Vector3( 0, - 1, - 0 ), new 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" );
+
+			var 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( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+			var b = new Sphere().copy( a );
+
+			assert.ok( b.center.equals( one3 ), "Passed!" );
+			assert.ok( b.radius == 1, "Passed!" );
+
+			// ensure that it is a true copy
+			a.center = zero3;
+			a.radius = 0;
+			assert.ok( b.center.equals( one3 ), "Passed!" );
+			assert.ok( b.radius == 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "empty", ( assert ) => {
+
+			var a = new Sphere();
+			assert.ok( a.empty(), "Passed!" );
+
+			a.set( one3, 1 );
+			assert.ok( ! a.empty(), "Passed!" );
+
+		} );
+
+		QUnit.test( "containsPoint", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+
+			assert.ok( ! a.containsPoint( zero3 ), "Passed!" );
+			assert.ok( a.containsPoint( one3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "distanceToPoint", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+
+			assert.ok( ( a.distanceToPoint( zero3 ) - 0.7320 ) < 0.001, "Passed!" );
+			assert.ok( a.distanceToPoint( one3 ) === - 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsSphere", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+			var b = new Sphere( zero3.clone(), 1 );
+			var c = new Sphere( zero3.clone(), 0.25 );
+
+			assert.ok( a.intersectsSphere( b ), "Passed!" );
+			assert.ok( ! a.intersectsSphere( c ), "Passed!" );
+
+		} );
+
+		QUnit.test( "intersectsBox", ( assert ) => {
+
+			var a = new Sphere();
+			var b = new Sphere( new Vector3( - 5, - 5, - 5 ) );
+			var box = new Box3( zero3, one3 );
+
+			assert.strictEqual( a.intersectsBox( box ), true, "Check default sphere" );
+			assert.strictEqual( b.intersectsBox( box ), false, "Check shifted sphere" );
+
+		} );
+
+		QUnit.test( "intersectsPlane", ( assert ) => {
+
+			var a = new Sphere( zero3.clone(), 1 );
+			var b = new Plane( new Vector3( 0, 1, 0 ), 1 );
+			var c = new Plane( new Vector3( 0, 1, 0 ), 1.25 );
+			var d = new Plane( new Vector3( 0, - 1, 0 ), 1.25 );
+
+			assert.ok( a.intersectsPlane( b ), "Passed!" );
+			assert.ok( ! a.intersectsPlane( c ), "Passed!" );
+			assert.ok( ! a.intersectsPlane( d ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clampPoint", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+
+			assert.ok( a.clampPoint( new Vector3( 1, 1, 3 ) ).equals( new Vector3( 1, 1, 2 ) ), "Passed!" );
+			assert.ok( a.clampPoint( new Vector3( 1, 1, - 3 ) ).equals( new Vector3( 1, 1, 0 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "getBoundingBox", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+
+			assert.ok( a.getBoundingBox().equals( new Box3( zero3, two3 ) ), "Passed!" );
+
+			a.set( zero3, 0 );
+			assert.ok( a.getBoundingBox().equals( new Box3( zero3, zero3 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "applyMatrix4", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+			var m = new Matrix4().makeTranslation( 1, - 2, 1 );
+
+			assert.ok( a.clone().applyMatrix4( m ).getBoundingBox().equals( a.getBoundingBox().applyMatrix4( m ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "translate", ( assert ) => {
+
+			var a = new Sphere( one3.clone(), 1 );
+
+			a.translate( one3.clone().negate() );
+			assert.ok( a.center.equals( zero3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			var a = new Sphere();
+			var b = new Sphere( new Vector3( 1, 0, 0 ) );
+			var c = new Sphere( new 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()" );
+
+		} );
+
+	} );
+
+} );

+ 129 - 0
test/unit/src/math/Spherical.tests.js

@@ -0,0 +1,129 @@
+/**
+ * @author moraxy / https://github.com/moraxy
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Spherical } from '../../../../src/math/Spherical';
+import { Vector3 } from '../../../../src/math/Vector3';
+import {
+	eps
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Spherical', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new 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" );
+
+			var a = new 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" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isSpherical", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new 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", ( assert ) => {
+
+			var radius = 10.0;
+			var phi = Math.acos( - 0.5 );
+			var theta = Math.sqrt( Math.PI ) * phi;
+			var a = new 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", ( assert ) => {
+
+			var radius = 10.0;
+			var phi = Math.acos( - 0.5 );
+			var theta = Math.sqrt( Math.PI ) * phi;
+			var a = new Spherical( radius, phi, theta );
+			var b = new 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", ( assert ) => {
+
+			var EPS = 0.000001; // from source
+			var tooLow = 0.0;
+			var tooHigh = Math.PI;
+			var justRight = 1.5;
+			var a = new 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", ( assert ) => {
+
+			var a = new Spherical( 1, 1, 1 );
+			var b = new Vector3( 0, 0, 0 );
+			var c = new Vector3( Math.PI, 1, - Math.PI );
+			var expected = new 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" );
+
+		} );
+
+	} );
+
+} );

+ 289 - 0
test/unit/src/math/Triangle.tests.js

@@ -0,0 +1,289 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Triangle } from '../../../../src/math/Triangle';
+import { Vector3 } from '../../../../src/math/Vector3';
+import {
+	zero3,
+	one3,
+	two3
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Triangle', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Triangle();
+			assert.ok( a.a.equals( zero3 ), "Passed!" );
+			assert.ok( a.b.equals( zero3 ), "Passed!" );
+			assert.ok( a.c.equals( zero3 ), "Passed!" );
+
+			var a = new Triangle( one3.clone().negate(), one3.clone(), two3.clone() );
+			assert.ok( a.a.equals( one3.clone().negate() ), "Passed!" );
+			assert.ok( a.b.equals( one3 ), "Passed!" );
+			assert.ok( a.c.equals( two3 ), "Passed!" );
+
+		} );
+
+		// STATIC STUFF
+		QUnit.test( "normal", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "barycoordFromPoint", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "containsPoint", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Triangle();
+
+			a.set( one3.clone().negate(), one3, two3 );
+			assert.ok( a.a.equals( one3.clone().negate() ), "Passed!" );
+			assert.ok( a.b.equals( one3 ), "Passed!" );
+			assert.ok( a.c.equals( two3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "setFromPointsAndIndices", ( assert ) => {
+
+			var a = new Triangle();
+
+			var points = [ one3, one3.clone().negate(), two3 ];
+			a.setFromPointsAndIndices( points, 1, 0, 2 );
+			assert.ok( a.a.equals( one3.clone().negate() ), "Passed!" );
+			assert.ok( a.b.equals( one3 ), "Passed!" );
+			assert.ok( a.c.equals( two3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Triangle( one3.clone().negate(), one3.clone(), two3.clone() );
+			var b = new Triangle().copy( a );
+			assert.ok( b.a.equals( one3.clone().negate() ), "Passed!" );
+			assert.ok( b.b.equals( one3 ), "Passed!" );
+			assert.ok( b.c.equals( two3 ), "Passed!" );
+
+			// ensure that it is a true copy
+			a.a = one3;
+			a.b = zero3;
+			a.c = zero3;
+			assert.ok( b.a.equals( one3.clone().negate() ), "Passed!" );
+			assert.ok( b.b.equals( one3 ), "Passed!" );
+			assert.ok( b.c.equals( two3 ), "Passed!" );
+
+		} );
+
+		QUnit.test( "area", ( assert ) => {
+
+			var a = new Triangle();
+
+			assert.ok( a.area() == 0, "Passed!" );
+
+			var a = new Triangle( new Vector3( 0, 0, 0 ), new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ) );
+			assert.ok( a.area() == 0.5, "Passed!" );
+
+			var a = new Triangle( new Vector3( 2, 0, 0 ), new Vector3( 0, 0, 0 ), new Vector3( 0, 0, 2 ) );
+			assert.ok( a.area() == 2, "Passed!" );
+
+			// colinear triangle.
+			var a = new Triangle( new Vector3( 2, 0, 0 ), new Vector3( 0, 0, 0 ), new Vector3( 3, 0, 0 ) );
+			assert.ok( a.area() == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "midpoint", ( assert ) => {
+
+			var a = new Triangle();
+
+			assert.ok( a.midpoint().equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 0, 0, 0 ), new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ) );
+			assert.ok( a.midpoint().equals( new Vector3( 1 / 3, 1 / 3, 0 ) ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 2, 0, 0 ), new Vector3( 0, 0, 0 ), new Vector3( 0, 0, 2 ) );
+			assert.ok( a.midpoint().equals( new Vector3( 2 / 3, 0, 2 / 3 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "normal", ( assert ) => {
+
+			var a = new Triangle();
+
+			assert.ok( a.normal().equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 0, 0, 0 ), new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ) );
+			assert.ok( a.normal().equals( new Vector3( 0, 0, 1 ) ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 2, 0, 0 ), new Vector3( 0, 0, 0 ), new Vector3( 0, 0, 2 ) );
+			assert.ok( a.normal().equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "plane", ( assert ) => {
+
+			var a = new Triangle();
+
+			assert.notOk( isNaN( a.plane().distanceToPoint( a.a ) ), "Passed!" );
+			assert.notOk( isNaN( a.plane().distanceToPoint( a.b ) ), "Passed!" );
+			assert.notOk( isNaN( a.plane().distanceToPoint( a.c ) ), "Passed!" );
+			assert.notPropEqual( a.plane().normal, {
+				x: NaN,
+				y: NaN,
+				z: NaN
+			}, "Passed!" );
+
+			var a = new Triangle( new Vector3( 0, 0, 0 ), new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ) );
+			assert.ok( a.plane().distanceToPoint( a.a ) == 0, "Passed!" );
+			assert.ok( a.plane().distanceToPoint( a.b ) == 0, "Passed!" );
+			assert.ok( a.plane().distanceToPoint( a.c ) == 0, "Passed!" );
+			assert.ok( a.plane().normal.equals( a.normal() ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 2, 0, 0 ), new Vector3( 0, 0, 0 ), new Vector3( 0, 0, 2 ) );
+			assert.ok( a.plane().distanceToPoint( a.a ) == 0, "Passed!" );
+			assert.ok( a.plane().distanceToPoint( a.b ) == 0, "Passed!" );
+			assert.ok( a.plane().distanceToPoint( a.c ) == 0, "Passed!" );
+			assert.ok( a.plane().normal.clone().normalize().equals( a.normal() ), "Passed!" );
+
+		} );
+
+		QUnit.test( "barycoordFromPoint", ( assert ) => {
+
+			var a = new Triangle();
+
+			var bad = new Vector3( - 2, - 1, - 1 );
+
+			assert.ok( a.barycoordFromPoint( a.a ).equals( bad ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.b ).equals( bad ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.c ).equals( bad ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 0, 0, 0 ), new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ) );
+			assert.ok( a.barycoordFromPoint( a.a ).equals( new Vector3( 1, 0, 0 ) ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.b ).equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.c ).equals( new Vector3( 0, 0, 1 ) ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.midpoint() ).distanceTo( new Vector3( 1 / 3, 1 / 3, 1 / 3 ) ) < 0.0001, "Passed!" );
+
+			var a = new Triangle( new Vector3( 2, 0, 0 ), new Vector3( 0, 0, 0 ), new Vector3( 0, 0, 2 ) );
+			assert.ok( a.barycoordFromPoint( a.a ).equals( new Vector3( 1, 0, 0 ) ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.b ).equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.c ).equals( new Vector3( 0, 0, 1 ) ), "Passed!" );
+			assert.ok( a.barycoordFromPoint( a.midpoint() ).distanceTo( new Vector3( 1 / 3, 1 / 3, 1 / 3 ) ) < 0.0001, "Passed!" );
+
+		} );
+
+		QUnit.test( "containsPoint", ( assert ) => {
+
+			var a = new Triangle();
+
+			assert.ok( ! a.containsPoint( a.a ), "Passed!" );
+			assert.ok( ! a.containsPoint( a.b ), "Passed!" );
+			assert.ok( ! a.containsPoint( a.c ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 0, 0, 0 ), new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ) );
+			assert.ok( a.containsPoint( a.a ), "Passed!" );
+			assert.ok( a.containsPoint( a.b ), "Passed!" );
+			assert.ok( a.containsPoint( a.c ), "Passed!" );
+			assert.ok( a.containsPoint( a.midpoint() ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( - 1, - 1, - 1 ) ), "Passed!" );
+
+			var a = new Triangle( new Vector3( 2, 0, 0 ), new Vector3( 0, 0, 0 ), new Vector3( 0, 0, 2 ) );
+			assert.ok( a.containsPoint( a.a ), "Passed!" );
+			assert.ok( a.containsPoint( a.b ), "Passed!" );
+			assert.ok( a.containsPoint( a.c ), "Passed!" );
+			assert.ok( a.containsPoint( a.midpoint() ), "Passed!" );
+			assert.ok( ! a.containsPoint( new Vector3( - 1, - 1, - 1 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "closestPointToPoint", ( assert ) => {
+
+			var a = new Triangle( new Vector3( - 1, 0, 0 ), new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ) );
+
+			// point lies inside the triangle
+			var b0 = a.closestPointToPoint( new Vector3( 0, 0.5, 0 ) );
+			assert.ok( b0.equals( new Vector3( 0, 0.5, 0 ) ), "Passed!" );
+
+			// point lies on a vertex
+			var b0 = a.closestPointToPoint( a.a );
+			assert.ok( b0.equals( a.a ), "Passed!" );
+
+			var b0 = a.closestPointToPoint( a.b );
+			assert.ok( b0.equals( a.b ), "Passed!" );
+
+			var b0 = a.closestPointToPoint( a.c );
+			assert.ok( b0.equals( a.c ), "Passed!" );
+
+			// point lies on an edge
+			var b0 = a.closestPointToPoint( zero3.clone() );
+			assert.ok( b0.equals( zero3.clone() ), "Passed!" );
+
+			// point lies outside the triangle
+			var b0 = a.closestPointToPoint( new Vector3( - 2, 0, 0 ) );
+			assert.ok( b0.equals( new Vector3( - 1, 0, 0 ) ), "Passed!" );
+
+			var b0 = a.closestPointToPoint( new Vector3( 2, 0, 0 ) );
+			assert.ok( b0.equals( new Vector3( 1, 0, 0 ) ), "Passed!" );
+
+			var b0 = a.closestPointToPoint( new Vector3( 0, 2, 0 ) );
+			assert.ok( b0.equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+
+			var b0 = a.closestPointToPoint( new Vector3( 0, - 2, 0 ) );
+			assert.ok( b0.equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			var a = new Triangle(
+				new Vector3( 1, 0, 0 ),
+				new Vector3( 0, 1, 0 ),
+				new Vector3( 0, 0, 1 )
+			);
+			var b = new Triangle(
+				new Vector3( 0, 0, 1 ),
+				new Vector3( 0, 1, 0 ),
+				new Vector3( 1, 0, 0 )
+			);
+			var c = new Triangle(
+				new Vector3( - 1, 0, 0 ),
+				new Vector3( 0, 1, 0 ),
+				new 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()" );
+
+		} );
+
+	} );
+
+} );

+ 684 - 0
test/unit/src/math/Vector2.tests.js

@@ -0,0 +1,684 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Vector2 } from '../../../../src/math/Vector2';
+import { Matrix3 } from '../../../../src/math/Matrix3';
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+import {
+	x,
+	y
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Vector2', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Vector2();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+
+			var a = new Vector2( x, y );
+			assert.ok( a.x === x, "Passed!" );
+			assert.ok( a.y === y, "Passed!" );
+
+		} );
+
+		// PROPERTIES // ( [Itee] WHAT ??? o_O )
+		QUnit.test( "properties", ( assert ) => {
+
+			var a = new 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( "width", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "height", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isVector2", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Vector2();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+
+			a.set( x, y );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+
+		} );
+
+		QUnit.test( "setScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setX", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setY", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setComponent", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getComponent", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new Vector2().copy( a );
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+
+			// ensure that it is a true copy
+			a.x = 0;
+			a.y = - 1;
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+
+		} );
+
+		QUnit.test( "add", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new Vector2( - x, - y );
+
+			a.add( b );
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+
+			var c = new Vector2().addVectors( b, b );
+			assert.ok( c.x == - 2 * x, "Passed!" );
+			assert.ok( c.y == - 2 * y, "Passed!" );
+
+		} );
+
+		QUnit.test( "addScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "addVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "addScaledVector", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new 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( "sub", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new Vector2( - x, - y );
+
+			a.sub( b );
+			assert.ok( a.x == 2 * x, "Passed!" );
+			assert.ok( a.y == 2 * y, "Passed!" );
+
+			var c = new Vector2().subVectors( a, a );
+			assert.ok( c.x == 0, "Passed!" );
+			assert.ok( c.y == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "subScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "subVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "multiply", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "multiplyScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "divide", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "divideScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "applyMatrix3", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var m = new 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( "min", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "max", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clamp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clampScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clampLength", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "floor", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "ceil", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "round", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "roundToZero", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "negate", ( assert ) => {
+
+			var a = new Vector2( x, y );
+
+			a.negate();
+			assert.ok( a.x == - x, "Passed!" );
+			assert.ok( a.y == - y, "Passed!" );
+
+		} );
+
+		QUnit.test( "dot", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new Vector2( - x, - y );
+			var c = new Vector2();
+
+			var result = a.dot( b );
+			assert.ok( result == ( - x * x - y * y ), "Passed!" );
+
+			var result = a.dot( c );
+			assert.ok( result == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "lengthSq", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "length", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "manhattanLength", ( assert ) => {
+
+			var a = new Vector2( x, 0 );
+			var b = new Vector2( 0, - y );
+			var c = new Vector2();
+
+			assert.strictEqual( a.manhattanLength(), x, "Positive component" );
+			assert.strictEqual( b.manhattanLength(), y, "Negative component" );
+			assert.strictEqual( c.manhattanLength(), 0, "Empty component" );
+
+			a.set( x, y );
+			assert.strictEqual( a.manhattanLength(), Math.abs( x ) + Math.abs( y ), "Two components" );
+
+		} );
+
+		QUnit.test( "normalize", ( assert ) => {
+
+			var a = new Vector2( x, 0 );
+			var b = new Vector2( 0, - y );
+			var c = new Vector2();
+
+			a.normalize();
+			assert.ok( a.length() == 1, "Passed!" );
+			assert.ok( a.x == 1, "Passed!" );
+
+			b.normalize();
+			assert.ok( b.length() == 1, "Passed!" );
+			assert.ok( b.y == - 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "angle", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "distanceTo", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "distanceToSquared", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "manhattanDistanceTo", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setLength", ( assert ) => {
+
+			var a = new Vector2( x, 0 );
+
+			assert.ok( a.length() == x, "Passed!" );
+			a.setLength( y );
+			assert.ok( a.length() == y, "Passed!" );
+
+			var a = new Vector2( 0, 0 );
+			assert.ok( a.length() == 0, "Passed!" );
+			a.setLength( y );
+			assert.ok( a.length() == 0, "Passed!" );
+			a.setLength();
+			assert.ok( isNaN( a.length() ), "Passed!" );
+
+		} );
+
+		QUnit.test( "lerp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "lerpVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			var a = new Vector2( x, 0 );
+			var b = new Vector2( 0, - y );
+
+			assert.ok( a.x != b.x, "Passed!" );
+			assert.ok( a.y != b.y, "Passed!" );
+
+			assert.ok( ! a.equals( b ), "Passed!" );
+			assert.ok( ! b.equals( a ), "Passed!" );
+
+			a.copy( b );
+			assert.ok( a.x == b.x, "Passed!" );
+			assert.ok( a.y == b.y, "Passed!" );
+
+			assert.ok( a.equals( b ), "Passed!" );
+			assert.ok( b.equals( a ), "Passed!" );
+
+		} );
+
+		QUnit.test( "fromArray", ( assert ) => {
+
+			var a = new 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", ( assert ) => {
+
+			var a = new 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" );
+
+			var 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" );
+
+			var 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", ( assert ) => {
+
+			var a = new Vector2();
+			var attr = new 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" );
+
+		} );
+
+		QUnit.test( "rotateAround", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+
+		// TODO (Itee) refactor/split
+		QUnit.test( "setX,setY", ( assert ) => {
+
+			var a = new Vector2();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+
+			a.setX( x );
+			a.setY( y );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+
+		} );
+		QUnit.test( "setComponent,getComponent", ( assert ) => {
+
+			var a = new Vector2();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+
+			a.setComponent( 0, 1 );
+			a.setComponent( 1, 2 );
+			assert.ok( a.getComponent( 0 ) == 1, "Passed!" );
+			assert.ok( a.getComponent( 1 ) == 2, "Passed!" );
+
+		} );
+		QUnit.test( "multiply/divide", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new Vector2( - x, - y );
+
+			a.multiplyScalar( - 2 );
+			assert.ok( a.x == x * - 2, "Passed!" );
+			assert.ok( a.y == y * - 2, "Passed!" );
+
+			b.multiplyScalar( - 2 );
+			assert.ok( b.x == 2 * x, "Passed!" );
+			assert.ok( b.y == 2 * y, "Passed!" );
+
+			a.divideScalar( - 2 );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+
+			b.divideScalar( - 2 );
+			assert.ok( b.x == - x, "Passed!" );
+			assert.ok( b.y == - y, "Passed!" );
+
+		} );
+		QUnit.test( "min/max/clamp", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new Vector2( - x, - y );
+			var c = new Vector2();
+
+			c.copy( a ).min( b );
+			assert.ok( c.x == - x, "Passed!" );
+			assert.ok( c.y == - y, "Passed!" );
+
+			c.copy( a ).max( b );
+			assert.ok( c.x == x, "Passed!" );
+			assert.ok( c.y == y, "Passed!" );
+
+			c.set( - 2 * x, 2 * y );
+			c.clamp( b, a );
+			assert.ok( c.x == - x, "Passed!" );
+			assert.ok( c.y == y, "Passed!" );
+
+			c.set( - 2 * x, 2 * x );
+			c.clampScalar( - x, x );
+			assert.equal( c.x, - x, "scalar clamp x" );
+			assert.equal( c.y, x, "scalar clamp y" );
+
+		} );
+		QUnit.test( "rounding", ( assert ) => {
+
+			assert.deepEqual( new Vector2( - 0.1, 0.1 ).floor(), new Vector2( - 1, 0 ), "floor .1" );
+			assert.deepEqual( new Vector2( - 0.5, 0.5 ).floor(), new Vector2( - 1, 0 ), "floor .5" );
+			assert.deepEqual( new Vector2( - 0.9, 0.9 ).floor(), new Vector2( - 1, 0 ), "floor .9" );
+
+			assert.deepEqual( new Vector2( - 0.1, 0.1 ).ceil(), new Vector2( 0, 1 ), "ceil .1" );
+			assert.deepEqual( new Vector2( - 0.5, 0.5 ).ceil(), new Vector2( 0, 1 ), "ceil .5" );
+			assert.deepEqual( new Vector2( - 0.9, 0.9 ).ceil(), new Vector2( 0, 1 ), "ceil .9" );
+
+			assert.deepEqual( new Vector2( - 0.1, 0.1 ).round(), new Vector2( 0, 0 ), "round .1" );
+			assert.deepEqual( new Vector2( - 0.5, 0.5 ).round(), new Vector2( 0, 1 ), "round .5" );
+			assert.deepEqual( new Vector2( - 0.9, 0.9 ).round(), new Vector2( - 1, 1 ), "round .9" );
+
+			assert.deepEqual( new Vector2( - 0.1, 0.1 ).roundToZero(), new Vector2( 0, 0 ), "roundToZero .1" );
+			assert.deepEqual( new Vector2( - 0.5, 0.5 ).roundToZero(), new Vector2( 0, 0 ), "roundToZero .5" );
+			assert.deepEqual( new Vector2( - 0.9, 0.9 ).roundToZero(), new Vector2( 0, 0 ), "roundToZero .9" );
+			assert.deepEqual( new Vector2( - 1.1, 1.1 ).roundToZero(), new Vector2( - 1, 1 ), "roundToZero 1.1" );
+			assert.deepEqual( new Vector2( - 1.5, 1.5 ).roundToZero(), new Vector2( - 1, 1 ), "roundToZero 1.5" );
+			assert.deepEqual( new Vector2( - 1.9, 1.9 ).roundToZero(), new Vector2( - 1, 1 ), "roundToZero 1.9" );
+
+		} );
+		QUnit.test( "length/lengthSq", ( assert ) => {
+
+			var a = new Vector2( x, 0 );
+			var b = new Vector2( 0, - y );
+			var c = new Vector2();
+
+			assert.ok( a.length() == x, "Passed!" );
+			assert.ok( a.lengthSq() == x * x, "Passed!" );
+			assert.ok( b.length() == y, "Passed!" );
+			assert.ok( b.lengthSq() == y * y, "Passed!" );
+			assert.ok( c.length() == 0, "Passed!" );
+			assert.ok( c.lengthSq() == 0, "Passed!" );
+
+			a.set( x, y );
+			assert.ok( a.length() == Math.sqrt( x * x + y * y ), "Passed!" );
+			assert.ok( a.lengthSq() == ( x * x + y * y ), "Passed!" );
+
+		} );
+		QUnit.test( "distanceTo/distanceToSquared", ( assert ) => {
+
+			var a = new Vector2( x, 0 );
+			var b = new Vector2( 0, - y );
+			var c = new Vector2();
+
+			assert.ok( a.distanceTo( c ) == x, "Passed!" );
+			assert.ok( a.distanceToSquared( c ) == x * x, "Passed!" );
+
+			assert.ok( b.distanceTo( c ) == y, "Passed!" );
+			assert.ok( b.distanceToSquared( c ) == y * y, "Passed!" );
+
+		} );
+		QUnit.test( "lerp/clone", ( assert ) => {
+
+			var a = new Vector2( x, 0 );
+			var b = new Vector2( 0, - y );
+
+			assert.ok( a.lerp( a, 0 ).equals( a.lerp( a, 0.5 ) ), "Passed!" );
+			assert.ok( a.lerp( a, 0 ).equals( a.lerp( a, 1 ) ), "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 0 ).equals( a ), "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 0.5 ).x == x * 0.5, "Passed!" );
+			assert.ok( a.clone().lerp( b, 0.5 ).y == - y * 0.5, "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 1 ).equals( b ), "Passed!" );
+
+		} );
+		QUnit.test( "setComponent/getComponent exceptions", ( assert ) => {
+
+			var a = new 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( "setScalar/addScalar/subScalar", ( assert ) => {
+
+			var a = new 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( "multiply/divide", ( assert ) => {
+
+			var a = new Vector2( x, y );
+			var b = new Vector2( 2 * x, 2 * y );
+			var c = new 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" );
+
+		} );
+
+	} );
+
+} );

+ 998 - 0
test/unit/src/math/Vector3.tests.js

@@ -0,0 +1,998 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Vector3 } from '../../../../src/math/Vector3';
+import { Vector4 } from '../../../../src/math/Vector4';
+import { Matrix3 } from '../../../../src/math/Matrix3';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { Spherical } from '../../../../src/math/Spherical';
+import { Quaternion } from '../../../../src/math/Quaternion';
+import { Euler } from '../../../../src/math/Euler';
+import { Cylindrical } from '../../../../src/math/Cylindrical';
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+import { PerspectiveCamera } from '../../../../src/cameras/PerspectiveCamera';
+import {
+	x,
+	y,
+	z,
+	w,
+	eps
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Vector3', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Vector3();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+
+			var a = new Vector3( x, y, z );
+			assert.ok( a.x === x, "Passed!" );
+			assert.ok( a.y === y, "Passed!" );
+			assert.ok( a.z === z, "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isVector3", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Vector3();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+
+			a.set( x, y, z );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+			assert.ok( a.z == z, "Passed!" );
+
+		} );
+
+		QUnit.test( "setScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setX", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setY", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setZ", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setComponent", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getComponent", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3().copy( a );
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+			assert.ok( b.z == z, "Passed!" );
+
+			// ensure that it is a true copy
+			a.x = 0;
+			a.y = - 1;
+			a.z = - 2;
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+			assert.ok( b.z == z, "Passed!" );
+
+		} );
+
+		QUnit.test( "add", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( - x, - y, - z );
+
+			a.add( b );
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+
+			var c = new Vector3().addVectors( b, b );
+			assert.ok( c.x == - 2 * x, "Passed!" );
+			assert.ok( c.y == - 2 * y, "Passed!" );
+			assert.ok( c.z == - 2 * z, "Passed!" );
+
+		} );
+
+		QUnit.test( "addScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "addVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "addScaledVector", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new 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( "sub", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( - x, - y, - z );
+
+			a.sub( b );
+			assert.ok( a.x == 2 * x, "Passed!" );
+			assert.ok( a.y == 2 * y, "Passed!" );
+			assert.ok( a.z == 2 * z, "Passed!" );
+
+			var c = new Vector3().subVectors( a, a );
+			assert.ok( c.x == 0, "Passed!" );
+			assert.ok( c.y == 0, "Passed!" );
+			assert.ok( c.z == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "subScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "subVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "multiply", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "multiplyScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "multiplyVectors", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( 2, 3, - 5 );
+
+			var c = new 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( "applyEuler", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var euler = new Euler( 90, - 45, 0 );
+			var expected = new 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", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var axis = new Vector3( 0, 1, 0 );
+			var angle = Math.PI / 4.0;
+			var expected = new 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( "applyMatrix3", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var m = new 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( "applyMatrix4", ( assert ) => {
+
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector4( x, y, z, 1 );
+
+			var m = new Matrix4().makeRotationX( Math.PI );
+			a.applyMatrix4( m );
+			b.applyMatrix4( m );
+			assert.ok( a.x == b.x / b.w, "Passed!" );
+			assert.ok( a.y == b.y / b.w, "Passed!" );
+			assert.ok( a.z == b.z / b.w, "Passed!" );
+
+			var m = new Matrix4().makeTranslation( 3, 2, 1 );
+			a.applyMatrix4( m );
+			b.applyMatrix4( m );
+			assert.ok( a.x == b.x / b.w, "Passed!" );
+			assert.ok( a.y == b.y / b.w, "Passed!" );
+			assert.ok( a.z == b.z / b.w, "Passed!" );
+
+			var m = new Matrix4().set(
+				1, 0, 0, 0,
+				0, 1, 0, 0,
+				0, 0, 1, 0,
+				0, 0, 1, 0
+			);
+			a.applyMatrix4( m );
+			b.applyMatrix4( m );
+			assert.ok( a.x == b.x / b.w, "Passed!" );
+			assert.ok( a.y == b.y / b.w, "Passed!" );
+			assert.ok( a.z == b.z / b.w, "Passed!" );
+
+		} );
+
+		QUnit.test( "applyQuaternion", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+
+			a.applyQuaternion( new 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 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", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "unproject", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "transformDirection", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var m = new Matrix4();
+			var transformed = new 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( "divide", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "divideScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "min", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "max", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clamp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clampScalar", ( assert ) => {
+
+			var a = new Vector3( - 0.01, 0.5, 1.5 );
+			var clamped = new 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( "clampLength", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "floor", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "ceil", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "round", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "roundToZero", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "negate", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+
+			a.negate();
+			assert.ok( a.x == - x, "Passed!" );
+			assert.ok( a.y == - y, "Passed!" );
+			assert.ok( a.z == - z, "Passed!" );
+
+		} );
+
+		QUnit.test( "dot", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( - x, - y, - z );
+			var c = new Vector3();
+
+			var result = a.dot( b );
+			assert.ok( result == ( - x * x - y * y - z * z ), "Passed!" );
+
+			var result = a.dot( c );
+			assert.ok( result == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "lengthSq", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "length", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "manhattanLength", ( assert ) => {
+
+			var a = new Vector3( x, 0, 0 );
+			var b = new Vector3( 0, - y, 0 );
+			var c = new Vector3( 0, 0, z );
+			var d = new Vector3();
+
+			assert.ok( a.manhattanLength() == x, "Positive x" );
+			assert.ok( b.manhattanLength() == y, "Negative y" );
+			assert.ok( c.manhattanLength() == z, "Positive z" );
+			assert.ok( d.manhattanLength() == 0, "Empty initialization" );
+
+			a.set( x, y, z );
+			assert.ok( a.manhattanLength() == Math.abs( x ) + Math.abs( y ) + Math.abs( z ), "All components" );
+
+		} );
+
+		QUnit.test( "normalize", ( assert ) => {
+
+			var a = new Vector3( x, 0, 0 );
+			var b = new Vector3( 0, - y, 0 );
+			var c = new Vector3( 0, 0, z );
+
+			a.normalize();
+			assert.ok( a.length() == 1, "Passed!" );
+			assert.ok( a.x == 1, "Passed!" );
+
+			b.normalize();
+			assert.ok( b.length() == 1, "Passed!" );
+			assert.ok( b.y == - 1, "Passed!" );
+
+			c.normalize();
+			assert.ok( c.length() == 1, "Passed!" );
+			assert.ok( c.z == 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "setLength", ( assert ) => {
+
+			var a = new Vector3( x, 0, 0 );
+
+			assert.ok( a.length() == x, "Passed!" );
+			a.setLength( y );
+			assert.ok( a.length() == y, "Passed!" );
+
+			var a = new Vector3( 0, 0, 0 );
+			assert.ok( a.length() == 0, "Passed!" );
+			a.setLength( y );
+			assert.ok( a.length() == 0, "Passed!" );
+			a.setLength();
+			assert.ok( isNaN( a.length() ), "Passed!" );
+
+		} );
+
+		QUnit.test( "lerp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "lerpVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "cross", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( 2 * x, - y, 0.5 * z );
+			var crossed = new 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", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( x, - y, z );
+			var c = new Vector3();
+			var crossed = new 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( "projectOnVector", ( assert ) => {
+
+			var a = new Vector3( 1, 0, 0 );
+			var b = new Vector3();
+			var normal = new Vector3( 10, 0, 0 );
+
+			assert.ok( b.copy( a ).projectOnVector( normal ).equals( new Vector3( 1, 0, 0 ) ), "Passed!" );
+
+			a.set( 0, 1, 0 );
+			assert.ok( b.copy( a ).projectOnVector( normal ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+			a.set( 0, 0, - 1 );
+			assert.ok( b.copy( a ).projectOnVector( normal ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+			a.set( - 1, 0, 0 );
+			assert.ok( b.copy( a ).projectOnVector( normal ).equals( new Vector3( - 1, 0, 0 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "projectOnPlane", ( assert ) => {
+
+			var a = new Vector3( 1, 0, 0 );
+			var b = new Vector3();
+			var normal = new Vector3( 1, 0, 0 );
+
+			assert.ok( b.copy( a ).projectOnPlane( normal ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+			a.set( 0, 1, 0 );
+			assert.ok( b.copy( a ).projectOnPlane( normal ).equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+
+			a.set( 0, 0, - 1 );
+			assert.ok( b.copy( a ).projectOnPlane( normal ).equals( new Vector3( 0, 0, - 1 ) ), "Passed!" );
+
+			a.set( - 1, 0, 0 );
+			assert.ok( b.copy( a ).projectOnPlane( normal ).equals( new Vector3( 0, 0, 0 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "reflect", ( assert ) => {
+
+			var a = new Vector3();
+			var normal = new Vector3( 0, 1, 0 );
+			var b = new Vector3();
+
+			a.set( 0, - 1, 0 );
+			assert.ok( b.copy( a ).reflect( normal ).equals( new Vector3( 0, 1, 0 ) ), "Passed!" );
+
+			a.set( 1, - 1, 0 );
+			assert.ok( b.copy( a ).reflect( normal ).equals( new Vector3( 1, 1, 0 ) ), "Passed!" );
+
+			a.set( 1, - 1, 0 );
+			normal.set( 0, - 1, 0 );
+			assert.ok( b.copy( a ).reflect( normal ).equals( new Vector3( 1, 1, 0 ) ), "Passed!" );
+
+		} );
+
+		QUnit.test( "angleTo", ( assert ) => {
+
+			var a = new Vector3( 0, - 0.18851655680720186, 0.9820700116639124 );
+			var b = new Vector3( 0, 0.18851655680720186, - 0.9820700116639124 );
+
+			assert.equal( a.angleTo( a ), 0 );
+			assert.equal( a.angleTo( b ), Math.PI );
+
+			var x = new Vector3( 1, 0, 0 );
+			var y = new Vector3( 0, 1, 0 );
+			var z = new Vector3( 0, 0, 1 );
+
+			assert.equal( x.angleTo( y ), Math.PI / 2 );
+			assert.equal( x.angleTo( z ), Math.PI / 2 );
+			assert.equal( z.angleTo( x ), Math.PI / 2 );
+
+			assert.ok( Math.abs( x.angleTo( new Vector3( 1, 1, 0 ) ) - ( Math.PI / 4 ) ) < 0.0000001 );
+
+		} );
+
+		QUnit.test( "distanceTo", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "distanceToSquared", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "manhattanDistanceTo", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setFromSpherical", ( assert ) => {
+
+			var a = new Vector3();
+			var phi = Math.acos( - 0.5 );
+			var theta = Math.sqrt( Math.PI ) * phi;
+			var sph = new Spherical( 10, phi, theta );
+			var expected = new 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", ( assert ) => {
+
+			var a = new Vector3();
+			var cyl = new Cylindrical( 10, Math.PI * 0.125, 20 );
+			var expected = new 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", ( assert ) => {
+
+			var a = new Vector3();
+			var m = new 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", ( assert ) => {
+
+			var a = new Vector3();
+			var m = new Matrix4().set( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 );
+			var expected = new 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", ( assert ) => {
+
+			var a = new Vector3();
+			var m = new 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( "equals", ( assert ) => {
+
+			var a = new Vector3( x, 0, z );
+			var b = new Vector3( 0, - y, 0 );
+
+			assert.ok( a.x != b.x, "Passed!" );
+			assert.ok( a.y != b.y, "Passed!" );
+			assert.ok( a.z != b.z, "Passed!" );
+
+			assert.ok( ! a.equals( b ), "Passed!" );
+			assert.ok( ! b.equals( a ), "Passed!" );
+
+			a.copy( b );
+			assert.ok( a.x == b.x, "Passed!" );
+			assert.ok( a.y == b.y, "Passed!" );
+			assert.ok( a.z == b.z, "Passed!" );
+
+			assert.ok( a.equals( b ), "Passed!" );
+			assert.ok( b.equals( a ), "Passed!" );
+
+		} );
+
+		QUnit.test( "fromArray", ( assert ) => {
+
+			var a = new 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", ( assert ) => {
+
+			var a = new 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" );
+
+			var 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" );
+
+			var 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", ( assert ) => {
+
+			var a = new Vector3();
+			var attr = new 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" );
+
+		} );
+
+		// TODO (Itee) refactor/split
+		QUnit.test( "setX,setY,setZ", ( assert ) => {
+
+			var a = new Vector3();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+
+			a.setX( x );
+			a.setY( y );
+			a.setZ( z );
+
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+			assert.ok( a.z == z, "Passed!" );
+
+		} );
+		QUnit.test( "setComponent,getComponent", ( assert ) => {
+
+			var a = new Vector3();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+
+			a.setComponent( 0, 1 );
+			a.setComponent( 1, 2 );
+			a.setComponent( 2, 3 );
+			assert.ok( a.getComponent( 0 ) == 1, "Passed!" );
+			assert.ok( a.getComponent( 1 ) == 2, "Passed!" );
+			assert.ok( a.getComponent( 2 ) == 3, "Passed!" );
+
+		} );
+		QUnit.test( "setComponent/getComponent exceptions", ( assert ) => {
+
+			var a = new 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( "min/max/clamp", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( - x, - y, - z );
+			var c = new Vector3();
+
+			c.copy( a ).min( b );
+			assert.ok( c.x == - x, "Passed!" );
+			assert.ok( c.y == - y, "Passed!" );
+			assert.ok( c.z == - z, "Passed!" );
+
+			c.copy( a ).max( b );
+			assert.ok( c.x == x, "Passed!" );
+			assert.ok( c.y == y, "Passed!" );
+			assert.ok( c.z == z, "Passed!" );
+
+			c.set( - 2 * x, 2 * y, - 2 * z );
+			c.clamp( b, a );
+			assert.ok( c.x == - x, "Passed!" );
+			assert.ok( c.y == y, "Passed!" );
+			assert.ok( c.z == - z, "Passed!" );
+
+		} );
+		QUnit.test( "distanceTo/distanceToSquared", ( assert ) => {
+
+			var a = new Vector3( x, 0, 0 );
+			var b = new Vector3( 0, - y, 0 );
+			var c = new Vector3( 0, 0, z );
+			var d = new Vector3();
+
+			assert.ok( a.distanceTo( d ) == x, "Passed!" );
+			assert.ok( a.distanceToSquared( d ) == x * x, "Passed!" );
+
+			assert.ok( b.distanceTo( d ) == y, "Passed!" );
+			assert.ok( b.distanceToSquared( d ) == y * y, "Passed!" );
+
+			assert.ok( c.distanceTo( d ) == z, "Passed!" );
+			assert.ok( c.distanceToSquared( d ) == z * z, "Passed!" );
+
+		} );
+		QUnit.test( "setScalar/addScalar/subScalar", ( assert ) => {
+
+			var a = new 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( "multiply/divide", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( 2 * x, 2 * y, 2 * z );
+			var c = new 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( "multiply/divide", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var b = new Vector3( - x, - y, - z );
+
+			a.multiplyScalar( - 2 );
+			assert.ok( a.x == x * - 2, "Passed!" );
+			assert.ok( a.y == y * - 2, "Passed!" );
+			assert.ok( a.z == z * - 2, "Passed!" );
+
+			b.multiplyScalar( - 2 );
+			assert.ok( b.x == 2 * x, "Passed!" );
+			assert.ok( b.y == 2 * y, "Passed!" );
+			assert.ok( b.z == 2 * z, "Passed!" );
+
+			a.divideScalar( - 2 );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+			assert.ok( a.z == z, "Passed!" );
+
+			b.divideScalar( - 2 );
+			assert.ok( b.x == - x, "Passed!" );
+			assert.ok( b.y == - y, "Passed!" );
+			assert.ok( b.z == - z, "Passed!" );
+
+		} );
+		QUnit.test( "project/unproject", ( assert ) => {
+
+			var a = new Vector3( x, y, z );
+			var camera = new PerspectiveCamera( 75, 16 / 9, 0.1, 300.0 );
+			var projected = new 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( "length/lengthSq", ( assert ) => {
+
+			var a = new Vector3( x, 0, 0 );
+			var b = new Vector3( 0, - y, 0 );
+			var c = new Vector3( 0, 0, z );
+			var d = new Vector3();
+
+			assert.ok( a.length() == x, "Passed!" );
+			assert.ok( a.lengthSq() == x * x, "Passed!" );
+			assert.ok( b.length() == y, "Passed!" );
+			assert.ok( b.lengthSq() == y * y, "Passed!" );
+			assert.ok( c.length() == z, "Passed!" );
+			assert.ok( c.lengthSq() == z * z, "Passed!" );
+			assert.ok( d.length() == 0, "Passed!" );
+			assert.ok( d.lengthSq() == 0, "Passed!" );
+
+			a.set( x, y, z );
+			assert.ok( a.length() == Math.sqrt( x * x + y * y + z * z ), "Passed!" );
+			assert.ok( a.lengthSq() == ( x * x + y * y + z * z ), "Passed!" );
+
+		} );
+		QUnit.test( "lerp/clone", ( assert ) => {
+
+			var a = new Vector3( x, 0, z );
+			var b = new Vector3( 0, - y, 0 );
+
+			assert.ok( a.lerp( a, 0 ).equals( a.lerp( a, 0.5 ) ), "Passed!" );
+			assert.ok( a.lerp( a, 0 ).equals( a.lerp( a, 1 ) ), "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 0 ).equals( a ), "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 0.5 ).x == x * 0.5, "Passed!" );
+			assert.ok( a.clone().lerp( b, 0.5 ).y == - y * 0.5, "Passed!" );
+			assert.ok( a.clone().lerp( b, 0.5 ).z == z * 0.5, "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 1 ).equals( b ), "Passed!" );
+
+		} );
+
+	} );
+
+} );

+ 726 - 0
test/unit/src/math/Vector4.tests.js

@@ -0,0 +1,726 @@
+/**
+ * @author bhouston / http://exocortex.com
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { Vector4 } from '../../../../src/math/Vector4';
+import { Matrix4 } from '../../../../src/math/Matrix4';
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+import {
+	x,
+	y,
+	z,
+	w,
+	eps
+} from './Constants.tests';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module.todo( 'Vector4', () => {
+
+		// INSTANCING
+		QUnit.test( "Instancing", ( assert ) => {
+
+			var a = new Vector4();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+			assert.ok( a.w == 1, "Passed!" );
+
+			var a = new Vector4( x, y, z, w );
+			assert.ok( a.x === x, "Passed!" );
+			assert.ok( a.y === y, "Passed!" );
+			assert.ok( a.z === z, "Passed!" );
+			assert.ok( a.w === w, "Passed!" );
+
+		} );
+
+		// PUBLIC STUFF
+		QUnit.test( "isVector4", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "set", ( assert ) => {
+
+			var a = new Vector4();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+			assert.ok( a.w == 1, "Passed!" );
+
+			a.set( x, y, z, w );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+			assert.ok( a.z == z, "Passed!" );
+			assert.ok( a.w == w, "Passed!" );
+
+		} );
+
+		QUnit.test( "setScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setX", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setY", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setZ", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setW", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setComponent", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "getComponent", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clone", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "copy", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var b = new Vector4().copy( a );
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+			assert.ok( b.z == z, "Passed!" );
+			assert.ok( b.w == w, "Passed!" );
+
+			// ensure that it is a true copy
+			a.x = 0;
+			a.y = - 1;
+			a.z = - 2;
+			a.w = - 3;
+			assert.ok( b.x == x, "Passed!" );
+			assert.ok( b.y == y, "Passed!" );
+			assert.ok( b.z == z, "Passed!" );
+			assert.ok( b.w == w, "Passed!" );
+
+		} );
+
+		QUnit.test( "add", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var b = new Vector4( - x, - y, - z, - w );
+
+			a.add( b );
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+			assert.ok( a.w == 0, "Passed!" );
+
+			var c = new Vector4().addVectors( b, b );
+			assert.ok( c.x == - 2 * x, "Passed!" );
+			assert.ok( c.y == - 2 * y, "Passed!" );
+			assert.ok( c.z == - 2 * z, "Passed!" );
+			assert.ok( c.w == - 2 * w, "Passed!" );
+
+		} );
+
+		QUnit.test( "addScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "addVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "addScaledVector", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var b = new 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( "sub", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var b = new Vector4( - x, - y, - z, - w );
+
+			a.sub( b );
+			assert.ok( a.x == 2 * x, "Passed!" );
+			assert.ok( a.y == 2 * y, "Passed!" );
+			assert.ok( a.z == 2 * z, "Passed!" );
+			assert.ok( a.w == 2 * w, "Passed!" );
+
+			var c = new Vector4().subVectors( a, a );
+			assert.ok( c.x == 0, "Passed!" );
+			assert.ok( c.y == 0, "Passed!" );
+			assert.ok( c.z == 0, "Passed!" );
+			assert.ok( c.w == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "subScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "subVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "multiplyScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "applyMatrix4", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var m = new Matrix4().makeRotationX( Math.PI );
+			var expected = new 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( "divideScalar", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setAxisAngleFromQuaternion", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "setAxisAngleFromRotationMatrix", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "min", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "max", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clamp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "clampScalar", ( assert ) => {
+
+			var a = new Vector4( - 0.1, 0.01, 0.5, 1.5 );
+			var clamped = new 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( "clampLength", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "floor", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "ceil", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "round", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "roundToZero", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "negate", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+
+			a.negate();
+			assert.ok( a.x == - x, "Passed!" );
+			assert.ok( a.y == - y, "Passed!" );
+			assert.ok( a.z == - z, "Passed!" );
+			assert.ok( a.w == - w, "Passed!" );
+
+		} );
+
+		QUnit.test( "dot", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var b = new Vector4( - x, - y, - z, - w );
+			var c = new Vector4( 0, 0, 0, 0 );
+
+			var result = a.dot( b );
+			assert.ok( result == ( - x * x - y * y - z * z - w * w ), "Passed!" );
+
+			var result = a.dot( c );
+			assert.ok( result == 0, "Passed!" );
+
+		} );
+
+		QUnit.test( "lengthSq", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "length", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "manhattanLength", ( assert ) => {
+
+			var a = new Vector4( x, 0, 0, 0 );
+			var b = new Vector4( 0, - y, 0, 0 );
+			var c = new Vector4( 0, 0, z, 0 );
+			var d = new Vector4( 0, 0, 0, w );
+			var e = new Vector4( 0, 0, 0, 0 );
+
+			assert.ok( a.manhattanLength() == x, "Positive x" );
+			assert.ok( b.manhattanLength() == y, "Negative y" );
+			assert.ok( c.manhattanLength() == z, "Positive z" );
+			assert.ok( d.manhattanLength() == w, "Positive w" );
+			assert.ok( e.manhattanLength() == 0, "Empty initialization" );
+
+			a.set( x, y, z, w );
+			assert.ok(
+				a.manhattanLength() == Math.abs( x ) + Math.abs( y ) + Math.abs( z ) + Math.abs( w ),
+				"All components"
+			);
+
+		} );
+
+		QUnit.test( "normalize", ( assert ) => {
+
+			var a = new Vector4( x, 0, 0, 0 );
+			var b = new Vector4( 0, - y, 0, 0 );
+			var c = new Vector4( 0, 0, z, 0 );
+			var d = new Vector4( 0, 0, 0, - w );
+
+			a.normalize();
+			assert.ok( a.length() == 1, "Passed!" );
+			assert.ok( a.x == 1, "Passed!" );
+
+			b.normalize();
+			assert.ok( b.length() == 1, "Passed!" );
+			assert.ok( b.y == - 1, "Passed!" );
+
+			c.normalize();
+			assert.ok( c.length() == 1, "Passed!" );
+			assert.ok( c.z == 1, "Passed!" );
+
+			d.normalize();
+			assert.ok( d.length() == 1, "Passed!" );
+			assert.ok( d.w == - 1, "Passed!" );
+
+		} );
+
+		QUnit.test( "setLength", ( assert ) => {
+
+			var a = new Vector4( x, 0, 0, 0 );
+
+			assert.ok( a.length() == x, "Passed!" );
+			a.setLength( y );
+			assert.ok( a.length() == y, "Passed!" );
+
+			var a = new Vector4( 0, 0, 0, 0 );
+			assert.ok( a.length() == 0, "Passed!" );
+			a.setLength( y );
+			assert.ok( a.length() == 0, "Passed!" );
+			a.setLength();
+			assert.ok( isNaN( a.length() ), "Passed!" );
+
+		} );
+
+		QUnit.test( "lerp", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "lerpVectors", ( assert ) => {
+
+			assert.ok( false, "everything's gonna be alright" );
+
+		} );
+
+		QUnit.test( "equals", ( assert ) => {
+
+			var a = new Vector4( x, 0, z, 0 );
+			var b = new Vector4( 0, - y, 0, - w );
+
+			assert.ok( a.x != b.x, "Passed!" );
+			assert.ok( a.y != b.y, "Passed!" );
+			assert.ok( a.z != b.z, "Passed!" );
+			assert.ok( a.w != b.w, "Passed!" );
+
+			assert.ok( ! a.equals( b ), "Passed!" );
+			assert.ok( ! b.equals( a ), "Passed!" );
+
+			a.copy( b );
+			assert.ok( a.x == b.x, "Passed!" );
+			assert.ok( a.y == b.y, "Passed!" );
+			assert.ok( a.z == b.z, "Passed!" );
+			assert.ok( a.w == b.w, "Passed!" );
+
+			assert.ok( a.equals( b ), "Passed!" );
+			assert.ok( b.equals( a ), "Passed!" );
+
+		} );
+
+		QUnit.test( "fromArray", ( assert ) => {
+
+			var a = new 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", ( assert ) => {
+
+			var a = new 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" );
+
+			var 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" );
+
+			var 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", ( assert ) => {
+
+			var a = new Vector4();
+			var attr = new 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" );
+
+		} );
+
+		// TODO (Itee) refactor/split
+		QUnit.test( "setX,setY,setZ,setW", ( assert ) => {
+
+			var a = new Vector4();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+			assert.ok( a.w == 1, "Passed!" );
+
+			a.setX( x );
+			a.setY( y );
+			a.setZ( z );
+			a.setW( w );
+
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+			assert.ok( a.z == z, "Passed!" );
+			assert.ok( a.w == w, "Passed!" );
+
+		} );
+		QUnit.test( "setComponent,getComponent", ( assert ) => {
+
+			var a = new Vector4();
+			assert.ok( a.x == 0, "Passed!" );
+			assert.ok( a.y == 0, "Passed!" );
+			assert.ok( a.z == 0, "Passed!" );
+			assert.ok( a.w == 1, "Passed!" );
+
+			a.setComponent( 0, 1 );
+			a.setComponent( 1, 2 );
+			a.setComponent( 2, 3 );
+			a.setComponent( 3, 4 );
+			assert.ok( a.getComponent( 0 ) == 1, "Passed!" );
+			assert.ok( a.getComponent( 1 ) == 2, "Passed!" );
+			assert.ok( a.getComponent( 2 ) == 3, "Passed!" );
+			assert.ok( a.getComponent( 3 ) == 4, "Passed!" );
+
+		} );
+		QUnit.test( "setComponent/getComponent exceptions", ( assert ) => {
+
+			var a = new 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( "setScalar/addScalar/subScalar", ( assert ) => {
+
+			var a = new 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( "multiply/divide", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var b = new Vector4( - x, - y, - z, - w );
+
+			a.multiplyScalar( - 2 );
+			assert.ok( a.x == x * - 2, "Passed!" );
+			assert.ok( a.y == y * - 2, "Passed!" );
+			assert.ok( a.z == z * - 2, "Passed!" );
+			assert.ok( a.w == w * - 2, "Passed!" );
+
+			b.multiplyScalar( - 2 );
+			assert.ok( b.x == 2 * x, "Passed!" );
+			assert.ok( b.y == 2 * y, "Passed!" );
+			assert.ok( b.z == 2 * z, "Passed!" );
+			assert.ok( b.w == 2 * w, "Passed!" );
+
+			a.divideScalar( - 2 );
+			assert.ok( a.x == x, "Passed!" );
+			assert.ok( a.y == y, "Passed!" );
+			assert.ok( a.z == z, "Passed!" );
+			assert.ok( a.w == w, "Passed!" );
+
+			b.divideScalar( - 2 );
+			assert.ok( b.x == - x, "Passed!" );
+			assert.ok( b.y == - y, "Passed!" );
+			assert.ok( b.z == - z, "Passed!" );
+			assert.ok( b.w == - w, "Passed!" );
+
+		} );
+		QUnit.test( "min/max/clamp", ( assert ) => {
+
+			var a = new Vector4( x, y, z, w );
+			var b = new Vector4( - x, - y, - z, - w );
+			var c = new Vector4();
+
+			c.copy( a ).min( b );
+			assert.ok( c.x == - x, "Passed!" );
+			assert.ok( c.y == - y, "Passed!" );
+			assert.ok( c.z == - z, "Passed!" );
+			assert.ok( c.w == - w, "Passed!" );
+
+			c.copy( a ).max( b );
+			assert.ok( c.x == x, "Passed!" );
+			assert.ok( c.y == y, "Passed!" );
+			assert.ok( c.z == z, "Passed!" );
+			assert.ok( c.w == w, "Passed!" );
+
+			c.set( - 2 * x, 2 * y, - 2 * z, 2 * w );
+			c.clamp( b, a );
+			assert.ok( c.x == - x, "Passed!" );
+			assert.ok( c.y == y, "Passed!" );
+			assert.ok( c.z == - z, "Passed!" );
+			assert.ok( c.w == w, "Passed!" );
+
+		} );
+		QUnit.test( "length/lengthSq", ( assert ) => {
+
+			var a = new Vector4( x, 0, 0, 0 );
+			var b = new Vector4( 0, - y, 0, 0 );
+			var c = new Vector4( 0, 0, z, 0 );
+			var d = new Vector4( 0, 0, 0, w );
+			var e = new Vector4( 0, 0, 0, 0 );
+
+			assert.ok( a.length() == x, "Passed!" );
+			assert.ok( a.lengthSq() == x * x, "Passed!" );
+			assert.ok( b.length() == y, "Passed!" );
+			assert.ok( b.lengthSq() == y * y, "Passed!" );
+			assert.ok( c.length() == z, "Passed!" );
+			assert.ok( c.lengthSq() == z * z, "Passed!" );
+			assert.ok( d.length() == w, "Passed!" );
+			assert.ok( d.lengthSq() == w * w, "Passed!" );
+			assert.ok( e.length() == 0, "Passed!" );
+			assert.ok( e.lengthSq() == 0, "Passed!" );
+
+			a.set( x, y, z, w );
+			assert.ok( a.length() == Math.sqrt( x * x + y * y + z * z + w * w ), "Passed!" );
+			assert.ok( a.lengthSq() == ( x * x + y * y + z * z + w * w ), "Passed!" );
+
+		} );
+		QUnit.test( "lerp/clone", ( assert ) => {
+
+			var a = new Vector4( x, 0, z, 0 );
+			var b = new Vector4( 0, - y, 0, - w );
+
+			assert.ok( a.lerp( a, 0 ).equals( a.lerp( a, 0.5 ) ), "Passed!" );
+			assert.ok( a.lerp( a, 0 ).equals( a.lerp( a, 1 ) ), "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 0 ).equals( a ), "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 0.5 ).x == x * 0.5, "Passed!" );
+			assert.ok( a.clone().lerp( b, 0.5 ).y == - y * 0.5, "Passed!" );
+			assert.ok( a.clone().lerp( b, 0.5 ).z == z * 0.5, "Passed!" );
+			assert.ok( a.clone().lerp( b, 0.5 ).w == - w * 0.5, "Passed!" );
+
+			assert.ok( a.clone().lerp( b, 1 ).equals( b ), "Passed!" );
+
+		} );
+
+	} );
+
+} );

+ 51 - 0
test/unit/src/math/interpolants/CubicInterpolant.tests.js

@@ -0,0 +1,51 @@
+/**
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { CubicInterpolant } from '../../../../../src/math/interpolants/CubicInterpolant';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module( 'Interpolants', () => {
+
+		QUnit.module.todo( 'CubicInterpolant', () => {
+
+			// INHERITANCE
+			QUnit.test( "Extending", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// INSTANCING
+			QUnit.test( "Instancing", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// PRIVATE STUFF
+			QUnit.test( "DefaultSettings_", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			QUnit.test( "intervalChanged_", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			QUnit.test( "interpolate_", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+		} );
+
+	} );
+
+} );

+ 39 - 0
test/unit/src/math/interpolants/DiscreteInterpolant.tests.js

@@ -0,0 +1,39 @@
+/**
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { DiscreteInterpolant } from '../../../../../src/math/interpolants/DiscreteInterpolant';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module( 'Interpolants', () => {
+
+		QUnit.module.todo( 'DiscreteInterpolant', () => {
+
+			// INHERITANCE
+			QUnit.test( "Extending", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// INSTANCING
+			QUnit.test( "Instancing", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// PRIVATE STUFF
+			QUnit.test( "interpolate_", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+		} );
+
+	} );
+
+} );

+ 39 - 0
test/unit/src/math/interpolants/LinearInterpolant.tests.js

@@ -0,0 +1,39 @@
+/**
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { LinearInterpolant } from '../../../../../src/math/interpolants/LinearInterpolant';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module( 'Interpolants', () => {
+
+		QUnit.module.todo( 'LinearInterpolant', () => {
+
+			// INHERITANCE
+			QUnit.test( "Extending", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// INSTANCING
+			QUnit.test( "Instancing", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// PRIVATE STUFF
+			QUnit.test( "interpolate_", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+		} );
+
+	} );
+
+} );

+ 39 - 0
test/unit/src/math/interpolants/QuaternionLinearInterpolant.tests.js

@@ -0,0 +1,39 @@
+/**
+ * @author TristanVALCKE / https://github.com/Itee
+ */
+/* global QUnit */
+
+import { QuaternionLinearInterpolant } from '../../../../../src/math/interpolants/QuaternionLinearInterpolant';
+
+export default QUnit.module( 'Maths', () => {
+
+	QUnit.module( 'Interpolants', () => {
+
+		QUnit.module.todo( 'QuaternionLinearInterpolant', () => {
+
+			// INHERITANCE
+			QUnit.test( "Extending", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// INSTANCING
+			QUnit.test( "Instancing", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+			// PRIVATE STUFF
+			QUnit.test( "interpolate_", ( assert ) => {
+
+				assert.ok( false, "everything's gonna be alright" );
+
+			} );
+
+		} );
+
+	} );
+
+} );