···33 Converts astrodynamics position vectors (km) to GL coordinates where Earth
44 radius = 1.0 and Y axis points north. *)
5566-val of_astro : x:float -> y:float -> z:float -> Math.Vec3.t
77-(** [of_astro ~x ~y ~z] maps (x, y, z) in km to GL coordinates. *)
66+val of_kepler : Kepler.Vec3.t -> Math.Vec3.t
77+(** [of_kepler v] converts a Kepler position vector (km) to GL coordinates. *)
8899val of_coordinate : Coordinate.vec3 -> Math.Vec3.t
1010(** [of_coordinate v] converts a {!Coordinate.vec3} to GL coordinates. *)
1111+1212+val of_astro : x:float -> y:float -> z:float -> Math.Vec3.t
1313+(** [of_astro ~x ~y ~z] maps (x, y, z) in km to GL coordinates. Prefer
1414+ {!of_kepler} or {!of_coordinate} when you have a vector. *)
+4-4
lib/satellite.ml
···2626 let n = 120 in
2727 (* Use period for bound orbits, 5400s arc for unbound *)
2828 let dur = if Float.is_finite period && period > 0. then period else 5400. in
2929- let fallback = Gl_coord.of_astro ~x:pos.x ~y:pos.y ~z:pos.z in
2929+ let fallback = Gl_coord.of_kepler pos in
3030 Array.init n (fun i ->
3131 let t = Float.of_int i *. dur /. Float.of_int n in
3232 let p = Kepler.Analytic.at_precomputed elements ~dt:t in
3333 if Float.is_finite p.x && Float.is_finite p.y && Float.is_finite p.z
3434- then Some (Gl_coord.of_astro ~x:p.x ~y:p.y ~z:p.z)
3434+ then Some (Gl_coord.of_kepler p)
3535 else Some fallback)
3636 in
3737 { elements; pos; vel; color; period; epoch_unix; ghost_points; trail_length }
···4545let position_at t ~dt =
4646 let p = Kepler.Analytic.at_precomputed t.elements ~dt in
4747 if Float.is_finite p.x && Float.is_finite p.y && Float.is_finite p.z then
4848- Gl_coord.of_astro ~x:p.x ~y:p.y ~z:p.z
4848+ Gl_coord.of_kepler p
4949 else epoch_position t
50505151let trail_positions t ~dt =
···6161 let t_offset = dt -. (Float.of_int (n - 1 - i) *. step) in
6262 let p = Kepler.Analytic.at_precomputed t.elements ~dt:t_offset in
6363 if Float.is_finite p.x && Float.is_finite p.y && Float.is_finite p.z then
6464- Some (Gl_coord.of_astro ~x:p.x ~y:p.y ~z:p.z)
6464+ Some (Gl_coord.of_kepler p)
6565 else Some fallback)
66666767let dot t ~dt =
+40-26
test/test_project.ml
···991010open Globe
11111212-let proj = Math.Mat4.(to_float_array
1313- (perspective ~fovy:(Float.pi /. 4.) ~aspect:1.333 ~near:0.1 ~far:100.))
1414-let view = Math.Mat4.(to_float_array
1515- (look_at
1616- ~eye:(Math.Vec3.create 0. 0. 3.5)
1717- ~center:Math.Vec3.zero
1818- ~up:(Math.Vec3.create 0. 1. 0.)))
1212+let proj =
1313+ Math.Mat4.(
1414+ to_float_array
1515+ (perspective ~fovy:(Float.pi /. 4.) ~aspect:1.333 ~near:0.1 ~far:100.))
1616+1717+let view =
1818+ Math.Mat4.(
1919+ to_float_array
2020+ (look_at
2121+ ~eye:(Math.Vec3.create 0. 0. 3.5)
2222+ ~center:Math.Vec3.zero
2323+ ~up:(Math.Vec3.create 0. 1. 0.)))
19242025(** Origin projects to screen center. *)
2126let test_center () =
2222- match Project.to_screen ~projection:proj ~view ~width:800 ~height:600
2323- Math.Vec3.zero with
2727+ match
2828+ Project.to_screen ~projection:proj ~view ~width:800 ~height:600
2929+ Math.Vec3.zero
3030+ with
2431 | None -> Alcotest.fail "origin must be visible from front camera"
2532 | Some (sx, sy, _) ->
2633 Alcotest.(check bool) "center x ~400" true (sx > 390. && sx < 410.);
···2936(** Point behind camera is rejected. *)
3037let test_behind_camera () =
3138 let pos = Math.Vec3.create 0. 0. 10. in
3232- Alcotest.(check bool) "behind camera → None" true
3939+ Alcotest.(check bool)
4040+ "behind camera → None" true
3341 (Project.to_screen ~projection:proj ~view ~width:800 ~height:600 pos = None)
34423543(** Positive X projects right of center. *)
3644let test_right_of_center () =
3737- match Project.to_screen ~projection:proj ~view ~width:800 ~height:600
3838- (Math.Vec3.create 0.5 0. 0.) with
4545+ match
4646+ Project.to_screen ~projection:proj ~view ~width:800 ~height:600
4747+ (Math.Vec3.create 0.5 0. 0.)
4848+ with
3949 | None -> Alcotest.fail "right point must project"
4040- | Some (sx, _, _) ->
4141- Alcotest.(check bool) "right of center" true (sx > 400.)
5050+ | Some (sx, _, _) -> Alcotest.(check bool) "right of center" true (sx > 400.)
42514352(** Positive Y projects above center (smaller screen Y). *)
4453let test_above_center () =
4545- match Project.to_screen ~projection:proj ~view ~width:800 ~height:600
4646- (Math.Vec3.create 0. 0.5 0.) with
5454+ match
5555+ Project.to_screen ~projection:proj ~view ~width:800 ~height:600
5656+ (Math.Vec3.create 0. 0.5 0.)
5757+ with
4758 | None -> Alcotest.fail "above point must project"
4848- | Some (_, sy, _) ->
4949- Alcotest.(check bool) "above center" true (sy < 300.)
5959+ | Some (_, sy, _) -> Alcotest.(check bool) "above center" true (sy < 300.)
50605161(** Nearer objects have smaller depth. *)
5262let test_depth_ordering () =
···62726373(** project_visible filters out behind-camera points. *)
6474let test_project_visible () =
6565- let positions = [
6666- (0, Math.Vec3.zero);
6767- (1, Math.Vec3.create 0. 0. 10.); (* behind camera *)
6868- (2, Math.Vec3.create 0.5 0. 0.);
6969- ] in
7575+ let positions =
7676+ [
7777+ (0, Math.Vec3.zero);
7878+ (1, Math.Vec3.create 0. 0. 10.);
7979+ (* behind camera *)
8080+ (2, Math.Vec3.create 0.5 0. 0.);
8181+ ]
8282+ in
7083 let visible =
7171- Project.project_visible ~projection:proj ~view
7272- ~width:800 ~height:600 positions
8484+ Project.project_visible ~projection:proj ~view ~width:800 ~height:600
8585+ positions
7386 in
7487 Alcotest.(check int) "2 of 3 visible" 2 (List.length visible);
7588 let indices = List.map (fun (i, _, _, _) -> i) visible in
···95108(** Far clip: point beyond far plane is rejected. *)
96109let test_far_clip () =
97110 let pos = Math.Vec3.create 0. 0. (-200.) in
9898- Alcotest.(check bool) "beyond far → None" true
111111+ Alcotest.(check bool)
112112+ "beyond far → None" true
99113 (Project.to_screen ~projection:proj ~view ~width:800 ~height:600 pos = None)
100114101115let suite =