mod trigger_button_mesh;
mod trigger_inder_mesh;
mod trigger_simple_mesh;
mod trigger_star_mesh;
mod trigger_wire_d_mesh;
use crate::vpx::gameitem::primitive::VertexWrapper;
use crate::vpx::gameitem::trigger::{Trigger, TriggerShape};
use crate::vpx::math::{Matrix3D, Vertex3D};
use crate::vpx::model::Vertex3dNoTex2;
use crate::vpx::obj::VpxFace;
use trigger_button_mesh::{TRIGGER_BUTTON_INDICES, TRIGGER_BUTTON_MESH};
use trigger_inder_mesh::{TRIGGER_INDER_INDICES, TRIGGER_INDER_MESH};
use trigger_simple_mesh::{TRIGGER_SIMPLE_INDICES, TRIGGER_SIMPLE_MESH};
use trigger_star_mesh::{TRIGGER_STAR_INDICES, TRIGGER_STAR_MESH};
use trigger_wire_d_mesh::{TRIGGER_WIRE_D_INDICES, TRIGGER_WIRE_D_MESH};
fn get_mesh_for_shape(shape: &TriggerShape) -> Option<(&'static [Vertex3dNoTex2], &'static [u16])> {
match shape {
TriggerShape::None => None,
TriggerShape::WireA | TriggerShape::WireB | TriggerShape::WireC => {
Some((&TRIGGER_SIMPLE_MESH, &TRIGGER_SIMPLE_INDICES))
}
TriggerShape::WireD => Some((&TRIGGER_WIRE_D_MESH, &TRIGGER_WIRE_D_INDICES)),
TriggerShape::Star => Some((&TRIGGER_STAR_MESH, &TRIGGER_STAR_INDICES)),
TriggerShape::Button => Some((&TRIGGER_BUTTON_MESH, &TRIGGER_BUTTON_INDICES)),
TriggerShape::Inder => Some((&TRIGGER_INDER_MESH, &TRIGGER_INDER_INDICES)),
}
}
fn get_z_offset(shape: &TriggerShape) -> f32 {
match shape {
TriggerShape::Button => 5.0,
TriggerShape::WireC => -19.0,
_ => 0.0,
}
}
fn get_rotation_matrix(shape: &TriggerShape, rotation: f32) -> Matrix3D {
match shape {
TriggerShape::WireB => {
Matrix3D::rotate_x((-23.0_f32).to_radians()) * Matrix3D::rotate_z(rotation.to_radians())
}
TriggerShape::WireC => {
Matrix3D::rotate_x(140.0_f32.to_radians()) * Matrix3D::rotate_z(rotation.to_radians())
}
_ => Matrix3D::rotate_z(rotation.to_radians()),
}
}
fn uses_radius_scaling(shape: &TriggerShape) -> bool {
matches!(shape, TriggerShape::Button | TriggerShape::Star)
}
fn uses_wire_thickness(shape: &TriggerShape) -> bool {
matches!(
shape,
TriggerShape::WireA
| TriggerShape::WireB
| TriggerShape::WireC
| TriggerShape::WireD
| TriggerShape::Inder
)
}
pub fn build_trigger_mesh(trigger: &Trigger) -> Option<(Vec<VertexWrapper>, Vec<VpxFace>)> {
if !trigger.is_visible {
return None;
}
let (mesh, indices) = get_mesh_for_shape(&trigger.shape)?;
let z_offset = get_z_offset(&trigger.shape);
let full_matrix = get_rotation_matrix(&trigger.shape, trigger.rotation);
let uses_radius = uses_radius_scaling(&trigger.shape);
let apply_wire_thickness = uses_wire_thickness(&trigger.shape);
let wire_thickness = trigger.wire_thickness.unwrap_or(0.0);
let vertices: Vec<VertexWrapper> = mesh
.iter()
.map(|v| {
let pos = Vertex3D::new(v.x, v.y, v.z);
let rotated = full_matrix.transform_vertex(pos);
let (x, y, z) = if uses_radius {
(
rotated.x * trigger.radius,
rotated.y * trigger.radius,
rotated.z * trigger.radius + z_offset,
)
} else {
(
rotated.x * trigger.scale_x,
rotated.y * trigger.scale_y,
rotated.z * 1.0 + z_offset,
)
};
let normal = full_matrix.transform_normal(v.nx, v.ny, v.nz);
let normal = normal.normalized();
let (final_x, final_y, final_z) = if apply_wire_thickness {
(
x + normal.x * wire_thickness,
y + normal.y * wire_thickness,
z + normal.z * wire_thickness,
)
} else {
(x, y, z)
};
VertexWrapper {
vpx_encoded_vertex: [0u8; 32],
vertex: Vertex3dNoTex2 {
x: final_x,
y: final_y,
z: final_z,
nx: normal.x,
ny: normal.y,
nz: normal.z,
tu: v.tu,
tv: v.tv,
},
}
})
.collect();
let faces: Vec<VpxFace> = indices
.chunks(3)
.map(|chunk| VpxFace {
i0: chunk[0] as i64,
i1: chunk[1] as i64,
i2: chunk[2] as i64,
})
.collect();
Some((vertices, faces))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vpx::gameitem::vertex2d::Vertex2D;
fn make_test_trigger(shape: TriggerShape, is_visible: bool) -> Trigger {
Trigger {
center: Vertex2D { x: 100.0, y: 200.0 },
radius: 25.0,
rotation: 45.0,
scale_x: 1.0,
scale_y: 1.0,
wire_thickness: Some(2.0),
shape,
is_visible,
..Default::default()
}
}
#[test]
fn test_build_trigger_mesh_wire_a() {
let trigger = make_test_trigger(TriggerShape::WireA, true);
let result = build_trigger_mesh(&trigger);
assert!(result.is_some());
let (vertices, faces) = result.unwrap();
assert_eq!(vertices.len(), 49);
assert_eq!(faces.len(), 216 / 3);
}
#[test]
fn test_build_trigger_mesh_star() {
let trigger = make_test_trigger(TriggerShape::Star, true);
let result = build_trigger_mesh(&trigger);
assert!(result.is_some());
let (vertices, faces) = result.unwrap();
assert_eq!(vertices.len(), 231);
assert_eq!(faces.len(), 510 / 3);
}
#[test]
fn test_build_trigger_mesh_button() {
let trigger = make_test_trigger(TriggerShape::Button, true);
let result = build_trigger_mesh(&trigger);
assert!(result.is_some());
let (vertices, faces) = result.unwrap();
assert_eq!(vertices.len(), 528);
assert_eq!(faces.len(), 948 / 3);
}
#[test]
fn test_build_trigger_mesh_wire_d() {
let trigger = make_test_trigger(TriggerShape::WireD, true);
let result = build_trigger_mesh(&trigger);
assert!(result.is_some());
let (vertices, faces) = result.unwrap();
assert_eq!(vertices.len(), 203);
assert_eq!(faces.len(), 798 / 3);
}
#[test]
fn test_build_trigger_mesh_inder() {
let trigger = make_test_trigger(TriggerShape::Inder, true);
let result = build_trigger_mesh(&trigger);
assert!(result.is_some());
let (vertices, faces) = result.unwrap();
assert_eq!(vertices.len(), 152);
assert_eq!(faces.len(), 312 / 3);
}
#[test]
fn test_build_trigger_mesh_none_shape() {
let trigger = make_test_trigger(TriggerShape::None, true);
let result = build_trigger_mesh(&trigger);
assert!(result.is_none());
}
#[test]
fn test_build_trigger_mesh_invisible() {
let trigger = make_test_trigger(TriggerShape::WireA, false);
let result = build_trigger_mesh(&trigger);
assert!(result.is_none());
}
#[test]
fn test_all_wire_shapes_use_simple_mesh() {
for shape in [
TriggerShape::WireA,
TriggerShape::WireB,
TriggerShape::WireC,
] {
let trigger = make_test_trigger(shape.clone(), true);
let result = build_trigger_mesh(&trigger);
assert!(result.is_some(), "Failed for shape {:?}", shape);
let (vertices, _) = result.unwrap();
assert_eq!(
vertices.len(),
49,
"Wrong vertex count for shape {:?}",
shape
);
}
}
}