Compare commits
No commits in common. "31d4d5331615d1d20db0036af52cffddf33db818" and "12d61b25c45c95c800162fc9d650e37459698662" have entirely different histories.
31d4d53316
...
12d61b25c4
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,12 +1 @@
|
||||
/target
|
||||
**/target
|
||||
/Cargo.lock
|
||||
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
8
.idea/modules.xml
generated
Normal 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
11
.idea/orbital_simulation.iml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
2530
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
Cargo.toml
32
Cargo.toml
@ -1,5 +1,27 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"solar_engine",
|
||||
"simulator"
|
||||
]
|
||||
[package]
|
||||
name = "orbital_simulation"
|
||||
version = "0.1.0"
|
||||
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
18
build.rs
Normal 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, ©_options)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
BIN
res/cobble-diffuse.png
Normal file
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
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
14
res/cobble_sphere.mtl
Normal 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
2041
res/cobble_sphere.obj
Normal file
File diff suppressed because it is too large
Load Diff
BIN
res/cube-diffuse.jpg
Normal file
BIN
res/cube-diffuse.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
res/cube-normal.png
Normal file
BIN
res/cube-normal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
14
res/cube.mtl
Normal file
14
res/cube.mtl
Normal 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
933
res/cube.obj
Normal 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
BIN
res/pure-sky.hdr
Normal file
Binary file not shown.
BIN
res/skybox.glb
Normal file
BIN
res/skybox.glb
Normal file
Binary file not shown.
BIN
res/skybox.hdr
Normal file
BIN
res/skybox.hdr
Normal file
Binary file not shown.
22
res/skybox.mtl
Normal file
22
res/skybox.mtl
Normal 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
2568
res/skybox.obj
Normal file
File diff suppressed because it is too large
Load Diff
BIN
res/skybox.png
Normal file
BIN
res/skybox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 MiB |
@ -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"
|
||||
@ -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());
|
||||
}
|
||||
7
solar_engine/Cargo.lock
generated
7
solar_engine/Cargo.lock
generated
@ -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"
|
||||
@ -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"
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
189
src/camera.rs
Normal 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
99
src/debug.rs
Normal 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
87
src/equirectangular.wgsl
Normal 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
BIN
src/happy-tree.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
160
src/hdr.rs
Normal file
160
src/hdr.rs
Normal 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
55
src/hdr.wgsl
Normal 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
947
src/lib.rs
Normal 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
45
src/light.wgsl
Normal 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
93
src/main.rs
Normal 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
350
src/model.rs
Normal 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
347
src/resources.rs
Normal 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
139
src/shader.wgsl
Normal 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
45
src/sky.wgsl
Normal 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
255
src/texture.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user