WIP sources and TODOs.

This commit is contained in:
Roz K 2022-08-28 05:09:52 +02:00
parent 28ba30ab53
commit 94a150307b
Signed by: roz
GPG Key ID: 51FBF4E483E1C822
52 changed files with 28435 additions and 0 deletions

3
.gitignore vendored
View File

@ -152,3 +152,6 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
# RK
*.blend1
*.rkar

11
BUILDING Normal file
View File

@ -0,0 +1,11 @@
Submodules:
git submodule update --init
Building engine:
make -C engine all
Building resources:
python3 -m game.obj2rkar data/rk_island.rkar data/tiles/tiles.obj data/tests.obj

3
DEPENDENCIES Normal file
View File

@ -0,0 +1,3 @@
Python (3.8+) modules:
- png

2
data/LICENSE Normal file
View File

@ -0,0 +1,2 @@
CC BY-NC-SA 4.0
https://creativecommons.org/licenses/by-nc-sa/4.0/

BIN
data/sea_bump.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
data/sea_bump.xcf Normal file

Binary file not shown.

BIN
data/sea_bump1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
data/sea_bump1.xcf Normal file

Binary file not shown.

BIN
data/sea_bump2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
data/sea_bump2.xcf Normal file

Binary file not shown.

BIN
data/tests.blend Normal file

Binary file not shown.

32
data/tests.mtl Normal file
View File

@ -0,0 +1,32 @@
# Blender 3.2.1 MTL File: 'tests.blend'
# www.blender.org
newmtl blob
Ns 1000.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 0.278396 0.454914
Ks 1.000000 1.000000 1.000000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
newmtl clouds
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 1.000000 1.000000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 1
newmtl cube
Ns 562.500000
Ka 0.750000 0.750000 0.750000
Kd 1.000000 0.886007 0.495536
Ks 0.250000 0.250000 0.250000
Ke 0.000000 0.000000 0.000000
Ni 0.500000
d 1.000000
illum 3

26258
data/tests.obj Normal file

File diff suppressed because it is too large Load Diff

BIN
data/tiles/forest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
data/tiles/forest.xcf Normal file

Binary file not shown.

BIN
data/tiles/forest_r.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

BIN
data/tiles/forest_r.xcf Normal file

Binary file not shown.

BIN
data/tiles/grass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
data/tiles/grass.xcf Normal file

Binary file not shown.

BIN
data/tiles/lava.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
data/tiles/lava.xcf Normal file

Binary file not shown.

BIN
data/tiles/mud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
data/tiles/mud.xcf Normal file

Binary file not shown.

BIN
data/tiles/rock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
data/tiles/rock.xcf Normal file

Binary file not shown.

BIN
data/tiles/sand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
data/tiles/sand.xcf Normal file

Binary file not shown.

BIN
data/tiles/tiles.blend Normal file

Binary file not shown.

5
data/tiles/tiles.hacks Normal file
View File

@ -0,0 +1,5 @@
water = _water mesh grass
sand = _sand mesh grass
rock = _rock mesh grass
mud = _mud mesh grass
lava = _lava mesh grass

80
data/tiles/tiles.mtl Normal file
View File

@ -0,0 +1,80 @@
# Blender 3.2.2 MTL File: 'tiles.blend'
# www.blender.org
newmtl forest
Ns 10.000005
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.100000 0.100000 0.100000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
map_Kd forest.png
map_Ns forest_r.png
newmtl grass
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.100000 0.100000 0.100000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
map_Kd grass.png
newmtl lava
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.200000 0.200000 0.200000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
map_Kd lava.png
newmtl mud
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.100000 0.100000 0.100000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
map_Kd mud.png
newmtl rock
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.100000 0.100000 0.100000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
map_Kd rock.png
newmtl sand
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.100000 0.100000 0.100000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
map_Kd sand.png
newmtl water
Ns 62.500000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 1.000000 1.000000 1.000000
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
map_Kd water.png

395
data/tiles/tiles.obj Normal file
View File

