rsaber_lib 0.7.0

Beat Saber clone written in Rust
use std::cell::RefCell;
use std::sync::LazyLock;

use cgmath::{One, Quaternion, Vector3, Zero};
use wgpu::BufferUsages;
use wgpu::util::BufferInitDescriptor;

use crate::asset::AssetManagerRc;
use crate::output::OutputDeviceRc;
use crate::render::model::{Color, InstObstacleBuf, InstShaderImplType, InstShaderType, Mesh, Model, ModelFactory, ModelHandle, Submesh, VertexPosNormal, VertexShaderType, get_default_primitive_state};
use crate::ui::UIManagerRc;

const POS: f32 = 0.5;

static VERTEXES: LazyLock<Box<[VertexPosNormal]>> = LazyLock::new(|| {
    // Implementation notes:
    // - We don't have .obj file for box, calculate mesh.
    // - The box is freely scalable, however its outline width
    //   is constant.
    // - The side_datas (see below) is replicated to all 6 sides
    //   to construct the final box.
    // - The normal component of VertexPosNormal is used to store
    //   the active face.
    //   TODO: This is not efficient, improve it using custom vertex format?

    let side_datas = [
        (-POS, POS),
        (-POS, -POS),
        (POS, -POS),
        (POS, POS),
    ];

    let tr_x_neg = |(pos_x, pos_y): (f32, f32)| VertexPosNormal { pos: [-POS, -pos_x, pos_y], normal: [-1.0, 0.0, 0.0] };
    let tr_x_pos = |(pos_x, pos_y): (f32, f32)| VertexPosNormal { pos: [POS, pos_x, pos_y], normal: [1.0, 0.0, 0.0] };
    let tr_y_neg = |(pos_x, pos_y): (f32, f32)| VertexPosNormal { pos: [pos_x, -POS, pos_y], normal: [0.0, -1.0, 0.0] };
    let tr_y_pos = |(pos_x, pos_y): (f32, f32)| VertexPosNormal { pos: [-pos_x, POS, pos_y], normal: [0.0, 1.0, 0.0] };
    let tr_z_neg = |(pos_x, pos_y): (f32, f32)| VertexPosNormal { pos: [pos_x, -pos_y, -POS], normal: [0.0, 0.0, -1.0] };
    let tr_z_pos = |(pos_x, pos_y): (f32, f32)| VertexPosNormal { pos: [pos_x, pos_y, POS], normal: [0.0, 0.0, 1.0] };

    let mut vertexes = Vec::new();

    for tr in [tr_x_neg, tr_x_pos, tr_y_neg, tr_y_pos, tr_z_neg, tr_z_pos] {
        for side_data in side_datas {
            let vertex = tr(side_data);
            vertexes.push(vertex);
        }
    }

    vertexes.into_boxed_slice()
});

pub struct ObstacleParam {
    color: Color,
    outline_width: f32,
}

impl ObstacleParam {
    pub fn new(color: &Color, outline_width: f32) -> Self {
        Self {
            color: *color,
            outline_width,
        }
    }    
}

impl ModelFactory for ObstacleParam {
    type Model = Obstacle;

    fn get_mesh(_asset_mgr: AssetManagerRc, output_device: OutputDeviceRc) -> Mesh {
        // Mesh is unit (1m) sized and the object center is at the origin.

        let mut indexes: Vec<u16> = Vec::new();
        let mut vertex_index = 0;

        for _i in 0..6 {
            indexes.push(vertex_index);
            indexes.push(vertex_index + 1);
            indexes.push(vertex_index + 2);
            indexes.push(vertex_index);
            indexes.push(vertex_index + 2);
            indexes.push(vertex_index + 3);

            vertex_index += 4;
        }

        let mut primitive_state = get_default_primitive_state();
        primitive_state.cull_mode = None;

        let submesh = Submesh::new(0, indexes.len() as u32, 0, primitive_state, InstShaderType::Obstacle); // 0

        // Create buffers.

        let vertex_buf = output_device.create_buffer_init(&BufferInitDescriptor {
            label: None,
            contents: bytemuck::cast_slice(&VERTEXES),
            usage: BufferUsages::VERTEX,
        });

        let index_buf = output_device.create_buffer_init(&BufferInitDescriptor {
            label: None,
            contents: bytemuck::cast_slice(&indexes),
            usage: BufferUsages::INDEX,
        });

        let submeshes = Box::from([submesh]);

        Mesh::new(vertex_buf, index_buf, VertexShaderType::PosNormal, submeshes)
    }

    fn create(self, handle: ModelHandle, _output_device: OutputDeviceRc, _inst_sh_impls: &mut [InstShaderImplType], _ui_manager: UIManagerRc) -> Self::Model {
        Obstacle::new(self, handle)
    }
}

pub struct Obstacle {
    param: ObstacleParam,
    handle: ModelHandle,
    inner: RefCell<Inner>,
}

struct Inner {
    scale: (f32, f32, f32),
    pos: Vector3<f32>,
    rot: Quaternion<f32>,
}

impl Obstacle {
    fn new(param: ObstacleParam, handle: ModelHandle) -> Self {
        Self {
            param,
            handle,
            inner: RefCell::new(Inner {
                scale: (1.0, 1.0, 1.0),
                pos: Vector3::zero(),
                rot: Quaternion::one(),
            }),
        }
    }

    pub fn set_visible(&self, visible: bool) {
        self.handle.set_visible(0, visible);
    }

    pub fn set_scale(&self, scale_x: f32, scale_y: f32, scale_z: f32) {
        self.inner.borrow_mut().scale = (scale_x, scale_y, scale_z);
    }

    pub fn set_pos(&self, pos: &Vector3<f32>) {
        self.inner.borrow_mut().pos = *pos;
    }

    pub fn set_rot(&self, rot: &Quaternion<f32>) {
        self.inner.borrow_mut().rot = *rot;
    }

    pub fn calc_box(&self) -> (Vector3<f32>, Vector3<f32>) {
        // Calculate bounding box, left-front-bottom and right-back-top.

        let inner = self.inner.borrow();

        let (min_x, min_y, min_z, max_x, max_y, max_z) = VERTEXES.iter().fold(
            (f32::INFINITY, f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), 
            |(min_x, min_y, min_z, max_x, max_y, max_z), vertex| {
                let v = inner.pos + inner.rot * Vector3::new(vertex.pos[0] * inner.scale.0, vertex.pos[1] * inner.scale.1, vertex.pos[2] * inner.scale.2);
                (min_x.min(v.x), min_y.min(v.y), min_z.min(v.z), max_x.max(v.x), max_y.max(v.y), max_z.max(v.z))
            }
        );
        
        (Vector3::new(min_x, min_y, min_z), Vector3::new(max_x, max_y, max_z))
    }
}

impl Model for Obstacle {
    fn get_pos(&self, inst_index: u16) -> Vector3<f32> {
        assert!(inst_index == 0);

        let inner = self.inner.borrow();
        inner.pos // TODO: More accurate way of calculating distance from camera.
    }

    fn fill_obstacle(&self, inst_index: u16) -> InstObstacleBuf {
        assert!(inst_index == 0);

        let inner = self.inner.borrow();
        InstObstacleBuf::fill(&self.param.color, self.param.outline_width, &Vector3::new(inner.scale.0, inner.scale.1, inner.scale.2), &inner.rot, &inner.pos)
    }
}