1use crate::{
2 assets::{self, TypeModel},
3 renderer,
4};
5use {
6 cgmath::{Vector2, Vector3},
7 std::io::{BufReader, Cursor},
8 wgpu::{BindGroup, Buffer, util::DeviceExt},
9};
10
11#[cfg(not(target_arch = "wasm32"))]
12use std::path::{Path, PathBuf};
13
14#[derive(Clone)]
15pub struct Asset {
16 pub out_dir: String,
17}
18
19impl Default for Asset {
20 fn default() -> Self {
21 Self {
22 out_dir: "res".to_string(),
23 }
24 }
25}
26
27impl Asset {
28 pub fn new(out_dir: String) -> Self {
29 Self { out_dir }
30 }
31}
32
33pub trait AssetSource: Send + Sync {
34 #[cfg(target_arch = "wasm32")]
35 fn format_url(&self, file_name: &str) -> reqwest::Url;
36 #[cfg(not(target_arch = "wasm32"))]
37 fn resolve_resource_path(&self, file_name: &str) -> anyhow::Result<PathBuf>;
38 fn load_string(&self, file_name: &str) -> impl Future<Output = anyhow::Result<String>> + Send;
39 fn load_binary(&self, file_name: &str) -> impl Future<Output = anyhow::Result<Vec<u8>>> + Send;
40}
41
42impl AssetSource for Asset {
43 #[cfg(target_arch = "wasm32")]
44 fn format_url(&self, file_name: &str) -> reqwest::Url {
45 let base = reqwest::Url::parse(self.out_dir).unwrap();
46 base.join(file_name).unwrap()
47 }
48
49 #[cfg(not(target_arch = "wasm32"))]
50 fn resolve_resource_path(&self, file_name: &str) -> anyhow::Result<PathBuf> {
51 let out_dir_path = Path::new(self.out_dir.as_str()).join(file_name);
52 if out_dir_path.is_file() {
53 return Ok(out_dir_path);
54 }
55
56 if let Ok(exe) = std::env::current_exe()
57 && let Some(exe_dir) = exe.parent()
58 {
59 let exe_path = exe_dir.join("res").join(file_name);
60 if exe_path.is_file() {
61 return Ok(exe_path);
62 }
63 }
64
65 anyhow::bail!("Recurso no encontrado: '{file_name}'")
66 }
67
68 async fn load_string(&self, file_name: &str) -> anyhow::Result<String> {
69 log::debug!("Open: {:?}", file_name);
70
71 #[cfg(target_arch = "wasm32")]
72 let text = {
73 let url = self.format_url(file_name);
74 log::debug!("[load_string] GET {}", url);
75 let resp = reqwest::get(url.clone()).await.map_err(|e| {
76 log::error!("[load_string] fetch failed for '{}': {}", url, e);
77 e
78 })?;
79 if !resp.status().is_success() {
80 log::error!("[load_string] HTTP {} for '{}'", resp.status(), url);
81 anyhow::bail!("HTTP {} fetching '{}'", resp.status(), url);
82 }
83 resp.text().await?
84 };
85
86 #[cfg(not(target_arch = "wasm32"))]
87 let text: String = {
88 let path: PathBuf = self.resolve_resource_path(file_name).map_err(|e| {
89 log::error!("[load_string] resource not found '{}': {}", file_name, e);
90 e
91 })?;
92
93 log::debug!("[load_string] resolved '{}' -> {:?}", file_name, path);
94 std::fs::read_to_string(path)?
95 };
96
97 Ok(text)
98 }
99
100 async fn load_binary(&self, file_name: &str) -> anyhow::Result<Vec<u8>> {
101 #[cfg(target_arch = "wasm32")]
102 let data = {
103 let url = self.format_url(file_name);
104 log::debug!("[load_binary] GET {}", url);
105 let resp = reqwest::get(url.clone()).await.map_err(|e| {
106 log::error!("[load_binary] fetch failed for '{}': {}", url, e);
107 e
108 })?;
109 if !resp.status().is_success() {
110 log::error!("[load_binary] HTTP {} for '{}'", resp.status(), url);
111 anyhow::bail!("HTTP {} fetching '{}'", resp.status(), url);
112 }
113 resp.bytes().await?.to_vec()
114 };
115
116 #[cfg(not(target_arch = "wasm32"))]
117 let data = {
118 let path: PathBuf = self.resolve_resource_path(file_name).map_err(|e| {
119 log::error!("[load_binary] resource not found '{}': {}", file_name, e);
120 e
121 })?;
122
123 std::fs::read(path)?
124 };
125
126 Ok(data)
127 }
128}
129
130pub fn load_texture(
131 data: Vec<u8>,
132 file_name: &str,
133 device: &wgpu::Device,
134 queue: &wgpu::Queue,
135 is_normal_map: bool,
136) -> anyhow::Result<renderer::Texture> {
137 renderer::Texture::from_bytes(device, queue, &data, file_name, is_normal_map)
138}
139
140pub async fn load_model(
141 asset: &impl AssetSource,
142 file_name: &str,
143 device: &wgpu::Device,
144 queue: &wgpu::Queue,
145 layout: &wgpu::BindGroupLayout,
146 instances: Vec<assets::Instance>,
147 type_model: TypeModel,
148) -> anyhow::Result<assets::Model> {
149 let model_binary: Vec<u8> = asset.load_binary(file_name).await?;
150 let mut obj_reader = BufReader::new(Cursor::new(model_binary));
151
152 let parent_path: String = std::path::Path::new(file_name)
153 .parent()
154 .and_then(|p| p.to_str())
155 .unwrap_or("")
156 .to_string();
157
158 let (models, obj_materials): (
159 Vec<tobj::Model>,
160 Result<Vec<tobj::Material>, tobj::LoadError>,
161 ) = tobj::load_obj_buf_async(
162 &mut obj_reader,
163 &tobj::LoadOptions {
164 triangulate: true,
165 single_index: true,
166 ..Default::default()
167 },
168 |mtl_path| {
169 let parent_path: String = parent_path.clone();
171 async move {
172 let full_mtl_path: String = if parent_path.is_empty() {
173 mtl_path.clone()
174 } else {
175 std::path::Path::new(&parent_path)
176 .join(&mtl_path)
177 .to_str()
178 .unwrap()
179 .to_string()
180 };
181 log::debug!("Buscando mtl: {}", full_mtl_path);
182 let mat_text: String = asset.load_string(&full_mtl_path).await.unwrap();
183 tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text)))
184 }
185 },
186 )
187 .await?;
188
189 let mut materials: Vec<renderer::Material> = Vec::new();
190 for m in obj_materials? {
191 let full_diffuse_path: String = if parent_path.is_empty() {
192 m.diffuse_texture.clone()
193 } else {
194 std::path::Path::new(&parent_path)
195 .join(&m.diffuse_texture)
196 .to_str()
197 .unwrap()
198 .to_string()
199 };
200
201 let diffuse_binary = asset.load_binary(&full_diffuse_path).await?;
202 let diffuse_texture: renderer::Texture =
203 load_texture(diffuse_binary, &full_diffuse_path, device, queue, false)?;
204
205 let normal_texture: renderer::Texture = if m.normal_texture.is_empty() {
206 renderer::Texture::default_normal(device, queue)
207 } else {
208 let full_normal_path: String = if parent_path.is_empty() {
209 m.normal_texture.clone()
210 } else {
211 std::path::Path::new(&parent_path)
212 .join(&m.normal_texture)
213 .to_str()
214 .unwrap()
215 .to_string()
216 };
217 let texture_binary: Vec<u8> = asset.load_binary(&full_normal_path).await?;
218 load_texture(texture_binary, &full_normal_path, device, queue, true)?
219 };
220
221 let bind_group: BindGroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
222 layout,
223 entries: &[
224 wgpu::BindGroupEntry {
225 binding: 0,
226 resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
227 },
228 wgpu::BindGroupEntry {
229 binding: 1,
230 resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
231 },
232 wgpu::BindGroupEntry {
233 binding: 2,
234 resource: wgpu::BindingResource::TextureView(&normal_texture.view),
235 },
236 wgpu::BindGroupEntry {
237 binding: 3,
238 resource: wgpu::BindingResource::Sampler(&normal_texture.sampler),
239 },
240 ],
241 label: None,
242 });
243
244 materials.push(renderer::Material {
245 name: m.name,
246 diffuse_texture,
247 normal_texture,
248 bind_group,
249 });
250 }
251
252 let meshes: Vec<renderer::Mesh> = models
253 .into_iter()
254 .map(|m| {
255 let has_colors: bool = !m.mesh.vertex_color.is_empty();
256 let has_normals: bool = !m.mesh.normals.is_empty();
257 let has_texcoords: bool = !m.mesh.texcoords.is_empty();
258 let has_normal_indices: bool = !m.mesh.normal_indices.is_empty();
259
260 let mut vertices: Vec<assets::ModelVertex> = (0..m.mesh.positions.len() / 3)
261 .map(|i| {
262 let pi = i * 3;
263
264 let position = [
265 m.mesh.positions[pi],
266 m.mesh.positions[pi + 1],
267 m.mesh.positions[pi + 2],
268 ];
269
270 let text_cords = if has_texcoords {
271 [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]]
272 } else {
273 [0.0, 0.0]
274 };
275
276 let normal = if has_normals {
277 let ni = if has_normal_indices {
278 m.mesh.normal_indices[i] as usize * 3
279 } else {
280 pi
281 };
282 [
283 m.mesh.normals[ni],
284 m.mesh.normals[ni + 1],
285 m.mesh.normals[ni + 2],
286 ]
287 } else {
288 [0.0, 0.0, 0.0]
289 };
290
291 let color = if has_colors {
292 [
293 m.mesh.vertex_color[pi],
294 m.mesh.vertex_color[pi + 1],
295 m.mesh.vertex_color[pi + 2],
296 ]
297 } else {
298 [1.0, 1.0, 1.0]
299 };
300
301 let tangent: [f32; 3] = [0.0, 0.0, 0.0];
302
303 let bitangent: [f32; 3] = [0.0, 0.0, 0.0];
304
305 assets::ModelVertex {
306 position,
307 text_cords,
308 normal,
309 color,
310 tangent,
311 bitangent,
312 }
313 })
314 .collect::<Vec<_>>();
315
316 let indices: &Vec<u32> = &m.mesh.indices;
317 let mut trangles_included = vec![0; vertices.len()];
318
319 for c in indices.chunks(3) {
320 let v0: assets::ModelVertex = vertices[c[0] as usize];
321 let v1: assets::ModelVertex = vertices[c[1] as usize];
322 let v2: assets::ModelVertex = vertices[c[2] as usize];
323
324 let pos0: Vector3<f32> = v0.position.into();
325 let pos1: Vector3<f32> = v1.position.into();
326 let pos2: Vector3<f32> = v2.position.into();
327
328 let uv0: Vector2<f32> = v0.text_cords.into();
329 let uv1: Vector2<f32> = v1.text_cords.into();
330 let uv2: Vector2<f32> = v2.text_cords.into();
331
332 let delta_pos1: Vector3<f32> = pos1 - pos0;
334 let delta_pos2: Vector3<f32> = pos2 - pos0;
335
336 let delta_uv1: Vector2<f32> = uv1 - uv0;
339 let delta_uv2: Vector2<f32> = uv2 - uv0;
340
341 let r: f32 = 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv1.y * delta_uv2.x);
347 let tanget: Vector3<f32> =
348 (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
349 let bitanget: Vector3<f32> =
350 (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * -r;
351
352 vertices[c[0] as usize].tangent =
354 (tanget + Vector3::from(vertices[c[0] as usize].tangent)).into();
355 vertices[c[1] as usize].tangent =
356 (tanget + Vector3::from(vertices[c[1] as usize].tangent)).into();
357 vertices[c[2] as usize].tangent =
358 (tanget + Vector3::from(vertices[c[2] as usize].tangent)).into();
359
360 vertices[c[0] as usize].bitangent =
362 (bitanget + Vector3::from(vertices[c[0] as usize].bitangent)).into();
363 vertices[c[1] as usize].bitangent =
364 (bitanget + Vector3::from(vertices[c[1] as usize].bitangent)).into();
365 vertices[c[2] as usize].bitangent =
366 (bitanget + Vector3::from(vertices[c[2] as usize].bitangent)).into();
367
368 trangles_included[c[0] as usize] += 1;
370 trangles_included[c[1] as usize] += 1;
371 trangles_included[c[2] as usize] += 1;
372 }
373
374 for (i, n) in trangles_included.into_iter().enumerate() {
376 let denom: f32 = 1.0 / n as f32;
377 let v: &mut assets::ModelVertex = &mut vertices[i];
378 v.tangent = (Vector3::from(v.tangent) * denom).into();
379 v.bitangent = (Vector3::from(v.bitangent) * denom).into();
380 }
381
382 let vertex_buffer: Buffer =
383 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
384 label: Some(&format!("{:?} Vertex Buffer", file_name)),
385 contents: bytemuck::cast_slice(&vertices),
386 usage: wgpu::BufferUsages::VERTEX,
387 });
388
389 let index_buffer: Buffer =
390 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
391 label: Some(&format!("{:?} Index Buffer", file_name)),
392 contents: bytemuck::cast_slice(&m.mesh.indices),
393 usage: wgpu::BufferUsages::INDEX,
394 });
395
396 renderer::Mesh {
397 name: file_name.to_string(),
398 vertex_buffer,
399 index_buffer,
400 material: m.mesh.material_id.unwrap_or(0),
401 indices: m.mesh.indices.len() as u32,
402 }
403 })
404 .collect::<Vec<_>>();
405
406 let instances_raws: Vec<assets::InstanceRaw> = instances.iter().map(|i| i.to_raw()).collect();
407
408 let label: String = format!("{file_name}_instance_buffer");
409 let instance_buffer: Buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
410 label: Some(&label),
411 contents: bytemuck::cast_slice(&instances_raws),
412 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
413 });
414
415 Ok(assets::Model {
416 meshes,
417 materials,
418 instances,
419 instance_buffer,
420 type_model,
421 })
422}