├── .gitignore ├── LICENSE.md ├── README.md ├── assets ├── maps │ ├── m1.map │ └── m2.map ├── models │ ├── boulder.obj │ ├── box.obj │ ├── grenade.obj │ ├── hound.obj │ ├── hound_run_1.obj │ ├── hound_run_2.obj │ ├── nailgun.obj │ ├── q.obj │ ├── torch_1.obj │ ├── torch_2.obj │ ├── torch_3.obj │ ├── unit_fire.obj │ ├── unit_idle.obj │ ├── unit_run_1.obj │ ├── unit_run_2.obj │ ├── unit_run_3.obj │ └── unit_run_4.obj └── textures │ ├── 0.png │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 2.png │ ├── 20.png │ ├── 21.png │ ├── 22.png │ ├── 25.png │ ├── 26.png │ ├── 27.png │ ├── 28.png │ ├── 29.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── build.sh ├── build └── dummy.txt ├── index.html ├── pack_js.php ├── pack_map.c ├── pack_model.php └── source ├── audio.js ├── document.js ├── entity.js ├── entity_barrel.js ├── entity_door.js ├── entity_enemy.js ├── entity_enemy_enforcer.js ├── entity_enemy_grunt.js ├── entity_enemy_hound.js ├── entity_enemy_ogre.js ├── entity_enemy_zombie.js ├── entity_light.js ├── entity_particle.js ├── entity_pickup.js ├── entity_pickup_grenadelauncher.js ├── entity_pickup_grenades.js ├── entity_pickup_health.js ├── entity_pickup_key.js ├── entity_pickup_nailgun.js ├── entity_pickup_nails.js ├── entity_player.js ├── entity_projectile_gib.js ├── entity_projectile_grenade.js ├── entity_projectile_nail.js ├── entity_projectile_plasma.js ├── entity_projectile_shell.js ├── entity_torch.js ├── entity_trigger_level.js ├── game.js ├── html_template.html ├── input.js ├── main.js ├── map.js ├── math_utils.js ├── model.js ├── music.js ├── renderer.js ├── textures.js ├── ttt.js ├── weapons.js ├── wrap_post.js └── wrap_pre.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json 3 | package-lock.json 4 | autosave/ 5 | build/ 6 | pack_map 7 | \#* 8 | *~ 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dominic Szablewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Q1K3 - A JS13k GAME 2 | 3 | My entry for the 2021 [js13k](https://js13kgames.com/) competition. 4 | 5 | Play here: https://phoboslab.org/q1k3/ 6 | 7 | Making Of: https://phoboslab.org/log/2021/09/q1k3-making-of 8 | 9 | ### Controls 10 | - Movement: WASD or Arrow Keys 11 | - Attack: Left Mouse Button 12 | - Jump: Space or Right Mouse Button 13 | - Switch Weapon: Q/E or Mousewheel 14 | 15 | ### Features 16 | - 2 Levels 17 | - 5 Types of enemies 18 | - 3 Weapons 19 | - 30 different textures 20 | - Music from Andy Lösch: [no-fate](http://no-fate.net/) 21 | - Dynamic lighting 22 | - Doors(!) 23 | - Somewhat robust collision detection, even for fast moving objects 24 | - Enemy AI with line of sight checks. No pathfinding, but still does a reasonable job following the player 25 | - "Spacial" Audio (Stero separation and falloff by distance) 26 | - Maps build with [TrenchBroom](https://trenchbroom.github.io/) 27 | - A map compiler written in C (used during build) 28 | 29 | ### Libs used 30 | - [Tiny Texture Tumbler](https://github.com/phoboslab/ttt) for texture generation 31 | - A minified fork of [Sonant-X](https://github.com/nicolas-van/sonant-x) for sounds and music 32 | - [UglifyJS3](https://www.npmjs.com/package/uglify-js) to minify the source 33 | - [Roadroller](https://github.com/lifthrasiir/roadroller/) for further source compression 34 | 35 | 36 | ### License 37 | MIT Licensed 38 | 39 | Please be aware that this projects makes use of Sonant-X (albeit heavily modified) 40 | which is published under the zlib license. 41 | -------------------------------------------------------------------------------- /assets/models/boulder.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.5 2 | mtllib boulder.mtl 3 | o Cube2 4 | #7 vertices, 8 faces 5 | v -1.76671199 -1.11166500 -1.27988643 6 | v -0.66717809 -1.13106144 0.80598548 7 | v -0.75547184 0.39059562 -1.03424222 8 | v 1.71502654 -1.25440865 -1.89713495 9 | v 1.00000000 -1.00000000 1.00000000 10 | v 0.43931125 8.8170497e-2 -1.22055291 11 | v 0.42936450 0.36758775 0.33316664 12 | g Cube2_default 13 | usemtl default 14 | s 1 15 | f 1// 6// 4// 16 | f 2// 3// 1// 17 | f 2// 7// 3// 18 | f 3// 6// 1// 19 | f 3// 7// 6// 20 | f 4// 6// 5// 21 | f 5// 6// 7// 22 | f 5// 7// 2// 23 | -------------------------------------------------------------------------------- /assets/models/box.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib box.mtl 3 | o Cube1 4 | #8 vertices, 10 faces 5 | v -1.00000000 -1.00000000 -1.00000000 6 | v -1.00000000 -1.00000000 1.00000000 7 | v -1.00000000 1.00000000 -1.00000000 8 | v -1.00000000 1.00000000 1.00000000 9 | v 1.00000000 -1.00000000 -1.00000000 10 | v 1.00000000 -1.00000000 1.00000000 11 | v 1.00000000 1.00000000 -1.00000000 12 | v 1.00000000 1.00000000 1.00000000 13 | g Cube1_default 14 | usemtl default 15 | s 1 16 | f 2// 3// 1// 17 | f 2// 4// 3// 18 | f 2// 8// 4// 19 | f 3// 5// 1// 20 | f 3// 7// 5// 21 | f 3// 8// 7// 22 | f 4// 8// 3// 23 | f 5// 8// 6// 24 | f 6// 8// 2// 25 | f 7// 8// 5// 26 | -------------------------------------------------------------------------------- /assets/models/grenade.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib grenade.mtl 3 | o Cylinder1 4 | #10 vertices, 16 faces 5 | v -1.50000000 1.00000000 0.0000000e+0 6 | v -1.50000000 0.30901699 0.95105652 7 | v -1.50000000 -0.80901699 0.58778525 8 | v -1.50000000 -0.80901699 -0.58778525 9 | v -1.50000000 0.30901699 -0.95105652 10 | v 1.50000000 1.00000000 0.0000000e+0 11 | v 1.50000000 0.30901699 0.95105652 12 | v 1.50000000 -0.80901699 0.58778525 13 | v 1.50000000 -0.80901699 -0.58778525 14 | v 1.50000000 0.30901699 -0.95105652 15 | g Cylinder1_default 16 | usemtl default 17 | s 1 18 | f 1// 4// 2// 19 | f 1// 6// 5// 20 | f 2// 4// 3// 21 | f 2// 6// 1// 22 | f 2// 8// 7// 23 | f 3// 8// 2// 24 | f 4// 8// 3// 25 | f 4// 10// 9// 26 | f 5// 4// 1// 27 | f 5// 6// 10// 28 | f 5// 10// 4// 29 | f 6// 9// 10// 30 | f 7// 6// 2// 31 | f 7// 8// 6// 32 | f 9// 6// 8// 33 | f 9// 8// 4// 34 | -------------------------------------------------------------------------------- /assets/models/hound.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib hound.mtl 3 | o Cube1 4 | #17 vertices, 30 faces 5 | v -0.27414249 0.20231664 1.59224039 6 | v 0.24584539 0.20231664 1.59224039 7 | v -0.34489706 0.68997600 -5.9876242e-2 8 | v 0.31659996 0.68997600 -5.9876242e-2 9 | v -0.44658541 0.30520433 -1.66534505 10 | v 0.41828831 0.30520433 -1.66534505 11 | v -1.4148550e-2 -0.39042135 0.70643627 12 | v -1.4148550e-2 -6.9785484e-2 -2.34491358 13 | v -1.4148550e-2 -0.23375148 -0.42106184 14 | v -0.43375565 -1.00192308 -1.66343399 15 | v 0.40545855 -1.00192308 -1.66343399 16 | v -0.60915295 -0.99285579 0.59310583 17 | v 0.58085585 -0.99285579 0.59310583 18 | v -1.4148550e-2 -9.7830546e-3 -1.28127565 19 | v -1.4148550e-2 -5.9388855e-2 0.21094748 20 | v -0.45093850 0.58881440 1.65639487 21 | v 0.42264140 0.58881440 1.65639487 22 | g Cube1_default 23 | usemtl default 24 | s 1 25 | f 1// 7// 2// 26 | f 1// 17// 16// 27 | f 2// 7// 17// 28 | f 2// 17// 1// 29 | f 3// 5// 9// 30 | f 3// 12// 7// 31 | f 3// 17// 4// 32 | f 4// 6// 3// 33 | f 4// 13// 9// 34 | f 4// 17// 7// 35 | f 5// 3// 6// 36 | f 5// 6// 8// 37 | f 6// 11// 8// 38 | f 7// 13// 4// 39 | f 7// 15// 13// 40 | f 7// 16// 3// 41 | f 8// 10// 5// 42 | f 8// 14// 10// 43 | f 9// 5// 10// 44 | f 9// 6// 4// 45 | f 9// 12// 3// 46 | f 9// 14// 11// 47 | f 9// 15// 12// 48 | f 10// 14// 9// 49 | f 11// 6// 9// 50 | f 11// 14// 8// 51 | f 12// 15// 7// 52 | f 13// 15// 9// 53 | f 16// 7// 1// 54 | f 16// 17// 3// 55 | -------------------------------------------------------------------------------- /assets/models/hound_run_1.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib hound_run_1.mtl 3 | o Cube1 4 | #17 vertices, 30 faces 5 | v -0.27414249 0.20231664 1.59224039 6 | v 0.24584539 0.20231664 1.59224039 7 | v -0.34489706 0.68997600 -5.9876242e-2 8 | v 0.31659996 0.68997600 -5.9876242e-2 9 | v -0.44658541 0.30520433 -1.66534505 10 | v 0.41828831 0.30520433 -1.66534505 11 | v -1.4148550e-2 -0.39042135 0.70643627 12 | v -1.4148550e-2 -6.9785484e-2 -2.34491358 13 | v -1.4148550e-2 -0.23375148 -0.42106184 14 | v -0.43375565 -0.93382778 -0.95044611 15 | v 0.40545855 -0.83101396 -0.66615665 16 | v -0.60915295 -0.95696320 0.16541373 17 | v 0.58085585 -0.81540550 -7.0403956e-2 18 | v -1.4148550e-2 -9.7830546e-3 -1.28127565 19 | v -1.4148550e-2 -5.9388855e-2 0.21094748 20 | v -0.45093850 0.58881440 1.65639487 21 | v 0.42264140 0.58881440 1.65639487 22 | g Cube1_default 23 | usemtl default 24 | s 1 25 | f 1// 7// 2// 26 | f 1// 17// 16// 27 | f 2// 7// 17// 28 | f 2// 17// 1// 29 | f 3// 5// 9// 30 | f 3// 12// 7// 31 | f 3// 17// 4// 32 | f 4// 6// 3// 33 | f 4// 13// 9// 34 | f 4// 17// 7// 35 | f 5// 3// 6// 36 | f 5// 6// 8// 37 | f 6// 11// 8// 38 | f 7// 13// 4// 39 | f 7// 15// 13// 40 | f 7// 16// 3// 41 | f 8// 10// 5// 42 | f 8// 14// 10// 43 | f 9// 5// 10// 44 | f 9// 6// 4// 45 | f 9// 12// 3// 46 | f 9// 14// 11// 47 | f 9// 15// 12// 48 | f 10// 14// 9// 49 | f 11// 6// 9// 50 | f 11// 14// 8// 51 | f 12// 15// 7// 52 | f 13// 15// 9// 53 | f 16// 7// 1// 54 | f 16// 17// 3// 55 | -------------------------------------------------------------------------------- /assets/models/hound_run_2.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib hound_run_2.mtl 3 | o Cube1 4 | #17 vertices, 30 faces 5 | v -0.27414249 0.32374765 1.53588567 6 | v 0.24584539 0.32374765 1.53588567 7 | v -0.34489706 0.87178707 -2.8008988e-2 8 | v 0.31659996 0.87178707 -2.8008988e-2 9 | v -0.44658541 0.48701540 -1.63347780 10 | v 0.41828831 0.48701540 -1.63347780 11 | v -1.4148550e-2 -0.20861028 0.73830352 12 | v -1.4148550e-2 0.11202559 -2.31304633 13 | v -1.4148550e-2 -5.1940403e-2 -0.38919459 14 | v -0.43375565 -0.70455128 -2.48896361 15 | v 0.40545855 -0.68576637 -2.05640756 16 | v -0.60915295 -0.59619234 1.41092021 17 | v 0.58085585 -0.54855918 1.19422288 18 | v -1.4148550e-2 0.17202802 -1.24940840 19 | v -1.4148550e-2 0.12242222 0.24281474 20 | v -0.45093850 0.71024542 1.60004015 21 | v 0.42264140 0.71024542 1.60004015 22 | g Cube1_default 23 | usemtl default 24 | s 1 25 | f 1// 7// 2// 26 | f 1// 17// 16// 27 | f 2// 7// 17// 28 | f 2// 17// 1// 29 | f 3// 5// 9// 30 | f 3// 12// 7// 31 | f 3// 17// 4// 32 | f 4// 6// 3// 33 | f 4// 13// 9// 34 | f 4// 17// 7// 35 | f 5// 3// 6// 36 | f 5// 6// 8// 37 | f 6// 11// 8// 38 | f 7// 13// 4// 39 | f 7// 15// 13// 40 | f 7// 16// 3// 41 | f 8// 10// 5// 42 | f 8// 14// 10// 43 | f 9// 5// 10// 44 | f 9// 6// 4// 45 | f 9// 12// 3// 46 | f 9// 14// 11// 47 | f 9// 15// 12// 48 | f 10// 14// 9// 49 | f 11// 6// 9// 50 | f 11// 14// 8// 51 | f 12// 15// 7// 52 | f 13// 15// 9// 53 | f 16// 7// 1// 54 | f 16// 17// 3// 55 | -------------------------------------------------------------------------------- /assets/models/nailgun.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib nailgun.mtl 3 | o Cube1 4 | #17 vertices, 22 faces 5 | v -0.49808545 7.0738526e-4 -0.35027558 6 | v 0.73207036 7.0738526e-4 -0.35027558 7 | v -0.49808545 7.0738526e-4 0.23452442 8 | v 0.73207036 7.0738526e-4 0.23452442 9 | v 0.73207036 -0.70631483 -5.7875579e-2 10 | v 0.93949391 -0.16061271 0.49790863 11 | v -1.14322716 -0.16061271 0.49790863 12 | v 0.93949391 0.26298729 0.49790863 13 | v -1.14322716 0.26298729 0.49790863 14 | v 0.93949391 3.7539161e-2 9.6285961e-2 15 | v -1.14322716 3.7539161e-2 9.6285961e-2 16 | v 0.93949391 -0.16061271 -0.61738635 17 | v -1.14322716 -0.16061271 -0.61738635 18 | v 0.93949391 0.26298729 -0.61738635 19 | v -1.14322716 0.26298729 -0.61738635 20 | v 0.93949391 3.7539161e-2 -0.21576367 21 | v -1.14322716 3.7539161e-2 -0.21576367 22 | g Cube1_default 23 | usemtl default 24 | s 1 25 | f 1// 3// 2// 26 | f 1// 5// 3// 27 | f 2// 5// 1// 28 | f 3// 4// 2// 29 | f 3// 5// 4// 30 | f 4// 5// 2// 31 | f 6// 8// 7// 32 | f 6// 10// 8// 33 | f 7// 10// 6// 34 | f 7// 11// 10// 35 | f 8// 9// 7// 36 | f 8// 10// 9// 37 | f 9// 11// 7// 38 | f 10// 11// 9// 39 | f 12// 15// 14// 40 | f 12// 17// 13// 41 | f 13// 15// 12// 42 | f 13// 17// 15// 43 | f 14// 16// 12// 44 | f 14// 17// 16// 45 | f 15// 17// 14// 46 | f 16// 17// 12// 47 | -------------------------------------------------------------------------------- /assets/models/q.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib q.mtl 3 | o entity0_brush3 4 | #25 vertices, 23 faces 5 | v -1.47660400 0.89508300 -2.93151200 6 | v -4.41993200 2.36674700 -2.93151100 7 | v 1.46672400 0.89508300 -2.93151200 8 | v 4.41005200 2.36674700 -2.93151100 9 | v 1.46672400 -2.78407800 -2.93151200 10 | v 1.46672400 -7.19907000 -2.93151200 11 | v 7.35338000 -0.57658200 -2.93151200 12 | v 10.29670800 -2.78407800 -2.93151200 13 | v 7.35338000 -0.57658200 2.95514500 14 | v 5.88171600 15.61172400 -2.93151100 15 | v 11.03254000 6.78173800 -2.93151100 16 | v 11.03254000 6.78173800 2.95514500 17 | v 13.24003700 7.51757000 -2.93151100 18 | v -7.36326000 -0.57658200 -2.93151200 19 | v -10.30658800 -2.78407800 -2.93151200 20 | v -7.36326000 -0.57658200 2.95514500 21 | v -1.47660400 -7.19907000 -2.93151200 22 | v -1.47660400 -2.78407800 -2.93151200 23 | v -13.24991700 7.51757000 -2.93151100 24 | v -11.04242000 6.78173800 2.95514500 25 | v -11.04242000 6.78173800 -2.93151100 26 | v -5.89159600 15.61172400 -2.93151100 27 | v -1.47660400 -9.40656600 -2.93151200 28 | v -4.9400000e-3 -18.23655100 -2.93151200 29 | v 1.46672400 -9.40656600 -2.93151200 30 | g entity0_brush3_default 31 | usemtl default 32 | s 1 33 | f 1// 4// 3// 34 | f 1// 25// 23// 35 | f 2// 4// 1// 36 | f 3// 25// 1// 37 | f 5// 7// 6// 38 | f 5// 9// 7// 39 | f 7// 8// 6// 40 | f 7// 11// 8// 41 | f 9// 11// 7// 42 | f 9// 12// 11// 43 | f 10// 13// 11// 44 | f 11// 12// 10// 45 | f 11// 13// 8// 46 | f 14// 17// 15// 47 | f 14// 18// 17// 48 | f 14// 20// 16// 49 | f 14// 21// 20// 50 | f 15// 21// 14// 51 | f 16// 18// 14// 52 | f 19// 21// 15// 53 | f 19// 22// 21// 54 | f 21// 22// 20// 55 | f 23// 25// 24// 56 | -------------------------------------------------------------------------------- /assets/models/torch_1.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib torch.mtl 3 | o cone2 4 | #4 vertices, 3 faces 5 | v 0.56160000 0.93902222 4.8672177e-17 6 | v -0.28080000 0.93902222 0.48635987 7 | v -0.28080000 0.93902222 -0.48635987 8 | v 0.0000000e+0 2.15875556 0.0000000e+0 9 | g cone2_default 10 | usemtl default 11 | s 1 12 | f 1// 4// 2// 13 | f 2// 4// 3// 14 | f 3// 4// 1// 15 | o cone1 16 | #4 vertices, 4 faces 17 | v 1.00000000 1.00000000 0.0000000e+0 18 | v -0.50000000 1.00000000 0.86602540 19 | v -0.50000000 1.00000000 -0.86602540 20 | v 0.0000000e+0 -1.00000000 0.0000000e+0 21 | g cone1_default 22 | usemtl default 23 | s 1 24 | f 5// 7// 6// 25 | f 5// 8// 7// 26 | f 6// 8// 5// 27 | f 7// 8// 6// 28 | -------------------------------------------------------------------------------- /assets/models/torch_2.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib torch2.mtl 3 | o cone2 4 | #4 vertices, 3 faces 5 | v 0.56160000 0.93902222 4.8672177e-17 6 | v -0.28080000 0.93902222 0.48635987 7 | v -0.28080000 0.93902222 -0.48635987 8 | v 0.37141710 2.34754042 -0.37242647 9 | g cone2_default 10 | usemtl default 11 | s 1 12 | f 1// 4// 2// 13 | f 2// 4// 3// 14 | f 3// 4// 1// 15 | o cone1 16 | #4 vertices, 4 faces 17 | v 1.00000000 1.00000000 0.0000000e+0 18 | v -0.50000000 1.00000000 0.86602540 19 | v -0.50000000 1.00000000 -0.86602540 20 | v 0.0000000e+0 -1.00000000 0.0000000e+0 21 | g cone1_default 22 | usemtl default 23 | s 1 24 | f 5// 7// 6// 25 | f 5// 8// 7// 26 | f 6// 8// 5// 27 | f 7// 8// 6// 28 | -------------------------------------------------------------------------------- /assets/models/torch_3.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.7 2 | mtllib torch_3.mtl 3 | o cone2 4 | #4 vertices, 3 faces 5 | v 0.56160000 0.93902222 4.8672177e-17 6 | v -0.28080000 0.93902222 0.48635987 7 | v -0.28080000 0.93902222 -0.48635987 8 | v 0.10185419 1.89578758 0.31045814 9 | g cone2_default 10 | usemtl default 11 | s 1 12 | f 1// 4// 2// 13 | f 2// 4// 3// 14 | f 3// 4// 1// 15 | o cone1 16 | #4 vertices, 4 faces 17 | v 1.00000000 1.00000000 0.0000000e+0 18 | v -0.50000000 1.00000000 0.86602540 19 | v -0.50000000 1.00000000 -0.86602540 20 | v 0.0000000e+0 -1.00000000 0.0000000e+0 21 | g cone1_default 22 | usemtl default 23 | s 1 24 | f 5// 7// 6// 25 | f 5// 8// 7// 26 | f 6// 8// 5// 27 | f 7// 8// 6// 28 | -------------------------------------------------------------------------------- /assets/models/unit_fire.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.5 2 | mtllib unit_fire.mtl 3 | o idle 4 | #36 vertices, 68 faces 5 | v -9.7072365e-2 0.56526052 0.14813200 6 | v -5.1174828e-3 0.58241863 3.6241454e-2 7 | v -7.3432045e-2 0.41571136 8.8730558e-2 8 | v -2.7773366e-3 0.38195589 0.18768463 9 | v -0.16457846 0.28956454 0.14866615 10 | v -0.22256966 0.36820969 6.5821860e-2 11 | v -2.2042426e-3 0.20761891 0.17635056 12 | v -0.16376325 0.24789118 2.7255653e-2 13 | v -3.2828151e-3 -5.9558653e-2 -3.2466908e-2 14 | v -0.11705512 -1.8998070e-2 0.10033982 15 | v -5.2468113e-2 -0.37762396 0.11261478 16 | v -0.12733850 -0.38368582 0.11398771 17 | v -0.11232770 -0.31817914 4.0267488e-2 18 | v -0.18392516 0.29905284 0.36931274 19 | v -0.21375240 0.22504257 0.28526370 20 | v -0.16363894 0.20062898 0.33478576 21 | v -3.1666221e-3 0.58110829 0.22659559 22 | v -4.3491030e-3 0.40731243 4.3715603e-2 23 | v -1.6209207e-3 -0.12923391 0.10318519 24 | v -0.13664447 0.22269181 0.71114277 25 | v -6.2110567e-2 -0.72441694 1.6821488e-2 26 | v -4.2262828e-3 0.60741476 0.13309524 27 | v 8.9237988e-2 0.56599678 0.14622765 28 | v 6.5570994e-2 0.41626067 8.7309755e-2 29 | v 7.8519940e-2 0.28988820 0.20138750 30 | v 0.14416942 0.38159031 0.14693994 31 | v 0.17385655 0.30958720 9.0735796e-2 32 | v 8.1999016e-2 -1.8089508e-2 9.1851922e-2 33 | v 5.1369741e-2 -0.37721362 0.11155341 34 | v 0.12629811 -0.38268350 0.11139519 35 | v 0.10926638 -0.31730344 3.8002490e-2 36 | v 4.7935402e-2 0.20296119 0.35368779 37 | v 9.7607717e-2 0.13718946 0.30493926 38 | v 2.5761572e-2 0.15405508 0.25758703 39 | v -0.28779708 0.20832952 0.44720995 40 | v 6.1792415e-2 -0.72392731 1.5555028e-2 41 | g idle_default 42 | usemtl default 43 | s 1 44 | f 1// 22// 2// 45 | f 2// 3// 1// 46 | f 2// 24// 18// 47 | f 3// 4// 1// 48 | f 3// 8// 6// 49 | f 4// 23// 17// 50 | f 5// 3// 6// 51 | f 5// 4// 3// 52 | f 5// 10// 7// 53 | f 6// 8// 15// 54 | f 6// 14// 5// 55 | f 7// 4// 5// 56 | f 7// 25// 4// 57 | f 7// 28// 25// 58 | f 8// 9// 10// 59 | f 8// 14// 16// 60 | f 8// 18// 9// 61 | f 9// 13// 10// 62 | f 9// 29// 19// 63 | f 10// 5// 8// 64 | f 10// 19// 7// 65 | f 11// 10// 12// 66 | f 11// 12// 21// 67 | f 11// 19// 10// 68 | f 11// 21// 13// 69 | f 12// 10// 13// 70 | f 13// 9// 11// 71 | f 13// 21// 12// 72 | f 14// 6// 15// 73 | f 14// 8// 5// 74 | f 15// 20// 14// 75 | f 16// 15// 8// 76 | f 16// 20// 15// 77 | f 17// 1// 4// 78 | f 17// 22// 1// 79 | f 17// 23// 22// 80 | f 18// 3// 2// 81 | f 18// 8// 3// 82 | f 18// 26// 27// 83 | f 18// 27// 9// 84 | f 19// 11// 9// 85 | f 19// 28// 7// 86 | f 20// 16// 14// 87 | f 22// 23// 2// 88 | f 23// 4// 24// 89 | f 23// 24// 2// 90 | f 24// 4// 25// 91 | f 25// 27// 32// 92 | f 25// 32// 26// 93 | f 26// 18// 24// 94 | f 26// 24// 25// 95 | f 27// 25// 28// 96 | f 27// 26// 34// 97 | f 28// 9// 27// 98 | f 28// 19// 29// 99 | f 28// 31// 9// 100 | f 29// 9// 31// 101 | f 30// 28// 29// 102 | f 30// 36// 31// 103 | f 31// 28// 30// 104 | f 31// 36// 29// 105 | f 32// 34// 35// 106 | f 32// 35// 33// 107 | f 33// 26// 32// 108 | f 33// 34// 26// 109 | f 33// 35// 34// 110 | f 34// 32// 27// 111 | f 36// 30// 29// 112 | -------------------------------------------------------------------------------- /assets/models/unit_idle.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.5 2 | mtllib u42-idle.mtl 3 | o idle 4 | #36 vertices, 68 faces 5 | v -9.7072365e-2 0.56526052 0.14813200 6 | v -5.1174828e-3 0.58241863 3.6241454e-2 7 | v -7.3432045e-2 0.41571136 8.8730558e-2 8 | v -2.7773366e-3 0.38195589 0.18768463 9 | v -0.16457846 0.28956454 0.14866615 10 | v -0.22256966 0.36820969 6.5821860e-2 11 | v -2.2042426e-3 0.20761891 0.17635056 12 | v -0.12240829 0.27200369 1.9776168e-2 13 | v -3.2828151e-3 -5.9558653e-2 -3.2466908e-2 14 | v -0.11705512 -1.8998070e-2 0.10033982 15 | v -5.2468113e-2 -0.37762396 0.11261478 16 | v -0.12733850 -0.38368582 0.11398771 17 | v -0.11232770 -0.31817914 4.0267488e-2 18 | v -0.26195311 6.1200154e-2 8.9171027e-2 19 | v -0.27269328 7.0984903e-2 2.8453086e-2 20 | v -0.19946210 8.9864434e-2 3.4271377e-3 21 | v -3.1666221e-3 0.58110829 0.22659559 22 | v -4.3491030e-3 0.40731243 4.3715603e-2 23 | v -1.6209207e-3 -0.12923391 0.10318519 24 | v -0.26237064 -0.27025472 0.23936610 25 | v -6.2110567e-2 -0.72441694 1.6821488e-2 26 | v -4.2262828e-3 0.60741476 0.13309524 27 | v 8.9237988e-2 0.56599678 0.14622765 28 | v 6.5570994e-2 0.41626067 8.7309755e-2 29 | v 0.15891751 0.29084293 0.14535957 30 | v 0.21457992 0.36993722 6.1353587e-2 31 | v 0.11426169 0.27293896 1.7357074e-2 32 | v 0.11285624 -1.8089508e-2 9.7989805e-2 33 | v 5.1369741e-2 -0.37721362 0.11155341 34 | v 0.12629811 -0.38268350 0.11139519 35 | v 0.10926638 -0.31730344 3.8002490e-2 36 | v 0.25685736 6.3250386e-2 8.3868066e-2 37 | v 0.26627654 7.3114801e-2 2.2944069e-2 38 | v 0.19240220 9.1413002e-2 -5.7825739e-4 39 | v 0.26296420 -0.26817871 0.23399645 40 | v 6.1792415e-2 -0.72392731 1.5555028e-2 41 | g idle_default 42 | usemtl default 43 | s 1 44 | f 1// 22// 2// 45 | f 2// 3// 1// 46 | f 2// 24// 18// 47 | f 3// 4// 1// 48 | f 3// 8// 6// 49 | f 4// 23// 17// 50 | f 5// 3// 6// 51 | f 5// 4// 3// 52 | f 5// 10// 7// 53 | f 6// 8// 15// 54 | f 6// 14// 5// 55 | f 7// 4// 5// 56 | f 7// 25// 4// 57 | f 7// 28// 25// 58 | f 8// 9// 10// 59 | f 8// 14// 16// 60 | f 8// 18// 9// 61 | f 9// 13// 10// 62 | f 9// 29// 19// 63 | f 10// 5// 8// 64 | f 10// 19// 7// 65 | f 11// 10// 12// 66 | f 11// 12// 21// 67 | f 11// 19// 10// 68 | f 11// 21// 13// 69 | f 12// 10// 13// 70 | f 13// 9// 11// 71 | f 13// 21// 12// 72 | f 14// 6// 15// 73 | f 14// 8// 5// 74 | f 15// 20// 14// 75 | f 16// 15// 8// 76 | f 16// 20// 15// 77 | f 17// 1// 4// 78 | f 17// 22// 1// 79 | f 17// 23// 22// 80 | f 18// 3// 2// 81 | f 18// 8// 3// 82 | f 18// 26// 27// 83 | f 18// 27// 9// 84 | f 19// 11// 9// 85 | f 19// 28// 7// 86 | f 20// 16// 14// 87 | f 22// 23// 2// 88 | f 23// 4// 24// 89 | f 23// 24// 2// 90 | f 24// 4// 25// 91 | f 25// 27// 32// 92 | f 25// 32// 26// 93 | f 26// 18// 24// 94 | f 26// 24// 25// 95 | f 27// 25// 28// 96 | f 27// 26// 34// 97 | f 28// 9// 27// 98 | f 28// 19// 29// 99 | f 28// 31// 9// 100 | f 29// 9// 31// 101 | f 30// 28// 29// 102 | f 30// 36// 31// 103 | f 31// 28// 30// 104 | f 31// 36// 29// 105 | f 32// 34// 35// 106 | f 32// 35// 33// 107 | f 33// 26// 32// 108 | f 33// 34// 26// 109 | f 33// 35// 34// 110 | f 34// 32// 27// 111 | f 36// 30// 29// 112 | -------------------------------------------------------------------------------- /assets/models/unit_run_1.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.5 2 | mtllib u42-run-1.mtl 3 | o run_1 4 | #36 vertices, 68 faces 5 | v -9.5272919e-2 0.58320065 0.23895644 6 | v -3.3180374e-3 0.62435371 0.13350350 7 | v -7.1632599e-2 0.45021114 0.14836243 8 | v -9.7789112e-4 0.39568245 0.23756984 9 | v -0.17307984 0.32466544 0.13174644 10 | v -0.20495298 0.42235179 5.5243521e-2 11 | v -2.6982217e-2 0.25733845 0.18552924 12 | v -9.5805943e-2 0.33183820 1.9155900e-2 13 | v -3.2828150e-3 1.1936972e-2 -2.9445966e-2 14 | v -0.11705512 5.2497554e-2 0.10336076 15 | v -5.5121527e-2 -0.21726415 0.30762026 16 | v -0.12999191 -0.21814619 0.31377274 17 | v -0.11393341 -0.25917247 0.23816510 18 | v -0.23980691 0.24937442 -6.0965392e-2 19 | v -0.23273167 0.23590209 -0.12151453 20 | v -0.15540189 0.25233529 -0.13129183 21 | v -1.3671766e-3 0.58155046 0.31898745 22 | v -2.5496575e-3 0.45183419 0.10259941 23 | v -1.6209207e-3 -5.7738288e-2 0.10620614 24 | v -0.22503228 -2.6466381e-2 0.19543165 25 | v -6.2140773e-2 -0.49248177 0.54917297 26 | v -2.4268373e-3 0.62761985 0.23347747 27 | v 9.1037433e-2 0.58433460 0.23725856 28 | v 6.7370439e-2 0.45105716 0.14709568 29 | v 0.13784712 0.30642002 0.21923207 30 | v 0.21521175 0.39769622 0.17346542 31 | v 0.13166854 0.31848984 8.3160476e-2 32 | v 0.11285624 5.3406116e-2 0.10101075 33 | v 5.6280724e-2 -0.26007502 -2.0635962e-2 34 | v 0.13120909 -0.26515311 -2.2674963e-2 35 | v 0.11417736 -0.17843586 -6.8947620e-2 36 | v 0.21505817 0.24739267 0.31347123 37 | v 0.24150950 0.20354683 0.27775525 38 | v 0.18039399 0.17439735 0.24201011 39 | v 8.5574373e-2 0.14637362 0.54237953 40 | v 8.9212783e-2 -0.30204016 -0.43882211 41 | g run_1_default 42 | usemtl default 43 | s 1 44 | f 1// 22// 2// 45 | f 2// 3// 1// 46 | f 2// 24// 18// 47 | f 3// 4// 1// 48 | f 3// 8// 6// 49 | f 4// 23// 17// 50 | f 5// 3// 6// 51 | f 5// 4// 3// 52 | f 5// 10// 7// 53 | f 6// 8// 15// 54 | f 6// 14// 5// 55 | f 7// 4// 5// 56 | f 7// 25// 4// 57 | f 7// 28// 25// 58 | f 8// 9// 10// 59 | f 8// 14// 16// 60 | f 8// 18// 9// 61 | f 9// 13// 10// 62 | f 9// 29// 19// 63 | f 10// 5// 8// 64 | f 10// 19// 7// 65 | f 11// 10// 12// 66 | f 11// 12// 21// 67 | f 11// 19// 10// 68 | f 11// 21// 13// 69 | f 12// 10// 13// 70 | f 13// 9// 11// 71 | f 13// 21// 12// 72 | f 14// 6// 15// 73 | f 14// 8// 5// 74 | f 15// 20// 14// 75 | f 16// 15// 8// 76 | f 16// 20// 15// 77 | f 17// 1// 4// 78 | f 17// 22// 1// 79 | f 17// 23// 22// 80 | f 18// 3// 2// 81 | f 18// 8// 3// 82 | f 18// 26// 27// 83 | f 18// 27// 9// 84 | f 19// 11// 9// 85 | f 19// 28// 7// 86 | f 20// 16// 14// 87 | f 22// 23// 2// 88 | f 23// 4// 24// 89 | f 23// 24// 2// 90 | f 24// 4// 25// 91 | f 25// 27// 32// 92 | f 25// 32// 26// 93 | f 26// 18// 24// 94 | f 26// 24// 25// 95 | f 27// 25// 28// 96 | f 27// 26// 34// 97 | f 28// 9// 27// 98 | f 28// 19// 29// 99 | f 29// 9// 31// 100 | f 30// 9// 28// 101 | f 30// 28// 29// 102 | f 30// 36// 31// 103 | f 31// 9// 30// 104 | f 31// 36// 29// 105 | f 32// 34// 35// 106 | f 32// 35// 33// 107 | f 33// 26// 32// 108 | f 33// 34// 26// 109 | f 33// 35// 34// 110 | f 34// 32// 27// 111 | f 36// 30// 29// 112 | -------------------------------------------------------------------------------- /assets/models/unit_run_2.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.5 2 | mtllib u42-run-2.mtl 3 | o run_2 4 | #36 vertices, 68 faces 5 | v -9.4991472e-2 0.52373540 0.23964533 6 | v -3.0365897e-3 0.56488846 0.13419239 7 | v -7.1351151e-2 0.39074589 0.14905133 8 | v -6.9644348e-4 0.33621720 0.23825873 9 | v -0.17267823 0.26520019 0.15406434 10 | v -0.21436437 0.36288654 8.2434335e-2 11 | v -2.0762743e-2 0.19787320 0.18810538 12 | v -0.11093143 0.27237295 3.2264433e-2 13 | v -3.0013674e-3 -4.7528279e-2 -2.8757072e-2 14 | v -0.11677367 -6.9676967e-3 0.10404965 15 | v -6.1941435e-2 -0.36213984 0.21486963 16 | v -0.13681182 -0.36634357 0.21944779 17 | v -0.12075331 -0.35747908 0.13388442 18 | v -0.27214113 0.16564009 1.8179960e-2 19 | v -0.27311477 0.14229232 -3.9713862e-2 20 | v -0.19775045 0.15521696 -6.2045768e-2 21 | v -1.0857290e-3 0.52208521 0.31967635 22 | v -2.2682098e-3 0.39236894 0.10328831 23 | v -1.3394730e-3 -0.11720354 0.10689503 24 | v -0.21276320 -3.2220363e-2 0.25209224 25 | v -9.3144493e-2 -0.70483668 0.17970538 26 | v -2.1453897e-3 0.56815460 0.23416636 27 | v 9.1318881e-2 0.52486934 0.23794745 28 | v 6.7651887e-2 0.39159191 0.14778457 29 | v 0.14707201 0.24695477 0.19977100 30 | v 0.21772350 0.33823097 0.14419908 31 | v 0.12299816 0.25902459 6.5703468e-2 32 | v 0.11313769 -6.0591343e-3 0.10169964 33 | v 5.6562172e-2 -0.34172597 8.3847930e-2 34 | v 0.13149054 -0.34717436 8.4357499e-2 35 | v 0.11445881 -0.29122596 3.5440300e-3 36 | v 0.25704387 0.13904658 0.16957344 37 | v 0.27855275 8.9755721e-2 0.13786319 38 | v 0.21325613 5.6754116e-2 0.11528535 39 | v 0.15532922 -3.1631277e-2 0.40939898 40 | v 8.9494231e-2 -0.52086355 -0.28286119 41 | g run_2_default 42 | usemtl default 43 | s 1 44 | f 1// 22// 2// 45 | f 2// 3// 1// 46 | f 2// 24// 18// 47 | f 3// 4// 1// 48 | f 3// 8// 6// 49 | f 4// 23// 17// 50 | f 5// 3// 6// 51 | f 5// 4// 3// 52 | f 5// 10// 7// 53 | f 6// 8// 15// 54 | f 6// 14// 5// 55 | f 7// 4// 5// 56 | f 7// 25// 4// 57 | f 7// 28// 25// 58 | f 8// 9// 10// 59 | f 8// 14// 16// 60 | f 8// 18// 9// 61 | f 9// 13// 10// 62 | f 9// 29// 19// 63 | f 10// 5// 8// 64 | f 10// 19// 7// 65 | f 11// 10// 12// 66 | f 11// 12// 21// 67 | f 11// 19// 10// 68 | f 11// 21// 13// 69 | f 12// 10// 13// 70 | f 13// 9// 11// 71 | f 13// 21// 12// 72 | f 14// 6// 15// 73 | f 14// 8// 5// 74 | f 15// 20// 14// 75 | f 16// 15// 8// 76 | f 16// 20// 15// 77 | f 17// 1// 4// 78 | f 17// 22// 1// 79 | f 17// 23// 22// 80 | f 18// 3// 2// 81 | f 18// 8// 3// 82 | f 18// 26// 27// 83 | f 18// 27// 9// 84 | f 19// 11// 9// 85 | f 19// 28// 7// 86 | f 20// 16// 14// 87 | f 22// 23// 2// 88 | f 23// 4// 24// 89 | f 23// 24// 2// 90 | f 24// 4// 25// 91 | f 25// 27// 32// 92 | f 25// 32// 26// 93 | f 26// 18// 24// 94 | f 26// 24// 25// 95 | f 27// 25// 28// 96 | f 27// 26// 34// 97 | f 28// 9// 27// 98 | f 28// 19// 29// 99 | f 29// 9// 31// 100 | f 30// 9// 28// 101 | f 30// 28// 29// 102 | f 30// 36// 31// 103 | f 31// 9// 30// 104 | f 31// 36// 29// 105 | f 32// 34// 35// 106 | f 32// 35// 33// 107 | f 33// 26// 32// 108 | f 33// 34// 26// 109 | f 33// 35// 34// 110 | f 34// 32// 27// 111 | f 36// 30// 29// 112 | -------------------------------------------------------------------------------- /assets/models/unit_run_3.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.5 2 | mtllib u42-run-3.mtl 3 | o run_3 4 | #36 vertices, 68 faces 5 | v -9.7534954e-2 0.58537971 0.23727152 6 | v -5.5800717e-3 0.62207596 0.13434820 7 | v -7.3894633e-2 0.44530355 0.15807461 8 | v -3.2399255e-3 0.39839816 0.25151563 9 | v -0.14811650 0.30785729 0.23172695 10 | v -0.22184769 0.39518773 0.17386227 11 | v 2.9874070e-2 0.25219372 0.18896602 12 | v -0.13324204 0.30880016 9.5796106e-2 13 | v -3.0261434e-3 1.2834357e-2 -2.8933793e-2 14 | v -0.10962990 5.0988036e-2 0.10517160 15 | v -4.1698825e-2 -0.26559037 -1.8795665e-2 16 | v -0.11656921 -0.27177960 -1.9365307e-2 17 | v -0.10155841 -0.18668084 -6.9205025e-2 18 | v -0.24752637 0.24555925 0.31401265 19 | v -0.27201893 0.22587890 0.26943932 20 | v -0.20708582 0.17323236 0.24465303 21 | v -3.6292110e-3 0.58033840 0.31604327 22 | v -4.8116918e-3 0.45317336 0.10171045 23 | v 4.8726959e-5 -5.7632780e-2 0.10082565 24 | v -0.15387665 0.14921342 0.53804817 25 | v -8.6656471e-2 -0.30440046 -0.43683459 26 | v -4.6888717e-3 0.62918786 0.22811015 27 | v 8.8775399e-2 0.58636824 0.23548505 28 | v 6.5108405e-2 0.45331084 0.14611670 29 | v 0.17212974 0.33058228 0.13766338 30 | v 0.20940429 0.42167997 5.5669264e-2 31 | v 0.10220107 0.32864714 2.0166273e-2 32 | v 0.11389002 5.2669771e-2 9.9938702e-2 33 | v 5.2010784e-2 -0.22039388 0.29971756 34 | v 0.12924803 -0.21231946 0.30276019 35 | v 0.11527123 -0.26319363 0.23743150 36 | v 0.22868222 0.25165315 -6.1196965e-2 37 | v 0.22720815 0.23037277 -0.11930067 38 | v 0.15008199 0.25256058 -0.13060470 39 | v 0.21252622 -3.0157464e-2 0.20240516 40 | v 6.2049086e-2 -0.49335414 0.55060042 41 | g run_3_default 42 | usemtl default 43 | s 1 44 | f 1// 22// 2// 45 | f 2// 3// 1// 46 | f 2// 24// 18// 47 | f 3// 4// 1// 48 | f 3// 8// 6// 49 | f 4// 23// 17// 50 | f 5// 3// 6// 51 | f 5// 4// 3// 52 | f 5// 10// 7// 53 | f 6// 8// 15// 54 | f 6// 14// 5// 55 | f 7// 4// 5// 56 | f 7// 25// 4// 57 | f 7// 28// 25// 58 | f 8// 9// 10// 59 | f 8// 14// 16// 60 | f 8// 18// 9// 61 | f 9// 13// 10// 62 | f 9// 29// 19// 63 | f 10// 5// 8// 64 | f 10// 19// 7// 65 | f 11// 10// 12// 66 | f 11// 12// 21// 67 | f 11// 19// 10// 68 | f 11// 21// 13// 69 | f 12// 10// 13// 70 | f 13// 9// 11// 71 | f 13// 21// 12// 72 | f 14// 6// 15// 73 | f 14// 8// 5// 74 | f 15// 20// 14// 75 | f 16// 15// 8// 76 | f 16// 20// 15// 77 | f 17// 1// 4// 78 | f 17// 22// 1// 79 | f 17// 23// 22// 80 | f 18// 3// 2// 81 | f 18// 8// 3// 82 | f 18// 27// 9// 83 | f 19// 11// 9// 84 | f 19// 28// 7// 85 | f 20// 16// 14// 86 | f 22// 23// 2// 87 | f 23// 4// 24// 88 | f 23// 24// 2// 89 | f 24// 4// 25// 90 | f 24// 27// 18// 91 | f 25// 27// 32// 92 | f 25// 32// 26// 93 | f 26// 24// 25// 94 | f 26// 27// 24// 95 | f 27// 25// 28// 96 | f 27// 33// 34// 97 | f 28// 9// 27// 98 | f 28// 19// 29// 99 | f 28// 31// 9// 100 | f 29// 9// 31// 101 | f 30// 28// 29// 102 | f 30// 36// 31// 103 | f 31// 28// 30// 104 | f 31// 36// 29// 105 | f 32// 34// 35// 106 | f 32// 35// 33// 107 | f 33// 26// 32// 108 | f 33// 27// 26// 109 | f 33// 35// 34// 110 | f 34// 32// 27// 111 | f 36// 30// 29// 112 | -------------------------------------------------------------------------------- /assets/models/unit_run_4.obj: -------------------------------------------------------------------------------- 1 | # Exported from Wings 3D 2.1.5 2 | mtllib u42-run-4.mtl 3 | o run_4 4 | #36 vertices, 68 faces 5 | v -8.7448640e-2 0.52260968 0.23811789 6 | v 1.1106415e-2 0.56619990 0.13601636 7 | v -5.6652628e-2 0.39253244 0.14723325 8 | v 4.7983532e-3 0.33685461 0.23791788 9 | v -0.14201056 0.24676130 0.19890273 10 | v -0.21395473 0.33766742 0.14321628 11 | v 1.5828108e-2 0.25132493 0.18909199 12 | v -0.11559322 0.25643607 6.3974881e-2 13 | v 5.8659423e-3 -4.6614671e-2 -2.8852696e-2 14 | v -0.12367586 5.0119248e-2 0.10529757 15 | v -5.9869043e-2 -0.34160511 8.3280232e-2 16 | v -0.13473943 -0.34779433 8.2710589e-2 17 | v -0.11085266 -0.29407513 4.8260947e-3 18 | v -0.25132952 0.13731816 0.17143274 19 | v -0.27232641 8.9198561e-2 0.13324630 20 | v -0.20953794 5.7890222e-2 0.11187209 21 | v 5.7439754e-3 0.52158520 0.32087826 22 | v 1.4994713e-2 0.38418936 0.11355618 23 | v 9.5050338e-3 -0.11737256 9.6571469e-2 24 | v -0.15147117 -2.8967763e-2 0.40656236 25 | v -8.6836875e-2 -0.51910125 -0.27711023 26 | v 8.1007774e-3 0.56742535 0.23507916 27 | v 0.10858180 0.52745008 0.23670572 28 | v 7.4975063e-2 0.38772831 0.14527201 29 | v 0.17898781 0.26745693 0.15584951 30 | v 0.22056911 0.36000702 8.3859511e-2 31 | v 0.11885495 0.26917230 2.9631907e-2 32 | v 9.9844063e-2 4.2294354e-2 9.3354108e-2 33 | v 6.9120406e-2 -0.36382724 0.20320527 34 | v 0.14171710 -0.36592997 0.21060239 35 | v 0.12317708 -0.35863673 0.13900796 36 | v 0.27620662 0.16435731 1.6758612e-2 37 | v 0.28008560 0.14336818 -3.9632239e-2 38 | v 0.20394231 0.15390316 -6.2704812e-2 39 | v 0.21981481 -3.0190650e-2 0.24883182 40 | v 9.2836181e-2 -0.69912263 0.18131087 41 | g run_4_default 42 | usemtl default 43 | s 1 44 | f 1// 22// 2// 45 | f 2// 3// 1// 46 | f 2// 24// 18// 47 | f 3// 4// 1// 48 | f 3// 8// 6// 49 | f 4// 23// 17// 50 | f 5// 3// 6// 51 | f 5// 4// 3// 52 | f 5// 10// 7// 53 | f 6// 8// 15// 54 | f 6// 14// 5// 55 | f 7// 4// 5// 56 | f 7// 25// 4// 57 | f 7// 28// 25// 58 | f 8// 9// 10// 59 | f 8// 14// 16// 60 | f 8// 18// 9// 61 | f 9// 13// 10// 62 | f 9// 29// 19// 63 | f 10// 5// 8// 64 | f 10// 19// 7// 65 | f 11// 10// 12// 66 | f 11// 12// 21// 67 | f 11// 19// 10// 68 | f 11// 21// 13// 69 | f 12// 10// 13// 70 | f 13// 9// 11// 71 | f 13// 21// 12// 72 | f 14// 6// 15// 73 | f 14// 8// 5// 74 | f 15// 20// 14// 75 | f 16// 15// 8// 76 | f 16// 20// 15// 77 | f 17// 1// 4// 78 | f 17// 22// 1// 79 | f 17// 23// 22// 80 | f 18// 3// 2// 81 | f 18// 8// 3// 82 | f 18// 26// 27// 83 | f 18// 27// 9// 84 | f 19// 11// 9// 85 | f 19// 28// 7// 86 | f 20// 16// 14// 87 | f 22// 23// 2// 88 | f 23// 4// 24// 89 | f 23// 24// 2// 90 | f 24// 4// 25// 91 | f 25// 27// 32// 92 | f 25// 32// 26// 93 | f 26// 18// 24// 94 | f 26// 24// 25// 95 | f 27// 25// 28// 96 | f 27// 26// 34// 97 | f 28// 9// 27// 98 | f 28// 19// 29// 99 | f 29// 9// 31// 100 | f 30// 9// 28// 101 | f 30// 28// 29// 102 | f 30// 36// 31// 103 | f 31// 9// 30// 104 | f 31// 36// 29// 105 | f 32// 34// 35// 106 | f 32// 35// 33// 107 | f 33// 26// 32// 108 | f 33// 34// 26// 109 | f 33// 35// 34// 110 | f 34// 32// 27// 111 | f 36// 30// 29// 112 | -------------------------------------------------------------------------------- /assets/textures/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/0.png -------------------------------------------------------------------------------- /assets/textures/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/1.png -------------------------------------------------------------------------------- /assets/textures/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/10.png -------------------------------------------------------------------------------- /assets/textures/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/11.png -------------------------------------------------------------------------------- /assets/textures/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/12.png -------------------------------------------------------------------------------- /assets/textures/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/13.png -------------------------------------------------------------------------------- /assets/textures/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/14.png -------------------------------------------------------------------------------- /assets/textures/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/15.png -------------------------------------------------------------------------------- /assets/textures/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/16.png -------------------------------------------------------------------------------- /assets/textures/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/2.png -------------------------------------------------------------------------------- /assets/textures/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/20.png -------------------------------------------------------------------------------- /assets/textures/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/21.png -------------------------------------------------------------------------------- /assets/textures/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/22.png -------------------------------------------------------------------------------- /assets/textures/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/25.png -------------------------------------------------------------------------------- /assets/textures/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/26.png -------------------------------------------------------------------------------- /assets/textures/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/27.png -------------------------------------------------------------------------------- /assets/textures/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/28.png -------------------------------------------------------------------------------- /assets/textures/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/29.png -------------------------------------------------------------------------------- /assets/textures/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/3.png -------------------------------------------------------------------------------- /assets/textures/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/4.png -------------------------------------------------------------------------------- /assets/textures/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/5.png -------------------------------------------------------------------------------- /assets/textures/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/6.png -------------------------------------------------------------------------------- /assets/textures/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/7.png -------------------------------------------------------------------------------- /assets/textures/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/8.png -------------------------------------------------------------------------------- /assets/textures/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoboslab/q1k3/8ee934856c3f5de3c584724b9dd554314a911694/assets/textures/9.png -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | # Compile the pack_map 4 | gcc -std=c99 pack_map.c -lm -o pack_map 5 | 6 | # Pack maps 7 | ./pack_map assets/maps/m1.map build/m1.plb 8 | ./pack_map assets/maps/m2.map build/m2.plb 9 | 10 | # Concat all maps into one file 11 | cat \ 12 | build/m1.plb \ 13 | build/m2.plb \ 14 | > build/l 15 | 16 | # Pack models 17 | php pack_model.php assets/models/boulder.obj build/boulder.rmf 18 | php pack_model.php assets/models/q.obj build/q.rmf 19 | php pack_model.php assets/models/grenade.obj build/grenade.rmf 20 | php pack_model.php \ 21 | assets/models/hound_run_1.obj \ 22 | assets/models/hound_run_2.obj \ 23 | build/hound.rmf 24 | php pack_model.php \ 25 | assets/models/unit_idle.obj \ 26 | assets/models/unit_run_1.obj \ 27 | assets/models/unit_run_2.obj \ 28 | assets/models/unit_run_3.obj \ 29 | assets/models/unit_run_4.obj \ 30 | assets/models/unit_fire.obj \ 31 | build/unit.rmf 32 | 33 | php pack_model.php assets/models/box.obj build/box.rmf 34 | php pack_model.php assets/models/nailgun.obj build/nailgun.rmf 35 | php pack_model.php \ 36 | assets/models/torch_1.obj \ 37 | assets/models/torch_2.obj \ 38 | assets/models/torch_3.obj \ 39 | build/torch.rmf 40 | 41 | # Concat all models into one file 42 | cat \ 43 | build/boulder.rmf \ 44 | build/unit.rmf \ 45 | build/grenade.rmf \ 46 | build/q.rmf \ 47 | build/hound.rmf \ 48 | build/box.rmf \ 49 | build/nailgun.rmf \ 50 | build/torch.rmf \ 51 | > build/m 52 | 53 | 54 | # Concat js Source 55 | cat \ 56 | source/wrap_pre.js \ 57 | source/document.js \ 58 | source/textures.js \ 59 | source/music.js \ 60 | source/audio.js \ 61 | source/input.js \ 62 | source/ttt.js \ 63 | source/math_utils.js \ 64 | source/renderer.js \ 65 | source/model.js \ 66 | source/map.js \ 67 | source/entity.js \ 68 | source/entity_player.js \ 69 | source/entity_door.js \ 70 | source/entity_light.js \ 71 | source/entity_torch.js \ 72 | source/entity_barrel.js \ 73 | source/entity_particle.js \ 74 | source/entity_projectile_grenade.js \ 75 | source/entity_projectile_nail.js \ 76 | source/entity_projectile_plasma.js \ 77 | source/entity_projectile_gib.js \ 78 | source/entity_projectile_shell.js \ 79 | source/entity_enemy.js \ 80 | source/entity_enemy_grunt.js \ 81 | source/entity_enemy_enforcer.js \ 82 | source/entity_enemy_ogre.js \ 83 | source/entity_enemy_zombie.js \ 84 | source/entity_enemy_hound.js \ 85 | source/entity_pickup.js \ 86 | source/entity_pickup_key.js \ 87 | source/entity_pickup_nailgun.js \ 88 | source/entity_pickup_grenadelauncher.js \ 89 | source/entity_pickup_health.js \ 90 | source/entity_pickup_nails.js \ 91 | source/entity_pickup_grenades.js \ 92 | source/entity_trigger_level.js \ 93 | source/weapons.js \ 94 | source/game.js \ 95 | source/main.js \ 96 | source/wrap_post.js \ 97 | > build/game.js 98 | 99 | 100 | # Compress WebGL calls, remove DEBUG[...] 101 | php pack_js.php build/game.js > build/game.packed.js 102 | 103 | # Uglify JS 104 | npx uglify-js build/game.packed.js \ 105 | --compress --mangle toplevel -c --beautify --mangle-props regex=/^_/ \ 106 | -o build/game.min.beauty.js 107 | 108 | npx uglify-js build/game.packed.js \ 109 | --compress --mangle toplevel --mangle-props regex=/^_/ \ 110 | -o build/game.min.js 111 | 112 | npx roadroller -Zab14 -Zlr930 -Zmd19 -Zpr14 -S0,1,2,3,7,13,14,19,58,97,305,422 build/game.min.js -o build/game.roadrolled.js 113 | 114 | # Embed source into HTML 115 | sed -e '/GAME_SOURCE/{r build/game.roadrolled.js' -e 'd;}' source/html_template.html > build/index.html 116 | 117 | # Build ZIP 118 | rm -f -- build/game.zip 119 | cd build 120 | zip game.zip index.html l m 121 | cd .. 122 | 123 | advzip -z -4 build/game.zip 124 | ls -la build/ 125 | -------------------------------------------------------------------------------- /build/dummy.txt: -------------------------------------------------------------------------------- 1 | dummy -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Q1K3 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /pack_js.php: -------------------------------------------------------------------------------- 1 | 35721, 4 | 'ACTIVE_ATTRIBUTE_MAX_LENGTH' => 35722, 5 | 'ACTIVE_TEXTURE' => 34016, 6 | 'ACTIVE_UNIFORMS' => 35718, 7 | 'ACTIVE_UNIFORM_MAX_LENGTH' => 35719, 8 | 'ALIASED_LINE_WIDTH_RANGE' => 33902, 9 | 'ALIASED_POINT_SIZE_RANGE' => 33901, 10 | 'ALPHA' => 6406, 11 | 'ALPHA_BITS' => 3413, 12 | 'ALWAYS' => 519, 13 | 'ARRAY_BUFFER' => 34962, 14 | 'ARRAY_BUFFER_BINDING' => 34964, 15 | 'ATTACHED_SHADERS' => 35717, 16 | 'BACK' => 1029, 17 | 'BLEND' => 3042, 18 | 'BLEND_COLOR' => 32773, 19 | 'BLEND_DST_ALPHA' => 32970, 20 | 'BLEND_DST_RGB' => 32968, 21 | 'BLEND_EQUATION' => 32777, 22 | 'BLEND_EQUATION_ALPHA' => 34877, 23 | 'BLEND_EQUATION_RGB' => 32777, 24 | 'BLEND_SRC_ALPHA' => 32971, 25 | 'BLEND_SRC_RGB' => 32969, 26 | 'BLUE_BITS' => 3412, 27 | 'BOOL' => 35670, 28 | 'BOOL_VEC2' => 35671, 29 | 'BOOL_VEC3' => 35672, 30 | 'BOOL_VEC4' => 35673, 31 | 'BROWSER_DEFAULT_WEBGL' => 37444, 32 | 'BUFFER_SIZE' => 34660, 33 | 'BUFFER_USAGE' => 34661, 34 | 'BYTE' => 5120, 35 | 'CCW' => 2305, 36 | 'CLAMP_TO_EDGE' => 33071, 37 | 'COLOR_ATTACHMENT0' => 36064, 38 | 'COLOR_BUFFER_BIT' => 16384, 39 | 'COLOR_CLEAR_VALUE' => 3106, 40 | 'COLOR_WRITEMASK' => 3107, 41 | 'COMPILE_STATUS' => 35713, 42 | 'COMPRESSED_TEXTURE_FORMATS' => 34467, 43 | 'CONSTANT_ALPHA' => 32771, 44 | 'CONSTANT_COLOR' => 32769, 45 | 'CONTEXT_LOST_WEBGL' => 37442, 46 | 'CULL_FACE' => 2884, 47 | 'CULL_FACE_MODE' => 2885, 48 | 'CURRENT_PROGRAM' => 35725, 49 | 'CURRENT_VERTEX_ATTRIB' => 34342, 50 | 'CW' => 2304, 51 | 'DECR' => 7683, 52 | 'DECR_WRAP' => 34056, 53 | 'DELETE_STATUS' => 35712, 54 | 'DEPTH_ATTACHMENT' => 36096, 55 | 'DEPTH_BITS' => 3414, 56 | 'DEPTH_BUFFER_BIT' => 256, 57 | 'DEPTH_CLEAR_VALUE' => 2931, 58 | 'DEPTH_COMPONENT' => 6402, 59 | 'DEPTH_COMPONENT16' => 33189, 60 | 'DEPTH_FUNC' => 2932, 61 | 'DEPTH_RANGE' => 2928, 62 | 'DEPTH_STENCIL' => 34041, 63 | 'DEPTH_STENCIL_ATTACHMENT' => 33306, 64 | 'DEPTH_TEST' => 2929, 65 | 'DEPTH_WRITEMASK' => 2930, 66 | 'DITHER' => 3024, 67 | 'DONT_CARE' => 4352, 68 | 'DST_ALPHA' => 772, 69 | 'DST_COLOR' => 774, 70 | 'DYNAMIC_DRAW' => 35048, 71 | 'ELEMENT_ARRAY_BUFFER' => 34963, 72 | 'ELEMENT_ARRAY_BUFFER_BINDING' => 34965, 73 | 'EQUAL' => 514, 74 | 'FASTEST' => 4353, 75 | 'FLOAT' => 5126, 76 | 'FLOAT_MAT2' => 35674, 77 | 'FLOAT_MAT3' => 35675, 78 | 'FLOAT_MAT4' => 35676, 79 | 'FLOAT_VEC2' => 35664, 80 | 'FLOAT_VEC3' => 35665, 81 | 'FLOAT_VEC4' => 35666, 82 | 'FRAGMENT_SHADER' => 35632, 83 | 'FRAMEBUFFER' => 36160, 84 | 'FRAMEBUFFER_ATTACHMENT_OBJECT_NAME' => 36049, 85 | 'FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE' => 36048, 86 | 'FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE' => 36051, 87 | 'FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL' => 36050, 88 | 'FRAMEBUFFER_BINDING' => 36006, 89 | 'FRAMEBUFFER_COMPLETE' => 36053, 90 | 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT' => 36054, 91 | 'FRAMEBUFFER_INCOMPLETE_DIMENSIONS' => 36057, 92 | 'FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT' => 36055, 93 | 'FRAMEBUFFER_UNSUPPORTED' => 36061, 94 | 'FRONT' => 1028, 95 | 'FRONT_AND_BACK' => 1032, 96 | 'FRONT_FACE' => 2886, 97 | 'FUNC_ADD' => 32774, 98 | 'FUNC_REVERSE_SUBTRACT' => 32779, 99 | 'FUNC_SUBTRACT' => 32778, 100 | 'GENERATE_MIPMAP_HINT' => 33170, 101 | 'GEQUAL' => 518, 102 | 'GREATER' => 516, 103 | 'GREEN_BITS' => 3411, 104 | 'HIGH_FLOAT' => 36338, 105 | 'HIGH_INT' => 36341, 106 | 'INCR' => 7682, 107 | 'INCR_WRAP' => 34055, 108 | 'INFO_LOG_LENGTH' => 35716, 109 | 'INT' => 5124, 110 | 'INT_VEC2' => 35667, 111 | 'INT_VEC3' => 35668, 112 | 'INT_VEC4' => 35669, 113 | 'INVALID_ENUM' => 1280, 114 | 'INVALID_FRAMEBUFFER_OPERATION' => 1286, 115 | 'INVALID_OPERATION' => 1282, 116 | 'INVALID_VALUE' => 1281, 117 | 'INVERT' => 5386, 118 | 'KEEP' => 7680, 119 | 'LEQUAL' => 515, 120 | 'LESS' => 513, 121 | 'LINEAR' => 9729, 122 | 'LINEAR_MIPMAP_LINEAR' => 9987, 123 | 'LINEAR_MIPMAP_NEAREST' => 9985, 124 | 'LINES' => 1, 125 | 'LINE_LOOP' => 2, 126 | 'LINE_STRIP' => 3, 127 | 'LINE_WIDTH' => 2849, 128 | 'LINK_STATUS' => 35714, 129 | 'LOW_FLOAT' => 36336, 130 | 'LOW_INT' => 36339, 131 | 'LUMINANCE' => 6409, 132 | 'LUMINANCE_ALPHA' => 6410, 133 | 'MAX_COMBINED_TEXTURE_IMAGE_UNITS' => 35661, 134 | 'MAX_CUBE_MAP_TEXTURE_SIZE' => 34076, 135 | 'MAX_FRAGMENT_UNIFORM_VECTORS' => 36349, 136 | 'MAX_RENDERBUFFER_SIZE' => 34024, 137 | 'MAX_TEXTURE_IMAGE_UNITS' => 34930, 138 | 'MAX_TEXTURE_SIZE' => 3379, 139 | 'MAX_VARYING_VECTORS' => 36348, 140 | 'MAX_VERTEX_ATTRIBS' => 34921, 141 | 'MAX_VERTEX_TEXTURE_IMAGE_UNITS' => 35660, 142 | 'MAX_VERTEX_UNIFORM_VECTORS' => 36347, 143 | 'MAX_VIEWPORT_DIMS' => 3386, 144 | 'MEDIUM_FLOAT' => 36337, 145 | 'MEDIUM_INT' => 36340, 146 | 'MIRRORED_REPEAT' => 33648, 147 | 'NEAREST' => 9728, 148 | 'NEAREST_MIPMAP_LINEAR' => 9986, 149 | 'NEAREST_MIPMAP_NEAREST' => 9984, 150 | 'NEVER' => 512, 151 | 'NICEST' => 4354, 152 | 'NONE' => 0, 153 | 'NOTEQUAL' => 517, 154 | 'NO_ERROR' => 0, 155 | 'NUM_COMPRESSED_TEXTURE_FORMATS' => 34466, 156 | 'ONE' => 1, 157 | 'ONE_MINUS_CONSTANT_ALPHA' => 32772, 158 | 'ONE_MINUS_CONSTANT_COLOR' => 32770, 159 | 'ONE_MINUS_DST_ALPHA' => 773, 160 | 'ONE_MINUS_DST_COLOR' => 775, 161 | 'ONE_MINUS_SRC_ALPHA' => 771, 162 | 'ONE_MINUS_SRC_COLOR' => 769, 163 | 'OUT_OF_MEMORY' => 1285, 164 | 'PACK_ALIGNMENT' => 3333, 165 | 'POINTS' => 0, 166 | 'POLYGON_OFFSET_FACTOR' => 32824, 167 | 'POLYGON_OFFSET_FILL' => 32823, 168 | 'POLYGON_OFFSET_UNITS' => 10752, 169 | 'RED_BITS' => 3410, 170 | 'RENDERBUFFER' => 36161, 171 | 'RENDERBUFFER_ALPHA_SIZE' => 36179, 172 | 'RENDERBUFFER_BINDING' => 36007, 173 | 'RENDERBUFFER_BLUE_SIZE' => 36178, 174 | 'RENDERBUFFER_DEPTH_SIZE' => 36180, 175 | 'RENDERBUFFER_GREEN_SIZE' => 36177, 176 | 'RENDERBUFFER_HEIGHT' => 36163, 177 | 'RENDERBUFFER_INTERNAL_FORMAT' => 36164, 178 | 'RENDERBUFFER_RED_SIZE' => 36176, 179 | 'RENDERBUFFER_STENCIL_SIZE' => 36181, 180 | 'RENDERBUFFER_WIDTH' => 36162, 181 | 'RENDERER' => 7937, 182 | 'REPEAT' => 10497, 183 | 'REPLACE' => 7681, 184 | 'RGB' => 6407, 185 | 'RGB5_A1' => 32855, 186 | 'RGB565' => 36194, 187 | 'RGBA' => 6408, 188 | 'RGBA4' => 32854, 189 | 'SAMPLER_2D' => 35678, 190 | 'SAMPLER_CUBE' => 35680, 191 | 'SAMPLES' => 32937, 192 | 'SAMPLE_ALPHA_TO_COVERAGE' => 32926, 193 | 'SAMPLE_BUFFERS' => 32936, 194 | 'SAMPLE_COVERAGE' => 32928, 195 | 'SAMPLE_COVERAGE_INVERT' => 32939, 196 | 'SAMPLE_COVERAGE_VALUE' => 32938, 197 | 'SCISSOR_BOX' => 3088, 198 | 'SCISSOR_TEST' => 3089, 199 | 'SHADER_COMPILER' => 36346, 200 | 'SHADER_SOURCE_LENGTH' => 35720, 201 | 'SHADER_TYPE' => 35663, 202 | 'SHADING_LANGUAGE_VERSION' => 35724, 203 | 'SHORT' => 5122, 204 | 'SRC_ALPHA' => 770, 205 | 'SRC_ALPHA_SATURATE' => 776, 206 | 'SRC_COLOR' => 768, 207 | 'STATIC_DRAW' => 35044, 208 | 'STENCIL_ATTACHMENT' => 36128, 209 | 'STENCIL_BACK_FAIL' => 34817, 210 | 'STENCIL_BACK_FUNC' => 34816, 211 | 'STENCIL_BACK_PASS_DEPTH_FAIL' => 34818, 212 | 'STENCIL_BACK_PASS_DEPTH_PASS' => 34819, 213 | 'STENCIL_BACK_REF' => 36003, 214 | 'STENCIL_BACK_VALUE_MASK' => 36004, 215 | 'STENCIL_BACK_WRITEMASK' => 36005, 216 | 'STENCIL_BITS' => 3415, 217 | 'STENCIL_BUFFER_BIT' => 1024, 218 | 'STENCIL_CLEAR_VALUE' => 2961, 219 | 'STENCIL_FAIL' => 2964, 220 | 'STENCIL_FUNC' => 2962, 221 | 'STENCIL_INDEX' => 6401, 222 | 'STENCIL_INDEX8' => 36168, 223 | 'STENCIL_PASS_DEPTH_FAIL' => 2965, 224 | 'STENCIL_PASS_DEPTH_PASS' => 2966, 225 | 'STENCIL_REF' => 2967, 226 | 'STENCIL_TEST' => 2960, 227 | 'STENCIL_VALUE_MASK' => 2963, 228 | 'STENCIL_WRITEMASK' => 2968, 229 | 'STREAM_DRAW' => 35040, 230 | 'SUBPIXEL_BITS' => 3408, 231 | 'TEXTURE' => 5890, 232 | 'TEXTURE0' => 33984, 233 | 'TEXTURE1' => 33985, 234 | 'TEXTURE2' => 33986, 235 | 'TEXTURE3' => 33987, 236 | 'TEXTURE4' => 33988, 237 | 'TEXTURE5' => 33989, 238 | 'TEXTURE6' => 33990, 239 | 'TEXTURE7' => 33991, 240 | 'TEXTURE8' => 33992, 241 | 'TEXTURE9' => 33993, 242 | 'TEXTURE10' => 33994, 243 | 'TEXTURE11' => 33995, 244 | 'TEXTURE12' => 33996, 245 | 'TEXTURE13' => 33997, 246 | 'TEXTURE14' => 33998, 247 | 'TEXTURE15' => 33999, 248 | 'TEXTURE16' => 34000, 249 | 'TEXTURE17' => 34001, 250 | 'TEXTURE18' => 34002, 251 | 'TEXTURE19' => 34003, 252 | 'TEXTURE20' => 34004, 253 | 'TEXTURE21' => 34005, 254 | 'TEXTURE22' => 34006, 255 | 'TEXTURE23' => 34007, 256 | 'TEXTURE24' => 34008, 257 | 'TEXTURE25' => 34009, 258 | 'TEXTURE26' => 34010, 259 | 'TEXTURE27' => 34011, 260 | 'TEXTURE28' => 34012, 261 | 'TEXTURE29' => 34013, 262 | 'TEXTURE30' => 34014, 263 | 'TEXTURE31' => 34015, 264 | 'TEXTURE_2D' => 3553, 265 | 'TEXTURE_BINDING_2D' => 32873, 266 | 'TEXTURE_BINDING_CUBE_MAP' => 34068, 267 | 'TEXTURE_CUBE_MAP' => 34067, 268 | 'TEXTURE_CUBE_MAP_NEGATIVE_X' => 34070, 269 | 'TEXTURE_CUBE_MAP_NEGATIVE_Y' => 34072, 270 | 'TEXTURE_CUBE_MAP_NEGATIVE_Z' => 34074, 271 | 'TEXTURE_CUBE_MAP_POSITIVE_X' => 34069, 272 | 'TEXTURE_CUBE_MAP_POSITIVE_Y' => 34071, 273 | 'TEXTURE_CUBE_MAP_POSITIVE_Z' => 34073, 274 | 'TEXTURE_MAG_FILTER' => 10240, 275 | 'TEXTURE_MIN_FILTER' => 10241, 276 | 'TEXTURE_WRAP_S' => 10242, 277 | 'TEXTURE_WRAP_T' => 10243, 278 | 'TRIANGLES' => 4, 279 | 'TRIANGLE_FAN' => 6, 280 | 'TRIANGLE_STRIP' => 5, 281 | 'UNPACK_ALIGNMENT' => 3317, 282 | 'UNPACK_COLORSPACE_CONVERSION_WEBGL' => 37443, 283 | 'UNPACK_FLIP_Y_WEBGL' => 37440, 284 | 'UNPACK_PREMULTIPLY_ALPHA_WEBGL' => 37441, 285 | 'UNSIGNED_BYTE' => 5121, 286 | 'UNSIGNED_INT' => 5125, 287 | 'UNSIGNED_SHORT' => 5123, 288 | 'UNSIGNED_SHORT_4_4_4_4' => 32819, 289 | 'UNSIGNED_SHORT_5_5_5_1' => 32820, 290 | 'UNSIGNED_SHORT_5_6_5' => 33635, 291 | 'VALIDATE_STATUS' => 35715, 292 | 'VENDOR' => 7936, 293 | 'VERSION' => 7938, 294 | 'VERTEX_ATTRIB_ARRAY_BUFFER_BINDING' => 34975, 295 | 'VERTEX_ATTRIB_ARRAY_ENABLED' => 34338, 296 | 'VERTEX_ATTRIB_ARRAY_NORMALIZED' => 34922, 297 | 'VERTEX_ATTRIB_ARRAY_POINTER' => 34373, 298 | 'VERTEX_ATTRIB_ARRAY_SIZE' => 34339, 299 | 'VERTEX_ATTRIB_ARRAY_STRIDE' => 34340, 300 | 'VERTEX_ATTRIB_ARRAY_TYPE' => 34341, 301 | 'VERTEX_SHADER' => 35633, 302 | 'VIEWPORT' => 2978, 303 | 'ZERO' => 0, 304 | 305 | 'activeTexture' => "gl.acT", 306 | 'attachShader' => "gl.atS", 307 | 'bindAttribLocation' => "gl.biAL", 308 | 'bindBuffer' => "gl.biB", 309 | 'bindFramebuffer' => "gl.biF", 310 | 'bindRenderbuffer' => "gl.biR", 311 | 'bindTexture' => "gl.biT", 312 | 'blendColor' => "gl.blC", 313 | 'blendEquation' => "gl.blE", 314 | 'blendEquationSeparate' => "gl.blES", 315 | 'blendFunc' => "gl.blF", 316 | 'blendFuncSeparate' => "gl.blFS", 317 | 'bufferData' => "gl.buD", 318 | 'bufferSubData' => "gl.buSD", 319 | 'checkFramebufferStatus' => "gl.chFS", 320 | 'clear' => "gl.cl", 321 | 'clearColor' => "gl.clC", 322 | 'clearDepth' => "gl.clD", 323 | 'clearStencil' => "gl.clS", 324 | 'colorMask' => "gl.coM", 325 | 'compileShader' => "gl.coS", 326 | 'compressedTexImage2D' => "gl.coTI2D", 327 | 'compressedTexSubImage2D' => "gl.coTSI2D", 328 | 'copyTexImage2D' => "gl.coTI2D", 329 | 'copyTexSubImage2D' => "gl.coTSI2D", 330 | 'createBuffer' => "gl.crB", 331 | 'createFramebuffer' => "gl.crF", 332 | 'createProgram' => "gl.crP", 333 | 'createRenderbuffer' => "gl.crR", 334 | 'createShader' => "gl.crS", 335 | 'createTexture' => "gl.crT", 336 | 'cullFace' => "gl.cuF", 337 | 'deleteBuffer' => "gl.deB", 338 | 'deleteFramebuffer' => "gl.deF", 339 | 'deleteProgram' => "gl.deP", 340 | 'deleteRenderbuffer' => "gl.deR", 341 | 'deleteShader' => "gl.deS", 342 | 'deleteTexture' => "gl.deT", 343 | 'depthFunc' => "gl.deF", 344 | 'depthMask' => "gl.deM", 345 | 'depthRange' => "gl.deR", 346 | 'detachShader' => "gl.deS", 347 | 'disable' => "gl.di", 348 | 'disableVertexAttribArray' => "gl.diVAA", 349 | 'drawArrays' => "gl.drA", 350 | 'drawElements' => "gl.drE", 351 | 'enable' => "gl.en", 352 | 'enableVertexAttribArray' => "gl.enVAA", 353 | 'finish' => "gl.fi", 354 | 'flush' => "gl.fl", 355 | 'framebufferRenderbuffer' => "gl.frR", 356 | 'framebufferTexture2D' => "gl.frT2D", 357 | 'frontFace' => "gl.frF", 358 | 'generateMipmap' => "gl.geM", 359 | 'getActiveAttrib' => "gl.geAA", 360 | 'getActiveUniform' => "gl.geAU", 361 | 'getAttachedShaders' => "gl.geAS", 362 | 'getAttribLocation' => "gl.geAL", 363 | 'getBufferParameter' => "gl.geBP", 364 | 'getContextAttributes' => "gl.geCA", 365 | 'getError' => "gl.geE", 366 | 'getExtension' => "gl.geE", 367 | 'getFramebufferAttachmentParameter' => "gl.geFAP", 368 | 'getParameter' => "gl.geP", 369 | 'getProgramParameter' => "gl.gePP", 370 | 'getProgramInfoLog' => "gl.gePIL", 371 | 'getRenderbufferParameter' => "gl.geRP", 372 | 'getShaderParameter' => "gl.geSP", 373 | 'getShaderInfoLog' => "gl.geSIL", 374 | 'getShaderPrecisionFormat' => "gl.geSPF", 375 | 'getShaderSource' => "gl.geSS", 376 | 'getSupportedExtensions' => "gl.geSE", 377 | 'getTexParameter' => "gl.geTP", 378 | 'getUniform' => "gl.geU", 379 | 'getUniformLocation' => "gl.geUL", 380 | 'getVertexAttrib' => "gl.geVA", 381 | 'getVertexAttribOffset' => "gl.geVAO", 382 | 'hint' => "gl.hi", 383 | 'isBuffer' => "gl.isB", 384 | 'isContextLost' => "gl.isCL", 385 | 'isEnabled' => "gl.isE", 386 | 'isFramebuffer' => "gl.isF", 387 | 'isProgram' => "gl.isP", 388 | 'isRenderbuffer' => "gl.isR", 389 | 'isShader' => "gl.isS", 390 | 'isTexture' => "gl.isT", 391 | 'lineWidth' => "gl.liW", 392 | 'linkProgram' => "gl.liP", 393 | 'pixelStorei' => "gl.piS", 394 | 'polygonOffset' => "gl.poO", 395 | 'readPixels' => "gl.reP", 396 | 'renderbufferStorage' => "gl.reS", 397 | 'sampleCoverage' => "gl.saC", 398 | 'scissor' => "gl.sc", 399 | 'shaderSource' => "gl.shS", 400 | 'stencilFunc' => "gl.stF", 401 | 'stencilFuncSeparate' => "gl.stFS", 402 | 'stencilMask' => "gl.stM", 403 | 'stencilMaskSeparate' => "gl.stMS", 404 | 'stencilOp' => "gl.stO", 405 | 'stencilOpSeparate' => "gl.stOS", 406 | 'texParameterf' => "gl.teP", 407 | 'texParameteri' => "gl.teP", 408 | 'texImage2D' => "gl.teI2D", 409 | 'texSubImage2D' => "gl.teSI2D", 410 | 'uniform1f' => "gl.un1f", 411 | 'uniform1fv' => "gl.un1fv", 412 | 'uniform1i' => "gl.un1i", 413 | 'uniform1iv' => "gl.un1iv", 414 | 'uniform2f' => "gl.un2f", 415 | 'uniform2fv' => "gl.un2fv", 416 | 'uniform2i' => "gl.un2i", 417 | 'uniform2iv' => "gl.un2iv", 418 | 'uniform3f' => "gl.un3f", 419 | 'uniform3fv' => "gl.un3fv", 420 | 'uniform3i' => "gl.un3i", 421 | 'uniform3iv' => "gl.un3iv", 422 | 'uniform4f' => "gl.un4f", 423 | 'uniform4fv' => "gl.un4fv", 424 | 'uniform4i' => "gl.un4i", 425 | 'uniform4iv' => "gl.un4iv", 426 | 'uniformMatrix2fv' => "gl.unM2fv", 427 | 'uniformMatrix3fv' => "gl.unM3fv", 428 | 'uniformMatrix4fv' => "gl.unM4fv", 429 | 'useProgram' => "gl.usP", 430 | 'validateProgram' => "gl.vaP", 431 | 'vertexAttrib1f' => "gl.veA1f", 432 | 'vertexAttrib1fv' => "gl.veA1fv", 433 | 'vertexAttrib2f' => "gl.veA2f", 434 | 'vertexAttrib2fv' => "gl.veA2fv", 435 | 'vertexAttrib3f' => "gl.veA3f", 436 | 'vertexAttrib3fv' => "gl.veA3fv", 437 | 'vertexAttrib4f' => "gl.veA4f", 438 | 'vertexAttrib4fv' => "gl.veA4fv", 439 | 'vertexAttribPointer' => "gl.veAP", 440 | 'viewport' => "gl.vi" 441 | ]; 442 | 443 | $js = file_get_contents($argv[1]); 444 | foreach ($WEBGL_REPLACE as $search => $replace) { 445 | $js = preg_replace('/\bgl\.'.$search.'\b/', $replace, $js); 446 | } 447 | $js = preg_replace('/\/\*DEBUG\[.*?\/\*\]\*\//sm', '', $js); 448 | echo $js; 449 | 450 | -------------------------------------------------------------------------------- /pack_map.c: -------------------------------------------------------------------------------- 1 | // Compile with 2 | // gcc -std=c99 map_packer.c -lm -o map_packer 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // ----------------------------------------------------------------------------- 14 | // Utils, Vec2/3 15 | 16 | typedef uint8_t u8; 17 | typedef int8_t i8; 18 | typedef uint16_t u16; 19 | typedef int16_t i16; 20 | typedef uint32_t u32; 21 | typedef int32_t i32; 22 | typedef uint64_t u64; 23 | typedef int64_t i64; 24 | 25 | typedef float f32; 26 | typedef double f64; 27 | 28 | #define F32_COMPARE_EPSILON 0.001 29 | 30 | bool equals(f32 a, f32 b) { 31 | return fabs(a - b) < F32_COMPARE_EPSILON; 32 | } 33 | 34 | #define M_PI 3.14159265358 35 | 36 | f32 deg_to_rad(f32 deg) { 37 | return (deg/180.0) * M_PI; 38 | } 39 | 40 | #define max(a,b) \ 41 | ({ __typeof__ (a) _a = (a); \ 42 | __typeof__ (b) _b = (b); \ 43 | _a > _b ? _a : _b; }) 44 | #define min(a,b) \ 45 | ({ __typeof__ (a) _a = (a); \ 46 | __typeof__ (b) _b = (b); \ 47 | _a < _b ? _a : _b; }) 48 | 49 | 50 | typedef struct { 51 | char *ptr; 52 | i32 length; 53 | } string_t; 54 | 55 | #define string(PTR, LENGTH) ((string_t){.ptr = PTR, .length = LENGTH}) 56 | #define s(STR) ((string_t){.ptr = STR, .length = sizeof(STR)-1}) 57 | 58 | bool string_equals(string_t a, string_t b) { 59 | if (a.length != b.length) { 60 | return false; 61 | } 62 | return memcmp(a.ptr, b.ptr, a.length) == 0; 63 | } 64 | 65 | char string_temp[1024]; 66 | char *string_cstring(string_t s) { 67 | i32 length = min(sizeof(string_temp)-1, s.length); 68 | memcpy(string_temp, s.ptr, length); 69 | string_temp[length] = '\0'; 70 | return string_temp; 71 | } 72 | 73 | 74 | 75 | typedef struct { 76 | f32 x, y, z; 77 | } vec3_t; 78 | 79 | #define vec3(X, Y, Z) ((vec3_t){.x = X, .y = Y, .z = Z}) 80 | 81 | vec3_t vec3_add(vec3_t a, vec3_t b) { 82 | return vec3(a.x + b.x, a.y + b.y, a.z + b.z); 83 | } 84 | 85 | vec3_t vec3_sub(vec3_t a, vec3_t b) { 86 | return vec3(a.x - b.x, a.y - b.y, a.z - b.z); 87 | } 88 | 89 | vec3_t vec3_mul(vec3_t a, vec3_t b) { 90 | return vec3(a.x * b.x, a.y * b.y, a.z * b.z); 91 | } 92 | 93 | vec3_t vec3_mulf(vec3_t a, f32 f) { 94 | return vec3(a.x * f, a.y * f, a.z * f); 95 | } 96 | 97 | vec3_t vec3_divf(vec3_t a, f32 f) { 98 | return vec3(a.x / f, a.y / f, a.z / f); 99 | } 100 | 101 | bool vec3_equals(vec3_t a, vec3_t b) { 102 | return (equals(a.x, b.x) && equals(a.y, b.y) && equals(a.z, b.z)); 103 | } 104 | 105 | f32 vec3_length(vec3_t a) { 106 | return sqrt(a.x * a.x + a.y * a.y + a.z * a.z); 107 | } 108 | 109 | vec3_t vec3_cross(vec3_t a, vec3_t b) { 110 | return vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); 111 | } 112 | 113 | f32 vec3_dot(vec3_t a, vec3_t b) { 114 | return a.x * b.x + a.y * b.y + a.z * b.z; 115 | } 116 | 117 | vec3_t vec3_normalize(vec3_t a) { 118 | f32 length = vec3_length(a); 119 | return vec3(a.x / length, a.y / length, a.z / length); 120 | } 121 | 122 | vec3_t vec3_face_normal(vec3_t v0, vec3_t v1, vec3_t v2) { 123 | vec3_t a = vec3_sub(v0, v1); 124 | vec3_t b = vec3_sub(v2, v1); 125 | 126 | vec3_t pn = vec3_cross(a, b); 127 | return vec3_normalize(pn); 128 | } 129 | 130 | 131 | typedef struct { 132 | f32 x, y; 133 | } vec2_t; 134 | 135 | #define vec2(X, Y) ((vec2_t){.x = X, .y = Y}) 136 | 137 | vec2_t vec2_add(vec2_t a, vec2_t b) { 138 | return vec2(a.x + b.x, a.y + b.y); 139 | } 140 | 141 | vec2_t vec2_rotate(vec2_t a, f32 rad) { 142 | return vec2(a.x * cos(rad) - a.y * sin(rad), a.x * sin(rad) + a.y * cos(rad)); 143 | } 144 | 145 | vec2_t vec2_sub(vec2_t a, vec2_t b) { 146 | return vec2(a.x - b.x, a.y - b.y); 147 | } 148 | 149 | vec2_t vec2_mul(vec2_t a, vec2_t b) { 150 | return vec2(a.x * b.x, a.y * b.y); 151 | } 152 | 153 | vec2_t vec2_mulf(vec2_t a, float f) { 154 | return vec2(a.x * f, a.y * f); 155 | } 156 | 157 | vec2_t vec2_div(vec2_t a, vec2_t b) { 158 | return vec2(a.x / b.x, a.y / b.y); 159 | } 160 | 161 | vec2_t vec2_divf(vec2_t a, float f) { 162 | return vec2(a.x / f, a.y / f); 163 | } 164 | 165 | 166 | // ----------------------------------------------------------------------------- 167 | // Memvec 168 | 169 | #define memvec_declare(TYPE) struct { u32 length; u32 capacity; TYPE *data; } 170 | #define memvec_alloc(MV, c) \ 171 | MV.capacity = max(1, c); \ 172 | MV.length = 0; \ 173 | MV.data = calloc(MV.capacity, sizeof(MV.data[0])); 174 | 175 | #define memvec_ensure_capacity(MV, CAPACITY) ( \ 176 | (CAPACITY > MV.capacity) \ 177 | ? \ 178 | MV.data = realloc(MV.data, sizeof(MV.data[0]) * max(CAPACITY, MV.capacity * 2)), \ 179 | memset(MV.data + MV.capacity, 0, MV.capacity * sizeof(MV.data[0])), \ 180 | MV.capacity *= 2, 0 \ 181 | : 0) 182 | 183 | #define memvec_get(MV, i) &MV.data[i] 184 | #define memvec_clear(MV) MV.length = 0 185 | #define memvec_free(MV) (MV.data ? free(MV.data), 0 : 0); 186 | #define memvec_push(MV, ...) ( \ 187 | memvec_ensure_capacity(MV, MV.length + 1), \ 188 | MV.data[MV.length] = (__typeof__(*MV.data)) __VA_ARGS__, \ 189 | MV.length++ \ 190 | ) 191 | #define memvec_add_n(MV, n) (memvec_ensure_capacity(MV, MV.length + n), MV.length += n, &MV.data[MV.length-n]) 192 | #define memvec_add(MV) memvec_add_n(MV, 1) 193 | 194 | 195 | // ----------------------------------------------------------------------------- 196 | // Winding 197 | 198 | #define WINDING_MAX_VERTS 32 199 | 200 | typedef struct { 201 | vec3_t pos; 202 | vec2_t uv; 203 | } winding_vertex_t; 204 | 205 | typedef struct { 206 | vec3_t normal; 207 | u32 num_vertices; 208 | winding_vertex_t vertices[WINDING_MAX_VERTS]; 209 | } winding_t; 210 | 211 | void winding_copy(winding_t *dst, winding_t *src) { 212 | u32 base_size = sizeof(winding_t) - sizeof(winding_vertex_t) * WINDING_MAX_VERTS; 213 | memcpy(dst, src, base_size + src->num_vertices * sizeof(winding_vertex_t)); 214 | } 215 | 216 | void winding_add(winding_t *self, vec3_t pos, vec3_t normal, vec2_t uv) { 217 | // Find duplicate 218 | for (u32 i = 0; i < self->num_vertices; i++) { 219 | if (vec3_equals(self->vertices[i].pos, pos)) { 220 | return; 221 | } 222 | } 223 | 224 | // We may need one extra vertex when splitting! 225 | assert(self->num_vertices < WINDING_MAX_VERTS-1); 226 | 227 | self->normal = normal; 228 | self->vertices[self->num_vertices++] = (winding_vertex_t){ 229 | .pos = pos, 230 | .uv = uv 231 | }; 232 | } 233 | 234 | vec3_t winding_sort_center; 235 | vec3_t winding_sort_basis; 236 | vec3_t winding_sort_normal; 237 | 238 | i32 winding_compare(const void *vlp, const void *vrp) { 239 | winding_vertex_t *vl = (winding_vertex_t *)vlp; 240 | winding_vertex_t *vr = (winding_vertex_t *)vrp; 241 | 242 | vec3_t u = vec3_normalize(winding_sort_basis); 243 | vec3_t v = vec3_normalize(vec3_cross(u, winding_sort_normal)); 244 | 245 | vec3_t local_lhs = vec3_sub(vl->pos, winding_sort_center); 246 | f32 lhs_pu = vec3_dot(local_lhs, u); 247 | f32 lhs_pv = vec3_dot(local_lhs, v); 248 | 249 | vec3_t local_rhs = vec3_sub(vr->pos, winding_sort_center); 250 | f32 rhs_pu = vec3_dot(local_rhs, u); 251 | f32 rhs_pv = vec3_dot(local_rhs, v); 252 | 253 | f32 lhs_angle = atan2(lhs_pv, lhs_pu); 254 | f32 rhs_angle = atan2(rhs_pv, rhs_pu); 255 | 256 | if (lhs_angle < rhs_angle) { 257 | return 1; 258 | } 259 | else if (lhs_angle > rhs_angle) { 260 | return -1; 261 | } 262 | return 0; 263 | } 264 | 265 | void winding_sort_ccw(winding_t *self) { 266 | winding_sort_normal = self->normal; 267 | winding_sort_basis = vec3_sub(self->vertices[1].pos, self->vertices[0].pos); 268 | winding_sort_center = vec3(0, 0, 0); 269 | for (u32 i = 0; i < self->num_vertices; i++) { 270 | winding_sort_center = vec3_add(winding_sort_center, self->vertices[i].pos); 271 | } 272 | winding_sort_center = vec3_divf(winding_sort_center, self->num_vertices); 273 | 274 | qsort(self->vertices, self->num_vertices, sizeof(winding_vertex_t), winding_compare); 275 | } 276 | 277 | 278 | // ----------------------------------------------------------------------------- 279 | // Interface 280 | 281 | typedef struct { 282 | vec3_t pos; 283 | vec3_t normal; 284 | vec2_t uv; 285 | } vertex_t; 286 | 287 | typedef struct { 288 | vertex_t vertices[3]; 289 | } face_t; 290 | 291 | typedef struct { 292 | vec3_t vertices[3]; 293 | vec3_t normal; 294 | vec2_t uv_offset; 295 | vec2_t uv_scale; 296 | f32 uv_rotation; 297 | f32 dist; 298 | string_t texture_name; 299 | } map_plane_t; 300 | 301 | typedef struct { 302 | u32 planes_index; 303 | u32 planes_length; 304 | map_plane_t *planes; 305 | 306 | u32 faces_index; 307 | u32 faces_length; 308 | face_t *faces; 309 | } map_brush_t; 310 | 311 | typedef struct { 312 | u32 kvs_index; 313 | u32 kvs_length; 314 | 315 | u32 brushes_index; 316 | u32 brushes_length; 317 | 318 | u32 faces_index; 319 | u32 faces_length; 320 | } map_entity_t; 321 | 322 | typedef struct { 323 | string_t key; 324 | string_t value; 325 | } entity_kv_t; 326 | 327 | typedef struct { 328 | entity_kv_t *kvs; 329 | u32 kvs_length; 330 | 331 | map_brush_t *brushes; 332 | u32 brushes_length; 333 | 334 | face_t *faces; 335 | u32 faces_length; 336 | } entity_t; 337 | 338 | string_t entity_get(entity_t *entity, string_t key) { 339 | for (u32 i = 0; i < entity->kvs_length; i++) { 340 | if (string_equals(key, entity->kvs[i].key)) { 341 | return entity->kvs[i].value; 342 | } 343 | } 344 | return s(""); 345 | } 346 | 347 | typedef struct { 348 | string_t file; 349 | char *char_ptr; 350 | 351 | char *error; 352 | char *error_at; 353 | 354 | memvec_declare(map_entity_t) map_entities; 355 | memvec_declare(map_brush_t) brushes; 356 | memvec_declare(map_plane_t) planes; 357 | memvec_declare(face_t) faces; 358 | memvec_declare(entity_kv_t) entity_kvs; 359 | 360 | entity_t *entities; 361 | u32 entities_length; 362 | } map_t; 363 | 364 | void map_delete(map_t *self); 365 | void map_parse(map_t *self); 366 | void map_build(map_t *self); 367 | 368 | 369 | map_t *map_new(char *file_name, char **error, u32 *error_at) { 370 | FILE *fh = fopen(file_name, "rb"); 371 | if (!fh) { 372 | *error = "Couldn't open file"; 373 | *error_at = 0; 374 | return NULL; 375 | } 376 | 377 | map_t *self = calloc(1, sizeof(map_t)); 378 | 379 | fseek(fh, 0, SEEK_END); 380 | i32 size = ftell(fh); 381 | fseek(fh, 0, SEEK_SET); 382 | 383 | self->file = string(calloc(size+1, sizeof(char)), size); 384 | fread(self->file.ptr, 1, size, fh); 385 | fclose(fh); 386 | self->file.ptr[size] = '\0'; 387 | 388 | self->char_ptr = self->file.ptr; 389 | 390 | // Make some educated guesses of the buffer sizes we might need 391 | memvec_alloc(self->map_entities, self->file.length / 1000); 392 | memvec_alloc(self->brushes, self->file.length / 300); 393 | memvec_alloc(self->planes, self->file.length / 50); 394 | memvec_alloc(self->entity_kvs, self->map_entities.capacity * 3); 395 | 396 | map_parse(self); 397 | 398 | if (self->error) { 399 | *error = self->error; 400 | *error_at = self->error_at - self->file.ptr; 401 | free(self); 402 | return NULL; 403 | } 404 | 405 | memvec_alloc(self->faces, self->planes.length * 3); 406 | map_build(self); 407 | 408 | // Fill the entity_desc array for clients, now that we are sure we 409 | // don't need to realloc any data and pointers stay fixed 410 | self->entities_length = self->map_entities.length; 411 | self->entities = calloc(self->entities_length, sizeof(entity_t)); 412 | for (u32 i = 0; i < self->map_entities.length; i++) { 413 | map_entity_t *entity = memvec_get(self->map_entities, i); 414 | 415 | self->entities[i] = (entity_t){ 416 | .kvs = (entity->kvs_length > 0) 417 | ? &self->entity_kvs.data[entity->kvs_index] 418 | : NULL, 419 | .kvs_length = entity->kvs_length, 420 | .brushes = (entity->brushes_length > 0) 421 | ? &self->brushes.data[entity->brushes_index] 422 | : NULL, 423 | .brushes_length = entity->brushes_length, 424 | .faces = (entity->faces_length > 0) 425 | ? &self->faces.data[entity->faces_index] 426 | : NULL, 427 | .faces_length = entity->faces_length, 428 | }; 429 | 430 | for (u32 b = 0; b < entity->brushes_length; b++) { 431 | map_brush_t *brush = &self->brushes.data[entity->brushes_index + b]; 432 | brush->faces = (brush->faces_length > 0) 433 | ? &self->faces.data[brush->faces_index] 434 | : NULL; 435 | brush->planes = (brush->planes_length > 0) 436 | ? &self->planes.data[brush->planes_index] 437 | : NULL; 438 | } 439 | } 440 | 441 | return self; 442 | } 443 | 444 | void map_delete(map_t *self) { 445 | memvec_free(self->brushes); 446 | memvec_free(self->planes); 447 | memvec_free(self->map_entities); 448 | memvec_free(self->entity_kvs); 449 | memvec_free(self->faces); 450 | free(self->entities); 451 | free(self->file.ptr); 452 | } 453 | 454 | 455 | // ----------------------------------------------------------------------------- 456 | // Vertex Builder 457 | 458 | void map_build_entity(map_t *self, map_entity_t *entity); 459 | void map_build_brush(map_t *self, map_brush_t *brush); 460 | void map_build_planes(map_t *self, map_brush_t *brush, u32 i0, u32 i1, u32 i2, winding_t *winding); 461 | void map_build_faces(map_t *self, map_plane_t *plane, winding_t *winding); 462 | void map_insert_winding(map_t *self, winding_t *winding); 463 | 464 | void map_build(map_t *self) { 465 | 466 | // Load textures 467 | for (int i = 0; i < self->planes.length; i++) { 468 | // Unused 469 | // self->planes.data[i].texture_name 470 | } 471 | 472 | // Build all map_entities 473 | for (int i = 0; i < self->map_entities.length; i++) { 474 | map_entity_t *entity = memvec_get(self->map_entities, i); 475 | map_build_entity(self, entity); 476 | } 477 | } 478 | 479 | void map_build_entity(map_t *self, map_entity_t *entity) { 480 | entity->faces_index = self->faces.length; 481 | 482 | for (int i = 0; i < entity->brushes_length; i++) { 483 | map_brush_t *brush = &self->brushes.data[entity->brushes_index + i]; 484 | map_build_brush(self, brush); 485 | } 486 | 487 | entity->faces_length = self->faces.length - entity->faces_index; 488 | } 489 | 490 | void map_build_brush(map_t *self, map_brush_t *brush) { 491 | winding_t windings[brush->planes_length]; 492 | memset(windings, 0, sizeof(windings)); 493 | 494 | // Test all plane combinations to find intersection points 495 | for (u32 i0 = 0; i0 < brush->planes_length; i0++) { 496 | for (u32 i1 = i0 + 1; i1 < brush->planes_length; i1++) { 497 | for (u32 i2 = i1 + 1; i2 < brush->planes_length; i2++) { 498 | map_build_planes(self, brush, i0, i1, i2, windings); 499 | } 500 | } 501 | } 502 | 503 | // Sort the raw vertices and fill the self->faces buffer 504 | brush->faces_index = self->faces.length; 505 | brush->faces_length = brush->planes_length; 506 | for (u32 i = 0; i < brush->planes_length; i++) { 507 | map_plane_t *plane = &self->planes.data[brush->planes_index + i]; 508 | map_build_faces(self, plane, &windings[i]); 509 | } 510 | } 511 | 512 | void map_build_planes(map_t *self, map_brush_t *brush, u32 i0, u32 i1, u32 i2, winding_t *winding) { 513 | map_plane_t *p0 = &self->planes.data[brush->planes_index + i0]; 514 | map_plane_t *p1 = &self->planes.data[brush->planes_index + i1]; 515 | map_plane_t *p2 = &self->planes.data[brush->planes_index + i2]; 516 | 517 | // Plane intersection 518 | f32 denom = vec3_dot(vec3_cross(p0->normal, p1->normal), p2->normal); 519 | if (equals(denom, 0)) { 520 | return; 521 | } 522 | 523 | vec3_t p0d = vec3_mulf(vec3_cross(p1->normal, p2->normal), p0->dist); 524 | vec3_t p1d = vec3_mulf(vec3_cross(p2->normal, p0->normal), p1->dist); 525 | vec3_t p2d = vec3_mulf(vec3_cross(p0->normal, p1->normal), p2->dist); 526 | vec3_t intersection = vec3_divf(vec3_add(vec3_add(p0d, p1d), p2d), denom); 527 | 528 | // Make sure the produced intersection point is within the hull 529 | for (u32 i = 0; i < brush->planes_length; i++) { 530 | map_plane_t *plane = &self->planes.data[brush->planes_index + i]; 531 | f32 proj = vec3_dot(plane->normal, intersection); 532 | if (proj - plane->dist > F32_COMPARE_EPSILON) { 533 | return; 534 | } 535 | } 536 | 537 | // Add the intersection point to all 3 planes 538 | winding_add(&winding[i0], intersection, p0->normal, vec2(0, 0)); 539 | winding_add(&winding[i1], intersection, p1->normal, vec2(0, 0)); 540 | winding_add(&winding[i2], intersection, p2->normal, vec2(0, 0)); 541 | } 542 | 543 | void map_build_faces(map_t *self, map_plane_t *plane, winding_t *winding) { 544 | // Sort vertices by winding 545 | winding_sort_ccw(winding); 546 | 547 | // Compute UV coords 548 | f32 du = fabs(vec3_dot(plane->normal, vec3(0, 0, 1))); 549 | f32 dr = fabs(vec3_dot(plane->normal, vec3(0, 1, 0))); 550 | f32 df = fabs(vec3_dot(plane->normal, vec3(1, 0, 0))); 551 | 552 | vec3_t axis_u, axis_v; 553 | if (du >= dr && du >= df) { // project z axis 554 | axis_u = vec3(1, 0, 0); 555 | axis_v = vec3(0, 1, 0); 556 | } 557 | else if (dr > du && dr > df) { // project y axis 558 | axis_u = vec3(1, 0, 0); 559 | axis_v = vec3(0, 0, 1); 560 | } 561 | else { // (df >= du && df >= dr) project x axis 562 | axis_u = vec3(0, -1, 0); 563 | axis_v = vec3(0, 0, 1); 564 | } 565 | 566 | vec2_t tsize = vec2(64, 64); 567 | for (u32 i = 0; i < winding->num_vertices; i++) { 568 | vec3_t p = winding->vertices[i].pos; 569 | vec2_t uv = vec2(vec3_dot(p, axis_u), vec3_dot(p, axis_v)); 570 | 571 | uv = vec2_rotate(uv, deg_to_rad(plane->uv_rotation)); 572 | uv = vec2_div(uv, plane->uv_scale); 573 | uv = vec2_add(uv, plane->uv_offset); 574 | uv = vec2_div(uv, tsize); 575 | 576 | winding->vertices[i].uv = uv; 577 | } 578 | 579 | map_insert_winding(self, winding); 580 | } 581 | 582 | void map_insert_winding(map_t *self, winding_t *winding) { 583 | for (u32 vi = 2; vi < winding->num_vertices; vi++) { 584 | face_t *face = memvec_add(self->faces); 585 | 586 | face->vertices[0] = (vertex_t){ 587 | .pos = winding->vertices[0].pos, 588 | .normal = winding->normal, 589 | .uv = winding->vertices[0].uv 590 | }; 591 | 592 | face->vertices[1] = (vertex_t){ 593 | .pos = winding->vertices[vi-1].pos, 594 | .normal = winding->normal, 595 | .uv = winding->vertices[vi-1].uv 596 | }; 597 | face->vertices[2] = (vertex_t){ 598 | .pos = winding->vertices[vi].pos, 599 | .normal = winding->normal, 600 | .uv = winding->vertices[vi].uv 601 | }; 602 | } 603 | } 604 | 605 | // ----------------------------------------------------------------------------- 606 | // Parser 607 | 608 | #define peek() (*self->char_ptr) 609 | #define next() (self->char_ptr++) 610 | #define invalid(msg) self->error = msg; self->error_at = self->char_ptr; 611 | #define expect(c) if (peek() != c) { invalid("Unexpected char"); } else { next(); } 612 | #define skip_whitespace()\ 613 | while (\ 614 | peek() == ' ' || peek() == '\n' || peek() == '\r' || peek() == '\t' \ 615 | ) { next(); } 616 | #define skip_until(c) while (peek() && peek() != c) { next(); } 617 | 618 | void map_parse_entity(map_t *self); 619 | void map_parse_brush(map_t *self, map_entity_t *entity); 620 | void map_parse_key_value(map_t *self, map_entity_t *entity); 621 | string_t map_parse_quoted_string(map_t *self); 622 | void map_parse_plane(map_t *self, map_brush_t *brush); 623 | vec3_t map_parse_vec3(map_t *self); 624 | f32 map_parse_f32(map_t *self); 625 | string_t map_parse_texture(map_t *self); 626 | 627 | void map_parse(map_t *self) { 628 | while (!self->error && peek()) { 629 | skip_whitespace(); 630 | 631 | switch (peek()) { 632 | case '/': skip_until('\n'); break; 633 | case '{': map_parse_entity(self); break; 634 | case '\0': break; 635 | default: invalid("Unexpected char in global scope"); break; 636 | } 637 | } 638 | } 639 | 640 | void map_parse_entity(map_t *self) { 641 | expect('{'); 642 | 643 | map_entity_t *entity = memvec_add(self->map_entities); 644 | entity->kvs_index = self->entity_kvs.length; 645 | entity->brushes_index = self->brushes.length; 646 | 647 | while (!self->error && peek()) { 648 | skip_whitespace(); 649 | 650 | switch (peek()) { 651 | case '/': skip_until('\n'); break; 652 | case '"': map_parse_key_value(self, entity); break; 653 | case '{': map_parse_brush(self, entity); break; 654 | case '}': next(); return; 655 | default: invalid("Unexpected char in entity scope") break; 656 | } 657 | } 658 | } 659 | 660 | void map_parse_brush(map_t *self, map_entity_t *entity) { 661 | expect('{'); 662 | entity->brushes_length++; 663 | 664 | map_brush_t *brush = memvec_add(self->brushes); 665 | brush->planes_index = self->planes.length; 666 | 667 | while (!self->error && peek()) { 668 | skip_whitespace(); 669 | 670 | switch (peek()) { 671 | case '(': map_parse_plane(self, brush); break; 672 | case '}': next(); return; 673 | default: invalid("Unexpected char in brush scope"); break; 674 | } 675 | } 676 | } 677 | 678 | void map_parse_key_value(map_t *self, map_entity_t *entity) { 679 | entity->kvs_length++; 680 | 681 | entity_kv_t *kv = memvec_add(self->entity_kvs); 682 | kv->key = map_parse_quoted_string(self); 683 | kv->value = map_parse_quoted_string(self); 684 | } 685 | 686 | string_t map_parse_quoted_string(map_t *self) { 687 | skip_whitespace(); 688 | expect('"'); 689 | 690 | char *start = self->char_ptr; 691 | skip_until('"'); 692 | u32 length = self->char_ptr - start; 693 | expect('"'); 694 | return string(start, length); 695 | } 696 | 697 | void map_parse_plane(map_t *self, map_brush_t *brush) { 698 | brush->planes_length++; 699 | 700 | map_plane_t *plane = memvec_add(self->planes); 701 | 702 | plane->vertices[0] = map_parse_vec3(self); 703 | plane->vertices[1] = map_parse_vec3(self); 704 | plane->vertices[2] = map_parse_vec3(self); 705 | plane->normal = vec3_face_normal(plane->vertices[0], plane->vertices[1], plane->vertices[2]); 706 | 707 | plane->texture_name = map_parse_texture(self); 708 | 709 | plane->uv_offset.x = map_parse_f32(self); 710 | plane->uv_offset.y = map_parse_f32(self); 711 | plane->uv_rotation = map_parse_f32(self); 712 | plane->uv_scale.x = map_parse_f32(self); 713 | plane->uv_scale.y = map_parse_f32(self); 714 | plane->dist = vec3_dot(plane->normal, plane->vertices[1]); 715 | } 716 | 717 | vec3_t map_parse_vec3(map_t *self) { 718 | skip_whitespace(); 719 | expect('('); 720 | 721 | vec3_t v = vec3( 722 | map_parse_f32(self), 723 | map_parse_f32(self), 724 | map_parse_f32(self) 725 | ); 726 | 727 | skip_whitespace(); 728 | expect(')'); 729 | return v; 730 | } 731 | 732 | f32 map_parse_f32(map_t *self) { 733 | skip_whitespace(); 734 | 735 | char *start = self->char_ptr; 736 | while ((peek() >= '0' && peek() <= '9') || peek() == '.' || peek() == '-') { 737 | next(); 738 | } 739 | u32 length = self->char_ptr - start; 740 | 741 | if (length == 0 || length > 16) { 742 | invalid("Expected float") 743 | return 0; 744 | } 745 | 746 | char buf[length+1]; 747 | memcpy((void *)buf, start, length); 748 | buf[length] = '\0'; 749 | return atof(buf); 750 | } 751 | 752 | string_t map_parse_texture(map_t *self) { 753 | skip_whitespace(); 754 | 755 | char *start = self->char_ptr; 756 | skip_until(' '); 757 | u32 length = self->char_ptr - start; 758 | 759 | return string(start, length); 760 | } 761 | 762 | #undef peek 763 | #undef next 764 | #undef invalid 765 | #undef expect 766 | #undef skip_whitespace 767 | #undef skip_until 768 | 769 | 770 | 771 | 772 | // ----------------------------------------------------------------------------- 773 | 774 | #define BLOCK_RES_XZ 32 775 | #define BLOCK_RES_Y 16 776 | 777 | #define BLOCK_POS_MAX_XZ ((1<<8) * BLOCK_RES_XZ) 778 | #define BLOCK_POS_MAX_Y ((1<<8) * BLOCK_RES_Y) 779 | 780 | #define BLOCK_SIZE_MAX_XZ ((1<<8) * BLOCK_RES_XZ) 781 | #define BLOCK_SIZE_MAX_Y ((1<<8) * BLOCK_RES_Y) 782 | 783 | typedef struct { 784 | u8 x, y, z; 785 | u8 sx, sy, sz; 786 | u8 tex; 787 | } block_t; 788 | 789 | typedef struct { 790 | u8 x, y, z; 791 | u8 sx, sy, sz; 792 | } block_out_t; 793 | 794 | typedef struct { 795 | u8 sentinel; 796 | u8 tex; 797 | } block_texture_t; 798 | 799 | typedef struct { 800 | char type; 801 | u8 x, y, z; 802 | u8 data1, data2; 803 | } block_entity_t; 804 | 805 | u32 brushes_to_blocks(block_t *blocks, map_brush_t *brushes, u32 brushes_length) { 806 | u32 blocks_length = 0; 807 | for (u32 b = 0; b < brushes_length; b++) { 808 | map_brush_t *brush = &brushes[b]; 809 | 810 | // Find min, max vert of this brush 811 | vec3_t vmin = vec3(INFINITY, INFINITY, INFINITY); 812 | vec3_t vmax = vec3(-INFINITY, -INFINITY, -INFINITY); 813 | for (u32 f = 0; f < brush->faces_length; f++) { 814 | face_t *face = &brush->faces[f]; 815 | 816 | for (u32 v = 0; v < 3; v++) { 817 | vmin.x = min(vmin.x, round(face->vertices[v].pos.x)); 818 | vmin.y = min(vmin.y, round(face->vertices[v].pos.y)); 819 | vmin.z = min(vmin.z, round(face->vertices[v].pos.z)); 820 | 821 | vmax.x = max(vmax.x, round(face->vertices[v].pos.x)); 822 | vmax.y = max(vmax.y, round(face->vertices[v].pos.y)); 823 | vmax.z = max(vmax.z, round(face->vertices[v].pos.z)); 824 | } 825 | } 826 | 827 | vec3_t vsize = vec3_sub(vmax, vmin); 828 | if ( 829 | (vmin.x < 0 || vmin.y < 0 || vmin.z < 0) || 830 | (vmin.x >= BLOCK_POS_MAX_XZ || vmin.y >= BLOCK_POS_MAX_XZ || vmin.z >= BLOCK_POS_MAX_Y) || 831 | (vsize.x >= BLOCK_SIZE_MAX_XZ || vsize.y >= BLOCK_SIZE_MAX_XZ || vsize.z >= BLOCK_SIZE_MAX_Y) || 832 | (vsize.x < BLOCK_RES_XZ || vsize.y < BLOCK_RES_XZ || vsize.z < BLOCK_RES_Y) 833 | ) { 834 | printf( 835 | "Brush %d has unsupported dimensions: pos(%g %g %g) size(%g %g %g)\n", 836 | b, vmin.x, vmin.y, vmin.z, vsize.x, vsize.y, vsize.z 837 | ); 838 | continue; 839 | } 840 | 841 | // This assumes all textures are name XX.png 842 | u8 tex = atoi(string_cstring(brush->planes[0].texture_name)); 843 | 844 | // Build block, swap y<>z 845 | block_t block = (block_t){ 846 | .x = (u8)(round(vmin.x / BLOCK_RES_XZ)), 847 | .y = (u8)(round(vmin.z / BLOCK_RES_Y)), 848 | .z = (u8)(round(vmin.y / BLOCK_RES_XZ)), 849 | 850 | .sx = (u8)(round(vsize.x / BLOCK_RES_XZ)), 851 | .sy = (u8)(round(vsize.z / BLOCK_RES_Y)), 852 | .sz = (u8)(round(vsize.y / BLOCK_RES_XZ)), 853 | 854 | .tex = tex 855 | }; 856 | blocks[blocks_length++] = block; 857 | } 858 | return blocks_length; 859 | } 860 | 861 | i32 block_compare(const void *vlp, const void *vrp) { 862 | block_t *bl = (block_t *)vlp; 863 | block_t *br = (block_t *)vrp; 864 | 865 | i32 lt = bl->tex; 866 | i32 rt = br->tex; 867 | 868 | return lt == rt ? bl->sx - br->sx : lt - rt; 869 | } 870 | 871 | i32 block_entity_compare(const void *vlp, const void *vrp) { 872 | block_entity_t *bl = (block_entity_t *)vlp; 873 | block_entity_t *br = (block_entity_t *)vrp; 874 | 875 | return bl->type - br->type; 876 | } 877 | 878 | i32 main(i32 argc, char **argv) { 879 | if (argc < 3) { 880 | printf("Usage: ./map_packer infile.map outfile.plblocks\n"); 881 | exit(1); 882 | } 883 | 884 | printf("sizeof(block_out_t) = %d\n", sizeof(block_out_t)); 885 | 886 | char *error; 887 | u32 error_at; 888 | map_t *map = map_new(argv[1], &error, &error_at); 889 | if (!map) { 890 | printf("Error loading %s: %s at %d\n", argv[1], error, error_at); 891 | exit(1); 892 | } 893 | 894 | printf( 895 | "Loaded %s: %d entities, %d brushes, %d planes, %d faces\n", 896 | argv[1], 897 | map->entities_length, map->brushes.length, 898 | map->planes.length, map->faces.length 899 | ); 900 | 901 | FILE *fh = fopen(argv[2], "wb"); 902 | if (!fh) { 903 | printf("Failed to open %s for writing\n", argv[2]); 904 | exit(1); 905 | } 906 | 907 | // Find worldspawn, build brushes 908 | for (u32 i = 0; i < map->entities_length; i++) { 909 | entity_t *entity = &map->entities[i]; 910 | string_t classname = entity_get(entity, s("classname")); 911 | 912 | if (!string_equals(classname, s("worldspawn"))) { 913 | continue; 914 | } 915 | 916 | block_t *blocks = calloc(entity->brushes_length, sizeof(block_t)); 917 | u16 blocks_length = brushes_to_blocks(blocks, entity->brushes, entity->brushes_length); 918 | 919 | 920 | // Sort blocks by texture index 921 | qsort(blocks, blocks_length, sizeof(block_t), block_compare); 922 | 923 | // Write blocks length 924 | u16 num_textures = 0; 925 | u8 last_texture_index = 255; 926 | for (u32 j = 0; j < blocks_length; j++) { 927 | if (blocks[j].tex != last_texture_index) { 928 | last_texture_index = blocks[j].tex; 929 | num_textures++; 930 | } 931 | } 932 | 933 | u32 blocks_size = blocks_length * sizeof(block_out_t) + num_textures * sizeof(block_texture_t); 934 | printf("%d blocks, size: %d\n", blocks_length, blocks_size); 935 | fwrite(&blocks_size, sizeof(u16), 1, fh); 936 | 937 | // Go through all blocks, write the block and the texture_t whenever 938 | // the texture changes. 939 | last_texture_index = 255; 940 | for (u32 j = 0; j < blocks_length; j++) { 941 | if (blocks[j].tex != last_texture_index) { 942 | last_texture_index = blocks[j].tex; 943 | block_texture_t bt = {255, last_texture_index}; 944 | fwrite(&bt, sizeof(block_texture_t), 1, fh); 945 | } 946 | block_out_t bo = { 947 | .x = blocks[j].x, 948 | .y = blocks[j].y, 949 | .z = blocks[j].z, 950 | .sx = blocks[j].sx, 951 | .sy = blocks[j].sy, 952 | .sz = blocks[j].sz 953 | }; 954 | fwrite(&bo, sizeof(block_out_t), 1, fh); 955 | } 956 | 957 | free(blocks); 958 | break; 959 | } 960 | 961 | // Gather all entities 962 | block_entity_t *block_entities = calloc(map->entities_length, sizeof(block_entity_t)); 963 | u16 block_entities_length = 0; 964 | for (u32 i = 0; i < map->entities_length; i++) { 965 | entity_t *entity = &map->entities[i]; 966 | string_t classname = entity_get(entity, s("classname")); 967 | 968 | char type; 969 | u8 extra_data1 = 0; 970 | u8 extra_data2 = 0; 971 | if (string_equals(classname, s("worldspawn"))) { 972 | continue; 973 | } 974 | if (string_equals(classname, s("info_player_start"))) { 975 | type = 0; 976 | } 977 | else if (string_equals(classname, s("enemy_grunt"))) { 978 | type = 1; 979 | extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol")))); 980 | } 981 | else if (string_equals(classname, s("enemy_enforcer"))) { 982 | type = 2; 983 | extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol")))); 984 | } 985 | else if (string_equals(classname, s("enemy_ogre"))) { 986 | type = 3; 987 | extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol")))); 988 | } 989 | else if (string_equals(classname, s("enemy_zombie"))) { 990 | type = 4; 991 | extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol")))); 992 | } 993 | else if (string_equals(classname, s("enemy_hound"))) { 994 | type = 5; 995 | extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol")))); 996 | } 997 | else if (string_equals(classname, s("pickup_nailgun"))) { 998 | type = 6; 999 | } 1000 | else if (string_equals(classname, s("pickup_grenadelauncher"))) { 1001 | type = 7; 1002 | } 1003 | else if (string_equals(classname, s("pickup_health"))) { 1004 | type = 8; 1005 | } 1006 | else if (string_equals(classname, s("pickup_nails"))) { 1007 | type = 9; 1008 | } 1009 | else if (string_equals(classname, s("pickup_grenades"))) { 1010 | type = 10; 1011 | } 1012 | else if (string_equals(classname, s("barrel"))) { 1013 | type = 11; 1014 | } 1015 | else if (string_equals(classname, s("light"))) { 1016 | type = 12; 1017 | extra_data1 = atoi(string_cstring(entity_get(entity, s("light")))); 1018 | 1019 | // Convert 24 bit r g b string into 8 bit color value 1020 | char *color = string_cstring(entity_get(entity, s("color"))); 1021 | i32 r, g, b; 1022 | sscanf(color, "%d %d %d", &r, &g, &b); 1023 | 1024 | union { 1025 | struct { 1026 | u8 r: 3; 1027 | u8 g: 3; 1028 | u8 b: 2; 1029 | } rgb; 1030 | u8 v; 1031 | } rgb8 = {.rgb = {r >> 5, g >> 5, b >> 6}}; 1032 | extra_data2 = rgb8.v; 1033 | } 1034 | else if (string_equals(classname, s("trigger_levelchange"))) { 1035 | type = 13; 1036 | } 1037 | else if (string_equals(classname, s("door"))) { 1038 | type = 14; 1039 | extra_data1 = atoi(string_cstring(entity_get(entity, s("texture")))); 1040 | extra_data2 = atoi(string_cstring(entity_get(entity, s("dir")))); 1041 | } 1042 | else if (string_equals(classname, s("pickup_key"))) { 1043 | type = 15; 1044 | } 1045 | else if (string_equals(classname, s("torch"))) { 1046 | type = 16; 1047 | } 1048 | else { 1049 | printf("Unknown entity %s\n", string_cstring(classname)); 1050 | continue; 1051 | } 1052 | 1053 | f32 x, y, z; 1054 | char *origin = string_cstring(entity_get(entity, s("origin"))); 1055 | sscanf(origin, "%f %f %f", &x, &y, &z); 1056 | 1057 | // Build entity, swap y<>z 1058 | block_entity_t be = { 1059 | .type = type, 1060 | .x = (u8)(round(x / BLOCK_RES_XZ)), 1061 | .y = (u8)(round(z / BLOCK_RES_Y)), 1062 | .z = (u8)(round(y / BLOCK_RES_XZ)), 1063 | .data1 = extra_data1, 1064 | .data2 = extra_data2, 1065 | }; 1066 | block_entities[block_entities_length++] = be; 1067 | } 1068 | 1069 | qsort(block_entities, block_entities_length, sizeof(block_entity_t), block_entity_compare); 1070 | 1071 | printf("%d entities, size: %d\n", block_entities_length, block_entities_length * sizeof(block_entity_t)); 1072 | fwrite(&block_entities_length, sizeof(u16), 1, fh); 1073 | fwrite(block_entities, sizeof(block_entity_t), block_entities_length, fh); 1074 | 1075 | fclose(fh); 1076 | map_delete(map); 1077 | } 1078 | -------------------------------------------------------------------------------- /pack_model.php: -------------------------------------------------------------------------------- 1 | $v) { 62 | $x = round(($v[0]/$max)*15)+15; 63 | $y = round(($v[1]/$max)*15)+15; 64 | $z = round(($v[2]/$max)*15)+15; 65 | 66 | // echo "Vertex $i => ($x, $y, $z)\n"; 67 | $packed .= 68 | pack('C', $x). 69 | pack('C', $y). 70 | pack('C', $z); 71 | 72 | } 73 | 74 | // Pack indices w. vertex index 75 | $a_last_index = 0; 76 | foreach ($indices as $i => $f) { 77 | $a_address_inc = $f[0] - $a_last_index; 78 | if ($a_address_inc > 3) { 79 | die("Face $i index a increment exceeds 2 bits ($a_address_inc)\n"); 80 | } 81 | $a_last_index = $f[0]; 82 | 83 | if ($f[1] > 127 || $f[2] > 127) { 84 | die("Face $i index exceeds 7 bits ({$f[1]}, {$f[1]}, {$f[2]})\n"); 85 | } 86 | 87 | // echo "Face $i => ({$f[1]}, {$f[1]}, {$f[2]})\n"; 88 | // $packed .= pack('v', ($a_address_inc << 14) | ($f[1] << 7) | $f[2]); 89 | $packed .= 90 | pack('C', $a_address_inc). 91 | pack('C', $f[1]). 92 | pack('C', $f[2]); 93 | } 94 | 95 | // Write 96 | $packedfile = $argv[count($argv)-1]; 97 | file_put_contents($packedfile, $packed); 98 | 99 | echo "Wrote $packedfile: ". 100 | count($infiles)." frame(s), ". 101 | count($verts)." verts, ". 102 | count($indices)." indices, ". 103 | strlen($packed)." bytes\n"; 104 | -------------------------------------------------------------------------------- /source/audio.js: -------------------------------------------------------------------------------- 1 | 2 | // Gutted for js13k and modified to use Float32 buffers directly 3 | // ~ Dominic Szablewski, phoboslab.org, Sep 2018 4 | 5 | // Almost re-written for for jsk13 2019. Oscilators now use a lookup table 6 | // instead of calling functions. This and various other changes result in a 7 | // ~10x performance increase and smaller file size. 8 | // ~ Dominic Szablewski, phoboslab.org, Sep 2019 9 | 10 | // Again updated for js13k 2021. Song and sound definitions are now just arrays 11 | // instead of objects. 12 | 13 | 14 | // 15 | // Sonant-X 16 | // 17 | // Copyr (c) 2014 Nicolas Vanhoren 18 | // 19 | // Sonant-X is a fork of js-sonant by Marcus Geelnard and Jake Taylor. It is 20 | // still published using the same license (zlib license, see below). 21 | // 22 | // Copyr (c) 2011 Marcus Geelnard 23 | // Copyr (c) 2008-2009 Jake Taylor 24 | // 25 | // This software is provided 'as-is', without any express or implied 26 | // warranty. In no event will the authors be held liable for any damages 27 | // arising from the use of this software. 28 | // 29 | // Permission is granted to anyone to use this software for any purpose, 30 | // including commercial applications, and to alter it and redistribute it 31 | // freely, subject to the following restrictions: 32 | // 33 | // 1. The origin of this software must not be misrepresented; you must not 34 | // claim that you wrote the original software. If you use this software 35 | // in a product, an acknowledgment in the product documentation would be 36 | // appreciated but is not required. 37 | // 38 | // 2. Altered source versions must be plainly marked as such, and must not be 39 | // misrepresented as being the original software. 40 | // 41 | // 3. This notice may not be removed or altered from any source 42 | // distribution. 43 | 44 | let 45 | audio_ctx, 46 | AUDIO_SAMPLERATE = 44100, // Samples per second 47 | AUDIO_TAB_SIZE = 4096, 48 | AUDIO_TAB_MASK = AUDIO_TAB_SIZE-1, 49 | AUDIO_TAB = new Float32Array(AUDIO_TAB_SIZE*4), // 4 oscilators 50 | 51 | audio_init = () => { 52 | // This function needs to be called in response to a user action, as it 53 | // tries to activate the audio context. 54 | audio_ctx = new AudioContext(); 55 | audio_ctx.resume(); 56 | 57 | // Generate the lookup tables 58 | for (let i = 0; i < AUDIO_TAB_SIZE; i++) { 59 | AUDIO_TAB[i ] = Math.sin(i*6.283184/AUDIO_TAB_SIZE); // sin 60 | AUDIO_TAB[i + AUDIO_TAB_SIZE ] = AUDIO_TAB[i] < 0 ? -1 : 1; // square 61 | AUDIO_TAB[i + AUDIO_TAB_SIZE * 2] = i / AUDIO_TAB_SIZE - 0.5; // saw 62 | AUDIO_TAB[i + AUDIO_TAB_SIZE * 3] = i < AUDIO_TAB_SIZE/2 ? (i/(AUDIO_TAB_SIZE/4)) - 1 : 3 - (i/(AUDIO_TAB_SIZE/4)); // tri 63 | } 64 | }, 65 | 66 | audio_play = (buffer, volume = 1, loop = 0, pan = 0) => { 67 | let gain = audio_ctx.createGain(), 68 | source = audio_ctx.createBufferSource(), 69 | panner = audio_ctx.createStereoPanner(); 70 | gain.gain.value = volume; 71 | gain.connect(audio_ctx.destination); 72 | panner.connect(gain); 73 | panner.pan.value = pan; 74 | source.buffer = buffer; 75 | source.loop = loop; 76 | source.connect(panner); 77 | source.start(); 78 | }, 79 | 80 | audio_get_ctx_buffer = (buf_l, buf_r) => { 81 | let buffer = audio_ctx.createBuffer(2, buf_l.length, AUDIO_SAMPLERATE); 82 | buffer.getChannelData(0).set(buf_l); 83 | buffer.getChannelData(1).set(buf_r); 84 | return buffer; 85 | }, 86 | 87 | audio_generate_sound = ( 88 | row_len, note, buf_l, buf_r, write_pos, 89 | 90 | // Instrument properties 91 | osc1_oct, osc1_det, osc1_detune, osc1_xenv, osc1_vol, osc1_waveform, 92 | osc2_oct, osc2_det, osc2_detune, osc2_xenv, osc2_vol, osc2_waveform, 93 | noise_fader, attack, sustain, release, master, 94 | fx_filter, fx_freq, fx_resonance, fx_delay_time, fx_delay_amt, fx_pan_freq_p, fx_pan_amt, 95 | lfo_osc1_freq, lfo_fx_freq, lfo_freq_p, lfo_amt, lfo_waveform 96 | ) => { 97 | let osc_lfo_offset = lfo_waveform * AUDIO_TAB_SIZE, 98 | osc1_offset = osc1_waveform * AUDIO_TAB_SIZE, 99 | osc2_offset = osc2_waveform * AUDIO_TAB_SIZE, 100 | fx_pan_freq = Math.pow(2, fx_pan_freq_p - 8) / row_len, 101 | lfo_freq = Math.pow(2, lfo_freq_p - 8) / row_len, 102 | 103 | c1 = 0, 104 | c2 = 0, 105 | 106 | q = fx_resonance / 255, 107 | low = 0, 108 | band = 0, 109 | high = 0, 110 | 111 | buf_length = buf_l.length, 112 | num_samples = attack + sustain + release - 1, 113 | 114 | osc1_freq = 115 | Math.pow(1.059463094, (note + (osc1_oct - 8) * 12 + osc1_det) - 128) 116 | * 0.00390625 * (1 + 0.0008 * osc1_detune), 117 | osc2_freq = 118 | Math.pow(1.059463094, (note + (osc2_oct - 8) * 12 + osc2_det) - 128) 119 | * 0.00390625 * (1 + 0.0008 * osc2_detune); 120 | 121 | for (let j = num_samples; j >= 0; --j) { 122 | let 123 | // Buffer positions 124 | k = j + write_pos, 125 | 126 | // LFO 127 | lfor = AUDIO_TAB[osc_lfo_offset + ((k * lfo_freq * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK)] * lfo_amt / 512 + 0.5, 128 | 129 | sample = 0, 130 | filter_f = fx_freq, 131 | temp_f, 132 | envelope = 1; 133 | 134 | // Envelope 135 | if (j < attack) { 136 | envelope = j / attack; 137 | } 138 | else if (j >= attack + sustain) { 139 | envelope -= (j - attack - sustain) / release; 140 | } 141 | 142 | // Oscillator 1 143 | temp_f = osc1_freq; 144 | if (lfo_osc1_freq) { 145 | temp_f *= lfor; 146 | } 147 | if (osc1_xenv) { 148 | temp_f *= envelope * envelope; 149 | } 150 | c1 += temp_f; 151 | sample += AUDIO_TAB[osc1_offset + ((c1 * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK)] * osc1_vol; 152 | 153 | // Oscillator 2 154 | temp_f = osc2_freq; 155 | if (osc2_xenv) { 156 | temp_f *= envelope * envelope; 157 | } 158 | c2 += temp_f; 159 | sample += AUDIO_TAB[osc2_offset + ((c2 * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK)] * osc2_vol; 160 | 161 | // Noise oscillator 162 | if (noise_fader) { 163 | sample += (2*Math.random()-1) * noise_fader * envelope; 164 | } 165 | 166 | sample *= envelope / 255; 167 | 168 | // State variable filter 169 | if (lfo_fx_freq) { 170 | filter_f *= lfor; 171 | } 172 | 173 | filter_f = 1.5 * AUDIO_TAB[(filter_f * 0.5 / AUDIO_SAMPLERATE * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK]; 174 | low += filter_f * band; 175 | high = q * (sample - band) - low; 176 | band += filter_f * high; 177 | sample = [sample, high, low, band, low + high][fx_filter]; 178 | 179 | // Panning & master volume 180 | temp_f = AUDIO_TAB[(k * fx_pan_freq * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK] * fx_pan_amt / 512 + 0.5; 181 | sample *= 0.00476 * master; // 39 / 8192 = 0.00476 182 | 183 | buf_l[k] += sample * (1-temp_f); 184 | buf_r[k] += sample * temp_f; 185 | } 186 | }, 187 | 188 | audio_create_song = (row_len, pattern_len, song_len, tracks) => { 189 | let num_samples = AUDIO_SAMPLERATE * song_len, 190 | mix_buf_l = new Float32Array(num_samples), 191 | mix_buf_r = new Float32Array(num_samples); 192 | 193 | for (let track of tracks) { 194 | let buf_l = new Float32Array(num_samples), 195 | buf_r = new Float32Array(num_samples), 196 | write_pos = 0, 197 | delay_shift = (track[0/*instrument*/][20/*fx_delay_time*/] * row_len) >> 1, 198 | delay_amount = track[0/*instrument*/][21/*fx_delay_amt*/] / 255; 199 | 200 | for (let p = 0; p < pattern_len; p++) { 201 | for (let row = 0; row < 32; row++) { 202 | let note = track[2/*notes*/][track[1/*pattern*/][p] - 1]?.[row]; 203 | if (note) { 204 | audio_generate_sound(row_len, note, buf_l, buf_r, write_pos, ...track[0/*instrument*/]); 205 | } 206 | write_pos += row_len; 207 | } 208 | } 209 | 210 | audio_apply_delay(delay_shift, delay_amount, buf_l, buf_r); 211 | for (let b = 0; b < num_samples; b++) { 212 | mix_buf_l[b] += buf_l[b]; 213 | mix_buf_r[b] += buf_r[b]; 214 | } 215 | } 216 | return audio_get_ctx_buffer(mix_buf_l, mix_buf_r); 217 | }, 218 | 219 | audio_create_sound = (note, instrument, row_len = 5605) => { 220 | let delay_shift = (instrument[20/*fx_delay_time*/] * row_len) >> 1, 221 | delay_amount = instrument[21/*fx_delay_amt*/] / 255, 222 | num_samples = 223 | instrument[13/*env_attack*/] + 224 | instrument[14/*env_sustain*/] + 225 | instrument[15/*env_release*/] + 226 | delay_shift * 32 * delay_amount, 227 | buf_l = new Float32Array(num_samples), 228 | buf_r = new Float32Array(num_samples); 229 | audio_generate_sound(row_len, note, buf_l, buf_r, 0, ...instrument); 230 | audio_apply_delay(delay_shift, delay_amount, buf_l, buf_r); 231 | return audio_get_ctx_buffer(buf_l, buf_r); 232 | }, 233 | 234 | audio_apply_delay = (shift, amount, buf_l, buf_r) => { 235 | for (let i = 0; i < buf_l.length - shift; i++) { 236 | buf_l[i + shift] += buf_r[i] * amount; 237 | buf_r[i + shift] += buf_l[i] * amount; 238 | } 239 | }; 240 | 241 | -------------------------------------------------------------------------------- /source/document.js: -------------------------------------------------------------------------------- 1 | 2 | document.body.innerHTML += 3 | ''+ 14 | '
'+ 15 | ''+ 16 | '

