rustraight 0.6.4

A simple 2D game library for Rust, inspired by DXLib
// geometry3d.rs — 3Dレンダリングパイプラインは持たず、ゲーム側で3D→2D投影を行うための
// 軽量なVec3演算とicosphere(測地線球)メッシュ生成ユーティリティを提供する。

use std::collections::HashMap;
use std::ops::{Add, Div, Mul, Neg, Sub};

/// 3次元ベクトル。
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Vec3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl Vec3 {
    pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };

    pub const fn new(x: f32, y: f32, z: f32) -> Self {
        Self { x, y, z }
    }

    pub fn dot(self, rhs: Vec3) -> f32 {
        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
    }

    pub fn cross(self, rhs: Vec3) -> Vec3 {
        Vec3::new(
            self.y * rhs.z - self.z * rhs.y,
            self.z * rhs.x - self.x * rhs.z,
            self.x * rhs.y - self.y * rhs.x,
        )
    }

    pub fn length_squared(self) -> f32 {
        self.dot(self)
    }

    pub fn length(self) -> f32 {
        self.length_squared().sqrt()
    }

    /// 単位ベクトル化する。長さが0に近い場合のガードは無い
    /// (呼び出し側がゼロベクトルを渡さないことを前提とする)。
    pub fn normalize(self) -> Vec3 {
        self / self.length()
    }
}

impl Add<Vec3> for Vec3 {
    type Output = Vec3;
    fn add(self, rhs: Vec3) -> Vec3 {
        Vec3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
    }
}

impl Sub<Vec3> for Vec3 {
    type Output = Vec3;
    fn sub(self, rhs: Vec3) -> Vec3 {
        Vec3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
    }
}

impl Mul<f32> for Vec3 {
    type Output = Vec3;
    fn mul(self, rhs: f32) -> Vec3 {
        Vec3::new(self.x * rhs, self.y * rhs, self.z * rhs)
    }
}

impl Div<f32> for Vec3 {
    type Output = Vec3;
    fn div(self, rhs: f32) -> Vec3 {
        Vec3::new(self.x / rhs, self.y / rhs, self.z / rhs)
    }
}

impl Neg for Vec3 {
    type Output = Vec3;
    fn neg(self) -> Vec3 {
        Vec3::new(-self.x, -self.y, -self.z)
    }
}

/// 三角形インデックスメッシュ。
pub struct Mesh3D {
    pub vertices: Vec<Vec3>,
    pub faces: Vec<[u32; 3]>,
}

/// 辺 (a, b) の中点頂点を返す。既に生成済みならキャッシュから再利用し、
/// 分割時に共有辺で頂点が重複生成されるのを防ぐ。
fn midpoint(vertices: &mut Vec<Vec3>, cache: &mut HashMap<(u32, u32), u32>, a: u32, b: u32) -> u32 {
    let key = if a < b { (a, b) } else { (b, a) };
    if let Some(&idx) = cache.get(&key) {
        return idx;
    }
    let mid = ((vertices[a as usize] + vertices[b as usize]) * 0.5).normalize();
    let idx = vertices.len() as u32;
    vertices.push(mid);
    cache.insert(key, idx);
    idx
}

/// 正20面体を再帰分割した単位球(半径1.0)のメッシュを生成する。
/// `subdivisions = 0` で基本正20面体(頂点12・面20)。1段階ごとに各三角形を
/// 4分割する(頂点数: 2+10·4^n、面数: 20·4^n)。半径を変えたい場合は
/// `mesh.vertices` の各要素に `* radius` を適用すること。
pub fn icosphere(subdivisions: u32) -> Mesh3D {
    let t = (1.0 + 5f32.sqrt()) / 2.0;
    let mut vertices: Vec<Vec3> = [
        (-1.0, t, 0.0), (1.0, t, 0.0), (-1.0, -t, 0.0), (1.0, -t, 0.0),
        (0.0, -1.0, t), (0.0, 1.0, t), (0.0, -1.0, -t), (0.0, 1.0, -t),
        (t, 0.0, -1.0), (t, 0.0, 1.0), (-t, 0.0, -1.0), (-t, 0.0, 1.0),
    ]
    .into_iter()
    .map(|(x, y, z)| Vec3::new(x, y, z).normalize())
    .collect();

    let mut faces: Vec<[u32; 3]> = vec![
        [0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10], [0, 10, 11],
        [1, 5, 9], [5, 11, 4], [11, 10, 2], [10, 7, 6], [7, 1, 8],
        [3, 9, 4], [3, 4, 2], [3, 2, 6], [3, 6, 8], [3, 8, 9],
        [4, 9, 5], [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1],
    ];

    for _ in 0..subdivisions {
        let mut cache: HashMap<(u32, u32), u32> = HashMap::new();
        let mut next_faces = Vec::with_capacity(faces.len() * 4);
        for [a, b, c] in faces {
            let ab = midpoint(&mut vertices, &mut cache, a, b);
            let bc = midpoint(&mut vertices, &mut cache, b, c);
            let ca = midpoint(&mut vertices, &mut cache, c, a);
            next_faces.push([a, ab, ca]);
            next_faces.push([ab, b, bc]);
            next_faces.push([ca, bc, c]);
            next_faces.push([ab, bc, ca]);
        }
        faces = next_faces;
    }

    Mesh3D { vertices, faces }
}