├── .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 | --------------------------------------------------------------------------------