qt-tracing/src/shape.rs

349 lines
6.8 KiB
Rust

use std::f32::consts::PI;
use glam::{Vec3, vec3};
pub type Face = [u16; 3];
#[derive(Debug, Clone)]
pub struct Mesh {
pub vertices: Vec<Vec3>,
pub indices: Vec<Face>,
}
pub fn sphere(subdiv: usize) -> Mesh {
assert!(subdiv >= 2);
assert!(2 * subdiv * (subdiv - 1) + 2 <= 1 << 16);
let mut vertices = Vec::new();
vertices.push(Vec3::Z);
for j in 1..subdiv {
let v = j as f32 / subdiv as f32;
let theta = PI * v;
let (xy, z) = theta.sin_cos();
for i in 0..2 * subdiv {
let u = i as f32 / (2 * subdiv) as f32;
let phi = 2. * PI * u;
let (y, x) = phi.sin_cos();
vertices.push(vec3(xy * x, xy * y, z));
}
}
vertices.push(-Vec3::Z);
assert_eq!(vertices.len(), 2 * subdiv * (subdiv - 1) + 2);
let mut indices = Vec::new();
let subdiv = subdiv as u16;
for i in 0..2 * subdiv - 1 {
indices.push([0, i + 1, i + 2]);
}
indices.push([0, 2 * subdiv, 1]);
for j in 0..subdiv - 2 {
let k1 = j * 2 * subdiv + 1;
let k2 = k1 + 2 * subdiv;
for i in 0..2 * subdiv - 1 {
let a = k1 + i;
let b = k2 + i;
indices.push([a, b, b + 1]);
indices.push([a, b + 1, a + 1]);
}
let a = k1 + 2 * subdiv - 1;
let b = k2 + 2 * subdiv - 1;
indices.push([a, b, k2]);
indices.push([a, k2, k1]);
}
let k1 = 2 * subdiv * (subdiv - 2) + 1;
let k2 = 2 * subdiv * (subdiv - 1) + 1;
for i in 0..2 * subdiv - 1 {
indices.push([k1 + i, k2, k1 + i + 1]);
}
indices.push([k1 + 2 * subdiv - 1, k2, k1]);
Mesh { vertices, indices }
}
pub fn octo_sphere(subdiv: usize) -> Mesh {
assert!(subdiv >= 2);
let mut vertices = Vec::new();
let theta_step = PI / subdiv as f32;
let v_belt = |j: usize, n: usize| {
let phi_step = 2. * PI / n as f32;
let theta = j as f32 * theta_step;
let (xy, z) = theta.sin_cos();
(0..n).map(move |i| {
let phi = i as f32 * phi_step;
let (y, x) = phi.sin_cos();
vec3(xy * x, xy * y, z)
})
};
vertices.push(Vec3::Z);
for j in 1..subdiv / 2 {
vertices.extend(v_belt(j, 4 * j));
}
{
let j = subdiv / 2;
vertices.extend(v_belt(j, 4 * j));
}
for j in (subdiv + 2) / 2..subdiv {
vertices.extend(v_belt(j, 4 * (subdiv - j)));
}
vertices.push(-Vec3::Z);
fn i_cap(top: usize, start: usize) -> impl Iterator<Item = Face> {
gen move {
yield [top, start, start + 1];
yield [top, start + 1, start + 2];
yield [top, start + 2, start + 3];
yield [top, start + 3, start];
}
.map(face)
}
fn i_skew_belt(
short_start: usize,
long_start: usize,
seg_short_face: usize,
) -> impl Iterator<Item = Face> {
gen move {
let seg_long_face = seg_short_face + 1;
let n_short_total = 4 * seg_short_face;
let n_long_total = 4 * seg_long_face;
for face in 0..4 {
let short = move |k| short_start + (face * seg_short_face + k) % n_short_total;
let long = move |k| long_start + (face * seg_long_face + k) % n_long_total;
yield [short(0), long(0), long(1)];
for k in 0..seg_short_face {
yield [short(k), long(k + 1), short(k + 1)];
yield [short(k + 1), long(k + 1), long(k + 2)];
}
}
}
.map(face)
}
fn i_belt(start1: usize, start2: usize, seg_per_face: usize) -> impl Iterator<Item = Face> {
gen move {
let n = 4 * seg_per_face;
for k in 0..n {
let k2 = (k + 1) % n;
let a = start1 + k;
let b = start2 + k;
let c = start1 + k2;
let d = start2 + k2;
yield [a, b, c];
yield [c, b, d];
}
}
.map(face)
}
fn face(f: [usize; 3]) -> Face {
f.map(|i| i as u16)
}
fn flip(f: Face) -> Face {
[f[1], f[0], f[2]]
}
let mut indices = Vec::new();
indices.extend(i_cap(0, 1));
let mut k = 1;
for j in 1..subdiv - 1 {
let j2 = (subdiv - 1) - j;
match j.cmp(&j2) {
std::cmp::Ordering::Less => {
let n = 4 * j;
indices.extend(i_skew_belt(k, k + n, j));
k += n;
}
std::cmp::Ordering::Equal => {
let n = 4 * j;
indices.extend(i_belt(k, k + n, j));
k += n;
}
std::cmp::Ordering::Greater => {
let n = 4 * (j2 + 1);
indices.extend(i_skew_belt(k + n, k, j2).map(flip));
k += n;
}
}
}
indices.extend(i_cap(k + 4, k).map(flip));
Mesh { vertices, indices }
}
#[cfg(test)]
mod tests {
use approx::abs_diff_eq;
use super::*;
#[test]
fn test_sphere_2_topology() {
assert_eq!(
sphere(2).indices,
vec![
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
[1, 5, 2],
[2, 5, 3],
[3, 5, 4],
[4, 5, 1],
]
);
}
fn are_equal_eps(left: &[Vec3], right: &[Vec3], eps: f32) -> bool {
if left.len() != right.len() {
return false;
}
left.iter()
.zip(right.iter())
.all(|(a, b)| abs_diff_eq!(a, b, epsilon = eps))
}
#[test]
fn test_sphere_2_geometry() {
let left = sphere(2).vertices;
let right = [Vec3::Z, Vec3::X, Vec3::Y, -Vec3::X, -Vec3::Y, -Vec3::Z];
assert!(
are_equal_eps(&left, &right, 1e-6),
"assertion `left == right` failed\n left: {left:?}\n right: {right:?}"
);
}
#[test]
fn test_sphere_3_topology() {
assert_eq!(
sphere(3).indices,
vec![
// top
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 5],
[0, 5, 6],
[0, 6, 1],
// belt
[1, 7, 8],
[1, 8, 2],
[2, 8, 9],
[2, 9, 3],
[3, 9, 10],
[3, 10, 4],
[4, 10, 11],
[4, 11, 5],
[5, 11, 12],
[5, 12, 6],
[6, 12, 7],
[6, 7, 1],
// bottom
[7, 13, 8],
[8, 13, 9],
[9, 13, 10],
[10, 13, 11],
[11, 13, 12],
[12, 13, 7],
]
);
}
#[test]
fn test_octo_sphere_2_topology() {
assert_eq!(
octo_sphere(2).indices,
vec![
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
[1, 5, 2],
[2, 5, 3],
[3, 5, 4],
[4, 5, 1],
]
);
}
#[test]
fn test_octo_sphere_2_geometry() {
let left = octo_sphere(2).vertices;
let right = [Vec3::Z, Vec3::X, Vec3::Y, -Vec3::X, -Vec3::Y, -Vec3::Z];
assert!(
are_equal_eps(&left, &right, 1e-6),
"assertion `left == right` failed\n left: {left:?}\n right: {right:?}"
);
}
#[test]
fn test_octo_sphere_3_topology() {
assert_eq!(
octo_sphere(3).indices,
vec![
// top
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
// belt
[1, 5, 2],
[2, 5, 6],
[2, 6, 3],
[3, 6, 7],
[3, 7, 4],
[4, 7, 8],
[4, 8, 1],
[1, 8, 5],
// bottom
[5, 9, 6],
[6, 9, 7],
[7, 9, 8],
[8, 9, 5],
]
);
}
#[test]
fn test_octo_sphere_4_topology() {
assert_eq!(
octo_sphere(4).indices,
vec![
// top
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
// belt 1
[1, 5, 6],
[1, 6, 2],
[2, 6, 7],
[2, 7, 8],
[2, 8, 3],
[3, 8, 9],
[3, 9, 10],
[3, 10, 4],
[4, 10, 11],
[4, 11, 12],
[4, 12, 1],
[1, 12, 5],
// belt 2
[5, 13, 6],
[6, 13, 14],
[6, 14, 7],
[7, 14, 8],
[8, 14, 15],
[8, 15, 9],
[9, 15, 10],
[10, 15, 16],
[10, 16, 11],
[11, 16, 12],
[12, 16, 13],
[12, 13, 5],
// bottom
[13, 17, 14],
[14, 17, 15],
[15, 17, 16],
[16, 17, 13],
]
);
}
}