scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use crate::scene::Vec3;

use super::{GeometryDesc, GeometryTopology, GeometryVertex};
use super::{add, new_with_tex_coords, normalize, triangle_normal};

impl GeometryDesc {
    pub fn box_xyz_with_bevel(width: f32, height: f32, depth: f32, bevel: f32) -> Self {
        let half = Vec3::new(width.abs() * 0.5, height.abs() * 0.5, depth.abs() * 0.5);
        let max_bevel = half.x.min(half.y).min(half.z) * 0.95;
        let bevel = bevel.abs().min(max_bevel);
        if bevel <= f32::EPSILON {
            return Self::box_xyz(width, height, depth);
        }

        let inner = Vec3::new(half.x - bevel, half.y - bevel, half.z - bevel);
        let mut vertices = Vec::with_capacity(96);
        let mut tex_coords0 = Vec::with_capacity(96);
        let mut indices = Vec::with_capacity(132);

        for sx in [-1.0_f32, 1.0] {
            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                Vec3::new(sx, 0.0, 0.0),
                &[
                    Vec3::new(sx * half.x, -inner.y, -inner.z),
                    Vec3::new(sx * half.x, -inner.y, inner.z),
                    Vec3::new(sx * half.x, inner.y, inner.z),
                    Vec3::new(sx * half.x, inner.y, -inner.z),
                ],
            );
        }
        for sy in [-1.0_f32, 1.0] {
            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                Vec3::new(0.0, sy, 0.0),
                &[
                    Vec3::new(-inner.x, sy * half.y, -inner.z),
                    Vec3::new(inner.x, sy * half.y, -inner.z),
                    Vec3::new(inner.x, sy * half.y, inner.z),
                    Vec3::new(-inner.x, sy * half.y, inner.z),
                ],
            );
        }
        for sz in [-1.0_f32, 1.0] {
            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                Vec3::new(0.0, 0.0, sz),
                &[
                    Vec3::new(-inner.x, -inner.y, sz * half.z),
                    Vec3::new(-inner.x, inner.y, sz * half.z),
                    Vec3::new(inner.x, inner.y, sz * half.z),
                    Vec3::new(inner.x, -inner.y, sz * half.z),
                ],
            );
        }

        for sy in [-1.0_f32, 1.0] {
            for sz in [-1.0_f32, 1.0] {
                push_polygon(
                    &mut vertices,
                    &mut tex_coords0,
                    &mut indices,
                    normalize(Vec3::new(0.0, sy, sz)),
                    &[
                        Vec3::new(-inner.x, sy * half.y, sz * inner.z),
                        Vec3::new(inner.x, sy * half.y, sz * inner.z),
                        Vec3::new(inner.x, sy * inner.y, sz * half.z),
                        Vec3::new(-inner.x, sy * inner.y, sz * half.z),
                    ],
                );
            }
        }
        for sx in [-1.0_f32, 1.0] {
            for sz in [-1.0_f32, 1.0] {
                push_polygon(
                    &mut vertices,
                    &mut tex_coords0,
                    &mut indices,
                    normalize(Vec3::new(sx, 0.0, sz)),
                    &[
                        Vec3::new(sx * half.x, -inner.y, sz * inner.z),
                        Vec3::new(sx * half.x, inner.y, sz * inner.z),
                        Vec3::new(sx * inner.x, inner.y, sz * half.z),
                        Vec3::new(sx * inner.x, -inner.y, sz * half.z),
                    ],
                );
            }
        }
        for sx in [-1.0_f32, 1.0] {
            for sy in [-1.0_f32, 1.0] {
                push_polygon(
                    &mut vertices,
                    &mut tex_coords0,
                    &mut indices,
                    normalize(Vec3::new(sx, sy, 0.0)),
                    &[
                        Vec3::new(sx * half.x, sy * inner.y, -inner.z),
                        Vec3::new(sx * half.x, sy * inner.y, inner.z),
                        Vec3::new(sx * inner.x, sy * half.y, inner.z),
                        Vec3::new(sx * inner.x, sy * half.y, -inner.z),
                    ],
                );
            }
        }

        for sx in [-1.0_f32, 1.0] {
            for sy in [-1.0_f32, 1.0] {
                for sz in [-1.0_f32, 1.0] {
                    push_polygon(
                        &mut vertices,
                        &mut tex_coords0,
                        &mut indices,
                        normalize(Vec3::new(sx, sy, sz)),
                        &[
                            Vec3::new(sx * half.x, sy * inner.y, sz * inner.z),
                            Vec3::new(sx * inner.x, sy * half.y, sz * inner.z),
                            Vec3::new(sx * inner.x, sy * inner.y, sz * half.z),
                        ],
                    );
                }
            }
        }

        new_with_tex_coords(GeometryTopology::Triangles, vertices, indices, tex_coords0)
    }

    pub fn cylinder_with_bevel(radius: f32, height: f32, segments: u32, bevel: f32) -> Self {
        let radius = radius.abs();
        let half_height = height.abs() * 0.5;
        let segments = segments.max(3);
        let max_bevel = radius.min(half_height) * 0.95;
        let bevel = bevel.abs().min(max_bevel);
        if bevel <= f32::EPSILON {
            return Self::cylinder(radius, height, segments);
        }

        let side_bottom_y = -half_height + bevel;
        let side_top_y = half_height - bevel;
        let cap_radius = radius - bevel;
        let mut vertices = Vec::with_capacity(segments as usize * 14);
        let mut tex_coords0 = Vec::with_capacity(segments as usize * 14);
        let mut indices = Vec::with_capacity(segments as usize * 18);

        for segment in 0..segments {
            let next = (segment + 1) % segments;
            let u0 = segment as f32 / segments as f32;
            let u1 = next as f32 / segments as f32;
            let theta0 = u0 * std::f32::consts::TAU;
            let theta1 = u1 * std::f32::consts::TAU;
            let radial0 = Vec3::new(theta0.cos(), 0.0, theta0.sin());
            let radial1 = Vec3::new(theta1.cos(), 0.0, theta1.sin());
            let radial_mid = normalize(add(radial0, radial1));

            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                radial_mid,
                &[
                    Vec3::new(radial0.x * radius, side_bottom_y, radial0.z * radius),
                    Vec3::new(radial1.x * radius, side_bottom_y, radial1.z * radius),
                    Vec3::new(radial1.x * radius, side_top_y, radial1.z * radius),
                    Vec3::new(radial0.x * radius, side_top_y, radial0.z * radius),
                ],
            );
            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                normalize(add(radial_mid, Vec3::new(0.0, 1.0, 0.0))),
                &[
                    Vec3::new(radial0.x * radius, side_top_y, radial0.z * radius),
                    Vec3::new(radial1.x * radius, side_top_y, radial1.z * radius),
                    Vec3::new(radial1.x * cap_radius, half_height, radial1.z * cap_radius),
                    Vec3::new(radial0.x * cap_radius, half_height, radial0.z * cap_radius),
                ],
            );
            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                normalize(add(radial_mid, Vec3::new(0.0, -1.0, 0.0))),
                &[
                    Vec3::new(radial0.x * cap_radius, -half_height, radial0.z * cap_radius),
                    Vec3::new(radial1.x * cap_radius, -half_height, radial1.z * cap_radius),
                    Vec3::new(radial1.x * radius, side_bottom_y, radial1.z * radius),
                    Vec3::new(radial0.x * radius, side_bottom_y, radial0.z * radius),
                ],
            );
            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                Vec3::new(0.0, 1.0, 0.0),
                &[
                    Vec3::new(0.0, half_height, 0.0),
                    Vec3::new(radial1.x * cap_radius, half_height, radial1.z * cap_radius),
                    Vec3::new(radial0.x * cap_radius, half_height, radial0.z * cap_radius),
                ],
            );
            push_polygon(
                &mut vertices,
                &mut tex_coords0,
                &mut indices,
                Vec3::new(0.0, -1.0, 0.0),
                &[
                    Vec3::new(0.0, -half_height, 0.0),
                    Vec3::new(radial0.x * cap_radius, -half_height, radial0.z * cap_radius),
                    Vec3::new(radial1.x * cap_radius, -half_height, radial1.z * cap_radius),
                ],
            );
        }

        new_with_tex_coords(GeometryTopology::Triangles, vertices, indices, tex_coords0)
    }
}

