Compare commits

..

No commits in common. "31d4d5331615d1d20db0036af52cffddf33db818" and "12d61b25c45c95c800162fc9d650e37459698662" have entirely different histories.

54 changed files with 11011 additions and 2189 deletions

11
.gitignore vendored
View File

@ -1,12 +1 @@
/target /target
**/target
/Cargo.lock
.vscode/
*.code-workspace
.idea/
.DS_Store
*.swp
*.swo
*~

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/orbital_simulation.iml" filepath="$PROJECT_DIR$/.idea/orbital_simulation.iml" />
</modules>
</component>
</project>

11
.idea/orbital_simulation.iml generated Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

2530
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,27 @@
[workspace] [package]
members = [ name = "orbital_simulation"
"solar_engine", version = "0.1.0"
"simulator" edition = "2021"
]
[dependencies]
cfg-if = "1"
anyhow = "1.0"
bytemuck = { version = "1.16", features = [ "derive" ] }
cgmath = "0.18"
env_logger = "0.10"
pollster = "0.3"
log = "0.4"
tobj = { version = "3.2", default-features = false, features = ["async"]}
wgpu = { version = "22.0"}
winit = { version = "0.30.8", features = ["rwh_05"] }
instant = "0.1"
[dependencies.image]
version = "0.24"
default-features = false
features = ["png", "jpeg", "hdr"]
[build-dependencies]
anyhow = "1.0"
fs_extra = "1.2"
glob = "0.3"

18
build.rs Normal file
View File

@ -0,0 +1,18 @@
use anyhow::*;
use fs_extra::copy_items;
use fs_extra::dir::CopyOptions;
use std::env;
fn main() -> Result<()> {
// This tells Cargo to rerun this script if something in /res/ changes.
println!("cargo:rerun-if-changed=res/*");
let out_dir = env::var("OUT_DIR")?;
let mut copy_options = CopyOptions::new();
copy_options.overwrite = true;
let mut paths_to_copy = Vec::new();
paths_to_copy.push("res/");
copy_items(&paths_to_copy, out_dir, &copy_options)?;
Ok(())
}

BIN
res/cobble-diffuse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
res/cobble-normal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

14
res/cobble_sphere.mtl Normal file
View File

@ -0,0 +1,14 @@
# Blender MTL File: 'cobble_sphere.blend'
# Material Count: 1
newmtl Material
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Bump cobble-normal.png
map_Kd cobble-diffuse.png

2041
res/cobble_sphere.obj Normal file

File diff suppressed because it is too large Load Diff

BIN
res/cube-diffuse.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
res/cube-normal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

14
res/cube.mtl Normal file
View File

@ -0,0 +1,14 @@
# Blender MTL File: 'cube.blend'
# Material Count: 1
newmtl Material.001
Ns 323.999994
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Bump cube-normal.png
map_Kd cube-diffuse.jpg

933
res/cube.obj Normal file
View File

