package gpu_tests import gpu ".." import "core:math" import "core:testing" // --- Easing --- @(test) test_ease_boundaries :: proc(t: ^testing.T) { // All easing types should return 0 at t=0 and 1 at t=1 types := []gpu.Ease_Type { .LINEAR, .SINE_IN, .SINE_OUT, .SINE_IN_OUT, .QUAD_IN, .QUAD_OUT, .QUAD_IN_OUT, .CUBIC_IN, .CUBIC_OUT, .CUBIC_IN_OUT, .EXPO_IN, .EXPO_OUT, .EXPO_IN_OUT, } for et in types { v0 := gpu.ease(0, et) v1 := gpu.ease(1, et) testing.expectf(t, math.abs(v0) < 1e-4, "ease(%v, 0) = %f, want ~0", et, v0) testing.expectf(t, math.abs(v1 - 1) < 1e-4, "ease(%v, 1) = %f, want ~1", et, v1) } } @(test) test_ease_clamping :: proc(t: ^testing.T) { // Values outside [0,1] should be clamped testing.expect(t, gpu.ease(-1) == 0, "ease(-1) should be 0") testing.expect(t, gpu.ease(2) == 1, "ease(2) should be 1") } @(test) test_ease_monotonic :: proc(t: ^testing.T) { // Standard easing types should be monotonically increasing types := []gpu.Ease_Type { .LINEAR, .SINE_IN, .SINE_OUT, .SINE_IN_OUT, .QUAD_IN, .QUAD_OUT, .QUAD_IN_OUT, .CUBIC_IN, .CUBIC_OUT, .CUBIC_IN_OUT, } for et in types { prev: f32 = 0 for i in 1 ..= 10 { s := f32(i) / 10.0 v := gpu.ease(s, et) testing.expectf( t, v >= prev - 1e-6, "ease(%v) not monotonic at %f: %f < %f", et, s, v, prev, ) prev = v } } } // --- Camera Lerp 2D --- @(test) test_camera_lerp_2d_endpoints :: proc(t: ^testing.T) { from := gpu.Camera2D { offset = {0, 0}, target = {0, 0}, rotation = 0, zoom = 1, } to := gpu.Camera2D { offset = {100, 200}, target = {50, 50}, rotation = 90, zoom = 2, } at0 := gpu.camera_lerp_2d(from, to, 0) testing.expect(t, at0.offset.x == 0 && at0.offset.y == 0, "lerp2d t=0 offset") testing.expect(t, at0.zoom == 1, "lerp2d t=0 zoom") at1 := gpu.camera_lerp_2d(from, to, 1) testing.expect(t, math.abs(at1.offset.x - 100) < 1e-4, "lerp2d t=1 offset.x") testing.expect(t, math.abs(at1.rotation - 90) < 1e-4, "lerp2d t=1 rotation") testing.expect(t, math.abs(at1.zoom - 2) < 1e-4, "lerp2d t=1 zoom") } @(test) test_camera_lerp_2d_midpoint :: proc(t: ^testing.T) { from := gpu.Camera2D { target = {0, 0}, zoom = 1, } to := gpu.Camera2D { target = {10, 20}, zoom = 3, } mid := gpu.camera_lerp_2d(from, to, 0.5) testing.expect(t, math.abs(mid.target.x - 5) < 1e-4, "lerp2d midpoint target.x") testing.expect(t, math.abs(mid.target.y - 10) < 1e-4, "lerp2d midpoint target.y") testing.expect(t, math.abs(mid.zoom - 2) < 1e-4, "lerp2d midpoint zoom") } // --- Camera Lerp 3D --- @(test) test_camera_lerp_3d_endpoints :: proc(t: ^testing.T) { from := gpu.Camera3D { position = {0, 0, 5}, target = {0, 0, 0}, up = {0, 1, 0}, fovy = 60, near = 0.1, far = 100, } to := gpu.Camera3D { position = {10, 5, 0}, target = {5, 2, -3}, up = {0, 1, 0}, fovy = 90, near = 0.5, far = 500, } at0 := gpu.camera_lerp_3d(from, to, 0) testing.expect(t, math.abs(at0.position.x) < 1e-4, "lerp3d t=0 pos.x") testing.expect(t, math.abs(at0.fovy - 60) < 1e-4, "lerp3d t=0 fovy") at1 := gpu.camera_lerp_3d(from, to, 1) testing.expect(t, math.abs(at1.position.x - 10) < 1e-4, "lerp3d t=1 pos.x") testing.expect(t, math.abs(at1.fovy - 90) < 1e-4, "lerp3d t=1 fovy") } @(test) test_camera_lerp_3d_up_normalized :: proc(t: ^testing.T) { from := gpu.Camera3D { up = {0, 1, 0}, } to := gpu.Camera3D { up = {1, 0, 0}, } mid := gpu.camera_lerp_3d(from, to, 0.5) up_len := math.sqrt(mid.up.x * mid.up.x + mid.up.y * mid.up.y + mid.up.z * mid.up.z) testing.expect(t, math.abs(up_len - 1) < 1e-4, "lerp3d up should be normalized") } // --- Coordinate Conversion 2D --- @(test) test_screen_world_2d_identity :: proc(t: ^testing.T) { // Default camera (no offset, no rotation, zoom=1) should be identity cam := gpu.Camera2D { zoom = 1, } world := gpu.get_screen_to_world_2d({100, 200}, cam) testing.expect(t, math.abs(world.x - 100) < 1e-4, "identity screen->world x") testing.expect(t, math.abs(world.y - 200) < 1e-4, "identity screen->world y") } @(test) test_screen_world_2d_roundtrip :: proc(t: ^testing.T) { cam := gpu.Camera2D { offset = {50, 30}, target = {100, 200}, rotation = 45, zoom = 1.5, } original := gpu.Vec2{300, 150} screen := gpu.get_world_to_screen_2d(original, cam) back := gpu.get_screen_to_world_2d(screen, cam) testing.expectf( t, math.abs(back.x - original.x) < 0.1, "roundtrip x: got %f want %f", back.x, original.x, ) testing.expectf( t, math.abs(back.y - original.y) < 0.1, "roundtrip y: got %f want %f", back.y, original.y, ) } @(test) test_world_to_screen_2d_zoom :: proc(t: ^testing.T) { cam := gpu.Camera2D { zoom = 2, } screen := gpu.get_world_to_screen_2d({10, 20}, cam) testing.expect(t, math.abs(screen.x - 20) < 1e-4, "zoom 2x should double x") testing.expect(t, math.abs(screen.y - 40) < 1e-4, "zoom 2x should double y") } // --- Camera Path --- @(test) test_camera_path_2d_empty :: proc(t: ^testing.T) { path := gpu.make_camera_path_2d({}) cam := gpu.camera_path_eval_2d(path, 0) // Should return default camera testing.expect(t, cam.zoom == 1, "empty path should return default camera") } @(test) test_camera_path_2d_single :: proc(t: ^testing.T) { kf := [?]gpu.Camera_Keyframe_2D{{camera = gpu.Camera2D{target = {5, 5}, zoom = 2}, time = 0}} path := gpu.make_camera_path_2d(kf[:]) cam := gpu.camera_path_eval_2d(path, 0) testing.expect(t, math.abs(cam.target.x - 5) < 1e-4, "single keyframe target.x") testing.expect(t, math.abs(cam.zoom - 2) < 1e-4, "single keyframe zoom") } @(test) test_camera_path_2d_interpolation :: proc(t: ^testing.T) { kf := [?]gpu.Camera_Keyframe_2D { {camera = gpu.Camera2D{target = {0, 0}, zoom = 1}, time = 0, easing = .LINEAR}, {camera = gpu.Camera2D{target = {100, 0}, zoom = 2}, time = 2, easing = .LINEAR}, } path := gpu.make_camera_path_2d(kf[:]) // At t=1 (midpoint) cam := gpu.camera_path_eval_2d(path, 1) testing.expectf( t, math.abs(cam.target.x - 50) < 1e-2, "path midpoint target.x: got %f want 50", cam.target.x, ) testing.expectf( t, math.abs(cam.zoom - 1.5) < 1e-2, "path midpoint zoom: got %f want 1.5", cam.zoom, ) // Before start cam_start := gpu.camera_path_eval_2d(path, -1) testing.expect(t, math.abs(cam_start.target.x) < 1e-4, "before start should clamp") // After end cam_end := gpu.camera_path_eval_2d(path, 10) testing.expect(t, math.abs(cam_end.target.x - 100) < 1e-4, "after end should clamp") } @(test) test_camera_path_3d_interpolation :: proc(t: ^testing.T) { kf := [?]gpu.Camera_Keyframe_3D { { camera = gpu.Camera3D { position = {0, 0, 5}, target = {0, 0, 0}, up = {0, 1, 0}, fovy = 60, near = 0.1, far = 100, }, time = 0, easing = .LINEAR, }, { camera = gpu.Camera3D { position = {10, 0, 5}, target = {10, 0, 0}, up = {0, 1, 0}, fovy = 60, near = 0.1, far = 100, }, time = 4, easing = .LINEAR, }, } path := gpu.make_camera_path_3d(kf[:]) cam := gpu.camera_path_eval_3d(path, 2) testing.expectf( t, math.abs(cam.position.x - 5) < 1e-2, "3d path midpoint pos.x: got %f want 5", cam.position.x, ) } // --- Camera View/Proj Matrix --- @(test) test_camera_view_matrix_identity_look :: proc(t: ^testing.T) { // Camera at origin looking down -Z cam := gpu.Camera3D { position = {0, 0, 0}, target = {0, 0, -1}, up = {0, 1, 0}, } view := gpu.camera_view_matrix(cam) // Should be close to identity (with Z negated for right-handed) testing.expect(t, math.abs(view[0, 0] - 1) < 1e-4, "view[0,0] should be 1") testing.expect(t, math.abs(view[1, 1] - 1) < 1e-4, "view[1,1] should be 1") testing.expect(t, math.abs(view[2, 2] - 1) < 1e-4, "view[2,2] should be 1") testing.expect(t, math.abs(view[3, 3] - 1) < 1e-4, "view[3,3] should be 1") }