mirror of
https://github.com/hannobraun/Fornjot
synced 2025-10-01 13:38:21 +00:00
Merge pull request #1573 from erenoku/navigation_cube
Add navigation cube
This commit is contained in:
commit
5879a0c650
40
Cargo.lock
generated
40
Cargo.lock
generated
@ -51,6 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
@ -581,6 +582,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.1.0"
|
||||
@ -1267,9 +1274,12 @@ dependencies = [
|
||||
"fj-interop",
|
||||
"fj-math",
|
||||
"getrandom",
|
||||
"image",
|
||||
"nalgebra",
|
||||
"raw-window-handle 0.5.0",
|
||||
"rfd",
|
||||
"thiserror",
|
||||
"tobj",
|
||||
"tracing",
|
||||
"wgpu",
|
||||
"wgpu_glyph",
|
||||
@ -1807,6 +1817,21 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder 1.4.3",
|
||||
"color_quant",
|
||||
"jpeg-decoder",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.2"
|
||||
@ -1910,6 +1935,12 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.61"
|
||||
@ -3737,6 +3768,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tobj"
|
||||
version = "3.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d0bde887a49e2e09f30ba3b454cdba3fbc971703e436c527f9f69a035b45b9b"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.25.0"
|
||||
|
BIN
assets/navigation_cube/bottom.png
Normal file
BIN
assets/navigation_cube/bottom.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
assets/navigation_cube/cube.blend
Normal file
BIN
assets/navigation_cube/cube.blend
Normal file
Binary file not shown.
62
assets/navigation_cube/cube.mtl
Normal file
62
assets/navigation_cube/cube.mtl
Normal file
@ -0,0 +1,62 @@
|
||||
# Blender 3.4.1 MTL File: 'cube.blend'
|
||||
# www.blender.org
|
||||
|
||||
newmtl bottom
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd bottom.png
|
||||
|
||||
newmtl front
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd front.png
|
||||
|
||||
newmtl left
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd left.png
|
||||
|
||||
newmtl rear
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd rear.png
|
||||
|
||||
newmtl right
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd right.png
|
||||
|
||||
newmtl top
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd top.png
|
51
assets/navigation_cube/cube.obj
Normal file
51
assets/navigation_cube/cube.obj
Normal file
@ -0,0 +1,51 @@
|
||||
# Blender 3.4.1
|
||||
# www.blender.org
|
||||
mtllib cube.mtl
|
||||
o Cube
|
||||
v 1.000000 1.000000 1.000000
|
||||
v 1.000000 -1.000000 1.000000
|
||||
v -1.000000 1.000000 1.000000
|
||||
v -1.000000 -1.000000 1.000000
|
||||
v 1.000000 1.000000 -1.000000
|
||||
v 1.000000 -1.000000 -1.000000
|
||||
v -1.000000 1.000000 -1.000000
|
||||
v -1.000000 -1.000000 -1.000000
|
||||
vn -0.0000 1.0000 -0.0000
|
||||
vn -1.0000 -0.0000 -0.0000
|
||||
vn -0.0000 -0.0000 -1.0000
|
||||
vn -0.0000 -1.0000 -0.0000
|
||||
vn -0.0000 -0.0000 1.0000
|
||||
vn 1.0000 -0.0000 -0.0000
|
||||
vt 0.999900 0.000100
|
||||
vt 0.999900 0.999900
|
||||
vt 0.000100 0.999900
|
||||
vt 0.999900 0.999900
|
||||
vt 0.999900 0.000100
|
||||
vt 0.000100 0.000100
|
||||
vt 0.000100 0.000100
|
||||
vt 0.999900 0.999900
|
||||
vt 0.000100 0.999900
|
||||
vt 0.999900 0.000100
|
||||
vt 0.000100 0.999900
|
||||
vt 0.000100 0.000100
|
||||
vt 0.000100 0.999900
|
||||
vt 0.999900 0.999900
|
||||
vt 0.000100 0.000100
|
||||
vt 0.999900 0.000100
|
||||
vt 0.000100 0.999900
|
||||
vt 0.999900 0.999900
|
||||
vt 0.999900 0.000100
|
||||
vt 0.000100 0.000100
|
||||
s 0
|
||||
usemtl top
|
||||
f 1/1/1 5/14/1 7/17/1 3/7/1
|
||||
usemtl left
|
||||
f 4/10/2 3/8/2 7/17/2 8/20/2
|
||||
usemtl front
|
||||
f 2/5/5 1/2/5 3/9/5 4/12/5
|
||||
usemtl right
|
||||
f 6/16/6 5/14/6 1/3/6 2/6/6
|
||||
usemtl rear
|
||||
f 8/19/3 7/18/3 5/13/3 6/15/3
|
||||
usemtl bottom
|
||||
f 6/16/4 2/4/4 4/11/4 8/20/4
|
BIN
assets/navigation_cube/front.png
Normal file
BIN
assets/navigation_cube/front.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
assets/navigation_cube/left.png
Normal file
BIN
assets/navigation_cube/left.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
assets/navigation_cube/rear.png
Normal file
BIN
assets/navigation_cube/rear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
assets/navigation_cube/right.png
Normal file
BIN
assets/navigation_cube/right.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
assets/navigation_cube/top.png
Normal file
BIN
assets/navigation_cube/top.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -40,6 +40,13 @@ impl Transform {
|
||||
))
|
||||
}
|
||||
|
||||
/// Construct a scaling
|
||||
pub fn scale(scaling_factor: f64) -> Self {
|
||||
Self(nalgebra::Transform::from_matrix_unchecked(
|
||||
nalgebra::OMatrix::new_scaling(scaling_factor),
|
||||
))
|
||||
}
|
||||
|
||||
/// Transform the given point
|
||||
pub fn transform_point(&self, point: &Point<3>) -> Point<3> {
|
||||
Point::from(self.0.transform_point(&point.to_na()))
|
||||
@ -119,6 +126,11 @@ impl Transform {
|
||||
array.map(Scalar::from)
|
||||
}
|
||||
|
||||
/// Return a copy of the inner nalgebra transform
|
||||
pub fn get_inner(&self) -> nalgebra::Transform<f64, nalgebra::TAffine, 3> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Transform the given axis-aligned bounding box
|
||||
pub fn transform_aabb(&self, aabb: &Aabb<3>) -> Aabb<3> {
|
||||
Aabb {
|
||||
|
@ -18,11 +18,18 @@ egui = "0.20.1"
|
||||
egui-wgpu = "0.20.0"
|
||||
fj-interop.workspace = true
|
||||
fj-math.workspace = true
|
||||
nalgebra = "0.32.1"
|
||||
tobj = "3.2.4"
|
||||
raw-window-handle = "0.5.0"
|
||||
thiserror = "1.0.35"
|
||||
tracing = "0.1.37"
|
||||
wgpu_glyph = "0.18.0"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.24"
|
||||
default-features = false
|
||||
features = ["png", "jpeg"]
|
||||
|
||||
[dependencies.rfd]
|
||||
version = "0.11.1"
|
||||
default_features = false
|
||||
|
59
crates/fj-viewer/src/assets.rs
Normal file
59
crates/fj-viewer/src/assets.rs
Normal file
@ -0,0 +1,59 @@
|
||||
pub struct Assets<'a> {
|
||||
pub cube_obj: &'a [u8],
|
||||
pub cube_mtl: &'a [u8],
|
||||
pub front_texture: &'a [u8],
|
||||
pub right_texture: &'a [u8],
|
||||
pub rear_texture: &'a [u8],
|
||||
pub left_texture: &'a [u8],
|
||||
pub top_texture: &'a [u8],
|
||||
pub bottom_texture: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Assets<'a> {
|
||||
pub fn get_instance() -> Self {
|
||||
let cube_obj: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/cube.obj");
|
||||
let cube_mtl: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/cube.mtl");
|
||||
let front_texture: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/front.png");
|
||||
let right_texture: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/right.png");
|
||||
let rear_texture: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/rear.png");
|
||||
let left_texture: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/left.png");
|
||||
let top_texture: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/top.png");
|
||||
let bottom_texture: &[u8] =
|
||||
include_bytes!("../../../assets/navigation_cube/bottom.png");
|
||||
|
||||
Self {
|
||||
cube_obj,
|
||||
cube_mtl,
|
||||
front_texture,
|
||||
right_texture,
|
||||
rear_texture,
|
||||
left_texture,
|
||||
top_texture,
|
||||
bottom_texture,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_asset(&self, file_name: &str) -> &[u8] {
|
||||
match file_name {
|
||||
"cube.obj" => self.cube_obj,
|
||||
"cube.mtl" => self.cube_mtl,
|
||||
"front.png" => self.front_texture,
|
||||
"right.png" => self.right_texture,
|
||||
"rear.png" => self.rear_texture,
|
||||
"left.png" => self.left_texture,
|
||||
"top.png" => self.top_texture,
|
||||
"bottom.png" => self.bottom_texture,
|
||||
_ => unreachable!(
|
||||
"An unknown asset: {} is trying to be loaded",
|
||||
file_name
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
@ -3,9 +3,12 @@
|
||||
mod draw_config;
|
||||
mod drawables;
|
||||
mod geometries;
|
||||
mod model;
|
||||
mod navigation_cube;
|
||||
mod pipelines;
|
||||
mod renderer;
|
||||
mod shaders;
|
||||
mod texture;
|
||||
mod transform;
|
||||
mod uniforms;
|
||||
mod vertices;
|
||||
|
226
crates/fj-viewer/src/graphics/model.rs
Normal file
226
crates/fj-viewer/src/graphics/model.rs
Normal file
@ -0,0 +1,226 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use tobj::LoadError;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use super::texture::{self, LoadTextureError};
|
||||
use crate::assets::Assets;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct ModelVertex {
|
||||
pub position: [f32; 3],
|
||||
pub tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
impl ModelVertex {
|
||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Model {
|
||||
pub meshes: Vec<Mesh>,
|
||||
pub materials: Vec<Material>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Material {
|
||||
pub name: String,
|
||||
pub diffuse_texture: super::texture::Texture,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mesh {
|
||||
pub name: String,
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub num_elements: u32,
|
||||
pub material: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LoadModelError {
|
||||
#[error("Object loading error")]
|
||||
ObjLoad(#[from] LoadError),
|
||||
|
||||
#[error("Load texture error")]
|
||||
Texture(#[from] LoadTextureError),
|
||||
}
|
||||
|
||||
pub fn load_model(
|
||||
file_name: &str,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> Result<Model, LoadModelError> {
|
||||
let assets = Assets::get_instance();
|
||||
|
||||
let (models, obj_materials) = tobj::load_obj_buf(
|
||||
&mut assets.get_asset(file_name),
|
||||
&tobj::LoadOptions {
|
||||
triangulate: true,
|
||||
single_index: true,
|
||||
..Default::default()
|
||||
},
|
||||
|p| {
|
||||
tobj::load_mtl_buf(
|
||||
&mut assets.get_asset(
|
||||
p.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.expect("OsStr could not be converted to a str"),
|
||||
),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut materials = Vec::new();
|
||||
for m in obj_materials? {
|
||||
let texture_data: &[u8] = assets.get_asset(m.diffuse_texture.as_str());
|
||||
|
||||
let diffuse_texture = texture::Texture::from_bytes(
|
||||
device,
|
||||
queue,
|
||||
texture_data,
|
||||
file_name,
|
||||
)?;
|
||||
|
||||
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,
|
||||
),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
materials.push(Material {
|
||||
name: m.name,
|
||||
diffuse_texture,
|
||||
bind_group,
|
||||
})
|
||||
}
|
||||
|
||||
let meshes = models
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
let vertices = (0..m.mesh.positions.len() / 3)
|
||||
.map(|i| 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],
|
||||
],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let vertex_buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("{file_name:?} Vertex Buffer")),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let index_buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("{file_name:?} Index Buffer")),
|
||||
contents: bytemuck::cast_slice(&m.mesh.indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
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 { meshes, materials })
|
||||
}
|
||||
|
||||
pub trait DrawModel<'a> {
|
||||
fn draw_mesh(&mut self, mesh: &'a Mesh, material: &'a Material);
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'a Mesh,
|
||||
material: &'a Material,
|
||||
instances: Range<u32>,
|
||||
);
|
||||
|
||||
fn draw_model(&mut self, model: &'a Model);
|
||||
fn draw_model_instanced(&mut self, model: &'a Model, instances: Range<u32>);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawModel<'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(&mut self, mesh: &'b Mesh, material: &'b Material) {
|
||||
self.draw_mesh_instanced(mesh, material, 0..1);
|
||||
}
|
||||
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
) {
|
||||
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.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_model(&mut self, model: &'b Model) {
|
||||
self.draw_model_instanced(model, 0..1);
|
||||
}
|
||||
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
let material = &model.materials[mesh.material];
|
||||
self.draw_mesh_instanced(mesh, material, instances.clone());
|
||||
}
|
||||
}
|
||||
}
|
220
crates/fj-viewer/src/graphics/navigation_cube.rs
Normal file
220
crates/fj-viewer/src/graphics/navigation_cube.rs
Normal file
@ -0,0 +1,220 @@
|
||||
use fj_math::Transform;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use super::model::{self, load_model, DrawModel, Model};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NavigationCubeRenderer {
|
||||
cube_model: Model,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
mvp_matrix_bind_group: wgpu::BindGroup,
|
||||
mvp_matrix_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
const SCALE_FACTOR: f64 = 0.13;
|
||||
const CUBE_TRANSLATION: [f64; 3] = [0.8, 0.7, 0.0];
|
||||
|
||||
impl NavigationCubeRenderer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
config: &wgpu::SurfaceConfiguration,
|
||||
aspect_ratio: f64,
|
||||
) -> Self {
|
||||
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,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: true,
|
||||
},
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::Filtering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group_layout"),
|
||||
});
|
||||
|
||||
let mvp_matrix =
|
||||
Self::get_mvp_matrix(Transform::identity(), aspect_ratio);
|
||||
|
||||
let mvp_matrix_buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Model Matrix Buffer"),
|
||||
contents: bytemuck::cast_slice(&[mvp_matrix]),
|
||||
usage: wgpu::BufferUsages::UNIFORM
|
||||
| wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let mvp_matrix_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("mvp_matrix_group_layout"),
|
||||
});
|
||||
let mvp_matrix_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &mvp_matrix_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: mvp_matrix_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("mvp_matrix_bind_group"),
|
||||
});
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Shadow Display Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
include_str!("navigation_cube.wgsl").into(),
|
||||
),
|
||||
});
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[
|
||||
&texture_bind_group_layout,
|
||||
&mvp_matrix_bind_group_layout,
|
||||
],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Navigation Cube Renderer"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vertex",
|
||||
buffers: &[model::ModelVertex::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fragment",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent::REPLACE,
|
||||
alpha: wgpu::BlendComponent::REPLACE,
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
// Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE
|
||||
// or Features::POLYGON_MODE_POINT
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
// Requires Features::DEPTH_CLIP_CONTROL
|
||||
unclipped_depth: false,
|
||||
// Requires Features::CONSERVATIVE_RASTERIZATION
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let cube_model =
|
||||
load_model("cube.obj", device, queue, &texture_bind_group_layout)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
cube_model,
|
||||
render_pipeline,
|
||||
mvp_matrix_bind_group,
|
||||
mvp_matrix_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
view: &wgpu::TextureView,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
queue: &wgpu::Queue,
|
||||
aspect_ratio: f64,
|
||||
rotation: Transform,
|
||||
) {
|
||||
let mvp_matrix = Self::get_mvp_matrix(rotation, aspect_ratio);
|
||||
queue.write_buffer(
|
||||
&self.mvp_matrix_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[mvp_matrix]),
|
||||
);
|
||||
|
||||
let mut render_pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Depth Visual Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.set_bind_group(1, &self.mvp_matrix_bind_group, &[]);
|
||||
render_pass.draw_model(&self.cube_model);
|
||||
}
|
||||
|
||||
fn get_mvp_matrix(rotation: Transform, aspect_ratio: f64) -> [f32; 16] {
|
||||
let scale = Transform::scale(SCALE_FACTOR);
|
||||
let world_translation = Transform::translation([0.0, 0.0, -1.0]);
|
||||
|
||||
let mut model_matrix = Transform::identity();
|
||||
model_matrix = model_matrix * world_translation;
|
||||
model_matrix = model_matrix * rotation;
|
||||
model_matrix = model_matrix * scale;
|
||||
|
||||
let perspective =
|
||||
nalgebra::Perspective3::new(aspect_ratio, 30.0, 0.1, 2.0);
|
||||
|
||||
let view_matrix = nalgebra::Matrix4::look_at_lh(
|
||||
&nalgebra::Point3::new(0.0, 0.0, 0.0),
|
||||
&nalgebra::Point3::new(0.0, 0.0, 1.0),
|
||||
&nalgebra::Vector3::new(0.0, -1.0, 0.0),
|
||||
);
|
||||
|
||||
let screen_translation = Transform::translation(CUBE_TRANSLATION);
|
||||
|
||||
let matrix = screen_translation.get_inner().matrix()
|
||||
* *perspective.to_projective().matrix()
|
||||
* view_matrix
|
||||
* model_matrix.get_inner().matrix();
|
||||
|
||||
let mut mat = [0.; 16];
|
||||
mat.copy_from_slice(matrix.as_slice());
|
||||
mat.map(|x| x as f32)
|
||||
}
|
||||
}
|
36
crates/fj-viewer/src/graphics/navigation_cube.wgsl
Normal file
36
crates/fj-viewer/src/graphics/navigation_cube.wgsl
Normal file
@ -0,0 +1,36 @@
|
||||
// Vertex shader
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> mvp_matrix: mat4x4<f32>;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vertex(
|
||||
model: VertexInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.clip_position = mvp_matrix * vec4<f32>(model.position, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0)@binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
}
|
@ -13,8 +13,9 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
draw_config::DrawConfig, drawables::Drawables, geometries::Geometries,
|
||||
pipelines::Pipelines, transform::Transform, uniforms::Uniforms,
|
||||
vertices::Vertices, DEPTH_FORMAT, SAMPLE_COUNT,
|
||||
navigation_cube::NavigationCubeRenderer, pipelines::Pipelines,
|
||||
transform::Transform, uniforms::Uniforms, vertices::Vertices, DEPTH_FORMAT,
|
||||
SAMPLE_COUNT,
|
||||
};
|
||||
|
||||
/// Graphics rendering state and target abstraction
|
||||
@ -34,6 +35,8 @@ pub struct Renderer {
|
||||
|
||||
geometries: Geometries,
|
||||
pipelines: Pipelines,
|
||||
|
||||
navigation_cube_renderer: NavigationCubeRenderer,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
@ -183,6 +186,13 @@ impl Renderer {
|
||||
let pipelines =
|
||||
Pipelines::new(&device, &bind_group_layout, color_format);
|
||||
|
||||
let navigation_cube_renderer = NavigationCubeRenderer::new(
|
||||
&device,
|
||||
&queue,
|
||||
&surface_config,
|
||||
800.0 / 600.0,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
surface,
|
||||
features,
|
||||
@ -198,6 +208,8 @@ impl Renderer {
|
||||
|
||||
geometries,
|
||||
pipelines,
|
||||
|
||||
navigation_cube_renderer,
|
||||
})
|
||||
}
|
||||
|
||||
@ -331,6 +343,14 @@ impl Renderer {
|
||||
gui.draw(&mut render_pass, &clipped_primitives, &screen_descriptor);
|
||||
}
|
||||
|
||||
self.navigation_cube_renderer.draw(
|
||||
&color_view,
|
||||
&mut encoder,
|
||||
&self.queue,
|
||||
aspect_ratio,
|
||||
camera.rotation,
|
||||
);
|
||||
|
||||
let command_buffer = encoder.finish();
|
||||
self.queue.submit(Some(command_buffer));
|
||||
|
||||
|
86
crates/fj-viewer/src/graphics/texture.rs
Normal file
86
crates/fj-viewer/src/graphics/texture.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use image::{GenericImageView, ImageError};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Texture {
|
||||
pub texture: wgpu::Texture,
|
||||
pub view: wgpu::TextureView,
|
||||
pub sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LoadTextureError {
|
||||
#[error("Image processing error")]
|
||||
ImageError(#[from] ImageError),
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn from_bytes(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bytes: &[u8],
|
||||
label: &str,
|
||||
) -> Result<Self, LoadTextureError> {
|
||||
let img = image::load_from_memory(bytes)?;
|
||||
Ok(Self::from_image(device, queue, &img, Some(label)))
|
||||
}
|
||||
|
||||
pub fn from_image(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
img: &image::DynamicImage,
|
||||
label: Option<&str>,
|
||||
) -> Self {
|
||||
let dimensions = img.dimensions();
|
||||
let rgba = img.to_rgba8();
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label,
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING
|
||||
| wgpu::TextureUsages::COPY_DST,
|
||||
});
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
&rgba,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: NonZeroU32::new(4 * dimensions.0),
|
||||
rows_per_image: NonZeroU32::new(dimensions.1),
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
||||
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::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Self {
|
||||
texture,
|
||||
view,
|
||||
sampler,
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod assets;
|
||||
mod camera;
|
||||
mod graphics;
|
||||
mod gui;
|
||||
|
Loading…
x
Reference in New Issue
Block a user