@ -0,0 +1,933 @@
# Blender v2.82 (sub 7) OBJ File: 'cube.blend'
# www.blender.org
mtllib cube.mtl
o Cube_Finished_Cube.001
v 0.900000 0.900000 -1.000000
v 0.900000 1.000000 -0.900000
v 1.000000 0.900000 -0.900000
v 0.900000 0.930907 -0.995104
v 0.900000 0.958769 -0.980909
v 0.930907 0.900000 -0.995104
v 0.931727 0.931906 -0.989305
v 0.930693 0.957414 -0.975905
v 0.958769 0.900000 -0.980909
v 0.957466 0.930772 -0.975834
v 0.952912 0.952912 -0.966338
v 0.930907 0.995104 -0.900000
v 0.958769 0.980909 -0.900000
v 0.900000 0.995104 -0.930907
v 0.931906 0.989305 -0.931727
v 0.957414 0.975905 -0.930693
v 0.900000 0.980909 -0.958769
v 0.930772 0.975834 -0.957466
v 0.952912 0.966338 -0.952912
v 0.995104 0.900000 -0.930907
v 0.980909 0.900000 -0.958769
v 0.995104 0.930907 -0.900000
v 0.989305 0.931727 -0.931906
v 0.975905 0.930693 -0.957414
v 0.980909 0.958769 -0.900000
v 0.975834 0.957466 -0.930772
v 0.966338 0.952912 -0.952912
v 0.900000 -1.000000 -0.900000
v 0.900000 -0.900000 -1.000000
v 1.000000 -0.900000 -0.900000
v 0.900000 -0.995104 -0.930907
v 0.900000 -0.980909 -0.958769
v 0.930907 -0.995104 -0.900000
v 0.931727 -0.989305 -0.931906
v 0.930693 -0.975905 -0.957414
v 0.958769 -0.980909 -0.900000
v 0.957466 -0.975834 -0.930772
v 0.952912 -0.966338 -0.952912
v 0.930907 -0.900000 -0.995104
v 0.958769 -0.900000 -0.980909
v 0.900000 -0.930907 -0.995104
v 0.931906 -0.931727 -0.989305
v 0.957414 -0.930693 -0.975905
v 0.900000 -0.958769 -0.980909
v 0.930772 -0.957466 -0.975834
v 0.952912 -0.952912 -0.966338
v 0.995104 -0.930907 -0.900000
v 0.980909 -0.958769 -0.900000
v 0.995104 -0.900000 -0.930907
v 0.989305 -0.931906 -0.931727
v 0.975905 -0.957414 -0.930693
v 0.980909 -0.900000 -0.958769
v 0.975834 -0.930772 -0.957466
v 0.966338 -0.952912 -0.952912
v 1.000000 0.900000 0.900000
v 0.900000 1.000000 0.900000
v 0.900000 0.900000 1.000000
v 0.995104 0.930907 0.900000
v 0.980909 0.958769 0.900000
v 0.995104 0.900000 0.930907
v 0.989305 0.931906 0.931727
v 0.975905 0.957414 0.930693
v 0.980909 0.900000 0.958769
v 0.975834 0.930772 0.957466
v 0.966338 0.952912 0.952912
v 0.900000 0.995104 0.930907
v 0.900000 0.980909 0.958769
v 0.930907 0.995104 0.900000
v 0.931727 0.989305 0.931906
v 0.930693 0.975905 0.957414
v 0.958769 0.980909 0.900000
v 0.957466 0.975834 0.930772
v 0.952912 0.966338 0.952912
v 0.930907 0.900000 0.995104
v 0.958769 0.900000 0.980909
v 0.900000 0.930907 0.995104
v 0.931906 0.931727 0.989305
v 0.957414 0.930693 0.975905
v 0.900000 0.958769 0.980909
v 0.930772 0.957466 0.975834
v 0.952912 0.952912 0.966338
v 1.000000 -0.900000 0.900000
v 0.900000 -0.900000 1.000000
v 0.900000 -1.000000 0.900000
v 0.995104 -0.900000 0.930907
v 0.980909 -0.900000 0.958769
v 0.995104 -0.930907 0.900000
v 0.989305 -0.931727 0.931906
v 0.975905 -0.930693 0.957414
v 0.980909 -0.958769 0.900000
v 0.975834 -0.957466 0.930772
v 0.966338 -0.952912 0.952912
v 0.900000 -0.930907 0.995104
v 0.900000 -0.958769 0.980909
v 0.930907 -0.900000 0.995104
v 0.931727 -0.931906 0.989305
v 0.930693 -0.957414 0.975905
v 0.958769 -0.900000 0.980909
v 0.957466 -0.930772 0.975834
v 0.952912 -0.952912 0.966338
v 0.930907 -0.995104 0.900000
v 0.958769 -0.980909 0.900000
v 0.900000 -0.995104 0.930907
v 0.931906 -0.989305 0.931727
v 0.957414 -0.975905 0.930693
v 0.900000 -0.980909 0.958769
v 0.930772 -0.975834 0.957466
v 0.952912 -0.966338 0.952912
v -0.900000 0.900000 -1.000000
v -1.000000 0.900000 -0.900000
v -0.900000 1.000000 -0.900000
v -0.930907 0.900000 -0.995104
v -0.958769 0.900000 -0.980909
v -0.900000 0.930907 -0.995104
v -0.931906 0.931727 -0.989305
v -0.957414 0.930693 -0.975905
v -0.900000 0.958769 -0.980909
v -0.930772 0.957466 -0.975834
v -0.952912 0.952912 -0.966338
v -0.995104 0.930907 -0.900000
v -0.980909 0.958769 -0.900000
v -0.995104 0.900000 -0.930907
v -0.989305 0.931906 -0.931727
v -0.975905 0.957414 -0.930693
v -0.980909 0.900000 -0.958769
v -0.975834 0.930772 -0.957466
v -0.966338 0.952912 -0.952912
v -0.900000 0.995104 -0.930907
v -0.900000 0.980909 -0.958769
v -0.930907 0.995104 -0.900000
v -0.931727 0.989305 -0.931906
v -0.930693 0.975905 -0.957414
v -0.958769 0.980909 -0.900000
v -0.957466 0.975834 -0.930772
v -0.952912 0.966338 -0.952912
v -1.000000 -0.900000 -0.900000
v -0.900000 -0.900000 -1.000000
v -0.900000 -1.000000 -0.900000
v -0.995104 -0.900000 -0.930907
v -0.980909 -0.900000 -0.958769
v -0.995104 -0.930907 -0.900000
v -0.989305 -0.931727 -0.931906
v -0.975905 -0.930693 -0.957414
v -0.980909 -0.958769 -0.900000
v -0.975834 -0.957466 -0.930772
v -0.966338 -0.952912 -0.952912
v -0.900000 -0.930907 -0.995104
v -0.900000 -0.958769 -0.980909
v -0.930907 -0.900000 -0.995104
v -0.931727 -0.931906 -0.989305
v -0.930693 -0.957414 -0.975905
v -0.958769 -0.900000 -0.980909
v -0.957466 -0.930772 -0.975834
v -0.952912 -0.952912 -0.966338
v -0.930907 -0.995104 -0.900000
v -0.958769 -0.980909 -0.900000
v -0.900000 -0.995104 -0.930907
v -0.931906 -0.989305 -0.931727
v -0.957414 -0.975905 -0.930693
v -0.900000 -0.980909 -0.958769
v -0.930772 -0.975834 -0.957466
v -0.952912 -0.966338 -0.952912
v -1.000000 0.900000 0.900000
v -0.900000 0.900000 1.000000
v -0.900000 1.000000 0.900000
v -0.995104 0.900000 0.930907
v -0.980909 0.900000 0.958769
v -0.995104 0.930907 0.900000
v -0.989305 0.931727 0.931906
v -0.975905 0.930693 0.957414
v -0.980909 0.958769 0.900000
v -0.975834 0.957466 0.930772
v -0.966338 0.952912 0.952912
v -0.900000 0.930907 0.995104
v -0.900000 0.958769 0.980909
v -0.930907 0.900000 0.995104
v -0.931727 0.931906 0.989305
v -0.930693 0.957414 0.975905
v -0.958769 0.900000 0.980909
v -0.957466 0.930772 0.975834
v -0.952912 0.952912 0.966338
v -0.930907 0.995104 0.900000
v -0.958769 0.980909 0.900000
v -0.900000 0.995104 0.930907
v -0.931906 0.989305 0.931727
v -0.957414 0.975905 0.930693
v -0.900000 0.980909 0.958769
v -0.930772 0.975834 0.957466
v -0.952912 0.966338 0.952912
v -0.900000 -1.000000 0.900000
v -0.900000 -0.900000 1.000000
v -1.000000 -0.900000 0.900000
v -0.900000 -0.995104 0.930907
v -0.900000 -0.980909 0.958769
v -0.930907 -0.995104 0.900000
v -0.931727 -0.989305 0.931906
v -0.930693 -0.975905 0.957414
v -0.958769 -0.980909 0.900000
v -0.957466 -0.975834 0.930772
v -0.952912 -0.966338 0.952912
v -0.930907 -0.900000 0.995104
v -0.958769 -0.900000 0.980909
v -0.900000 -0.930907 0.995104
v -0.931906 -0.931727 0.989305
v -0.957414 -0.930693 0.975905
v -0.900000 -0.958769 0.980909
v -0.930772 -0.957466 0.975834
v -0.952912 -0.952912 0.966338
v -0.995104 -0.930907 0.900000
v -0.980909 -0.958769 0.900000
v -0.995104 -0.900000 0.930907
v -0.989305 -0.931906 0.931727
v -0.975905 -0.957414 0.930693
v -0.980909 -0.900000 0.958769
v -0.975834 -0.930772 0.957466
v -0.966338 -0.952912 0.952912
vt 0.137500 0.512500
vt 0.362500 0.512500
vt 0.362500 0.737500
vt 0.137500 0.737500
vt 0.387500 0.012500
vt 0.612500 0.012500
vt 0.612500 0.237500
vt 0.387500 0.237500
vt 0.387500 0.762500
vt 0.612500 0.762500
vt 0.612500 0.987500
vt 0.387500 0.987500
vt 0.637500 0.512500
vt 0.862500 0.512500
vt 0.862500 0.737500
vt 0.637500 0.737500
vt 0.387500 0.512500
vt 0.612500 0.512500
vt 0.612500 0.737500
vt 0.387500 0.737500
vt 0.612500 0.487500
vt 0.616363 0.487500
vt 0.616488 0.491466
vt 0.612500 0.491363
vt 0.619846 0.487500
vt 0.619677 0.491337
vt 0.625000 0.487500
vt 0.625000 0.491347
vt 0.616346 0.494683
vt 0.612500 0.494846
vt 0.619114 0.494114
vt 0.625000 0.494114
vt 0.633637 0.512500
vt 0.633512 0.508534
vt 0.637500 0.508637
vt 0.630154 0.512500
vt 0.630323 0.508663
vt 0.619846 0.512500
vt 0.619683 0.508653
vt 0.633654 0.505317
vt 0.637500 0.505154
vt 0.630886 0.505886
vt 0.619114 0.505886
vt 0.612500 0.508637
vt 0.616466 0.508512
vt 0.616363 0.512500
vt 0.612500 0.505154
vt 0.616337 0.505323
vt 0.619114 0.500000
vt 0.362500 0.508637
vt 0.366466 0.508512
vt 0.366363 0.512500
vt 0.362500 0.505154
vt 0.366337 0.505323
vt 0.362500 0.500000
vt 0.366347 0.500000
vt 0.369683 0.508653
vt 0.369846 0.512500
vt 0.369114 0.505886
vt 0.369114 0.500000
vt 0.387500 0.487500
vt 0.387500 0.491363
vt 0.383534 0.491488
vt 0.383637 0.487500
vt 0.387500 0.494846
vt 0.383663 0.494677
vt 0.387500 0.505154
vt 0.383653 0.505317
vt 0.380317 0.491346
vt 0.380154 0.487500
vt 0.380886 0.494114
vt 0.380886 0.505886
vt 0.383637 0.512500
vt 0.383512 0.508534
vt 0.387500 0.508637
vt 0.380154 0.512500
vt 0.380323 0.508663
vt 0.375000 0.505886
vt 0.616363 0.737500
vt 0.616488 0.741466
vt 0.612500 0.741363
vt 0.619846 0.737500
vt 0.619677 0.741337
vt 0.630154 0.737500
vt 0.630317 0.741346
vt 0.616346 0.744683
vt 0.612500 0.744846
vt 0.619114 0.744114
vt 0.630886 0.744114
vt 0.637500 0.741363
vt 0.633534 0.741488
vt 0.633637 0.737500
vt 0.637500 0.744846
vt 0.633664 0.744677
vt 0.637500 0.750000
vt 0.633653 0.750000
vt 0.630886 0.750000
vt 0.612500 0.758637
vt 0.616466 0.758512
vt 0.616363 0.762500
vt 0.612500 0.755154
vt 0.616337 0.755323
vt 0.619683 0.758653
vt 0.619846 0.762500
vt 0.619114 0.755886
vt 0.625000 0.744114
vt 0.619114 0.750000
vt 0.387500 0.741363
vt 0.383534 0.741488
vt 0.383637 0.737500
vt 0.387500 0.744846
vt 0.383663 0.744677
vt 0.387500 0.755154
vt 0.383653 0.755317
vt 0.380317 0.741346
vt 0.380154 0.737500
vt 0.380886 0.744114
vt 0.380886 0.755886
vt 0.383637 0.762500
vt 0.383512 0.758534
vt 0.387500 0.758637
vt 0.380154 0.762500
vt 0.380323 0.758663
vt 0.375000 0.762500
vt 0.375000 0.758654
vt 0.375000 0.755886
vt 0.366363 0.737500
vt 0.366488 0.741466
vt 0.362500 0.741363
vt 0.369846 0.737500
vt 0.369677 0.741337
vt 0.366347 0.744683
vt 0.362500 0.744846
vt 0.369114 0.744114
vt 0.380886 0.750000
vt 0.375000 0.744114
vt 0.612500 0.262500
vt 0.612500 0.258637
vt 0.616466 0.258512
vt 0.616363 0.262500
vt 0.612500 0.255154
vt 0.616337 0.255323
vt 0.612500 0.244846
vt 0.616346 0.244683
vt 0.619683 0.258653
vt 0.619846 0.262500
vt 0.619114 0.255886
vt 0.619114 0.244114
vt 0.616363 0.237500
vt 0.616488 0.241466
vt 0.612500 0.241363
vt 0.619846 0.237500
vt 0.619677 0.241337
vt 0.625000 0.237500
vt 0.625000 0.241347
vt 0.625000 0.244114
vt 0.862500 0.508637
vt 0.866466 0.508512
vt 0.866363 0.512500
vt 0.862500 0.505154
vt 0.866337 0.505323
vt 0.862500 0.500000
vt 0.866347 0.500000
vt 0.869683 0.508653
vt 0.869846 0.512500
vt 0.869114 0.505886
vt 0.869114 0.500000
vt 0.619114 0.250000
vt 0.625000 0.255886
vt 0.387500 0.241363
vt 0.383534 0.241488
vt 0.383637 0.237500
vt 0.387500 0.244846
vt 0.383663 0.244677
vt 0.387500 0.255154
vt 0.383653 0.255317
vt 0.380317 0.241346
vt 0.380154 0.237500
vt 0.380886 0.244114
vt 0.380886 0.255886
vt 0.387500 0.262500
vt 0.383637 0.262500
vt 0.383512 0.258534
vt 0.387500 0.258637
vt 0.380154 0.262500
vt 0.380323 0.258663
vt 0.375000 0.262500
vt 0.375000 0.258653
vt 0.375000 0.255886
vt 0.133637 0.512500
vt 0.133512 0.508534
vt 0.137500 0.508637
vt 0.130154 0.512500
vt 0.130323 0.508663
vt 0.125000 0.512500
vt 0.125000 0.508654
vt 0.133653 0.505317
vt 0.137500 0.505154
vt 0.130886 0.505886
vt 0.125000 0.505886
vt 0.380886 0.250000
vt 0.375000 0.244114
vt 0.612500 0.008637
vt 0.616466 0.008512
vt 0.616363 0.012500
vt 0.612500 0.005154
vt 0.616337 0.005323
vt 0.612500 0.000000
vt 0.616346 0.000000
vt 0.619683 0.008654
vt 0.619846 0.012500
vt 0.619114 0.005886
vt 0.619114 0.000000
vt 0.616363 0.987500
vt 0.616488 0.991466
vt 0.612500 0.991363
vt 0.619846 0.987500
vt 0.619677 0.991337
vt 0.625000 0.987500
vt 0.625000 0.991346
vt 0.616346 0.994683
vt 0.612500 0.994846
vt 0.619114 0.994114
vt 0.625000 0.994114
vt 0.866363 0.737500
vt 0.866488 0.741466
vt 0.862500 0.741363
vt 0.869846 0.737500
vt 0.869677 0.741337
vt 0.875000 0.737500
vt 0.875000 0.741347
vt 0.866346 0.744683
vt 0.862500 0.744846
vt 0.869114 0.744114
vt 0.875000 0.744114
vt 0.625000 0.005886
vt 0.137500 0.741363
vt 0.133534 0.741488
vt 0.133637 0.737500
vt 0.137500 0.744846
vt 0.133663 0.744677
vt 0.137500 0.750000
vt 0.133653 0.750000
vt 0.130317 0.741346
vt 0.130154 0.737500
vt 0.130886 0.744114
vt 0.130886 0.750000
vt 0.387500 0.991363
vt 0.383534 0.991488
vt 0.383637 0.987500
vt 0.387500 0.994846
vt 0.383663 0.994677
vt 0.387500 1.000000
vt 0.383654 1.000000
vt 0.380317 0.991346
vt 0.380154 0.987500
vt 0.380886 0.994114
vt 0.380886 1.000000
vt 0.383637 0.012500
vt 0.383512 0.008534
vt 0.387500 0.008637
vt 0.380154 0.012500
vt 0.380323 0.008663
vt 0.375000 0.012500
vt 0.375000 0.008653
vt 0.383653 0.005317
vt 0.387500 0.005154
vt 0.380886 0.005886
vt 0.375000 0.005886
vt 0.125000 0.744114
vt 0.125000 0.737500
vt 0.137500 0.500000
vt 0.612500 1.000000
vt 0.862500 0.750000
vt 0.362500 0.750000
vt 0.875000 0.512500
vt 0.637500 0.500000
vn -0.0802 -0.9935 -0.0802
vn 0.0802 -0.9935 -0.0802
vn 0.0802 -0.9935 0.0802
vn -0.0802 -0.9935 0.0802
vn -0.9935 -0.0802 0.0802
vn -0.9935 0.0802 0.0802
vn -0.9935 0.0802 -0.0802
vn -0.9935 -0.0802 -0.0802
vn 0.0802 -0.0802 0.9935
vn 0.0802 0.0802 0.9935
vn -0.0802 0.0802 0.9935
vn -0.0802 -0.0802 0.9935
vn 0.0802 0.9935 -0.0802
vn -0.0802 0.9935 -0.0802
vn -0.0802 0.9935 0.0802
vn 0.0802 0.9935 0.0802
vn 0.9935 -0.0802 -0.0802
vn 0.9935 0.0802 -0.0802
vn 0.9935 0.0802 0.0802
vn 0.9935 -0.0802 0.0802
vn 0.0802 0.0802 -0.9935
vn 0.0801 0.3083 -0.9479
vn 0.3068 0.3077 -0.9006
vn 0.3084 0.0804 -0.9478
vn 0.0754 0.5855 -0.8071
vn 0.2854 0.5696 -0.7707
vn 0.0757 0.8072 -0.5853
vn 0.2858 0.7704 -0.5698
vn 0.5698 0.2858 -0.7704
vn 0.5853 0.0757 -0.8072
vn 0.5155 0.5155 -0.6844
vn 0.5155 0.6844 -0.5155
vn 0.3083 0.9479 -0.0801
vn 0.3077 0.9006 -0.3068
vn 0.0804 0.9478 -0.3084
vn 0.5855 0.8071 -0.0754
vn 0.5696 0.7707 -0.2854
vn 0.8072 0.5853 -0.0757
vn 0.7704 0.5698 -0.2858
vn 0.6844 0.5155 -0.5155
vn 0.9479 0.0801 -0.3083
vn 0.9006 0.3068 -0.3077
vn 0.9478 0.3084 -0.0804
vn 0.8071 0.0754 -0.5855
vn 0.7707 0.2854 -0.5696
vn 0.0801 -0.9479 -0.3083
vn 0.3068 -0.9006 -0.3077
vn 0.3084 -0.9478 -0.0804
vn 0.0754 -0.8071 -0.5855
vn 0.2854 -0.7707 -0.5696
vn 0.0757 -0.5853 -0.8072
vn 0.2858 -0.5698 -0.7704
vn 0.5698 -0.7704 -0.2858
vn 0.5853 -0.8072 -0.0757
vn 0.5155 -0.6844 -0.5155
vn 0.5155 -0.5155 -0.6844
vn 0.0802 -0.0802 -0.9935
vn 0.3083 -0.0801 -0.9479
vn 0.3077 -0.3068 -0.9006
vn 0.0804 -0.3084 -0.9478
vn 0.5855 -0.0754 -0.8071
vn 0.5696 -0.2854 -0.7707
vn 0.8072 -0.0757 -0.5853
vn 0.7704 -0.2858 -0.5698
vn 0.6844 -0.5155 -0.5155
vn 0.9479 -0.3083 -0.0801
vn 0.9006 -0.3077 -0.3068
vn 0.9478 -0.0804 -0.3084
vn 0.8071 -0.5855 -0.0754
vn 0.7707 -0.5696 -0.2854
vn 0.9479 0.3083 0.0801
vn 0.9006 0.3077 0.3068
vn 0.9478 0.0804 0.3084
vn 0.8071 0.5855 0.0754
vn 0.7707 0.5696 0.2854
vn 0.5853 0.8072 0.0757
vn 0.5698 0.7704 0.2858
vn 0.7704 0.2858 0.5698
vn 0.8072 0.0757 0.5853
vn 0.6844 0.5155 0.5155
vn 0.5155 0.6844 0.5155
vn 0.0801 0.9479 0.3083
vn 0.3068 0.9006 0.3077
vn 0.3084 0.9478 0.0804
vn 0.0754 0.8071 0.5855
vn 0.2854 0.7707 0.5696
vn 0.0757 0.5853 0.8072
vn 0.2858 0.5698 0.7704
vn 0.5155 0.5155 0.6844
vn 0.3083 0.0801 0.9479
vn 0.3077 0.3068 0.9006
vn 0.0804 0.3084 0.9478
vn 0.5855 0.0754 0.8071
vn 0.5696 0.2854 0.7707
vn 0.9479 -0.0801 0.3083
vn 0.9006 -0.3068 0.3077
vn 0.9478 -0.3084 0.0804
vn 0.8071 -0.0754 0.5855
vn 0.7707 -0.2854 0.5696
vn 0.5853 -0.0757 0.8072
vn 0.5698 -0.2858 0.7704
vn 0.7704 -0.5698 0.2858
vn 0.8072 -0.5853 0.0757
vn 0.6844 -0.5155 0.5155
vn 0.5155 -0.5155 0.6844
vn 0.0801 -0.3083 0.9479
vn 0.3068 -0.3077 0.9006
vn 0.3084 -0.0804 0.9478
vn 0.0754 -0.5855 0.8071
vn 0.2854 -0.5696 0.7707
vn 0.0757 -0.8072 0.5853
vn 0.2858 -0.7704 0.5698
vn 0.5155 -0.6844 0.5155
vn 0.3083 -0.9479 0.0801
vn 0.3077 -0.9006 0.3068
vn 0.0804 -0.9478 0.3084
vn 0.5855 -0.8071 0.0754
vn 0.5696 -0.7707 0.2854
vn -0.0802 0.0802 -0.9935
vn -0.3083 0.0801 -0.9479
vn -0.3077 0.3068 -0.9006
vn -0.0804 0.3084 -0.9478
vn -0.5855 0.0754 -0.8071
vn -0.5696 0.2854 -0.7707
vn -0.8072 0.0757 -0.5853
vn -0.7704 0.2858 -0.5698
vn -0.2858 0.5698 -0.7704
vn -0.0757 0.5853 -0.8072
vn -0.5155 0.5155 -0.6844
vn -0.6844 0.5155 -0.5155
vn -0.9479 0.3083 -0.0801
vn -0.9006 0.3077 -0.3068
vn -0.9478 0.0804 -0.3084
vn -0.8071 0.5855 -0.0754
vn -0.7707 0.5696 -0.2854
vn -0.5853 0.8072 -0.0757
vn -0.5698 0.7704 -0.2858
vn -0.5155 0.6844 -0.5155
vn -0.0801 0.9479 -0.3083
vn -0.3068 0.9006 -0.3077
vn -0.3084 0.9478 -0.0804
vn -0.0754 0.8071 -0.5855
vn -0.2854 0.7707 -0.5696
vn -0.9479 -0.0801 -0.3083
vn -0.9006 -0.3068 -0.3077
vn -0.9478 -0.3084 -0.0804
vn -0.8071 -0.0754 -0.5855
vn -0.7707 -0.2854 -0.5696
vn -0.5853 -0.0757 -0.8072
vn -0.5698 -0.2858 -0.7704
vn -0.7704 -0.5698 -0.2858
vn -0.8072 -0.5853 -0.0757
vn -0.6844 -0.5155 -0.5155
vn -0.5155 -0.5155 -0.6844
vn -0.0802 -0.0802 -0.9935
vn -0.0801 -0.3083 -0.9479
vn -0.3068 -0.3077 -0.9006
vn -0.3084 -0.0804 -0.9478
vn -0.0754 -0.5855 -0.8071
vn -0.2854 -0.5696 -0.7707
vn -0.0757 -0.8072 -0.5853
vn -0.2858 -0.7704 -0.5698
vn -0.5155 -0.6844 -0.5155
vn -0.3083 -0.9479 -0.0801
vn -0.3077 -0.9006 -0.3068
vn -0.0804 -0.9478 -0.3084
vn -0.5855 -0.8071 -0.0754
vn -0.5696 -0.7707 -0.2854
vn -0.9479 0.0801 0.3083
vn -0.9006 0.3068 0.3077
vn -0.9478 0.3084 0.0804
vn -0.8071 0.0754 0.5855
vn -0.7707 0.2854 0.5696
vn -0.5853 0.0757 0.8072
vn -0.5698 0.2858 0.7704
vn -0.7704 0.5698 0.2858
vn -0.8072 0.5853 0.0757
vn -0.6844 0.5155 0.5155
vn -0.5155 0.5155 0.6844
vn -0.0801 0.3083 0.9479
vn -0.3068 0.3077 0.9006
vn -0.3084 0.0804 0.9478
vn -0.0754 0.5855 0.8071
vn -0.2854 0.5696 0.7707
vn -0.0757 0.8072 0.5853
vn -0.2858 0.7704 0.5698
vn -0.5155 0.6844 0.5155
vn -0.3083 0.9479 0.0801
vn -0.3077 0.9006 0.3068
vn -0.0804 0.9478 0.3084
vn -0.5855 0.8071 0.0754
vn -0.5696 0.7707 0.2854
vn -0.0801 -0.9479 0.3083
vn -0.3068 -0.9006 0.3077
vn -0.3084 -0.9478 0.0804
vn -0.0754 -0.8071 0.5855
vn -0.2854 -0.7707 0.5696
vn -0.0757 -0.5853 0.8072
vn -0.2858 -0.5698 0.7704
vn -0.5698 -0.7704 0.2858
vn -0.5853 -0.8072 0.0757
vn -0.5155 -0.6844 0.5155
vn -0.5155 -0.5155 0.6844
vn -0.3083 -0.0801 0.9479
vn -0.3077 -0.3068 0.9006
vn -0.0804 -0.3084 0.9478
vn -0.5855 -0.0754 0.8071
vn -0.5696 -0.2854 0.7707
vn -0.8072 -0.0757 0.5853
vn -0.7704 -0.2858 0.5698
vn -0.6844 -0.5155 0.5155
vn -0.9479 -0.3083 0.0801
vn -0.9006 -0.3077 0.3068
vn -0.9478 -0.0804 0.3084
vn -0.8071 -0.5855 0.0754
vn -0.7707 -0.5696 0.2854
usemtl Material.001
s 1
f 138/1/1 28/2/2 84/3/3 190/4/4
f 192/5/5 163/6/6 110/7/7 136/8/8
f 83/9/9 57/10/10 164/11/11 191/12/12
f 2/13/13 111/14/14 165/15/15 56/16/16
f 30/17/17 3/18/18 55/19/19 82/20/20
f 1/21/21 4/22/22 7/23/23 6/24/24
f 4/22/22 5/25/25 8/26/26 7/23/23
f 5/25/25 17/27/27 18/28/28 8/26/26
f 6/24/24 7/23/23 10/29/29 9/30/30
f 7/23/23 8/26/26 11/31/31 10/29/29
f 8/26/26 18/28/28 19/32/32 11/31/31
f 2/13/13 12/33/33 15/34/34 14/35/35
f 12/33/33 13/36/36 16/37/37 15/34/34
f 13/36/36 25/38/38 26/39/39 16/37/37
f 14/35/35 15/34/34 18/40/28 17/41/27
f 15/34/34 16/37/37 19/42/32 18/40/28
f 16/37/37 26/39/39 27/43/40 19/42/32
f 3/18/18 20/44/41 23/45/42 22/46/43
f 20/44/41 21/47/44 24/48/45 23/45/42
f 21/47/44 9/30/30 10/29/29 24/48/45
f 22/46/43 23/45/42 26/39/39 25/38/38
f 23/45/42 24/48/45 27/43/40 26/39/39
f 24/48/45 10/29/29 11/31/31 27/43/40
f 11/31/31 19/32/32 27/49/40
f 28/2/2 31/50/46 34/51/47 33/52/48
f 31/50/46 32/53/49 35/54/50 34/51/47
f 32/53/49 44/55/51 45/56/52 35/54/50
f 33/52/48 34/51/47 37/57/53 36/58/54
f 34/51/47 35/54/50 38/59/55 37/57/53
f 35/54/50 45/56/52 46/60/56 38/59/55
f 29/61/57 39/62/58 42/63/59 41/64/60
f 39/62/58 40/65/61 43/66/62 42/63/59
f 40/65/61 52/67/63 53/68/64 43/66/62
f 41/64/60 42/63/59 45/69/52 44/70/51
f 42/63/59 43/66/62 46/71/56 45/69/52
f 43/66/62 53/68/64 54/72/65 46/71/56
f 30/17/17 47/73/66 50/74/67 49/75/68
f 47/73/66 48/76/69 51/77/70 50/74/67
f 48/76/69 36/58/54 37/57/53 51/77/70
f 49/75/68 50/74/67 53/68/64 52/67/63
f 50/74/67 51/77/70 54/72/65 53/68/64
f 51/77/70 37/57/53 38/59/55 54/72/65
f 38/59/55 46/60/56 54/78/65
f 55/19/19 58/79/71 61/80/72 60/81/73
f 58/79/71 59/82/74 62/83/75 61/80/72
f 59/82/74 71/84/76 72/85/77 62/83/75
f 60/81/73 61/80/72 64/86/78 63/87/79
f 61/80/72 62/83/75 65/88/80 64/86/78
f 62/83/75 72/85/77 73/89/81 65/88/80
f 56/16/16 66/90/82 69/91/83 68/92/84
f 66/90/82 67/93/85 70/94/86 69/91/83
f 67/93/85 79/95/87 80/96/88 70/94/86
f 68/92/84 69/91/83 72/85/77 71/84/76
f 69/91/83 70/94/86 73/89/81 72/85/77
f 70/94/86 80/96/88 81/97/89 73/89/81
f 57/10/10 74/98/90 77/99/91 76/100/92
f 74/98/90 75/101/93 78/102/94 77/99/91
f 75/101/93 63/87/79 64/86/78 78/102/94
f 76/100/92 77/99/91 80/103/88 79/104/87
f 77/99/91 78/102/94 81/105/89 80/103/88
f 78/102/94 64/86/78 65/88/80 81/105/89
f 65/88/80 73/106/81 81/107/89
f 82/20/20 85/108/95 88/109/96 87/110/97
f 85/108/95 86/111/98 89/112/99 88/109/96
f 86/111/98 98/113/100 99/114/101 89/112/99
f 87/110/97 88/109/96 91/115/102 90/116/103
f 88/109/96 89/112/99 92/117/104 91/115/102
f 89/112/99 99/114/101 100/118/105 92/117/104
f 83/9/9 93/119/106 96/120/107 95/121/108
f 93/119/106 94/122/109 97/123/110 96/120/107
f 94/122/109 106/124/111 107/125/112 97/123/110
f 95/121/108 96/120/107 99/114/101 98/113/100
f 96/120/107 97/123/110 100/118/105 99/114/101
f 97/123/110 107/125/112 108/126/113 100/118/105
f 84/3/3 101/127/114 104/128/115 103/129/116
f 101/127/114 102/130/117 105/131/118 104/128/115
f 102/130/117 90/116/103 91/115/102 105/131/118
f 103/129/116 104/128/115 107/132/112 106/133/111
f 104/128/115 105/131/118 108/134/113 107/132/112
f 105/131/118 91/115/102 92/117/104 108/134/113
f 92/117/104 100/135/105 108/136/113
f 109/137/119 112/138/120 115/139/121 114/140/122
f 112/138/120 113/141/123 116/142/124 115/139/121
f 113/141/123 125/143/125 126/144/126 116/142/124
f 114/140/122 115/139/121 118/145/127 117/146/128
f 115/139/121 116/142/124 119/147/129 118/145/127
f 116/142/124 126/144/126 127/148/130 119/147/129
f 110/7/7 120/149/131 123/150/132 122/151/133
f 120/149/131 121/152/134 124/153/135 123/150/132
f 121/152/134 133/154/136 134/155/137 124/153/135
f 122/151/133 123/150/132 126/144/126 125/143/125
f 123/150/132 124/153/135 127/148/130 126/144/126
f 124/153/135 134/155/137 135/156/138 127/148/130
f 111/14/14 128/157/139 131/158/140 130/159/141
f 128/157/139 129/160/142 132/161/143 131/158/140
f 129/160/142 117/162/128 118/163/127 132/161/143
f 130/159/141 131/158/140 134/164/137 133/165/136
f 131/158/140 132/161/143 135/166/138 134/164/137
f 132/161/143 118/163/127 119/167/129 135/166/138
f 119/147/129 127/168/130 135/169/138
f 136/8/8 139/170/144 142/171/145 141/172/146
f 139/170/144 140/173/147 143/174/148 142/171/145
f 140/173/147 152/175/149 153/176/150 143/174/148
f 141/172/146 142/171/145 145/177/151 144/178/152
f 142/171/145 143/174/148 146/179/153 145/177/151
f 143/174/148 153/176/150 154/180/154 146/179/153
f 137/181/155 147/182/156 150/183/157 149/184/158
f 147/182/156 148/185/159 151/186/160 150/183/157
f 148/185/159 160/187/161 161/188/162 151/186/160
f 149/184/158 150/183/157 153/176/150 152/175/149
f 150/183/157 151/186/160 154/180/154 153/176/150
f 151/186/160 161/188/162 162/189/163 154/180/154
f 138/1/1 155/190/164 158/191/165 157/192/166
f 155/190/164 156/193/167 159/194/168 158/191/165
f 156/193/167 144/195/152 145/196/151 159/194/168
f 157/192/166 158/191/165 161/197/162 160/198/161
f 158/191/165 159/194/168 162/199/163 161/197/162
f 159/194/168 145/196/151 146/200/153 162/199/163
f 146/179/153 154/201/154 162/202/163
f 163/6/6 166/203/169 169/204/170 168/205/171
f 166/203/169 167/206/172 170/207/173 169/204/170
f 167/206/172 179/208/174 180/209/175 170/207/173
f 168/205/171 169/204/170 172/210/176 171/211/177
f 169/204/170 170/207/173 173/212/178 172/210/176
f 170/207/173 180/209/175 181/213/179 173/212/178
f 164/11/11 174/214/180 177/215/181 176/216/182
f 174/214/180 175/217/183 178/218/184 177/215/181
f 175/217/183 187/219/185 188/220/186 178/218/184
f 176/216/182 177/215/181 180/221/175 179/222/174
f 177/215/181 178/218/184 181/223/179 180/221/175
f 178/218/184 188/220/186 189/224/187 181/223/179
f 165/15/15 182/225/188 185/226/189 184/227/190
f 182/225/188 183/228/191 186/229/192 185/226/189
f 183/228/191 171/230/177 172/231/176 186/229/192
f 184/227/190 185/226/189 188/232/186 187/233/185
f 185/226/189 186/229/192 189/234/187 188/232/186
f 186/229/192 172/231/176 173/235/178 189/234/187
f 173/212/178 181/213/179 189/236/187
f 190/4/4 193/237/193 196/238/194 195/239/195
f 193/237/193 194/240/196 197/241/197 196/238/194
f 194/240/196 206/242/198 207/243/199 197/241/197
f 195/239/195 196/238/194 199/244/200 198/245/201
f 196/238/194 197/241/197 200/246/202 199/244/200
f 197/241/197 207/243/199 208/247/203 200/246/202
f 191/12/12 201/248/204 204/249/205 203/250/206
f 201/248/204 202/251/207 205/252/208 204/249/205
f 202/251/207 214/253/209 215/254/210 205/252/208
f 203/250/206 204/249/205 207/255/199 206/256/198
f 204/249/205 205/252/208 208/257/203 207/255/199
f 205/252/208 215/254/210 216/258/211 208/257/203
f 192/5/5 209/259/212 212/260/213 211/261/214
f 209/259/212 210/262/215 213/263/216 212/260/213
f 210/262/215 198/264/201 199/265/200 213/263/216
f 211/261/214 212/260/213 215/266/210 214/267/209
f 212/260/213 213/263/216 216/268/211 215/266/210
f 213/263/216 199/265/200 200/269/202 216/268/211
f 200/246/202 208/247/203 216/270/211
f 138/1/1 190/4/4 195/239/195 155/190/164
f 155/190/164 195/239/195 198/245/201 156/193/167
f 156/193/167 198/245/201 210/271/215 144/195/152
f 144/178/152 210/262/215 209/259/212 141/172/146
f 141/172/146 209/259/212 192/5/5 136/8/8
f 28/2/2 138/1/1 157/192/166 31/50/46
f 31/50/46 157/192/166 160/198/161 32/53/49
f 32/53/49 160/198/161 148/272/159 44/55/51
f 44/70/51 148/185/159 147/182/156 41/64/60
f 41/64/60 147/182/156 137/181/155 29/61/57
f 3/18/18 30/17/17 49/75/68 20/44/41
f 20/44/41 49/75/68 52/67/63 21/47/44
f 21/47/44 52/67/63 40/65/61 9/30/30
f 9/30/30 40/65/61 39/62/58 6/24/24
f 6/24/24 39/62/58 29/61/57 1/21/21
f 191/12/12 164/11/11 176/216/182 201/248/204
f 201/248/204 176/216/182 179/222/174 202/251/207
f 202/251/207 179/222/174 167/273/172 214/253/209
f 214/267/209 167/206/172 166/203/169 211/261/214
f 211/261/214 166/203/169 163/6/6 192/5/5
f 57/10/10 83/9/9 95/121/108 74/98/90
f 74/98/90 95/121/108 98/113/100 75/101/93
f 75/101/93 98/113/100 86/111/98 63/87/79
f 63/87/79 86/111/98 85/108/95 60/81/73
f 60/81/73 85/108/95 82/20/20 55/19/19
f 109/137/119 137/181/155 149/184/158 112/138/120
f 112/138/120 149/184/158 152/175/149 113/141/123
f 113/141/123 152/175/149 140/173/147 125/143/125
f 125/143/125 140/173/147 139/170/144 122/151/133
f 122/151/133 139/170/144 136/8/8 110/7/7
f 56/16/16 165/15/15 184/227/190 66/90/82
f 66/90/82 184/227/190 187/233/185 67/93/85
f 67/93/85 187/233/185 175/274/183 79/95/87
f 79/104/87 175/217/183 174/214/180 76/100/92
f 76/100/92 174/214/180 164/11/11 57/10/10
f 2/13/13 56/16/16 68/92/84 12/33/33
f 12/33/33 68/92/84 71/84/76 13/36/36
f 13/36/36 71/84/76 59/82/74 25/38/38
f 25/38/38 59/82/74 58/79/71 22/46/43
f 22/46/43 58/79/71 55/19/19 3/18/18
f 190/4/4 84/3/3 103/129/116 193/237/193
f 193/237/193 103/129/116 106/133/111 194/240/196
f 194/240/196 106/133/111 94/275/109 206/242/198
f 206/256/198 94/122/109 93/119/106 203/250/206
f 203/250/206 93/119/106 83/9/9 191/12/12
f 165/15/15 111/14/14 130/159/141 182/225/188
f 182/225/188 130/159/141 133/165/136 183/228/191
f 183/228/191 133/165/136 121/276/134 171/230/177
f 171/211/177 121/152/134 120/149/131 168/205/171
f 168/205/171 120/149/131 110/7/7 163/6/6
f 111/14/14 2/13/13 14/35/35 128/157/139
f 128/157/139 14/35/35 17/41/27 129/160/142
f 129/160/142 17/41/27 5/277/25 117/162/128
f 117/146/128 5/25/25 4/22/22 114/140/122
f 114/140/122 4/22/22 1/21/21 109/137/119
f 84/3/3 28/2/2 33/52/48 101/127/114
f 101/127/114 33/52/48 36/58/54 102/130/117
f 102/130/117 36/58/54 48/76/69 90/116/103
f 90/116/103 48/76/69 47/73/66 87/110/97
f 87/110/97 47/73/66 30/17/17 82/20/20
f 137/181/155 109/137/119 1/21/21 29/61/57

