euv-engine 0.25.21

A high-performance 2D game engine built on the euv framework, featuring ECS, fixed-timestep game loop, canvas rendering, physics, collision detection, sprite animation, and audio.
Documentation
use euv_engine::*;

#[test]
fn lambert_diffuse_face_normal() {
    let light: Light =
        Light::new_directional(Vector3D::new(0.0, 1.0, 0.0), Vector3D::new(1.0, 0.0, 0.0));
    let material: Material = Material::lambert(Vector3D::new(0.5, 0.5, 0.5));
    let normal: Vector3D = Vector3D::new(0.0, 1.0, 0.0);
    let result: Vector3D = compute_lambert(&light, normal, &material);
    let expected: f64 = 1.0 * 1.0 * 1.0 * 0.5;
    assert!(
        (result.get_x() - expected).abs() < EPSILON,
        "expected red channel {expected}, got {}",
        result.get_x(),
    );
    assert!(
        result.get_y().abs() < EPSILON,
        "expected green channel 0.0, got {}",
        result.get_y(),
    );
    assert!(
        result.get_z().abs() < EPSILON,
        "expected blue channel 0.0, got {}",
        result.get_z(),
    );
}

#[test]
fn phong_specular_peak() {
    let normal: Vector3D = Vector3D::new(0.0, 1.0, 0.0);
    let light_dir: Vector3D = Vector3D::new(0.0, -1.0, 0.0);
    let view_dir: Vector3D = Vector3D::new(0.0, 1.0, 0.0);
    let light: Light = Light::new(
        LightType::Directional,
        Vector3D::zero(),
        light_dir,
        Vector3D::new(1.0, 1.0, 1.0),
        1.0,
        0.0,
        0.0,
    );
    let material: Material = Material::phong(Vector3D::new(1.0, 1.0, 1.0), 1.0, 32.0);
    let result: Vector3D = compute_phong(&light, normal, view_dir, &material);
    assert!(
        (result.get_x() - 1.0).abs() < EPSILON,
        "expected specular peak ~1.0, got {}",
        result.get_x(),
    );
    assert!(
        (result.get_y() - 1.0).abs() < EPSILON,
        "expected specular peak ~1.0, got {}",
        result.get_y(),
    );
    assert!(
        (result.get_z() - 1.0).abs() < EPSILON,
        "expected specular peak ~1.0, got {}",
        result.get_z(),
    );
}

#[test]
fn point_light_falloff_distance() {
    let falloff: f64 = 1.0;
    let f0: f64 = apply_falloff(0.0, falloff);
    let f1: f64 = apply_falloff(1.0, falloff);
    let f2: f64 = apply_falloff(2.0, falloff);
    assert!((f0 - 1.0).abs() < EPSILON, "d=0 should yield 1.0, got {f0}");
    assert!(
        (f1 - 1.0 / (1.0 + 1.0)).abs() < EPSILON,
        "d=1 should yield 0.5, got {f1}",
    );
    assert!(
        (f2 - 1.0 / (1.0 + 4.0)).abs() < EPSILON,
        "d=2 should yield 0.2, got {f2}",
    );
}

#[test]
fn ray_sphere_intersect_hit_miss_inside() {
    let origin: Vector3D = Vector3D::new(0.0, 0.0, 5.0);
    let dir: Vector3D = Vector3D::new(0.0, 0.0, -1.0);
    let center: Vector3D = Vector3D::zero();
    let radius: f64 = 1.0;
    let hit: Option<(f64, Vector3D)> = ray_sphere_intersect(origin, dir, center, radius);
    assert!(hit.is_some(), "ray from outside should hit sphere");
    let (t, normal): (f64, Vector3D) = hit.unwrap();
    assert!((t - 4.0).abs() < EPSILON, "expected t=4, got {t}");
    assert!(
        (normal.get_z() - 1.0).abs() < EPSILON,
        "expected normal (0,0,1), got (0,0,{})",
        normal.get_z(),
    );
    let origin_miss: Vector3D = Vector3D::new(10.0, 0.0, 5.0);
    let dir_miss: Vector3D = Vector3D::new(0.0, 0.0, -1.0);
    let miss: Option<(f64, Vector3D)> = ray_sphere_intersect(origin_miss, dir_miss, center, radius);
    assert!(miss.is_none(), "ray far from sphere should miss");
    let origin_in: Vector3D = Vector3D::zero();
    let dir_in: Vector3D = Vector3D::new(1.0, 0.0, 0.0);
    let inside: Option<(f64, Vector3D)> = ray_sphere_intersect(origin_in, dir_in, center, radius);
    assert!(
        inside.is_some(),
        "ray from inside should still hit exit point"
    );
    let (t_in, normal_in): (f64, Vector3D) = inside.unwrap();
    assert!(
        (t_in - 1.0).abs() < EPSILON,
        "expected t=1 (exit through +x), got {t_in}",
    );
    assert!(
        (normal_in.get_x() - 1.0).abs() < EPSILON,
        "expected exit normal (1,0,0), got ({},0,0)",
        normal_in.get_x(),
    );
}

#[test]
fn soft_shadow_no_occluder_returns_one() {
    let origin: Vector3D = Vector3D::zero();
    let light_pos: Vector3D = Vector3D::new(0.0, 0.0, 10.0);
    let occluders: [(Vector3D, f64); 0] = [];
    let v: f64 = soft_shadow_factor(origin, light_pos, &occluders);
    assert!(
        (v - 1.0).abs() < EPSILON,
        "empty occluders should yield 1.0, got {v}"
    );
}