@ -0,0 +1,395 @@
# Blender 3.2.2
# www.blender.org
mtllib tiles.mtl
o grass
v -4.000000 -4.000000 0.000000
v 0.000000 -4.000000 0.000000
v 4.000000 -4.000000 0.000000
v -4.000000 0.000000 0.000000
v 0.000000 0.000000 0.000000
v 4.000000 0.000000 0.000000
v -4.000000 4.000000 0.000000
v 0.000000 4.000000 0.000000
v 4.000000 4.000000 0.000000
v -2.000000 -2.000000 0.000000
v 2.000000 -2.000000 0.000000
v -2.000000 2.000000 0.000000
v 2.000000 2.000000 0.000000
vn -0.0000 -0.0000 1.0000
vt 0.000000 0.000000
vt 0.500000 0.000000
vt 1.000000 0.000000
vt 0.000000 0.500000
vt 0.500000 0.500000
vt 1.000000 0.500000
vt 0.000000 1.000000
vt 0.500000 1.000000
vt 1.000000 1.000000
vt 0.250000 0.250000
vt 0.750000 0.250000
vt 0.250000 0.750000
vt 0.750000 0.750000
s 0
usemtl grass
f 2/2/1 3/3/1 11/11/1
f 4/4/1 5/5/1 12/12/1
f 5/5/1 6/6/1 13/13/1
f 1/1/1 2/2/1 10/10/1
f 2/2/1 5/5/1 10/10/1
f 5/5/1 4/4/1 10/10/1
f 4/4/1 1/1/1 10/10/1
f 3/3/1 6/6/1 11/11/1
f 6/6/1 5/5/1 11/11/1
f 5/5/1 2/2/1 11/11/1
f 5/5/1 8/8/1 12/12/1
f 8/8/1 7/7/1 12/12/1
f 7/7/1 4/4/1 12/12/1
f 6/6/1 9/9/1 13/13/1
f 9/9/1 8/8/1 13/13/1
f 8/8/1 5/5/1 13/13/1
o _water
v 4.000000 4.000000 0.000000
v -4.000000 4.000000 0.000000
v 4.000000 -4.000000 0.000000
v -4.000000 -4.000000 0.000000
vn -0.0000 -0.0000 1.0000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
s 0
usemtl water
f 16/16/2 14/14/2 15/15/2
f 16/16/2 15/15/2 17/17/2
o _sand
v 4.000000 4.000000 0.000000
v -4.000000 4.000000 0.000000
v 4.000000 -4.000000 0.000000
v -4.000000 -4.000000 0.000000
vn -0.0000 -0.0000 1.0000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
s 0
usemtl sand
f 20/20/3 18/18/3 19/19/3
f 20/20/3 19/19/3 21/21/3
o _rock
v 4.000000 4.000000 0.000000
v -4.000000 4.000000 0.000000
v 4.000000 -4.000000 0.000000
v -4.000000 -4.000000 0.000000
vn -0.0000 -0.0000 1.0000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
s 0
usemtl rock
f 24/24/4 22/22/4 23/23/4
f 24/24/4 23/23/4 25/25/4
o _mud
v 4.000000 4.000000 0.000000
v -4.000000 4.000000 0.000000
v 4.000000 -4.000000 0.000000
v -4.000000 -4.000000 0.000000
vn -0.0000 -0.0000 1.0000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
s 0
usemtl mud
f 28/28/5 26/26/5 27/27/5
f 28/28/5 27/27/5 29/29/5
o _lava
v -4.000000 -4.000000 0.000000
v 4.000000 -4.000000 0.000000
v -4.000000 4.000000 0.000000
v 4.000000 4.000000 0.000000
vn -0.0000 -0.0000 1.0000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
s 0
usemtl lava
f 31/31/6 32/32/6 30/30/6
f 31/31/6 33/33/6 32/32/6
o forest
v -4.000000 -4.000000 0.000000
v 0.000000 -4.000000 0.000000
v 4.000000 -4.000000 0.000000
v -4.000000 0.000000 0.000000
v 4.000000 0.000000 0.000000
v -4.000000 4.000000 0.000000
v 0.000000 4.000000 0.000000
v 4.000000 4.000000 0.000000
v -1.362234 -1.413421 0.000000
v 0.300244 -1.447442 -0.000000
v 2.000000 2.000000 -0.000000
v -2.500000 -2.500000 -0.000000
v -0.492377 -3.454313 -0.000000
v 2.000000 -2.000000 -0.000000
v -2.000000 2.000000 0.000000
v 2.067242 -0.486847 -0.000000
v -0.500000 -1.818340 0.740029
v 0.818340 -0.500000 0.563972
v -1.818340 -0.500000 0.563972
v -0.500000 0.818340 0.683588
v 0.575498 -0.487720 3.437187
v -0.507330 -1.421268 3.375877
v -0.512277 0.339565 3.328456
v -1.427362 -0.522726 3.423728
v 2.886405 -0.483161 5.390676
v -0.487300 -3.870228 5.409079
v -0.469270 2.809886 5.425605
v -4.131104 -0.500174 5.195535
v 2.312899 -0.687081 9.665462
v -0.505750 -3.461892 9.618814
v -0.244152 2.336861 9.637057
v -3.688147 -0.482677 9.398805
v -3.115998 -3.115998 8.130575
v -3.110920 2.110920 6.971905
v 2.082467 2.063153 8.144089
v 2.240869 -3.240869 6.971905
v -0.502522 -0.497707 10.881164
v -1.198718 0.104834 3.915295
v -1.328801 -1.139141 4.040936
v -3.441890 -0.495848 -0.000000
v -1.496232 0.385397 -0.000000
v -0.500000 2.662470 -0.000000
v 0.191434 0.532933 -0.000000
v 0.311519 -1.083752 4.188119
v 0.218048 0.042459 3.963527
vn -0.0000 -0.0000 1.0000
vn -0.0201 0.0161 0.9997
vn -0.0167 0.0210 0.9996
vn -0.0009 0.0032 1.0000
vn 0.0244 0.0111 0.9996
vn 0.0019 0.0045 1.0000
vn -0.0161 -0.0201 0.9997
vn 0.0022 0.0009 1.0000
vn 0.0235 -0.0145 0.9996
vn -0.0013 -0.0031 1.0000
vn 0.0145 0.0235 0.9996
vn 0.0019 -0.0045 1.0000
vn -0.0222 -0.0167 0.9996
vn -0.0022 0.0009 1.0000
vn 0.0167 -0.0222 0.9996
vn 0.0098 -0.7987 0.6016
vn -0.6002 -0.5408 0.5893
vn 0.0044 -0.4121 0.9111
vn 0.7681 -0.0085 0.6403
vn 0.6329 -0.5379 0.5569
vn 0.4115 0.0013 0.9114
vn -0.7225 -0.0072 0.6914
vn -0.5435 0.5132 0.6643
vn -0.3281 -0.0009 0.9446
vn 0.0396 0.7635 0.6445
vn 0.6430 0.5228 0.5597
vn 0.0118 0.3475 0.9376
vn 0.0112 -0.9310 -0.3649
vn -0.0190 -0.0189 0.9996
vn 0.0255 -0.0085 0.9996
vn -0.0021 0.0025 1.0000
vn -0.0059 0.0251 0.9997
vn 0.0258 -0.0005 0.9997
vn -0.0242 0.0132 0.9996
vn 0.0084 -0.0263 0.9996
vn 0.0013 0.0020 1.0000
vn -0.0255 0.0110 0.9996
vn 0.0047 -0.0013 1.0000
vn 0.0163 0.0225 0.9996
vn -0.0405 0.9454 -0.3235
vn -0.8888 -0.0010 -0.4582
vn -0.5741 0.5901 -0.5676
vn 0.9300 0.0097 -0.3674
vn 0.0432 0.9043 -0.4247
vn -0.8888 -0.0140 -0.4582
vn -0.6604 0.7507 -0.0172
vn -0.5551 -0.6008 -0.5753
vn 0.8903 0.0610 -0.4513
vn 0.7025 0.6869 0.1860
vn -0.0339 -0.8975 -0.4397
vn 0.7100 -0.7018 -0.0587
vn 0.0210 -0.7749 0.6317
vn 0.7640 -0.0622 0.6422
vn -0.0221 0.0118 0.9997
vn -0.0259 -0.0071 0.9996
vn 0.0113 -0.0243 0.9996
vn 0.0108 0.0234 0.9997
vn 0.0154 -0.0207 0.9997
vn 0.0042 0.0024 1.0000
vn -0.0167 0.0220 0.9996
vn -0.0033 -0.0001 1.0000
vn -0.0233 -0.0149 0.9996
vn -0.0083 0.0265 0.9996
vn 0.0268 0.0073 0.9996
vn -0.0005 0.0023 1.0000
vn -0.6682 -0.7164 0.2005
vn -0.7942 0.0579 0.6049
vn 0.0056 0.7710 0.6368
vn 0.5913 -0.6605 -0.4628
vn 0.6398 -0.1352 -0.7566
vn 0.4614 -0.4637 -0.7564
vn 0.0956 -0.6354 -0.7663
vn 0.5767 0.6707 -0.4664
vn 0.0524 0.6458 -0.7617
vn 0.4830 0.4998 -0.7190
vn -0.5477 -0.0500 -0.8352
vn -0.4291 0.5258 -0.7344
vn -0.4247 -0.5067 -0.7503
vt 0.000000 0.000000
vt 0.500000 0.000000
vt 1.000000 0.000000
vt -0.000000 0.500000
vt 1.000000 0.500000
vt 0.000000 1.000000
vt 0.500000 1.000000
vt 1.000000 1.000000
vt 0.273438 0.289062
vt 0.046875 0.046875
vt 0.726923 0.289572
vt 0.953125 0.046875
vt 0.750000 0.750000
vt 0.250000 0.250000
vt 0.492188 0.070312
vt 0.140625 -0.000000
vt 0.859375 -0.000000
vt 0.750000 0.250000
vt 0.250000 0.750000
vt 0.929688 0.492188
vt 1.000000 0.093750
vt 1.000000 0.906250
vt 0.492188 0.085938
vt 0.914062 0.492188
vt 0.085938 0.492188
vt 0.492188 0.898438
vt 0.882812 0.492188
vt 0.492188 0.117188
vt 0.492188 0.867188
vt 0.117188 0.492188
vt 0.742188 0.492188
vt 0.835938 0.492188
vt 0.492188 0.164062
vt 0.492188 0.257812
vt 0.492188 0.742188
vt 0.492188 0.835938
vt 0.257812 0.492188
vt 0.164062 0.492188
vt 0.601562 0.492188
vt 0.492188 0.382812
vt 0.492188 0.617188
vt 0.382812 0.492188
vt 0.398438 0.398438
vt 0.398438 0.585938
vt 0.585938 0.585938
vt 0.585938 0.398438
vt 0.492188 0.492188
vt 0.335938 0.664062
vt 0.304688 0.710938
vt 0.335938 0.335938
vt 0.289062 0.289062
vt 0.070312 0.492188
vt 0.000000 0.109375
vt 0.000000 0.875000
vt 0.289062 0.710938
vt 0.046875 0.953125
vt 0.492188 0.914062
vt 0.125000 1.000000
vt 0.875000 1.000000
vt 0.726562 0.695312
vt 0.953125 0.953125
vt 0.664062 0.335938
vt 0.710938 0.289062
vt 0.664062 0.664062
vt 0.710938 0.695312
s 0
usemtl forest
f 35/35/7 36/36/7 47/51/8
f 34/34/7 35/35/7 45/47/9
f 37/37/10 34/34/7 45/47/11
f 36/36/7 38/38/12 47/51/13
f 40/40/14 39/39/7 48/52/15
f 39/39/7 37/37/16 48/52/17
f 38/38/18 41/41/7 44/46/19
f 41/41/7 40/40/20 44/46/21
s 1
f 50/56/22 42/42/23 46/48/24
f 51/57/25 43/44/26 49/53/27
f 52/58/28 74/88/29 73/85/30
f 53/59/31 74/88/29 52/58/28
f 53/59/31 76/93/32 75/90/33
f 50/56/22 46/48/24 43/44/26
f 51/57/25 50/56/22 43/44/26
f 51/57/25 49/53/27 76/93/32
f 53/59/31 51/57/25 76/93/32
f 53/59/31 75/90/33 74/88/29
f 52/58/28 73/85/30 42/42/23
f 52/58/28 42/42/23 50/56/22
f 51/57/25 55/61/34 50/56/22
s 0
f 45/47/35 46/49/7 42/43/7
f 45/47/36 73/86/7 37/37/37
f 47/51/38 46/50/7 35/35/7
f 47/51/39 49/54/7 43/45/7
f 48/52/40 73/87/7 74/89/7
f 48/52/41 75/91/7 40/40/42
f 44/46/43 49/55/7 38/38/44
f 44/46/45 75/92/7 76/94/7
s 1
f 56/62/46 57/63/47 71/82/48
f 53/59/31 54/60/49 51/57/25
f 52/58/28 56/62/46 53/59/31
f 50/56/22 57/63/47 52/58/28
f 60/68/50 61/70/51 67/77/52
f 57/63/47 55/61/34 72/84/53
f 58/64/54 60/68/50 68/78/55
f 59/67/56 58/64/54 69/79/57
f 63/73/58 62/72/59 70/80/60
s 0
f 45/47/61 35/35/7 46/49/7
f 45/47/62 42/43/7 73/86/7
f 47/51/63 43/45/7 46/50/7
f 47/51/64 38/38/65 49/54/7
f 48/52/66 37/37/67 73/87/7
f 48/52/68 74/89/7 75/91/7
f 44/46/69 76/94/7 49/55/7
f 44/46/70 40/40/71 75/92/7
s 1
f 61/70/51 59/67/56 66/76/72
f 59/67/56 63/73/58 66/76/72
f 63/73/58 65/75/73 66/76/72
f 65/75/73 61/70/51 66/76/72
f 61/70/51 65/75/73 67/77/52
f 65/75/73 64/74/74 67/77/52
f 64/74/74 60/68/50 67/77/52
f 60/68/50 64/74/74 68/78/55
f 64/74/74 62/72/59 68/78/55
f 62/72/59 58/64/54 68/78/55
f 58/64/54 62/72/59 69/79/57
f 62/72/59 63/73/58 69/79/57
f 63/73/58 59/67/56 69/79/57
f 62/72/59 64/74/74 70/80/60
f 64/74/74 65/75/73 70/80/60
f 65/75/73 63/73/58 70/80/60
f 55/61/34 54/60/49 77/96/75
f 54/60/49 58/65/76 77/96/75
f 58/64/54 59/67/56 77/95/77
f 59/66/78 55/61/34 77/96/75
f 54/60/49 56/62/46 78/98/79
f 56/62/46 60/69/80 78/98/79
f 60/68/50 58/64/54 78/97/81
f 58/65/76 54/60/49 78/98/79
f 57/63/47 61/71/82 71/82/48
f 61/70/51 60/68/50 71/81/83
f 60/69/80 56/62/46 71/82/48
f 55/61/34 59/66/78 72/84/53
f 59/67/56 61/70/51 72/83/84
f 61/71/82 57/63/47 72/84/53
f 51/57/25 54/60/49 55/61/34
f 53/59/31 56/62/46 54/60/49
f 52/58/28 57/63/47 56/62/46
f 50/56/22 55/61/34 57/63/47

