├── .gitignore
├── .npmignore
├── .nvmrc
├── .prettierrc.js
├── LICENSE
├── README.md
├── babel.config.js
├── config
├── index.js
├── rollup.main.js
└── rollup.physics.js
├── dist
└── ammo.js
├── examples
├── 3d_ui
│ ├── index.html
│ ├── index.js
│ └── ui.js
├── animations
│ ├── assets
│ │ ├── animated.fbx
│ │ ├── character_duck.fbx
│ │ ├── duck_animation.fbx
│ │ ├── fire.png
│ │ └── skeleton_animation.fbx
│ ├── index.html
│ └── index.js
├── cars_physics
│ ├── .DS_Store
│ ├── assets
│ │ ├── .DS_Store
│ │ └── models
│ │ │ ├── .DS_Store
│ │ │ ├── buggy.gltf
│ │ │ ├── model.bin
│ │ │ ├── model.gltf
│ │ │ └── wheel.gltf
│ ├── index.html
│ ├── index.js
│ └── speedometer.js
├── controls_events
│ ├── box_A.fbx
│ ├── car_police.obj
│ ├── firehydrant.gltf
│ ├── index.html
│ ├── index.js
│ ├── materials
│ │ ├── car_police.mtl
│ │ ├── firehydrant.bin
│ │ ├── radio.mtl
│ │ └── watertower.mtl
│ ├── radio.obj
│ ├── simplescene.mtl
│ ├── simplescene.obj
│ ├── textures
│ │ └── citybits_texture.png
│ └── watertower.obj
├── css
│ ├── .DS_Store
│ ├── M.css
│ ├── app.css
│ └── ka1.ttf
├── deferredSound
│ ├── click.ogg
│ ├── fire.png
│ ├── fire.wav
│ ├── forest.mp3
│ ├── index.html
│ ├── index.js
│ ├── radio.mtl
│ ├── radio.obj
│ ├── radio.wav
│ ├── radiotune.mp3
│ ├── simplescene.model
│ ├── simplescene.mtl
│ ├── simplescene.obj
│ ├── sound.png
│ ├── speaker-hifi-bold.png
│ └── swat.mp3
├── directionalSound
│ ├── click.ogg
│ ├── fire.png
│ ├── fire.wav
│ ├── forest.mp3
│ ├── index.html
│ ├── index.js
│ ├── radio.mtl
│ ├── radio.obj
│ ├── radio.wav
│ ├── radiotune.mp3
│ ├── simplescene.model
│ ├── simplescene.mtl
│ ├── simplescene.obj
│ └── swat.mp3
├── hierarchy
│ ├── index.html
│ └── index.js
├── index.html
├── loadModel
│ ├── click.ogg
│ ├── fire.png
│ ├── fire.wav
│ ├── forest.mp3
│ ├── index.html
│ ├── index.js
│ ├── radio.mtl
│ ├── radio.obj
│ ├── radio.wav
│ ├── radiotune.mp3
│ ├── simplescene.model
│ ├── simplescene.mtl
│ ├── simplescene.obj
│ ├── sound.png
│ ├── speaker-hifi-bold.png
│ └── swat.mp3
├── loadModelMaterials
│ ├── box_A.fbx
│ ├── car_police.obj
│ ├── firehydrant.gltf
│ ├── index.html
│ ├── index.js
│ ├── materials
│ │ ├── car_police.mtl
│ │ ├── firehydrant.bin
│ │ ├── radio.mtl
│ │ └── watertower.mtl
│ ├── radio.obj
│ ├── simplescene.mtl
│ ├── simplescene.obj
│ ├── textures
│ │ └── citybits_texture.png
│ └── watertower.obj
├── material_test
│ ├── assets
│ │ └── models
│ │ │ ├── buggy.gltf
│ │ │ ├── castletrack.fbx
│ │ │ ├── castletrack.glb
│ │ │ ├── castletrack.mtl
│ │ │ ├── castletrack.obj
│ │ │ ├── forestTileTower.glb
│ │ │ ├── hex_forest_roadB.gltf.glb
│ │ │ ├── model.bin
│ │ │ ├── model.gltf
│ │ │ └── wheel.gltf
│ ├── index.html
│ └── index.js
├── particles_custom_emitter
│ ├── dot.png
│ ├── dot_1.png
│ ├── fire.png
│ ├── green_energy.png
│ ├── hex_forest_roadB.gltf.glb
│ ├── index.html
│ └── index.js
├── particles_explosion
│ ├── .DS_Store
│ ├── dot.png
│ ├── dot_1.png
│ ├── fire.png
│ ├── index.html
│ └── index.js
├── particles_fire
│ ├── dot.png
│ ├── dot_1.png
│ ├── fire.png
│ ├── index.html
│ └── index.js
├── particles_sparkles
│ ├── dot.png
│ ├── dot_1.png
│ ├── fire.png
│ ├── green_energy.png
│ ├── index.html
│ └── index.js
├── physics
│ ├── index.html
│ └── index.js
├── rotateTo
│ ├── assets
│ │ └── models
│ │ │ ├── buggy.gltf
│ │ │ ├── castletrack.fbx
│ │ │ ├── castletrack.glb
│ │ │ ├── castletrack.mtl
│ │ │ ├── castletrack.obj
│ │ │ ├── forestTileTower.glb
│ │ │ ├── hex_forest_roadB.gltf.glb
│ │ │ ├── model.bin
│ │ │ ├── model.gltf
│ │ │ └── wheel.gltf
│ ├── index.html
│ └── index.js
├── scaleTo
│ ├── assets
│ │ └── models
│ │ │ ├── buggy.gltf
│ │ │ ├── castletrack.fbx
│ │ │ ├── castletrack.glb
│ │ │ ├── castletrack.mtl
│ │ │ ├── castletrack.obj
│ │ │ ├── forestTileTower.glb
│ │ │ ├── hex_forest_roadB.gltf.glb
│ │ │ ├── model.bin
│ │ │ ├── model.gltf
│ │ │ └── wheel.gltf
│ ├── index.html
│ └── index.js
├── selection_effect
│ ├── index.html
│ └── index.js
├── sound
│ ├── click.ogg
│ ├── index.html
│ └── index.js
├── spotlight
│ ├── assets
│ │ ├── models
│ │ │ ├── Textures
│ │ │ │ └── roof(Clone).png
│ │ │ ├── lamp.gltf
│ │ │ ├── lamp.json
│ │ │ ├── lamp.mtl
│ │ │ └── lamp.obj
│ │ └── textures
│ │ │ ├── ambientlight.png
│ │ │ ├── hemispherelight.png
│ │ │ ├── lightbulb.png
│ │ │ ├── pointlight.png
│ │ │ ├── spotlight.png
│ │ │ ├── sunlight.png
│ │ │ └── target.png
│ ├── index.html
│ └── index.js
├── sunlight
│ ├── assets
│ │ ├── models
│ │ │ ├── Textures
│ │ │ │ └── roof(Clone).png
│ │ │ ├── lamp.gltf
│ │ │ ├── lamp.json
│ │ │ ├── lamp.mtl
│ │ │ └── lamp.obj
│ │ └── textures
│ │ │ ├── ambientlight.png
│ │ │ ├── hemispherelight.png
│ │ │ ├── lightbulb.png
│ │ │ ├── pointlight.png
│ │ │ ├── spotlight.png
│ │ │ ├── sunlight.png
│ │ │ └── target.png
│ ├── index.html
│ └── index.js
├── textures
│ ├── assets
│ │ ├── Wood_025_ambientOcclusion.jpg
│ │ ├── Wood_025_basecolor.jpg
│ │ ├── Wood_025_height.png
│ │ ├── Wood_025_normal.jpg
│ │ └── Wood_025_roughness.jpg
│ ├── index.html
│ └── index.js
└── trails
│ ├── index.html
│ ├── index.js
│ └── particle.png
├── package-lock.json
├── package.json
└── src
├── .DS_Store
├── audio
├── Audio.js
├── ambientSound.js
├── directionalSound.js
└── sound.js
├── controls
├── Controls.js
├── FirstPersonControl.js
├── FlyControl.js
├── Orbit.js
├── Transform.js
├── TransformGizmo.js
├── TransformPlane.js
└── constants.js
├── core
├── Assets.js
├── Level.js
├── Scene.js
├── Stats.js
├── config.js
├── input
│ ├── Gamepad.js
│ ├── Input.js
│ ├── Keyboard.js
│ ├── Mouse.js
│ └── constants.js
├── universe.js
└── window.js
├── entities
├── Element.js
├── Entity.js
├── animations
│ ├── AnimationHandler.js
│ ├── SpriteAnimationHandler.js
│ └── lib
│ │ └── NodeFrame.js
├── base
│ ├── AnimatedSprite.js
│ ├── Axes.js
│ ├── Box.js
│ ├── Cone.js
│ ├── Cube.js
│ ├── CurveLine.js
│ ├── Cylinder.js
│ ├── Grid.js
│ ├── HelperSprite.js
│ ├── Label.js
│ ├── Line.js
│ ├── Plane.js
│ ├── Sphere.js
│ ├── Sprite.js
│ └── index.js
├── camera.js
├── constants.js
└── index.js
├── fx
├── materials
│ ├── Atmosphere.js
│ ├── Mirror.js
│ ├── Ocean.js
│ ├── OceanShaders.js
│ ├── Water.js
│ └── Water.old.js
├── particles
│ ├── Explosion.js
│ ├── Fire.js
│ ├── Fountain.js
│ ├── ParticleEmitter.js
│ ├── ParticleEmitterGroup.js
│ ├── Particles.js
│ ├── ProtonParticleEmitter.js
│ ├── Rain.js
│ ├── Snow.js
│ ├── Trail.js
│ └── constants.js
├── postprocessing
│ ├── .DS_Store
│ ├── BloomPass.js
│ ├── BokehPass.js
│ ├── DepthOfField.js
│ ├── GlitchEffect.js
│ ├── HueSaturationEffect.js
│ ├── OutlineEffect.js
│ ├── PixelEffect.js
│ ├── PostProcessing.js
│ ├── SelectiveOutline.js
│ ├── SepiaEffect.js
│ ├── passes
│ │ ├── ClearMaskPass.js
│ │ ├── EffectComposer.js
│ │ ├── MaskPass.js
│ │ ├── Pass.js
│ │ ├── RenderPass.js
│ │ └── ShaderPass.js
│ └── shaders
│ │ ├── BokehShader.js
│ │ ├── ConvolutionShader.js
│ │ ├── CopyShader.js
│ │ └── DigitalGlitchShader.js
├── scenery
│ ├── Sky.js
│ └── Skybox.js
└── shaders
│ ├── Shaders.js
│ └── shader.js
├── images
└── Images.js
├── index.js
├── lib
├── Color.js
├── colliders.js
├── constants.js
├── dom.js
├── easing.js
├── features.js
├── fflate.js
├── functions.js
├── location.js
├── map.js
├── math.js
├── meshUtils.js
├── messages.js
├── network.js
├── palettes.js
├── query.js
├── strings.js
├── url.js
├── utils
│ └── assets.js
├── uuid.js
└── workers.js
├── lights
├── HemisphereLight.js
├── Lights.js
├── SpotLight.js
├── SunLight.js
├── ambientLight.js
├── constants.js
├── csm
│ ├── CascadeShadowMaps.js
│ ├── Frustum.js
│ └── Shader.js
├── light.js
├── pointLight.js
└── utils.js
├── loaders
├── ColladaLoader.js
├── FBXLoader.js
├── GLTFLoader.js
├── Loader.js
├── MTLLoader.js
├── OBJLoader.js
├── OBJMTLLoader.js
├── RequirementsTracer.js
├── TGALoader.js
├── curves
│ ├── NURBSCurve.js
│ └── NURBSUtils.js
└── utils.js
├── materials
├── Toon.js
├── constants.js
└── helpers.js
├── models
├── Models.js
└── SkeletonUtils.js
├── physics
├── constants.js
├── hitbox.js
├── index.js
├── messages.js
├── utils.js
└── worker
│ ├── effects.js
│ ├── elements.js
│ ├── index.js
│ ├── lib
│ ├── dispatcher.js
│ └── math.js
│ ├── player.js
│ ├── vehicles.js
│ └── world.js
├── router
└── Router.js
├── runner
└── GameRunner.js
├── scripts
├── BaseScript.js
├── Scripts.js
├── StaticScript.js
└── builtin
│ ├── BaseCar.js
│ ├── SmoothCameraFollow.js
│ ├── SmoothCarFollow.js
│ └── Trails.js
├── storage
└── storage.js
├── store
├── Store.js
├── actions
│ ├── input.js
│ ├── location.js
│ ├── network.js
│ ├── storage.js
│ ├── types.js
│ └── ui.js
├── index.js
└── reducers
│ ├── index.js
│ ├── info.js
│ ├── input.js
│ ├── location.js
│ ├── network.js
│ ├── root.js
│ ├── storage.js
│ └── ui.js
├── ui
├── BaseUI.js
├── LabelComponent.js
└── index.js
└── video
└── Video.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | npm-debug.log
3 |
4 | examples/**/node_modules/*
5 |
6 | dist/mage.js
7 | dist/mage.node.js
8 |
9 | .DS_Store
10 | .idea
11 | .vscode
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | examples
3 | template
4 | scripts
5 | .DS_Store
6 | config
7 | babel.config.js
8 | webpack.config.js
9 | webpack.node.js
10 | .gitignore
11 | package-lock.json
12 | !dist/mage.js
13 | !dist/mage.node.js
14 | !dist/mage.physics.js
15 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14.19.3
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | printWidth: 100,
5 | trailingComma: "all",
6 | arrowParens: "avoid",
7 | semi: true,
8 | tabWidth: 4,
9 |
10 | overrides: [
11 | {
12 | files: ["*.md"],
13 | options: { printWidth: 120 },
14 | },
15 | {
16 | files: [".toolsharerc"],
17 | options: { parser: "yaml" },
18 | },
19 | ],
20 | };
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 - 2018 by Marco Stagni and contributors.
2 |
3 | Some rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are
7 | met:
8 |
9 | * Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above
13 | copyright notice, this list of conditions and the following
14 | disclaimer in the documentation and/or other materials provided
15 | with the distribution.
16 |
17 | * The names of the contributors may not be used to endorse or
18 | promote products derived from this software without specific
19 | prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
33 |
34 | Mage contains third party software in the 'app/vendor' directory: each
35 | file/module in this directory is distributed under its original license.
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mage
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 |
9 | ---
10 |
11 | Mage is a game engine built on top of THREEJS. It features all you need to create fully interactive 3D application that can be distributed via web, desktop or mobile.
12 |
13 | [Documentation](https://www.mage.studio/docs)
14 |
15 | ## Development
16 | The engine is under heavy development. The best way to stay up to date with changes, new features and bug fixes is to [join the Discord server](https://discord.gg/NR5ZDGFG5j).
17 |
18 | ---
19 |
20 | ## Licence
21 |
22 | Copyright (c) 2015 - 2018 by Marco Stagni and contributors.
23 |
24 | Some rights reserved.
25 |
26 | Redistribution and use in source and binary forms, with or without
27 | modification, are permitted provided that the following conditions are
28 | met:
29 |
30 | * Redistributions of source code must retain the above copyright
31 | notice, this list of conditions and the following disclaimer.
32 |
33 | * Redistributions in binary form must reproduce the above
34 | copyright notice, this list of conditions and the following
35 | disclaimer in the documentation and/or other materials provided
36 | with the distribution.
37 |
38 | * The names of the contributors may not be used to endorse or
39 | promote products derived from this software without specific
40 | prior written permission.
41 |
42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
43 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
44 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
45 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
46 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
48 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
49 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
50 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
51 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
52 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
53 |
54 |
55 | Mage contains third party software in the 'app/vendor' directory: each
56 | file/module in this directory is distributed under its original license.
57 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | presets: [
3 | ["@babel/preset-env", {
4 | "targets": {
5 | "browsers": ["> 0.25%", "ie >= 11"]
6 | }
7 | }],
8 | ],
9 | plugins: [
10 | 'babel-plugin-syntax-jsx',
11 | ["babel-plugin-inferno", {"imports": true}],
12 | "@babel/plugin-proposal-class-properties",
13 | "@babel/plugin-transform-classes"
14 | ]
15 | };
16 |
17 | module.exports = config;
18 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | import main from './rollup.main';
2 |
3 | export default [main]
--------------------------------------------------------------------------------
/config/rollup.main.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import resolve from '@rollup/plugin-node-resolve';
3 | import commonjs from '@rollup/plugin-commonjs';
4 | import json from '@rollup/plugin-json';
5 | import replace from '@rollup/plugin-replace';
6 |
7 | import { terser } from 'rollup-plugin-terser';
8 | import webWorkerLoader from 'rollup-plugin-web-worker-loader';
9 |
10 | export default {
11 | input: './src/index.js',
12 | output: {
13 | file: './dist/mage.js',
14 | format: 'esm',
15 | compact: true,
16 | minifyInternalExports: false,
17 | name: 'M',
18 | globals: {
19 | process: {
20 | env: {
21 | NODE_ENV: 'development'
22 | }
23 | }
24 | }
25 | },
26 | cache: true,
27 | perf: true,
28 | plugins: [
29 | replace({
30 | 'process.env.NODE_ENV': JSON.stringify('production')
31 | }),
32 | resolve(),
33 | babel({
34 | exclude: ['node_modules/**']
35 | }),
36 | commonjs(),
37 | webWorkerLoader({
38 | pattern: /worker:(.+)/,
39 | targetPlatform: 'browser'
40 | }),
41 | // terser(),
42 | json()
43 | ]
44 | }
--------------------------------------------------------------------------------
/config/rollup.physics.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import resolve from 'rollup-plugin-node-resolve';
3 | import commonjs from 'rollup-plugin-commonjs';
4 | import json from '@rollup/plugin-json';
5 | import { terser } from 'rollup-plugin-terser';
6 |
7 | export default {
8 | input: './src/physics.index.js',
9 | output: {
10 | file: './dist/mage.physics.js',
11 | format: 'umd',
12 | compact: true,
13 | minifyInternalExports: false,
14 | name: 'Ammo'
15 | },
16 | cache: true,
17 | perf: true,
18 | plugins: [
19 | resolve(),
20 | babel({ exclude: ['node_modules/**'] }),
21 | commonjs(),
22 | json()
23 | ]
24 | }
--------------------------------------------------------------------------------
/examples/3d_ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/examples/3d_ui/ui.js:
--------------------------------------------------------------------------------
1 | import { LabelComponent, createElement } from '../../dist/mage.js';
2 |
3 | class UI extends LabelComponent {
4 |
5 | constructor(props) {
6 | super(props);
7 |
8 | this.state = { test: 0 };
9 | }
10 |
11 | componentDidMount() {
12 | super.componentDidMount();
13 | this.setState({ test: 1 });
14 | setInterval(() => {
15 | this.setState({ test: this.state.test + 1 });
16 | }, 2000);
17 | }
18 |
19 | render() {
20 | const span = createElement('span', { className: 'progress-bar-fill', style: `width: ${this.state.test}%;` });
21 | const progressbar = createElement('div', { className: 'progress-bar', children: [span, `${this.state.test}`] });
22 |
23 | return createElement('div', { ref: this.element, className: 'wrapper', children: progressbar });
24 | }
25 | }
26 |
27 | export default UI;
--------------------------------------------------------------------------------
/examples/animations/assets/animated.fbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/animations/assets/animated.fbx
--------------------------------------------------------------------------------
/examples/animations/assets/character_duck.fbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/animations/assets/character_duck.fbx
--------------------------------------------------------------------------------
/examples/animations/assets/duck_animation.fbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/animations/assets/duck_animation.fbx
--------------------------------------------------------------------------------
/examples/animations/assets/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/animations/assets/fire.png
--------------------------------------------------------------------------------
/examples/animations/assets/skeleton_animation.fbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/animations/assets/skeleton_animation.fbx
--------------------------------------------------------------------------------
/examples/animations/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/cars_physics/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/cars_physics/.DS_Store
--------------------------------------------------------------------------------
/examples/cars_physics/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/cars_physics/assets/.DS_Store
--------------------------------------------------------------------------------
/examples/cars_physics/assets/models/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/cars_physics/assets/models/.DS_Store
--------------------------------------------------------------------------------
/examples/cars_physics/assets/models/model.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/cars_physics/assets/models/model.bin
--------------------------------------------------------------------------------
/examples/cars_physics/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
34 |
35 |
36 |
37 | 0
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/examples/cars_physics/speedometer.js:
--------------------------------------------------------------------------------
1 | import { Label, math, LabelComponent, BaseScript, rxjs, createElement } from "../../dist/mage.js";
2 |
3 | export class SpeedometerLabel extends LabelComponent {
4 |
5 | constructor(props) {
6 | super(props);
7 | this.state = { value: 0 };
8 | }
9 |
10 | componentDidMount() {
11 | super.componentDidMount();
12 | const { speed } = this.props;
13 | speed
14 | .subscribe((_value) => {
15 | const value = math.clamp(_value, 0, _value);
16 | this.setState({ value: Math.floor(value) })
17 | });
18 | }
19 |
20 | render() {
21 | return createElement('span', { ref: this.element, className: 'speedometer', children: `${this.state.value}` });
22 | }
23 | }
24 |
25 | export default class Speedometer extends BaseScript {
26 |
27 | constructor() {
28 | super('Speedometer');
29 | }
30 |
31 | start(car) {
32 | this.car = car;
33 | this.speed = new rxjs.Subject();
34 | const label = new Label({ Component: SpeedometerLabel, speed: this.speed, width: 1, height: 1 });
35 |
36 | this.car.add(label, car.getBody(), { waitForBody: 200, waitForBodyMaxRetries: 5 })
37 | .then(() => label.setPosition({ x: -2 }));
38 | }
39 |
40 | physicsUpdate(dt) {
41 | this.speed.next(this.car.getPhysicsState().speed)
42 | }
43 | }
--------------------------------------------------------------------------------
/examples/controls_events/box_A.fbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/controls_events/box_A.fbx
--------------------------------------------------------------------------------
/examples/controls_events/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | In order to play any sound, a basic interaction from the user is needed
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/controls_events/materials/car_police.mtl:
--------------------------------------------------------------------------------
1 | # Blender MTL File: 'CityBits_Export.blend'
2 | # Material Count: 1
3 |
4 | newmtl citybits_texture
5 | Ns 359.999993
6 | Ka 1.000000 1.000000 1.000000
7 | Kd 1.000000 1.000000 1.000000
8 | Ks 0.500000 0.500000 0.500000
9 | Ke 0.000000 0.000000 0.000000
10 | Ni 1.450000
11 | d 1.000000
12 | illum 2
13 | map_Kd citybits_texture.png
14 |
--------------------------------------------------------------------------------
/examples/controls_events/materials/firehydrant.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/controls_events/materials/firehydrant.bin
--------------------------------------------------------------------------------
/examples/controls_events/materials/radio.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl metalMedium(Clone)
4 | Kd 0.3686275 0.4666667 0.4666667
5 |
6 | newmtl wood(Clone)
7 | Kd 0.8962265 0.6015712 0.3931559
8 |
9 | newmtl metal(Clone)
10 | Kd 0.7406105 0.8228667 0.8396227
11 |
12 |
--------------------------------------------------------------------------------
/examples/controls_events/materials/watertower.mtl:
--------------------------------------------------------------------------------
1 | # Blender MTL File: 'CityBits_Export.blend'
2 | # Material Count: 1
3 |
4 | newmtl citybits_texture
5 | Ns 359.999993
6 | Ka 1.000000 1.000000 1.000000
7 | Kd 1.000000 1.000000 1.000000
8 | Ks 0.500000 0.500000 0.500000
9 | Ke 0.000000 0.000000 0.000000
10 | Ni 1.450000
11 | d 1.000000
12 | illum 2
13 | map_Kd citybits_texture.png
14 |
--------------------------------------------------------------------------------
/examples/controls_events/simplescene.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Stone(Clone)
4 | Kd 0.192 0.228 0.246
5 |
6 | newmtl foliage(Clone)
7 | Kd 0.26 0.849 0.603
8 |
9 | newmtl wood(Clone)
10 | Kd 0.896 0.602 0.393
11 |
12 | newmtl BrownDark(Clone)
13 | Kd 0.327 0.102 0.059
14 |
15 | newmtl metal(Clone)
16 | Kd 0.773 0.796 0.906
17 |
18 | newmtl rock(Clone)
19 | Kd 0.915 0.824 0.626
20 |
21 | newmtl woodDark(Clone)
22 | Kd 0.738 0.459 0.254
23 |
24 | newmtl _defaultMat(Clone)
25 | Kd 1 1 1
26 |
27 | newmtl Green(Clone)
28 | Kd 0.065 0.501 0.266
29 |
30 | newmtl GreenDark(Clone)
31 | Kd 0.021 0.257 0.267
32 |
33 |
--------------------------------------------------------------------------------
/examples/controls_events/textures/citybits_texture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/controls_events/textures/citybits_texture.png
--------------------------------------------------------------------------------
/examples/css/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/css/.DS_Store
--------------------------------------------------------------------------------
/examples/css/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #E4DDCE;
3 | }
4 |
5 | a {
6 | text-decoration: none !important;
7 | }
--------------------------------------------------------------------------------
/examples/css/ka1.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/css/ka1.ttf
--------------------------------------------------------------------------------
/examples/deferredSound/click.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/click.ogg
--------------------------------------------------------------------------------
/examples/deferredSound/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/fire.png
--------------------------------------------------------------------------------
/examples/deferredSound/fire.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/fire.wav
--------------------------------------------------------------------------------
/examples/deferredSound/forest.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/forest.mp3
--------------------------------------------------------------------------------
/examples/deferredSound/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | In order to play any sound, a basic interaction from the user is needed
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/deferredSound/radio.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl metalMedium(Clone)
4 | Kd 0.3686275 0.4666667 0.4666667
5 |
6 | newmtl wood(Clone)
7 | Kd 0.8962265 0.6015712 0.3931559
8 |
9 | newmtl metal(Clone)
10 | Kd 0.7406105 0.8228667 0.8396227
11 |
12 |
--------------------------------------------------------------------------------
/examples/deferredSound/radio.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/radio.wav
--------------------------------------------------------------------------------
/examples/deferredSound/radiotune.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/radiotune.mp3
--------------------------------------------------------------------------------
/examples/deferredSound/simplescene.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Stone(Clone)
4 | Kd 0.192 0.228 0.246
5 |
6 | newmtl foliage(Clone)
7 | Kd 0.26 0.849 0.603
8 |
9 | newmtl wood(Clone)
10 | Kd 0.896 0.602 0.393
11 |
12 | newmtl BrownDark(Clone)
13 | Kd 0.327 0.102 0.059
14 |
15 | newmtl metal(Clone)
16 | Kd 0.773 0.796 0.906
17 |
18 | newmtl rock(Clone)
19 | Kd 0.915 0.824 0.626
20 |
21 | newmtl woodDark(Clone)
22 | Kd 0.738 0.459 0.254
23 |
24 | newmtl _defaultMat(Clone)
25 | Kd 1 1 1
26 |
27 | newmtl Green(Clone)
28 | Kd 0.065 0.501 0.266
29 |
30 | newmtl GreenDark(Clone)
31 | Kd 0.021 0.257 0.267
32 |
33 |
--------------------------------------------------------------------------------
/examples/deferredSound/sound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/sound.png
--------------------------------------------------------------------------------
/examples/deferredSound/speaker-hifi-bold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/speaker-hifi-bold.png
--------------------------------------------------------------------------------
/examples/deferredSound/swat.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/deferredSound/swat.mp3
--------------------------------------------------------------------------------
/examples/directionalSound/click.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/directionalSound/click.ogg
--------------------------------------------------------------------------------
/examples/directionalSound/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/directionalSound/fire.png
--------------------------------------------------------------------------------
/examples/directionalSound/fire.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/directionalSound/fire.wav
--------------------------------------------------------------------------------
/examples/directionalSound/forest.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/directionalSound/forest.mp3
--------------------------------------------------------------------------------
/examples/directionalSound/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | In order to play any sound, a basic interaction from the user is needed
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/directionalSound/radio.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl metalMedium(Clone)
4 | Kd 0.3686275 0.4666667 0.4666667
5 |
6 | newmtl wood(Clone)
7 | Kd 0.8962265 0.6015712 0.3931559
8 |
9 | newmtl metal(Clone)
10 | Kd 0.7406105 0.8228667 0.8396227
11 |
12 |
--------------------------------------------------------------------------------
/examples/directionalSound/radio.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/directionalSound/radio.wav
--------------------------------------------------------------------------------
/examples/directionalSound/radiotune.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/directionalSound/radiotune.mp3
--------------------------------------------------------------------------------
/examples/directionalSound/simplescene.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Stone(Clone)
4 | Kd 0.192 0.228 0.246
5 |
6 | newmtl foliage(Clone)
7 | Kd 0.26 0.849 0.603
8 |
9 | newmtl wood(Clone)
10 | Kd 0.896 0.602 0.393
11 |
12 | newmtl BrownDark(Clone)
13 | Kd 0.327 0.102 0.059
14 |
15 | newmtl metal(Clone)
16 | Kd 0.773 0.796 0.906
17 |
18 | newmtl rock(Clone)
19 | Kd 0.915 0.824 0.626
20 |
21 | newmtl woodDark(Clone)
22 | Kd 0.738 0.459 0.254
23 |
24 | newmtl _defaultMat(Clone)
25 | Kd 1 1 1
26 |
27 | newmtl Green(Clone)
28 | Kd 0.065 0.501 0.266
29 |
30 | newmtl GreenDark(Clone)
31 | Kd 0.021 0.257 0.267
32 |
33 |
--------------------------------------------------------------------------------
/examples/directionalSound/swat.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/directionalSound/swat.mp3
--------------------------------------------------------------------------------
/examples/hierarchy/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/loadModel/click.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/click.ogg
--------------------------------------------------------------------------------
/examples/loadModel/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/fire.png
--------------------------------------------------------------------------------
/examples/loadModel/fire.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/fire.wav
--------------------------------------------------------------------------------
/examples/loadModel/forest.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/forest.mp3
--------------------------------------------------------------------------------
/examples/loadModel/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | In order to play any sound, a basic interaction from the user is needed
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/loadModel/radio.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl metalMedium(Clone)
4 | Kd 0.3686275 0.4666667 0.4666667
5 |
6 | newmtl wood(Clone)
7 | Kd 0.8962265 0.6015712 0.3931559
8 |
9 | newmtl metal(Clone)
10 | Kd 0.7406105 0.8228667 0.8396227
11 |
12 |
--------------------------------------------------------------------------------
/examples/loadModel/radio.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/radio.wav
--------------------------------------------------------------------------------
/examples/loadModel/radiotune.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/radiotune.mp3
--------------------------------------------------------------------------------
/examples/loadModel/simplescene.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Stone(Clone)
4 | Kd 0.192 0.228 0.246
5 |
6 | newmtl foliage(Clone)
7 | Kd 0.26 0.849 0.603
8 |
9 | newmtl wood(Clone)
10 | Kd 0.896 0.602 0.393
11 |
12 | newmtl BrownDark(Clone)
13 | Kd 0.327 0.102 0.059
14 |
15 | newmtl metal(Clone)
16 | Kd 0.773 0.796 0.906
17 |
18 | newmtl rock(Clone)
19 | Kd 0.915 0.824 0.626
20 |
21 | newmtl woodDark(Clone)
22 | Kd 0.738 0.459 0.254
23 |
24 | newmtl _defaultMat(Clone)
25 | Kd 1 1 1
26 |
27 | newmtl Green(Clone)
28 | Kd 0.065 0.501 0.266
29 |
30 | newmtl GreenDark(Clone)
31 | Kd 0.021 0.257 0.267
32 |
33 |
--------------------------------------------------------------------------------
/examples/loadModel/sound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/sound.png
--------------------------------------------------------------------------------
/examples/loadModel/speaker-hifi-bold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/speaker-hifi-bold.png
--------------------------------------------------------------------------------
/examples/loadModel/swat.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModel/swat.mp3
--------------------------------------------------------------------------------
/examples/loadModelMaterials/box_A.fbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModelMaterials/box_A.fbx
--------------------------------------------------------------------------------
/examples/loadModelMaterials/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | In order to play any sound, a basic interaction from the user is needed
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/loadModelMaterials/materials/car_police.mtl:
--------------------------------------------------------------------------------
1 | # Blender MTL File: 'CityBits_Export.blend'
2 | # Material Count: 1
3 |
4 | newmtl citybits_texture
5 | Ns 359.999993
6 | Ka 1.000000 1.000000 1.000000
7 | Kd 1.000000 1.000000 1.000000
8 | Ks 0.500000 0.500000 0.500000
9 | Ke 0.000000 0.000000 0.000000
10 | Ni 1.450000
11 | d 1.000000
12 | illum 2
13 | map_Kd citybits_texture.png
14 |
--------------------------------------------------------------------------------
/examples/loadModelMaterials/materials/firehydrant.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModelMaterials/materials/firehydrant.bin
--------------------------------------------------------------------------------
/examples/loadModelMaterials/materials/radio.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl metalMedium(Clone)
4 | Kd 0.3686275 0.4666667 0.4666667
5 |
6 | newmtl wood(Clone)
7 | Kd 0.8962265 0.6015712 0.3931559
8 |
9 | newmtl metal(Clone)
10 | Kd 0.7406105 0.8228667 0.8396227
11 |
12 |
--------------------------------------------------------------------------------
/examples/loadModelMaterials/materials/watertower.mtl:
--------------------------------------------------------------------------------
1 | # Blender MTL File: 'CityBits_Export.blend'
2 | # Material Count: 1
3 |
4 | newmtl citybits_texture
5 | Ns 359.999993
6 | Ka 1.000000 1.000000 1.000000
7 | Kd 1.000000 1.000000 1.000000
8 | Ks 0.500000 0.500000 0.500000
9 | Ke 0.000000 0.000000 0.000000
10 | Ni 1.450000
11 | d 1.000000
12 | illum 2
13 | map_Kd citybits_texture.png
14 |
--------------------------------------------------------------------------------
/examples/loadModelMaterials/simplescene.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Stone(Clone)
4 | Kd 0.192 0.228 0.246
5 |
6 | newmtl foliage(Clone)
7 | Kd 0.26 0.849 0.603
8 |
9 | newmtl wood(Clone)
10 | Kd 0.896 0.602 0.393
11 |
12 | newmtl BrownDark(Clone)
13 | Kd 0.327 0.102 0.059
14 |
15 | newmtl metal(Clone)
16 | Kd 0.773 0.796 0.906
17 |
18 | newmtl rock(Clone)
19 | Kd 0.915 0.824 0.626
20 |
21 | newmtl woodDark(Clone)
22 | Kd 0.738 0.459 0.254
23 |
24 | newmtl _defaultMat(Clone)
25 | Kd 1 1 1
26 |
27 | newmtl Green(Clone)
28 | Kd 0.065 0.501 0.266
29 |
30 | newmtl GreenDark(Clone)
31 | Kd 0.021 0.257 0.267
32 |
33 |
--------------------------------------------------------------------------------
/examples/loadModelMaterials/textures/citybits_texture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/loadModelMaterials/textures/citybits_texture.png
--------------------------------------------------------------------------------
/examples/material_test/assets/models/castletrack.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/material_test/assets/models/castletrack.glb
--------------------------------------------------------------------------------
/examples/material_test/assets/models/castletrack.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Brown(Clone)
4 | Kd 0.578 0.233 0.115
5 |
6 | newmtl Beige(Clone)
7 | Kd 0.703 0.425 0.207
8 |
9 | newmtl BrownDark(Clone)
10 | Kd 0.327 0.102 0.059
11 |
12 | newmtl Green(Clone)
13 | Kd 0.065 0.501 0.266
14 |
15 | newmtl GreenDark(Clone)
16 | Kd 0.021 0.257 0.267
17 |
18 | newmtl Stone(Clone)
19 | Kd 0.192 0.228 0.246
20 |
21 |
--------------------------------------------------------------------------------
/examples/material_test/assets/models/forestTileTower.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/material_test/assets/models/forestTileTower.glb
--------------------------------------------------------------------------------
/examples/material_test/assets/models/hex_forest_roadB.gltf.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/material_test/assets/models/hex_forest_roadB.gltf.glb
--------------------------------------------------------------------------------
/examples/material_test/assets/models/model.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/material_test/assets/models/model.bin
--------------------------------------------------------------------------------
/examples/material_test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/material_test/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | Router,
3 | store,
4 | Level,
5 | Box,
6 | Scene,
7 | Cube,
8 | Controls,
9 | Models,
10 | AmbientLight,
11 | PHYSICS_EVENTS,
12 | constants,
13 | Scripts,
14 | PALETTES,
15 | SunLight,
16 | HemisphereLight,
17 | Sky,
18 | } from "../../dist/mage.js";
19 |
20 | export default class Intro extends Level {
21 | addAmbientLight() {
22 | const ambientLight = new AmbientLight({
23 | color: PALETTES.FRENCH_PALETTE.SPRAY,
24 | intensity: 0.5,
25 | });
26 |
27 | const hemisphereLight = new HemisphereLight({
28 | color: {
29 | sky: PALETTES.FRENCH_PALETTE.SQUASH_BLOSSOM,
30 | ground: PALETTES.FRENCH_PALETTE.REEF_ENCOUNTER,
31 | },
32 | intensity: 1,
33 | });
34 |
35 | const sunLight = new SunLight({
36 | color: PALETTES.FRENCH_PALETTE.MELON_MELODY,
37 | intensity: 1,
38 | far: 20,
39 | mapSize: 2048,
40 | });
41 | sunLight.setPosition({ y: 4, z: -3, x: -3 });
42 | // sunLight.addHelpers();
43 | }
44 |
45 | createSky() {
46 | const sky = new Sky();
47 | const inclination = 0.1;
48 | const azimuth = 0.1;
49 | const distance = 100;
50 |
51 | sky.setSun(inclination, azimuth, distance);
52 | }
53 |
54 | onCreate() {
55 | Scene.getCamera().setPosition({ y: 10 });
56 | Controls.setOrbitControl();
57 | this.addAmbientLight();
58 | this.createSky();
59 |
60 | const tile = Models.getModel("tile");
61 |
62 | tile.setMaterialFromName(constants.MATERIALS.STANDARD, { roughness: 0.5, metalness: 0 });
63 | }
64 | }
65 |
66 | const assets = {
67 | "/": {
68 | models: {
69 | tile: "assets/models/hex_forest_roadB.gltf.glb",
70 | },
71 | },
72 | };
73 |
74 | const { SHADOW_TYPES } = constants;
75 |
76 | const config = {
77 | screen: {
78 | h: window ? window.innerHeight : 800,
79 | w: window ? window.innerWidth : 600,
80 | ratio: window ? window.innerWidth / window.innerHeight : 600 / 800,
81 | frameRate: 60,
82 | alpha: true,
83 | },
84 |
85 | lights: {
86 | shadows: true,
87 | shadowType: SHADOW_TYPES.HARD,
88 | textureAnisotropy: 32,
89 | },
90 |
91 | physics: {
92 | enabled: true,
93 | path: "dist/ammo.js",
94 | gravity: { x: 0, y: -9.8, z: 0 },
95 | },
96 |
97 | tween: {
98 | enabled: false,
99 | },
100 |
101 | camera: {
102 | fov: 75,
103 | near: 0.1,
104 | far: 3000000,
105 | },
106 | };
107 |
108 | window.addEventListener("load", () => {
109 | store.createStore({}, {}, true);
110 |
111 | Router.on("/", Intro);
112 |
113 | Router.start(config, assets);
114 | });
115 |
--------------------------------------------------------------------------------
/examples/particles_custom_emitter/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_custom_emitter/dot.png
--------------------------------------------------------------------------------
/examples/particles_custom_emitter/dot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_custom_emitter/dot_1.png
--------------------------------------------------------------------------------
/examples/particles_custom_emitter/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_custom_emitter/fire.png
--------------------------------------------------------------------------------
/examples/particles_custom_emitter/green_energy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_custom_emitter/green_energy.png
--------------------------------------------------------------------------------
/examples/particles_custom_emitter/hex_forest_roadB.gltf.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_custom_emitter/hex_forest_roadB.gltf.glb
--------------------------------------------------------------------------------
/examples/particles_custom_emitter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/particles_explosion/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_explosion/.DS_Store
--------------------------------------------------------------------------------
/examples/particles_explosion/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_explosion/dot.png
--------------------------------------------------------------------------------
/examples/particles_explosion/dot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_explosion/dot_1.png
--------------------------------------------------------------------------------
/examples/particles_explosion/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_explosion/fire.png
--------------------------------------------------------------------------------
/examples/particles_explosion/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/particles_explosion/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | Router,
3 | store,
4 | Level,
5 | Box,
6 | Scene,
7 | Controls,
8 | AmbientLight,
9 | Particles,
10 | PARTICLES,
11 | Input,
12 | INPUT_EVENTS,
13 | } from "../../dist/mage.js";
14 |
15 | export default class Intro extends Level {
16 | addAmbientLight() {
17 | this.ambientLight = new AmbientLight({ color: 0xffffff });
18 | }
19 |
20 | createFloor() {
21 | const floor = new Box(50, 1, 50, 0xffffff);
22 |
23 | floor.setPosition({ y: -5 });
24 | }
25 |
26 | explode(position = { x: 0, y: 0, z: 0 }) {
27 | console.log("exploding");
28 | Particles.addParticleEmitter(PARTICLES.EXPLOSION, {
29 | texture: "dot",
30 | hasDebris: true,
31 | }).emit("once");
32 | }
33 |
34 | onKeyDown({ event }) {
35 | console.log(event);
36 | if (event.key === "e") {
37 | this.explode();
38 | }
39 | }
40 |
41 | onCreate() {
42 | this.addAmbientLight();
43 | Controls.setOrbitControl();
44 |
45 | Input.enable();
46 | Input.keyboard.addEventListener(INPUT_EVENTS.KEY_DOWN, this.onKeyDown.bind(this));
47 |
48 | Scene.getCamera().setPosition({ y: 15, z: 45 });
49 |
50 | this.createFloor();
51 | }
52 | }
53 |
54 | const assets = {
55 | textures: {
56 | dot: "dot.png",
57 | fire: "fire.png",
58 | },
59 | };
60 |
61 | const config = {
62 | screen: {
63 | h: window ? window.innerHeight : 800,
64 | w: window ? window.innerWidth : 600,
65 | ratio: window ? window.innerWidth / window.innerHeight : 600 / 800,
66 | frameRate: 60,
67 | alpha: true,
68 | },
69 |
70 | lights: {
71 | shadows: true,
72 | },
73 |
74 | physics: {
75 | enabled: false,
76 | path: "dist/ammo.js",
77 | gravity: { x: 0, y: -9.8, z: 0 },
78 | },
79 |
80 | tween: {
81 | enabled: false,
82 | },
83 |
84 | camera: {
85 | fov: 75,
86 | near: 0.1,
87 | far: 3000000,
88 | },
89 | };
90 |
91 | window.addEventListener("load", () => {
92 | store.createStore({}, {}, true);
93 |
94 | Router.on("/", Intro);
95 |
96 | Router.start(config, assets);
97 | });
98 |
--------------------------------------------------------------------------------
/examples/particles_fire/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_fire/dot.png
--------------------------------------------------------------------------------
/examples/particles_fire/dot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_fire/dot_1.png
--------------------------------------------------------------------------------
/examples/particles_fire/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_fire/fire.png
--------------------------------------------------------------------------------
/examples/particles_fire/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/particles_sparkles/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_sparkles/dot.png
--------------------------------------------------------------------------------
/examples/particles_sparkles/dot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_sparkles/dot_1.png
--------------------------------------------------------------------------------
/examples/particles_sparkles/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_sparkles/fire.png
--------------------------------------------------------------------------------
/examples/particles_sparkles/green_energy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/particles_sparkles/green_energy.png
--------------------------------------------------------------------------------
/examples/particles_sparkles/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/physics/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/physics/index.js:
--------------------------------------------------------------------------------
1 | import { Router, store, Level, Box, Cube, Sphere, Scene, Controls, Color, PHYSICS_CONSTANTS } from '../../dist/mage.js';
2 |
3 | class Intro extends Level {
4 |
5 | createCube() {
6 | const size = 2;
7 | const color = Color.randomColor(true);
8 | const cube = new Cube(size, color);
9 | const position = {
10 | x: Math.random() * 30 - 15,
11 | z: Math.random() * 30 - 15,
12 | y: Math.random() * 10 + 10
13 | }
14 |
15 | const rotation = {
16 | x: Math.random(),
17 | y: Math.random(),
18 | z: Math.random()
19 | }
20 |
21 | cube.setPosition(position);
22 | cube.setRotation(rotation);
23 | cube.enablePhysics({ debug: true, mass: 1 });
24 | }
25 |
26 | createSphere() {
27 | const radius = 2;
28 | const color = Color.randomColor(true);
29 | const sphere = new Sphere(radius, color);
30 | const position = {
31 | x: Math.random() * 30 - 15,
32 | y: Math.random() * 30 - 15,
33 | z: Math.random() * 10 + 10
34 | }
35 |
36 | const rotation = {
37 | x: Math.random(),
38 | y: Math.random(),
39 | z: Math.random()
40 | }
41 |
42 | sphere.setPosition(position);
43 | sphere.setRotation(rotation);
44 | sphere.enablePhysics({ debug: true, mass: 1, colliderType: PHYSICS_CONSTANTS.COLLIDER_TYPES.SPHERE });
45 | }
46 |
47 | spawnItems(count) {
48 | if (count === 0) return;
49 |
50 | setTimeout(() => {
51 | Math.random() > 0.5 ? this.createCube() : this.createSphere();
52 | this.spawnItems(count - 1);
53 | }, 500)
54 | }
55 |
56 | onCreate() {
57 | Controls.setOrbitControl();
58 |
59 | Scene
60 | .getCamera()
61 | .setPosition({ y: 15, z: 45 });
62 |
63 | const floor = new Box(50, 1, 50, 0xffffff);
64 |
65 | floor.enablePhysics({ mass: 0, debug: true });
66 |
67 | this.spawnItems(100);
68 | }
69 | }
70 |
71 | const config = {
72 | screen: {
73 | h: window ? window.innerHeight : 800,
74 | w: window ? window.innerWidth : 600,
75 | ratio: window ? window.innerWidth / window.innerHeight : 600 / 800,
76 | frameRate: 60,
77 | alpha: true,
78 | },
79 |
80 | lights: {
81 | shadows: true,
82 | },
83 |
84 | physics: {
85 | enabled: true,
86 | path: 'dist/ammo.js'
87 | },
88 |
89 | tween: {
90 | enabled: false,
91 | },
92 |
93 | camera: {
94 | fov: 75,
95 | near: 0.1,
96 | far: 3000000,
97 | },
98 | };
99 |
100 | window.addEventListener('load', () => {
101 | store.createStore({}, {}, true);
102 |
103 | Router.on('/', Intro);
104 |
105 | Router.start(config, {});
106 | });
107 |
--------------------------------------------------------------------------------
/examples/rotateTo/assets/models/castletrack.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/rotateTo/assets/models/castletrack.glb
--------------------------------------------------------------------------------
/examples/rotateTo/assets/models/castletrack.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Brown(Clone)
4 | Kd 0.578 0.233 0.115
5 |
6 | newmtl Beige(Clone)
7 | Kd 0.703 0.425 0.207
8 |
9 | newmtl BrownDark(Clone)
10 | Kd 0.327 0.102 0.059
11 |
12 | newmtl Green(Clone)
13 | Kd 0.065 0.501 0.266
14 |
15 | newmtl GreenDark(Clone)
16 | Kd 0.021 0.257 0.267
17 |
18 | newmtl Stone(Clone)
19 | Kd 0.192 0.228 0.246
20 |
21 |
--------------------------------------------------------------------------------
/examples/rotateTo/assets/models/forestTileTower.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/rotateTo/assets/models/forestTileTower.glb
--------------------------------------------------------------------------------
/examples/rotateTo/assets/models/hex_forest_roadB.gltf.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/rotateTo/assets/models/hex_forest_roadB.gltf.glb
--------------------------------------------------------------------------------
/examples/rotateTo/assets/models/model.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/rotateTo/assets/models/model.bin
--------------------------------------------------------------------------------
/examples/rotateTo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/scaleTo/assets/models/castletrack.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/scaleTo/assets/models/castletrack.glb
--------------------------------------------------------------------------------
/examples/scaleTo/assets/models/castletrack.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl Brown(Clone)
4 | Kd 0.578 0.233 0.115
5 |
6 | newmtl Beige(Clone)
7 | Kd 0.703 0.425 0.207
8 |
9 | newmtl BrownDark(Clone)
10 | Kd 0.327 0.102 0.059
11 |
12 | newmtl Green(Clone)
13 | Kd 0.065 0.501 0.266
14 |
15 | newmtl GreenDark(Clone)
16 | Kd 0.021 0.257 0.267
17 |
18 | newmtl Stone(Clone)
19 | Kd 0.192 0.228 0.246
20 |
21 |
--------------------------------------------------------------------------------
/examples/scaleTo/assets/models/forestTileTower.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/scaleTo/assets/models/forestTileTower.glb
--------------------------------------------------------------------------------
/examples/scaleTo/assets/models/hex_forest_roadB.gltf.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/scaleTo/assets/models/hex_forest_roadB.gltf.glb
--------------------------------------------------------------------------------
/examples/scaleTo/assets/models/model.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/scaleTo/assets/models/model.bin
--------------------------------------------------------------------------------
/examples/scaleTo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/scaleTo/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | Router,
3 | store,
4 | Level,
5 | Box,
6 | Scene,
7 | Cube,
8 | Controls,
9 | Models,
10 | AmbientLight,
11 | PHYSICS_EVENTS,
12 | constants,
13 | Scripts,
14 | PALETTES,
15 | SunLight,
16 | HemisphereLight,
17 | Sky,
18 | } from "../../dist/mage.js";
19 |
20 | export default class Intro extends Level {
21 | addAmbientLight() {
22 | const ambientLight = new AmbientLight({
23 | color: PALETTES.FRENCH_PALETTE.SPRAY,
24 | intensity: 0.5,
25 | });
26 |
27 | const hemisphereLight = new HemisphereLight({
28 | color: {
29 | sky: PALETTES.FRENCH_PALETTE.SQUASH_BLOSSOM,
30 | ground: PALETTES.FRENCH_PALETTE.REEF_ENCOUNTER,
31 | },
32 | intensity: 1,
33 | });
34 |
35 | const sunLight = new SunLight({
36 | color: PALETTES.FRENCH_PALETTE.MELON_MELODY,
37 | intensity: 1,
38 | far: 20,
39 | mapSize: 2048,
40 | });
41 | sunLight.setPosition({ y: 4, z: -3, x: -3 });
42 | // sunLight.addHelpers();
43 | }
44 |
45 | createSky() {
46 | const sky = new Sky();
47 | const inclination = 0.1;
48 | const azimuth = 0.1;
49 | const distance = 100;
50 |
51 | sky.setSun(inclination, azimuth, distance);
52 | }
53 |
54 | onCreate() {
55 | Scene.getCamera().setPosition({ y: 10 });
56 | Controls.setOrbitControl();
57 | this.addAmbientLight();
58 | this.createSky();
59 |
60 | const cube = new Cube(5, 0xffeeaa);
61 |
62 | cube.setMaterialFromName(constants.MATERIALS.STANDARD, { roughness: 0.5, metalness: 0 });
63 | setTimeout(() => {
64 | cube.scaleTo({ x: 4, y: 4, z: 4 }, 5000, { loop: "bounce" });
65 | }, 2000);
66 | }
67 | }
68 |
69 | const assets = {};
70 |
71 | const { SHADOW_TYPES } = constants;
72 |
73 | const config = {
74 | screen: {
75 | h: window ? window.innerHeight : 800,
76 | w: window ? window.innerWidth : 600,
77 | ratio: window ? window.innerWidth / window.innerHeight : 600 / 800,
78 | frameRate: 60,
79 | alpha: true,
80 | },
81 |
82 | lights: {
83 | shadows: true,
84 | shadowType: SHADOW_TYPES.HARD,
85 | textureAnisotropy: 32,
86 | },
87 |
88 | physics: {
89 | enabled: true,
90 | path: "dist/ammo.js",
91 | gravity: { x: 0, y: -9.8, z: 0 },
92 | },
93 |
94 | tween: {
95 | enabled: false,
96 | },
97 |
98 | camera: {
99 | fov: 75,
100 | near: 0.1,
101 | far: 3000000,
102 | },
103 | };
104 |
105 | window.addEventListener("load", () => {
106 | store.createStore({}, {}, true);
107 |
108 | Router.on("/", Intro);
109 |
110 | Router.start(config, assets);
111 | });
112 |
--------------------------------------------------------------------------------
/examples/selection_effect/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/sound/click.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sound/click.ogg
--------------------------------------------------------------------------------
/examples/sound/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/sound/index.js:
--------------------------------------------------------------------------------
1 | import { Router, store, Level, Box, Scene, Controls, AmbientLight, AmbientSound, Input, INPUT_EVENTS } from '../../dist/mage.js';
2 |
3 | export default class Example extends Level {
4 |
5 | addAmbientLight() {
6 | this.ambientLight = new AmbientLight({ color: 0xffffff });
7 | }
8 |
9 | createFloor() {
10 | const floor = new Box(50, 1, 50, 0xffffff);
11 |
12 | floor.setPosition({ y: -5 });
13 | }
14 |
15 | playSound() {
16 | new AmbientSound('click', { autoplay: true });
17 | }
18 |
19 | onKeyDown({ event }) {
20 | console.log(event);
21 | if (event.key === 'e') {
22 | this.playSound();
23 | }
24 | }
25 |
26 | onCreate() {
27 | this.addAmbientLight();
28 | Controls.setOrbitControl();
29 |
30 | Scene
31 | .getCamera()
32 | .setPosition({ y: 15, z: 45 });
33 | Input.enable();
34 | Input.keyboard.addEventListener(INPUT_EVENTS.KEY_DOWN, this.onKeyDown.bind(this));
35 |
36 | // this.createFloor();/
37 | }
38 | }
39 |
40 | const assets = {
41 | audio: {
42 | 'click': 'click.ogg'
43 | }
44 | };
45 |
46 | const config = {
47 | screen: {
48 | h: window ? window.innerHeight : 800,
49 | w: window ? window.innerWidth : 600,
50 | ratio: window ? window.innerWidth / window.innerHeight : 600 / 800,
51 | frameRate: 60,
52 | alpha: true,
53 | },
54 |
55 | lights: {
56 | shadows: true,
57 | },
58 |
59 | physics: {
60 | enabled: false,
61 | path: 'dist/ammo.js',
62 | gravity: { x: 0, y: -9.8, z: 0}
63 | },
64 |
65 | tween: {
66 | enabled: false,
67 | },
68 |
69 | camera: {
70 | fov: 75,
71 | near: 0.1,
72 | far: 3000000,
73 | },
74 | };
75 |
76 | window.addEventListener('load', () => {
77 | store.createStore({}, {}, true);
78 |
79 | Router.on('/', Example);
80 |
81 | Router.start(config, assets);
82 | });
83 |
--------------------------------------------------------------------------------
/examples/spotlight/assets/models/Textures/roof(Clone).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/models/Textures/roof(Clone).png
--------------------------------------------------------------------------------
/examples/spotlight/assets/models/lamp.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl roof(Clone)
4 | Kd 1 1 1
5 | map_Kd Textures/roof(Clone).png
6 |
7 |
--------------------------------------------------------------------------------
/examples/spotlight/assets/textures/ambientlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/textures/ambientlight.png
--------------------------------------------------------------------------------
/examples/spotlight/assets/textures/hemispherelight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/textures/hemispherelight.png
--------------------------------------------------------------------------------
/examples/spotlight/assets/textures/lightbulb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/textures/lightbulb.png
--------------------------------------------------------------------------------
/examples/spotlight/assets/textures/pointlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/textures/pointlight.png
--------------------------------------------------------------------------------
/examples/spotlight/assets/textures/spotlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/textures/spotlight.png
--------------------------------------------------------------------------------
/examples/spotlight/assets/textures/sunlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/textures/sunlight.png
--------------------------------------------------------------------------------
/examples/spotlight/assets/textures/target.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/spotlight/assets/textures/target.png
--------------------------------------------------------------------------------
/examples/spotlight/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/sunlight/assets/models/Textures/roof(Clone).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/models/Textures/roof(Clone).png
--------------------------------------------------------------------------------
/examples/sunlight/assets/models/lamp.mtl:
--------------------------------------------------------------------------------
1 | # Created by Kenney (www.kenney.nl)
2 |
3 | newmtl roof(Clone)
4 | Kd 1 1 1
5 | map_Kd Textures/roof(Clone).png
6 |
7 |
--------------------------------------------------------------------------------
/examples/sunlight/assets/textures/ambientlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/textures/ambientlight.png
--------------------------------------------------------------------------------
/examples/sunlight/assets/textures/hemispherelight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/textures/hemispherelight.png
--------------------------------------------------------------------------------
/examples/sunlight/assets/textures/lightbulb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/textures/lightbulb.png
--------------------------------------------------------------------------------
/examples/sunlight/assets/textures/pointlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/textures/pointlight.png
--------------------------------------------------------------------------------
/examples/sunlight/assets/textures/spotlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/textures/spotlight.png
--------------------------------------------------------------------------------
/examples/sunlight/assets/textures/sunlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/textures/sunlight.png
--------------------------------------------------------------------------------
/examples/sunlight/assets/textures/target.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/sunlight/assets/textures/target.png
--------------------------------------------------------------------------------
/examples/sunlight/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/textures/assets/Wood_025_ambientOcclusion.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/textures/assets/Wood_025_ambientOcclusion.jpg
--------------------------------------------------------------------------------
/examples/textures/assets/Wood_025_basecolor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/textures/assets/Wood_025_basecolor.jpg
--------------------------------------------------------------------------------
/examples/textures/assets/Wood_025_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/textures/assets/Wood_025_height.png
--------------------------------------------------------------------------------
/examples/textures/assets/Wood_025_normal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/textures/assets/Wood_025_normal.jpg
--------------------------------------------------------------------------------
/examples/textures/assets/Wood_025_roughness.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/textures/assets/Wood_025_roughness.jpg
--------------------------------------------------------------------------------
/examples/textures/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/trails/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/trails/index.js:
--------------------------------------------------------------------------------
1 | import { Router, store, Level, Cube, Scene, Controls, AmbientLight, Particles, PARTICLES, BaseScript, Scripts, BUILTIN } from '../../dist/mage.js';
2 |
3 | class SimpleScript extends BaseScript {
4 | constructor() { super('SimpleScript'); }
5 |
6 | start(element) {
7 | this.element = element;
8 | this.angle = 0;
9 | }
10 |
11 | update(dt) {
12 | this.angle += 5 * dt;
13 |
14 | this.element.setPosition({ x: Math.sin(this.angle) * 10 });
15 | }
16 | }
17 |
18 | export default class Intro extends Level {
19 |
20 | addAmbientLight() {
21 | this.ambientLight = new AmbientLight({ color: 0xffffff });
22 | }
23 |
24 | createCube() {
25 | const cube = new Cube(1, 0xeeeaaa);
26 |
27 | cube.addScript('SimpleScript');
28 | const trail = Particles.addParticleEmitter(PARTICLES.TRAIL, { texture: 'particle' });
29 | trail.emit(Infinity);
30 | cube.add(trail);
31 |
32 | // trail.addScript('SimpleScript');
33 |
34 |
35 | cube.setWireframe(true);
36 | }
37 |
38 | onCreate() {
39 | Scripts.create('SimpleScript', SimpleScript);
40 | this.addAmbientLight();
41 | Controls.setOrbitControl();
42 |
43 | this.createCube();
44 |
45 | Scene
46 | .getCamera()
47 | .setPosition({ y: 15, z: 45 });
48 | }
49 | }
50 |
51 | const assets = {
52 | textures: {
53 | particle: 'particle.png'
54 | }
55 | };
56 |
57 | const config = {
58 | screen: {
59 | h: window ? window.innerHeight : 800,
60 | w: window ? window.innerWidth : 600,
61 | ratio: window ? window.innerWidth / window.innerHeight : 600 / 800,
62 | frameRate: 60,
63 | alpha: true,
64 | },
65 |
66 | lights: {
67 | shadows: true,
68 | },
69 |
70 | physics: {
71 | enabled: false,
72 | path: 'dist/ammo.js',
73 | gravity: { x: 0, y: -9.8, z: 0}
74 | },
75 |
76 | tween: {
77 | enabled: false,
78 | },
79 |
80 | camera: {
81 | fov: 75,
82 | near: 0.1,
83 | far: 3000000,
84 | },
85 | };
86 |
87 | window.addEventListener('load', () => {
88 | store.createStore({}, {}, true);
89 |
90 | Router.on('/', Intro);
91 |
92 | Router.start(config, assets);
93 | });
94 |
--------------------------------------------------------------------------------
/examples/trails/particle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/examples/trails/particle.png
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/src/.DS_Store
--------------------------------------------------------------------------------
/src/audio/ambientSound.js:
--------------------------------------------------------------------------------
1 | import { ENTITY_TYPES } from "../entities/constants";
2 | import { generateRandomName } from "../lib/uuid";
3 | import Sound from "./sound";
4 | export default class AmbientSound extends Sound {
5 | constructor(source, { name = generateRandomName("AmbientSound"), ...options } = {}) {
6 | super({ source, name, ...options });
7 | this.setEntityType(ENTITY_TYPES.AUDIO.AMBIENT);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/controls/constants.js:
--------------------------------------------------------------------------------
1 | export const CONTROLS = {
2 | ORBIT: "CONTROLS:ORBIT",
3 | TRANSFORM: "CONTROLS:TRANSFORM",
4 | FPS: "CONTROLS:FPS",
5 | FLY: "CONTROLS:FLY",
6 | };
7 |
8 | export const AVAILABLE_CONTROLS = Object.keys(CONTROLS);
9 |
--------------------------------------------------------------------------------
/src/core/Stats.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 | import { Subject } from 'rxjs';
5 | import features, { FEATURES } from '../lib/features';
6 |
7 | const ONE_MB = 1048576;
8 |
9 | class Stats {
10 |
11 | constructor() {
12 | this.beginTime = (performance || Date).now();
13 | this.prevTime = this.beginTime;
14 |
15 | this.frames = 0;
16 | this._fps = 0;
17 | this._fpsMax = 100;
18 | this.fps = new Subject();
19 | this.memory = new Subject();
20 | this.memoryMax = new Subject();
21 | }
22 |
23 | init() {
24 | this.beginTime = (performance || Date).now();
25 | }
26 |
27 | getFPS() {
28 | this.frames++;
29 | const time = (performance || Date).now();
30 |
31 | if (time >= this.prevTime + 1000) {
32 | this._fps = (this.frames * 1000)/(time - this.prevTime);
33 | this.prevTime = time;
34 | this.frames = 0;
35 | this.fps.next(this._fps);
36 | }
37 |
38 | this.beginTime = time;
39 |
40 | return this._fps;
41 | }
42 |
43 | subscribe(handler) {
44 | this.fps.subscribe(handler);
45 |
46 | return () => this.fps.unsubscribe(handler);
47 | }
48 |
49 | getMemoryUsage() {
50 | if (features.isFeatureSupported(FEATURES.MEMORY)) {
51 | this.memory.next(performance.memory.usedJSHeapSize / ONE_MB);
52 | this.memoryMax.next(performance.memory.jsHeapSizeLimit / ONE_MB);
53 | }
54 | }
55 |
56 | update() {
57 | this.getFPS();
58 | }
59 | }
60 |
61 | export default new Stats();
62 |
--------------------------------------------------------------------------------
/src/core/input/Keyboard.js:
--------------------------------------------------------------------------------
1 | import hotkeys from "hotkeys-js";
2 | import { KEYBOARD_COMBO_ALREADY_REGISTERED, KEYBOARD_COMBO_IS_INVALID } from "../../lib/messages";
3 |
4 | import { EventDispatcher } from "three";
5 |
6 | const HOTKEYS_DEFAULT_OPTIONS = {
7 | keyup: true,
8 | keydown: true,
9 | };
10 |
11 | const COMBINATION_DIVIDER = ",";
12 |
13 | export const KEY_PRESS = "keyPress";
14 | export const KEY_DOWN = "keyDown";
15 | export const KEY_UP = "keyUp";
16 | export const KEY_COMBO = "keyCombo";
17 | export default class Keyboard extends EventDispatcher {
18 | constructor() {
19 | super();
20 |
21 | this.combos = [];
22 |
23 | this.enabled = false;
24 | }
25 |
26 | listenTo(combos = []) {
27 | if (this.enabled) {
28 | if (!(combos instanceof Array) || !combos.length) {
29 | console.warn(KEYBOARD_COMBO_IS_INVALID);
30 | }
31 | const parsedCombos = combos.join(COMBINATION_DIVIDER);
32 |
33 | hotkeys(parsedCombos, HOTKEYS_DEFAULT_OPTIONS, this.handleCombo);
34 | }
35 | }
36 |
37 | handleCombo = event => {
38 | this.dispatchEvent({
39 | type: KEY_COMBO,
40 | event,
41 | });
42 | };
43 |
44 | handleKeydown = event => {
45 | this.dispatchEvent({
46 | type: KEY_DOWN,
47 | event,
48 | });
49 | };
50 |
51 | handleKeyup = event => {
52 | this.dispatchEvent({
53 | type: KEY_UP,
54 | event,
55 | });
56 | };
57 |
58 | handleKeypress = event => {
59 | this.dispatchEvent({
60 | type: KEY_PRESS,
61 | event,
62 | });
63 | };
64 |
65 | enable(options = {}) {
66 | this.enabled = true;
67 |
68 | if (options.combos) {
69 | this.listenTo(options.combos);
70 | }
71 |
72 | window.addEventListener(KEY_DOWN.toLowerCase(), this.handleKeydown.bind(this));
73 | window.addEventListener(KEY_UP.toLowerCase(), this.handleKeyup.bind(this));
74 | window.addEventListener(KEY_PRESS.toLowerCase(), this.handleKeypress.bind(this));
75 | }
76 |
77 | disable() {
78 | this.enabled = false;
79 |
80 | hotkeys.unbind();
81 | window.removeEventListener(KEY_DOWN.toLowerCase(), this.handleKeydown.bind(this));
82 | window.removeEventListener(KEY_UP.toLowerCase(), this.handleKeyup.bind(this));
83 | window.removeEventListener(KEY_PRESS.toLowerCase(), this.handleKeypress.bind(this));
84 | }
85 |
86 | isPressed(key) {
87 | console.log(key, hotkeys.getPressedKeyCodes());
88 | return hotkeys.isPressed(key);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/core/input/constants.js:
--------------------------------------------------------------------------------
1 | export const STANDARD = 'STANDARD';
2 |
3 | export const GAMEPAD_BUTTON_MAPPINGS = {
4 | [STANDARD]: [
5 | 'X',
6 | 'B',
7 | 'A',
8 | 'Y',
9 | 'L1',
10 | 'R1',
11 | null,
12 | null,
13 | 'SELECT',
14 | 'START'
15 | ]
16 | }
--------------------------------------------------------------------------------
/src/core/universe.js:
--------------------------------------------------------------------------------
1 | export class Universe {
2 | constructor() {
3 | this.reality = {};
4 | this.realityUUID = {};
5 | this.worker = undefined;
6 | }
7 |
8 | get(id) {
9 | return this.reality[id];
10 | }
11 |
12 | find(element) {
13 | if (!element) return;
14 |
15 | let found;
16 | this.forEach(el => {
17 | if (el.has(element) && !found) {
18 | found = el;
19 | }
20 | });
21 |
22 | return found;
23 | }
24 |
25 | getByUUID(uuid) {
26 | const id = this.realityUUID[uuid.toString()];
27 |
28 | if (id) {
29 | return this.get(id);
30 | }
31 | }
32 |
33 | getByTag(tagName) {
34 | const elements = [];
35 | this.forEach(element => {
36 | if (element.hasTag(tagName)) {
37 | elements.push(element);
38 | }
39 | });
40 |
41 | return elements;
42 | }
43 |
44 | set(id, value) {
45 | this.reality[id] = value;
46 | }
47 |
48 | reset = () => {
49 | this.reality = {};
50 | this.realityUUID = {};
51 | };
52 |
53 | storeUUIDToElementNameReference(uuid, name) {
54 | this.realityUUID[uuid] = name;
55 | }
56 |
57 | remove(id) {
58 | delete this.reality[id];
59 | }
60 |
61 | forEach = callback => {
62 | Object.keys(this.reality).forEach(k => callback(this.reality[k], k));
63 | };
64 |
65 | forEachAsync = callback => {
66 | const keys = Object.keys(this.reality);
67 |
68 | return new Promise(resolve => {
69 | Promise.all(keys.map(k => callback(this.reality[k], k))).then(resolve);
70 | });
71 | };
72 |
73 | update(dt) {
74 | Object.keys(this.reality).forEach(k => this.reality[k].update(dt));
75 | }
76 |
77 | onPhysicsUpdate(dt) {
78 | Object.keys(this.reality).forEach(k => this.reality[k].onPhysicsUpdate(dt));
79 | }
80 |
81 | bigfreeze = () => {
82 | this.forEach(o => {
83 | o && o.dispose && o.dispose();
84 | });
85 | this.reset();
86 | };
87 |
88 | toJSON(parseJSON = false) {
89 | const elements = Object.keys(this.reality)
90 | .map(k => this.get(k))
91 | .filter(element => element.serializable && element.isMesh())
92 | .map(element => element.toJSON(parseJSON));
93 |
94 | return { elements };
95 | }
96 | }
97 |
98 | export default new Universe();
99 |
--------------------------------------------------------------------------------
/src/core/window.js:
--------------------------------------------------------------------------------
1 | export const getWindow = () => {
2 | try {
3 | const win = window || global.window;
4 |
5 | return win;
6 | } catch (e) {
7 | return null;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/entities/animations/AnimationHandler.js:
--------------------------------------------------------------------------------
1 | import { AnimationMixer, AnimationClip, LoopRepeat, EventDispatcher } from 'three';
2 | import { ENTITY_EVENTS } from '../constants';
3 | import { ANIMATION_NOT_FOUND } from '../../lib/messages';
4 |
5 | export default class AnimationHandler extends EventDispatcher {
6 |
7 | constructor(mesh, animations = []) {
8 | super();
9 | this.mixer = new AnimationMixer(mesh);
10 | this.animations = animations;
11 | this.isPlaying = false;
12 | this.addEventsListeners();
13 | }
14 |
15 | addEventsListeners() {
16 | this.mixer.addEventListener('loop', this.getAnimationEventHandler(ENTITY_EVENTS.ANIMATION.LOOP));
17 | this.mixer.addEventListener('finished', this.getAnimationEventHandler(ENTITY_EVENTS.ANIMATION.FINISHED));
18 | }
19 |
20 | getAnimationEventHandler = (type) => ({ action, direction }) => {
21 | this.dispatchEvent({
22 | type,
23 | action,
24 | direction
25 | })
26 | }
27 |
28 | getAction(id) {
29 | let action;
30 |
31 | if (typeof id === 'number') {
32 | action = this.animations[id];
33 | } else if (typeof id === 'string') {
34 | action = AnimationClip.findByName(this.animations, id);
35 | }
36 |
37 | return action;
38 | }
39 |
40 | getAvailableAnimations() {
41 | return this.animations.map(({ name }) => name);
42 | }
43 |
44 | stopAll() {
45 | this.mixer.stopAllAction();
46 | this.isPlaying = false;
47 | }
48 |
49 | stopCurrentAnimation() {
50 | if (this.currentAction) {
51 | this.currentAction.stop();
52 | }
53 |
54 | this.isPlaying = false;
55 | }
56 |
57 | playAnimation(id, options) {
58 | const action = this.getAction(id);
59 | const { loop = LoopRepeat } = options;
60 | this.isPlaying = true;
61 |
62 | if (this.currentAction) {
63 | this.fadeToAnimation(action, options);
64 | } else if (action) {
65 | this.currentAction = this.mixer
66 | .clipAction(action)
67 | .setLoop(loop)
68 | .play();
69 | } else {
70 | console.warn(ANIMATION_NOT_FOUND);
71 | }
72 | }
73 |
74 | fadeToAnimation(action, { duration = 0.2, loop = LoopRepeat } = {}) {
75 | const previousAction = this.currentAction;
76 | this.currentAction = this.mixer.clipAction(action);
77 |
78 | if (previousAction !== this.currentAction) {
79 | previousAction.fadeOut(duration);
80 | }
81 |
82 | this.currentAction
83 | .reset()
84 | .setEffectiveTimeScale(1)
85 | .setEffectiveWeight(1)
86 | .fadeIn(duration)
87 | .setLoop(loop)
88 | .play();
89 | }
90 |
91 | update(dt) {
92 | this.mixer.update(dt);
93 | }
94 | }
--------------------------------------------------------------------------------
/src/entities/animations/SpriteAnimationHandler.js:
--------------------------------------------------------------------------------
1 | export default class SpriteAnimationHandler {
2 |
3 | };
--------------------------------------------------------------------------------
/src/entities/animations/lib/NodeFrame.js:
--------------------------------------------------------------------------------
1 | export default class NodeFrame {
2 |
3 | constructor(time) {
4 | this.time = time !== undefined ? time : 0;
5 | this.id = 0;
6 | }
7 |
8 | update( delta ) {
9 | ++ this.id;
10 |
11 | his.time += delta;
12 | this.delta = delta;
13 |
14 | return this;
15 | }
16 |
17 | setRenderer( renderer ) {
18 |
19 | this.renderer = renderer;
20 |
21 | return this;
22 |
23 | }
24 |
25 | setRenderTexture( renderTexture ) {
26 |
27 | this.renderTexture = renderTexture;
28 | return this;
29 |
30 | }
31 |
32 | updateNode( node ) {
33 |
34 | if ( node.frameId === this.id ) return this;
35 |
36 | node.updateFrame( this );
37 |
38 | node.frameId = this.id;
39 |
40 | return this;
41 |
42 | }
43 |
44 | };
--------------------------------------------------------------------------------
/src/entities/base/AnimatedSprite.js:
--------------------------------------------------------------------------------
1 | import Element from '../Element';
2 | import { ENTITY_TYPES } from '../constants';
3 | import { Images } from "../../images/Images";
4 |
5 | import {
6 | THREESprite,
7 | SpriteNodeMaterial
8 | } from 'three';
9 |
10 | export default class AnimatedSprite extends Element {
11 |
12 | constructor(width = 20, height = 20, spriteTexture, options = {}) {
13 | super(options);
14 |
15 | const material = new SpriteNodeMaterial({
16 | map: Images.get(spriteTexture),
17 | ...options
18 | });
19 |
20 | const body = new THREESprite(material);
21 | body.scale.x = width;
22 | body.scale.y = height;
23 |
24 | this.setBody({ body });
25 | this.setEntityType(ENTITY_TYPES.SPRITE);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/entities/base/Axes.js:
--------------------------------------------------------------------------------
1 | import Element from '../Element';
2 | import { ENTITY_TYPES } from '../constants';
3 | import {
4 | LineSegments,
5 | LineBasicMaterial,
6 | Float32BufferAttribute,
7 | Color,
8 | BufferGeometry
9 | } from 'three';
10 | import { generateRandomName } from '../../lib/uuid';
11 |
12 | class AxesHelper extends LineSegments {
13 |
14 | constructor( size = 1, thickness = 1 ) {
15 |
16 | const vertices = [
17 | 0, 0, 0, size, 0, 0,
18 | 0, 0, 0, 0, size, 0,
19 | 0, 0, 0, 0, 0, size
20 | ];
21 |
22 | const colors = [
23 | 1, 0, 0, 1, 0.6, 0,
24 | 0, 1, 0, 0.6, 1, 0,
25 | 0, 0, 1, 0, 0.6, 1
26 | ];
27 |
28 | const geometry = new BufferGeometry();
29 | geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
30 | geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
31 |
32 | const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false, linewidth: thickness } );
33 |
34 | super( geometry, material );
35 |
36 | this.type = 'AxesHelper';
37 | }
38 |
39 | setColors( xAxisColor, yAxisColor, zAxisColor ) {
40 | const color = new Color();
41 | const array = this.geometry.attributes.color.array;
42 |
43 | color.set( xAxisColor );
44 | color.toArray( array, 0 );
45 | color.toArray( array, 3 );
46 |
47 | color.set( yAxisColor );
48 | color.toArray( array, 6 );
49 | color.toArray( array, 9 );
50 |
51 | color.set( zAxisColor );
52 | color.toArray( array, 12 );
53 | color.toArray( array, 15 );
54 |
55 | this.geometry.attributes.color.needsUpdate = true;
56 |
57 | return this;
58 | }
59 |
60 | dispose() {
61 | this.geometry.dispose();
62 | this.material.dispose();
63 | }
64 |
65 | }
66 |
67 |
68 | export default class Axes extends Element {
69 |
70 | constructor(size, thickness) {
71 | const options = {
72 | size,
73 | thickness,
74 | name: generateRandomName('AxesHelper')
75 | };
76 |
77 | super(options);
78 | const body = new AxesHelper(size, thickness);
79 |
80 | this.setBody({ body });
81 | this.setEntityType(ENTITY_TYPES.HELPER.AXES);
82 | }
83 |
84 | update() {}
85 | }
86 |
--------------------------------------------------------------------------------
/src/entities/base/Box.js:
--------------------------------------------------------------------------------
1 | import { BoxGeometry, MeshBasicMaterial } from "three";
2 | import Element from "../Element";
3 | import { ENTITY_TYPES } from "../constants";
4 | import Color from "../../lib/Color";
5 |
6 | export default class Box extends Element {
7 | constructor(
8 | width = 10,
9 | height = 10,
10 | depth = 10,
11 | color = Color.randomColor(true),
12 | options = {},
13 | ) {
14 | super(options);
15 |
16 | const geometry = new BoxGeometry(width, height, depth);
17 | const material = new MeshBasicMaterial({
18 | color,
19 | wireframe: false,
20 | ...options,
21 | });
22 |
23 | this.setBody({ geometry, material });
24 | this.setEntityType(ENTITY_TYPES.MESH);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/entities/base/Cone.js:
--------------------------------------------------------------------------------
1 | import {
2 | ConeGeometry,
3 | MeshBasicMaterial
4 | } from 'three';
5 | import Color from '../../lib/Color';
6 | import Element from '../Element';
7 | import { ENTITY_TYPES } from '../constants';
8 |
9 | const DEFAULT_RADIUS = 5;
10 | const DEFAULT_HEIGHT = 5;
11 | const DEFAULT_RADIAL_SEGMENTS = 8;
12 | const DEFAULT_HEIGHT_SEGMENTS = 1;
13 | const DEFAULT_OPENENDED = false;
14 |
15 | export default class Cone extends Element {
16 |
17 | constructor(radius = DEFAULT_RADIUS, height = DEFAULT_HEIGHT, color = Color.randomColor(true), options = {}) {
18 | super(options);
19 |
20 | const {
21 | radialSegments = DEFAULT_RADIAL_SEGMENTS,
22 | heightSegments = DEFAULT_HEIGHT_SEGMENTS,
23 | openEnded = DEFAULT_OPENENDED
24 | } = options;
25 |
26 | const geometry = new ConeGeometry(radius, height, radialSegments, heightSegments, openEnded);
27 | const material = new MeshBasicMaterial({
28 | color,
29 | wireframe: false,
30 | ...options
31 | });
32 |
33 | this.setBody({ geometry, material });
34 | this.setEntityType(ENTITY_TYPES.MESH);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/entities/base/Cube.js:
--------------------------------------------------------------------------------
1 | import { BoxGeometry, MeshBasicMaterial } from "three";
2 | import Element from "../Element";
3 | import { ENTITY_TYPES } from "../constants";
4 | import Color from "../../lib/Color";
5 |
6 | export default class Cube extends Element {
7 | constructor(side = 10, color = Color.randomColor(true), options = {}) {
8 | super(options);
9 |
10 | const geometry = new BoxGeometry(side, side, side);
11 | const material = new MeshBasicMaterial({
12 | color,
13 | wireframe: false,
14 | ...options,
15 | });
16 |
17 | this.setBody({ geometry, material });
18 | this.setEntityType(ENTITY_TYPES.MESH);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/entities/base/CurveLine.js:
--------------------------------------------------------------------------------
1 |
2 | import { Vector3, CatmullRomCurve3 } from 'three';
3 | import Line from './Line';
4 |
5 | const DEFAULT_CURVE_LINE_DIVISIONS = 20;
6 |
7 | export default class CurveLine extends Line {
8 |
9 | constructor(points = [], options = {}) {
10 | const {
11 | divisions = DEFAULT_CURVE_LINE_DIVISIONS
12 | } = options;
13 | const curve = CurveLine.genereateCurve(points, divisions);
14 |
15 | super(curve, options);
16 |
17 | this.curve = curve;
18 | }
19 |
20 | static genereateCurve(points, divisions) {
21 | return new CatmullRomCurve3(
22 | points.map(({ x, y, z }) => new Vector3(x, y, z))
23 | ).getPoints(divisions);
24 | }
25 | };
--------------------------------------------------------------------------------
/src/entities/base/Cylinder.js:
--------------------------------------------------------------------------------
1 | import { CylinderGeometry, MeshBasicMaterial } from "three";
2 | import Element from "../Element";
3 | import { ENTITY_TYPES } from "../constants";
4 | import Color from "../../lib/Color";
5 |
6 | export default class Cylinder extends Element {
7 | constructor(
8 | radiusTop = 10,
9 | radiusBottom = 10,
10 | height = 10,
11 | color = Color.randomColor(true),
12 | options = {},
13 | ) {
14 | super(options);
15 |
16 | const segments = 32;
17 |
18 | const geometry = new CylinderGeometry(radiusTop, radiusBottom, height, segments);
19 | const material = new MeshBasicMaterial({
20 | color,
21 | wireframe: false,
22 | ...options,
23 | });
24 |
25 | this.setBody({ geometry, material });
26 | this.setEntityType(ENTITY_TYPES.MESH);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/entities/base/Grid.js:
--------------------------------------------------------------------------------
1 | import { GridHelper } from "three";
2 | import Element from "../Element";
3 | import { ENTITY_TYPES } from "../constants";
4 | import { generateRandomName } from "../../lib/uuid";
5 | import Color from "../../lib/Color";
6 |
7 | export default class Grid extends Element {
8 | constructor(
9 | size,
10 | division,
11 | color1 = Color.randomColor(true),
12 | color2 = Color.randomColor(true),
13 | ) {
14 | const options = {
15 | size,
16 | division,
17 | color1,
18 | color2,
19 | name: generateRandomName("GridHelper"),
20 | };
21 |
22 | super(options);
23 | const body = new GridHelper(size, division, color1, color2);
24 |
25 | this.setBody({ body });
26 | this.setEntityType(ENTITY_TYPES.HELPER.GRID);
27 | }
28 |
29 | update() {}
30 | }
31 |
--------------------------------------------------------------------------------
/src/entities/base/HelperSprite.js:
--------------------------------------------------------------------------------
1 | import Sprite from "./Sprite";
2 |
3 | export default class HelperSprite extends Sprite {
4 | constructor(...options) {
5 | super(...options);
6 |
7 | this.helperTarget = undefined;
8 | }
9 |
10 | setHelperTarget(element) {
11 | this.helperTarget = element;
12 | }
13 |
14 | getHelperTarget() {
15 | return this.helperTarget;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/entities/base/Line.js:
--------------------------------------------------------------------------------
1 | import Element from '../Element';
2 | import { ENTITY_TYPES } from '../constants';
3 |
4 | import {
5 | LineBasicMaterial,
6 | LineDashedMaterial,
7 | BufferGeometry,
8 | Line as THREELine,
9 | Vector3
10 | } from 'three';
11 |
12 | const DEFAULT_LINE_COLOR = 0xffffff;
13 | const DEFAULT_LINE_THICKNESS = 2;
14 |
15 | export default class Line extends Element {
16 |
17 | constructor(points = [], options = {}) {
18 | super(options);
19 |
20 | this.points = points;
21 |
22 | const material = this.getMaterial();
23 | const geometry = this.getGeometryFromPoints();
24 | const body = new THREELine(geometry, material);
25 |
26 | this.setBody({ body });
27 | this.setEntityType(ENTITY_TYPES.MESH);
28 | }
29 |
30 | postBodyCreation() {
31 | super.postBodyCreation();
32 | const { dashed = false } = this.options;
33 |
34 | if (dashed) {
35 | this.body.computeLineDistances();
36 | }
37 | }
38 |
39 | getMaterial() {
40 | const {
41 | color = DEFAULT_LINE_COLOR,
42 | dashed = false,
43 | thickness = DEFAULT_LINE_THICKNESS
44 | } = this.options;
45 |
46 | return dashed ?
47 | new LineDashedMaterial({ color, linewidth: thickness }) :
48 | new LineBasicMaterial({ color, linewidth: thickness });
49 | }
50 |
51 | getGeometryFromPoints() {
52 | const vectors = this.points.map(({ x = 0, y = 0, z = 0}) => new Vector3(x, y, z));
53 |
54 | return new BufferGeometry()
55 | .setFromPoints(vectors);
56 | }
57 |
58 | updatePoints = (points) => {
59 | const vectors = points.map(({ x, y, z }) => new Vector3(x, y, z));
60 | this.body.geometry.vertices = vectors;
61 | this.body.geometry.verticesNeedUpdate = true;
62 | }
63 |
64 | setThickness(thickness = DEFAULT_LINE_THICKNESS) {
65 | this.body.material.linewidth = thickness;
66 | }
67 | }
--------------------------------------------------------------------------------
/src/entities/base/Plane.js:
--------------------------------------------------------------------------------
1 | import Element from "../Element";
2 | import { ENTITY_TYPES } from "../constants";
3 |
4 | import { PlaneGeometry, DoubleSide, Vector3, MeshBasicMaterial } from "three";
5 | import Color from "../../lib/Color";
6 |
7 | const UP = new Vector3(0, 1, 0);
8 | const DOWN = new Vector3(0, -1, 0);
9 |
10 | export default class Plane extends Element {
11 | constructor(height, width, color = Color.randomColor(true), options = {}) {
12 | super(options);
13 |
14 | const { transparent = false, opacity = 1 } = options;
15 |
16 | const material = new MeshBasicMaterial({ color, side: DoubleSide, transparent, opacity });
17 | const geometry = new PlaneGeometry(width, height);
18 |
19 | this.setBody({ geometry, material });
20 | this.setEntityType(ENTITY_TYPES.MESH);
21 | }
22 |
23 | static get UP() {
24 | return UP;
25 | }
26 | static get DOWN() {
27 | return DOWN;
28 | }
29 |
30 | face(direction) {
31 | const vector = new Vector3(direction.x, direction.y, direction.z);
32 |
33 | this.body.lookAt(vector);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/entities/base/Sphere.js:
--------------------------------------------------------------------------------
1 | import { SphereGeometry, MeshBasicMaterial } from "three";
2 | import Element from "../Element";
3 | import { ENTITY_TYPES } from "../constants";
4 | import Color from "../../lib/Color";
5 |
6 | export default class Sphere extends Element {
7 | constructor(radius = 10, color = Color.randomColor(true), options = {}) {
8 | super(options);
9 |
10 | const segments = 32;
11 |
12 | const geometry = new SphereGeometry(radius, segments, segments);
13 | const material = new MeshBasicMaterial({
14 | color,
15 | wireframe: false,
16 | ...options,
17 | });
18 |
19 | this.setBody({ geometry, material });
20 | this.setEntityType(ENTITY_TYPES.MESH);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/entities/base/Sprite.js:
--------------------------------------------------------------------------------
1 | import Images from "../../images/Images";
2 | import Scene from "../../core/Scene";
3 | import { SpriteMaterial, Sprite as THREESprite } from "three";
4 | import Element from "../Element";
5 | import { ENTITY_TYPES } from "../constants";
6 |
7 | const validateAnisotropy = anisotropy => {
8 | const max = Scene.getRenderer().capabilities.getMaxAnisotropy();
9 |
10 | return anisotropy > max ? max : anisotropy;
11 | };
12 |
13 | export default class Sprite extends Element {
14 | constructor(width = 20, height = 20, spriteTexture, options = {}) {
15 | super(options);
16 |
17 | const { anisotropy = 1, ...rest } = options;
18 | const texture = Images.get(spriteTexture);
19 |
20 | texture.anisotropy = validateAnisotropy(anisotropy);
21 |
22 | const material = new SpriteMaterial({
23 | map: texture,
24 | ...rest,
25 | });
26 |
27 | const body = new THREESprite(material);
28 | body.scale.x = width;
29 | body.scale.y = height;
30 |
31 | this.setBody({ body });
32 | this.setEntityType(ENTITY_TYPES.SPRITE);
33 | }
34 |
35 | getRotation() {
36 | return this.body.material.rotation;
37 | }
38 |
39 | setRotation(rotation = this.getRotation()) {
40 | this.body.material.rotation = rotation;
41 | }
42 |
43 | setSizeAttenuation(attenuation = true) {
44 | this.body.material.sizeAttenuation = attenuation;
45 | }
46 |
47 | setDepthTest(depthTest = true) {
48 | this.body.material.depthTest = depthTest;
49 | }
50 |
51 | setDepthWrite(depthWrite = true) {
52 | this.body.material.depthWrite = depthWrite;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/entities/base/index.js:
--------------------------------------------------------------------------------
1 | import Grid from "./Grid";
2 | import Axes from "./Axes";
3 | import Cube from "./Cube";
4 | import Sphere from "./Sphere";
5 | import Cylinder from "./Cylinder";
6 | import Line from "./Line";
7 | import CurveLine from "./CurveLine";
8 | import Plane from "./Plane";
9 | import Box from "./Box";
10 | import Sprite from "./Sprite";
11 | import HelperSprite from "./HelperSprite";
12 | import Label from "./Label";
13 | import Cone from "./Cone";
14 |
15 | export {
16 | Sphere,
17 | Grid,
18 | Cube,
19 | Cylinder,
20 | Line,
21 | CurveLine,
22 | Plane,
23 | Box,
24 | Sprite,
25 | HelperSprite,
26 | Axes,
27 | Label,
28 | Cone,
29 | };
30 |
--------------------------------------------------------------------------------
/src/entities/camera.js:
--------------------------------------------------------------------------------
1 | import { PerspectiveCamera, Vector3 } from "three";
2 | import config from "../core/config";
3 | import { ENTITY_TYPES } from "./constants";
4 | import Entity from "./Entity";
5 |
6 | export default class Camera extends Entity {
7 | constructor(options = {}) {
8 | const {
9 | name = "Main PerspectiveCamera",
10 | fov = config.camera().fov,
11 | ratio = config.screen().ratio,
12 | near = config.camera().near,
13 | far = config.camera().far,
14 | } = options;
15 |
16 | super({ name });
17 |
18 | this.options = options;
19 | const body = new PerspectiveCamera(fov, ratio, near, far);
20 |
21 | this.setBody({ body });
22 | this.setEntityType(ENTITY_TYPES.CAMERA);
23 | this.setName(name);
24 | }
25 |
26 | getPosition() {
27 | return this.body.position;
28 | }
29 |
30 | getDirection() {
31 | const vector = new Vector3();
32 | const { x, y, z } = this.getBody().getWorldDirection(vector);
33 |
34 | return {
35 | x,
36 | y,
37 | z,
38 | };
39 | }
40 |
41 | lookAt(position = {}) {
42 | const { x = 0, y = 0, z = 0 } = position;
43 | this.body.lookAt(x, y, z);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/entities/constants.js:
--------------------------------------------------------------------------------
1 | export const ENTITY_TYPES = {
2 | SCENE: 'SCENE',
3 | CAMERA: 'CAMERA',
4 | MESH: 'MESH',
5 | LABEL: 'LABEL',
6 | LIGHT: {
7 | DEFAULT: 'LIGHT.DEFAULT',
8 | AMBIENT: 'LIGHT.AMBIENT',
9 | SUN: 'LIGHT.SUN',
10 | HEMISPHERE: 'LIGHT.HEMISPHERE',
11 | POINT: 'LIGHT.POINT',
12 | SPOT: 'LIGHT.SPOT'
13 | },
14 | AUDIO: {
15 | DEFAULT: 'AUDIO.DEFAULT',
16 | DIRECTIONAL: 'AUDIO.DIRECTIONAL',
17 | AMBIENT: 'AUDIO.AMBIENT'
18 | },
19 | MODEL: 'MODEL',
20 | SPRITE: 'SPRITE',
21 | PARTICLE: 'PARTICLE',
22 | EFFECT: {
23 | PARTICLE: 'EFFECT.PARTICLE',
24 | SCENERY: 'EFFECT.SCENERY'
25 | },
26 | HELPER: {
27 | GRID: 'HELPER.GRID',
28 | AXES: 'HELPER.AXES'
29 | },
30 | UNKNOWN: 'UNKNOWN'
31 | };
32 |
33 | export const FLAT_ENTITY_TYPES = [
34 | ENTITY_TYPES.SCENE,
35 | ENTITY_TYPES.CAMERA,
36 | ENTITY_TYPES.MESH,
37 | ENTITY_TYPES.LABEL,
38 | ENTITY_TYPES.LIGHT.DEFAULT,
39 | ENTITY_TYPES.LIGHT.AMBIENT,
40 | ENTITY_TYPES.LIGHT.SUN,
41 | ENTITY_TYPES.LIGHT.HEMISPHERE,
42 | ENTITY_TYPES.LIGHT.POINT,
43 | ENTITY_TYPES.LIGHT.SPOT,
44 | ENTITY_TYPES.AUDIO.DEFAULT,
45 | ENTITY_TYPES.AUDIO.AMBIENT,
46 | ENTITY_TYPES.AUDIO.DIRECTIONAL,
47 | ENTITY_TYPES.AUDIO.BACKGROUND,
48 | ENTITY_TYPES.MODEL,
49 | ENTITY_TYPES.SPRITE,
50 | ENTITY_TYPES.PARTICLE,
51 | ENTITY_TYPES.EFFECT.PARTICLE,
52 | ENTITY_TYPES.EFFECT.SCENERY,
53 | ENTITY_TYPES.HELPER.GRID,
54 | ENTITY_TYPES.HELPER.AXES,
55 | ENTITY_TYPES.UNKNOWN
56 | ]
57 |
58 | export const ENTITY_EVENTS = {
59 | DISPOSE: 'DISPOSE',
60 | STATE_MACHINE: {
61 | CHANGE: 'STATE_MACHINE_CHANGE',
62 | },
63 | ANIMATION: {
64 | LOOP: 'LOOP',
65 | FINISHED: 'FINISHED'
66 | },
67 | AUDIO: {
68 | ENDED: 'ended'
69 | }
70 | };
71 |
72 | export const DEFAULT_TAG = 'all';
--------------------------------------------------------------------------------
/src/entities/index.js:
--------------------------------------------------------------------------------
1 | import { ENTITY_TYPES, ENTITY_EVENTS } from "./constants";
2 | import {
3 | Box,
4 | Cube,
5 | Cylinder,
6 | Grid,
7 | Line,
8 | CurveLine,
9 | Plane,
10 | Sphere,
11 | Sprite,
12 | HelperSprite,
13 | Axes,
14 | Label,
15 | Cone,
16 | } from "./base";
17 |
18 | export {
19 | ENTITY_TYPES,
20 | ENTITY_EVENTS,
21 | Box,
22 | Cube,
23 | Cylinder,
24 | Grid,
25 | Line,
26 | CurveLine,
27 | Plane,
28 | Sprite,
29 | HelperSprite,
30 | Sphere,
31 | Axes,
32 | Label,
33 | Cone,
34 | };
35 |
--------------------------------------------------------------------------------
/src/fx/materials/Atmosphere.js:
--------------------------------------------------------------------------------
1 | import {
2 | FrontSide,
3 | AdditiveBlending,
4 | Color
5 | } from 'three';
6 |
7 | export default class Atmosphere {
8 |
9 | constructor() {
10 | this.options = {
11 | side: FrontSide,
12 | blending: AdditiveBlending,
13 | transparent: true,
14 | depthWrite: false,
15 | };
16 |
17 | this.attributes = {};
18 | }
19 |
20 | static vertex() {
21 | return [
22 | 'varying vec3 vNormal;',
23 | 'void main(){',
24 | ' // compute intensity',
25 | ' vNormal = normalize( normalMatrix * normal );',
26 | ' // set gl_Position',
27 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
28 | '}',
29 | ].join('\n');
30 | }
31 |
32 | static fragment() {
33 | return [
34 | 'uniform float coeficient;',
35 | 'uniform float power;',
36 | 'uniform vec3 glowColor;',
37 |
38 | 'varying vec3 vNormal;',
39 |
40 | 'void main(){',
41 | ' float intensity = pow( coeficient - dot(vNormal, vec3(0.0, 0.0, 1.0)), power );',
42 | ' gl_FragColor = vec4( glowColor * intensity, 1.0 );',
43 | '}',
44 | ].join('\n');
45 | }
46 |
47 | static uniforms() {
48 | return {
49 | coeficient: {
50 | type: "f",
51 | value: 1.0
52 | },
53 | power: {
54 | type: "f",
55 | value: 2
56 | },
57 | glowColor: {
58 | type: "c",
59 | value: new Color('pink')
60 | },
61 | };
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/fx/particles/Explosion.js:
--------------------------------------------------------------------------------
1 | import Proton from 'three.proton';
2 | import ParticleEmitterGroup from './ParticleEmitterGroup';
3 | import ProtonParticleEmitter from './ProtonParticleEmitter';
4 |
5 | const DEFAULT_SIZE = 4;
6 |
7 | const getSparksInitializers = (size = DEFAULT_SIZE) => ([
8 | new Proton.Mass(0.1),
9 | new Proton.Radius(size * .3),
10 | new Proton.Life(1),
11 | new Proton.Position(new Proton.SphereZone(size * 1.5))
12 | ]);
13 |
14 | const getSparksBehaviours = (size = DEFAULT_SIZE) => ([
15 | new Proton.RandomDrift(size * 0.75, size * 0.75, size * 0.75, .5),
16 | new Proton.Color('#ffffff'),
17 | new Proton.Scale(1, 0.1)
18 | ]);
19 |
20 | const getSparksRate = () => new Proton.Rate(50, .5);
21 | const getDebrisRate = () => new Proton.Rate(10, .4);
22 | const getFireRate = () => new Proton.Rate(10, .4);
23 |
24 | const getDebrisInitializers = (size = DEFAULT_SIZE) => ([
25 | new Proton.Mass(10),
26 | new Proton.Radius(size * .25),
27 | new Proton.Life(2),
28 | new Proton.Position(new Proton.SphereZone(size * .75))
29 | ]);
30 |
31 | const getDebrisBehaviours = (size = DEFAULT_SIZE) => {
32 | const zone = new Proton.BoxZone(0, 50, 0, 300, 100, 300)
33 | zone.friction = 0.95;
34 | zone.max = 7;
35 | return ([
36 | new Proton.CrossZone(zone, "bound"),
37 | new Proton.Repulsion(new Proton.Vector3D(0, 0, 0), size * 12.5, size * 1.5),
38 | new Proton.G(3),
39 | new Proton.Color('#95a5a6', '#000000')
40 | ])
41 | };
42 |
43 | const getFireInitializers = (size = DEFAULT_SIZE) => ([
44 | new Proton.Mass(1),
45 | new Proton.Radius(size * 1.5),
46 | new Proton.Life(.2, .5),
47 | new Proton.Position(new Proton.SphereZone(size * 1.25))
48 | ]);
49 |
50 | const getFireBehaviours = () => ([
51 | new Proton.Scale(1, 2),
52 | new Proton.Color('#c0392b', '#f1c40f')
53 | ]);
54 | export default class Explosion extends ParticleEmitterGroup {
55 |
56 | constructor(options = {}) {
57 | const {
58 | texture = false,
59 | hasDebris = false,
60 | size = DEFAULT_SIZE
61 | } = options;
62 |
63 | const sparks = new ProtonParticleEmitter({ rate: getSparksRate(), texture, initializers: getSparksInitializers(size), behaviours: getSparksBehaviours(size)});
64 | const fire = new ProtonParticleEmitter({ rate: getFireRate(), texture, initializers: getFireInitializers(size), behaviours: getFireBehaviours(size) });
65 |
66 | const system = [
67 | sparks,
68 | fire
69 | ];
70 |
71 | if (hasDebris) {
72 | system.push(new ProtonParticleEmitter({ rate: getDebrisRate(), texture, initializers: getDebrisInitializers(size), behaviours: getDebrisBehaviours(size) }))
73 | }
74 |
75 | const name = 'ExplosionGroup';
76 |
77 | super({ system, name });
78 | }
79 | }
--------------------------------------------------------------------------------
/src/fx/particles/Fire.js:
--------------------------------------------------------------------------------
1 | import Proton from 'three.proton';
2 | import { Vector3 } from 'three';
3 | import ProtonParticleEmitter from "./ProtonParticleEmitter";
4 | import PALETTES from '../../lib/palettes';
5 | const getFireRate = () => new Proton.Rate(new Proton.Span(10, 15), new Proton.Span(.05, .1));
6 |
7 | const getFireInitializers = (direction, strength, size) => ([
8 | new Proton.Mass(1),
9 | new Proton.Life(1, 2),
10 | new Proton.Radius(size / 2, size / 1.5, 'center'),
11 | new Proton.Position(new Proton.SphereZone(size)),
12 | new Proton.V(new Proton.Span(strength, strength * 2), new Proton.Vector3D(direction.x, direction.y, direction.z), 5), //new Proton.Span(200, 500)
13 | ]);
14 |
15 | const getFireBehaviours = (direction, strength, colors = [PALETTES.FRENCH.MANDARIN_RED, PALETTES.FRENCH.MELON_MELODY]) => ([
16 | new Proton.Scale(new Proton.Span(2, 2.5), 0),
17 | new Proton.G(strength / 100),
18 | new Proton.Color(colors[0], colors[1], Infinity, Proton.easeOutSine),
19 | new Proton.RandomDrift(direction.x / 100, direction.y / 100, direction.z / 100, 2.5)
20 | ]);
21 |
22 | export default class Fire extends ProtonParticleEmitter {
23 |
24 | constructor(options) {
25 | const {
26 | texture,
27 | direction = new Vector3(0, 1, 0),
28 | size = 20,
29 | strength = 100,
30 | colors,
31 | ...rest
32 | } = options;
33 |
34 | const fireOptions = {
35 | rate: getFireRate(),
36 | texture,
37 | initializers: getFireInitializers(direction, strength, size),
38 | behaviours: getFireBehaviours(direction, strength, colors),
39 | ...rest
40 | }
41 |
42 | super(fireOptions);
43 | }
44 | }
--------------------------------------------------------------------------------
/src/fx/particles/Fountain.js:
--------------------------------------------------------------------------------
1 | import { Vector3 } from 'three';
2 | import { Randomizers, Emitter } from 'mage-engine.particles';
3 |
4 | import ParticleEmitter from './ParticleEmitter';
5 |
6 | export default class Fountain extends ParticleEmitter {
7 |
8 | constructor({ container, autostart, particles, system }) {
9 | const options = {
10 | container,
11 | autostart,
12 | particles: {
13 | ttl: 10,
14 | globalSize: 5,
15 | velocity: new Randomizers.SphereRandomizer(12.5),
16 | velocityBonus: new Vector3(0, 25, 0),
17 | particlesCount: 1000,
18 | blending: 'blend',
19 | gravity: -10,
20 | startAlpha: 1,
21 | endAlpha: 0,
22 | startColor: new Randomizers.ColorsRandomizer(),
23 | endColor: new Randomizers.ColorsRandomizer(),
24 | startAlphaChangeAt: 0,
25 | onUpdate: (particle) => {
26 | let floorY = -10;
27 | if (particle.position.y < floorY) {
28 | particle.position.y = floorY;
29 | particle.velocity.y *= -0.5;
30 | particle.velocity.x *= 0.5;
31 | particle.velocity.z *= 0.5;
32 | }
33 | },
34 | ...particles
35 | },
36 | system: {
37 | particlesCount: 100,
38 | emitters: new Emitter({
39 | onInterval: new Randomizers.MinMaxRandomizer(0, 5),
40 | interval: new Randomizers.MinMaxRandomizer(0, 0.25),
41 | }),
42 | speed: 1,
43 | ...system
44 | }
45 | };
46 |
47 | super(options);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/fx/particles/Rain.js:
--------------------------------------------------------------------------------
1 | import { Vector3, Color } from 'three';
2 | import { Randomizers, Emitter } from 'mage-engine.particles';
3 |
4 | import ParticleEmitter from './ParticleEmitter';
5 |
6 | export default class Rain extends ParticleEmitter {
7 |
8 | constructor({ container, autostart, particles, system }) {
9 | const options = {
10 | container,
11 | autostart,
12 | particles: {
13 | globalSize: 0.4,
14 | ttl: 5,
15 | velocity: new Vector3(0, -2, 0),
16 | velocityBonus: new Vector3(0, -60, 0),
17 | gravity: -10,
18 | startAlpha: 1,
19 | endAlpha: 0,
20 | startColor: new Color(1, 1, 1),
21 | endColor: new Color(0, 0, 0),
22 | startAlphaChangeAt: 0,
23 | blending: "additive",
24 | onSpawn: (particle) => {
25 | particle.position.x = Math.random() * 100;
26 | particle.position.z = Math.random() * 100;
27 | },
28 | ...particles
29 | },
30 | system: {
31 | particlesCount: 2000,
32 | emitters: new Emitter({
33 | onInterval: new Randomizers.MinMaxRandomizer(0, 5),
34 | interval: new Randomizers.MinMaxRandomizer(0, 0.25),
35 | }),
36 | speed: 1.5,
37 | ...system
38 | }
39 | };
40 |
41 | super(options);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/fx/particles/Snow.js:
--------------------------------------------------------------------------------
1 | import { Vector3, Color } from 'three';
2 | import { Randomizers, Emitter } from 'mage-engine.particles';
3 |
4 | import ParticleEmitter from './ParticleEmitter';
5 |
6 | export default class Snow extends ParticleEmitter {
7 |
8 | constructor({ container, autostart, particles, system }) {
9 | const options = {
10 | container,
11 | autostart,
12 | particles: {
13 | globalSize: 0.7,
14 | ttl: 5,
15 | velocity: new Randomizers.SphereRandomizer(30),
16 | velocityBonus: new Vector3(0, -30, 0),
17 | gravity: -10,
18 | startAlpha: 0.8,
19 | endAlpha: 0,
20 | startColor: new Color(1, 1, 1),
21 | endColor: new Color(0, 0, 0),
22 | startAlphaChangeAt: 0,
23 | blending: "blend",
24 | onSpawn: (particle) => {
25 | particle.position.x = Math.random() * 200;
26 | particle.position.z = Math.random() * 200;
27 | },
28 | ...particles
29 | },
30 | system: {
31 | particlesCount: 4000,
32 | emitters: new Emitter({
33 | onInterval: new Randomizers.MinMaxRandomizer(0, 5),
34 | interval: new Randomizers.MinMaxRandomizer(0, 0.25),
35 | }),
36 | speed: 1.5,
37 | ...system
38 | }
39 | };
40 |
41 | super(options);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/fx/particles/Trail.js:
--------------------------------------------------------------------------------
1 | import Proton from 'three.proton';
2 | import ParticleEmitterGroup from './ParticleEmitterGroup';
3 | import ProtonParticleEmitter from './ProtonParticleEmitter';
4 |
5 | const DEFAULT_SIZE = 4;
6 |
7 | const getTrailRate = () => new Proton.Rate(
8 | new Proton.Span(10, 20),
9 | new Proton.Span(0.01, 0.015)
10 | );
11 |
12 | const getTrailInitialisers = (size) => ([
13 | new Proton.Mass(1),
14 | new Proton.Life(1, 2),
15 | new Proton.Radius(size),
16 | ]);
17 |
18 | const getTrailBehaviour = () => ([
19 | new Proton.Alpha(1, 0),
20 | new Proton.Color("#ffffff"),
21 | new Proton.Scale(.5, 0.1)
22 | ])
23 |
24 | export default class Trail extends ParticleEmitterGroup {
25 |
26 | constructor(options = {}) {
27 | const {
28 | texture = false,
29 | size = DEFAULT_SIZE
30 | } = options;
31 |
32 | const system = [
33 | new ProtonParticleEmitter({
34 | rate: getTrailRate(),
35 | texture,
36 | initializers: getTrailInitialisers(size),
37 | behaviours: getTrailBehaviour(size)
38 | })
39 | ];
40 |
41 | const name = 'TrailGroup';
42 |
43 | super({ system, name });
44 | }
45 | }
--------------------------------------------------------------------------------
/src/fx/particles/constants.js:
--------------------------------------------------------------------------------
1 | export const PARTICLE_EMITTER_TYPES = {
2 | SINGLE: 'SINGLE',
3 | GROUP: 'GROUP'
4 | };
--------------------------------------------------------------------------------
/src/fx/postprocessing/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MageStudio/Mage/b9d7e2283d3bde54b5c071be647be6530c6f701f/src/fx/postprocessing/.DS_Store
--------------------------------------------------------------------------------
/src/fx/postprocessing/DepthOfField.js:
--------------------------------------------------------------------------------
1 | import Scene from '../../core/Scene';
2 | import config from '../../core/config';
3 |
4 | import BokehPass from './BokehPass';
5 |
6 | export default class DepthOfField extends BokehPass {
7 |
8 | constructor(params) {
9 | const { w: width, h: height, ratio: aspect } = config.screen();
10 | super(
11 | Scene.scene,
12 | Scene.getCameraBody(),
13 | { ...params, width, height, aspect });
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/HueSaturationEffect.js:
--------------------------------------------------------------------------------
1 | import ShaderPass from './passes/ShaderPass';
2 |
3 | const HueSaturationShader = {
4 |
5 | uniforms: {
6 | "tDiffuse": { value: null },
7 | "hue": { value: 0 },
8 | "saturation": { value: 0 }
9 | },
10 |
11 | vertexShader: [
12 |
13 | "varying vec2 vUv;",
14 |
15 | "void main() {",
16 |
17 | "vUv = uv;",
18 |
19 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
20 |
21 | "}"
22 |
23 | ].join( "\n" ),
24 |
25 | fragmentShader: [
26 |
27 | "uniform sampler2D tDiffuse;",
28 | "uniform float hue;",
29 | "uniform float saturation;",
30 |
31 | "varying vec2 vUv;",
32 |
33 | "void main() {",
34 |
35 | "gl_FragColor = texture2D( tDiffuse, vUv );",
36 |
37 | // hue
38 | "float angle = hue * 3.14159265;",
39 | "float s = sin(angle), c = cos(angle);",
40 | "vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0;",
41 | "float len = length(gl_FragColor.rgb);",
42 | "gl_FragColor.rgb = vec3(",
43 | "dot(gl_FragColor.rgb, weights.xyz),",
44 | "dot(gl_FragColor.rgb, weights.zxy),",
45 | "dot(gl_FragColor.rgb, weights.yzx)",
46 | ");",
47 |
48 | // saturation
49 | "float average = (gl_FragColor.r + gl_FragColor.g + gl_FragColor.b) / 3.0;",
50 | "if (saturation > 0.0) {",
51 | "gl_FragColor.rgb += (average - gl_FragColor.rgb) * (1.0 - 1.0 / (1.001 - saturation));",
52 | "} else {",
53 | "gl_FragColor.rgb += (average - gl_FragColor.rgb) * (-saturation);",
54 | "}",
55 |
56 | "}"
57 |
58 | ].join( "\n" )
59 |
60 | };
61 |
62 | export default ({ hue = 0, saturation = 0, renderToScreen = false }) => {
63 | const shader = {
64 | ...HueSaturationShader
65 | };
66 |
67 | shader.uniforms.hue.value = hue;
68 | shader.uniforms.saturation.value = saturation;
69 |
70 | const pass = new ShaderPass(shader);
71 | pass.renderToScreen = renderToScreen;
72 |
73 | return pass;
74 | }
75 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/PixelEffect.js:
--------------------------------------------------------------------------------
1 | import {
2 | Vector2
3 | } from 'three';
4 | import ShaderPass from './passes/ShaderPass';
5 | import Config from '../../core/config';
6 |
7 | const PixelShader = {
8 |
9 | uniforms: {
10 |
11 | "tDiffuse": { value: null },
12 | "resolution": { value: null },
13 | "pixelSize": { value: 1. },
14 |
15 | },
16 |
17 | vertexShader: [
18 |
19 | "varying highp vec2 vUv;",
20 |
21 | "void main() {",
22 |
23 | "vUv = uv;",
24 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
25 |
26 | "}"
27 |
28 | ].join( "\n" ),
29 |
30 | fragmentShader: [
31 |
32 | "uniform sampler2D tDiffuse;",
33 | "uniform float pixelSize;",
34 | "uniform vec2 resolution;",
35 |
36 | "varying highp vec2 vUv;",
37 |
38 | "void main(){",
39 |
40 | "vec2 dxy = pixelSize / resolution;",
41 | "vec2 coord = dxy * floor( vUv / dxy );",
42 | "gl_FragColor = texture2D(tDiffuse, coord);",
43 |
44 | "}"
45 |
46 | ].join( "\n" )
47 | };
48 |
49 | export default class PixelEffect extends ShaderPass {
50 |
51 | constructor({ pixelSize = 16, renderToScreen = false }) {
52 | const { h, w, devicePixelRatio } = Config.screen();
53 |
54 | super(PixelShader);
55 |
56 | this.renderToScreen = renderToScreen;
57 |
58 | this.uniforms["pixelSize"].value = pixelSize;
59 | this.uniforms["resolution"].value = new Vector2(w, h);
60 | this.uniforms["resolution"].value.multiplyScalar(devicePixelRatio);
61 | }
62 |
63 | setPixelSize(pixelSize = 16) {
64 | this.uniforms["pixelSize"].value = pixelSize;
65 | }
66 |
67 | onWindowResize() {
68 | const { h, w, devicePixelRatio } = Config.screen();
69 |
70 | this.uniforms["resolution"].value.set(w, h).multiplyScalar(devicePixelRatio);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/SepiaEffect.js:
--------------------------------------------------------------------------------
1 | import ShaderPass from './passes/ShaderPass';
2 |
3 | const SepiaShader = {
4 | uniforms: {
5 | "tDiffuse": { value: null },
6 | "amount": { value: 1.0 }
7 | },
8 | vertexShader: [
9 | "varying vec2 vUv;",
10 | "void main() {",
11 | "vUv = uv;",
12 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
13 | "}"
14 | ].join( "\n" ),
15 | fragmentShader: [
16 | "uniform float amount;",
17 | "uniform sampler2D tDiffuse;",
18 | "varying vec2 vUv;",
19 | "void main() {",
20 | "vec4 color = texture2D( tDiffuse, vUv );",
21 | "vec3 c = color.rgb;",
22 | "color.r = dot( c, vec3( 1.0 - 0.607 * amount, 0.769 * amount, 0.189 * amount ) );",
23 | "color.g = dot( c, vec3( 0.349 * amount, 1.0 - 0.314 * amount, 0.168 * amount ) );",
24 | "color.b = dot( c, vec3( 0.272 * amount, 0.534 * amount, 1.0 - 0.869 * amount ) );",
25 | "gl_FragColor = vec4( min( vec3( 1.0 ), color.rgb ), color.a );",
26 | "}"
27 | ].join( "\n" )
28 | }
29 |
30 | export default ({ value = 1.0, renderToScreen = false }) => {
31 | const shader = SepiaShader;
32 | shader.uniforms.amount.value = value;
33 |
34 | const pass = new ShaderPass(shader);
35 | pass.renderToScreen = renderToScreen;
36 |
37 | return pass;
38 | }
39 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/passes/ClearMaskPass.js:
--------------------------------------------------------------------------------
1 | import Pass from './Pass';
2 |
3 | export default class ClearMaskPass extends Pass {
4 |
5 | constructor() {
6 | super();
7 | this.needsSwap = false;
8 | }
9 |
10 | render(renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */) {
11 | renderer.state.buffers.stencil.setLocked(false);
12 | renderer.state.buffers.stencil.setTest(false);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/passes/MaskPass.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @author alteredq / http://alteredqualia.com/
4 | */
5 |
6 | import Pass from "./Pass.js";
7 |
8 | export default class MaskPass extends Pass {
9 |
10 | constructor(scene, camera) {
11 | super();
12 |
13 | this.scene = scene;
14 | this.camera = camera;
15 |
16 | this.clear = true;
17 | this.needsSwap = false;
18 |
19 | this.inverse = false;
20 | }
21 |
22 | render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) {
23 |
24 | const context = renderer.getContext();
25 | const state = renderer.state;
26 |
27 | // don't update color or depth
28 |
29 | state.buffers.color.setMask(false);
30 | state.buffers.depth.setMask(false);
31 |
32 | // lock buffers
33 |
34 | state.buffers.color.setLocked(true);
35 | state.buffers.depth.setLocked(true);
36 |
37 | // set up stencil
38 |
39 | let writeValue, clearValue;
40 |
41 | if (this.inverse) {
42 | writeValue = 0;
43 | clearValue = 1;
44 | } else {
45 | writeValue = 1;
46 | clearValue = 0;
47 | }
48 |
49 | state.buffers.stencil.setTest(true);
50 | state.buffers.stencil.setOp(context.REPLACE, context.REPLACE, context.REPLACE);
51 | state.buffers.stencil.setFunc(context.ALWAYS, writeValue, 0xffffffff);
52 | state.buffers.stencil.setClear(clearValue);
53 | state.buffers.stencil.setLocked(true);
54 |
55 | // draw into the stencil buffer
56 |
57 | renderer.setRenderTarget(readBuffer);
58 | if (this.clear) renderer.clear();
59 | renderer.render(this.scene, this.camera);
60 |
61 | renderer.setRenderTarget(writeBuffer);
62 | if (this.clear) renderer.clear();
63 | renderer.render(this.scene, this.camera);
64 |
65 | // unlock color and depth buffer for subsequent rendering
66 |
67 | state.buffers.color.setLocked(false);
68 | state.buffers.depth.setLocked(false);
69 |
70 | // only render where stencil is set to 1
71 |
72 | state.buffers.stencil.setLocked(false);
73 | state.buffers.stencil.setFunc(context.EQUAL, 1, 0xffffffff); // draw if == 1
74 | state.buffers.stencil.setOp(context.KEEP, context.KEEP, context.KEEP);
75 | state.buffers.stencil.setLocked(true);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/passes/Pass.js:
--------------------------------------------------------------------------------
1 | import {
2 | Mesh,
3 | OrthographicCamera,
4 | BufferGeometry,
5 | Float32BufferAttribute
6 | } from 'three';
7 |
8 | export default class Pass {
9 |
10 | constructor() {
11 | // if set to true, the pass is processed by the composer
12 | this.enabled = true;
13 |
14 | // if set to true, the pass indicates to swap read and write buffer after rendering
15 | this.needsSwap = true;
16 |
17 | // if set to true, the pass clears its buffer before rendering
18 | this.clear = false;
19 |
20 | // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer.
21 | this.renderToScreen = false;
22 |
23 | this.isPass = true;
24 | }
25 |
26 | setSize( /* width, height */ ) {}
27 |
28 | render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
29 | console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
30 | }
31 |
32 | }
33 |
34 | // Helper for passes that need to fill the viewport with a single quad.
35 |
36 | const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
37 |
38 | // https://github.com/mrdoob/three.js/pull/21358
39 |
40 | const _geometry = new BufferGeometry();
41 | _geometry.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) );
42 | _geometry.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) );
43 |
44 | export class FullScreenQuad {
45 |
46 | constructor( material ) {
47 | this._mesh = new Mesh( _geometry, material );
48 | }
49 |
50 | dispose() {
51 | this._mesh.geometry.dispose();
52 | }
53 |
54 | render( renderer ) {
55 | renderer.render( this._mesh, _camera );
56 | }
57 |
58 | get material() {
59 | return this._mesh.material;
60 | }
61 |
62 | set material( value ) {
63 | this._mesh.material = value;
64 | }
65 | }
--------------------------------------------------------------------------------
/src/fx/postprocessing/passes/RenderPass.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | */
4 | import { Color } from 'three';
5 | import Pass from "./Pass.js";
6 |
7 | export default class RenderPass extends Pass {
8 |
9 | constructor(scene, camera, overrideMaterial, clearColor, clearAlpha) {
10 | super();
11 |
12 | this.scene = scene;
13 | this.camera = camera;
14 |
15 | this.overrideMaterial = overrideMaterial;
16 |
17 | this.clearColor = clearColor;
18 | this.clearAlpha = (clearAlpha !== undefined) ? clearAlpha : 0;
19 |
20 | this.clear = true;
21 | this.clearDepth = false;
22 | this.needsSwap = false;
23 | this._oldClearColor = new Color();
24 | }
25 |
26 | render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) {
27 |
28 | const oldAutoClear = renderer.autoClear;
29 | renderer.autoClear = false;
30 |
31 | let oldClearAlpha, oldOverrideMaterial;
32 |
33 | if (this.overrideMaterial !== undefined) {
34 | oldOverrideMaterial = this.scene.overrideMaterial;
35 | this.scene.overrideMaterial = this.overrideMaterial;
36 | }
37 |
38 | if (this.clearColor) {
39 | renderer.getClearColor(this._oldClearColor);
40 | oldClearAlpha = renderer.getClearAlpha();
41 |
42 | renderer.setClearColor(this.clearColor, this.clearAlpha);
43 | }
44 |
45 | if (this.clearDepth) {
46 | renderer.clearDepth();
47 | }
48 |
49 | renderer.setRenderTarget(this.renderToScreen ? null : readBuffer);
50 |
51 | // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
52 | if (this.clear) renderer.clear(renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil);
53 | renderer.render(this.scene, this.camera);
54 |
55 | if (this.clearColor) {
56 | renderer.setClearColor(this._oldClearColor, oldClearAlpha);
57 | }
58 |
59 | if (this.overrideMaterial !== undefined) {
60 | this.scene.overrideMaterial = oldOverrideMaterial;
61 | }
62 |
63 | renderer.autoClear = oldAutoClear;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/passes/ShaderPass.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | */
4 |
5 | import {
6 | ShaderMaterial,
7 | UniformsUtils
8 | } from "three";
9 | import Pass, { FullScreenQuad } from "./Pass";
10 |
11 | export default class ShaderPass extends Pass {
12 |
13 | constructor(shader, textureID) {
14 |
15 | super();
16 |
17 | this.textureID = (textureID !== undefined) ? textureID : 'tDiffuse';
18 |
19 | if (shader instanceof ShaderMaterial) {
20 | this.uniforms = shader.uniforms;
21 | this.material = shader;
22 | } else if (shader) {
23 | this.uniforms = UniformsUtils.clone(shader.uniforms);
24 |
25 | this.material = new ShaderMaterial({
26 | defines: Object.assign({}, shader.defines),
27 | uniforms: this.uniforms,
28 | vertexShader: shader.vertexShader,
29 | fragmentShader: shader.fragmentShader
30 | });
31 | }
32 |
33 | this.fsQuad = new FullScreenQuad(this.material);
34 | }
35 |
36 | render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) {
37 |
38 | if (this.uniforms[ this.textureID ]) {
39 | this.uniforms[ this.textureID ].value = readBuffer.texture;
40 | }
41 |
42 | this.fsQuad.material = this.material;
43 |
44 | if (this.renderToScreen) {
45 | renderer.setRenderTarget(null);
46 | this.fsQuad.render(renderer);
47 | } else {
48 | renderer.setRenderTarget(writeBuffer);
49 | // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
50 | if (this.clear) renderer.clear(renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil);
51 | this.fsQuad.render(renderer);
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/fx/postprocessing/shaders/ConvolutionShader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | *
4 | * Convolution shader
5 | * ported from o3d sample to WebGL / GLSL
6 | * http://o3d.googlecode.com/svn/trunk/samples/convolution.html
7 | */
8 |
9 | import {
10 | Vector2
11 | } from "three";
12 |
13 | const ConvolutionShader = {
14 |
15 | defines: {
16 | "KERNEL_SIZE_FLOAT": "25.0",
17 | "KERNEL_SIZE_INT": "25"
18 | },
19 |
20 | uniforms: {
21 | "tDiffuse": { value: null },
22 | "uImageIncrement": { value: new Vector2(0.001953125, 0.0) },
23 | "cKernel": { value: [] }
24 | },
25 |
26 | vertexShader: [
27 |
28 | "uniform vec2 uImageIncrement;",
29 |
30 | "varying vec2 vUv;",
31 |
32 | "void main() {",
33 |
34 | " vUv = uv - ((KERNEL_SIZE_FLOAT - 1.0) / 2.0) * uImageIncrement;",
35 | " gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
36 |
37 | "}"
38 |
39 | ].join("\n"),
40 |
41 | fragmentShader: [
42 |
43 | "uniform float cKernel[KERNEL_SIZE_INT];",
44 |
45 | "uniform sampler2D tDiffuse;",
46 | "uniform vec2 uImageIncrement;",
47 |
48 | "varying vec2 vUv;",
49 |
50 | "void main() {",
51 |
52 | " vec2 imageCoord = vUv;",
53 | " vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);",
54 |
55 | " for(int i = 0; i < KERNEL_SIZE_INT; i ++) {",
56 |
57 | " sum += texture2D(tDiffuse, imageCoord) * cKernel[i];",
58 | " imageCoord += uImageIncrement;",
59 |
60 | " }",
61 |
62 | " gl_FragColor = sum;",
63 |
64 | "}"
65 |
66 |
67 | ].join("\n"),
68 |
69 | buildKernel: function (sigma) {
70 |
71 | // We lop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway.
72 |
73 | function gauss(x, sigma) {
74 | return Math.exp(- (x * x) / (2.0 * sigma * sigma));
75 | }
76 |
77 | let i, values, sum, halfWidth, kMaxKernelSize = 25, kernelSize = 2 * Math.ceil(sigma * 3.0) + 1;
78 |
79 | if (kernelSize > kMaxKernelSize) kernelSize = kMaxKernelSize;
80 | halfWidth = (kernelSize - 1) * 0.5;
81 |
82 | values = new Array(kernelSize);
83 | sum = 0.0;
84 | for (i = 0; i < kernelSize; ++ i) {
85 |
86 | values[i] = gauss(i - halfWidth, sigma);
87 | sum += values[i];
88 |
89 | }
90 |
91 | // normalize the kernel
92 |
93 | for (i = 0; i < kernelSize; ++ i) values[i] /= sum;
94 |
95 | return values;
96 |
97 | }
98 |
99 | };
100 |
101 | export default ConvolutionShader;
102 |
--------------------------------------------------------------------------------
/src/fx/postprocessing/shaders/CopyShader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Full-screen textured quad shader
3 | */
4 |
5 | const CopyShader = {
6 |
7 | uniforms: {
8 | 'tDiffuse': { value: null },
9 | 'opacity': { value: 1.0 }
10 | },
11 |
12 | vertexShader: /* glsl */`
13 |
14 | varying vec2 vUv;
15 |
16 | void main() {
17 | vUv = uv;
18 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
19 | }`,
20 |
21 | fragmentShader: /* glsl */`
22 |
23 | uniform float opacity;
24 |
25 | uniform sampler2D tDiffuse;
26 |
27 | varying vec2 vUv;
28 |
29 | void main() {
30 | gl_FragColor = texture2D( tDiffuse, vUv );
31 | gl_FragColor.a *= opacity;
32 | }`
33 |
34 | };
35 |
36 | export default CopyShader;
--------------------------------------------------------------------------------
/src/fx/scenery/Skybox.js:
--------------------------------------------------------------------------------
1 | import {
2 | CubeTexture,
3 | RGBFormat,
4 | MeshBasicMaterial,
5 | BoxGeometry,
6 | BackSide
7 | } from 'three';
8 |
9 | import { generateRandomName, generateUUID } from '../../lib/uuid';
10 | import Images from '../../images/Images';
11 | import Element from '../../entities/Element';
12 |
13 | export default class Skybox extends Element {
14 |
15 | constructor(options) {
16 | const {
17 | name = generateRandomName('Skybox'),
18 | texture = 'skybox',
19 | ...rest
20 | } = options;
21 |
22 | super({ name, texture, ...rest });
23 |
24 | this.cubeMap = typeof texture === 'string' ? Images.get(texture) : texture;
25 |
26 | const material = new MeshBasicMaterial({
27 | envMap: this.cubeMap,
28 | side: BackSide
29 | });
30 | const geometry = new BoxGeometry( 1000000, 1000000, 1000000 );
31 |
32 | this.setBody({ material, geometry });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/fx/shaders/Shaders.js:
--------------------------------------------------------------------------------
1 | import Atmosphere from '../materials/Atmosphere';
2 | import Mirror from '../materials/Mirror';
3 | import Ocean from '../materials/Ocean';
4 | import Water from '../materials/Water';
5 | import OceanShaders from '../materials/OceanShaders';
6 | import Skybox from '../scenery/Skybox';
7 | import Sky from '../scenery/Sky';
8 |
9 | export class Shaders {
10 |
11 | constructor() {
12 | this.map = {
13 | Atmosphere,
14 | Mirror,
15 | Ocean,
16 | OceanShaders,
17 | Skybox,
18 | Sky,
19 | Water
20 | };
21 |
22 | this.shaders = {};
23 |
24 | this.numShaders = 0;
25 | this.shadersLoaded = 0;
26 | }
27 |
28 | get = (id) => {
29 | return this.map[id] || false;
30 | }
31 |
32 | create(name, params) {
33 | this.map[name] = {
34 | name,
35 | vertex: params.vertex || "",
36 | fragment: params.fragment || "",
37 | options: params.options || {},
38 | attributes: params.attributes || {},
39 | uniforms: params.uniforms || {},
40 | instance: params.instance || false
41 | };
42 | }
43 |
44 | add(name, shader) {
45 | this.map[name] = shader;
46 | }
47 | }
48 |
49 | export default new Shaders();
50 |
--------------------------------------------------------------------------------
/src/fx/shaders/shader.js:
--------------------------------------------------------------------------------
1 | import { ShaderMaterial } from 'three';
2 | import Shaders from './Shaders';
3 |
4 | export default class Shader {
5 |
6 | constructor( name, attributes, uniforms, options ) {
7 | this.instance = Shaders.get(name);
8 |
9 | if (!this.instance) {
10 | this.name = this.shader.name;
11 | this.vertex = this.shader.vertex;
12 | this.fragment = this.shader.fragment;
13 | this.attributes = attributes ? attributes : this.shader.attributes;
14 | this.uniforms = uniforms ? uniforms : this.shader.uniforms;
15 |
16 | //creating shader options
17 | var object = {
18 | 'attributes': this.attributes,
19 | 'uniforms': this.uniforms,
20 | 'vertexShader': this.shader.vertex,
21 | 'fragmentShader': this.shader.fragment
22 | };
23 |
24 | //storing user options in shader options
25 | var opt = options ? options : this.shader.options;
26 | for (o in opt) {
27 | object[o] = opt[o];
28 | }
29 |
30 | //creating the actual material
31 | this.material = new ShaderMaterial(object);
32 | } else {
33 | this.instance = this.shader.instance;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/lib/Color.js:
--------------------------------------------------------------------------------
1 | import { Color as THREEColor } from "three";
2 |
3 | export default class Color {
4 | static randomColor(asNumber = false) {
5 | const letters = "0123456789ABCDEF".split("");
6 | let color = "";
7 | for (let i = 0; i < 6; i++) {
8 | color += letters[Math.floor(Math.random() * 16)];
9 | }
10 | return asNumber ? Number(`0x${color}`) : `#${color}`;
11 | }
12 |
13 | static componentToHex(c) {
14 | const hex = c.toString(16);
15 | return hex.length == 1 ? "0" + hex : hex;
16 | }
17 |
18 | static gbToHex(r, g, b) {
19 | return "0x" + Color.componentToHex(r) + Color.componentToHex(g) + Color.componentToHex(b);
20 | }
21 |
22 | static getIntValueFromHex(hex) {
23 | return parseInt(hex, 16);
24 | }
25 |
26 | constructor(color) {
27 | this.color = new THREEColor(color);
28 | }
29 |
30 | getColor() {
31 | return this.color;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/lib/colliders.js:
--------------------------------------------------------------------------------
1 | import {
2 | Vector3,
3 | Raycaster
4 | } from 'three';
5 | import Scene from '../core/Scene';
6 | import { Line } from '../entities';
7 |
8 | export const DEFAULT_COLLIDER_OFFSET = { x: 0, y: 0, z: 0 };
9 | export const DEFAULT_NEAR = 0;
10 | export const DEFAULT_FAR = 10;
11 |
12 | export const getPointsFromRayCollider = (ray, position) => {
13 | const origin = position.clone();
14 | const end = origin.add(ray.ray.direction.clone().multiplyScalar(ray.far));
15 |
16 | return [origin, end];
17 | };
18 |
19 | export const createColliderHelper = (ray, position) => {
20 | return new Line(getPointsFromRayCollider(ray, position));
21 | };
22 |
23 | export const parseColliderFromDescription = ({ type, vector }, options, mesh, isSprite = false) => {
24 | const { near = DEFAULT_NEAR, far = DEFAULT_FAR, offset = DEFAULT_COLLIDER_OFFSET, debug = false } = options;
25 | const parsedOffset = {
26 | ...DEFAULT_COLLIDER_OFFSET,
27 | ...offset
28 | };
29 | const position = mesh.position
30 | .clone()
31 | .add(new Vector3(parsedOffset.x, parsedOffset.y, parsedOffset.z));
32 | const ray = new Raycaster(position, vector, near, far);
33 | const helper = debug && createColliderHelper(ray, position);
34 |
35 | if (isSprite) {
36 | ray.setFromCamera(position, Scene.getCameraBody());
37 | }
38 |
39 | return {
40 | type,
41 | ray,
42 | helper,
43 | offset: parsedOffset
44 | };
45 | };
--------------------------------------------------------------------------------
/src/lib/dom.js:
--------------------------------------------------------------------------------
1 | import { removeFirst } from "./strings";
2 |
3 | export const isClassname = word => word.startsWith('.');
4 |
5 | export const isId = word => word.startsWith('#');
6 |
7 | export const DEFAULT_FULL_SIZE_STYLE = {
8 | position: 'absolute',
9 | height: '100%',
10 | width: '100%',
11 | margin: '0'
12 | };
13 |
14 | export const getOrCreateElement = (selector, type, style) => (
15 | document.querySelector(selector) ||
16 | createElementFromSelector(selector, type, style)
17 | )
18 |
19 | export const removeElement = selector => (
20 | document.querySelector(selector).remove()
21 | )
22 |
23 | export const createElementFromSelector = (selector, type = 'div', style = DEFAULT_FULL_SIZE_STYLE) => {
24 | const element = document.createElement(type);
25 |
26 | Object
27 | .keys(style)
28 | .forEach((property) => {
29 | element.style.setProperty(property, style[property]);
30 | });
31 |
32 | if (isClassname(selector)) {
33 | element.className = removeFirst(selector);
34 | } else if (isId(selector)) {
35 | element.id = removeFirst(selector);
36 | }
37 |
38 | return element;
39 | };
40 |
--------------------------------------------------------------------------------
/src/lib/easing.js:
--------------------------------------------------------------------------------
1 | import between from 'between.js';
2 |
3 | export const LOOPING = {
4 | BOUNCE: 'bounce',
5 | REPEAT: 'repeat',
6 | NONE: false
7 | };
8 |
9 | export const EASING_EVENTS = {
10 | UPDATE: 'update',
11 | COMPLETE: 'complete'
12 | };
13 |
14 | export const FUNCTIONS = {
15 | ...between.Easing
16 | };
17 |
18 | export const tweenTo = (origin, target, options = {}) => (
19 | new Promise((resolve) => {
20 | const {
21 | time,
22 | easing = FUNCTIONS.Linear.None,
23 | loop = LOOPING.NONE,
24 | onUpdate = f => f,
25 | repeat = undefined
26 | } = options;
27 |
28 | const tween = new between(origin, target)
29 | .time(time)
30 | .easing(easing)
31 | .on(EASING_EVENTS.UPDATE, onUpdate)
32 |
33 | const infinite = loop && !repeat;
34 | const onComplete = () => resolve(tween, infinite);
35 |
36 | if (loop) {
37 | tween.loop(loop, repeat);
38 | }
39 |
40 | if (infinite) {
41 | const timeToCompleteLoop = time * 2;
42 | setTimeout(onComplete, timeToCompleteLoop);
43 | } else {
44 | tween.on(EASING_EVENTS.COMPLETE, onComplete);
45 | }
46 | })
47 | );
48 |
49 |
--------------------------------------------------------------------------------
/src/lib/functions.js:
--------------------------------------------------------------------------------
1 | export const debounce = (func, wait, immediate, context = this) =>{
2 | let timeout;
3 | return function() {
4 | const args = arguments;
5 | const later = function() {
6 | timeout = null;
7 | if (!immediate) func.apply(context, args);
8 | };
9 | const callNow = immediate && !timeout;
10 | clearTimeout(timeout);
11 | timeout = setTimeout(later, wait);
12 | if (callNow) func.apply(context, args);
13 | };
14 | };
15 |
16 | export const NOOP = f => f;
--------------------------------------------------------------------------------
/src/lib/location.js:
--------------------------------------------------------------------------------
1 | import { HASH } from "./constants";
2 |
3 | export const hasLocation = () => window && window.location;
4 |
5 | export const getLocationHash = () => (
6 | hasLocation() ?
7 | location.hash :
8 | HASH
9 | );
10 |
11 | export const setLocationHash = (hash = '', options = '') => {
12 | if (hasLocation()) {
13 | location.hash =`${hash}${options}`;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/lib/map.js:
--------------------------------------------------------------------------------
1 | export const serializeMap = map => {
2 | const reducer = m =>
3 | [...m].reduce((a, v) => {
4 | a[v[0]] = v[1];
5 | return a;
6 | }, {});
7 | const replacer = (key, value) => (value instanceof Map ? reducer(value) : value);
8 |
9 | return JSON.stringify(map, replacer);
10 | };
11 |
12 | export const deserialiseMap = o => {
13 | const json = JSON.parse(o);
14 | const m = new Map();
15 |
16 | Object.keys(json).forEach(k => {
17 | m.set(k, json[k]);
18 | });
19 |
20 | return m;
21 | };
22 |
23 | export const populateMap = (map, data) => {
24 | Object.keys(data).forEach(k => {
25 | map.set(k, data[k]);
26 | });
27 |
28 | return map;
29 | };
30 |
--------------------------------------------------------------------------------
/src/lib/network.js:
--------------------------------------------------------------------------------
1 | import { dispatch } from "../store/Store";
2 | import { networkChanged } from "../store/actions/network";
3 |
4 | const ONLINE_EVENT = 'online';
5 | const OFFLINE_EVENT = 'offline';
6 |
7 | const handleOnline = () => dispatch(networkChanged(true));
8 | const handleOffline = () => dispatch(networkChanged(false));
9 |
10 | export const listenToNetworkChanges = () => {
11 | window.addEventListener(ONLINE_EVENT, handleOnline);
12 | window.addEventListener(OFFLINE_EVENT, handleOffline);
13 | };
14 |
15 | export const stopListeningToNetworkChanges = () => {
16 | window.removeEventListener(ONLINE_EVENT, handleOnline);
17 | window.removeEventListener(OFFLINE_EVENT, handleOffline);
18 | };
--------------------------------------------------------------------------------
/src/lib/palettes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * THANKS TO https://flatuicolors.com/ for providing the palettes below.
3 | */
4 |
5 | const BASE = {
6 | WHITE: 0XFFFFFF,
7 | BLACK: 0x000000,
8 | RED: 0xFF0000,
9 | GREEN: 0x00FF00,
10 | BLUE: 0x0000FF,
11 | YELLOW: 0xFFFF00,
12 | CYAN: 0x00FFFF,
13 | MAGENTA: 0xFF00FF,
14 | SILVER: 0xC0C0C0,
15 | GRAY: 0x808080,
16 | MAROON: 0x800000,
17 | OLIVE: 0x808000,
18 | PURPLE: 0x800080,
19 | TEAL: 0x008080,
20 | NAVY: 0x000080,
21 | };
22 |
23 |
24 | const FRENCH_PALETTE = {
25 | /**
26 | * french palette by Lea Poisson https://bit.ly/3iEo73B from 🇫🇷
27 | */
28 |
29 | FLAT_FLESH: 0Xfad390,
30 | SQUASH_BLOSSOM: 0Xf6b93b,
31 | ICELAND_POPPY: 0Xfa983a,
32 | CARROT_ORANGE: 0Xe58e26,
33 |
34 | MELON_MELODY: 0Xf8c291,
35 | MANDARIN_RED: 0Xe55039,
36 | TOMATO_RED: 0Xeb2f06,
37 | JALAPENO_RED: 0Xb71540,
38 |
39 | LIVID: 0X6a89cc,
40 | AZRAQ_BLUE: 0X4a69bd,
41 | YUE_GUANG_LAN_BLUE: 0X1e3799,
42 | DARK_SAPPHIRE: 0X0c2461,
43 |
44 | SPRAY: 0X82ccdd,
45 | DUPAIN: 0X60a3bc,
46 | GOOD_SAMARITAN: 0X3c6382,
47 | FOREST_BLUE: 0X0a3d62,
48 |
49 | PARADISE_GREEN: 0Xb8e994,
50 | AURORA_GREEN: 0X78e08f,
51 | WATERFALL: 0X38ada9,
52 | REEF_ENCOUNTER: 0X079992
53 | };
54 |
55 | export default {
56 | BASE,
57 | FRENCH_PALETTE,
58 | FRENCH: FRENCH_PALETTE,
59 | };
--------------------------------------------------------------------------------
/src/lib/query.js:
--------------------------------------------------------------------------------
1 | const QUERY_PREFIX = '?';
2 | const AMPERSAND = '&';
3 | const EQUALS = '=';
4 | const EMPTY = '';
5 | const EMPTY_QUERY = {};
6 |
7 | export const toQueryString = (query = {}) => {
8 | const keys = Object.keys(query);
9 | if (keys.length == 0) {
10 | return EMPTY;
11 | }
12 |
13 | const q = QUERY_PREFIX + (keys.map(k => k + EQUALS + query[k] + AMPERSAND).join(EMPTY));
14 |
15 | return q.endsWith(AMPERSAND) ? q.substr(0, q.length -1) : q;
16 | };
17 |
18 | export const parseQuery = (queryString = '') => {
19 | return queryString.length > 1 ?
20 | queryString
21 | .replace(QUERY_PREFIX, EMPTY)
22 | .split(AMPERSAND)
23 | .map(couple => {
24 | const split = couple.split(EQUALS);
25 | return { [split[0]]: split[1] };
26 | })
27 | .reduce((acc, param) => ({ ...acc, ...param }), {}) :
28 | EMPTY_QUERY;
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/strings.js:
--------------------------------------------------------------------------------
1 | export const upperCaseFirst = (sentence = '') => (
2 | sentence && typeof sentence === 'string' && sentence.length ?
3 | `${sentence[0].toUpperCase()}${sentence.slice(1, sentence.length)}` :
4 | ''
5 | );
6 |
7 | export const removeFirst = sentence => sentence.slice(1, sentence.length);
8 |
--------------------------------------------------------------------------------
/src/lib/url.js:
--------------------------------------------------------------------------------
1 | export const getHostURL = () => `${document.location.protocol}//${document.location.host}`;
--------------------------------------------------------------------------------
/src/lib/utils/assets.js:
--------------------------------------------------------------------------------
1 | import { DIVIDER } from '../constants';
2 |
3 | export const buildAssetId = (name, level) => level ? `${level}_${name}` : name;
4 | export const isLevelName = level => level.startsWith(DIVIDER);
--------------------------------------------------------------------------------
/src/lib/uuid.js:
--------------------------------------------------------------------------------
1 | export const generateUUID = () => {
2 | const s4 = () => Math
3 | .floor((1 + Math.random()) * 0x10000)
4 | .toString(16)
5 | .substring(1);
6 |
7 | return s4() + s4();
8 | };
9 |
10 | export const generateRandomName = (prefix) => `${prefix}_${generateUUID()}`;
--------------------------------------------------------------------------------
/src/lib/workers.js:
--------------------------------------------------------------------------------
1 | import { WORKERS_NOT_AVAILABLE } from './messages';
2 | import Features, { FEATURES } from './features';
3 |
4 | const BLOB_TYPE = 'application/javascript';
5 |
6 | export const createBlob = (task) => (
7 | new Blob(['(', task.toString(), ')()'], { type: BLOB_TYPE })
8 | );
9 |
10 | export const createWorker = (task, message) => {
11 | if (Features.isFeatureSupported(FEATURES.WEBWORKER)) {
12 | const url = URL.createObjectURL(createBlob(task));
13 | const worker = new Worker(url);
14 |
15 | // Won't be needing this anymore
16 | URL.revokeObjectURL(url);
17 |
18 | if (message) {
19 | worker.postMessage(message);
20 | }
21 |
22 | return worker;
23 | } else {
24 | console.error(WORKERS_NOT_AVAILABLE);
25 | return null;
26 | }
27 | }
28 |
29 | export const createPromiseWorker = (task, message) => {
30 | return new Promise((resolve, reject) => {
31 | const worker = createWorker(task, message);
32 |
33 | if (worker) {
34 | worker.onmessage = (data) => {
35 | resolve(data);
36 | worker.terminate();
37 | };
38 | } else {
39 | reject(WORKERS_NOT_AVAILABLE);
40 | }
41 | });
42 | }
43 |
--------------------------------------------------------------------------------
/src/lights/HemisphereLight.js:
--------------------------------------------------------------------------------
1 | import Light from "./light";
2 | import Scene from "../core/Scene";
3 | import { HemisphereLight as THREEHemisphereLight, HemisphereLightHelper } from "three";
4 | import { ENTITY_TYPES } from "../entities/constants";
5 | import { generateRandomName } from "../lib/uuid";
6 | import { serializeColor } from "../lib/meshUtils";
7 |
8 | const DEFAULT_INTENSITY = 0.5;
9 |
10 | const DEFAULT_SKY_COLOR = 0xffffff;
11 | const DEFAULT_GROUND_COLOR = 0x555555;
12 |
13 | const GREEN = 0x2ecc71;
14 |
15 | export default class HemisphereLight extends Light {
16 | constructor(options = {}) {
17 | const {
18 | color = {
19 | sky: DEFAULT_SKY_COLOR,
20 | ground: DEFAULT_GROUND_COLOR,
21 | },
22 | intensity = DEFAULT_INTENSITY,
23 | name = generateRandomName("HemisphereLight"),
24 | } = options;
25 |
26 | super({ color, intensity, name });
27 | this.options = options;
28 | this.setLight({ color, intensity });
29 | this.setEntityType(ENTITY_TYPES.LIGHT.HEMISPHERE);
30 | this.setName(name);
31 | }
32 |
33 | setLight({
34 | light,
35 | color = {
36 | sky: DEFAULT_SKY_COLOR,
37 | ground: DEFAULT_GROUND_COLOR,
38 | },
39 | intensity = DEFAULT_INTENSITY,
40 | }) {
41 | if (light) {
42 | this.setBody({ body: light });
43 | } else {
44 | const { sky = DEFAULT_SKY_COLOR, ground = DEFAULT_GROUND_COLOR } = color;
45 | this.setBody({ body: new THREEHemisphereLight(sky, ground, intensity) });
46 | }
47 |
48 | if (this.hasBody()) {
49 | this.addToScene();
50 | }
51 | }
52 |
53 | setColor = ({ sky, ground } = {}) => {
54 | this.getBody().color = sky;
55 | this.getBody().groundColor = ground;
56 | };
57 |
58 | getColor = () => {
59 | return {
60 | sky: this.getBody().color,
61 | ground: this.getBody().groundColor,
62 | };
63 | };
64 |
65 | addHelpers({ holderName = "hemispherelightholder", holderSize = 0.05 } = {}) {
66 | this.helper = new HemisphereLightHelper(this.getBody(), 2, GREEN);
67 | this.addHolder(holderName, holderSize);
68 |
69 | this.isUsingHelper = true;
70 |
71 | Scene.add(this.helper, null, false);
72 | }
73 |
74 | update(dt) {
75 | super.update(dt);
76 | if (this.usingHelper()) {
77 | this.setPosition(this.holder.getPosition(), { updateHolder: false });
78 | }
79 | }
80 |
81 | toJSON(parseJSON = false) {
82 | const color = this.getColor();
83 | return {
84 | ...super.toJSON(parseJSON),
85 | color: parseJSON ? serializeColor(color) : color,
86 | };
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/lights/Lights.js:
--------------------------------------------------------------------------------
1 | import { CascadeShadowMaps } from "./csm/CascadeShadowMaps";
2 |
3 | export const POINTLIGHT = "pointlight";
4 | export const AMBIENTLIGHT = "ambientlight";
5 | export const SUNLIGHT = "sunlight";
6 | export const SPOTLIGHT = "spotlight";
7 | export const HEMISPHERELIGHT = "hemisphere";
8 |
9 | export class Lights {
10 | constructor() {
11 | this.lights = [];
12 | this.csm = undefined;
13 | }
14 |
15 | getLights() {
16 | return this.lights;
17 | }
18 |
19 | isUsingCascadeShadowMaps() {
20 | return !!this.csm;
21 | }
22 |
23 | createCascadeShadowMaps(options = {}) {
24 | this.csm = new CascadeShadowMaps(options);
25 |
26 | return this.csm;
27 | }
28 |
29 | add(light) {
30 | this.lights.push(light);
31 | }
32 |
33 | update(dt) {
34 | if (this.isUsingCascadeShadowMaps()) {
35 | this.csm.update();
36 | }
37 | }
38 |
39 | toJSON(parseJSON = false) {
40 | return {
41 | lights: this.lights.map(l => l.toJSON(parseJSON)),
42 | };
43 | }
44 | }
45 |
46 | export default new Lights();
47 |
--------------------------------------------------------------------------------
/src/lights/ambientLight.js:
--------------------------------------------------------------------------------
1 | import Light from "./Light";
2 | import { AmbientLight as THREEAmbientLight } from "three";
3 | import { AMBIENTLIGHT } from "./Lights";
4 | import { ENTITY_TYPES } from "../entities/constants";
5 | import { generateRandomName } from "../lib/uuid";
6 |
7 | const DEFAULT_POSITION = { x: 0, y: 0, z: 0 };
8 | const DEFAULT_INTENSITY = 0.5;
9 | const WHITE = 0xffffff;
10 |
11 | export default class AmbientLight extends Light {
12 | constructor(options = {}) {
13 | const {
14 | color = WHITE,
15 | intensity = DEFAULT_INTENSITY,
16 | name = generateRandomName("AmbientLight"),
17 | } = options;
18 | super({ color, intensity, name });
19 |
20 | this.options = options;
21 | this.setLight({ color, intensity });
22 | this.setEntityType(ENTITY_TYPES.LIGHT.AMBIENT);
23 | this.setName(name);
24 | }
25 |
26 | setLight({ light, color = WHITE, intensity = DEFAULT_INTENSITY }) {
27 | if (light) {
28 | this.setBody({ body: light });
29 | } else {
30 | this.setBody({ body: new THREEAmbientLight(color, intensity) });
31 | }
32 |
33 | if (this.hasBody()) {
34 | this.postLightCreation();
35 | }
36 | }
37 |
38 | postLightCreation() {
39 | const { position = DEFAULT_POSITION } = this.options;
40 |
41 | this.setPosition(position);
42 | this.addToScene();
43 | }
44 |
45 | addHelpers({ holderName = "ambientlightholder", holderSize = 0.05 } = {}) {
46 | this.addHolder(holderName, holderSize);
47 | this.isUsingHelper = true;
48 | }
49 |
50 | update(dt) {
51 | super.update(dt);
52 | if (this.usingHelper()) {
53 | this.setPosition(this.holder.getPosition(), { updateHolder: false });
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/lights/constants.js:
--------------------------------------------------------------------------------
1 | export const SHADOW_TYPES = {
2 | BASIC: 'BASIC',
3 | SOFT: 'SOFT',
4 | HARD: 'HARD',
5 | };
6 |
7 | export const DEFAULT_SHADOWTYPE = SHADOW_TYPES.BASIC;
8 |
--------------------------------------------------------------------------------
/src/lights/utils.js:
--------------------------------------------------------------------------------
1 | import { BasicShadowMap, PCFShadowMap, PCFSoftShadowMap } from "three";
2 | import { DEFAULT_SHADOWTYPE, SHADOW_TYPES } from "./constants";
3 |
4 | export const mapShadowTypeToShadowMap = (shadowType = DEFAULT_SHADOWTYPE) => ({
5 | [SHADOW_TYPES.BASIC]: BasicShadowMap,
6 | [SHADOW_TYPES.SOFT]: PCFSoftShadowMap,
7 | [SHADOW_TYPES.HARD]: PCFShadowMap
8 | })[shadowType] || BasicShadowMap;
--------------------------------------------------------------------------------
/src/loaders/Loader.js:
--------------------------------------------------------------------------------
1 | import {
2 | ObjectLoader
3 | } from 'three';
4 |
5 | export default class Loader {
6 |
7 | constructor() {
8 | this.loader = new ObjectLoader();
9 | }
10 |
11 | load() {}
12 | }
13 |
--------------------------------------------------------------------------------
/src/loaders/OBJMTLLoader.js:
--------------------------------------------------------------------------------
1 | import MTLLoader from "./MTLLoader";
2 | import OBJLoader from "./OBJLoader";
3 | import RequirementsTracer, { MODELS_REQUIREMENTS } from "./RequirementsTracer";
4 |
5 | const EXTENSIONS = {
6 | OBJ: ".obj",
7 | MTL: ".mtl",
8 | };
9 |
10 | export const buildOBJMTLLoader = () => {
11 | const tracer = new RequirementsTracer();
12 |
13 | class OBJMTLLoader {
14 | constructor(options) {
15 | this.options = options || {};
16 | this.materialLoader = new MTLLoader();
17 | this.objLoader = new OBJLoader();
18 | }
19 |
20 | setOptions(options) {
21 | this.options = options;
22 | this.materialLoader.setOptions(options);
23 | }
24 |
25 | tryLoadingMTL(path) {
26 | const { material } = this.options;
27 | const materialPath = material || path.replace(EXTENSIONS.OBJ, EXTENSIONS.MTL);
28 |
29 | return new Promise((resolve, reject) => {
30 | this.materialLoader.load(
31 | materialPath,
32 | material => {
33 | material.preload();
34 | resolve(material);
35 | },
36 | () => {},
37 | () => {
38 | tracer.trace(MODELS_REQUIREMENTS.MATERIAL);
39 | reject();
40 | },
41 | );
42 | });
43 | }
44 |
45 | loadObj(path, material) {
46 | if (material) {
47 | this.objLoader.setMaterials(material);
48 | }
49 |
50 | return new Promise(resolve => this.objLoader.load(path, resolve));
51 | }
52 |
53 | load(path, onComplete) {
54 | this.tryLoadingMTL(path)
55 | .then(material => this.loadObj(path, material))
56 | .then(onComplete);
57 | }
58 | }
59 |
60 | return { tracer, loader: new OBJMTLLoader() };
61 | };
62 |
--------------------------------------------------------------------------------
/src/loaders/RequirementsTracer.js:
--------------------------------------------------------------------------------
1 | import { EventDispatcher } from "three";
2 |
3 | export const REQUIREMENTS_EVENTS = {
4 | MISSING: "requirements:missing",
5 | FULFILLED: "requirements:fulfilled",
6 | };
7 |
8 | export const MODELS_REQUIREMENTS = {
9 | TEXTURE: "texture",
10 | MATERIAL: "material",
11 | BINARY: "binary",
12 | };
13 |
14 | export default class RequirementsTracer extends EventDispatcher {
15 | constructor() {
16 | super();
17 | this.requirements = [];
18 | }
19 |
20 | trace(requirement) {
21 | this.requirements.push(requirement);
22 | this.dispatchEvent({ type: REQUIREMENTS_EVENTS.MISSING, requirements: this.requirements });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/loaders/curves/NURBSCurve.js:
--------------------------------------------------------------------------------
1 | import {
2 | Curve,
3 | Vector3,
4 | Vector4
5 | } from 'three';
6 | import * as NURBSUtils from '../curves/NURBSUtils.js';
7 |
8 | /**
9 | * NURBS curve object
10 | *
11 | * Derives from Curve, overriding getPoint and getTangent.
12 | *
13 | * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
14 | *
15 | **/
16 |
17 | class NURBSCurve extends Curve {
18 |
19 | constructor(
20 | degree,
21 | knots /* array of reals */,
22 | controlPoints /* array of Vector(2|3|4) */,
23 | startKnot /* index in knots */,
24 | endKnot /* index in knots */
25 | ) {
26 |
27 | super();
28 |
29 | this.degree = degree;
30 | this.knots = knots;
31 | this.controlPoints = [];
32 | // Used by periodic NURBS to remove hidden spans
33 | this.startKnot = startKnot || 0;
34 | this.endKnot = endKnot || ( this.knots.length - 1 );
35 |
36 | for ( let i = 0; i < controlPoints.length; ++ i ) {
37 |
38 | // ensure Vector4 for control points
39 | const point = controlPoints[ i ];
40 | this.controlPoints[ i ] = new Vector4( point.x, point.y, point.z, point.w );
41 |
42 | }
43 |
44 | }
45 |
46 | getPoint( t, optionalTarget = new Vector3() ) {
47 |
48 | const point = optionalTarget;
49 |
50 | const u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u
51 |
52 | // following results in (wx, wy, wz, w) homogeneous point
53 | const hpoint = NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
54 |
55 | if ( hpoint.w !== 1.0 ) {
56 |
57 | // project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
58 | hpoint.divideScalar( hpoint.w );
59 |
60 | }
61 |
62 | return point.set( hpoint.x, hpoint.y, hpoint.z );
63 |
64 | }
65 |
66 | getTangent( t, optionalTarget = new Vector3() ) {
67 |
68 | const tangent = optionalTarget;
69 |
70 | const u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] );
71 | const ders = NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 );
72 | tangent.copy( ders[ 1 ] ).normalize();
73 |
74 | return tangent;
75 |
76 | }
77 |
78 | }
79 |
80 | export { NURBSCurve };
--------------------------------------------------------------------------------
/src/loaders/utils.js:
--------------------------------------------------------------------------------
1 | export const isAbsoluteURL = url => {
2 | return /^(?:\w+:)\/\//.test(url);
3 | };
4 |
--------------------------------------------------------------------------------
/src/materials/constants.js:
--------------------------------------------------------------------------------
1 | import { MATERIALS, TEXTURES } from "../lib/constants";
2 |
3 | export const MATERIAL_TEXTURE_MAP = {
4 | [MATERIALS.BASIC]: [
5 | TEXTURES.ALPHA,
6 | TEXTURES.AO,
7 | TEXTURES.ENV,
8 | TEXTURES.LIGHT,
9 | TEXTURES.MAP,
10 | TEXTURES.SPECULAR,
11 | ],
12 | [MATERIALS.LAMBERT]: [
13 | TEXTURES.ALPHA,
14 | TEXTURES.AO,
15 | TEXTURES.ENV,
16 | TEXTURES.LIGHT,
17 | TEXTURES.MAP,
18 | TEXTURES.SPECULAR,
19 | TEXTURES.EMISSIVE,
20 | ],
21 | [MATERIALS.PHONG]: [
22 | TEXTURES.ALPHA,
23 | TEXTURES.AO,
24 | TEXTURES.ENV,
25 | TEXTURES.LIGHT,
26 | TEXTURES.MAP,
27 | TEXTURES.SPECULAR,
28 | TEXTURES.EMISSIVE,
29 | TEXTURES.BUMP,
30 | TEXTURES.DISPLACEMENT,
31 | TEXTURES.NORMAL,
32 | ],
33 | [MATERIALS.DEPTH]: [TEXTURES.ALPHA, TEXTURES.MAP, TEXTURES.DISPLACEMENT],
34 | [MATERIALS.STANDARD]: [
35 | TEXTURES.ALPHA,
36 | TEXTURES.AO,
37 | TEXTURES.ENV,
38 | TEXTURES.LIGHT,
39 | TEXTURES.MAP,
40 | TEXTURES.EMISSIVE,
41 | TEXTURES.BUMP,
42 | TEXTURES.DISPLACEMENT,
43 | TEXTURES.NORMAL,
44 | TEXTURES.METALNESS,
45 | TEXTURES.ROUGHNESS,
46 | ],
47 | [MATERIALS.THREE_TOON]: [
48 | TEXTURES.ALPHA,
49 | TEXTURES.AO,
50 | TEXTURES.BUMP,
51 | TEXTURES.DISPLACEMENT,
52 | TEXTURES.EMISSIVE,
53 | TEXTURES.GRADIENT,
54 | TEXTURES.LIGHT,
55 | TEXTURES.MAP,
56 | TEXTURES.NORMAL,
57 | ],
58 | };
59 |
--------------------------------------------------------------------------------
/src/materials/helpers.js:
--------------------------------------------------------------------------------
1 | import { MATERIAL_TEXTURE_MAP } from "./constants";
2 |
3 | export const isTextureMapAllowedForMaterial = (materialType, textureType) =>
4 | MATERIAL_TEXTURE_MAP[materialType] && MATERIAL_TEXTURE_MAP[materialType].includes(textureType);
5 |
--------------------------------------------------------------------------------
/src/physics/constants.js:
--------------------------------------------------------------------------------
1 | export const LIBRARY_NAME = 'ammo.js';
2 |
3 | export const TYPES = {
4 | BOX: 'BOX',
5 | SPHERE: 'SPHERE',
6 | VEHICLE: 'VEHICLE',
7 | MESH: 'MESH',
8 | PLAYER: 'PLAYER'
9 | };
10 |
11 | export const COLLIDER_TYPES = {
12 | BOX: 'BOX',
13 | VEHICLE: 'VEHICLE',
14 | PLAYER: 'PLAYER',
15 | SPHERE: 'SPHERE'
16 | };
17 |
18 | export const DEFAULT_VEHICLE_STATE = {
19 | vehicleSteering: 0,
20 | acceleration: false,
21 | breaking: false,
22 | right: false,
23 | left: false
24 | };
25 |
26 | export const DEFAULT_RIGIDBODY_STATE = {
27 | velocity: { x: 0, y: 0, z: 0 },
28 | movement: {
29 | forward: false,
30 | backwards: false,
31 | left: false,
32 | right: false
33 | },
34 | direction: {
35 | x: 0,
36 | y: 0,
37 | z: 0
38 | }
39 | };
40 |
41 | export const DEFAULT_SCALE = { x: 1, y: 1, z: 1 };
42 | export const DEFAULT_QUATERNION = { x: 0, y: 0, z: 0, w: 1 };
43 | export const DEFAULT_POSITION = { x: 0, y: 0, z: 0 };
44 | export const DEFAULT_LINEAR_VELOCITY = { x: 0, y: 0, z: 0 };
45 | export const DEFAULT_ANGULAR_VELOCITY = { x: 0, y: 0, z: 0 };
46 | export const DEFAULT_IMPULSE = { x: 0, y: 0, z: 0 };
47 |
48 | export const DISABLE_DEACTIVATION = 4;
49 | export const GRAVITY = { x: 0, y: -10, z: 0 };
50 |
51 | export const FRONT_LEFT = 0;
52 | export const FRONT_RIGHT = 1;
53 | export const BACK_LEFT = 2;
54 | export const BACK_RIGHT = 3;
55 |
56 | export const DEFAULT_STEERING_INCREMENT = .04;
57 | export const DEFAULT_STEERING_CLAMP = .5;
58 | export const DEFAULT_MAX_ENGINE_FORCE = 2000;
59 | export const DEFAULT_MAX_BREAKING_FORCE = 100;
60 |
61 | export const EXPLOSION_SIZES = {
62 | SMALL: 4,
63 | MEDIUM: 6,
64 | LARGE: 8,
65 | MASSIVE: 12
66 | };
67 |
68 | export const EXPLOSION_STRENGTHS = {
69 | VERY_WEAK: 2,
70 | WEAK: 4,
71 | MEDIUM: 8,
72 | LARGE: 16,
73 | MASSIVE: 32,
74 | OK_NO: 64
75 | };
--------------------------------------------------------------------------------
/src/physics/hitbox.js:
--------------------------------------------------------------------------------
1 | import { Vector3 } from 'three';
2 | import Box from '../entities/base/Box';
3 | import Sphere from '../entities/base/Sphere';
4 | import { COLLIDER_TYPES } from './constants';
5 |
6 | const HIT_BOX_COLOR = 0xf368e0;
7 | const HIT_BOX_INCREASE = .03;
8 |
9 | const DEFAULT_HITBOX_OPTIONS = {
10 | shadowsEnabled: false
11 | };
12 |
13 | export const getBoxHitbox = element => {
14 | const size = new Vector3();
15 | element.boundingBox.getSize(size);
16 |
17 | const scaledSize = {
18 | x: size.x + HIT_BOX_INCREASE,
19 | y: size.y + HIT_BOX_INCREASE,
20 | z: size.z + HIT_BOX_INCREASE
21 | };
22 | const box = new Box(scaledSize.x, scaledSize.y, scaledSize.z, HIT_BOX_COLOR, DEFAULT_HITBOX_OPTIONS);
23 |
24 | //box.setQuaternion(quaternion);
25 | box.setWireframe(true);
26 | box.setWireframeLineWidth(2);
27 |
28 | return box;
29 | }
30 |
31 | export const getSphereHitbox = element => {
32 | const radius = element.boundingSphere.radius;
33 | const sphere = new Sphere(radius, HIT_BOX_COLOR, DEFAULT_HITBOX_OPTIONS);
34 |
35 | sphere.setWireframe(true);
36 | sphere.setWireframeLineWidth(2);
37 |
38 | return sphere;
39 | };
40 |
41 | export const mapColliderTypeToHitbox = (colliderType = COLLIDER_TYPES.BOX) => ({
42 | [COLLIDER_TYPES.BOX]: getBoxHitbox,
43 | [COLLIDER_TYPES.SPHERE]: getSphereHitbox
44 | }[colliderType] || getBoxHitbox);
45 |
46 | export const addHitBox = element => {
47 | const colliderType = element.getPhysicsOptions('colliderType');
48 | const getHitbox = mapColliderTypeToHitbox(colliderType)
49 |
50 | element.add(getHitbox(element));
51 | };
--------------------------------------------------------------------------------
/src/physics/messages.js:
--------------------------------------------------------------------------------
1 | export const PHYSICS_EVENTS = {
2 | DISPATCH: 'physics:dispatch',
3 | TERMINATE: 'physics:terminate',
4 | LOAD: {
5 | AMMO: 'physics:load:ammo',
6 | },
7 | READY: 'physics:ready',
8 | INIT: 'physics:init',
9 | UPDATE: 'physics:update',
10 |
11 | ADD: {
12 | BOX: 'physics:add:box',
13 | VEHICLE: 'physics:add:vehicle',
14 | MODEL: 'physics:add:model',
15 | PLAYER: 'physics:add:player',
16 | SPHERE: 'physics:add:sphere',
17 | },
18 |
19 | ELEMENT: {
20 | DISPOSE: 'physics:element:dispose',
21 | COLLISION: 'physics:element:collision',
22 | UPDATE: 'physics:element:update',
23 | CREATED: 'physics:element:created',
24 |
25 | SET: {
26 | POSITION: 'physics:element:set:position',
27 | QUATERNION: 'physics:element:set:quaternion',
28 | LINEAR_VELOCITY: 'physics:element:set:linear_velocity'
29 | },
30 |
31 | RESET: 'physics:element:reset',
32 |
33 | APPLY: {
34 | IMPULSE: 'physics:element:apply:impulse'
35 | }
36 | },
37 |
38 | VEHICLE: {
39 | SET: {
40 | POSITION: 'physics:vehicle:set:position',
41 | QUATERNION: 'physics:vehicle:set:quaternion'
42 | },
43 | RESET: 'physics:vehicle:reset',
44 |
45 | SPEED: 'physics:vehicle:speed',
46 | DIRECTION: 'physics:vehicle:direction'
47 | },
48 |
49 | EFFECTS: {
50 | EXPLOSION: 'physics:effects:explosion'
51 | }
52 | };
--------------------------------------------------------------------------------
/src/physics/worker/effects.js:
--------------------------------------------------------------------------------
1 | import { EXPLOSION_SIZES, EXPLOSION_STRENGTHS } from "../constants";
2 | import world from "./world";
3 |
4 | export const createGhostCollider = (radius, position) => {
5 | const ghostCollider = new Ammo.btGhostObject();
6 | const transform = new Ammo.btTransform();
7 |
8 | ghostCollider.setCollisionShape(new Ammo.btSphereShape(radius));
9 | ghostCollider.getWorldTransform(transform);
10 |
11 | transform.setIdentity();
12 | transform.setOrigin(position);
13 | transform.setRotation(new Ammo.btQuaternion(0, 0, 0, 1));
14 |
15 | ghostCollider.setWorldTransform(transform);
16 |
17 | return { ghostCollider, transform };
18 | };
19 |
20 | export const forEachGhostCollision = (ghostCollider, forEachCallback = () => {}) => {
21 | const collisions = ghostCollider.getNumOverlappingObjects();
22 | for (let i=0; i {
34 | let explosionPosition = position;
35 | if (!explosionPosition) {
36 | const { body } = world.getElement(uuid);
37 | const motionState = body.getMotionState();
38 | const transform = new Ammo.btTransform();
39 |
40 | motionState.getWorldTransform(transform);
41 | explosionPosition = transform.getOrigin();
42 | }
43 |
44 | return explosionPosition;
45 | };
46 |
47 | export const getExplosionImpulse = (position, explosionPosition, strength) => {
48 | const distance = position.op_sub(explosionPosition);
49 | distance.normalize();
50 | const impulse = distance.op_mul(strength);
51 |
52 | impulse.setY(impulse.y() + strength);
53 |
54 | return impulse;
55 | }
56 |
57 | export const createExplosion = ({
58 | uuid,
59 | position,
60 | radius = EXPLOSION_SIZES.SMALL,
61 | strength = EXPLOSION_STRENGTHS.MEDIUM
62 | }) => {
63 | try {
64 | const explosionPosition = getExplosionPosition(uuid, position);
65 | const { ghostCollider, transform } = createGhostCollider(radius, explosionPosition);
66 |
67 | world.addCollisionObject(ghostCollider);
68 |
69 | forEachGhostCollision(ghostCollider, (object, objectTransform) => {
70 | const origin = objectTransform.getOrigin();
71 | object.activate(true);
72 | object.applyCentralImpulse(getExplosionImpulse(origin, explosionPosition, strength));
73 | });
74 |
75 | world.getDynamicsWorld().removeCollisionObject(ghostCollider);
76 | Ammo.destroy(ghostCollider);
77 | Ammo.destroy(transform);
78 | } catch(e) {
79 | console.log(e);
80 | }
81 | }
--------------------------------------------------------------------------------
/src/physics/worker/lib/dispatcher.js:
--------------------------------------------------------------------------------
1 | import { PHYSICS_EVENTS } from '../../messages';
2 |
3 | export class Dispatcher {
4 |
5 | sendPhysicsUpdate = dt => postMessage({ event: PHYSICS_EVENTS.UPDATE, dt })
6 | sendReadyEvent = () => postMessage({ event: PHYSICS_EVENTS.READY });
7 | sendTerminateEvent = () => postMessage({ event: PHYSICS_EVENTS.TERMINATE });
8 |
9 | sendBodyUpdate = (uuid, position, rotation, dt, extraData) => postMessage({
10 | event: PHYSICS_EVENTS.ELEMENT.UPDATE,
11 | uuid,
12 | position: { x: position.x(), y: position.y(), z: position.z() },
13 | quaternion: { x: rotation.x(), y: rotation.y(), z: rotation.z(), w: rotation.w() },
14 | ...extraData,
15 | dt
16 | });
17 |
18 | sendDispatchEvent = (uuid, eventName, eventData) => postMessage({
19 | event: PHYSICS_EVENTS.DISPATCH,
20 | uuid,
21 | eventName,
22 | eventData
23 | });
24 | };
25 |
26 | export default new Dispatcher();
--------------------------------------------------------------------------------
/src/physics/worker/lib/math.js:
--------------------------------------------------------------------------------
1 | export const applyMatrix4ToVector3 = ({ x = 0, y = 0, z = 0 }, matrix = []) => {
2 | const w = 1 / ( matrix[ 3 ] * x + matrix[ 7 ] * y + matrix[ 11 ] * z + matrix[ 15 ] );
3 |
4 | return {
5 | x: ( matrix[ 0 ] * x + matrix[ 4 ] * y + matrix[ 8 ] * z + matrix[ 12 ] ) * w,
6 | y: ( matrix[ 1 ] * x + matrix[ 5 ] * y + matrix[ 9 ] * z + matrix[ 13 ] ) * w,
7 | z: ( matrix[ 2 ] * x + matrix[ 6 ] * y + matrix[ 10 ] * z + matrix[ 14 ] ) * w,
8 | }
9 | };
--------------------------------------------------------------------------------
/src/physics/worker/player.js:
--------------------------------------------------------------------------------
1 | import { createRigidBody } from './elements';
2 | import world from './world';
3 |
4 | import {
5 | TYPES,
6 | DEFAULT_RIGIDBODY_STATE
7 | } from '../constants';
8 |
9 | export const addPlayer = (data) => {
10 | const { uuid, width, height, position, quaternion, mass, friction } = data;
11 |
12 | const capsule = new Ammo.btCapsuleShape(width, height);
13 | const body = createRigidBody(capsule, { uuid, position, quaternion, mass, friction });
14 |
15 | // disabliing rotation for collisions
16 | body.setAngularFactor(0);
17 |
18 | world.addRigidBody(body);
19 | world.addElement({ uuid, body, type: TYPES.PLAYER, state: DEFAULT_RIGIDBODY_STATE });
20 | };
21 |
22 | export const handlePlayerUpdate = ({ body, uuid, state = DEFAULT_RIGIDBODY_STATE }, dt) => {
23 | const { movement, direction, cameraDirection, quaternion, position } = state;
24 |
25 | const MAX_SPEED = 1;
26 | const walkVelocity = .1;
27 |
28 | const motionState = body.getMotionState();
29 |
30 | if (motionState) {
31 | const transform = new Ammo.btTransform();
32 | motionState.getWorldTransform(transform);
33 |
34 | const linearVelocity = body.getLinearVelocity();
35 | const speed = linearVelocity.length();
36 |
37 | const forwardDir = transform.getBasis().getRow(2);
38 | forwardDir.normalize();
39 | const walkDirection = new Ammo.btVector3(0.0, 0.0, 0.0);
40 |
41 | const walkSpeed = walkVelocity * dt;
42 |
43 | if (movement.forward) {
44 | walkDirection.setX( walkDirection.x() + forwardDir.x());
45 | //walkDirection.setY( walkDirection.y() + forwardDir.y());
46 | walkDirection.setZ( walkDirection.z() + forwardDir.z());
47 | }
48 |
49 | if (movement.backwards) {
50 | walkDirection.setX( walkDirection.x() - forwardDir.x());
51 | //walkDirection.setY( walkDirection.y() - forwardDir.y());
52 | walkDirection.setZ( walkDirection.z() - forwardDir.z());
53 | }
54 |
55 | if (!movement.forward && !movement.backwards) {
56 | linearVelocity.setX(linearVelocity.x() * 0.2);
57 | linearVelocity.setZ(linearVelocity.z() * 0.2);
58 | } else if (speed < MAX_SPEED) {
59 | linearVelocity.setX(linearVelocity.x() + cameraDirection.x * walkSpeed);
60 | linearVelocity.setZ(linearVelocity.z() + cameraDirection.z * walkSpeed);
61 | }
62 |
63 | body.setLinearVelocity(linearVelocity);
64 |
65 | body.getMotionState().setWorldTransform(transform);
66 | body.setCenterOfMassTransform(transform);
67 |
68 | let origin = transform.getOrigin();
69 | let rotation = transform.getRotation();
70 |
71 | dispatcher.sendBodyUpdate(uuid, origin, rotation, dt);
72 | }
73 | };
--------------------------------------------------------------------------------
/src/scripts/BaseScript.js:
--------------------------------------------------------------------------------
1 | export default class BaseScript {
2 | __check() {
3 | return true;
4 | }
5 |
6 | __isStatic() {
7 | return false;
8 | }
9 |
10 | __hasStarted() {
11 | return this.hasStarted;
12 | }
13 |
14 | __isDisposed() {
15 | return this.isDisposed;
16 | }
17 |
18 | __setStartedFlag(flag) {
19 | this.hasStarted = flag;
20 | }
21 |
22 | __setDisposedFlag(flag) {
23 | this.isDisposed = flag;
24 | }
25 |
26 | constructor(name) {
27 | this.__name = name || this.constructor.name;
28 | this.hasStarted = false;
29 | this.isDisposed = false;
30 | this.options = {};
31 | }
32 |
33 | getName() {
34 | return this.__name;
35 | }
36 |
37 | start(element, options) {}
38 |
39 | update(dt) {}
40 |
41 | physicsUpdate(dt) {}
42 |
43 | onDispose() {}
44 |
45 | toJSON(parseJSON = false) {
46 | return {
47 | name: this.name(),
48 | };
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/scripts/Scripts.js:
--------------------------------------------------------------------------------
1 | import BaseScript from './BaseScript';
2 | import Input from '../core/input/Input';
3 | import { fetch } from 'whatwg-fetch';
4 | import BaseCar from './builtin/BaseCar';
5 | import SmoothCarFollow from './builtin/SmoothCarFollow';
6 | import { DEPRECATIONS } from '../lib/messages';
7 |
8 | export const BUILTIN = {
9 | BASECAR: 'BaseCar',
10 | TRAILS: 'Trails',
11 | SMOOTH_CAR_FOLLOW: 'SmoothCarFollow'
12 | };
13 | export class Scripts {
14 |
15 | constructor() {
16 | this.map = {
17 | [BUILTIN.BASECAR] : BaseCar,
18 | [BUILTIN.SMOOTH_CAR_FOLLOW]: SmoothCarFollow
19 | };
20 | }
21 |
22 | get BUILTIN() {
23 | return BUILTIN;
24 | }
25 |
26 | update() {}
27 |
28 | load = (scripts, level) => {
29 | this.scripts = scripts;
30 | const keys = Object.keys(scripts);
31 |
32 | if (!keys.length) {
33 | return Promise.resolve('scripts');
34 | }
35 |
36 | return Promise.all(keys.map(name => this.loadSingleScript(name, level)));
37 | }
38 |
39 | loadSingleScript = (name, level) => {
40 | const path = this.scripts[name];
41 |
42 | return new Promise(resolve => {
43 |
44 | fetch(path)
45 | .then(response => response.text())
46 | .then((text) => {
47 | this.createFromString(text);
48 | resolve();
49 | });
50 | });
51 | }
52 |
53 | set(id, ScriptClass) {
54 | this.map[id] = ScriptClass;
55 | }
56 |
57 | get(name) {
58 | const ScriptClass = this.map[name];
59 |
60 | if (ScriptClass) {
61 | return new ScriptClass(name);
62 | }
63 |
64 | return false;
65 | }
66 |
67 | parseScript(content) {
68 | // does this mean we can send whatever we want down to the script?
69 | return new Function('Script', 'Input', 'return ' + content + ';')(BaseScript, Input);
70 | }
71 |
72 | createFromString(stringContent) {
73 | const Script = this.parseScript(stringContent);
74 | const s = new Script();
75 |
76 | this.set(s.name(), s);
77 | return s;
78 | }
79 |
80 | create(name, ScriptClass) {
81 | console.warn(DEPRECATIONS.SCRIPTS_CREATE);
82 | this.register(name, ScriptClass);
83 | }
84 |
85 | register(name, ScriptClass) {
86 | if (ScriptClass) {
87 | const script = new ScriptClass();
88 | if (script.__check && script.__check()) {
89 | this.set(name, ScriptClass);
90 | } else {
91 | console.error('[Mage] Script:', name, 'needs to be an instance of Script.');
92 | }
93 | } else {
94 | console.error('[Mage] Script not provided.');
95 | }
96 | }
97 | }
98 |
99 | export default new Scripts();
100 |
--------------------------------------------------------------------------------
/src/scripts/StaticScript.js:
--------------------------------------------------------------------------------
1 | export default class StaticScript {
2 | __isStatic() {
3 | return true;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/scripts/builtin/BaseCar.js:
--------------------------------------------------------------------------------
1 | import Input from "../../core/input/Input";
2 | import BaseScript from "../BaseScript";
3 | import Physics from "../../physics";
4 |
5 | export default class BaseCar extends BaseScript {
6 | constructor() {
7 | super("BaseCar");
8 | }
9 |
10 | start(element, options) {
11 | const {
12 | wheels,
13 | accelerationKey = "w",
14 | brakingKey = "s",
15 | rightKey = "d",
16 | leftKey = "a",
17 | debug = false,
18 | autostart = true,
19 | ...physicsOptions
20 | } = options;
21 |
22 | this.car = element;
23 | this.wheels = wheels;
24 |
25 | this.state = {
26 | acceleration: false,
27 | braking: false,
28 | right: false,
29 | left: false,
30 | };
31 |
32 | this.accelerationKey = accelerationKey;
33 | this.brakingKey = brakingKey;
34 | this.rightKey = rightKey;
35 | this.leftKey = leftKey;
36 |
37 | this.engineStarted = autostart;
38 |
39 | Input.enable();
40 | Input.keyboard.listenTo([accelerationKey, brakingKey, rightKey, leftKey]);
41 | Physics.addVehicle(this.car, { wheels: wheels.map(w => w.uuid()), ...physicsOptions });
42 | }
43 |
44 | startEngine() {
45 | this.engineStarted = true;
46 | }
47 |
48 | stopEngine() {
49 | this.engineStarted = false;
50 | }
51 |
52 | handleInput() {
53 | this.state.acceleration = Input.keyboard.isPressed(this.accelerationKey);
54 | this.state.braking = Input.keyboard.isPressed(this.brakingKey);
55 | this.state.right = Input.keyboard.isPressed(this.rightKey);
56 | this.state.left = Input.keyboard.isPressed(this.leftKey);
57 | }
58 |
59 | sendCarUpdate() {
60 | Physics.updateBodyState(this.car, this.state);
61 | }
62 |
63 | update(dt) {
64 | if (this.engineStarted) {
65 | this.handleInput();
66 | this.sendCarUpdate();
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/scripts/builtin/SmoothCameraFollow.js:
--------------------------------------------------------------------------------
1 | import BaseScript from '../BaseScript';
2 |
3 | export default class SmoothCameraFollow extends BaseScript {
4 |
5 | constructor() {
6 | super('SmoothCameraFollow');
7 | }
8 |
9 | start(camera, options) {
10 | const {
11 | target,
12 | distance = 20.0,
13 | height = 5.0,
14 | lookAtHeight = 1.0,
15 | heightDamping = 2.0,
16 | rotationSnapTime = 0.3,
17 | distanceSnapTime = 0.1
18 | } = options;
19 |
20 | this.height = height;
21 | this.distance = distance;
22 | this.heightDamping = heightDamping;
23 | this.rotationSnapTime = rotationSnapTime;
24 | this.distanceSnapTime = distanceSnapTime;
25 |
26 | this.camera = camera;
27 | this.target = target;
28 |
29 | this.yVelocity = 0.0;
30 | this.zVelocity = 0.0;
31 | this.usedDistance = 0.0;
32 |
33 | this.lookAtVector = { x: 0, y: lookAtHeight, z: 0 };
34 | }
35 |
36 | physicsUpdate(dt) {
37 |
38 | const wantedHeight = this.target.getPosition().y + this.height;
39 | let currentHeight = this.camera.getPosition().y;
40 |
41 | const wantedRotationAngle = this.target.getRotation().y;
42 | const currentRotationAngle = this.camera.getRotation().y;
43 |
44 | const currentRotationAngle = Mathf.SmoothDampAngle(currentRotationAngle, wantedRotationAngle, this.yVelocity, this.rotationSnapTime);
45 |
46 | currentHeight = Mathf.Lerp(currentHeight, wantedHeight, this.heightDamping * dt);
47 |
48 | const wantedPosition = this.target.getPosition();
49 | wantedPosition.y = currentHeight;
50 |
51 |
52 | this.usedDistance = Mathf.SmoothDampAngle(this.usedDistance, this.distance, this.zVelocity, this.distanceSnapTime);
53 |
54 | wantedPosition += Quaternion.Euler(0, currentRotationAngle, 0) * new Vector3(0, 0, -usedDistance);
55 |
56 | this.camera.setPosition(wantedPosition);
57 | this.camera.lookAt(target.position + lookAtVector);
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/src/scripts/builtin/SmoothCarFollow.js:
--------------------------------------------------------------------------------
1 | import BaseScript from '../BaseScript';
2 | import { Vector3 } from 'three';
3 |
4 | const DEFAULT_DISTANCE = 5.0;
5 | const DEFAULT_HEIGHT = 3.0;
6 | const DEFAULT_HEIGHT_DAMPING = 2.0;
7 | const DEFAULT_LOOK_AT_HEIGHT = 1;
8 | const DEFAULT_ROTATION_SNAP_TIME = 0.3;
9 | const DEFAULT_DISTANCE_SNAP_TIME = 0.5;
10 | const DEFAULT_DISTANCE_MULTIPLIER = 1;
11 |
12 | export default class SmoothCarFollow extends BaseScript {
13 |
14 | constructor() {
15 | super('SmoothCarFollow');
16 | }
17 |
18 | start(camera, options) {
19 | const {
20 | target,
21 | height = DEFAULT_HEIGHT,
22 | heightDamping = DEFAULT_HEIGHT_DAMPING,
23 | lookAtHeight = DEFAULT_LOOK_AT_HEIGHT,
24 | distance = DEFAULT_DISTANCE,
25 | rotationSnapTime = DEFAULT_ROTATION_SNAP_TIME,
26 | distanceSnapTime = DEFAULT_DISTANCE_SNAP_TIME,
27 | distanceMultiplier = DEFAULT_DISTANCE_MULTIPLIER,
28 | lerpFactor
29 | } = options;
30 |
31 | this.camera = camera;
32 | this.target = target;
33 |
34 | this.height = height;
35 | this.heightDamping = heightDamping;
36 |
37 | this.distance = distance;
38 |
39 | this.rotationSnapTime = rotationSnapTime;
40 | this.distanceSnapTime = distanceSnapTime;
41 |
42 | this.distanceMultiplier = distanceMultiplier;
43 | this.lerpFactor = lerpFactor;
44 |
45 | this.lookAtVector = new Vector3(0, lookAtHeight, 0);
46 | }
47 |
48 | followCar(dt) {
49 | const direction = this.target.getPhysicsState('direction');
50 | if (direction) {
51 | const { x, y, z } = direction;
52 | const cameraPosition = this.camera.getPosition();
53 | const targetPosition = this.target.getPosition();
54 | const vector = new Vector3(x, y, z)
55 | .negate()
56 | .normalize()
57 | .multiplyScalar(this.distance);
58 |
59 | vector.y = y + this.height;
60 | const desiredPosition = targetPosition.add(vector);
61 | const lerpFactor = this.lerpFactor || 1 - Math.pow(0.1, dt);
62 |
63 | cameraPosition.lerpVectors(cameraPosition, desiredPosition, lerpFactor);
64 |
65 | this.camera.setPosition(cameraPosition);
66 |
67 | const lookAtTarget = new Vector3();
68 | lookAtTarget.copy(this.target.getPosition().add(this.lookAtVector));
69 |
70 | this.camera.lookAt(lookAtTarget);
71 | }
72 | }
73 |
74 | update(dt) {
75 | this.followCar(dt);
76 | }
77 |
78 | }
--------------------------------------------------------------------------------
/src/scripts/builtin/Trails.js:
--------------------------------------------------------------------------------
1 | import Particles, { PARTICLES } from "../../fx/particles/Particles";
2 |
3 | import BaseScript from "../BaseScript"
4 |
5 | export default class Trails extends BaseScript {
6 |
7 | constructor() {
8 | super('Trails');
9 | }
10 |
11 | start(element, { texture = false, size }) {
12 | this.element = element;
13 | this.emitter = Particles.add(PARTICLES.TRAIL, { texture, size });
14 |
15 | this.emitter.start(Infinity);
16 | }
17 |
18 | stop() {
19 | this.emitter.dispose();
20 | }
21 |
22 | update() {
23 | this.emitter.setPosition(this.element.getPosition());
24 | }
25 | }
--------------------------------------------------------------------------------
/src/store/Store.js:
--------------------------------------------------------------------------------
1 | // creates the store
2 | import * as redux from 'redux';
3 | import thunk from 'redux-thunk';
4 |
5 | import createRootReducer, * as DEFAULT_REDUCERS from './reducers';
6 |
7 | import { STORE_DOESNT_EXIST } from '../lib/messages';
8 | import { NOOP } from '../lib/functions';
9 |
10 | let store,
11 | latestAction;
12 |
13 | let unsubscribe = NOOP;
14 | let subscribers = [];
15 |
16 | const applyMiddlewares = (mdws, debug) => {
17 | if (debug) {
18 | return redux.compose(
19 | redux.applyMiddleware(...mdws),
20 | window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f
21 | );
22 | }
23 | return redux.applyMiddleware(...mdws);
24 | }
25 |
26 | const defaultMiddleware = () => [thunk];
27 |
28 | const combineReducers = (reducers = {}) => (
29 | redux.combineReducers({
30 | ...reducers,
31 | ...DEFAULT_REDUCERS
32 | })
33 | );
34 |
35 | export const getState = () => {
36 | if (store) {
37 | return store.getState();
38 | } else {
39 | console.log(STORE_DOESNT_EXIST);
40 | }
41 | };
42 |
43 | export const getStore = () => store;
44 |
45 | const handleSubscriptions = (...args) => (
46 | subscribers
47 | .forEach((subscriber) => {
48 | if (subscriber.onStateChange) {
49 | subscriber.onStateChange(getState(), latestAction);
50 | }
51 | })
52 | )
53 |
54 | export const createStore = (reducers, initialState = {}, debug = false) => {
55 | const storeReducers = combineReducers(reducers);
56 |
57 | if (!store) {
58 | store = redux.createStore(
59 | createRootReducer(storeReducers),
60 | initialState,
61 | applyMiddlewares(defaultMiddleware(), debug)
62 | );
63 | unsubscribe = store.subscribe(handleSubscriptions);
64 | }
65 | };
66 |
67 | export const subscribe = subscriber => subscribers.push(subscriber);
68 | export const unsubscribeAll = () => {
69 | unsubscribe();
70 | subscribers = [];
71 | }
72 |
73 | export const dispatch = (action) => {
74 | if (store && action) {
75 | store.dispatch(action);
76 | latestAction = action;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/store/actions/input.js:
--------------------------------------------------------------------------------
1 | import {
2 | INPUT_ENABLED,
3 | KEYBOARD_ENABLED,
4 | MOUSE_ENABLED,
5 | GAMEPAD_ENABLED,
6 | INPUT_DISABLED,
7 | KEYBOARD_DISABLED,
8 | MOUSE_DISABLED,
9 | GAMEPAD_DISABLED,
10 | GAMEPAD_CONNECTED,
11 | GAMEPAD_DISCONNECTED
12 | } from "./types";
13 |
14 | export const inputEnabled = () => ({
15 | type: INPUT_ENABLED
16 | });
17 |
18 | export const keyboardEnabled = () => ({
19 | type: KEYBOARD_ENABLED
20 | });
21 |
22 | export const mouseEnabled = () => ({
23 | type: MOUSE_ENABLED
24 | });
25 |
26 | export const gamepadEnabled = () => ({
27 | type: GAMEPAD_ENABLED
28 | });
29 |
30 | export const inputDisabled = () => ({
31 | type: INPUT_DISABLED
32 | });
33 |
34 | export const keyboardDisabled = () => ({
35 | type: KEYBOARD_DISABLED
36 | });
37 |
38 | export const mouseDisabled = () => ({
39 | type: MOUSE_DISABLED
40 | });
41 |
42 | export const gamepadDisabled = () => ({
43 | type: GAMEPAD_DISABLED
44 | });
45 |
46 | export const gamepadConnected = (gamepads) => ({
47 | type: GAMEPAD_CONNECTED,
48 | gamepads
49 | });
50 |
51 | export const gamepadDisconnected = (gamepads) => ({
52 | type: GAMEPAD_DISCONNECTED,
53 | gamepads
54 | });
55 |
--------------------------------------------------------------------------------
/src/store/actions/location.js:
--------------------------------------------------------------------------------
1 | import { LOCATION_PATH_CHANGE } from "./types";
2 |
3 | export const locationPathChange = path => ({
4 | type: LOCATION_PATH_CHANGE,
5 | path
6 | });
--------------------------------------------------------------------------------
/src/store/actions/network.js:
--------------------------------------------------------------------------------
1 | import {
2 | NETWORK_CHANGED
3 | } from './types';
4 |
5 | export const networkChanged = isOnline => ({
6 | type: NETWORK_CHANGED,
7 | isOnline
8 | });
--------------------------------------------------------------------------------
/src/store/actions/storage.js:
--------------------------------------------------------------------------------
1 | import {
2 | SAVE_STARTED,
3 | SAVE_COMPLETED,
4 | SAVE_ERROR,
5 | LOAD_STARTED,
6 | LOAD_COMPLETED,
7 | LOAD_ERROR,
8 | RESET
9 | } from './types';
10 |
11 | import storage from '../../storage/storage';
12 |
13 | export const saveStarted = () => ({
14 | type: SAVE_STARTED
15 | });
16 |
17 | export const saveCompleted = (sceneName, currentPath, timestamp) => ({
18 | type: SAVE_COMPLETED,
19 | timestamp,
20 | sceneName,
21 | currentPath
22 | });
23 |
24 | export const saveError = (errorDetails) => ({
25 | type: SAVE_ERROR,
26 | errorDetails
27 | });
28 |
29 | export const loadStarted = () => ({
30 | type: LOAD_STARTED
31 | });
32 |
33 | export const loadCompleted = () => ({
34 | type: LOAD_COMPLETED
35 | });
36 |
37 | export const loadError = (errorDetails) => ({
38 | type: LOAD_ERROR,
39 | errorDetails
40 | });
41 |
42 | export const resetState = (state) => ({
43 | type: RESET,
44 | state
45 | });
46 |
47 | export const saveGame = () => (dispatch) => {
48 | dispatch(saveStarted());
49 |
50 | storage
51 | .save()
52 | .then(({ sceneName, currentPath, timestamp }) =>
53 | dispatch(saveCompleted(sceneName, currentPath, timestamp))
54 | )
55 | .catch((e) => dispatch(saveError(e)));
56 | };
57 |
58 | export const loadGame = () => (dispatch) => {
59 | dispatch(loadStarted());
60 |
61 | storage
62 | .loadState()
63 | .then(state => dispatch(resetState(state)))
64 | .catch((e) => dispatch(loadError(e)));
65 |
66 | storage
67 | .loadCurrentPath()
68 | .then(currentPath => Router.goTo(currentPath, { loading: true }))
69 | .then(() => dispatch(loadCompleted()))
70 | .catch((e) => dispatch(loadError(e)));
71 | }
72 |
--------------------------------------------------------------------------------
/src/store/actions/types.js:
--------------------------------------------------------------------------------
1 | export const SAVE_STARTED = 'SAVE_STARTED';
2 | export const SAVE_COMPLETED = 'SAVE_COMPLETED';
3 | export const SAVE_ERROR = 'SAVE_ERROR';
4 |
5 | export const LOAD_STARTED = 'LOAD_STARTED';
6 | export const LOAD_COMPLETED= 'LOAD_COMPLETED';
7 | export const LOAD_ERROR = 'LOAD_ERROR';
8 |
9 | export const KEYBOARD_ENABLED = 'KEYBOARD_ENABLED';
10 | export const MOUSE_ENABLED = 'MOUSE_ENABLED';
11 | export const GAMEPAD_ENABLED = 'GAMEPAD_ENABLED';
12 | export const INPUT_ENABLED = 'INPUT_ENABLED';
13 |
14 | export const KEYBOARD_DISABLED = 'KEYBOARD_DISABLED';
15 | export const MOUSE_DISABLED = 'MOUSE_DISABLED';
16 | export const GAMEPAD_DISABLED = 'GAMEPAD_DISABLED';
17 | export const INPUT_DISABLED = 'INPUT_DISABLED';
18 |
19 | export const GAMEPAD_CONNECTED = 'GAMEPAD_CONNECTED';
20 | export const GAMEPAD_DISCONNECTED = 'GAMEPAD_DISCONNECTED';
21 |
22 | export const NETWORK_CHANGED = 'NETWORK_CHANGED';
23 |
24 | export const UI_LOADING_SCREEN = 'UI_LOADING_SCREEN';
25 |
26 | export const LOCATION_PATH_CHANGE = 'LOCATION_PATH_CHANGE';
27 |
28 | export const RESET = 'RESET';
29 |
--------------------------------------------------------------------------------
/src/store/actions/ui.js:
--------------------------------------------------------------------------------
1 | import {
2 | UI_LOADING_SCREEN
3 | } from './types';
4 |
5 | export const showLoadingScreen = () => ({
6 | type: UI_LOADING_SCREEN,
7 | loadingScreenVisible: true
8 | });
9 |
10 | export const hideLoadingScreen = () => ({
11 | type: UI_LOADING_SCREEN,
12 | loadingScreenVisible: false
13 | });
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | getState,
3 | createStore,
4 | dispatch
5 | } from './Store';
6 |
7 | export {
8 | getState,
9 | createStore,
10 | dispatch
11 | };
12 |
--------------------------------------------------------------------------------
/src/store/reducers/index.js:
--------------------------------------------------------------------------------
1 | import info from './info';
2 | import storage from './storage';
3 | import input from './input';
4 | import network from './network';
5 | import ui from './ui';
6 | import location from './location';
7 | import { createRootReducer } from './root';
8 |
9 | export {
10 | info,
11 | input,
12 | storage,
13 | network,
14 | ui,
15 | location,
16 | };
17 |
18 | export default createRootReducer;
19 |
--------------------------------------------------------------------------------
/src/store/reducers/info.js:
--------------------------------------------------------------------------------
1 | import packageJSON from '../../../package.json';
2 |
3 | const DEFAULT_STATE = {
4 | mage: packageJSON.version
5 | }
6 |
7 | export default (state = DEFAULT_STATE, action = {}) => {
8 | return state;
9 | };
10 |
--------------------------------------------------------------------------------
/src/store/reducers/input.js:
--------------------------------------------------------------------------------
1 | import {
2 | INPUT_ENABLED,
3 | KEYBOARD_ENABLED,
4 | MOUSE_ENABLED,
5 | GAMEPAD_ENABLED,
6 | INPUT_DISABLED,
7 | KEYBOARD_DISABLED,
8 | MOUSE_DISABLED,
9 | GAMEPAD_DISABLED,
10 | GAMEPAD_CONNECTED,
11 | GAMEPAD_DISCONNECTED
12 | } from "../actions/types";
13 |
14 | const DEFAULT_STATE = {
15 | keyboard: false,
16 | mouse: false,
17 | gamepad: false,
18 | gamepads: {}
19 | };
20 |
21 | export default (state = DEFAULT_STATE, action = {}) => {
22 | switch(action.type) {
23 | case INPUT_ENABLED:
24 | return {
25 | ...state,
26 | keyboard: true,
27 | mouse: true,
28 | gamepad: true
29 | };
30 | case KEYBOARD_ENABLED:
31 | return {
32 | ...state,
33 | keyboard: true
34 | };
35 | case MOUSE_ENABLED:
36 | return {
37 | ...state,
38 | mouse: true
39 | };
40 | case GAMEPAD_ENABLED:
41 | return {
42 | ...state,
43 | gamepad: true
44 | };
45 |
46 | case INPUT_DISABLED:
47 | return {
48 | ...state,
49 | keyboard: false,
50 | mouse: false,
51 | gamepad: false
52 | };
53 | case KEYBOARD_DISABLED:
54 | return {
55 | ...state,
56 | keyboard: false
57 | };
58 | case MOUSE_DISABLED:
59 | return {
60 | ...state,
61 | mouse: false
62 | };
63 | case GAMEPAD_DISABLED:
64 | return {
65 | ...state,
66 | gamepad: false
67 | };
68 |
69 | case GAMEPAD_CONNECTED:
70 | case GAMEPAD_DISCONNECTED:
71 | return {
72 | ...state,
73 | gamepads: action.gamepads
74 | };
75 | default:
76 | return state;
77 | }
78 | };
79 |
--------------------------------------------------------------------------------
/src/store/reducers/location.js:
--------------------------------------------------------------------------------
1 | import { LOCATION_PATH_CHANGE } from "../actions/types";
2 |
3 | const DEFAULT_STATE = {
4 | path: '/'
5 | };
6 |
7 | export default (state = DEFAULT_STATE, action = {}) => {
8 | switch(action.type) {
9 | case LOCATION_PATH_CHANGE:
10 | return {
11 | ...state,
12 | path: action.path
13 | };
14 | default:
15 | return state;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/store/reducers/network.js:
--------------------------------------------------------------------------------
1 | import {
2 | NETWORK_CHANGED
3 | } from '../actions/types';
4 |
5 | const DEFAULT_STATE = {
6 | isOnline: true
7 | };
8 |
9 | export default (state = DEFAULT_STATE, action) => {
10 | switch(action.type) {
11 | case NETWORK_CHANGED:
12 | return {
13 | ...state,
14 | isOnline: action.isOnline
15 | }
16 | default:
17 | return state;
18 | }
19 | };
--------------------------------------------------------------------------------
/src/store/reducers/root.js:
--------------------------------------------------------------------------------
1 | import {
2 | RESET
3 | } from '../actions/types';
4 |
5 | export const createRootReducer = (combinedReducer) => (state, action) => {
6 | switch (action.type) {
7 | case RESET:
8 | if (Object.keys(action.state).length > 0) {
9 | return {
10 | ...state,
11 | ...action.state
12 | };
13 | } else {
14 | return state;
15 | }
16 | default:
17 | return combinedReducer(state, action);
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/src/store/reducers/storage.js:
--------------------------------------------------------------------------------
1 | import {
2 | SAVE_STARTED,
3 | SAVE_ERROR,
4 | SAVE_COMPLETED,
5 | LOAD_STARTED,
6 | LOAD_COMPLETED,
7 | LOAD_ERROR
8 | } from '../actions/types';
9 |
10 | const DEFAULT_STATE = {
11 | error: false,
12 | saving: false,
13 | loading: false,
14 | errorDetails: '',
15 | sceneName: '',
16 | currentPath: '/',
17 | timestamp: ''
18 | }
19 |
20 | export default (state = DEFAULT_STATE, action = {}) => {
21 | switch(action.type) {
22 | case SAVE_STARTED:
23 | return {
24 | ...state,
25 | saving: true
26 | };
27 | case SAVE_COMPLETED:
28 | return {
29 | ...state,
30 | saving: false,
31 | error: false,
32 | sceneName: action.currentScene,
33 | currentPath: action.currentPath,
34 | timestamp: action.timestamp
35 | }
36 | case SAVE_ERROR:
37 | return {
38 | ...state,
39 | saving: false,
40 | error: true,
41 | errorDetails: action.errorDetails
42 | };
43 | case LOAD_STARTED:
44 | return {
45 | ...state,
46 | loading: true
47 | };
48 | case LOAD_COMPLETED:
49 | return {
50 | ...state,
51 | loading: false,
52 | error: false
53 | };
54 | case LOAD_ERROR:
55 | return {
56 | ...state,
57 | loading: false,
58 | error: true,
59 | errorDetails: action.errorDetails
60 | }
61 | default:
62 | return state;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/store/reducers/ui.js:
--------------------------------------------------------------------------------
1 | import {
2 | UI_LOADING_SCREEN
3 | } from '../actions/types';
4 |
5 | export const DEFAULT_STATE = {
6 | loadingScreenVisible: false
7 | };
8 |
9 | export default (state = DEFAULT_STATE, action = {}) => {
10 | switch(action.type) {
11 | case UI_LOADING_SCREEN:
12 | return {
13 | ...state,
14 | loadingScreenVisible: action.loadingScreenVisible
15 | };
16 | default:
17 | return state;
18 | }
19 | };
--------------------------------------------------------------------------------
/src/ui/BaseUI.js:
--------------------------------------------------------------------------------
1 | const BaseUI = () => null;
2 |
3 | export default BaseUI;
4 |
--------------------------------------------------------------------------------
/src/ui/LabelComponent.js:
--------------------------------------------------------------------------------
1 | import { Component, createRef } from "inferno";
2 |
3 | export default class LabelComponent extends Component {
4 |
5 | constructor(props) {
6 | super(props);
7 | this.element = createRef();
8 | }
9 |
10 | componentDidUpdate() {
11 | this.props.onUpdate(this.element.current);
12 | }
13 |
14 | componentDidMount() {
15 | this.props.onMount(this.element.current);
16 | }
17 |
18 | componentWillUnmount() {
19 | this.props.onUnmount(this.element.current);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/ui/index.js:
--------------------------------------------------------------------------------
1 | import { render } from 'inferno';
2 | import { createElement } from 'inferno-create-element';
3 | import { Provider } from 'inferno-redux';
4 | import { getStore } from '../store/Store';
5 | import BaseUI from './BaseUI';
6 | import Config from '../core/config';
7 | import Router from '../router/Router';
8 | import { dispatch } from '../store';
9 | import { createElementFromSelector, removeElement } from '../lib/dom';
10 | import { showLoadingScreen, hideLoadingScreen } from '../store/actions/ui';
11 | import { locationPathChange } from '../store/actions/location';
12 |
13 | const ROOT_ID = '#ui';
14 | export const LABELS_ROOT_ID = '#labels_ui';
15 |
16 | const createProps = () => ({
17 | level: Router.getCurrentLevel()
18 | });
19 |
20 | export const getUIContainer = (id = ROOT_ID) => {
21 | let rootElement = document.querySelector(id);
22 |
23 | if (!rootElement) {
24 | rootElement = createElementFromSelector(id);
25 | document.body.appendChild(rootElement);
26 | }
27 |
28 | return rootElement;
29 | };
30 |
31 | export const requestLoadingScreen = () => dispatch(showLoadingScreen());
32 | export const removeLoadingScreen = () => dispatch(hideLoadingScreen());
33 |
34 | export const dispatchLocationPathChange = path => dispatch(locationPathChange(path));
35 |
36 | export const mount = () => {
37 | const { root = BaseUI, enabled = true } = Config.ui();
38 | if (!enabled) return;
39 |
40 | const store = getStore();
41 | const uiElement = createElement(root, createProps());
42 |
43 | if (store) {
44 | render(
45 | createElement(Provider, {
46 | store: getStore(),
47 | children: uiElement
48 | }),
49 | getUIContainer()
50 | );
51 | } else {
52 | render(uiElement, getUIContainer())
53 | }
54 | };
55 |
56 | export const unmount = (id = ROOT_ID) => {
57 | const container = document.querySelector(id);
58 |
59 | // Rendering null will trigger unmount lifecycle hooks for whole vDOM tree and remove global event listeners.
60 | // https://github.com/infernojs/inferno#tear-down
61 | render(null, container);
62 | // removing the container as well
63 | removeElement(id);
64 | };
65 |
--------------------------------------------------------------------------------
/src/video/Video.js:
--------------------------------------------------------------------------------
1 | class Video {
2 |
3 | constructor() {}
4 |
5 | load() { return Promise.resolve(); }
6 | }
7 |
8 | const engine = new Video();
9 |
10 | export default engine;
11 |
--------------------------------------------------------------------------------