Reusable 3D Earth globe widget (pure OCaml + WebGL)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Earth occlusion for picking and filter hover by brightness

Satellites behind the Earth are no longer hoverable or clickable.
Hover highlight only shows for satellites with brightness > 0.05,
preventing invisible satellites from popping in on mouseover.

+36 -12
+5
lib/raycast.ml
··· 81 81 match intersect_sphere ray ~center:Math.Vec3.zero ~radius:1.0 with 82 82 | None -> None 83 83 | Some t -> Some (Math.Vec3.add ray.origin (Math.Vec3.scale t ray.direction)) 84 + 85 + let occluded_by_earth ray ~hit_distance = 86 + match intersect_sphere ray ~center:Math.Vec3.zero ~radius:1.0 with 87 + | None -> false 88 + | Some earth_t -> earth_t < hit_distance
+4
lib/raycast.mli
··· 44 44 val pick_earth : ray -> Math.Vec3.t option 45 45 (** [pick_earth ray] returns where the ray hits the unit sphere (Earth surface), 46 46 or [None] if it misses. *) 47 + 48 + val occluded_by_earth : ray -> hit_distance:float -> bool 49 + (** [occluded_by_earth ray ~hit_distance] is [true] if the Earth (unit sphere) 50 + blocks the ray before [hit_distance]. *)
+27 -12
lib/webgl/scene.ml
··· 264 264 let cam_pos = camera_position t in 265 265 Orbit.draw gl t.orbit ~projection:p ~view:v ~camera_pos:cam_pos ~far:12.0 266 266 ~dots; 267 - (* Draw hovered satellite larger *) 267 + (* Draw hovered satellite larger, but only if it's visible *) 268 268 match t.hovered with 269 269 | Some idx -> ( 270 - match List.nth_opt (satellite_positions t) idx with 271 - | Some pos -> 272 - let color = 273 - match List.nth_opt t.satellites idx with 274 - | Some sat -> Globe.Satellite.color sat 275 - | None -> Globe.Color.white 276 - in 270 + match List.nth_opt t.satellites idx with 271 + | Some sat when Globe.Satellite.brightness sat > 0.05 -> 272 + let dt = sat_dt sat t.current_unix in 273 + let pos = Globe.Satellite.position_at sat ~dt in 274 + let color = Globe.Satellite.color sat in 277 275 Orbit.draw_dot gl t.orbit 278 276 ~proj:(Tarray.of_float_array Tarray.Float32 p) 279 277 ~vw:(Tarray.of_float_array Tarray.Float32 v) 280 278 pos ~color ~size:14.0 ~alpha:1.0 281 - | None -> ()) 279 + | _ -> ()) 282 280 | None -> () 283 281 end; 284 282 List.iter (fun f -> f ~projection:p ~view:v) layers.custom; ··· 299 297 | Some ray -> ( 300 298 let positions = satellite_positions t in 301 299 match Globe.Raycast.pick_nearest ray positions ~threshold:0.05 with 302 - | Some hit -> `Satellite hit.Globe.Raycast.index 303 - | None -> ( 300 + | Some hit 301 + when not 302 + (Globe.Raycast.occluded_by_earth ray 303 + ~hit_distance:hit.Globe.Raycast.distance) -> 304 + `Satellite hit.Globe.Raycast.index 305 + | _ -> ( 304 306 match Globe.Raycast.pick_earth ray with 305 307 | Some pos -> `Earth pos 306 308 | None -> `Miss)) ··· 337 339 let positions = satellite_positions t in 338 340 t.hovered <- 339 341 (match Globe.Raycast.pick_nearest ray positions ~threshold:0.08 with 340 - | Some hit -> Some hit.Globe.Raycast.index 342 + | Some hit -> 343 + (* Skip satellites behind the Earth *) 344 + let occluded = 345 + Globe.Raycast.occluded_by_earth ray 346 + ~hit_distance:hit.Globe.Raycast.distance 347 + in 348 + (* Only hover satellites that are visibly bright *) 349 + let visible = 350 + match List.nth_opt t.satellites hit.Globe.Raycast.index with 351 + | Some sat -> Globe.Satellite.brightness sat > 0.05 352 + | None -> false 353 + in 354 + if (not occluded) && visible then Some hit.Globe.Raycast.index 355 + else None 341 356 | None -> None)) 342 357 343 358 let click t ~screen_x ~screen_y =