WIP sources and TODOs.
3
.gitignore
vendored
@ -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
@ -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
@ -0,0 +1,3 @@
|
|||||||
|
Python (3.8+) modules:
|
||||||
|
|
||||||
|
- png
|
2
data/LICENSE
Normal 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
After Width: | Height: | Size: 109 KiB |
BIN
data/sea_bump.xcf
Normal file
BIN
data/sea_bump1.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
data/sea_bump1.xcf
Normal file
BIN
data/sea_bump2.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
data/sea_bump2.xcf
Normal file
BIN
data/tests.blend
Normal file
32
data/tests.mtl
Normal 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
BIN
data/tiles/forest.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
data/tiles/forest.xcf
Normal file
BIN
data/tiles/forest_r.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
data/tiles/forest_r.xcf
Normal file
BIN
data/tiles/grass.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
data/tiles/grass.xcf
Normal file
BIN
data/tiles/lava.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
data/tiles/lava.xcf
Normal file
BIN
data/tiles/mud.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
data/tiles/mud.xcf
Normal file
BIN
data/tiles/rock.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
data/tiles/rock.xcf
Normal file
BIN
data/tiles/sand.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
data/tiles/sand.xcf
Normal file
BIN
data/tiles/tiles.blend
Normal file
5
data/tiles/tiles.hacks
Normal 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
@ -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
@ -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
After Width: | Height: | Size: 6.8 KiB |
BIN
data/tiles/water.xcf
Normal file
BIN
docs/tiling.odg
Normal file
0
game/__init__.py
Normal file
60
game/batch.py
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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)
|
90
game/shaders/sky_opengles.frag
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
28
game/shaders/sky_opengles.vert
Normal 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);
|
||||||
|
}
|
56
game/shaders/terrain_opengles.frag
Normal 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);
|
||||||
|
}
|
63
game/shaders/terrain_opengles.vert
Normal 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
@ -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
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec python3 rk_island.py
|
3
rk_island-profile.sh
Executable 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
@ -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
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec python3 -O rk_island.py
|