BIN
res/pure-sky.hdr Normal file

Binary file not shown.

BIN
res/skybox.glb Normal file

Binary file not shown.

BIN
res/skybox.hdr Normal file

Binary file not shown.

22
res/skybox.mtl Normal file
View File

@ -0,0 +1,22 @@
# File generated by ImageToStl.com - Free Image and 3D model conversion tools
newmtl mat0
Ns 0
Ka 1.0 1.0 1.0
Kd 0 0 0
Ks 0.5 0.5 0.5
Ke 1 1 1
Ni 1.0
d 1
illum 2
newmtl mat1
Ns 250
Ka 1.0 1.0 1.0
Kd 0.85 0.85 0.85
Ks 0 0 0
Ke 0.0 0.0 0.0
Ni 1.0
d 1
illum 1

2568
res/skybox.obj Normal file

File diff suppressed because it is too large Load Diff

BIN
res/skybox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 MiB

View File

@ -1,11 +0,0 @@
[package]
name = "simulator"
version = "0.1.0"
edition = "2021"
[dependencies]
solar_engine = { path = "../solar_engine" }
log = "0.4"
env_logger = "0.11.8"
pollster = "0.4.0"
cgmath = "0.18.0"

View File

@ -1,146 +0,0 @@
use cgmath::{Rotation3, Vector3};
use solar_engine::{Application, Body, InputEvent, Key, Light, MouseButton, RenderInstance, Shape, Simulator};
use std::sync::{Arc, RwLock};
use std::thread;
pub async fn run() {
let simulator = Arc::new(RwLock::new(Simulator::new()));
{
let mut sim = simulator.write().unwrap();
sim.add_body(Body {
name: "Sun".into(),
position: Vector3::new(0.0, 0.0, 0.0),
velocity: Vector3::new(0.0, 0.0, 0.0),
mass: 1.989e30,
radius: 6.963e8,
});
sim.add_body(Body {
name: "Earth".into(),
position: Vector3::new(1.496e11, 0.0, 0.0),
velocity: Vector3::new(0.0, 29780.0, 0.0),
mass: 5.972e24,
radius: 6.371e6,
});
let earth_position = sim.bodies[1].position;
let earth_velocity = sim.bodies[1].velocity;
sim.add_body(Body {
name: "Moon".into(),
position: earth_position + Vector3::new(384.4e6, 0.0, 0.0),
velocity: earth_velocity + Vector3::new(0.0, 1022.0, 0.0),
mass: 7.342e22,
radius: 1.737e6,
});
}
let sim_clone = simulator.clone();
thread::spawn(move || {
use std::time::{Duration, Instant};
let mut last = Instant::now();
loop {
let now = Instant::now();
let dt = now.duration_since(last).as_secs_f64();
last = now;
{
let mut sim = sim_clone.write().unwrap();
let timewarp = sim.get_timewarp();
sim.step(dt * timewarp as f64);
}
thread::sleep(Duration::from_nanos(1));
}
});
let simulator_clone = simulator.clone();
Application::new()
.on_update(move |state| {
let sim = simulator_clone.read().unwrap();
let bodies = &sim.bodies;
let sun_pos = bodies[0].position / 1.496e11;
state.light_manager.clear();
state.light_manager.add_light(Light::new_point(
sun_pos.cast::<f32>().unwrap(),
Vector3::from([1.0, 1.0, 0.8]),
5.0,
1.0 / 1000.0,
));
let instances = bodies
.iter()
.enumerate()
.map(|(i, b)| {
RenderInstance {
position: ((b.position / 1.496e11) - sun_pos).cast::<f32>().unwrap(),
rotation: cgmath::Quaternion::from_angle_z(cgmath::Deg(0.0)),
color: match i {
0 => [1.0, 1.0, 0.0], // Sun
1 => [0.0, 0.0, 1.0], // Earth
_ => [0.5, 0.5, 0.5],
},
scale: 0.05,
shape: Shape::Sphere,
always_lit: i == 0, // Sun
is_transparent: false,
}
})
.collect();
state.set_instances(instances);
})
.on_input({
let simulator = simulator.clone();
move |state, event| {
match event {
InputEvent::MouseDragged { delta, button: MouseButton::Left } => {
state.camera_mut().rotate_yaw_pitch(-delta.x as f32 * 0.1, delta.y as f32 * 0.1);
}
InputEvent::MouseWheel { delta } => {
state.camera_mut().zoom(delta * 0.05);
}
InputEvent::KeyPressed { key, .. } => {
match key {
Key::ArrowLeft => {
state.camera_mut().translate(Vector3::new(1.0, 0.0, 0.0));
}
Key::ArrowRight => {
state.camera_mut().translate(Vector3::new(-1.0, 0.0, 0.0));
}
Key::ArrowUp => {
state.camera_mut().translate(Vector3::new(0.0, -1.0, 0.0));
}
Key::ArrowDown => {
state.camera_mut().translate(Vector3::new(0.0, 1.0, 0.0));
}
Key::Period => {
let mut sim = simulator.write().unwrap();
sim.increase_timewarp();
println!("Timewarp: {}", sim.get_timewarp());
}
Key::Comma => {
let mut sim = simulator.write().unwrap();
sim.decrease_timewarp();
println!("Timewarp: {}", sim.get_timewarp());
}
Key::Minus => {
let mut sim = simulator.write().unwrap();
sim.reset_timewarp();
println!("Timewarp: {}", sim.get_timewarp());
}
_ => {}
}
}
_ => {}
}
}
})
.run();
}
fn main() {
pollster::block_on(run());
}

View File

@ -1,7 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "solar_engine"
version = "0.1.0"

View File

@ -1,14 +0,0 @@
[package]
name = "solar_engine"
version = "0.1.0"
edition = "2021"
[dependencies]
rayon = "1.8"
winit = "0.30.10"
log = "0.4"
env_logger = "0.11.8"
bytemuck = "1.23.0"
wgpu = "25.0"
pollster = "0.4.0"
cgmath = "0.18.0"

View File

@ -1,88 +0,0 @@
use winit::application::ApplicationHandler;
use winit::event::{ElementState, Modifiers, MouseScrollDelta, WindowEvent};
use winit::event_loop::ActiveEventLoop;
use winit::window::{Window, WindowId};
use crate::input::{InputEvent, InputTracker};
pub struct StateApplication<'a> {
state: Option<crate::state::State<'a>>,
modifiers: Modifiers,
update_fn: Option<Box<dyn FnMut(&mut crate::state::State<'a>) + 'a>>,
input_fn: Option<Box<dyn FnMut(&mut crate::state::State<'a>, &InputEvent) + 'a>>,
input_tracker: InputTracker
}
impl<'a> StateApplication<'a> {
pub fn new() -> Self {
Self { state: None, update_fn: None, input_fn: None, modifiers: Modifiers::default(), input_tracker: InputTracker::default() }
}
pub fn on_update<F: FnMut(&mut crate::state::State<'a>) + 'a>(mut self, func: F) -> Self {
self.update_fn = Some(Box::new(func));
self
}
pub fn on_input<F: FnMut(&mut crate::state::State<'a>, &InputEvent) + 'a>(mut self, func: F) -> Self {
self.input_fn = Some(Box::new(func));
self
}
pub fn run(mut self) {
let event_loop = winit::event_loop::EventLoop::new().unwrap();
let _ = event_loop.run_app(&mut self);
}
}
impl<'a> ApplicationHandler for StateApplication<'a> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window = event_loop
.create_window(Window::default_attributes().with_title("Solar Engine"))
.unwrap();
self.state = Some(crate::state::State::new(window));
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let window = self.state.as_ref().unwrap().window();
if window.id() == window_id {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(physical_size) => {
self.state.as_mut().unwrap().resize(physical_size);
}
WindowEvent::RedrawRequested => {
if let (Some(state), Some(update_fn)) = (self.state.as_mut(), self.update_fn.as_mut()) {
update_fn(state);
}
self.state.as_mut().unwrap().render().unwrap();
}
WindowEvent::ModifiersChanged(modifiers) => {
self.modifiers = modifiers;
}
_ => {
if let Some(state) = self.state.as_mut() {
if let Some(event) = self.input_tracker.handle_window_event(&event, self.modifiers) {
if let Some(input_fn) = self.input_fn.as_mut() {
input_fn(state, &event);
}
}
}
}
}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(state) = self.state.as_ref() {
state.window().request_redraw();
}
}
}

View File

@ -1,22 +0,0 @@
use cgmath::Vector3;
#[derive(Debug)]
pub struct Body {
pub name: String,
pub mass: f64,
pub position: Vector3<f64>,
pub velocity: Vector3<f64>,
pub radius: f64,
}
impl Body {
pub fn new(name: &str, mass: f64, position: Vector3<f64>, velocity: Vector3<f64>, radius: f64) -> Self {
Self {
name: name.to_string(),
mass,
position,
velocity,
radius,
}
}
}

View File

@ -1,68 +0,0 @@
use cgmath::{Matrix4, Point3, Vector3, Deg, perspective, InnerSpace, Rotation, Quaternion, Rotation3, Rad};
#[derive(Debug)]
pub struct Camera {
pub eye: Point3<f32>,
pub target: Point3<f32>,
pub up: Vector3<f32>,
pub fov_y: Deg<f32>,
pub aspect: f32,
pub znear: f32,
pub zfar: f32,
}
impl Camera {
pub fn new(aspect: f32) -> Self {
Self {
eye: Point3::new(0.0, 0.0, 5.0),
target: Point3::new(0.0, 0.0, 0.0),
up: Vector3::unit_y(),
fov_y: Deg(45.0),
aspect,
znear: 0.1,
zfar: 100.0,
}
}
pub fn build_view_projection_matrix(&self) -> Matrix4<f32> {
let view = Matrix4::look_at_rh(self.eye, self.target, self.up);
let proj = perspective(self.fov_y, self.aspect, self.znear, self.zfar);
proj * view
}
pub fn build_view_matrix(&self) -> Matrix4<f32> {
Matrix4::look_at_rh(self.eye, self.target, self.up)
}
pub fn rotate_yaw_pitch(&mut self, yaw: f32, pitch: f32) {
let offset = self.eye - self.target;
let yaw_q = Quaternion::from_axis_angle(Vector3::unit_y(), Rad(yaw.to_radians()));
let right = offset.cross(self.up).normalize();
let pitch_q = Quaternion::from_axis_angle(right, Rad(pitch.to_radians()));
let rotation = yaw_q * pitch_q;
let new_offset = rotation.rotate_vector(offset);
self.eye = self.target + new_offset;
self.up = rotation.rotate_vector(self.up);
}
pub fn translate(&mut self, translation: Vector3<f32>) {
let dir = (self.target - self.eye).normalize();
let horizontal = Vector3::unit_y().cross(dir).normalize();
let vertical = horizontal.cross(dir).normalize();
self.eye += horizontal * translation.x + vertical * translation.y;
self.target += horizontal * translation.x + vertical * translation.y;
}
pub fn zoom(&mut self, amount: f32) {
let dir = (self.target - self.eye).normalize();
self.eye += dir * amount;
}
pub fn set_aspect(&mut self, aspect: f32) {
self.aspect = aspect;
}
}

View File

@ -1,10 +0,0 @@
use wgpu::{Device, Queue, Surface, SurfaceConfiguration};
use crate::state::SampleCount;
pub struct DeviceManager<'a> {
pub surface: Surface<'a>,
pub device: Device,
pub queue: Queue,
pub config: SurfaceConfiguration,
pub sample_count: SampleCount,
}

View File

@ -1,142 +0,0 @@
use std::collections::HashMap;
use wgpu::{Device, Buffer};
use wgpu::util::DeviceExt;
use crate::renderer::Vertex;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Shape {
Circle,
Sphere,
}
pub struct Geometry {
pub(crate) vertex_buffer: Buffer,
pub(crate) index_buffer: Buffer,
pub(crate) index_count: u32,
}
pub struct GeometryManager {
pub geometries: HashMap<Shape, Geometry>,
}
impl GeometryManager {
pub fn new(device: &Device) -> Self {
let mut geometries = HashMap::new();
// Circle
let (circle_vertices, circle_indices) = create_circle_vertices(512, 0.5, [0.5, 0.5, 0.5]);
geometries.insert(
Shape::Circle,
Self::create_geometry(device, &circle_vertices, &circle_indices),
);
// Sphere
let (sphere_vertices, sphere_indices) = create_sphere_vertices(32, 32, 0.5, [0.5, 0.5, 0.5]);
geometries.insert(
Shape::Sphere,
Self::create_geometry(device, &sphere_vertices, &sphere_indices),
);
// Füge hier beliebige weitere Shapes hinzu
Self { geometries }
}
pub fn get(&self, shape: &Shape) -> Option<&Geometry> {
self.geometries.get(shape)
}
pub fn shapes(&self) -> impl Iterator<Item = Shape> + '_ {
self.geometries.keys().copied()
}
fn create_geometry(device: &Device, vertices: &[Vertex], indices: &[u16]) -> Geometry {
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX,
});
Geometry {
vertex_buffer,
index_buffer,
index_count: indices.len() as u32,
}
}
}
pub fn create_circle_vertices(segment_count: usize, radius: f32, color: [f32; 3]) -> (Vec<Vertex>, Vec<u16>) {
let mut vertices = vec![Vertex {
position: [0.0, 0.0, 0.0],
color,
normal: [0.0, 0.0, 1.0],
}];
let mut indices = vec![];
for i in 0..=segment_count {
let theta = (i as f32) / (segment_count as f32) * std::f32::consts::TAU;
let x = radius * theta.cos();
let y = radius * theta.sin();
vertices.push(Vertex {
position: [x, y, 0.0],
color,
normal: [0.0, 0.0, 1.0],
});
}
for i in 1..=segment_count {
indices.push(0);
indices.push(i as u16);
indices.push((i % segment_count + 1) as u16);
}
(vertices, indices)
}
pub fn create_sphere_vertices(stacks: usize, slices: usize, radius: f32, color: [f32; 3]) -> (Vec<Vertex>, Vec<u16>) {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for i in 0..=stacks {
let phi = std::f32::consts::PI * (i as f32) / (stacks as f32);
let y = phi.cos();
let r = phi.sin();
for j in 0..=slices {
let theta = 2.0 * std::f32::consts::PI * (j as f32) / (slices as f32);
let x = r * theta.cos();
let z = r * theta.sin();
let normal = [x, y, z];
vertices.push(Vertex {
position: [x * radius, y * radius, z * radius],
color,
normal,
});
}
}
for i in 0..stacks {
for j in 0..slices {
let first = i * (slices + 1) + j;
let second = first + slices + 1;
indices.push(first as u16);
indices.push(second as u16);
indices.push((first + 1) as u16);
indices.push(second as u16);
indices.push((second + 1) as u16);
indices.push((first + 1) as u16);
}
}
(vertices, indices)
}

