├── .gitignore
├── examples
├── css
│ └── styles.css
├── textures
│ ├── 5_normal.png
│ ├── 173_normal.png
│ ├── 210_normal.png
│ ├── 213_normal.png
│ ├── 214_normal.png
│ ├── 228_normal.png
│ ├── 254_normal.png
│ ├── 5_diffuse.jpg
│ ├── 5_specular.png
│ ├── attribution
│ ├── 173_diffuse.png
│ ├── 173_specular.png
│ ├── 210_diffuse.png
│ ├── 210_specular.png
│ ├── 213_diffuse.png
│ ├── 213_specular.png
│ ├── 214_diffuse.png
│ ├── 214_specular.png
│ ├── 228_diffuse.png
│ └── 254_diffuse.png
├── boxes.html
├── constraint-weld.html
├── constraint-slider.html
├── stack.html
├── convex-shapes.html
├── constraint-hinge.html
├── spheres.html
├── mesh-shape.html
├── compound-shapes.html
├── mesh-shape-statue.html
├── constraint-point.html
├── mesh-mesh.html
└── shapes.html
├── src
├── intro.js
├── outro.js
├── classes
│ ├── RayTracing
│ │ └── RayIntersection.js
│ ├── Constraints
│ │ ├── Constraint.js
│ │ ├── ContactConstraint.js
│ │ ├── PointConstraint.js
│ │ ├── FrictionConstraint.js
│ │ ├── SliderConstraint.js
│ │ ├── HingeConstraint.js
│ │ ├── WeldConstraint.js
│ │ └── ConstraintRow.js
│ ├── Shapes
│ │ ├── CompoundShapeChild.js
│ │ ├── SphereShape.js
│ │ ├── Swept
│ │ │ └── LineSweptShape.js
│ │ ├── MeshShape.js
│ │ ├── CompoundShape.js
│ │ ├── TriangleShape.js
│ │ └── BoxShape.js
│ ├── GhostBody.js
│ ├── Utils
│ │ ├── CollisionUtils.js
│ │ └── MinHeap.js
│ ├── EventEmitter.js
│ ├── ContactManifoldList.js
│ ├── RigidBodyProxy.js
│ ├── ContactDetails.js
│ ├── ForceGenerators
│ │ └── DragForce.js
│ ├── Collision
│ │ ├── SphereSphere.js
│ │ └── BoxSphere.js
│ ├── ForceGenerator.js
│ ├── ObjectPool.js
│ ├── Math
│ │ ├── Quaternion.js
│ │ ├── Vector3.js
│ │ └── Matrix3.js
│ └── BroadPhases
│ │ └── BasicBroadphase.js
└── libglobals.js
├── tests
├── css
│ ├── styles.css
│ └── mocha.css
├── box-sphere.html
├── balance.html
├── sphere-sphere.html
├── gravity.html
├── contact-events.html
├── sweptshapes.html
├── convexshape.html
├── damping.html
├── math
│ ├── quaternion.html
│ └── vector3.html
├── restitution.html
├── gjk_spheres.html
└── gjk_boxes.html
├── .jshintrc
├── bower.json
├── package.json
├── LICENSE
├── gulpfile.js
├── lib
├── stats.min.js
└── ConvexGeometry.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | docs
3 | node_modules
4 | references
--------------------------------------------------------------------------------
/examples/css/styles.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | overflow: hidden;
5 | }
--------------------------------------------------------------------------------
/src/intro.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Goblin Physics
3 | *
4 | * @module Goblin
5 | */
6 | (function(){
7 | var Goblin = {};
--------------------------------------------------------------------------------
/examples/textures/5_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/5_normal.png
--------------------------------------------------------------------------------
/examples/textures/173_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/173_normal.png
--------------------------------------------------------------------------------
/examples/textures/210_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/210_normal.png
--------------------------------------------------------------------------------
/examples/textures/213_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/213_normal.png
--------------------------------------------------------------------------------
/examples/textures/214_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/214_normal.png
--------------------------------------------------------------------------------
/examples/textures/228_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/228_normal.png
--------------------------------------------------------------------------------
/examples/textures/254_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/254_normal.png
--------------------------------------------------------------------------------
/examples/textures/5_diffuse.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/5_diffuse.jpg
--------------------------------------------------------------------------------
/examples/textures/5_specular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/5_specular.png
--------------------------------------------------------------------------------
/examples/textures/attribution:
--------------------------------------------------------------------------------
1 | All textures used in these examples were created by yughues (http://opengameart.org/users/yughues)
--------------------------------------------------------------------------------
/examples/textures/173_diffuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/173_diffuse.png
--------------------------------------------------------------------------------
/examples/textures/173_specular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/173_specular.png
--------------------------------------------------------------------------------
/examples/textures/210_diffuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/210_diffuse.png
--------------------------------------------------------------------------------
/examples/textures/210_specular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/210_specular.png
--------------------------------------------------------------------------------
/examples/textures/213_diffuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/213_diffuse.png
--------------------------------------------------------------------------------
/examples/textures/213_specular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/213_specular.png
--------------------------------------------------------------------------------
/examples/textures/214_diffuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/214_diffuse.png
--------------------------------------------------------------------------------
/examples/textures/214_specular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/214_specular.png
--------------------------------------------------------------------------------
/examples/textures/228_diffuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/228_diffuse.png
--------------------------------------------------------------------------------
/examples/textures/254_diffuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandlerprall/GoblinPhysics/HEAD/examples/textures/254_diffuse.png
--------------------------------------------------------------------------------
/tests/css/styles.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | overflow-x: hidden;
5 | }
6 |
7 | #mocha {
8 | position: absolute;
9 | top: 0;
10 | width: 100%;
11 | }
--------------------------------------------------------------------------------
/src/outro.js:
--------------------------------------------------------------------------------
1 | if ( typeof window !== 'undefined' ) window.Goblin = Goblin;
2 | if ( typeof self !== 'undefined' ) self.Goblin = Goblin;
3 | if ( typeof module !== 'undefined' ) module.exports = Goblin;
4 | })();
--------------------------------------------------------------------------------
/src/classes/RayTracing/RayIntersection.js:
--------------------------------------------------------------------------------
1 | Goblin.RayIntersection = function() {
2 | this.object = null;
3 | this.point = new Goblin.Vector3();
4 | this.t = null;
5 | this.normal = new Goblin.Vector3();
6 | };
--------------------------------------------------------------------------------
/src/libglobals.js:
--------------------------------------------------------------------------------
1 | Goblin.EPSILON = 0.00001;
2 |
3 | var _tmp_vec3_1 = new Goblin.Vector3(),
4 | _tmp_vec3_2 = new Goblin.Vector3(),
5 | _tmp_vec3_3 = new Goblin.Vector3(),
6 |
7 | _tmp_quat4_1 = new Goblin.Quaternion(),
8 | _tmp_quat4_2 = new Goblin.Quaternion(),
9 |
10 | _tmp_mat3_1 = new Goblin.Matrix3(),
11 | _tmp_mat3_2 = new Goblin.Matrix3();
--------------------------------------------------------------------------------
/src/classes/Constraints/Constraint.js:
--------------------------------------------------------------------------------
1 | Goblin.Constraint = function() {
2 | this.active = true;
3 |
4 | this.object_a = null;
5 |
6 | this.object_b = null;
7 |
8 | this.rows = [];
9 |
10 | this.factor = 1;
11 |
12 | this.last_impulse = new Goblin.Vector3();
13 |
14 | this.breaking_threshold = 0;
15 |
16 | this.listeners = {};
17 | };
18 | Goblin.EventEmitter.apply( Goblin.Constraint );
19 |
20 | Goblin.Constraint.prototype.deactivate = function() {
21 | this.active = false;
22 | this.emit( 'deactivate' );
23 | };
24 |
25 | Goblin.Constraint.prototype.update = function(){};
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "typed": true,
3 | "curly": true,
4 | "eqeqeq": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "boss": true,
11 | "eqnull": true,
12 | "smarttabs": true,
13 | "globals": {
14 | "vec3": false,
15 | "quat4": true,
16 | "mat4": false,
17 | "mat3": false,
18 | "Goblin": true,
19 | "_tmp_vec3_1": true,
20 | "_tmp_vec3_2": true,
21 | "_tmp_vec3_3": true,
22 | "_tmp_quat4_1": true,
23 | "_tmp_quat4_2": true,
24 | "_tmp_mat3_1": true,
25 | "_tmp_mat3_2": true,
26 | "_tmp_mat4_1": true
27 | }
28 | }
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "goblinphysics",
3 | "version": "0.9.1",
4 | "description": "3D physics engine for JavaScript",
5 | "homepage": "https://github.com/chandlerprall/GoblinPhysics",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/chandlerprall/GoblinPhysics"
9 | },
10 | "main": "./build/goblin.min.js",
11 | "keywords": [
12 | "goblin", "physics", "engine", "3d"
13 | ],
14 | "authors": [
15 | {
16 | "name": "Chandler Prall",
17 | "email": "chandler.prall@gmail.com"
18 | }
19 | ],
20 | "license": "zlib",
21 | "ignore": [
22 | "examples",
23 | "lib",
24 | "src",
25 | "tests"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src/classes/Shapes/CompoundShapeChild.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class CompoundShapeChild
3 | * @constructor
4 | */
5 | Goblin.CompoundShapeChild = function( shape, position, rotation ) {
6 | this.shape = shape;
7 |
8 | this.position = new Goblin.Vector3( position.x, position.y, position.z );
9 | this.rotation = new Goblin.Quaternion( rotation.x, rotation.y, rotation.z, rotation.w );
10 |
11 | this.transform = new Goblin.Matrix4();
12 | this.transform_inverse = new Goblin.Matrix4();
13 | this.transform.makeTransform( this.rotation, this.position );
14 | this.transform.invertInto( this.transform_inverse );
15 |
16 | this.aabb = new Goblin.AABB();
17 | this.aabb.transform( this.shape.aabb, this.transform );
18 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "goblinphysics",
3 | "version": "0.9.2",
4 | "description": "3D physics engine for JavaScript",
5 | "homepage": "https://github.com/chandlerprall/GoblinPhysics",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/chandlerprall/GoblinPhysics"
9 | },
10 | "bugs": "https://github.com/chandlerprall/GoblinPhysics/issues",
11 | "main": "./build/goblin.min.js",
12 | "keywords": [
13 | "goblin", "physics", "engine", "3d"
14 | ],
15 | "author": {
16 | "name": "Chandler Prall",
17 | "email": "chandler.prall@gmail.com"
18 | },
19 | "license": "zlib",
20 | "devDependencies": {
21 | "gulp": "3.9.x",
22 | "gulp-concat": "2.5.x",
23 | "gulp-uglify": "1.2.x",
24 | "gulp-jshint": "1.11.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | zlib License
2 |
3 | Copyright © 2015 Chandler Prall
4 | https://github.com/chandlerprall/GoblinPhysics
5 |
6 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
7 |
8 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
9 |
10 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
11 |
12 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
13 |
14 | 3. This notice may not be removed or altered from any source distribution.
15 |
16 |
17 | ----------------------------------------
18 |
19 |
20 | Three.js (https://github.com/mrdoob/three.js/) and Stats.js (https://github.com/mrdoob/stats.js), used in examples and tests, is licensed under the MIT license.
--------------------------------------------------------------------------------
/src/classes/GhostBody.js:
--------------------------------------------------------------------------------
1 | Goblin.GhostBody = function( shape ) {
2 | Goblin.RigidBody.call( this, shape, Infinity );
3 |
4 | this.contacts = [];
5 | this.tick_contacts = [];
6 |
7 | this.addListener( 'speculativeContact', Goblin.GhostBody.prototype.onSpeculativeContact );
8 | };
9 |
10 | Goblin.GhostBody.prototype = Object.create( Goblin.RigidBody.prototype );
11 |
12 | Goblin.GhostBody.prototype.onSpeculativeContact = function( object_b, contact ) {
13 | this.tick_contacts.push( object_b );
14 | if ( this.contacts.indexOf( object_b ) === -1 ) {
15 | this.contacts.push( object_b );
16 | this.emit( 'contactStart', object_b, contact );
17 | } else {
18 | this.emit( 'contactContinue', object_b, contact );
19 | }
20 |
21 | return false;
22 | };
23 |
24 | Goblin.GhostBody.prototype.checkForEndedContacts = function() {
25 | for ( var i = 0; i < this.contacts.length; i++ ) {
26 | if ( this.tick_contacts.indexOf( this.contacts[i] ) === -1 ) {
27 | this.emit( 'contactEnd', this.contacts[i] );
28 | this.contacts.splice( i, 1 );
29 | i -= 1;
30 | }
31 | }
32 | this.tick_contacts.length = 0;
33 | };
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require( 'gulp' ),
2 | uglify = require('gulp-uglify' ),
3 | concat = require( 'gulp-concat' ),
4 | jshint = require( 'gulp-jshint' );
5 |
6 | gulp.task('lint', function(){
7 | gulp.src([
8 | 'src/classes/**/*.js'
9 | ])
10 | .pipe( jshint() )
11 | .pipe( jshint.reporter( 'default' ) );
12 | });
13 |
14 | gulp.task('build', ['lint'], function(){
15 | gulp.src([
16 | 'src/intro.js',
17 | 'src/classes/Math/**.js',
18 | 'src/libglobals.js',
19 | 'src/classes/EventEmitter.js',
20 | 'src/classes/RigidBody.js',
21 | 'src/classes/ForceGenerator.js',
22 | 'src/classes/**/*.js',
23 | 'src/outro.js'
24 | ])
25 | .pipe( concat( 'goblin.js' ) )
26 | .pipe( gulp.dest( 'build' ) );
27 | });
28 |
29 | gulp.task('build-minified', ['lint'], function(){
30 | gulp.src([
31 | 'src/intro.js',
32 | 'src/classes/Math/**.js',
33 | 'src/libglobals.js',
34 | 'src/classes/EventEmitter.js',
35 | 'src/classes/RigidBody.js',
36 | 'src/classes/ForceGenerator.js',
37 | 'src/classes/**/*.js',
38 | 'src/outro.js'
39 | ])
40 | .pipe( concat( 'goblin.min.js' ) )
41 | .pipe( uglify() )
42 | .pipe( gulp.dest( 'build' ) );
43 | });
44 |
45 | gulp.task('default', ['build', 'build-minified'], function(){});
--------------------------------------------------------------------------------
/src/classes/Utils/CollisionUtils.js:
--------------------------------------------------------------------------------
1 | Goblin.CollisionUtils = {};
2 |
3 | Goblin.CollisionUtils.canBodiesCollide = function( object_a, object_b ) {
4 | if ( object_a._mass === Infinity && object_b._mass === Infinity ) {
5 | // Two static objects aren't considered to be in contact
6 | return false;
7 | }
8 |
9 | // Check collision masks
10 | if ( object_a.collision_mask !== 0 ) {
11 | if ( ( object_a.collision_mask & 1 ) === 0 ) {
12 | // object_b must not be in a matching group
13 | if ( ( object_a.collision_mask & object_b.collision_groups ) !== 0 ) {
14 | return false;
15 | }
16 | } else {
17 | // object_b must be in a matching group
18 | if ( ( object_a.collision_mask & object_b.collision_groups ) === 0 ) {
19 | return false;
20 | }
21 | }
22 | }
23 | if ( object_b.collision_mask !== 0 ) {
24 | if ( ( object_b.collision_mask & 1 ) === 0 ) {
25 | // object_a must not be in a matching group
26 | if ( ( object_b.collision_mask & object_a.collision_groups ) !== 0 ) {
27 | return false;
28 | }
29 | } else {
30 | // object_a must be in a matching group
31 | if ( ( object_b.collision_mask & object_a.collision_groups ) === 0 ) {
32 | return false;
33 | }
34 | }
35 | }
36 |
37 | return true;
38 | };
--------------------------------------------------------------------------------
/examples/boxes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Boxes | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/examples/constraint-weld.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Weld Constraint | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/examples/constraint-slider.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Slider Constraint | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/tests/box-sphere.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Box-Sphere | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/classes/EventEmitter.js:
--------------------------------------------------------------------------------
1 | Goblin.EventEmitter = function(){};
2 |
3 | Goblin.EventEmitter.prototype = {
4 | addListener: function( event, listener ) {
5 | if ( this.listeners[event] == null ) {
6 | this.listeners[event] = [];
7 | }
8 |
9 | if ( this.listeners[event].indexOf( listener ) === -1 ) {
10 | this.listeners[event].push( listener );
11 | }
12 | },
13 |
14 | removeListener: function( event, listener ) {
15 | if ( this.listeners[event] == null ) {
16 | this.listeners[event] = [];
17 | }
18 |
19 | var index = this.listeners[event].indexOf( listener );
20 | if ( index !== -1 ) {
21 | this.listeners[event].splice( index, 1 );
22 | }
23 | },
24 |
25 | removeAllListeners: function() {
26 | var listeners = Object.keys( this.listeners );
27 | for ( var i = 0; i < listeners.length; i++ ) {
28 | this.listeners[listeners[i]].length = 0;
29 | }
30 | },
31 |
32 | emit: function( event ) {
33 | var event_arguments = Array.prototype.slice.call( arguments, 1 ),
34 | ret_value;
35 |
36 | if ( this.listeners[event] instanceof Array ) {
37 | var listeners = this.listeners[event].slice();
38 | for ( var i = 0; i < listeners.length; i++ ) {
39 | ret_value = listeners[i].apply( this, event_arguments );
40 | if ( ret_value === false ) {
41 | return false;
42 | }
43 | }
44 | }
45 | }
46 | };
47 |
48 | Goblin.EventEmitter.apply = function( klass ) {
49 | klass.prototype.addListener = Goblin.EventEmitter.prototype.addListener;
50 | klass.prototype.removeListener = Goblin.EventEmitter.prototype.removeListener;
51 | klass.prototype.removeAllListeners = Goblin.EventEmitter.prototype.removeAllListeners;
52 | klass.prototype.emit = Goblin.EventEmitter.prototype.emit;
53 | };
--------------------------------------------------------------------------------
/src/classes/Utils/MinHeap.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | Goblin.MinHeap = function( array ) {
3 | this.heap = array == null ? [] : array.slice();
4 |
5 | if ( this.heap.length > 0 ) {
6 | this.heapify();
7 | }
8 | };
9 | Goblin.MinHeap.prototype = {
10 | heapify: function() {
11 | var start = ~~( ( this.heap.length - 2 ) / 2 );
12 | while ( start >= 0 ) {
13 | this.siftUp( start, this.heap.length - 1 );
14 | start--;
15 | }
16 | },
17 | siftUp: function( start, end ) {
18 | var root = start;
19 |
20 | while ( root * 2 + 1 <= end ) {
21 | var child = root * 2 + 1;
22 |
23 | if ( child + 1 <= end && this.heap[child + 1].valueOf() < this.heap[child].valueOf() ) {
24 | child++;
25 | }
26 |
27 | if ( this.heap[child].valueOf() < this.heap[root].valueOf() ) {
28 | var tmp = this.heap[child];
29 | this.heap[child] = this.heap[root];
30 | this.heap[root] = tmp;
31 | root = child;
32 | } else {
33 | return;
34 | }
35 | }
36 | },
37 | push: function( item ) {
38 | this.heap.push( item );
39 |
40 | var root = this.heap.length - 1;
41 | while ( root !== 0 ) {
42 | var parent = ~~( ( root - 1 ) / 2 );
43 |
44 | if ( this.heap[parent].valueOf() > this.heap[root].valueOf() ) {
45 | var tmp = this.heap[parent];
46 | this.heap[parent] = this.heap[root];
47 | this.heap[root] = tmp;
48 | }
49 |
50 | root = parent;
51 | }
52 | },
53 | peek: function() {
54 | return this.heap.length > 0 ? this.heap[0] : null;
55 | },
56 | pop: function() {
57 | var entry = this.heap[0];
58 | this.heap[0] = this.heap[this.heap.length - 1];
59 | this.heap.length = this.heap.length - 1;
60 | this.siftUp( 0, this.heap.length - 1 );
61 |
62 | return entry;
63 | }
64 | };
65 | })();
--------------------------------------------------------------------------------
/src/classes/ContactManifoldList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * List/Manager of ContactManifolds
3 | *
4 | * @Class ContactManifoldList
5 | * @constructor
6 | */
7 | Goblin.ContactManifoldList = function() {
8 | /**
9 | * The first ContactManifold in the list
10 | *
11 | * @property first
12 | * @type {ContactManifold}
13 | */
14 | this.first = null;
15 | };
16 |
17 | /**
18 | * Inserts a ContactManifold into the list
19 | *
20 | * @method insert
21 | * @param {ContactManifold} contact_manifold contact manifold to insert into the list
22 | */
23 | Goblin.ContactManifoldList.prototype.insert = function( contact_manifold ) {
24 | // The list is completely unordered, throw the manifold at the beginning
25 | contact_manifold.next_manifold = this.first;
26 | this.first = contact_manifold;
27 | };
28 |
29 | /**
30 | * Returns (and possibly creates) a ContactManifold for the two rigid bodies
31 | *
32 | * @param {RigidBody} object_a
33 | * @param {RigidBoxy} object_b
34 | * @returns {ContactManifold}
35 | */
36 | Goblin.ContactManifoldList.prototype.getManifoldForObjects = function( object_a, object_b ) {
37 | var manifold = null;
38 | if ( this.first !== null ) {
39 | var current = this.first;
40 | while ( current !== null ) {
41 | if (
42 | current.object_a === object_a && current.object_b === object_b ||
43 | current.object_a === object_b && current.object_b === object_a
44 | ) {
45 | manifold = current;
46 | break;
47 | }
48 | current = current.next_manifold;
49 | }
50 | }
51 |
52 | if ( manifold === null ) {
53 | // A manifold for these two objects does not exist, create one
54 | manifold = Goblin.ObjectPool.getObject( 'ContactManifold' );
55 | manifold.object_a = object_a;
56 | manifold.object_b = object_b;
57 | this.insert( manifold );
58 | }
59 |
60 | return manifold;
61 | };
--------------------------------------------------------------------------------
/src/classes/RigidBodyProxy.js:
--------------------------------------------------------------------------------
1 | Goblin.RigidBodyProxy = function() {
2 | this.parent = null;
3 | this.id = null;
4 |
5 | this.shape = null;
6 |
7 | this.aabb = new Goblin.AABB();
8 |
9 | this._mass = null;
10 | this._mass_inverted = null;
11 |
12 | this.position = new Goblin.Vector3();
13 | this.rotation = new Goblin.Quaternion();
14 |
15 | this.transform = new Goblin.Matrix4();
16 | this.transform_inverse = new Goblin.Matrix4();
17 |
18 | this.restitution = null;
19 | this.friction = null;
20 | };
21 |
22 | Object.defineProperty(
23 | Goblin.RigidBodyProxy.prototype,
24 | 'mass',
25 | {
26 | get: function() {
27 | return this._mass;
28 | },
29 | set: function( n ) {
30 | this._mass = n;
31 | this._mass_inverted = 1 / n;
32 | this.inertiaTensor = this.shape.getInertiaTensor( n );
33 | }
34 | }
35 | );
36 |
37 | Goblin.RigidBodyProxy.prototype.setFrom = function( parent, shape_data ) {
38 | this.parent = parent;
39 |
40 | this.id = parent.id;
41 |
42 | this.shape = shape_data.shape;
43 | this.shape_data = shape_data;
44 |
45 | this._mass = parent._mass;
46 |
47 | parent.transform.transformVector3Into( shape_data.position, this.position );
48 | this.rotation.multiplyQuaternions( parent.rotation, shape_data.rotation );
49 |
50 | this.transform.makeTransform( this.rotation, this.position );
51 | this.transform.invertInto( this.transform_inverse );
52 |
53 | this.aabb.transform( this.shape.aabb, this.transform );
54 |
55 | this.restitution = parent.restitution;
56 | this.friction = parent.friction;
57 | };
58 |
59 | Goblin.RigidBodyProxy.prototype.findSupportPoint = Goblin.RigidBody.prototype.findSupportPoint;
60 |
61 | Goblin.RigidBodyProxy.prototype.getRigidBody = function() {
62 | var body = this.parent;
63 | while ( body.parent ) {
64 | body = this.parent;
65 | }
66 | return body;
67 | };
--------------------------------------------------------------------------------
/examples/stack.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Stack | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/tests/balance.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Balance | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/examples/convex-shapes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Convex Shapes | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/classes/ContactDetails.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Structure which holds information about a contact between two objects
3 | *
4 | * @Class ContactDetails
5 | * @constructor
6 | */
7 | Goblin.ContactDetails = function() {
8 | /**
9 | * first body in the contact
10 | *
11 | * @property object_a
12 | * @type {Goblin.RigidBody}
13 | */
14 | this.object_a = null;
15 |
16 | /**
17 | * second body in the contact
18 | *
19 | * @property object_b
20 | * @type {Goblin.RigidBody}
21 | */
22 | this.object_b = null;
23 |
24 | /**
25 | * point of contact in world coordinates
26 | *
27 | * @property contact_point
28 | * @type {vec3}
29 | */
30 | this.contact_point = new Goblin.Vector3();
31 |
32 | /**
33 | * contact point in local frame of `object_a`
34 | *
35 | * @property contact_point_in_a
36 | * @type {vec3}
37 | */
38 | this.contact_point_in_a = new Goblin.Vector3();
39 |
40 | /**
41 | * contact point in local frame of `object_b`
42 | *
43 | * @property contact_point_in_b
44 | * @type {vec3}
45 | */
46 | this.contact_point_in_b = new Goblin.Vector3();
47 |
48 | /**
49 | * normal vector, in world coordinates, of the contact
50 | *
51 | * @property contact_normal
52 | * @type {vec3}
53 | */
54 | this.contact_normal = new Goblin.Vector3();
55 |
56 | /**
57 | * how far the objects are penetrated at the point of contact
58 | *
59 | * @property penetration_depth
60 | * @type {Number}
61 | */
62 | this.penetration_depth = 0;
63 |
64 | /**
65 | * amount of restitution between the objects in contact
66 | *
67 | * @property restitution
68 | * @type {Number}
69 | */
70 | this.restitution = 0;
71 |
72 | /**
73 | * amount of friction between the objects in contact
74 | *
75 | * @property friction
76 | * @type {*}
77 | */
78 | this.friction = 0;
79 |
80 | this.listeners = {};
81 | };
82 | Goblin.EventEmitter.apply( Goblin.ContactDetails );
83 |
84 | Goblin.ContactDetails.prototype.destroy = function() {
85 | this.emit( 'destroy' );
86 | Goblin.ObjectPool.freeObject( 'ContactDetails', this );
87 | };
--------------------------------------------------------------------------------
/tests/sphere-sphere.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sphere-Sphere | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/classes/ForceGenerators/DragForce.js:
--------------------------------------------------------------------------------
1 | /**
2 | * adds a drag force to associated objects
3 | *
4 | * @class DragForce
5 | * @extends ForceGenerator
6 | * @constructor
7 | */
8 | Goblin.DragForce = function( drag_coefficient, squared_drag_coefficient ) {
9 | /**
10 | * drag coefficient
11 | *
12 | * @property drag_coefficient
13 | * @type {Number}
14 | * @default 0
15 | */
16 | this.drag_coefficient = drag_coefficient || 0;
17 |
18 | /**
19 | * drag coefficient
20 | *
21 | * @property drag_coefficient
22 | * @type {Number}
23 | * @default 0
24 | */
25 | this.squared_drag_coefficient = squared_drag_coefficient || 0;
26 |
27 | /**
28 | * whether or not the force generator is enabled
29 | *
30 | * @property enabled
31 | * @type {Boolean}
32 | * @default true
33 | */
34 | this.enabled = true;
35 |
36 | /**
37 | * array of objects affected by the generator
38 | *
39 | * @property affected
40 | * @type {Array}
41 | * @default []
42 | * @private
43 | */
44 | this.affected = [];
45 | };
46 | Goblin.DragForce.prototype.enable = Goblin.ForceGenerator.prototype.enable;
47 | Goblin.DragForce.prototype.disable = Goblin.ForceGenerator.prototype.disable;
48 | Goblin.DragForce.prototype.affect = Goblin.ForceGenerator.prototype.affect;
49 | Goblin.DragForce.prototype.unaffect = Goblin.ForceGenerator.prototype.unaffect;
50 | /**
51 | * applies force to the associated objects
52 | *
53 | * @method applyForce
54 | */
55 | Goblin.DragForce.prototype.applyForce = function() {
56 | if ( !this.enabled ) {
57 | return;
58 | }
59 |
60 | var i, affected_count, object, drag,
61 | force = _tmp_vec3_1;
62 |
63 | for ( i = 0, affected_count = this.affected.length; i < affected_count; i++ ) {
64 | object = this.affected[i];
65 |
66 | force.copy( object.linear_velocity );
67 |
68 | // Calculate the total drag coefficient.
69 | drag = force.length();
70 | drag = ( this.drag_coefficient * drag ) + ( this.squared_drag_coefficient * drag * drag );
71 |
72 | // Calculate the final force and apply it.
73 | force.normalize();
74 | force.scale( -drag );
75 | object.applyForce( force );
76 | }
77 | };
--------------------------------------------------------------------------------
/lib/stats.min.js:
--------------------------------------------------------------------------------
1 | // stats.js - http://github.com/mrdoob/stats.js
2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:12,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};"object"===typeof module&&(module.exports=Stats);
7 |
--------------------------------------------------------------------------------
/tests/gravity.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Gravity | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/classes/Collision/SphereSphere.js:
--------------------------------------------------------------------------------
1 | Goblin.SphereSphere = function( object_a, object_b ) {
2 | // Cache positions of the spheres
3 | var position_a = object_a.position,
4 | position_b = object_b.position;
5 |
6 | // Get the vector between the two objects
7 | _tmp_vec3_1.subtractVectors( position_b, position_a );
8 | var distance = _tmp_vec3_1.length();
9 |
10 | // If the distance between the objects is greater than their combined radii
11 | // then they are not touching, continue processing the other possible contacts
12 | if ( distance > object_a.shape.radius + object_b.shape.radius ) {
13 | return;
14 | }
15 |
16 | // Get a ContactDetails object and fill out it's information
17 | var contact = Goblin.ObjectPool.getObject( 'ContactDetails' );
18 | contact.object_a = object_a;
19 | contact.object_b = object_b;
20 |
21 | // Because we already have the distance (vector magnitude), don't normalize
22 | // instead we will calculate this value manually
23 | contact.contact_normal.scaleVector( _tmp_vec3_1, 1 / distance );
24 |
25 | // Calculate contact position
26 | _tmp_vec3_1.scale( -0.5 );
27 | contact.contact_point.addVectors( _tmp_vec3_1, position_a );
28 |
29 | // Calculate penetration depth
30 | contact.penetration_depth = object_a.shape.radius + object_b.shape.radius - distance;
31 |
32 | // Contact points in both objects - in world coordinates at first
33 | contact.contact_point_in_a.scaleVector( contact.contact_normal, contact.object_a.shape.radius );
34 | contact.contact_point_in_a.add( contact.object_a.position );
35 | contact.contact_point_in_b.scaleVector( contact.contact_normal, -contact.object_b.shape.radius );
36 | contact.contact_point_in_b.add( contact.object_b.position );
37 |
38 | // Find actual contact point
39 | contact.contact_point.addVectors( contact.contact_point_in_a, contact.contact_point_in_b );
40 | contact.contact_point.scale( 0.5 );
41 |
42 | // Convert contact_point_in_a and contact_point_in_b to those objects' local frames
43 | contact.object_a.transform_inverse.transformVector3( contact.contact_point_in_a );
44 | contact.object_b.transform_inverse.transformVector3( contact.contact_point_in_b );
45 |
46 | contact.restitution = ( object_a.restitution + object_b.restitution ) / 2;
47 | contact.friction = ( object_a.friction + object_b.friction ) / 2;
48 |
49 | return contact;
50 | };
--------------------------------------------------------------------------------
/examples/constraint-hinge.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hinge Constraint | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tests/contact-events.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Contact Events | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/classes/ForceGenerator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * adds a constant force to associated objects
3 | *
4 | * @class ForceGenerator
5 | * @constructor
6 | * @param force {vec3} [optional] force the generator applies
7 | */
8 | Goblin.ForceGenerator = function( force ) {
9 | /**
10 | * force which will be applied to affected objects
11 | *
12 | * @property force
13 | * @type {vec3}
14 | * @default [ 0, 0, 0 ]
15 | */
16 | this.force = force || new Goblin.Vector3();
17 |
18 | /**
19 | * whether or not the force generator is enabled
20 | *
21 | * @property enabled
22 | * @type {Boolean}
23 | * @default true
24 | */
25 | this.enabled = true;
26 |
27 | /**
28 | * array of objects affected by the generator
29 | *
30 | * @property affected
31 | * @type {Array}
32 | * @default []
33 | * @private
34 | */
35 | this.affected = [];
36 | };
37 | /**
38 | * applies force to the associated objects
39 | *
40 | * @method applyForce
41 | */
42 | Goblin.ForceGenerator.prototype.applyForce = function() {
43 | if ( !this.enabled ) {
44 | return;
45 | }
46 |
47 | var i, affected_count;
48 | for ( i = 0, affected_count = this.affected.length; i < affected_count; i++ ) {
49 | this.affected[i].applyForce( this.force );
50 | }
51 | };
52 | /**
53 | * enables the force generator
54 | *
55 | * @method enable
56 | */
57 | Goblin.ForceGenerator.prototype.enable = function() {
58 | this.enabled = true;
59 | };
60 | /**
61 | * disables the force generator
62 | *
63 | * @method disable
64 | */
65 | Goblin.ForceGenerator.prototype.disable = function() {
66 | this.enabled = false;
67 | };
68 | /**
69 | * adds an object to be affected by the generator
70 | *
71 | * @method affect
72 | * @param object {Mixed} object to be affected, must have `applyForce` method
73 | */
74 | Goblin.ForceGenerator.prototype.affect = function( object ) {
75 | var i, affected_count;
76 | // Make sure this object isn't already affected
77 | for ( i = 0, affected_count = this.affected.length; i < affected_count; i++ ) {
78 | if ( this.affected[i] === object ) {
79 | return;
80 | }
81 | }
82 |
83 | this.affected.push( object );
84 | };
85 | /**
86 | * removes an object from being affected by the generator
87 | *
88 | * @method unaffect
89 | * @param object {Mixed} object to be affected, must have `applyForce` method
90 | */
91 | Goblin.ForceGenerator.prototype.unaffect = function( object ) {
92 | var i, affected_count;
93 | for ( i = 0, affected_count = this.affected.length; i < affected_count; i++ ) {
94 | if ( this.affected[i] === object ) {
95 | this.affected.splice( i, 1 );
96 | return;
97 | }
98 | }
99 | };
--------------------------------------------------------------------------------
/examples/spheres.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Spheres | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/examples/mesh-shape.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mesh Shape | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/examples/compound-shapes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Compound Shapes | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/classes/ObjectPool.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Manages pools for various types of objects, provides methods for creating and freeing pooled objects
3 | *
4 | * @class ObjectPool
5 | * @static
6 | */
7 | Goblin.ObjectPool = {
8 | /**
9 | * key/value map of registered types
10 | *
11 | * @property types
12 | * @private
13 | */
14 | types: {},
15 |
16 | /**
17 | * key/pool map of object type - to - object pool
18 | *
19 | * @property pools
20 | * @private
21 | */
22 | pools: {},
23 |
24 | /**
25 | * registers a type of object to be available in pools
26 | *
27 | * @param key {String} key associated with the object to register
28 | * @param constructing_function {Function} function which will return a new object
29 | */
30 | registerType: function( key, constructing_function ) {
31 | this.types[ key ] = constructing_function;
32 | this.pools[ key ] = [];
33 | },
34 |
35 | /**
36 | * retrieve a free object from the specified pool, or creates a new object if one is not available
37 | *
38 | * @param key {String} key of the object type to retrieve
39 | * @return {Mixed} object of the type asked for, when done release it with `ObjectPool.freeObject`
40 | */
41 | getObject: function( key ) {
42 | var pool = this.pools[ key ];
43 |
44 | if ( pool.length !== 0 ) {
45 | return pool.pop();
46 | } else {
47 | return this.types[ key ]();
48 | }
49 | },
50 |
51 | /**
52 | * adds on object to the object pool so it can be reused
53 | *
54 | * @param key {String} type of the object being freed, matching the key given to `registerType`
55 | * @param object {Mixed} object to release into the pool
56 | */
57 | freeObject: function( key, object ) {
58 | if ( object.removeAllListeners != null ) {
59 | object.removeAllListeners();
60 | }
61 | this.pools[ key ].push( object );
62 | }
63 | };
64 |
65 | // register the objects used in Goblin
66 | Goblin.ObjectPool.registerType( 'ContactDetails', function() { return new Goblin.ContactDetails(); } );
67 | Goblin.ObjectPool.registerType( 'ContactManifold', function() { return new Goblin.ContactManifold(); } );
68 | Goblin.ObjectPool.registerType( 'GJK2SupportPoint', function() { return new Goblin.GjkEpa.SupportPoint( new Goblin.Vector3(), new Goblin.Vector3(), new Goblin.Vector3() ); } );
69 | Goblin.ObjectPool.registerType( 'ConstraintRow', function() { return new Goblin.ConstraintRow(); } );
70 | Goblin.ObjectPool.registerType( 'ContactConstraint', function() { return new Goblin.ContactConstraint(); } );
71 | Goblin.ObjectPool.registerType( 'FrictionConstraint', function() { return new Goblin.FrictionConstraint(); } );
72 | Goblin.ObjectPool.registerType( 'RayIntersection', function() { return new Goblin.RayIntersection(); } );
73 | Goblin.ObjectPool.registerType( 'RigidBodyProxy', function() { return new Goblin.RigidBodyProxy(); } );
--------------------------------------------------------------------------------
/src/classes/Constraints/ContactConstraint.js:
--------------------------------------------------------------------------------
1 | Goblin.ContactConstraint = function() {
2 | Goblin.Constraint.call( this );
3 |
4 | this.contact = null;
5 | };
6 | Goblin.ContactConstraint.prototype = Object.create( Goblin.Constraint.prototype );
7 |
8 | Goblin.ContactConstraint.prototype.buildFromContact = function( contact ) {
9 | this.object_a = contact.object_a;
10 | this.object_b = contact.object_b;
11 | this.contact = contact;
12 |
13 | var self = this;
14 | var onDestroy = function() {
15 | this.removeListener( 'destroy', onDestroy );
16 | self.deactivate();
17 | };
18 | this.contact.addListener( 'destroy', onDestroy );
19 |
20 | var row = this.rows[0] || Goblin.ObjectPool.getObject( 'ConstraintRow' );
21 | row.lower_limit = 0;
22 | row.upper_limit = Infinity;
23 | this.rows[0] = row;
24 |
25 | this.update();
26 | };
27 |
28 | Goblin.ContactConstraint.prototype.update = function() {
29 | var row = this.rows[0];
30 |
31 | if ( this.object_a == null || this.object_a._mass === Infinity ) {
32 | row.jacobian[0] = row.jacobian[1] = row.jacobian[2] = 0;
33 | row.jacobian[3] = row.jacobian[4] = row.jacobian[5] = 0;
34 | } else {
35 | row.jacobian[0] = -this.contact.contact_normal.x;
36 | row.jacobian[1] = -this.contact.contact_normal.y;
37 | row.jacobian[2] = -this.contact.contact_normal.z;
38 |
39 | _tmp_vec3_1.subtractVectors( this.contact.contact_point, this.contact.object_a.position );
40 | _tmp_vec3_1.cross( this.contact.contact_normal );
41 | row.jacobian[3] = -_tmp_vec3_1.x;
42 | row.jacobian[4] = -_tmp_vec3_1.y;
43 | row.jacobian[5] = -_tmp_vec3_1.z;
44 | }
45 |
46 | if ( this.object_b == null || this.object_b._mass === Infinity ) {
47 | row.jacobian[6] = row.jacobian[7] = row.jacobian[8] = 0;
48 | row.jacobian[9] = row.jacobian[10] = row.jacobian[11] = 0;
49 | } else {
50 | row.jacobian[6] = this.contact.contact_normal.x;
51 | row.jacobian[7] = this.contact.contact_normal.y;
52 | row.jacobian[8] = this.contact.contact_normal.z;
53 |
54 | _tmp_vec3_1.subtractVectors( this.contact.contact_point, this.contact.object_b.position );
55 | _tmp_vec3_1.cross( this.contact.contact_normal );
56 | row.jacobian[9] = _tmp_vec3_1.x;
57 | row.jacobian[10] = _tmp_vec3_1.y;
58 | row.jacobian[11] = _tmp_vec3_1.z;
59 | }
60 |
61 | // Pre-calc error
62 | row.bias = 0;
63 |
64 | // Apply restitution
65 | var velocity_along_normal = 0;
66 | if ( this.object_a._mass !== Infinity ) {
67 | this.object_a.getVelocityInLocalPoint( this.contact.contact_point_in_a, _tmp_vec3_1 );
68 | velocity_along_normal += _tmp_vec3_1.dot( this.contact.contact_normal );
69 | }
70 | if ( this.object_b._mass !== Infinity ) {
71 | this.object_b.getVelocityInLocalPoint( this.contact.contact_point_in_b, _tmp_vec3_1 );
72 | velocity_along_normal -= _tmp_vec3_1.dot( this.contact.contact_normal );
73 | }
74 |
75 | // Add restitution to bias
76 | row.bias += velocity_along_normal * this.contact.restitution;
77 | };
--------------------------------------------------------------------------------
/src/classes/Math/Quaternion.js:
--------------------------------------------------------------------------------
1 | Goblin.Quaternion = function( x, y, z, w ) {
2 | this.x = x != null ? x : 0;
3 | this.y = y != null ? y : 0;
4 | this.z = z != null ? z : 0;
5 | this.w = w != null ? w : 1;
6 | this.normalize();
7 | };
8 |
9 | Goblin.Quaternion.prototype = {
10 | set: function( x, y, z, w ) {
11 | this.x = x;
12 | this.y = y;
13 | this.z = z;
14 | this.w = w;
15 | },
16 |
17 | multiply: function( q ) {
18 | var x = this.x, y = this.y, z = this.z, w = this.w,
19 | qx = q.x, qy = q.y, qz = q.z, qw = q.w;
20 |
21 | this.x = x * qw + w * qx + y * qz - z * qy;
22 | this.y = y * qw + w * qy + z * qx - x * qz;
23 | this.z = z * qw + w * qz + x * qy - y * qx;
24 | this.w = w * qw - x * qx - y * qy - z * qz;
25 | },
26 |
27 | multiplyQuaternions: function( a, b ) {
28 | this.x = a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y;
29 | this.y = a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z;
30 | this.z = a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x;
31 | this.w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z;
32 | },
33 |
34 | normalize: function() {
35 | var x = this.x, y = this.y, z = this.z, w = this.w,
36 | length = Math.sqrt( x * x + y * y + z * z + w * w );
37 |
38 | if ( length === 0) {
39 | this.x = this.y = this.z = this.w = 0;
40 | } else {
41 | length = 1 / length;
42 | this.x *= length;
43 | this.y *= length;
44 | this.z *= length;
45 | this.w *= length;
46 | }
47 | },
48 |
49 | invertQuaternion: function( q ) {
50 | var x = q.x, y = q.y, z = q.z, w = q.w,
51 | dot = x * x + y * y + z * z + w * w;
52 |
53 | if ( dot === 0 ) {
54 | this.x = this.y = this.z = this.w = 0;
55 | } else {
56 | var inv_dot = -1 / dot;
57 | this.x = q.x * inv_dot;
58 | this.y = q.y * inv_dot;
59 | this.z = q.z * inv_dot;
60 | this.w = q.w * -inv_dot;
61 | }
62 | },
63 |
64 | transformVector3: function( v ) {
65 | var x = v.x, y = v.y, z = v.z,
66 | qx = this.x, qy = this.y, qz = this.z, qw = this.w,
67 |
68 | // calculate quat * vec
69 | ix = qw * x + qy * z - qz * y,
70 | iy = qw * y + qz * x - qx * z,
71 | iz = qw * z + qx * y - qy * x,
72 | iw = -qx * x - qy * y - qz * z;
73 |
74 | // calculate result * inverse quat
75 | v.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
76 | v.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
77 | v.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
78 | },
79 |
80 | transformVector3Into: function( v, dest ) {
81 | var x = v.x, y = v.y, z = v.z,
82 | qx = this.x, qy = this.y, qz = this.z, qw = this.w,
83 |
84 | // calculate quat * vec
85 | ix = qw * x + qy * z - qz * y,
86 | iy = qw * y + qz * x - qx * z,
87 | iz = qw * z + qx * y - qy * x,
88 | iw = -qx * x - qy * y - qz * z;
89 |
90 | // calculate result * inverse quat
91 | dest.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
92 | dest.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
93 | dest.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
94 | }
95 | };
--------------------------------------------------------------------------------
/src/classes/Math/Vector3.js:
--------------------------------------------------------------------------------
1 | Goblin.Vector3 = function( x, y, z ) {
2 | this.x = x || 0;
3 | this.y = y || 0;
4 | this.z = z || 0;
5 | };
6 |
7 | Goblin.Vector3.prototype = {
8 | set: function( x, y, z ) {
9 | this.x = x;
10 | this.y = y;
11 | this.z = z;
12 | },
13 |
14 | copy: function( v ) {
15 | this.x = v.x;
16 | this.y = v.y;
17 | this.z = v.z;
18 | },
19 |
20 | add: function( v ) {
21 | this.x += v.x;
22 | this.y += v.y;
23 | this.z += v.z;
24 | },
25 |
26 | addVectors: function( a, b ) {
27 | this.x = a.x + b.x;
28 | this.y = a.y + b.y;
29 | this.z = a.z + b.z;
30 | },
31 |
32 | subtract: function( v ) {
33 | this.x -= v.x;
34 | this.y -= v.y;
35 | this.z -= v.z;
36 | },
37 |
38 | subtractVectors: function( a, b ) {
39 | this.x = a.x - b.x;
40 | this.y = a.y - b.y;
41 | this.z = a.z - b.z;
42 | },
43 |
44 | multiply: function( v ) {
45 | this.x *= v.x;
46 | this.y *= v.y;
47 | this.z *= v.z;
48 | },
49 |
50 | multiplyVectors: function( a, b ) {
51 | this.x = a.x * b.x;
52 | this.y = a.y * b.y;
53 | this.z = a.z * b.z;
54 | },
55 |
56 | scale: function( scalar ) {
57 | this.x *= scalar;
58 | this.y *= scalar;
59 | this.z *= scalar;
60 | },
61 |
62 | scaleVector: function( v, scalar ) {
63 | this.x = v.x * scalar;
64 | this.y = v.y * scalar;
65 | this.z = v.z * scalar;
66 | },
67 |
68 | lengthSquared: function() {
69 | return this.dot( this );
70 | },
71 |
72 | length: function() {
73 | return Math.sqrt( this.lengthSquared() );
74 | },
75 |
76 | normalize: function() {
77 | var length = this.length();
78 | if ( length === 0 ) {
79 | this.x = this.y = this.z = 0;
80 | } else {
81 | this.scale( 1 / length );
82 | }
83 | },
84 |
85 | normalizeVector: function( v ) {
86 | this.copy( v );
87 | this.normalize();
88 | },
89 |
90 | dot: function( v ) {
91 | return this.x * v.x + this.y * v.y + this.z * v.z;
92 | },
93 |
94 | cross: function( v ) {
95 | var x = this.x, y = this.y, z = this.z;
96 |
97 | this.x = y * v.z - z * v.y;
98 | this.y = z * v.x - x * v.z;
99 | this.z = x * v.y - y * v.x;
100 | },
101 |
102 | crossVectors: function( a, b ) {
103 | this.x = a.y * b.z - a.z * b.y;
104 | this.y = a.z * b.x - a.x * b.z;
105 | this.z = a.x * b.y - a.y * b.x;
106 | },
107 |
108 | distanceTo: function( v ) {
109 | var x = v.x - this.x,
110 | y = v.y - this.y,
111 | z = v.z - this.z;
112 | return Math.sqrt( x*x + y*y + z*z );
113 | },
114 |
115 | findOrthogonal: function( o1, o2 ) {
116 | var a, k;
117 | if ( Math.abs( this.z ) > 0.7071067811865476 ) {
118 | // choose p in y-z plane
119 | a = -this.y * this.y + this.z * this.z;
120 | k = 1 / Math.sqrt( a );
121 | o1.set( 0, -this.z * k, this.y * k );
122 | // set q = n x p
123 | o2.set( a * k, -this.x * o1.z, this.x * o1.y );
124 | }
125 | else {
126 | // choose p in x-y plane
127 | a = this.x * this.x + this.y * this.y;
128 | k = 1 / Math.sqrt( a );
129 | o1.set( -this.y * k, this.x * k, 0 );
130 | // set q = n x p
131 | o2.set( -this.z * o1.y, this.z * o1.x, a * k );
132 | }
133 | }
134 | };
--------------------------------------------------------------------------------
/src/classes/Constraints/PointConstraint.js:
--------------------------------------------------------------------------------
1 | Goblin.PointConstraint = function( object_a, point_a, object_b, point_b ) {
2 | Goblin.Constraint.call( this );
3 |
4 | this.object_a = object_a;
5 | this.point_a = point_a;
6 |
7 | this.object_b = object_b || null;
8 | if ( this.object_b != null ) {
9 | this.point_b = point_b;
10 | } else {
11 | this.point_b = new Goblin.Vector3();
12 | this.object_a.updateDerived(); // Ensure the body's transform is correct
13 | this.object_a.transform.transformVector3Into( this.point_a, this.point_b );
14 | }
15 |
16 | this.erp = 0.1;
17 |
18 | // Create rows
19 | for ( var i = 0; i < 3; i++ ) {
20 | this.rows[i] = Goblin.ObjectPool.getObject( 'ConstraintRow' );
21 | this.rows[i].lower_limit = -Infinity;
22 | this.rows[i].upper_limit = Infinity;
23 | this.rows[i].bias = 0;
24 |
25 | this.rows[i].jacobian[6] = this.rows[i].jacobian[7] = this.rows[i].jacobian[8] =
26 | this.rows[i].jacobian[9] = this.rows[i].jacobian[10] = this.rows[i].jacobian[11] = 0;
27 | }
28 | };
29 | Goblin.PointConstraint.prototype = Object.create( Goblin.Constraint.prototype );
30 |
31 | Goblin.PointConstraint.prototype.update = (function(){
32 | var r1 = new Goblin.Vector3(),
33 | r2 = new Goblin.Vector3();
34 |
35 | return function( time_delta ) {
36 | this.object_a.transform.transformVector3Into( this.point_a, _tmp_vec3_1 );
37 | r1.subtractVectors( _tmp_vec3_1, this.object_a.position );
38 |
39 | this.rows[0].jacobian[0] = -1;
40 | this.rows[0].jacobian[1] = 0;
41 | this.rows[0].jacobian[2] = 0;
42 | this.rows[0].jacobian[3] = 0;
43 | this.rows[0].jacobian[4] = -r1.z;
44 | this.rows[0].jacobian[5] = r1.y;
45 |
46 | this.rows[1].jacobian[0] = 0;
47 | this.rows[1].jacobian[1] = -1;
48 | this.rows[1].jacobian[2] = 0;
49 | this.rows[1].jacobian[3] = r1.z;
50 | this.rows[1].jacobian[4] = 0;
51 | this.rows[1].jacobian[5] = -r1.x;
52 |
53 | this.rows[2].jacobian[0] = 0;
54 | this.rows[2].jacobian[1] = 0;
55 | this.rows[2].jacobian[2] = -1;
56 | this.rows[2].jacobian[3] = -r1.y;
57 | this.rows[2].jacobian[4] = r1.x;
58 | this.rows[2].jacobian[5] = 0;
59 |
60 | if ( this.object_b != null ) {
61 | this.object_b.transform.transformVector3Into( this.point_b, _tmp_vec3_2 );
62 | r2.subtractVectors( _tmp_vec3_2, this.object_b.position );
63 |
64 | this.rows[0].jacobian[6] = 1;
65 | this.rows[0].jacobian[7] = 0;
66 | this.rows[0].jacobian[8] = 0;
67 | this.rows[0].jacobian[9] = 0;
68 | this.rows[0].jacobian[10] = r2.z;
69 | this.rows[0].jacobian[11] = -r2.y;
70 |
71 | this.rows[1].jacobian[6] = 0;
72 | this.rows[1].jacobian[7] = 1;
73 | this.rows[1].jacobian[8] = 0;
74 | this.rows[1].jacobian[9] = -r2.z;
75 | this.rows[1].jacobian[10] = 0;
76 | this.rows[1].jacobian[11] = r2.x;
77 |
78 | this.rows[2].jacobian[6] = 0;
79 | this.rows[2].jacobian[7] = 0;
80 | this.rows[2].jacobian[8] = 1;
81 | this.rows[2].jacobian[9] = r2.y;
82 | this.rows[2].jacobian[10] = -r2.x;
83 | this.rows[2].jacobian[11] = 0;
84 | } else {
85 | _tmp_vec3_2.copy( this.point_b );
86 | }
87 |
88 | _tmp_vec3_3.subtractVectors( _tmp_vec3_1, _tmp_vec3_2 );
89 | _tmp_vec3_3.scale( this.erp / time_delta );
90 | this.rows[0].bias = _tmp_vec3_3.x;
91 | this.rows[1].bias = _tmp_vec3_3.y;
92 | this.rows[2].bias = _tmp_vec3_3.z;
93 | };
94 | })( );
95 |
--------------------------------------------------------------------------------
/examples/mesh-shape-statue.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mesh Shape, Statue | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/classes/Shapes/SphereShape.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class SphereShape
3 | * @param radius {Number} sphere radius
4 | * @constructor
5 | */
6 | Goblin.SphereShape = function( radius ) {
7 | this.radius = radius;
8 |
9 | this.aabb = new Goblin.AABB();
10 | this.calculateLocalAABB( this.aabb );
11 | };
12 |
13 | /**
14 | * Calculates this shape's local AABB and stores it in the passed AABB object
15 | *
16 | * @method calculateLocalAABB
17 | * @param aabb {AABB}
18 | */
19 | Goblin.SphereShape.prototype.calculateLocalAABB = function( aabb ) {
20 | aabb.min.x = aabb.min.y = aabb.min.z = -this.radius;
21 | aabb.max.x = aabb.max.y = aabb.max.z = this.radius;
22 | };
23 |
24 | Goblin.SphereShape.prototype.getInertiaTensor = function( mass ) {
25 | var element = 0.4 * mass * this.radius * this.radius;
26 | return new Goblin.Matrix3(
27 | element, 0, 0,
28 | 0, element, 0,
29 | 0, 0, element
30 | );
31 | };
32 |
33 | /**
34 | * Given `direction`, find the point in this body which is the most extreme in that direction.
35 | * This support point is calculated in local coordinates and stored in the second parameter `support_point`
36 | *
37 | * @method findSupportPoint
38 | * @param direction {vec3} direction to use in finding the support point
39 | * @param support_point {vec3} vec3 variable which will contain the supporting point after calling this method
40 | */
41 | Goblin.SphereShape.prototype.findSupportPoint = (function(){
42 | var temp = new Goblin.Vector3();
43 | return function( direction, support_point ) {
44 | temp.normalizeVector( direction );
45 | support_point.scaleVector( temp, this.radius );
46 | };
47 | })();
48 |
49 | /**
50 | * Checks if a ray segment intersects with the shape
51 | *
52 | * @method rayIntersect
53 | * @property start {vec3} start point of the segment
54 | * @property end {vec3{ end point of the segment
55 | * @return {RayIntersection|null} if the segment intersects, a RayIntersection is returned, else `null`
56 | */
57 | Goblin.SphereShape.prototype.rayIntersect = (function(){
58 | var direction = new Goblin.Vector3(),
59 | length;
60 |
61 | return function( start, end ) {
62 | direction.subtractVectors( end, start );
63 | length = direction.length();
64 | direction.scale( 1 / length ); // normalize direction
65 |
66 | var a = start.dot( direction ),
67 | b = start.dot( start ) - this.radius * this.radius;
68 |
69 | // if ray starts outside of sphere and points away, exit
70 | if ( a >= 0 && b >= 0 ) {
71 | return null;
72 | }
73 |
74 | var discr = a * a - b;
75 |
76 | // Check for ray miss
77 | if ( discr < 0 ) {
78 | return null;
79 | }
80 |
81 | // ray intersects, find closest intersection point
82 | var discr_sqrt = Math.sqrt( discr ),
83 | t = -a - discr_sqrt;
84 | if ( t < 0 ) {
85 | t = -a + discr_sqrt;
86 | }
87 |
88 | // verify the segment intersects
89 | if ( t > length ) {
90 | return null;
91 | }
92 |
93 | var intersection = Goblin.ObjectPool.getObject( 'RayIntersection' );
94 | intersection.object = this;
95 | intersection.point.scaleVector( direction, t );
96 | intersection.t = t;
97 | intersection.point.add( start );
98 |
99 | intersection.normal.normalizeVector( intersection.point );
100 |
101 | return intersection;
102 | };
103 | })();
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | GoblinPhysics
2 | ==============
3 |
4 | GoblinPhysics is an open source physics engine written from the ground up in JavaScript. It aims to provide a fast and dependable physics simulation regardless of platform (desktop vs. mobile, browser vs. nodejs).
5 |
6 | Examples
7 | --------
8 | * [Mesh Shape](http://www.goblinphysics.com/examples/mesh-shape-statue.html)
9 | * [Boxes](http://www.goblinphysics.com/examples/boxes.html)
10 | * [Box Stack](http://www.goblinphysics.com/examples/stack.html)
11 | * [Sphere Stack](http://www.goblinphysics.com/examples/spheres.html)
12 | * [Shapes](http://www.goblinphysics.com/examples/shapes.html)
13 | * [Compound Shapes](http://www.goblinphysics.com/examples/compound-shapes.html)
14 | * [Infinite Boxes](http://www.goblinphysics.com/examples/boxes.html)
15 | * [Point Constraint](http://www.goblinphysics.com/examples/constraint-point.html)
16 | * [Slider Constraint](http://www.goblinphysics.com/examples/constraint-slider.html)
17 | * [Weld Constraint](http://www.goblinphysics.com/examples/constraint-weld.html)
18 | * [Ray Traycer](http://www.goblinphysics.com/examples/raytracer.html)
19 |
20 | Features
21 | --------
22 | * Rigid body simulation
23 | * Sphere, Box, Cone, Cylinder, Plane, Convex, Mesh, and Compound shapes
24 | * Weld, Slider, and Point constraints
25 | * Basic event callback system
26 | * Ray tracing
27 | * Example scripts
28 | * Test suite
29 |
30 | Documentation
31 | -------------
32 | See `Building` below for how to generate documentation. Hosted version available at [http://www.goblinphysics.com/documentation](http://www.goblinphysics.com/documentation).
33 |
34 | Roadmap
35 | -------
36 | **Non-exhaustive list of planned features**
37 | * Sweep & Prune broad phase
38 | * More event callbacks
39 | * More constraints
40 | * Internal object re-use (framework for this is in place, need to actually use it)
41 | * Island solver
42 | * Box-Box detection for better performance and stability
43 | * Force generators
44 |
45 | Tests
46 | -----
47 | * [Balance](http://www.goblinphysics.com/tests/balance.html)
48 | * [Box-Sphere](http://www.goblinphysics.com/tests/box-sphere.html)
49 | * [GJK-Boxes](http://www.goblinphysics.com/tests/gjk_boxes.html)
50 | * [GJK-Spheres](http://www.goblinphysics.com/tests/gjk_spheres.html)
51 | * [Gravity](http://www.goblinphysics.com/tests/gravity.html)
52 | * [Ray Tracing](http://www.goblinphysics.com/tests/raytracing.html)
53 | * [Restitution](http://www.goblinphysics.com/tests/restitution.html)
54 | * [Sphere-Sphere](http://www.goblinphysics.com/tests/sphere-sphere.html)
55 | * [Support Points](http://www.goblinphysics.com/tests/support-points.html)
56 |
57 | Building
58 | --------
59 | [gulp](http://gulpjs.com/) is used to build the library and generate documentation. Follow gulp's [getting started](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md) page for a quick setup. To install the packages necessary to build GoblinPhysics, run `npm install` in the git checkout directory. Once all of the packages have been downloaded you can build by running `gulp default`. To generate documenation, run `gulp docs`.
60 |
61 | License
62 | -------
63 | GoblinPhysics is distributed under the [zlib license](https://github.com/chandlerprall/GoblinPhysics/blob/master/LICENSE). This means you can use the library to do whatever you want, free of charge, with or without giving attribution (although attribution is always appreciated). [Three.js](https://github.com/mrdoob/three.js/) and [Stats.js](https://github.com/mrdoob/stats.js), used in GoblinPhysics' examples and tests, are distributed under the MIT license which requires attribution if used.
--------------------------------------------------------------------------------
/tests/sweptshapes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Swept Shapes | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/classes/Shapes/Swept/LineSweptShape.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Extends a given shape by sweeping a line around it
3 | *
4 | * @class LineSweptShape
5 | * @param start {Vector3} starting point of the line
6 | * @param end {Vector3} line's end point
7 | * @param shape any Goblin shape object
8 | * @constructor
9 | */
10 | Goblin.LineSweptShape = function( start, end, shape ) {
11 | /**
12 | * starting point of the line
13 | *
14 | * @property start
15 | * @type {Vector3}
16 | */
17 | this.start = start;
18 |
19 | /**
20 | * line's end point
21 | *
22 | * @property end
23 | * @type {Vector3}
24 | */
25 | this.end = end;
26 |
27 | /**
28 | * shape being swept
29 | *
30 | * @property shape
31 | */
32 | this.shape = shape;
33 |
34 | /**
35 | * unit direction of the line
36 | *
37 | * @property direction
38 | * @type {Vector3}
39 | */
40 | this.direction = new Goblin.Vector3();
41 | this.direction.subtractVectors( end, start );
42 |
43 | /**
44 | * length of the line
45 | *
46 | * @property length
47 | * @type {Number}
48 | */
49 | this.length = this.direction.length();
50 | this.direction.normalize();
51 |
52 | /**
53 | * axis-aligned bounding box of this shape
54 | *
55 | * @property aabb
56 | * @type {AABB}
57 | */
58 | this.aabb = new Goblin.AABB();
59 | this.calculateLocalAABB( this.aabb );
60 | };
61 |
62 | /**
63 | * Calculates this shape's local AABB and stores it in the passed AABB object
64 | *
65 | * @method calculateLocalAABB
66 | * @param aabb {AABB}
67 | */
68 | Goblin.LineSweptShape.prototype.calculateLocalAABB = function( aabb ) {
69 | this.shape.calculateLocalAABB( aabb );
70 |
71 | aabb.min.x = Math.min( aabb.min.x + this.start.x, aabb.min.x + this.end.x );
72 | aabb.min.y = Math.min( aabb.min.y + this.start.y, aabb.min.y + this.end.y );
73 | aabb.min.z = Math.min( aabb.min.z + this.start.z, aabb.min.z + this.end.z );
74 |
75 | aabb.max.x = Math.max( aabb.max.x + this.start.x, aabb.max.x + this.end.x );
76 | aabb.max.y = Math.max( aabb.max.y + this.start.y, aabb.max.y + this.end.y );
77 | aabb.max.z = Math.max( aabb.max.z + this.start.z, aabb.max.z + this.end.z );
78 | };
79 |
80 | Goblin.LineSweptShape.prototype.getInertiaTensor = function( mass ) {
81 | // this is wrong, but currently not used for anything
82 | return this.shape.getInertiaTensor( mass );
83 | };
84 |
85 | /**
86 | * Given `direction`, find the point in this body which is the most extreme in that direction.
87 | * This support point is calculated in world coordinates and stored in the second parameter `support_point`
88 | *
89 | * @method findSupportPoint
90 | * @param direction {vec3} direction to use in finding the support point
91 | * @param support_point {vec3} vec3 variable which will contain the supporting point after calling this method
92 | */
93 | Goblin.LineSweptShape.prototype.findSupportPoint = function( direction, support_point ) {
94 | this.shape.findSupportPoint( direction, support_point );
95 |
96 | // Add whichever point of this line lies in `direction`
97 | var dot = this.direction.dot( direction );
98 |
99 | if ( dot < 0 ) {
100 | support_point.add( this.start );
101 | } else {
102 | support_point.add( this.end );
103 | }
104 | };
105 |
106 | /**
107 | * Checks if a ray segment intersects with the shape
108 | *
109 | * @method rayIntersect
110 | * @property start {vec3} start point of the segment
111 | * @property end {vec3} end point of the segment
112 | * @return {RayIntersection|null} if the segment intersects, a RayIntersection is returned, else `null`
113 | */
114 | Goblin.LineSweptShape.prototype.rayIntersect = function(){
115 | return null;
116 | };
--------------------------------------------------------------------------------
/examples/constraint-point.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Point Constraint | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/classes/BroadPhases/BasicBroadphase.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Performs a n^2 check of all collision objects to see if any could be in contact
3 | *
4 | * @class BasicBroadphase
5 | * @constructor
6 | */
7 | Goblin.BasicBroadphase = function() {
8 | /**
9 | * Holds all of the collision objects that the broadphase is responsible for
10 | *
11 | * @property bodies
12 | * @type {Array}
13 | */
14 | this.bodies = [];
15 |
16 | /**
17 | * Array of all (current) collision pairs between the broadphases' bodies
18 | *
19 | * @property collision_pairs
20 | * @type {Array}
21 | */
22 | this.collision_pairs = [];
23 | };
24 |
25 | /**
26 | * Adds a body to the broadphase for contact checking
27 | *
28 | * @method addBody
29 | * @param body {RigidBody} body to add to the broadphase contact checking
30 | */
31 | Goblin.BasicBroadphase.prototype.addBody = function( body ) {
32 | this.bodies.push( body );
33 | };
34 |
35 | /**
36 | * Removes a body from the broadphase contact checking
37 | *
38 | * @method removeBody
39 | * @param body {RigidBody} body to remove from the broadphase contact checking
40 | */
41 | Goblin.BasicBroadphase.prototype.removeBody = function( body ) {
42 | var i,
43 | body_count = this.bodies.length;
44 |
45 | for ( i = 0; i < body_count; i++ ) {
46 | if ( this.bodies[i] === body ) {
47 | this.bodies.splice( i, 1 );
48 | break;
49 | }
50 | }
51 | };
52 |
53 | /**
54 | * Checks all collision objects to find any which are possibly in contact
55 | * resulting contact pairs are held in the object's `collision_pairs` property
56 | *
57 | * @method update
58 | */
59 | Goblin.BasicBroadphase.prototype.update = function() {
60 | var i, j,
61 | object_a, object_b,
62 | bodies_count = this.bodies.length;
63 |
64 | // Clear any old contact pairs
65 | this.collision_pairs.length = 0;
66 |
67 | // Loop over all collision objects and check for overlapping boundary spheres
68 | for ( i = 0; i < bodies_count; i++ ) {
69 | object_a = this.bodies[i];
70 |
71 | for ( j = 0; j < bodies_count; j++ ) {
72 | if ( i <= j ) {
73 | // if i < j then we have already performed this check
74 | // if i === j then the two objects are the same and can't be in contact
75 | continue;
76 | }
77 |
78 | object_b = this.bodies[j];
79 |
80 | if( Goblin.CollisionUtils.canBodiesCollide( object_a, object_b ) ) {
81 | if ( object_a.aabb.intersects( object_b.aabb ) ) {
82 | this.collision_pairs.push( [ object_b, object_a ] );
83 | }
84 | }
85 | }
86 | }
87 | };
88 |
89 | /**
90 | * Returns an array of objects the given body may be colliding with
91 | *
92 | * @method intersectsWith
93 | * @param object_a {RigidBody}
94 | * @return Array
95 | */
96 | Goblin.BasicBroadphase.prototype.intersectsWith = function( object_a ) {
97 | var i, object_b,
98 | bodies_count = this.bodies.length,
99 | intersections = [];
100 |
101 | // Loop over all collision objects and check for overlapping boundary spheres
102 | for ( i = 0; i < bodies_count; i++ ) {
103 | object_b = this.bodies[i];
104 |
105 | if ( object_a === object_b ) {
106 | continue;
107 | }
108 |
109 | if ( object_a.aabb.intersects( object_b.aabb ) ) {
110 | intersections.push( object_b );
111 | }
112 | }
113 |
114 | return intersections;
115 | };
116 |
117 | /**
118 | * Checks if a ray segment intersects with objects in the world
119 | *
120 | * @method rayIntersect
121 | * @property start {vec3} start point of the segment
122 | * @property end {vec3{ end point of the segment
123 | * @return {Array} an unsorted array of intersections
124 | */
125 | Goblin.BasicBroadphase.prototype.rayIntersect = function( start, end ) {
126 | var bodies_count = this.bodies.length,
127 | i, body,
128 | intersections = [];
129 | for ( i = 0; i < bodies_count; i++ ) {
130 | body = this.bodies[i];
131 | if ( body.aabb.testRayIntersect( start, end ) ) {
132 | body.rayIntersect( start, end, intersections );
133 | }
134 | }
135 |
136 | return intersections;
137 | };
--------------------------------------------------------------------------------
/src/classes/Shapes/MeshShape.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class MeshShape
3 | * @param vertices {Array} vertices comprising the mesh
4 | * @param faces {Array} array of indices indicating which vertices compose a face; faces[0..2] represent the first face, faces[3..5] are the second, etc
5 | * @constructor
6 | */
7 | Goblin.MeshShape = function( vertices, faces ) {
8 | this.vertices = vertices;
9 |
10 | this.triangles = [];
11 | for ( var i = 0; i < faces.length; i += 3 ) {
12 | this.triangles.push( new Goblin.TriangleShape( vertices[faces[i]], vertices[faces[i+1]], vertices[faces[i+2]] ) );
13 | }
14 |
15 | /**
16 | * the convex mesh's volume
17 | * @property volume
18 | * @type {number}
19 | */
20 | this.volume = 0;
21 |
22 | /**
23 | * coordinates of the mesh's COM
24 | * @property center_of_mass
25 | * @type {vec3}
26 | */
27 | this.center_of_mass = new Goblin.Vector3();
28 |
29 | /**
30 | * used in computing the mesh's center of mass & volume
31 | * @property _intergral
32 | * @type {Float32Array}
33 | * @private
34 | */
35 | this._integral = new Float32Array( 10 );
36 |
37 | this.hierarchy = new Goblin.BVH( this.triangles ).tree;
38 |
39 | var polygon_faces = this.triangles.map(
40 | function( triangle ) {
41 | return new Goblin.GjkEpa.Face(
42 | null,
43 | { point: triangle.a },
44 | { point: triangle.b },
45 | { point: triangle.c }
46 | );
47 | }
48 | );
49 |
50 | Goblin.ConvexShape.prototype.computeVolume.call( this, polygon_faces );
51 |
52 | this.aabb = new Goblin.AABB();
53 | this.calculateLocalAABB( this.aabb );
54 | };
55 |
56 | /**
57 | * Calculates this shape's local AABB and stores it in the passed AABB object
58 | *
59 | * @method calculateLocalAABB
60 | * @param aabb {AABB}
61 | */
62 | Goblin.MeshShape.prototype.calculateLocalAABB = function( aabb ) {
63 | aabb.min.x = aabb.min.y = aabb.min.z = 0;
64 | aabb.max.x = aabb.max.y = aabb.max.z = 0;
65 |
66 | for ( var i = 0; i < this.vertices.length; i++ ) {
67 | aabb.min.x = Math.min( aabb.min.x, this.vertices[i].x );
68 | aabb.min.y = Math.min( aabb.min.y, this.vertices[i].y );
69 | aabb.min.z = Math.min( aabb.min.z, this.vertices[i].z );
70 |
71 | aabb.max.x = Math.max( aabb.max.x, this.vertices[i].x );
72 | aabb.max.y = Math.max( aabb.max.y, this.vertices[i].y );
73 | aabb.max.z = Math.max( aabb.max.z, this.vertices[i].z );
74 | }
75 | };
76 |
77 | Goblin.MeshShape.prototype.getInertiaTensor = function( mass ) {
78 | return Goblin.ConvexShape.prototype.getInertiaTensor.call( this, mass );
79 | };
80 |
81 | /**
82 | * noop
83 | *
84 | * @method findSupportPoint
85 | * @param direction {vec3} direction to use in finding the support point
86 | * @param support_point {vec3} vec3 variable which will contain the supporting point after calling this method
87 | */
88 | Goblin.MeshShape.prototype.findSupportPoint = function( direction, support_point ) {
89 | return; // MeshShape isn't convex so it cannot be used directly in GJK
90 | };
91 |
92 | /**
93 | * Checks if a ray segment intersects with the shape
94 | *
95 | * @method rayIntersect
96 | * @property start {vec3} start point of the segment
97 | * @property end {vec3} end point of the segment
98 | * @return {RayIntersection|null} if the segment intersects, a RayIntersection is returned, else `null`
99 | */
100 | Goblin.MeshShape.prototype.rayIntersect = (function(){
101 | var intersections = [],
102 | tSort = function( a, b ) {
103 | if ( a.t < b.t ) {
104 | return -1;
105 | } else if ( a.t > b.t ) {
106 | return 1;
107 | } else {
108 | return 0;
109 | }
110 | };
111 |
112 | return function( start, end ) {
113 | // Traverse the BVH and return the closest point of contact, if any
114 | var nodes = [ this.hierarchy ],
115 | node;
116 | intersections.length = 0;
117 |
118 | var count = 0;
119 | while ( nodes.length > 0 ) {
120 | count++;
121 | node = nodes.shift();
122 |
123 | if ( node.aabb.testRayIntersect( start, end ) ) {
124 | // Ray intersects this node's AABB
125 | if ( node.isLeaf() ) {
126 | var intersection = node.object.rayIntersect( start, end );
127 | if ( intersection != null ) {
128 | intersections.push( intersection );
129 | }
130 | } else {
131 | nodes.push( node.left, node.right );
132 | }
133 | }
134 | }
135 |
136 | intersections.sort( tSort );
137 | return intersections[0] || null;
138 | };
139 | })();
--------------------------------------------------------------------------------
/src/classes/Constraints/FrictionConstraint.js:
--------------------------------------------------------------------------------
1 | Goblin.FrictionConstraint = function() {
2 | Goblin.Constraint.call( this );
3 |
4 | this.contact = null;
5 | };
6 | Goblin.FrictionConstraint.prototype = Object.create( Goblin.Constraint.prototype );
7 |
8 | Goblin.FrictionConstraint.prototype.buildFromContact = function( contact ) {
9 | this.rows[0] = this.rows[0] || Goblin.ObjectPool.getObject( 'ConstraintRow' );
10 | this.rows[1] = this.rows[1] || Goblin.ObjectPool.getObject( 'ConstraintRow' );
11 |
12 | this.object_a = contact.object_a;
13 | this.object_b = contact.object_b;
14 | this.contact = contact;
15 |
16 | var self = this;
17 | var onDestroy = function() {
18 | this.removeListener( 'destroy', onDestroy );
19 | self.deactivate();
20 | };
21 | this.contact.addListener( 'destroy', onDestroy );
22 |
23 | this.update();
24 | };
25 |
26 | Goblin.FrictionConstraint.prototype.update = (function(){
27 | var rel_a = new Goblin.Vector3(),
28 | rel_b = new Goblin.Vector3(),
29 | u1 = new Goblin.Vector3(),
30 | u2 = new Goblin.Vector3();
31 |
32 | return function updateFrictionConstraint() {
33 | var row_1 = this.rows[0],
34 | row_2 = this.rows[1];
35 |
36 | // Find the contact point relative to object_a and object_b
37 | rel_a.subtractVectors( this.contact.contact_point, this.object_a.position );
38 | rel_b.subtractVectors( this.contact.contact_point, this.object_b.position );
39 |
40 | this.contact.contact_normal.findOrthogonal( u1, u2 );
41 |
42 | if ( this.object_a == null || this.object_a._mass === Infinity ) {
43 | row_1.jacobian[0] = row_1.jacobian[1] = row_1.jacobian[2] = 0;
44 | row_1.jacobian[3] = row_1.jacobian[4] = row_1.jacobian[5] = 0;
45 | row_2.jacobian[0] = row_2.jacobian[1] = row_2.jacobian[2] = 0;
46 | row_2.jacobian[3] = row_2.jacobian[4] = row_2.jacobian[5] = 0;
47 | } else {
48 | row_1.jacobian[0] = -u1.x;
49 | row_1.jacobian[1] = -u1.y;
50 | row_1.jacobian[2] = -u1.z;
51 |
52 | _tmp_vec3_1.crossVectors( rel_a, u1 );
53 | row_1.jacobian[3] = -_tmp_vec3_1.x;
54 | row_1.jacobian[4] = -_tmp_vec3_1.y;
55 | row_1.jacobian[5] = -_tmp_vec3_1.z;
56 |
57 | row_2.jacobian[0] = -u2.x;
58 | row_2.jacobian[1] = -u2.y;
59 | row_2.jacobian[2] = -u2.z;
60 |
61 | _tmp_vec3_1.crossVectors( rel_a, u2 );
62 | row_2.jacobian[3] = -_tmp_vec3_1.x;
63 | row_2.jacobian[4] = -_tmp_vec3_1.y;
64 | row_2.jacobian[5] = -_tmp_vec3_1.z;
65 | }
66 |
67 | if ( this.object_b == null || this.object_b._mass === Infinity ) {
68 | row_1.jacobian[6] = row_1.jacobian[7] = row_1.jacobian[8] = 0;
69 | row_1.jacobian[9] = row_1.jacobian[10] = row_1.jacobian[11] = 0;
70 | row_2.jacobian[6] = row_2.jacobian[7] = row_2.jacobian[8] = 0;
71 | row_2.jacobian[9] = row_2.jacobian[10] = row_2.jacobian[11] = 0;
72 | } else {
73 | row_1.jacobian[6] = u1.x;
74 | row_1.jacobian[7] = u1.y;
75 | row_1.jacobian[8] = u1.z;
76 |
77 | _tmp_vec3_1.crossVectors( rel_b, u1 );
78 | row_1.jacobian[9] = _tmp_vec3_1.x;
79 | row_1.jacobian[10] = _tmp_vec3_1.y;
80 | row_1.jacobian[11] = _tmp_vec3_1.z;
81 |
82 | row_2.jacobian[6] = u2.x;
83 | row_2.jacobian[7] = u2.y;
84 | row_2.jacobian[8] = u2.z;
85 |
86 | _tmp_vec3_1.crossVectors( rel_b, u2 );
87 | row_2.jacobian[9] = _tmp_vec3_1.x;
88 | row_2.jacobian[10] = _tmp_vec3_1.y;
89 | row_2.jacobian[11] = _tmp_vec3_1.z;
90 | }
91 |
92 | // Find total velocity between the two bodies along the contact normal
93 | this.object_a.getVelocityInLocalPoint( this.contact.contact_point_in_a, _tmp_vec3_1 );
94 |
95 | // Include accumulated forces
96 | if ( this.object_a._mass !== Infinity ) {
97 | // accumulated linear velocity
98 | _tmp_vec3_1.scaleVector( this.object_a.accumulated_force, 1 / this.object_a._mass );
99 | _tmp_vec3_1.add( this.object_a.linear_velocity );
100 |
101 | // accumulated angular velocity
102 | this.object_a.inverseInertiaTensorWorldFrame.transformVector3Into( this.object_a.accumulated_torque, _tmp_vec3_3 );
103 | _tmp_vec3_3.add( this.object_a.angular_velocity );
104 |
105 | _tmp_vec3_3.cross( this.contact.contact_point_in_a );
106 | _tmp_vec3_1.add( _tmp_vec3_3 );
107 | _tmp_vec3_1.scale( this.object_a._mass );
108 | } else {
109 | _tmp_vec3_1.set( 0, 0, 0 );
110 | }
111 |
112 | var limit = this.contact.friction * 25;
113 | if ( limit < 0 ) {
114 | limit = 0;
115 | }
116 | row_1.lower_limit = row_2.lower_limit = -limit;
117 | row_1.upper_limit = row_2.upper_limit = limit;
118 |
119 | row_1.bias = row_2.bias = 0;
120 |
121 | this.rows[0] = row_1;
122 | this.rows[1] = row_2;
123 | };
124 | })();
--------------------------------------------------------------------------------
/tests/convexshape.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Convex Shape | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/examples/mesh-mesh.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mesh Shape | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/tests/damping.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Damping | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/tests/math/quaternion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Math: Quaternion | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/classes/Constraints/SliderConstraint.js:
--------------------------------------------------------------------------------
1 | Goblin.SliderConstraint = function( object_a, axis, object_b ) {
2 | Goblin.Constraint.call( this );
3 |
4 | this.object_a = object_a;
5 | this.axis = axis;
6 | this.object_b = object_b;
7 |
8 | // Find the initial distance between the two objects in object_a's local frame
9 | this.position_error = new Goblin.Vector3();
10 | this.position_error.subtractVectors( this.object_b.position, this.object_a.position );
11 | _tmp_quat4_1.invertQuaternion( this.object_a.rotation );
12 | _tmp_quat4_1.transformVector3( this.position_error );
13 |
14 | this.rotation_difference = new Goblin.Quaternion();
15 | if ( this.object_b != null ) {
16 | _tmp_quat4_1.invertQuaternion( this.object_b.rotation );
17 | this.rotation_difference.multiplyQuaternions( _tmp_quat4_1, this.object_a.rotation );
18 | }
19 |
20 | this.erp = 0.1;
21 |
22 | // First two rows constrain the linear velocities orthogonal to `axis`
23 | // Rows three through five constrain angular velocities
24 | for ( var i = 0; i < 5; i++ ) {
25 | this.rows[i] = Goblin.ObjectPool.getObject( 'ConstraintRow' );
26 | this.rows[i].lower_limit = -Infinity;
27 | this.rows[i].upper_limit = Infinity;
28 | this.rows[i].bias = 0;
29 |
30 | this.rows[i].jacobian[0] = this.rows[i].jacobian[1] = this.rows[i].jacobian[2] =
31 | this.rows[i].jacobian[3] = this.rows[i].jacobian[4] = this.rows[i].jacobian[5] =
32 | this.rows[i].jacobian[6] = this.rows[i].jacobian[7] = this.rows[i].jacobian[8] =
33 | this.rows[i].jacobian[9] = this.rows[i].jacobian[10] = this.rows[i].jacobian[11] = 0;
34 | }
35 | };
36 | Goblin.SliderConstraint.prototype = Object.create( Goblin.Constraint.prototype );
37 |
38 | Goblin.SliderConstraint.prototype.update = (function(){
39 | var _axis = new Goblin.Vector3(),
40 | n1 = new Goblin.Vector3(),
41 | n2 = new Goblin.Vector3();
42 |
43 | return function( time_delta ) {
44 | // `axis` is in object_a's local frame, convert to world
45 | this.object_a.rotation.transformVector3Into( this.axis, _axis );
46 |
47 | // Find two vectors that are orthogonal to `axis`
48 | _axis.findOrthogonal( n1, n2 );
49 |
50 | this._updateLinearConstraints( time_delta, n1, n2 );
51 | this._updateAngularConstraints( time_delta, n1, n2 );
52 | };
53 | })();
54 |
55 | Goblin.SliderConstraint.prototype._updateLinearConstraints = function( time_delta, n1, n2 ) {
56 | var c = new Goblin.Vector3();
57 | c.subtractVectors( this.object_b.position, this.object_a.position );
58 | //c.scale( 0.5 );
59 |
60 | var cx = new Goblin.Vector3( );
61 |
62 | // first linear constraint
63 | cx.crossVectors( c, n1 );
64 | this.rows[0].jacobian[0] = -n1.x;
65 | this.rows[0].jacobian[1] = -n1.y;
66 | this.rows[0].jacobian[2] = -n1.z;
67 | //this.rows[0].jacobian[3] = -cx[0];
68 | //this.rows[0].jacobian[4] = -cx[1];
69 | //this.rows[0].jacobian[5] = -cx[2];
70 |
71 | this.rows[0].jacobian[6] = n1.x;
72 | this.rows[0].jacobian[7] = n1.y;
73 | this.rows[0].jacobian[8] = n1.z;
74 | this.rows[0].jacobian[9] = 0;
75 | this.rows[0].jacobian[10] = 0;
76 | this.rows[0].jacobian[11] = 0;
77 |
78 | // second linear constraint
79 | cx.crossVectors( c, n2 );
80 | this.rows[1].jacobian[0] = -n2.x;
81 | this.rows[1].jacobian[1] = -n2.y;
82 | this.rows[1].jacobian[2] = -n2.z;
83 | //this.rows[1].jacobian[3] = -cx[0];
84 | //this.rows[1].jacobian[4] = -cx[1];
85 | //this.rows[1].jacobian[5] = -cx[2];
86 |
87 | this.rows[1].jacobian[6] = n2.x;
88 | this.rows[1].jacobian[7] = n2.y;
89 | this.rows[1].jacobian[8] = n2.z;
90 | this.rows[1].jacobian[9] = 0;
91 | this.rows[1].jacobian[10] = 0;
92 | this.rows[1].jacobian[11] = 0;
93 |
94 | // linear constraint error
95 | //c.scale( 2 );
96 | this.object_a.rotation.transformVector3Into( this.position_error, _tmp_vec3_1 );
97 | _tmp_vec3_2.subtractVectors( c, _tmp_vec3_1 );
98 | _tmp_vec3_2.scale( this.erp / time_delta );
99 | this.rows[0].bias = -n1.dot( _tmp_vec3_2 );
100 | this.rows[1].bias = -n2.dot( _tmp_vec3_2 );
101 | };
102 |
103 | Goblin.SliderConstraint.prototype._updateAngularConstraints = function( time_delta, n1, n2, axis ) {
104 | this.rows[2].jacobian[3] = this.rows[3].jacobian[4] = this.rows[4].jacobian[5] = -1;
105 | this.rows[2].jacobian[9] = this.rows[3].jacobian[10] = this.rows[4].jacobian[11] = 1;
106 |
107 | _tmp_quat4_1.invertQuaternion( this.object_b.rotation );
108 | _tmp_quat4_1.multiply( this.object_a.rotation );
109 |
110 | _tmp_quat4_2.invertQuaternion( this.rotation_difference );
111 | _tmp_quat4_2.multiply( _tmp_quat4_1 );
112 | // _tmp_quat4_2 is now the rotational error that needs to be corrected
113 |
114 | var error = new Goblin.Vector3();
115 | error.x = _tmp_quat4_2.x;
116 | error.y = _tmp_quat4_2.y;
117 | error.z = _tmp_quat4_2.z;
118 | error.scale( this.erp / time_delta );
119 |
120 | //this.rows[2].bias = error[0];
121 | //this.rows[3].bias = error[1];
122 | //this.rows[4].bias = error[2];
123 | };
--------------------------------------------------------------------------------
/src/classes/Shapes/CompoundShape.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class CompoundShape
3 | * @constructor
4 | */
5 | Goblin.CompoundShape = function() {
6 | this.child_shapes = [];
7 |
8 | this.aabb = new Goblin.AABB();
9 | this.calculateLocalAABB( this.aabb );
10 | };
11 |
12 | /**
13 | * Adds the child shape at `position` and `rotation` relative to the compound shape
14 | *
15 | * @method addChildShape
16 | * @param shape
17 | * @param position
18 | * @param rotation
19 | */
20 | Goblin.CompoundShape.prototype.addChildShape = function( shape, position, rotation ) {
21 | this.child_shapes.push( new Goblin.CompoundShapeChild( shape, position, rotation ) );
22 | this.calculateLocalAABB( this.aabb );
23 | };
24 |
25 | /**
26 | * Calculates this shape's local AABB and stores it in the passed AABB object
27 | *
28 | * @method calculateLocalAABB
29 | * @param aabb {AABB}
30 | */
31 | Goblin.CompoundShape.prototype.calculateLocalAABB = function( aabb ) {
32 | aabb.min.x = aabb.min.y = aabb.min.z = Infinity;
33 | aabb.max.x = aabb.max.y = aabb.max.z = -Infinity;
34 |
35 | var i, shape;
36 |
37 | for ( i = 0; i < this.child_shapes.length; i++ ) {
38 | shape = this.child_shapes[i];
39 |
40 | aabb.min.x = Math.min( aabb.min.x, shape.aabb.min.x );
41 | aabb.min.y = Math.min( aabb.min.y, shape.aabb.min.y );
42 | aabb.min.z = Math.min( aabb.min.z, shape.aabb.min.z );
43 |
44 | aabb.max.x = Math.max( aabb.max.x, shape.aabb.max.x );
45 | aabb.max.y = Math.max( aabb.max.y, shape.aabb.max.y );
46 | aabb.max.z = Math.max( aabb.max.z, shape.aabb.max.z );
47 | }
48 | };
49 |
50 | Goblin.CompoundShape.prototype.getInertiaTensor = function( mass ) {
51 | var tensor = new Goblin.Matrix3(),
52 | j = new Goblin.Matrix3(),
53 | i,
54 | child,
55 | child_tensor;
56 | tensor.identity();
57 |
58 | mass /= this.child_shapes.length;
59 |
60 | // Holds center of current tensor
61 | _tmp_vec3_1.x = _tmp_vec3_1.y = _tmp_vec3_1.z = 0;
62 |
63 | for ( i = 0; i < this.child_shapes.length; i++ ) {
64 | child = this.child_shapes[i];
65 |
66 | _tmp_vec3_1.subtract( child.position );
67 |
68 | j.e00 = mass * -( _tmp_vec3_1.y * _tmp_vec3_1.y + _tmp_vec3_1.z * _tmp_vec3_1.z );
69 | j.e10 = mass * _tmp_vec3_1.x * _tmp_vec3_1.y;
70 | j.e20 = mass * _tmp_vec3_1.x * _tmp_vec3_1.z;
71 |
72 | j.e01 = mass * _tmp_vec3_1.x * _tmp_vec3_1.y;
73 | j.e11 = mass * -( _tmp_vec3_1.x * _tmp_vec3_1.x + _tmp_vec3_1.z * _tmp_vec3_1.z );
74 | j.e21 = mass * _tmp_vec3_1.y * _tmp_vec3_1.z;
75 |
76 | j.e02 = mass * _tmp_vec3_1.x * _tmp_vec3_1.z;
77 | j.e12 = mass * _tmp_vec3_1.y * _tmp_vec3_1.z;
78 | j.e22 = mass * -( _tmp_vec3_1.x * _tmp_vec3_1.x + _tmp_vec3_1.y * _tmp_vec3_1.y );
79 |
80 | _tmp_mat3_1.fromMatrix4( child.transform );
81 | child_tensor = child.shape.getInertiaTensor( mass );
82 | _tmp_mat3_1.transposeInto( _tmp_mat3_2 );
83 | _tmp_mat3_1.multiply( child_tensor );
84 | _tmp_mat3_1.multiply( _tmp_mat3_2 );
85 |
86 | tensor.e00 += _tmp_mat3_1.e00 + j.e00;
87 | tensor.e10 += _tmp_mat3_1.e10 + j.e10;
88 | tensor.e20 += _tmp_mat3_1.e20 + j.e20;
89 | tensor.e01 += _tmp_mat3_1.e01 + j.e01;
90 | tensor.e11 += _tmp_mat3_1.e11 + j.e11;
91 | tensor.e21 += _tmp_mat3_1.e21 + j.e21;
92 | tensor.e02 += _tmp_mat3_1.e02 + j.e02;
93 | tensor.e12 += _tmp_mat3_1.e12 + j.e12;
94 | tensor.e22 += _tmp_mat3_1.e22 + j.e22;
95 | }
96 |
97 | return tensor;
98 | };
99 |
100 | /**
101 | * Checks if a ray segment intersects with the shape
102 | *
103 | * @method rayIntersect
104 | * @property ray_start {vec3} start point of the segment
105 | * @property ray_end {vec3} end point of the segment
106 | * @return {RayIntersection|null} if the segment intersects, a RayIntersection is returned, else `null`
107 | */
108 | Goblin.CompoundShape.prototype.rayIntersect = (function(){
109 | var tSort = function( a, b ) {
110 | if ( a.t < b.t ) {
111 | return -1;
112 | } else if ( a.t > b.t ) {
113 | return 1;
114 | } else {
115 | return 0;
116 | }
117 | };
118 | return function( ray_start, ray_end ) {
119 | var intersections = [],
120 | local_start = new Goblin.Vector3(),
121 | local_end = new Goblin.Vector3(),
122 | intersection,
123 | i, child;
124 |
125 | for ( i = 0; i < this.child_shapes.length; i++ ) {
126 | child = this.child_shapes[i];
127 |
128 | child.transform_inverse.transformVector3Into( ray_start, local_start );
129 | child.transform_inverse.transformVector3Into( ray_end, local_end );
130 |
131 | intersection = child.shape.rayIntersect( local_start, local_end );
132 | if ( intersection != null ) {
133 | intersection.object = this; // change from the shape to the body
134 | child.transform.transformVector3( intersection.point ); // transform child's local coordinates to the compound's coordinates
135 | intersections.push( intersection );
136 | }
137 | }
138 |
139 | intersections.sort( tSort );
140 | return intersections[0] || null;
141 | };
142 | })();
--------------------------------------------------------------------------------
/src/classes/Shapes/TriangleShape.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class TriangleShape
3 | * @param vertex_a {Vector3} first vertex
4 | * @param vertex_b {Vector3} second vertex
5 | * @param vertex_c {Vector3} third vertex
6 | * @constructor
7 | */
8 | Goblin.TriangleShape = function( vertex_a, vertex_b, vertex_c ) {
9 | /**
10 | * first vertex of the triangle
11 | *
12 | * @property a
13 | * @type {Vector3}
14 | */
15 | this.a = vertex_a;
16 |
17 | /**
18 | * second vertex of the triangle
19 | *
20 | * @property b
21 | * @type {Vector3}
22 | */
23 | this.b = vertex_b;
24 |
25 | /**
26 | * third vertex of the triangle
27 | *
28 | * @property c
29 | * @type {Vector3}
30 | */
31 | this.c = vertex_c;
32 |
33 | /**
34 | * normal vector of the triangle
35 | *
36 | * @property normal
37 | * @type {Goblin.Vector3}
38 | */
39 | this.normal = new Goblin.Vector3();
40 | _tmp_vec3_1.subtractVectors( this.b, this.a );
41 | _tmp_vec3_2.subtractVectors( this.c, this.a );
42 | this.normal.crossVectors( _tmp_vec3_1, _tmp_vec3_2 );
43 |
44 | /**
45 | * area of the triangle
46 | *
47 | * @property volume
48 | * @type {Number}
49 | */
50 | this.volume = this.normal.length() / 2;
51 |
52 | this.normal.normalize();
53 |
54 | this.aabb = new Goblin.AABB();
55 | this.calculateLocalAABB( this.aabb );
56 | };
57 |
58 | /**
59 | * Calculates this shape's local AABB and stores it in the passed AABB object
60 | *
61 | * @method calculateLocalAABB
62 | * @param aabb {AABB}
63 | */
64 | Goblin.TriangleShape.prototype.calculateLocalAABB = function( aabb ) {
65 | aabb.min.x = Math.min( this.a.x, this.b.x, this.c.x );
66 | aabb.min.y = Math.min( this.a.y, this.b.y, this.c.y );
67 | aabb.min.z = Math.min( this.a.z, this.b.z, this.c.z );
68 |
69 | aabb.max.x = Math.max( this.a.x, this.b.x, this.c.x );
70 | aabb.max.y = Math.max( this.a.y, this.b.y, this.c.y );
71 | aabb.max.z = Math.max( this.a.z, this.b.z, this.c.z );
72 | };
73 |
74 | Goblin.TriangleShape.prototype.getInertiaTensor = function( mass ) {
75 | // @TODO http://www.efunda.com/math/areas/triangle.cfm
76 | return new Goblin.Matrix3(
77 | 0, 0, 0,
78 | 0, 0, 0,
79 | 0, 0, 0
80 | );
81 | };
82 |
83 | Goblin.TriangleShape.prototype.classifyVertex = function( vertex ) {
84 | var w = this.normal.dot( this.a );
85 | return this.normal.dot( vertex ) - w;
86 | };
87 |
88 | /**
89 | * Given `direction`, find the point in this body which is the most extreme in that direction.
90 | * This support point is calculated in world coordinates and stored in the second parameter `support_point`
91 | *
92 | * @method findSupportPoint
93 | * @param direction {vec3} direction to use in finding the support point
94 | * @param support_point {vec3} vec3 variable which will contain the supporting point after calling this method
95 | */
96 | Goblin.TriangleShape.prototype.findSupportPoint = function( direction, support_point ) {
97 | var dot, best_dot = -Infinity;
98 |
99 | dot = direction.dot( this.a );
100 | if ( dot > best_dot ) {
101 | support_point.copy( this.a );
102 | best_dot = dot;
103 | }
104 |
105 | dot = direction.dot( this.b );
106 | if ( dot > best_dot ) {
107 | support_point.copy( this.b );
108 | best_dot = dot;
109 | }
110 |
111 | dot = direction.dot( this.c );
112 | if ( dot > best_dot ) {
113 | support_point.copy( this.c );
114 | }
115 | };
116 |
117 | /**
118 | * Checks if a ray segment intersects with the shape
119 | *
120 | * @method rayIntersect
121 | * @property start {vec3} start point of the segment
122 | * @property end {vec3{ end point of the segment
123 | * @return {RayIntersection|null} if the segment intersects, a RayIntersection is returned, else `null`
124 | */
125 | Goblin.TriangleShape.prototype.rayIntersect = (function(){
126 | var d1 = new Goblin.Vector3(),
127 | d2 = new Goblin.Vector3(),
128 | n = new Goblin.Vector3(),
129 | segment = new Goblin.Vector3(),
130 | b = new Goblin.Vector3(),
131 | u = new Goblin.Vector3();
132 |
133 | return function( start, end ) {
134 | d1.subtractVectors( this.b, this.a );
135 | d2.subtractVectors( this.c, this.a );
136 | n.crossVectors( d1, d2 );
137 |
138 | segment.subtractVectors( end, start );
139 | var det = -segment.dot( n );
140 |
141 | if ( det <= 0 ) {
142 | // Ray is parallel to triangle or triangle's normal points away from ray
143 | return null;
144 | }
145 |
146 | b.subtractVectors( start, this.a );
147 |
148 | var t = b.dot( n ) / det;
149 | if ( 0 > t || t > 1 ) {
150 | // Ray doesn't intersect the triangle's plane
151 | return null;
152 | }
153 |
154 | u.crossVectors( b, segment );
155 | var u1 = d2.dot( u ) / det,
156 | u2 = -d1.dot( u ) / det;
157 |
158 | if ( u1 + u2 > 1 || u1 < 0 || u2 < 0 ) {
159 | // segment does not intersect triangle
160 | return null;
161 | }
162 |
163 | var intersection = Goblin.ObjectPool.getObject( 'RayIntersection' );
164 | intersection.object = this;
165 | intersection.t = t * segment.length();
166 | intersection.point.scaleVector( segment, t );
167 | intersection.point.add( start );
168 | intersection.normal.copy( this.normal );
169 |
170 | return intersection;
171 | };
172 | })();
--------------------------------------------------------------------------------
/lib/ConvexGeometry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @fileoverview This is a convex hull generator using the incremental method.
4 | * The complexity is O(n^2) where n is the number of vertices.
5 | * O(nlogn) algorithms do exist, but they are much more complicated.
6 | *
7 | * Benchmark:
8 | *
9 | * Platform: CPU: P7350 @2.00GHz Engine: V8
10 | *
11 | * Num Vertices Time(ms)
12 | *
13 | * 10 1
14 | * 20 3
15 | * 30 19
16 | * 40 48
17 | * 50 107
18 | */
19 |
20 | THREE.ConvexGeometry = function( vertices ) {
21 |
22 | THREE.Geometry.call( this );
23 |
24 | var faces = [ [ 0, 1, 2 ], [ 0, 2, 1 ] ];
25 |
26 | for ( var i = 3; i < vertices.length; i++ ) {
27 |
28 | addPoint( i );
29 |
30 | }
31 |
32 |
33 | function addPoint( vertexId ) {
34 |
35 | var vertex = vertices[ vertexId ].clone();
36 |
37 | var mag = vertex.length();
38 | vertex.x += mag * randomOffset();
39 | vertex.y += mag * randomOffset();
40 | vertex.z += mag * randomOffset();
41 |
42 | var hole = [];
43 |
44 | for ( var f = 0; f < faces.length; ) {
45 |
46 | var face = faces[ f ];
47 |
48 | // for each face, if the vertex can see it,
49 | // then we try to add the face's edges into the hole.
50 | if ( visible( face, vertex ) ) {
51 |
52 | for ( var e = 0; e < 3; e++ ) {
53 |
54 | var edge = [ face[ e ], face[ ( e + 1 ) % 3 ] ];
55 | var boundary = true;
56 |
57 | // remove duplicated edges.
58 | for ( var h = 0; h < hole.length; h++ ) {
59 |
60 | if ( equalEdge( hole[ h ], edge ) ) {
61 |
62 | hole[ h ] = hole[ hole.length - 1 ];
63 | hole.pop();
64 | boundary = false;
65 | break;
66 |
67 | }
68 |
69 | }
70 |
71 | if ( boundary ) {
72 |
73 | hole.push( edge );
74 |
75 | }
76 |
77 | }
78 |
79 | // remove faces[ f ]
80 | faces[ f ] = faces[ faces.length - 1 ];
81 | faces.pop();
82 |
83 | } else { // not visible
84 |
85 | f++;
86 |
87 | }
88 | }
89 |
90 | // construct the new faces formed by the edges of the hole and the vertex
91 | for ( var h = 0; h < hole.length; h++ ) {
92 |
93 | faces.push( [
94 | hole[ h ][ 0 ],
95 | hole[ h ][ 1 ],
96 | vertexId
97 | ] );
98 |
99 | }
100 | }
101 |
102 | /**
103 | * Whether the face is visible from the vertex
104 | */
105 | function visible( face, vertex ) {
106 |
107 | var va = vertices[ face[ 0 ] ];
108 | var vb = vertices[ face[ 1 ] ];
109 | var vc = vertices[ face[ 2 ] ];
110 |
111 | var n = normal( va, vb, vc );
112 |
113 | // distance from face to origin
114 | var dist = n.dot( va );
115 |
116 | return n.dot( vertex ) >= dist;
117 |
118 | }
119 |
120 | /**
121 | * Face normal
122 | */
123 | function normal( va, vb, vc ) {
124 |
125 | var cb = new THREE.Vector3();
126 | var ab = new THREE.Vector3();
127 |
128 | cb.subVectors( vc, vb );
129 | ab.subVectors( va, vb );
130 | cb.cross( ab );
131 |
132 | cb.normalize();
133 |
134 | return cb;
135 |
136 | }
137 |
138 | /**
139 | * Detect whether two edges are equal.
140 | * Note that when constructing the convex hull, two same edges can only
141 | * be of the negative direction.
142 | */
143 | function equalEdge( ea, eb ) {
144 |
145 | return ea[ 0 ] === eb[ 1 ] && ea[ 1 ] === eb[ 0 ];
146 |
147 | }
148 |
149 | /**
150 | * Create a random offset between -1e-6 and 1e-6.
151 | */
152 | function randomOffset() {
153 |
154 | return ( Math.random() - 0.5 ) * 2 * 1e-6;
155 |
156 | }
157 |
158 |
159 | /**
160 | * XXX: Not sure if this is the correct approach. Need someone to review.
161 | */
162 | function vertexUv( vertex ) {
163 |
164 | var mag = vertex.length();
165 | return new THREE.Vector2( vertex.x / mag, vertex.y / mag );
166 |
167 | }
168 |
169 | // Push vertices into `this.vertices`, skipping those inside the hull
170 | var id = 0;
171 | var newId = new Array( vertices.length ); // map from old vertex id to new id
172 |
173 | for ( var i = 0; i < faces.length; i++ ) {
174 |
175 | var face = faces[ i ];
176 |
177 | for ( var j = 0; j < 3; j++ ) {
178 |
179 | if ( newId[ face[ j ] ] === undefined ) {
180 |
181 | newId[ face[ j ] ] = id++;
182 | this.vertices.push( vertices[ face[ j ] ] );
183 |
184 | }
185 |
186 | face[ j ] = newId[ face[ j ] ];
187 |
188 | }
189 |
190 | }
191 |
192 | // Convert faces into instances of THREE.Face3
193 | for ( var i = 0; i < faces.length; i++ ) {
194 |
195 | this.faces.push( new THREE.Face3(
196 | faces[ i ][ 0 ],
197 | faces[ i ][ 1 ],
198 | faces[ i ][ 2 ]
199 | ) );
200 |
201 | }
202 |
203 | // Compute UVs
204 | for ( var i = 0; i < this.faces.length; i++ ) {
205 |
206 | var face = this.faces[ i ];
207 |
208 | this.faceVertexUvs[ 0 ].push( [
209 | vertexUv( this.vertices[ face.a ] ),
210 | vertexUv( this.vertices[ face.b ] ),
211 | vertexUv( this.vertices[ face.c ])
212 | ] );
213 |
214 | }
215 |
216 | this.computeFaceNormals();
217 | this.computeVertexNormals();
218 |
219 | };
220 |
221 | THREE.ConvexGeometry.prototype = Object.create( THREE.Geometry.prototype );
222 |
--------------------------------------------------------------------------------
/examples/shapes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Shapes | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/tests/css/mocha.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | body {
4 | margin:0;
5 | }
6 |
7 | #mocha {
8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | margin: 60px 50px;
10 | }
11 |
12 | #mocha ul, #mocha li {
13 | margin: 0;
14 | padding: 0;
15 | }
16 |
17 | #mocha ul {
18 | list-style: none;
19 | }
20 |
21 | #mocha h1, #mocha h2 {
22 | margin: 0;
23 | }
24 |
25 | #mocha h1 {
26 | margin-top: 15px;
27 | font-size: 1em;
28 | font-weight: 200;
29 | }
30 |
31 | #mocha h1 a {
32 | text-decoration: none;
33 | color: inherit;
34 | }
35 |
36 | #mocha h1 a:hover {
37 | text-decoration: underline;
38 | }
39 |
40 | #mocha .suite .suite h1 {
41 | margin-top: 0;
42 | font-size: .8em;
43 | }
44 |
45 | #mocha .hidden {
46 | display: none;
47 | }
48 |
49 | #mocha h2 {
50 | font-size: 12px;
51 | font-weight: normal;
52 | cursor: pointer;
53 | }
54 |
55 | #mocha .suite {
56 | margin-left: 15px;
57 | }
58 |
59 | #mocha .test {
60 | margin-left: 15px;
61 | overflow: hidden;
62 | }
63 |
64 | #mocha .test.pending:hover h2::after {
65 | content: '(pending)';
66 | font-family: arial, sans-serif;
67 | }
68 |
69 | #mocha .test.pass.medium .duration {
70 | background: #C09853;
71 | }
72 |
73 | #mocha .test.pass.slow .duration {
74 | background: #B94A48;
75 | }
76 |
77 | #mocha .test.pass::before {
78 | content: '✓';
79 | font-size: 12px;
80 | display: block;
81 | float: left;
82 | margin-right: 5px;
83 | color: #00d6b2;
84 | }
85 |
86 | #mocha .test.pass .duration {
87 | font-size: 9px;
88 | margin-left: 5px;
89 | padding: 2px 5px;
90 | color: white;
91 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
92 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
93 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
94 | -webkit-border-radius: 5px;
95 | -moz-border-radius: 5px;
96 | -ms-border-radius: 5px;
97 | -o-border-radius: 5px;
98 | border-radius: 5px;
99 | }
100 |
101 | #mocha .test.pass.fast .duration {
102 | display: none;
103 | }
104 |
105 | #mocha .test.pending {
106 | color: #0b97c4;
107 | }
108 |
109 | #mocha .test.pending::before {
110 | content: '◦';
111 | color: #0b97c4;
112 | }
113 |
114 | #mocha .test.fail {
115 | color: #c00;
116 | }
117 |
118 | #mocha .test.fail pre {
119 | color: black;
120 | }
121 |
122 | #mocha .test.fail::before {
123 | content: '✖';
124 | font-size: 12px;
125 | display: block;
126 | float: left;
127 | margin-right: 5px;
128 | color: #c00;
129 | }
130 |
131 | #mocha .test pre.error {
132 | color: #c00;
133 | max-height: 300px;
134 | overflow: auto;
135 | }
136 |
137 | #mocha .test pre {
138 | display: block;
139 | float: left;
140 | clear: left;
141 | font: 12px/1.5 monaco, monospace;
142 | margin: 5px;
143 | padding: 15px;
144 | border: 1px solid #eee;
145 | border-bottom-color: #ddd;
146 | -webkit-border-radius: 3px;
147 | -webkit-box-shadow: 0 1px 3px #eee;
148 | -moz-border-radius: 3px;
149 | -moz-box-shadow: 0 1px 3px #eee;
150 | border-radius: 3px;
151 | }
152 |
153 | #mocha .test h2 {
154 | position: relative;
155 | }
156 |
157 | #mocha .test a.replay {
158 | position: absolute;
159 | top: 3px;
160 | right: 0;
161 | text-decoration: none;
162 | vertical-align: middle;
163 | display: block;
164 | width: 15px;
165 | height: 15px;
166 | line-height: 15px;
167 | text-align: center;
168 | background: #eee;
169 | font-size: 15px;
170 | -moz-border-radius: 15px;
171 | border-radius: 15px;
172 | -webkit-transition: opacity 200ms;
173 | -moz-transition: opacity 200ms;
174 | transition: opacity 200ms;
175 | opacity: 0.3;
176 | color: #888;
177 | }
178 |
179 | #mocha .test:hover a.replay {
180 | opacity: 1;
181 | }
182 |
183 | #mocha-report.pass .test.fail {
184 | display: none;
185 | }
186 |
187 | #mocha-report.fail .test.pass {
188 | display: none;
189 | }
190 |
191 | #mocha-report.pending .test.pass,
192 | #mocha-report.pending .test.fail {
193 | display: none;
194 | }
195 | #mocha-report.pending .test.pass.pending {
196 | display: block;
197 | }
198 |
199 | #mocha-error {
200 | color: #c00;
201 | font-size: 1.5em;
202 | font-weight: 100;
203 | letter-spacing: 1px;
204 | }
205 |
206 | #mocha-stats {
207 | position: fixed;
208 | top: 15px;
209 | right: 10px;
210 | font-size: 12px;
211 | margin: 0;
212 | color: #888;
213 | z-index: 1;
214 | }
215 |
216 | #mocha-stats .progress {
217 | float: right;
218 | padding-top: 0;
219 | }
220 |
221 | #mocha-stats em {
222 | color: black;
223 | }
224 |
225 | #mocha-stats a {
226 | text-decoration: none;
227 | color: inherit;
228 | }
229 |
230 | #mocha-stats a:hover {
231 | border-bottom: 1px solid #eee;
232 | }
233 |
234 | #mocha-stats li {
235 | display: inline-block;
236 | margin: 0 5px;
237 | list-style: none;
238 | padding-top: 11px;
239 | }
240 |
241 | #mocha-stats canvas {
242 | width: 40px;
243 | height: 40px;
244 | }
245 |
246 | #mocha code .comment { color: #ddd }
247 | #mocha code .init { color: #2F6FAD }
248 | #mocha code .string { color: #5890AD }
249 | #mocha code .keyword { color: #8A6343 }
250 | #mocha code .number { color: #2F6FAD }
251 |
252 | @media screen and (max-device-width: 480px) {
253 | #mocha {
254 | margin: 60px 0px;
255 | }
256 |
257 | #mocha #stats {
258 | position: absolute;
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/tests/restitution.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Restitution | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/src/classes/Constraints/HingeConstraint.js:
--------------------------------------------------------------------------------
1 | Goblin.HingeConstraint = function( object_a, hinge_a, point_a, object_b, point_b ) {
2 | Goblin.Constraint.call( this );
3 |
4 | this.object_a = object_a;
5 | this.hinge_a = hinge_a;
6 | this.point_a = point_a;
7 |
8 | this.object_b = object_b || null;
9 | this.point_b = new Goblin.Vector3();
10 | this.hinge_b = new Goblin.Vector3();
11 | if ( this.object_b != null ) {
12 | this.object_a.rotation.transformVector3Into( this.hinge_a, this.hinge_b );
13 | _tmp_quat4_1.invertQuaternion( this.object_b.rotation );
14 | _tmp_quat4_1.transformVector3( this.hinge_b );
15 |
16 | this.point_b = point_b;
17 | } else {
18 | this.object_a.updateDerived(); // Ensure the body's transform is correct
19 | this.object_a.rotation.transformVector3Into( this.hinge_a, this.hinge_b );
20 | this.object_a.transform.transformVector3Into( this.point_a, this.point_b );
21 | }
22 |
23 | this.erp = 0.1;
24 |
25 | // Create rows
26 | // rows 0,1,2 are the same as point constraint and constrain the objects' positions
27 | // rows 3,4 introduce the rotational constraints which constrains angular velocity orthogonal to the hinge axis
28 | for ( var i = 0; i < 5; i++ ) {
29 | this.rows[i] = Goblin.ObjectPool.getObject( 'ConstraintRow' );
30 | this.rows[i].lower_limit = -Infinity;
31 | this.rows[i].upper_limit = Infinity;
32 | this.rows[i].bias = 0;
33 |
34 | this.rows[i].jacobian[0] = this.rows[i].jacobian[1] = this.rows[i].jacobian[2] =
35 | this.rows[i].jacobian[3] = this.rows[i].jacobian[4] = this.rows[i].jacobian[5] =
36 | this.rows[i].jacobian[6] = this.rows[i].jacobian[7] = this.rows[i].jacobian[8] =
37 | this.rows[i].jacobian[9] = this.rows[i].jacobian[10] = this.rows[i].jacobian[11] = 0;
38 | }
39 | };
40 | Goblin.HingeConstraint.prototype = Object.create( Goblin.Constraint.prototype );
41 |
42 | Goblin.HingeConstraint.prototype.update = (function(){
43 | var r1 = new Goblin.Vector3(),
44 | r2 = new Goblin.Vector3(),
45 | t1 = new Goblin.Vector3(),
46 | t2 = new Goblin.Vector3(),
47 | world_axis = new Goblin.Vector3();
48 |
49 | return function( time_delta ) {
50 | this.object_a.rotation.transformVector3Into( this.hinge_a, world_axis );
51 |
52 | this.object_a.transform.transformVector3Into( this.point_a, _tmp_vec3_1 );
53 | r1.subtractVectors( _tmp_vec3_1, this.object_a.position );
54 |
55 | // 0,1,2 are positional, same as PointConstraint
56 | this.rows[0].jacobian[0] = -1;
57 | this.rows[0].jacobian[1] = 0;
58 | this.rows[0].jacobian[2] = 0;
59 | this.rows[0].jacobian[3] = 0;
60 | this.rows[0].jacobian[4] = -r1.z;
61 | this.rows[0].jacobian[5] = r1.y;
62 |
63 | this.rows[1].jacobian[0] = 0;
64 | this.rows[1].jacobian[1] = -1;
65 | this.rows[1].jacobian[2] = 0;
66 | this.rows[1].jacobian[3] = r1.z;
67 | this.rows[1].jacobian[4] = 0;
68 | this.rows[1].jacobian[5] = -r1.x;
69 |
70 | this.rows[2].jacobian[0] = 0;
71 | this.rows[2].jacobian[1] = 0;
72 | this.rows[2].jacobian[2] = -1;
73 | this.rows[2].jacobian[3] = -r1.y;
74 | this.rows[2].jacobian[4] = r1.x;
75 | this.rows[2].jacobian[5] = 0;
76 |
77 | // 3,4 are rotational, constraining motion orthogonal to axis
78 | world_axis.findOrthogonal( t1, t2 );
79 | this.rows[3].jacobian[3] = -t1.x;
80 | this.rows[3].jacobian[4] = -t1.y;
81 | this.rows[3].jacobian[5] = -t1.z;
82 |
83 | this.rows[4].jacobian[3] = -t2.x;
84 | this.rows[4].jacobian[4] = -t2.y;
85 | this.rows[4].jacobian[5] = -t2.z;
86 |
87 | if ( this.object_b != null ) {
88 | this.object_b.transform.transformVector3Into( this.point_b, _tmp_vec3_2 );
89 | r2.subtractVectors( _tmp_vec3_2, this.object_b.position );
90 |
91 | // 0,1,2 are positional, same as PointConstraint
92 | this.rows[0].jacobian[6] = 1;
93 | this.rows[0].jacobian[7] = 0;
94 | this.rows[0].jacobian[8] = 0;
95 | this.rows[0].jacobian[9] = 0;
96 | this.rows[0].jacobian[10] = r2.z;
97 | this.rows[0].jacobian[11] = -r2.y;
98 |
99 | this.rows[1].jacobian[6] = 0;
100 | this.rows[1].jacobian[7] = 1;
101 | this.rows[1].jacobian[8] = 0;
102 | this.rows[1].jacobian[9] = -r2.z;
103 | this.rows[1].jacobian[10] = 0;
104 | this.rows[1].jacobian[11] = r2.x;
105 |
106 | this.rows[2].jacobian[6] = 0;
107 | this.rows[2].jacobian[7] = 0;
108 | this.rows[2].jacobian[8] = 1;
109 | this.rows[2].jacobian[9] = r2.y;
110 | this.rows[2].jacobian[10] = -r2.z;
111 | this.rows[2].jacobian[11] = 0;
112 |
113 | // 3,4 are rotational, constraining motion orthogonal to axis
114 | this.rows[3].jacobian[9] = t1.x;
115 | this.rows[3].jacobian[10] = t1.y;
116 | this.rows[3].jacobian[11] = t1.z;
117 |
118 | this.rows[4].jacobian[9] = t2.x;
119 | this.rows[4].jacobian[10] = t2.y;
120 | this.rows[4].jacobian[11] = t2.z;
121 | } else {
122 | _tmp_vec3_2.copy( this.point_b );
123 | }
124 |
125 | // Linear error correction
126 | _tmp_vec3_3.subtractVectors( _tmp_vec3_1, _tmp_vec3_2 );
127 | _tmp_vec3_3.scale( this.erp / time_delta );
128 | this.rows[0].bias = _tmp_vec3_3.x;
129 | this.rows[1].bias = _tmp_vec3_3.y;
130 | this.rows[2].bias = _tmp_vec3_3.z;
131 |
132 | // Angular error correction
133 | if (this.object_b != null) {
134 | this.object_a.rotation.transformVector3Into(this.hinge_a, _tmp_vec3_1);
135 | this.object_b.rotation.transformVector3Into(this.hinge_b, _tmp_vec3_2);
136 | _tmp_vec3_1.cross(_tmp_vec3_2);
137 | this.rows[3].bias = -_tmp_vec3_1.dot(t1);
138 | this.rows[4].bias = -_tmp_vec3_1.dot(t2);
139 | } else {
140 | this.rows[3].bias = this.rows[4].bias = 0;
141 | }
142 | };
143 | })( );
--------------------------------------------------------------------------------
/src/classes/Constraints/WeldConstraint.js:
--------------------------------------------------------------------------------
1 | Goblin.WeldConstraint = function( object_a, point_a, object_b, point_b ) {
2 | Goblin.Constraint.call( this );
3 |
4 | this.object_a = object_a;
5 | this.point_a = point_a;
6 |
7 | this.object_b = object_b || null;
8 | this.point_b = point_b || null;
9 |
10 | this.rotation_difference = new Goblin.Quaternion();
11 | if ( this.object_b != null ) {
12 | _tmp_quat4_1.invertQuaternion( this.object_b.rotation );
13 | this.rotation_difference.multiplyQuaternions( _tmp_quat4_1, this.object_a.rotation );
14 | }
15 |
16 | this.erp = 0.1;
17 |
18 | // Create translation constraint rows
19 | for ( var i = 0; i < 3; i++ ) {
20 | this.rows[i] = Goblin.ObjectPool.getObject( 'ConstraintRow' );
21 | this.rows[i].lower_limit = -Infinity;
22 | this.rows[i].upper_limit = Infinity;
23 | this.rows[i].bias = 0;
24 |
25 | if ( this.object_b == null ) {
26 | this.rows[i].jacobian[0] = this.rows[i].jacobian[1] = this.rows[i].jacobian[2] =
27 | this.rows[i].jacobian[4] = this.rows[i].jacobian[5] = this.rows[i].jacobian[6] =
28 | this.rows[i].jacobian[7] = this.rows[i].jacobian[8] = this.rows[i].jacobian[9] =
29 | this.rows[i].jacobian[10] = this.rows[i].jacobian[11] = this.rows[i].jacobian[12] = 0;
30 | this.rows[i].jacobian[i] = 1;
31 | }
32 | }
33 |
34 | // Create rotation constraint rows
35 | for ( i = 3; i < 6; i++ ) {
36 | this.rows[i] = Goblin.ObjectPool.getObject( 'ConstraintRow' );
37 | this.rows[i].lower_limit = -Infinity;
38 | this.rows[i].upper_limit = Infinity;
39 | this.rows[i].bias = 0;
40 |
41 | if ( this.object_b == null ) {
42 | this.rows[i].jacobian[0] = this.rows[i].jacobian[1] = this.rows[i].jacobian[2] =
43 | this.rows[i].jacobian[4] = this.rows[i].jacobian[5] = this.rows[i].jacobian[6] =
44 | this.rows[i].jacobian[7] = this.rows[i].jacobian[8] = this.rows[i].jacobian[9] =
45 | this.rows[i].jacobian[10] = this.rows[i].jacobian[11] = this.rows[i].jacobian[12] = 0;
46 | this.rows[i].jacobian[i] = 1;
47 | } else {
48 | this.rows[i].jacobian[0] = this.rows[i].jacobian[1] = this.rows[i].jacobian[2] = 0;
49 | this.rows[i].jacobian[3] = this.rows[i].jacobian[4] = this.rows[i].jacobian[5] = 0;
50 | this.rows[i].jacobian[ i ] = -1;
51 |
52 | this.rows[i].jacobian[6] = this.rows[i].jacobian[7] = this.rows[i].jacobian[8] = 0;
53 | this.rows[i].jacobian[9] = this.rows[i].jacobian[10] = this.rows[i].jacobian[11] = 0;
54 | this.rows[i].jacobian[ i + 6 ] = 1;
55 | }
56 | }
57 | };
58 | Goblin.WeldConstraint.prototype = Object.create( Goblin.Constraint.prototype );
59 |
60 | Goblin.WeldConstraint.prototype.update = (function(){
61 | var r1 = new Goblin.Vector3(),
62 | r2 = new Goblin.Vector3();
63 |
64 | return function( time_delta ) {
65 | if ( this.object_b == null ) {
66 | // No need to update the constraint, all motion is already constrained
67 | return;
68 | }
69 |
70 | this.object_a.transform.transformVector3Into( this.point_a, _tmp_vec3_1 );
71 | r1.subtractVectors( _tmp_vec3_1, this.object_a.position );
72 |
73 | this.rows[0].jacobian[0] = -1;
74 | this.rows[0].jacobian[1] = 0;
75 | this.rows[0].jacobian[2] = 0;
76 | this.rows[0].jacobian[3] = 0;
77 | this.rows[0].jacobian[4] = -r1.z;
78 | this.rows[0].jacobian[5] = r1.y;
79 |
80 | this.rows[1].jacobian[0] = 0;
81 | this.rows[1].jacobian[1] = -1;
82 | this.rows[1].jacobian[2] = 0;
83 | this.rows[1].jacobian[3] = r1.z;
84 | this.rows[1].jacobian[4] = 0;
85 | this.rows[1].jacobian[5] = -r1.x;
86 |
87 | this.rows[2].jacobian[0] = 0;
88 | this.rows[2].jacobian[1] = 0;
89 | this.rows[2].jacobian[2] = -1;
90 | this.rows[2].jacobian[3] = -r1.y;
91 | this.rows[2].jacobian[4] = r1.x;
92 | this.rows[2].jacobian[5] = 0;
93 |
94 | if ( this.object_b != null ) {
95 | this.object_b.transform.transformVector3Into( this.point_b, _tmp_vec3_2 );
96 | r2.subtractVectors( _tmp_vec3_2, this.object_b.position );
97 |
98 | this.rows[0].jacobian[6] = 1;
99 | this.rows[0].jacobian[7] = 0;
100 | this.rows[0].jacobian[8] = 0;
101 | this.rows[0].jacobian[9] = 0;
102 | this.rows[0].jacobian[10] = r2.z;
103 | this.rows[0].jacobian[11] = -r2.y;
104 |
105 | this.rows[1].jacobian[6] = 0;
106 | this.rows[1].jacobian[7] = 1;
107 | this.rows[1].jacobian[8] = 0;
108 | this.rows[1].jacobian[9] = -r2.z;
109 | this.rows[1].jacobian[10] = 0;
110 | this.rows[1].jacobian[11] = r2.x;
111 |
112 | this.rows[2].jacobian[6] = 0;
113 | this.rows[2].jacobian[7] = 0;
114 | this.rows[2].jacobian[8] = 1;
115 | this.rows[2].jacobian[9] = r2.y;
116 | this.rows[2].jacobian[10] = -r2.x;
117 | this.rows[2].jacobian[11] = 0;
118 | } else {
119 | _tmp_vec3_2.copy( this.point_b );
120 | }
121 |
122 | var error = new Goblin.Vector3();
123 |
124 | // Linear correction
125 | error.subtractVectors( _tmp_vec3_1, _tmp_vec3_2 );
126 | error.scale( this.erp / time_delta );
127 | this.rows[0].bias = error.x;
128 | this.rows[1].bias = error.y;
129 | this.rows[2].bias = error.z;
130 |
131 | // Rotation correction
132 | _tmp_quat4_1.invertQuaternion( this.object_b.rotation );
133 | _tmp_quat4_1.multiply( this.object_a.rotation );
134 |
135 | _tmp_quat4_2.invertQuaternion( this.rotation_difference );
136 | _tmp_quat4_2.multiply( _tmp_quat4_1 );
137 | // _tmp_quat4_2 is now the rotational error that needs to be corrected
138 |
139 | error.x = _tmp_quat4_2.x;
140 | error.y = _tmp_quat4_2.y;
141 | error.z = _tmp_quat4_2.z;
142 | error.scale( this.erp / time_delta );
143 |
144 | this.rows[3].bias = error.x;
145 | this.rows[4].bias = error.y;
146 | this.rows[5].bias = error.z;
147 | };
148 | })( );
--------------------------------------------------------------------------------
/src/classes/Collision/BoxSphere.js:
--------------------------------------------------------------------------------
1 | Goblin.BoxSphere = function( object_a, object_b ) {
2 | var sphere = object_a.shape instanceof Goblin.SphereShape ? object_a : object_b,
3 | box = object_a.shape instanceof Goblin.SphereShape ? object_b : object_a,
4 | contact, distance;
5 |
6 | // Transform the center of the sphere into box coordinates
7 | box.transform_inverse.transformVector3Into( sphere.position, _tmp_vec3_1 );
8 |
9 | // Early out check to see if we can exclude the contact
10 | if ( Math.abs( _tmp_vec3_1.x ) - sphere.shape.radius > box.shape.half_width ||
11 | Math.abs( _tmp_vec3_1.y ) - sphere.shape.radius > box.shape.half_height ||
12 | Math.abs( _tmp_vec3_1.z ) - sphere.shape.radius > box.shape.half_depth )
13 | {
14 | return;
15 | }
16 |
17 | // `_tmp_vec3_1` is the center of the sphere in relation to the box
18 | // `_tmp_vec3_2` will hold the point on the box closest to the sphere
19 | _tmp_vec3_2.x = _tmp_vec3_2.y = _tmp_vec3_2.z = 0;
20 |
21 | // Clamp each coordinate to the box.
22 | distance = _tmp_vec3_1.x;
23 | if ( distance > box.shape.half_width ) {
24 | distance = box.shape.half_width;
25 | } else if (distance < -box.shape.half_width ) {
26 | distance = -box.shape.half_width;
27 | }
28 | _tmp_vec3_2.x = distance;
29 |
30 | distance = _tmp_vec3_1.y;
31 | if ( distance > box.shape.half_height ) {
32 | distance = box.shape.half_height;
33 | } else if (distance < -box.shape.half_height ) {
34 | distance = -box.shape.half_height;
35 | }
36 | _tmp_vec3_2.y = distance;
37 |
38 | distance = _tmp_vec3_1.z;
39 | if ( distance > box.shape.half_depth ) {
40 | distance = box.shape.half_depth;
41 | } else if (distance < -box.shape.half_depth ) {
42 | distance = -box.shape.half_depth;
43 | }
44 | _tmp_vec3_2.z = distance;
45 |
46 | // Check we're in contact
47 | _tmp_vec3_3.subtractVectors( _tmp_vec3_2, _tmp_vec3_1 );
48 | distance = _tmp_vec3_3.lengthSquared();
49 | if ( distance > sphere.shape.radius * sphere.shape.radius ) {
50 | return;
51 | }
52 |
53 | // Get a ContactDetails object populate it
54 | contact = Goblin.ObjectPool.getObject( 'ContactDetails' );
55 | contact.object_a = sphere;
56 | contact.object_b = box;
57 |
58 | if ( distance === 0 ) {
59 |
60 | // The center of the sphere is contained within the box
61 | Goblin.BoxSphere.spherePenetration( box.shape, _tmp_vec3_1, _tmp_vec3_2, contact );
62 |
63 | } else {
64 |
65 | // Center of the sphere is outside of the box
66 |
67 | // Find contact normal and penetration depth
68 | contact.contact_normal.subtractVectors( _tmp_vec3_2, _tmp_vec3_1 );
69 | contact.penetration_depth = -contact.contact_normal.length();
70 | contact.contact_normal.scale( -1 / contact.penetration_depth );
71 |
72 | // Set contact point of `object_b` (the box )
73 | contact.contact_point_in_b.copy( _tmp_vec3_2 );
74 |
75 | }
76 |
77 | // Update penetration depth to include sphere's radius
78 | contact.penetration_depth += sphere.shape.radius;
79 |
80 | // Convert contact normal to world coordinates
81 | box.transform.rotateVector3( contact.contact_normal );
82 |
83 | // Contact point in `object_a` (the sphere) is the normal * radius converted to the sphere's frame
84 | sphere.transform_inverse.rotateVector3Into( contact.contact_normal, contact.contact_point_in_a );
85 | contact.contact_point_in_a.scale( sphere.shape.radius );
86 |
87 | // Find contact position
88 | contact.contact_point.scaleVector( contact.contact_normal, sphere.shape.radius - contact.penetration_depth / 2 );
89 | contact.contact_point.add( sphere.position );
90 |
91 | contact.restitution = ( sphere.restitution + box.restitution ) / 2;
92 | contact.friction = ( sphere.friction + box.friction ) / 2;
93 |
94 | return contact;
95 | };
96 |
97 | Goblin.BoxSphere.spherePenetration = function( box, sphere_center, box_point, contact ) {
98 | var min_distance, face_distance;
99 |
100 | if ( sphere_center.x < 0 ) {
101 | min_distance = box.half_width + sphere_center.x;
102 | box_point.x = -box.half_width;
103 | box_point.y = box_point.z = 0;
104 | contact.penetration_depth = min_distance;
105 | } else {
106 | min_distance = box.half_width - sphere_center.x;
107 | box_point.x = box.half_width;
108 | box_point.y = box_point.z = 0;
109 | contact.penetration_depth = min_distance;
110 | }
111 |
112 | if ( sphere_center.y < 0 ) {
113 | face_distance = box.half_height + sphere_center.y;
114 | if ( face_distance < min_distance ) {
115 | min_distance = face_distance;
116 | box_point.y = -box.half_height;
117 | box_point.x = box_point.z = 0;
118 | contact.penetration_depth = min_distance;
119 | }
120 | } else {
121 | face_distance = box.half_height - sphere_center.y;
122 | if ( face_distance < min_distance ) {
123 | min_distance = face_distance;
124 | box_point.y = box.half_height;
125 | box_point.x = box_point.z = 0;
126 | contact.penetration_depth = min_distance;
127 | }
128 | }
129 |
130 | if ( sphere_center.z < 0 ) {
131 | face_distance = box.half_depth + sphere_center.z;
132 | if ( face_distance < min_distance ) {
133 | box_point.z = -box.half_depth;
134 | box_point.x = box_point.y = 0;
135 | contact.penetration_depth = min_distance;
136 | }
137 | } else {
138 | face_distance = box.half_depth - sphere_center.z;
139 | if ( face_distance < min_distance ) {
140 | box_point.z = box.half_depth;
141 | box_point.x = box_point.y = 0;
142 | contact.penetration_depth = min_distance;
143 | }
144 | }
145 |
146 | // Set contact point of `object_b` (the box)
147 | contact.contact_point_in_b.copy( _tmp_vec3_2 );
148 | contact.contact_normal.scaleVector( contact.contact_point_in_b, -1 );
149 | contact.contact_normal.normalize();
150 | };
--------------------------------------------------------------------------------
/src/classes/Shapes/BoxShape.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class BoxShape
3 | * @param half_width {Number} half width of the cube ( X axis )
4 | * @param half_height {Number} half height of the cube ( Y axis )
5 | * @param half_depth {Number} half depth of the cube ( Z axis )
6 | * @constructor
7 | */
8 | Goblin.BoxShape = function( half_width, half_height, half_depth ) {
9 | /**
10 | * Half width of the cube ( X axis )
11 | *
12 | * @property half_width
13 | * @type {Number}
14 | */
15 | this.half_width = half_width;
16 |
17 | /**
18 | * Half height of the cube ( Y axis )
19 | *
20 | * @property half_height
21 | * @type {Number}
22 | */
23 | this.half_height = half_height;
24 |
25 | /**
26 | * Half width of the cube ( Z axis )
27 | *
28 | * @property half_height
29 | * @type {Number}
30 | */
31 | this.half_depth = half_depth;
32 |
33 | this.aabb = new Goblin.AABB();
34 | this.calculateLocalAABB( this.aabb );
35 | };
36 |
37 | /**
38 | * Calculates this shape's local AABB and stores it in the passed AABB object
39 | *
40 | * @method calculateLocalAABB
41 | * @param aabb {AABB}
42 | */
43 | Goblin.BoxShape.prototype.calculateLocalAABB = function( aabb ) {
44 | aabb.min.x = -this.half_width;
45 | aabb.min.y = -this.half_height;
46 | aabb.min.z = -this.half_depth;
47 |
48 | aabb.max.x = this.half_width;
49 | aabb.max.y = this.half_height;
50 | aabb.max.z = this.half_depth;
51 | };
52 |
53 | Goblin.BoxShape.prototype.getInertiaTensor = function( mass ) {
54 | var height_squared = this.half_height * this.half_height * 4,
55 | width_squared = this.half_width * this.half_width * 4,
56 | depth_squared = this.half_depth * this.half_depth * 4,
57 | element = 0.0833 * mass;
58 | return new Goblin.Matrix3(
59 | element * ( height_squared + depth_squared ), 0, 0,
60 | 0, element * ( width_squared + depth_squared ), 0,
61 | 0, 0, element * ( height_squared + width_squared )
62 | );
63 | };
64 |
65 | /**
66 | * Given `direction`, find the point in this body which is the most extreme in that direction.
67 | * This support point is calculated in world coordinates and stored in the second parameter `support_point`
68 | *
69 | * @method findSupportPoint
70 | * @param direction {vec3} direction to use in finding the support point
71 | * @param support_point {vec3} vec3 variable which will contain the supporting point after calling this method
72 | */
73 | Goblin.BoxShape.prototype.findSupportPoint = function( direction, support_point ) {
74 | /*
75 | support_point = [
76 | sign( direction.x ) * half_width,
77 | sign( direction.y ) * half_height,
78 | sign( direction.z ) * half_depth
79 | ]
80 | */
81 |
82 | // Calculate the support point in the local frame
83 | if ( direction.x < 0 ) {
84 | support_point.x = -this.half_width;
85 | } else {
86 | support_point.x = this.half_width;
87 | }
88 |
89 | if ( direction.y < 0 ) {
90 | support_point.y = -this.half_height;
91 | } else {
92 | support_point.y = this.half_height;
93 | }
94 |
95 | if ( direction.z < 0 ) {
96 | support_point.z = -this.half_depth;
97 | } else {
98 | support_point.z = this.half_depth;
99 | }
100 | };
101 |
102 | /**
103 | * Checks if a ray segment intersects with the shape
104 | *
105 | * @method rayIntersect
106 | * @property start {vec3} start point of the segment
107 | * @property end {vec3} end point of the segment
108 | * @return {RayIntersection|null} if the segment intersects, a RayIntersection is returned, else `null`
109 | */
110 | Goblin.BoxShape.prototype.rayIntersect = (function(){
111 | var direction = new Goblin.Vector3(),
112 | tmin, tmax,
113 | axis, ood, t1, t2, extent;
114 |
115 | return function( start, end ) {
116 | tmin = 0;
117 |
118 | direction.subtractVectors( end, start );
119 | tmax = direction.length();
120 | direction.scale( 1 / tmax ); // normalize direction
121 |
122 | for ( var i = 0; i < 3; i++ ) {
123 | axis = i === 0 ? 'x' : ( i === 1 ? 'y' : 'z' );
124 | extent = ( i === 0 ? this.half_width : ( i === 1 ? this.half_height : this.half_depth ) );
125 |
126 | if ( Math.abs( direction[axis] ) < Goblin.EPSILON ) {
127 | // Ray is parallel to axis
128 | if ( start[axis] < -extent || start[axis] > extent ) {
129 | return null;
130 | }
131 | }
132 |
133 | ood = 1 / direction[axis];
134 | t1 = ( -extent - start[axis] ) * ood;
135 | t2 = ( extent - start[axis] ) * ood;
136 | if ( t1 > t2 ) {
137 | ood = t1; // ood is a convenient temp variable as it's not used again
138 | t1 = t2;
139 | t2 = ood;
140 | }
141 |
142 | // Find intersection intervals
143 | tmin = Math.max( tmin, t1 );
144 | tmax = Math.min( tmax, t2 );
145 |
146 | if ( tmin > tmax ) {
147 | return null;
148 | }
149 | }
150 |
151 | var intersection = Goblin.ObjectPool.getObject( 'RayIntersection' );
152 | intersection.object = this;
153 | intersection.t = tmin;
154 | intersection.point.scaleVector( direction, tmin );
155 | intersection.point.add( start );
156 |
157 | // Find face normal
158 | var max = Infinity;
159 | for ( i = 0; i < 3; i++ ) {
160 | axis = i === 0 ? 'x' : ( i === 1 ? 'y' : 'z' );
161 | extent = ( i === 0 ? this.half_width : ( i === 1 ? this.half_height : this.half_depth ) );
162 | if ( extent - Math.abs( intersection.point[axis] ) < max ) {
163 | intersection.normal.x = intersection.normal.y = intersection.normal.z = 0;
164 | intersection.normal[axis] = intersection.point[axis] < 0 ? -1 : 1;
165 | max = extent - Math.abs( intersection.point[axis] );
166 | }
167 | }
168 |
169 | return intersection;
170 | };
171 | })();
--------------------------------------------------------------------------------
/src/classes/Math/Matrix3.js:
--------------------------------------------------------------------------------
1 | Goblin.Matrix3 = function( e00, e01, e02, e10, e11, e12, e20, e21, e22 ) {
2 | this.e00 = e00 || 0;
3 | this.e01 = e01 || 0;
4 | this.e02 = e02 || 0;
5 |
6 | this.e10 = e10 || 0;
7 | this.e11 = e11 || 0;
8 | this.e12 = e12 || 0;
9 |
10 | this.e20 = e20 || 0;
11 | this.e21 = e21 || 0;
12 | this.e22 = e22 || 0;
13 | };
14 |
15 | Goblin.Matrix3.prototype = {
16 | identity: function() {
17 | this.e00 = 1;
18 | this.e01 = 0;
19 | this.e02 = 0;
20 |
21 | this.e10 = 0;
22 | this.e11 = 1;
23 | this.e12 = 0;
24 |
25 | this.e20 = 0;
26 | this.e21 = 0;
27 | this.e22 = 1;
28 | },
29 |
30 | fromMatrix4: function( m ) {
31 | this.e00 = m.e00;
32 | this.e01 = m.e01;
33 | this.e02 = m.e02;
34 |
35 | this.e10 = m.e10;
36 | this.e11 = m.e11;
37 | this.e12 = m.e12;
38 |
39 | this.e20 = m.e20;
40 | this.e21 = m.e21;
41 | this.e22 = m.e22;
42 | },
43 |
44 | fromQuaternion: function( q ) {
45 | var x2 = q.x + q.x,
46 | y2 = q.y + q.y,
47 | z2 = q.z + q.z,
48 |
49 | xx = q.x * x2,
50 | xy = q.x * y2,
51 | xz = q.x * z2,
52 | yy = q.y * y2,
53 | yz = q.y * z2,
54 | zz = q.z * z2,
55 | wx = q.w * x2,
56 | wy = q.w * y2,
57 | wz = q.w * z2;
58 |
59 | this.e00 = 1 - (yy + zz);
60 | this.e10 = xy + wz;
61 | this.e20 = xz - wy;
62 |
63 | this.e01 = xy - wz;
64 | this.e11 = 1 - (xx + zz);
65 | this.e21 = yz + wx;
66 |
67 | this.e02 = xz + wy;
68 | this.e12 = yz - wx;
69 | this.e22 = 1 - (xx + yy);
70 | },
71 |
72 | transformVector3: function( v ) {
73 | var x = v.x,
74 | y = v.y,
75 | z = v.z;
76 | v.x = this.e00 * x + this.e01 * y + this.e02 * z;
77 | v.y = this.e10 * x + this.e11 * y + this.e12 * z;
78 | v.z = this.e20 * x + this.e21 * y + this.e22 * z;
79 | },
80 |
81 | transformVector3Into: function( v, dest ) {
82 | dest.x = this.e00 * v.x + this.e01 * v.y + this.e02 * v.z;
83 | dest.y = this.e10 * v.x + this.e11 * v.y + this.e12 * v.z;
84 | dest.z = this.e20 * v.x + this.e21 * v.y + this.e22 * v.z;
85 | },
86 |
87 | transposeInto: function( m ) {
88 | m.e00 = this.e00;
89 | m.e10 = this.e01;
90 | m.e20 = this.e02;
91 | m.e01 = this.e10;
92 | m.e11 = this.e11;
93 | m.e21 = this.e12;
94 | m.e02 = this.e20;
95 | m.e12 = this.e21;
96 | m.e22 = this.e22;
97 | },
98 |
99 | invert: function() {
100 | var a00 = this.e00, a01 = this.e01, a02 = this.e02,
101 | a10 = this.e10, a11 = this.e11, a12 = this.e12,
102 | a20 = this.e20, a21 = this.e21, a22 = this.e22,
103 |
104 | b01 = a22 * a11 - a12 * a21,
105 | b11 = -a22 * a10 + a12 * a20,
106 | b21 = a21 * a10 - a11 * a20,
107 |
108 | d = a00 * b01 + a01 * b11 + a02 * b21,
109 | id;
110 |
111 | if ( !d ) {
112 | return true;
113 | }
114 | id = 1 / d;
115 |
116 | this.e00 = b01 * id;
117 | this.e01 = (-a22 * a01 + a02 * a21) * id;
118 | this.e02 = (a12 * a01 - a02 * a11) * id;
119 | this.e10 = b11 * id;
120 | this.e11 = (a22 * a00 - a02 * a20) * id;
121 | this.e12 = (-a12 * a00 + a02 * a10) * id;
122 | this.e20 = b21 * id;
123 | this.e21 = (-a21 * a00 + a01 * a20) * id;
124 | this.e22 = (a11 * a00 - a01 * a10) * id;
125 |
126 | return true;
127 | },
128 |
129 | invertInto: function( m ) {
130 | var a00 = this.e00, a01 = this.e01, a02 = this.e02,
131 | a10 = this.e10, a11 = this.e11, a12 = this.e12,
132 | a20 = this.e20, a21 = this.e21, a22 = this.e22,
133 |
134 | b01 = a22 * a11 - a12 * a21,
135 | b11 = -a22 * a10 + a12 * a20,
136 | b21 = a21 * a10 - a11 * a20,
137 |
138 | d = a00 * b01 + a01 * b11 + a02 * b21,
139 | id;
140 |
141 | if ( !d ) {
142 | return false;
143 | }
144 | id = 1 / d;
145 |
146 | m.e00 = b01 * id;
147 | m.e01 = (-a22 * a01 + a02 * a21) * id;
148 | m.e02 = (a12 * a01 - a02 * a11) * id;
149 | m.e10 = b11 * id;
150 | m.e11 = (a22 * a00 - a02 * a20) * id;
151 | m.e12 = (-a12 * a00 + a02 * a10) * id;
152 | m.e20 = b21 * id;
153 | m.e21 = (-a21 * a00 + a01 * a20) * id;
154 | m.e22 = (a11 * a00 - a01 * a10) * id;
155 |
156 | return true;
157 | },
158 |
159 | multiply: function( m ) {
160 | var a00 = this.e00, a01 = this.e01, a02 = this.e02,
161 | a10 = this.e10, a11 = this.e11, a12 = this.e12,
162 | a20 = this.e20, a21 = this.e21, a22 = this.e22,
163 |
164 | b00 = m.e00, b01 = m.e01, b02 = m.e02,
165 | b10 = m.e10, b11 = m.e11, b12 = m.e12,
166 | b20 = m.e20, b21 = m.e21, b22 = m.e22;
167 |
168 | this.e00 = b00 * a00 + b10 * a01 + b20 * a02;
169 | this.e10 = b00 * a10 + b10 * a11 + b20 * a12;
170 | this.e20 = b00 * a20 + b10 * a21 + b20 * a22;
171 |
172 | this.e01 = b01 * a00 + b11 * a01 + b21 * a02;
173 | this.e11 = b01 * a10 + b11 * a11 + b21 * a12;
174 | this.e21 = b01 * a20 + b11 * a21 + b21 * a22;
175 |
176 | this.e02 = b02 * a00 + b12 * a01 + b22 * a02;
177 | this.e12 = b02 * a10 + b12 * a11 + b22 * a12;
178 | this.e22 = b02 * a20 + b12 * a21 + b22 * a22;
179 | },
180 |
181 | multiplyFrom: function( a, b ) {
182 | var a00 = a.e00, a01 = a.e01, a02 = a.e02,
183 | a10 = a.e10, a11 = a.e11, a12 = a.e12,
184 | a20 = a.e20, a21 = a.e21, a22 = a.e22,
185 |
186 | b00 = b.e00, b01 = b.e01, b02 = b.e02,
187 | b10 = b.e10, b11 = b.e11, b12 = b.e12,
188 | b20 = b.e20, b21 = b.e21, b22 = b.e22;
189 |
190 | this.e00 = b00 * a00 + b10 * a01 + b20 * a02;
191 | this.e10 = b00 * a10 + b10 * a11 + b20 * a12;
192 | this.e20 = b00 * a20 + b10 * a21 + b20 * a22;
193 |
194 | this.e01 = b01 * a00 + b11 * a01 + b21 * a02;
195 | this.e11 = b01 * a10 + b11 * a11 + b21 * a12;
196 | this.e21 = b01 * a20 + b11 * a21 + b21 * a22;
197 |
198 | this.e02 = b02 * a00 + b12 * a01 + b22 * a02;
199 | this.e12 = b02 * a10 + b12 * a11 + b22 * a12;
200 | this.e22 = b02 * a20 + b12 * a21 + b22 * a22;
201 | }
202 | };
--------------------------------------------------------------------------------
/tests/gjk_spheres.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | GJK with spheres | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/tests/gjk_boxes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | GJK with boxes | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/classes/Constraints/ConstraintRow.js:
--------------------------------------------------------------------------------
1 | Goblin.ConstraintRow = function() {
2 | this.jacobian = new Float64Array( 12 );
3 | this.B = new Float64Array( 12 ); // `B` is the jacobian multiplied by the objects' inverted mass & inertia tensors
4 | this.D = 0; // Length of the jacobian
5 |
6 | this.lower_limit = -Infinity;
7 | this.upper_limit = Infinity;
8 |
9 | this.bias = 0;
10 | this.multiplier = 0;
11 | this.multiplier_cached = 0;
12 | this.eta = 0;
13 | this.eta_row = new Float64Array( 12 );
14 | };
15 |
16 | Goblin.ConstraintRow.prototype.computeB = function( constraint ) {
17 | var invmass;
18 |
19 | if ( constraint.object_a != null && constraint.object_a._mass !== Infinity ) {
20 | invmass = constraint.object_a._mass_inverted;
21 |
22 | this.B[0] = invmass * this.jacobian[0] * constraint.object_a.linear_factor.x;
23 | this.B[1] = invmass * this.jacobian[1] * constraint.object_a.linear_factor.y;
24 | this.B[2] = invmass * this.jacobian[2] * constraint.object_a.linear_factor.z;
25 |
26 | _tmp_vec3_1.x = this.jacobian[3];
27 | _tmp_vec3_1.y = this.jacobian[4];
28 | _tmp_vec3_1.z = this.jacobian[5];
29 | constraint.object_a.inverseInertiaTensorWorldFrame.transformVector3( _tmp_vec3_1 );
30 | this.B[3] = _tmp_vec3_1.x * constraint.object_a.angular_factor.x;
31 | this.B[4] = _tmp_vec3_1.y * constraint.object_a.angular_factor.y;
32 | this.B[5] = _tmp_vec3_1.z * constraint.object_a.angular_factor.z;
33 | } else {
34 | this.B[0] = this.B[1] = this.B[2] = 0;
35 | this.B[3] = this.B[4] = this.B[5] = 0;
36 | }
37 |
38 | if ( constraint.object_b != null && constraint.object_b._mass !== Infinity ) {
39 | invmass = constraint.object_b._mass_inverted;
40 | this.B[6] = invmass * this.jacobian[6] * constraint.object_b.linear_factor.x;
41 | this.B[7] = invmass * this.jacobian[7] * constraint.object_b.linear_factor.y;
42 | this.B[8] = invmass * this.jacobian[8] * constraint.object_b.linear_factor.z;
43 |
44 | _tmp_vec3_1.x = this.jacobian[9];
45 | _tmp_vec3_1.y = this.jacobian[10];
46 | _tmp_vec3_1.z = this.jacobian[11];
47 | constraint.object_b.inverseInertiaTensorWorldFrame.transformVector3( _tmp_vec3_1 );
48 | this.B[9] = _tmp_vec3_1.x * constraint.object_b.linear_factor.x;
49 | this.B[10] = _tmp_vec3_1.y * constraint.object_b.linear_factor.y;
50 | this.B[11] = _tmp_vec3_1.z * constraint.object_b.linear_factor.z;
51 | } else {
52 | this.B[6] = this.B[7] = this.B[8] = 0;
53 | this.B[9] = this.B[10] = this.B[11] = 0;
54 | }
55 | };
56 |
57 | Goblin.ConstraintRow.prototype.computeD = function() {
58 | this.D = (
59 | this.jacobian[0] * this.B[0] +
60 | this.jacobian[1] * this.B[1] +
61 | this.jacobian[2] * this.B[2] +
62 | this.jacobian[3] * this.B[3] +
63 | this.jacobian[4] * this.B[4] +
64 | this.jacobian[5] * this.B[5] +
65 | this.jacobian[6] * this.B[6] +
66 | this.jacobian[7] * this.B[7] +
67 | this.jacobian[8] * this.B[8] +
68 | this.jacobian[9] * this.B[9] +
69 | this.jacobian[10] * this.B[10] +
70 | this.jacobian[11] * this.B[11]
71 | );
72 | };
73 |
74 | Goblin.ConstraintRow.prototype.computeEta = function( constraint, time_delta ) {
75 | var invmass,
76 | inverse_time_delta = 1 / time_delta;
77 |
78 | if ( constraint.object_a == null || constraint.object_a._mass === Infinity ) {
79 | this.eta_row[0] = this.eta_row[1] = this.eta_row[2] = this.eta_row[3] = this.eta_row[4] = this.eta_row[5] = 0;
80 | } else {
81 | invmass = constraint.object_a._mass_inverted;
82 |
83 | this.eta_row[0] = ( constraint.object_a.linear_velocity.x + ( invmass * constraint.object_a.accumulated_force.x ) ) * inverse_time_delta;
84 | this.eta_row[1] = ( constraint.object_a.linear_velocity.y + ( invmass * constraint.object_a.accumulated_force.y ) ) * inverse_time_delta;
85 | this.eta_row[2] = ( constraint.object_a.linear_velocity.z + ( invmass * constraint.object_a.accumulated_force.z ) ) * inverse_time_delta;
86 |
87 | _tmp_vec3_1.copy( constraint.object_a.accumulated_torque );
88 | constraint.object_a.inverseInertiaTensorWorldFrame.transformVector3( _tmp_vec3_1 );
89 | this.eta_row[3] = ( constraint.object_a.angular_velocity.x + _tmp_vec3_1.x ) * inverse_time_delta;
90 | this.eta_row[4] = ( constraint.object_a.angular_velocity.y + _tmp_vec3_1.y ) * inverse_time_delta;
91 | this.eta_row[5] = ( constraint.object_a.angular_velocity.z + _tmp_vec3_1.z ) * inverse_time_delta;
92 | }
93 |
94 | if ( constraint.object_b == null || constraint.object_b._mass === Infinity ) {
95 | this.eta_row[6] = this.eta_row[7] = this.eta_row[8] = this.eta_row[9] = this.eta_row[10] = this.eta_row[11] = 0;
96 | } else {
97 | invmass = constraint.object_b._mass_inverted;
98 |
99 | this.eta_row[6] = ( constraint.object_b.linear_velocity.x + ( invmass * constraint.object_b.accumulated_force.x ) ) * inverse_time_delta;
100 | this.eta_row[7] = ( constraint.object_b.linear_velocity.y + ( invmass * constraint.object_b.accumulated_force.y ) ) * inverse_time_delta;
101 | this.eta_row[8] = ( constraint.object_b.linear_velocity.z + ( invmass * constraint.object_b.accumulated_force.z ) ) * inverse_time_delta;
102 |
103 | _tmp_vec3_1.copy( constraint.object_b.accumulated_torque );
104 | constraint.object_b.inverseInertiaTensorWorldFrame.transformVector3( _tmp_vec3_1 );
105 | this.eta_row[9] = ( constraint.object_b.angular_velocity.x + _tmp_vec3_1.x ) * inverse_time_delta;
106 | this.eta_row[10] = ( constraint.object_b.angular_velocity.y + _tmp_vec3_1.y ) * inverse_time_delta;
107 | this.eta_row[11] = ( constraint.object_b.angular_velocity.z + _tmp_vec3_1.z ) * inverse_time_delta;
108 | }
109 |
110 | var jdotv = this.jacobian[0] * this.eta_row[0] +
111 | this.jacobian[1] * this.eta_row[1] +
112 | this.jacobian[2] * this.eta_row[2] +
113 | this.jacobian[3] * this.eta_row[3] +
114 | this.jacobian[4] * this.eta_row[4] +
115 | this.jacobian[5] * this.eta_row[5] +
116 | this.jacobian[6] * this.eta_row[6] +
117 | this.jacobian[7] * this.eta_row[7] +
118 | this.jacobian[8] * this.eta_row[8] +
119 | this.jacobian[9] * this.eta_row[9] +
120 | this.jacobian[10] * this.eta_row[10] +
121 | this.jacobian[11] * this.eta_row[11];
122 |
123 | this.eta = ( this.bias * inverse_time_delta ) - jdotv;
124 | };
--------------------------------------------------------------------------------
/tests/math/vector3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Math: Vector3 | Goblin Physics
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
179 |
180 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------