/* * TODO: * - impulsive orbital maneuvers * - remove inline functions from orbits.h * - replace instances of glm:: with using directives * - compile with '-pedantic' and fix macro warnings * - orbits with nu (true anom at T0) other than 0 degrees * - plane change maneuvers * - Hohmman transfer orbits * - make an 'overlay' graphic for things like apoapsis, perisapsis, f1, f2, * flight path * - fix coordinate directions overlay * - patched conic method for transferring between 2 grav bodies * - test parabolic/hyperbolic trajectories * - organize orbit functions into interface/internal functions * - add an 'orbits' namespace * - drop orbit from interface functions, * eg) 'orbitGetTimeOfFlight()' becomes 'getTimeOfFlight()' * * - Gooey: * - Add maneuver controls for circularize_raising/lowering * - can we make the input slider for maneuver anomaly snap to certain angles? * - 0, apoapse, periapse? */ #include #include #include #define GLM_FORCE_XYZW_ONLY #include #include #include "input.h" #include "tangerine.h" #include "game.h" #include "gooey.h" #include "orbits.h" // TODO: move globals to config file? #define DEFAULT_SIM_SPEED 4000 #define GAME_ARENA_SIZE 16 * 1024 * 1024 // 16MB #define DEFAULT_MAX_ORBITS 10000 #define DEFAULT_MANUEVER_NODES 3 * DEFAULT_MAX_ORBITS const double SCALING = 0.001; const vec4 g_light_direction = vec4(-2, 1, 3, 1); const vec4 g_light_color = vec4(0.5, 0.5, 0.5, 1); const uvec4 g_light_intensities = uvec4(1, 0, 0, 0); void initCamera(RenderState* rs, vec3 cam_pos, vec3 cam_focus, vec3 cam_up) { float aspect_ratio = (float) rs->window_dims.x / (float) rs->window_dims.y; cameraInitPerspective(rs->camera, cam_pos, cam_focus, cam_up, aspect_ratio); GLBuffer* xform_ubo = getUBOByName(rs->gl_ctx, "matrices"); assert(xform_ubo != nullptr); updateGLBuffer(xform_ubo, &rs->camera->xforms); } void initLights(RenderState* rs) { LightsBuffer* lb = rs->lights_buf; u32 idx = 0; // NOTE: add a directional light idx = (*lb->active_d_lights)++; lb->dl_directions[idx] = g_light_direction; lb->dl_colors[idx] = g_light_color; lb->dl_intensities[idx] = g_light_intensities; GLBuffer* lights_ubo = getUBOByName(rs->gl_ctx, "lights"); assert(lights_ubo != nullptr); updateGLBuffer(lights_ubo, lb->buffer); } Entity* addCoordinateOverlay(RenderState* rs) { const u32 num_vertices = 6, num_indicies = 6; Mesh* m = meshInit(rs->assets.arena, num_vertices, num_indicies, false, true); m->vertices[0] = vec3(0, 0, 0); m->vertices[1] = vec3(1, 0, 0); m->vertices[2] = vec3(0, 0, 0); m->vertices[3] = vec3(0, 1, 0); m->vertices[4] = vec3(0, 0, 0); m->vertices[5] = vec3(0, 0, 1); u16 indices[num_indicies] = { 0, 1, 2, 3, 4, 5 }; memcpy(m->indices, indices, num_indicies * sizeof(*indices)); m->colors[0] = vec3(1, 0, 0); m->colors[1] = vec3(1, 0, 0); m->colors[2] = vec3(0, 1, 0); m->colors[3] = vec3(0, 1, 0); m->colors[4] = vec3(0, 0, 1); m->colors[5] = vec3(0, 0, 1); Model* mdl = modelInitManual(rs->assets.arena, 1, m); RenderGroup* rg = getRenderGroupByName(rs, "manual mesh group"); Entity* e = getFreeEntity(rg); initEntity(e, rs->gl_ctx, rs->rg_arena, mdl, rg->shader->num_vertex_attribs, rg->shader->attrib_mappings, "coordinate overlay", GL_LINES); // TODO: add a gameoverlay that can 'unproject' world coordinates to screen // coords // NOTE: place overlay at origin, and scale so it's big enough to see setEntityPosition(e, vec3(0, 0, 0)); scaleEntity(e, 20 / SCALING); return e; } Entity* initEllipseEntity(RenderState* rs, GameOrbit* orbit, u32 num_vertices) { // FIXME: it might be worth using stack memory here, and in // initSatelliteEntity(), so we're not storing basically the same model // over and over with just different vertices // Could also use a temporary arena maybe? orbit->e3d = ellipse3DInit(orbit->system.rotation, orbit->system.ep, num_vertices); Mesh* m = meshInit(rs->assets.arena, num_vertices, num_vertices, false, true); for (uint i = 0; i < orbit->e3d.vert_count; i++) { m->vertices[i] = orbit->e3d.vertices[i]; m->colors[i] = DEFAULT_ELLIPSE_COLOR; m->indices[i] = i; } Model* mdl = modelInitManual(rs->assets.arena, 1, m); RenderGroup* rg = getRenderGroupByName(rs, "manual mesh group"); Entity* e = getFreeEntity(rg); initEntity(e, rs->gl_ctx, rs->rg_arena, mdl, rg->shader->num_vertex_attribs, rg->shader->attrib_mappings, "ellipse 01", GL_LINES, GL_DYNAMIC_DRAW); return e; } Entity* initPlanetEntity(RenderState* rs, double r) { ShaderProgram* shader_lit = getShaderByName("full_lighting", rs->gl_ctx); RenderGroup* rg_full = getFreeRenderGroup(rs); initRenderGroup(rg_full, rs->rg_arena, shader_lit, 256, "lit group"); Model* icosphere = getModelByPath(&rs->assets, "../data/icosphere.gltf"); Entity* e = getFreeEntity(rg_full); assert(icosphere && e); initEntity(e, rs->gl_ctx, rs->rg_arena, icosphere, shader_lit->num_vertex_attribs, shader_lit->attrib_mappings, "grav body 01"); scaleEntity(e, r); return e; } Entity* initSatelliteEntity(RenderState* rs, const char* name) { u32 num_vertices = 3; vec3 vertex_color = vec3(255, 0, 0); Mesh* m = meshInit(rs->assets.arena, num_vertices, num_vertices, false, true); m->vertices[0] = vec3(0, 1, 0); m->colors[0] = vertex_color; m->indices[0] = 0; m->vertices[1] = vec3(-1, -1, 0); m->colors[1] = vertex_color; m->indices[1] = 1; m->vertices[2] = vec3(1, -1, 0); m->colors[2] = vertex_color; m->indices[2] = 2; Model* mdl = modelInitManual(rs->assets.arena, 1, m); RenderGroup* rg = getRenderGroupByName(rs, "manual mesh group"); Entity* e = getFreeEntity(rg); initEntity(e, rs->gl_ctx, rs->rg_arena, mdl, rg->shader->num_vertex_attribs, rg->shader->attrib_mappings, name, GL_TRIANGLES); return e; } GameOrbit* loadOrbit(GameState* gs, RenderState* rs, const GravBody& body, const OrbitalElements& elements, RenderGroup* rg, const char* sat_name) { GameOrbit* orbit = getFreeOrbit(gs); systemInit(orbit->system, body, elements); orbit->ellipse_entity = initEllipseEntity(rs, orbit, DEFAULT_ORBIT_VERTICES); setEntityPosition(orbit->ellipse_entity, vec3(0, 0, 0)); orbit->satellite_entity = initSatelliteEntity(rs, sat_name); scaleEntity(orbit->satellite_entity, 1 / SCALING); setEntityPosition(orbit->satellite_entity, orbit->system.sat.position); // FIXME: rotate satellite model to face camera // TODO: billboards in libTangerine //Camera* cam = rs->camera; //Entity* sat = orbit->satellite_entity; //*sat->model_xform = cam->xforms.view * sat->model_xform; return orbit; } void loadRandomOrbits(GameState* gs, RenderState* rs, const u32 count, const GravBody& body, RenderGroup* rg) { assert(gs->num_orbits + count < DEFAULT_MAX_ORBITS); srand(1); // NOTE: use the same seed every time const double max_semi_major_axis = 50000; const double min_semi_major_axis = 20000; const double semi_major_axis_range = max_semi_major_axis - min_semi_major_axis; const double max_eccentricity = 0.7; const double max_nu = 2 * M_PI; for (u32 i = 0; i < count; i++) { double semi_major_axis = fmod(fabs((double) rand()), semi_major_axis_range) + min_semi_major_axis; double eccentricity = max_eccentricity * rand() / RAND_MAX; double nu = max_nu * rand() / RAND_MAX; double omega = 2 * M_PI * rand() / RAND_MAX; double iota = 2 * M_PI * rand() / RAND_MAX; double ohm = 2 * M_PI * rand() / RAND_MAX; GameOrbit* gorb = loadOrbit(gs, rs, body, orbitInit(semi_major_axis, eccentricity, iota, ohm, omega, nu), rg, "???"); addManeuver(gs, gorb, ImpulseType::CIRCULARIZE_LOWERING); } } bool loadScene(GameState* gs, RenderState* rs) { initCamera(rs, vec3(-70 / SCALING, -70 / SCALING, 70 / SCALING), vec3(0, 0, 0), vec3(0,0,1)); initLights(rs); gs->running = true; double mu = 398601.68; // gravitational parameter double r = 6378; // body radius in km GravBody body = gravBodyInit(mu, r); initPlanetEntity(rs, r); // TODO: streamline the boilerplate in init_X_Entity functions ShaderProgram* shader = getShaderByName("colored_vertices", rs->gl_ctx); RenderGroup* rg = getFreeRenderGroup(rs); initRenderGroup(rg, rs->rg_arena, shader, 10000, "manual mesh group"); gs->coord_overlay = addCoordinateOverlay(rs); OrbitalElements el1 = orbitInit(8000, 0, 0, 0, 0, DEG2RAD(-90)); OrbitalElements el2 = orbitInit(20000, 0, 0, 0, 0, 0); loadOrbit(gs, rs, body, el1, rg, "sat_01"); GameOrbit* go_2 = loadOrbit(gs, rs, body, el2, rg, "sat_02"); double v_transfer = orbitGetTransferVelocity(go_2->system, el1); addManeuver(gs, go_2, ImpulseType::PROGRADE, DEG2RAD(45), v_transfer); addManeuver(gs, go_2, ImpulseType::CIRCULARIZE_LOWERING); // NOTE: pre-select an orbit for testing selectOrbit(gs, go_2); // NOTE: add more orbits with maneuvers to try and provoke a failure loadRandomOrbits(gs, rs, 1000, body, rg); return true; } void processInput(RenderState* rs) { static InputState is = {}; SDL_Event e; bool gooey_wants = false; while (SDL_PollEvent(&e)) { // FIXME: we should check for escape key here first gooey_wants = gooProcessEvent(e); if (!gooey_wants) inputProcessEvent(&is, e); } if (is.window_closed || is.escape) rs->running = false; } void updateSatelliteModel(TwoBodySystem& sys, double time_step) { Satellite& sat = sys.sat; sat.theta = orbitGetPropagatedTrueAnomaly(sys, sat.theta, time_step); sat.gamma = orbitGetFlightPathAngle(sys.ep.e, sat.theta); sat.r = orbitGetRadialDistance(sys.ep.e, sys.ep.p, sat.theta); sat.v = orbitGetVelocity(sys.epsilon, sys.body.mu, sat.r); sat.position = sys.rotation * glm::vec3(polarToRect(sat.theta, sat.r), 0); } void updateOrbit(GameOrbit* orbit, bool running, double time_step) { assert(orbit); if (running && orbit->in_use) { double previous_true_anom = orbit->system.sat.theta; updateSatelliteModel(orbit->system, time_step); setEntityPosition(orbit->satellite_entity, orbit->system.sat.position); if (orbit->first_maneuver && testManeuverStep(orbit->first_maneuver, previous_true_anom, orbit->system.sat.theta)) { applyManeuver(orbit, orbit->first_maneuver); removeManeuver(orbit, orbit->first_maneuver); } } } void preFrameCallback(RenderState* rs, void* user_data = nullptr) { processInput(rs); assert(user_data != nullptr); GameState* gs = (GameState*) user_data; double time_step = getTimeStep(gs); for (u32 i = 0; i < gs->num_orbits; i++) updateOrbit(&gs->orbits[i], gs->running, time_step); } void postFrameCallback(RenderState* rs, void* user_data = nullptr) { assert(user_data != nullptr); gooDraw(rs->handles.window, (GameState*) user_data, rs); } int main() { RenderState* rs = initRenderState("orbital shipping", uvec2(1600, 900), SDL_INIT_VIDEO | SDL_INIT_TIMER); if (rs == nullptr) { LOG(Error) << "Error Initialzing renderer\n"; return 1; } if (!gooInit(rs->handles.window, rs->handles.sdl_gl_ctx)) { LOG(Error) << "Error Initialzing gooey\n"; return 1; } GameState gs = {0}; gs.arena = arenaInit(GAME_ARENA_SIZE); gs.sim_speed = DEFAULT_SIM_SPEED; gs.max_orbits = DEFAULT_MAX_ORBITS; gs.orbits = ARENA_ALLOC(gs.arena, GameOrbit, DEFAULT_MAX_ORBITS); gs.max_maneuvers = DEFAULT_MANUEVER_NODES; gs.maneuver_nodes = ARENA_ALLOC(gs.arena, ManeuverNode, DEFAULT_MANUEVER_NODES); if (!loadScene(&gs, rs)) { LOGF(Error, "Error loading scene\n"); return 1; } doRenderLoop(rs, 60, preFrameCallback, postFrameCallback, &gs); gooFree(); freeRenderState(rs); return 0; }