View File

@ -1,94 +0,0 @@
use crate::camera::Camera;
use wgpu::{BindGroup, BindGroupLayout, Buffer, Device, Queue};
use bytemuck::{Pod, Zeroable};
use wgpu::util::DeviceExt;
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct GlobalsUniform {
pub view_proj: [[f32; 4]; 4],
pub resolution: [f32; 2],
_padding: [f32; 2],
}
pub struct GlobalsManager {
buffer: Buffer,
pub(crate) bind_group: BindGroup,
layout: BindGroupLayout,
resolution: [f32; 2],
}
impl GlobalsManager {
pub fn new(device: &Device, width: u32, height: u32, camera: &Camera) -> Self {
let resolution = [width as f32, height as f32];
let view_proj = camera.build_view_projection_matrix();
let data = GlobalsUniform {
view_proj: view_proj.into(),
resolution,
_padding: [0.0; 2],
};
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Globals Buffer"),
contents: bytemuck::cast_slice(&[data]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Globals Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Globals Bind Group"),
layout: &layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
});
Self {
buffer,
bind_group,
layout,
resolution,
}
}
pub fn update(&mut self, queue: &Queue, camera: &Camera) {
let view_proj = camera.build_view_projection_matrix();
let data = GlobalsUniform {
view_proj: view_proj.into(),
resolution: self.resolution,
_padding: [0.0; 2],
};
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[data]));
}
pub fn resize(&mut self, width: u32, height: u32) {
self.resolution = [width as f32, height as f32];
}
pub fn layout(&self) -> &BindGroupLayout {
&self.layout
}
pub fn bind_group(&self) -> &BindGroup {
&self.bind_group
}
pub fn resolution(&self) -> [f32; 2] {
self.resolution
}
}

View File

@ -1,257 +0,0 @@
use cgmath::Vector2;
use winit::event::{ElementState, KeyEvent, MouseScrollDelta, Modifiers, WindowEvent};
use winit::keyboard::{Key as WinitKey, ModifiersKeyState};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Key {
// Symbols
Comma, Period, Minus, Plus, Slash, Backslash, Semicolon, Apostrophe,
LeftBracket, RightBracket, Grave, Equal,
// Digits
Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
// Letters
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
// Arrows
ArrowUp, ArrowDown, ArrowLeft, ArrowRight,
// Controls
Escape, Enter, Space, Tab, Backspace,
Insert, Delete, Home, End, PageUp, PageDown,
// Modifier keys
Shift, Control, Alt, Super,
// Function keys
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
// Numpad
Numpad0, Numpad1, Numpad2, Numpad3, Numpad4,
Numpad5, Numpad6, Numpad7, Numpad8, Numpad9,
NumpadAdd, NumpadSubtract, NumpadMultiply, NumpadDivide, NumpadEnter,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyModifiers {
pub lshift: bool,
pub rshift: bool,
pub lcontrol: bool,
pub rcontrol: bool,
pub lalt: bool,
pub ralt: bool,
pub rsuper_key: bool,
pub lsuper_key: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MouseButton {
Left,
Right,
Middle,
Other(u16),
}
#[derive(Debug, Clone)]
pub enum InputEvent {
KeyPressed {
key: Key,
modifiers: KeyModifiers,
text: String,
},
KeyReleased {
key: Key,
modifiers: KeyModifiers,
},
MouseMoved {
position: (f64, f64),
},
MouseDragged {
delta: Vector2<f64>,
button: MouseButton,
},
MouseWheel {
delta: f32,
},
}
#[derive(Default)]
pub struct InputTracker {
pub last_cursor_pos: Option<(f64, f64)>,
pub dragging_button: Option<MouseButton>,
}
impl InputTracker {
pub fn handle_window_event(&mut self, event: &WindowEvent, modifiers: Modifiers) -> Option<InputEvent> {
match event {
WindowEvent::KeyboardInput { event, .. } => {
Some(handle_keyboard_input(event, modifiers))
}
WindowEvent::CursorMoved { position, .. } => {
if let (Some(last), Some(button)) = (self.last_cursor_pos, self.dragging_button) {
let delta = Vector2::new(position.x - last.0, position.y - last.1);
self.last_cursor_pos = Some((position.x, position.y));
Some(InputEvent::MouseDragged { delta, button })
} else {
self.last_cursor_pos = Some((position.x, position.y));
Some(InputEvent::MouseMoved {
position: (position.x, position.y),
})
}
}
WindowEvent::MouseInput { state, button, .. } => {
let mapped = map_button(*button);
match state {
ElementState::Pressed => {
self.dragging_button = Some(mapped);
}
ElementState::Released => {
self.dragging_button = None;
}
}
None
}
WindowEvent::MouseWheel { delta, .. } => {
let scroll = match delta {
MouseScrollDelta::LineDelta(_, y) => *y,
MouseScrollDelta::PixelDelta(p) => p.y as f32,
};
Some(InputEvent::MouseWheel { delta: scroll })
}
_ => None,
}
}
}
fn handle_keyboard_input(event: &KeyEvent, modifiers: Modifiers) -> InputEvent {
let key = map_winit_key(&event.logical_key);
let mods = KeyModifiers {
lshift: modifiers.lshift_state() == ModifiersKeyState::Pressed,
rshift: modifiers.rshift_state() == ModifiersKeyState::Pressed,
lcontrol: modifiers.lcontrol_state() == ModifiersKeyState::Pressed,
rcontrol: modifiers.rcontrol_state() == ModifiersKeyState::Pressed,
lalt: modifiers.lalt_state() == ModifiersKeyState::Pressed,
ralt: modifiers.ralt_state() == ModifiersKeyState::Pressed,
rsuper_key: modifiers.rsuper_state() == ModifiersKeyState::Pressed,
lsuper_key: modifiers.lsuper_state() == ModifiersKeyState::Pressed,
};
match event.state {
ElementState::Pressed => InputEvent::KeyPressed {
key,
modifiers: mods,
text: event.text.clone().unwrap_or_default().into(),
},
ElementState::Released => InputEvent::KeyReleased {
key,
modifiers: mods,
},
}
}
pub fn map_button(button: winit::event::MouseButton) -> MouseButton {
match button {
winit::event::MouseButton::Left => MouseButton::Left,
winit::event::MouseButton::Right => MouseButton::Right,
winit::event::MouseButton::Middle => MouseButton::Middle,
winit::event::MouseButton::Other(n) => MouseButton::Other(n),
_ => MouseButton::Other(0),
}
}
pub fn map_winit_key(key: &WinitKey) -> Key {
use Key::*;
use winit::keyboard::NamedKey;
match key {
WinitKey::Character(s) => match s.as_str() {
"a" | "A" => A,
"b" | "B" => B,
"c" | "C" => C,
"d" | "D" => D,
"e" | "E" => E,
"f" | "F" => F,
"g" | "G" => G,
"h" | "H" => H,
"i" | "I" => I,
"j" | "J" => J,
"k" | "K" => K,
"l" | "L" => L,
"m" | "M" => M,
"n" | "N" => N,
"o" | "O" => O,
"p" | "P" => P,
"q" | "Q" => Q,
"r" | "R" => R,
"s" | "S" => S,
"t" | "T" => T,
"u" | "U" => U,
"v" | "V" => V,
"w" | "W" => W,
"x" | "X" => X,
"y" | "Y" => Y,
"z" | "Z" => Z,
"0" => Num0,
"1" => Num1,
"2" => Num2,
"3" => Num3,
"4" => Num4,
"5" => Num5,
"6" => Num6,
"7" => Num7,
"8" => Num8,
"9" => Num9,
"," => Comma,
"." => Period,
"-" => Minus,
"+" => Plus,
"/" => Slash,
"\\" => Backslash,
";" => Semicolon,
"'" => Apostrophe,
"[" => LeftBracket,
"]" => RightBracket,
"`" => Grave,
"=" => Equal,
_ => Unknown,
},
WinitKey::Named(named_key) => match named_key {
NamedKey::ArrowUp => ArrowUp,
NamedKey::ArrowDown => ArrowDown,
NamedKey::ArrowLeft => ArrowLeft,
NamedKey::ArrowRight => ArrowRight,
NamedKey::Escape => Escape,
NamedKey::Enter => Enter,
NamedKey::Tab => Tab,
NamedKey::Space => Space,
NamedKey::Backspace => Backspace,
NamedKey::Insert => Insert,
NamedKey::Delete => Delete,
NamedKey::Home => Home,
NamedKey::End => End,
NamedKey::PageUp => PageUp,
NamedKey::PageDown => PageDown,
NamedKey::Shift => Shift,
NamedKey::Control => Control,
NamedKey::Alt => Alt,
NamedKey::Super => Super,
NamedKey::F1 => F1,
NamedKey::F2 => F2,
NamedKey::F3 => F3,
NamedKey::F4 => F4,
NamedKey::F5 => F5,
NamedKey::F6 => F6,
NamedKey::F7 => F7,
NamedKey::F8 => F8,
NamedKey::F9 => F9,
NamedKey::F10 => F10,
NamedKey::F11 => F11,
NamedKey::F12 => F12,
_ => Unknown,
},
_ => Unknown,
}
}

View File

@ -1,66 +0,0 @@
use wgpu::{Buffer, Device, Queue};
use wgpu::util::DeviceExt;
use std::mem::size_of;
use crate::geometry_manager::Shape;
use crate::renderer::{InstanceRaw, RenderInstance};
pub struct InstanceManager {
instances: Vec<RenderInstance>,
raw: Vec<InstanceRaw>,
buffer: Buffer,
}
impl InstanceManager {
pub fn new(device: &Device) -> Self {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Buffer (empty)"),
size: 1,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
instances: Vec::new(),
raw: Vec::new(),
buffer,
}
}
pub fn set_instances(&mut self, device: &Device, queue: &Queue, instances: Vec<RenderInstance>) {
self.raw = instances.iter().map(RenderInstance::to_raw).collect();
let byte_len = (self.raw.len() * size_of::<InstanceRaw>()) as wgpu::BufferAddress;
if byte_len > self.buffer.size() {
self.buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Instance Buffer (resized)"),
contents: bytemuck::cast_slice(&self.raw),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
} else {
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&self.raw));
}
self.instances = instances;
}
pub fn raw_instances_for_shape(&self, shape: Shape) -> Vec<&InstanceRaw> {
self.instances
.iter()
.zip(self.raw.iter())
.filter(|(inst, _)| inst.shape == shape)
.map(|(_, raw)| raw)
.collect()
}
pub fn buffer(&self) -> &Buffer {
&self.buffer
}
pub fn len(&self) -> usize {
self.instances.len()
}
pub fn is_empty(&self) -> bool {
self.instances.is_empty()
}
}

View File

@ -1,33 +0,0 @@
mod body;
mod simulator;
mod state;
mod application;
mod input;
mod camera;
mod light;
mod renderer;
mod device_manager;
mod instance_manager;
mod globals;
mod geometry_manager;
pub use body::Body;
pub use simulator::Simulator;
pub use simulator::distance_squared;
pub use application::StateApplication as Application;
pub use state::State;
pub use renderer::RenderInstance;
pub use light::Light;
pub use light::LightType;
pub use geometry_manager::Shape;
pub use input::Key;
pub use input::map_winit_key;
pub use input::InputEvent;
pub use input::MouseButton;

View File

@ -1,360 +0,0 @@
use bytemuck::{Pod, Zeroable};
use cgmath::{EuclideanSpace, Matrix4, Point3, Transform, Vector3, Zero};
use wgpu::util::DeviceExt;
#[repr(u32)]
#[derive(Clone, Copy, Debug)]
pub enum LightType {
Directional = 0,
Point = 1,
Spot = 2
}
pub const CLUSTER_COUNT_X: usize = 16;
pub const CLUSTER_COUNT_Y: usize = 9;
pub const CLUSTER_COUNT_Z: usize = 24;
pub const MAX_LIGHTS_PER_CLUSTER: usize = 32;
pub const TOTAL_CLUSTERS: usize = CLUSTER_COUNT_X * CLUSTER_COUNT_Y * CLUSTER_COUNT_Z;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable, Debug)]
pub struct GpuLight {
pub position: [f32; 3],
pub light_type: u32,
pub color: [f32; 3],
pub intensity: f32,
pub direction: [f32; 3],
pub range: f32,
pub inner_cutoff: f32,
pub outer_cutoff: f32,
}
pub struct Light {
pub light_type: LightType,
pub position: Vector3<f32>,
pub direction: Vector3<f32>,
pub color: Vector3<f32>,
pub intensity: f32,
pub range: f32,
pub inner_cutoff: f32,
pub outer_cutoff: f32,
}
impl Light {
pub fn to_gpu(&self) -> GpuLight {
GpuLight {
position: self.position.into(),
light_type: self.light_type as u32,
color: self.color.into(),
intensity: self.intensity,
direction: self.direction.into(),
range: self.range,
inner_cutoff: self.inner_cutoff,
outer_cutoff: self.outer_cutoff,
}
}
pub fn new_point(position: Vector3<f32>, color: Vector3<f32>, intensity: f32, min_attenuation: f32) -> Self {
let range = (intensity / min_attenuation).sqrt().max(1.0);
Self {
light_type: LightType::Point,
position,
direction: Vector3::zero(),
color,
intensity,
range,
inner_cutoff: 0.0,
outer_cutoff: 0.0,
}
}
}
pub struct LightManager {
pub lights: Vec<Light>,
pub buffer: wgpu::Buffer,
pub bind_group: wgpu::BindGroup,
pub count_buffer: wgpu::Buffer,
pub layout: wgpu::BindGroupLayout,
pub cluster_buffers: Option<ClusterBuffers>,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable, Debug)]
pub struct LightCount {
pub count: u32,
}
pub struct ClusterBuffers {
pub light_indices: wgpu::Buffer,
pub offsets: wgpu::Buffer,
pub bind_group: wgpu::BindGroup,
pub layout: wgpu::BindGroupLayout,
}
#[derive(Debug, Clone)]
pub struct ClusterAssignment {
pub cluster_light_indices: Vec<u32>,
pub cluster_offsets: Vec<(u32, u32)>,
}
impl LightManager {
pub fn new(device: &wgpu::Device, max_lights: usize) -> Self {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Light Buffer"),
size: (max_lights * size_of::<GpuLight>()) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let count_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Light Count Buffer"),
size: size_of::<LightCount>() as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Light Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Light Bind Group"),
layout: &layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: count_buffer.as_entire_binding(),
},
],
});
Self {
lights: Vec::new(),
buffer,
count_buffer,
bind_group,
layout,
cluster_buffers: None,
}
}
pub fn add_light(&mut self, light: Light) {
self.lights.push(light);
}
pub fn clear(&mut self) {
self.lights.clear();
}
pub fn update_gpu(&self, queue: &wgpu::Queue) {
let gpu_lights: Vec<GpuLight> = self.lights.iter().map(|l| l.to_gpu()).collect();
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&gpu_lights));
let count = LightCount {
count: self.lights.len() as u32,
};
queue.write_buffer(&self.count_buffer, 0, bytemuck::bytes_of(&count));
}
pub fn bind_group(&self) -> &wgpu::BindGroup {
&self.bind_group
}
pub fn layout(&self) -> &wgpu::BindGroupLayout {
&self.layout
}
pub fn create_cluster_buffers(
&self,
device: &wgpu::Device,
assignment: &ClusterAssignment,
) -> ClusterBuffers {
let cluster_light_indices = if assignment.cluster_light_indices.is_empty() {
vec![0u32]
} else {
assignment.cluster_light_indices.clone()
};
let offset_pairs: Vec<[u32; 2]> = if assignment.cluster_offsets.is_empty() {
vec![[0, 0]]
} else {
assignment.cluster_offsets
.iter()
.map(|&(o, c)| [o, c])
.collect()
};
let light_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Cluster Light Indices"),
contents: bytemuck::cast_slice(&cluster_light_indices),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let offset_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Cluster Offsets"),
contents: bytemuck::cast_slice(&offset_pairs),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Cluster Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Cluster Bind Group"),
layout: &layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: light_index_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: offset_buffer.as_entire_binding(),
},
],
});
ClusterBuffers {
light_indices: light_index_buffer,
offsets: offset_buffer,
layout,
bind_group,
}
}
pub fn update_cluster_buffers(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
assignment: &ClusterAssignment,
) {
let new_index_bytes = assignment.cluster_light_indices.len() * std::mem::size_of::<u32>();
let new_offset_bytes = assignment.cluster_offsets.len() * std::mem::size_of::<[u32; 2]>();
let needs_resize = self.cluster_buffers.as_ref().map(|buffers| {
let index_size_ok = new_index_bytes <= buffers.light_indices.size() as usize;
let offset_size_ok = new_offset_bytes <= buffers.offsets.size() as usize;
!(index_size_ok && offset_size_ok)
}).unwrap_or(true);
if needs_resize {
let buffers = self.create_cluster_buffers(device, assignment);
self.cluster_buffers = Some(buffers);
}
if let Some(buffers) = &self.cluster_buffers {
queue.write_buffer(&buffers.light_indices, 0, bytemuck::cast_slice(&assignment.cluster_light_indices));
let offset_pairs: Vec<[u32; 2]> = assignment
.cluster_offsets
.iter()
.map(|&(o, c)| [o, c])
.collect();
queue.write_buffer(&buffers.offsets, 0, bytemuck::cast_slice(&offset_pairs));
}
}
pub fn compute_cluster_assignments(
&self,
view_matrix: Matrix4<f32>,
_projection_matrix: Matrix4<f32>,
_screen_width: f32,
_screen_height: f32,
) -> ClusterAssignment {
let mut cluster_light_lists = vec![Vec::new(); TOTAL_CLUSTERS];
let log_near = 0.1f32.log2();
let log_far = 1000.0f32.log2();
let log_range = log_far - log_near;
for (i, light) in self.lights.iter().enumerate() {
let pos_view = view_matrix.transform_point(Point3::from_vec(light.position));
let radius = light.range;
let z_bounds = [
(-pos_view.z - radius).max(0.1).log2(),
(-pos_view.z + radius).max(0.1).log2(),
];
let z_start = ((z_bounds[0].min(z_bounds[1]) - log_near) / log_range * CLUSTER_COUNT_Z as f32).floor() as usize;
let z_end = ((z_bounds[0].max(z_bounds[1]) - log_near) / log_range * CLUSTER_COUNT_Z as f32).ceil() as usize;
for z in z_start.min(CLUSTER_COUNT_Z)..z_end.min(CLUSTER_COUNT_Z) {
for y in 0..CLUSTER_COUNT_Y {
for x in 0..CLUSTER_COUNT_X {
let cluster = x + y * CLUSTER_COUNT_X + z * CLUSTER_COUNT_X * CLUSTER_COUNT_Y;
if cluster_light_lists[cluster].len() < MAX_LIGHTS_PER_CLUSTER {
cluster_light_lists[cluster].push(i as u32);
}
}
}
}
}
let mut cluster_light_indices = Vec::new();
let mut cluster_offsets = Vec::with_capacity(TOTAL_CLUSTERS);
for lights in cluster_light_lists {
let offset = cluster_light_indices.len() as u32;
let count = lights.len() as u32;
cluster_light_indices.extend(lights);
cluster_offsets.push((offset, count));
}
ClusterAssignment {
cluster_light_indices,
cluster_offsets,
}
}
}

