├── .gitignore ├── .npmignore ├── assets ├── Goose model │ ├── goose.jpg │ └── GooseModelCredits.txt ├── BoilerplateApp │ ├── index.htm │ ├── scripts │ │ └── app.js │ └── content │ │ └── appComplete.js ├── CubeColors │ ├── index.htm │ ├── scripts │ │ └── app.js │ └── content │ │ └── appComplete.js └── BoilerplatePhysics │ ├── index.htm │ ├── scripts │ ├── app.js │ ├── physijs_worker.js │ └── physi.js │ └── content │ └── appComplete.js ├── exercises ├── 01_introduction │ ├── exercise.js │ └── problem.md ├── 02_coordinates │ ├── exercise.js │ └── problem.md ├── 03_manipulate_objects │ ├── exercise.js │ └── problem.md ├── 04_scene_hierarchy │ ├── exercise.js │ └── problem.md ├── 06_detecting_mouse_clicks │ ├── exercise.js │ └── problem.md ├── 07_threejs_and_physics │ ├── exercise.js │ └── problem.md ├── 05_loading_3d_objects_and_textures │ ├── exercise.js │ └── problem.md └── menu.json ├── credits.txt ├── help.txt ├── credits.js ├── README.md ├── introtowebgl.js ├── package.json └── .jshintrc /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | learnyounode.png 2 | -------------------------------------------------------------------------------- /assets/Goose model/goose.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmackey/IntroToWebGLWithThreeJS/HEAD/assets/Goose model/goose.jpg -------------------------------------------------------------------------------- /exercises/01_introduction/exercise.js: -------------------------------------------------------------------------------- 1 | var exercise = require('workshopper-exercise')() 2 | 3 | module.exports = exercise 4 | -------------------------------------------------------------------------------- /exercises/02_coordinates/exercise.js: -------------------------------------------------------------------------------- 1 | var exercise = require('workshopper-exercise')() 2 | 3 | module.exports = exercise 4 | -------------------------------------------------------------------------------- /exercises/03_manipulate_objects/exercise.js: -------------------------------------------------------------------------------- 1 | var exercise = require('workshopper-exercise')() 2 | 3 | module.exports = exercise 4 | -------------------------------------------------------------------------------- /exercises/04_scene_hierarchy/exercise.js: -------------------------------------------------------------------------------- 1 | var exercise = require('workshopper-exercise')() 2 | 3 | module.exports = exercise 4 | -------------------------------------------------------------------------------- /exercises/06_detecting_mouse_clicks/exercise.js: -------------------------------------------------------------------------------- 1 | var exercise = require('workshopper-exercise')() 2 | 3 | module.exports = exercise 4 | -------------------------------------------------------------------------------- /exercises/07_threejs_and_physics/exercise.js: -------------------------------------------------------------------------------- 1 | var exercise = require('workshopper-exercise')() 2 | 3 | module.exports = exercise 4 | -------------------------------------------------------------------------------- /assets/Goose model/GooseModelCredits.txt: -------------------------------------------------------------------------------- 1 | Goose model used with permission for CampJS use from user jc1 (http://tf3dm.com/3d-model/domestic-goose-13.html) -------------------------------------------------------------------------------- /exercises/05_loading_3d_objects_and_textures/exercise.js: -------------------------------------------------------------------------------- 1 | var exercise = require('workshopper-exercise')() 2 | 3 | module.exports = exercise 4 | -------------------------------------------------------------------------------- /credits.txt: -------------------------------------------------------------------------------- 1 | {yellow}{bold}learnyounode is brought to you by the following dedicated hackers:{/bold}{/yellow} 2 | 3 | {bold}Name GitHub Username{/bold} 4 | ----------------------------------- 5 | Alex Mackey @Alex Mackey 6 | 7 | -------------------------------------------------------------------------------- /exercises/menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | "01 Introduction", 3 | "02 Coordinates", 4 | "03 Manipulate objects", 5 | "04 Scene hierarchy", 6 | "05 Loading 3d objects and textures", 7 | "06 Detecting mouse clicks", 8 | "07 ThreeJs and physics" 9 | ] 10 | -------------------------------------------------------------------------------- /help.txt: -------------------------------------------------------------------------------- 1 | {yellow}{bold}Found a bug with {appname} or just want to contribute?{/bold}{/yellow} 2 | 3 | The official repository for {appname} is: 4 | https://github.com/alexmackey/IntroToWebGLWithThreeJS 5 | Feel free to file a bug report or (preferably) a pull request. -------------------------------------------------------------------------------- /assets/BoilerplateApp/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Intro to WebGL with three.js 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /assets/CubeColors/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Intro to WebGL with three.js 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /credits.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | , path = require('path') 3 | , colorsTmpl = require('colors-tmpl') 4 | 5 | 6 | function credits () { 7 | fs.readFile(path.join(__dirname, './credits.txt'), 'utf8', function (err, data) { 8 | if (err) 9 | throw err 10 | 11 | console.log(colorsTmpl(data)) 12 | }) 13 | } 14 | 15 | module.exports = credits 16 | -------------------------------------------------------------------------------- /assets/BoilerplatePhysics/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Intro to WebGL with three.js - physics 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An introduction to WebGL with three.js 2 | 3 | Workshopper tutorial created for Melbourne CampJS 2014 covering three.js basics including lighting, basic shapes, mouse projections, collision detection and physics. 4 | 5 | ## usage 6 | ``` 7 | npm install -g introtowebgl 8 | 9 | introtowebgl 10 | ``` 11 | ### Contributors 12 | 13 | Alex Mackey (@alexjmackey) 14 | 15 | ## License 16 | 17 | **An introduction to WebGL with three.js** is licenced under Creative Commons Attribution-NonCommercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/). 18 | 19 | Please note Goose Model remains copyright user jc1 on tf3dm.com (http://tf3dm.com/3d-model/domestic-goose-13.html) and used with permission for this workshop. 20 | -------------------------------------------------------------------------------- /introtowebgl.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const workshopper = require('workshopper') 4 | , path = require('path') 5 | , credits = require('./credits') 6 | , menu = require('./exercises/menu') 7 | 8 | , name = 'introtowebgl' 9 | , title = 'Intro to WebGL with three.js' 10 | , subtitle = '\x1b[23mSelect an exercise and hit \x1b[3mEnter\x1b[23m to begin' 11 | 12 | 13 | function fpath (f) { 14 | return path.join(__dirname, f) 15 | } 16 | 17 | workshopper({ 18 | name : name 19 | , title : title 20 | , subtitle : subtitle 21 | , exerciseDir : fpath('./exercises/') 22 | , appDir : __dirname 23 | , helpFile : fpath('help.txt') 24 | , menuItems : [ { 25 | name : 'credits' 26 | , handler : credits 27 | } ] 28 | }) 29 | 30 | //updated to fix windows line endings 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "introtowebgl", 3 | "version": "1.0.0", 4 | "description": "Introduction to WebGL with three.js", 5 | "author": "Alex Mackey (https://github.com/alexmackey)", 6 | "contributors": [ 7 | "Alex Mackey (https://github.com/alexmackey)" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/alexmackey/IntroToWebGLWithThreeJS.git" 12 | }, 13 | "license": "CC-BY-NC-3.0", 14 | "dependencies": { 15 | "workshopper": "^1.0.0-alpha05", 16 | "workshopper-exercise": "^0.2.2", 17 | "workshopper-wrappedexec": "^0.1.1", 18 | "workshopper-boilerplate": "0.0.1", 19 | "concat-stream": "^1.4.1", 20 | "duplexer": "^0.1.1", 21 | "through": "^2.3.4", 22 | "boganipsum": "^0.1.0", 23 | "hyperquest": "^0.2.0", 24 | "bl": "^0.7.0", 25 | "through2-map": "^1.2.1", 26 | "colors-tmpl": "^0.1.0", 27 | "after": "^0.8.1", 28 | "rimraf": "^2.2.6", 29 | "chalk": "^0.4.0", 30 | "through2": "^0.4.1" 31 | }, 32 | "bin": "./introtowebgl.js", 33 | "preferGlobal": true 34 | } 35 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ ] 3 | , "bitwise": false 4 | , "camelcase": false 5 | , "curly": false 6 | , "eqeqeq": false 7 | , "forin": false 8 | , "immed": false 9 | , "latedef": false 10 | , "noarg": true 11 | , "noempty": true 12 | , "nonew": true 13 | , "plusplus": false 14 | , "quotmark": true 15 | , "regexp": false 16 | , "undef": true 17 | , "unused": true 18 | , "strict": false 19 | , "trailing": true 20 | , "maxlen": 120 21 | , "asi": true 22 | , "boss": true 23 | , "debug": true 24 | , "eqnull": true 25 | , "esnext": true 26 | , "evil": true 27 | , "expr": true 28 | , "funcscope": false 29 | , "globalstrict": false 30 | , "iterator": false 31 | , "lastsemic": true 32 | , "laxbreak": true 33 | , "laxcomma": true 34 | , "loopfunc": true 35 | , "multistr": false 36 | , "onecase": false 37 | , "proto": false 38 | , "regexdash": false 39 | , "scripturl": true 40 | , "smarttabs": false 41 | , "shadow": false 42 | , "sub": true 43 | , "supernew": false 44 | , "validthis": true 45 | , "browser": true 46 | , "couch": false 47 | , "devel": false 48 | , "dojo": false 49 | , "mootools": false 50 | , "node": true 51 | , "nonstandard": true 52 | , "prototypejs": false 53 | , "rhino": false 54 | , "worker": true 55 | , "wsh": false 56 | , "nomen": false 57 | , "onevar": true 58 | , "passfail": false 59 | } -------------------------------------------------------------------------------- /assets/BoilerplateApp/scripts/app.js: -------------------------------------------------------------------------------- 1 | var demo = (function(){ 2 | 3 | "use strict"; 4 | 5 | var scene=new THREE.Scene(), 6 | light= new THREE.AmbientLight(0xffffff), 7 | renderer, 8 | camera, 9 | renderer = new THREE.WebGLRenderer(), 10 | box, 11 | ground, 12 | controls=null; 13 | 14 | function initScene(){ 15 | 16 | renderer.setSize( window.innerWidth, window.innerHeight ); 17 | document.getElementById("webgl-container").appendChild(renderer.domElement); 18 | 19 | scene.add(light); 20 | 21 | camera = new THREE.PerspectiveCamera( 22 | 35, 23 | window.innerWidth / window.innerHeight, 24 | 1, 25 | 1000 26 | ); 27 | 28 | camera.position.set( 0, 0, 100 ); 29 | 30 | scene.add(camera); 31 | 32 | box = new THREE.Mesh( 33 | new THREE.CubeGeometry( 34 | 20, 35 | 20, 36 | 20), 37 | new THREE.MeshBasicMaterial({color: 0xFF0000})); 38 | 39 | scene.add(box); 40 | 41 | requestAnimationFrame(render); 42 | 43 | }; 44 | 45 | function render() { 46 | renderer.render(scene, camera); 47 | requestAnimationFrame(render); 48 | }; 49 | 50 | window.onload = initScene; 51 | 52 | })(); 53 | -------------------------------------------------------------------------------- /exercises/01_introduction/problem.md: -------------------------------------------------------------------------------- 1 | 01 Introduction to WebGL with three.js 2 | ---------------------------------------------------------------------- 3 | 4 | Welcome to the world of WebGL & Three.js! 5 | 6 | WebGL is now supported in all major browsers and mobile support is increasing thus making it an excellent choice for developing games, visualizations & cool effects. 7 | 8 | Before we can start creating the next TitanFall there are a few basics we will need to understand. 9 | 10 | These exercises will introduce you to many of the basics you need to put together something more complex (and interesting!). 11 | 12 | By the end of this workshop you will understand the following: 13 | 14 | * Three.js coordinate system 15 | * Primitives such as lighting & materials 16 | * How to move the camera and objects 17 | * How to resize and rotate objects 18 | * How to load a textured 3d model 19 | * How to detect mouse clicks on an object 20 | 21 | ---------------------------------------------------------------------- 22 | Sample code 23 | ---------------------------------------------------------------------- 24 | In the assets folder you will find 3 other folders: 25 | 26 | * Boilerplate App - All three.js scenes require a bit of setup - a scene, camera, lighting & rendering loop. This app sets up a simple scene and is a good template to use for the exercises 27 | * Boilerplate physics - this setups a simple scene & the physics engine physijs (exercise 7) 28 | * Goose model - a textured 3d model Goose (exercise 5) 29 | * Game example - simple balancing game using physisjs physics engine 30 | 31 | ---------------------------------------------------------------------- 32 | No verification 33 | ---------------------------------------------------------------------- 34 | Unlike some other workshopper courses such as learnyounode there is no verification step with three.js exercises - if it doesnt appear its probably not working! -------------------------------------------------------------------------------- /exercises/04_scene_hierarchy/problem.md: -------------------------------------------------------------------------------- 1 | 04 Scene hierarchy 2 | ---------------------------------------------------------------------- 3 | Three.js scenes have a hierarchy. We have already seen how we can add a camera to a scene with the scene.add method. 4 | 5 | We can say the camera is a child of the scene and scene is a parent of the camera. Three.js provides properties & collections for transversing this hierarchy. 6 | 7 | Let’s add another box and make it a child of the parent box: 8 | 9 | ```js 10 | var childBox = new THREE.Mesh( 11 | new THREE.CubeGeometry(20, 20, 20), 12 | new THREE.MeshBasicMaterial({color: 0x00FF00})); 13 | ``` 14 | 15 | Now lets make it a child of the parent box: 16 | ```js 17 | box.add(childBox) 18 | ``` 19 | 20 | We can examine the child objects in three.js using the children property which returns an array of child objects e.g. 21 | ```js 22 | box.children 23 | ``` 24 | 25 | Or we could refer directly to an individual object: 26 | ```js 27 | box.children[0] 28 | ``` 29 | 30 | When an object is added to a three.js scene it is allocated an ID – this isn’t the most friendly thing to use so you can & should set a name: 31 | ```js 32 | childBox.name=”test”; 33 | ``` 34 | 35 | If we have set a name on an object we can use the getObjectByName method to retrieve it: 36 | ```js 37 | box.getObjectByName(‘test’) 38 | ``` 39 | 40 | When parameters are modified on the parent object they are also applied to the child object e.g. moving a parent object will also move a parents child objects. 41 | 42 | ---------------------------------------------------------------------- 43 | Exercise 44 | ---------------------------------------------------------------------- 45 | * Add another cube (suggest using a different colour to distinguish it) as a child to the existing cube and position it to the right of the parent cube 46 | * Animate the parent cubes position and move it left & right across the screen -------------------------------------------------------------------------------- /exercises/03_manipulate_objects/problem.md: -------------------------------------------------------------------------------- 1 | 03 Manipulate objects (Scale & Rotation) 2 | ---------------------------------------------------------------------- 3 | Sometimes you will want to change the size of objects in your scene. We can modify the scale of an object using the scale.set method that changes the size of the object relative to its original size. 4 | 5 | For example to make an object twice as big as its initial size: 6 | 7 | ```js 8 | box.scale.set(2, 2, 2) //twice as big 9 | ``` 10 | 11 | Or we could manipulate it on just one dimension/axis: 12 | ```js 13 | box.scale.x=2; //x twice as big 14 | ``` 15 | 16 | We can rotate an object using the rotation property or methods. 17 | Note you may be expecting the rotate methods to operate in degrees but Three.js requires you specify these values in radians. 18 | 19 | There is a simple formula to convert from degrees to Radians: 20 | 21 | Radians = Degrees x (PI /180) 22 | 23 | So to rotate an object 90 degrees we would do the following: 24 | 25 | 90 X (3.14/180) = 1.57 radians 26 | 27 | Let’s rotate our box 45 degrees: 28 | ```js 29 | var degrees=45; 30 | box.rotation.y = degrees * (Math.PI / 180); 31 | ``` 32 | 33 | or we could also do: 34 | ```js 35 | box.rotateY(degrees * (Math.PI / 180)) 36 | ``` 37 | 38 | Note that rotations are performed relative to the objects internal coordinate system not relative to how it appears to you. So if you did the following twice you are essentially rotating the object 90 degrees from its start position: 39 | 40 | ```js 41 | var degrees=45; 42 | box.rotation.y = degrees * (Math.PI / 180); 43 | box.rotation.y = degrees * (Math.PI / 180); 44 | ``` 45 | 46 | We can also rotate the camera if we wanted: 47 | ```js 48 | camera.rotateY(45 * (Math.PI / 180)) 49 | ``` 50 | 51 | Its probably not too important right now but note that rotations are performed in Euler order x, y, z not necessarily the order you call them in. 52 | 53 | ---------------------------------------------------------------------- 54 | Exercise 55 | ---------------------------------------------------------------------- 56 | * Rotate the cube 45 degrees 57 | * Create an animation that rotates and resizes the cube -------------------------------------------------------------------------------- /exercises/05_loading_3d_objects_and_textures/problem.md: -------------------------------------------------------------------------------- 1 | 05 Loading 3d objects and textures 2 | ---------------------------------------------------------------------- 3 | 4 | It would take a long time to create an entire three.js scene using just the primitive shapes so more complex models will be designed using a 3d modelling package. 5 | 6 | Let's learn how to load & apply texture to a model. 7 | 8 | Three.js has loaders that can load many different types of model but the preferred format is three.js's json format (exporter extensions exist for converting other types to this type). 9 | 10 | In the folder Assets/GooseModel you will find 2 files gooseFull.js (a JSON file defining the goose) & goose.jpg (the texture of the model). Copy these files into your exercise. 11 | 12 | Now add the following code within app.js to load the model - note the use of a callback function to load the texture. 13 | 14 | ```js 15 | var loader = new THREE.JSONLoader(), 16 | mesh; 17 | 18 | loader.load('gooseFull.js', function (geometry) { 19 | var gooseMaterial = new THREE.MeshLambertMaterial({ 20 | map: THREE.ImageUtils.loadTexture('goose.jpg') 21 | }); 22 | 23 | mesh = new THREE.Mesh(geometry, gooseMaterial); 24 | mesh.scale.set(1000, 1000, 1000); 25 | 26 | scene.add(mesh); 27 | }); 28 | ``` 29 | 30 | You should find a nice Goose textured Goose model has been loaded into the scene! 31 | 32 | Note you may also want to declare the mesh and materials variables at the top level var in app.js to allow you to modify it from any function whilst you are experimenting. 33 | 34 | This model is actually quite big so if you were doing this on a public website you would probably simplify the model – one way to do this is with the free open source 3d model package, Blender. 35 | 36 | ---------------------------------------------------------------------- 37 | Exercise 38 | ---------------------------------------------------------------------- 39 | * Create an animation to rotate the goose model 40 | * Create a simple scene for the goose with ground, a simple tree and a sun in the sky 41 | * Find and load another 3d model - note look for three.js format or you will need to use one of the other loaders -------------------------------------------------------------------------------- /assets/CubeColors/scripts/app.js: -------------------------------------------------------------------------------- 1 | var demo = (function(){ 2 | 3 | "use strict"; 4 | 5 | var scene=new THREE.Scene(), 6 | light= new THREE.DirectionalLight (0xffffff, 1), 7 | light2= new THREE.DirectionalLight (0xffffff, 0.5), 8 | 9 | renderer, 10 | camera, 11 | renderer = new THREE.WebGLRenderer(), 12 | box, 13 | ground, 14 | controls=null; 15 | 16 | light.position.set( -10, 20, 0 ); 17 | light2.position.set( 10, 20, 0 ); 18 | 19 | 20 | var initScene = function(){ 21 | 22 | renderer.setSize( window.innerWidth, window.innerHeight ); 23 | document.getElementById("webgl-container").appendChild(renderer.domElement); 24 | 25 | scene.add(light); 26 | scene.add(light2); 27 | 28 | camera = new THREE.PerspectiveCamera( 29 | 35, 30 | window.innerWidth / window.innerHeight, 31 | 1, 32 | 1000 33 | ); 34 | 35 | camera.position.set( 0, 20, 100 ); 36 | camera.lookAt(new THREE.Vector3(0,1,0)); 37 | 38 | scene.add( camera ); 39 | 40 | box = new THREE.Mesh( 41 | new THREE.CubeGeometry( 42 | 20, 43 | 20, 44 | 20), 45 | new THREE.MeshBasicMaterial({ 46 | color: 0xffffff, 47 | //shading: THREE.FlatShading, 48 | vertexColors: THREE.VertexColors 49 | })); 50 | 51 | for (var i = 0; i <12; i+=2) { 52 | var r= Math.random(); 53 | var g= Math.random(); 54 | var b= Math.random(); 55 | 56 | box.geometry.faces[i].color.setRGB(r,g,b); 57 | box.geometry.faces[i+1].color.setRGB(r,g,b); 58 | } 59 | 60 | scene.add(box); 61 | 62 | requestAnimationFrame(render); 63 | 64 | }; 65 | 66 | function render() { 67 | box.rotation.y +=0.1; 68 | renderer.render(scene, camera); 69 | requestAnimationFrame(render); 70 | }; 71 | 72 | window.onload = initScene; 73 | 74 | })(); 75 | -------------------------------------------------------------------------------- /exercises/06_detecting_mouse_clicks/problem.md: -------------------------------------------------------------------------------- 1 | 06 Detecting mouse clicks 2 | ---------------------------------------------------------------------- 3 | Sooner or later you will want to enable a user to interact with your scene. 4 | 5 | To do this we will use a technique called ray casting to draw an imaginary line from where the mouse is clicked to the point in our scene and detect any objects that touch this line. 6 | 7 | To make this work we will need to project from mouse coordinates to scene coordinates – don’t worry too much about the Maths behind this – you probably won’t ever need to modify it! 8 | 9 | The first thing we will need to do is listen for a mouse event and stop the default event: 10 | 11 | ```js 12 | document.addEventListener('mousedown', onDocumentMouseDown, false); 13 | ``` 14 | 15 | Create a function to handle the click & make sure it doesn’t fire any other DOM events: 16 | 17 | ```js 18 | function onDocumentMouseDown(event) { 19 | event.preventDefault(); 20 | } 21 | ``` 22 | 23 | We will then create a projector which we will use to do raycasting: 24 | ```js 25 | var projector = new THREE.Projector(); 26 | ``` 27 | 28 | Now we can project from the mouse clicked coordinates to the scene with the following formula: 29 | 30 | ```js 31 | var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5); 32 | ``` 33 | 34 | Finally we need to unproject & normalize the vector 35 | 36 | ```js 37 | projector.unprojectVector(vector, camera); 38 | var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize()); 39 | ``` 40 | 41 | This will give us a collection of intersections that we can iterate through to look at any objects we are interested in: 42 | 43 | ```js 44 | var intersects = raycaster.intersectObjects(scene.children); 45 | ``` 46 | 47 | And to illustrate the click has occurred we will change the colour of the object: 48 | 49 | ```js 50 | if (intersects.length > 0) { 51 | intersects[0].object.material.color.setHex(Math.random() * 0xffffff); 52 | } 53 | ``` 54 | 55 | ---------------------------------------------------------------------- 56 | Exercise 57 | ---------------------------------------------------------------------- 58 | * Create a simple game. Position a number of objects randomly on the screen. When the user clicks on any of them they are awarded 10 points. When a user clicks on an object animate it shrinking and then remove it from the scene. -------------------------------------------------------------------------------- /exercises/02_coordinates/problem.md: -------------------------------------------------------------------------------- 1 | 02 Coordinates 2 | ---------------------------------------------------------------------- 3 | 4 | We will need a way of defining the positions of our objects in 3D space. Three.js uses the Cartesian coordinate system. This system has been around since the 17th Century & was invented & named after René Descartes (the Latin version of Rene’s last name is “Cartesius” in case you wondering). You will no doubt come this system before if you have done well anything from plotting graphs to CSS positioning. 5 | 6 | Three.js uses 3 axis x, y and z. Imagine 2 lines/axis running across the screen and crossing in the centre. These are the x & y axis (x goes left/right and y up and down). Now imagine a 3rd line projecting towards and away from you through where the other axis meet. 7 | 8 | The centre point is sometimes called the origin and we can identify it by 3 values, x, y and z which are normally specified in this order so (0,0,0) refers to the dead centre of the screen. (50, 0, 0) would refer to a point right of the centre and . (-50, 0, 0) to the left of the centre. 9 | 10 | When objects are added to a scene in three.js if you don’t specify a position the object is added at 0,0,0. 11 | 12 | 13 | Open up index.htm in folder assets/BoilerplateApp/. 14 | 15 | In the scripts folder is a file called app.js that sets up a simple scene with a cube in the centre. There are many different types of primitives in three.js such as spheres, triangles, planes (a flat shape used for ground etc) but we will stick with a cube for these exercises as we are square like that. 16 | 17 | We have not defined a position for the cube so it will be positioned at 0,0,0. 18 | Note the camera’s z coordinate has been set to 100 as otherwise it would be inside the cube and wouldn’t see anything – remember this when you are scratching your head why you don’t see anything as its very easy to do. 19 | 20 | Like many frameworks there are many ways to accomplish the same thing in three.js that may be more appropriate at different times. 21 | 22 | Three.js allows you to define the position of an object in 3 main ways: 23 | 24 | * By setting an individual position e.g. camera.position.z=100; 25 | * By using the position.set method e.g. camera.position.set(0,0, 200); 26 | * By setting a vector e.g. camera.position = new THREE.Vector3(0,0, 100); 27 | 28 | You can use all these methods to move an object or camera around a scene. 29 | Camera also has another method lookAt that allows us to point it at a specific direction e.g. 30 | 31 | ```js 32 | camera.lookAt(new THREE.Vector3(-50,0, -200)); 33 | ``` 34 | ---------------------------------------------------------------------- 35 | Exercise 36 | ---------------------------------------------------------------------- 37 | 1. Move the camera closer to the cube 38 | 2. Move the camera to the left of the cube 39 | 3. Move the cube to the right and further away 40 | 4. Make the camera look down on the cube 41 | 5. Animate the camera so it gradually moves closer to the cube 42 | 43 | -------------------------------------------------------------------------------- /assets/BoilerplatePhysics/scripts/app.js: -------------------------------------------------------------------------------- 1 | var demo = (function(){ 2 | 3 | "use strict"; 4 | 5 | Physijs.scripts.worker = 'scripts/physijs_worker.js'; 6 | Physijs.scripts.ammo = 'ammo.js'; 7 | 8 | var scene=new Physijs.Scene(), 9 | light= new THREE.AmbientLight(0xffffff), 10 | renderer, 11 | camera, 12 | renderer = new THREE.WebGLRenderer(), 13 | box, 14 | ground; 15 | 16 | var initScene = function(){ 17 | 18 | scene.setGravity(new THREE.Vector3( 0, -50, 0 )); 19 | 20 | renderer.setSize( window.innerWidth, window.innerHeight ); 21 | 22 | document.getElementById("webgl-container").appendChild(renderer.domElement); 23 | 24 | scene.add(light); 25 | 26 | camera = new THREE.PerspectiveCamera( 27 | 35, 28 | window.innerWidth / window.innerHeight, 29 | 1, 30 | 1000 31 | ); 32 | 33 | camera.position.set( 60, 50, 60 ); 34 | camera.lookAt( scene.position ); 35 | scene.add( camera ); 36 | 37 | var boxMaterial= Physijs.createMaterial( 38 | new THREE.MeshBasicMaterial({ color: 0xFF0000 }), 39 | 0, //friction 40 | 0.8 //restitution/bounciness 41 | ); 42 | 43 | box= new Physijs.BoxMesh( 44 | new THREE.CubeGeometry( 15, 15, 15 ), 45 | boxMaterial 46 | ); 47 | 48 | box.position.y=40; 49 | box.rotation.z=90; 50 | box.rotation.y=50; 51 | 52 | box.addEventListener( 'collision', function( 53 | otherObject, 54 | relative_velocity, 55 | relative_rotation, 56 | contact_normal ) { 57 | 58 | if(otherObject.name=="ground"){ 59 | alert('hit ground') 60 | } 61 | 62 | }); 63 | 64 | scene.add(box); 65 | 66 | var groundMaterial = Physijs.createMaterial( 67 | new THREE.MeshBasicMaterial({ color: 0x008888 }), 68 | 0, //friction 69 | 0 //restitution/bounciness 70 | ); 71 | 72 | ground = new Physijs.BoxMesh( 73 | new THREE.CubeGeometry(150, 5, 150), 74 | groundMaterial, 75 | 0 76 | ); 77 | 78 | ground.name='ground'; 79 | ground.position.y = -25; 80 | scene.add( ground ); 81 | 82 | requestAnimationFrame(render); 83 | }; 84 | 85 | function render() { 86 | scene.simulate(); 87 | renderer.render( scene, camera); 88 | requestAnimationFrame(render); 89 | }; 90 | 91 | window.onload = initScene; 92 | 93 | })(); 94 | -------------------------------------------------------------------------------- /assets/CubeColors/content/appComplete.js: -------------------------------------------------------------------------------- 1 | var demo = (function(){ 2 | 3 | "use strict"; 4 | 5 | Physijs.scripts.worker = 'scripts/physijs_worker.js'; 6 | Physijs.scripts.ammo = 'ammo.js'; 7 | 8 | 9 | var scene=new Physijs.Scene(), 10 | light= new THREE.AmbientLight(0xffffff), 11 | renderer, 12 | camera, 13 | containerWidth = 800, 14 | containerHeight = 600, 15 | renderer = new THREE.WebGLRenderer(), 16 | box, 17 | ground, 18 | controls=null; 19 | 20 | var initScene = function(){ 21 | 22 | 23 | renderer.setSize( window.innerWidth, window.innerHeight ); 24 | document.getElementById("webgl-container").appendChild(renderer.domElement); 25 | 26 | scene.add(light); 27 | 28 | scene.setGravity(new THREE.Vector3( 0, -50, 0 )); 29 | 30 | camera = new THREE.PerspectiveCamera( 31 | 35, 32 | window.innerWidth / window.innerHeight, 33 | 1, 34 | 1000 35 | ); 36 | camera.position.set( 60, 50, 60 ); 37 | camera.lookAt( scene.position ); 38 | scene.add( camera ); 39 | 40 | // var material = new THREE.MeshBasicMaterial( { color: 0xFF0000 } ); 41 | 42 | var material = Physijs.createMaterial( 43 | new THREE.MeshBasicMaterial({ color: 0xFF0000 }), 44 | 0, //friction 45 | 0.8 //restitution/bounciness 46 | ); 47 | 48 | box = new Physijs.BoxMesh( 49 | new THREE.CubeGeometry( 15, 15, 15 ), 50 | material 51 | ); 52 | 53 | box.position.y=40; 54 | box.rotation.z=90; 55 | box.rotation.y=50; 56 | 57 | 58 | 59 | scene.add(box); 60 | 61 | var groundMaterial = Physijs.createMaterial( 62 | new THREE.MeshBasicMaterial({ color: 0x008888 }), 63 | 0, //friction 64 | 0.5 //restitution/bounciness 65 | ); 66 | 67 | ground = new Physijs.BoxMesh( 68 | new THREE.CubeGeometry(150, 5, 150), 69 | groundMaterial, 70 | 0, 71 | { restitution: 0.3, friction: 0.5 } 72 | ); 73 | 74 | ground.name='ground'; 75 | ground.position.y = -25; 76 | scene.add( ground ); 77 | 78 | /* 79 | box.addEventListener( 'collision', function( otherObject, relative_velocity, relative_rotation, contact_normal ) { 80 | 81 | if(otherObject.name=="ground"){ 82 | alert('hit ground') 83 | } 84 | 85 | }); 86 | */ 87 | 88 | requestAnimationFrame( render ); 89 | }; 90 | 91 | var render = function() { 92 | scene.simulate(); // run physics 93 | renderer.render( scene, camera); // render the scene 94 | requestAnimationFrame( render ); 95 | }; 96 | 97 | window.onload = initScene; 98 | 99 | })(); 100 | -------------------------------------------------------------------------------- /assets/BoilerplateApp/content/appComplete.js: -------------------------------------------------------------------------------- 1 | var demo = (function(){ 2 | 3 | "use strict"; 4 | 5 | Physijs.scripts.worker = 'scripts/physijs_worker.js'; 6 | Physijs.scripts.ammo = 'ammo.js'; 7 | 8 | 9 | var scene=new Physijs.Scene(), 10 | light= new THREE.AmbientLight(0xffffff), 11 | renderer, 12 | camera, 13 | containerWidth = 800, 14 | containerHeight = 600, 15 | renderer = new THREE.WebGLRenderer(), 16 | box, 17 | ground, 18 | controls=null; 19 | 20 | var initScene = function(){ 21 | 22 | 23 | renderer.setSize( window.innerWidth, window.innerHeight ); 24 | document.getElementById("webgl-container").appendChild(renderer.domElement); 25 | 26 | scene.add(light); 27 | 28 | scene.setGravity(new THREE.Vector3( 0, -50, 0 )); 29 | 30 | camera = new THREE.PerspectiveCamera( 31 | 35, 32 | window.innerWidth / window.innerHeight, 33 | 1, 34 | 1000 35 | ); 36 | camera.position.set( 60, 50, 60 ); 37 | camera.lookAt( scene.position ); 38 | scene.add( camera ); 39 | 40 | // var material = new THREE.MeshBasicMaterial( { color: 0xFF0000 } ); 41 | 42 | var material = Physijs.createMaterial( 43 | new THREE.MeshBasicMaterial({ color: 0xFF0000 }), 44 | 0, //friction 45 | 0.8 //restitution/bounciness 46 | ); 47 | 48 | box = new Physijs.BoxMesh( 49 | new THREE.CubeGeometry( 15, 15, 15 ), 50 | material 51 | ); 52 | 53 | box.position.y=40; 54 | box.rotation.z=90; 55 | box.rotation.y=50; 56 | 57 | 58 | 59 | scene.add(box); 60 | 61 | var groundMaterial = Physijs.createMaterial( 62 | new THREE.MeshBasicMaterial({ color: 0x008888 }), 63 | 0, //friction 64 | 0.5 //restitution/bounciness 65 | ); 66 | 67 | ground = new Physijs.BoxMesh( 68 | new THREE.CubeGeometry(150, 5, 150), 69 | groundMaterial, 70 | 0, 71 | { restitution: 0.3, friction: 0.5 } 72 | ); 73 | 74 | ground.name='ground'; 75 | ground.position.y = -25; 76 | scene.add( ground ); 77 | 78 | /* 79 | box.addEventListener( 'collision', function( otherObject, relative_velocity, relative_rotation, contact_normal ) { 80 | 81 | if(otherObject.name=="ground"){ 82 | alert('hit ground') 83 | } 84 | 85 | }); 86 | */ 87 | 88 | requestAnimationFrame( render ); 89 | }; 90 | 91 | var render = function() { 92 | scene.simulate(); // run physics 93 | renderer.render( scene, camera); // render the scene 94 | requestAnimationFrame( render ); 95 | }; 96 | 97 | window.onload = initScene; 98 | 99 | })(); 100 | -------------------------------------------------------------------------------- /assets/BoilerplatePhysics/content/appComplete.js: -------------------------------------------------------------------------------- 1 | var demo = (function(){ 2 | 3 | "use strict"; 4 | 5 | Physijs.scripts.worker = 'scripts/physijs_worker.js'; 6 | Physijs.scripts.ammo = 'ammo.js'; 7 | 8 | 9 | var scene=new Physijs.Scene(), 10 | light= new THREE.AmbientLight(0xffffff), 11 | renderer, 12 | camera, 13 | containerWidth = 800, 14 | containerHeight = 600, 15 | renderer = new THREE.WebGLRenderer(), 16 | box, 17 | ground, 18 | controls=null; 19 | 20 | var initScene = function(){ 21 | 22 | 23 | renderer.setSize( window.innerWidth, window.innerHeight ); 24 | document.getElementById("webgl-container").appendChild(renderer.domElement); 25 | 26 | scene.add(light); 27 | 28 | scene.setGravity(new THREE.Vector3( 0, -50, 0 )); 29 | 30 | camera = new THREE.PerspectiveCamera( 31 | 35, 32 | window.innerWidth / window.innerHeight, 33 | 1, 34 | 1000 35 | ); 36 | camera.position.set( 60, 50, 60 ); 37 | camera.lookAt( scene.position ); 38 | scene.add( camera ); 39 | 40 | // var material = new THREE.MeshBasicMaterial( { color: 0xFF0000 } ); 41 | 42 | var material = Physijs.createMaterial( 43 | new THREE.MeshBasicMaterial({ color: 0xFF0000 }), 44 | 0, //friction 45 | 0.8 //restitution/bounciness 46 | ); 47 | 48 | box = new Physijs.BoxMesh( 49 | new THREE.CubeGeometry( 15, 15, 15 ), 50 | material 51 | ); 52 | 53 | box.position.y=40; 54 | box.rotation.z=90; 55 | box.rotation.y=50; 56 | 57 | 58 | 59 | scene.add(box); 60 | 61 | var groundMaterial = Physijs.createMaterial( 62 | new THREE.MeshBasicMaterial({ color: 0x008888 }), 63 | 0, //friction 64 | 0.5 //restitution/bounciness 65 | ); 66 | 67 | ground = new Physijs.BoxMesh( 68 | new THREE.CubeGeometry(150, 5, 150), 69 | groundMaterial, 70 | 0, 71 | { restitution: 0.3, friction: 0.5 } 72 | ); 73 | 74 | ground.name='ground'; 75 | ground.position.y = -25; 76 | scene.add( ground ); 77 | 78 | /* 79 | box.addEventListener( 'collision', function( otherObject, relative_velocity, relative_rotation, contact_normal ) { 80 | 81 | if(otherObject.name=="ground"){ 82 | alert('hit ground') 83 | } 84 | 85 | }); 86 | */ 87 | 88 | requestAnimationFrame( render ); 89 | }; 90 | 91 | var render = function() { 92 | scene.simulate(); // run physics 93 | renderer.render( scene, camera); // render the scene 94 | requestAnimationFrame( render ); 95 | }; 96 | 97 | window.onload = initScene; 98 | 99 | })(); 100 | -------------------------------------------------------------------------------- /exercises/07_threejs_and_physics/problem.md: -------------------------------------------------------------------------------- 1 | 07 ThreeJs and physics 2 | ---------------------------------------------------------------------- 3 | As we have seen three.js is an excellent framework for developing WebGL applications. It is important to note however that Three.js’s primary focus is displaying content on the screen. 4 | Many applications such as games will require functionality such as the ability to detect when one object touches another. 5 | 6 | Three.js does contain a basic collision detection system which uses a technique called ray casting but for some more advanced cases this may not be sufficient for your purposes. 7 | 8 | For example let’s say you are creating a 3D pool game. It’s probably not too tricky to create the balls and table but getting the balls to behave realistically when hit could be very tricky indeed! 9 | 10 | A ball will behave different depending on the speed and angle its hit & will also need to interact with other balls and the boundary of the table. Or say you are creating a car based game – players are now used to vehicles behaving realistically. 11 | 12 | Whilst you could try and code how these objects should behave it would take a very long time to get satisfactory results. 13 | 14 | Well probably the best solution to this issue is to incorporate a physics engine into your application. Physics engines simulate concepts such as gravity, velocity and how objects will behave when they interact with other objects in an environment. 15 | 16 | A great option when using three.js is is to use the Physijs & ammo.js libraries. PhysiJs is basically a three.js focussed wrapper for another library called ammo.js. Physijs performs all the calculations in a webworker to avoid interfering with the UI as much as possible. 17 | 18 | Physijs is actually using another library called Ammo.js which itself is actually a JS translation of a C++ library called bullet. Bullet is used extensively in many games. 19 | 20 | Anyway don’t worry too much about this but do be aware there is a heap of additional functionality in ammo.js that isn’t directly exposed by physijs. 21 | 22 | In the assets folder you will find a folder called BoilerPlatePhysics that contains a template project using the PhysiJS engine a cube will descend gradually and an alert box will display when it hits the ground. 23 | 24 | ---------------------------------------------------------------------- 25 | Gravity 26 | ---------------------------------------------------------------------- 27 | You can change the scenes gravity by using the setGravity method and specifying a vector e.g. to invert gravity change the y parameter to positive 50 like so: 28 | 29 | ```js 30 | scene.setGravity(new THREE.Vector3( 0, -50, 0 )); 31 | ``` 32 | 33 | Another aspect Physijs allows you to define is a materials properties. 34 | 35 | You can define 3 properties friction (which will slow down an object), restitution (bounciness) and weight. Friction and Bounciness are given a value between 0 and 1. 36 | 37 | ``` 38 | var boxMaterial= Physijs.createMaterial( 39 | new THREE.MeshBasicMaterial({ color: 0xFF0000 }), 40 | 0, //friction 41 | 0.8 //restitution/bounciness 42 | ); 43 | ``` 44 | 45 | Physijs also allows modification of a Mesh’s mass as an optional third parameter. 46 | 47 | ---------------------------------------------------------------------- 48 | Exercise 49 | ---------------------------------------------------------------------- 50 | * Modify a scenes gravity so objects will drift to the right 51 | * Experiment with the friction, restitution and bounciness properties and a mesh’s mass property 52 | * Drop a number of cubes from a height and if they collide change the color of the cubes using the setHex methods e.g. object.material.color.setHex(0xff0000); 53 | * For a longer challenge create a pinball or Pong game! 54 | -------------------------------------------------------------------------------- /assets/BoilerplatePhysics/scripts/physijs_worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var 3 | transferableMessage = self.webkitPostMessage || self.postMessage, 4 | 5 | // enum 6 | MESSAGE_TYPES = { 7 | WORLDREPORT: 0, 8 | COLLISIONREPORT: 1, 9 | VEHICLEREPORT: 2, 10 | CONSTRAINTREPORT: 3 11 | }, 12 | 13 | // temp variables 14 | _object, 15 | _vector, 16 | _transform, 17 | 18 | // functions 19 | public_functions = {}, 20 | getShapeFromCache, 21 | setShapeCache, 22 | createShape, 23 | reportWorld, 24 | reportVehicles, 25 | reportCollisions, 26 | reportConstraints, 27 | 28 | // world variables 29 | fixedTimeStep, // used when calling stepSimulation 30 | rateLimit, // sets whether or not to sync the simulation rate with fixedTimeStep 31 | last_simulation_time, 32 | last_simulation_duration = 0, 33 | world, 34 | transform, 35 | _vec3_1, 36 | _vec3_2, 37 | _vec3_3, 38 | _quat, 39 | // private cache 40 | _objects = {}, 41 | _vehicles = {}, 42 | _constraints = {}, 43 | _materials = {}, 44 | _objects_ammo = {}, 45 | _num_objects = 0, 46 | _num_wheels = 0, 47 | _num_constraints = 0, 48 | _object_shapes = {}, 49 | 50 | // The following objects are to track objects that ammo.js doesn't clean 51 | // up. All are cleaned up when they're corresponding body is destroyed. 52 | // Unfortunately, it's very difficult to get at these objects from the 53 | // body, so we have to track them ourselves. 54 | _motion_states = {}, 55 | // Don't need to worry about it for cached shapes. 56 | _noncached_shapes = {}, 57 | // A body with a compound shape always has a regular shape as well, so we 58 | // have track them separately. 59 | _compound_shapes = {}, 60 | 61 | // object reporting 62 | REPORT_CHUNKSIZE, // report array is increased in increments of this chunk size 63 | 64 | WORLDREPORT_ITEMSIZE = 14, // how many float values each reported item needs 65 | worldreport, 66 | 67 | COLLISIONREPORT_ITEMSIZE = 5, // one float for each object id, and a Vec3 contact normal 68 | collisionreport, 69 | 70 | VEHICLEREPORT_ITEMSIZE = 9, // vehicle id, wheel index, 3 for position, 4 for rotation 71 | vehiclereport, 72 | 73 | CONSTRAINTREPORT_ITEMSIZE = 6, // constraint id, offset object, offset, applied impulse 74 | constraintreport; 75 | 76 | var ab = new ArrayBuffer( 1 ); 77 | 78 | transferableMessage( ab, [ab] ); 79 | var SUPPORT_TRANSFERABLE = ( ab.byteLength === 0 ); 80 | 81 | getShapeFromCache = function ( cache_key ) { 82 | if ( _object_shapes[ cache_key ] !== undefined ) { 83 | return _object_shapes[ cache_key ]; 84 | } 85 | return null; 86 | }; 87 | 88 | setShapeCache = function ( cache_key, shape ) { 89 | _object_shapes[ cache_key ] = shape; 90 | } 91 | 92 | createShape = function( description ) { 93 | var cache_key, shape; 94 | 95 | _transform.setIdentity(); 96 | switch ( description.type ) { 97 | case 'plane': 98 | cache_key = 'plane_' + description.normal.x + '_' + description.normal.y + '_' + description.normal.z; 99 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 100 | _vec3_1.setX(description.normal.x); 101 | _vec3_1.setY(description.normal.y); 102 | _vec3_1.setZ(description.normal.z); 103 | shape = new Ammo.btStaticPlaneShape(_vec3_1, 0 ); 104 | setShapeCache( cache_key, shape ); 105 | } 106 | break; 107 | 108 | case 'box': 109 | cache_key = 'box_' + description.width + '_' + description.height + '_' + description.depth; 110 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 111 | _vec3_1.setX(description.width / 2); 112 | _vec3_1.setY(description.height / 2); 113 | _vec3_1.setZ(description.depth / 2); 114 | shape = new Ammo.btBoxShape(_vec3_1); 115 | setShapeCache( cache_key, shape ); 116 | } 117 | break; 118 | 119 | case 'sphere': 120 | cache_key = 'sphere_' + description.radius; 121 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 122 | shape = new Ammo.btSphereShape( description.radius ); 123 | setShapeCache( cache_key, shape ); 124 | } 125 | break; 126 | 127 | case 'cylinder': 128 | cache_key = 'cylinder_' + description.width + '_' + description.height + '_' + description.depth; 129 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 130 | _vec3_1.setX(description.width / 2); 131 | _vec3_1.setY(description.height / 2); 132 | _vec3_1.setZ(description.depth / 2); 133 | shape = new Ammo.btCylinderShape(_vec3_1); 134 | setShapeCache( cache_key, shape ); 135 | } 136 | break; 137 | 138 | case 'capsule': 139 | cache_key = 'capsule_' + description.radius + '_' + description.height; 140 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 141 | // In Bullet, capsule height excludes the end spheres 142 | shape = new Ammo.btCapsuleShape( description.radius, description.height - 2 * description.radius ); 143 | setShapeCache( cache_key, shape ); 144 | } 145 | break; 146 | 147 | case 'cone': 148 | cache_key = 'cone_' + description.radius + '_' + description.height; 149 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 150 | shape = new Ammo.btConeShape( description.radius, description.height ); 151 | setShapeCache( cache_key, shape ); 152 | } 153 | break; 154 | 155 | case 'concave': 156 | var i, triangle, triangle_mesh = new Ammo.btTriangleMesh; 157 | if (!description.triangles.length) return false 158 | 159 | for ( i = 0; i < description.triangles.length; i++ ) { 160 | triangle = description.triangles[i]; 161 | 162 | _vec3_1.setX(triangle[0].x); 163 | _vec3_1.setY(triangle[0].y); 164 | _vec3_1.setZ(triangle[0].z); 165 | 166 | _vec3_2.setX(triangle[1].x); 167 | _vec3_2.setY(triangle[1].y); 168 | _vec3_2.setZ(triangle[1].z); 169 | 170 | _vec3_3.setX(triangle[2].x); 171 | _vec3_3.setY(triangle[2].y); 172 | _vec3_3.setZ(triangle[2].z); 173 | 174 | triangle_mesh.addTriangle( 175 | _vec3_1, 176 | _vec3_2, 177 | _vec3_3, 178 | true 179 | ); 180 | } 181 | 182 | shape = new Ammo.btBvhTriangleMeshShape( 183 | triangle_mesh, 184 | true, 185 | true 186 | ); 187 | _noncached_shapes[description.id] = shape; 188 | break; 189 | 190 | case 'convex': 191 | var i, point, shape = new Ammo.btConvexHullShape; 192 | for ( i = 0; i < description.points.length; i++ ) { 193 | point = description.points[i]; 194 | 195 | _vec3_1.setX(point.x); 196 | _vec3_1.setY(point.y); 197 | _vec3_1.setZ(point.z); 198 | 199 | shape.addPoint(_vec3_1); 200 | 201 | } 202 | _noncached_shapes[description.id] = shape; 203 | break; 204 | 205 | case 'heightfield': 206 | 207 | var ptr = Ammo.allocate(4 * description.xpts * description.ypts, "float", Ammo.ALLOC_NORMAL); 208 | 209 | for (var f = 0; f < description.points.length; f++) { 210 | Ammo.setValue(ptr + f, description.points[f] , 'float'); 211 | } 212 | 213 | shape = new Ammo.btHeightfieldTerrainShape( 214 | description.xpts, 215 | description.ypts, 216 | ptr, 217 | 1, 218 | -description.absMaxHeight, 219 | description.absMaxHeight, 220 | 2, 221 | 0, 222 | false 223 | ); 224 | 225 | _vec3_1.setX(description.xsize/(description.xpts - 1)); 226 | _vec3_1.setY(description.ysize/(description.ypts - 1)); 227 | _vec3_1.setZ(1); 228 | 229 | shape.setLocalScaling(_vec3_1); 230 | _noncached_shapes[description.id] = shape; 231 | break; 232 | 233 | default: 234 | // Not recognized 235 | return; 236 | break; 237 | } 238 | 239 | return shape; 240 | }; 241 | 242 | public_functions.init = function( params ) { 243 | importScripts( params.ammo ); 244 | 245 | _transform = new Ammo.btTransform; 246 | _vec3_1 = new Ammo.btVector3(0,0,0); 247 | _vec3_2 = new Ammo.btVector3(0,0,0); 248 | _vec3_3 = new Ammo.btVector3(0,0,0); 249 | _quat = new Ammo.btQuaternion(0,0,0,0); 250 | 251 | REPORT_CHUNKSIZE = params.reportsize || 50; 252 | if ( SUPPORT_TRANSFERABLE ) { 253 | // Transferable messages are supported, take advantage of them with TypedArrays 254 | worldreport = new Float32Array(2 + REPORT_CHUNKSIZE * WORLDREPORT_ITEMSIZE); // message id + # of objects to report + chunk size * # of values per object 255 | collisionreport = new Float32Array(2 + REPORT_CHUNKSIZE * COLLISIONREPORT_ITEMSIZE); // message id + # of collisions to report + chunk size * # of values per object 256 | vehiclereport = new Float32Array(2 + REPORT_CHUNKSIZE * VEHICLEREPORT_ITEMSIZE); // message id + # of vehicles to report + chunk size * # of values per object 257 | constraintreport = new Float32Array(2 + REPORT_CHUNKSIZE * CONSTRAINTREPORT_ITEMSIZE); // message id + # of constraints to report + chunk size * # of values per object 258 | } else { 259 | // Transferable messages are not supported, send data as normal arrays 260 | worldreport = []; 261 | collisionreport = []; 262 | vehiclereport = []; 263 | constraintreport = []; 264 | } 265 | worldreport[0] = MESSAGE_TYPES.WORLDREPORT; 266 | collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; 267 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 268 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 269 | 270 | var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration, 271 | dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration ), 272 | solver = new Ammo.btSequentialImpulseConstraintSolver, 273 | broadphase; 274 | 275 | if ( !params.broadphase ) params.broadphase = { type: 'dynamic' }; 276 | switch ( params.broadphase.type ) { 277 | case 'sweepprune': 278 | 279 | _vec3_1.setX(params.broadphase.aabbmin.x); 280 | _vec3_1.setY(params.broadphase.aabbmin.y); 281 | _vec3_1.setZ(params.broadphase.aabbmin.z); 282 | 283 | _vec3_2.setX(params.broadphase.aabbmax.x); 284 | _vec3_2.setY(params.broadphase.aabbmax.y); 285 | _vec3_2.setZ(params.broadphase.aabbmax.z); 286 | 287 | broadphase = new Ammo.btAxisSweep3( 288 | _vec3_1, 289 | _vec3_2 290 | ); 291 | 292 | break; 293 | 294 | case 'dynamic': 295 | default: 296 | broadphase = new Ammo.btDbvtBroadphase; 297 | break; 298 | } 299 | 300 | world = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); 301 | 302 | fixedTimeStep = params.fixedTimeStep; 303 | rateLimit = params.rateLimit; 304 | 305 | transferableMessage({ cmd: 'worldReady' }); 306 | }; 307 | 308 | public_functions.registerMaterial = function( description ) { 309 | _materials[ description.id ] = description; 310 | }; 311 | 312 | public_functions.unRegisterMaterial = function( description ) { 313 | delete _materials[ description.id ]; 314 | }; 315 | 316 | public_functions.setFixedTimeStep = function( description ) { 317 | fixedTimeStep = description; 318 | }; 319 | 320 | public_functions.setGravity = function( description ) { 321 | _vec3_1.setX(description.x); 322 | _vec3_1.setY(description.y); 323 | _vec3_1.setZ(description.z); 324 | world.setGravity(_vec3_1); 325 | }; 326 | 327 | public_functions.addObject = function( description ) { 328 | 329 | var i, 330 | localInertia, shape, motionState, rbInfo, body; 331 | 332 | shape = createShape( description ); 333 | if (!shape) return 334 | // If there are children then this is a compound shape 335 | if ( description.children ) { 336 | var compound_shape = new Ammo.btCompoundShape, _child; 337 | compound_shape.addChildShape( _transform, shape ); 338 | 339 | for ( i = 0; i < description.children.length; i++ ) { 340 | _child = description.children[i]; 341 | 342 | var trans = new Ammo.btTransform; 343 | trans.setIdentity(); 344 | 345 | _vec3_1.setX(_child.position_offset.x); 346 | _vec3_1.setY(_child.position_offset.y); 347 | _vec3_1.setZ(_child.position_offset.z); 348 | trans.setOrigin(_vec3_1); 349 | 350 | _quat.setX(_child.rotation.x); 351 | _quat.setY(_child.rotation.y); 352 | _quat.setZ(_child.rotation.z); 353 | _quat.setW(_child.rotation.w); 354 | trans.setRotation(_quat); 355 | 356 | shape = createShape( description.children[i] ); 357 | compound_shape.addChildShape( trans, shape ); 358 | Ammo.destroy(trans); 359 | } 360 | 361 | shape = compound_shape; 362 | _compound_shapes[ description.id ] = shape; 363 | } 364 | _vec3_1.setX(0); 365 | _vec3_1.setY(0); 366 | _vec3_1.setZ(0); 367 | shape.calculateLocalInertia( description.mass, _vec3_1 ); 368 | 369 | _transform.setIdentity(); 370 | 371 | _vec3_2.setX(description.position.x); 372 | _vec3_2.setY(description.position.y); 373 | _vec3_2.setZ(description.position.z); 374 | _transform.setOrigin(_vec3_2); 375 | 376 | _quat.setX(description.rotation.x); 377 | _quat.setY(description.rotation.y); 378 | _quat.setZ(description.rotation.z); 379 | _quat.setW(description.rotation.w); 380 | _transform.setRotation(_quat); 381 | 382 | motionState = new Ammo.btDefaultMotionState( _transform ); // #TODO: btDefaultMotionState supports center of mass offset as second argument - implement 383 | rbInfo = new Ammo.btRigidBodyConstructionInfo( description.mass, motionState, shape, _vec3_1 ); 384 | 385 | if ( description.materialId !== undefined ) { 386 | rbInfo.set_m_friction( _materials[ description.materialId ].friction ); 387 | rbInfo.set_m_restitution( _materials[ description.materialId ].restitution ); 388 | } 389 | 390 | body = new Ammo.btRigidBody( rbInfo ); 391 | Ammo.destroy(rbInfo); 392 | 393 | if ( typeof description.collision_flags !== 'undefined' ) { 394 | body.setCollisionFlags( description.collision_flags ); 395 | } 396 | 397 | world.addRigidBody( body ); 398 | 399 | body.id = description.id; 400 | _objects[ body.id ] = body; 401 | _motion_states[ body.id ] = motionState; 402 | 403 | var ptr = body.a != undefined ? body.a : body.ptr; 404 | _objects_ammo[ptr] = body.id; 405 | _num_objects++; 406 | 407 | transferableMessage({ cmd: 'objectReady', params: body.id }); 408 | }; 409 | 410 | public_functions.addVehicle = function( description ) { 411 | var vehicle_tuning = new Ammo.btVehicleTuning(), 412 | vehicle; 413 | 414 | vehicle_tuning.set_m_suspensionStiffness( description.suspension_stiffness ); 415 | vehicle_tuning.set_m_suspensionCompression( description.suspension_compression ); 416 | vehicle_tuning.set_m_suspensionDamping( description.suspension_damping ); 417 | vehicle_tuning.set_m_maxSuspensionTravelCm( description.max_suspension_travel ); 418 | vehicle_tuning.set_m_maxSuspensionForce( description.max_suspension_force ); 419 | 420 | vehicle = new Ammo.btRaycastVehicle( vehicle_tuning, _objects[ description.rigidBody ], new Ammo.btDefaultVehicleRaycaster( world ) ); 421 | vehicle.tuning = vehicle_tuning; 422 | 423 | _objects[ description.rigidBody ].setActivationState( 4 ); 424 | vehicle.setCoordinateSystem( 0, 1, 2 ); 425 | 426 | world.addVehicle( vehicle ); 427 | _vehicles[ description.id ] = vehicle; 428 | }; 429 | public_functions.removeVehicle = function( description ) { 430 | delete _vehicles[ description.id ]; 431 | }; 432 | 433 | public_functions.addWheel = function( description ) { 434 | if ( _vehicles[description.id] !== undefined ) { 435 | var tuning = _vehicles[description.id].tuning; 436 | if ( description.tuning !== undefined ) { 437 | tuning = new Ammo.btVehicleTuning(); 438 | tuning.set_m_suspensionStiffness( description.tuning.suspension_stiffness ); 439 | tuning.set_m_suspensionCompression( description.tuning.suspension_compression ); 440 | tuning.set_m_suspensionDamping( description.tuning.suspension_damping ); 441 | tuning.set_m_maxSuspensionTravelCm( description.tuning.max_suspension_travel ); 442 | tuning.set_m_maxSuspensionForce( description.tuning.max_suspension_force ); 443 | } 444 | 445 | _vec3_1.setX(description.connection_point.x); 446 | _vec3_1.setY(description.connection_point.y); 447 | _vec3_1.setZ(description.connection_point.z); 448 | 449 | _vec3_2.setX(description.wheel_direction.x); 450 | _vec3_2.setY(description.wheel_direction.y); 451 | _vec3_2.setZ(description.wheel_direction.z); 452 | 453 | _vec3_3.setX(description.wheel_axle.x); 454 | _vec3_3.setY(description.wheel_axle.y); 455 | _vec3_3.setZ(description.wheel_axle.z); 456 | 457 | _vehicles[description.id].addWheel( 458 | _vec3_1, 459 | _vec3_2, 460 | _vec3_3, 461 | description.suspension_rest_length, 462 | description.wheel_radius, 463 | tuning, 464 | description.is_front_wheel 465 | ); 466 | } 467 | 468 | _num_wheels++; 469 | 470 | if ( SUPPORT_TRANSFERABLE ) { 471 | vehiclereport = new Float32Array(1 + _num_wheels * VEHICLEREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) 472 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 473 | } else { 474 | vehiclereport = [ MESSAGE_TYPES.VEHICLEREPORT ]; 475 | } 476 | }; 477 | 478 | public_functions.setSteering = function( details ) { 479 | if ( _vehicles[details.id] !== undefined ) { 480 | _vehicles[details.id].setSteeringValue( details.steering, details.wheel ); 481 | } 482 | }; 483 | public_functions.setBrake = function( details ) { 484 | if ( _vehicles[details.id] !== undefined ) { 485 | _vehicles[details.id].setBrake( details.brake, details.wheel ); 486 | } 487 | }; 488 | public_functions.applyEngineForce = function( details ) { 489 | if ( _vehicles[details.id] !== undefined ) { 490 | _vehicles[details.id].applyEngineForce( details.force, details.wheel ); 491 | } 492 | }; 493 | 494 | public_functions.removeObject = function( details ) { 495 | world.removeRigidBody( _objects[details.id] ); 496 | Ammo.destroy(_objects[details.id]); 497 | Ammo.destroy(_motion_states[details.id]); 498 | if (_compound_shapes[details.id]) Ammo.destroy(_compound_shapes[details.id]); 499 | if (_noncached_shapes[details.id]) Ammo.destroy(_noncached_shapes[details.id]); 500 | var ptr = _objects[details.id].a != undefined ? _objects[details.id].a : _objects[details.id].ptr; 501 | delete _objects_ammo[ptr]; 502 | delete _objects[details.id]; 503 | delete _motion_states[details.id]; 504 | if (_compound_shapes[details.id]) delete _compound_shapes[details.id]; 505 | if (_noncached_shapes[details.id]) delete _noncached_shapes[details.id]; 506 | _num_objects--; 507 | }; 508 | 509 | public_functions.updateTransform = function( details ) { 510 | _object = _objects[details.id]; 511 | _object.getMotionState().getWorldTransform( _transform ); 512 | 513 | if ( details.pos ) { 514 | _vec3_1.setX(details.pos.x); 515 | _vec3_1.setY(details.pos.y); 516 | _vec3_1.setZ(details.pos.z); 517 | _transform.setOrigin(_vec3_1); 518 | } 519 | 520 | if ( details.quat ) { 521 | _quat.setX(details.quat.x); 522 | _quat.setY(details.quat.y); 523 | _quat.setZ(details.quat.z); 524 | _quat.setW(details.quat.w); 525 | _transform.setRotation(_quat); 526 | } 527 | 528 | _object.setWorldTransform( _transform ); 529 | _object.activate(); 530 | }; 531 | 532 | public_functions.updateMass = function( details ) { 533 | // #TODO: changing a static object into dynamic is buggy 534 | _object = _objects[details.id]; 535 | 536 | // Per http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?p=&f=9&t=3663#p13816 537 | world.removeRigidBody( _object ); 538 | 539 | _vec3_1.setX(0); 540 | _vec3_1.setY(0); 541 | _vec3_1.setZ(0); 542 | 543 | _object.setMassProps( details.mass, _vec3_1 ); 544 | world.addRigidBody( _object ); 545 | _object.activate(); 546 | }; 547 | 548 | public_functions.applyCentralImpulse = function ( details ) { 549 | 550 | _vec3_1.setX(details.x); 551 | _vec3_1.setY(details.y); 552 | _vec3_1.setZ(details.z); 553 | 554 | _objects[details.id].applyCentralImpulse(_vec3_1); 555 | _objects[details.id].activate(); 556 | }; 557 | 558 | public_functions.applyImpulse = function ( details ) { 559 | 560 | _vec3_1.setX(details.impulse_x); 561 | _vec3_1.setY(details.impulse_y); 562 | _vec3_1.setZ(details.impulse_z); 563 | 564 | _vec3_2.setX(details.x); 565 | _vec3_2.setY(details.y); 566 | _vec3_2.setZ(details.z); 567 | 568 | _objects[details.id].applyImpulse( 569 | _vec3_1, 570 | _vec3_2 571 | ); 572 | _objects[details.id].activate(); 573 | }; 574 | 575 | public_functions.applyCentralForce = function ( details ) { 576 | 577 | _vec3_1.setX(details.x); 578 | _vec3_1.setY(details.y); 579 | _vec3_1.setZ(details.z); 580 | 581 | _objects[details.id].applyCentralForce(_vec3_1); 582 | _objects[details.id].activate(); 583 | }; 584 | 585 | public_functions.applyForce = function ( details ) { 586 | 587 | _vec3_1.setX(details.impulse_x); 588 | _vec3_1.setY(details.impulse_y); 589 | _vec3_1.setZ(details.impulse_z); 590 | 591 | _vec3_2.setX(details.x); 592 | _vec3_2.setY(details.y); 593 | _vec3_2.setZ(details.z); 594 | 595 | _objects[details.id].applyForce( 596 | _vec3_1, 597 | _vec3_2 598 | ); 599 | _objects[details.id].activate(); 600 | }; 601 | 602 | public_functions.setAngularVelocity = function ( details ) { 603 | 604 | _vec3_1.setX(details.x); 605 | _vec3_1.setY(details.y); 606 | _vec3_1.setZ(details.z); 607 | 608 | _objects[details.id].setAngularVelocity( 609 | _vec3_1 610 | ); 611 | _objects[details.id].activate(); 612 | }; 613 | 614 | public_functions.setLinearVelocity = function ( details ) { 615 | 616 | _vec3_1.setX(details.x); 617 | _vec3_1.setY(details.y); 618 | _vec3_1.setZ(details.z); 619 | 620 | _objects[details.id].setLinearVelocity( 621 | _vec3_1 622 | ); 623 | _objects[details.id].activate(); 624 | }; 625 | 626 | public_functions.setAngularFactor = function ( details ) { 627 | 628 | _vec3_1.setX(details.x); 629 | _vec3_1.setY(details.y); 630 | _vec3_1.setZ(details.z); 631 | 632 | _objects[details.id].setAngularFactor( 633 | _vec3_1 634 | ); 635 | }; 636 | 637 | public_functions.setLinearFactor = function ( details ) { 638 | 639 | _vec3_1.setX(details.x); 640 | _vec3_1.setY(details.y); 641 | _vec3_1.setZ(details.z); 642 | 643 | _objects[details.id].setLinearFactor( 644 | _vec3_1 645 | ); 646 | }; 647 | 648 | public_functions.setDamping = function ( details ) { 649 | _objects[details.id].setDamping( details.linear, details.angular ); 650 | }; 651 | 652 | public_functions.setCcdMotionThreshold = function ( details ) { 653 | _objects[details.id].setCcdMotionThreshold( details.threshold ); 654 | }; 655 | 656 | public_functions.setCcdSweptSphereRadius = function ( details ) { 657 | _objects[details.id].setCcdSweptSphereRadius( details.radius ); 658 | }; 659 | 660 | public_functions.addConstraint = function ( details ) { 661 | var constraint; 662 | 663 | switch ( details.type ) { 664 | 665 | case 'point': 666 | if ( details.objectb === undefined ) { 667 | 668 | _vec3_1.setX(details.positiona.x); 669 | _vec3_1.setY(details.positiona.y); 670 | _vec3_1.setZ(details.positiona.z); 671 | 672 | constraint = new Ammo.btPoint2PointConstraint( 673 | _objects[ details.objecta ], 674 | _vec3_1 675 | ); 676 | } else { 677 | 678 | _vec3_1.setX(details.positiona.x); 679 | _vec3_1.setY(details.positiona.y); 680 | _vec3_1.setZ(details.positiona.z); 681 | 682 | _vec3_2.setX(details.positionb.x); 683 | _vec3_2.setY(details.positionb.y); 684 | _vec3_2.setZ(details.positionb.z); 685 | 686 | constraint = new Ammo.btPoint2PointConstraint( 687 | _objects[ details.objecta ], 688 | _objects[ details.objectb ], 689 | _vec3_1, 690 | _vec3_2 691 | ); 692 | } 693 | break; 694 | 695 | case 'hinge': 696 | if ( details.objectb === undefined ) { 697 | 698 | _vec3_1.setX(details.positiona.x); 699 | _vec3_1.setY(details.positiona.y); 700 | _vec3_1.setZ(details.positiona.z); 701 | 702 | _vec3_2.setX(details.axis.x); 703 | _vec3_2.setY(details.axis.y); 704 | _vec3_2.setZ(details.axis.z); 705 | 706 | constraint = new Ammo.btHingeConstraint( 707 | _objects[ details.objecta ], 708 | _vec3_1, 709 | _vec3_2 710 | ); 711 | } else { 712 | 713 | _vec3_1.setX(details.positiona.x); 714 | _vec3_1.setY(details.positiona.y); 715 | _vec3_1.setZ(details.positiona.z); 716 | 717 | _vec3_2.setX(details.positionb.x); 718 | _vec3_2.setY(details.positionb.y); 719 | _vec3_2.setZ(details.positionb.z); 720 | 721 | _vec3_3.setX(details.axis.x); 722 | _vec3_3.setY(details.axis.y); 723 | _vec3_3.setZ(details.axis.z); 724 | 725 | constraint = new Ammo.btHingeConstraint( 726 | _objects[ details.objecta ], 727 | _objects[ details.objectb ], 728 | _vec3_1, 729 | _vec3_2, 730 | _vec3_3, 731 | _vec3_3 732 | ); 733 | } 734 | break; 735 | 736 | case 'slider': 737 | var transforma, transformb, rotation; 738 | 739 | transforma = new Ammo.btTransform(); 740 | 741 | _vec3_1.setX(details.positiona.x); 742 | _vec3_1.setY(details.positiona.y); 743 | _vec3_1.setZ(details.positiona.z); 744 | 745 | transforma.setOrigin(_vec3_1); 746 | 747 | var rotation = transforma.getRotation(); 748 | rotation.setEuler( details.axis.x, details.axis.y, details.axis.z ); 749 | transforma.setRotation( rotation ); 750 | 751 | if ( details.objectb ) { 752 | transformb = new Ammo.btTransform(); 753 | 754 | _vec3_2.setX(details.positionb.x); 755 | _vec3_2.setY(details.positionb.y); 756 | _vec3_2.setZ(details.positionb.z); 757 | 758 | transformb.setOrigin(_vec3_2); 759 | 760 | rotation = transformb.getRotation(); 761 | rotation.setEuler( details.axis.x, details.axis.y, details.axis.z ); 762 | transformb.setRotation( rotation ); 763 | 764 | constraint = new Ammo.btSliderConstraint( 765 | _objects[ details.objecta ], 766 | _objects[ details.objectb ], 767 | transforma, 768 | transformb, 769 | true 770 | ); 771 | } else { 772 | constraint = new Ammo.btSliderConstraint( 773 | _objects[ details.objecta ], 774 | transforma, 775 | true 776 | ); 777 | } 778 | 779 | Ammo.destroy(transforma); 780 | if (transformb != undefined) { 781 | Ammo.destroy(transformb); 782 | } 783 | break; 784 | 785 | case 'conetwist': 786 | var transforma, transformb; 787 | 788 | transforma = new Ammo.btTransform(); 789 | transforma.setIdentity(); 790 | 791 | transformb = new Ammo.btTransform(); 792 | transformb.setIdentity(); 793 | 794 | _vec3_1.setX(details.positiona.x); 795 | _vec3_1.setY(details.positiona.y); 796 | _vec3_1.setZ(details.positiona.z); 797 | 798 | _vec3_2.setX(details.positionb.x); 799 | _vec3_2.setY(details.positionb.y); 800 | _vec3_2.setZ(details.positionb.z); 801 | 802 | transforma.setOrigin(_vec3_1); 803 | transformb.setOrigin(_vec3_2); 804 | 805 | var rotation = transforma.getRotation(); 806 | rotation.setEulerZYX( -details.axisa.z, -details.axisa.y, -details.axisa.x ); 807 | transforma.setRotation( rotation ); 808 | 809 | rotation = transformb.getRotation(); 810 | rotation.setEulerZYX( -details.axisb.z, -details.axisb.y, -details.axisb.x ); 811 | transformb.setRotation( rotation ); 812 | 813 | constraint = new Ammo.btConeTwistConstraint( 814 | _objects[ details.objecta ], 815 | _objects[ details.objectb ], 816 | transforma, 817 | transformb 818 | ); 819 | 820 | constraint.setLimit( Math.PI, 0, Math.PI ); 821 | 822 | Ammo.destroy(transforma); 823 | Ammo.destroy(transformb); 824 | 825 | break; 826 | 827 | case 'dof': 828 | var transforma, transformb, rotation; 829 | 830 | transforma = new Ammo.btTransform(); 831 | transforma.setIdentity(); 832 | 833 | _vec3_1.setX(details.positiona.x); 834 | _vec3_1.setY(details.positiona.y); 835 | _vec3_1.setZ(details.positiona.z); 836 | 837 | transforma.setOrigin(_vec3_1 ); 838 | 839 | rotation = transforma.getRotation(); 840 | rotation.setEulerZYX( -details.axisa.z, -details.axisa.y, -details.axisa.x ); 841 | transforma.setRotation( rotation ); 842 | 843 | if ( details.objectb ) { 844 | transformb = new Ammo.btTransform(); 845 | transformb.setIdentity(); 846 | 847 | _vec3_2.setX(details.positionb.x); 848 | _vec3_2.setY(details.positionb.y); 849 | _vec3_2.setZ(details.positionb.z); 850 | 851 | transformb.setOrigin(_vec3_2); 852 | 853 | rotation = transformb.getRotation(); 854 | rotation.setEulerZYX( -details.axisb.z, -details.axisb.y, -details.axisb.x ); 855 | transformb.setRotation( rotation ); 856 | 857 | constraint = new Ammo.btGeneric6DofConstraint( 858 | _objects[ details.objecta ], 859 | _objects[ details.objectb ], 860 | transforma, 861 | transformb 862 | ); 863 | } else { 864 | constraint = new Ammo.btGeneric6DofConstraint( 865 | _objects[ details.objecta ], 866 | transforma 867 | ); 868 | } 869 | Ammo.destroy(transforma); 870 | if (transformb != undefined) { 871 | Ammo.destroy(transformb); 872 | } 873 | break; 874 | 875 | default: 876 | return; 877 | 878 | }; 879 | 880 | world.addConstraint( constraint ); 881 | 882 | constraint.enableFeedback(); 883 | _constraints[ details.id ] = constraint; 884 | _num_constraints++; 885 | 886 | if ( SUPPORT_TRANSFERABLE ) { 887 | constraintreport = new Float32Array(1 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) 888 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 889 | } else { 890 | constraintreport = [ MESSAGE_TYPES.CONSTRAINTREPORT ]; 891 | } 892 | }; 893 | 894 | public_functions.removeConstraint = function( details ) { 895 | var constraint = _constraints[ details.id ]; 896 | if ( constraint !== undefined ) { 897 | world.removeConstraint( constraint ); 898 | delete _constraints[ details.id ]; 899 | _num_constraints--; 900 | } 901 | }; 902 | 903 | public_functions.constraint_setBreakingImpulseThreshold = function( details ) { 904 | var constraint = _constraints[ details.id ]; 905 | if ( constraint !== undefind ) { 906 | constraint.setBreakingImpulseThreshold( details.threshold ); 907 | } 908 | }; 909 | 910 | public_functions.simulate = function simulate( params ) { 911 | if ( world ) { 912 | params = params || {}; 913 | 914 | if ( !params.timeStep ) { 915 | if ( last_simulation_time ) { 916 | params.timeStep = 0; 917 | while ( params.timeStep + last_simulation_duration <= fixedTimeStep ) { 918 | params.timeStep = ( Date.now() - last_simulation_time ) / 1000; // time since last simulation 919 | } 920 | } else { 921 | params.timeStep = fixedTimeStep; // handle first frame 922 | } 923 | } else { 924 | if ( params.timeStep < fixedTimeStep ) { 925 | params.timeStep = fixedTimeStep; 926 | } 927 | } 928 | 929 | params.maxSubSteps = params.maxSubSteps || Math.ceil( params.timeStep / fixedTimeStep ); // If maxSubSteps is not defined, keep the simulation fully up to date 930 | 931 | last_simulation_duration = Date.now(); 932 | world.stepSimulation( params.timeStep, params.maxSubSteps, fixedTimeStep ); 933 | 934 | reportVehicles(); 935 | reportCollisions(); 936 | reportConstraints(); 937 | reportWorld(); 938 | 939 | last_simulation_duration = ( Date.now() - last_simulation_duration ) / 1000; 940 | last_simulation_time = Date.now(); 941 | } 942 | }; 943 | 944 | 945 | // Constraint functions 946 | public_functions.hinge_setLimits = function( params ) { 947 | _constraints[ params.constraint ].setLimit( params.low, params.high, 0, params.bias_factor, params.relaxation_factor ); 948 | }; 949 | public_functions.hinge_enableAngularMotor = function( params ) { 950 | var constraint = _constraints[ params.constraint ]; 951 | constraint.enableAngularMotor( true, params.velocity, params.acceleration ); 952 | constraint.getRigidBodyA().activate(); 953 | if ( constraint.getRigidBodyB() ) { 954 | constraint.getRigidBodyB().activate(); 955 | } 956 | }; 957 | public_functions.hinge_disableMotor = function( params ) { 958 | _constraints[ params.constraint ].enableMotor( false ); 959 | if ( constraint.getRigidBodyB() ) { 960 | constraint.getRigidBodyB().activate(); 961 | } 962 | }; 963 | 964 | public_functions.slider_setLimits = function( params ) { 965 | var constraint = _constraints[ params.constraint ]; 966 | constraint.setLowerLinLimit( params.lin_lower || 0 ); 967 | constraint.setUpperLinLimit( params.lin_upper || 0 ); 968 | 969 | constraint.setLowerAngLimit( params.ang_lower || 0 ); 970 | constraint.setUpperAngLimit( params.ang_upper || 0 ); 971 | }; 972 | public_functions.slider_setRestitution = function( params ) { 973 | var constraint = _constraints[ params.constraint ]; 974 | constraint.setSoftnessLimLin( params.linear || 0 ); 975 | constraint.setSoftnessLimAng( params.angular || 0 ); 976 | }; 977 | public_functions.slider_enableLinearMotor = function( params ) { 978 | var constraint = _constraints[ params.constraint ]; 979 | constraint.setTargetLinMotorVelocity( params.velocity ); 980 | constraint.setMaxLinMotorForce( params.acceleration ); 981 | constraint.setPoweredLinMotor( true ); 982 | constraint.getRigidBodyA().activate(); 983 | if ( constraint.getRigidBodyB ) { 984 | constraint.getRigidBodyB().activate(); 985 | } 986 | }; 987 | public_functions.slider_disableLinearMotor = function( params ) { 988 | var constraint = _constraints[ params.constraint ]; 989 | constraint.setPoweredLinMotor( false ); 990 | if ( constraint.getRigidBodyB() ) { 991 | constraint.getRigidBodyB().activate(); 992 | } 993 | }; 994 | public_functions.slider_enableAngularMotor = function( params ) { 995 | var constraint = _constraints[ params.constraint ]; 996 | constraint.setTargetAngMotorVelocity( params.velocity ); 997 | constraint.setMaxAngMotorForce( params.acceleration ); 998 | constraint.setPoweredAngMotor( true ); 999 | constraint.getRigidBodyA().activate(); 1000 | if ( constraint.getRigidBodyB() ) { 1001 | constraint.getRigidBodyB().activate(); 1002 | } 1003 | }; 1004 | public_functions.slider_disableAngularMotor = function( params ) { 1005 | var constraint = _constraints[ params.constraint ]; 1006 | constraint.setPoweredAngMotor( false ); 1007 | constraint.getRigidBodyA().activate(); 1008 | if ( constraint.getRigidBodyB() ) { 1009 | constraint.getRigidBodyB().activate(); 1010 | } 1011 | }; 1012 | 1013 | public_functions.conetwist_setLimit = function( params ) { 1014 | _constraints[ params.constraint ].setLimit( params.z, params.y, params.x ); // ZYX order 1015 | }; 1016 | public_functions.conetwist_enableMotor = function( params ) { 1017 | var constraint = _constraints[ params.constraint ]; 1018 | constraint.enableMotor( true ); 1019 | constraint.getRigidBodyA().activate(); 1020 | constraint.getRigidBodyB().activate(); 1021 | }; 1022 | public_functions.conetwist_setMaxMotorImpulse = function( params ) { 1023 | var constraint = _constraints[ params.constraint ]; 1024 | constraint.setMaxMotorImpulse( params.max_impulse ); 1025 | constraint.getRigidBodyA().activate(); 1026 | constraint.getRigidBodyB().activate(); 1027 | }; 1028 | public_functions.conetwist_setMotorTarget = function( params ) { 1029 | var constraint = _constraints[ params.constraint ]; 1030 | 1031 | _quat.setX(params.x); 1032 | _quat.setY(params.y); 1033 | _quat.setZ(params.z); 1034 | _quat.setW(params.w); 1035 | 1036 | constraint.setMotorTarget(_quat); 1037 | 1038 | constraint.getRigidBodyA().activate(); 1039 | constraint.getRigidBodyB().activate(); 1040 | }; 1041 | public_functions.conetwist_disableMotor = function( params ) { 1042 | var constraint = _constraints[ params.constraint ]; 1043 | constraint.enableMotor( false ); 1044 | constraint.getRigidBodyA().activate(); 1045 | constraint.getRigidBodyB().activate(); 1046 | }; 1047 | 1048 | public_functions.dof_setLinearLowerLimit = function( params ) { 1049 | var constraint = _constraints[ params.constraint ]; 1050 | 1051 | _vec3_1.setX(params.x); 1052 | _vec3_1.setY(params.y); 1053 | _vec3_1.setZ(params.z); 1054 | 1055 | constraint.setLinearLowerLimit(_vec3_1); 1056 | 1057 | constraint.getRigidBodyA().activate(); 1058 | if ( constraint.getRigidBodyB() ) { 1059 | constraint.getRigidBodyB().activate(); 1060 | } 1061 | }; 1062 | public_functions.dof_setLinearUpperLimit = function( params ) { 1063 | var constraint = _constraints[ params.constraint ]; 1064 | 1065 | _vec3_1.setX(params.x); 1066 | _vec3_1.setY(params.y); 1067 | _vec3_1.setZ(params.z); 1068 | 1069 | constraint.setLinearUpperLimit(_vec3_1); 1070 | 1071 | constraint.getRigidBodyA().activate(); 1072 | if ( constraint.getRigidBodyB() ) { 1073 | constraint.getRigidBodyB().activate(); 1074 | } 1075 | }; 1076 | public_functions.dof_setAngularLowerLimit = function( params ) { 1077 | var constraint = _constraints[ params.constraint ]; 1078 | 1079 | _vec3_1.setX(params.x); 1080 | _vec3_1.setY(params.y); 1081 | _vec3_1.setZ(params.z); 1082 | 1083 | constraint.setAngularLowerLimit(_vec3_1); 1084 | 1085 | constraint.getRigidBodyA().activate(); 1086 | if ( constraint.getRigidBodyB() ) { 1087 | constraint.getRigidBodyB().activate(); 1088 | } 1089 | }; 1090 | public_functions.dof_setAngularUpperLimit = function( params ) { 1091 | var constraint = _constraints[ params.constraint ]; 1092 | 1093 | _vec3_1.setX(params.x); 1094 | _vec3_1.setY(params.y); 1095 | _vec3_1.setZ(params.z); 1096 | 1097 | constraint.setAngularUpperLimit(_vec3_1); 1098 | 1099 | constraint.getRigidBodyA().activate(); 1100 | if ( constraint.getRigidBodyB() ) { 1101 | constraint.getRigidBodyB().activate(); 1102 | } 1103 | }; 1104 | public_functions.dof_enableAngularMotor = function( params ) { 1105 | var constraint = _constraints[ params.constraint ]; 1106 | 1107 | var motor = constraint.getRotationalLimitMotor( params.which ); 1108 | motor.set_m_enableMotor( true ); 1109 | 1110 | constraint.getRigidBodyA().activate(); 1111 | if ( constraint.getRigidBodyB() ) { 1112 | constraint.getRigidBodyB().activate(); 1113 | } 1114 | }; 1115 | public_functions.dof_configureAngularMotor = function( params ) { 1116 | var constraint = _constraints[ params.constraint ]; 1117 | 1118 | var motor = constraint.getRotationalLimitMotor( params.which ); 1119 | 1120 | motor.set_m_loLimit( params.low_angle ); 1121 | motor.set_m_hiLimit( params.high_angle ); 1122 | motor.set_m_targetVelocity( params.velocity ); 1123 | motor.set_m_maxMotorForce( params.max_force ); 1124 | 1125 | constraint.getRigidBodyA().activate(); 1126 | if ( constraint.getRigidBodyB() ) { 1127 | constraint.getRigidBodyB().activate(); 1128 | } 1129 | }; 1130 | public_functions.dof_disableAngularMotor = function( params ) { 1131 | var constraint = _constraints[ params.constraint ]; 1132 | 1133 | var motor = constraint.getRotationalLimitMotor( params.which ); 1134 | motor.set_m_enableMotor( false ); 1135 | 1136 | constraint.getRigidBodyA().activate(); 1137 | if ( constraint.getRigidBodyB() ) { 1138 | constraint.getRigidBodyB().activate(); 1139 | } 1140 | }; 1141 | 1142 | reportWorld = function() { 1143 | var index, object, 1144 | transform, origin, rotation, 1145 | offset = 0, 1146 | i = 0; 1147 | 1148 | if ( SUPPORT_TRANSFERABLE ) { 1149 | if ( worldreport.length < 2 + _num_objects * WORLDREPORT_ITEMSIZE ) { 1150 | worldreport = new Float32Array( 1151 | 2 + // message id & # objects in report 1152 | ( Math.ceil( _num_objects / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * WORLDREPORT_ITEMSIZE // # of values needed * item size 1153 | ); 1154 | worldreport[0] = MESSAGE_TYPES.WORLDREPORT; 1155 | } 1156 | } 1157 | 1158 | worldreport[1] = _num_objects; // record how many objects we're reporting on 1159 | 1160 | //for ( i = 0; i < worldreport[1]; i++ ) { 1161 | for ( index in _objects ) { 1162 | if ( _objects.hasOwnProperty( index ) ) { 1163 | object = _objects[index]; 1164 | 1165 | // #TODO: we can't use center of mass transform when center of mass can change, 1166 | // but getMotionState().getWorldTransform() screws up on objects that have been moved 1167 | //object.getMotionState().getWorldTransform( transform ); 1168 | transform = object.getCenterOfMassTransform(); 1169 | 1170 | origin = transform.getOrigin(); 1171 | rotation = transform.getRotation(); 1172 | 1173 | // add values to report 1174 | offset = 2 + (i++) * WORLDREPORT_ITEMSIZE; 1175 | 1176 | worldreport[ offset ] = object.id; 1177 | 1178 | worldreport[ offset + 1 ] = origin.x(); 1179 | worldreport[ offset + 2 ] = origin.y(); 1180 | worldreport[ offset + 3 ] = origin.z(); 1181 | 1182 | worldreport[ offset + 4 ] = rotation.x(); 1183 | worldreport[ offset + 5 ] = rotation.y(); 1184 | worldreport[ offset + 6 ] = rotation.z(); 1185 | worldreport[ offset + 7 ] = rotation.w(); 1186 | 1187 | _vector = object.getLinearVelocity(); 1188 | worldreport[ offset + 8 ] = _vector.x(); 1189 | worldreport[ offset + 9 ] = _vector.y(); 1190 | worldreport[ offset + 10 ] = _vector.z(); 1191 | 1192 | _vector = object.getAngularVelocity(); 1193 | worldreport[ offset + 11 ] = _vector.x(); 1194 | worldreport[ offset + 12 ] = _vector.y(); 1195 | worldreport[ offset + 13 ] = _vector.z(); 1196 | } 1197 | } 1198 | 1199 | 1200 | if ( SUPPORT_TRANSFERABLE ) { 1201 | transferableMessage( worldreport.buffer, [worldreport.buffer] ); 1202 | } else { 1203 | transferableMessage( worldreport ); 1204 | } 1205 | 1206 | }; 1207 | 1208 | reportCollisions = function() { 1209 | var i, offset, 1210 | dp = world.getDispatcher(), 1211 | num = dp.getNumManifolds(), 1212 | manifold, num_contacts, j, pt, 1213 | _collided = false; 1214 | 1215 | if ( SUPPORT_TRANSFERABLE ) { 1216 | if ( collisionreport.length < 2 + num * COLLISIONREPORT_ITEMSIZE ) { 1217 | collisionreport = new Float32Array( 1218 | 2 + // message id & # objects in report 1219 | ( Math.ceil( _num_objects / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * COLLISIONREPORT_ITEMSIZE // # of values needed * item size 1220 | ); 1221 | collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; 1222 | } 1223 | } 1224 | 1225 | collisionreport[1] = 0; // how many collisions we're reporting on 1226 | 1227 | for ( i = 0; i < num; i++ ) { 1228 | manifold = dp.getManifoldByIndexInternal( i ); 1229 | 1230 | num_contacts = manifold.getNumContacts(); 1231 | if ( num_contacts === 0 ) { 1232 | continue; 1233 | } 1234 | 1235 | for ( j = 0; j < num_contacts; j++ ) { 1236 | pt = manifold.getContactPoint( j ); 1237 | //if ( pt.getDistance() < 0 ) { 1238 | offset = 2 + (collisionreport[1]++) * COLLISIONREPORT_ITEMSIZE; 1239 | collisionreport[ offset ] = _objects_ammo[ manifold.getBody0() ]; 1240 | collisionreport[ offset + 1 ] = _objects_ammo[ manifold.getBody1() ]; 1241 | 1242 | _vector = pt.get_m_normalWorldOnB(); 1243 | collisionreport[ offset + 2 ] = _vector.x(); 1244 | collisionreport[ offset + 3 ] = _vector.y(); 1245 | collisionreport[ offset + 4 ] = _vector.z(); 1246 | break; 1247 | //} 1248 | 1249 | transferableMessage( _objects_ammo ); 1250 | 1251 | } 1252 | } 1253 | 1254 | 1255 | if ( SUPPORT_TRANSFERABLE ) { 1256 | transferableMessage( collisionreport.buffer, [collisionreport.buffer] ); 1257 | } else { 1258 | transferableMessage( collisionreport ); 1259 | } 1260 | }; 1261 | 1262 | reportVehicles = function() { 1263 | var index, vehicle, 1264 | transform, origin, rotation, 1265 | offset = 0, 1266 | i = 0, j = 0; 1267 | 1268 | if ( SUPPORT_TRANSFERABLE ) { 1269 | if ( vehiclereport.length < 2 + _num_wheels * VEHICLEREPORT_ITEMSIZE ) { 1270 | vehiclereport = new Float32Array( 1271 | 2 + // message id & # objects in report 1272 | ( Math.ceil( _num_wheels / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * VEHICLEREPORT_ITEMSIZE // # of values needed * item size 1273 | ); 1274 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 1275 | } 1276 | } 1277 | 1278 | for ( index in _vehicles ) { 1279 | if ( _vehicles.hasOwnProperty( index ) ) { 1280 | vehicle = _vehicles[index]; 1281 | 1282 | for ( j = 0; j < vehicle.getNumWheels(); j++ ) { 1283 | 1284 | //vehicle.updateWheelTransform( j, true ); 1285 | 1286 | //transform = vehicle.getWheelTransformWS( j ); 1287 | transform = vehicle.getWheelInfo( j ).get_m_worldTransform(); 1288 | 1289 | origin = transform.getOrigin(); 1290 | rotation = transform.getRotation(); 1291 | 1292 | // add values to report 1293 | offset = 1 + (i++) * VEHICLEREPORT_ITEMSIZE; 1294 | 1295 | vehiclereport[ offset ] = index; 1296 | vehiclereport[ offset + 1 ] = j; 1297 | 1298 | vehiclereport[ offset + 2 ] = origin.x(); 1299 | vehiclereport[ offset + 3 ] = origin.y(); 1300 | vehiclereport[ offset + 4 ] = origin.z(); 1301 | 1302 | vehiclereport[ offset + 5 ] = rotation.x(); 1303 | vehiclereport[ offset + 6 ] = rotation.y(); 1304 | vehiclereport[ offset + 7 ] = rotation.z(); 1305 | vehiclereport[ offset + 8 ] = rotation.w(); 1306 | 1307 | } 1308 | 1309 | } 1310 | } 1311 | 1312 | if ( j !== 0 ) { 1313 | if ( SUPPORT_TRANSFERABLE ) { 1314 | transferableMessage( vehiclereport.buffer, [vehiclereport.buffer] ); 1315 | } else { 1316 | transferableMessage( vehiclereport ); 1317 | } 1318 | } 1319 | }; 1320 | 1321 | reportConstraints = function() { 1322 | var index, constraint, 1323 | offset_body, 1324 | transform, origin, 1325 | offset = 0, 1326 | i = 0; 1327 | 1328 | if ( SUPPORT_TRANSFERABLE ) { 1329 | if ( constraintreport.length < 2 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE ) { 1330 | constraintreport = new Float32Array( 1331 | 2 + // message id & # objects in report 1332 | ( Math.ceil( _num_constraints / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * CONSTRAINTREPORT_ITEMSIZE // # of values needed * item size 1333 | ); 1334 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 1335 | } 1336 | } 1337 | 1338 | for ( index in _constraints ) { 1339 | if ( _constraints.hasOwnProperty( index ) ) { 1340 | constraint = _constraints[index]; 1341 | offset_body = constraint.getRigidBodyA(); 1342 | transform = constraint.getFrameOffsetA(); 1343 | origin = transform.getOrigin(); 1344 | 1345 | // add values to report 1346 | offset = 1 + (i++) * CONSTRAINTREPORT_ITEMSIZE; 1347 | 1348 | constraintreport[ offset ] = index; 1349 | constraintreport[ offset + 1 ] = offset_body.id; 1350 | constraintreport[ offset + 2 ] = origin.getX(); 1351 | constraintreport[ offset + 3 ] = origin.getY(); 1352 | constraintreport[ offset + 4 ] = origin.getZ(); 1353 | constraintreport[ offset + 5 ] = constraint.getAppliedImpulse(); 1354 | } 1355 | } 1356 | 1357 | 1358 | if ( i !== 0 ) { 1359 | if ( SUPPORT_TRANSFERABLE ) { 1360 | transferableMessage( constraintreport.buffer, [constraintreport.buffer] ); 1361 | } else { 1362 | transferableMessage( constraintreport ); 1363 | } 1364 | } 1365 | 1366 | }; 1367 | 1368 | self.onmessage = function( event ) { 1369 | 1370 | if ( event.data instanceof Float32Array ) { 1371 | // transferable object 1372 | 1373 | switch ( event.data[0] ) { 1374 | case MESSAGE_TYPES.WORLDREPORT: 1375 | worldreport = new Float32Array( event.data ); 1376 | break; 1377 | 1378 | case MESSAGE_TYPES.COLLISIONREPORT: 1379 | collisionreport = new Float32Array( event.data ); 1380 | break; 1381 | 1382 | case MESSAGE_TYPES.VEHICLEREPORT: 1383 | vehiclereport = new Float32Array( event.data ); 1384 | break; 1385 | 1386 | case MESSAGE_TYPES.CONSTRAINTREPORT: 1387 | constraintreport = new Float32Array( event.data ); 1388 | break; 1389 | } 1390 | 1391 | return; 1392 | } 1393 | 1394 | if ( event.data.cmd && public_functions[event.data.cmd] ) { 1395 | //if ( event.data.params.id !== undefined && _objects[event.data.params.id] === undefined && event.data.cmd !== 'addObject' && event.data.cmd !== 'registerMaterial' ) return; 1396 | public_functions[event.data.cmd]( event.data.params ); 1397 | } 1398 | 1399 | }; 1400 | -------------------------------------------------------------------------------- /assets/BoilerplatePhysics/scripts/physi.js: -------------------------------------------------------------------------------- 1 | window.Physijs = (function() { 2 | 'use strict'; 3 | 4 | var SUPPORT_TRANSFERABLE, 5 | _is_simulating = false, 6 | _Physijs = Physijs, // used for noConflict method 7 | Physijs = {}, // object assigned to window.Physijs 8 | Eventable, // class to provide simple event methods 9 | getObjectId, // returns a unique ID for a Physijs mesh object 10 | getEulerXYZFromQuaternion, getQuatertionFromEuler, 11 | convertWorldPositionToObject, // Converts a world-space position to object-space 12 | addObjectChildren, 13 | 14 | _temp1, _temp2, 15 | _temp_vector3_1 = new THREE.Vector3, 16 | _temp_vector3_2 = new THREE.Vector3, 17 | _temp_matrix4_1 = new THREE.Matrix4, 18 | _quaternion_1 = new THREE.Quaternion, 19 | 20 | // constants 21 | MESSAGE_TYPES = { 22 | WORLDREPORT: 0, 23 | COLLISIONREPORT: 1, 24 | VEHICLEREPORT: 2, 25 | CONSTRAINTREPORT: 3 26 | }, 27 | REPORT_ITEMSIZE = 14, 28 | COLLISIONREPORT_ITEMSIZE = 5, 29 | VEHICLEREPORT_ITEMSIZE = 9, 30 | CONSTRAINTREPORT_ITEMSIZE = 6; 31 | 32 | Physijs.scripts = {}; 33 | 34 | Eventable = function() { 35 | this._eventListeners = {}; 36 | }; 37 | Eventable.prototype.addEventListener = function( event_name, callback ) { 38 | if ( !this._eventListeners.hasOwnProperty( event_name ) ) { 39 | this._eventListeners[event_name] = []; 40 | } 41 | this._eventListeners[event_name].push( callback ); 42 | }; 43 | Eventable.prototype.removeEventListener = function( event_name, callback ) { 44 | var index; 45 | 46 | if ( !this._eventListeners.hasOwnProperty( event_name ) ) return false; 47 | 48 | if ( (index = this._eventListeners[event_name].indexOf( callback )) >= 0 ) { 49 | this._eventListeners[event_name].splice( index, 1 ); 50 | return true; 51 | } 52 | 53 | return false; 54 | }; 55 | Eventable.prototype.dispatchEvent = function( event_name ) { 56 | var i, 57 | parameters = Array.prototype.splice.call( arguments, 1 ); 58 | 59 | if ( this._eventListeners.hasOwnProperty( event_name ) ) { 60 | for ( i = 0; i < this._eventListeners[event_name].length; i++ ) { 61 | this._eventListeners[event_name][i].apply( this, parameters ); 62 | } 63 | } 64 | }; 65 | Eventable.make = function( obj ) { 66 | obj.prototype.addEventListener = Eventable.prototype.addEventListener; 67 | obj.prototype.removeEventListener = Eventable.prototype.removeEventListener; 68 | obj.prototype.dispatchEvent = Eventable.prototype.dispatchEvent; 69 | }; 70 | 71 | getObjectId = (function() { 72 | var _id = 1; 73 | return function() { 74 | return _id++; 75 | }; 76 | })(); 77 | 78 | getEulerXYZFromQuaternion = function ( x, y, z, w ) { 79 | return new THREE.Vector3( 80 | Math.atan2( 2 * ( x * w - y * z ), ( w * w - x * x - y * y + z * z ) ), 81 | Math.asin( 2 * ( x * z + y * w ) ), 82 | Math.atan2( 2 * ( z * w - x * y ), ( w * w + x * x - y * y - z * z ) ) 83 | ); 84 | }; 85 | 86 | getQuatertionFromEuler = function( x, y, z ) { 87 | var c1, s1, c2, s2, c3, s3, c1c2, s1s2; 88 | c1 = Math.cos( y ); 89 | s1 = Math.sin( y ); 90 | c2 = Math.cos( -z ); 91 | s2 = Math.sin( -z ); 92 | c3 = Math.cos( x ); 93 | s3 = Math.sin( x ); 94 | 95 | c1c2 = c1 * c2; 96 | s1s2 = s1 * s2; 97 | 98 | return { 99 | w: c1c2 * c3 - s1s2 * s3, 100 | x: c1c2 * s3 + s1s2 * c3, 101 | y: s1 * c2 * c3 + c1 * s2 * s3, 102 | z: c1 * s2 * c3 - s1 * c2 * s3 103 | }; 104 | }; 105 | 106 | convertWorldPositionToObject = function( position, object ) { 107 | _temp_matrix4_1.identity(); // reset temp matrix 108 | 109 | // Set the temp matrix's rotation to the object's rotation 110 | _temp_matrix4_1.identity().makeRotationFromQuaternion( object.quaternion ); 111 | 112 | // Invert rotation matrix in order to "unrotate" a point back to object space 113 | _temp_matrix4_1.getInverse( _temp_matrix4_1 ); 114 | 115 | // Yay! Temp vars! 116 | _temp_vector3_1.copy( position ); 117 | _temp_vector3_2.copy( object.position ); 118 | 119 | // Apply the rotation 120 | 121 | return _temp_vector3_1.sub( _temp_vector3_2 ).applyMatrix4( _temp_matrix4_1 ); 122 | }; 123 | 124 | 125 | 126 | // Physijs.noConflict 127 | Physijs.noConflict = function() { 128 | window.Physijs = _Physijs; 129 | return Physijs; 130 | }; 131 | 132 | 133 | // Physijs.createMaterial 134 | Physijs.createMaterial = function( material, friction, restitution ) { 135 | var physijs_material = function(){}; 136 | physijs_material.prototype = material; 137 | physijs_material = new physijs_material; 138 | 139 | physijs_material._physijs = { 140 | id: material.id, 141 | friction: friction === undefined ? .8 : friction, 142 | restitution: restitution === undefined ? .2 : restitution 143 | }; 144 | 145 | return physijs_material; 146 | }; 147 | 148 | 149 | // Constraints 150 | Physijs.PointConstraint = function( objecta, objectb, position ) { 151 | if ( position === undefined ) { 152 | position = objectb; 153 | objectb = undefined; 154 | } 155 | 156 | this.type = 'point'; 157 | this.appliedImpulse = 0; 158 | this.id = getObjectId(); 159 | this.objecta = objecta._physijs.id; 160 | this.positiona = convertWorldPositionToObject( position, objecta ).clone(); 161 | 162 | if ( objectb ) { 163 | this.objectb = objectb._physijs.id; 164 | this.positionb = convertWorldPositionToObject( position, objectb ).clone(); 165 | } 166 | }; 167 | Physijs.PointConstraint.prototype.getDefinition = function() { 168 | return { 169 | type: this.type, 170 | id: this.id, 171 | objecta: this.objecta, 172 | objectb: this.objectb, 173 | positiona: this.positiona, 174 | positionb: this.positionb 175 | }; 176 | }; 177 | 178 | Physijs.HingeConstraint = function( objecta, objectb, position, axis ) { 179 | if ( axis === undefined ) { 180 | axis = position; 181 | position = objectb; 182 | objectb = undefined; 183 | } 184 | 185 | this.type = 'hinge'; 186 | this.appliedImpulse = 0; 187 | this.id = getObjectId(); 188 | this.scene = objecta.parent; 189 | this.objecta = objecta._physijs.id; 190 | this.positiona = convertWorldPositionToObject( position, objecta ).clone(); 191 | this.position = position.clone(); 192 | this.axis = axis; 193 | 194 | if ( objectb ) { 195 | this.objectb = objectb._physijs.id; 196 | this.positionb = convertWorldPositionToObject( position, objectb ).clone(); 197 | } 198 | }; 199 | Physijs.HingeConstraint.prototype.getDefinition = function() { 200 | return { 201 | type: this.type, 202 | id: this.id, 203 | objecta: this.objecta, 204 | objectb: this.objectb, 205 | positiona: this.positiona, 206 | positionb: this.positionb, 207 | axis: this.axis 208 | }; 209 | }; 210 | /* 211 | * low = minimum angle in radians 212 | * high = maximum angle in radians 213 | * bias_factor = applied as a factor to constraint error 214 | * relaxation_factor = controls bounce (0.0 == no bounce) 215 | */ 216 | Physijs.HingeConstraint.prototype.setLimits = function( low, high, bias_factor, relaxation_factor ) { 217 | this.scene.execute( 'hinge_setLimits', { constraint: this.id, low: low, high: high, bias_factor: bias_factor, relaxation_factor: relaxation_factor } ); 218 | }; 219 | Physijs.HingeConstraint.prototype.enableAngularMotor = function( velocity, acceleration ) { 220 | this.scene.execute( 'hinge_enableAngularMotor', { constraint: this.id, velocity: velocity, acceleration: acceleration } ); 221 | }; 222 | Physijs.HingeConstraint.prototype.disableMotor = function( velocity, acceleration ) { 223 | this.scene.execute( 'hinge_disableMotor', { constraint: this.id } ); 224 | }; 225 | 226 | Physijs.SliderConstraint = function( objecta, objectb, position, axis ) { 227 | if ( axis === undefined ) { 228 | axis = position; 229 | position = objectb; 230 | objectb = undefined; 231 | } 232 | 233 | this.type = 'slider'; 234 | this.appliedImpulse = 0; 235 | this.id = getObjectId(); 236 | this.scene = objecta.parent; 237 | this.objecta = objecta._physijs.id; 238 | this.positiona = convertWorldPositionToObject( position, objecta ).clone(); 239 | this.axis = axis; 240 | 241 | if ( objectb ) { 242 | this.objectb = objectb._physijs.id; 243 | this.positionb = convertWorldPositionToObject( position, objectb ).clone(); 244 | } 245 | }; 246 | Physijs.SliderConstraint.prototype.getDefinition = function() { 247 | return { 248 | type: this.type, 249 | id: this.id, 250 | objecta: this.objecta, 251 | objectb: this.objectb, 252 | positiona: this.positiona, 253 | positionb: this.positionb, 254 | axis: this.axis 255 | }; 256 | }; 257 | Physijs.SliderConstraint.prototype.setLimits = function( lin_lower, lin_upper, ang_lower, ang_upper ) { 258 | this.scene.execute( 'slider_setLimits', { constraint: this.id, lin_lower: lin_lower, lin_upper: lin_upper, ang_lower: ang_lower, ang_upper: ang_upper } ); 259 | }; 260 | Physijs.SliderConstraint.prototype.setRestitution = function( linear, angular ) { 261 | this.scene.execute( 262 | 'slider_setRestitution', 263 | { 264 | constraint: this.id, 265 | linear: linear, 266 | angular: angular 267 | } 268 | ); 269 | }; 270 | Physijs.SliderConstraint.prototype.enableLinearMotor = function( velocity, acceleration) { 271 | this.scene.execute( 'slider_enableLinearMotor', { constraint: this.id, velocity: velocity, acceleration: acceleration } ); 272 | }; 273 | Physijs.SliderConstraint.prototype.disableLinearMotor = function() { 274 | this.scene.execute( 'slider_disableLinearMotor', { constraint: this.id } ); 275 | }; 276 | Physijs.SliderConstraint.prototype.enableAngularMotor = function( velocity, acceleration ) { 277 | this.scene.execute( 'slider_enableAngularMotor', { constraint: this.id, velocity: velocity, acceleration: acceleration } ); 278 | }; 279 | Physijs.SliderConstraint.prototype.disableAngularMotor = function() { 280 | this.scene.execute( 'slider_disableAngularMotor', { constraint: this.id } ); 281 | }; 282 | 283 | Physijs.ConeTwistConstraint = function( objecta, objectb, position ) { 284 | if ( position === undefined ) { 285 | throw 'Both objects must be defined in a ConeTwistConstraint.'; 286 | } 287 | this.type = 'conetwist'; 288 | this.appliedImpulse = 0; 289 | this.id = getObjectId(); 290 | this.scene = objecta.parent; 291 | this.objecta = objecta._physijs.id; 292 | this.positiona = convertWorldPositionToObject( position, objecta ).clone(); 293 | this.objectb = objectb._physijs.id; 294 | this.positionb = convertWorldPositionToObject( position, objectb ).clone(); 295 | this.axisa = { x: objecta.rotation.x, y: objecta.rotation.y, z: objecta.rotation.z }; 296 | this.axisb = { x: objectb.rotation.x, y: objectb.rotation.y, z: objectb.rotation.z }; 297 | }; 298 | Physijs.ConeTwistConstraint.prototype.getDefinition = function() { 299 | return { 300 | type: this.type, 301 | id: this.id, 302 | objecta: this.objecta, 303 | objectb: this.objectb, 304 | positiona: this.positiona, 305 | positionb: this.positionb, 306 | axisa: this.axisa, 307 | axisb: this.axisb 308 | }; 309 | }; 310 | Physijs.ConeTwistConstraint.prototype.setLimit = function( x, y, z ) { 311 | this.scene.execute( 'conetwist_setLimit', { constraint: this.id, x: x, y: y, z: z } ); 312 | }; 313 | Physijs.ConeTwistConstraint.prototype.enableMotor = function() { 314 | this.scene.execute( 'conetwist_enableMotor', { constraint: this.id } ); 315 | }; 316 | Physijs.ConeTwistConstraint.prototype.setMaxMotorImpulse = function( max_impulse ) { 317 | this.scene.execute( 'conetwist_setMaxMotorImpulse', { constraint: this.id, max_impulse: max_impulse } ); 318 | }; 319 | Physijs.ConeTwistConstraint.prototype.setMotorTarget = function( target ) { 320 | if ( target instanceof THREE.Vector3 ) { 321 | target = new THREE.Quaternion().setFromEuler( new THREE.Euler( target.x, target.y, target.z ) ); 322 | } else if ( target instanceof THREE.Euler ) { 323 | target = new THREE.Quaternion().setFromEuler( target ); 324 | } else if ( target instanceof THREE.Matrix4 ) { 325 | target = new THREE.Quaternion().setFromRotationMatrix( target ); 326 | } 327 | this.scene.execute( 'conetwist_setMotorTarget', { constraint: this.id, x: target.x, y: target.y, z: target.z, w: target.w } ); 328 | }; 329 | Physijs.ConeTwistConstraint.prototype.disableMotor = function() { 330 | this.scene.execute( 'conetwist_disableMotor', { constraint: this.id } ); 331 | }; 332 | 333 | Physijs.DOFConstraint = function( objecta, objectb, position ) { 334 | if ( position === undefined ) { 335 | position = objectb; 336 | objectb = undefined; 337 | } 338 | this.type = 'dof'; 339 | this.appliedImpulse = 0; 340 | this.id = getObjectId(); 341 | this.scene = objecta.parent; 342 | this.objecta = objecta._physijs.id; 343 | this.positiona = convertWorldPositionToObject( position, objecta ).clone(); 344 | this.axisa = { x: objecta.rotation.x, y: objecta.rotation.y, z: objecta.rotation.z }; 345 | 346 | if ( objectb ) { 347 | this.objectb = objectb._physijs.id; 348 | this.positionb = convertWorldPositionToObject( position, objectb ).clone(); 349 | this.axisb = { x: objectb.rotation.x, y: objectb.rotation.y, z: objectb.rotation.z }; 350 | } 351 | }; 352 | Physijs.DOFConstraint.prototype.getDefinition = function() { 353 | return { 354 | type: this.type, 355 | id: this.id, 356 | objecta: this.objecta, 357 | objectb: this.objectb, 358 | positiona: this.positiona, 359 | positionb: this.positionb, 360 | axisa: this.axisa, 361 | axisb: this.axisb 362 | }; 363 | }; 364 | Physijs.DOFConstraint.prototype.setLinearLowerLimit = function( limit ) { 365 | this.scene.execute( 'dof_setLinearLowerLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 366 | }; 367 | Physijs.DOFConstraint.prototype.setLinearUpperLimit = function( limit ) { 368 | this.scene.execute( 'dof_setLinearUpperLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 369 | }; 370 | Physijs.DOFConstraint.prototype.setAngularLowerLimit = function( limit ) { 371 | this.scene.execute( 'dof_setAngularLowerLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 372 | }; 373 | Physijs.DOFConstraint.prototype.setAngularUpperLimit = function( limit ) { 374 | this.scene.execute( 'dof_setAngularUpperLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 375 | }; 376 | Physijs.DOFConstraint.prototype.enableAngularMotor = function( which ) { 377 | this.scene.execute( 'dof_enableAngularMotor', { constraint: this.id, which: which } ); 378 | }; 379 | Physijs.DOFConstraint.prototype.configureAngularMotor = function( which, low_angle, high_angle, velocity, max_force ) { 380 | this.scene.execute( 'dof_configureAngularMotor', { constraint: this.id, which: which, low_angle: low_angle, high_angle: high_angle, velocity: velocity, max_force: max_force } ); 381 | }; 382 | Physijs.DOFConstraint.prototype.disableAngularMotor = function( which ) { 383 | this.scene.execute( 'dof_disableAngularMotor', { constraint: this.id, which: which } ); 384 | }; 385 | 386 | // Physijs.Scene 387 | Physijs.Scene = function( params ) { 388 | var self = this; 389 | 390 | Eventable.call( this ); 391 | THREE.Scene.call( this ); 392 | 393 | this._worker = new Worker( Physijs.scripts.worker || 'physijs_worker.js' ); 394 | this._worker.transferableMessage = this._worker.webkitPostMessage || this._worker.postMessage; 395 | this._materials_ref_counts = {}; 396 | this._objects = {}; 397 | this._vehicles = {}; 398 | this._constraints = {}; 399 | 400 | var ab = new ArrayBuffer( 1 ); 401 | this._worker.transferableMessage( ab, [ab] ); 402 | SUPPORT_TRANSFERABLE = ( ab.byteLength === 0 ); 403 | 404 | this._worker.onmessage = function ( event ) { 405 | var _temp, 406 | data = event.data; 407 | 408 | if ( data instanceof ArrayBuffer && data.byteLength !== 1 ) { // byteLength === 1 is the worker making a SUPPORT_TRANSFERABLE test 409 | data = new Float32Array( data ); 410 | } 411 | 412 | if ( data instanceof Float32Array ) { 413 | 414 | // transferable object 415 | switch ( data[0] ) { 416 | case MESSAGE_TYPES.WORLDREPORT: 417 | self._updateScene( data ); 418 | break; 419 | 420 | case MESSAGE_TYPES.COLLISIONREPORT: 421 | self._updateCollisions( data ); 422 | break; 423 | 424 | case MESSAGE_TYPES.VEHICLEREPORT: 425 | self._updateVehicles( data ); 426 | break; 427 | 428 | case MESSAGE_TYPES.CONSTRAINTREPORT: 429 | self._updateConstraints( data ); 430 | break; 431 | } 432 | 433 | } else { 434 | 435 | if ( data.cmd ) { 436 | 437 | // non-transferable object 438 | switch ( data.cmd ) { 439 | case 'objectReady': 440 | _temp = data.params; 441 | if ( self._objects[ _temp ] ) { 442 | self._objects[ _temp ].dispatchEvent( 'ready' ); 443 | } 444 | break; 445 | 446 | case 'worldReady': 447 | self.dispatchEvent( 'ready' ); 448 | break; 449 | 450 | case 'vehicle': 451 | window.test = data; 452 | break; 453 | 454 | default: 455 | // Do nothing, just show the message 456 | console.debug('Received: ' + data.cmd); 457 | console.dir(data.params); 458 | break; 459 | } 460 | 461 | } else { 462 | 463 | switch ( data[0] ) { 464 | case MESSAGE_TYPES.WORLDREPORT: 465 | self._updateScene( data ); 466 | break; 467 | 468 | case MESSAGE_TYPES.COLLISIONREPORT: 469 | self._updateCollisions( data ); 470 | break; 471 | 472 | case MESSAGE_TYPES.VEHICLEREPORT: 473 | self._updateVehicles( data ); 474 | break; 475 | 476 | case MESSAGE_TYPES.CONSTRAINTREPORT: 477 | self._updateConstraints( data ); 478 | break; 479 | } 480 | 481 | } 482 | 483 | } 484 | }; 485 | 486 | 487 | params = params || {}; 488 | params.ammo = Physijs.scripts.ammo || 'ammo.js'; 489 | params.fixedTimeStep = params.fixedTimeStep || 1 / 60; 490 | params.rateLimit = params.rateLimit || true; 491 | this.execute( 'init', params ); 492 | }; 493 | Physijs.Scene.prototype = new THREE.Scene; 494 | Physijs.Scene.prototype.constructor = Physijs.Scene; 495 | Eventable.make( Physijs.Scene ); 496 | 497 | Physijs.Scene.prototype._updateScene = function( data ) { 498 | var num_objects = data[1], 499 | object, 500 | i, offset; 501 | 502 | for ( i = 0; i < num_objects; i++ ) { 503 | offset = 2 + i * REPORT_ITEMSIZE; 504 | object = this._objects[ data[ offset ] ]; 505 | 506 | if ( object === undefined ) { 507 | continue; 508 | } 509 | 510 | if ( object.__dirtyPosition === false ) { 511 | object.position.set( 512 | data[ offset + 1 ], 513 | data[ offset + 2 ], 514 | data[ offset + 3 ] 515 | ); 516 | } 517 | 518 | if ( object.__dirtyRotation === false ) { 519 | object.quaternion.set( 520 | data[ offset + 4 ], 521 | data[ offset + 5 ], 522 | data[ offset + 6 ], 523 | data[ offset + 7 ] 524 | ); 525 | } 526 | 527 | object._physijs.linearVelocity.set( 528 | data[ offset + 8 ], 529 | data[ offset + 9 ], 530 | data[ offset + 10 ] 531 | ); 532 | 533 | object._physijs.angularVelocity.set( 534 | data[ offset + 11 ], 535 | data[ offset + 12 ], 536 | data[ offset + 13 ] 537 | ); 538 | 539 | } 540 | 541 | if ( SUPPORT_TRANSFERABLE ) { 542 | // Give the typed array back to the worker 543 | this._worker.transferableMessage( data.buffer, [data.buffer] ); 544 | } 545 | 546 | _is_simulating = false; 547 | this.dispatchEvent( 'update' ); 548 | }; 549 | 550 | Physijs.Scene.prototype._updateVehicles = function( data ) { 551 | var vehicle, wheel, 552 | i, offset; 553 | 554 | for ( i = 0; i < ( data.length - 1 ) / VEHICLEREPORT_ITEMSIZE; i++ ) { 555 | offset = 1 + i * VEHICLEREPORT_ITEMSIZE; 556 | vehicle = this._vehicles[ data[ offset ] ]; 557 | 558 | if ( vehicle === undefined ) { 559 | continue; 560 | } 561 | 562 | wheel = vehicle.wheels[ data[ offset + 1 ] ]; 563 | 564 | wheel.position.set( 565 | data[ offset + 2 ], 566 | data[ offset + 3 ], 567 | data[ offset + 4 ] 568 | ); 569 | 570 | wheel.quaternion.set( 571 | data[ offset + 5 ], 572 | data[ offset + 6 ], 573 | data[ offset + 7 ], 574 | data[ offset + 8 ] 575 | ); 576 | } 577 | 578 | if ( SUPPORT_TRANSFERABLE ) { 579 | // Give the typed array back to the worker 580 | this._worker.transferableMessage( data.buffer, [data.buffer] ); 581 | } 582 | }; 583 | 584 | Physijs.Scene.prototype._updateConstraints = function( data ) { 585 | var constraint, object, 586 | i, offset; 587 | 588 | for ( i = 0; i < ( data.length - 1 ) / CONSTRAINTREPORT_ITEMSIZE; i++ ) { 589 | offset = 1 + i * CONSTRAINTREPORT_ITEMSIZE; 590 | constraint = this._constraints[ data[ offset ] ]; 591 | object = this._objects[ data[ offset + 1 ] ]; 592 | 593 | if ( constraint === undefined || object === undefined ) { 594 | continue; 595 | } 596 | 597 | _temp_vector3_1.set( 598 | data[ offset + 2 ], 599 | data[ offset + 3 ], 600 | data[ offset + 4 ] 601 | ); 602 | _temp_matrix4_1.extractRotation( object.matrix ); 603 | _temp_vector3_1.applyMatrix4( _temp_matrix4_1 ); 604 | 605 | constraint.positiona.addVectors( object.position, _temp_vector3_1 ); 606 | constraint.appliedImpulse = data[ offset + 5 ] ; 607 | } 608 | 609 | if ( SUPPORT_TRANSFERABLE ) { 610 | // Give the typed array back to the worker 611 | this._worker.transferableMessage( data.buffer, [data.buffer] ); 612 | } 613 | }; 614 | 615 | Physijs.Scene.prototype._updateCollisions = function( data ) { 616 | /** 617 | * #TODO 618 | * This is probably the worst way ever to handle collisions. The inherent evilness is a residual 619 | * effect from the previous version's evilness which mutated when switching to transferable objects. 620 | * 621 | * If you feel inclined to make this better, please do so. 622 | */ 623 | 624 | var i, j, offset, object, object2, 625 | collisions = {}, collided_with = [], normal_offsets = {}; 626 | 627 | // Build collision manifest 628 | for ( i = 0; i < data[1]; i++ ) { 629 | offset = 2 + i * COLLISIONREPORT_ITEMSIZE; 630 | object = data[ offset ]; 631 | object2 = data[ offset + 1 ]; 632 | 633 | normal_offsets[ object + '-' + object2 ] = offset + 2; 634 | normal_offsets[ object2 + '-' + object ] = -1 * ( offset + 2 ); 635 | 636 | if ( !collisions[ object ] ) collisions[ object ] = []; 637 | collisions[ object ].push( object2 ); 638 | } 639 | 640 | // Deal with collisions 641 | for ( object in this._objects ) { 642 | if ( !this._objects.hasOwnProperty( object ) ) return; 643 | object = this._objects[ object ]; 644 | 645 | if ( collisions[ object._physijs.id ] ) { 646 | 647 | // this object is touching others 648 | collided_with.length = 0; 649 | 650 | for ( j = 0; j < collisions[ object._physijs.id ].length; j++ ) { 651 | object2 = this._objects[ collisions[ object._physijs.id ][j] ]; 652 | 653 | if ( object2 ) { 654 | if ( object._physijs.touches.indexOf( object2._physijs.id ) === -1 ) { 655 | object._physijs.touches.push( object2._physijs.id ); 656 | 657 | _temp_vector3_1.subVectors( object.getLinearVelocity(), object2.getLinearVelocity() ); 658 | _temp1 = _temp_vector3_1.clone(); 659 | 660 | _temp_vector3_1.subVectors( object.getAngularVelocity(), object2.getAngularVelocity() ); 661 | _temp2 = _temp_vector3_1.clone(); 662 | 663 | var normal_offset = normal_offsets[ object._physijs.id + '-' + object2._physijs.id ]; 664 | if ( normal_offset > 0 ) { 665 | _temp_vector3_1.set( 666 | -data[ normal_offset ], 667 | -data[ normal_offset + 1 ], 668 | -data[ normal_offset + 2 ] 669 | ); 670 | } else { 671 | normal_offset *= -1; 672 | _temp_vector3_1.set( 673 | data[ normal_offset ], 674 | data[ normal_offset + 1 ], 675 | data[ normal_offset + 2 ] 676 | ); 677 | } 678 | 679 | object.dispatchEvent( 'collision', object2, _temp1, _temp2, _temp_vector3_1 ); 680 | object2.dispatchEvent( 'collision', object, _temp1, _temp2, _temp_vector3_1.negate() ); 681 | } 682 | 683 | collided_with.push( object2._physijs.id ); 684 | } 685 | } 686 | for ( j = 0; j < object._physijs.touches.length; j++ ) { 687 | if ( collided_with.indexOf( object._physijs.touches[j] ) === -1 ) { 688 | object._physijs.touches.splice( j--, 1 ); 689 | } 690 | } 691 | 692 | } else { 693 | 694 | // not touching other objects 695 | object._physijs.touches.length = 0; 696 | 697 | } 698 | 699 | } 700 | 701 | // if A is in B's collision list, then B should be in A's collision list 702 | for (var id in collisions) { 703 | if ( collisions.hasOwnProperty( id ) && collisions[id] ) { 704 | for ( j = 0; j < collisions[id].length; j++) { 705 | if (collisions[id][j]) { 706 | collisions[ collisions[id][j] ] = collisions[ collisions[id][j] ] || []; 707 | collisions[ collisions[id][j] ].push(id); 708 | } 709 | } 710 | } 711 | } 712 | 713 | this.collisions = collisions; 714 | 715 | if ( SUPPORT_TRANSFERABLE ) { 716 | // Give the typed array back to the worker 717 | this._worker.transferableMessage( data.buffer, [data.buffer] ); 718 | } 719 | }; 720 | 721 | Physijs.Scene.prototype.addConstraint = function ( constraint, show_marker ) { 722 | this._constraints[ constraint.id ] = constraint; 723 | this.execute( 'addConstraint', constraint.getDefinition() ); 724 | 725 | if ( show_marker ) { 726 | var marker; 727 | 728 | switch ( constraint.type ) { 729 | case 'point': 730 | marker = new THREE.Mesh( 731 | new THREE.SphereGeometry( 1.5 ), 732 | new THREE.MeshNormalMaterial 733 | ); 734 | marker.position.copy( constraint.positiona ); 735 | this._objects[ constraint.objecta ].add( marker ); 736 | break; 737 | 738 | case 'hinge': 739 | marker = new THREE.Mesh( 740 | new THREE.SphereGeometry( 1.5 ), 741 | new THREE.MeshNormalMaterial 742 | ); 743 | marker.position.copy( constraint.positiona ); 744 | this._objects[ constraint.objecta ].add( marker ); 745 | break; 746 | 747 | case 'slider': 748 | marker = new THREE.Mesh( 749 | new THREE.CubeGeometry( 10, 1, 1 ), 750 | new THREE.MeshNormalMaterial 751 | ); 752 | marker.position.copy( constraint.positiona ); 753 | // This rotation isn't right if all three axis are non-0 values 754 | // TODO: change marker's rotation order to ZYX 755 | marker.rotation.set( 756 | constraint.axis.y, // yes, y and 757 | constraint.axis.x, // x axis are swapped 758 | constraint.axis.z 759 | ); 760 | this._objects[ constraint.objecta ].add( marker ); 761 | break; 762 | 763 | case 'conetwist': 764 | marker = new THREE.Mesh( 765 | new THREE.SphereGeometry( 1.5 ), 766 | new THREE.MeshNormalMaterial 767 | ); 768 | marker.position.copy( constraint.positiona ); 769 | this._objects[ constraint.objecta ].add( marker ); 770 | break; 771 | 772 | case 'dof': 773 | marker = new THREE.Mesh( 774 | new THREE.SphereGeometry( 1.5 ), 775 | new THREE.MeshNormalMaterial 776 | ); 777 | marker.position.copy( constraint.positiona ); 778 | this._objects[ constraint.objecta ].add( marker ); 779 | break; 780 | } 781 | } 782 | 783 | return constraint; 784 | }; 785 | 786 | Physijs.Scene.prototype.removeConstraint = function( constraint ) { 787 | if ( this._constraints[constraint.id ] !== undefined ) { 788 | this.execute( 'removeConstraint', { id: constraint.id } ); 789 | delete this._constraints[ constraint.id ]; 790 | } 791 | }; 792 | 793 | Physijs.Scene.prototype.execute = function( cmd, params ) { 794 | this._worker.postMessage({ cmd: cmd, params: params }); 795 | }; 796 | 797 | addObjectChildren = function( parent, object ) { 798 | var i; 799 | 800 | for ( i = 0; i < object.children.length; i++ ) { 801 | if ( object.children[i]._physijs ) { 802 | object.children[i].updateMatrix(); 803 | object.children[i].updateMatrixWorld(); 804 | 805 | _temp_vector3_1.getPositionFromMatrix( object.children[i].matrixWorld ); 806 | _quaternion_1.setFromRotationMatrix( object.children[i].matrixWorld ); 807 | 808 | object.children[i]._physijs.position_offset = { 809 | x: _temp_vector3_1.x, 810 | y: _temp_vector3_1.y, 811 | z: _temp_vector3_1.z 812 | }; 813 | 814 | object.children[i]._physijs.rotation = { 815 | x: _quaternion_1.x, 816 | y: _quaternion_1.y, 817 | z: _quaternion_1.z, 818 | w: _quaternion_1.w 819 | }; 820 | 821 | parent._physijs.children.push( object.children[i]._physijs ); 822 | } 823 | 824 | addObjectChildren( parent, object.children[i] ); 825 | } 826 | }; 827 | 828 | Physijs.Scene.prototype.add = function( object ) { 829 | THREE.Mesh.prototype.add.call( this, object ); 830 | 831 | if ( object._physijs ) { 832 | 833 | object.world = this; 834 | 835 | if ( object instanceof Physijs.Vehicle ) { 836 | 837 | this.add( object.mesh ); 838 | this._vehicles[ object._physijs.id ] = object; 839 | this.execute( 'addVehicle', object._physijs ); 840 | 841 | } else { 842 | 843 | object.__dirtyPosition = false; 844 | object.__dirtyRotation = false; 845 | this._objects[object._physijs.id] = object; 846 | 847 | if ( object.children.length ) { 848 | object._physijs.children = []; 849 | addObjectChildren( object, object ); 850 | } 851 | 852 | if ( object.material._physijs ) { 853 | if ( !this._materials_ref_counts.hasOwnProperty( object.material._physijs.id ) ) { 854 | this.execute( 'registerMaterial', object.material._physijs ); 855 | object._physijs.materialId = object.material._physijs.id; 856 | this._materials_ref_counts[object.material._physijs.id] = 1; 857 | } else { 858 | this._materials_ref_counts[object.material._physijs.id]++; 859 | } 860 | } 861 | 862 | // Object starting position + rotation 863 | object._physijs.position = { x: object.position.x, y: object.position.y, z: object.position.z }; 864 | object._physijs.rotation = { x: object.quaternion.x, y: object.quaternion.y, z: object.quaternion.z, w: object.quaternion.w }; 865 | 866 | // Check for scaling 867 | var mass_scaling = new THREE.Vector3( 1, 1, 1 ); 868 | if ( object._physijs.width ) { 869 | object._physijs.width *= object.scale.x; 870 | } 871 | if ( object._physijs.height ) { 872 | object._physijs.height *= object.scale.y; 873 | } 874 | if ( object._physijs.depth ) { 875 | object._physijs.depth *= object.scale.z; 876 | } 877 | 878 | this.execute( 'addObject', object._physijs ); 879 | 880 | } 881 | } 882 | }; 883 | 884 | Physijs.Scene.prototype.remove = function( object ) { 885 | if ( object instanceof Physijs.Vehicle ) { 886 | this.execute( 'removeVehicle', { id: object._physijs.id } ); 887 | while( object.wheels.length ) { 888 | this.remove( object.wheels.pop() ); 889 | } 890 | this.remove( object.mesh ); 891 | delete this._vehicles[ object._physijs.id ]; 892 | } else { 893 | THREE.Mesh.prototype.remove.call( this, object ); 894 | if ( object._physijs ) { 895 | delete this._objects[object._physijs.id]; 896 | this.execute( 'removeObject', { id: object._physijs.id } ); 897 | } 898 | } 899 | if ( object.material && object.material._physijs && this._materials_ref_counts.hasOwnProperty( object.material._physijs.id ) ) { 900 | this._materials_ref_counts[object.material._physijs.id]--; 901 | if(this._materials_ref_counts[object.material._physijs.id] == 0) { 902 | this.execute( 'unRegisterMaterial', object.material._physijs ); 903 | delete this._materials_ref_counts[object.material._physijs.id]; 904 | } 905 | } 906 | }; 907 | 908 | Physijs.Scene.prototype.setFixedTimeStep = function( fixedTimeStep ) { 909 | if ( fixedTimeStep ) { 910 | this.execute( 'setFixedTimeStep', fixedTimeStep ); 911 | } 912 | }; 913 | 914 | Physijs.Scene.prototype.setGravity = function( gravity ) { 915 | if ( gravity ) { 916 | this.execute( 'setGravity', gravity ); 917 | } 918 | }; 919 | 920 | Physijs.Scene.prototype.simulate = function( timeStep, maxSubSteps ) { 921 | var object_id, object, update; 922 | 923 | if ( _is_simulating ) { 924 | return false; 925 | } 926 | 927 | _is_simulating = true; 928 | 929 | for ( object_id in this._objects ) { 930 | if ( !this._objects.hasOwnProperty( object_id ) ) continue; 931 | 932 | object = this._objects[object_id]; 933 | 934 | if ( object.__dirtyPosition || object.__dirtyRotation ) { 935 | update = { id: object._physijs.id }; 936 | 937 | if ( object.__dirtyPosition ) { 938 | update.pos = { x: object.position.x, y: object.position.y, z: object.position.z }; 939 | object.__dirtyPosition = false; 940 | } 941 | 942 | if ( object.__dirtyRotation ) { 943 | update.quat = { x: object.quaternion.x, y: object.quaternion.y, z: object.quaternion.z, w: object.quaternion.w }; 944 | object.__dirtyRotation = false; 945 | } 946 | 947 | this.execute( 'updateTransform', update ); 948 | } 949 | } 950 | 951 | this.execute( 'simulate', { timeStep: timeStep, maxSubSteps: maxSubSteps } ); 952 | 953 | return true; 954 | }; 955 | 956 | 957 | // Phsijs.Mesh 958 | Physijs.Mesh = function ( geometry, material, mass ) { 959 | var index; 960 | 961 | if ( !geometry ) { 962 | return; 963 | } 964 | 965 | Eventable.call( this ); 966 | THREE.Mesh.call( this, geometry, material ); 967 | 968 | if ( !geometry.boundingBox ) { 969 | geometry.computeBoundingBox(); 970 | } 971 | 972 | this._physijs = { 973 | type: null, 974 | id: getObjectId(), 975 | mass: mass || 0, 976 | touches: [], 977 | linearVelocity: new THREE.Vector3, 978 | angularVelocity: new THREE.Vector3 979 | }; 980 | }; 981 | Physijs.Mesh.prototype = new THREE.Mesh; 982 | Physijs.Mesh.prototype.constructor = Physijs.Mesh; 983 | Eventable.make( Physijs.Mesh ); 984 | 985 | // Physijs.Mesh.mass 986 | Physijs.Mesh.prototype.__defineGetter__('mass', function() { 987 | return this._physijs.mass; 988 | }); 989 | Physijs.Mesh.prototype.__defineSetter__('mass', function( mass ) { 990 | this._physijs.mass = mass; 991 | if ( this.world ) { 992 | this.world.execute( 'updateMass', { id: this._physijs.id, mass: mass } ); 993 | } 994 | }); 995 | 996 | // Physijs.Mesh.applyCentralImpulse 997 | Physijs.Mesh.prototype.applyCentralImpulse = function ( force ) { 998 | if ( this.world ) { 999 | this.world.execute( 'applyCentralImpulse', { id: this._physijs.id, x: force.x, y: force.y, z: force.z } ); 1000 | } 1001 | }; 1002 | 1003 | // Physijs.Mesh.applyImpulse 1004 | Physijs.Mesh.prototype.applyImpulse = function ( force, offset ) { 1005 | if ( this.world ) { 1006 | this.world.execute( 'applyImpulse', { id: this._physijs.id, impulse_x: force.x, impulse_y: force.y, impulse_z: force.z, x: offset.x, y: offset.y, z: offset.z } ); 1007 | } 1008 | }; 1009 | 1010 | // Physijs.Mesh.applyCentralForce 1011 | Physijs.Mesh.prototype.applyCentralForce = function ( force ) { 1012 | if ( this.world ) { 1013 | this.world.execute( 'applyCentralForce', { id: this._physijs.id, x: force.x, y: force.y, z: force.z } ); 1014 | } 1015 | }; 1016 | 1017 | // Physijs.Mesh.applyForce 1018 | Physijs.Mesh.prototype.applyForce = function ( force, offset ) { 1019 | if ( this.world ) { 1020 | this.world.execute( 'applyForce', { id: this._physijs.id, force_x: force.x, force_y : force.y, force_z : force.z, x: offset.x, y: offset.y, z: offset.z } ); 1021 | } 1022 | }; 1023 | 1024 | // Physijs.Mesh.getAngularVelocity 1025 | Physijs.Mesh.prototype.getAngularVelocity = function () { 1026 | return this._physijs.angularVelocity; 1027 | }; 1028 | 1029 | // Physijs.Mesh.setAngularVelocity 1030 | Physijs.Mesh.prototype.setAngularVelocity = function ( velocity ) { 1031 | if ( this.world ) { 1032 | this.world.execute( 'setAngularVelocity', { id: this._physijs.id, x: velocity.x, y: velocity.y, z: velocity.z } ); 1033 | } 1034 | }; 1035 | 1036 | // Physijs.Mesh.getLinearVelocity 1037 | Physijs.Mesh.prototype.getLinearVelocity = function () { 1038 | return this._physijs.linearVelocity; 1039 | }; 1040 | 1041 | // Physijs.Mesh.setLinearVelocity 1042 | Physijs.Mesh.prototype.setLinearVelocity = function ( velocity ) { 1043 | if ( this.world ) { 1044 | this.world.execute( 'setLinearVelocity', { id: this._physijs.id, x: velocity.x, y: velocity.y, z: velocity.z } ); 1045 | } 1046 | }; 1047 | 1048 | // Physijs.Mesh.setAngularFactor 1049 | Physijs.Mesh.prototype.setAngularFactor = function ( factor ) { 1050 | if ( this.world ) { 1051 | this.world.execute( 'setAngularFactor', { id: this._physijs.id, x: factor.x, y: factor.y, z: factor.z } ); 1052 | } 1053 | }; 1054 | 1055 | // Physijs.Mesh.setLinearFactor 1056 | Physijs.Mesh.prototype.setLinearFactor = function ( factor ) { 1057 | if ( this.world ) { 1058 | this.world.execute( 'setLinearFactor', { id: this._physijs.id, x: factor.x, y: factor.y, z: factor.z } ); 1059 | } 1060 | }; 1061 | 1062 | // Physijs.Mesh.setDamping 1063 | Physijs.Mesh.prototype.setDamping = function ( linear, angular ) { 1064 | if ( this.world ) { 1065 | this.world.execute( 'setDamping', { id: this._physijs.id, linear: linear, angular: angular } ); 1066 | } 1067 | }; 1068 | 1069 | // Physijs.Mesh.setCcdMotionThreshold 1070 | Physijs.Mesh.prototype.setCcdMotionThreshold = function ( threshold ) { 1071 | if ( this.world ) { 1072 | this.world.execute( 'setCcdMotionThreshold', { id: this._physijs.id, threshold: threshold } ); 1073 | } 1074 | }; 1075 | 1076 | // Physijs.Mesh.setCcdSweptSphereRadius 1077 | Physijs.Mesh.prototype.setCcdSweptSphereRadius = function ( radius ) { 1078 | if ( this.world ) { 1079 | this.world.execute( 'setCcdSweptSphereRadius', { id: this._physijs.id, radius: radius } ); 1080 | } 1081 | }; 1082 | 1083 | 1084 | // Physijs.PlaneMesh 1085 | Physijs.PlaneMesh = function ( geometry, material, mass ) { 1086 | var width, height; 1087 | 1088 | Physijs.Mesh.call( this, geometry, material, mass ); 1089 | 1090 | if ( !geometry.boundingBox ) { 1091 | geometry.computeBoundingBox(); 1092 | } 1093 | 1094 | width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1095 | height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1096 | 1097 | this._physijs.type = 'plane'; 1098 | this._physijs.normal = geometry.faces[0].normal.clone(); 1099 | this._physijs.mass = (typeof mass === 'undefined') ? width * height : mass; 1100 | }; 1101 | Physijs.PlaneMesh.prototype = new Physijs.Mesh; 1102 | Physijs.PlaneMesh.prototype.constructor = Physijs.PlaneMesh; 1103 | 1104 | // Physijs.HeightfieldMesh 1105 | Physijs.HeightfieldMesh = function ( geometry, material, mass, xdiv, ydiv) { 1106 | Physijs.Mesh.call( this, geometry, material, mass ); 1107 | 1108 | this._physijs.type = 'heightfield'; 1109 | this._physijs.xsize = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1110 | this._physijs.ysize = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1111 | this._physijs.xpts = (typeof xdiv === 'undefined') ? Math.sqrt(geometry.vertices.length) : xdiv + 1; 1112 | this._physijs.ypts = (typeof ydiv === 'undefined') ? Math.sqrt(geometry.vertices.length) : ydiv + 1; 1113 | // note - this assumes our plane geometry is square, unless we pass in specific xdiv and ydiv 1114 | this._physijs.absMaxHeight = Math.max(geometry.boundingBox.max.z,Math.abs(geometry.boundingBox.min.z)); 1115 | 1116 | var points = []; 1117 | 1118 | var a, b; 1119 | for ( var i = 0; i < geometry.vertices.length; i++ ) { 1120 | 1121 | a = i % this._physijs.xpts; 1122 | b = Math.round( ( i / this._physijs.xpts ) - ( (i % this._physijs.xpts) / this._physijs.xpts ) ); 1123 | points[i] = geometry.vertices[ a + ( ( this._physijs.ypts - b - 1 ) * this._physijs.ypts ) ].z; 1124 | 1125 | //points[i] = geometry.vertices[i]; 1126 | } 1127 | 1128 | this._physijs.points = points; 1129 | }; 1130 | Physijs.HeightfieldMesh.prototype = new Physijs.Mesh; 1131 | Physijs.HeightfieldMesh.prototype.constructor = Physijs.HeightfieldMesh; 1132 | 1133 | // Physijs.BoxMesh 1134 | Physijs.BoxMesh = function( geometry, material, mass ) { 1135 | var width, height, depth; 1136 | 1137 | Physijs.Mesh.call( this, geometry, material, mass ); 1138 | 1139 | if ( !geometry.boundingBox ) { 1140 | geometry.computeBoundingBox(); 1141 | } 1142 | 1143 | width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1144 | height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1145 | depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; 1146 | 1147 | this._physijs.type = 'box'; 1148 | this._physijs.width = width; 1149 | this._physijs.height = height; 1150 | this._physijs.depth = depth; 1151 | this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; 1152 | }; 1153 | Physijs.BoxMesh.prototype = new Physijs.Mesh; 1154 | Physijs.BoxMesh.prototype.constructor = Physijs.BoxMesh; 1155 | 1156 | 1157 | // Physijs.SphereMesh 1158 | Physijs.SphereMesh = function( geometry, material, mass ) { 1159 | Physijs.Mesh.call( this, geometry, material, mass ); 1160 | 1161 | if ( !geometry.boundingSphere ) { 1162 | geometry.computeBoundingSphere(); 1163 | } 1164 | 1165 | this._physijs.type = 'sphere'; 1166 | this._physijs.radius = geometry.boundingSphere.radius; 1167 | this._physijs.mass = (typeof mass === 'undefined') ? (4/3) * Math.PI * Math.pow(this._physijs.radius, 3) : mass; 1168 | }; 1169 | Physijs.SphereMesh.prototype = new Physijs.Mesh; 1170 | Physijs.SphereMesh.prototype.constructor = Physijs.SphereMesh; 1171 | 1172 | 1173 | // Physijs.CylinderMesh 1174 | Physijs.CylinderMesh = function( geometry, material, mass ) { 1175 | var width, height, depth; 1176 | 1177 | Physijs.Mesh.call( this, geometry, material, mass ); 1178 | 1179 | if ( !geometry.boundingBox ) { 1180 | geometry.computeBoundingBox(); 1181 | } 1182 | 1183 | width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1184 | height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1185 | depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; 1186 | 1187 | this._physijs.type = 'cylinder'; 1188 | this._physijs.width = width; 1189 | this._physijs.height = height; 1190 | this._physijs.depth = depth; 1191 | this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; 1192 | }; 1193 | Physijs.CylinderMesh.prototype = new Physijs.Mesh; 1194 | Physijs.CylinderMesh.prototype.constructor = Physijs.CylinderMesh; 1195 | 1196 | 1197 | // Physijs.CapsuleMesh 1198 | Physijs.CapsuleMesh = function( geometry, material, mass ) { 1199 | var width, height, depth; 1200 | 1201 | Physijs.Mesh.call( this, geometry, material, mass ); 1202 | 1203 | if ( !geometry.boundingBox ) { 1204 | geometry.computeBoundingBox(); 1205 | } 1206 | 1207 | width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1208 | height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1209 | depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; 1210 | 1211 | this._physijs.type = 'capsule'; 1212 | this._physijs.radius = Math.max(width / 2, depth / 2); 1213 | this._physijs.height = height; 1214 | this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; 1215 | }; 1216 | Physijs.CapsuleMesh.prototype = new Physijs.Mesh; 1217 | Physijs.CapsuleMesh.prototype.constructor = Physijs.CapsuleMesh; 1218 | 1219 | 1220 | // Physijs.ConeMesh 1221 | Physijs.ConeMesh = function( geometry, material, mass ) { 1222 | var width, height, depth; 1223 | 1224 | Physijs.Mesh.call( this, geometry, material, mass ); 1225 | 1226 | if ( !geometry.boundingBox ) { 1227 | geometry.computeBoundingBox(); 1228 | } 1229 | 1230 | width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1231 | height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1232 | 1233 | this._physijs.type = 'cone'; 1234 | this._physijs.radius = width / 2; 1235 | this._physijs.height = height; 1236 | this._physijs.mass = (typeof mass === 'undefined') ? width * height : mass; 1237 | }; 1238 | Physijs.ConeMesh.prototype = new Physijs.Mesh; 1239 | Physijs.ConeMesh.prototype.constructor = Physijs.ConeMesh; 1240 | 1241 | 1242 | // Physijs.ConcaveMesh 1243 | Physijs.ConcaveMesh = function( geometry, material, mass ) { 1244 | var i, 1245 | width, height, depth, 1246 | vertices, face, triangles = []; 1247 | 1248 | Physijs.Mesh.call( this, geometry, material, mass ); 1249 | 1250 | if ( !geometry.boundingBox ) { 1251 | geometry.computeBoundingBox(); 1252 | } 1253 | 1254 | vertices = geometry.vertices; 1255 | 1256 | for ( i = 0; i < geometry.faces.length; i++ ) { 1257 | face = geometry.faces[i]; 1258 | if ( face instanceof THREE.Face3) { 1259 | 1260 | triangles.push([ 1261 | { x: vertices[face.a].x, y: vertices[face.a].y, z: vertices[face.a].z }, 1262 | { x: vertices[face.b].x, y: vertices[face.b].y, z: vertices[face.b].z }, 1263 | { x: vertices[face.c].x, y: vertices[face.c].y, z: vertices[face.c].z } 1264 | ]); 1265 | 1266 | } else if ( face instanceof THREE.Face4 ) { 1267 | 1268 | triangles.push([ 1269 | { x: vertices[face.a].x, y: vertices[face.a].y, z: vertices[face.a].z }, 1270 | { x: vertices[face.b].x, y: vertices[face.b].y, z: vertices[face.b].z }, 1271 | { x: vertices[face.d].x, y: vertices[face.d].y, z: vertices[face.d].z } 1272 | ]); 1273 | triangles.push([ 1274 | { x: vertices[face.b].x, y: vertices[face.b].y, z: vertices[face.b].z }, 1275 | { x: vertices[face.c].x, y: vertices[face.c].y, z: vertices[face.c].z }, 1276 | { x: vertices[face.d].x, y: vertices[face.d].y, z: vertices[face.d].z } 1277 | ]); 1278 | 1279 | } 1280 | } 1281 | 1282 | width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1283 | height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1284 | depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; 1285 | 1286 | this._physijs.type = 'concave'; 1287 | this._physijs.triangles = triangles; 1288 | this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; 1289 | }; 1290 | Physijs.ConcaveMesh.prototype = new Physijs.Mesh; 1291 | Physijs.ConcaveMesh.prototype.constructor = Physijs.ConcaveMesh; 1292 | 1293 | 1294 | // Physijs.ConvexMesh 1295 | Physijs.ConvexMesh = function( geometry, material, mass ) { 1296 | var i, 1297 | width, height, depth, 1298 | points = []; 1299 | 1300 | Physijs.Mesh.call( this, geometry, material, mass ); 1301 | 1302 | if ( !geometry.boundingBox ) { 1303 | geometry.computeBoundingBox(); 1304 | } 1305 | 1306 | for ( i = 0; i < geometry.vertices.length; i++ ) { 1307 | points.push({ 1308 | x: geometry.vertices[i].x, 1309 | y: geometry.vertices[i].y, 1310 | z: geometry.vertices[i].z 1311 | }); 1312 | } 1313 | 1314 | 1315 | width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 1316 | height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; 1317 | depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; 1318 | 1319 | this._physijs.type = 'convex'; 1320 | this._physijs.points = points; 1321 | this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; 1322 | }; 1323 | Physijs.ConvexMesh.prototype = new Physijs.Mesh; 1324 | Physijs.ConvexMesh.prototype.constructor = Physijs.ConvexMesh; 1325 | 1326 | 1327 | // Physijs.Vehicle 1328 | Physijs.Vehicle = function( mesh, tuning ) { 1329 | tuning = tuning || new Physijs.VehicleTuning; 1330 | this.mesh = mesh; 1331 | this.wheels = []; 1332 | this._physijs = { 1333 | id: getObjectId(), 1334 | rigidBody: mesh._physijs.id, 1335 | suspension_stiffness: tuning.suspension_stiffness, 1336 | suspension_compression: tuning.suspension_compression, 1337 | suspension_damping: tuning.suspension_damping, 1338 | max_suspension_travel: tuning.max_suspension_travel, 1339 | friction_slip: tuning.friction_slip, 1340 | max_suspension_force: tuning.max_suspension_force 1341 | }; 1342 | }; 1343 | Physijs.Vehicle.prototype.addWheel = function( wheel_geometry, wheel_material, connection_point, wheel_direction, wheel_axle, suspension_rest_length, wheel_radius, is_front_wheel, tuning ) { 1344 | var wheel = new THREE.Mesh( wheel_geometry, wheel_material ); 1345 | wheel.castShadow = wheel.receiveShadow = true; 1346 | wheel.position.copy( wheel_direction ).multiplyScalar( suspension_rest_length / 100 ).add( connection_point ); 1347 | this.world.add( wheel ); 1348 | this.wheels.push( wheel ); 1349 | 1350 | this.world.execute( 'addWheel', { 1351 | id: this._physijs.id, 1352 | connection_point: { x: connection_point.x, y: connection_point.y, z: connection_point.z }, 1353 | wheel_direction: { x: wheel_direction.x, y: wheel_direction.y, z: wheel_direction.z }, 1354 | wheel_axle: { x: wheel_axle.x, y: wheel_axle.y, z: wheel_axle.z }, 1355 | suspension_rest_length: suspension_rest_length, 1356 | wheel_radius: wheel_radius, 1357 | is_front_wheel: is_front_wheel, 1358 | tuning: tuning 1359 | }); 1360 | }; 1361 | Physijs.Vehicle.prototype.setSteering = function( amount, wheel ) { 1362 | if ( wheel !== undefined && this.wheels[ wheel ] !== undefined ) { 1363 | this.world.execute( 'setSteering', { id: this._physijs.id, wheel: wheel, steering: amount } ); 1364 | } else if ( this.wheels.length > 0 ) { 1365 | for ( var i = 0; i < this.wheels.length; i++ ) { 1366 | this.world.execute( 'setSteering', { id: this._physijs.id, wheel: i, steering: amount } ); 1367 | } 1368 | } 1369 | }; 1370 | Physijs.Vehicle.prototype.setBrake = function( amount, wheel ) { 1371 | if ( wheel !== undefined && this.wheels[ wheel ] !== undefined ) { 1372 | this.world.execute( 'setBrake', { id: this._physijs.id, wheel: wheel, brake: amount } ); 1373 | } else if ( this.wheels.length > 0 ) { 1374 | for ( var i = 0; i < this.wheels.length; i++ ) { 1375 | this.world.execute( 'setBrake', { id: this._physijs.id, wheel: i, brake: amount } ); 1376 | } 1377 | } 1378 | }; 1379 | Physijs.Vehicle.prototype.applyEngineForce = function( amount, wheel ) { 1380 | if ( wheel !== undefined && this.wheels[ wheel ] !== undefined ) { 1381 | this.world.execute( 'applyEngineForce', { id: this._physijs.id, wheel: wheel, force: amount } ); 1382 | } else if ( this.wheels.length > 0 ) { 1383 | for ( var i = 0; i < this.wheels.length; i++ ) { 1384 | this.world.execute( 'applyEngineForce', { id: this._physijs.id, wheel: i, force: amount } ); 1385 | } 1386 | } 1387 | }; 1388 | 1389 | // Physijs.VehicleTuning 1390 | Physijs.VehicleTuning = function( suspension_stiffness, suspension_compression, suspension_damping, max_suspension_travel, friction_slip, max_suspension_force ) { 1391 | this.suspension_stiffness = suspension_stiffness !== undefined ? suspension_stiffness : 5.88; 1392 | this.suspension_compression = suspension_compression !== undefined ? suspension_compression : 0.83; 1393 | this.suspension_damping = suspension_damping !== undefined ? suspension_damping : 0.88; 1394 | this.max_suspension_travel = max_suspension_travel !== undefined ? max_suspension_travel : 500; 1395 | this.friction_slip = friction_slip !== undefined ? friction_slip : 10.5; 1396 | this.max_suspension_force = max_suspension_force !== undefined ? max_suspension_force : 6000; 1397 | }; 1398 | 1399 | return Physijs; 1400 | })(); 1401 | --------------------------------------------------------------------------------