You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
8.7 KiB
311 lines
8.7 KiB
|
|
/* |
|
* 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 <cassert> |
|
#include <cmath> |
|
|
|
#include <SDL2/SDL.h> |
|
#include <glm/glm.hpp> |
|
#include <glm/gtc/matrix_transform.hpp> |
|
|
|
#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; |
|
} |
|
|
|
|