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.
418 lines
12 KiB
418 lines
12 KiB
|
|
/* |
|
* 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 <cassert> |
|
#include <cmath> |
|
|
|
#include <SDL2/SDL.h> |
|
#define GLM_FORCE_XYZW_ONLY |
|
#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" |
|
|
|
// 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; |
|
} |
|
|
|
|