BIN
data/tiles/water.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
data/tiles/water.xcf Normal file

Binary file not shown.

BIN
docs/tiling.odg Normal file

Binary file not shown.

0
game/__init__.py Normal file
View File

60
game/batch.py Normal file
View File

@ -0,0 +1,60 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from array import array
from engine import (
INSTANCE_FLAG_SPAWNED, BATCH_MAX_SIZE, BATCH_ORIENTATION_FORMAT_NONE, create_batch, draw_batch, destroy_batch)
class Batch:
__slots__ = '_batch', 'max_size', 'flags', 'texlevels', 'meshes', 'translations', 'orientations'
def __init__(self, max_size, translation_format, orientation_format):
assert max_size <= BATCH_MAX_SIZE
self._batch = create_batch(max_size, translation_format, orientation_format)
self.max_size = max_size
self.flags = array('B')
self.texlevels = array('H')
self.meshes = array('I')
self.translations = array('f')
if orientation_format != BATCH_ORIENTATION_FORMAT_NONE:
self.orientations = array('f')
else:
self.orientations = None
def __del__(self):
destroy_batch(self._batch)
def append(self, flags, texlevel, mesh, translation, orientation):
assert len(translation) == 3
assert orientation is None or len(orientation) == 3
index = len(self.flags)
assert index < self.max_size
self.flags.append(flags | INSTANCE_FLAG_SPAWNED)
self.texlevels.append(texlevel)
self.meshes.append(mesh)
self.translations.extend(translation)
if self.orientations is not None:
self.orientations.extend(orientation)
return index
def set_translation(self, index, translation):
self.translations[index * 3 : index * 3 + 3] = translation
def set_orientation(self, index, orientation):
self.orientations[index * 3 : index * 3 + 3] = orientation
def draw(self):
draw_batch(self._batch, self.flags, self.texlevels, self.meshes, self.translations, self.orientations)

88
game/environment.py Normal file
View File

@ -0,0 +1,88 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from math import radians, cos
from engine import *
from game.math import vec3_add, vec3_sub, vec3_scale, vec3_mul, vec3_dot
def _angles(start, end):
cmin = round(cos(radians(start)), 6)
cmax = round(cos(radians(end)), 6)
return (cmin, cmax, 1.0 / (cmax - cmin))
def _floats(a, b):
return (a, b - a)
def _colors(a, b):
return (a, vec3_sub(b, a))
_light_color = (
(_angles(180.0, 0.0), _colors((1.0, 1.0, 1.0), (1.0, 1.0, 1.0))),
)
_horizon_color = (
(_angles(180.0, 0.0), _colors((0.75, 0.75, 1.0), (0.75, 0.75, 1.0))),
)
_sky_color = (
(_angles(180.0, 0.0), _colors((0.0, 0.0, 0.5), (0.0, 0.0, 0.5))),
)
_sun_color = (
(_angles(180.0, 0.0), _colors((8.0, 8.0, 4.0), (8.0, 8.0, 4.0))),
)
_light_power = (
(_angles(180.0, 90.0), _floats(0.0, 0.0)),
(_angles( 90.0, 0.0), _floats(0.0, 1.0))
)
def _resolve(ranges, c):
for (cmin, cmax, crng), ab in ranges:
if c >= cmin and c <= cmax:
return ((c - cmin) * crng, ab)
return None
def _resolve_float(ranges, c):
w, (a, b) = _resolve(ranges, c)
return a + (b * w)
def _resolve_color(ranges, c):
w, (a, b) = _resolve(ranges, c)
return vec3_add(a, vec3_scale(b, w))
def resolve_inputs():
light_direction = resolve_input(b'u_light_direction')
light_color = resolve_input(b'u_light_color')
horizon_color = resolve_input(b'u_horizon_color')
sky_color = resolve_input(b'u_sky_color')
sun_color = resolve_input(b'u_sun_color')
return (light_direction, light_color, horizon_color, sky_color, sun_color)
def from_sun(sun_direction, sun_power):
c = vec3_dot(sun_direction, vec3_up)
light_power = _resolve_float(_light_power, c) * sun_power
light_color = vec3_scale(_resolve_color(_light_color, c), sun_power) # vec3_scale(_resolve_color(_light_color, c), light_power)
horizon_color = vec3_mul(_resolve_color(_horizon_color, c), light_color)
sky_color = vec3_mul(_resolve_color(_sky_color, c), light_color)
sun_color = _resolve_color(_sun_color, c)
return (vec3(sun_direction), vec3(light_color), vec3(horizon_color), vec3(sky_color), vec3(sun_color))
_modes = (INPUT_VIEW_ORIENTATION, INPUT_IDENTITY, INPUT_IDENTITY, INPUT_IDENTITY, INPUT_IDENTITY)
def set_inputs(inputs, values):
list(map(set_input_vec3, inputs, values, _modes))

251
game/game.py Normal file
View File

