flow_ngin/resources/
texture.rs1use std::io::{BufReader, Cursor};
2
3use crate::data_structures::{model, texture};
4
5pub fn diffuse_normal_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
6 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
7 entries: &[
8 wgpu::BindGroupLayoutEntry {
9 binding: 0,
10 visibility: wgpu::ShaderStages::FRAGMENT,
11 ty: wgpu::BindingType::Texture {
12 multisampled: false,
13 view_dimension: wgpu::TextureViewDimension::D2,
14 sample_type: wgpu::TextureSampleType::Float { filterable: true },
15 },
16 count: None,
17 },
18 wgpu::BindGroupLayoutEntry {
19 binding: 1,
20 visibility: wgpu::ShaderStages::FRAGMENT,
21 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
22 count: None,
23 },
24 wgpu::BindGroupLayoutEntry {
25 binding: 2,
26 visibility: wgpu::ShaderStages::FRAGMENT,
27 ty: wgpu::BindingType::Texture {
28 multisampled: false,
29 sample_type: wgpu::TextureSampleType::Float { filterable: true },
30 view_dimension: wgpu::TextureViewDimension::D2,
31 },
32 count: None,
33 },
34 wgpu::BindGroupLayoutEntry {
35 binding: 3,
36 visibility: wgpu::ShaderStages::FRAGMENT,
37 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
38 count: None,
39 },
40 ],
41 label: Some("Model texture_bind_group_layout"),
42 })
43}
44
45#[cfg(target_arch = "wasm32")]
46pub fn format_url(file_name: &str) -> reqwest::Url {
47 let window = web_sys::window().unwrap();
48 let location = window.location();
49 let mut origin = location.origin().unwrap();
50 if !origin.ends_with("learn-wgpu") {
51 origin = format!("{}/assets", origin);
52 }
53 let base = reqwest::Url::parse(&format!("{}/", origin,)).unwrap();
54 base.join(file_name).unwrap()
55}
56
57pub async fn load_string(file_name: &str) -> anyhow::Result<String> {
58 #[cfg(target_arch = "wasm32")]
59 let txt = {
60 let url = format_url(file_name);
61 reqwest::get(url).await?.text().await?
62 };
63 #[cfg(not(target_arch = "wasm32"))]
64 let txt = {
65 let path = std::path::Path::new("./")
67 .join("assets")
68 .join(file_name);
69 tokio::fs::read_to_string(path).await?
70 };
71
72 Ok(txt)
73}
74
75pub async fn load_binary(file_name: &str) -> anyhow::Result<Vec<u8>> {
76 #[cfg(target_arch = "wasm32")]
77 let data = {
78 let url = format_url(file_name);
79 reqwest::get(url).await?.bytes().await?.to_vec()
80 };
81 #[cfg(not(target_arch = "wasm32"))]
82 let data = {
84 let path = std::path::Path::new("./")
86 .join("assets")
87 .join(file_name);
88 tokio::fs::read(path).await?
89 };
90
91 Ok(data)
92}
93
94pub async fn load_texture(
95 file_name: &str,
96 is_normal_map: bool,
97 device: &wgpu::Device,
98 queue: &wgpu::Queue,
99 format: Option<&str>,
100) -> anyhow::Result<texture::Texture> {
101 let data = load_binary(file_name).await?;
102 texture::Texture::from_bytes(device, queue, &data, file_name, format, is_normal_map)
103}
104
105pub async fn load_textures(
106 file_name: &str,
107 queue: &wgpu::Queue,
108 device: &wgpu::Device,
109 layout: &wgpu::BindGroupLayout,
110) -> anyhow::Result<(Vec<model::Material>, Vec<tobj::Model>)> {
111 let obj_text: String = load_string(file_name).await?;
112 let obj_cursor = Cursor::new(obj_text);
114 let mut obj_reader = BufReader::new(obj_cursor);
115
116 let (models, obj_materials) = tobj::load_obj_buf_async(
117 &mut obj_reader,
118 &tobj::LoadOptions {
119 triangulate: true,
120 single_index: true,
121 ..Default::default()
122 },
123 |p| async move {
124 let mat_text = load_string(&p)
125 .await
126 .expect(format!("Material Texture not found for {p}.").as_str());
127 tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text)))
128 },
129 )
130 .await?;
131
132 let mut materials = Vec::new();
134 for m in obj_materials? {
135 if let Some(m_diffuse_texture) = &m.diffuse_texture {
136 let diffuse_texture =
137 load_texture(&m_diffuse_texture, false, device, queue, None).await?;
138 let normal_texture = match &m.normal_texture {
139 Some(m_normal_texture) => {
140 load_texture(&m_normal_texture, true, device, queue, None).await?
141 },
142 None => texture::Texture::create_default_normal_map(1, 1, device, queue)
143 };
144 if let Ok(model) = model::Material::new(
145 device,
146 &m.name,
147 diffuse_texture,
148 normal_texture,
149 layout,
150 ) {
151 materials.push(model);
152 } else {
153 log::warn!("Failed to create material for mtl ({}) in obj ({})", m.name, file_name);
154 }
155 } else {
156 log::error!("This material's mtl ({file_name}) references no texture.");
157 }
158 }
159 Ok((materials, models))
160}