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.
 
 
 
 
 

380 lines
10 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()'
*/
#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"
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);
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;
}
bool
loadScene(GameState* gs, RenderState* rs)
{
initCamera(rs, vec3(-20 / SCALING, -20 / SCALING, 20 / 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, 256, "manual mesh group");
gs->coord_overlay = addCoordinateOverlay(rs);
OrbitalElements el1 = orbitInit(8000, 0, 0, 0, 0, DEG2RAD(-90));
GameOrbit* go_1 = loadOrbit(gs, rs, body, el1, rg, "sat_01");
OrbitalElements el2 = orbitInit(20000, 0, 0, 0, 0, 0);
loadOrbit(gs, rs, body, el2, rg, "sat_02");
//GameOrbit* go_2 = loadOrbit(gs, rs, body, el2, rg, "sat_02");
double v_transfer = orbitGetTransferVelocity(go_1->system, el2);
addManeuver(gs, go_1, ImpulseType::PROGRADE, DEG2RAD(180), v_transfer);
// FIXME: can't get circularization here unless we simulate the transfer
// orbit ahead of time, eg) applyManeuver to a dummy orbit
// could also add a ImpulseType::CIRCULARIZE and calculate in
// applyManeuver()
addManeuver(gs, go_1, ImpulseType::PROGRADE, DEG2RAD(180), 0.500);
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);
}
#define DEFAULT_SIM_SPEED 500
#define GAME_ARENA_SIZE 16 * 1024 * 1024 // 16MB
#define DEFAULT_MAX_ORBITS 10000
#define DEFAULT_MANUEVER_NODES 3 * DEFAULT_MAX_ORBITS
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;
}