/* * TODO: * - organize orbit functions into interface/internal functions * - add an 'orbits' namespace * - drop orbit from interface functions, * eg) 'orbitGetTimeOfFlight()' becomes 'getTimeOfFlight()' * - make an 'overlay' graphic for things like apoapsis, perisapsis, f1, f2, * flight path * - impulsive orbital maneuvers * - Hohmman transfer orbits * - patched conic method for transferring between 2 grav bodies */ #include #include #include #include #include #include "input.h" #include "tangerine.h" #include "game.h" #include "gooey.h" #include "orbits.h" 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); } // FIXME: could pass EllipseParameters and Ellipse3D instead of TwoBodySystem Entity* initEllipseEntity(RenderState* rs, TwoBodySystem* system, u32 num_vertices) { system->e3d = ellipseInit3D(system->ep, num_vertices); Mesh* m = meshInit(rs->rg_arena, num_vertices, num_vertices, false, true); vec3 vertex_color = vec3(255, 0, 255); for (uint i = 0; i < system->e3d.vert_count; i++) { m->vertices[i] = system->e3d.vertices[i]; m->colors[i] = vertex_color; m->indices[i] = i; } Model* mdl = modelInitManual(rs->rg_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_LINE_LOOP); 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) { u32 num_vertices = 3; vec3 vertex_color = vec3(255, 0, 0); Mesh* m = meshInit(rs->rg_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->rg_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, "ship 01", GL_TRIANGLES); return e; } void loadScene(GameState* gs, RenderState* rs) { initCamera(rs, vec3(0, -75 / SCALING, 0), vec3(0, 0, 0), vec3(0,0,1)); initLights(rs); gs->running = true; double a = 26564.5; // semi-major axis in km double e = 0.7411; // eccentricity double mu = 398601.68; // gravitational parameter double r = 6378; // body radius in km GameOrbit* orbit_1 = getFreeOrbit(gs); systemInit(orbit_1->system, gravBodyInit(mu, r), orbitInit(a, e)); // TODO: streamline the boilerplate in init_X_Entity functions ShaderProgram* shader = getShaderByName("colored_vertices", rs->gl_ctx); initRenderGroup( getFreeRenderGroup(rs), rs->rg_arena, shader, 256, "manual mesh group"); orbit_1->ellipse_entity = initEllipseEntity(rs, &orbit_1->system, 256); setEntityPosition(orbit_1->ellipse_entity, vec3(0, 0, 0)); rotateEntity(orbit_1->ellipse_entity, vec3(1, 0, 0), (float) M_PI / 2); initPlanetEntity(rs, r); orbit_1->satellite_entity = initSatelliteEntity(rs); scaleEntity(orbit_1->satellite_entity, 1 / SCALING); rotateEntity(orbit_1->satellite_entity, vec3(1, 0, 0), (float) M_PI / 2); setEntityPosition(orbit_1->satellite_entity, orbit_1->system.e3d.vertices[0]); // NOTE: testing multiple orbits double a_2 = r + 10000; // km double e_2 = 0; GameOrbit* orbit_2 = getFreeOrbit(gs); systemInit(orbit_2->system, gravBodyInit(mu, r), orbitInit(a_2, e_2)); orbit_2->ellipse_entity = initEllipseEntity(rs, &orbit_2->system, 256); setEntityPosition(orbit_2->ellipse_entity, vec3(0, 0, 0)); rotateEntity(orbit_2->ellipse_entity, vec3(1, 0, 0), (float) M_PI / 2); } 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 = glm::vec3(polarToRect(sat.theta, sat.r), 0); } void updateSatelliteEntity(Entity* e, const Satellite& sat) { const static mat4 xform = glm::rotate(mat4(1.0), (float) M_PI_2, vec3(1, 0, 0)); const vec3& v = sat.position; setEntityPosition(e, xform * vec4(v.x, v.y, v.z, 1)); } // NOTE: use ellipseValidate(ep) before calling to avoid failing assertions void updateOrbit(TwoBodySystem sys, Entity& ellipse_entity) { #if 0 ellipse3DUpdate(sys.ep, sys.e3d); for (uint i = 0; i < sys.e3d.vert_count; i++) ellipse_entity.mesh->vertices[i] = sys.e3d.vertices[i]; entUpdateSimpleMesh(ellipse_entity, ellipse_entity.mesh, GL_LINE_LOOP); #endif } void preFrameCallback(RenderState* rs, void* user_data = nullptr) { 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; // TODO: sim time stuff should have dedicated functions, and maybe a test // since it's pretty important that the simulation is accurate-ish // TODO: verify time to apoapsis/periapsis are correct assert(user_data != nullptr); GameState* gs = (GameState*) user_data; u64 last_sdl_tick = gs->game_time_ms; gs->game_time_ms = SDL_GetTicks64(); u64 current_tick = gs->game_time_ms - last_sdl_tick; // NOTE: update sim time base on time since last frame * sim_speed if (gs->running) { u64 last_game_tick = gs->sim_time_ms; gs->sim_time_ms = gs->sim_time_ms + current_tick * gs->sim_speed; u32 diff_ms = gs->sim_time_ms - last_game_tick; double time_step = double(diff_ms) / 1000; for (u32 i = 0; i < gs->num_orbits; i++) { GameOrbit& orbit = gs->orbits[i]; if (orbit.in_use && orbit.satellite_entity) { updateSatelliteModel(orbit.system, time_step); updateSatelliteEntity(orbit.satellite_entity, orbit.system.sat); // TODO: update EllipseEntity per frame //updateOrbit(gs->system, ellipse_entity); } } } } void postFrameCallback(RenderState* rs, void* user_data = nullptr) { assert(user_data != nullptr); GameState* gs = (GameState*) user_data; GameOrbit& orbit = gs->orbits[0]; assert(orbit.in_use); gooDraw(rs->handles.window, orbit.system, gs->running, gs->sim_time_ms, gs->sim_speed); } #define DEFAULT_SIM_SPEED 2000 #define GAME_ARENA_SIZE 16 * 1024 * 1024 // 16MB #define DEFAULT_MAX_ORBITS 10000 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); loadScene(&gs, rs); doRenderLoop(rs, 60, preFrameCallback, postFrameCallback, &gs); gooFree(); freeRenderState(rs); return 0; }