fn push_polygon(
    vertices: &mut Vec<GeometryVertex>,
    tex_coords0: &mut Vec<[f32; 2]>,
    indices: &mut Vec<u32>,
    normal: Vec3,
    positions: &[Vec3],
) {
    debug_assert!(positions.len() >= 3);
    let normal = normalize(normal);
    let base = vertices.len() as u32;
    for (index, position) in positions.iter().copied().enumerate() {
        vertices.push(GeometryVertex { position, normal });
        tex_coords0.push(polygon_uv(index, positions.len()));
    }
    for index in 1..positions.len().saturating_sub(1) {
        let a = positions[0];
        let b = positions[index];
        let c = positions[index + 1];
        let face = triangle_normal(a, b, c);
        let aligns = face.x * normal.x + face.y * normal.y + face.z * normal.z >= 0.0;
        if aligns {
            indices.extend_from_slice(&[base, base + index as u32, base + index as u32 + 1]);
        } else {
            indices.extend_from_slice(&[base, base + index as u32 + 1, base + index as u32]);
        }
    }
}

fn polygon_uv(index: usize, len: usize) -> [f32; 2] {
    match (len, index) {
        (3, 0) => [0.5, 1.0],
        (3, 1) => [0.0, 0.0],
        (3, _) => [1.0, 0.0],
        (_, 0) => [0.0, 0.0],
        (_, 1) => [1.0, 0.0],
        (_, 2) => [1.0, 1.0],
        _ => [0.0, 1.0],
    }
}