├── LICENSE ├── README.md ├── dist ├── assets │ ├── audio │ │ ├── pulse.ogg │ │ └── spirit-of-the-girl.ogg │ ├── baller_base │ │ ├── baller_base.bin │ │ ├── baller_base.gltf │ │ ├── diffuse.jpg │ │ └── normal.jpg │ └── texture │ │ ├── ball_diffuse.jpg │ │ ├── ball_normal.jpg │ │ ├── ball_specular.jpg │ │ └── dot.png ├── index.html └── js │ ├── Tween.min.js │ ├── ammo.wasm.js │ ├── ammo.wasm.wasm │ ├── ammo.worker.js │ ├── comlink.js │ └── typed.js ├── logo.png ├── package.json ├── src ├── Application.js ├── Game.js ├── index.js ├── libs │ ├── CamControl.js │ ├── EventType.js │ ├── Misc.js │ ├── ProtonPlus.js │ ├── RealityBridge.js │ ├── ResourceCache.js │ ├── Stage.js │ ├── TextOverlay.js │ └── ViewPort.js └── threejs │ ├── GLTFLoader.js │ └── stats.js └── webpack.config.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 BlueMagnificent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | ----- 6 | 7 | A simple rolling ball browser based 3d game built with fun. [Play it here](https://bluemagnificent.com/baller/) 8 | 9 | ## Libraries Used 10 | 11 | 12 | * [three.js](https://github.com/mrdoob/three.js/) for the game engine 13 | * [ammo.js](https://github.com/kripken/ammo.js/) a javascript port of [Bullet](https://github.com/bulletphysics/bullet3) physics engine 14 | * [Comlink](https://github.com/GoogleChromeLabs/comlink) for smooth webworker communication 15 | * [tween.js](https://github.com/tweenjs/tween.js/) for object animation 16 | * [Proton](https://github.com/a-jie/Proton) as [three.proton](https://github.com/a-jie/three.proton) for particles 17 | * [typed.js](https://github.com/mattboldt/typed.js/) for text animation 18 | * and [async](https://github.com/caolan/async) 19 | 20 | Very worthy of mention is [Ammo.lab](https://github.com/lo-th/Ammo.lab) by [lo-th](https://github.com/lo-th) from which the bridging process of threejs and ammojs was derived. 21 | 22 | 23 | ## Credits 24 | ### Textures: 25 | `dist/assets/texture/dot.png` : gotten from three.proton project.
26 | Every other texture courtesy of [Yughues Nobiax](https://twitter.com/Yughues_Nobiax) 27 | 28 | ### Audio: 29 | `dist/assets/audio/spirit-of-the-girl.ogg` : A modified form of Music from https://filmmusic.io: "Spirit of the Girl" by Kevin MacLeod (https://incompetech.com)
30 | Licence: CC BY (https://creativecommons.org/licenses/by/4.0/) 31 | 32 | 33 | `dist/assets/audio/pulse.ogg`: Created by self
34 | Licence: CC 0 (https://creativecommons.org/publicdomain/zero/1.0/) 35 | 36 | 37 | ## To Run Baller 38 | You can either dowload the repo as zip or 39 | ``` 40 | git clone git://github.com/BlueMagnificent/baller.git 41 | ``` 42 | Navigate to the project directory:
43 | ``` 44 | cd ./baller 45 | ``` 46 | install dependencies: 47 | ``` 48 | npm install 49 | ``` 50 | 51 | then run it:
52 | ``` 53 | npm start 54 | ``` 55 | your browser should open up with the game if every thing was successful. 56 | 57 | ## Note 58 | You might notice slight physics anomalies, like the ball falling out of the board, well... just restart the game, we'll fix that up later :stuck_out_tongue_closed_eyes: 59 | 60 | Libraries such as tween.js, typed.js and Comnlink were added to the project as [webpack externals](https://webpack.js.org/configuration/externals/) 61 | 62 | ## ToDo (Not A Promise) 63 | * Upgrade the RealityBridge class to become a separate project of its own. 64 | * Find a way to tackle some of the physics anomalies. 65 | * Add support for mobile browsers 66 | 67 | ## License 68 | MIT 69 | -------------------------------------------------------------------------------- /dist/assets/audio/pulse.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/audio/pulse.ogg -------------------------------------------------------------------------------- /dist/assets/audio/spirit-of-the-girl.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/audio/spirit-of-the-girl.ogg -------------------------------------------------------------------------------- /dist/assets/baller_base/baller_base.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/baller_base/baller_base.bin -------------------------------------------------------------------------------- /dist/assets/baller_base/baller_base.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors" : [ 3 | { 4 | "bufferView" : 0, 5 | "componentType" : 5121, 6 | "count" : 84, 7 | "max" : [ 8 | 55 9 | ], 10 | "min" : [ 11 | 0 12 | ], 13 | "type" : "SCALAR" 14 | }, 15 | { 16 | "bufferView" : 1, 17 | "componentType" : 5126, 18 | "count" : 56, 19 | "max" : [ 20 | 15.0, 21 | 1.3300000429153442, 22 | 15.0 23 | ], 24 | "min" : [ 25 | -15.0, 26 | -0.6699999570846558, 27 | -15.0 28 | ], 29 | "type" : "VEC3" 30 | }, 31 | { 32 | "bufferView" : 2, 33 | "componentType" : 5126, 34 | "count" : 56, 35 | "max" : [ 36 | 1.0, 37 | 1.0, 38 | 1.0 39 | ], 40 | "min" : [ 41 | -1.0, 42 | -1.0, 43 | -1.0 44 | ], 45 | "type" : "VEC3" 46 | }, 47 | { 48 | "bufferView" : 3, 49 | "componentType" : 5126, 50 | "count" : 56, 51 | "max" : [ 52 | 1.0, 53 | 1.0, 54 | 0.40824753046035767, 55 | 1.0 56 | ], 57 | "min" : [ 58 | -1.0, 59 | -1.0, 60 | -0.408246785402298, 61 | 1.0 62 | ], 63 | "type" : "VEC4" 64 | }, 65 | { 66 | "bufferView" : 4, 67 | "componentType" : 5126, 68 | "count" : 56, 69 | "max" : [ 70 | 14.200021743774414, 71 | 8.514256477355957 72 | ], 73 | "min" : [ 74 | -13.200021743774414, 75 | -7.0836076736450195 76 | ], 77 | "type" : "VEC2" 78 | } 79 | ], 80 | "asset" : { 81 | "generator" : "Khronos Blender glTF 2.0 exporter", 82 | "version" : "2.0" 83 | }, 84 | "bufferViews" : [ 85 | { 86 | "buffer" : 0, 87 | "byteLength" : 84, 88 | "byteOffset" : 0, 89 | "target" : 34963 90 | }, 91 | { 92 | "buffer" : 0, 93 | "byteLength" : 672, 94 | "byteOffset" : 84, 95 | "target" : 34962 96 | }, 97 | { 98 | "buffer" : 0, 99 | "byteLength" : 672, 100 | "byteOffset" : 756, 101 | "target" : 34962 102 | }, 103 | { 104 | "buffer" : 0, 105 | "byteLength" : 896, 106 | "byteOffset" : 1428, 107 | "target" : 34962 108 | }, 109 | { 110 | "buffer" : 0, 111 | "byteLength" : 448, 112 | "byteOffset" : 2324, 113 | "target" : 34962 114 | } 115 | ], 116 | "buffers" : [ 117 | { 118 | "byteLength" : 2772, 119 | "uri" : "baller_base.bin" 120 | } 121 | ], 122 | "images" : [ 123 | { 124 | "name" : "diffuse", 125 | "uri" : "diffuse.jpg" 126 | }, 127 | { 128 | "name" : "normal", 129 | "uri" : "normal.jpg" 130 | } 131 | ], 132 | "materials" : [ 133 | { 134 | "name" : "base_mat", 135 | "pbrMetallicRoughness" : { 136 | "baseColorTexture" : { 137 | "index" : 0 138 | }, 139 | "metallicFactor" : 0.0 140 | }, 141 | "normalTexture" : { 142 | "index" : 1, 143 | "scale" : 3 144 | } 145 | } 146 | ], 147 | "meshes" : [ 148 | { 149 | "name" : "Cube.001", 150 | "primitives" : [ 151 | { 152 | "attributes" : { 153 | "NORMAL" : 2, 154 | "POSITION" : 1, 155 | "TANGENT" : 3, 156 | "TEXCOORD_0" : 4 157 | }, 158 | "indices" : 0, 159 | "material" : 0 160 | } 161 | ] 162 | } 163 | ], 164 | "nodes" : [ 165 | { 166 | "mesh" : 0, 167 | "name" : "baller_base" 168 | } 169 | ], 170 | "samplers" : [ 171 | {} 172 | ], 173 | "scene" : 0, 174 | "scenes" : [ 175 | { 176 | "name" : "Scene", 177 | "nodes" : [ 178 | 0 179 | ] 180 | } 181 | ], 182 | "textures" : [ 183 | { 184 | "sampler" : 0, 185 | "source" : 0 186 | }, 187 | { 188 | "sampler" : 0, 189 | "source" : 1 190 | } 191 | ] 192 | } 193 | -------------------------------------------------------------------------------- /dist/assets/baller_base/diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/baller_base/diffuse.jpg -------------------------------------------------------------------------------- /dist/assets/baller_base/normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/baller_base/normal.jpg -------------------------------------------------------------------------------- /dist/assets/texture/ball_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/texture/ball_diffuse.jpg -------------------------------------------------------------------------------- /dist/assets/texture/ball_normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/texture/ball_normal.jpg -------------------------------------------------------------------------------- /dist/assets/texture/ball_specular.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/texture/ball_specular.jpg -------------------------------------------------------------------------------- /dist/assets/texture/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/assets/texture/dot.png -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ..Baller.. 6 | 7 | 8 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /dist/js/Tween.min.js: -------------------------------------------------------------------------------- 1 | var TWEEN=TWEEN||function(){var n=[];return{getAll:function(){return n},removeAll:function(){n=[]},add:function(t){n.push(t)},remove:function(t){var r=n.indexOf(t);-1!==r&&n.splice(r,1)},update:function(t,r){if(0===n.length)return!1;var i=0;for(t=void 0!==t?t:TWEEN.now();in;n++)p[n].stop()},this.delay=function(n){return s=n,this},this.repeat=function(n){return e=n,this},this.yoyo=function(n){return a=n,this},this.easing=function(n){return l=n,this},this.interpolation=function(n){return E=n,this},this.chain=function(){return p=arguments,this},this.onStart=function(n){return d=n,this},this.onUpdate=function(n){return w=n,this},this.onComplete=function(n){return I=n,this},this.onStop=function(n){return M=n,this},this.update=function(n){var f,M,T;if(h>n)return!0;v===!1&&(null!==d&&d.call(t),v=!0),M=(n-h)/u,M=M>1?1:M,T=l(M);for(f in i)if(void 0!==r[f]){var N=r[f]||0,W=i[f];W instanceof Array?t[f]=E(W,T):("string"==typeof W&&(W="+"===W.charAt(0)||"-"===W.charAt(0)?N+parseFloat(W,10):parseFloat(W,10)),"number"==typeof W&&(t[f]=N+(W-N)*T))}if(null!==w&&w.call(t,T),1===M){if(e>0){isFinite(e)&&e--;for(f in o){if("string"==typeof i[f]&&(o[f]=o[f]+parseFloat(i[f],10)),a){var O=o[f];o[f]=i[f],i[f]=O}r[f]=o[f]}return a&&(c=!c),h=n+s,!0}null!==I&&I.call(t);for(var m=0,g=p.length;g>m;m++)p[m].start(h+u);return!1}return!0}},TWEEN.Easing={Linear:{None:function(n){return n}},Quadratic:{In:function(n){return n*n},Out:function(n){return n*(2-n)},InOut:function(n){return(n*=2)<1?.5*n*n:-.5*(--n*(n-2)-1)}},Cubic:{In:function(n){return n*n*n},Out:function(n){return--n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n:.5*((n-=2)*n*n+2)}},Quartic:{In:function(n){return n*n*n*n},Out:function(n){return 1- --n*n*n*n},InOut:function(n){return(n*=2)<1?.5*n*n*n*n:-.5*((n-=2)*n*n*n-2)}},Quintic:{In:function(n){return n*n*n*n*n},Out:function(n){return--n*n*n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n*n*n:.5*((n-=2)*n*n*n*n+2)}},Sinusoidal:{In:function(n){return 1-Math.cos(n*Math.PI/2)},Out:function(n){return Math.sin(n*Math.PI/2)},InOut:function(n){return.5*(1-Math.cos(Math.PI*n))}},Exponential:{In:function(n){return 0===n?0:Math.pow(1024,n-1)},Out:function(n){return 1===n?1:1-Math.pow(2,-10*n)},InOut:function(n){return 0===n?0:1===n?1:(n*=2)<1?.5*Math.pow(1024,n-1):.5*(-Math.pow(2,-10*(n-1))+2)}},Circular:{In:function(n){return 1-Math.sqrt(1-n*n)},Out:function(n){return Math.sqrt(1- --n*n)},InOut:function(n){return(n*=2)<1?-.5*(Math.sqrt(1-n*n)-1):.5*(Math.sqrt(1-(n-=2)*n)+1)}},Elastic:{In:function(n){return 0===n?0:1===n?1:-Math.pow(2,10*(n-1))*Math.sin(5*(n-1.1)*Math.PI)},Out:function(n){return 0===n?0:1===n?1:Math.pow(2,-10*n)*Math.sin(5*(n-.1)*Math.PI)+1},InOut:function(n){return 0===n?0:1===n?1:(n*=2,1>n?-.5*Math.pow(2,10*(n-1))*Math.sin(5*(n-1.1)*Math.PI):.5*Math.pow(2,-10*(n-1))*Math.sin(5*(n-1.1)*Math.PI)+1)}},Back:{In:function(n){var t=1.70158;return n*n*((t+1)*n-t)},Out:function(n){var t=1.70158;return--n*n*((t+1)*n+t)+1},InOut:function(n){var t=2.5949095;return(n*=2)<1?.5*(n*n*((t+1)*n-t)):.5*((n-=2)*n*((t+1)*n+t)+2)}},Bounce:{In:function(n){return 1-TWEEN.Easing.Bounce.Out(1-n)},Out:function(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375},InOut:function(n){return.5>n?.5*TWEEN.Easing.Bounce.In(2*n):.5*TWEEN.Easing.Bounce.Out(2*n-1)+.5}}},TWEEN.Interpolation={Linear:function(n,t){var r=n.length-1,i=r*t,o=Math.floor(i),u=TWEEN.Interpolation.Utils.Linear;return 0>t?u(n[0],n[1],i):t>1?u(n[r],n[r-1],r-i):u(n[o],n[o+1>r?r:o+1],i-o)},Bezier:function(n,t){for(var r=0,i=n.length-1,o=Math.pow,u=TWEEN.Interpolation.Utils.Bernstein,e=0;i>=e;e++)r+=o(1-t,i-e)*o(t,e)*n[e]*u(i,e);return r},CatmullRom:function(n,t){var r=n.length-1,i=r*t,o=Math.floor(i),u=TWEEN.Interpolation.Utils.CatmullRom;return n[0]===n[r]?(0>t&&(o=Math.floor(i=r*(1+t))),u(n[(o-1+r)%r],n[o],n[(o+1)%r],n[(o+2)%r],i-o)):0>t?n[0]-(u(n[0],n[0],n[1],n[1],-i)-n[0]):t>1?n[r]-(u(n[r],n[r],n[r-1],n[r-1],i-r)-n[r]):u(n[o?o-1:0],n[o],n[o+1>r?r:o+1],n[o+2>r?r:o+2],i-o)},Utils:{Linear:function(n,t,r){return(t-n)*r+n},Bernstein:function(n,t){var r=TWEEN.Interpolation.Utils.Factorial;return r(n)/r(t)/r(n-t)},Factorial:function(){var n=[1];return function(t){var r=1;if(n[t])return n[t];for(var i=t;i>1;i--)r*=i;return n[t]=r,r}}(),CatmullRom:function(n,t,r,i,o){var u=.5*(r-n),e=.5*(i-t),a=o*o,f=o*a;return(2*t-2*r+u+e)*f+(-3*t+3*r-2*u-e)*a+u*o+t}}},function(n){"function"==typeof define&&define.amd?define([],function(){return TWEEN}):"undefined"!=typeof module&&"object"==typeof exports?module.exports=TWEEN:void 0!==n&&(n.TWEEN=TWEEN)}(this); 2 | //# sourceMappingURL=Tween.min.js.map -------------------------------------------------------------------------------- /dist/js/ammo.wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/dist/js/ammo.wasm.wasm -------------------------------------------------------------------------------- /dist/js/ammo.worker.js: -------------------------------------------------------------------------------- 1 | // Original work Copyright © 2010-2017 three.js authors 2 | // Modified work Copyright 2019 BlueMagnificent 3 | 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | 23 | /** _ _____ _ _ 24 | * | | |_ _| |_| | 25 | * | |_ _| | | _ | 26 | * |___|_|_| |_| |_| 27 | * @author lo.th / http://lo-th.github.io/labs/ 28 | * AMMO worker ultimate 29 | * 30 | * By default, Bullet assumes units to be in meters and time in seconds. 31 | * Moving objects are assumed to be in the range of 0.05 units, about the size of a pebble, 32 | * to 10, the size of a truck. 33 | * The simulation steps in fraction of seconds (1/60 sec or 60 hertz), 34 | * and gravity in meters per square second (9.8 m/s^2). 35 | * 36 | * 37 | * --------------------------------------------------------------------------- 38 | * 39 | * Slightly modified by "Blue, The Magnificent" 40 | * 41 | */ 42 | 43 | 44 | 45 | //import comlink script 46 | importScripts('comlink.js'); 47 | 48 | var Module = { TOTAL_MEMORY: 256*1024*1024 }; 49 | 50 | var Ammo, start; 51 | 52 | var world = null; 53 | var worldInfo = null; 54 | var solver, collision, dispatcher, broadphase, ghostPairCallback; 55 | 56 | 57 | var trans, pos, quat, gravity; 58 | var tmpTrans, tmpPos, tmpQuat, origineTrans; 59 | var tmpPos1, tmpPos2, tmpPos3, tmpPos4, tmpZero; 60 | var tmpTrans1, tmpTrans2; 61 | var inertia; 62 | 63 | // forces 64 | var tmpForce = [];//null; 65 | 66 | // kinematic 67 | var tmpMatrix = []; 68 | 69 | //var tmpset = null; 70 | 71 | // array 72 | var bodys, solids, joints, terrains, contacts, contactGroups; 73 | // object 74 | var byName; 75 | 76 | var timeStep = 1/60; 77 | //var timerStep = timeStep * 1000; 78 | 79 | var numStep = 2;//4//3;// default is 1. 2 or more make simulation more accurate. 80 | var ddt = 1; 81 | var key = [ 0,0,0,0,0,0,0,0 ]; 82 | 83 | var debug = false; 84 | 85 | var ArLng, ArPos, ArMax; 86 | 87 | 88 | var fixedTime = 0.01667; 89 | var last_step = Date.now(); 90 | var timePassed = 0; 91 | 92 | var STATE = { 93 | ACTIVE : 1, 94 | ISLAND_SLEEPING : 2, 95 | WANTS_DEACTIVATION : 3, 96 | DISABLE_DEACTIVATION : 4, 97 | DISABLE_SIMULATION : 5 98 | } 99 | 100 | var FLAGS = { 101 | STATIC_OBJECT : 1, 102 | KINEMATIC_OBJECT : 2, 103 | NO_CONTACT_RESPONSE : 4, 104 | CUSTOM_MATERIAL_CALLBACK : 8, 105 | CHARACTER_OBJECT : 16, 106 | DISABLE_VISUALIZE_OBJECT : 32, 107 | DISABLE_SPU_COLLISION_PROCESSING : 64 108 | }; 109 | 110 | var GROUP = { 111 | DEFAULT : 1, 112 | STATIC : 2, 113 | KINEMATIC : 4, 114 | DEBRIS : 8, 115 | SENSORTRIGGER : 16, 116 | NOCOLLISION : 32, 117 | GROUP0 : 64, 118 | GROUP1 : 128, 119 | GROUP2 : 256, 120 | GROUP3 : 512, 121 | GROUP4 : 1024, 122 | GROUP5 : 2048, 123 | GROUP6 : 4096, 124 | GROUP7 : 8192, 125 | ALL : -1 126 | } 127 | 128 | 129 | //-------------------------------------------------- 130 | // 131 | // WORLD 132 | // 133 | //-------------------------------------------------- 134 | 135 | async function init ( o ) { 136 | 137 | debug = o.debug || false; 138 | 139 | ArLng = o.settings[0]; 140 | ArPos = o.settings[1]; 141 | ArMax = o.settings[2]; 142 | 143 | 144 | importScripts( o.blob ); 145 | 146 | await new Promise(resolve=>{ 147 | 148 | 149 | Ammo().then( function( Ammo ) { 150 | 151 | initMath(); 152 | 153 | // active transform 154 | 155 | trans = new Ammo.btTransform(); 156 | quat = new Ammo.btQuaternion(); 157 | pos = new Ammo.btVector3(); 158 | 159 | // tmp Transform 160 | 161 | origineTrans = new Ammo.btTransform(); 162 | 163 | tmpTrans = new Ammo.btTransform(); 164 | tmpPos = new Ammo.btVector3(); 165 | tmpQuat = new Ammo.btQuaternion(); 166 | 167 | //inertia 168 | inertia = new Ammo.btVector3( 0, 0, 0 ); 169 | 170 | // extra vector 171 | 172 | tmpPos1 = new Ammo.btVector3(); 173 | tmpPos2 = new Ammo.btVector3(); 174 | tmpPos3 = new Ammo.btVector3(); 175 | tmpPos4 = new Ammo.btVector3(); 176 | 177 | tmpZero = new Ammo.btVector3( 0,0,0 ); 178 | 179 | // extra transform 180 | 181 | tmpTrans1 = new Ammo.btTransform(); 182 | tmpTrans2 = new Ammo.btTransform(); 183 | 184 | // gravity 185 | gravity = new Ammo.btVector3(); 186 | 187 | addWorld(o); 188 | 189 | bodys = []; // 0 190 | joints = []; // 1 191 | terrains = []; 192 | solids = []; 193 | 194 | contacts = []; 195 | contactGroups = []; 196 | 197 | // use for get object by name 198 | byName = {}; 199 | 200 | resolve(); 201 | 202 | }); 203 | 204 | 205 | 206 | }) 207 | 208 | 209 | return true; 210 | 211 | }; 212 | 213 | function step( o ){ 214 | 215 | // ------- pre step 216 | 217 | key = o.key; 218 | 219 | // update matrix 220 | 221 | updateMatrix(); 222 | 223 | // update forces 224 | 225 | updateForce(); 226 | 227 | // terrain update 228 | 229 | terrainUpdate(); 230 | 231 | return runStepSimulation(o.timeStep || timeStep, numStep ); 232 | 233 | } 234 | 235 | function runStepSimulation(timeStep, numStep){ 236 | 237 | // ------- step 238 | world.stepSimulation(timeStep, numStep ); 239 | 240 | let Ar = []; 241 | 242 | stepRigidBody( Ar, ArPos[0] ); 243 | 244 | stepConstraint( Ar, ArPos[1] ); 245 | stepContact(); 246 | 247 | return { status: true, Ar:Ar, contacts:contacts }; 248 | } 249 | 250 | 251 | function setWorldParameter( o ){ 252 | 253 | o = o || {}; 254 | 255 | timeStep = o.timeStep !== undefined ? o.timeStep : 0.016; 256 | numStep = o.numStep !== undefined ? o.numStep : 2; 257 | 258 | // gravity 259 | setGravity( {g: o.gravity} ); 260 | 261 | 262 | // penetration 263 | var dInfo = world.getDispatchInfo(); 264 | if( o.penetration !== undefined ) dInfo.set_m_allowedCcdPenetration( o.penetration );// default 0.0399 265 | 266 | return true; 267 | 268 | } 269 | 270 | function reset ( o ) { 271 | 272 | 273 | tmpForce = []; 274 | tmpMatrix = []; 275 | 276 | clearContact(); 277 | clearJoint(); 278 | clearRigidBody(); 279 | clearTerrain(); 280 | 281 | // clear body name object 282 | byName = {}; 283 | 284 | if( o.full ){ 285 | 286 | clearWorld(); 287 | addWorld( o ); 288 | 289 | } 290 | 291 | setGravity(); 292 | 293 | return true; 294 | 295 | }; 296 | 297 | 298 | 299 | function wipe (obj) { 300 | for (var p in obj) { 301 | if ( obj.hasOwnProperty( p ) ) delete obj[p]; 302 | } 303 | }; 304 | 305 | //-------------------------------------------------- 306 | // 307 | // ADD 308 | // 309 | //-------------------------------------------------- 310 | 311 | function add ( o, extra ) { 312 | 313 | o.type = o.type === undefined ? 'box' : o.type; 314 | 315 | var type = o.type; 316 | var prev = o.type.substring( 0, 4 ); 317 | 318 | 319 | if( prev === 'join' ) addJoint( o ); 320 | else if( type === 'terrain' ) addTerrain( o ); 321 | else addRigidBody( o, extra ); 322 | 323 | }; 324 | 325 | 326 | 327 | function remove( o ){ 328 | 329 | if( o.name === undefined || o.name === null || o.name === '') return; 330 | 331 | let b = getByName( o.name ); 332 | 333 | if( b === null) return; 334 | 335 | delete byName[o.name]; 336 | 337 | bodys = bodys.filter( body => body !== b ); 338 | 339 | world.removeRigidBody( b ); 340 | Ammo.destroy( b ); 341 | 342 | b = null; 343 | 344 | } 345 | 346 | 347 | function removeArray( o ){ 348 | 349 | if( !Array.isArray( o ) ) return; 350 | 351 | o.forEach( obj => remove( obj )); 352 | 353 | } 354 | 355 | //-------------------------------------------------- 356 | // 357 | // RAY 358 | // 359 | //-------------------------------------------------- 360 | 361 | function addRay ( o ) { 362 | 363 | if( o.p1 !== undefined ) tmpPos1.fromArray( o.p1 ); 364 | if( o.p2 !== undefined ) tmpPos2.fromArray( o.p2 ); 365 | 366 | var rayCallback = new Ammo.ClosestRayResultCallback( tmpPos1, tmpPos2 ); 367 | world.rayTest( tmpPos1, tmpPos2, rayCallback ); 368 | 369 | //if(rayCallback.hasHit()){ 370 | // printf("Collision at: <%.2f, %.2f, %.2f>\n", rayCallback.m_hitPointWorld.getX(), rayCallback.m_hitPointWorld.getY(), rayCallback.m_hitPointWorld.getZ()); 371 | // } 372 | 373 | }; 374 | 375 | //-------------------------------------------------- 376 | // 377 | // GET OBJECT 378 | // 379 | //-------------------------------------------------- 380 | 381 | function getByName( n ){ 382 | 383 | return byName[ n ] || null; 384 | 385 | } 386 | 387 | function getByIdx( n ){ 388 | 389 | var u = n.toFixed(1); 390 | var id = parseInt( u ); 391 | var range = Number( u.substring( u.lastIndexOf('.') + 1 )); 392 | 393 | switch( range ){ 394 | 395 | case 1 : return bodys[id]; break; 396 | case 2 : return solids[id]; break; 397 | case 3 : return terrains[id]; break; 398 | case 4 : return joints[id]; break; 399 | 400 | } 401 | 402 | } 403 | 404 | 405 | //--------------------- 406 | // FORCES 407 | //--------------------- 408 | 409 | function updateForce () { 410 | 411 | while( tmpForce.length > 0 ) applyForce( tmpForce.pop() ); 412 | 413 | } 414 | 415 | function applyForce ( r ) { 416 | 417 | var b = getByName( r[0] ); 418 | 419 | if( b === null ) return; 420 | 421 | var type = r[1] || 'force'; 422 | 423 | if( r[2] !== undefined ) tmpPos1.fromArray( r[2] ); 424 | if( r[3] !== undefined ) tmpPos2.fromArray( r[3] ); 425 | else tmpPos2.zero(); 426 | 427 | switch( type ){ 428 | case 'force' : case 0 : b.applyForce( tmpPos1, tmpPos2 ); break;// force , rel_pos 429 | case 'torque' : case 1 : b.applyTorque( tmpPos1 ); break; 430 | case 'localTorque' : case 2 : b.applyLocalTorque( tmpPos1 ); break; 431 | case 'forceCentral' :case 3 : b.applyCentralForce( tmpPos1 ); break; 432 | case 'forceLocal' : case 4 : b.applyCentralLocalForce( tmpPos1 ); break; 433 | case 'impulse' : case 5 : b.applyImpulse( tmpPos1, tmpPos2 ); break;// impulse , rel_pos 434 | case 'impulseCentral' : case 6 : b.applyCentralImpulse( tmpPos1 ); break; 435 | 436 | // joint 437 | 438 | case 'motor' : case 7 : b.enableAngularMotor( true, r[2][0], r[2][1] ); break; // bool, targetVelocity, maxMotorImpulse 439 | 440 | } 441 | 442 | 443 | } 444 | 445 | //--------------------- 446 | // MATRIX 447 | //--------------------- 448 | 449 | function updateMatrix () { 450 | 451 | while( tmpMatrix.length > 0 ) applyMatrix( tmpMatrix.pop() ); 452 | 453 | } 454 | 455 | function applyMatrix ( r ) { 456 | 457 | var isOr = false; 458 | 459 | var b = getByName( r[0] ); 460 | 461 | if( b === undefined ) return; 462 | if( b === null ) return; 463 | 464 | var isK = b.isKinematic || false; 465 | 466 | if(r[3]){ // keep original position 467 | 468 | b.getMotionState().getWorldTransform( origineTrans ); 469 | var or = []; 470 | origineTrans.toArray( or ); 471 | var i = r[3].length, a; 472 | 473 | isOr = true; 474 | 475 | while(i--){ 476 | 477 | a = r[3][i]; 478 | if( a === 'x' ) r[1][0] = or[0]-r[1][0]; 479 | if( a === 'y' ) r[1][1] = or[1]-r[1][1]; 480 | if( a === 'z' ) r[1][2] = or[2]-r[1][2]; 481 | if( a === 'rot' ) r[2] = [ or[3], or[4], or[5], or[6] ]; 482 | 483 | } 484 | } 485 | 486 | 487 | 488 | tmpTrans.setIdentity(); 489 | 490 | if( r[1] !== undefined ) { tmpPos.fromArray( r[1] ); tmpTrans.setOrigin( tmpPos ); } 491 | if( r[2] !== undefined ) { tmpQuat.fromArray( r[2] ); tmpTrans.setRotation( tmpQuat ); } 492 | //else { tmpQuat.fromArray( [2] ); tmpTrans.setRotation( tmpQuat ); } 493 | 494 | if(!isK && !isOr){ 495 | 496 | // console.log('ss') 497 | 498 | // zero force 499 | b.setAngularVelocity( tmpZero ); 500 | b.setLinearVelocity( tmpZero ); 501 | 502 | } 503 | 504 | if(!isK ){ 505 | b.setWorldTransform( tmpTrans ); 506 | b.activate(); 507 | } else{ 508 | b.getMotionState().setWorldTransform( tmpTrans ); 509 | } 510 | 511 | } 512 | 513 | //-------------------------------------------------- 514 | // 515 | // WORLD 516 | // 517 | //-------------------------------------------------- 518 | 519 | function clearWorld () { 520 | 521 | //world.getBroadphase().resetPool( world.getDispatcher() ); 522 | //world.getConstraintSolver().reset(); 523 | 524 | Ammo.destroy( world ); 525 | Ammo.destroy( solver ); 526 | Ammo.destroy( collision ); 527 | Ammo.destroy( dispatcher ); 528 | Ammo.destroy( broadphase ); 529 | 530 | world = null; 531 | 532 | }; 533 | 534 | function addWorld ( o ) { 535 | 536 | o = o || {}; 537 | 538 | if( world !== null ) return; 539 | 540 | 541 | solver = new Ammo.btSequentialImpulseConstraintSolver(); 542 | collision = new Ammo.btDefaultCollisionConfiguration(); 543 | dispatcher = new Ammo.btCollisionDispatcher( collision ); 544 | 545 | switch( o.broadphase === undefined ? 2 : o.broadphase ){ 546 | 547 | //case 0: broadphase = new Ammo.btSimpleBroadphase(); break; 548 | case 1: var s = 1000; broadphase = new Ammo.btAxisSweep3( new Ammo.btVector3(-s,-s,-s), new Ammo.btVector3(s,s,s), 4096 ); break;//16384; 549 | case 2: broadphase = new Ammo.btDbvtBroadphase(); break; 550 | 551 | } 552 | 553 | world = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collision ); 554 | 555 | //console.log(world.getSolverInfo()) 556 | 557 | 558 | /* 559 | ghostPairCallback = new Ammo.btGhostPairCallback(); 560 | world.getPairCache().setInternalGhostPairCallback( ghostPairCallback ); 561 | */ 562 | 563 | var dInfo = world.getDispatchInfo(); 564 | 565 | if( o.penetration !== undefined ) dInfo.set_m_allowedCcdPenetration( o.penetration );// default 0.0399 566 | 567 | 568 | //console.log(world) 569 | 570 | 571 | 572 | //console.log(dInfo.get_m_convexConservativeDistanceThreshold()) 573 | 574 | /* 575 | 576 | dInfo.set_m_convexConservativeDistanceThreshold() // 0 577 | dInfo.set_m_dispatchFunc() // 1 578 | dInfo.set_m_enableSPU() // true 579 | dInfo.set_m_enableSatConvex() // false 580 | dInfo.set_m_stepCount() // 0 581 | dInfo.set_m_timeOfImpact() // 1 582 | dInfo.set_m_timeStep() // 0 583 | dInfo.set_m_useContinuous() // true 584 | dInfo.set_m_useConvexConservativeDistanceUtil() // false 585 | dInfo.set_m_useEpa() // true 586 | 587 | */ 588 | 589 | 590 | setGravity( o ); 591 | 592 | }; 593 | 594 | function setGravity ( o ) { 595 | 596 | o = o || {}; 597 | 598 | if( world === null ) return; 599 | 600 | gravity.fromArray( o.g || [0,-10, 0] ); 601 | world.setGravity( gravity ); 602 | 603 | }; 604 | 605 | 606 | 607 | 608 | 609 | //-------------------------------------------------- 610 | // 611 | // AMMO MATH 612 | // 613 | //-------------------------------------------------- 614 | 615 | var torad = 0.0174532925199432957; 616 | var todeg = 57.295779513082320876; 617 | 618 | //-------------------------------------------------- 619 | // 620 | // btTransform extend 621 | // 622 | //-------------------------------------------------- 623 | 624 | function initMath(){ 625 | 626 | 627 | 628 | Ammo.btTransform.prototype.toArray = function( array, offset ){ 629 | 630 | //if ( offset === undefined ) offset = 0; 631 | offset = offset || 0; 632 | 633 | this.getOrigin().toArray( array , offset ); 634 | this.getRotation().toArray( array , offset + 3 ); 635 | 636 | //return array; 637 | 638 | }; 639 | 640 | //-------------------------------------------------- 641 | // 642 | // btVector3 extend 643 | // 644 | //-------------------------------------------------- 645 | 646 | Ammo.btVector3.prototype.zero = function( v ){ 647 | 648 | this.setValue( 0, 0, 0 ); 649 | return this; 650 | 651 | }; 652 | 653 | Ammo.btVector3.prototype.negate = function( v ){ 654 | 655 | this.setValue( -this.x(), -this.y(), -this.z() ); 656 | return this; 657 | 658 | }; 659 | 660 | Ammo.btVector3.prototype.add = function( v ){ 661 | 662 | this.setValue( this.x() + v.x(), this.y() + v.y(), this.z() + v.z() ); 663 | return this; 664 | 665 | }; 666 | 667 | Ammo.btVector3.prototype.fromArray = function( array, offset ){ 668 | 669 | //if ( offset === undefined ) offset = 0; 670 | offset = offset || 0; 671 | 672 | this.setValue( array[ offset ], array[ offset + 1 ], array[ offset + 2 ] ); 673 | 674 | return this; 675 | 676 | }; 677 | 678 | Ammo.btVector3.prototype.toArray = function( array, offset ){ 679 | 680 | //if ( array === undefined ) array = []; 681 | //if ( offset === undefined ) offset = 0; 682 | offset = offset || 0; 683 | 684 | array[ offset ] = this.x(); 685 | array[ offset + 1 ] = this.y(); 686 | array[ offset + 2 ] = this.z(); 687 | 688 | //return array; 689 | 690 | }; 691 | 692 | Ammo.btVector3.prototype.direction = function( q ){ 693 | 694 | // quaternion 695 | 696 | var qx = q.x(); 697 | var qy = q.y(); 698 | var qz = q.z(); 699 | var qw = q.w(); 700 | 701 | var x = this.x(); 702 | var y = this.y(); 703 | var z = this.z(); 704 | 705 | // calculate quat * vector 706 | 707 | var ix = qw * x + qy * z - qz * y; 708 | var iy = qw * y + qz * x - qx * z; 709 | var iz = qw * z + qx * y - qy * x; 710 | var iw = - qx * x - qy * y - qz * z; 711 | 712 | // calculate result * inverse quat 713 | 714 | var xx = ix * qw + iw * - qx + iy * - qz - iz * - qy; 715 | var yy = iy * qw + iw * - qy + iz * - qx - ix * - qz; 716 | var zz = iz * qw + iw * - qz + ix * - qy - iy * - qx; 717 | 718 | this.setValue( xx, yy, zz ); 719 | 720 | }; 721 | 722 | //-------------------------------------------------- 723 | // 724 | // btQuaternion extend 725 | // 726 | //-------------------------------------------------- 727 | 728 | Ammo.btQuaternion.prototype.fromArray = function( array, offset ){ 729 | 730 | //if ( offset === undefined ) offset = 0; 731 | offset = offset || 0; 732 | this.setValue( array[ offset ], array[ offset + 1 ], array[ offset + 2 ], array[ offset + 3 ] ); 733 | 734 | }; 735 | 736 | Ammo.btQuaternion.prototype.toArray = function( array, offset ){ 737 | 738 | //if ( array === undefined ) array = []; 739 | //if ( offset === undefined ) offset = 0; 740 | offset = offset || 0; 741 | 742 | array[ offset ] = this.x(); 743 | array[ offset + 1 ] = this.y(); 744 | array[ offset + 2 ] = this.z(); 745 | array[ offset + 3 ] = this.w(); 746 | 747 | //return array; 748 | 749 | }; 750 | 751 | Ammo.btQuaternion.prototype.setFromAxisAngle = function( axis, angle ){ 752 | 753 | var halfAngle = angle * 0.5, s = Math.sin( halfAngle ); 754 | this.setValue( axis[0] * s, axis[1] * s, axis[2] * s, Math.cos( halfAngle ) ); 755 | 756 | }; 757 | 758 | /*Ammo.btTypedConstraint.prototype.getA = function( v ){ 759 | 760 | return 1 761 | 762 | };*/ 763 | 764 | 765 | } 766 | 767 | 768 | function force ( o ){ 769 | 770 | tmpForce.push( o ); 771 | 772 | } 773 | 774 | 775 | function forceArray (o ){ 776 | 777 | tmpForce = o 778 | 779 | } 780 | 781 | function matrix ( o ){ 782 | 783 | tmpMatrix.push( o ); 784 | 785 | } 786 | 787 | 788 | function matrixArray ( o ){ 789 | 790 | tmpMatrix = o; 791 | 792 | } 793 | 794 | function stepConstraint ( AR, N ) { 795 | 796 | //if( !joints.length ) return; 797 | 798 | joints.forEach( function ( b, id ) { 799 | 800 | var n = N + (id * 4); 801 | 802 | if( b.type ){ 803 | 804 | AR[ n ] = b.type; 805 | 806 | } 807 | 808 | 809 | }); 810 | 811 | }; 812 | 813 | function clearJoint () { 814 | 815 | var j; 816 | 817 | while( joints.length > 0 ){ 818 | 819 | j = joints.pop(); 820 | world.removeConstraint( j ); 821 | Ammo.destroy( j ); 822 | 823 | } 824 | 825 | joints = []; 826 | 827 | }; 828 | 829 | 830 | function addJoint ( o ) { 831 | 832 | var noAllowCollision = true; 833 | var collision = o.collision || false; 834 | if( collision ) noAllowCollision = false; 835 | 836 | if(o.body1) o.b1 = o.body1; 837 | if(o.body2) o.b2 = o.body2; 838 | 839 | var b1 = getByName( o.b1 ); 840 | var b2 = getByName( o.b2 ); 841 | 842 | tmpPos1.fromArray( o.pos1 || [0,0,0] ); 843 | tmpPos2.fromArray( o.pos2 || [0,0,0] ); 844 | tmpPos3.fromArray( o.axe1 || [1,0,0] ); 845 | tmpPos4.fromArray( o.axe2 || [1,0,0] ); 846 | 847 | 848 | if(o.type !== "joint_p2p" && o.type !== "joint_hinge" && o.type !== "joint" ){ 849 | 850 | // frame A 851 | 852 | tmpTrans1.setIdentity(); 853 | tmpTrans1.setOrigin( tmpPos1 ); 854 | if( o.quatA ){ 855 | tmpQuat.fromArray( o.quatA ); 856 | tmpTrans1.setRotation( tmpQuat ); 857 | } 858 | 859 | // frame B 860 | 861 | tmpTrans2.setIdentity(); 862 | tmpTrans2.setOrigin( tmpPos2 ); 863 | if( o.quatB ){ 864 | tmpQuat.fromArray( o.quatB ); 865 | tmpTrans2.setRotation( tmpQuat ); 866 | } 867 | 868 | } 869 | 870 | // use fixed frame A for linear llimits useLinearReferenceFrameA 871 | var useA = o.useA !== undefined ? o.useA : true; 872 | 873 | var joint = null; 874 | var t = 0; 875 | 876 | switch(o.type){ 877 | case "joint_p2p": 878 | t = 1; 879 | joint = new Ammo.btPoint2PointConstraint( b1, b2, tmpPos1, tmpPos2 ); 880 | if(o.strength) joint.get_m_setting().set_m_tau( o.strength ); 881 | if(o.damping) joint.get_m_setting().set_m_damping( o.damping ); 882 | if(o.impulse) joint.get_m_setting().set_m_impulseClamp( o.impulse ); 883 | break; 884 | case "joint_hinge": case "joint": t = 2; joint = new Ammo.btHingeConstraint( b1, b2, tmpPos1, tmpPos2, tmpPos3, tmpPos4, useA ); break; 885 | case "joint_slider": t = 3; joint = new Ammo.btSliderConstraint( b1, b2, tmpTrans1, tmpTrans2, useA ); break; 886 | case "joint_conetwist": t = 4; joint = new Ammo.btConeTwistConstraint( b1, b2, tmpTrans1, tmpTrans2 ); break; 887 | case "joint_dof": t = 5; joint = new Ammo.btGeneric6DofConstraint( b1, b2, tmpTrans1, tmpTrans2, useA ); break; 888 | case "joint_spring_dof": t = 6; joint = new Ammo.btGeneric6DofSpringConstraint( b1, b2, tmpTrans1, tmpTrans2, useA ); break; 889 | //case "joint_gear": joint = new Ammo.btGearConstraint( b1, b2, point1, point2, o.ratio || 1); break; 890 | } 891 | 892 | // EXTRA SETTING 893 | 894 | if(o.breaking) joint.setBreakingImpulseThreshold( o.breaking ); 895 | 896 | // hinge 897 | 898 | // limite min, limite max, softness, bias, relaxation 899 | if(o.limit){ 900 | if(o.type === 'joint_hinge' || o.type === 'joint' ) joint.setLimit( o.limit[0]*torad, o.limit[1]*torad, o.limit[2] || 0.9, o.limit[3] || 0.3, o.limit[4] || 1.0 ); 901 | else if(o.type === 'joint_conetwist' ) joint.setLimit( o.limit[0]*torad, o.limit[1]*torad, o.limit[2]*torad, o.limit[3] || 0.9, o.limit[4] || 0.3, o.limit[5] || 1.0 ); 902 | } 903 | if(o.motor) joint.enableAngularMotor( o.motor[0], o.motor[1], o.motor[2] ); 904 | 905 | 906 | // slider & dof 907 | 908 | if(o.linLower){ tmpPos.fromArray(o.linLower); joint.setLinearLowerLimit( tmpPos ); } 909 | if(o.linUpper){ tmpPos.fromArray(o.linUpper); joint.setLinearUpperLimit( tmpPos ); } 910 | 911 | if(o.angLower){ tmpPos.fromArray(o.angLower); joint.setAngularLowerLimit( tmpPos ); } 912 | if(o.angUpper){ tmpPos.fromArray(o.angUpper); joint.setAngularUpperLimit( tmpPos ); } 913 | 914 | // spring dof 915 | 916 | if(o.feedback) joint.enableFeedback( o.feedback ); 917 | if(o.enableSpring) joint.enableSpring( o.enableSpring[0], o.enableSpring[1] ); 918 | if(o.damping) joint.setDamping( o.damping[0], o.damping[1] ); 919 | if(o.stiffness) joint.setStiffness( o.stiffness[0], o.stiffness[1] ); 920 | 921 | if(o.angularOnly) joint.setAngularOnly( o.angularOnly ); 922 | if(o.enableMotor) joint.enableMotor( o.enableMotor ); 923 | if(o.maxMotorImpulse) joint.setMaxMotorImpulse( o.maxMotorImpulse ); 924 | if(o.motorTarget) joint.setMotorTarget( tmpQuat.fromArray( o.motorTarget ) ); 925 | 926 | 927 | // debug test 928 | joint.type = 0; 929 | if( o.debug ){ 930 | joint.type = t 931 | joint.bodyA = b1; 932 | joint.bodyB = b2; 933 | } 934 | 935 | world.addConstraint( joint, noAllowCollision ); 936 | 937 | if( o.name ) byName[o.name] = joint; 938 | 939 | joints.push( joint ); 940 | 941 | //console.log( joint ); 942 | 943 | o = null; 944 | 945 | }; 946 | 947 | 948 | 949 | 950 | /** _ _____ _ _ 951 | * | | |_ _| |_| | 952 | * | |_ _| | | _ | 953 | * |___|_|_| |_| |_| 954 | * @author lo.th / https://github.com/lo-th 955 | * AMMO CONTACT 956 | */ 957 | 958 | function addContact ( o ) { 959 | 960 | var id = contactGroups.length; 961 | 962 | var c = new Contact( o, id ); 963 | if( c.valide ){ 964 | contactGroups.push( c ); 965 | contacts.push(0); 966 | } 967 | 968 | }; 969 | 970 | function clearContact ( o ) { 971 | 972 | let ct = getContactByName( o ); 973 | 974 | if( ct ) { 975 | 976 | contactGroups = contactGroups.filter( val => val !== ct ); 977 | contacts.pop(); 978 | 979 | ct.clear(); 980 | ct = null; 981 | 982 | refreshContactIds(); 983 | 984 | return true 985 | 986 | } 987 | 988 | return false; 989 | 990 | }; 991 | 992 | 993 | function clearAllContact () { 994 | 995 | while( contactGroups.length > 0) contactGroups.pop().clear(); 996 | contactGroups = []; 997 | contacts = []; 998 | 999 | return true; 1000 | 1001 | }; 1002 | 1003 | 1004 | function disableContact ( o ){ 1005 | 1006 | let ct = getContactByName( o ); 1007 | if( ct ) ct.disable(); 1008 | 1009 | } 1010 | 1011 | 1012 | function disableAllContact ( ){ 1013 | 1014 | contactGroups.forEach((ct)=>{ 1015 | 1016 | ct.disable(); 1017 | 1018 | }) 1019 | 1020 | } 1021 | 1022 | 1023 | 1024 | function enableContact ( o ){ 1025 | 1026 | let ct = getContactByName( o ); 1027 | if( ct ) ct.enable(); 1028 | 1029 | } 1030 | 1031 | 1032 | function enableAllContact ( ){ 1033 | 1034 | contactGroups.forEach((ct)=>{ 1035 | 1036 | ct.enable(); 1037 | 1038 | }) 1039 | 1040 | } 1041 | 1042 | 1043 | function stepContact () { 1044 | 1045 | var i = contactGroups.length; 1046 | while( i-- ) contactGroups[i].step(); 1047 | 1048 | }; 1049 | 1050 | 1051 | function getContactByName( o ){ 1052 | 1053 | if( o.name === undefined || this.name === null) return null; 1054 | 1055 | let ct = null; 1056 | 1057 | contactGroups.forEach((val)=>{ 1058 | 1059 | if( val.name === o.name ) ct = val; 1060 | 1061 | }) 1062 | 1063 | return ct; 1064 | 1065 | } 1066 | 1067 | function refreshContactIds(){ 1068 | 1069 | contactGroups.forEach((ct, id)=>{ 1070 | 1071 | ct.id = id; 1072 | 1073 | }) 1074 | 1075 | } 1076 | 1077 | 1078 | 1079 | //-------------------------------------------------- 1080 | // 1081 | // CONTACT CLASS 1082 | // 1083 | //-------------------------------------------------- 1084 | 1085 | function Contact ( o, id ) { 1086 | 1087 | this.a = getByName( o.b1 ); 1088 | this.b = o.b2 !== undefined ? getByName( o.b2 ) : null; 1089 | this.name = o.name; 1090 | this.enabled = true; 1091 | 1092 | if( this.a !== null ){ 1093 | 1094 | this.id = id; 1095 | this.f = new Ammo.ConcreteContactResultCallback(); 1096 | this.f.addSingleResult = function(){ contacts[id] = 1; } 1097 | this.valide = true; 1098 | 1099 | } else { 1100 | 1101 | this.valide = false; 1102 | 1103 | } 1104 | 1105 | } 1106 | 1107 | Contact.prototype = { 1108 | 1109 | step: function () { 1110 | 1111 | if(!this.enabled) return; 1112 | 1113 | contacts[ this.id ] = 0; 1114 | if( this.b !== null ) world.contactPairTest( this.a, this.b, this.f ); 1115 | else world.contactTest( this.a, this.f ); 1116 | 1117 | }, 1118 | 1119 | clear: function () { 1120 | 1121 | this.a = null; 1122 | this.b = null; 1123 | Ammo.destroy( this.f ); 1124 | 1125 | }, 1126 | 1127 | enable: function () { this.enable = true; }, 1128 | 1129 | disable: function () { this.enable = false; } 1130 | 1131 | } 1132 | 1133 | //-------------------------------------------------- 1134 | // 1135 | // AMMO RIGIDBODY 1136 | // 1137 | //-------------------------------------------------- 1138 | 1139 | function stepRigidBody( AR, N ) { 1140 | 1141 | //if( !bodys.length ) return; 1142 | 1143 | 1144 | 1145 | bodys.forEach( function ( b, id ) { 1146 | 1147 | var n = N + (id * 8); 1148 | AR[n] = b.getLinearVelocity().length() * 9.8;//b.isActive() ? 1 : 0; 1149 | 1150 | //console.log(b.getLinearVelocity().length() * 9.8); 1151 | 1152 | if ( AR[n] > 0 ) { 1153 | 1154 | b.getMotionState().getWorldTransform( trans ); 1155 | 1156 | trans.toArray( AR, n + 1 ); 1157 | 1158 | //trans.getOrigin().toArray( Br , n + 1 ); 1159 | //trans.getRotation().toArray( Br ,n + 4 ); 1160 | 1161 | } 1162 | 1163 | }); 1164 | 1165 | }; 1166 | 1167 | function clearRigidBody () { 1168 | 1169 | var b; 1170 | 1171 | while( bodys.length > 0 ){ 1172 | 1173 | b = bodys.pop(); 1174 | world.removeRigidBody( b ); 1175 | Ammo.destroy( b ); 1176 | 1177 | } 1178 | 1179 | while( solids.length > 0 ){ 1180 | 1181 | b = solids.pop(); 1182 | //world.removeRigidBody( b ); 1183 | world.removeCollisionObject( b ); 1184 | Ammo.destroy( b ); 1185 | 1186 | } 1187 | 1188 | bodys = []; 1189 | solids = []; 1190 | 1191 | }; 1192 | 1193 | function addRigidBody ( o, extra ) { 1194 | 1195 | 1196 | var isKinematic = false; 1197 | 1198 | if( o.density !== undefined ) o.mass = o.density; 1199 | if( o.bounce !== undefined ) o.restitution = o.bounce; 1200 | 1201 | if( o.kinematic ){ 1202 | 1203 | o.flag = 2; 1204 | o.state = 4; 1205 | //o.mass = 0; 1206 | isKinematic = true; 1207 | 1208 | } 1209 | 1210 | o.mass = o.mass === undefined ? 0 : o.mass; 1211 | o.size = o.size === undefined ? [1,1,1] : o.size; 1212 | o.pos = o.pos === undefined ? [0,0,0] : o.pos; 1213 | o.quat = o.quat === undefined ? [0,0,0,1] : o.quat; 1214 | 1215 | var shape = null; 1216 | switch( o.type ){ 1217 | 1218 | case 'plane': 1219 | tmpPos4.fromArray( o.dir || [0,1,0] ); 1220 | shape = new Ammo.btStaticPlaneShape( tmpPos4, 0 ); 1221 | break; 1222 | 1223 | case 'box': 1224 | tmpPos4.setValue( o.size[0]*0.5, o.size[1]*0.5, o.size[2]*0.5 ); 1225 | shape = new Ammo.btBoxShape( tmpPos4 ); 1226 | break; 1227 | 1228 | case 'sphere': 1229 | shape = new Ammo.btSphereShape( o.size[0] ); 1230 | break; 1231 | 1232 | case 'cylinder': 1233 | tmpPos4.setValue( o.size[0], o.size[1]*0.5, o.size[2]*0.5 ); 1234 | shape = new Ammo.btCylinderShape( tmpPos4 ); 1235 | break; 1236 | 1237 | case 'cone': 1238 | shape = new Ammo.btConeShape( o.size[0], o.size[1]*0.5 ); 1239 | break; 1240 | 1241 | case 'capsule': 1242 | shape = new Ammo.btCapsuleShape( o.size[0], o.size[1]*0.5 ); 1243 | break; 1244 | 1245 | case 'compound': 1246 | //shape = new Ammo.btCompoundShape(); 1247 | shape = getCompundShape(o.shapes) 1248 | break; 1249 | 1250 | case 'mesh': 1251 | var mTriMesh = new Ammo.btTriangleMesh(); 1252 | var removeDuplicateVertices = true; 1253 | var vx = o.v; 1254 | for (var i = 0, fMax = vx.length; i < fMax; i+=9){ 1255 | tmpPos1.setValue( vx[i+0]*o.size[0], vx[i+1]*o.size[1], vx[i+2]*o.size[2] ); 1256 | tmpPos2.setValue( vx[i+3]*o.size[0], vx[i+4]*o.size[1], vx[i+5]*o.size[2] ); 1257 | tmpPos3.setValue( vx[i+6]*o.size[0], vx[i+7]*o.size[1], vx[i+8]*o.size[2] ); 1258 | mTriMesh.addTriangle( tmpPos1, tmpPos2, tmpPos3, removeDuplicateVertices ); 1259 | } 1260 | if(o.mass == 0){ 1261 | // btScaledBvhTriangleMeshShape -- if scaled instances 1262 | shape = new Ammo.btBvhTriangleMeshShape( mTriMesh, true, true ); 1263 | }else{ 1264 | // btGimpactTriangleMeshShape -- complex? 1265 | // btConvexHullShape -- possibly better? 1266 | shape = new Ammo.btConvexTriangleMeshShape( mTriMesh, true ); 1267 | } 1268 | break; 1269 | 1270 | case 'convex': 1271 | shape = new Ammo.btConvexHullShape(); 1272 | var vx = o.v; 1273 | for (var i = 0, fMax = vx.length; i < fMax; i+=3){ 1274 | vx[i]*=o.size[0]; 1275 | vx[i+1]*=o.size[1]; 1276 | vx[i+2]*=o.size[2]; 1277 | 1278 | tmpPos1.fromArray( vx , i ); 1279 | shape.addPoint( tmpPos1 ); 1280 | }; 1281 | break; 1282 | } 1283 | 1284 | if( o.margin !== undefined && shape.setMargin !== undefined ) shape.setMargin( o.margin ); 1285 | 1286 | if( extra == 'isShape' ) return shape; 1287 | 1288 | if( extra == 'isGhost' ){ 1289 | var ghost = new Ammo.btGhostObject(); 1290 | ghost.setCollisionShape( shape ); 1291 | ghost.setCollisionFlags( o.flag || 1 ); 1292 | //o.f = new Ammo.btGhostPairCallback(); 1293 | //world.getPairCache().setInternalGhostPairCallback( o.f ); 1294 | return ghost; 1295 | } 1296 | 1297 | tmpPos.fromArray( o.pos ); 1298 | tmpQuat.fromArray( o.quat ); 1299 | 1300 | tmpTrans.setIdentity(); 1301 | tmpTrans.setOrigin( tmpPos ); 1302 | tmpTrans.setRotation( tmpQuat ); 1303 | 1304 | tmpPos1.setValue( 0,0,0 ); 1305 | shape.calculateLocalInertia( o.mass, tmpPos1 ); 1306 | var motionState = new Ammo.btDefaultMotionState( tmpTrans ); 1307 | 1308 | var rbInfo = new Ammo.btRigidBodyConstructionInfo( o.mass, motionState, shape, tmpPos1 ); 1309 | 1310 | //console.log(rbInfo.get_m_friction(), rbInfo.get_m_restitution(), rbInfo.get_m_rollingFriction()); 1311 | 1312 | if( o.friction !== undefined ) rbInfo.set_m_friction( o.friction ); 1313 | if( o.restitution !== undefined ) rbInfo.set_m_restitution( o.restitution ); 1314 | //Damping is the proportion of velocity lost per second. 1315 | if( o.linear !== undefined ) rbInfo.set_m_linearDamping( o.linear ); 1316 | if( o.angular !== undefined ) rbInfo.set_m_angularDamping( o.angular ); 1317 | // prevents rounded shapes, such as spheres, cylinders and capsules from rolling forever. 1318 | if( o.rolling !== undefined ) rbInfo.set_m_rollingFriction( o.rolling ); 1319 | 1320 | 1321 | var body = new Ammo.btRigidBody( rbInfo ); 1322 | body.isKinematic = isKinematic; 1323 | 1324 | //if( o.friction !== undefined ) body.setFriction( o.friction ); 1325 | //if( o.restitution !== undefined ) body.setRestitution( o.restitution ); 1326 | // prevents rounded shapes, such as spheres, cylinders and capsules from rolling forever. 1327 | //if( o.rolling !== undefined ){ 1328 | // body.setRollingFriction( o.rolling ); 1329 | // missing function 1330 | //body.setAnisotropicFriction( shape.getAnisotropicRollingFrictionDirection(), 2 ); 1331 | //} 1332 | //Damping is the proportion of velocity lost per second. 1333 | //if( o.linear !== undefined ) body.setLinearFactor( o.linear ); 1334 | //if( o.angular !== undefined ) body.setAngularFactor( o.angular ); 1335 | 1336 | //console.log(body) 1337 | 1338 | 1339 | if( o.name ) byName[ o.name ] = body; 1340 | else if ( o.mass !== 0 ) byName[ bodys.length ] = body; 1341 | 1342 | if ( o.mass === 0 && !isKinematic){ 1343 | 1344 | body.setCollisionFlags( o.flag || 1 ); 1345 | world.addCollisionObject( body, o.group || 1, o.mask || -1 ); 1346 | solids.push( body ); 1347 | 1348 | } else { 1349 | 1350 | // body.isKinematic = isKinematic; 1351 | body.setCollisionFlags( o.flag || 0 ); 1352 | world.addRigidBody( body, o.group || 1, o.mask || -1 ); 1353 | 1354 | 1355 | /*var n = bodys.length; 1356 | tmpPos.toArray( Br, n + 1 ); 1357 | tmpQuat.toArray( Br, n + 4 );*/ 1358 | 1359 | //body.activate(); 1360 | /* 1361 | AMMO.ACTIVE = 1; 1362 | AMMO.ISLAND_SLEEPING = 2; 1363 | AMMO.WANTS_DEACTIVATION = 3; 1364 | AMMO.DISABLE_DEACTIVATION = 4; 1365 | AMMO.DISABLE_SIMULATION = 5; 1366 | */ 1367 | body.setActivationState( o.state || 1 ); 1368 | bodys.push( body ); 1369 | 1370 | if(o.linearVelocity !== undefined) body.setLinearVelocity(new Ammo.btVector3( o.linearVelocity[0], o.linearVelocity[1], o.linearVelocity[2] ) ); 1371 | 1372 | } 1373 | 1374 | 1375 | //if ( o.mass === 0 && !isKinematic) solids.push( body ); 1376 | //else bodys.push( body ); 1377 | 1378 | 1379 | //Ammo.destroy( startTransform ); 1380 | //Ammo.destroy( localInertia ); 1381 | Ammo.destroy( rbInfo ); 1382 | 1383 | o = null; 1384 | 1385 | }; 1386 | 1387 | 1388 | function getCompundShape(shapes){ 1389 | 1390 | let compoundShape = new Ammo.btCompoundShape(); 1391 | 1392 | //let masses = []; 1393 | 1394 | 1395 | shapes.forEach(el=>{ 1396 | 1397 | tmpPos.fromArray( el.pos ); 1398 | tmpQuat.fromArray( el.quat ); 1399 | 1400 | tmpTrans.setIdentity(); 1401 | tmpTrans.setOrigin( tmpPos ); 1402 | tmpTrans.setRotation( tmpQuat ); 1403 | 1404 | let shape = addRigidBody(el, 'isShape'); 1405 | compoundShape.addChildShape( tmpTrans, shape ); 1406 | 1407 | //masses.push( btScalar(el.mass || 1) ); 1408 | }) 1409 | 1410 | return compoundShape; 1411 | 1412 | } 1413 | 1414 | 1415 | /** _ _____ _ _ 1416 | * | | |_ _| |_| | 1417 | * | |_ _| | | _ | 1418 | * |___|_|_| |_| |_| 1419 | * @author lo.th / https://github.com/lo-th 1420 | * AMMO TERRAIN 1421 | */ 1422 | 1423 | 1424 | function terrainPostStep ( o ){ 1425 | 1426 | var name = o.name; 1427 | if( byName[ name ] ) byName[ name ].setData( o.heightData ); 1428 | 1429 | } 1430 | 1431 | function terrainUpdate ( o ){ 1432 | 1433 | var i = terrains.length; 1434 | while(i--) terrains[ i ].update(); 1435 | 1436 | } 1437 | 1438 | function addTerrain ( o ) { 1439 | 1440 | var terrain = new Terrain( o ); 1441 | byName[ terrain.name ] = terrain; 1442 | terrains.push( terrain ); 1443 | 1444 | } 1445 | 1446 | function clearTerrain () { 1447 | 1448 | while( terrains.length > 0) terrains.pop().clear(); 1449 | terrains = []; 1450 | 1451 | }; 1452 | 1453 | //-------------------------------------------------- 1454 | // 1455 | // TERRAIN CLASS 1456 | // 1457 | //-------------------------------------------------- 1458 | 1459 | function Terrain ( o ) { 1460 | 1461 | this.needsUpdate = false; 1462 | this.data = null; 1463 | this.tmpData = null; 1464 | this.dataHeap = null; 1465 | 1466 | var name = o.name === undefined ? 'terrain' : o.name; 1467 | var size = o.size === undefined ? [1,1,1] : o.size; 1468 | var sample = o.sample === undefined ? [64,64] : o.sample; 1469 | var pos = o.pos === undefined ? [0,0,0] : o.pos; 1470 | var quat = o.quat === undefined ? [0,0,0,1] : o.quat; 1471 | 1472 | var mass = o.mass === undefined ? 0 : o.mass; 1473 | var margin = o.margin === undefined ? 0.02 : o.margin; 1474 | var friction = o.friction === undefined ? 0.5 : o.friction; 1475 | var restitution = o.restitution === undefined ? 0 : o.restitution; 1476 | 1477 | var flag = o.flag === undefined ? 1 : o.flag; 1478 | var group = o.group === undefined ? 1 : o.group; 1479 | var mask = o.mask === undefined ? -1 : o.mask; 1480 | 1481 | // This parameter is not really used, since we are using PHY_FLOAT height data type and hence it is ignored 1482 | var heightScale = o.heightScale === undefined ? 1 : o.heightScale; 1483 | 1484 | // Up axis = 0 for X, 1 for Y, 2 for Z. Normally 1 = Y is used. 1485 | var upAxis = o.upAxis === undefined ? 1 : o.upAxis; 1486 | 1487 | // hdt, height data type. "PHY_FLOAT" is used. Possible values are "PHY_FLOAT", "PHY_UCHAR", "PHY_SHORT" 1488 | var hdt = o.hdt || "PHY_FLOAT"; 1489 | 1490 | // Set this to your needs (inverts the triangles) 1491 | var flipEdge = o.flipEdge !== undefined ? o.flipEdge : false; 1492 | 1493 | // Creates height data buffer in Ammo heap 1494 | this.setData( o.heightData ); 1495 | this.update(); 1496 | 1497 | //var shape = new Ammo.btHeightfieldTerrainShape( sample[0], sample[1], terrainData[name], heightScale, -size[1], size[1], upAxis, hdt, flipEdge ); 1498 | var shape = new Ammo.btHeightfieldTerrainShape( sample[0], sample[1], this.data, heightScale, -size[1], size[1], upAxis, hdt, flipEdge ); 1499 | 1500 | //console.log(shape.getMargin()) 1501 | 1502 | tmpPos2.setValue( size[0]/sample[0], 1, size[2]/sample[1] ); 1503 | shape.setLocalScaling( tmpPos2 ); 1504 | 1505 | shape.setMargin( margin ); 1506 | 1507 | tmpPos.fromArray( pos ); 1508 | tmpQuat.fromArray( quat ); 1509 | 1510 | tmpTrans.setIdentity(); 1511 | tmpTrans.setOrigin( tmpPos ); 1512 | tmpTrans.setRotation( tmpQuat ); 1513 | 1514 | tmpPos1.setValue( 0,0,0 ); 1515 | //shape.calculateLocalInertia( mass, tmpPos1 ); 1516 | var motionState = new Ammo.btDefaultMotionState( tmpTrans ); 1517 | 1518 | var rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, shape, tmpPos1 ); 1519 | 1520 | rbInfo.set_m_friction( friction ); 1521 | rbInfo.set_m_restitution( restitution ); 1522 | 1523 | var body = new Ammo.btRigidBody( rbInfo ); 1524 | body.setCollisionFlags( flag ); 1525 | world.addCollisionObject( body, group, mask ); 1526 | 1527 | //solids.push( body ); 1528 | 1529 | this.name = name; 1530 | this.body = body; 1531 | 1532 | Ammo.destroy( rbInfo ); 1533 | 1534 | o = null; 1535 | 1536 | } 1537 | 1538 | Terrain.prototype = { 1539 | 1540 | setData: function ( data ) { 1541 | 1542 | this.tmpData = data; 1543 | this.nDataBytes = this.tmpData.length * this.tmpData.BYTES_PER_ELEMENT; 1544 | this.needsUpdate = true; 1545 | 1546 | }, 1547 | 1548 | update: function () { 1549 | 1550 | if( !this.needsUpdate ) return; 1551 | 1552 | this.malloc(); 1553 | //this.data = Malloc_Float( this.tmpData, this.data ); 1554 | //console.log(this.data) 1555 | self.postMessage({ m:'terrain', o:{ name:this.name } }); 1556 | this.needsUpdate = false; 1557 | this.tmpData = null; 1558 | 1559 | }, 1560 | 1561 | clear: function (){ 1562 | 1563 | world.removeCollisionObject( this.body ); 1564 | Ammo.destroy( this.body ); 1565 | Ammo._free( this.dataHeap.byteOffset ); 1566 | //Ammo.destroy( this.data ); 1567 | 1568 | this.body = null; 1569 | this.data = null; 1570 | this.tmpData = null; 1571 | this.dataHeap = null; 1572 | 1573 | }, 1574 | 1575 | malloc: function (){ 1576 | 1577 | //var nDataBytes = this.tmpData.length * this.tmpData.BYTES_PER_ELEMENT; 1578 | if( this.data === null ) this.data = Ammo._malloc( this.nDataBytes ); 1579 | this.dataHeap = new Uint8Array( Ammo.HEAPU8.buffer, this.data, this.nDataBytes ); 1580 | this.dataHeap.set( new Uint8Array( this.tmpData.buffer ) ); 1581 | 1582 | }, 1583 | 1584 | } 1585 | /* 1586 | function terrain_data ( name ){ 1587 | 1588 | var d = tmpData[name]; 1589 | terrainData[name] = Malloc_Float( d, terrainData[name] ); 1590 | 1591 | /* 1592 | var i = d.length, n; 1593 | // Creates height data buffer in Ammo heap 1594 | if( terrainData[name] == null ) terrainData[name] = Ammo._malloc( 4 * i ); 1595 | // Copy the javascript height data array to the Ammo one. 1596 | 1597 | while(i--){ 1598 | n = (i * 4); 1599 | Ammo.HEAPF32[ terrainData[name] + n >> 2 ] = d[i]; 1600 | } 1601 | */ 1602 | 1603 | /* self.postMessage({ m:'terrain', o:{ name:name } }); 1604 | 1605 | }; 1606 | */ 1607 | 1608 | 1609 | function Malloc_Float( f, q ) { 1610 | 1611 | var nDataBytes = f.length * f.BYTES_PER_ELEMENT; 1612 | if( q === undefined ) q = Ammo._malloc( nDataBytes ); 1613 | var dataHeap = new Uint8Array( Ammo.HEAPU8.buffer, q, nDataBytes ); 1614 | dataHeap.set( new Uint8Array( f.buffer ) ); 1615 | return q; 1616 | 1617 | } 1618 | 1619 | 1620 | function testWorker(){ 1621 | 1622 | console.log('Hello From Worker'); 1623 | 1624 | } 1625 | 1626 | 1627 | 1628 | function logOutput (msg){ 1629 | 1630 | if(!debug) return; 1631 | 1632 | console.log(msg); 1633 | } 1634 | 1635 | 1636 | Comlink.expose(self, self); 1637 | 1638 | 1639 | -------------------------------------------------------------------------------- /dist/js/comlink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | (function (factory) { 14 | if (typeof module === "object" && typeof module.exports === "object") { 15 | var v = factory(require, exports); 16 | if (v !== undefined) module.exports = v; 17 | } 18 | else if (typeof define === "function" && define.amd) { 19 | define(["require", "exports"], factory); 20 | } 21 | else {factory([], self.Comlink={});} 22 | })(function (require, exports) { 23 | "use strict"; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | const TRANSFERABLE_TYPES = ["ArrayBuffer", "MessagePort", "OffscreenCanvas"] 26 | .filter(f => f in self) 27 | .map(f => self[f]); 28 | const uid = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); 29 | const proxyValueSymbol = Symbol("proxyValue"); 30 | const throwSymbol = Symbol("throw"); 31 | const proxyTransferHandler = { 32 | canHandle: (obj) => obj && obj[proxyValueSymbol], 33 | serialize: (obj) => { 34 | const { port1, port2 } = new MessageChannel(); 35 | expose(obj, port1); 36 | return port2; 37 | }, 38 | deserialize: (obj) => { 39 | return proxy(obj); 40 | } 41 | }; 42 | const throwTransferHandler = { 43 | canHandle: (obj) => obj && obj[throwSymbol], 44 | serialize: (obj) => { 45 | const message = obj && obj.message; 46 | const stack = obj && obj.stack; 47 | return Object.assign({}, obj, { message, stack }); 48 | }, 49 | deserialize: (obj) => { 50 | throw Object.assign(Error(), obj); 51 | } 52 | }; 53 | exports.transferHandlers = new Map([ 54 | ["PROXY", proxyTransferHandler], 55 | ["THROW", throwTransferHandler] 56 | ]); 57 | let pingPongMessageCounter = 0; 58 | function proxy(endpoint, target) { 59 | if (isWindow(endpoint)) 60 | endpoint = windowEndpoint(endpoint); 61 | if (!isEndpoint(endpoint)) 62 | throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined"); 63 | activateEndpoint(endpoint); 64 | return cbProxy(async (irequest) => { 65 | let args = []; 66 | if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") 67 | args = irequest.argumentsList.map(wrapValue); 68 | const response = await pingPongMessage(endpoint, Object.assign({}, irequest, { argumentsList: args }), transferableProperties(args)); 69 | const result = response.data; 70 | return unwrapValue(result.value); 71 | }, [], target); 72 | } 73 | exports.proxy = proxy; 74 | function proxyValue(obj) { 75 | obj[proxyValueSymbol] = true; 76 | return obj; 77 | } 78 | exports.proxyValue = proxyValue; 79 | function expose(rootObj, endpoint) { 80 | if (isWindow(endpoint)) 81 | endpoint = windowEndpoint(endpoint); 82 | if (!isEndpoint(endpoint)) 83 | throw Error("endpoint does not have all of addEventListener, removeEventListener and postMessage defined"); 84 | activateEndpoint(endpoint); 85 | attachMessageHandler(endpoint, async function (event) { 86 | if (!event.data.id || !event.data.callPath) 87 | return; 88 | const irequest = event.data; 89 | let that = await irequest.callPath 90 | .slice(0, -1) 91 | .reduce((obj, propName) => obj[propName], rootObj); 92 | let obj = await irequest.callPath.reduce((obj, propName) => obj[propName], rootObj); 93 | let iresult = obj; 94 | let args = []; 95 | if (irequest.type === "APPLY" || irequest.type === "CONSTRUCT") 96 | args = irequest.argumentsList.map(unwrapValue); 97 | if (irequest.type === "APPLY") { 98 | try { 99 | iresult = await obj.apply(that, args); 100 | } 101 | catch (e) { 102 | iresult = e; 103 | iresult[throwSymbol] = true; 104 | } 105 | } 106 | if (irequest.type === "CONSTRUCT") { 107 | try { 108 | iresult = new obj(...args); // eslint-disable-line new-cap 109 | iresult = proxyValue(iresult); 110 | } 111 | catch (e) { 112 | iresult = e; 113 | iresult[throwSymbol] = true; 114 | } 115 | } 116 | if (irequest.type === "SET") { 117 | obj[irequest.property] = irequest.value; 118 | // FIXME: ES6 Proxy Handler `set` methods are supposed to return a 119 | // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯ 120 | iresult = true; 121 | } 122 | iresult = makeInvocationResult(iresult); 123 | iresult.id = irequest.id; 124 | return endpoint.postMessage(iresult, transferableProperties([iresult])); 125 | }); 126 | } 127 | exports.expose = expose; 128 | function wrapValue(arg) { 129 | // Is arg itself handled by a TransferHandler? 130 | for (const [key, transferHandler] of exports.transferHandlers) { 131 | if (transferHandler.canHandle(arg)) { 132 | return { 133 | type: key, 134 | value: transferHandler.serialize(arg) 135 | }; 136 | } 137 | } 138 | // If not, traverse the entire object and find handled values. 139 | let wrappedChildren = []; 140 | for (const item of iterateAllProperties(arg)) { 141 | for (const [key, transferHandler] of exports.transferHandlers) { 142 | if (transferHandler.canHandle(item.value)) { 143 | wrappedChildren.push({ 144 | path: item.path, 145 | wrappedValue: { 146 | type: key, 147 | value: transferHandler.serialize(item.value) 148 | } 149 | }); 150 | } 151 | } 152 | } 153 | for (const wrappedChild of wrappedChildren) { 154 | const container = wrappedChild.path 155 | .slice(0, -1) 156 | .reduce((obj, key) => obj[key], arg); 157 | container[wrappedChild.path[wrappedChild.path.length - 1]] = null; 158 | } 159 | return { 160 | type: "RAW", 161 | value: arg, 162 | wrappedChildren 163 | }; 164 | } 165 | function unwrapValue(arg) { 166 | if (exports.transferHandlers.has(arg.type)) { 167 | const transferHandler = exports.transferHandlers.get(arg.type); 168 | return transferHandler.deserialize(arg.value); 169 | } 170 | else if (isRawWrappedValue(arg)) { 171 | for (const wrappedChildValue of arg.wrappedChildren || []) { 172 | if (!exports.transferHandlers.has(wrappedChildValue.wrappedValue.type)) 173 | throw Error(`Unknown value type "${arg.type}" at ${wrappedChildValue.path.join(".")}`); 174 | const transferHandler = exports.transferHandlers.get(wrappedChildValue.wrappedValue.type); 175 | const newValue = transferHandler.deserialize(wrappedChildValue.wrappedValue.value); 176 | replaceValueInObjectAtPath(arg.value, wrappedChildValue.path, newValue); 177 | } 178 | return arg.value; 179 | } 180 | else { 181 | throw Error(`Unknown value type "${arg.type}"`); 182 | } 183 | } 184 | function replaceValueInObjectAtPath(obj, path, newVal) { 185 | const lastKey = path.slice(-1)[0]; 186 | const lastObj = path 187 | .slice(0, -1) 188 | .reduce((obj, key) => obj[key], obj); 189 | lastObj[lastKey] = newVal; 190 | } 191 | function isRawWrappedValue(arg) { 192 | return arg.type === "RAW"; 193 | } 194 | function windowEndpoint(w) { 195 | if (self.constructor.name !== "Window") 196 | throw Error("self is not a window"); 197 | return { 198 | addEventListener: self.addEventListener.bind(self), 199 | removeEventListener: self.removeEventListener.bind(self), 200 | postMessage: (msg, transfer) => w.postMessage(msg, "*", transfer) 201 | }; 202 | } 203 | function isEndpoint(endpoint) { 204 | return ("addEventListener" in endpoint && 205 | "removeEventListener" in endpoint && 206 | "postMessage" in endpoint); 207 | } 208 | function activateEndpoint(endpoint) { 209 | if (isMessagePort(endpoint)) 210 | endpoint.start(); 211 | } 212 | function attachMessageHandler(endpoint, f) { 213 | // Checking all possible types of `endpoint` manually satisfies TypeScript’s 214 | // type checker. Not sure why the inference is failing here. Since it’s 215 | // unnecessary code I’m going to resort to `any` for now. 216 | // if(isWorker(endpoint)) 217 | // endpoint.addEventListener('message', f); 218 | // if(isMessagePort(endpoint)) 219 | // endpoint.addEventListener('message', f); 220 | // if(isOtherWindow(endpoint)) 221 | // endpoint.addEventListener('message', f); 222 | endpoint.addEventListener("message", f); 223 | } 224 | function detachMessageHandler(endpoint, f) { 225 | // Same as above. 226 | endpoint.removeEventListener("message", f); 227 | } 228 | function isMessagePort(endpoint) { 229 | return endpoint.constructor.name === "MessagePort"; 230 | } 231 | function isWindow(endpoint) { 232 | // TODO: This doesn’t work on cross-origin iframes. 233 | // return endpoint.constructor.name === 'Window'; 234 | return ["window", "length", "location", "parent", "opener"].every(prop => prop in endpoint); 235 | } 236 | /** 237 | * `pingPongMessage` sends a `postMessage` and waits for a reply. Replies are 238 | * identified by a unique id that is attached to the payload. 239 | */ 240 | function pingPongMessage(endpoint, msg, transferables) { 241 | const id = `${uid}-${pingPongMessageCounter++}`; 242 | return new Promise(resolve => { 243 | attachMessageHandler(endpoint, function handler(event) { 244 | if (event.data.id !== id) 245 | return; 246 | detachMessageHandler(endpoint, handler); 247 | resolve(event); 248 | }); 249 | // Copy msg and add `id` property 250 | msg = Object.assign({}, msg, { id }); 251 | endpoint.postMessage(msg, transferables); 252 | }); 253 | } 254 | function cbProxy(cb, callPath = [], target = function () { }) { 255 | return new Proxy(target, { 256 | construct(_target, argumentsList, proxy) { 257 | return cb({ 258 | type: "CONSTRUCT", 259 | callPath, 260 | argumentsList 261 | }); 262 | }, 263 | apply(_target, _thisArg, argumentsList) { 264 | // We use `bind` as an indicator to have a remote function bound locally. 265 | // The actual target for `bind()` is currently ignored. 266 | if (callPath[callPath.length - 1] === "bind") 267 | return cbProxy(cb, callPath.slice(0, -1)); 268 | return cb({ 269 | type: "APPLY", 270 | callPath, 271 | argumentsList 272 | }); 273 | }, 274 | get(_target, property, proxy) { 275 | if (property === "then" && callPath.length === 0) { 276 | return { then: () => proxy }; 277 | } 278 | else if (property === "then") { 279 | const r = cb({ 280 | type: "GET", 281 | callPath 282 | }); 283 | return Promise.resolve(r).then.bind(r); 284 | } 285 | else { 286 | return cbProxy(cb, callPath.concat(property), _target[property]); 287 | } 288 | }, 289 | set(_target, property, value, _proxy) { 290 | return cb({ 291 | type: "SET", 292 | callPath, 293 | property, 294 | value 295 | }); 296 | } 297 | }); 298 | } 299 | function isTransferable(thing) { 300 | return TRANSFERABLE_TYPES.some(type => thing instanceof type); 301 | } 302 | function* iterateAllProperties(value, path = [], visited = null) { 303 | if (!value) 304 | return; 305 | if (!visited) 306 | visited = new WeakSet(); 307 | if (visited.has(value)) 308 | return; 309 | if (typeof value === "string") 310 | return; 311 | if (typeof value === "object") 312 | visited.add(value); 313 | if (ArrayBuffer.isView(value)) 314 | return; 315 | yield { value, path }; 316 | const keys = Object.keys(value); 317 | for (const key of keys) 318 | yield* iterateAllProperties(value[key], [...path, key], visited); 319 | } 320 | function transferableProperties(obj) { 321 | const r = []; 322 | for (const prop of iterateAllProperties(obj)) { 323 | if (isTransferable(prop.value)) 324 | r.push(prop.value); 325 | } 326 | return r; 327 | } 328 | function makeInvocationResult(obj) { 329 | for (const [type, transferHandler] of exports.transferHandlers) { 330 | if (transferHandler.canHandle(obj)) { 331 | const value = transferHandler.serialize(obj); 332 | return { 333 | value: { type, value } 334 | }; 335 | } 336 | } 337 | return { 338 | value: { 339 | type: "RAW", 340 | value: obj 341 | } 342 | }; 343 | } 344 | }); 345 | -------------------------------------------------------------------------------- /dist/js/typed.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 3 | * typed.js - A JavaScript Typing Animation Library 4 | * Author: Matt Boldt 5 | * Version: v2.0.9 6 | * Url: https://github.com/mattboldt/typed.js 7 | * License(s): MIT 8 | * 9 | */ 10 | (function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Typed=e():t.Typed=e()})(this,function(){return function(t){function e(n){if(s[n])return s[n].exports;var i=s[n]={exports:{},id:n,loaded:!1};return t[n].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var s={};return e.m=t,e.c=s,e.p="",e(0)}([function(t,e,s){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=function(){function t(t,e){for(var s=0;st.length)););var u=t.substring(0,e),l=t.substring(u.length+1,e+i),c=t.substring(e+i+1);t=u+l+c,i--}s.timeout=setTimeout(function(){s.toggleBlinking(!1),e===t.length?s.doneTyping(t,e):s.keepTyping(t,e,i),s.temporaryPause&&(s.temporaryPause=!1,s.options.onTypingResumed(s.arrayPos,s))},n)},n))}},{key:"keepTyping",value:function(t,e,s){0===e&&(this.toggleBlinking(!1),this.options.preStringTyped(this.arrayPos,this)),e+=s;var n=t.substr(0,e);this.replaceText(n),this.typewrite(t,e)}},{key:"doneTyping",value:function(t,e){var s=this;this.options.onStringTyped(this.arrayPos,this),this.toggleBlinking(!0),this.arrayPos===this.strings.length-1&&(this.complete(),this.loop===!1||this.curLoop===this.loopCount)||(this.timeout=setTimeout(function(){s.backspace(t,e)},this.backDelay))}},{key:"backspace",value:function(t,e){var s=this;if(this.pause.status===!0)return void this.setPauseStatus(t,e,!0);if(this.fadeOut)return this.initFadeOut();this.toggleBlinking(!1);var n=this.humanizer(this.backSpeed);this.timeout=setTimeout(function(){e=o.htmlParser.backSpaceHtmlChars(t,e,s);var n=t.substr(0,e);if(s.replaceText(n),s.smartBackspace){var i=s.strings[s.arrayPos+1];i&&n===i.substr(0,e)?s.stopNum=e:s.stopNum=0}e>s.stopNum?(e--,s.backspace(t,e)):e<=s.stopNum&&(s.arrayPos++,s.arrayPos===s.strings.length?(s.arrayPos=0,s.options.onLastStringBackspaced(),s.shuffleStringsIfNeeded(),s.begin()):s.typewrite(s.strings[s.sequence[s.arrayPos]],e))},n)}},{key:"complete",value:function(){this.options.onComplete(this),this.loop?this.curLoop++:this.typingComplete=!0}},{key:"setPauseStatus",value:function(t,e,s){this.pause.typewrite=s,this.pause.curString=t,this.pause.curStrPos=e}},{key:"toggleBlinking",value:function(t){this.cursor&&(this.pause.status||this.cursorBlinking!==t&&(this.cursorBlinking=t,t?this.cursor.classList.add("typed-cursor--blink"):this.cursor.classList.remove("typed-cursor--blink")))}},{key:"humanizer",value:function(t){return Math.round(Math.random()*t/2)+t}},{key:"shuffleStringsIfNeeded",value:function(){this.shuffle&&(this.sequence=this.sequence.sort(function(){return Math.random()-.5}))}},{key:"initFadeOut",value:function(){var t=this;return this.el.className+=" "+this.fadeOutClass,this.cursor&&(this.cursor.className+=" "+this.fadeOutClass),setTimeout(function(){t.arrayPos++,t.replaceText(""),t.strings.length>t.arrayPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],0):(t.typewrite(t.strings[0],0),t.arrayPos=0)},this.fadeOutDelay)}},{key:"replaceText",value:function(t){this.attr?this.el.setAttribute(this.attr,t):this.isInput?this.el.value=t:"html"===this.contentType?this.el.innerHTML=t:this.el.textContent=t}},{key:"bindFocusEvents",value:function(){var t=this;this.isInput&&(this.el.addEventListener("focus",function(e){t.stop()}),this.el.addEventListener("blur",function(e){t.el.value&&0!==t.el.value.length||t.start()}))}},{key:"insertCursor",value:function(){this.showCursor&&(this.cursor||(this.cursor=document.createElement("span"),this.cursor.className="typed-cursor",this.cursor.innerHTML=this.cursorChar,this.el.parentNode&&this.el.parentNode.insertBefore(this.cursor,this.el.nextSibling)))}}]),t}();e["default"]=a,t.exports=e["default"]},function(t,e,s){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var r=Object.assign||function(t){for(var e=1;e":";";t.substr(e+1).charAt(0)!==i&&(e++,!(e+1>t.length)););e++}return e}},{key:"backSpaceHtmlChars",value:function(t,e,s){if("html"!==s.contentType)return e;var n=t.substr(e).charAt(0);if(">"===n||";"===n){var i="";for(i=">"===n?"<":"&";t.substr(e-1).charAt(0)!==i&&(e--,!(e<0)););e--}return e}}]),t}();e["default"]=i;var r=new i;e.htmlParser=r}])}); 11 | //# sourceMappingURL=typed.min.js.map -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueMagnificent/baller/22ba281273c7b5268010835ad15122a972bdc2a1/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "baller", 3 | "version": "1.0.0", 4 | "description": "simple browser based 3d game", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --mode development --open", 8 | "build": "webpack" 9 | }, 10 | "keywords": [ 11 | "webgl", 12 | "threejs", 13 | "webworker", 14 | "ammojs", 15 | "3dgame" 16 | ], 17 | "author": "BlueMagnificent ", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/BlueMagnificent/baller.git" 21 | }, 22 | "license": "MIT", 23 | "devDependencies": { 24 | "imports-loader": "^0.8.0", 25 | "webpack": "^5.65.0", 26 | "webpack-cli": "^4.9.1", 27 | "webpack-dev-server": "^4.7.2" 28 | }, 29 | "dependencies": { 30 | "async-es": "^2.6.3", 31 | "three": "^0.136.0", 32 | "three.proton.js": "^0.2.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Application.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | 22 | 23 | import * as THREE from 'three'; 24 | import EventType from './libs/EventType' 25 | import ViewPort from './libs/ViewPort' 26 | 27 | 28 | export default class Application { 29 | constructor(opts = {}) { 30 | this.pitchLeft = false; 31 | this.pitchRight = false; 32 | this.eventHandlers = {}; 33 | this.domParent = opts.domParent || document.body; 34 | this.mixers = []; 35 | this.viewports = []; 36 | 37 | this.clock = new THREE.Clock(); 38 | 39 | this.bindMouseEvents(); 40 | this.bindKeyEvents(); 41 | this.bindResizeEvent(); 42 | } 43 | 44 | 45 | /** 46 | * Run the application class 47 | * 48 | * @memberof Application 49 | */ 50 | run(){ 51 | this.init(); 52 | } 53 | 54 | 55 | /** 56 | * bind window mouse events to call back 57 | * 58 | * @memberof Application 59 | * 60 | */ 61 | bindMouseEvents(){ 62 | window.addEventListener('mousemove', (ev)=>{ 63 | 64 | ev.preventDefault(); 65 | 66 | if(this.currentX === undefined) this.currentX = 0; 67 | if(this.currentY === undefined) this.currentY = 0; 68 | 69 | let newX = ev.screenX 70 | let newY = ev.screenY; 71 | 72 | let DX = newX - this.currentX; 73 | let DY = newY - this.currentY; 74 | 75 | let movementX = ev.movementX || ev.mozMovementX || ev.webkitMovementX || 0; 76 | let movementY = ev.movementY || ev.mozMovementY || ev.webkitMovementY || 0; 77 | 78 | this.currentX = newX; 79 | this.currentY = newY; 80 | 81 | let coordX = ( ev.clientX / window.innerWidth ) * 2 - 1; 82 | let coordY = - ( ev.clientY / window.innerHeight ) * 2 + 1; 83 | 84 | let eventData = {x: newX, y: newY, DX, DY, ctrlKey: ev.ctrlKey, shiftKey: ev.shiftKey, altKey: ev.altKey, coordX, coordY, movementX, movementY}; 85 | this.sendEvent(EventType.EVT_MOUSE_MOVE, eventData); 86 | }); 87 | 88 | 89 | window.addEventListener('click', (ev)=>{ 90 | 91 | let eventData = {coord: {x: ( ev.clientX / window.innerWidth ) * 2 - 1, y: - ( ev.clientY / window.innerHeight ) * 2 + 1}}; 92 | this.sendEvent(EventType.EVT_MOUSE_CLICK, eventData); 93 | 94 | }); 95 | 96 | window.addEventListener('wheel', (ev)=>{ 97 | 98 | let eventData = { DX: ev.deltaX, DY: ev.deltaY, DZ: ev.deltaZ }; 99 | this.sendEvent(EventType.EVT_WHEEL, eventData); 100 | 101 | }) 102 | } 103 | 104 | 105 | /** 106 | * bind keyboard key events 107 | * 108 | * @memberof Application 109 | */ 110 | bindKeyEvents(){ 111 | 112 | window.addEventListener('keydown', (ev)=>{ 113 | let eventData = ev; 114 | this.sendEvent(EventType.EVT_KEY_DOWN, eventData); 115 | }); 116 | 117 | window.addEventListener('keyup', (ev)=>{ 118 | let eventData = ev; 119 | this.sendEvent(EventType.EVT_KEY_UP, eventData); 120 | }); 121 | 122 | } 123 | 124 | /** 125 | * bind window resize event to callback 126 | * 127 | * @memberof Application 128 | */ 129 | bindResizeEvent(){ 130 | window.addEventListener('resize', (ev)=>{ 131 | this.sendEvent(EventType.EVT_WINDOW_RESIZE, {}); 132 | }) 133 | } 134 | 135 | 136 | /** 137 | * Add a handler for the respective event type 138 | * 139 | * @param {Symbol} eventType the name of the event 140 | * @param {Function} handler handler for the event 141 | * @memberof Application 142 | */ 143 | subscribeToEvent(eventType, handler){ 144 | if(this.eventHandlers[eventType] === undefined){ 145 | this.eventHandlers[eventType] = []; 146 | } 147 | 148 | this.eventHandlers[eventType].push(handler); 149 | } 150 | 151 | 152 | /** 153 | * Send/Invoke an event 154 | * 155 | * @param {Symbol} eventType the name of the event 156 | * @param {Object} eventData event data to be passed to the even handler 157 | * @memberof Application 158 | */ 159 | sendEvent(eventType, eventData){ 160 | if(eventData === undefined){ 161 | eventData = {}; 162 | } 163 | 164 | this.executeEventHandlers(eventType, eventData); 165 | } 166 | 167 | 168 | /** 169 | * Run every event handler for the supplied event 170 | * 171 | * @param {String} eventType the name of the event 172 | * @param {Object} eventData event data to be passed to the even handler 173 | * @memberof Application 174 | */ 175 | executeEventHandlers(eventType, eventData){ 176 | let handlers = this.eventHandlers[eventType]; 177 | 178 | if(handlers !== undefined){ 179 | handlers.forEach(handler => { 180 | handler(eventData); 181 | }); 182 | } 183 | } 184 | 185 | /** 186 | * Create a new viewport object and add it to the array of viewports 187 | * 188 | * @param {Object} scene 189 | * @param {Object} camera 190 | * @param {Object} renderer 191 | * @returns Viewport 192 | * @memberof Application 193 | */ 194 | addViewport(scene, camera, renderer){ 195 | let viewport = new ViewPort(scene, camera, renderer); 196 | this.viewports.push(viewport); 197 | return viewport; 198 | } 199 | 200 | /** 201 | * Run the event loop 202 | * 203 | * @memberof Application 204 | */ 205 | runRenderLoop() { 206 | let timeStep = this.clock.getDelta(); 207 | this.sendEvent(EventType.EVT_UPDATE, {timeStep}); 208 | 209 | this.viewports.forEach((viewport)=>{ 210 | viewport.renderToViewport(); 211 | }); 212 | 213 | this.sendEvent(EventType.EVT_POST_UPDATE, {timeStep}); 214 | 215 | requestAnimationFrame( this.runRenderLoop.bind(this) ); 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /src/Game.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | 22 | import * as THREE from 'three' 23 | import Application from './Application' 24 | import EventType from './libs/EventType' 25 | import RealityBridge from './libs/RealityBridge' 26 | import Stats from './threejs/stats' 27 | import textOverlay from './libs/TextOverlay' 28 | import rc from './libs/ResourceCache'; 29 | import * as MISC from './libs/Misc' 30 | import Stage from './libs/Stage' 31 | import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; 32 | 33 | export default class Game extends Application{ 34 | constructor(opts = {}){ 35 | 36 | super(opts); 37 | 38 | this.pos = new THREE.Vector3(); 39 | this.quat = new THREE.Quaternion(); 40 | 41 | this.isPaused = false; 42 | this.gameOver = false; 43 | 44 | this.currentGameTime = 0; 45 | this.gameTimeCounter = 0; 46 | 47 | this.inGame = false; 48 | 49 | this.ambientMusic = null; 50 | 51 | } 52 | 53 | /** 54 | * Initialise the application 55 | * 56 | * @memberof Application 57 | */ 58 | init(){ 59 | 60 | textOverlay.initialize("..Baller.."); 61 | 62 | this.stageResources(); 63 | 64 | rc.loadResources(this.onResourceLoaded.bind(this), this.onResourceLoadProgress.bind(this)); 65 | 66 | } 67 | 68 | 69 | /** 70 | * Stage all the necessary resources/assets to be loaded 71 | * 72 | * @memberof Game 73 | */ 74 | stageResources(){ 75 | 76 | 77 | 78 | this.gltfLoader = new GLTFLoader(); 79 | rc.stageForLoading(this.gltfLoader.load.bind(this.gltfLoader), "assets/baller_base/baller_base.gltf", "baller_base"); 80 | 81 | this.audioLoader = new THREE.AudioLoader(); 82 | rc.stageForLoading(this.audioLoader.load.bind(this.audioLoader), "assets/audio/pulse.ogg", "delay_sound"); 83 | rc.stageForLoading(this.audioLoader.load.bind(this.audioLoader), "assets/audio/spirit-of-the-girl.ogg", "ambient_sound"); 84 | 85 | this.textureLoader = new THREE.TextureLoader(); 86 | rc.stageForLoading(this.textureLoader.load.bind(this.textureLoader), "assets/texture/ball_diffuse.jpg", "ball_diffuse"); 87 | rc.stageForLoading(this.textureLoader.load.bind(this.textureLoader), "assets/texture/ball_normal.jpg", "ball_normal"); 88 | rc.stageForLoading(this.textureLoader.load.bind(this.textureLoader), "assets/texture/ball_specular.jpg", "ball_specular"); 89 | rc.stageForLoading(this.textureLoader.load.bind(this.textureLoader), "assets/texture/dot.png", "dot"); 90 | 91 | 92 | } 93 | 94 | 95 | /** 96 | * Called when all resources have been loaded 97 | * 98 | * @param {Error} err Error parameter. This value is set if there is any error 99 | * @returns nothing 100 | * @memberof Game 101 | */ 102 | async onResourceLoaded(err){ 103 | 104 | //if there was an error then return 105 | if(err) return console.log(err); 106 | 107 | try { 108 | 109 | this.createScene(); 110 | await this.bridgeReality(); 111 | 112 | this.createStage(); 113 | 114 | this.createRenderer(); 115 | this.createInitialViewport(); 116 | this.subscribeToEvents(); 117 | 118 | this.stats = new Stats(); 119 | this.domParent.appendChild( this.stats.dom ); 120 | 121 | 122 | textOverlay.showGameInstruction(); 123 | 124 | // 125 | 126 | } catch (error) { 127 | 128 | console.log(error.message); 129 | 130 | } 131 | 132 | } 133 | 134 | /** 135 | * Load progress event handler 136 | * 137 | * @param {Number} currProgress progress of the current asset being loaded 138 | * @param {Number} totalProgress Cummulative progress of all the assets being loaded 139 | * @param {String} resourceUrl The URL of the current resource being loaded 140 | * 141 | * @memberof Game 142 | */ 143 | onResourceLoadProgress(currProgress, totalProgress, resourceUrl){ 144 | 145 | textOverlay.updateLoadingDisplay(totalProgress, `Loading "${resourceUrl}" : ${currProgress}%`); 146 | 147 | } 148 | 149 | 150 | /** 151 | * Create the the default Scene 152 | * 153 | * @memberof Application 154 | */ 155 | createScene(){ 156 | 157 | 158 | let scene = this.scene = new THREE.Scene(); 159 | scene.background = new THREE.Color().setHSL( 0.1, 0.1, 0.1 ); 160 | scene.fog = new THREE.Fog( scene.background, 1, 5000 ); 161 | 162 | 163 | this.camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 5000 ); 164 | 165 | this.setupSoundSystem(this.camera); 166 | 167 | // LIGHTS 168 | 169 | let hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.1 ); 170 | hemiLight.color.setHSL( 0.6, 0.6, 0.6 ); 171 | hemiLight.groundColor.setHSL( 0.1, 1, 0.4 ); 172 | hemiLight.position.set( 0, 50, 0 ); 173 | scene.add( hemiLight ); 174 | 175 | let dirLight = this.dirLight = new THREE.DirectionalLight( 0xffffff , 1); 176 | dirLight.color.setHSL( 0.1, 1, 0.95 ); 177 | dirLight.position.set( -1, 1.75, 1 ); 178 | dirLight.position.multiplyScalar( 100 ); 179 | scene.add( dirLight ); 180 | 181 | dirLight.castShadow = true; 182 | 183 | dirLight.shadow.mapSize.width = 2048; 184 | dirLight.shadow.mapSize.height = 2048; 185 | 186 | let d = 50; 187 | 188 | dirLight.shadow.camera.left = -d; 189 | dirLight.shadow.camera.right = d; 190 | dirLight.shadow.camera.top = d; 191 | dirLight.shadow.camera.bottom = -d; 192 | 193 | dirLight.shadow.camera.far = 13500; 194 | 195 | } 196 | 197 | /** 198 | * Setup the sound system 199 | * 200 | * @param {any} camera viewport camera 201 | * @returns null 202 | * @memberof Game 203 | */ 204 | setupSoundSystem(camera){ 205 | 206 | if(camera === undefined) return; 207 | 208 | let listener = this.listener = new THREE.AudioListener(); 209 | camera.add( listener ); 210 | 211 | let ambientMusic = this.ambientMusic = new THREE.Audio( listener ); 212 | let buffer = rc.getResource('ambient_sound'); 213 | ambientMusic.setBuffer( buffer ); 214 | 215 | ambientMusic.setVolume( 0.5 ); 216 | ambientMusic.setLoop( true ); 217 | 218 | } 219 | 220 | /** 221 | * Initialise the Reality Bridge ;) 222 | * 223 | * @memberof Game 224 | */ 225 | async bridgeReality(){ 226 | 227 | this.realityBridge = null; 228 | 229 | if(this.scene !== null) 230 | { 231 | this.realityBridge = new RealityBridge(this); 232 | 233 | await this.realityBridge.startReality({}, true); 234 | await this.realityBridge.set( {gravity: [0, -250, 0]} ); 235 | await this.realityBridge.updateReality(); 236 | 237 | } 238 | } 239 | 240 | 241 | /** 242 | * Create Game Stage 243 | * 244 | * @memberof Game 245 | */ 246 | createStage(){ 247 | 248 | this.gameStage = new Stage({ 249 | scene : this.scene, 250 | camera : this.camera, 251 | realityBridge : this.realityBridge, 252 | resourceCache : rc, 253 | listener : this.listener, 254 | gameOverCallBack: this.gameOverCallBack.bind(this) 255 | }); 256 | 257 | } 258 | 259 | 260 | 261 | /** 262 | * Create the renderer for the application 263 | * 264 | * @memberof Application 265 | */ 266 | createRenderer(){ 267 | 268 | let renderer = this.renderer = new THREE.WebGLRenderer( { antialias: true } ); 269 | renderer.setClearColor( 0xbfd1e5 ); 270 | renderer.setPixelRatio( window.devicePixelRatio ); 271 | renderer.setSize( window.innerWidth, window.innerHeight ); 272 | this.domParent.appendChild( this.renderer.domElement ); 273 | 274 | renderer.gammaInput = true; 275 | renderer.gammaOutput = true; 276 | 277 | renderer.shadowMap.enabled = true; 278 | } 279 | 280 | /** 281 | * Create the primary render viewport 282 | * 283 | * @memberof Game 284 | */ 285 | createInitialViewport(){ 286 | 287 | this.mainViewport = this.addViewport(this.scene, this.camera, this.renderer); 288 | 289 | } 290 | 291 | 292 | 293 | 294 | /** 295 | * Begin the render loop of the application 296 | * 297 | * @memberof Game 298 | */ 299 | startGame(){ 300 | 301 | 302 | this.gameStage.start(()=>{ 303 | 304 | 305 | this.gameOver = false; 306 | this.isPaused = false; 307 | 308 | textOverlay.showGameViewOverlay(); 309 | this.initialiseGameTime(); 310 | 311 | this.runRenderLoop(); 312 | 313 | }) 314 | 315 | } 316 | 317 | /** 318 | * init Game Time 319 | * 320 | * @memberof Game 321 | */ 322 | initialiseGameTime(){ 323 | 324 | 325 | this.updateGameTime(0); 326 | this.gameTimeCounter = 0; 327 | 328 | } 329 | 330 | 331 | 332 | /** 333 | * Restart the game 334 | * 335 | * @memberof Game 336 | */ 337 | restartGame(){ 338 | 339 | this.gameStage.restart(()=>{ 340 | 341 | this.gameOver = false; 342 | 343 | textOverlay.hidePauseDisplay(); 344 | this.initialiseGameTime(); 345 | 346 | 347 | }); 348 | 349 | } 350 | 351 | 352 | 353 | lockMousePointer(){ 354 | 355 | if(document.pointerLockElement !== this.domParent && document.mozPointerLockElement !== this.domParent) { 356 | 357 | this.domParent.requestPointerLock(); 358 | 359 | } 360 | 361 | } 362 | 363 | /** 364 | * Method to be called back when the game is completed 365 | * 366 | * @param {boolean} [win=false] win status 367 | * @memberof Game 368 | */ 369 | gameOverCallBack( win = false ){ 370 | 371 | //if win = true do something 372 | if( win ){ 373 | 374 | this.gameOver = true; 375 | this.inGame = false; 376 | 377 | textOverlay.showGameOver(); 378 | 379 | console.log('Game Over'); 380 | 381 | } 382 | //else do something different 383 | 384 | } 385 | 386 | 387 | /** 388 | * Subscribe to events needed in the application 389 | * 390 | * @memberof Application 391 | */ 392 | subscribeToEvents(){ 393 | 394 | this.subscribeToEvent(EventType.EVT_UPDATE, this.handleUpdate.bind(this)); 395 | this.subscribeToEvent(EventType.EVT_WINDOW_RESIZE, this.handleWindowResize.bind(this)); 396 | this.subscribeToEvent(EventType.EVT_KEY_DOWN, this.handleKeyDown.bind(this)); 397 | this.subscribeToEvent(EventType.EVT_POST_UPDATE, this.handlePostUpdate.bind(this)); 398 | this.subscribeToEvent(EventType.EVT_MOUSE_MOVE, this.handleMouseMove.bind(this)); 399 | 400 | 401 | //add pointerlockchange event handler as gotten from MDN 402 | if ("onpointerlockchange" in document) { 403 | 404 | document.addEventListener('pointerlockchange', this.handlePointerLockChange.bind(this), false); 405 | 406 | } else if ("onmozpointerlockchange" in document) { 407 | 408 | document.addEventListener('mozpointerlockchange', this.handlePointerLockChange.bind(this), false); 409 | 410 | } 411 | 412 | } 413 | 414 | 415 | /** 416 | * Handles pre-render update 417 | * 418 | * @param {Object} eventData Event Data passed to the method 419 | * @memberof Application 420 | */ 421 | handleUpdate(eventData){ 422 | 423 | if(this.inGame && !this.isPaused) { 424 | 425 | //Update the game timer 426 | let timeStep = eventData.timeStep; 427 | 428 | this.gameTimeCounter += timeStep; 429 | 430 | if(this.gameTimeCounter >= 1){ 431 | 432 | this.updateGameTime(this.currentGameTime + 1); 433 | this.gameTimeCounter = 0; 434 | 435 | } 436 | 437 | //Update the game stage 438 | this.gameStage.handleUpdate(eventData); 439 | 440 | } 441 | 442 | } 443 | 444 | 445 | /** 446 | * Handle post update 447 | * 448 | * @memberof Game 449 | */ 450 | handlePostUpdate(){ 451 | 452 | this.stats.update(); 453 | 454 | } 455 | 456 | 457 | /** 458 | * Handle mouse move event 459 | * 460 | * @param {Object} eventData object that contains relevant event details 461 | * @memberof Game 462 | */ 463 | handleMouseMove(eventData){ 464 | 465 | if(this.inGame && !this.isPaused) this.gameStage.handleMouseMove(eventData); 466 | 467 | } 468 | 469 | 470 | /** 471 | * Handles instances where the browser window is resized 472 | * 473 | * @memberof Application 474 | */ 475 | handleWindowResize(){ 476 | 477 | this.camera.aspect = window.innerWidth / window.innerHeight; 478 | this.camera.updateProjectionMatrix(); 479 | this.renderer.setSize( window.innerWidth, window.innerHeight ); 480 | 481 | } 482 | 483 | 484 | 485 | /** 486 | * Handle keydown event 487 | * 488 | * @param {Object} eventData Event Data passed to the method 489 | * @memberof Application 490 | */ 491 | handleKeyDown(eventData){ 492 | 493 | 494 | if(this.inGame){ 495 | 496 | this.inGameKeyDownHandler(eventData); 497 | 498 | } 499 | else{ 500 | 501 | this.outGameKeyDownHandler(eventData); 502 | 503 | } 504 | 505 | 506 | } 507 | 508 | /** 509 | * Key down handler where inGame = false; 510 | * 511 | * @param {any} eventData Event data object 512 | * @memberof Game 513 | */ 514 | outGameKeyDownHandler(eventData){ 515 | 516 | 517 | switch ( eventData.keyCode ) { 518 | case 32: // space key 519 | //if the game has been played before restart() else start() 520 | if(this.gameOver){ 521 | 522 | textOverlay.showGameViewOverlay(); 523 | textOverlay.hidePauseDisplay(); 524 | textOverlay.hideGameOver(); 525 | 526 | this.restartGame(); 527 | } 528 | else 529 | { 530 | 531 | this.lockMousePointer(); 532 | this.ambientMusic.play(); 533 | this.startGame(); 534 | 535 | } 536 | 537 | this.inGame = true; 538 | break; 539 | 540 | } 541 | 542 | } 543 | 544 | 545 | /** 546 | * Key down event handler where inGame = true 547 | * 548 | * @param {any} eventData Event data object 549 | * @memberof Game 550 | */ 551 | inGameKeyDownHandler(eventData){ 552 | 553 | switch ( eventData.keyCode ) { 554 | case 80: //P key to toggle game pause 555 | this.toggleGamePause(); 556 | break; 557 | 558 | case 32: //Space key to restart game 559 | if(this.isPaused){ 560 | this.toggleGamePause(); 561 | this.restartGame(); 562 | } 563 | break; 564 | default: // any other key should be forwarded to Stage if game is not paused 565 | if(!this.isPaused) this.gameStage.handleKeyDown(eventData); 566 | } 567 | 568 | } 569 | 570 | /** 571 | * Pointer lock change event andler 572 | * 573 | * @memberof Game 574 | */ 575 | handlePointerLockChange(){ 576 | 577 | //handle only when we've lost the pointer lock 578 | if(document.pointerLockElement !== this.domParent && document.mozPointerLockElement !== this.domParent){ 579 | 580 | if(!this.isPaused){ 581 | 582 | this.toggleGamePause(); 583 | 584 | } 585 | 586 | } 587 | 588 | } 589 | 590 | 591 | updateGameTime(timeValue){ 592 | 593 | this.currentGameTime = timeValue; 594 | let gameTimeText = MISC.intToTimeString(this.currentGameTime); 595 | 596 | textOverlay.updateTimeDisplay(gameTimeText); 597 | 598 | } 599 | 600 | 601 | toggleGamePause(){ 602 | 603 | this.isPaused = !this.isPaused; 604 | 605 | if(this.isPaused) textOverlay.showPauseDisplay(); 606 | else{ 607 | 608 | textOverlay.hidePauseDisplay(); 609 | 610 | //regain pointer lock just incase it was lost through pressing ESC 611 | this.lockMousePointer(); 612 | 613 | } 614 | 615 | } 616 | 617 | 618 | 619 | } 620 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | 22 | import Game from './Game' 23 | 24 | let game = new Game({domParent: document.body}); 25 | game.run(); -------------------------------------------------------------------------------- /src/libs/CamControl.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /** 22 | * Custom Cameral Controller 23 | */ 24 | 25 | import * as THREE from 'three' 26 | import * as MISC from './Misc' 27 | 28 | 29 | export default class CamControl extends THREE.Group{ 30 | 31 | constructor(camera){ 32 | super(); 33 | 34 | this.pitchController = new THREE.Group(); 35 | this.yawController = new THREE.Group(); 36 | 37 | this.pitchController.add(camera); 38 | this.yawController.add(this.pitchController); 39 | this.add(this.yawController); 40 | 41 | this.camera = camera; 42 | this.camTurnSpan = 90 * MISC.deg2Rad; 43 | 44 | this.tween = null; 45 | 46 | this.resetPerspective(); 47 | 48 | } 49 | 50 | resetPerspective(){ 51 | 52 | this.inTransition = false; 53 | this.onTurnCompleted = null; 54 | 55 | this.turnDirection = 'right'; 56 | this.turnObj = {initial: 0, current: 0, direction: 1}; 57 | 58 | 59 | if(this.tween){ 60 | 61 | this.tween.stop(); 62 | 63 | } 64 | else{ 65 | 66 | this.tween = new TWEEN.Tween(this.turnObj); 67 | 68 | } 69 | 70 | 71 | this.camera.position.set(0, 0, 30); 72 | 73 | this.pitchController.rotation.set(0, 0, 0); 74 | this.pitchController.rotateX( -90 * MISC.deg2Rad ); 75 | 76 | this.yawController.rotation.set(0, 0, 0); 77 | 78 | } 79 | 80 | 81 | /** 82 | * Rotate the controller by 90 degrees 83 | * 84 | * @param {Boolean} right a boolean indicating right direction or left if otherwise 85 | * @param {Function} cb Callback to be invoked when the rotation transition is completed 86 | * @memberof CamControl 87 | */ 88 | turn(right, cb){ 89 | 90 | 91 | if(right === null || right === undefined || !cb ) return; 92 | if(typeof right !== "boolean") return; 93 | 94 | this.turnObj.initial = this.yawController.rotation.y; 95 | this.turnObj.current = 0; 96 | this.turnObj.direction = right ? 1 : -1; 97 | 98 | 99 | 100 | this.onTurnCompleted = cb; 101 | 102 | this.tween.to( { current: this.camTurnSpan } , 300) 103 | .easing(TWEEN.Easing.Quadratic.Out) 104 | .onUpdate(()=>{ 105 | 106 | let newRot = this.turnObj.initial + (this.turnObj.current * this.turnObj.direction); 107 | this.yawController.rotation.y = newRot; 108 | 109 | }) 110 | .start() 111 | .onComplete(()=>{ this.inTransition = false; this.onTurnCompleted();}); 112 | 113 | this.inTransition = true; 114 | } 115 | 116 | 117 | 118 | /** 119 | * Rotate the controller Right by 90 degrees 120 | * 121 | * {Function} cb Callback to be invoked when the rotation transition is completed 122 | * @memberof CamControl 123 | */ 124 | turnRight(cb){ 125 | 126 | if(!cb) return; 127 | 128 | this.turn(true, cb); 129 | } 130 | 131 | 132 | 133 | /** 134 | * Rotate the controller Left by 90 degrees 135 | * 136 | * @param {Function} cb Callback to be invoked when the rotation transition is completed 137 | * @memberof CamControl 138 | */ 139 | turnLeft(cb){ 140 | 141 | if(!cb) return; 142 | 143 | this.turn(false, cb); 144 | } 145 | 146 | 147 | translateCamera( units ){ 148 | 149 | this.camera.translateZ(units) 150 | 151 | } 152 | 153 | 154 | /** 155 | * Subscribe to the update event of the main application 156 | * 157 | * @memberof CamControl 158 | */ 159 | handleUpdate(){ 160 | 161 | if(!this.inTransition) return; 162 | 163 | TWEEN.update(); 164 | 165 | } 166 | 167 | } -------------------------------------------------------------------------------- /src/libs/EventType.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | export default { 22 | EVT_UPDATE : Symbol(), 23 | EVT_POST_UPDATE : Symbol(), 24 | EVT_MOUSE_DOWN : Symbol(), 25 | EVT_MOUSE_MOVE : Symbol(), 26 | EVT_MOUSE_CLICK : Symbol(), 27 | EVT_WINDOW_RESIZE : Symbol(), 28 | EVT_KEY_DOWN : Symbol(), 29 | EVT_KEY_UP : Symbol(), 30 | EVT_WHEEL : Symbol() 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/libs/Misc.js: -------------------------------------------------------------------------------- 1 | 2 | import * as THREE from 'three' 3 | 4 | function intToTimeString (integerTime) { 5 | let sec_num = parseInt(integerTime, 10); // don't forget the second param 6 | let hours = Math.floor(sec_num / 3600); 7 | let minutes = Math.floor((sec_num - (hours * 3600)) / 60); 8 | let seconds = sec_num - (hours * 3600) - (minutes * 60); 9 | 10 | if (hours < 10) {hours = "0"+hours;} 11 | if (minutes < 10) {minutes = "0"+minutes;} 12 | if (seconds < 10) {seconds = "0"+seconds;} 13 | return hours+':'+minutes+':'+seconds; 14 | } 15 | 16 | const deg2Rad = 0.017456; 17 | const rad2Deg = 57.288; 18 | 19 | 20 | export { 21 | intToTimeString, 22 | deg2Rad, 23 | rad2Deg 24 | } -------------------------------------------------------------------------------- /src/libs/ProtonPlus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Proton Plus 3 | * minor addition to Proton Js for emission of particles based on a reference node if provided 4 | * and for hack over Mesh Zone 5 | * 6 | */ 7 | import * as THREE from 'three'; 8 | import Proton from 'three.proton.js'; 9 | 10 | //Base render tweak 11 | Proton.BaseRender.prototype.init = function(proton) { 12 | var self = this; 13 | this.proton = proton; 14 | 15 | this.proton.addEventListener("PROTON_UPDATE", function(proton) { 16 | self.onProtonUpdate.call(self, proton); 17 | }); 18 | 19 | this.proton.addEventListener("PARTICLE_CREATED", function(particle) { 20 | self.onParticleCreated.call(self, particle); 21 | }); 22 | 23 | this.proton.addEventListener("PARTICLE_CREATED_PLUS", function(particle) { 24 | self.onParticleCreatedPlus.call(self, particle); 25 | }); 26 | 27 | this.proton.addEventListener("PARTICLE_UPDATE", function(particle) { 28 | self.onParticleUpdate.call(self, particle); 29 | }); 30 | 31 | this.proton.addEventListener("PARTICLE_UPDATE_PLUS", function(particle) { 32 | self.onParticleUpdatePlus.call(self, particle); 33 | }); 34 | 35 | this.proton.addEventListener("PARTICLE_DEAD", function(particle) { 36 | self.onParticleDead.call(self, particle); 37 | }); 38 | }; 39 | 40 | Proton.BaseRender.prototype.onParticleCreatedPlus = function(particle) { 41 | 42 | }; 43 | 44 | Proton.BaseRender.prototype.onParticleUpdatePlus = function(particle) { 45 | 46 | }; 47 | 48 | 49 | //Mesh Render 50 | Proton.MeshRender.prototype.onParticleCreatedPlus = function(particleWrapper) { 51 | var particle = particleWrapper.particle; 52 | var refnode = particleWrapper.refnode; 53 | var relativeEmission = particleWrapper.relativeEmission; 54 | 55 | particle.relativeEmission = relativeEmission; 56 | 57 | if (!particle.target) { 58 | //set target 59 | if (!particle.body) particle.body = this._body; 60 | particle.target = this._targetPool.get(particle.body); 61 | 62 | //set material 63 | if (particle.useAlpha || particle.useColor) { 64 | particle.target.material.__puid = Proton.PUID.id(particle.body.material);; 65 | particle.target.material = this._materialPool.get(particle.target.material); 66 | } 67 | } 68 | 69 | if (particle.target) { 70 | particle.target.position.copy(particle.p); 71 | 72 | if(relativeEmission === true) refnode.add(particle.target); 73 | else{ 74 | 75 | let v = new THREE.Vector3(); 76 | refnode.getWorldPosition(v); 77 | particle.refPos = {x: v.x, y: v.y, z: v.z}; 78 | this.container.add(particle.target); 79 | } 80 | } 81 | }; 82 | 83 | 84 | Proton.MeshRender.prototype.onParticleUpdatePlus = function(particle) { 85 | if (particle.target && particle.target.parent) { 86 | 87 | if(particle.relativeEmission){ 88 | particle.target.position.copy(particle.p); 89 | } 90 | else{ 91 | let v = particle.refPos || {x: 0, y: 0, z: 0}; 92 | particle.target.position.set(v.x + particle.p.x, v.y + particle.p.y, v.z + particle.p.z); 93 | } 94 | 95 | particle.target.rotation.set(particle.rotation.x, particle.rotation.y, particle.rotation.z); 96 | this.scale(particle); 97 | 98 | if (particle.useAlpha) { 99 | particle.target.material.opacity = particle.alpha; 100 | particle.target.material.transparent = true; 101 | } 102 | 103 | if (particle.useColor) { 104 | particle.target.material.color.copy(particle.color); 105 | } 106 | } 107 | }; 108 | 109 | 110 | //Advanced Emitter 111 | function AdvEmitter(refnode, pObj){ 112 | 113 | if(!refnode) throw new Exception("Invalide reference node"); 114 | 115 | this.refnode = refnode; 116 | 117 | this.relativeEmission = true; 118 | 119 | AdvEmitter._super_.call(this, pObj); 120 | 121 | } 122 | 123 | Proton.Util.inherits(AdvEmitter, Proton.Emitter); 124 | Proton.EventDispatcher.initialize(AdvEmitter.prototype); 125 | 126 | AdvEmitter.prototype.update = function(time) { 127 | this.age += time; 128 | if (this.dead || this.age >= this.life) { 129 | this.destroy(); 130 | } 131 | 132 | this.emitting(time); 133 | this.integrate(time); 134 | 135 | var particle, i = this.particles.length; 136 | while (i--) { 137 | particle = this.particles[i]; 138 | if (particle.dead) { 139 | this.parent && this.parent.dispatchEvent("PARTICLE_DEAD", particle); 140 | Proton.bindEmtterEvent && this.dispatchEvent("PARTICLE_DEAD", particle); 141 | 142 | this.parent.pool.expire(particle.reset()); 143 | this.particles.splice(i, 1); 144 | } 145 | } 146 | }; 147 | 148 | AdvEmitter.prototype.createParticle = function(initialize, behaviour) { 149 | var particle = this.parent.pool.get(Proton.Particle); 150 | this.setupParticle(particle, initialize, behaviour); 151 | var particleWrapper = {particle: particle, refnode: this.refnode, relativeEmission: this.relativeEmission} 152 | 153 | this.parent && this.parent.dispatchEvent("PARTICLE_CREATED_PLUS", particleWrapper); 154 | Proton.bindEmtterEvent && this.dispatchEvent("PARTICLE_CREATED_PLUS", particleWrapper); 155 | 156 | return particle; 157 | }; 158 | 159 | 160 | AdvEmitter.prototype.integrate = function(time) { 161 | var damping = 1 - this.damping; 162 | Proton.integrator.integrate(this, time, damping); 163 | 164 | var i = this.particles.length; 165 | while (i--) { 166 | var particle = this.particles[i]; 167 | particle.update(time, i); 168 | Proton.integrator.integrate(particle, time, damping); 169 | 170 | this.parent && this.parent.dispatchEvent("PARTICLE_UPDATE_PLUS", particle); 171 | Proton.bindEmtterEvent && this.dispatchEvent("PARTICLE_UPDATE_PLUS", particle); 172 | } 173 | }; 174 | 175 | 176 | Proton.AdvEmitter = AdvEmitter; 177 | 178 | 179 | /** 180 | * MeshZoneMod is a threejs mesh zone ( actually a mode of Mesh Zone) 181 | * @param {Geometry|Mesh} geometry - a THREE.Geometry or THREE.Mesh object 182 | * @example 183 | * var geometry = new THREE.CylinderGeometry( 5, 5, 20, 32 ); 184 | * var cylinder = new THREE.Mesh( geometry, material ); 185 | * var meshZone = new Proton.MeshZoneMod(geometry); 186 | * or 187 | * var meshZone = new Proton.MeshZoneMod(cylinder); 188 | * @extends {Proton.Zone} 189 | * @constructor 190 | */ 191 | 192 | function MeshZoneMod(geometry, scale) { 193 | MeshZoneMod._super_.call(this); 194 | if (!!geometry.vertices ) { 195 | this.geometry = geometry; 196 | } else { 197 | throw new Error("invalid geometry for mesh zone"); 198 | } 199 | 200 | this.scale = scale || 1; 201 | } 202 | 203 | Proton.Util.inherits(MeshZoneMod, Proton.Zone); 204 | MeshZoneMod.prototype.getPosition = function() { 205 | var vertices = this.geometry.vertices; 206 | var rVector = vertices[(vertices.length * Math.random()) >> 0]; 207 | this.vector.x = rVector.x * this.scale; 208 | this.vector.y = rVector.y * this.scale; 209 | this.vector.z = rVector.z * this.scale; 210 | return this.vector; 211 | } 212 | 213 | MeshZoneMod.prototype.crossing = function(particle) { 214 | if (this.log) { 215 | console.error('Sorry MeshZoneMod does not support crossing method'); 216 | this.log = false; 217 | } 218 | } 219 | 220 | Proton.MeshZoneMod = MeshZoneMod; 221 | 222 | export default Proton; -------------------------------------------------------------------------------- /src/libs/RealityBridge.js: -------------------------------------------------------------------------------- 1 | // Original work Copyright © 2010-2017 three.js authors 2 | // Modified work Copyright 2019 BlueMagnificent 3 | 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | /** 23 | * Reality-Bridge by "Blue, The Magnificent" 24 | * 25 | * DERIVED FROM 26 | * 27 | * ammo.init.js (AMMO worker launcher) by lo.th / http://lo-th.github.io/labs/ 28 | * 29 | * 30 | **/ 31 | 32 | 33 | import * as THREE from 'three' 34 | 35 | export default class RealityBridge { 36 | constructor(parent){ 37 | 38 | this.parent = parent; 39 | this.scene = parent.scene; 40 | this.physicsWorker = null; 41 | 42 | this.resetOrInitialiseWorld(); 43 | this.setupInitialVariables(); 44 | this.setupPhysicsWorker(); 45 | 46 | this.contactsId = 0 47 | 48 | } 49 | 50 | 51 | resetOrInitialiseWorld(){ 52 | 53 | this.extraGeo = []; 54 | this.solids = []; 55 | this.bodys = []; 56 | 57 | this.contacts = []; 58 | this.contactCallback = []; 59 | 60 | this.timeSpan = 0; 61 | 62 | this.byName = {}; 63 | 64 | this.canStepSimulation = false; 65 | 66 | } 67 | 68 | setupInitialVariables(){ 69 | 70 | this.worker = null; 71 | this.blob = null; 72 | 73 | this.timestep = 1/60; 74 | this.substep = 2; //7; 75 | 76 | this.debug = false; 77 | this.gravity = [0, 0, 0]; 78 | 79 | 80 | //just a hack to keep things in order 81 | //should be removed later 82 | this.user = {key: 2}; 83 | 84 | let ArLng = this.ArLng = [ 85 | 1000 * 8, // rigid 86 | 100 * 4, // joint 87 | ]; 88 | 89 | this.ArPos = [ 90 | 0, 91 | ArLng[0], 92 | ]; 93 | 94 | this.ArMax = ArLng[0] + ArLng[1]; 95 | } 96 | 97 | 98 | 99 | setupPhysicsWorker(){ 100 | 101 | this.physicsWorker = Comlink.proxy(new Worker('./js/ammo.worker.js')); 102 | 103 | } 104 | 105 | startReality(o, debug){ 106 | 107 | o = o || {}; 108 | 109 | if(o.gravity) this.gravity = o.gravity; 110 | 111 | this.debug = debug || false; 112 | 113 | this.blob = document.location.href.replace(/\/[^/]*$/,"/") + "./js/ammo.wasm.js" ; 114 | 115 | 116 | return new Promise(resolve=>{ 117 | 118 | this.physicsWorker.init({ blob: this.blob, debug: this.debug, timestep: this.timestep, substep: this.substep, settings: [ this.ArLng, this.ArPos, this.ArMax ] }) 119 | .then(()=>{ 120 | 121 | window.URL.revokeObjectURL( this.blob ); 122 | this.blob = null; 123 | 124 | this.canStepSimulation = true; 125 | 126 | resolve(true); 127 | 128 | }) 129 | 130 | }) 131 | 132 | } 133 | 134 | 135 | 136 | updateReality(timeStep, forceStep){ 137 | 138 | this.timeSpan += forceStep ? 1/60 : (timeStep || 1/60); 139 | 140 | 141 | 142 | return new Promise(resolve=>{ 143 | 144 | if(!this.canStepSimulation && !forceStep){ 145 | 146 | resolve(false); 147 | } 148 | else{ 149 | 150 | this.canStepSimulation = false; 151 | this.timeSpan = 0; 152 | 153 | this.physicsWorker.step( { key: this.user.key, timeStep: this.timeSpan} ) 154 | .then(( o )=>{ 155 | 156 | if( o.status === true ) this.step( o ); 157 | 158 | resolve(true) 159 | 160 | }); 161 | } 162 | 163 | 164 | }) 165 | 166 | 167 | } 168 | 169 | resetPhysicsObjects(){ 170 | 171 | return this.physicsWorker.reset({}); 172 | 173 | } 174 | 175 | 176 | set ( o ){ 177 | 178 | return this.physicsWorker.setWorldParameter( o ); 179 | 180 | } 181 | 182 | 183 | step ( o ) { 184 | 185 | let Ar = o.Ar; 186 | let contacts = o.contacts; 187 | 188 | let ArPos = this.ArPos; 189 | 190 | this.bodyStep( Ar, ArPos[0] ); 191 | 192 | this.updateContact( contacts ); 193 | 194 | this.canStepSimulation = true; 195 | 196 | } 197 | 198 | 199 | setContactPairCheck(o){ 200 | 201 | o.name = o.name || `ct${this.contactsId++}`; 202 | this.contactCallback.push({name: o.name, f: o.f}); delete(o.f); 203 | 204 | return this.physicsWorker.addContact( o ); 205 | 206 | } 207 | 208 | 209 | 210 | clearContact(o){ 211 | 212 | if( !o.name ) return new Promise(resolve=>resolve(false)); 213 | 214 | return new Promise(resolve=>{ 215 | 216 | this.physicsWorker.clearContact( o ) 217 | .then(status=>{ 218 | 219 | if(status) { 220 | 221 | this.contactCallback = this.contactCallback.filter( ct => ct.name !== o.name); 222 | 223 | resolve(true); 224 | 225 | } 226 | else{ 227 | 228 | resolve(false) 229 | 230 | } 231 | 232 | }) 233 | 234 | }) 235 | 236 | 237 | } 238 | 239 | 240 | clearAllContact(){ 241 | 242 | return new Promise(resolve=>{ 243 | 244 | this.physicsWorker.clearAllContact() 245 | .then(status=>{ 246 | 247 | if(status) { 248 | 249 | this.contactCallback = []; 250 | resolve(true) 251 | 252 | } 253 | else{ 254 | 255 | resolve(false); 256 | 257 | } 258 | 259 | }) 260 | 261 | }) 262 | 263 | } 264 | 265 | 266 | 267 | updateContact ( contacts ) { 268 | 269 | this.contactCallback.forEach( ( ct, id ) => { 270 | 271 | ct.f( contacts[id] || 0 ); 272 | 273 | }); 274 | 275 | } 276 | 277 | bodyStep( AR, N ){ 278 | 279 | if( !this.bodys.length ) return; 280 | 281 | this.bodys.forEach( function( b, id ) { 282 | 283 | let n = N + ( id * 8 ); 284 | let s = AR[n]; 285 | if ( s > 0 ) { 286 | 287 | b.position.fromArray( AR, n + 1 ); 288 | b.quaternion.fromArray( AR, n + 4 ); 289 | 290 | } 291 | 292 | }); 293 | 294 | } 295 | 296 | 297 | 298 | setMatrix ( o ){ 299 | 300 | return this.physicsWorker.matrix( o ); 301 | 302 | } 303 | 304 | setMatrixArray ( o ){ 305 | 306 | return this.physicsWorker.matrixArray( o ); 307 | 308 | } 309 | 310 | 311 | //-------------------------------------- 312 | // RIGIDBODY 313 | //-------------------------------------- 314 | 315 | 316 | add ( o ) { 317 | 318 | o.type = o.type === undefined ? 'box' : o.type; 319 | 320 | let isKinematic = o.kinematic !== undefined ? o.kinematic : false; 321 | 322 | if( o.density !== undefined ) o.mass = o.density; 323 | else o.density = o.mass; 324 | 325 | o.mass = o.mass === undefined ? 0 : o.mass; 326 | 327 | let moveType = 1; 328 | if( o.move !== undefined ) moveType = 0;// dynamic 329 | //if( o.density !== undefined ) moveType = 0; 330 | if( o.mass !== 0 ) moveType = 0; 331 | if( isKinematic ) moveType = 2; 332 | 333 | 334 | // position 335 | o.pos = o.pos == undefined ? [0,0,0] : o.pos; 336 | 337 | // size 338 | o.size = o.size == undefined ? [1,1,1] : o.size; 339 | if(o.size.length == 1){ o.size[1] = o.size[0]; } 340 | if(o.size.length == 2){ o.size[2] = o.size[0]; } 341 | 342 | 343 | // rotation is in degree 344 | o.rot = o.rot == undefined ? [0,0,0] : Math.vectorad(o.rot); 345 | o.quat = new THREE.Quaternion().setFromEuler( new THREE.Euler().fromArray( o.rot ) ).toArray(); 346 | 347 | if(o.rotA) o.quatA = new THREE.Quaternion().setFromEuler( new THREE.Euler().fromArray( Math.vectorad( o.rotA ) ) ).toArray(); 348 | if(o.rotB) o.quatB = new THREE.Quaternion().setFromEuler( new THREE.Euler().fromArray( Math.vectorad( o.rotB ) ) ).toArray(); 349 | 350 | if(o.angUpper) o.angUpper = Math.vectorad( o.angUpper ); 351 | if(o.angLower) o.angLower = Math.vectorad( o.angLower ); 352 | 353 | 354 | let mesh = o.body || new THREE.Object3D(); 355 | 356 | if(o.type.substring(0,5) === 'joint') { 357 | 358 | return this.physicsWorker.add( o ); 359 | 360 | } 361 | 362 | if(o.type === 'plane'){ 363 | 364 | this.grid.position.set( o.pos[0], o.pos[1], o.pos[2] ) 365 | 366 | return this.physicsWorker.add( o ); 367 | 368 | } 369 | 370 | 371 | //Terrain is disabled for now 372 | // if(o.type === 'terrain'){ 373 | // return this.terrain( o ); 374 | // } 375 | 376 | 377 | if( o.body ) delete ( o.body ); 378 | 379 | 380 | // static 381 | if( moveType === 1 && !isKinematic ) this.solids.push( mesh ); 382 | // dynamique 383 | else this.bodys.push( mesh ); 384 | 385 | if( o.name ) this.byName[ o.name ] = mesh; 386 | 387 | return this.physicsWorker.add( o ); 388 | 389 | } 390 | 391 | 392 | remove ( o ){ 393 | 394 | this.removeMesh( o ); 395 | 396 | return this.physicsWorker.remove( o ); 397 | 398 | } 399 | 400 | 401 | removeArray ( o ){ 402 | 403 | if( !Array.isArray(o)) new Promise(resolve=>resolve(true)); 404 | 405 | o.forEach(obj => removeMesh( obj )); 406 | 407 | return this.physicsWorker.removeArray( o ); 408 | 409 | } 410 | 411 | 412 | removeMesh( o ){ 413 | 414 | if( o.name === undefined || o.name === '') return; 415 | 416 | let mesh = this.byName[ o.name ] || null; 417 | 418 | if( mesh === null) return; 419 | 420 | delete this.byName[o.name]; 421 | 422 | o.type = o.type || 'dynamic'; 423 | 424 | if( o.type === 'dynamic') this.bodys = this.bodys.filter( body => body !== mesh ); 425 | else if ( o.type === 'static') this.solids = this.solids.filter( solid => solid !== mesh ); 426 | 427 | } 428 | 429 | 430 | 431 | 432 | //-------------------------------------- 433 | // 434 | // TERRAIN 435 | // 436 | //-------------------------------------- 437 | 438 | terrain ( o ) { 439 | 440 | o.name = o.name === undefined ? 'terrain' : o.name; 441 | 442 | o.sample = o.sample === undefined ? [64,64] : o.sample; 443 | o.pos = o.pos === undefined ? [0,0,0] : o.pos; 444 | o.complexity = o.complexity === undefined ? 30 : o.complexity; 445 | 446 | 447 | let terrain = new Terrain( o ); 448 | 449 | terrain.physicsUpdate = function () { return this.physicsWorker.terrainPostStep({ name:this.name, heightData:this.heightData })} 450 | 451 | 452 | 453 | 454 | this.scene.add( terrain ); 455 | this.solids.push( terrain ); 456 | 457 | 458 | o.heightData = terrain.heightData; 459 | 460 | o.offset = 0; 461 | 462 | //this.mat['terrain'] = mesh.material; 463 | 464 | this.byName[ o.name ] = terrain; 465 | 466 | // send to worker 467 | return this.physicsWorker.add( o ); 468 | 469 | } 470 | 471 | completeTerrain ( name ){ 472 | 473 | let t = this.byName[ name ]; 474 | if(t) t.updateGeometry(); 475 | this.isTmove = false; 476 | 477 | } 478 | 479 | updateTerrain ( name ){ 480 | 481 | let t = this.byName[ name ]; 482 | 483 | if(t.isWater){ t.local.y += 0.25; t.local.z += 0.25; t.update( true ) } 484 | else t.easing( true ); 485 | 486 | } 487 | 488 | moveTerrainTo ( name, x, z ){ 489 | 490 | this.isTmove = true; 491 | let t = this.byName[ name ]; 492 | t.local.x += x || 0; 493 | t.local.z += z || 0; 494 | t.update( true ); 495 | 496 | 497 | } 498 | 499 | 500 | testWorker(){ 501 | 502 | return this.physicsWorker.testWorker(); 503 | 504 | } 505 | 506 | 507 | logOutput (msg){ 508 | 509 | if(!this.debug) return; 510 | 511 | console.log(msg); 512 | } 513 | } -------------------------------------------------------------------------------- /src/libs/ResourceCache.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | 22 | /** 23 | * Resource Manager 24 | * 25 | */ 26 | 27 | import series from 'async-es/series'; 28 | 29 | 30 | class ResourceCache { 31 | constructor(){ 32 | 33 | this.DEFAULT_CATEGORY = 'general'; 34 | 35 | this.cache = {}; 36 | this.loadStaging = []; 37 | 38 | this.createDefaultCategory(); 39 | 40 | 41 | } 42 | 43 | createDefaultCategory(){ 44 | 45 | this.cache[this.DEFAULT_CATEGORY] = {}; 46 | 47 | } 48 | 49 | /** 50 | * This adds a resource and its respective loader into the staging to be loaded later 51 | * 52 | * @param {Function} resourceLoader Function to load the resource (required) 53 | * @param {String} resourceUrl URL of the resource (required) 54 | * @param {String} resourceName unique name of resource (required) 55 | * @param {String} resourceCategory Resource category (optional) 56 | * @returns {Bool} 57 | * 58 | * @memberof ResourceCache 59 | */ 60 | stageForLoading(resourceLoader, resourceUrl, resourceName, resourceCategory){ 61 | 62 | if(!resourceLoader || !resourceUrl || !resourceName) return false; 63 | 64 | if(typeof resourceLoader !== 'function') return false; 65 | if(typeof resourceUrl !== 'string') return false; 66 | if(typeof resourceName !== 'string') return false; 67 | 68 | resourceCategory = (!resourceCategory || typeof resourceCategory !== 'string') ? this.DEFAULT_CATEGORY : resourceCategory.toLowerCase(); 69 | 70 | //check for unique resource url 71 | let urlIndex = this.loadStaging.findIndex( obj =>obj.resourceUrl === resourceUrl); 72 | 73 | //if the url already exists then throw an error 74 | if(urlIndex !== -1) throw Error(`Resource URL "${resourceUrl}" already exists`); 75 | 76 | //equally check for unique resource names in the same category; 77 | resourceName = resourceName.toLowerCase(); 78 | let nameIndex = this.loadStaging.findIndex( obj => obj.resourceName === resourceName && obj.resourceCategory === resourceCategory); 79 | 80 | //if the name already exits throw an error 81 | if(nameIndex !== -1) throw Error(`Resource name "${resourceName}" already exists in categroy {${resourceCategory}}`); 82 | 83 | 84 | 85 | this.loadStaging.push({resourceLoader, resourceUrl, resourceName, resourceCategory}); 86 | 87 | 88 | return true; 89 | 90 | } 91 | 92 | /** 93 | * Load the resources that have been staged 94 | * 95 | * @param {Function} onCompleted callback function to be called when loading has been completed (requried) 96 | * @param {Function} onProgress callback function to be called showing progress of the loading (optional) 97 | * 98 | * @memberof ResourceCache 99 | */ 100 | loadResources(onCompleted, onProgress){ 101 | 102 | let resourceCount = this.loadStaging.length; 103 | let totalResourceProgress = 0; 104 | let progressAccumulator = 0; 105 | let progressAccumulatorStep = 100; 106 | 107 | //if the onProgress call back is null then create one 108 | if(!onProgress){ 109 | onProgress = (currentResourceProgress, totalResourceProgress, resourceUrl)=>{ 110 | console.log(`${totalResourceProgress}% completed: Loading ${resourceUrl} at ${currentResourceProgress}% `); 111 | } 112 | } 113 | 114 | 115 | //create wrapper function to be passed to async 116 | let functs = this.loadStaging.map(obj=>{ 117 | 118 | return (cb)=>{ 119 | 120 | obj.resourceLoader( 121 | obj.resourceUrl, 122 | (...args)=>{ 123 | 124 | if(this.cache[obj.resourceCategory] === undefined) this.cache[obj.resourceCategory] = {}; 125 | 126 | //since we do not know the number of parameters that will be passed we simply stores all the 127 | //passed arguments as an array. But when the resource is to be retrieved by name the first element of 128 | //the array is returned; 129 | this.cache[obj.resourceCategory][obj.resourceName] = [...args]; 130 | 131 | //Update the load progress accumulator 132 | progressAccumulator += progressAccumulatorStep; 133 | 134 | console.log(progressAccumulator); 135 | 136 | cb(null); 137 | 138 | }, 139 | (xhr)=>{ 140 | 141 | //Get the current resource loading progress in percentage 142 | let currentResourceProgress = xhr.loaded / xhr.total * 100; 143 | 144 | //Get the total resource loading progress in percentage 145 | totalResourceProgress = (progressAccumulator + currentResourceProgress) / resourceCount; 146 | 147 | onProgress(currentResourceProgress, totalResourceProgress, obj.resourceUrl); 148 | 149 | }, 150 | (err)=>{ 151 | 152 | console.log(`ERROR: Failed to load "${obj.resourceName}" of category "${obj.resourceCategory}"`); 153 | console.log(`ERROR: URL "${obj.resourceUrl}"`); 154 | cb(err); 155 | 156 | } 157 | ) 158 | } 159 | } 160 | ); 161 | 162 | //execute the created wrapper functions serially 163 | series(functs, (err)=>{ 164 | 165 | onCompleted(err); 166 | 167 | }) 168 | 169 | } 170 | 171 | /** 172 | * Retrieves the resource from the cache based on the supplied name 173 | * 174 | * @param {String} resourceName the name of the resource to retrieve (required) 175 | * @param {String} resourceCategory the name of the resource category of the interested resource (optional) 176 | * @returns {Bool} 177 | * 178 | * @memberof ResourceCache 179 | */ 180 | getResource(resourceName, resourceCategory){ 181 | 182 | if(typeof resourceName !== 'string') return null 183 | resourceName = resourceName.toLowerCase(); 184 | 185 | resourceCategory = (!resourceCategory || typeof resourceCategory !== 'string') ? this.DEFAULT_CATEGORY : resourceCategory.toLowerCase(); 186 | 187 | return this.cache[resourceCategory][resourceName][0]; 188 | 189 | } 190 | 191 | /** 192 | * Retrieves the resource from the cache by indexing the resource array of the supplied name 193 | * 194 | * @param {String} resourceName Name of the resource to be retrieved (required) 195 | * @param {String} resourceIndex Index of the resource to be retrieved (required) 196 | * @param {String} resourceCategory Category of resource if any. Defaults to "general" 197 | * @returns {Resource} 198 | * 199 | * @memberof ResourceCache 200 | */ 201 | getResourceByIndex(resourceName, resourceIndex, resourceCategory){ 202 | 203 | if(typeof resourceName !== 'string') return null; 204 | if(typeof resourceIndex !== 'number') return null; 205 | 206 | resourceCategory = (!resourceCategory || typeof resourceCategory !== 'string') ? this.DEFAULT_CATEGORY : resourceCategory.toLowerCase(); 207 | 208 | resourceName = resourceName.toLowerCase(); 209 | 210 | return this.cache[resourceCategory][resourceName][resourceIndex]; 211 | } 212 | 213 | } 214 | 215 | const rc = new ResourceCache(); 216 | 217 | export default rc -------------------------------------------------------------------------------- /src/libs/Stage.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | 22 | /** 23 | * Game Stage Class 24 | * 25 | * 26 | */ 27 | 28 | import * as THREE from 'three' 29 | import * as MISC from './Misc' 30 | import CamControl from './CamControl' 31 | import Proton from './ProtonPlus' 32 | 33 | 34 | export default class Stage { 35 | 36 | constructor({scene = null, camera = null, realityBridge = null, resourceCache = null, listener = null, gameOverCallBack = null}){ 37 | 38 | if( scene === null || camera === null || realityBridge === null || resourceCache === null || listener === null || gameOverCallBack === null ) throw Error("Incomplete initialisation paramters"); 39 | 40 | this.scene = scene; 41 | this.realityBridge = realityBridge; 42 | this.rc = resourceCache; 43 | this.listener = listener; 44 | this.gameOverCallBack = gameOverCallBack; 45 | 46 | this.isSimulating = false; 47 | 48 | this.delayContactCbCheck = 0; //0: Ok to run; 1: Instance is currently running; 2: action has been posted to endpoint 49 | 50 | 51 | 52 | this.setupPlayer(camera); 53 | this.setupSound(listener); 54 | 55 | this.playerHasUpdate = false; 56 | 57 | this.rotDeterminant = [ 58 | {axis: 'x', direction: 1} , 59 | {axis: 'z', direction: 1} , 60 | {axis: 'x', direction: -1} , 61 | {axis: 'z', direction: -1} 62 | ]; 63 | 64 | this.rotDeterminantCursor = 0; 65 | 66 | //temp values 67 | this.pos = new THREE.Vector3(); 68 | this.quat = new THREE.Quaternion(); 69 | 70 | 71 | this.ballerBasePhysicsName = "bb"; 72 | this.rollingBallPhysicsName = "rb"; 73 | this.delayObstaclePhysicsName = "do"; 74 | this.homeObjectPhysicsName = "ho"; 75 | 76 | this.ballDelayContactName = "ctbd"; 77 | this.ballHomeContactName = "ctbh"; 78 | 79 | this.ballMass = 2; 80 | 81 | this.obstacles = [{name:"ob1",pos:[8.074526,0.6,11.857162],scale:[0.6,1.4,5.179307]},{name:"ob2",pos:[1.908815,0.6,6.112903],scale:[0.6,1.4,8.980436]},{name:"ob3",pos:[8.417303,0.599999,5.926694],scale:[12.431125,1.4,0.6]},{name:"ob4",pos:[-4.564306,0.6,-1.919216],scale:[19.636231,1.4,0.6]},{name:"ob5",pos:[6.381232,0.6,-8.360071],scale:[8.756301,1.4,0.6]},{name:"ob6",pos:[6.189222,0.6,-12.406277],scale:[0.6,1.4,3.973]},{name:"ob7",pos:[-9.018096,0.6,-8.399204],scale:[10.819665,1.4,0.6]},{name:"ob8",pos:[-7.182541,0.6,-12.376284],scale:[0.6,1.4,3.973096]}]; 82 | this.delayObstacles = [{name:"dob1",pos:[-8.126742,0.6,7.723256],scale:[12.552326,1.4,0.6]},{name:"dob2",pos:[-3.200462,0.6,4.036478],scale:[9.583503,1.4,0.6]},{name:"dob3",pos:[8.967835,0.6,-1.19023],scale:[0.6,1.4,7.596193]},{name:"dob4",pos:[-7.557407,0.6,-4.21888],scale:[0.6,1.4,3.973096]},{name:"dob5",pos:[-0.825243,0.6,-7.89795],scale:[0.6,1.4,6.541291]}]; 83 | this.stagePhysicsData = [{name:"base",pos:[0,-5,0],scale:[30,10,30]},{name:"north_wall",pos:[0,0.7,-14.7],scale:[28.8,1.4,0.6]},{name:"south_wall",pos:[0,0.7,14.7],scale:[28.8,1.4,0.6]},{name:"east_wall",pos:[14.7,0.7,0],scale:[0.6,1.4,28.8]},{name:"west_wall",pos:[-14.7,0.7,0],scale:[0.6,1.4,28.8]},{name:"top_cover",pos:[0,3,0],scale:[30,2,30]}]; 84 | 85 | this.boxBuff = new THREE.BoxBufferGeometry(); 86 | this.proton = new Proton(); 87 | 88 | } 89 | 90 | 91 | /** 92 | * Setup the player which is actually the camera view 93 | * 94 | * @param {Camera} camera viewing camera 95 | * @memberof Stage 96 | */ 97 | setupPlayer(camera){ 98 | 99 | //The player 100 | this.player = new THREE.Group(); 101 | this.playerPitchNode = new THREE.Group(); 102 | this.playerRollNode = new THREE.Group(); 103 | 104 | this.playerPitchNode.add(this.playerRollNode); 105 | this.player.add(this.playerPitchNode); 106 | 107 | this.scene.add(this.player); 108 | 109 | 110 | this.camController = new CamControl(camera) 111 | this.camInTransition = false; 112 | this.player.add(this.camController); 113 | 114 | 115 | this.playerPitch = 0; 116 | this.playerRoll = 0; 117 | 118 | } 119 | 120 | 121 | 122 | /** 123 | * Setup the sound listener 124 | * 125 | * @param {AudioListener} listener Audio listener for the sound 126 | * @memberof Stage 127 | */ 128 | setupSound(listener){ 129 | 130 | let sound = this.sound = new THREE.Audio( listener ); 131 | let buffer = this.rc.getResource('delay_sound'); 132 | sound.setBuffer( buffer ); 133 | 134 | sound.setVolume( 0.5 ); 135 | 136 | } 137 | 138 | 139 | 140 | 141 | async start( cb ){ 142 | 143 | this.loadBallerBase(); 144 | await this.createStagePhysics(); 145 | await this.createBallPhysics(); 146 | 147 | await this.createDelayObstacle(); 148 | await this.createHomeObject(); 149 | 150 | 151 | await this.realityBridge.setContactPairCheck({name: this.ballDelayContactName, b1: this.rollingBallPhysicsName, b2: this.delayObstaclePhysicsName, f: this.onHitDelayObstacle.bind(this)}); 152 | await this.realityBridge.setContactPairCheck({name: this.ballHomeContactName, b1: this.rollingBallPhysicsName, b2: this.homeObjectPhysicsName, f: this.onHitHomeObject.bind(this)}); 153 | 154 | 155 | cb(); 156 | 157 | } 158 | 159 | 160 | 161 | 162 | 163 | 164 | /** 165 | * Load the mesh object for the stage base 166 | * 167 | * @memberof Stage 168 | */ 169 | loadBallerBase(){ 170 | 171 | let ballerBase = this.ballerBase = this.rc.getResource("baller_base").scene.children[0].clone(); 172 | 173 | ballerBase.position.fromArray([0, 0.06999 ,0]); 174 | 175 | ballerBase.receiveShadow = true; 176 | ballerBase.visible = true; 177 | 178 | this.playerRollNode.add(ballerBase); 179 | 180 | } 181 | 182 | /** 183 | * Create the physics body for the stage 184 | * 185 | * @memberof Stage 186 | */ 187 | createStagePhysics(){ 188 | 189 | let shapes = []; 190 | let quat = [0, 0, 0, 1]; 191 | 192 | this.stagePhysicsData.forEach((obj)=>{ 193 | 194 | let pos = obj.pos; 195 | let size = obj.scale; 196 | 197 | shapes.push({ type:'box', size, pos, quat }) 198 | 199 | }); 200 | 201 | this.createObstacle(shapes); 202 | 203 | 204 | return this.realityBridge.add({ 205 | type : 'compound', 206 | name : this.ballerBasePhysicsName, 207 | shapes, 208 | friction: 0.5, 209 | kinematic : true, 210 | mass : 0 211 | }); 212 | 213 | } 214 | 215 | 216 | /** 217 | * Create obstacle walls for the stage 218 | * 219 | * @param {Array} shapes an array for shapes for the compound physics ridigbody 220 | * @memberof Stage 221 | */ 222 | createObstacle(shapes){ 223 | 224 | let pos = new THREE.Vector3(); 225 | let scale = new THREE.Vector3(); 226 | let quat = [0, 0, 0, 1]; 227 | 228 | let mesh = new THREE.Mesh(this.boxBuff, new THREE.MeshPhongMaterial({color: 0x2c3636})); 229 | 230 | this.obstacles.forEach(obstacle=>{ 231 | 232 | let obstacleMesh = mesh.clone(); 233 | 234 | pos.fromArray(obstacle.pos); 235 | obstacleMesh.position.copy(pos); 236 | 237 | scale.fromArray(obstacle.scale); 238 | obstacleMesh.scale.copy(scale); 239 | 240 | obstacleMesh.castShadow = true; 241 | 242 | this.playerRollNode.add(obstacleMesh); 243 | 244 | shapes.push({type: "box", size: scale.toArray(), pos: pos.toArray(), quat}); 245 | 246 | }); 247 | 248 | } 249 | 250 | /** 251 | * Create the obstacles that will delay the player when hit 252 | * 253 | * @memberof Stage 254 | */ 255 | createDelayObstacle(){ 256 | 257 | let shapes = []; 258 | let pos = new THREE.Vector3(); 259 | let scale = new THREE.Vector3(); 260 | let quat = [0, 0, 0, 1]; 261 | 262 | let mesh = new THREE.Mesh(this.boxBuff, new THREE.MeshPhongMaterial({color: 0x7A0408})); 263 | 264 | this.delayObstacles.forEach(obstacle=>{ 265 | 266 | let obstacleMesh = mesh.clone(); 267 | 268 | pos.fromArray(obstacle.pos); 269 | obstacleMesh.position.copy(pos); 270 | 271 | scale.fromArray(obstacle.scale); 272 | obstacleMesh.scale.copy(scale); 273 | 274 | obstacleMesh.castShadow = true; 275 | 276 | this.playerRollNode.add(obstacleMesh); 277 | 278 | shapes.push({type: "box", size: scale.toArray(), pos: pos.toArray(), quat}); 279 | 280 | }); 281 | 282 | return this.realityBridge.add({ 283 | type : 'compound', 284 | name : this.delayObstaclePhysicsName, 285 | shapes, 286 | kinematic : true, 287 | mass : 0 288 | }); 289 | 290 | } 291 | 292 | 293 | /** 294 | * Create physics for the rolling ball 295 | * 296 | * @memberof Stage 297 | */ 298 | createBallPhysics(){ 299 | 300 | //Create the rolling ball 301 | let ballRadius = 0.6; 302 | let posArray = [11, 0.6, 11]; 303 | 304 | let sphereGeom = new THREE.SphereBufferGeometry(ballRadius, 20, 8); 305 | 306 | let diffuseTex = this.rc.getResource("ball_diffuse"); 307 | let normalTex = this.rc.getResource("ball_normal"); 308 | let specularTex = this.rc.getResource("ball_specular"); 309 | 310 | 311 | let ballMat = new THREE.MeshPhongMaterial({map: diffuseTex, normalMap: normalTex, specularMap: specularTex, shininess: 153}); 312 | 313 | let ball = this.ball = new THREE.Mesh(sphereGeom, ballMat); 314 | 315 | ball.castShadow = true; 316 | 317 | this.scene.add(ball); 318 | ball.position.set(posArray[0], posArray[1], posArray[2]); 319 | 320 | 321 | return this.realityBridge.add({ 322 | type: 'sphere', 323 | name: this.rollingBallPhysicsName, 324 | body: ball, 325 | size: [ballRadius, ballRadius, ballRadius], 326 | pos: posArray, 327 | mass: this.ballMass, 328 | friction: 0.5, 329 | linear: 0.5, 330 | rolling: 0.3 331 | }); 332 | 333 | } 334 | 335 | 336 | createHomeObject(){ 337 | 338 | let radius = 1; 339 | 340 | 341 | let emitNode = this.emitNode = new THREE.Group(); 342 | emitNode.position.set(-12.5, 1, -12.5); 343 | emitNode.scale.set(0.2, 0.2, 0.2) 344 | this.playerRollNode.add(emitNode); 345 | 346 | 347 | 348 | var ar = Array.from(new THREE.SphereBufferGeometry().getAttribute('position').array); 349 | var vertexBuffer = []; 350 | for(var i = 0; i < ar.length; i += 3){ 351 | vertexBuffer.push({x: ar[i], y: ar[i + 1], z: ar[i + 2]}) 352 | } 353 | 354 | let meshZoneGeo = {vertices: vertexBuffer }; 355 | 356 | this.proton.addEmitter(this.createEmitter(emitNode, meshZoneGeo)); 357 | this.proton.addRender(new Proton.SpriteRender( this.scene)); 358 | 359 | 360 | 361 | //Create physics 362 | let shapes = []; 363 | let quat = [0, 0, 0, 1]; 364 | shapes.push({type: "sphere", size: [radius, radius, radius], pos: emitNode.position.toArray(), quat}); 365 | 366 | return this.realityBridge.add({ 367 | type : 'compound', 368 | name : this.homeObjectPhysicsName, 369 | shapes, 370 | kinematic : true, 371 | mass : 0 372 | }); 373 | 374 | 375 | 376 | } 377 | 378 | 379 | 380 | createSprite() { 381 | var map = this.rc.getResource("dot"); 382 | var material = new THREE.SpriteMaterial({ 383 | map: map, 384 | color: 0xff0000, 385 | blending: THREE.AdditiveBlending, 386 | fog: true 387 | }); 388 | return new THREE.Sprite(material); 389 | } 390 | 391 | 392 | createEmitter(refnode, meshZoneGeo) { 393 | 394 | let emitter = this.emitter = new Proton.AdvEmitter(refnode); 395 | emitter.rate = new Proton.Rate(new Proton.Span(1, 3), new Proton.Span(.02)); 396 | //addInitialize 397 | emitter.addInitialize(new Proton.Position(new Proton.MeshZoneMod(meshZoneGeo, 5))); 398 | emitter.addInitialize(new Proton.Mass(1)); 399 | emitter.addInitialize(new Proton.Radius(1, 3)); 400 | emitter.addInitialize(new Proton.Life(0.6)); 401 | emitter.addInitialize(new Proton.Body(this.createSprite())); 402 | 403 | //addBehaviour 404 | let randomBehaviour = new Proton.RandomDrift(0.1, 0.1, 0.1); 405 | let gravity = new Proton.Gravity(0); 406 | 407 | emitter.addBehaviour(gravity); 408 | emitter.addBehaviour(randomBehaviour); 409 | emitter.addBehaviour(new Proton.Color(['#00aeff', '#0fa954', '#54396e', '#e61d5f'])); 410 | emitter.addBehaviour(new Proton.Color('random')); 411 | 412 | emitter.p.x = 0; 413 | emitter.p.y = 0; 414 | emitter.p.z = 0; 415 | emitter.emit(); 416 | 417 | 418 | return emitter; 419 | } 420 | 421 | 422 | async restart( cb ){ 423 | 424 | await this.realityBridge.clearAllContact(); 425 | 426 | await this.realityBridge.remove({name: this.rollingBallPhysicsName}); 427 | 428 | this.rotDeterminantCursor = 0; 429 | 430 | this.playerPitch = 0; 431 | this.playerRoll = 0; 432 | 433 | this.playerPitchNode.rotation.set(0, 0, 0); 434 | this.playerRollNode.rotation.set(0, 0, 0); 435 | 436 | 437 | let pos = [0, 0, 0]; 438 | let quat = [0, 0, 0, 1]; 439 | 440 | let matrixArray = []; 441 | 442 | matrixArray.push([this.ballerBasePhysicsName, pos, quat]); 443 | matrixArray.push([this.delayObstaclePhysicsName, pos, quat]); 444 | matrixArray.push([this.homeObjectPhysicsName, pos, quat]); 445 | 446 | await this.realityBridge.setMatrixArray(matrixArray); 447 | 448 | await this.realityBridge.updateReality(null, true); 449 | 450 | 451 | // recreate ball physics 452 | let ballRadius = 0.6; 453 | let posArray = [11, 0.6, 11]; 454 | 455 | 456 | this.ball.rotation.set(0, 0, 0); 457 | this.ball.position.fromArray(posArray); 458 | 459 | await this.realityBridge.add({ 460 | type: 'sphere', 461 | name: this.rollingBallPhysicsName, 462 | body: this.ball, 463 | size: [ballRadius, ballRadius, ballRadius], 464 | pos: posArray, 465 | mass: this.ballMass, 466 | friction: 0.5, 467 | linear: 0.5, 468 | rolling: 0.3 469 | }); 470 | 471 | await this.realityBridge.setContactPairCheck({name: this.ballDelayContactName, b1: this.rollingBallPhysicsName, b2: this.delayObstaclePhysicsName, f: this.onHitDelayObstacle.bind(this)}); 472 | await this.realityBridge.setContactPairCheck({name: this.ballHomeContactName, b1: this.rollingBallPhysicsName, b2: this.homeObjectPhysicsName, f: this.onHitHomeObject.bind(this)}); 473 | 474 | //reset the camera perspective 475 | this.camController.resetPerspective(); 476 | 477 | cb(); 478 | 479 | } 480 | 481 | 482 | /** 483 | * Handle when there is contact between the rolling ball and delay obstacle 484 | * 485 | * @param {Boolean} status True when there is a contact and False if otherwise 486 | * @memberof Stage 487 | */ 488 | async onHitDelayObstacle(status){ 489 | 490 | if(!status) return; 491 | 492 | //to prevent being called when an already called instance has not yet finished executing 493 | if(this.delayContactCbCheck === 1 || this.delayContactCbCheck === 2) return; 494 | 495 | if(this.sound.isPlaying) this.sound.stop(); 496 | 497 | this.sound.play(); 498 | 499 | this.pos.set(11, 0.6, 11); 500 | let posArray = this.ballerBase.localToWorld(this.pos).toArray();// [11, 0.6, 11]; 501 | let quatArray = [0, 0, 0, 1]; 502 | 503 | await this.realityBridge.setMatrix([this.rollingBallPhysicsName, posArray, quatArray]); 504 | 505 | this.delayContactCbCheck = 2 506 | 507 | } 508 | 509 | 510 | onHitHomeObject(status){ 511 | 512 | if(!status) return; 513 | 514 | this.gameOverCallBack(true); 515 | 516 | } 517 | 518 | 519 | 520 | /** 521 | * Handles pre-render update 522 | * 523 | * @param {Object} eventData Event Data passed to the method 524 | * @memberof Application 525 | */ 526 | async handleUpdate(eventData){ 527 | 528 | //update proton 529 | this.proton.update(); 530 | 531 | //rotate the home object 532 | this.emitNode.rotateX(MISC.deg2Rad * 3); 533 | 534 | if(this.isSimulating === true) return; 535 | 536 | //stop any instance of this function from executing while physics simulation is going on 537 | this.isSimulating = true; 538 | 539 | //Get the time stamp value 540 | let timeStep = eventData.timeStep; 541 | 542 | 543 | //run update for the camera controller 544 | this.camController.handleUpdate(eventData); 545 | 546 | //update player transform if player has update and there is no pending delay contact action 547 | if(this.delayContactCbCheck === 0 && this.playerHasUpdate) await this.updatePlayerTransform(); 548 | 549 | 550 | //lastly, step the physics world 551 | await this.realityBridge.updateReality(timeStep); 552 | 553 | 554 | if( this.delayContactCbCheck === 2 ) this.delayContactCbCheck = 0; 555 | 556 | this.isSimulating = false; 557 | 558 | } 559 | 560 | 561 | /** 562 | * Handle keydown event 563 | * 564 | * @param {Object} eventData Event Data passed to the method 565 | * @memberof Application 566 | */ 567 | handleKeyDown(eventData){ 568 | 569 | switch ( eventData.keyCode ) { 570 | case 65: // a 571 | this.turnCamController(false) 572 | break; 573 | 574 | case 68: // d 575 | this.turnCamController(true) 576 | break; 577 | 578 | } 579 | 580 | } 581 | 582 | handleMouseMove(eventData){ 583 | 584 | if(this.camInTransition !== false || this.playerHasUpdate === true ) return; 585 | 586 | let rotDet = this.rotDeterminant[this.rotDeterminantCursor]; 587 | let moveScaleFactor = 0.3; 588 | 589 | let pitch = eventData.movementY * rotDet.direction * moveScaleFactor; 590 | let roll = eventData.movementX * rotDet.direction * moveScaleFactor; 591 | 592 | //Swapping with destructured arrays 593 | if(rotDet.axis === 'z') [pitch, roll] = [-roll, pitch] 594 | 595 | this.rotatePlayer(pitch, roll); 596 | 597 | } 598 | 599 | handleChangedCamView( moveRight ){ 600 | 601 | if(moveRight === undefined || typeof moveRight !== 'boolean') moveRight = true; 602 | 603 | let direction = moveRight ? 1 : -1 604 | this.rotDeterminantCursor = this.rotDeterminantCursor + direction; 605 | 606 | //ensure the cursor loops round 607 | if(this.rotDeterminantCursor < 0 ) this.rotDeterminantCursor = 3 608 | else if(this.rotDeterminantCursor > 3) this.rotDeterminantCursor = 0 609 | 610 | 611 | } 612 | 613 | 614 | rotatePlayer(pitch, roll){ 615 | 616 | this.playerPitch = pitch; 617 | this.playerRoll = -roll; 618 | 619 | this.playerHasUpdate = true; 620 | 621 | } 622 | 623 | 624 | turnCamController(right){ 625 | 626 | if( this.camInTransition ) return; 627 | 628 | if( right === null || right === undefined || typeof right !== "boolean") right = true; 629 | 630 | this.camInTransition = true; 631 | 632 | if(right) this.camController.turnRight(()=>{ this.camInTransition = false;}); 633 | else this.camController.turnLeft(()=>{ this.camInTransition = false;}); 634 | 635 | this.handleChangedCamView(right); 636 | 637 | } 638 | 639 | 640 | 641 | updatePlayerTransform(){ 642 | 643 | //cap the rotation values to avoid extreme angles 644 | let x = this.playerPitchNode.rotation.x; 645 | let z = this.playerRollNode.rotation.z; 646 | 647 | 648 | let rotateXValue = Math.abs( x + MISC.deg2Rad * this.playerPitch * 0.2 ) > 0.116 ? 0 : MISC.deg2Rad * this.playerPitch * 0.2; 649 | let rotateZValue = Math.abs( z + MISC.deg2Rad * this.playerRoll * 0.2 ) > 0.116 ? 0 : MISC.deg2Rad * this.playerRoll * 0.2; 650 | 651 | this.playerPitchNode.rotateX( rotateXValue); 652 | this.playerRollNode.rotateZ( rotateZValue ); 653 | 654 | this.ballerBase.getWorldQuaternion(this.quat); 655 | this.ballerBase.getWorldPosition(this.pos); 656 | 657 | let pos = [0, 0, 0]; 658 | 659 | let matrixArray = []; 660 | 661 | matrixArray.push([this.ballerBasePhysicsName, pos, this.quat.toArray()]); 662 | matrixArray.push([this.delayObstaclePhysicsName, pos, this.quat.toArray()]); 663 | matrixArray.push([this.homeObjectPhysicsName, pos, this.quat.toArray()]); 664 | 665 | this.playerHasUpdate = false; 666 | 667 | return this.realityBridge.setMatrixArray(matrixArray); 668 | 669 | 670 | } 671 | 672 | 673 | 674 | } -------------------------------------------------------------------------------- /src/libs/TextOverlay.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | 22 | /** 23 | * Text Overlay Class 24 | * 25 | * @class TextOverlay 26 | */ 27 | class TextOverlay{ 28 | constructor(){ 29 | 30 | this.textOverlayDisplay = document.createElement('div'); 31 | this.injectCSS(); 32 | this.isTyping = false; 33 | 34 | } 35 | 36 | 37 | /** 38 | * Initilize the class to display overlay 39 | * 40 | * @param {String} displayTitle what should be displayed 41 | * @memberof TextOverlay 42 | */ 43 | initialize(displayTitle){ 44 | displayTitle = displayTitle || "Loading..." 45 | 46 | this.textOverlayDisplay.innerHTML = ` 47 |
48 | 49 |
50 |
51 | ${displayTitle} 52 |
53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 | 72 |
73 | 74 | 75 | 76 |
77 | 78 | 103 | 104 | 105 | 106 | 121 | 122 |
123 | `; 124 | 125 | document.body.appendChild(this.textOverlayDisplay); 126 | 127 | this.showCoverOverlay(); 128 | 129 | } 130 | 131 | 132 | showCoverOverlay(){ 133 | 134 | document.getElementById('cover-overlay').style.display = "block"; 135 | document.getElementById('gameview-overlay').style.display = "none"; 136 | 137 | } 138 | 139 | showGameViewOverlay(){ 140 | 141 | 142 | document.getElementById('cover-overlay').style.display = "none"; 143 | document.getElementById('gameview-overlay').style.display = "block"; 144 | 145 | 146 | } 147 | 148 | 149 | 150 | showTimerDisplay(){ 151 | 152 | document.getElementById('timer-parent').style.display = 'block'; 153 | 154 | } 155 | 156 | hideTimerDisplay(){ 157 | 158 | document.getElementById('timer-parent').style.display = 'none'; 159 | 160 | } 161 | 162 | showGameInstruction(){ 163 | 164 | document.getElementById("loader").style.display = "none"; 165 | let instruction = "`Instruction`"; 166 | instruction += "^1000\nMove the ball to the rotating particles at the end of the board"; 167 | instruction += "^2500\nBe careful not to collide with the red bricks, they will delay you"; 168 | instruction += "^2500\n\n`Controls`" 169 | instruction += "^1000\nUse mouse movement to tilt board and Key A and D to change viewing angle"; 170 | 171 | 172 | //somehow its seems like Typed.js npm modules skips the first string in the strings array 173 | //so an empty string is used to make it behave as expected 174 | var typed = new Typed("#instruction", { 175 | 176 | startDelay: 1000, 177 | strings: ["", instruction], 178 | smartBackspace: true, // Default value 179 | typeSpeed: 10, 180 | showCursor: false, 181 | onStart: ()=>{ 182 | 183 | this.isTyping = true; 184 | 185 | }, 186 | onComplete: ()=>{ 187 | 188 | setTimeout(()=>{ 189 | 190 | document.getElementById('startMsg').style.display = 'block'; 191 | this.isTyping = false; 192 | 193 | }, 3000) 194 | 195 | } 196 | 197 | }); 198 | 199 | typed.start(); 200 | 201 | } 202 | 203 | showPauseDisplay(){ 204 | 205 | document.getElementById('pause-overlay').style.display = 'block'; 206 | 207 | } 208 | 209 | 210 | hidePauseDisplay(){ 211 | 212 | document.getElementById('pause-overlay').style.display = 'none'; 213 | 214 | } 215 | 216 | 217 | showGameOver(){ 218 | 219 | 220 | document.getElementById('cover-overlay').style.display = "none"; 221 | document.getElementById('gameview-overlay').style.display = "none"; 222 | 223 | 224 | document.getElementById('time-score').innerText = document.getElementById('timer-display').innerText; 225 | document.getElementById('gameover-overlay').style.display = "block"; 226 | 227 | } 228 | 229 | hideGameOver(){ 230 | 231 | document.getElementById('gameover-overlay').style.display = 'none'; 232 | 233 | } 234 | 235 | 236 | 237 | 238 | 239 | 240 | /** 241 | * Update to show the progress of the loading process 242 | * 243 | * @param {any} percentage A normalized value (0 - 1) showing the current load progress (required) 244 | * @param {any} message An optional text to display (optional) 245 | * @memberof TextOverlay 246 | */ 247 | updateLoadingDisplay(percentage, message){ 248 | 249 | percentage = percentage || 0; 250 | 251 | if(Number.isNaN(Number.parseFloat(percentage))) percentage = 0; 252 | if(percentage > 100) percentage = 100; 253 | if(percentage < 0) percentage = 0; 254 | 255 | if(typeof message !== 'string') message = 'loading...'; 256 | 257 | //document.getElementById('progress-display').innerText = percentage + "%"; 258 | document.getElementById('progress-text').innerText = message; 259 | 260 | } 261 | 262 | updateTimeDisplay(timeText){ 263 | 264 | document.getElementById('timer-display').innerText = timeText; 265 | 266 | } 267 | 268 | 269 | 270 | /** 271 | * Inject the needed css style to the head of the html document 272 | * 273 | * @memberof TextOverlay 274 | */ 275 | injectCSS(){ 276 | 277 | let css = ` 278 | .lds-dual-ring { 279 | display: inline-block; 280 | width: 64px; 281 | height: 64px; 282 | } 283 | .lds-dual-ring:after { 284 | content: " "; 285 | display: block; 286 | width: 46px; 287 | height: 46px; 288 | margin: 1px; 289 | border-radius: 50%; 290 | border: 5px solid #cef; 291 | border-color: #cef transparent #cef transparent; 292 | animation: lds-dual-ring 1.2s linear infinite; 293 | } 294 | @keyframes lds-dual-ring { 295 | 0% { 296 | transform: rotate(0deg); 297 | } 298 | 100% { 299 | transform: rotate(360deg); 300 | } 301 | } 302 | 303 | .grid-display{ 304 | 305 | 306 | background-color: #3c3c3c !important; 307 | background-color: rgba(149, 138, 124, 0.5); 308 | background-image: linear-gradient(0deg, transparent 24%, rgba(255, 255, 255, 0.05) 25%, rgba(255, 255, 255, 0.05) 26%, transparent 27%, transparent 74%, rgba(255, 255, 255, 0.05) 75%, rgba(255, 255, 255, 0.05) 76%, transparent 77%, transparent), linear-gradient(90deg, transparent 24%, rgba(255, 255, 255, 0.05) 25%, rgba(255, 255, 255, 0.05) 26%, transparent 27%, transparent 74%, rgba(255, 255, 255, 0.05) 75%, rgba(255, 255, 255, 0.05) 76%, transparent 77%, transparent); 309 | background-size: 50px 50px; 310 | 311 | } 312 | 313 | #text-overlay{ 314 | width: 100%; 315 | height: 100% 316 | } 317 | 318 | #cover-overlay { 319 | position: absolute; 320 | width: 100%; 321 | height: 100%; 322 | } 323 | 324 | #display-title{ 325 | width: 500px; 326 | height: 150px; 327 | margin: 0px auto; 328 | font-size: 100px; 329 | font-family: jokerman; 330 | color: #cb785e; 331 | text-shadow: 2px 2px 5px black; 332 | text-align: center; 333 | margin-top: 50px; 334 | } 335 | 336 | 337 | 338 | 339 | #overlay-box{ 340 | 341 | width: 100%; 342 | height: 200px; 343 | 344 | display: -webkit-box; 345 | display: -moz-box; 346 | display: box; 347 | 348 | -webkit-box-orient: horizontal; 349 | -moz-box-orient: horizontal; 350 | box-orient: horizontal; 351 | 352 | -webkit-box-pack: center; 353 | -moz-box-pack: center; 354 | box-pack: center; 355 | 356 | -webkit-box-align: center; 357 | -moz-box-align: center; 358 | box-align: center; 359 | 360 | color: #ffffff; 361 | text-align: center; 362 | } 363 | 364 | #progress-display{ 365 | font-family: jokerman; 366 | font-size: 30px; 367 | color: #cedeac; 368 | text-shadow: 2px 2px 5px black; 369 | } 370 | 371 | #progress-text{ 372 | font-family: "Comic Sans MS"; 373 | color: black; 374 | } 375 | 376 | #timer-parent{ 377 | position: absolute; 378 | width: 100%; 379 | height: 100%; 380 | margin: 0px auto; 381 | text-align: right; 382 | font-family: jokerman; 383 | color: #cb785e; 384 | text-shadow: 2px 2px 2px #8a6161 385 | } 386 | 387 | #timer-display{ 388 | font-size: 20px 389 | } 390 | 391 | #pause-overlay { 392 | position: absolute; 393 | width: 100%; 394 | height: 100%; 395 | background-color: rgba(59, 60, 60, 0.9); 396 | } 397 | 398 | #gameover-overlay { 399 | position: absolute; 400 | width: 100%; 401 | height: 100%; 402 | background-color: rgba(59, 60, 60, 0.9); 403 | } 404 | `, 405 | head = document.head || document.getElementsByTagName('head')[0], 406 | style = document.createElement('style'); 407 | 408 | style.type = 'text/css'; 409 | if (style.styleSheet){ 410 | // This is required for IE8 and below. 411 | style.styleSheet.cssText = css; 412 | } else { 413 | style.appendChild(document.createTextNode(css)); 414 | } 415 | 416 | head.appendChild(style); 417 | } 418 | } 419 | 420 | const ldOverlay = new TextOverlay(); 421 | 422 | export default ldOverlay; -------------------------------------------------------------------------------- /src/libs/ViewPort.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 BlueMagnificent 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | 22 | export default class ViewPort { 23 | /** 24 | * Creates an instance of ViewPort. 25 | * @param {Object} scene 26 | * @param {Object} camera 27 | * @param {Object} renderer 28 | * @memberof ViewPort 29 | */ 30 | constructor(scene, camera, renderer){ 31 | this.scene = scene; 32 | this.camera = camera; 33 | this.renderer = renderer; 34 | } 35 | 36 | /** 37 | * Render a scene to the viewport based on the scene and camera 38 | * 39 | * @memberof ViewPort 40 | */ 41 | renderToViewport(){ 42 | this.renderer.render( this.scene, this.camera ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/threejs/stats.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function h(a){c.appendChild(a.dom);return a}function k(a){for(var d=0;de+1E3&&(r.update(1E3*a/(c-e),100),e=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){g=this.end()},domElement:c,setMode:k}}; 4 | Stats.Panel=function(h,k,l){var c=Infinity,g=0,e=Math.round,a=e(window.devicePixelRatio||1),r=80*a,f=48*a,t=3*a,u=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=f;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,f);b.fillStyle=k;b.fillText(h,t,u);b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(f, 5 | v){c=Math.min(c,f);g=Math.max(g,f);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=k;b.fillText(e(f)+" "+h+" ("+e(c)+"-"+e(g)+")",t,u);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,e((1-f/v)*p))}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | 4 | module.exports = { 5 | devServer: { 6 | static: './dist', 7 | port: 3333, 8 | historyApiFallback: true, 9 | watchFiles: './src', 10 | client: { 11 | overlay: false, 12 | }, 13 | }, 14 | entry: './src/index.js', 15 | output: { 16 | filename: 'js/index.js', 17 | path: path.resolve(__dirname, 'dist') 18 | }, 19 | devtool: 'inline-source-map', 20 | externals: { 21 | TWEEN: 'TWEEN', 22 | Typed: 'Typed', 23 | Comlink: 'Comlink' 24 | } 25 | }; --------------------------------------------------------------------------------