├── .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 = () => {
--------------------------------------------------------------------------------