View File

@ -1,298 +0,0 @@
use crate::camera::Camera;
use crate::geometry_manager::{GeometryManager, Shape};
use crate::globals::GlobalsManager;
use crate::instance_manager::InstanceManager;
use crate::light::LightManager;
use wgpu::{Device, Queue, SurfaceTexture, TextureView};
pub struct RenderInstance {
pub position: cgmath::Vector3<f32>,
pub rotation: cgmath::Quaternion<f32>,
pub color: [f32; 3],
pub scale: f32,
pub shape: Shape,
pub always_lit: bool,
pub is_transparent: bool
}
impl RenderInstance {
pub fn to_raw(&self) -> InstanceRaw {
let model = cgmath::Matrix4::from_translation(self.position)
* cgmath::Matrix4::from(self.rotation)
* cgmath::Matrix4::from_scale(self.scale);
InstanceRaw {
model: model.into(),
color: self.color,
flags: (self.always_lit as u32) | ((self.is_transparent as u32) << 1)
}
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InstanceRaw {
model: [[f32; 4]; 4],
color: [f32; 3],
flags: u32,
}
impl InstanceRaw {
pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: size_of::<InstanceRaw>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
wgpu::VertexAttribute { offset: 0, shader_location: 5, format: wgpu::VertexFormat::Float32x4 },
wgpu::VertexAttribute { offset: 16, shader_location: 6, format: wgpu::VertexFormat::Float32x4 },
wgpu::VertexAttribute { offset: 32, shader_location: 7, format: wgpu::VertexFormat::Float32x4 },
wgpu::VertexAttribute { offset: 48, shader_location: 8, format: wgpu::VertexFormat::Float32x4 },
wgpu::VertexAttribute { offset: 64, shader_location: 9, format: wgpu::VertexFormat::Float32x3 },
wgpu::VertexAttribute { offset: 76, shader_location: 10, format: wgpu::VertexFormat::Uint32 },
],
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
pub(crate) position: [f32; 3],
pub(crate) color: [f32; 3],
pub(crate) normal: [f32; 3],
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 3] =
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3];
pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}
pub struct Renderer {
pipeline: wgpu::RenderPipeline,
depth_texture: TextureView,
sample_count: u32,
}
impl Renderer {
pub fn new(
device: &Device,
config: &wgpu::SurfaceConfiguration,
global_layout: &wgpu::BindGroupLayout,
light_manager: &mut LightManager,
camera: &Camera,
sample_count: u32,
) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
});
let cluster_assignment = light_manager.compute_cluster_assignments(
camera.build_view_matrix(),
camera.build_view_projection_matrix(),
config.width as f32,
config.height as f32,
);
let cluster_buffers = light_manager.create_cluster_buffers(device, &cluster_assignment);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[
global_layout,
&light_manager.layout,
&cluster_buffers.layout,
],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc(), InstanceRaw::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let depth_texture =
Self::create_depth_texture(device, config.width, config.height, sample_count);
Self {
pipeline,
depth_texture,
sample_count,
}
}
pub fn resize(&mut self, device: &Device, width: u32, height: u32) {
self.depth_texture = Self::create_depth_texture(device, width, height, self.sample_count);
}
fn create_depth_texture(
device: &Device,
width: u32,
height: u32,
sample_count: u32,
) -> TextureView {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Depth Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
texture.create_view(&Default::default())
}
pub fn render_frame(
&mut self,
device: &Device,
queue: &Queue,
output: SurfaceTexture,
view: &TextureView,
surface_format: wgpu::TextureFormat,
globals: &mut GlobalsManager,
camera: &Camera,
light_manager: &mut LightManager,
geometry: &GeometryManager,
instances: &InstanceManager,
) -> Result<(), wgpu::SurfaceError> {
// Update uniform buffer
globals.update(queue, camera);
// Update cluster buffers
let assignment = light_manager.compute_cluster_assignments(
camera.build_view_matrix(),
camera.build_view_projection_matrix(),
globals.resolution()[0],
globals.resolution()[1],
);
light_manager.update_cluster_buffers(device, queue, &assignment);
light_manager.update_gpu(queue);
let multisampled_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Multisample Target"),
size: wgpu::Extent3d {
width: globals.resolution()[0] as u32,
height: globals.resolution()[1] as u32,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: self.sample_count,
dimension: wgpu::TextureDimension::D2,
format: surface_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let multisampled_view = multisampled_texture.create_view(&Default::default());
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Main Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &multisampled_view,
resolve_target: Some(view),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.1,
b: 0.2,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
});
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &globals.bind_group, &[]);
pass.set_bind_group(1, &light_manager.bind_group, &[]);
if let Some(clusters) = &light_manager.cluster_buffers {
pass.set_bind_group(2, &clusters.bind_group, &[]);
}
for shape in geometry.shapes() {
if let Some(mesh) = geometry.get(&shape) {
let relevant = instances.raw_instances_for_shape(shape);
if relevant.is_empty() {
continue;
}
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
pass.set_vertex_buffer(1, instances.buffer().slice(..));
pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..mesh.index_count, 0, 0..relevant.len() as u32);
}
}
}
queue.submit(Some(encoder.finish()));
output.present();
Ok(())
}
}

View File

@ -1,168 +0,0 @@
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) color: vec3<f32>,
@location(2) normal: vec3<f32>,
};
const CLUSTER_COUNT_X: u32 = 16u;
const CLUSTER_COUNT_Y: u32 = 9u;
const CLUSTER_COUNT_Z: u32 = 24u;
const NEAR_PLANE: f32 = 0.1;
const FAR_PLANE: f32 = 1000.0;
struct InstanceInput {
@location(5) model_row0: vec4<f32>,
@location(6) model_row1: vec4<f32>,
@location(7) model_row2: vec4<f32>,
@location(8) model_row3: vec4<f32>,
@location(9) color: vec3<f32>,
@location(10) flags: u32,
};
struct VSOutput {
@builtin(position) position: vec4<f32>,
@location(0) frag_color: vec3<f32>,
@location(1) world_pos: vec3<f32>,
@location(2) normal: vec3<f32>,
@location(3) flags: u32,
};
struct LightCount {
count: u32,
};
@group(1) @binding(1)
var<uniform> light_count: LightCount;
struct Globals {
view_proj: mat4x4<f32>,
resolution: vec2<f32>,
}
@group(0) @binding(0)
var<uniform> globals: Globals;
struct GpuLight {
position: vec3<f32>,
light_type: u32,
color: vec3<f32>,
intensity: f32,
direction: vec3<f32>,
range: f32,
inner_cutoff: f32,
outer_cutoff: f32,
};
@group(1) @binding(0)
var<storage, read> all_lights: array<GpuLight>;
@group(2) @binding(0)
var<storage, read> cluster_light_indices: array<u32>;
@group(2) @binding(1)
var<storage, read> cluster_offsets: array<vec2<u32>>;
@vertex
fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput {
var out: VSOutput;
let model = mat4x4<f32>(
instance.model_row0,
instance.model_row1,
instance.model_row2,
instance.model_row3
);
let world_position = (model * vec4<f32>(vertex.position, 1.0)).xyz;
let normal_matrix = mat3x3<f32>(
instance.model_row0.xyz,
instance.model_row1.xyz,
instance.model_row2.xyz
);
out.position = globals.view_proj * vec4<f32>(world_position, 1.0);
out.frag_color = instance.color * vertex.color;
out.world_pos = world_position;
out.normal = normalize(normal_matrix * vertex.normal);
out.flags = instance.flags;
return out;
}
fn compute_cluster_id(frag_coord: vec4<f32>, view_pos_z: f32, screen_size: vec2<f32>) -> u32 {
let x_frac = frag_coord.x / screen_size.x;
let y_frac = frag_coord.y / screen_size.y;
let x = clamp(u32(x_frac * f32(CLUSTER_COUNT_X)), 0u, CLUSTER_COUNT_X - 1u);
let y = clamp(u32(y_frac * f32(CLUSTER_COUNT_Y)), 0u, CLUSTER_COUNT_Y - 1u);
// Z: logarithmic depth
let depth = -view_pos_z; // view-space z is negative
let depth_clamped = clamp(depth, NEAR_PLANE, FAR_PLANE);
let log_depth = log2(depth_clamped);
let z = clamp(u32((log_depth / log2(FAR_PLANE / NEAR_PLANE)) * f32(CLUSTER_COUNT_Z)), 0u, CLUSTER_COUNT_Z - 1u);
return x + y * CLUSTER_COUNT_X + z * CLUSTER_COUNT_X * CLUSTER_COUNT_Y;
}
fn is_nan_f32(x: f32) -> bool {
return x != x;
}
fn is_nan_vec3(v: vec3<f32>) -> bool {
return any(vec3<bool>(v != v));
}
@fragment
fn fs_main(input: VSOutput) -> @location(0) vec4<f32> {
var lighting: vec3<f32> = vec3<f32>(0.0);
let always_lit = (input.flags & 0x1u) != 0u;
let cluster_id = compute_cluster_id(input.position, input.world_pos.z, globals.resolution);
let offset_info = cluster_offsets[cluster_id];
let offset = offset_info.x;
let count = offset_info.y;
for (var i = 0u; i < count; i = i + 1u) {
let light_index = cluster_light_indices[offset + i];
let light = all_lights[light_index];
var light_contrib: vec3<f32> = vec3<f32>(0.0);
let light_dir = normalize(light.position - input.world_pos);
let diff = max(dot(input.normal, light_dir), 0.0);
switch (light.light_type) {
case 0u: { // Directional
light_contrib = light.color * light.intensity * diff;
}
case 1u: { // Point
let dist = distance(light.position, input.world_pos);
if (dist < light.range) {
let attenuation = 1.0 / (dist * dist);
light_contrib = light.color * light.intensity * diff * attenuation;
}
}
case 2u: { // Spot
let spot_dir = normalize(-light.direction);
let angle = dot(spot_dir, light_dir);
if (angle > light.outer_cutoff) {
let intensity = clamp((angle - light.outer_cutoff) / (light.inner_cutoff - light.outer_cutoff), 0.0, 1.0);
let dist = distance(light.position, input.world_pos);
let attenuation = 1.0 / (dist * dist);
light_contrib = light.color * light.intensity * diff * attenuation * intensity;
}
}
default: {}
}
if (!always_lit) {
lighting += light_contrib;
}
}
if (always_lit) {
lighting = vec3<f32>(1.0, 1.0, 1.0) * 2.0;
}
return vec4<f32>(input.frag_color * lighting, 1.0);
}

View File

@ -1,174 +0,0 @@
use std::collections::HashMap;
use std::sync::Mutex;
use cgmath::{InnerSpace, Vector3};
use crate::body::Body;
use rayon::prelude::*;
const G: f64 = 6.67430e-11;
pub struct Simulator {
pub bodies: Vec<Body>,
pub time: f64,
timewarp: u32
}
pub fn distance_squared(a: Vector3<f64>, b: Vector3<f64>) -> f64 {
let d = a - b;
d.magnitude2()
}
const MAX_TIMEWARP: u32 = 536870912;
impl Simulator {
pub fn new() -> Self {
Self {
bodies: Vec::new(),
time: 0.0,
timewarp: 1,
}
}
pub fn add_body(&mut self, body: Body) {
self.bodies.push(body);
}
pub fn step(&mut self, dt: f64) {
let n = self.bodies.len();
#[derive(Clone)]
struct State {
position: Vector3<f64>,
velocity: Vector3<f64>,
}
let original_states: Vec<State> = self
.bodies
.iter()
.map(|b| State {
position: b.position,
velocity: b.velocity,
})
.collect();
let masses: Vec<f64> = self.bodies.iter().map(|b| b.mass).collect();
fn compute_accelerations(states: &[State], masses: &[f64], ownership: &HashMap<usize, usize>) -> Vec<Vector3<f64>> {
let mut accels = vec![Vector3::new(0.0, 0.0, 0.0); states.len()];
for (&i, &j) in ownership {
let r = states[j].position - states[i].position;
let dist_sq = r.magnitude2();
let dist = dist_sq.sqrt();
if dist < 1e-8 {
continue;
}
let force = G * masses[i] * masses[j] / dist_sq;
let accel = force * r / (dist * masses[i]);
accels[i] += accel;
}
accels
}
let ownership = self.compute_soi_owners();
let k1_pos = original_states.iter().map(|s| s.velocity).collect::<Vec<_>>();
let k1_vel = compute_accelerations(&original_states, &masses, &ownership);
let mut temp_states: Vec<State> = original_states.iter().enumerate().map(|(i, s)| {
State {
position: s.position + k1_pos[i] * (dt / 2.0),
velocity: s.velocity + k1_vel[i] * (dt / 2.0),
}
}).collect();
let k2_pos = temp_states.iter().map(|s| s.velocity).collect::<Vec<_>>();
let k2_vel = compute_accelerations(&temp_states, &masses, &ownership);
for i in 0..n {
temp_states[i].position = original_states[i].position + k2_pos[i] * (dt / 2.0);
temp_states[i].velocity = original_states[i].velocity + k2_vel[i] * (dt / 2.0);
}
let k3_pos = temp_states.iter().map(|s| s.velocity).collect::<Vec<_>>();
let k3_vel = compute_accelerations(&temp_states, &masses, &ownership);
for i in 0..n {
temp_states[i].position = original_states[i].position + k3_pos[i] * dt;
temp_states[i].velocity = original_states[i].velocity + k3_vel[i] * dt;
}
let k4_pos = temp_states.iter().map(|s| s.velocity).collect::<Vec<_>>();
let k4_vel = compute_accelerations(&temp_states, &masses, &ownership);
for i in 0..n {
let body = &mut self.bodies[i];
body.position += (k1_pos[i] + 2.0 * k2_pos[i] + 2.0 * k3_pos[i] + k4_pos[i]) * (dt / 6.0);
body.velocity += (k1_vel[i] + 2.0 * k2_vel[i] + 2.0 * k3_vel[i] + k4_vel[i]) * (dt / 6.0);
}
self.time += dt;
}
fn compute_soi_owners(&self) -> HashMap<usize, usize> {
let mut ownership = HashMap::new();
for (i, body) in self.bodies.iter().enumerate() {
let mut min_distance = f64::MAX;
let mut dominant_index = None;
for (j, other) in self.bodies.iter().enumerate() {
if i == j {
continue;
}
let r = (body.position - other.position).magnitude();
let soi_radius = r * (body.mass / other.mass).powf(2.0 / 5.0);
if r < soi_radius && r < min_distance {
min_distance = r;
dominant_index = Some(j);
}
}
if let Some(j) = dominant_index {
ownership.insert(i, j);
}
}
ownership
}
pub fn increase_timewarp(&mut self) {
if let Some(new) = self.timewarp.checked_mul(2) {
if new <= MAX_TIMEWARP {
self.timewarp = new;
} else {
println!("Timewarp is already at maximum ({}).", MAX_TIMEWARP);
}
} else {
println!("Timewarp multiplication would overflow.");
}
}
pub fn decrease_timewarp(&mut self) {
if let Some(new) = self.timewarp.checked_div(2) {
if new >= 1 {
self.timewarp = new;
} else {
println!("Timewarp is already at minimum.");
}
} else {
println!("Timewarp is already at minimum.");
}
}
pub fn reset_timewarp(&mut self) {
self.timewarp = 1;
}
pub fn get_timewarp(&self) -> u32 {
self.timewarp
}
}

View File

@ -1,215 +0,0 @@
use std::sync::{Arc};
use log::info;
use pollster::FutureExt;
use wgpu::{Adapter, Device, Instance, PresentMode, Queue, Surface, SurfaceCapabilities, SurfaceConfiguration, SurfaceError};
use winit::dpi::PhysicalSize;
use winit::window::{Window};
use crate::camera::Camera;
use crate::geometry_manager::GeometryManager;
use crate::globals::GlobalsManager;
use crate::instance_manager::InstanceManager;
use crate::light::{LightManager};
use crate::renderer::{RenderInstance, Renderer};
pub struct SampleCount(pub u32);
impl SampleCount {
pub fn get(&self) -> u32 {
self.0
}
}
pub struct State<'a> {
surface: Surface<'a>,
device: Device,
queue: Queue,
config: SurfaceConfiguration,
sample_count: SampleCount,
size: PhysicalSize<u32>,
window: Arc<Window>,
pub camera: Camera,
pub globals: GlobalsManager,
pub geometry_manager: GeometryManager,
pub instance_manager: InstanceManager,
pub light_manager: LightManager,
pub renderer: Renderer,
}
impl<'a> State<'a> {
pub(crate) fn new(window: Window) -> Self {
let window = Arc::new(window);
let size = window.inner_size();
let instance = Self::create_gpu_instance();
let surface = instance.create_surface(window.clone()).unwrap();
let adapter = Self::create_adapter(instance, &surface);
let (device, queue) = Self::create_device(&adapter);
let capabilities = surface.get_capabilities(&adapter);
let config = Self::create_surface_config(size, capabilities);
surface.configure(&device, &config);
let sample_count = SampleCount(Self::probe_msaa_support(&device, &config));
info!("MSAA sample count: {}", sample_count.get());
let camera = Camera::new(config.width as f32 / config.height as f32);
let globals = GlobalsManager::new(&device, config.width, config.height, &camera);
let geometry_manager = GeometryManager::new(&device);
let instance_manager = InstanceManager::new(&device);
let mut light_manager = LightManager::new(&device, 100);
let renderer = Renderer::new(
&device,
&config,
globals.layout(),
&mut light_manager,
&camera,
sample_count.get(),
);
Self {
surface,
device,
queue,
config,
sample_count,
size,
window,
camera,
globals,
geometry_manager,
instance_manager,
light_manager,
renderer,
}
}
fn probe_msaa_support(device: &Device, config: &SurfaceConfiguration) -> u32 {
pollster::block_on(async {
for &count in &[16, 8, 4, 2] {
device.push_error_scope(wgpu::ErrorFilter::Validation);
let _ = device.create_texture(&wgpu::TextureDescriptor {
label: Some("MSAA Probe"),
size: wgpu::Extent3d {
width: 4,
height: 4,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: count,
dimension: wgpu::TextureDimension::D2,
format: config.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
if device.pop_error_scope().await.is_none() {
return count;
}
}
1 // fallback
})
}
fn create_surface_config(size: PhysicalSize<u32>, capabilities: SurfaceCapabilities) -> wgpu::SurfaceConfiguration {
let surface_format = capabilities.formats.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(capabilities.formats[0]);
SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: PresentMode::AutoVsync,
alpha_mode: capabilities.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
}
}
fn create_device(adapter: &Adapter) -> (Device, Queue) {
adapter.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
required_limits: wgpu::Limits::default(),
memory_hints: Default::default(),
label: None,
trace: Default::default(),
}).block_on().unwrap()
}
fn create_adapter(instance: Instance, surface: &Surface) -> Adapter {
instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}
).block_on().unwrap()
}
fn create_gpu_instance() -> Instance {
Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
})
}
pub(crate) fn resize(&mut self, new_size: PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
self.camera.set_aspect(new_size.width as f32 / new_size.height as f32);
self.globals.resize(new_size.width, new_size.height);
self.renderer.resize(&self.device, new_size.width, new_size.height);
}
}
pub fn render(&mut self) -> Result<(), SurfaceError> {
let output = self.surface.get_current_texture()?;
let view = output.texture.create_view(&Default::default());
self.renderer.render_frame(
&self.device,
&self.queue,
output,
&view,
self.config.format,
&mut self.globals,
&self.camera,
&mut self.light_manager,
&self.geometry_manager,
&self.instance_manager,
)
}
pub fn set_instances(&mut self, instances: Vec<RenderInstance>) {
self.instance_manager.set_instances(&self.device, &self.queue, instances);
}
pub fn window(&self) -> &Window {
&self.window
}
pub fn camera_mut(&mut self) -> &mut crate::camera::Camera {
&mut self.camera
}
pub fn camera(&self) -> &crate::camera::Camera {
&self.camera
}
}

