libredr_common/
geometry.rs

1use std::path::Path;
2use std::collections::HashSet;
3use chrono::Utc;
4use tracing::info;
5use blake3::Hasher;
6use nalgebra as na;
7use anyhow::{Result, ensure, bail};
8use serde::{Deserialize, Serialize};
9use ndarray::{prelude::*, Slice, concatenate};
10use tobj::{load_obj, GPU_LOAD_OPTIONS};
11use super::message::*;
12
13#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
14struct TriMesh {
15  /// 4 * 4 homogeneous transformation matrix for vertex coordinate
16  transform_v: Array2<f32>,
17  /// 3 * 3 homogeneous transformation matrix for vertex texture coordinate
18  transform_vt: Array2<f32>,
19  /// Hash of TriMeshData
20  trimesh_data: Hash,
21}
22
23fn area_cross(uv_a: na::Vector2<f32>, uv_b: na::Vector2<f32>, uv_o: na::Vector2<f32>) -> f32 {
24	(uv_a[0] - uv_o[0]) * (uv_b[1] - uv_o[1]) - (uv_a[1] - uv_o[1]) * (uv_b[0] - uv_o[0])
25}
26
27fn dist_dot(uv_a: na::Vector2<f32>, uv_o: na::Vector2<f32>) -> f32 {
28	f32::sqrt((uv_a - uv_o).dot(&(uv_a - uv_o)))
29}
30
31/// Generate a uv_xyz map from a combined geometry
32///
33/// xyz coordinate + padding distance
34pub fn uv_xyz(vertex: ArrayView3<f32>, texture_resolution: usize, padding: usize) -> Result<Array3<f32>> {
35  let size_geometry = vertex.shape()[2];
36  assert_eq!(vertex.shape(), &[8, 3, size_geometry]);
37  let mut xyz = Array3::zeros((4, texture_resolution, texture_resolution));
38  xyz.index_axis_mut(Axis(0), 3).fill(padding as f32);
39  for vertex in vertex.axis_iter(Axis(2)) {
40    // 3 rows for 3 vertex
41    let v_curr = vertex.slice_axis(Axis(0), Slice::from(0..3)).flatten().to_vec();
42    let v_curr = na::Matrix3::<f32>::from_vec(v_curr);
43    let vt_curr = vertex.slice_axis(Axis(0), Slice::from(6..8)).flatten().to_vec();
44    let vt_curr = texture_resolution as f32 * na::Matrix3x2::<f32>::from_vec(vt_curr);
45    // dbg!((v_curr, vt_curr));
46    let area = area_cross(vt_curr.row(1).transpose(), vt_curr.row(2).transpose(), vt_curr.row(0).transpose());
47    if f32::abs(area) < 1e-12 {
48      continue;
49    }
50    let u_min = f32::max(0., vt_curr.column(0).min() - padding as f32) as usize;
51    let u_max = f32::min(texture_resolution as f32, vt_curr.column(0).max() + padding as f32 + 1.) as usize;
52    let v_min = f32::max(0., vt_curr.column(1).min() - padding as f32) as usize;
53    let v_max = f32::min(texture_resolution as f32, vt_curr.column(1).max() + padding as f32 + 1.) as usize;
54    for u in u_min..u_max {
55      for v in v_min..v_max {
56        let xyz_index = (texture_resolution - 1 - v, u);
57        let center = na::Vector2::new(u as f32 + 0.5, v as f32 + 0.5);
58        let area_0 = area_cross(center, vt_curr.row(2).transpose(), vt_curr.row(1).transpose());
59        let dist_0 = area_0 / dist_dot(vt_curr.row(2).transpose(), vt_curr.row(1).transpose());
60        let area_1 = area_cross(center, vt_curr.row(0).transpose(), vt_curr.row(2).transpose());
61        let dist_1 = area_1 / dist_dot(vt_curr.row(0).transpose(), vt_curr.row(2).transpose());
62        let area_2 = area_cross(center, vt_curr.row(1).transpose(), vt_curr.row(0).transpose());
63        let dist_2 = area_2 / dist_dot(vt_curr.row(1).transpose(), vt_curr.row(0).transpose());
64        let w = f32::max(1e-6, f32::min(1. - 1e-6, -area_2 / area));
65        let v = f32::max(1e-6, f32::min(1. - 1e-6 - w, -area_1 / area));
66        let u = 1. - 1e-6 - w - v;
67        let dist_max = f32::max(dist_0, f32::max(dist_1, dist_2));
68        let xyz_curr = v_curr.row(0) * u + v_curr.row(1) * v + v_curr.row(2) * w;
69        if xyz[(3, xyz_index.0, xyz_index.1)] > dist_max {
70          xyz[(0, xyz_index.0, xyz_index.1)] = xyz_curr[0];
71          xyz[(1, xyz_index.0, xyz_index.1)] = xyz_curr[1];
72          xyz[(2, xyz_index.0, xyz_index.1)] = xyz_curr[2];
73          xyz[(3, xyz_index.0, xyz_index.1)] = dist_max;
74        }
75      }
76    }
77    // dbg!((u_min, u_max, v_min, v_max));
78    // break;
79  }
80  // {
81  //   use std::io::Write as _;
82  //   let mut debug = std::fs::File::create("debug_xyz.bin")?;
83  //   for val in xyz.iter() {
84  //     debug.write_all(&val.to_le_bytes())?;
85  //   }
86  // }
87  Ok(xyz)
88}
89
90/// Rust interface for Geometry
91///
92/// Trimeshes in scene geometry are lazy-loaded
93#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
94pub struct Geometry {
95  trimesh: Vec<TriMesh>,
96}
97
98impl Default for Geometry {
99  fn default() -> Self {
100    Geometry::new()
101  }
102}
103
104impl Geometry {
105  /// Construct an empty `Geometry`
106  pub fn new() -> Self {
107    Geometry {
108      trimesh: Vec::new(),
109    }
110  }
111
112  /// Number of all faces in `Geometry`
113  pub fn size(&self, data_cache: &DataCache) -> Result<usize> {
114    let mut size = 0;
115    let data_cache = data_cache.lock().expect("No task should panic");
116    for trimesh_data_hash in self.required_data() {
117      data_cache.get(&trimesh_data_hash).map_or_else(|| {
118        bail!("Geometry::size: Hash {trimesh_data_hash} not found");
119      }, |entry| {
120        let data = &entry.1;
121        if let Data::TriMeshData(vertex) = data {
122          size += vertex.shape()[2];
123        } else {
124          bail!("Geometry::size: Wrong data {trimesh_data_hash} {data}");
125        }
126        Ok(())
127      })?;
128    }
129    Ok(size)
130  }
131
132  /// Calculate hash for lazy-loading
133  ///
134  /// Currently use 32-byte long hash from BLAKE3 algorithm.
135  pub fn hash(&self) -> Hash {
136    let mut hasher = Hasher::new();
137    let msg = postcard::to_stdvec(self).expect("Internal Data Struct");
138    hasher.update(&msg);
139    Hash(*hasher.finalize().as_bytes())
140  }
141
142  /// Get all hashes of lazy-loading `Data`
143  ///
144  /// This includes all `v`, `vn`, and `vt`
145  pub fn required_data(&self) -> HashSet<Hash> {
146    HashSet::from_iter(self.trimesh.iter().map(|trimesh| trimesh.trimesh_data.to_owned()))
147  }
148
149  /// Add a trimesh to [`Geometry`] from hashed `vertex`
150  ///
151  /// # Arguments
152  /// * `vertex_hash` - Vertex hash, generated from previous [`Self::add_trimesh`] or [`Self::add_obj`]
153  /// * `transform_v` - Homogeneous transformation matrix for vertex coordinate 4 * 4
154  /// * `transform_vt` - Homogeneous transformation matrix for vertex texture coordinate 3 * 3
155  ///
156  /// `vertex` is hashed and cached for better performance
157  /// Return `vertex` hash for later use in `add_vertex_hash`
158  pub fn add_vertex_hash (
159      &mut self,
160      vertex_hash: Hash,
161      transform_v: Array2<f32>,
162      transform_vt: Array2<f32>,
163      data_cache: &DataCache) -> Result<Hash> {
164    {
165      let data_cache = data_cache.lock().expect("No task should panic");
166      ensure!(data_cache.contains_key(&vertex_hash),
167        "Geometry::add_vertex_hash: vertex hash {vertex_hash} doesn't exist in data_cache");
168    }
169    let trimesh = TriMesh {
170      transform_v,
171      transform_vt,
172      trimesh_data: vertex_hash.to_owned(),
173    };
174    self.trimesh.push(trimesh);
175    Ok(vertex_hash)
176  }
177
178  /// Add a trimesh to [`Geometry`]
179  ///
180  /// # Arguments
181  /// * `vertex` - Vertex coordinate, normal, and texture coordinate (3 + 3 + 2) * 3 * N
182  /// * `transform_v` - Homogeneous transformation matrix for vertex coordinate 4 * 4
183  /// * `transform_vt` - Homogeneous transformation matrix for vertex texture coordinate 3 * 3
184  ///
185  /// `vertex` is hashed and cached for better performance
186  /// Return `vertex` hash for later use in [`Self::add_vertex_hash`]
187  pub fn add_trimesh(
188      &mut self,
189      vertex: Array3<f32>,
190      transform_v: Array2<f32>,
191      transform_vt: Array2<f32>,
192      data_cache: &DataCache) -> Result<Hash> {
193    let nf = vertex.shape()[2];
194    ensure!(vertex.shape() == [8, 3, nf], "Geometry::add_trimesh: vertex.shape {:?}, expected {:?}",
195      vertex.shape(), [8, 3, nf]);
196    ensure!(transform_v.shape() == [4, 4], "Geometry::add_trimesh: transform_v.shape {:?}, expected {:?}",
197      transform_v.shape(), [4, 4]);
198    ensure!(transform_vt.shape() == [3, 3], "Geometry::add_trimesh: transform_vt.shape {:?}, expected {:?}",
199      transform_vt.shape(), [3, 3]);
200    let trimesh_data = Data::TriMeshData(vertex);
201    let trimesh_data_hash = trimesh_data.hash();
202    {
203      let mut data_cache = data_cache.lock().expect("No task should panic");
204      data_cache.insert(trimesh_data_hash.to_owned(), (Utc::now().timestamp(), trimesh_data));
205    }
206    self.add_vertex_hash(trimesh_data_hash, transform_v, transform_vt, data_cache)
207  }
208
209  /// Load Wavefront .obj trimesh to [`Geometry`]
210  ///
211  /// # Arguments
212  /// * `filename` - Wavefront .obj file path
213  /// * `transform_v` - Homogeneous transformation matrix for vertex coordinate 4 * 4
214  /// * `transform_vt` - Homogeneous transformation matrix for vertex texture coordinate 3 * 3
215  ///
216  /// `vertex` is hashed and cached for better performance
217  /// Return `vertex` hash for later use in `add_vertex_hash`
218  pub fn add_obj(
219      &mut self,
220      filename: &Path,
221      transform_v: Array2<f32>,
222      transform_vt: Array2<f32>,
223      data_cache: &DataCache) -> Result<Hash> {
224    info!("Geometry::add_obj: loading {}", filename.display());
225    let obj = load_obj(filename, &GPU_LOAD_OPTIONS)?.0;
226    let mut vertex: Array3<f32> = Array3::default((8, 3, 0));
227    for model in obj {
228      let mesh = model.mesh;
229      ensure!(!mesh.indices.is_empty(), "Geometry::add_obj: obj file {} is empty", filename.display());
230      ensure!(!mesh.positions.is_empty(), "Geometry::add_obj: obj file {} is empty", filename.display());
231      ensure!(!mesh.normals.is_empty(), "Geometry::add_obj: obj file {} has no vertex normal",
232        filename.display());
233      ensure!(!mesh.texcoords.is_empty(), "Geometry::add_obj: obj file {} has no texture coordinate",
234        filename.display());
235      // Number of faces
236      let nf = mesh.indices.len() / 3;
237      // Number of vertices
238      let nv = mesh.positions.len() / 3;
239      let mesh_indices = Array::from_shape_vec((nf, 3), mesh.indices)?;
240      let mesh_positions = Array::from_shape_vec((nv, 3), mesh.positions)?;
241      let mesh_normals = Array::from_shape_vec((nv, 3), mesh.normals)?;
242      let mesh_texcoords = Array::from_shape_vec((nv, 2), mesh.texcoords)?;
243      for indices in mesh_indices.axis_iter(Axis(0)) {
244        let mut curr_vertex: Array2<f32> = Array2::default((8, 0));
245        for j in 0..3 {
246          // Don't transform now for better data cache
247          curr_vertex.push(Axis(1), concatenate![Axis(0),
248            mesh_positions.row(indices[j] as usize),
249            mesh_normals.row(indices[j] as usize),
250            mesh_texcoords.row(indices[j] as usize)].view())?;
251        }
252        vertex.push(Axis(2), curr_vertex.view())?;
253      }
254    }
255    self.add_trimesh(vertex, transform_v, transform_vt, data_cache)
256  }
257
258  /// Apply transform and combine all trimeshes
259  pub fn combine(&self, data_cache: &DataCache) -> Result<Array3<f32>> {
260    let curr_timestamp = Utc::now().timestamp();
261    let mut vertex: Array3<f32> = Array3::default((8, 3, 0));
262    for trimesh in &self.trimesh {
263      let mut data_cache = data_cache.lock().expect("No task should panic");
264      let trimesh_data = data_cache.get_mut(&trimesh.trimesh_data).map_or_else(|| {
265        bail!("Geometry::combine: Hash {} not found", trimesh.trimesh_data)
266      }, |trimesh_data| {
267        trimesh_data.0 = curr_timestamp;
268        if let Data::TriMeshData(trimesh_data) = &trimesh_data.1 {
269          Ok(trimesh_data)
270        } else {
271          bail!("Geometry::combine: Wrong data for `trimesh`: {}", &trimesh_data.1);
272        }
273      })?;
274      let nf = trimesh_data.shape()[2];
275      ensure!(trimesh_data.shape() == [8, 3, nf],
276        "Geometry::combine: Wrong data shape {:?}, expected {:?}",
277        trimesh_data.shape(), [8, 3, nf]);
278      let mut v = trimesh_data.slice(s![..3, .., ..]).to_shape([3, 3 * nf])?.into_owned();
279      v.push(Axis(0), Array1::ones(3 * nf).view())?;
280      let v = trimesh.transform_v.dot(&v).slice(s![..3, ..]).to_shape([3, 3, nf])?.into_owned();
281      let vn = trimesh_data.slice(s![3..6, .., ..]).to_shape([3, 3 * nf])?.into_owned();
282      let vn = trimesh.transform_v.slice(s![..3, ..3]).dot(&vn).to_shape([3, 3, nf])?.into_owned();
283      let mut vt = trimesh_data.slice(s![6..8, .., ..]).to_shape([2, 3 * nf])?.into_owned();
284      vt.push(Axis(0), Array1::ones(3 * nf).view())?;
285      let vt = trimesh.transform_vt.dot(&vt).slice(s![..2, ..]).to_shape([2, 3, nf])?.into_owned();
286      vertex.append(Axis(2), concatenate![Axis(0), v, vn, vt].view())?;
287    }
288    Ok(vertex)
289  }
290
291  /// Calculate uv_xyz of the combined geometry and cache the result
292  pub fn uv_xyz_cached(&self, texture_resolution: usize, padding: usize, data_cache: &DataCache) -> Result<Array3<f32>> {
293    let mut hasher = Hasher::new();
294    let msg = postcard::to_stdvec(&(&self, texture_resolution, padding)).expect("Internal Data Struct");
295    hasher.update(&msg);
296    let hash = Hash(*hasher.finalize().as_bytes());
297    {
298      let mut data_cache = data_cache.lock().expect("No task should panic");
299      if let Some(data) = data_cache.get_mut(&hash) {
300        data.0 = Utc::now().timestamp();
301        if let Data::TriMeshUVXYZ(uv_xyz) = &data.1 {
302          return Ok(uv_xyz.to_owned());
303        } else {
304          bail!("Geometry::uv_xyz_cached: Wrong data for `uv_xyz`: {}", &data.1);
305        }
306      }
307    }
308    let vertex = self.combine(data_cache)?.as_standard_layout().into_owned();
309    let uv_xyz = uv_xyz(vertex.view(), texture_resolution, padding)?;
310    {
311      let mut data_cache = data_cache.lock().expect("No task should panic");
312      data_cache.insert(hash, (Utc::now().timestamp(), Data::TriMeshUVXYZ(uv_xyz.to_owned())));
313    }
314    Ok(uv_xyz)
315  }
316}