use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
use earcutr::earcut;
use geo::Polygon;
#[derive(Debug, Clone)]
pub struct MeshGroup {
pub indices: Vec<[usize; 3]>,
pub name: &'static str,
}
#[derive(Debug, Clone)]
pub struct Mesh3D {
pub vertices: Vec<[f64; 3]>,
pub uvs: Vec<[f64; 2]>,
pub faces: Vec<MeshGroup>,
}
impl Mesh3D {
pub fn export_obj(&self, obj_path: &Path, mtl_path: &Path, front_texture: &str, back_texture: &str, side_texture: &str) -> std::io::Result<()> {
self.export_mtl(mtl_path, front_texture, back_texture, side_texture)?;
let file = File::create(obj_path)?;
let mut writer = BufWriter::new(file);
let mtl_filename = mtl_path.file_name().unwrap().to_string_lossy();
writeln!(writer, "mtllib {}", mtl_filename)?;
writeln!(writer, "o Mesh3D")?;
for [x, y, z] in &self.vertices {
writeln!(writer, "v {} {} {}", x, y, z)?;
}
for [u, v] in &self.uvs {
writeln!(writer, "vt {} {}", u, v)?;
}
for group in &self.faces {
writeln!(writer, "usemtl {}", group.name)?;
writeln!(writer, "g {}", group.name)?;
for [i0, i1, i2] in &group.indices {
writeln!(
writer,
"f {0}/{0} {1}/{1} {2}/{2}",
i0 + 1,
i1 + 1,
i2 + 1
)?;
}
}
Ok(())
}
fn export_mtl(&self, path: &Path, front_texture: &str, back_texture: &str, side_texture: &str) -> std::io::Result<()> {
let mut file = File::create(path)?;
writeln!(file, "newmtl front")?;
writeln!(file, "Ka 1.0 1.0 1.0")?;
writeln!(file, "Kd 1.0 1.0 1.0")?;
writeln!(file, "Ks 0.0 0.0 0.0")?;
writeln!(file, "d 1.0")?;
writeln!(file, "Ns 10.0")?;
writeln!(file, "illum 2")?;
writeln!(file, "map_Kd textures/{}", front_texture)?;
writeln!(file, "\nnewmtl back")?;
writeln!(file, "Ka 1.0 1.0 1.0")?;
writeln!(file, "Kd 1.0 1.0 1.0")?;
writeln!(file, "Ks 0.0 0.0 0.0")?;
writeln!(file, "d 1.0")?;
writeln!(file, "Ns 10.0")?;
writeln!(file, "illum 2")?;
writeln!(file, "map_Kd textures/{}", back_texture)?;
writeln!(file, "\nnewmtl side")?;
writeln!(file, "Ka 1.0 1.0 1.0")?;
writeln!(file, "Kd 1.0 1.0 1.0")?;
writeln!(file, "Ks 0.0 0.0 0.0")?;
writeln!(file, "d 1.0")?;
writeln!(file, "Ns 10.0")?;
writeln!(file, "illum 2")?;
writeln!(file, "map_Kd textures/{}", side_texture)?;
Ok(())
}
#[inline]
pub fn get_vertices(&self) -> &Vec<[f64; 3]> {
&self.vertices
}
#[inline]
pub fn get_faces(&self) -> &Vec<MeshGroup> {
&self.faces
}
}
impl Mesh3D {
pub fn new() -> Self {
Self {
vertices: Vec::new(),
uvs: Vec::new(),
faces: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct Mesh2D {
pub vertices: Vec<[f64; 2]>,
pub indices: Vec<usize>,
}
impl Mesh2D {
pub fn export_obj(&self, path: &Path) -> std::io::Result<()> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
for [x, y] in &self.vertices {
writeln!(writer, "v {} {} 0.0", x, -y)?;
}
for face in self.indices.chunks(3) {
writeln!(
writer,
"f {} {} {}",
face[0] + 1,
face[1] + 1,
face[2] + 1
)?;
}
Ok(())
}
pub fn extrude(&self, depth: f64, image_width: f64, image_height: f64) -> Mesh3D {
let n = self.vertices.len();
let mut vertices = Vec::new();
let mut uvs = Vec::new();
let mut front_indices = Vec::new();
let mut back_indices = Vec::new();
let mut side_indices = Vec::new();
for [x, y] in &self.vertices {
vertices.push([*x, -*y, 0.0]);
uvs.push([*x / image_width, -*y / image_height]);
}
for [x, y] in &self.vertices {
vertices.push([*x, -*y, depth]);
uvs.push([*x / image_width, -*y / image_height]);
}
for triangle in self.indices.chunks(3) {
let i0 = triangle[0];
let i1 = triangle[1];
let i2 = triangle[2];
front_indices.push([i2 + n, i1 + n, i0 + n]);
back_indices.push([i0, i1, i2]);
}
let mut edge_count = std::collections::HashMap::new();
for triangle in self.indices.chunks(3) {
for e in 0..3 {
let i0 = triangle[e];
let i1 = triangle[(e + 1) % 3];
let edge = if i0 < i1 { (i0, i1) } else { (i1, i0) };
*edge_count.entry(edge).or_insert(0) += 1;
}
}
let mut boundary_edges: Vec<(usize, usize)> = edge_count.iter()
.filter(|&(_, &count)| count == 1)
.map(|(&(i0, i1), _)| (i0, i1))
.collect();
boundary_edges.sort_by_key(|&(i0, i1)| i0.min(i1));
let total_perimeter: f64 = boundary_edges.iter()
.map(|&(i0, i1)| {
let p0 = self.vertices[i0];
let p1 = self.vertices[i1];
((p1[0] - p0[0]).powi(2) + (p1[1] - p0[1]).powi(2)).sqrt()
})
.sum();
let mut current_u = 0.0;
for &(i0, i1) in &boundary_edges {
let p0 = self.vertices[i0];
let p1 = self.vertices[i1];
let edge_length = ((p1[0] - p0[0]).powi(2) + (p1[1] - p0[1]).powi(2)).sqrt();
let u0 = current_u / total_perimeter;
let u1 = (current_u + edge_length) / total_perimeter;
current_u += edge_length;
let base_idx = vertices.len();
vertices.push([p0[0], -p0[1], 0.0]);
uvs.push([u0, 0.0]);
vertices.push([p1[0], -p1[1], 0.0]);
uvs.push([u1, 0.0]);
vertices.push([p1[0], -p1[1], depth]);
uvs.push([u1, -1.0]);
vertices.push([p0[0], -p0[1], depth]);
uvs.push([u0, -1.0]);
side_indices.push([base_idx, base_idx + 1, base_idx + 2]);
side_indices.push([base_idx, base_idx + 2, base_idx + 3]);
}
Mesh3D {
vertices,
uvs,
faces: vec![
MeshGroup { indices: front_indices, name: "front" },
MeshGroup { indices: back_indices, name: "back" },
MeshGroup { indices: side_indices, name: "side" },
],
}
}
pub fn get_vertices(&self) -> &Vec<[f64; 2]> {
&self.vertices
}
pub fn get_indices(&self) -> &Vec<usize> {
&self.indices
}
}
pub trait PolygonMesh {
fn mesh2d(&self) -> anyhow::Result<Mesh2D>;
}
impl PolygonMesh for Polygon {
fn mesh2d(&self) -> anyhow::Result<Mesh2D> {
let mut vertices: Vec<[f64; 2]> = Vec::new();
let mut coords: Vec<f64> = Vec::new();
let mut holes: Vec<usize> = Vec::new();
for coord in self.exterior().points() {
let x = coord.x();
let y = coord.y();
coords.push(x);
coords.push(y);
vertices.push([x, y]);
}
for hole in self.interiors() {
holes.push(coords.len() / 2);
let hole_points: Vec<_> = hole.points().collect();
let hole_coords = if hole_points.len() > 1 &&
hole_points[0].x() == hole_points[hole_points.len()-1].x() &&
hole_points[0].y() == hole_points[hole_points.len()-1].y() {
&hole_points[..hole_points.len()-1]
} else {
&hole_points[..]
};
for coord in hole_coords {
let x = coord.x();
let y = coord.y();
coords.push(x);
coords.push(y);
vertices.push([x, y]);
}
}
let indices = earcut(&coords, &holes, 2)?;
Ok(Mesh2D { vertices, indices })
}
}