189
src/camera.rs Normal file
View File

@ -0,0 +1,189 @@
use cgmath::*;
use std::f32::consts::FRAC_PI_2;
use std::time::Duration;
use winit::dpi::PhysicalPosition;
use winit::event::*;
use winit::keyboard::KeyCode;
const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
#[derive(Debug)]
pub struct Camera {
pub position: Point3<f32>,
yaw: Rad<f32>,
pitch: Rad<f32>,
}
impl Camera {
pub fn new<V: Into<Point3<f32>>, Y: Into<Rad<f32>>, P: Into<Rad<f32>>>(
position: V,
yaw: Y,
pitch: P,
) -> Self {
Self {
position: position.into(),
yaw: yaw.into(),
pitch: pitch.into(),
}
}
pub fn calc_matrix(&self) -> Matrix4<f32> {
let (sin_pitch, cos_pitch) = self.pitch.0.sin_cos();
let (sin_yaw, cos_yaw) = self.yaw.0.sin_cos();
Matrix4::look_to_rh(
self.position,
Vector3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(),
Vector3::unit_y(),
)
}
}
pub struct Projection {
aspect: f32,
fovy: Rad<f32>,
znear: f32,
zfar: f32,
}
impl Projection {
pub fn new<F: Into<Rad<f32>>>(width: u32, height: u32, fovy: F, znear: f32, zfar: f32) -> Self {
Self {
aspect: width as f32 / height as f32,
fovy: fovy.into(),
znear,
zfar,
}
}
pub fn resize(&mut self, width: u32, height: u32) {
self.aspect = width as f32 / height as f32;
}
pub fn calc_matrix(&self) -> Matrix4<f32> {
// UDPATE
perspective(self.fovy, self.aspect, self.znear, self.zfar)
}
}
#[derive(Debug)]
pub struct CameraController {
amount_left: f32,
amount_right: f32,
amount_forward: f32,
amount_backward: f32,
amount_up: f32,
amount_down: f32,
rotate_horizontal: f32,
rotate_vertical: f32,
scroll: f32,
speed: f32,
sensitivity: f32,
}
impl CameraController {
pub fn new(speed: f32, sensitivity: f32) -> Self {
Self {
amount_left: 0.0,
amount_right: 0.0,
amount_forward: 0.0,
amount_backward: 0.0,
amount_up: 0.0,
amount_down: 0.0,
rotate_horizontal: 0.0,
rotate_vertical: 0.0,
scroll: 0.0,
speed,
sensitivity,
}
}
pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed {
1.0
} else {
0.0
};
match key {
KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount;
true
}
KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount;
true
}
KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount;
true
}
KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount;
true
}
KeyCode::ShiftLeft => {
self.amount_up = amount;
true
}
KeyCode::ControlLeft => {
self.amount_down = amount;
true
}
_ => false,
}
}
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
self.rotate_horizontal += mouse_dx as f32 * self.sensitivity;
self.rotate_vertical += mouse_dy as f32 * self.sensitivity;
}
pub fn process_scroll(&mut self, delta: &MouseScrollDelta) {
self.scroll = match delta {
MouseScrollDelta::LineDelta(_, scroll) => scroll * 4.0 * self.speed,
MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll as f32 * self.speed,
};
}
pub fn update_camera(&mut self, camera: &mut Camera, dt: Duration) {
let dt = dt.as_secs_f32();
// Move forward/backward and left/right
let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize();
camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt;
camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt;
// Move in/out (aka. "zoom")
// Note: this isn't an actual zoom. The camera's position
// changes when zooming. I've added this to make it easier
// to get closer to an object you want to focus on.
let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos();
let scrollward =
Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
self.scroll = 0.0;
// Move up/down. Since we don't use roll, we can just
// modify the y coordinate directly.
camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt;
// Rotate
camera.yaw += Rad(self.rotate_horizontal) * self.sensitivity * dt;
camera.pitch += Rad(-self.rotate_vertical) * self.sensitivity * dt;
// If process_mouse isn't called every frame, these values
// will not get set to zero, and the camera will rotate
// when moving in a non cardinal direction.
self.rotate_horizontal = 0.0;
self.rotate_vertical = 0.0;
// Keep the camera's angle from going too high/low.
if camera.pitch < -Rad(SAFE_FRAC_PI_2) {
camera.pitch = -Rad(SAFE_FRAC_PI_2);
} else if camera.pitch > Rad(SAFE_FRAC_PI_2) {
camera.pitch = Rad(SAFE_FRAC_PI_2);
}
}
}

99
src/debug.rs Normal file
View File

@ -0,0 +1,99 @@
use std::mem::size_of;
use wgpu::util::{BufferInitDescriptor, DeviceExt};
use crate::create_render_pipeline;
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PositionColor {
position: [f32; 3],
color: [f32; 3],
}
const AXIS_COLORS: &'static [PositionColor] = &[
// X
PositionColor {
position: [0.0, 0.0, 0.0],
color: [0.5, 0.0, 0.0],
},
PositionColor {
position: [1.0, 0.0, 0.0],
color: [1.0, 0.0, 0.0],
},
// Y
PositionColor {
position: [0.0, 0.0, 0.0],
color: [0.0, 0.5, 0.0],
},
PositionColor {
position: [0.0, 1.0, 0.0],
color: [0.0, 1.0, 0.0],
},
// Z
PositionColor {
position: [0.0, 0.0, 0.0],
color: [0.0, 0.0, 0.5],
},
PositionColor {
position: [0.0, 0.0, 1.0],
color: [0.0, 0.0, 1.0],
},
];
const POSITION_COLOR_LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: size_of::<PositionColor>() as _,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![
0 => Float32x3,
1 => Float32x3,
],
};
pub struct Debug {
color_lines: wgpu::RenderPipeline,
axis: wgpu::Buffer,
}
impl Debug {
pub fn new(
device: &wgpu::Device,
camera_layout: &wgpu::BindGroupLayout,
color_format: wgpu::TextureFormat,
) -> Self {
let axis = device.create_buffer_init(&BufferInitDescriptor {
label: Some("Debug::axis"),
contents: bytemuck::cast_slice(AXIS_COLORS),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
});
let shader = wgpu::include_wgsl!("debug.wgsl");
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[camera_layout],
push_constant_ranges: &[],
});
let color_lines = create_render_pipeline(
device,
&layout,
color_format,
None,
&[POSITION_COLOR_LAYOUT],
wgpu::PrimitiveTopology::LineList,
shader,
);
Self { color_lines, axis }
}
pub fn draw_axis<'a: 'b, 'b>(
&'a self,
pass: &'b mut wgpu::RenderPass<'a>,
camera: &'a wgpu::BindGroup,
) {
pass.set_pipeline(&self.color_lines);
pass.set_bind_group(0, camera, &[]);
pass.set_vertex_buffer(0, self.axis.slice(..));
pass.draw(0..AXIS_COLORS.len() as u32, 0..1);
}
}

87
src/equirectangular.wgsl Normal file
View File

@ -0,0 +1,87 @@
const PI: f32 = 3.1415926535897932384626433832795;
struct Face {
forward: vec3<f32>,
up: vec3<f32>,
right: vec3<f32>,
}
@group(0)
@binding(0)
var src: texture_2d<f32>;
@group(0)
@binding(1)
var dst: texture_storage_2d_array<rgba32float, write>;
@compute
@workgroup_size(16, 16, 1)
fn compute_equirect_to_cubemap(
@builtin(global_invocation_id)
gid: vec3<u32>,
) {
// If texture size is not divisible by 32 we
// need to make sure we don't try to write to
// pixels that don't exist.
if gid.x >= u32(textureDimensions(dst).x) {
return;
}
var FACES: array<Face, 6> = array(
// FACES +X
Face(
vec3(1.0, 0.0, 0.0), // forward
vec3(0.0, 1.0, 0.0), // up
vec3(0.0, 0.0, -1.0), // right
),
// FACES -X
Face (
vec3(-1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0),
),
// FACES +Y
Face (
vec3(0.0, -1.0, 0.0),
vec3(0.0, 0.0, 1.0),
vec3(1.0, 0.0, 0.0),
),
// FACES -Y
Face (
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, -1.0),
vec3(1.0, 0.0, 0.0),
),
// FACES +Z
Face (
vec3(0.0, 0.0, 1.0),
vec3(0.0, 1.0, 0.0),
vec3(1.0, 0.0, 0.0),
),
// FACES -Z
Face (
vec3(0.0, 0.0, -1.0),
vec3(0.0, 1.0, 0.0),
vec3(-1.0, 0.0, 0.0),
),
);
// Get texture coords relative to cubemap face
let dst_dimensions = vec2<f32>(textureDimensions(dst));
let cube_uv = vec2<f32>(gid.xy) / dst_dimensions * 2.0 - 1.0;
// Get spherical coordinate from cube_uv
let face = FACES[gid.z];
let spherical = normalize(face.forward + face.right * cube_uv.x + face.up * cube_uv.y);
// Get coordinate on the equirectangular texture
let inv_atan = vec2(0.1591, 0.3183);
let eq_uv = vec2(atan2(spherical.z, spherical.x), asin(spherical.y)) * inv_atan + 0.5;
let eq_pixel = vec2<i32>(eq_uv * vec2<f32>(textureDimensions(src)));
// We use textureLoad() as textureSample() is not allowed in compute shaders
var sample = textureLoad(src, eq_pixel, 0);
textureStore(dst, gid.xy, gid.z, sample);
}

BIN
src/happy-tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

160
src/hdr.rs Normal file
View File

@ -0,0 +1,160 @@
use wgpu::Operations;
use crate::{create_render_pipeline, texture};
/// Owns the render texture and controls tonemapping
pub struct HdrPipeline {
pipeline: wgpu::RenderPipeline,
bind_group: wgpu::BindGroup,
texture: texture::Texture,
width: u32,
height: u32,
format: wgpu::TextureFormat,
layout: wgpu::BindGroupLayout,
}
impl HdrPipeline {
pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
let width = config.width;
let height = config.height;
// We could use `Rgba32Float`, but that requires some extra
// features to be enabled.
let format = wgpu::TextureFormat::Rgba16Float;
let texture = texture::Texture::create_2d_texture(
device,
width,
height,
format,
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
wgpu::FilterMode::Nearest,
Some("Hdr::texture"),
);
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Hdr::layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
// The Rgba16Float format cannot be filtered
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Hdr::bind_group"),
layout: &layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&texture.sampler),
},
],
});
let shader = wgpu::include_wgsl!("hdr.wgsl");
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&layout],
push_constant_ranges: &[],
});
let pipeline = create_render_pipeline(
device,
&pipeline_layout,
config.format.add_srgb_suffix(),
None,
&[],
wgpu::PrimitiveTopology::TriangleList,
shader,
);
Self {
pipeline,
bind_group,
layout,
texture,
width,
height,
format,
}
}
/// Resize the HDR texture
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
self.texture = texture::Texture::create_2d_texture(
device,
width,
height,
wgpu::TextureFormat::Rgba16Float,
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
wgpu::FilterMode::Nearest,
Some("Hdr::texture"),
);
self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Hdr::bind_group"),
layout: &self.layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.texture.sampler),
},
],
});
self.width = width;
self.height = height;
}
/// Exposes the HDR texture
pub fn view(&self) -> &wgpu::TextureView {
&self.texture.view
}
/// The format of the HDR texture
pub fn format(&self) -> wgpu::TextureFormat {
self.format
}
/// This renders the internal HDR texture to the [TextureView]
/// supplied as parameter.
pub fn process(&self, encoder: &mut wgpu::CommandEncoder, output: &wgpu::TextureView) {
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Hdr::process"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &output,
resolve_target: None,
ops: Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.bind_group, &[]);
pass.draw(0..3, 0..1);
}
}

55
src/hdr.wgsl Normal file
View File

@ -0,0 +1,55 @@
// Maps HDR values to linear values
// Based on http://www.oscars.org/science-technology/sci-tech-projects/aces
fn aces_tone_map(hdr: vec3<f32>) -> vec3<f32> {
let m1 = mat3x3(
0.59719, 0.07600, 0.02840,
0.35458, 0.90834, 0.13383,
0.04823, 0.01566, 0.83777,
);
let m2 = mat3x3(
1.60475, -0.10208, -0.00327,
-0.53108, 1.10813, -0.07276,
-0.07367, -0.00605, 1.07602,
);
let v = m1 * hdr;
let a = v * (v + 0.0245786) - 0.000090537;
let b = v * (0.983729 * v + 0.4329510) + 0.238081;
return clamp(m2 * (a / b), vec3(0.0), vec3(1.0));
}
struct VertexOutput {
@location(0) uv: vec2<f32>,
@builtin(position) clip_position: vec4<f32>,
};
@vertex
fn vs_main(
@builtin(vertex_index) vi: u32,
) -> VertexOutput {
var out: VertexOutput;
// Generate a triangle that covers the whole screen
out.uv = vec2<f32>(
f32((vi << 1u) & 2u),
f32(vi & 2u),
);
out.clip_position = vec4<f32>(out.uv * 2.0 - 1.0, 0.0, 1.0);
// We need to invert the y coordinate so the image
// is not upside down
out.uv.y = 1.0 - out.uv.y;
return out;
}
@group(0)
@binding(0)
var hdr_image: texture_2d<f32>;
@group(0)
@binding(1)
var hdr_sampler: sampler;
@fragment
fn fs_main(vs: VertexOutput) -> @location(0) vec4<f32> {
let hdr = textureSample(hdr_image, hdr_sampler, vs.uv);
let sdr = aces_tone_map(hdr.rgb);
return vec4(sdr, hdr.a);
}

947
src/lib.rs Normal file
View File