Q1K3

CLICK TO START
'+ 17 | '
'+ 18 | '
'+ 19 | '
'+ 20 | '

MOUSE SPEED: INVERT:

'+ 21 | '

'+ 22 | '

'+ 23 | 'code: phoboslab.org / music: no-fate.net'+ 24 | '

'; -------------------------------------------------------------------------------- /source/entity.js: -------------------------------------------------------------------------------- 1 | 2 | let ENTITY_GROUP_NONE = 0, 3 | ENTITY_GROUP_PLAYER = 1, 4 | ENTITY_GROUP_ENEMY = 2; 5 | 6 | class entity_t { 7 | constructor(pos, p1, p2) { 8 | this.a = vec3(); 9 | this.v = vec3(); 10 | this.p = pos; 11 | this.s = vec3(2,2,2); 12 | this.f = 0; 13 | 14 | this._health = 50; 15 | this._dead = 0; 16 | this._die_at = 0; 17 | this._step_height = 0; 18 | this._bounciness = 0; 19 | this._gravity = 1; 20 | this._yaw = 0; 21 | this._pitch = 0; 22 | this._anim = [1, [0]]; 23 | this._anim_time = Math.random(); 24 | this._on_ground = 0; 25 | this._keep_off_ledges = 0; 26 | 27 | this._check_against = ENTITY_GROUP_NONE; 28 | this._stepped_up_at = 0; 29 | 30 | this._init(p1, p2); 31 | } 32 | 33 | _init(p1, p2) {} 34 | 35 | _update() { 36 | if (this._model) { 37 | this._draw_model(); 38 | } 39 | } 40 | 41 | _update_physics() { 42 | if (this._die_at && this._die_at < game_time) { 43 | this._kill(); 44 | return; 45 | } 46 | 47 | // Apply Gravity 48 | this.a.y = -1200 * this._gravity; 49 | 50 | // Integrate acceleration & friction into velocity 51 | let ff = Math.min(this.f * game_tick, 1); 52 | this.v = vec3_add( 53 | this.v, vec3_sub( 54 | vec3_mulf(this.a, game_tick), 55 | vec3_mul(this.v, vec3(ff, 0, ff)) 56 | ) 57 | ); 58 | 59 | 60 | // Set up the _check_entities array for entity collisions 61 | this._check_entities = [ 62 | [], 63 | game_entities_friendly, 64 | game_entities_enemies 65 | ][this._check_against]; 66 | 67 | // Divide the physics integration into 16 unit steps; otherwise fast 68 | // projectiles may just move through walls. 69 | let 70 | original_step_height = this._step_height, 71 | move_dist = vec3_mulf(this.v, game_tick), 72 | steps = Math.ceil(vec3_length(move_dist) / 16), 73 | move_step = vec3_mulf(move_dist, 1/steps); 74 | 75 | for (let s = 0; s < steps; s++) { 76 | // Remember last position so we can roll back 77 | let lp = vec3_clone(this.p); 78 | 79 | // Integrate velocity into position 80 | this.p = vec3_add(this.p, move_step); 81 | 82 | // Collision with walls, horizonal 83 | if (this._collides(vec3(this.p.x, lp.y, lp.z))) { 84 | 85 | // Can we step up? 86 | if ( 87 | !this._step_height || !this._on_ground || this.v.y > 0 || 88 | this._collides(vec3(this.p.x, lp.y+this._step_height, lp.z)) 89 | ) { 90 | this._did_collide(0); 91 | this.p.x = lp.x; 92 | this.v.x = -this.v.x * this._bounciness; 93 | } 94 | else { 95 | lp.y += this._step_height; 96 | this._stepped_up_at = game_time; 97 | } 98 | 99 | s = steps; // stop after this iteration 100 | } 101 | 102 | // Collision with walls, vertical 103 | if (this._collides(vec3(this.p.x, lp.y, this.p.z))) { 104 | 105 | // Can we step up? 106 | if ( 107 | !this._step_height || !this._on_ground || this.v.y > 0 || 108 | this._collides(vec3(this.p.x, lp.y+this._step_height, this.p.z)) 109 | ) { 110 | this._did_collide(2); 111 | this.p.z = lp.z; 112 | this.v.z = -this.v.z * this._bounciness; 113 | } 114 | else { 115 | lp.y += this._step_height; 116 | this._stepped_up_at = game_time; 117 | } 118 | 119 | s = steps; // stop after this iteration 120 | } 121 | 122 | // Collision with ground/Ceiling 123 | if (this._collides(this.p)) { 124 | this._did_collide(1); 125 | this.p.y = lp.y; 126 | 127 | // Only bounce from ground/ceiling if we have enough velocity 128 | let bounce = Math.abs(this.v.y) > 200 ? this._bounciness : 0; 129 | this._on_ground = this.v.y < 0 && !bounce; 130 | this.v.y = -this.v.y * bounce; 131 | 132 | s = steps; // stop after this iteration 133 | } 134 | 135 | this._step_height = original_step_height; 136 | } 137 | 138 | } 139 | 140 | _collides(p) { 141 | if (this._dead) { 142 | return; 143 | } 144 | for (let entity of this._check_entities) { 145 | if (vec3_dist(p, entity.p) < this.s.y + entity.s.y) { 146 | // If we collide with an entity set the step height to 0, 147 | // so we don't climb up on its shoulders :/ 148 | this._step_height = 0; 149 | this._did_collide_with_entity(entity); 150 | return true; 151 | } 152 | } 153 | 154 | // Check if there's no block beneath this point. We want the AI to keep 155 | // off of ledges. 156 | if ( 157 | this._on_ground && this._keep_off_ledges && 158 | !map_block_at(p.x >> 5, (p.y-this.s.y-8) >> 4, p.z >>5) && 159 | !map_block_at(p.x >> 5, (p.y-this.s.y-24) >> 4, p.z >>5) 160 | ) { 161 | return true; 162 | } 163 | 164 | // Do the normal collision check with the whole box 165 | return map_block_at_box(vec3_sub(p, this.s), vec3_add(p, this.s)); 166 | } 167 | 168 | _did_collide(axis) {} 169 | 170 | _did_collide_with_entity(other) {} 171 | 172 | _draw_model() { 173 | this._anim_time += game_tick; 174 | 175 | // Calculate which frames to use and how to mix them 176 | let f = (this._anim_time / this._anim[0]), 177 | mix = f - (f|0), 178 | frame_cur = this._anim[1][(f|0) % this._anim[1].length], 179 | frame_next = this._anim[1][((f+1)|0) % this._anim[1].length]; 180 | 181 | // Swap frames if we're looping to the first frame again 182 | if (frame_next < frame_cur) { 183 | [frame_next, frame_cur] = [frame_cur, frame_next]; 184 | mix = 1-mix; 185 | } 186 | r_draw( 187 | this.p, this._yaw, this._pitch, this._texture, 188 | this._model.f[frame_cur], this._model.f[frame_next], mix, 189 | this._model.nv 190 | ); 191 | } 192 | 193 | _spawn_particles(amount, speed = 1, model, texture, lifetime) { 194 | for (let i = 0; i < amount; i++) { 195 | let particle = game_spawn(entity_particle_t, this.p); 196 | particle._model = model; 197 | particle._texture = texture; 198 | particle._die_at = game_time + lifetime + Math.random() * lifetime * 0.2; 199 | particle.v = vec3( 200 | (Math.random() - 0.5) * speed, 201 | Math.random() * speed, 202 | (Math.random() - 0.5) * speed 203 | ); 204 | } 205 | } 206 | 207 | _receive_damage(from, amount) { 208 | if (this._dead) { 209 | return; 210 | } 211 | this._health -= amount; 212 | if (this._health <= 0) { 213 | this._kill(); 214 | } 215 | } 216 | 217 | _play_sound(sound) { 218 | let volume = clamp(scale(vec3_dist(this.p, r_camera), 64, 1200, 1, 0),0,1), 219 | pan = Math.sin(vec3_2d_angle(this.p, r_camera)-r_camera_yaw)*-1; 220 | audio_play(sound, volume, 0, pan); 221 | } 222 | 223 | _kill() { 224 | this._dead = 1; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /source/entity_barrel.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_barrel_t extends entity_t { 3 | _init() { 4 | this._model = model_barrel; 5 | this._texture = 21; 6 | this._pitch = Math.PI/2; 7 | this._health = 10; 8 | this.s = vec3(8, 32, 8); 9 | 10 | game_entities_enemies.push(this); 11 | } 12 | 13 | _kill() { 14 | // Deal some damage to nearby entities 15 | for (let entity of game_entities_enemies) { 16 | let dist = vec3_dist(this.p, entity.p); 17 | if (entity !== this && dist < 256) { 18 | entity._receive_damage(this, scale(dist, 0, 256, 60, 0)); 19 | } 20 | } 21 | 22 | super._kill(); 23 | this._play_sound(sfx_grenade_explode); 24 | for (let m of model_gib_pieces) { 25 | this._spawn_particles(2, 600, m, 21, 1); 26 | } 27 | game_spawn(entity_light_t, vec3_add(this.p, vec3(0,16,0)), 250, 0x08f)._die_at = game_time + 0.2; 28 | game_entities_enemies = game_entities_enemies.filter(e => e != this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/entity_door.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_door_t extends entity_t { 3 | _init(texture, dir) { 4 | this._model = model_door; 5 | this._texture = texture; 6 | this._health = 10; 7 | this.s = vec3(64, 64, 64); 8 | this._start_pos = vec3_clone(this.p); 9 | 10 | this._reset_state_at = 0; 11 | this._yaw = dir * Math.PI/2; 12 | this._open = 0; 13 | 14 | // Map 1 only has one door and it needs a key. Should be a flag 15 | // in the entity data instead :/ 16 | this._needs_key = game_map_index == 1; 17 | 18 | // Doors block enemies and players 19 | game_entities_enemies.push(this); 20 | game_entities_friendly.push(this); 21 | } 22 | 23 | _update() { 24 | this._draw_model(); 25 | if (vec3_dist(this.p, game_entity_player.p) < 128) { 26 | if (this._needs_key) { 27 | game_show_message('YOU NEED THE KEY...'); 28 | return; 29 | } 30 | this._reset_state_at = game_time + 3; 31 | } 32 | 33 | if (this._reset_state_at < game_time) { 34 | this._open = Math.max(0, this._open-game_tick); 35 | } 36 | else { 37 | this._open = Math.min(1, this._open+game_tick); 38 | } 39 | 40 | this.p = vec3_add(this._start_pos, vec3_rotate_y(vec3(96 * this._open,0,0), this._yaw)); 41 | } 42 | 43 | _receive_damage() {} 44 | } 45 | -------------------------------------------------------------------------------- /source/entity_enemy.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_enemy_t extends entity_t { 3 | _init(patrol_dir) { 4 | 5 | // Animations 6 | this._ANIMS =[ 7 | [1, [0]], // 0: Idle 8 | [0.40, [1,2,3,4]], // 1: Walk 9 | [0.20, [1,2,3,4]], // 2: Run 10 | [0.25, [0,5,5,5]], // 3: Attack prepare 11 | [0.25, [5,0,0,0]], // 4: Attack 12 | ]; 13 | 14 | // State definitions 15 | // [0: anim_index, 1: speed, 2: next_state_update, 3: next_state] 16 | this._STATE_IDLE = [0, 0, 0.1]; 17 | this._STATE_PATROL = [1, 0.5, 0.5]; 18 | this._STATE_FOLLOW = [2, 1, 0.3]; 19 | this._STATE_ATTACK_RECOVER = [0, 0, 0.1, this._STATE_FOLLOW]; 20 | this._STATE_ATTACK_EXEC = [4, 0, 0.4, this._STATE_ATTACK_RECOVER]; 21 | this._STATE_ATTACK_PREPARE = [3, 0, 0.4, this._STATE_ATTACK_EXEC]; 22 | this._STATE_ATTACK_AIM = [0, 0, 0.1, this._STATE_ATTACK_PREPARE]; 23 | this._STATE_EVADE = [2, 1, 0.8, this._STATE_ATTACK_AIM]; 24 | 25 | this.s = vec3(12,28,12); 26 | 27 | this._step_height = 17; 28 | this._speed = 196; 29 | 30 | this._target_yaw = this._yaw; 31 | this._state_update_at = 0; 32 | this._attack_distance = 800; 33 | this._evade_distance = 96; 34 | this._attack_chance = 0.65; 35 | this._keep_off_ledges = 1; 36 | this._turn_bias = 1; 37 | 38 | this._check_against = ENTITY_GROUP_PLAYER; 39 | 40 | game_entities_enemies.push(this); 41 | 42 | // If patrol_dir is non-zero it determines the partrol direction in 43 | // increments of 90°. Otherwise we just idle. 44 | if (patrol_dir) { 45 | this._set_state(this._STATE_PATROL); 46 | this._target_yaw = (Math.PI/2) * patrol_dir; 47 | this._anim_time = Math.random(); 48 | } 49 | else { 50 | this._set_state(this._STATE_IDLE); 51 | } 52 | } 53 | 54 | _set_state(state) { 55 | this._state = state; 56 | this._anim = this._ANIMS[state[0]]; 57 | this._anim_time = 0; 58 | this._state_update_at = game_time + state[2] + state[2]/4 * Math.random(); 59 | } 60 | 61 | _update() { 62 | // Is it time for a state update? 63 | if (this._state_update_at < game_time) { 64 | 65 | // Choose a new turning bias for FOLLOW/EVADE when we hit a wall 66 | this._turn_bias = Math.random() > 0.5 ? 0.5 : -0.5; 67 | 68 | let distance_to_player = vec3_dist(this.p, game_entity_player.p), 69 | angle_to_player = vec3_2d_angle(this.p, game_entity_player.p); 70 | 71 | if (this._state[3]) { 72 | this._set_state(this._state[3]); 73 | } 74 | 75 | // Try to minimize distance to the player 76 | if (this._state == this._STATE_FOLLOW) { 77 | 78 | // Do we have a line of sight? 79 | if (!map_trace(this.p, game_entity_player.p)) { 80 | this._target_yaw = angle_to_player; 81 | } 82 | 83 | // Are we close enough to attack? 84 | if (distance_to_player < this._attack_distance) { 85 | 86 | // Are we too close? Evade! 87 | if ( 88 | distance_to_player < this._evade_distance || 89 | Math.random() > this._attack_chance 90 | ) { 91 | this._set_state(this._STATE_EVADE); 92 | this._target_yaw += Math.PI/2 + Math.random() * Math.PI; 93 | } 94 | 95 | // Just the right distance to attack! 96 | else { 97 | this._set_state(this._STATE_ATTACK_AIM); 98 | } 99 | } 100 | } 101 | 102 | // We just attacked; just keep looking at the player 0_o 103 | if (this._state == this._STATE_ATTACK_RECOVER) { 104 | this._target_yaw = angle_to_player; 105 | } 106 | 107 | // Wake up from patroling or idlyng if we have a line of sight 108 | // and are near enough 109 | if (this._state == this._STATE_PATROL || this._state == this._STATE_IDLE) { 110 | if ( 111 | distance_to_player < 700 && 112 | !map_trace(this.p, game_entity_player.p) 113 | ) { 114 | this._set_state(this._STATE_ATTACK_AIM); 115 | } 116 | } 117 | 118 | // Aiming - reorient the entity towards the player, check 119 | // if we have a line of sight 120 | if (this._state == this._STATE_ATTACK_AIM) { 121 | this._target_yaw = angle_to_player; 122 | 123 | // No line of sight? Randomly shuffle around :/ 124 | if (map_trace(this.p, game_entity_player.p)) { 125 | this._set_state(this._STATE_EVADE); 126 | } 127 | } 128 | 129 | // Execute the attack! 130 | if (this._state == this._STATE_ATTACK_EXEC) { 131 | this._attack(); 132 | } 133 | } 134 | 135 | // Rotate to desired angle 136 | this._yaw += anglemod(this._target_yaw - this._yaw) * 0.1; 137 | 138 | 139 | // Move along the yaw direction with the current speed (which might be 0) 140 | if (this._on_ground) { 141 | this.v = vec3_rotate_y(vec3(0, this.v.y, this._state[1] * this._speed), this._target_yaw); 142 | } 143 | 144 | this._update_physics(); 145 | this._draw_model(); 146 | } 147 | 148 | _spawn_projectile(type, speed, yaw_offset, pitch_offset) { 149 | let projectile = game_spawn(type, this.p); 150 | projectile._check_against = ENTITY_GROUP_PLAYER; 151 | projectile._yaw = this._yaw + Math.PI/2; 152 | 153 | projectile.v = vec3_rotate_yaw_pitch( 154 | vec3(0, 0, speed), 155 | this._yaw + yaw_offset, 156 | Math.atan2( 157 | this.p.y-game_entity_player.p.y, 158 | vec3_dist(this.p, game_entity_player.p) 159 | ) + pitch_offset 160 | ); 161 | return projectile; 162 | } 163 | 164 | _receive_damage(from, amount) { 165 | super._receive_damage(from, amount); 166 | this._play_sound(sfx_enemy_hit); 167 | 168 | // Wake up if we're idle or patrolling 169 | if (this._state == this._STATE_IDLE || this._state == this._STATE_PATROL) { 170 | this._target_yaw = vec3_2d_angle(this.p, game_entity_player.p); 171 | this._set_state(this._STATE_FOLLOW); 172 | } 173 | 174 | this._spawn_particles(2, 200, model_blood, 18, 0.5); 175 | } 176 | 177 | _kill() { 178 | super._kill(); 179 | for (let m of model_gib_pieces) { 180 | this._spawn_particles(2, 300, m, 18, 1); 181 | } 182 | this._play_sound(sfx_enemy_gib); 183 | game_entities_enemies = game_entities_enemies.filter(e => e != this); 184 | } 185 | 186 | _did_collide(axis) { 187 | if (axis == 1) { 188 | return; 189 | } 190 | 191 | // If we hit a wall/ledge while patrolling just turn around 180 192 | if (this._state == this._STATE_PATROL) { 193 | this._target_yaw += Math.PI; 194 | } 195 | else { 196 | this._target_yaw += this._turn_bias; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /source/entity_enemy_enforcer.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_enemy_enforcer_t extends entity_enemy_t { 3 | _init(patrol_dir) { 4 | super._init(patrol_dir); 5 | this._model = model_enforcer; 6 | this._texture = 19; 7 | this._health = 80; 8 | this.s = vec3(14,44,14); 9 | } 10 | 11 | _attack() { 12 | this._play_sound(sfx_plasma_shoot); 13 | this._spawn_projectile(entity_projectile_plasma_t, 800, 0, 0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/entity_enemy_grunt.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_enemy_grunt_t extends entity_enemy_t { 3 | _init(patrol_dir) { 4 | super._init(patrol_dir); 5 | this._model = model_grunt; 6 | this._texture = 17; 7 | this._health = 40; 8 | } 9 | 10 | _attack() { 11 | this._play_sound(sfx_shotgun_shoot); 12 | game_spawn(entity_light_t, vec3_add(this.p, vec3(0,30,0)), 10, 0xff)._die_at = game_time + 0.1; 13 | 14 | for (let i = 0; i < 3; i++) { 15 | this._spawn_projectile( 16 | entity_projectile_shell_t, 10000, 17 | Math.random()*0.08-0.04, Math.random()*0.08-0.04 18 | ); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/entity_enemy_hound.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_enemy_hound_t extends entity_enemy_t { 3 | _init(patrol_dir) { 4 | super._init(patrol_dir); 5 | this._model = model_hound; 6 | this._texture = 22; 7 | this._health = 25; 8 | this._check_against = ENTITY_GROUP_PLAYER; 9 | 10 | this.s = vec3(12,16,12); 11 | 12 | this._attack_distance = 200; 13 | this._evade_distance = 64; 14 | this._attack_chance = 0.7; 15 | this._speed = 256; 16 | 17 | this._ANIMS = [ 18 | [1, [0]], // 0: Idle 19 | [0.15, [0,1]], // 2: Run 20 | [0.15, [0,1]], // 2: Run 21 | [1, [0]], // 3: Attack prepare 22 | [0.1, [0,1,1,1,0,0,0]], // 4: Attack 23 | ]; 24 | 25 | this._STATE_PATROL = [1, 0.2, 0.5]; 26 | this._STATE_ATTACK_RECOVER = [0, 0, 0.5, this._STATE_FOLLOW]; 27 | this._STATE_ATTACK_EXEC = [4, 0, 1, this._STATE_ATTACK_RECOVER]; 28 | this._STATE_ATTACK_PREPARE = [3, 0, 0.0, this._STATE_ATTACK_EXEC]; 29 | this._STATE_ATTACK_AIM = [0, 0, 0.0, this._STATE_ATTACK_PREPARE]; 30 | this._STATE_EVADE = [2, 1, 0.3, this._STATE_ATTACK_AIM]; 31 | 32 | this._set_state(this._STATE_IDLE); 33 | } 34 | 35 | _did_collide_with_entity(other) { 36 | if (!this._did_hit && this._state == this._STATE_ATTACK_EXEC) { 37 | this._did_hit = 1; 38 | other._receive_damage(this, 14); 39 | } 40 | } 41 | 42 | _attack() { 43 | this._play_sound(sfx_enemy_hound_attack); 44 | this.v = vec3_rotate_y(vec3(0, 250, 600), this._target_yaw); 45 | this._on_ground = 0; 46 | this._did_hit = 0; 47 | 48 | // Ignore ledges while attacking 49 | this._keep_off_ledges = 0; 50 | clearTimeout(this._reset_ledges); 51 | this._reset_ledges = setTimeout(()=>this._keep_off_ledges = 1, 1000); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /source/entity_enemy_ogre.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_enemy_ogre_t extends entity_enemy_t { 3 | _init(patrol_dir) { 4 | super._init(patrol_dir); 5 | this._model = model_ogre; 6 | this._texture = 20; 7 | this._speed = 96; 8 | this._health = 200; 9 | this.s = vec3(14,36,14); 10 | 11 | this._attack_distance = 350; 12 | this._ANIMS =[ 13 | [1, [0]], // 0: Idle 14 | [0.80, [1,2,3,4]], // 1: Walk 15 | [0.40, [1,2,3,4]], // 2: Run 16 | [0.35, [0,5,5,5]], // 3: Attack prepare 17 | [0.35, [5,0,0,0]], // 4: Attack 18 | ]; 19 | } 20 | 21 | _attack() { 22 | this._play_sound(sfx_grenade_shoot); 23 | this._spawn_projectile(entity_projectile_grenade_t, 600, 0, -0.4)._damage = 40; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/entity_enemy_zombie.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_enemy_zombie_t extends entity_enemy_t { 3 | _init(patrol_dir) { 4 | super._init(patrol_dir); 5 | this._model = model_zombie; 6 | this._texture = 18; 7 | this._speed = 0; 8 | this._attack_distance = 350; 9 | this._health = 60; 10 | 11 | this._ANIMS[3] = [0.25, [0,0,5,5]]; // 3: Attack prepare 12 | 13 | this._STATE_FOLLOW = [0, 0, 0.1]; 14 | this._STATE_ATTACK_RECOVER = [0, 0, 1.1, this._STATE_IDLE]; 15 | this._STATE_ATTACK_EXEC = [4, 0, 0.4, this._STATE_ATTACK_RECOVER]; 16 | this._STATE_ATTACK_PREPARE = [3, 0, 0.4, this._STATE_ATTACK_EXEC]; 17 | this._STATE_ATTACK_AIM = [0, 0, 0.1, this._STATE_ATTACK_PREPARE]; 18 | this._STATE_EVADE = [0, 0, 0.1, this._STATE_ATTACK_AIM]; 19 | 20 | this._set_state(this._STATE_IDLE); 21 | } 22 | 23 | _receive_damage(from, amount) { 24 | // Ignore damage that's not large enough to gib us 25 | if (amount > 60) { 26 | super._receive_damage(from, amount) 27 | } 28 | } 29 | 30 | _attack() { 31 | this._play_sound(sfx_enemy_hit); 32 | this._spawn_projectile(entity_projectile_gib_t, 600, 0, -0.5); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/entity_light.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_light_t extends entity_t { 3 | _init(light, color) { 4 | this._light = light; 5 | this._spawn_time = game_time; 6 | if (light == 1) { 7 | this._flicker = true; 8 | } 9 | if (!color) { 10 | console.log('no color!') 11 | } 12 | this._color = [ 13 | ((color & 0x7) << 5), 14 | ((color & 0x1c) << 3), 15 | (color & 0xc0) 16 | ]; 17 | } 18 | 19 | _update() { 20 | if (this._flicker && Math.random() > 0.9) { 21 | this._light = Math.random() > 0.5 ? 10 : 0; 22 | } 23 | let intensity = this._light; 24 | 25 | // If this light is a temporary one, fade it out over its lifetime 26 | if (this._die_at) { 27 | if (this._die_at < game_time) { 28 | this._kill(); 29 | } 30 | intensity = scale(game_time, this._spawn_time, this._die_at, 1, 0) * this._light; 31 | } 32 | 33 | r_push_light(this.p, intensity, this._color[0], this._color[1], this._color[2]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/entity_particle.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_particle_t extends entity_t { 3 | _init() { 4 | this._bounciness = 0.5; 5 | this.f = 0.1; 6 | } 7 | 8 | _update() { 9 | this._yaw += this.v.y * 0.001; 10 | this._pitch += this.v.x * 0.001; 11 | this._update_physics(); 12 | this._draw_model(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/entity_pickup.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_pickup_t extends entity_t { 3 | _init() { 4 | this._model = model_pickup_box; 5 | this.s = vec3(12,12,12); 6 | this._yaw += Math.PI/2; 7 | } 8 | 9 | _update() { 10 | if (!this._on_ground) { 11 | this._update_physics(); 12 | } 13 | this._draw_model(); 14 | if (vec3_dist(this.p, game_entity_player.p) < 40) { 15 | this._pickup(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/entity_pickup_grenadelauncher.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_pickup_grenadelauncher_t extends entity_pickup_t { 3 | _init() { 4 | super._init(); 5 | this._texture = 21; 6 | this._model = model_pickup_grenadelauncher; 7 | } 8 | 9 | _update() { 10 | this._yaw += 0.02; 11 | super._update(); 12 | } 13 | 14 | _pickup() { 15 | audio_play(sfx_pickup); 16 | game_entity_player._weapon_index = game_entity_player._weapons.push(new weapon_grenadelauncher_t) - 1; 17 | this._kill(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/entity_pickup_grenades.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_pickup_grenades_t extends entity_pickup_t { 3 | _init() { 4 | super._init(); 5 | this._texture = 25; 6 | this._model = model_pickup_grenades; 7 | } 8 | 9 | _pickup() { 10 | for (let w of game_entity_player._weapons) { 11 | if (w instanceof(weapon_grenadelauncher_t)) { 12 | w._ammo += 10; 13 | audio_play(sfx_pickup); 14 | this._kill(); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/entity_pickup_health.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_pickup_health_t extends entity_pickup_t { 3 | _init() { 4 | super._init(); 5 | this._texture = 23; 6 | } 7 | 8 | _pickup() { 9 | audio_play(sfx_pickup); 10 | game_entity_player._health += 25; 11 | this._kill(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/entity_pickup_key.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_pickup_key_t extends entity_pickup_t { 3 | _init() { 4 | super._init(); 5 | this._texture = 21; 6 | this._model = model_pickup_key; 7 | } 8 | 9 | _update() { 10 | this._yaw += 0.02; 11 | super._update(); 12 | } 13 | 14 | _pickup() { 15 | audio_play(sfx_pickup); 16 | game_show_message('YOU GOT THE KEY!'); 17 | for (let e of game_entities) { 18 | if (e._needs_key) { 19 | e._needs_key = 0; 20 | break; 21 | } 22 | } 23 | this._kill(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/entity_pickup_nailgun.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_pickup_nailgun_t extends entity_pickup_t { 3 | _init() { 4 | super._init(); 5 | this._texture = 12; 6 | this._model = model_pickup_nailgun; 7 | } 8 | 9 | _update() { 10 | this._yaw += 0.02; 11 | super._update(); 12 | } 13 | 14 | _pickup() { 15 | audio_play(sfx_pickup); 16 | game_entity_player._weapon_index = game_entity_player._weapons.push(new weapon_nailgun_t) - 1; 17 | this._kill(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/entity_pickup_nails.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_pickup_nails_t extends entity_pickup_t { 3 | _init() { 4 | super._init(); 5 | this._texture = 24; 6 | } 7 | 8 | _pickup() { 9 | for (let w of game_entity_player._weapons) { 10 | if (w instanceof(weapon_nailgun_t)) { 11 | w._ammo += 50; 12 | audio_play(sfx_pickup); 13 | this._kill(); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/entity_player.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_player_t extends entity_t { 3 | _init() { 4 | this.s = vec3(12,24,12); 5 | this.f = 10; 6 | this._speed = 3000; 7 | this._step_height = 17; 8 | this._can_jump = 0; 9 | this._can_shoot_at = 0; 10 | this._health = 100; 11 | 12 | this._check_against = ENTITY_GROUP_ENEMY; 13 | 14 | this._weapons = [new weapon_shotgun_t]; 15 | this._weapon_index = 0; 16 | 17 | // Map 1 needs some rotation of the starting look-at direction 18 | this._yaw += game_map_index * Math.PI; 19 | this._bob = 0; 20 | 21 | game_entity_player = this; 22 | game_entities_friendly.push(this); 23 | } 24 | 25 | _update() { 26 | // Mouse look 27 | this._pitch = clamp(this._pitch + mouse_y * m.value * (mi.checked ? -0.00015 : 0.00015), -1.5, 1.5); 28 | this._yaw = (this._yaw + mouse_x * m.value * 0.00015) % (Math.PI*2); 29 | 30 | // Acceleration in movement direction 31 | this.a = vec3_mulf( 32 | vec3_rotate_y( 33 | vec3( 34 | keys[key_right] - keys[key_left], 35 | 0, 36 | keys[key_up] - keys[key_down] 37 | ), 38 | this._yaw 39 | ), 40 | this._speed * (this._on_ground ? 1 : 0.3) 41 | ); 42 | 43 | if (keys[key_jump] && this._on_ground && this._can_jump) { 44 | this.v.y = 400; 45 | this._on_ground = 0; 46 | this._can_jump = 0; 47 | } 48 | if (!keys[key_jump]) { 49 | this._can_jump = 1; 50 | } 51 | 52 | this._weapon_index = ( 53 | this._weapon_index + keys[key_next] + this._weapons.length - keys[key_prev] 54 | ) % this._weapons.length; 55 | 56 | let shoot_wait = this._can_shoot_at - game_time, 57 | weapon = this._weapons[this._weapon_index]; 58 | 59 | // Shoot Weapon 60 | if (keys[key_action] && shoot_wait < 0) { 61 | this._can_shoot_at = game_time + weapon._reload; 62 | 63 | if (weapon._needs_ammo && weapon._ammo == 0) { 64 | audio_play(sfx_no_ammo); 65 | } 66 | else { 67 | weapon._shoot(this.p, this._yaw, this._pitch); 68 | game_spawn(entity_light_t, this.p, 10, 0xff)._die_at = game_time + 0.1; 69 | } 70 | } 71 | 72 | this._bob += vec3_length(this.a) * 0.0001; 73 | this.f = this._on_ground ? 10 : 2.5; 74 | this._update_physics(); 75 | 76 | r_camera.x = this.p.x; 77 | r_camera.z = this.p.z; 78 | 79 | // Smooth step up on stairs 80 | r_camera.y = this.p.y + 8 - clamp(game_time - this._stepped_up_at, 0, 0.1) * -160; 81 | 82 | r_camera_yaw = this._yaw; 83 | r_camera_pitch = this._pitch; 84 | 85 | 86 | // Draw weapon at camera position at an offset and add the current 87 | // recoil (calculated from shoot_wait and weapon._reload) accounting 88 | // for the current view yaw/pitch 89 | 90 | r_draw( 91 | vec3_add( 92 | r_camera, 93 | vec3_rotate_yaw_pitch( 94 | vec3( 95 | 0, 96 | -10 + Math.sin(this._bob)*0.3, 97 | 12 + clamp(scale(shoot_wait, 0, weapon._reload, 5, 0), 0, 5) 98 | ), 99 | this._yaw, this._pitch 100 | ) 101 | ), 102 | this._yaw + Math.PI/2, this._pitch, 103 | weapon._texture, weapon._model.f[0], weapon._model.f[0], 0, 104 | weapon._model.nv 105 | ); 106 | 107 | h.textContent = this._health|0; 108 | a.textContent = weapon._needs_ammo ? weapon._ammo : '∞'; 109 | 110 | // Debug: a light around the player 111 | // r_push_light(vec3_add(this.p, vec3(0,64,0)), 10, 255, 192, 32); 112 | } 113 | 114 | _receive_damage(from, amount) { 115 | audio_play(sfx_hurt); 116 | super._receive_damage(from, amount); 117 | } 118 | 119 | _kill() { 120 | super._kill(); 121 | h.textContent = this._health|0; 122 | title_show_message('YOU DIED'); 123 | setTimeout(() => game_init(game_map_index), 2000); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /source/entity_projectile_gib.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_projectile_gib_t extends entity_t { 3 | _init() { 4 | this._texture = 18; 5 | this._bounciness = 0; 6 | this._die_at = game_time + 2; 7 | this._model = model_gib; 8 | 9 | this._yaw = Math.random(); 10 | this._pitch = Math.random(); 11 | } 12 | 13 | _update() { 14 | super._update_physics(); 15 | this._draw_model(); 16 | this.f = this._on_ground ? 15 : 0; 17 | } 18 | 19 | _did_collide(axis) { 20 | if (axis == 1 && this.v.y < -128) { 21 | this._play_sound(sfx_enemy_hit); 22 | } 23 | } 24 | 25 | _did_collide_with_entity(other) { 26 | other._receive_damage(this, 10); 27 | this._kill(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/entity_projectile_grenade.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_projectile_grenade_t extends entity_t { 3 | _init() { 4 | this._texture = 8; 5 | this._model = model_grenade; 6 | this._die_at = game_time + 2; 7 | this._bounciness = 0.5; 8 | this._damage = 120; 9 | } 10 | 11 | _update() { 12 | super._update_physics(); 13 | this._draw_model(); 14 | r_push_light(vec3_add(this.p, vec3(0,16,0)), (Math.sin(game_time*10)+2)*0.5, 255, 32, 0); 15 | this.f = this._on_ground ? 5 : 0.5; 16 | } 17 | 18 | _did_collide(axis) { 19 | if (axis != 1 || this.v.y < -128) { 20 | this._yaw += Math.random(); 21 | this._play_sound(sfx_grenade_bounce); 22 | } 23 | } 24 | 25 | _did_collide_with_entity(other) { 26 | this._kill(); 27 | } 28 | 29 | _kill() { 30 | // Deal some damage to nearby entities 31 | for (let entity of this._check_entities) { 32 | let dist = vec3_dist(this.p, entity.p); 33 | if (dist < 196) { 34 | entity._receive_damage(this, scale(dist, 0, 196, this._damage, 0)); 35 | } 36 | } 37 | 38 | super._kill(); 39 | this._play_sound(sfx_grenade_explode); 40 | this._spawn_particles(20, 800, model_explosion, 8, 1); 41 | game_spawn(entity_light_t, vec3_add(this.p, vec3(0,16,0)), 250, 0x08f)._die_at = game_time + 0.2; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /source/entity_projectile_nail.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_projectile_nail_t extends entity_t { 3 | _init() { 4 | this._texture = 2; 5 | this._model = model_nail; 6 | this._gravity = 0; 7 | this._die_at = game_time + 3; 8 | } 9 | 10 | _update() { 11 | this._update_physics(); 12 | this._draw_model(); 13 | } 14 | 15 | _did_collide(axis) { 16 | this._kill(); 17 | this._play_sound(sfx_nailgun_hit); 18 | this._spawn_particles(2, 80, model_explosion, 8, 0.4); 19 | game_spawn(entity_light_t, this.p, 1, 0xff)._die_at = game_time + 0.1; 20 | } 21 | 22 | _did_collide_with_entity(other) { 23 | this._kill(); 24 | other._receive_damage(this, 9); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/entity_projectile_plasma.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_projectile_plasma_t extends entity_t { 3 | _init() { 4 | this._texture = 21; 5 | this._model = model_nail; 6 | this._gravity = 0; 7 | this._die_at = game_time + 3; 8 | } 9 | 10 | _update() { 11 | this._update_physics(); 12 | this._draw_model(); 13 | r_push_light(this.p, 5, 255, 128, 0); 14 | } 15 | 16 | _did_collide(axis) { 17 | this._kill(); 18 | this._play_sound(sfx_nailgun_hit); 19 | this._spawn_particles(2, 80, model_explosion, 8, 0.4); 20 | game_spawn(entity_light_t, vec3_add(this.p, vec3(0,10,0)), 5, 0xf5)._die_at = game_time + 0.1; 21 | } 22 | 23 | _did_collide_with_entity(other) { 24 | this._kill(); 25 | other._receive_damage(this, 15); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/entity_projectile_shell.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_projectile_shell_t extends entity_t { 3 | _init() { 4 | this._gravity = 0; 5 | this._die_at = game_time + 0.1; 6 | } 7 | 8 | _update() { 9 | this._update_physics(); 10 | } 11 | 12 | _did_collide(axis) { 13 | this._kill(); 14 | this._spawn_particles(2, 80, model_explosion, 4, 0.4); 15 | game_spawn(entity_light_t, this.p, 0.5, 0xff)._die_at = game_time + 0.1; 16 | } 17 | 18 | _did_collide_with_entity(other) { 19 | this._kill(); 20 | other._receive_damage(this, 4); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/entity_torch.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_torch_t extends entity_t { 3 | _init() { 4 | this._texture = 30; 5 | this._model = model_torch; 6 | 7 | this._anim = [0.05, [0,1,2,1,2,0,0,1,2]]; 8 | 9 | this.p.x -= 16; 10 | this.p.z -= 16; 11 | this._light_pos = this.p; 12 | 13 | // Find which wall we're on; move the torch model towards the wall and 14 | // the light position outwards 15 | for (let trace_dir of [vec3(-32,0,0), vec3(32,0,0), vec3(0,0,-32), vec3(0,0,32)]) { 16 | let trace_end = vec3_add(this.p, trace_dir); 17 | if (map_trace(this.p, vec3_add(this.p, trace_dir))) { 18 | this.p = vec3_add(this.p, vec3_mulf(trace_dir, 0.4)); 19 | this._light_pos = vec3_sub(this.p, vec3_mulf(trace_dir, 2)); 20 | break; 21 | } 22 | } 23 | 24 | this._light = 0; 25 | } 26 | 27 | _update() { 28 | super._update(); 29 | 30 | if (Math.random() > 0.8) { 31 | this._light = Math.random(); 32 | } 33 | r_push_light(this._light_pos, Math.sin(game_time)+this._light+6, 255,192,16); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/entity_trigger_level.js: -------------------------------------------------------------------------------- 1 | 2 | class entity_trigger_level_t extends entity_t { 3 | _update() { 4 | if (!this._dead && vec3_dist(this.p, game_entity_player.p) < 64) { 5 | game_next_level(); 6 | this._dead = 1; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/game.js: -------------------------------------------------------------------------------- 1 | 2 | let 3 | game_tick = 0, 4 | game_time = 0.016, 5 | game_real_time_last, 6 | game_message_timeout = 0, 7 | 8 | game_entities, 9 | game_entities_enemies, 10 | game_entities_friendly, 11 | game_entity_player, 12 | game_map_index, 13 | game_jump_to_next_level, 14 | 15 | game_init = (map_index) => { 16 | ts.style.display = 'none', 17 | 18 | game_entities = []; 19 | game_entities_enemies = []; 20 | game_entities_friendly = []; 21 | 22 | game_map_index = map_index; 23 | map_init(map_data[game_map_index]); 24 | }, 25 | 26 | game_next_level = () => { 27 | game_jump_to_next_level = 1; 28 | }, 29 | 30 | game_spawn = (type, pos, p1, p2) => { 31 | let entity = new (type)(pos, p1, p2) 32 | game_entities.push(entity); 33 | return entity; 34 | }, 35 | 36 | game_show_message = (text) => { 37 | msg.textContent = text; 38 | msg.style.display = 'block'; 39 | clearTimeout(game_message_timeout); 40 | game_message_timeout = setTimeout(()=>msg.style.display = 'none', 2000); 41 | }, 42 | 43 | title_show_message = (msg, sub = '') => { 44 | ts.innerHTML = '

