#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EntityId(pub usize);
impl std::fmt::Display for EntityId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Entity({})", self.0)
}
}
#[derive(Debug, Clone)]
pub struct HitInfo {
pub entity_id: EntityId,
pub point: Vector3,
pub normal: Vector3,
pub uv: Option<Vector2>,
pub distance: f32,
pub face_index: Option<usize>,
pub instance_id: Option<usize>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Vector2 {
pub x: f32,
pub y: f32,
}
impl Vector2 {
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Vector3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vector3 {
pub const ZERO: Self = Self {
x: 0.0,
y: 0.0,
z: 0.0,
};
pub const UP: Self = Self {
x: 0.0,
y: 1.0,
z: 0.0,
};
pub const RIGHT: Self = Self {
x: 1.0,
y: 0.0,
z: 0.0,
};
pub const FORWARD: Self = Self {
x: 0.0,
y: 0.0,
z: 1.0,
};
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalize(&self) -> Self {
let len = self.length();
if len > 0.0 {
Self {
x: self.x / len,
y: self.y / len,
z: self.z / len,
}
} else {
*self
}
}
pub fn distance(&self, other: &Self) -> f32 {
(*self - *other).length()
}
}
impl std::ops::Add for Vector3 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
}
}
impl std::ops::Sub for Vector3 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
}
}
impl std::ops::Mul<f32> for Vector3 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self::new(self.x * rhs, self.y * rhs, self.z * rhs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left,
Middle,
Right,
Back,
Forward,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CursorStyle {
#[default]
Default,
Pointer,
Grab,
Grabbing,
Crosshair,
Move,
Text,
Wait,
Help,
None,
}
impl CursorStyle {
pub fn as_css(&self) -> &'static str {
match self {
CursorStyle::Default => "default",
CursorStyle::Pointer => "pointer",
CursorStyle::Grab => "grab",
CursorStyle::Grabbing => "grabbing",
CursorStyle::Crosshair => "crosshair",
CursorStyle::Move => "move",
CursorStyle::Text => "text",
CursorStyle::Wait => "wait",
CursorStyle::Help => "help",
CursorStyle::None => "none",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RaycastConfig {
pub enabled: bool,
pub recursive: bool,
pub max_distance: f32,
pub layer_mask: Option<u32>,
}
impl Default for RaycastConfig {
fn default() -> Self {
Self {
enabled: true,
recursive: true,
max_distance: 1000.0,
layer_mask: None,
}
}
}
#[derive(Debug, Clone)]
pub struct PointerEvent {
pub hit: Option<HitInfo>,
pub screen_position: Vector2,
pub ndc_position: Vector2,
pub button: Option<MouseButton>,
pub shift_key: bool,
pub ctrl_key: bool,
pub alt_key: bool,
}
impl PointerEvent {
pub fn set_cursor(&self, _style: CursorStyle) {
}
}
#[derive(Debug, Clone)]
pub struct PointerDragEvent {
pub hit: Option<HitInfo>,
pub start_hit: Option<HitInfo>,
pub screen_position: Vector2,
pub start_screen_position: Vector2,
pub world_position: Vector3,
pub start_world_position: Vector3,
pub delta: Vector2,
pub total_delta: Vector2,
pub button: MouseButton,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GestureEvent {
Pinch { scale: f32, center: Vector2 },
Rotate { angle: f32, center: Vector2 },
Pan { delta: Vector2 },
}
#[derive(Debug, Clone)]
pub struct Raycaster {
pub origin: Vector3,
pub direction: Vector3,
pub near: f32,
pub far: f32,
}
impl Raycaster {
pub fn new(origin: Vector3, direction: Vector3) -> Self {
Self {
origin,
direction: direction.normalize(),
near: 0.0,
far: 1000.0,
}
}
pub fn from_camera(camera: &Camera, _screen_pos: Vector2) -> Self {
Self::new(camera.position, Vector3::FORWARD)
}
pub fn at(&self, distance: f32) -> Vector3 {
self.origin + self.direction * distance
}
}
#[derive(Debug, Clone)]
pub struct Camera {
pub position: Vector3,
pub target: Vector3,
pub up: Vector3,
pub fov: f32,
pub aspect: f32,
pub near: f32,
pub far: f32,
}
impl Camera {
pub fn new(position: Vector3, target: Vector3) -> Self {
Self {
position,
target,
up: Vector3::UP,
fov: 75.0_f32.to_radians(),
aspect: 1.0,
near: 0.1,
far: 1000.0,
}
}
}
pub type PointerEventHandler = Box<dyn Fn(PointerEvent)>;
pub type PointerDragEventHandler = Box<dyn Fn(PointerDragEvent)>;
pub type GestureEventHandler = Box<dyn Fn(GestureEvent)>;
#[derive(Debug, Clone, Default)]
pub struct InputState {
pub pointer_position: Vector2,
pub pointer_down: bool,
pub cursor_style: CursorStyle,
pub keys_pressed: Vec<String>,
pub mouse_delta: Vector2,
}
impl InputState {
pub fn is_key_pressed(&self, key: &str) -> bool {
self.keys_pressed
.iter()
.any(|k| k.eq_ignore_ascii_case(key))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector3_operations() {
let a = Vector3::new(1.0, 2.0, 3.0);
let b = Vector3::new(4.0, 5.0, 6.0);
let sum = a + b;
assert_eq!(sum.x, 5.0);
assert_eq!(sum.y, 7.0);
assert_eq!(sum.z, 9.0);
let diff = b - a;
assert_eq!(diff.x, 3.0);
assert_eq!(diff.y, 3.0);
assert_eq!(diff.z, 3.0);
let scaled = a * 2.0;
assert_eq!(scaled.x, 2.0);
assert_eq!(scaled.y, 4.0);
assert_eq!(scaled.z, 6.0);
}
#[test]
fn test_vector3_distance() {
let a = Vector3::new(0.0, 0.0, 0.0);
let b = Vector3::new(3.0, 4.0, 0.0);
assert_eq!(a.distance(&b), 5.0);
}
#[test]
fn test_entity_id_display() {
let id = EntityId(42);
assert_eq!(format!("{}", id), "Entity(42)");
}
}