@ -0,0 +1,251 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
from math import pi, tau, dist
from engine import *
from game import math, resources, batch, triangles, generator, environment, sea
def main():
print("Generating terrain...")
gen_begin = time.process_time()
generated = generator.Generator(256)
gen_end = time.process_time()
print("Done: ", round(gen_end - gen_begin, 2), "seconds")
print("Initializing...")
window = initialize(b'RK Island')
terrain_shader = load_shader(b'game/shaders/terrain')
sky_shader = load_shader(b'game/shaders/sky')
print("Loading resources...")
select_shader(terrain_shader)
archive = resources.RuntimeArchive.load('data/rk_island.rkar')
tiles_texture = archive.get_texture('tiles')
tiles_vertices = archive.get_vertices('tiles')
water = archive.get_model('water')
sand = archive.get_model('sand')
grass = archive.get_model('grass')
forest = archive.get_model('forest')
rock = archive.get_model('rock')
mud = archive.get_model('mud')
lava = archive.get_model('lava')
heightmap = create_texture(1, b'u_height_sampler',
TEXTURE_FORMAT_32F, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
generated.packed_heights)
normalmap = create_texture(2, b'u_normal_sampler',
TEXTURE_FORMAT_RGB10_A2, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
generated.packed_normals)
select_vertices(tiles_vertices)
terrain_batch = batch.Batch(
generated.size ** 2, BATCH_TRANSLATION_FORMAT_SHORT, BATCH_ORIENTATION_FORMAT_NONE)
unselect_vertices(tiles_vertices)
terrain_environment_inputs = environment.resolve_inputs()
tests_texture = archive.get_texture('tests')
tests_vertices = archive.get_vertices('tests')
blob = archive.get_model('blob')
cube = archive.get_model('cube')
clouds = archive.get_model('clouds')
select_vertices(tests_vertices)
tests_batch = batch.Batch(3, BATCH_TRANSLATION_FORMAT_FLOAT, BATCH_ORIENTATION_FORMAT_FLOAT)
unselect_vertices(tests_vertices)
unselect_shader(terrain_shader)
#TODO: generator & for real
print("Building tiles...")
vc = generated.volcano_c
vr = generated.volcano_r
for my, mx in generated.map_coords:
vd = dist((mx + 0.5, my + 0.5), vc)
nx, ny, nz, h = generated.unpack(my, mx)
r = generated.rivers[my * generated.size + mx]
if h == 0.0:
continue
if r > 0.0:
model = water
elif h < 2.0:
model = sand
elif h < 180:
if nz > 0.9:
if ny < -0.01 and nz > 0.93:
model = forest
else:
model = grass
else:
model = rock
elif vd < vr - 3.0 and nz > 0.999:
model = lava
elif vd < vr + 2.0:
model = mud
elif vd < vr + 6.0 and nz < 0.67:
model = mud
else:
model = rock
model.spawn(terrain_batch, (float(((mx - 128) * 8) + 4), float(((127 - my) * 8) + 4), 0.0), None)
blob_translation = vec3((-100.0, -500.0, 0.0))
cube_translation = vec3((100.0, -500.0, 0.0))
cube_orientation = vec3(vec3_forward)
clouds_orientation = vec3(vec3_forward)
blob_id = blob.spawn(tests_batch, blob_translation)
cube_id = cube.spawn(tests_batch, cube_translation, cube_orientation)
clouds_id = clouds.spawn(tests_batch, (0.0, 0.0, 32.0), clouds_orientation)
proj_hfov = pi * 0.25
proj_ratio = 16.0 / 9.0
proj_near_z = 8.0
proj_far_z = 3000.0
select_shader(sky_shader)
sky_environment_inputs = environment.resolve_inputs()
sea_phase = resolve_input(b'u_sea_phase')
sea_polar_textures = sea.load_polar_textures(('data/sea_bump1.png', 'data/sea_bump2.png'))
sea_detail_texture = sea.load_detail_texture('data/sea_bump.png')
sky_triangles = create_triangles(triangles.sky_triangles(64, proj_far_z - 0.1, proj_ratio))
unselect_shader(sky_shader)
sun_direction = vec3(math.vec3_normalize((1.0, 0.0, 1.0)))
sun_power = 1.0
camera = vec3((0.0, -1200.0, 500.0))
lookat = vec3((0.0, 500.0, -500.0))
start_time = time.monotonic()
current_time = 0.0
up = vec3(vec3_up)
_rotation = mat3()
_camera = vec3()
_lookat = vec3()
_blob_translation = vec3()
_cube_translation = vec3()
_cube_orientation = vec3()
_clouds_orientation = vec3()
print("Running...")
frame_min = 10000.0
frame_max = 0.0
frame_avg = 0.0
draw_min = 10000.0
draw_max = 0.0
draw_avg = 0.0
perf_count = 0
try:
for x in range(10000):
current_time = time.monotonic() - start_time
begin_frame()
frame_begin = time.thread_time()
mat3_rotation(_rotation, up, (current_time * 0.05) % tau)
mat3_mul_vec3(_camera, _rotation, camera)
mat3_mul_vec3(_lookat, _rotation, lookat)
set_view(_camera, _lookat)
set_projection(proj_hfov, proj_ratio, proj_near_z, proj_far_z)
mat3_rotation(_rotation, up, (current_time * 0.21) % tau)
mat3_mul_vec3(_blob_translation, _rotation, blob_translation)
tests_batch.set_translation(blob_id, _blob_translation)
tests_batch.set_orientation(blob_id, vec3(math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0))))
mat3_mul_vec3(_cube_translation, _rotation, cube_translation)
tests_batch.set_translation(cube_id, _cube_translation)
mat3_rotation(_rotation, up, (current_time * 0.43) % tau)
mat3_mul_vec3(_cube_orientation, _rotation, cube_orientation)
tests_batch.set_orientation(cube_id, _cube_orientation)
mat3_rotation(_rotation, up, (current_time * -0.037) % tau)
mat3_mul_vec3(_clouds_orientation, _rotation, clouds_orientation)
tests_batch.set_orientation(clouds_id, _clouds_orientation)
environment_values = environment.from_sun(sun_direction, sun_power)
select_shader(terrain_shader)
select_texture(heightmap)
select_texture(normalmap)
environment.set_inputs(terrain_environment_inputs, environment_values)
select_texture(tiles_texture)
select_vertices(tiles_vertices)
draw_begin = time.thread_time()
terrain_batch.draw()
draw_end = time.thread_time()
unselect_vertices(tiles_vertices)
unselect_texture(tiles_texture)
select_texture(tests_texture)
select_vertices(tests_vertices)
tests_batch.draw()
unselect_vertices(tests_vertices)
unselect_texture(tests_texture)
unselect_texture(normalmap)
unselect_texture(heightmap)
unselect_shader(terrain_shader)
select_shader(sky_shader)
environment.set_inputs(sky_environment_inputs, environment_values)
set_input_float(sea_phase, (current_time * 0.023) % 1.0)
select_texture(sea_polar_textures)
select_texture(sea_detail_texture)
draw_triangles(sky_triangles)
unselect_texture(sea_detail_texture)
unselect_texture(sea_polar_textures)
unselect_shader(sky_shader)
frame_end = time.thread_time()
end_frame()
draw_ms = draw_end - draw_begin
draw_min = min(draw_min, draw_ms)
draw_max = max(draw_max, draw_ms)
draw_avg += draw_ms
frame_ms = frame_end - frame_begin
frame_min = min(frame_min, frame_ms)
frame_max = max(frame_max, frame_ms)
frame_avg += frame_ms
perf_count += 1
except KeyboardInterrupt:
pass
print("\rDraw *", perf_count,
": min =", round(draw_min * 1000.0, 2),
", max =", round(draw_max * 1000.0, 2),
", avg =", round((draw_avg / perf_count) * 1000.0, 2), "ms")
print("\rFrame *", perf_count,
": min =", round(frame_min * 1000.0, 2),
", max =", round(frame_max * 1000.0, 2),
", avg =", round((frame_avg / perf_count) * 1000.0, 2), "ms")
# seed 666
# camera = vec3((0.0, -1200.0, 500.0))
# lookat = vec3((0.0, 500.0, -500.0))
# for x in range(10000)
# current_time = 0
# Draw * 10000 : min = 0.11 , max = 1.52 , avg = 0.18 ms
# Draw * 10000 : min = 0.12 , max = 1.74 , avg = 0.18 ms
# Draw * 10000 : min = 0.12 , max = 1.92 , avg = 0.18 ms
print("Quitting...")
del tests_batch
del terrain_batch
destroy_texture(sea_polar_textures)
destroy_texture(sea_detail_texture)
destroy_texture(heightmap)
destroy_triangles(sky_triangles)
archive.destroy()
destroy_shader(terrain_shader)
destroy_shader(sky_shader)
terminate()

223
game/generator.py Normal file
View File