'+msg+'

' + sub; 45 | ts.style.display = 'block'; 46 | }, 47 | 48 | game_run = (time_now) => { 49 | requestAnimationFrame(game_run); 50 | 51 | time_now *= 0.001; 52 | game_tick = Math.min((time_now - (game_real_time_last||time_now)),0.05); 53 | game_real_time_last = time_now; 54 | game_time += game_tick; 55 | 56 | r_prepare_frame(0.1, 0.2, 0.5); 57 | 58 | // Update and render entities 59 | let alive_entities = []; 60 | for (let entity of game_entities) { 61 | if (!entity._dead) { 62 | entity._update(); 63 | alive_entities.push(entity); 64 | } 65 | } 66 | game_entities = alive_entities; 67 | 68 | map_draw(); 69 | r_end_frame(); 70 | 71 | // Reset mouse movement and buttons that should be pressed, not held. 72 | mouse_x = mouse_y = 0; 73 | keys[key_next] = keys[key_prev] = 0; 74 | 75 | if (game_jump_to_next_level) { 76 | game_jump_to_next_level = 0; 77 | game_map_index++; 78 | if (game_map_index == 2) { 79 | title_show_message('THE END', 'THANKS FOR PLAYING ❤'); 80 | h.textContent = a.textContent = ''; 81 | game_entity_player._dead = 1; 82 | 83 | // Set camera position for end screen 84 | r_camera = vec3(1856,784,2272); 85 | r_camera_yaw = 0; 86 | r_camera_pitch = 0.5; 87 | } 88 | else { 89 | game_init(game_map_index); 90 | } 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /source/html_template.html: -------------------------------------------------------------------------------- 1 | Q1K3 -------------------------------------------------------------------------------- /source/input.js: -------------------------------------------------------------------------------- 1 | 2 | // We use the ev.code for keyboard input. This contains a string like "KeyW" 3 | // or "ArrowLeft", which is awkward to use, but it's keyboard layout neutral, 4 | // so that WASD should work with any layout. 5 | 6 | // We detect the 6th or 3rd char for each of those strings and map them to an 7 | // in-game button. 8 | 9 | // Movement, Action, Prev/Next, Jump 10 | let keymap = { 11 | W: 1, p: 1, // Key[W] or ArrowU[p] 12 | A: 2, e: 2, // Key[A] or ArrowL[e]ft 13 | S: 3, o: 3, // Key[S] or ArrowD[o]wn 14 | D: 4, i: 4, // Key[D] or ArrowR[i]ght 15 | Q: 5, // Key[Q] 16 | E: 6, // Key[E] 17 | c: 9, // KeySpa[c]e 18 | }, 19 | keys = [ 20 | // Unused zeroth key, so we can test the keymap result for truthiness 21 | 0, 22 | 23 | // WASD/Arrow Keys and prev next have to be set to zero, because we use 24 | // the value (0 or 1) to calculate the move direction or weapon switch 25 | 0,0,0,0, 26 | 0,0 27 | 28 | // Following Keys (action, jump) to not have to be set here 29 | // as they are just tested for truthiness 30 | ], 31 | key_up = 1, 32 | key_down = 3, 33 | key_left = 2, 34 | key_right = 4, 35 | key_prev = 5, 36 | key_next = 6, 37 | key_action = 7, // ev.button = 0 38 | key_jump = 9, // ev.button = 2 39 | mouse_x = 0, 40 | mouse_y = 0, 41 | last_wheel_event = 0; 42 | 43 | document.onkeydown = (ev) => { 44 | let k = keymap[ev.code[6] || ev.code[3]]; 45 | if (k) { 46 | ev.preventDefault(); 47 | keys[k] = 1; 48 | } 49 | }; 50 | 51 | document.onkeyup = (ev) => { 52 | let k = keymap[ev.code[6] || ev.code[3]]; 53 | if (k) { 54 | ev.preventDefault(); 55 | keys[k] = 0; 56 | } 57 | }; 58 | 59 | document.onwheel = (ev) => { 60 | // Allow for one wheel event every 0.1s. This sucks, but prevents free 61 | // spinning or touch scrolling mouses (eg. Apple Magic Mouse) from doing 62 | // wild things. 63 | if (game_time - last_wheel_event > 0.1) { 64 | keys[key_prev + (ev.deltaY > 1 ? 1 : 0)] = 1; 65 | last_wheel_event = game_time; 66 | } 67 | }; 68 | 69 | c.onmousemove = (ev) => { 70 | mouse_x += ev.movementX; 71 | mouse_y += ev.movementY; 72 | }; 73 | 74 | c.onmousedown = (ev) => { 75 | ev.preventDefault(); 76 | keys[key_action + ev.button] = 1; 77 | }; 78 | 79 | c.onmouseup = (ev) => { 80 | ev.preventDefault(); 81 | keys[key_action + ev.button] = 0; 82 | }; 83 | 84 | -------------------------------------------------------------------------------- /source/main.js: -------------------------------------------------------------------------------- 1 | 2 | let 3 | map_data, 4 | model_data, 5 | 6 | // Particles 7 | model_explosion, 8 | model_blood, 9 | model_gib, 10 | model_gib_pieces = [], 11 | 12 | // Enemies 13 | model_grunt, 14 | model_enforcer, 15 | model_ogre, 16 | model_zombie, 17 | model_hound, 18 | 19 | // Map Objects 20 | model_barrel, 21 | model_torch, 22 | 23 | // Weapon view models 24 | model_shotgun, 25 | model_nailgun, 26 | model_grenadelauncher, 27 | 28 | // Pickups 29 | model_pickup_nailgun, 30 | model_pickup_grenadelauncher, 31 | model_pickup_box, 32 | model_pickup_grenades, 33 | model_pickup_key, 34 | model_door, 35 | 36 | // Projectiles 37 | model_grenade, 38 | model_plasma, // aka. nail 39 | 40 | // Sounds 41 | sfx_enemy_hit, 42 | sfx_enemy_gib, 43 | sfx_enemy_hound_attack, 44 | 45 | sfx_no_ammo, 46 | sfx_hurt, 47 | sfx_pickup, 48 | 49 | sfx_plasma_shoot, 50 | 51 | sfx_shotgun_shoot, 52 | sfx_shotgun_reload, 53 | 54 | sfx_nailgun_shoot, 55 | sfx_nailgun_hit, 56 | 57 | sfx_grenade_shoot, 58 | sfx_grenade_bounce, 59 | sfx_grenade_explode, 60 | 61 | game_load = async () => { 62 | r_init(); 63 | 64 | // Create textures 65 | ttt(texture_data).map(r_create_texture); 66 | 67 | // Load map & model containers 68 | map_data = await map_load_container(/*DEBUG[*/ 'build/' + /*]*/ 'l'); 69 | model_data = await model_load_container(/*DEBUG[*/ 'build/' + /*]*/ 'm'); 70 | 71 | // Create models. Many models share the same geometry just with different 72 | // sizes and textures. 73 | // 0: generic blob 74 | // 1: humanoid 75 | // 2: barrel 76 | // 3: q logo 77 | // 4: hound 78 | // 5: box 79 | // 6: nailgun 80 | // 7: torch 81 | 82 | model_q = model_init(model_data[3]); 83 | 84 | model_explosion = model_init(model_data[0], 0.1,0.1,0.1); 85 | model_blood = model_init(model_data[0], 0.1,0.2,0.1); 86 | model_gib = model_init(model_data[0], 0.3,0.6,0.3); 87 | 88 | model_grunt = model_init(model_data[1], 2.5,2.2,2.5); 89 | model_enforcer = model_init(model_data[1], 3,2.7,3); 90 | model_zombie = model_init(model_data[1], 1.5,2,1.5); 91 | model_ogre = model_init(model_data[1], 4,3,4); 92 | model_hound = model_init(model_data[4],2.5,2.5,2.5); 93 | 94 | model_barrel = model_init(model_data[2], 2, 2, 2); 95 | model_torch = model_init(model_data[7], 0.6,1,0.6); 96 | 97 | model_pickup_nailgun = model_init(model_data[6], 1, 1, 1); 98 | model_pickup_grenadelauncher = model_init(model_data[2], 1, 0.5, 0.5); 99 | model_pickup_box = model_init(model_data[5], 0.7, 0.7, 0.7); 100 | model_pickup_grenades = model_init(model_data[5], 0.5, 1, 0.5); 101 | model_pickup_key = model_init(model_data[5], 0.1, 0.7, 0.1); 102 | 103 | model_door = model_init(model_data[5], 5, 5, 0.5); 104 | 105 | model_shotgun = model_init(model_data[2], 1,0.2,0.2); 106 | model_grenadelauncher = model_init(model_data[2], 0.7,0.4,0.4); 107 | model_nailgun = model_init(model_data[6], 0.7,0.7,0.7); 108 | 109 | model_grenade = model_init(model_data[2], 0.3,0.3,0.3); 110 | model_nail = model_init(model_data[2], 0.5,0.1,0.1); 111 | 112 | // Take some parts from the grunt model and build individual giblet models 113 | // from it. Arms and legs and stuff... 114 | for (let i = 0; i < 204; i+=34) { 115 | let m = model_init(model_data[1], 2,1,2); 116 | m.f[0] += i; 117 | m.nv = 34; 118 | model_gib_pieces.push(m); 119 | } 120 | 121 | 122 | r_submit_buffer(); 123 | requestAnimationFrame(run_frame); 124 | 125 | f.onclick = () => g.requestFullscreen(); 126 | g.onclick = () => { 127 | g.onclick = () => c.requestPointerLock(); 128 | g.onclick(); 129 | 130 | audio_init(); 131 | 132 | // Generate sounds 133 | sfx_enemy_hit = audio_create_sound(135, [8,0,0,1,148,1,3,5,0,0,139,1,0,2653,0,2193,255,2,639,119,2,23,0,0,0,0,0,0,0]); 134 | sfx_enemy_gib = audio_create_sound(140, [7,0,0,1,148,1,7,5,0,1,139,1,0,4611,789,15986,195,2,849,119,3,60,0,0,0,1,10,176,1]); 135 | sfx_enemy_hound_attack = audio_create_sound(132, [8,0,0,1,192,1,8,0,0,1,120,1,0,5614,0,20400,192,1,329,252,1,55,0,0,1,1,8,192,3]); 136 | 137 | sfx_no_ammo = audio_create_sound(120, [8,0,0,0,96,1,8,0,0,0,0,0,255,0,0,1075,232,1,2132,255,0,0,0,0,0,0,0,0,0]); 138 | sfx_hurt = audio_create_sound(135, [7,3,140,1,232,3,8,0,9,1,139,3,0,4611,1403,34215,256,4,1316,255,0,0,0,1,0,1,7,255,0]); 139 | sfx_pickup = audio_create_sound(140, [7,0,0,1,187,3,8,0,0,1,204,3,0,4298,927,1403,255,0,0,0,3,35,0,0,0,0,0,0,0]); 140 | 141 | sfx_plasma_shoot = audio_create_sound(135, [8,0,0,1,147,1,6,0,0,1,159,1,0,197,1234,21759,232,2,2902,255,2,53,0,0,0,0,0,0,0]); 142 | 143 | sfx_shotgun_shoot = audio_create_sound(135, [7,3,0,1,255,1,6,0,0,1,255,1,112,548,1979,11601,255,2,2902,176,2,77,0,0,1,0,10,255,1]); 144 | sfx_shotgun_reload = audio_create_sound(125, [9,0,0,1,131,1,0,0,0,0,0,3,255,137,22,1776,255,2,4498,176,2,36,2,84,0,0,3,96,0]); 145 | 146 | sfx_nailgun_shoot = audio_create_sound(130, [7,0,0,1,132,1,8,4,0,1,132,2,162,0,0,8339,232,2,2844,195,2,40,0,0,0,0,0,0,0]); 147 | sfx_nailgun_hit = audio_create_sound(135, [8,0,0,1,148,1,0,0,0,0,0,1,255,0,0,2193,128,2,6982,119,2,23,0,0,0,0,0,0,0]); 148 | 149 | sfx_grenade_shoot = audio_create_sound(127, [8,0,0,1,171,1,9,3,0,1,84,3,96,2653,0,13163,159,2,3206,255,2,64,0,0,0,1,9,226,0]); 150 | sfx_grenade_bounce = audio_create_sound(168, [7,0,124,0,128,0,8,5,127,0,128,0,125,88,0,2193,125,1,1238,240,1,91,3,47,0,0,0,0,0]); 151 | sfx_grenade_explode = audio_create_sound(135, [8,0,0,1,195,1,6,0,0,1,127,1,255,197,1234,21759,232,2,1052,255,4,73,3,25,1,0,10,227,1]); 152 | 153 | 154 | audio_play(audio_create_song(...music_data), 1, 1); 155 | game_init(0); 156 | run_frame = game_run; 157 | }; 158 | }, 159 | 160 | run_frame = (time_now) => { 161 | r_prepare_frame(); 162 | 163 | r_draw( 164 | vec3(0,0,0), 0, 0, 1, 165 | model_q.f[0], model_q.f[0], 0, 166 | model_q.nv 167 | ); 168 | r_push_light( 169 | vec3(Math.sin(time_now*0.00033)*200, 100, -100), 170 | 10, 255,192,32 171 | ); 172 | r_push_light( 173 | vec3_rotate_y(vec3(0, 0, 100),time_now*0.00063), 174 | 10, 32,64,255 175 | ); 176 | r_push_light( 177 | vec3_rotate_y(vec3(100, 0, 0),time_now*0.00053), 178 | 10, 196,128,255 179 | ); 180 | 181 | r_end_frame(); 182 | requestAnimationFrame(run_frame); 183 | }; 184 | 185 | game_load(); -------------------------------------------------------------------------------- /source/map.js: -------------------------------------------------------------------------------- 1 | 2 | let 3 | map, 4 | map_size = 128, 5 | 6 | map_load_container = async (path) => { 7 | /* Parse map container format 8 | typedef struct { 9 | u8 x, y, z; 10 | u8 sx, sy, sz; 11 | } block_t; 12 | 13 | typedef struct { 14 | u8 sentinel; 15 | u8 tex; 16 | } block_texture_t; 17 | 18 | typedef struct { 19 | char type; 20 | u8 x, y, z; 21 | u8 data1, data2; 22 | } entity_t; 23 | 24 | struct { 25 | u16 blocks_size; 26 | block_t blocks[]; 27 | u16 num_entities; 28 | entity_t entities[num_entities]; 29 | } map_data; 30 | 31 | Block data is interleaved with the block_texture_t struct to denote 32 | the texture index to use for the following blocks. 33 | */ 34 | 35 | let data = new Uint8Array(await (await fetch(path)).arrayBuffer()), 36 | maps = []; 37 | for (let i = 0; i < data.length;) { 38 | let blocks_size = data[i++] | (data[i++] << 8), 39 | cm = new Uint8Array(map_size * map_size * map_size >> 3), // collision map 40 | b = data.subarray(i, i += blocks_size), 41 | r = [], 42 | t; 43 | 44 | // Parse block data and construct geometry and the collision map 45 | for (let j = 0; j < b.length;) { 46 | 47 | // First value is either the x coordinate or a texture change 48 | // sentinel value (255) followed by the texture index 49 | if (b[j] == 255) { 50 | j++; 51 | t = b[j++]; 52 | } 53 | let 54 | x = b[j++], y = b[j++], z = b[j++], 55 | sx = b[j++], sy = b[j++], sz = b[j++]; 56 | 57 | // Submit the block to the render buffer; we get the vertex offset 58 | // of this block within the buffer back, so we can draw it later 59 | r.push({ 60 | t, 61 | b: r_push_block( 62 | x << 5, y << 4, z << 5, 63 | sx << 5, sy << 4, sz << 5, 64 | t 65 | ) 66 | }); 67 | 68 | // The collision map is a bitmap; 8 x blocks per byte 69 | for (let cz = z; cz < z + sz; cz++) { 70 | for (let cy = y; cy < y + sy; cy++) { 71 | for (let cx = x; cx < x + sx; cx++) { 72 | cm[ 73 | ( 74 | cz * map_size * map_size + 75 | cy * map_size + 76 | cx 77 | ) >> 3 78 | ] |= 1 << (cx & 7); 79 | } 80 | } 81 | } 82 | } 83 | 84 | // Slice of entity data; we parse it when we actually spawn 85 | // the entities in map_init() 86 | let num_entities = data[i++] | (data[i++] << 8), 87 | e = data.subarray(i, i += num_entities * 6 /*sizeof(entity_t)*/); 88 | maps.push({cm, e, r}); 89 | } 90 | return maps; 91 | }, 92 | 93 | map_init = (m) => { 94 | map = m; 95 | 96 | // Entity Id to class - must be consistent with map_packer.c line ~900 97 | let spawn_class = [ 98 | /* 00 */ entity_player_t, 99 | /* 01 */ entity_enemy_grunt_t, 100 | /* 02 */ entity_enemy_enforcer_t, 101 | /* 03 */ entity_enemy_ogre_t, 102 | /* 04 */ entity_enemy_zombie_t, 103 | /* 05 */ entity_enemy_hound_t, 104 | /* 06 */ entity_pickup_nailgun_t, 105 | /* 07 */ entity_pickup_grenadelauncher_t, 106 | /* 08 */ entity_pickup_health_t, 107 | /* 09 */ entity_pickup_nails_t, 108 | /* 10 */ entity_pickup_grenades_t, 109 | /* 11 */ entity_barrel_t, 110 | /* 12 */ entity_light_t, 111 | /* 13 */ entity_trigger_level_t, 112 | /* 14 */ entity_door_t, 113 | /* 15 */ entity_pickup_key_t, 114 | /* 16 */ entity_torch_t, 115 | ]; 116 | 117 | // Parse entity data and spawn all entities for this map 118 | for (let i = 0; i < map.e.length;) { 119 | let type = spawn_class[m.e[i++]]; 120 | game_spawn( 121 | type, 122 | vec3(m.e[i++] * 32, m.e[i++] * 16, m.e[i++] * 32), 123 | m.e[i++], m.e[i++] 124 | ); 125 | } 126 | }, 127 | 128 | map_block_at = (x, y, z) => 129 | map.cm[ 130 | ( 131 | z * map_size * map_size + 132 | y * map_size + 133 | x 134 | ) >> 3 135 | ] & (1 << (x & 7)), 136 | 137 | map_trace = (a, b) => { 138 | let diff = vec3_sub(b, a), 139 | step_dir = vec3_mulf(vec3_normalize(diff), 16), 140 | steps = vec3_length(diff)/16; 141 | 142 | for (let i = 0; i < steps; i++) { 143 | a = vec3_add(a, step_dir); 144 | if (map_block_at(a.x >> 5, a.y >> 4, a.z >> 5)) { 145 | return a; 146 | } 147 | } 148 | return null; 149 | }, 150 | 151 | map_block_at_box = (box_start, box_end) => { 152 | for (let z = box_start.z >> 5; z <= box_end.z >> 5; z++) { 153 | for (let y = box_start.y >> 4; y <= box_end.y >> 4; y++) { 154 | for (let x = box_start.x >> 5; x <= box_end.x >> 5; x++) { 155 | if (map_block_at(x, y, z)) { 156 | return true; 157 | } 158 | } 159 | } 160 | } 161 | return false; 162 | }, 163 | 164 | map_draw = () => { 165 | let p = vec3(); 166 | for (let r of map.r) { 167 | r_draw(p, 0, 0, r.t, r.b,r.b,0,36); 168 | } 169 | }; 170 | -------------------------------------------------------------------------------- /source/math_utils.js: -------------------------------------------------------------------------------- 1 | 2 | let 3 | clamp = (v, min, max) => v < min ? min : (v > max ? max : v), 4 | scale = (v, in_min, in_max, out_min, out_max) => out_min + ((out_max) - out_min) * (((v) - in_min) / ((in_max) - in_min)), 5 | anglemod = (r) => Math.atan2(Math.sin(r), Math.cos(r)), 6 | vec3 = (x = 0, y = 0, z = 0) => ({x, y, z}), 7 | vec3_rotate_yaw_pitch = (p, yaw, pitch) => vec3_rotate_y(vec3_rotate_x(p, pitch), yaw), 8 | vec3_rotate_y = (p, rad) => vec3(p.z * Math.sin(rad) + p.x * Math.cos(rad), p.y, p.z * Math.cos(rad) - p.x * Math.sin(rad)), 9 | vec3_rotate_x = (p, rad) => vec3(p.x, p.y * Math.cos(rad) - p.z * Math.sin(rad), p.y * Math.sin(rad) + p.z * Math.cos(rad)), 10 | vec3_2d_angle = (a, b) => Math.atan2(b.x - a.x, b.z - a.z), 11 | vec3_clone = (a) => vec3(a.x,a.y,a.z), 12 | vec3_length = (a) => Math.hypot(a.x,a.y,a.z), 13 | vec3_dist = (a, b) => vec3_length(vec3_sub(a, b)), 14 | vec3_dot = (a, b) => (a.x * b.x + a.y * b.y + a.z * b.z), 15 | vec3_add = (a, b) => vec3(a.x + b.x, a.y + b.y, a.z + b.z), 16 | vec3_sub = (a, b) => vec3(a.x - b.x, a.y - b.y, a.z - b.z), 17 | vec3_mul = (a, b) => vec3(a.x * b.x, a.y * b.y, a.z * b.z), 18 | vec3_mulf = (a, b) => vec3(a.x * b, a.y * b, a.z * b), 19 | vec3_cross = (a, b) => vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x), 20 | vec3_normalize = (v) => vec3_mulf(v, 1/vec3_length(v)), 21 | vec3_face_normal = (v0, v1, v2) => vec3_normalize(vec3_cross(vec3_sub(v0, v1), vec3_sub(v2, v1))); 22 | -------------------------------------------------------------------------------- /source/model.js: -------------------------------------------------------------------------------- 1 | 2 | let 3 | model_load_container = async (path) => { 4 | /* Parse Retarded Model Format (.rmf): 5 | struct { 6 | u8 num_frames; 7 | u8 num_verts; // per frame 8 | u8 num_indices; 9 | struct { 10 | u8 x, y, z; 11 | } verts[num_frames * num_verts]; 12 | struct { 13 | u8 a_address_inc, b_index, c_index; 14 | } indices[num_indices]; 15 | } rmf_data; 16 | */ 17 | let data = new Uint8Array(await (await fetch(path)).arrayBuffer()), 18 | models = []; 19 | 20 | for (let i = 0; i < data.length;) { 21 | // let model_size = num_frames * num_verts * 3 + num_indices * 3 22 | let model_size = (data[i++] * data[i++] + data[i++]) * 3; 23 | models.push(data.subarray(i-3, i += model_size)); 24 | } 25 | return models; 26 | }, 27 | 28 | model_init = (data, sx = 1, sy = 1, sz = 1) => { 29 | // Load header, prepare buffers 30 | let j = 0, 31 | num_frames = data[j++], 32 | num_vertices = data[j++], 33 | num_indices = data[j++], 34 | vertices = new Float32Array(num_vertices * num_frames * 3), 35 | indices = new Uint8Array(num_indices * 3), 36 | 37 | index_increment = 0, 38 | offset = 2, 39 | 40 | // Load vertices, center on origin (-15), scale, find the 41 | // min/max x and y to compute our UV coords accordingly. 42 | min_x = 16, 43 | max_x = -16, 44 | min_y = 16, 45 | max_y = -16; 46 | 47 | for (let i = 0; i < num_vertices * num_frames * 3; i += 3) { 48 | vertices[i] = (data[j++] - 15) * sx; 49 | vertices[i+1] = (data[j++] - 15) * sy; 50 | vertices[i+2] = (data[j++] - 15) * sz; 51 | 52 | // Find min/max only for the first frame 53 | if (i < num_vertices * 3) { 54 | min_x = Math.min(min_x, vertices[i]); 55 | max_x = Math.max(max_x, vertices[i]); 56 | min_y = Math.min(min_y, vertices[i+1]); 57 | max_y = Math.max(max_y, vertices[i+1]); 58 | } 59 | } 60 | 61 | // Load indices, 1x 2bit increment, 2x 7bit absolute 62 | for (let i = 0; i < num_indices * 3; i += 3) { 63 | index_increment += data[j++]; 64 | indices[i] = index_increment; 65 | indices[i+1] = data[j++]; 66 | indices[i+2] = data[j++]; 67 | } 68 | 69 | // UV coords in texture space and width/height as fraction of model size 70 | let uf = 1 / (max_x - min_x), 71 | u = -min_x * uf, 72 | vf = -1 / (max_y - min_y), 73 | v = max_y * vf; 74 | 75 | // Compute normals for each frame and face and submit to render buffer. 76 | // Capture the current vertex offset for the first vertex of each frame. 77 | let frames = []; 78 | 79 | for (let frame_index = 0; frame_index < num_frames; frame_index++) { 80 | frames.push(r_num_verts); 81 | 82 | let vertex_offset = frame_index * num_vertices * 3; 83 | for (let i = 0; i < num_indices * 3; i += 3) { 84 | 85 | let mv = [], uv = []; 86 | for (let face_vertex = 0, o = 0; face_vertex < 3; face_vertex++) { 87 | let idx = indices[i + face_vertex] * 3; 88 | mv[face_vertex] = vec3( 89 | vertices[vertex_offset + idx + 0], 90 | vertices[vertex_offset + idx + 1], 91 | vertices[vertex_offset + idx + 2] 92 | ); 93 | uv[face_vertex] = { 94 | u: vertices[idx + 0] * uf + u, 95 | v: vertices[idx + 1] * vf + v 96 | }; 97 | } 98 | 99 | let n = vec3_face_normal(mv[2], mv[1], mv[0]); 100 | r_push_vert(mv[2], n, uv[2].u, uv[2].v); 101 | r_push_vert(mv[1], n, uv[1].u, uv[1].v); 102 | r_push_vert(mv[0], n, uv[0].u, uv[0].v); 103 | } 104 | } 105 | 106 | return {f: frames, nv: num_indices * 3}; 107 | } 108 | -------------------------------------------------------------------------------- /source/music.js: -------------------------------------------------------------------------------- 1 | 2 | let music_data = [6014,21,88,[[[7,0,0,1,255,0,7,0,0,1,255,0,0,100,0,3636,254,2,1199,254,4,71,0,0,0,0,0,0,0],[1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1],[[126,126,0,0,126,0,0,0,0,0,0,0,0,0,0,0,126,126,0,0,126,0,0,0,0,0,0,0,0,0,0,0]]],[[6,0,0,0,255,2,6,0,18,0,255,2,0,100000,56363,100000,199,2,200,254,8,24,0,0,0,0,0,0,0],[0,0,2,2,3,4,2,2,3,5,2,2,3,4,2,2,3,5],[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]],[[7,0,0,0,87,2,8,0,0,0,16,3,8,0,22,2193,255,3,1162,51,10,182,2,190,0,1,10,96,0],[0,0,0,0,0,0,1,1,1,1,1,1,1,1],[[149,149,0,0,149,0,149,0,149,149,0,0,149,0,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]],[[8,0,0,0,65,2,6,0,0,0,243,3,0,200,7505,20000,204,4,6180,81,4,198,0,0,0,0,6,131,0],[0,0,0,0,0,0,0,0,0,0,1,1,2,3,1,1,2,3],[[132,0,0,0,0,0,0,0,133,0,0,0,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[132,0,0,0,0,0,0,0,133,0,0,0,130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[132,0,0,0,0,0,0,0,133,0,0,0,125,0,0,0,0,0,0,0,125,0,0,0,0,0,0,0,0,0,0,0]]]]]; -------------------------------------------------------------------------------- /source/renderer.js: -------------------------------------------------------------------------------- 1 | 2 | let 3 | options = {antialias: false}, 4 | gl = (c.getContext('webgl', options) || c.getContext('experimental-webgl', options)), 5 | 6 | R_MAX_VERTS = 1024 * 64, // allow 512k verts max 7 | R_MAX_LIGHT_V3 = 64, 8 | 9 | // Vertex shader source. This translates the model position & rotation and also 10 | // mixes positions of two buffers for animations. 11 | R_SOURCE_VS = 12 | 'precision highp float;' + 13 | 14 | // Vertex positions, normals and uv coords for the fragment shader 15 | 'varying vec3 vp,vn;' + 16 | 'varying vec2 vt;' + 17 | 18 | // Input vertex positions & normals and blend vertex positions & normals 19 | 'attribute vec3 p,n,p2,n2;' + 20 | 21 | // Input UV coords 22 | 'attribute vec2 t;' + 23 | 24 | // Camera position (x, y, z) and aspect ratio (w) 25 | 'uniform vec4 c;' + 26 | 27 | // Model position (x, y, z) 28 | 'uniform vec3 mp;' + 29 | 30 | // Model rotation (yaw, pitch) 31 | 'uniform vec2 mr;' + 32 | 33 | // Mouse rotation yaw (x), pitch (y) 34 | 'uniform vec2 m;' + 35 | 36 | // Blend factor between the two vertex positions 37 | 'uniform float f;' + 38 | 39 | // Generate a rotation Matrix around the x,y,z axis; 40 | // Used for model rotation and camera yaw 41 | 'mat4 rx(float r){' + 42 | 'return mat4(' + 43 | '1,0,0,0,' + 44 | '0,cos(r),sin(r),0,' + 45 | '0,-sin(r),cos(r),0,' + 46 | '0,0,0,1' + 47 | ');' + 48 | '}' + 49 | 50 | 'mat4 ry(float r){' + 51 | 'return mat4(' + 52 | 'cos(r),0,-sin(r),0,' + 53 | '0,1,0,0,' + 54 | 'sin(r),0,cos(r),0,' + 55 | '0,0,0,1' + 56 | ');' + 57 | '}' + 58 | 59 | 'mat4 rz(float r){' + 60 | 'return mat4(' + 61 | 'cos(r),sin(r),0,0,' + 62 | '-sin(r),cos(r),0,0,' + 63 | '0,0,1,0,' + 64 | '0,0,0,1' + 65 | ');' + 66 | '}' + 67 | 68 | 'void main(void){' + 69 | // Rotation Matrixes for model rotation 70 | 'mat4 '+ 71 | 'mry=ry(mr.x),' + 72 | 'mrz=rz(mr.y);' + 73 | 74 | // Mix vertex positions, rotate and add the model position 75 | 'vp=(mry*mrz*vec4(mix(p,p2,f),1.)).xyz+mp;' + 76 | 77 | // Mix normals 78 | 'vn=(mry*mrz*vec4(mix(n,n2,f),1.)).xyz;' + 79 | 80 | // UV coords are handed over to the fragment shader as is 81 | 'vt=t;' + 82 | 83 | // Final vertex position is transformed by the projection matrix, 84 | // rotated around mouse yaw/pitch and offset by the camera position 85 | // We use a FOV of 90, so the matrix[0] and [5] are conveniently 1. 86 | // (1 / Math.tan((90/180) * Math.PI / 2) === 1) 87 | 'gl_Position=' + 88 | 'mat4(' + 89 | '1,0,0,0,' + 90 | '0,c.w,0,0,' + 91 | '0,0,1,1,' + 92 | '0,0,-2,0' + 93 | ')*' + // projection 94 | 'rx(-m.y)*ry(-m.x)*' + 95 | 'vec4(vp-c.xyz,1.);' + 96 | '}', 97 | 98 | // Fragment shader source. Calculates the lighting, does some cheesy gamma 99 | // correction and reduces the colors of the final output. 100 | R_SOURCE_FS = 101 | 'precision highp float;' + 102 | 103 | // Vertex positions, normals and uv coords 104 | 'varying vec3 vp,vn;' + 105 | 'varying vec2 vt;' + 106 | 107 | 'uniform sampler2D s;' + 108 | 109 | // Lights [(x,y,z), [r,g,b], ...] 110 | 'uniform vec3 l['+R_MAX_LIGHT_V3+'];' + 111 | 112 | 'void main(void){' + 113 | 'gl_FragColor=texture2D(s,vt);' + 114 | 115 | // Debug: no textures 116 | // 'gl_FragColor=vec4(1.0,1.0,1.0,1.0);' + 117 | 118 | // Calculate all lights 119 | 'vec3 vl;' + 120 | 'for(int i=0;i<'+R_MAX_LIGHT_V3+';i+=2) {' + 121 | 'vl+=' + 122 | // Angle to normal 123 | 'max('+ 124 | 'dot('+ 125 | 'vn, normalize(l[i]-vp)' + 126 | ')' + 127 | ',0.)*' + 128 | '(1./pow(length(l[i]-vp),2.))' + // Inverse distance squared 129 | '*l[i+1];' + // Light color/intensity 130 | '}' + 131 | 132 | // Debug: full bright lights 133 | // 'vl = vec3(2,2,2);' + 134 | 135 | 'gl_FragColor.rgb=floor('+ 136 | 'gl_FragColor.rgb*pow(vl,vec3(0.75))'+ // Light, Gamma 137 | '*16.0+0.5'+ 138 | ')/16.0;' + // Reduce final output color for some extra dirty looks 139 | '}', 140 | 141 | // 8 properties per vert [x,y,z, u,v, nx,ny,nz] 142 | r_buffer = new Float32Array(R_MAX_VERTS*8), 143 | r_num_verts = 0, 144 | 145 | // 2 vec3 per light [(x,y,z), [r,g,b], ...] 146 | r_light_buffer = new Float32Array(R_MAX_LIGHT_V3*3), 147 | r_num_lights = 0, 148 | 149 | // Uniform locations 150 | r_u_camera, 151 | r_u_lights, 152 | r_u_mouse, 153 | r_u_pos, 154 | r_u_rotation, 155 | r_u_frame_mix, 156 | 157 | // Vertex attribute location for mixing 158 | r_va_p2, r_va_n2, 159 | 160 | // Texture handles 161 | r_textures = [], 162 | 163 | // Camera position 164 | r_camera = vec3(0, 0,-50), 165 | r_camera_pitch = 0.2, 166 | r_camera_yaw = 0, 167 | 168 | // We collect all draw calls in an array and draw them all at once at the end 169 | // the frame. This way the lights buffer will be completely filled and we 170 | // only need to set it once for all geometry 171 | r_draw_calls = [], 172 | 173 | r_init = () => { 174 | // Create shorthand WebGL function names 175 | // let webglShortFunctionNames = {}; 176 | for (let name in gl) { 177 | if (gl[name].length != undefined) { 178 | gl[name.match(/(^..|[A-Z]|\d.|v$)/g).join('')] = gl[name]; 179 | // webglShortFunctionNames[name] = 'gl.' +name.match(/(^..|[A-Z]|\d.|v$)/g).join(''); 180 | } 181 | } 182 | // console.log(JSON.stringify(webglShortFunctionNames, null, '\t')); 183 | 184 | let shader_program = gl.createProgram(); 185 | gl.attachShader(shader_program, r_compile_shader(gl.VERTEX_SHADER, R_SOURCE_VS)); 186 | gl.attachShader(shader_program, r_compile_shader(gl.FRAGMENT_SHADER, R_SOURCE_FS)); 187 | gl.linkProgram(shader_program); 188 | gl.useProgram(shader_program); 189 | 190 | r_u_camera = gl.getUniformLocation(shader_program, 'c'); 191 | r_u_lights = gl.getUniformLocation(shader_program, 'l'); 192 | r_u_mouse = gl.getUniformLocation(shader_program, 'm'); 193 | r_u_pos = gl.getUniformLocation(shader_program, 'mp'); 194 | r_u_rotation = gl.getUniformLocation(shader_program, 'mr'); 195 | r_u_frame_mix = gl.getUniformLocation(shader_program, 'f'); 196 | 197 | gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); 198 | 199 | r_vertex_attrib(shader_program, 'p', 3, 8, 0); // position 200 | r_vertex_attrib(shader_program, 't', 2, 8, 3); // texture coord 201 | r_vertex_attrib(shader_program, 'n', 3, 8, 5); // normals 202 | 203 | r_va_p2 = r_vertex_attrib(shader_program, 'p2', 3, 8, 0); // mix position 204 | r_va_n2 = r_vertex_attrib(shader_program, 'n2', 3, 8, 5); // mix normals 205 | 206 | gl.enable(gl.DEPTH_TEST); 207 | gl.enable(gl.BLEND); 208 | gl.enable(gl.CULL_FACE); 209 | gl.viewport(0,0,c.width,c.height); 210 | }, 211 | 212 | r_compile_shader = (shader_type, shader_source) => { 213 | let shader = gl.createShader(shader_type); 214 | gl.shaderSource(shader, shader_source); 215 | gl.compileShader(shader); 216 | // console.log(gl.getShaderInfoLog(shader)); 217 | return shader; 218 | }, 219 | 220 | r_vertex_attrib = (shader_program, attrib_name, count, vertex_size, offset) => { 221 | let location = gl.getAttribLocation(shader_program, attrib_name); 222 | gl.enableVertexAttribArray(location); 223 | gl.vertexAttribPointer(location, count, gl.FLOAT, false, vertex_size * 4, offset * 4); 224 | return location; 225 | }, 226 | 227 | r_create_texture = (c) => { 228 | let t = {t:gl.createTexture(), c}; 229 | gl.bindTexture(gl.TEXTURE_2D, t.t); 230 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c); 231 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 232 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST); 233 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 234 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 235 | gl.generateMipmap(gl.TEXTURE_2D); 236 | r_textures.push(t); 237 | }, 238 | 239 | r_prepare_frame = (r,g,b) => { 240 | gl.clearColor(r,g,b,1); 241 | gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT); 242 | 243 | r_num_lights = 0; 244 | r_light_buffer.fill(0); 245 | }, 246 | 247 | r_end_frame = () => { 248 | gl.uniform4f(r_u_camera, r_camera.x, r_camera.y, r_camera.z, 16/9); 249 | gl.uniform2f(r_u_mouse, r_camera_yaw, r_camera_pitch); 250 | gl.uniform3fv(r_u_lights, r_light_buffer); 251 | 252 | let vo = 0, 253 | last_texture = -1; 254 | for (let c of r_draw_calls) { 255 | 256 | // c = [x, y, z, yaw, pitch, texture, offset1, offset2, mix, length] 257 | 258 | // Bind new texture only if it changed from the previous one. The map 259 | // is sorted by texture indices, so this helps. 260 | if (last_texture != c[5]) { 261 | last_texture = c[5]; 262 | gl.bindTexture(gl.TEXTURE_2D, r_textures[last_texture].t); 263 | } 264 | 265 | gl.uniform3f(r_u_pos, c[0], c[1], c[2]); 266 | gl.uniform2f(r_u_rotation, c[3], c[4]); 267 | gl.uniform1f(r_u_frame_mix, c[8]); 268 | 269 | // If we have two different frames, calculate the offset from the 270 | // drawArrays call to the mix frame. 271 | // Setting the vertexAttribPointer is quite expensive, so we only 272 | // do this if we have to; i.e. for animated models. 273 | if (vo != (c[7]-c[6])) { 274 | vo = (c[7]-c[6]); 275 | gl.vertexAttribPointer(r_va_p2, 3, gl.FLOAT, false, 8 * 4, vo*8*4); 276 | gl.vertexAttribPointer(r_va_n2, 3, gl.FLOAT, false, 8 * 4, (vo*8+5)*4); 277 | } 278 | gl.drawArrays(gl.TRIANGLES, c[6], c[9]); 279 | } 280 | 281 | // Reset draw calls 282 | r_draw_calls = []; 283 | }, 284 | 285 | r_draw = (pos, yaw, pitch, texture, f1, f2, mix, num_verts) => { 286 | r_draw_calls.push([ 287 | pos.x, pos.y, pos.z, yaw, pitch, 288 | texture, f1, f2, mix, num_verts 289 | ]); 290 | }, 291 | 292 | r_submit_buffer = () => { 293 | gl.bufferData(gl.ARRAY_BUFFER, r_buffer.subarray(0, r_num_verts*8), gl.STATIC_DRAW); 294 | }, 295 | 296 | r_push_vert = (pos, normal, u, v) => { 297 | r_buffer.set([pos.x, pos.y, pos.z, u, v, normal.x, normal.y, normal.z], r_num_verts * 8); 298 | r_num_verts++; 299 | }, 300 | 301 | r_push_quad = (v0, v1, v2, v3, u, v) => { 302 | let n = vec3_face_normal(v0, v1, v2); 303 | r_push_vert(v0, n, u, 0); 304 | r_push_vert(v1, n, 0, 0); 305 | r_push_vert(v2, n, u, v); 306 | r_push_vert(v3, n, 0, v); 307 | r_push_vert(v2, n, u, v); 308 | r_push_vert(v1, n, 0, 0); 309 | }, 310 | 311 | r_push_block = (x, y, z, sx, sy, sz, texture) => { 312 | let canvas = r_textures[texture].c, 313 | index = r_num_verts, 314 | tx = sx/canvas.width, 315 | ty = sy/canvas.height, 316 | tz = sz/canvas.width, 317 | 318 | // top 319 | v0 = vec3(x, y + sy, z), 320 | v1 = vec3(x + sx, y + sy, z), 321 | v2 = vec3(x, y + sy, z + sz), 322 | v3 = vec3(x + sx, y + sy, z + sz), 323 | 324 | // bottom 325 | v4 = vec3(x, y, z + sz), 326 | v5 = vec3(x + sx, y, z + sz), 327 | v6 = vec3(x, y, z), 328 | v7 = vec3(x + sx, y, z); 329 | 330 | r_push_quad(v0, v1, v2, v3, tx, tz); // top 331 | r_push_quad(v4, v5, v6, v7, tx, tz); // bottom 332 | r_push_quad(v2, v3, v4, v5, tx, ty); // front 333 | r_push_quad(v1, v0, v7, v6, tx, ty); // back 334 | r_push_quad(v3, v1, v5, v7, tz, ty); // right 335 | r_push_quad(v0, v2, v6, v4, tz, ty); // left 336 | return index; 337 | }, 338 | 339 | r_push_light = (pos, intensity, r, g, b) => { 340 | // Calculate the distance to the light, fade it out between 768--1024 341 | let fade = clamp( 342 | scale( 343 | vec3_dist(pos, r_camera), 344 | 768, 1024, 1, 0 345 | ), 346 | 0, 1 347 | ) * intensity * 10; 348 | 349 | if (fade && r_num_lights < R_MAX_LIGHT_V3/2) { 350 | r_light_buffer.set([pos.x, pos.y, pos.z, r*fade, g*fade, b*fade], r_num_lights*6); 351 | r_num_lights++; 352 | } 353 | }; 354 | 355 | -------------------------------------------------------------------------------- /source/textures.js: -------------------------------------------------------------------------------- 1 | 2 | let texture_data = [[64,64,0,2,3,1.4,2,17176,1.3],[64,64,38751,1,18,4,2,2,27,9,65530,0,7,1,-1,9,13,5,52,8,8,65528,39039,4,0,0,0,64,512,15,4,0,0,0,64,64,14],[64,64,38751,1,18,4,2,2,27,9,65530,0,7,1,-1,0,13,64,52,64,8,65531,39039,4,0,0,0,64,512,15,4,0,0,0,64,64,14],[64,64,13119,4,1,0,0,64,64,15,0,24,11,17,50,65523,2,8],[32,32,21839,1,0,2,10,2,11,4,65528,10,25931,4,0,0,0,32,32,14],[32,32,17487,0,1,1,30,30,65528,11,21580,4,0,0,0,32,32,15],[32,32,30015,4,5,0,0,32,32,15,1,5,4,2,2,22,6,65522,0,8],[32,32,8751,1,1,1,8,4,11,5,65524,15,17487,4,0,0,0,64,64,15],[32,32,13119,4,4,0,0,32,32,15,1,10,3,11,6,25,10,64536,64568,65519],[32,32,8751,1,1,1,3,3,4,4,65524,14,21565,1,1,-1,15,1,16,16,65522,7,0,1,-1,0,1,15,16,6,65521,0,4,4,0,0,0,32,32,15],[32,32,8719,2,63506,1,4,0,0,0,32,32,12],[32,32,21295,4,10,0,-4,32,298,10,2,4372,1],[32,32,8463,1,-1,1,35,1,35,4,65522,10,34399,0,-1,6,34,6,65526,2,34399,2,29479,1,4,6,0,0,32,32,5],[32,32,5535,4,0,0,0,128,64,14],[32,32,8463,1,0,0,3,3,4,4,0,10,65521,0,4,4,23,23,10,64885,21551,3,4,16,13,0,11,"::][::",4,0,4,4,26,26,15],[32,32,8751,1,1,1,8,3,11,5,65524,15,17487,0,9,6,14,13,15,65525,4383,4,0,0,0,64,64,12,3,10,11,20267,0,8,"---"],[32,32,17487,4,5,0,0,32,32,15,1,4,4,3,3,22,22,65523,7,30587],[64,64,38767,2,36875,2.5,1,4,10,15,8,39,59,15,15,8463,1,3,30,14,5,15,6,12813,4367,38671,0,20,1,22,6,13119,10,38671,4,0,0,0,64,64,11],[32,32,40975,2,63308,1.5,2,63751,7.3],[64,64,13119,4,17,0,0,64,64,15,0,0,29,64,64,0,0,89,4,4,21,-6,22,24,15],[32,32,13119,4,9,0,0,32,32,15,4,8,6,-22,21,32,15,4,18,0,0,32,32,4],[64,64,13119,0,0,0,64,64,0,0,64271,3,-1,50,33795,0,32,"XXX",4,7,0,0,64,64,6],[64,64,34063,4,7,0,0,64,64,12,2,12554,1],[32,32,65535,4,12,0,0,32,32,9,3,6,30,61455,0,25,"+"],[32,32,5903,4,12,0,0,32,32,9,3,5,14,65529,0,12,"NIИ"],[32,32,64271,0,12,1,7,30,65528,8,63247,4,7,0,0,32,32,8],[32,32,13119,1,1,1,14,14,16,32,56328,15,26399,1,-7,17,14,14,16,32,56328,8,26159,2,29706,1,4,0,0,0,32,320,14],[32,32,33567,1,1,1,6,30,16,31,65526,15,33823,1,9,-14,6,30,16,32,65526,15,29743,2,55625,1.5,4,0,0,0,32,320,15],[32,32,12559,1,1,1,14,14,16,16,65525,7,21295,0,1,1,14,14,65525,0,34399,0,17,17,14,14,65524,0,34399,2,8,1.5],[32,32,9503,4,11,0,0,32,32,12,1,1,1,6,7,6,8,65521,0,4],[32,32,15,4,18,0,16,32,32,15,4,27,0,-16,32,32,10]]; 3 | -------------------------------------------------------------------------------- /source/ttt.js: -------------------------------------------------------------------------------- 1 | /* 2 | TTT Tiny Texture Tumbler 3 | Dominic Szablewski - https://phoboslab.org 4 | 5 | -- LICENSE: The MIT License(MIT) 6 | Copyright(c) 2019 Dominic Szablewski 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files(the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions : 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | // Compress with: 25 | // uglifyjs ttt.js --compress --screw-ie8 --mangle toplevel -o ttt.min.js 26 | let 27 | ttt=(td, only_this_index = -1,stack_depth = 0) => { 28 | return td.filter((d,i) => only_this_index < 0 || i == only_this_index).map(d => { 29 | let i = 0, 30 | e = document.createElement('canvas'), 31 | c = e.getContext('2d'), 32 | rgba_from_2byte = (c) => 33 | 'rgba(' + [ 34 | ((c>>12)&15) * 17, 35 | ((c>>8)&15) * 17, 36 | ((c>>4)&15) * 17, 37 | (c&15)/15 38 | ].join() + ')', 39 | fill_rect = (x, y, w, h, ...colors) => 40 | colors.map((color, j) => { 41 | c.fillStyle = rgba_from_2byte(color); 42 | c.fillRect(x+[-1,1,0][j], y+[-1,1,0][j], w, h); 43 | }) 44 | ; 45 | // Set up canvas width and height 46 | e.width = d[i++]; 47 | e.height = d[i++]; 48 | 49 | // Fill with background color 50 | fill_rect(0, 0, e.width, e.height, 0,0, d[i++]); 51 | 52 | // Perform all the steps for this texture 53 | while (i < d.length) { 54 | let f = [ 55 | // 0 - rectangle: x, y, width, height, top, bottom, fill 56 | (x, y, width, height, top, bottom, fill) => { 57 | fill_rect(x, y, width, height, top, bottom, fill) 58 | }, 59 | 60 | // 1 - rectangle_multiple: start_x, start_y, width, height, 61 | // inc_x, inc_y, top, bottom, fill 62 | (sx, sy, w, h, inc_x, inc_y, top, bottom, fill) => { 63 | for (let x = sx; x < e.width; x += inc_x) { 64 | for (let y = sy; y < e.height; y += inc_y) { 65 | fill_rect(x, y, w, h, top, bottom, fill); 66 | } 67 | } 68 | }, 69 | 70 | // 2 - random noise: color, size 71 | (color, size) => { 72 | for (let x = 0; x < e.width; x += size) { 73 | for (let y = 0; y < e.height; y += size) { 74 | // Take the color value (first 3 nibbles) and 75 | // randomize the alpha value (last nibble) 76 | // between 0 and the input alpha. 77 | fill_rect( 78 | x, y, size, size, 0, 0, 79 | (color&0xfff0) + Math.random()*(color&15) 80 | ); 81 | } 82 | } 83 | }, 84 | 85 | // 3 - text: x, y, color, font,size, text 86 | (x, y, color, font, size, text) => { 87 | c.fillStyle = rgba_from_2byte(color); 88 | c.font = size + 'px ' + ['sans-',''][font]+'serif'; 89 | c.fillText(text, x, y); 90 | }, 91 | 92 | // 4 - draw a previous texture 93 | // We limit the stack depth here to not end up in an infinite 94 | // loop by accident 95 | (texture_index, x, y, w, h, alpha) => { 96 | c.globalAlpha = alpha/15; 97 | ( 98 | texture_index < td.length && stack_depth < 16 && 99 | c.drawImage( 100 | ttt(td, texture_index, stack_depth+1)[0], 101 | x, y, w, h 102 | ) 103 | ); 104 | c.globalAlpha = 1; 105 | } 106 | ][d[i++]]; 107 | f(...d.slice(i, i+=f.length)); 108 | } 109 | return e; 110 | }); 111 | }; 112 | -------------------------------------------------------------------------------- /source/weapons.js: -------------------------------------------------------------------------------- 1 | 2 | class weapon_t { 3 | constructor() { 4 | this._needs_ammo = 1; 5 | this._projectile_offset = vec3(0,0,8); 6 | this._init(); 7 | } 8 | 9 | _shoot(pos, yaw, pitch) { 10 | if (this._needs_ammo) { 11 | this._ammo--; 12 | } 13 | audio_play(this._sound); 14 | this._spawn_projectile(pos, yaw, pitch); 15 | } 16 | 17 | _spawn_projectile(pos, yaw, pitch) { 18 | let projectile = game_spawn(this._projectile_type, vec3_add( 19 | pos, 20 | vec3_add( 21 | vec3(0, 12, 0), 22 | vec3_rotate_yaw_pitch( 23 | this._projectile_offset, 24 | yaw, pitch 25 | ) 26 | ) 27 | )); 28 | 29 | // Set the projectile velocity, yaw and pitch 30 | projectile.v = vec3_rotate_yaw_pitch( 31 | vec3(0, 0, this._projectile_speed), 32 | yaw, pitch 33 | ); 34 | projectile._yaw = yaw -Math.PI/2; 35 | projectile._pitch = -pitch; 36 | projectile._check_against = ENTITY_GROUP_ENEMY; 37 | 38 | // Alternate left/right fire for next projectile (nailgun) 39 | this._projectile_offset.x *= -1; 40 | } 41 | } 42 | 43 | class weapon_shotgun_t extends weapon_t { 44 | _init() { 45 | this._texture = 7; 46 | this._model = model_shotgun; 47 | this._sound = sfx_shotgun_shoot; 48 | this._needs_ammo = 0; 49 | this._reload = 0.9; 50 | this._projectile_type = entity_projectile_shell_t; 51 | this._projectile_speed = 10000; 52 | } 53 | 54 | _spawn_projectile(pos, yaw, pitch) { 55 | setTimeout(()=>audio_play(sfx_shotgun_reload), 200); 56 | setTimeout(()=>audio_play(sfx_shotgun_reload), 350); 57 | for (let i = 0; i < 8; i++) { 58 | super._spawn_projectile(pos, yaw+Math.random()*0.08-0.04, pitch+Math.random()*0.08-0.04); 59 | } 60 | } 61 | } 62 | 63 | class weapon_nailgun_t extends weapon_t { 64 | _init() { 65 | this._texture = 4; 66 | this._model = model_nailgun; 67 | this._sound = sfx_nailgun_shoot; 68 | this._ammo = 100; 69 | this._reload = 0.09; 70 | this._projectile_type = entity_projectile_nail_t; 71 | this._projectile_speed = 1300; 72 | this._projectile_offset = vec3(6,0,8); 73 | } 74 | } 75 | 76 | class weapon_grenadelauncher_t extends weapon_t { 77 | _init() { 78 | this._texture = 21; 79 | this._model = model_grenadelauncher; 80 | this._sound = sfx_grenade_shoot; 81 | this._ammo = 10; 82 | this._reload = 0.650; 83 | this._projectile_type = entity_projectile_grenade_t; 84 | this._projectile_speed = 900; 85 | } 86 | } -------------------------------------------------------------------------------- /source/wrap_post.js: -------------------------------------------------------------------------------- 1 | }; 2 | gs(); -------------------------------------------------------------------------------- /source/wrap_pre.js: -------------------------------------------------------------------------------- 1 | // Holy hell, wtf, bbq! If we don't wrap the whole game in a global function, 2 | // installed on the window scope, everything will be garbage collected in chrome 3 | // in the roadrolled version, even though the requestAnimationFrame should hold 4 | // on to the closure. 5 | 6 | // I assumme it's a weird interaction between eval() (used by roadroller) and 7 | // async functions. In any case, it seems to be a bug in v8. 8 | 9 | gs = () => { --------------------------------------------------------------------------------