Earth & Ships

using Makie
 using AbstractPlotting: textslider
 using GeometryTypes, FileIO
 using LinearAlgebra


 """
     example by @pbouffard from JuliaPlots/Makie.jl#307
     https://github.com/pbouffard/miniature-garbanzo/
 """

 const rearth_km = 6371.1f0
 const mearth_kg = 5.972e24
 const rmoon_km = 1738.1f0
 const mmoon_kg = 7.34e22
 const rship_km = 500f0
 const mship_kg = 1000.0
 const dbetween_km = 378_000.0f0
 const radius_mult = 1.0f0
 const timestep = 1.0f0

 mutable struct Ship
     mass_kg::Float32
     position_m::Vec3f0
     velocity_mps::Vec3f0
     color::Symbol
     mesh::Mesh
 end


 function moveto!(ship::Ship, (x, y, z))
     ship.position_m = Vec3f0(x, y, z)
     translate!(ship.mesh, x, y, z)
     ship.position_m, ship.velocity_mps
 end

 function orbit(r; steps=80)
     return [Point3f0(r*cos(x), r*sin(x), 0) for x in range(0, stop=2pi, length=steps)]
 end

 function loadordownload(localfname, url)
     return isfile(localfname) ? load(localfname) : load(download(url, localfname))
 end

 # http://corysimon.github.io/articles/uniformdistn-on-sphere/
 function makestars(n)
     return map(1:n) do i
         v = [0, 0, 0]  # initialize so we go into the while loop
         while norm(v) < .0001
             v = randn(Point3f0)
         end
         v = v / norm(v)  # normalize to unit norm
         v
     end
 end

 function makecontrols()
     orbit_slider, orbit_speed = textslider(0f0:10f0, "Speed", start=1.0)
     scale_slider, scale = textslider(1f0:20f0, "Scale", start=10.0)
     return orbit_slider, orbit_speed, scale_slider, scale
 end


 function makeships(scene, N)
     return map(1:N) do i
         sm = mesh!(scene, Sphere(Point3f0(0), rship_km*radius_mult), color=:green, show_axis=false)[end]
         ship = Ship(mship_kg, [0, 0, 0], [0, 0, 0], :green, sm)
         moveto!(ship, (100000f0 * randn(Float32), dbetween_km/2 + randn(Float32) * 50000f0, 50000f0*randn(Float32)))
         ship.velocity_mps = [40000*randn(Float32), -1000*randn(Float32), 1000*randn(Float32)]
         ship
     end
 end

 function makeobjects(scene)
     earthfname = "bluemarble-2048.png"
     earthurl = "https://svs.gsfc.nasa.gov/vis/a000000/a002900/a002915/" * earthfname
     earthbitmap = loadordownload(earthfname, earthurl)
     earth = mesh!(scene, Sphere(Point3f0(0), rearth_km*radius_mult), color=earthbitmap)[end]

     moonfname = "phases.0001_print.jpg"
     moonurl = "https://svs.gsfc.nasa.gov/vis/a000000/a004600/a004675/" * moonfname
     moonbitmap = loadordownload(moonfname, moonurl)
     moon = mesh!(scene, Sphere(Point3f0(0), rmoon_km*radius_mult), color=moonbitmap)[end]
     translate!(moon, Vec3f0(0, dbetween_km, 0))
     orb = lines!(scene, orbit(dbetween_km), color=:gray)
     # stars = scatter!(1000000*makestars(1000), color=:white, markersize=2000)

     return earth, moon #, stars#, ships
 end

 function spin(scene, orbit_speed, scale, moon, earth, ships)
     center!(scene) # update far / near automatically.
     # could also be done manually:
     # cam.near[] = 100
     # cam.far[] = 1.5*dbetween_km
     update_cam!(scene, Vec3f0(1.3032e5, 7.12119e5, 3.60022e5), Vec3f0(0, 0, 0))
     θ = 0.0
     # wait untill window is open
     while !isopen(scene)
         sleep(0.1)
     end
     while isopen(scene)
         scale!(moon, (scale[], scale[], scale[]))
         scale!(earth, (scale[], scale[], scale[]))
         for ship in ships
             scale!(ship.mesh, (scale[], scale[], scale[]))
         end
         translate!(moon, Vec3f0(-dbetween_km*sin(θ/28), dbetween_km*cos(θ/28), 0))
         rotate!(earth, Vec3f0(0, 0, 1), θ)
         rotate!(moon, Vec3f0(0, 0, 1), π/2 + θ/28)
         sleep(0.01)
         θ += 0.05 * orbit_speed[]
         timestep = 10 * orbit_speed[]
         gravity(timestep, ships, earth, moon)
     end
 end

 function gravity(timestep, ships, earth, moon)
     for ship in ships
         rship = ship.position_m #translation(ship)[]
         rearth = translation(earth)[]
         rmoon = translation(moon)[]
         rship_earth = (rearth - rship) * 1e3 # convert to m
         rship_moon = (rmoon - rship) * 1e3 # convert to m
         G = 6.67408e-11 # m^3⋅kg^-2⋅s^-1
         nrship_earth = max(0.1, norm(rship_earth))
         nrship_moon = max(0.1, norm(rship_moon))
         fearth = G * (mship_kg * mearth_kg)/(nrship_earth^2) * rship_earth/nrship_earth
         fmoon = G * (mship_kg * mmoon_kg)/(nrship_moon^2) * rship_earth/nrship_earth
         ftot = fearth + fmoon
         ship.velocity_mps += timestep * ftot
         drship = timestep * ship.velocity_mps / 1e3
         rship = rship + drship
         moveto!(ship, rship)
     end
 end
 universe = Scene(backgroundcolor=:black, show_axis=false, resolution=(2048,1024))
 ships = makeships(universe, 600)
 earth, moon = makeobjects(universe);
 orbit_slider, orbit_speed, scale_slider, scale = makecontrols();
 controls = (orbit_slider, scale_slider);
 scene = hbox(vbox(controls...), universe)
 universe.center = false
 task = @async spin(universe, orbit_speed, scale, moon, earth, ships)
 scene.center = false
 RecordEvents(scene, "output")