@ -0,0 +1,223 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from os import urandom
from operator import mul
from itertools import product, combinations, chain, starmap, repeat
from math import prod, sqrt, floor, ceil, dist, hypot, pi, cos, sin
from random import seed as set_seed, random, uniform, triangular, sample
from array import array
from struct import unpack
from game.math import vec3_normal_rgb10a2
class Generator:
def __init__(self, size):
seed = unpack('I', urandom(4))[0] # 666 # 2749145609 #
print("Seed =", seed)
set_seed(seed)
self.size = size
self.map_coords = tuple(product(range(size), repeat = 2))
self.field_coords = tuple(starmap(lambda y, x: (y + 0.5, x + 0.5), self.map_coords))
self.generate_field()
self.generate_heights()
self.generate_rivers()
self.smooth_heights()
self.generate_normals()
self.smooth_normals()
self.pack()
def weighted_samples(self, radius):
samples = []
smin = floor(radius)
tw = 0.0
for y, x in product(range(-smin, smin + 1), repeat = 2):
w = 1.0 - hypot(y, x) / radius
if w > 0.0:
tw += w
samples.append([y, x, w])
iw = 1.0 / tw
for sample in samples:
sample[2] *= iw
return samples
def generate_field(self):
half = self.size // 2
vhalf = (half, half)
rmin = self.size / 16
rmax = self.size / 8
vrad = self.size / 32
cones = [(vhalf, vrad, 0.2)]
for _ in range(180):
r = uniform(rmin, rmax)
d = uniform(r, half + r)
a = uniform(-pi, pi)
p = d / half
cones.append(((d * cos(a) + half, d * sin(a) + half), r, p))
def height(_position):
def influence(_center, _radius, _power):
d = dist(_position, _center)
return ((_radius ** 2.0) / (d ** 2.0) if d > _radius else (d ** 2.0) / (_radius ** 2.0)) * _power
return sum(starmap(influence, cones))
heights = list(map(height, self.field_coords))
heights = list(map(mul, heights, repeat(1.0 / max(heights))))
self.volcano_c = vhalf
self.volcano_r = vrad
self.cones = cones
self.heights = heights
def generate_heights(self):
size = self.size
half = size // 2
vhalf = (half, half)
heights = self.heights
def shape(_position, _height):
d = dist(vhalf, _position) / half
return max(0.0, _height - d) ** 2.5
heights = list(map(shape, self.field_coords, heights))
vy, vx = self.volcano_c
vrad = self.volcano_r
vmin = ceil(vrad)
vmax = vrad - 2.0
addrs = []
hmin = 1.0
for y, x in product(range(-vmin, vmin), repeat = 2):
if hypot(y + 0.5, x + 0.5) < vmax:
addr = (y + vy) * size + (x + vx)
addrs.append(addr)
hmin = min(hmin, heights[addr])
hmin -= 8.0 / 255.0
for addr in addrs:
heights[addr] = hmin
self.heights = list(map(mul, heights, repeat(255.0 / max(heights))))
def generate_rivers(self):
size = self.size
heights = self.heights
cw = 1.0 / sqrt(2.0)
offsets = (
# y x w y x w y x w
(-1, -1, cw), (-1, 0, 1.0), (-1, 1, cw),
( 0, -1, 1.0), ( 0, 1, 1.0),
( 1, -1, cw), ( 1, 0, 1.0), ( 1, 1, cw))
def river(_ry, _rx):
path = []
mins = []
minh = heights[_ry * size + _rx]
flowing = True
while flowing:
flowing = False
path.append((_ry, _rx))
mins.append(minh)
rh = heights[_ry * size + _rx]
if rh == 0.0:
break
for by, bx in reversed(path):
ns = []
for oy, ox, w in offsets:
ny = (_ry + oy) % size
nx = (_rx + ox) % size
if (ny, nx) not in path:
nh = heights[ny * size + nx]
ns.append(((nh - rh) * w, min(minh, nh), ny, nx))
if ns:
_, minh, _ry, _rx = min(ns)
flowing = True
break
return [(rx, ry, rh) for (rx, ry), rh in zip(path, mins)]
def center(_cy, _cx):
return (heights[_cy * size + _cx], _cy, _cx)
centers = sorted(
[center(round(cy) % size, round(cx) % size) for (cy, cx), _, _ in self.cones[1:]],
reverse = True)
sources = [(sy, sx) for _, sy, sx in centers[:32]]
paths = []
for sy, sx in sources:
paths.append(river(sy, sx))
rivers = [0.0 for _ in range(size ** 2)]
path = max(paths, key = len)
for ry, rx, minh in path:
minh = max(0.0, minh - 1.0)
addr = ry * size + rx
heights[addr] = minh
for oy, ox, _ in offsets:
heights[(ry + oy) * size + (rx + ox)] = minh
rivers[addr] = 1.0
self.rivers = rivers
def smooth_heights(self):
size = self.size
heights = self.heights
samples = self.weighted_samples(2.0)
def smooth(_y, _x):
return sum(heights[((_y + dy) % size) * size + ((_x + dx) % size)] * w for dy, dx, w in samples)
heights = list(starmap(smooth, self.map_coords))
self.heights = list(map(mul, heights, repeat(255.0 / max(heights))))
def generate_normals(self):
size = self.size
heights = self.heights
def normal(_y, _x):
height = heights[_y * size + _x]
def direction(_dy, _dx):
dz = (heights[((_y - _dy) % size) * size + ((_x + _dx) % size)] - height) / 8.0
di = 1.0 / sqrt(_dx ** 2.0 + _dy ** 2.0 + dz ** 2.0) #OPTIM: dx² + dy² = 1.0
return (_dx * di, _dy * di, dz * di)
hx, hy, hz = direction(0, 1)
vx, vy, vz = direction(1, 0)
return (hy * vz - hz * vy, hz * vx - hx * vz, hx * vy - hy * vx)
self.normals = list(starmap(normal, self.map_coords))
def smooth_normals(self):
size = self.size
normals = self.normals
samples = self.weighted_samples(2.0)
def smooth(_y, _x):
tx = ty = tz = 0.0
for dy, dx, w in samples:
nx, ny, nz = normals[((_y + dy) % size) * size + ((_x + dx) % size)]
tx += nx * w
ty += ny * w
tz += nz * w
di = 1.0 / sqrt(tx ** 2.0 + ty ** 2.0 + tz ** 2.0)
return (tx * di, ty * di, tz * di)
self.normals = list(starmap(smooth, self.map_coords))
def pack(self):
self.packed_heights = array('f', self.heights)
self.packed_normals = array('I', tuple(map(vec3_normal_rgb10a2, self.normals)))
def unpack(self, y, x):
addr = y * self.size + x
nx, ny, nz = self.normals[addr]
return (nx, ny, nz, self.heights[addr])

71
game/math.py Normal file
View File

@ -0,0 +1,71 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from math import hypot
def vec3_add(a, b):
return (a[0] + b[0], a[1] + b[1], a[2] + b[2])
def vec3_sub(a, b):
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
def vec3_normalize(v):
l = hypot(*v)
return (v[0] / l, v[1] / l, v[2] / l)
def vec3_direction(a, b):
v = (b[0] - a[0], b[1] - a[1], b[2] - a[2])
l = hypot(*v)
return (v[0] / l, v[1] / l, v[2] / l)
def vec3_scale(v, s):
return (v[0] * s, v[1] * s, v[2] * s)
def vec3_mul(a, b):
return (a[0] * b[0], a[1] * b[1], a[2] * b[2])
def vec3_dot(a, b):
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
def vec3_cross(a, b):
return (a[1] * b[2] - a[2] * b[1], -(a[0] * b[2] - a[2] * b[0]), a[0] * b[1] - a[1] * b[0])
# Texture formats
def float_s8(f):
return round((f ** (1.0 / 2.2)) * 255.0) #TODO: more accurate
def vec3_srgb8a8(v):
p = 1.0 / 2.2 #TODO: more accurate
return (round((v[0] ** p) * 255.0), round((v[1] ** p) * 255.0), round((v[2] ** p) * 255.0), 255)
def vec3_rgba8(v):
return (round(v[0] * 255.0), round(v[1] * 255.0), round(v[2] * 255.0), 255)
def vec3_normal_rgba8(n):
l = max(abs(n[0]), abs(n[1]), abs(n[2]))
return (
round((0.5 + (n[0] / l) * 0.5) * 255.0),
round((0.5 + (n[1] / l) * 0.5) * 255.0),
round((n[2] / l) * 255.0), 0)
def vec3_normal_rgb10a2(n):
l = max(abs(n[0]), abs(n[1]), abs(n[2]))
return \
round((0.5 + (n[0] / l) * 0.5) * 1023.0) | \
round((0.5 + (n[1] / l) * 0.5) * 1023.0) << 10 | \
round((n[2] / l) * 1023.0) << 20
# Vertex formats

271
game/obj2rkar.py Normal file
View File