@ -0,0 +1,947 @@
use std::{f32::consts::PI, iter};
use std::sync::Arc;
use cgmath::prelude::*;
use wgpu::util::DeviceExt;
use winit::{
event::*,
event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window,
};
use winit::application::ApplicationHandler;
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::event_loop::ActiveEventLoop;
use winit::window::WindowId;
mod camera;
mod hdr;
mod model;
mod resources;
mod texture;
#[cfg(feature = "debug")]
mod debug;
use model::{DrawLight, DrawModel, Vertex};
const NUM_INSTANCES_PER_ROW: u32 = 10;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CameraUniform {
view_position: [f32; 4],
view: [[f32; 4]; 4],
view_proj: [[f32; 4]; 4],
inv_proj: [[f32; 4]; 4],
inv_view: [[f32; 4]; 4],
}
impl CameraUniform {
fn new() -> Self {
Self {
view_position: [0.0; 4],
view: cgmath::Matrix4::identity().into(), // NEW!
view_proj: cgmath::Matrix4::identity().into(),
inv_proj: cgmath::Matrix4::identity().into(), // NEW!
inv_view: cgmath::Matrix4::identity().into(), // NEW!
}
}
fn update_view_proj(&mut self, camera: &camera::Camera, projection: &camera::Projection) {
self.view_position = camera.position.to_homogeneous().into();
let proj = projection.calc_matrix();
let view = camera.calc_matrix();
let view_proj = proj * view;
self.view = view.into();
self.view_proj = view_proj.into();
self.inv_proj = proj.invert().unwrap().into();
self.inv_view = view.transpose().into();
}
}
struct Instance {
position: cgmath::Vector3<f32>,
rotation: cgmath::Quaternion<f32>,
}
impl Instance {
fn to_raw(&self) -> InstanceRaw {
InstanceRaw {
model: (cgmath::Matrix4::from_translation(self.position)
* cgmath::Matrix4::from(self.rotation))
.into(),
normal: cgmath::Matrix3::from(self.rotation).into(),
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(dead_code)]
struct InstanceRaw {
model: [[f32; 4]; 4],
normal: [[f32; 3]; 3],
}
impl model::Vertex for InstanceRaw {
fn desc() -> wgpu::VertexBufferLayout<'static> {
use std::mem;
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<InstanceRaw>() as wgpu::BufferAddress,
// We need to switch from using a step mode of Vertex to Instance
// This means that our shaders will only change to use the next
// instance when the shader starts processing a new instance
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
// While our vertex shader only uses locations 0, and 1 now, in later tutorials we'll
// be using 2, 3, and 4, for Vertex. We'll start at slot 5 not conflict with them later
shader_location: 5,
format: wgpu::VertexFormat::Float32x4,
},
// A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot
// for each vec4. We don't have to do this in code though.
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 6,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 7,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
shader_location: 8,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
shader_location: 9,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 19]>() as wgpu::BufferAddress,
shader_location: 10,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 22]>() as wgpu::BufferAddress,
shader_location: 11,
format: wgpu::VertexFormat::Float32x3,
},
],
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct LightUniform {
position: [f32; 3],
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
_padding: u32,
color: [f32; 3],
_padding2: u32,
}
struct StateApplication<'a> {
state: Option<State<'a>>,
last_render_time: instant::Instant,
}
impl<'a> StateApplication<'a> {
pub fn new() -> Self {
Self {
state: None,
last_render_time: instant::Instant::now(),
}
}
}
impl<'a> ApplicationHandler for StateApplication<'a>{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window = event_loop
.create_window(Window::default_attributes().with_title(env!("CARGO_PKG_NAME")))
.unwrap();
self.state = Some(pollster::block_on(State::new(window)));
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
let window = self.state.as_ref().unwrap().window();
if window.id() == window_id {
if !self.state.as_mut().unwrap().input(&event) {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(physical_size) => {
self.state.as_mut().unwrap().resize(physical_size);
}
WindowEvent::RedrawRequested => {
let now = instant::Instant::now();
let dt = now - self.last_render_time;
self.last_render_time = now;
self.state.as_mut().unwrap().update(dt);
self.state.as_mut().unwrap().render().unwrap();
}
_ => {},
}
}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
let window = self.state.as_ref().unwrap().window();
window.request_redraw();
}
}
struct State<'a> {
window: Arc<Window>,
size: PhysicalSize<u32>,
surface: wgpu::Surface<'a>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
render_pipeline: wgpu::RenderPipeline,
obj_model: model::Model,
camera: camera::Camera,
projection: camera::Projection,
camera_controller: camera::CameraController,
camera_uniform: CameraUniform,
camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup,
last_mouse_position: PhysicalPosition<f64>,
instances: Vec<Instance>,
#[allow(dead_code)]
instance_buffer: wgpu::Buffer,
depth_texture: texture::Texture,
light_uniform: LightUniform,
light_buffer: wgpu::Buffer,
light_bind_group: wgpu::BindGroup,
light_render_pipeline: wgpu::RenderPipeline,
#[allow(dead_code)]
debug_material: model::Material,
mouse_pressed: bool,
hdr: hdr::HdrPipeline,
environment_bind_group: wgpu::BindGroup,
sky_pipeline: wgpu::RenderPipeline,
#[cfg(feature = "debug")]
debug: debug::Debug,
}
fn create_render_pipeline(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
color_format: wgpu::TextureFormat,
depth_format: Option<wgpu::TextureFormat>,
vertex_layouts: &[wgpu::VertexBufferLayout],
topology: wgpu::PrimitiveTopology, // NEW!
shader: wgpu::ShaderModuleDescriptor,
) -> wgpu::RenderPipeline {
let shader = device.create_shader_module(shader);
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(&format!("{:?}", shader)),
layout: Some(layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: vertex_layouts,
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology, // NEW!
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
// Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
polygon_mode: wgpu::PolygonMode::Fill,
// Requires Features::DEPTH_CLIP_CONTROL
unclipped_depth: false,
// Requires Features::CONSERVATIVE_RASTERIZATION
conservative: false,
},
depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
format,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::LessEqual, // UDPATED!
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
// If the pipeline will be used with a multiview render pass, this
// indicates how many array layers the attachments will have.
multiview: None,
cache: None,
})
}
impl<'a> State<'a> {
async fn new(window: Window) -> Self {
let window_arc = Arc::new(window);
let size = window_arc.inner_size();
// The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
});
let surface = instance.create_surface(window_arc.clone()).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
// UPDATED!
required_features: wgpu::Features::empty(),
// UPDATED!
required_limits: wgpu::Limits {
// Increase from 2048 to 8192
max_texture_dimension_2d: 8192,
..wgpu::Limits::default()
},
memory_hints: Default::default(),
},
None, // Trace path
)
.await
.unwrap();
let surface_caps = surface.get_capabilities(&adapter);
// Shader code in this tutorial assumes an Srgb surface texture. Using a different
// one will result all the colors comming out darker. If you want to support non
// Srgb surfaces, you'll need to account for that when drawing to the frame.
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
// NEW!
view_formats: vec![surface_format.add_srgb_suffix()],
desired_maximum_frame_latency: 2,
};
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
// normal map
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let camera = camera::Camera::new((0.0, 5.0, 10.0), cgmath::Deg(-90.0), cgmath::Deg(-20.0));
let projection =
camera::Projection::new(config.width, config.height, cgmath::Deg(45.0), 0.1, 100.0);
let camera_controller = camera::CameraController::new(4.0, 0.4);
let mut camera_uniform = CameraUniform::new();
camera_uniform.update_view_proj(&camera, &projection);
let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Camera Buffer"),
contents: bytemuck::cast_slice(&[camera_uniform]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
const SPACE_BETWEEN: f32 = 3.0;
let instances = (0..NUM_INSTANCES_PER_ROW)
.flat_map(|z| {
(0..NUM_INSTANCES_PER_ROW).map(move |x| {
let x = SPACE_BETWEEN * (x as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
let z = SPACE_BETWEEN * (z as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
let position = cgmath::Vector3 { x, y: 0.0, z };
let rotation = if position.is_zero() {
cgmath::Quaternion::from_axis_angle(
cgmath::Vector3::unit_z(),
cgmath::Deg(0.0),
)
} else {
cgmath::Quaternion::from_axis_angle(position.normalize(), cgmath::Deg(45.0))
};
Instance { position, rotation }
})
})
.collect::<Vec<_>>();
let instance_data = instances.iter().map(Instance::to_raw).collect::<Vec<_>>();
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Instance Buffer"),
contents: bytemuck::cast_slice(&instance_data),
usage: wgpu::BufferUsages::VERTEX,
});
let camera_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("camera_bind_group_layout"),
});
let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &camera_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: camera_buffer.as_entire_binding(),
}],
label: Some("camera_bind_group"),
});
let obj_model =
resources::load_model("cube.obj", &device, &queue, &texture_bind_group_layout)
.await
.unwrap();
let light_uniform = LightUniform {
position: [2.0, 2.0, 2.0],
_padding: 0,
color: [1.0, 1.0, 1.0],
_padding2: 0,
};
let light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Light VB"),
contents: bytemuck::cast_slice(&[light_uniform]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let light_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: None,
});
let light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &light_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: light_buffer.as_entire_binding(),
}],
label: None,
});
let depth_texture =
texture::Texture::create_depth_texture(&device, &config, "depth_texture");
let hdr = hdr::HdrPipeline::new(&device, &config);
let hdr_loader = resources::HdrLoader::new(&device);
let sky_bytes = resources::load_binary("skybox.hdr").await.unwrap();
let sky_texture = hdr_loader.from_equirectangular_bytes(
&device,
&queue,
&sky_bytes,
1080,
Some("Sky Texture"),
).unwrap();
let environment_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("environment_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
],
});
let environment_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("environment_bind_group"),
layout: &environment_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&sky_texture.view()),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sky_texture.sampler()),
},
],
});
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[
&texture_bind_group_layout,
&camera_bind_group_layout,
&light_bind_group_layout,
&environment_layout, // UPDATED!
],
push_constant_ranges: &[],
});
let render_pipeline = {
let shader = wgpu::ShaderModuleDescriptor {
label: Some("Normal Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
};
create_render_pipeline(
&device,
&render_pipeline_layout,
hdr.format(),
Some(texture::Texture::DEPTH_FORMAT),
&[model::ModelVertex::desc(), InstanceRaw::desc()],
wgpu::PrimitiveTopology::TriangleList,
shader,
)
};
let light_render_pipeline = {
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Light Pipeline Layout"),
bind_group_layouts: &[&camera_bind_group_layout, &light_bind_group_layout],
push_constant_ranges: &[],
});
let shader = wgpu::ShaderModuleDescriptor {
label: Some("Light Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("light.wgsl").into()),
};
create_render_pipeline(
&device,
&layout,
hdr.format(),
Some(texture::Texture::DEPTH_FORMAT),
&[model::ModelVertex::desc()],
wgpu::PrimitiveTopology::TriangleList,
shader,
)
};
// NEW!
let sky_pipeline = {
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sky Pipeline Layout"),
bind_group_layouts: &[&camera_bind_group_layout, &environment_layout],
push_constant_ranges: &[],
});
let shader = wgpu::include_wgsl!("sky.wgsl");
create_render_pipeline(
&device,
&layout,
hdr.format(),
Some(texture::Texture::DEPTH_FORMAT),
&[],
wgpu::PrimitiveTopology::TriangleList,
shader,
)
};
let debug_material = {
let diffuse_bytes = include_bytes!("../res/cobble-diffuse.png");
let normal_bytes = include_bytes!("../res/cobble-normal.png");
let diffuse_texture = texture::Texture::from_bytes(
&device,
&queue,
diffuse_bytes,
"res/alt-diffuse.png",
false,
)
.unwrap();
let normal_texture = texture::Texture::from_bytes(
&device,
&queue,
normal_bytes,
"res/alt-normal.png",
true,
)
.unwrap();
model::Material::new(
&device,
"alt-material",
diffuse_texture,
normal_texture,
&texture_bind_group_layout,
)
};
#[cfg(feature = "debug")]
let debug = debug::Debug::new(&device, &camera_bind_group_layout, surface_format);
Self {
window: window_arc,
size,
surface,
device,
queue,
config,
render_pipeline,
obj_model,
camera,
projection,
camera_controller,
camera_buffer,
camera_bind_group,
last_mouse_position: PhysicalPosition::new(0.0, 0.0),
camera_uniform,
instances,
instance_buffer,
depth_texture,
light_uniform,
light_buffer,
light_bind_group,
light_render_pipeline,
#[allow(dead_code)]
debug_material,
mouse_pressed: false,
hdr,
environment_bind_group,
sky_pipeline,
#[cfg(feature = "debug")]
debug,
}
}
pub fn window(&self) -> &Window {
&self.window
}
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
println!("Resizing to {:?}", new_size);
// Begrenzen der Dimensionen auf GPU-Limits
let max_texture_dimension = self.device.limits().max_texture_dimension_2d as u32;
let width = new_size.width.min(max_texture_dimension);
let height = new_size.height.min(max_texture_dimension);
self.projection.resize(width, height);
self.hdr.resize(&self.device, width, height);
self.size = PhysicalSize { width, height }; // Aktualisieren auf die begrenzten Dimensionen
self.config.width = width;
self.config.height = height;
self.surface.configure(&self.device, &self.config);
self.depth_texture =
texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture");
}
}
fn input(&mut self, event: &WindowEvent) -> bool {
println!("Handle event {:?}", event);
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: PhysicalKey::Code(key),
state,
..
},
..
} => self.camera_controller.process_keyboard(*key, *state),
WindowEvent::MouseWheel { delta, .. } => {
self.camera_controller.process_scroll(delta);
true
}
WindowEvent::MouseInput {
button: MouseButton::Left,
state,
..
} => {
self.mouse_pressed = *state == ElementState::Pressed;
true
}
WindowEvent::CursorMoved { position, .. } => {
if self.mouse_pressed {
let delta_x = position.x as f64 - self.last_mouse_position.x as f64;
let delta_y = position.y as f64 - self.last_mouse_position.y as f64;
self.camera_controller.process_mouse(delta_x, delta_y);
}
self.last_mouse_position = *position;
true
}
_ => false,
}
}
fn update(&mut self, dt: std::time::Duration) {
self.camera_controller.update_camera(&mut self.camera, dt);
self.camera_uniform
.update_view_proj(&self.camera, &self.projection);
self.queue.write_buffer(
&self.camera_buffer,
0,
bytemuck::cast_slice(&[self.camera_uniform]),
);
// Update the light
let old_position: cgmath::Vector3<_> = self.light_uniform.position.into();
self.light_uniform.position = (cgmath::Quaternion::from_axis_angle(
(0.0, 1.0, 0.0).into(),
cgmath::Deg(PI * dt.as_secs_f32()),
) * old_position)
.into();
self.queue.write_buffer(
&self.light_buffer,
0,
bytemuck::cast_slice(&[self.light_uniform]),
);
}
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?;
let view = output.texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(self.config.format.add_srgb_suffix()),
..Default::default()
});
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: self.hdr.view(), // UPDATED!
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture.view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
});
render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..));
render_pass.set_pipeline(&self.light_render_pipeline);
render_pass.draw_light_model(
&self.obj_model,
&self.camera_bind_group,
&self.light_bind_group,
);
render_pass.set_pipeline(&self.render_pipeline);
render_pass.draw_model_instanced(
&self.obj_model,
0..self.instances.len() as u32,
&self.camera_bind_group,
&self.light_bind_group,
&self.environment_bind_group,
);
render_pass.set_pipeline(&self.sky_pipeline);
render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
render_pass.set_bind_group(1, &self.environment_bind_group, &[]);
render_pass.draw(0..3, 0..1);
}
// NEW!
// Apply tonemapping
self.hdr.process(&mut encoder, &view);
#[cfg(feature = "debug")]
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Debug"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
self.debug.draw_axis(&mut pass, &self.camera_bind_group);
}
self.queue.submit(iter::once(encoder.finish()));
output.present();
Ok(())
}
}
pub async fn run() {
env_logger::init();
let event_loop = EventLoop::new().unwrap();
/*let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new()
.with_title(title)
// Limit dimensions to 1080p
.with_inner_size(winit::dpi::PhysicalSize::new(1920, 1080))
.build(&event_loop)
.unwrap();*/
let mut window_state = StateApplication::new();
let _ = event_loop.run_app(&mut window_state);
/*let mut state = State::new(&window).await.unwrap();
let mut last_render_time = instant::Instant::now();
event_loop.run(move |event, control_flow| {
match event {
Event::DeviceEvent {
event: DeviceEvent::MouseMotion{ delta, },
.. // We're not using device_id currently
} => if state.mouse_pressed {
state.camera_controller.process_mouse(delta.0, delta.1)
}
// UPDATED!
Event::WindowEvent {
ref event,
window_id,
} if window_id == state.window().id() && !state.input(event) => {
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
physical_key: PhysicalKey::Code(KeyCode::Escape),
..
},
..
} => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
state.window().request_redraw();
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(),
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
_ => {}
}
}
_ => {}
}
}).unwrap();*/
}

45
src/light.wgsl Normal file
View File

@ -0,0 +1,45 @@
// Vertex shader
struct Camera {
view_pos: vec4<f32>,
view: mat4x4<f32>,
view_proj: mat4x4<f32>,
inv_proj: mat4x4<f32>,
inv_view: mat4x4<f32>,
}
@group(0) @binding(0)
var<uniform> camera: Camera;
struct Light {
position: vec3<f32>,
color: vec3<f32>,
}
@group(1) @binding(0)
var<uniform> light: Light;
struct VertexInput {
@location(0) position: vec3<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec3<f32>,
};
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
let scale = 0.25;
var out: VertexOutput;
out.clip_position = camera.view_proj * vec4<f32>(model.position * scale + light.position, 1.0);
out.color = light.color;
return out;
}
// Fragment shader
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(in.color, 1.0);
}

93
src/main.rs Normal file
View File

@ -0,0 +1,93 @@
/*
This program is for simulating orbits of planets around a star.
A body can be a star, planet or moon. (Doesn't matter for now)
*/
use orbital_simulation::run;
// Gravitational constant
static G: f64 = 6.67430e-11;
struct Body {
name: String,
mass: f64,
radius: f64,
}
fn calculate_gravitational_force(body1: &Body, body2: &Body, distance: f64) -> f64 {
(G * body1.mass * body2.mass) / distance.powi(2)
}
fn calculate_required_velocity(body1: &Body, body2: &Body, distance: f64, force: f64) -> f64 {
(force * distance / body2.mass).sqrt()
}
fn main() {
// simulate a two body system to simplify the problem
let sun = Body {
name: "Sun".to_string(),
mass: 1.989e30,
radius: 6.9634e8,
};
let earth = Body {
name: "Earth".to_string(),
mass: 5.972e24,
radius: 6.371e6,
};
/*// Calculate the velocity pulling the earth towards the sun
let distance = 1.496e11;
let force = calculate_gravitational_force(&sun, &earth, distance);
let velocity = calculate_required_velocity(&sun, &earth, distance, force);
println!("The velocity of the earth is: {} m/s", velocity);*/
// Now we simulate a whole orbit around the sun
let distance = 1.496e11;
let force = calculate_gravitational_force(&sun, &earth, distance);
let velocity = calculate_required_velocity(&sun, &earth, distance, force);
let mut time = 0.0;
let mut position = 0.0;
let mut velocity = velocity;
let mut acceleration = 0.0;
let mut force = force;
let mut distance = distance;
let mut mass = earth.mass;
let mut radius = earth.radius;
let dt = 1.0;
let steps = 1000;
for _ in 0..steps {
// Calculate the acceleration
acceleration = force / mass;
// Calculate the new position
position += velocity * dt + 0.5 * acceleration * dt.powi(2);
// Calculate the new velocity
velocity += acceleration * dt;
// Calculate the new distance
distance = position;
// Calculate the new force
force = calculate_gravitational_force(&sun, &earth, distance);
// Calculate the new mass
mass = earth.mass;
// Calculate the new radius
radius = earth.radius;
// Calculate the new time
time += dt;
println!("Time: {} s, Position: {} m, Velocity: {} m/s, Acceleration: {} m/s^2, Force: {} N, Distance: {} m, Mass: {} kg, Radius: {} m", time, position, velocity, acceleration, force, distance, mass, radius);
}
pollster::block_on(run());
}

350
src/model.rs Normal file
View File

@ -0,0 +1,350 @@
use std::ops::Range;
use crate::texture;
pub trait Vertex {
fn desc() -> wgpu::VertexBufferLayout<'static>;
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ModelVertex {
pub position: [f32; 3],
pub tex_coords: [f32; 2],
pub normal: [f32; 3],
pub tangent: [f32; 3],
pub bitangent: [f32; 3],
}
impl Vertex for ModelVertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
use std::mem;
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<ModelVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
},
// Tangent and bitangent
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x3,
},
],
}
}
}
pub struct Material {
#[allow(unused)]
pub name: String,
#[allow(unused)]
pub diffuse_texture: texture::Texture,
#[allow(unused)]
pub normal_texture: texture::Texture,
pub bind_group: wgpu::BindGroup,
}
impl Material {
pub fn new(
device: &wgpu::Device,
name: &str,
diffuse_texture: texture::Texture,
normal_texture: texture::Texture,
layout: &wgpu::BindGroupLayout,
) -> Self {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&normal_texture.view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&normal_texture.sampler),
},
],
label: Some(name),
});
Self {
name: String::from(name),
diffuse_texture,
normal_texture,
bind_group,
}
}
}
pub struct Mesh {
#[allow(unused)]
pub name: String,
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub num_elements: u32,
pub material: usize,
}
pub struct Model {
pub meshes: Vec<Mesh>,
pub materials: Vec<Material>,
}
pub trait DrawModel<'a> {
#[allow(unused)]
fn draw_mesh(
&mut self,
mesh: &'a Mesh,
material: &'a Material,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
environment_bind_group: &'a wgpu::BindGroup,
);
fn draw_mesh_instanced(
&mut self,
mesh: &'a Mesh,
material: &'a Material,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
environment_bind_group: &'a wgpu::BindGroup,
);
#[allow(unused)]
fn draw_model(
&mut self,
model: &'a Model,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
environment_bind_group: &'a wgpu::BindGroup,
);
fn draw_model_instanced(
&mut self,
model: &'a Model,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
environment_bind_group: &'a wgpu::BindGroup,
);
#[allow(unused)]
fn draw_model_instanced_with_material(
&mut self,
model: &'a Model,
material: &'a Material,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
environment_bind_group: &'a wgpu::BindGroup,
);
}
impl<'a, 'b> DrawModel<'b> for wgpu::RenderPass<'a>
where
'b: 'a,
{
fn draw_mesh(
&mut self,
mesh: &'b Mesh,
material: &'b Material,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
environment_bind_group: &'b wgpu::BindGroup,
) {
self.draw_mesh_instanced(
mesh,
material,
0..1,
camera_bind_group,
light_bind_group,
environment_bind_group,
);
}
fn draw_mesh_instanced(
&mut self,
mesh: &'b Mesh,
material: &'b Material,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
environment_bind_group: &'b wgpu::BindGroup,
) {
self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
self.set_bind_group(0, &material.bind_group, &[]);
self.set_bind_group(1, camera_bind_group, &[]);
self.set_bind_group(2, light_bind_group, &[]);
self.set_bind_group(3, environment_bind_group, &[]);
self.draw_indexed(0..mesh.num_elements, 0, instances);
}
fn draw_model(
&mut self,
model: &'b Model,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
environment_bind_group: &'b wgpu::BindGroup,
) {
self.draw_model_instanced(
model,
0..1,
camera_bind_group,
light_bind_group,
environment_bind_group,
);
}
fn draw_model_instanced(
&mut self,
model: &'b Model,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
environment_bind_group: &'b wgpu::BindGroup, // NEW!
) {
for mesh in &model.meshes {
let material = &model.materials[mesh.material];
self.draw_mesh_instanced(
mesh,
material,
instances.clone(),
camera_bind_group,
light_bind_group,
environment_bind_group,
);
}
}
fn draw_model_instanced_with_material(
&mut self,
model: &'b Model,
material: &'b Material,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
environment_bind_group: &'b wgpu::BindGroup,
) {
for mesh in &model.meshes {
self.draw_mesh_instanced(
mesh,
material,
instances.clone(),
camera_bind_group,
light_bind_group,
environment_bind_group,
);
}
}
}
pub trait DrawLight<'a> {
#[allow(unused)]
fn draw_light_mesh(
&mut self,
mesh: &'a Mesh,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_light_mesh_instanced(
&mut self,
mesh: &'a Mesh,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_light_model(
&mut self,
model: &'a Model,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_light_model_instanced(
&mut self,
model: &'a Model,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
}
impl<'a, 'b> DrawLight<'b> for wgpu::RenderPass<'a>
where
'b: 'a,
{
fn draw_light_mesh(
&mut self,
mesh: &'b Mesh,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
self.draw_light_mesh_instanced(mesh, 0..1, camera_bind_group, light_bind_group);
}
fn draw_light_mesh_instanced(
&mut self,
mesh: &'b Mesh,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
self.set_bind_group(0, camera_bind_group, &[]);
self.set_bind_group(1, light_bind_group, &[]);
self.draw_indexed(0..mesh.num_elements, 0, instances);
}
fn draw_light_model(
&mut self,
model: &'b Model,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
self.draw_light_model_instanced(model, 0..1, camera_bind_group, light_bind_group);
}
fn draw_light_model_instanced(
&mut self,
model: &'b Model,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
for mesh in &model.meshes {
self.draw_light_mesh_instanced(
mesh,
instances.clone(),
camera_bind_group,
light_bind_group,
);
}
}
}

