Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file globe_widget.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226(*****************************************************************************)(* *)(* SPDX-License-Identifier: MIT *)(* Copyright (c) 2026 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(*****************************************************************************)letpi=Float.pilettwo_pi=2.0*.piletdeg_to_radd=d*.pi/.180.0letlatlon_to_xyzlat_deglon_deg=letlat=deg_to_radlat_deginletlon=deg_to_radlon_deginletcl=coslatin(cl*.coslon,sinlat,cl*.sinlon)lethaversine_km~lat1~lon1~lat2~lon2=letr=6371.0inletdlat=deg_to_rad(lat2-.lat1)inletdlon=deg_to_rad(lon2-.lon1)inleta=lets=sin(dlat/.2.0)inlets2=sin(dlon/.2.0)in(s*.s)+.(cos(deg_to_radlat1)*.cos(deg_to_radlat2)*.s2*.s2)inleta=max0.0(min1.0a)in2.0*.r*.atan2(sqrta)(sqrt(1.0-.a))typet={coastline:(float*float)array;is_land:(lat:float->lon:float->bool)option;yaw:float;pitch:float;rate:float;(* radians per second *)}letcreate?is_land~coastline()={coastline;is_land;yaw=0.0;pitch=0.2;rate=two_pi/.20.0}letyawt=t.yawletset_rotationt~yaw~pitch={twithyaw;pitch}letadvancet~dt=letyaw=mod_float(t.yaw+.(t.rate*.dt))two_piin{twithyaw}(* Apply yaw (around y axis) then pitch (around x axis). *)letrotate~yaw~pitch(x,y,z)=letcy=cosyawandsy=sinyawinletx1=(x*.cy)+.(z*.sy)inletz1=(-.x*.sy)+.(z*.cy)inletcp=cospitchandsp=sinpitchinlety2=(y*.cp)-.(z1*.sp)inletz2=(y*.sp)+.(z1*.cp)in(x1,y2,z2)(* Inverse of [rotate]: undo pitch then yaw to map a camera-space point back
to model space (where lat/lon make sense). *)letinv_rotate~yaw~pitch(xc,yc,zc)=letcp=cospitchandsp=sinpitchinletyp=(yc*.cp)+.(zc*.sp)inletzp=(-.yc*.sp)+.(zc*.cp)inletcy=cosyawandsy=sinyawinletxm=(xc*.cy)-.(zp*.sy)inletzm=(xc*.sy)+.(zp*.cy)in(xm,yp,zm)(* Pick a 256-color shade for a given diffuse term in [0..1].
Uses the cube + greyscale ramp:
bright land → 226 (yellow)
mid → 220, 214 (gold→amber)
limb → 240, 244 (greys)
Returns Some "256;N" SGR payload or None for very dark. *)letshade_colordiffuse=ifdiffuse<=0.0thenNoneelseifdiffuse>=0.85thenSome"38;5;226"elseifdiffuse>=0.65thenSome"38;5;220"elseifdiffuse>=0.45thenSome"38;5;214"elseifdiffuse>=0.25thenSome"38;5;208"elseSome"38;5;130"letlimb_color=Some"38;5;75"(* Ocean blue ramp by Lambert diffuse (in screen space — sun fixed at the
right of the viewport, so the right side stays bright as the globe
rotates underneath). *)letocean_colordiffuse=ifdiffuse<=0.05thenSome"38;5;17"elseifdiffuse<=0.2thenSome"38;5;18"elseifdiffuse<=0.4thenSome"38;5;19"elseifdiffuse<=0.6thenSome"38;5;20"elseifdiffuse<=0.8thenSome"38;5;26"elseifdiffuse<=0.95thenSome"38;5;33"elseSome"38;5;75"(* Sand/earth ramp for filled continents — same Lambert idea as the ocean. *)letland_fill_colordiffuse=ifdiffuse<=0.05thenSome"38;5;58"elseifdiffuse<=0.2thenSome"38;5;94"elseifdiffuse<=0.4thenSome"38;5;130"elseifdiffuse<=0.6thenSome"38;5;172"elseifdiffuse<=0.8thenSome"38;5;214"elseifdiffuse<=0.95thenSome"38;5;220"elseSome"38;5;226"letrendert~cols~rows=letcanvas=Octant_canvas.create~width:cols~height:rowsinletdot_w=cols*2inletdot_h=rows*4inletcx=float_of_intdot_w/.2.0inletcy=float_of_intdot_h/.2.0in(* Aspect: terminal cells are roughly 2× taller than wide, octant gives
2 dots wide × 4 dots tall per cell so dots are ~equally spaced. Use
a slightly squashed radius to keep the projection roughly circular. *)letradius=Float.mincxcy-.1.5in(* 0) Ocean fill — flood every cell whose centre lies inside the
inscribed disc with a Lambert-shaded blue. The shading is in screen
space so the sun stays on the right, and the coastline highlights
drawn later sweep across the lit hemisphere as the globe rotates. *)letr2=radius*.radiusinforcy_c=0torows-1doforcx_c=0tocols-1doletdx=float_of_int((cx_c*2)+1)-.cxinletdy=float_of_int((cy_c*4)+2)-.cyinletd2=(dx*.dx)+.(dy*.dy)inifd2<=r2thenbeginletnx=dx/.radiusinletny=-.dy/.radiusinletnz=sqrt(Float.max0.0(1.0-.(nx*.nx)-.(ny*.ny)))inletdiffuse=Float.max0.0nxinletis_land=matcht.is_landwith|None->false|Somef->letxm,ym,zm=inv_rotate~yaw:t.yaw~pitch:t.pitch(nx,ny,nz)inletlat=asin(Float.max(-1.0)(Float.min1.0ym))*.180.0/.piinletlon=atan2zmxm*.180.0/.piinf~lat~loninletcolor=ifis_landthenland_fill_colordiffuseelseocean_colordiffuseinforj=0to3dofori=0to1doOctant_canvas.set_dotcanvas~x:((cx_c*2)+i)~y:((cy_c*4)+j)~colordonedoneenddonedone;(* 1) Limb: faint outline circle. *)letn_limb=360infori=0ton_limb-1dolettheta=float_of_inti*.two_pi/.float_of_intn_limbinletx=cx+.(radius*.costheta)inlety=cy+.(radius*.sintheta)inOctant_canvas.set_dotcanvas~x:(int_of_floatx)~y:(int_of_floaty)~color:limb_colordone;(* 2) Graticule — equator and prime meridian (rotated). *)letprojectxyz=ifz<0.0thenNoneelseletsx=cx+.(x*.radius)inletsy=cy-.(y*.radius)inSome(int_of_floatsx,int_of_floatsy)inletplot_graticulelat_deglon_degcolor=letxyz=rotate~yaw:t.yaw~pitch:t.pitch(latlon_to_xyzlat_deglon_deg)inletx,y,z=xyzinmatchprojectxyzwith|None->()|Some(sx,sy)->Octant_canvas.set_dotcanvas~x:sx~y:sy~colorin(* equator *)fori=0to359doplot_graticule0.0(float_of_inti-.180.0)(Some"38;5;240")done;(* meridians every 30° *)form=0to11doletlon=(float_of_intm*.30.0)-.180.0inleti=ref(-90)inwhile!i<=90doplot_graticule(float_of_int!i)lon(Some"38;5;238");incridonedone;(* 3) Coastline overlay — only when no land classifier is supplied. With
[is_land] the per-cell fill already paints filled continents in the
correct shade; redrawing 60K coastline points on top would just add
visual noise (and substantial per-frame work). *)(matcht.is_landwith|Some_->()|None->Array.iter(fun(lat,lon)->letxyz=rotate~yaw:t.yaw~pitch:t.pitch(latlon_to_xyzlatlon)inletx,y,z=xyzinmatchprojectxyzwith|None->()|Some(sx,sy)->letdiffuse=xinletcolor=shade_colordiffuseinOctant_canvas.set_dotcanvas~x:sx~y:sy~color)t.coastline);Octant_canvas.rendercanvaslet()=Miaou_registry.register~name:"globe"~mli:[%blob"globe_widget.mli"]()