@ -0,0 +1,271 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import struct
from array import array
from pathlib import Path
from itertools import starmap, chain, repeat
from engine import *
from game.math import float_s8, vec3_srgb8a8
from game.resources import load_png, TextureData, VerticesData, ModelData, Archive
_texture_flags = TEXTURE_FLAG_MIPMAPS | TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_NEAREST
class ObjArchive(Archive):
def __init__(self):
super().__init__()
def import_hacks(self, path):
print("Importing hacks", path)
hacks = {}
for line in open(path):
data = line.split()
if not data:
continue
name = data.pop(0)
if name == '#':
continue
assert len(data) == 4 and data[0] == '=' and data[2] == 'mesh'
alias = data[1]
mesh = data[3]
assert name not in hacks.keys()
hacks[name] = (alias, mesh)
return hacks
def import_mtl(self, mtlpath):
def load_value(x):
x = round(float(x), 6)
if x == -0.0:
x = 0.0
return x
size = None
def load_texture(pngpath, nchannels):
width, height, pixels = load_png(pngpath)
assert pixels.typecode == 'B'
assert (size is None or size == (width, height))
assert len(pixels) == width * height * nchannels
return ((width, height), pixels)
print("Importing mtl", mtlpath)
texture = array('B')
texlevels = {}
name = ''
texlevel = 0
color = (0, 0, 0, 0)
metallic = 0
specular = 0
roughness = 0
bump = 0
def finish(_texture, _color, _metallic, _specular, _roughness, _bump):
width, height = size
if not isinstance(_color, array):
_color = array('B', _color * (width * height))
_texture.extend(_color)
if not isinstance(_metallic, array):
_metallic = repeat(_metallic, width * height)
if not isinstance(_specular, array):
_specular = repeat(_specular, width * height)
if not isinstance(_roughness, array):
_roughness = repeat(_roughness, width * height)
if not isinstance(_bump, array):
_bump = repeat(_bump, width * height)
_texture.extend(chain.from_iterable(zip(_metallic, _specular, _roughness, _bump)))
for line in open(mtlpath):
data = line.split()
if not data:
continue
code = data.pop(0)
if code == '#':
continue
elif code == 'newmtl':
if name:
if size is None:
size = (1, 1)
finish(texture, color, metallic, specular, roughness, bump)
texlevels[name] = texlevel
texlevel += 2
name = data[0]
print("Importing material", name)
color = (0, 0, 0, 0)
metallic = 0
specular = 0
roughness = 0
bump = 0
elif code == 'Kd': # color
color = vec3_srgb8a8(tuple(map(load_value, data)))
assert len(color) == 4
elif code == 'Ni': # ior -> metallic
metallic = float_s8(load_value(data[0]))
elif code == 'Ks': # specular
specular = float_s8(load_value(data[0]))
elif code == 'Ns': # roughness
roughness = float_s8(1.0 - load_value(data[0]) / 1000.0)
elif code == 'map_Kd':
pngpath = mtlpath.parent / data[0]
print("Importing color texture", pngpath)
size, color = load_texture(pngpath, 4)
pngpath = pngpath.parent / (pngpath.stem + '_b.png')
if pngpath.exists():
print("Importing bump channel", pngpath)
size, bump = load_texture(pngpath, 1)
elif code == 'map_Ni':
pngpath = mtlpath.parent / data[0]
print("Importing metallic channel", pngpath)
size, metallic = load_texture(pngpath, 1)
elif code == 'map_Ks':
pngpath = mtlpath.parent / data[0]
print("Importing specular channel", pngpath)
size, specular = load_texture(pngpath, 1)
elif code == 'map_Ns':
pngpath = mtlpath.parent / data[0]
print("Importing roughness channel", pngpath)
size, roughness = load_texture(pngpath, 1)
if name:
if size is None:
size = (1, 1)
finish(texture, color, metallic, specular, roughness, bump)
texlevels[name] = texlevel
texlevel += 2
name = str(mtlpath.stem)
assert texlevel < 255
assert name not in self.textures_db.keys()
print("Storing texture", name)
width, height = size
self.textures_db[name] = TextureData(
name, TEXTURE_FORMAT_SRGB8_A8, width, height, texlevel, _texture_flags, texture)
return texlevels
def import_obj(self, objpath):
def load_coord(x):
x = round(float(x), 6)
if x == -0.0:
x = 0.0
return x
def load_index(x):
return int(x) - 1
print("Importing obj", objpath)
texlevels = {}
name = ''
texlevel = 0
mesh = []
positions = []
normals = []
texcoords = []
objects = {}
for line in open(objpath):
data = line.split()
if not data:
continue
code = data.pop(0)
if code == '#':
continue
elif code == 'mtllib':
texlevels = self.import_mtl(objpath.parent / data[0])
elif code == 'o':
if name:
assert mesh
objects[name] = (texlevel, mesh)
name = data[0]
print("Importing object", name)
mesh = []
elif code == 'usemtl':
texlevel = texlevels[data[0]]
elif code == 'v':
position = tuple(map(load_coord, data))
assert len(position) == 3
positions.append(position)
elif code == 'vn':
normal = tuple(map(load_coord, data))
assert len(normal) == 3
normals.append(normal)
elif code == 'vt':
texcoord = tuple(map(load_coord, data))
assert len(texcoord) == 2
texcoords.append((texcoord[0], 1.0 - texcoord[1]))
elif code == 'f':
indices = tuple(map(lambda x: tuple(map(load_index, x.split('/'))), data))
assert len(indices) == 3
assert len(indices[0]) == 3
assert len(indices[1]) == 3
assert len(indices[2]) == 3
triangle = tuple(map(lambda x: positions[x[0]] + normals[x[2]] + texcoords[x[1]], indices))
assert len(triangle) == 3 and len(set(triangle)) == 3
mesh.append(triangle)
if name:
assert mesh
objects[name] = (texlevel, mesh)
vertices = set()
for _, mesh in objects.values():
vertices |= frozenset(chain.from_iterable(mesh))
vertices = tuple(vertices)
indices = []
models = {}
for name, (texlevel, mesh) in sorted(objects.items()):
if name[0] == '_':
print(name, ": texlevel =", texlevel)
models[name] = (texlevel, -1, -1)
else:
offset = len(indices)
assert offset < 65536
indices.extend(map(vertices.index, chain.from_iterable(mesh)))
count = len(indices) - offset
assert count % 3 == 0
count //= 3
assert count < 65536
print(name, ": texlevel =", texlevel, "offset =", offset, "count =", count)
models[name] = (texlevel, offset, count)
name = str(objpath.stem)
assert name not in self.vertices_db.keys()
#TODO: move to math
def pack_10(_x):
return round(_x * (512.0 if _x < 0.0 else 511.0)) & 1023
def pack_vertex(_px, _py, _pz, _nx, _ny, _nz, _s, _t):
n = (pack_10(_nx) << 20) | (pack_10(_ny) << 10) | (pack_10(_nz) << 0)
s = max(0, min(65535, round(_s * 65535.0)))
t = max(0, min(65535, round(_t * 65535.0)))
return struct.pack('fffIHH', _px, _py, _pz, n, s, t)
self.vertices_db[name] = VerticesData(name,
array('B', b''.join(starmap(pack_vertex, vertices))),
array('H', indices))
path = objpath.parent / (objpath.stem + '.hacks')
if path.exists():
hacks = self.import_hacks(path)
for name, (alias, mesh) in hacks.items():
print("Hacking", name, "from", alias, "with mesh", mesh)
assert name not in models.keys() and alias in models.keys() and mesh in models.keys()
texlevel, _, _ = models[alias]
_, offset, count = models[mesh]
models[name] = (texlevel, offset, count)
for name, (texlevel, offset, count) in sorted(models.items()):
if name[0] != '_':
print("Storing", name)
assert name not in self.models_db.keys()
self.models_db[name] = ModelData(
name, INSTANCE_FLAG_VISIBLE, texlevel, offset | count << 16)
if __name__ == '__main__':
if len(sys.argv) < 3:
print("Usage: python3 -m tools.obj2rkar output.rkar input.obj ...")
sys.exit(2)
importer = ObjArchive()
for argv in sys.argv[2:]:
objpath = Path(argv)
importer.import_obj(objpath)
outpath = Path(sys.argv[1])
print("Exporting", outpath)
importer.save(outpath)
print("Done.")

292
game/resources.py Normal file
View File

