Earth & Ships


using AbstractPlotting: textslider
using GeometryTypes, FileIO
using LinearAlgebra

using Makie


"""
    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")