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