@ -0,0 +1,292 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import struct
from array import array
from pathlib import Path
import png
import engine
VERTEX_SIZE = 20
VERTEX_FORMAT = engine.vertex_format(
engine.VERTEX_FORMAT_VEC3_FLOAT,
engine.VERTEX_FORMAT_VEC3_INT10,
engine.VERTEX_FORMAT_VEC2_USHORT)
def load_png(path):
width, height, data, _ = png.Reader(filename = path).read_flat()
return (width, height, data)
def _read_magic(file, magic):
assert magic
if file.read(len(magic)) != magic:
raise RuntimeError("Archive magic mismatch!", magic)
def _write_magic(file, magic):
assert magic
size = file.write(magic)
assert size == len(magic)
def _read_struct(file, format):
assert format
size = struct.calcsize(format)
assert size
data = file.read(size)
assert len(data) == size
_read_magic(file, b'RK')
return struct.unpack(format, data)
def _write_struct(file, format, elems):
assert format
data = struct.pack(format, *elems)
size = file.write(data)
assert size == len(data)
_write_magic(file, b'RK')
def _read_string(file):
length, = _read_struct(file, 'B')
assert length
data = file.read(length)
assert len(data) == length
_read_magic(file, b'RK')
return str(data, encoding='ascii')
def _write_string(file, string):
data = bytes(string, encoding='ascii')
assert data and len(data) < 256
_write_struct(file, 'B', (len(data),))
size = file.write(data)
assert size == len(data)
_write_magic(file, b'RK')
def _read_array(file, format, length):
assert format
data = array(format)
data.fromfile(file, length)
assert len(data) == length
_read_magic(file, b'RK')
return data
def _write_array(file, array):
assert array
array.tofile(file)
_write_magic(file, b'RK')
def _read_blob(file):
typecode, length = _read_struct(file, 'II')
typecode = chr(typecode)
assert typecode, length
data = array(typecode)
data.fromfile(file, length)
assert len(data) == length
_read_magic(file, b'RK')
return data
def _write_blob(file, array):
assert array
_write_struct(file, 'II', (ord(array.typecode), len(array)))
array.tofile(file)
_write_magic(file, b'RK')
class TextureData:
__slots__ = 'name', 'format', 'width', 'height', 'nlevels', 'flags', 'pixels'
def __init__(self, name, format, width, height, nlevels, flags, pixels):
assert pixels.typecode == engine.TEXTURE_FORMAT_TYPECODE[format]
assert len(pixels) == width * height * max(1, nlevels) * engine.TEXTURE_FORMAT_NELEMS[format]
self.name = name
self.format = format
self.width = width
self.height = height
self.nlevels = nlevels
self.flags = flags
self.pixels = pixels
@classmethod
def from_archive(cls, file):
_read_magic(file, b'TX')
name = _read_string(file)
format, width, height, nlevels, flags = _read_struct(file, 'IIIII')
pixels = _read_blob(file)
assert pixels.typecode == engine.TEXTURE_FORMAT_TYPECODE[format]
assert len(pixels) == width * height * max(1, nlevels) * engine.TEXTURE_FORMAT_NELEMS[format]
return cls(name, format, width, height, nlevels, flags, pixels)
def to_archive(self, file):
_write_magic(file, b'TX')
_write_string(file, self.name)
_write_struct(file, 'IIIII', (self.format, self.width, self.height, self.nlevels, self.flags))
_write_blob(file, self.pixels)
def create_texture(slot, input, data):
return engine.create_texture(
slot, input, data.format, data.width, data.height, data.nlevels, data.flags, data.pixels)
class VerticesData:
__slots__ = 'name', 'vertices', 'indices'
def __init__(self, name, vertices, indices):
if len(vertices) % VERTEX_SIZE != 0:
raise RuntimeError("Vertex format mismatch!")
self.name = name
self.vertices = vertices
self.indices = indices
@classmethod
def from_archive(cls, file):
_read_magic(file, b'VT')
name = _read_string(file)
nvertices, nindices = _read_struct(file, 'II')
vertices = _read_array(file, 'B', nvertices * VERTEX_SIZE)
indices = _read_array(file, 'H', nindices)
return cls(name, vertices, indices)
def to_archive(self, file):
_write_magic(file, b'VT')
_write_string(file, self.name)
_write_struct(file, 'II', (len(self.vertices) // VERTEX_SIZE, len(self.indices)))
_write_array(file, self.vertices)
_write_array(file, self.indices)
def create_vertices(data):
return engine.create_vertices(VERTEX_FORMAT, len(data.vertices) // VERTEX_SIZE, data.vertices, data.indices)
class ModelData:
__slots__ = 'name', 'flags', 'texlevel', 'mesh'
def __init__(self, name, flags, texlevel, mesh):
self.name = name
self.flags = flags
self.texlevel = texlevel
self.mesh = mesh
@classmethod
def from_archive(cls, file):
_read_magic(file, b'MD')
name = _read_string(file)
flags, texlevel, mesh = _read_struct(file, 'BHI')
return ModelData(name, flags, texlevel, mesh)
def to_archive(self, file):
_write_magic(file, b'MD')
_write_string(file, self.name)
_write_struct(file, 'BHI', (self.flags, self.texlevel, self.mesh))
class Model:
__slots__ = 'flags', 'texlevel', 'mesh'
def __init__(self, flags, texlevel, mesh):
self.flags = flags
self.texlevel = texlevel
self.mesh = mesh
def spawn(self, batch, translation = engine.vec3_zero, orientation = engine.vec3_forward):
return batch.append(self.flags, self.texlevel, self.mesh, translation, orientation)
def create_model(data):
return Model(data.flags, data.texlevel, data.mesh)
class Archive:
__slots__ = 'textures_db', 'vertices_db', 'models_db'
def __init__(self, textures_db = None, vertices_db = None, models_db = None):
self.textures_db = textures_db or {}
self.vertices_db = vertices_db or {}
self.models_db = models_db or {}
def destroy(self):
for vertices in self.vertices_db.values():
engine.destroy_vertices(vertices)
for texture in self.textures_db.values():
engine.destroy_texture(texture)
self.textures_db.clear()
self.vertices_db.clear()
self.models_db.clear()
def get_texture(self, name):
return self.textures_db[name]
def get_vertices(self, name):
return self.vertices_db[name]
def get_model(self, name):
return self.models_db[name]
@classmethod
def _new_texture(cls, data):
return data
@classmethod
def _new_vertices(cls, data):
return data
@classmethod
def _new_model(cls, data):
return data
@classmethod
def from_archive(cls, file):
textures_db = {}
vertices_db = {}
models_db = {}
_read_magic(file, b'RKAR')
ntextures, nvertices, nmodels = _read_struct(file, 'III')
for _ in range(ntextures):
data = TextureData.from_archive(file)
textures_db[data.name] = cls._new_texture(data)
for _ in range(nvertices):
data = VerticesData.from_archive(file)
vertices_db[data.name] = cls._new_vertices(data)
for _ in range(nmodels):
data = ModelData.from_archive(file)
models_db[data.name] = cls._new_model(data)
return cls(textures_db, vertices_db, models_db)
@classmethod
def load(cls, filename):
file = open(Path(filename), 'rb')
archive = cls.from_archive(file)
file.close()
return archive
def to_archive(self, file):
_write_magic(file, b'RKAR')
_write_struct(file, 'III', (len(self.textures_db), len(self.vertices_db), len(self.models_db)))
for _, data in self.textures_db.items():
data.to_archive(file)
for _, data in self.vertices_db.items():
data.to_archive(file)
for _, data in self.models_db.items():
data.to_archive(file)
def save(self, filename):
file = open(Path(filename), 'wb')
self.to_archive(file)
file.close()
class RuntimeArchive(Archive):
@classmethod
def _new_texture(cls, data):
return create_texture(0, b'u_texture_sampler', data)
@classmethod
def _new_vertices(cls, data):
return create_vertices(data)
@classmethod
def _new_model(cls, data):
return create_model(data)

71
game/sea.py Normal file
View File

@ -0,0 +1,71 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from itertools import product
from math import tau, cos, sin, copysign
from array import array
from engine import (create_texture,
TEXTURE_FORMAT_RGB10_A2, TEXTURE_FORMAT_TYPECODE, TEXTURE_FLAG_MIN_LINEAR, TEXTURE_FLAG_MAG_LINEAR)
from game.math import vec3_scale, vec3_direction, vec3_cross, vec3_normal_rgb10a2
from game.resources import load_png
_format = TEXTURE_FORMAT_RGB10_A2
_typecode = TEXTURE_FORMAT_TYPECODE[TEXTURE_FORMAT_RGB10_A2]
_conv = vec3_normal_rgb10a2
_flags = TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR
def load_polar_textures(paths, waves_height = 0.008):
def load_texture(_path):
width, height, data = load_png(_path)
assert data.typecode == 'H'
assert len(data) == width * height
def polar(_y, _x, _h):
d = 1.0 + (_y / height)
a = (_x / width) * tau
return (d * sin(a), d * cos(a), (_h / 65535.0) * waves_height)
def normal(_pos, _h):
y, x = _pos
o = polar(y, x, _h)
n = vec3_cross(
vec3_direction(o, polar(y, x + 1, data[y * width + ((x + 1) % width)])),
vec3_direction(o, polar(y + 1, x, data[((y + 1) % height) * width + x])))
return vec3_scale(n, copysign(1.0, n[2]))
return (width, height, list(map(normal, product(range(height), range(width)), data)))
width, height, normals = load_texture(paths[0])
data = array(_typecode, list(map(_conv, normals)))
for path in paths[1:]:
_width, _height, normals = load_texture(path)
assert _width == width and _height == height
data.extend(list(map(_conv, normals)))
return create_texture(0, b'u_sea_polar_sampler', _format, width, height, len(paths), _flags, data)
def load_detail_texture(path, scale = 0.5, waves_height = 0.002):
width, height, data = load_png(path)
assert data.typecode == 'H'
assert len(data) == width * height
def coord(_y, _x, _h):
return ((_x / width) * scale, (_y / height) * scale, (_h / 65535.0) * waves_height)
def normal(_pos, _h):
y, x = _pos
o = coord(y, x, _h)
n = vec3_cross(
vec3_direction(o, coord(y, x + 1, data[y * width + ((x + 1) % width)])),
vec3_direction(o, coord(y + 1, x, data[((y + 1) % height) * width + x])))
return vec3_scale(n, copysign(1.0, n[2]))
normals = list(map(normal, product(range(height), range(width)), data))
data = array(_typecode, list(map(_conv, normals)))
return create_texture(1, b'u_sea_detail_sampler', _format, width, height, 0, _flags, data)

View File

@ -0,0 +1,90 @@
// Copyright (C) 2022 RozK
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#version 320 es
precision highp float;
in vec3 v_position;
uniform mat4 u_view_km; // world space -> view space, unit = km
uniform vec3 u_light_direction; // view space (-direction_x, -direction_y, -direction_z)
uniform vec3 u_light_color;
uniform vec3 u_horizon_color;
uniform vec3 u_sky_color;
uniform vec3 u_sun_color;
uniform float u_sea_phase;
#define u_right u_view_km[0].xyz
#define u_forward u_view_km[1].xyz
#define u_up u_view_km[2].xyz
#define u_origin u_view_km[3].xyz
uniform highp sampler2DArray u_sea_polar_sampler;
uniform highp sampler2D u_sea_detail_sampler;
const float c_sea_radius = 637.1;
const float c_sea_radius_sq = c_sea_radius * c_sea_radius;
const float c_sky_radius = c_sea_radius + 10.0;
const vec3 c_normal_scale = vec3(2.0, 2.0, 1.0);
const vec3 c_normal_shift = vec3(-1.0, -1.0, 0.0);
const float c_detail_scale = 2.0;
layout(location = 0) out vec4 o_color;
vec3 sky(in vec3 ray_direction) {
float d = max(0.0, dot(ray_direction, u_light_direction));
return mix(u_horizon_color, u_sky_color, max(0.0, dot(ray_direction, u_up))) + u_sun_color * pow(d, 1000.0);
}
void main(void) {
vec3 direction = normalize(v_position);
vec3 earth_center = u_origin - u_up * c_sea_radius;
float p_dist = dot(direction, earth_center);
vec3 pc = earth_center - direction * p_dist;
if (p_dist <= 0.0 || dot(pc, pc) >= c_sea_radius_sq) {
// sky
o_color = vec4(sky(direction), 1.0);
} else {
// sea
vec3 sea_position = direction * (p_dist - sqrt(c_sea_radius_sq - dot(pc, pc))) - u_origin;
vec3 sea_direction = normalize(sea_position);
//TODO: vec2
float s = dot(u_forward, sea_direction);
if (dot(u_right, sea_direction) > 0.0) {
// [1.0 -1.0] -> [0.0 0.5]
s = (1.0 - s) * 0.25;
} else {
// [-1.0 1.0] -> [0.5 1.0] -> [0.0 0.5] + 0.5
s = (1.0 + s) * 0.25 + 0.5;
}
float t = sqrt(length(sea_position)); //TODO: more accurate
vec3 sea_polar1 = normalize(
c_normal_shift + texture(u_sea_polar_sampler, vec3(s, t + u_sea_phase, 0.0)).xyz * c_normal_scale);
vec3 sea_polar2 = normalize(
c_normal_shift + texture(u_sea_polar_sampler, vec3(s, t - u_sea_phase, 1.0)).xyz * c_normal_scale);
//TODO: vec2
s = (u_sea_phase + dot(sea_position, u_right)) * c_detail_scale;
t = (u_sea_phase + dot(sea_position, u_forward)) * c_detail_scale;
vec3 sea_detail = normalize(c_normal_shift + texture(u_sea_detail_sampler, vec2(s, t)).xyz * c_normal_scale);
//TODO: better blending, with earth normal
vec4 normal = u_view_km * vec4(normalize(sea_polar1 + sea_polar2 + sea_detail), 0.0);
float d = max(0.0, dot(normal.xyz, u_light_direction));
s = pow(max(0.0, dot(normal.xyz, normalize(u_light_direction - direction))), 500.0) * step(0.0, d);
o_color = vec4(
u_sky_color * d + //TODO: sea color
u_light_color * s +
sky(reflect(direction, normal.xyz)), 1.0);
}
}

View File

@ -0,0 +1,28 @@
// Copyright (C) 2022 RozK
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#version 320 es
precision highp float;
layout(location = 0) in vec3 a_position; // view space
uniform mat4 u_projection; // view space -> screen space
out vec3 v_position; // view space
void main(void) {
v_position = a_position;
gl_Position = u_projection * vec4(a_position, 1.0);
}

View File

@ -0,0 +1,56 @@
// Copyright (C) 2022 RozK
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#version 320 es
precision highp float;
in vec3 v_position; // view space
in vec3 v_normal; // view space
in vec4 v_terrain_normal; // view space (x, y, z, weight)
in vec4 v_texcoord; // texture space (s, t, pixel_level, material_level)
#define v_weight v_terrain_normal.w
uniform vec3 u_light_direction; // view space (-direction_x, -direction_y, -direction_z)
uniform vec3 u_light_color; // (color.r * power, color.g * power, color.b * power)
uniform highp sampler2DArray u_texture_sampler;
layout(location = 0) out vec4 o_color;
void main(void) {
vec4 pixel = texture(u_texture_sampler, v_texcoord.stp);
vec4 material = texture(u_texture_sampler, v_texcoord.stq);
#define m_metallic material.x
#define m_specular material.y
#define m_roughness material.z
vec3 normal = normalize(v_normal);
vec3 eye_dir = -normalize(v_position);
float d = dot(normal, u_light_direction);
float halfd = 0.5 + d * 0.5;
float td = dot(normalize(v_terrain_normal.xyz), u_light_direction);
float diffuse = halfd * mix(halfd, 0.5 + td * 0.5, v_weight) * (1.0 - m_metallic);
float s = max(0.0, dot(normal, normalize(u_light_direction + eye_dir)));
float shininess = 1.0 + pow(1.0 - m_roughness, 2.0) * 999.0;
float stepd = step(0.0, d);
float specular = pow(s, shininess) * mix(stepd, stepd * max(0.0, td), v_weight);
vec3 color = pixel.rgb * u_light_color;
o_color = vec4(
vec3(
color * diffuse +
color * (specular * m_metallic) +
u_light_color * (specular * m_specular)),
pixel.a);
}

View File

@ -0,0 +1,63 @@
// Copyright (C) 2022 RozK
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#version 320 es
precision highp float;
layout(location = 0) in vec3 a_position; // model space
layout(location = 1) in vec3 a_normal; // model space
layout(location = 2) in vec2 a_texcoord; // texture space
layout(location = 3) in vec4 i_translation; // per mesh, model space -> world space (x, y, z, texlevel)
layout(location = 4) in vec3 i_orientation; // per mesh, model space -> world space
#define i_texlevel i_translation.w
uniform mat4 u_view; // world space -> view space
uniform mat4 u_projection; // view space -> screen space
uniform highp sampler2D u_height_sampler;
uniform highp sampler2D u_normal_sampler;
const vec3 c_normal_scale = vec3(2.0, 2.0, 1.0);
const vec3 c_normal_shift = vec3(-1.0, -1.0, 0.0);
const vec2 c_terrain_scale = vec2(1.0 / 2048.0, -1.0 / 2048.0);
const vec2 c_terrain_shift = vec2(0.5, 0.5);
const float c_weight_scale = 1.0 / 64.f;
const vec3 c_world_forward = vec3(0.0, 1.0, 0.0);
const vec3 c_world_up = vec3(0.0, 0.0, 1.0);
out vec3 v_position; // view space
out vec3 v_normal; // view space
out vec4 v_terrain_normal; // view space (x, y, z, weigth)
out vec4 v_texcoord; // texture space (s, t, pixel_level, material_level)
void main(void) {
vec3 orientation = normalize(i_orientation);
mat3 rotation = mat3(cross(orientation, c_world_up), orientation, c_world_up);
vec4 world_position = vec4(i_translation.xyz + rotation * a_position, 1.0);
float weight = max(0.0, 1.0 - world_position.z * c_weight_scale);
vec3 world_normal = rotation * normalize(a_normal);
vec2 terrain_coords = c_terrain_shift + world_position.xy * c_terrain_scale;
world_position.z += texture(u_height_sampler, terrain_coords).r;
vec4 view_position = u_view * world_position;
vec3 terrain_normal = normalize(c_normal_shift + texture(u_normal_sampler, terrain_coords).rgb * c_normal_scale);
world_normal = mat3(cross(c_world_forward, terrain_normal), c_world_forward, terrain_normal) * world_normal;
v_position = view_position.xyz;
v_normal = (u_view * vec4(world_normal, 0.0)).xyz;
v_terrain_normal = vec4((u_view * vec4(terrain_normal, 0.0)).xyz, weight);
v_texcoord = vec4(a_texcoord, i_texlevel, i_texlevel + 1.0);
gl_Position = u_projection * view_position;
}

50
game/triangles.py Normal file
View File

@ -0,0 +1,50 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from itertools import chain
from array import array
from math import cos, sin
# TODO: with FOV
def sky_triangles(vsubdivs, distance, projection_ratio):
assert vsubdivs > 0
vertices = []
hsubdivs = round(vsubdivs * projection_ratio)
z = -distance
width = distance * projection_ratio
height = distance
startx = width * -0.5
starty = height * -0.5
stepx = width / hsubdivs
stepy = height / vsubdivs
y1 = starty
y2 = y1 + stepy
for sy in range(vsubdivs):
x1 = startx
x2 = x1 + stepx
for sx in range(hsubdivs):
a = (x1, y2, z)
b = (x2, y1, z)
vertices.append((x1, y1, z))
vertices.append(b)
vertices.append(a)
vertices.append((x2, y2, z))
vertices.append(a)
vertices.append(b)
x1 = x2
x2 += stepx
y1 = y2
y2 += stepy
return array('f', chain.from_iterable(vertices))

2
rk_island-dev.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
exec python3 rk_island.py

3
rk_island-profile.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
me=$(whoami)
exec sudo /bin/nice --adjustment=-20 su $me -c "exec python3 -O rk_island.py"

25
rk_island.py Normal file
View File

@ -0,0 +1,25 @@
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from pathlib import Path
libs = Path(sys.path[0]) / 'libs'
if libs.exists() and libs.is_dir():
sys.path[1:1] = [str(libs)]
from game import game
game.main()

2
rk_island.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
exec python3 -O rk_island.py