347
src/resources.rs Normal file
View File

@ -0,0 +1,347 @@
use std::io::{BufReader, Cursor};
use cfg_if::cfg_if;
use image::codecs::hdr::HdrDecoder;
use wgpu::util::DeviceExt;
use crate::{model, texture};
pub async fn load_string(file_name: &str) -> anyhow::Result<String> {
let path = std::path::Path::new(env!("OUT_DIR"))
.join("res")
.join(file_name);
let txt = std::fs::read_to_string(path)?;
Ok(txt)
}
pub async fn load_binary(file_name: &str) -> anyhow::Result<Vec<u8>> {
let path = std::path::Path::new(env!("OUT_DIR"))
.join("res")
.join(file_name);
let data = std::fs::read(path)?;
Ok(data)
}
pub async fn load_texture(
file_name: &str,
is_normal_map: bool,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> anyhow::Result<texture::Texture> {
let data = load_binary(file_name).await?;
texture::Texture::from_bytes(device, queue, &data, file_name, is_normal_map)
}
pub async fn load_model(
file_name: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Model> {
let obj_text = load_string(file_name).await?;
let obj_cursor = Cursor::new(obj_text);
let mut obj_reader = BufReader::new(obj_cursor);
let (models, obj_materials) = tobj::load_obj_buf_async(
&mut obj_reader,
&tobj::LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
},
|p| async move {
let mat_text = load_string(&p).await.unwrap();
tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text)))
},
)
.await?;
let mut materials = Vec::new();
for m in obj_materials? {
let diffuse_texture = load_texture(&m.diffuse_texture, false, device, queue).await?;
let normal_texture = load_texture(&m.normal_texture, true, device, queue).await?;
materials.push(model::Material::new(
device,
&m.name,
diffuse_texture,
normal_texture,
layout,
));
}
let meshes = models
.into_iter()
.map(|m| {
let mut vertices = (0..m.mesh.positions.len() / 3)
.map(|i| model::ModelVertex {
position: [
m.mesh.positions[i * 3],
m.mesh.positions[i * 3 + 1],
m.mesh.positions[i * 3 + 2],
],
tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]],
normal: [
m.mesh.normals[i * 3],
m.mesh.normals[i * 3 + 1],
m.mesh.normals[i * 3 + 2],
],
// We'll calculate these later
tangent: [0.0; 3],
bitangent: [0.0; 3],
})
.collect::<Vec<_>>();
let indices = &m.mesh.indices;
let mut triangles_included = vec![0; vertices.len()];
// Calculate tangents and bitangets. We're going to
// use the triangles, so we need to loop through the
// indices in chunks of 3
for c in indices.chunks(3) {
let v0 = vertices[c[0] as usize];
let v1 = vertices[c[1] as usize];
let v2 = vertices[c[2] as usize];
let pos0: cgmath::Vector3<_> = v0.position.into();
let pos1: cgmath::Vector3<_> = v1.position.into();
let pos2: cgmath::Vector3<_> = v2.position.into();
let uv0: cgmath::Vector2<_> = v0.tex_coords.into();
let uv1: cgmath::Vector2<_> = v1.tex_coords.into();
let uv2: cgmath::Vector2<_> = v2.tex_coords.into();
// Calculate the edges of the triangle
let delta_pos1 = pos1 - pos0;
let delta_pos2 = pos2 - pos0;
// This will give us a direction to calculate the
// tangent and bitangent
let delta_uv1 = uv1 - uv0;
let delta_uv2 = uv2 - uv0;
// Solving the following system of equations will
// give us the tangent and bitangent.
// delta_pos1 = delta_uv1.x * T + delta_u.y * B
// delta_pos2 = delta_uv2.x * T + delta_uv2.y * B
// Luckily, the place I found this equation provided
// the solution!
let r = 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv1.y * delta_uv2.x);
let tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
// We flip the bitangent to enable right-handed normal
// maps with wgpu texture coordinate system
let bitangent = (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * -r;
// We'll use the same tangent/bitangent for each vertex in the triangle
vertices[c[0] as usize].tangent =
(tangent + cgmath::Vector3::from(vertices[c[0] as usize].tangent)).into();
vertices[c[1] as usize].tangent =
(tangent + cgmath::Vector3::from(vertices[c[1] as usize].tangent)).into();
vertices[c[2] as usize].tangent =
(tangent + cgmath::Vector3::from(vertices[c[2] as usize].tangent)).into();
vertices[c[0] as usize].bitangent =
(bitangent + cgmath::Vector3::from(vertices[c[0] as usize].bitangent)).into();
vertices[c[1] as usize].bitangent =
(bitangent + cgmath::Vector3::from(vertices[c[1] as usize].bitangent)).into();
vertices[c[2] as usize].bitangent =
(bitangent + cgmath::Vector3::from(vertices[c[2] as usize].bitangent)).into();
// Used to average the tangents/bitangents
triangles_included[c[0] as usize] += 1;
triangles_included[c[1] as usize] += 1;
triangles_included[c[2] as usize] += 1;
}
// Average the tangents/bitangents
for (i, n) in triangles_included.into_iter().enumerate() {
let denom = 1.0 / n as f32;
let v = &mut vertices[i];
v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into();
v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into();
}
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Vertex Buffer", file_name)),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Index Buffer", file_name)),
contents: bytemuck::cast_slice(&m.mesh.indices),
usage: wgpu::BufferUsages::INDEX,
});
model::Mesh {
name: file_name.to_string(),
vertex_buffer,
index_buffer,
num_elements: m.mesh.indices.len() as u32,
material: m.mesh.material_id.unwrap_or(0),
}
})
.collect::<Vec<_>>();
Ok(model::Model { meshes, materials })
}
pub struct HdrLoader {
texture_format: wgpu::TextureFormat,
equirect_layout: wgpu::BindGroupLayout,
equirect_to_cubemap: wgpu::ComputePipeline,
}
impl HdrLoader {
pub fn new(device: &wgpu::Device) -> Self {
let module = device.create_shader_module(wgpu::include_wgsl!("equirectangular.wgsl"));
let texture_format = wgpu::TextureFormat::Rgba32Float;
let equirect_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("HdrLoader::equirect_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::StorageTexture {
access: wgpu::StorageTextureAccess::WriteOnly,
format: texture_format,
view_dimension: wgpu::TextureViewDimension::D2Array,
},
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&equirect_layout],
push_constant_ranges: &[],
});
let equirect_to_cubemap =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("equirect_to_cubemap"),
layout: Some(&pipeline_layout),
module: &module,
entry_point: "compute_equirect_to_cubemap",
compilation_options: Default::default(),
cache: None,
});
Self {
equirect_to_cubemap,
texture_format,
equirect_layout,
}
}
pub fn from_equirectangular_bytes(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
data: &[u8],
dst_size: u32,
label: Option<&str>,
) -> anyhow::Result<texture::CubeTexture> {
let hdr_decoder = HdrDecoder::new(Cursor::new(data))?;
let meta = hdr_decoder.metadata();
let pixels = {
let mut pixels = vec![[0.0, 0.0, 0.0, 0.0]; meta.width as usize * meta.height as usize];
hdr_decoder.read_image_transform(
|pix| {
let rgb = pix.to_hdr();
[rgb.0[0], rgb.0[1], rgb.0[2], 1.0f32]
},
&mut pixels[..],
)?;
pixels
};
let src = texture::Texture::create_2d_texture(
device,
meta.width,
meta.height,
self.texture_format,
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
wgpu::FilterMode::Linear,
None,
);
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &src.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&bytemuck::cast_slice(&pixels),
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(src.size.width * std::mem::size_of::<[f32; 4]>() as u32),
rows_per_image: Some(src.size.height),
},
src.size,
);
let dst = texture::CubeTexture::create_2d(
device,
dst_size,
dst_size,
self.texture_format,
1,
wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
wgpu::FilterMode::Nearest,
label,
);
let dst_view = dst.texture().create_view(&wgpu::TextureViewDescriptor {
label,
dimension: Some(wgpu::TextureViewDimension::D2Array),
// array_layer_count: Some(6),
..Default::default()
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label,
layout: &self.equirect_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&src.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&dst_view),
},
],
});
let mut encoder = device.create_command_encoder(&Default::default());
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label,
timestamp_writes: None,
});
let num_workgroups = (dst_size + 15) / 16;
pass.set_pipeline(&self.equirect_to_cubemap);
pass.set_bind_group(0, &bind_group, &[]);
pass.dispatch_workgroups(num_workgroups, num_workgroups, 6);
drop(pass);
queue.submit([encoder.finish()]);
Ok(dst)
}
}

139
src/shader.wgsl Normal file
View File

@ -0,0 +1,139 @@
// Vertex shader
struct Camera {
view_pos: vec4<f32>,
view: mat4x4<f32>,
view_proj: mat4x4<f32>,
inv_proj: mat4x4<f32>,
inv_view: mat4x4<f32>,
}
@group(1) @binding(0)
var<uniform> camera: Camera;
struct Light {
position: vec3<f32>,
color: vec3<f32>,
}
@group(2) @binding(0)
var<uniform> light: Light;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
@location(2) normal: vec3<f32>,
@location(3) tangent: vec3<f32>,
@location(4) bitangent: vec3<f32>,
}
struct InstanceInput {
@location(5) model_matrix_0: vec4<f32>,
@location(6) model_matrix_1: vec4<f32>,
@location(7) model_matrix_2: vec4<f32>,
@location(8) model_matrix_3: vec4<f32>,
@location(9) normal_matrix_0: vec3<f32>,
@location(10) normal_matrix_1: vec3<f32>,
@location(11) normal_matrix_2: vec3<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
// Updated!
@location(1) world_position: vec3<f32>,
@location(2) world_view_position: vec3<f32>,
@location(3) world_light_position: vec3<f32>,
@location(4) world_normal: vec3<f32>,
@location(5) world_tangent: vec3<f32>,
@location(6) world_bitangent: vec3<f32>,
}
@vertex
fn vs_main(
model: VertexInput,
instance: InstanceInput,
) -> VertexOutput {
let model_matrix = mat4x4<f32>(
instance.model_matrix_0,
instance.model_matrix_1,
instance.model_matrix_2,
instance.model_matrix_3,
);
let normal_matrix = mat3x3<f32>(
instance.normal_matrix_0,
instance.normal_matrix_1,
instance.normal_matrix_2,
);
// UPDATED!
let world_position = model_matrix * vec4<f32>(model.position, 1.0);
var out: VertexOutput;
out.clip_position = camera.view_proj * world_position;
out.tex_coords = model.tex_coords;
out.world_normal = normalize(normal_matrix * model.normal);
out.world_tangent = normalize(normal_matrix * model.tangent);
out.world_bitangent = normalize(normal_matrix * model.bitangent);
out.world_position = world_position.xyz;
out.world_view_position = camera.view_pos.xyz;
return out;
}
// Fragment shader
@group(0) @binding(0)
var t_diffuse: texture_2d<f32>;
@group(0)@binding(1)
var s_diffuse: sampler;
@group(0)@binding(2)
var t_normal: texture_2d<f32>;
@group(0) @binding(3)
var s_normal: sampler;
@group(3)
@binding(0)
var env_map: texture_cube<f32>;
@group(3)
@binding(1)
var env_sampler: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
let object_normal: vec4<f32> = textureSample(t_normal, s_normal, in.tex_coords);
// NEW!
// Adjust the tangent and bitangent using the Gramm-Schmidt process
// This makes sure that they are perpedicular to each other and the
// normal of the surface.
let world_tangent = normalize(in.world_tangent - dot(in.world_tangent, in.world_normal) * in.world_normal);
let world_bitangent = cross(world_tangent, in.world_normal);
// Convert the normal sample to world space
let TBN = mat3x3(
world_tangent,
world_bitangent,
in.world_normal,
);
let tangent_normal = object_normal.xyz * 2.0 - 1.0;
let world_normal = TBN * tangent_normal;
// Create the lighting vectors
let light_dir = normalize(light.position - in.world_position);
let view_dir = normalize(in.world_view_position - in.world_position);
let half_dir = normalize(view_dir + light_dir);
let diffuse_strength = max(dot(world_normal, light_dir), 0.0);
let diffuse_color = light.color * diffuse_strength;
let specular_strength = pow(max(dot(world_normal, half_dir), 0.0), 32.0);
let specular_color = specular_strength * light.color;
// NEW!
// Calculate reflections
let world_reflect = reflect(-view_dir, world_normal);
let reflection = textureSample(env_map, env_sampler, world_reflect).rgb;
let shininess = 0.1;
let result = (diffuse_color + specular_color) * object_color.xyz + reflection * shininess;
return vec4<f32>(result, object_color.a);
}

45
src/sky.wgsl Normal file
View File

@ -0,0 +1,45 @@
struct Camera {
view_pos: vec4<f32>,
view: mat4x4<f32>,
view_proj: mat4x4<f32>,
inv_proj: mat4x4<f32>,
inv_view: mat4x4<f32>,
}
@group(0) @binding(0)
var<uniform> camera: Camera;
@group(1)
@binding(0)
var env_map: texture_cube<f32>;
@group(1)
@binding(1)
var env_sampler: sampler;
struct VertexOutput {
@builtin(position) frag_position: vec4<f32>,
@location(0) clip_position: vec4<f32>,
}
@vertex
fn vs_main(
@builtin(vertex_index) id: u32,
) -> VertexOutput {
let uv = vec2<f32>(vec2<u32>(
id & 1u,
(id >> 1u) & 1u,
));
var out: VertexOutput;
out.clip_position = vec4(uv * 4.0 - 1.0, 1.0, 1.0);
out.frag_position = out.clip_position;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let view_pos_homogeneous = camera.inv_proj * in.clip_position;
let view_ray_direction = view_pos_homogeneous.xyz / view_pos_homogeneous.w;
var ray_direction = normalize((camera.inv_view * vec4(view_ray_direction, 0.0)).xyz);
let sample = textureSample(env_map, env_sampler, ray_direction);
return sample;
}

255
src/texture.rs Normal file
View File

@ -0,0 +1,255 @@
use anyhow::*;
use image::GenericImageView;
pub struct Texture {
#[allow(unused)]
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
pub size: wgpu::Extent3d,
}
impl Texture {
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
pub fn create_depth_texture(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
label: &str,
) -> Self {
let size = wgpu::Extent3d {
width: config.width.max(1),
height: config.height.max(1),
depth_or_array_layers: 1,
};
let desc = wgpu::TextureDescriptor {
label: Some(label),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Self::DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[Self::DEPTH_FORMAT],
};
let texture = device.create_texture(&desc);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
compare: Some(wgpu::CompareFunction::LessEqual),
lod_min_clamp: 0.0,
lod_max_clamp: 100.0,
..Default::default()
});
Self {
texture,
view,
sampler,
size, // NEW!
}
}
#[allow(dead_code)]
pub fn from_bytes(
device: &wgpu::Device,
queue: &wgpu::Queue,
bytes: &[u8],
label: &str,
is_normal_map: bool,
) -> Result<Self> {
let img = image::load_from_memory(bytes)?;
Self::from_image(device, queue, &img, Some(label), is_normal_map)
}
pub fn from_image(
device: &wgpu::Device,
queue: &wgpu::Queue,
img: &image::DynamicImage,
label: Option<&str>,
is_normal_map: bool,
) -> Result<Self> {
let dimensions = img.dimensions();
let rgba = img.to_rgba8();
let format = if is_normal_map {
wgpu::TextureFormat::Rgba8Unorm
} else {
wgpu::TextureFormat::Rgba8UnormSrgb
};
let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
let size = wgpu::Extent3d {
width: img.width(),
height: img.height(),
depth_or_array_layers: 1,
};
let texture = Self::create_2d_texture(
device,
size.width,
size.height,
format,
usage,
wgpu::FilterMode::Linear,
label,
);
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * dimensions.0),
rows_per_image: Some(dimensions.1),
},
size,
);
Ok(texture)
}
pub(crate) fn create_2d_texture(
device: &wgpu::Device,
width: u32,
height: u32,
format: wgpu::TextureFormat,
usage: wgpu::TextureUsages,
mag_filter: wgpu::FilterMode,
label: Option<&str>,
) -> Self {
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
Self::create_texture(
device,
label,
size,
format,
usage,
wgpu::TextureDimension::D2,
mag_filter,
)
}
pub fn create_texture(
device: &wgpu::Device,
label: Option<&str>,
size: wgpu::Extent3d,
format: wgpu::TextureFormat,
usage: wgpu::TextureUsages,
dimension: wgpu::TextureDimension,
mag_filter: wgpu::FilterMode,
) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension,
format,
usage,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
Self {
texture,
view,
sampler,
size,
}
}
}
pub struct CubeTexture {
texture: wgpu::Texture,
sampler: wgpu::Sampler,
view: wgpu::TextureView,
}
impl CubeTexture {
pub fn create_2d(
device: &wgpu::Device,
width: u32,
height: u32,
format: wgpu::TextureFormat,
mip_level_count: u32,
usage: wgpu::TextureUsages,
mag_filter: wgpu::FilterMode,
label: Option<&str>,
) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label,
size: wgpu::Extent3d {
width,
height,
// A cube has 6 sides, so we need 6 layers
depth_or_array_layers: 6,
},
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor {
label,
dimension: Some(wgpu::TextureViewDimension::Cube),
array_layer_count: Some(6),
..Default::default()
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label,
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
Self {
texture,
sampler,
view,
}
}
pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
pub fn view(&self) -> &wgpu::TextureView {
&self.view
}
pub fn sampler(&self) -> &wgpu::Sampler {
&self.sampler
}
}