text:
#include "ArrayList.h"
#include "Matrix.h"
#include "PhxMemory.h"
#include "OpenGL.h"
#include "Profiler.h"
#include "RefCounted.h"
#include "Resource.h"
#include "Shader.h"
#include "ShaderState.h"
#include "ShaderVar.h"
#include "ShaderVarType.h"
#include "StrMap.h"
#include "PhxString.h"
#include "Tex1D.h"
#include "Tex2D.h"
#include "Tex3D.h"
#include "TexCube.h"
#include "Vec2.h"
#include "Vec3.h"
#include "Vec4.h"

/* TODO : Implement custom directives to mimic layout functionality from GL3+. */
/* TODO : Use glShaderSource's array functionality to implement include files
 *        elegantly. */

static cstr includePath = "include/";
static cstr versionString = "#version 130\n";

struct ShaderVar {
  ShaderVarType type;
  cstr name;
  int index;
};

struct Shader {
  RefCounted;
  cstr name;
  uint vs;
  uint fs;
  uint program;
  uint texIndex;
  ArrayList(ShaderVar, vars);
};

static Shader* current = 0;
static StrMap* cache = 0;

static cstr GLSL_Load(cstr path, Shader*);
static cstr GLSL_Preprocess(cstr code, Shader*);

static int GetUniformIndex (Shader* self, cstr name, bool mustSucceed = false) {
  if (!self)
    Fatal("GetUniformIndex: No shader is bound");
  int index = glGetUniformLocation(self->program, name);
  if (index == -1 && mustSucceed)
    Fatal("GetUniformIndex: Shader <%s> has no variable <%s>", self->name, name);
  return index;
}

static uint CreateGLShader (cstr src, GLenum type) {
  uint self = glCreateShader(type);

  cstr srcs[] = {
    versionString,
    src,
  };

  GLCALL(glShaderSource(self, 2, srcs, 0))
  GLCALL(glCompileShader(self))

  /* Check for compile errors. */ {
    int status;
    GLCALL(glGetShaderiv(self, GL_COMPILE_STATUS, &status))
    if (status == GL_FALSE) {
      int length;
      GLCALL(glGetShaderiv(self, GL_INFO_LOG_LENGTH, &length))
      char* infoLog = (char*)MemAllocZero(length + 1);
      GLCALL(glGetShaderInfoLog(self, length, 0, infoLog))
      Fatal("CreateGLShader: Failed to compile shader:\n%s", infoLog);
    }
  }
  return self;
}

static uint CreateGLProgram (uint vs, uint fs) {
  uint self = glCreateProgram();
  GLCALL(glAttachShader(self, vs))
  GLCALL(glAttachShader(self, fs))

  /* TODO : Replace with custom directives. */ {
    GLCALL(glBindAttribLocation(self, 0, "vertex_position"))
    GLCALL(glBindAttribLocation(self, 1, "vertex_normal"))
    GLCALL(glBindAttribLocation(self, 2, "vertex_uv"))
  }

  GLCALL(glLinkProgram(self))

  /* Check for link errors. */ {
    int status;
    GLCALL(glGetProgramiv(self, GL_LINK_STATUS, &status))
    if (status == GL_FALSE) {
      int length;
      GLCALL(glGetProgramiv(self, GL_INFO_LOG_LENGTH, &length))
      char* infoLog = (char*)MemAllocZero(length + 1);
      GLCALL(glGetProgramInfoLog(self, length, 0, infoLog))
      Fatal("CreateGLProgram: Failed to link program:\n%s", infoLog);
    }
  }
  return self;
}

/* BUG : Cache does not contain information about custom preprocessor
 *       directives, hence cached shaders with custom directives do not work */
static cstr GLSL_Load (cstr name, Shader* self) {
  if (!cache)
    cache = StrMap_Create(16);
  void* cached = StrMap_Get(cache, name);
  if (cached)
    return (cstr)cached;
  cstr rawCode = Resource_LoadCstr(ResourceType_Shader, name);
  cstr code = StrReplace(rawCode, "\r\n", "\n");
  StrFree(rawCode);
  code = GLSL_Preprocess(code, self);
  /* BUG : Disable GLSL caching until preprocessor cache works. */
  // StrMap_Set(cache, name, (void*)code);
  return code;
}

static cstr GLSL_Preprocess (cstr code, Shader* self) {
  const int lenInclude = StrLen("#include");
  cstr begin;

  /* Parse Includes. */
  while ((begin = StrFind(code, "#include")) != 0) {
    cstr end = StrFind(begin, "\n");
    cstr name = StrSubStr(begin + lenInclude + 1, end);
    cstr path = StrAdd(includePath, name);
    cstr prev = code;
    code = StrSub(code, begin, end, GLSL_Load(path, self));
    StrFree(prev);
    StrFree(path);
    StrFree(name);
  }

  /* Parse automatic ShaderVar stack bindings. */
  while ((begin = StrFind(code, "#autovar")) != 0) {
    cstr end = StrFind(begin, "\n");
    cstr line = StrSubStr(begin, end);
    char varType[32] = { 0 };
    char varName[32] = { 0 };

    if (sscanf(line, "#autovar %31s %31s", varType, varName) == 2) {
      ShaderVar var = { 0 };
      var.type = ShaderVarType_FromStr(varType);
      if (var.type == ShaderVarType_None)
        Fatal("GLSL_Preprocess: Unknown shader variable type <%s> "
              "in directive:\n  %s", varType, line);
      var.name = StrDup(varName);
      var.index = -1;
      ArrayList_Append(self->vars, var);
    } else {
      Fatal("GLSL_Preprocess: Failed to parse directive:\n  %s", line);
    }

    cstr prev = code;
    code = StrSub(code, begin, end, "");
    StrFree(prev);
    StrFree(line);
  }
  return code;
}

static void Shader_BindVariables (Shader* self) {
  for (int i = 0; i < ArrayList_GetSize(self->vars); ++i) {
    ShaderVar* var = ArrayList_GetPtr(self->vars, i);
    var->index = glGetUniformLocation(self->program, var->name);
    if (var->index < 0)
      Warn("Shader_BindVariables: Automatic shader variable <%s> does not exist"
           " in shader <%s>", var->name, self->name);
  }
}

/* --- Creation ------------------------------------------------------------- */

Shader* Shader_Create (cstr vs, cstr fs) {
  Shader* self = MemNew(Shader);
  RefCounted_Init(self);
  ArrayList_Init(self->vars);
  vs = GLSL_Preprocess(StrDup(vs), self);
  fs = GLSL_Preprocess(StrDup(fs), self);
  self->vs = CreateGLShader(vs, GL_VERTEX_SHADER);
  self->fs = CreateGLShader(fs, GL_FRAGMENT_SHADER);
  self->program = CreateGLProgram(self->vs, self->fs);
  self->texIndex = 1;
  self->name = StrFormat("[anonymous shader @ %p]", self);
  StrFree(vs);
  StrFree(fs);
  Shader_BindVariables(self);
  return self;
}

Shader* Shader_Load (cstr vName, cstr fName) {
  Shader* self = MemNew(Shader);
  RefCounted_Init(self);
  ArrayList_Init(self->vars);
  cstr vs = GLSL_Load(vName, self);
  cstr fs = GLSL_Load(fName, self);
  self->vs = CreateGLShader(vs, GL_VERTEX_SHADER);
  self->fs = CreateGLShader(fs, GL_FRAGMENT_SHADER);
  self->program = CreateGLProgram(self->vs, self->fs);
  self->texIndex = 1;
  self->name = StrFormat("[vs: %s , fs: %s]", vName, fName);
  Shader_BindVariables(self);
  return self;
}

void Shader_Acquire (Shader* self) {
  RefCounted_Acquire(self);
}

void Shader_Free (Shader* self) {
  RefCounted_Free(self) {
    GLCALL(glDeleteShader(self->vs))
    GLCALL(glDeleteShader(self->fs))
    GLCALL(glDeleteProgram(self->program))
    ArrayList_Free(self->vars);
    StrFree(self->name);
    MemFree(self);
  }
}

ShaderState* Shader_ToShaderState (Shader* self) {
  return ShaderState_Create(self);
}

/* --- Usage ---------------------------------------------------------------- */

void Shader_Start (Shader* self) {
  FRAME_BEGIN;
  GLCALL(glUseProgram(self->program))
  current = self;
  self->texIndex = 1;

  /* Fetch & bind automatic variables from the shader var stack. */
  for (int i = 0; i < ArrayList_GetSize(self->vars); ++i) {
    ShaderVar* var = ArrayList_GetPtr(self->vars, i);
    if (var->index < 0) continue;
    void* pValue = ShaderVar_Get(var->name, var->type);
    if (!pValue)
      Fatal("Shader_Start: Shader variable stack does not contain variable <%s>", var->name);

    switch (var->type) {
      case ShaderVarType_Float: {
        float value = *(float*)pValue;
        GLCALL(glUniform1f(var->index, value));
        break; }
      case ShaderVarType_Float2: {
        Vec2f value = *(Vec2f*)pValue;
        GLCALL(glUniform2f(var->index, value.x, value.y));
        break; }
      case ShaderVarType_Float3: {
        Vec3f value = *(Vec3f*)pValue;
        GLCALL(glUniform3f(var->index, value.x, value.y, value.z));
        break; }
      case ShaderVarType_Float4: {
        Vec4f value = *(Vec4f*)pValue;
        GLCALL(glUniform4f(var->index, value.x, value.y, value.z, value.w));
        break; }
      case ShaderVarType_Int: {
        int value = *(int*)pValue;
        GLCALL(glUniform1i(var->index, value));
        break; }
      case ShaderVarType_Int2: {
        Vec2i value = *(Vec2i*)pValue;
        GLCALL(glUniform2i(var->index, value.x, value.y));
        break; }
      case ShaderVarType_Int3: {
        Vec3i value = *(Vec3i*)pValue;
        GLCALL(glUniform3i(var->index, value.x, value.y, value.z));
        break; }
      case ShaderVarType_Int4: {
        Vec4i value = *(Vec4i*)pValue;
        GLCALL(glUniform4i(var->index, value.x, value.y, value.z, value.w));
        break; }
      case ShaderVarType_Matrix: {
        Shader_ISetMatrix(var->index, *(Matrix**)pValue);
        break; }
      case ShaderVarType_Tex1D: {
        Shader_ISetTex1D(var->index, *(Tex1D**)pValue);
        break; }
      case ShaderVarType_Tex2D: {
        Shader_ISetTex2D(var->index, *(Tex2D**)pValue);
        break; }
      case ShaderVarType_Tex3D: {
        Shader_ISetTex3D(var->index, *(Tex3D**)pValue);
        break; }
      case ShaderVarType_TexCube: {
        Shader_ISetTexCube(var->index, *(TexCube**)pValue);
        break; }
    }
  }
  FRAME_END;
}

void Shader_Stop (Shader*) {
  GLCALL(glUseProgram(0))
  current = 0;
}

static void ShaderCache_FreeElem (cstr, void* data) {
  free(data);
}

void Shader_ClearCache () {
  if (cache) {
    StrMap_FreeEx(cache, ShaderCache_FreeElem);
    cache = 0;
  }
}

uint Shader_GetHandle (Shader* self) {
  return self->program;
}

int Shader_GetVariable (Shader* self, cstr name) {
  int index = glGetUniformLocation(self->program, name);
  if (index == -1)
    Fatal("Shader_GetVariable: Shader <%s> has no variable <%s>", self->name, name);
  return index;
}

bool Shader_HasVariable (Shader* self, cstr name) {
  return glGetUniformLocation(self->program, name) > -1;
}

/* --- Variable Binding ----------------------------------------------------- */

void Shader_SetFloat (cstr name, float value) {
  GLCALL(glUniform1f(GetUniformIndex(current, name), value))
}

void Shader_ISetFloat (int index, float value) {
  GLCALL(glUniform1f(index, value))
}

void Shader_SetFloat2 (cstr name, float x, float y) {
  GLCALL(glUniform2f(GetUniformIndex(current, name), x, y))
}

void Shader_ISetFloat2 (int index, float x, float y) {
  GLCALL(glUniform2f(index, x, y))
}

void Shader_SetFloat3 (cstr name, float x, float y, float z) {
  GLCALL(glUniform3f(GetUniformIndex(current, name), x, y, z))
}

void Shader_ISetFloat3 (int index, float x, float y, float z) {
  GLCALL(glUniform3f(index, x, y, z))
}

void Shader_SetFloat4 (cstr name, float x, float y, float z, float w) {
  GLCALL(glUniform4f(GetUniformIndex(current, name), x, y, z, w))
}

void Shader_ISetFloat4 (int index, float x, float y, float z, float w) {
  GLCALL(glUniform4f(index, x, y, z, w))
}

void Shader_SetInt (cstr name, int value) {
  GLCALL(glUniform1i(GetUniformIndex(current, name), value))
}

void Shader_ISetInt (int index, int value) {
  GLCALL(glUniform1i(index, value))
}

void Shader_SetMatrix (cstr name, Matrix* value) {
  GLCALL(glUniformMatrix4fv(GetUniformIndex(current, name), 1, true, (float*)value))
}

void Shader_SetMatrixT (cstr name, Matrix* value) {
  GLCALL(glUniformMatrix4fv(GetUniformIndex(current, name), 1, false, (float*)value))
}

void Shader_ISetMatrix (int index, Matrix* value) {
  GLCALL(glUniformMatrix4fv(index, 1, true, (float*)value))
}

void Shader_ISetMatrixT (int index, Matrix* value) {
  GLCALL(glUniformMatrix4fv(index, 1, false, (float*)value))
}

void Shader_SetTex1D (cstr name, Tex1D* value) {
  GLCALL(glUniform1i(GetUniformIndex(current, name), current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_1D, Tex1D_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}

void Shader_ISetTex1D (int index, Tex1D* value) {
  GLCALL(glUniform1i(index, current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_1D, Tex1D_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}

void Shader_SetTex2D (cstr name, Tex2D* value) {
  GLCALL(glUniform1i(GetUniformIndex(current, name), current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_2D, Tex2D_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}

void Shader_ISetTex2D (int index, Tex2D* value) {
  GLCALL(glUniform1i(index, current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_2D, Tex2D_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}

void Shader_SetTex3D (cstr name, Tex3D* value) {
  GLCALL(glUniform1i(GetUniformIndex(current, name), current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_3D, Tex3D_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}

void Shader_ISetTex3D (int index, Tex3D* value) {
  GLCALL(glUniform1i(index, current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_3D, Tex3D_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}

void Shader_SetTexCube (cstr name, TexCube* value) {
  GLCALL(glUniform1i(GetUniformIndex(current, name), current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_CUBE_MAP, TexCube_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}

void Shader_ISetTexCube (int index, TexCube* value) {
  GLCALL(glUniform1i(index, current->texIndex))
  GLCALL(glActiveTexture(GL_TEXTURE0 + current->texIndex++))
  GLCALL(glBindTexture(GL_TEXTURE_CUBE_MAP, TexCube_GetHandle(value)))
  GLCALL(glActiveTexture(GL_TEXTURE0))
}


text:
#include "PhxMemory.h"
#include "PhxSignal.h"

#include <signal.h>

/* TODO : Minidump? */

#define SIGNAL_X                                                               \
  X(Signal_Int,  SIGINT,  "Interrupt")                                         \
  X(Signal_Ill,  SIGILL,  "Illegal Instruction")                               \
  X(Signal_Fpe,  SIGFPE,  "FP Exception")                                      \
  X(Signal_Segv, SIGSEGV, "Memory Access Violation")                           \
  X(Signal_Term, SIGTERM, "Terminate")                                         \
  X(Signal_Abrt, SIGABRT, "Abort")

#define X(name, value, desc) const Signal name = value;
SIGNAL_X
#undef X

struct HandlerElem {
  HandlerElem* next;
  SignalHandler fn;
};

static bool ignoreDefault = false;
static SignalHandler handlerDefault[NSIG] = { 0 };
static HandlerElem*  handlerTable[NSIG] = { 0 };

static void SIGCALL Signal_Handler (Signal sig) {
  Warn("Signal_Handler: Caught %s", Signal_ToString(sig));

  /* Re-install default handlers. */ {
#define X(name, value, desc) signal(name, handlerDefault[name]);
  SIGNAL_X
#undef X
  }

  /* Call custom handlers. */ {
    for (HandlerElem* e = handlerTable[sig]; e; e = e->next)
      e->fn(sig);
  }

  if (ignoreDefault) {
    ignoreDefault = false;
    return;
  }

  /* Re-raise the signal to let the default handler run. */
  raise(sig);
}

void Signal_Init () {
#define X(name, value, desc) handlerDefault[name] = signal(name, Signal_Handler);
  SIGNAL_X
#undef X
}

void Signal_Free () {
#define X(name, value, desc) signal(name, handlerDefault[name]);
  SIGNAL_X
#undef X
}

void Signal_AddHandler (Signal sig, SignalHandler fn) {
  HandlerElem* e = MemNew(HandlerElem);
  e->next = handlerTable[sig];
  e->fn = fn;
  handlerTable[sig] = e;
}

void Signal_AddHandlerAll (SignalHandler fn) {
#define X(name, value, desc) Signal_AddHandler(name, fn);
  SIGNAL_X
#undef X
}

void Signal_RemoveHandler (Signal sig, SignalHandler fn) {
  HandlerElem** prev = &handlerTable[sig];
  HandlerElem*  curr =  handlerTable[sig];
  while (curr) {
    if (curr->fn == fn) {
      *prev = curr->next;
      return;
    }
    prev = &curr->next;
    curr =  curr->next;
  }

  Fatal("Signal_RemoveHandler: No such handler installed");
}

void Signal_RemoveHandlerAll (SignalHandler fn) {
#define X(name, value, desc) Signal_RemoveHandler(name, fn);
  SIGNAL_X
#undef X
}

cstr Signal_ToString (Signal self) {
  switch (self) {
#define X(name, value, desc) case name: return desc;
    SIGNAL_X
#undef X
  }
  return "<unknown signal>";
}

void Signal_IgnoreDefault () {
  ignoreDefault = true;
}

repo_name: JoshParnell/libphx

repo_language: C

mime_type: text/x-c++


text:
#include "ArrayList.h"
#include "Button.h"
#include "Device.h"
#include "DeviceType.h"
#include "Input.h"
#include "InputEvent.h"
#include "Modifier.h"
#include "PhxFloat.h"
#include "PhxMath.h"
#include "Profiler.h"
#include "Resource.h"
#include "SDL.h"
#include "State.h"
#include "Vec2.h"

/* --- Private API / Primary Functionality ---------------------------------- */

static const float Threshold_Pressed  = 0.5f;
static const float Threshold_Released = 0.4f;

struct DeviceState {
  int32  transitions[SDL_NUM_SCANCODES];
  bool   buttons[SDL_NUM_SCANCODES];
  float  axes[SDL_NUM_SCANCODES];
  uint32 lastEventTimestamp;
  bool   isConnected;
};

struct DeviceList {
  ArrayList(DeviceState, devices);
};

struct Input {
  Device                activeDevice;
  uint32                lastTimestamp;
  uint32                lastEventTimestamp;
  Vec2i                 lastMousePosition;
  bool                  autoHideMouse;
  DeviceList            deviceLists[DeviceType_COUNT];
  ArrayList(InputEvent, events);
  ArrayList(InputEvent, downButtons);
  ArrayList(InputEvent, autoRelease);
  ArrayList(InputEvent, injectedEvents);
} static self = { 0 };

inline static DeviceState* Input_EnsureDeviceState (Device device) {
  DeviceList* deviceList = &self.deviceLists[device.type];
  while ((uint32) ArrayList_GetSize(deviceList->devices) <= device.id) {
    DeviceState deviceState = {};
    ArrayList_Append(deviceList->devices, deviceState);
  }
  return ArrayList_GetPtr(deviceList->devices, device.id);
}

inline static DeviceState* Input_GetDeviceState (Device device) {
  DeviceList* deviceList = &self.deviceLists[device.type];
  return ArrayList_GetPtr(deviceList->devices, device.id);
}

inline static void Input_SetActiveDevice (Device device) {
  self.activeDevice = device;
  if (self.autoHideMouse)
    SDL_ShowCursor(device.type == DeviceType_Mouse ? SDL_ENABLE : SDL_DISABLE);
}

inline static bool Input_GetDeviceExists (Device device) {
  DeviceList* deviceList = &self.deviceLists[device.type];
  if (device.id < (uint32) ArrayList_GetSize(deviceList->devices)) {
    DeviceState* deviceState = ArrayList_GetPtr(deviceList->devices, device.id);
    return deviceState->isConnected;
  }
  return false;
}

inline static bool Input_GetDevicePressedImpl (Device device, Button button) {
  DeviceState* deviceState = Input_GetDeviceState(device);
  return deviceState->buttons[button]
    ? deviceState->transitions[button] > 0
    : deviceState->transitions[button] > 1;
}

inline static bool Input_GetDeviceDownImpl (Device device, Button button) {
  DeviceState* deviceState = Input_GetDeviceState(device);
  return deviceState->buttons[button] || deviceState->transitions[button] > 0;
}

inline static bool Input_GetDeviceReleasedImpl (Device device, Button button) {
  DeviceState* deviceState = Input_GetDeviceState(device);
  return deviceState->buttons[button]
    ? deviceState->transitions[button] > 1
    : deviceState->transitions[button] > 0;
}

inline static float Input_GetDeviceValueImpl (Device device, Button button) {
  DeviceState* deviceState = Input_GetDeviceState(device);
  return deviceState->axes[button];
}

inline static float Input_GetDeviceIdleTimeImpl (Device device) {
  DeviceState* deviceState = Input_GetDeviceState(device);
  return (self.lastTimestamp - deviceState->lastEventTimestamp) / 1000.0f;
}

inline static State Input_DetermineButtonState (InputEvent event) {
  State buttonState = State_Null;

  DeviceState* deviceState = Input_GetDeviceState(event.device);
  bool down = deviceState->buttons[event.button];
  if (!down && event.value > Threshold_Pressed)
    buttonState |= State_Pressed | State_Down;

  if (down && event.value < Threshold_Released)
    buttonState |= State_Released;

  return buttonState;
}

inline static void Input_AppendEvent (InputEvent event) {
  self.lastTimestamp      = event.timestamp;
  self.lastEventTimestamp = event.timestamp;
  ArrayList_Append(self.events, event);
};

inline static void Input_InjectEvent (InputEvent event) {
  self.lastTimestamp      = event.timestamp;
  self.lastEventTimestamp = event.timestamp;
  ArrayList_Append(self.injectedEvents, event);
};

inline static void Input_SetButton (InputEvent event) {
  Assert(event.button != 0);

  DeviceState* deviceState = Input_GetDeviceState(event.device);
  deviceState->axes[event.button] = event.value;

  bool down = deviceState->buttons[event.button];
  if (!down && HAS_FLAG(event.state, State_Pressed)) {
    deviceState->transitions[event.button]++;
    deviceState->buttons[event.button] = true;

    ArrayList_Append(self.downButtons, event);

    if (event.device.type != DeviceType_Null)
      Input_SetActiveDevice(event.device);
  }

  if (down && HAS_FLAG(event.state, State_Released)) {
    deviceState->transitions[event.button]++;
    deviceState->buttons[event.button] = false;

    ArrayList_ForEachIReverse(self.downButtons, i) {
      if (ArrayList_Get(self.downButtons, i).button == event.button) {
        ArrayList_RemoveAt(self.downButtons, i);
      }
    }
  }

  if (Button_IsAutoRelease(event.button))
    ArrayList_Append(self.autoRelease, event);
}

void Input_Init () {
  /* NOTE : This is a workaround for what appears to be a bug in SDL. Without
   *        this the first click after a window loses and regains focus will not
   *        send events. This still leaves us with not-completely-consistent
   *        behavior. Since the mouse state doesn't get screwed up when using
   *        the keyboard, the first click after alt tabbing will send mouse
   *        down, but only the second click after clicking off the window will
   *        send mouse down. We could further work around this by only only
   *        setting the hint when clicking off the window, but eh, more
   *        complexity for a much less irritating quirk. This bug has been
   *        reported.
   *        https://bugzilla.libsdl.org/show_bug.cgi?id=4165 */
  SDL_bool result = SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
  if (result != SDL_TRUE)
    Warn("Input_Init: SDL_SetHint failed");

  for (int iDev = 0; iDev < DeviceType_COUNT; iDev++) {
    Device device = { iDev, 0 };
    DeviceState* deviceState = Input_EnsureDeviceState(device);
    deviceState->isConnected = iDev != DeviceType_Gamepad;
  }

  ArrayList_Reserve(self.events,         16);
  ArrayList_Reserve(self.downButtons,    16);
  ArrayList_Reserve(self.autoRelease,    16);
  ArrayList_Reserve(self.injectedEvents, 16);

  Device device = { DeviceType_Mouse, 0 };
  Input_SetActiveDevice(device);
}

void Input_Free () {
  for (int iDev = 0; iDev < DeviceType_COUNT; iDev++)
    ArrayList_Free(self.deviceLists[iDev].devices);

  ArrayList_Free(self.events);
  ArrayList_Free(self.downButtons);
  ArrayList_Free(self.autoRelease);
  ArrayList_Free(self.injectedEvents);
}

void Input_Update () {
  FRAME_BEGIN;

  { /* Update Stale Data */
    self.lastTimestamp       = (uint32) SDL_GetTicks();
    self.lastMousePosition.x = (int) Input_GetValue(Button_Mouse_X);
    self.lastMousePosition.y = (int) Input_GetValue(Button_Mouse_Y);

    for (int iDev = 0; iDev < DeviceType_COUNT; iDev++) {
      DeviceList* deviceList = &self.deviceLists[iDev];
      ArrayList_ForEach(deviceList->devices, DeviceState, deviceState) {
        MemSet(deviceState->transitions, 0, sizeof(deviceState->transitions));
      }
    }

    ArrayList_Clear(self.events);
    ArrayList_ForEach(self.injectedEvents, InputEvent, event) {
      Input_AppendEvent(*event);
    }
    ArrayList_Clear(self.injectedEvents);
  }

  { /* Process Down Buttons */
    ArrayList_ForEach(self.autoRelease, InputEvent, down) {
      DeviceState* deviceState = Input_GetDeviceState(down->device);

      if (deviceState->axes[down->button] != 0.0f) {
        down->value     = 0.0f;
        down->state     = State_Changed | Input_DetermineButtonState(*down);
        down->timestamp = (uint32) SDL_GetTicks();
        Input_SetButton(*down);
        Input_AppendEvent(*down);
      }
    }

    ArrayList_ForEach(self.downButtons, InputEvent, down) {
      DeviceState* deviceState = Input_GetDeviceState(down->device);

      down->value     = deviceState->axes[down->button];
      down->state     = State_Down;
      down->timestamp = (uint32) SDL_GetTicks();
      Input_AppendEvent(*down);
    }
  }

  { /* Process New Input */
    SDL_Event sdl;
    while (SDL_PollEvent(&sdl) != 0) {
      InputEvent event = {};
      event.timestamp = sdl.common.timestamp;

      switch(sdl.type) {

        /* DeviceType_Keyboard */
        case SDL_KEYDOWN: {
          if (sdl.key.repeat) continue;
          Device device = { DeviceType_Keyboard, 0 };

          event.device = device;
          event.button = Button_FromSDLScancode(sdl.key.keysym.scancode);
          event.value  = 1.0f;
          event.state  = State_Changed | State_Pressed | State_Down;
          if (event.button == Button_Null) continue;
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        case SDL_KEYUP: {
          Device device = { DeviceType_Keyboard, 0 };

          event.device = device;
          event.button = Button_FromSDLScancode(sdl.key.keysym.scancode);
          event.value  = 0.0f;
          event.state  = State_Changed | State_Released;
          if (event.button == Button_Null) continue;
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        /* DeviceType_Mouse */
        case SDL_MOUSEBUTTONDOWN: {
          Device device = { DeviceType_Mouse, sdl.button.which };

          event.device = device;
          event.button = Button_FromSDLMouseButton(sdl.button.button);
          event.value  = 1.0f;
          event.state  = State_Changed | State_Pressed | State_Down;
          Input_EnsureDeviceState(event.device);
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        case SDL_MOUSEBUTTONUP: {
          Device device = { DeviceType_Mouse, sdl.button.which };

          event.device = device;
          event.button = Button_FromSDLMouseButton(sdl.button.button);
          event.value  = 0.0f;
          event.state  = State_Changed | State_Released;
          Input_EnsureDeviceState(event.device);
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        case SDL_MOUSEMOTION: {
          /* NOTE : Mouse motion never causes pressed/down/released events. */
          Device device = { DeviceType_Mouse, sdl.motion.which };
          DeviceState* deviceState = Input_EnsureDeviceState(device);

          event.device = device;
          event.button = Button_Mouse_X;
          event.value  = (float) sdl.motion.x;
          event.state  = State_Changed;
          if (event.value != deviceState->axes[event.button]) {
            deviceState->axes[event.button] = event.value;
            Input_SetActiveDevice(event.device);
            Input_AppendEvent(event);
          }

          event.device = device;
          event.button = Button_Mouse_Y;
          event.value  = (float) sdl.motion.y;
          event.state  = State_Changed;
          if (event.value != deviceState->axes[event.button]) {
            deviceState->axes[event.button] = event.value;
            Input_SetActiveDevice(event.device);
            Input_AppendEvent(event);
          }
        }
        break;

        case SDL_MOUSEWHEEL: {
          Device device = { DeviceType_Mouse, sdl.wheel.which };
          Input_EnsureDeviceState(device);

          event.device = device;
          event.button = Button_Mouse_ScrollX;
          event.value  = (float) sdl.wheel.x;
          event.state  = State_Changed | Input_DetermineButtonState(event);
          if (event.value != Input_GetDeviceValueImpl(event.device, event.button)) {
            Input_SetButton(event);
            Input_AppendEvent(event);
          }

          event.device = device;
          event.button = Button_Mouse_ScrollY;
          event.value  = (float) sdl.wheel.y;
          event.state  = State_Changed | Input_DetermineButtonState(event);
          if (event.value != Input_GetDeviceValueImpl(event.device, event.button)) {
            Input_SetButton(event);
            Input_AppendEvent(event);
          }
        }
        break;

        /* DeviceType_Gamepad */
        case SDL_CONTROLLERBUTTONDOWN: {
          Device device = { DeviceType_Gamepad, (uint32) sdl.cbutton.which };

          event.device = device;
          event.button = Button_FromSDLControllerButton((SDL_GameControllerButton) sdl.cbutton.button);
          event.value  = 1.0f;
          event.state  = State_Changed | State_Pressed | State_Down;
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        case SDL_CONTROLLERBUTTONUP: {
          Device device = { DeviceType_Gamepad, (uint32) sdl.cbutton.which };

          event.device = device;
          event.button = Button_FromSDLControllerButton((SDL_GameControllerButton) sdl.cbutton.button);
          event.value  = 0.0f;
          event.state  = State_Changed | State_Released;
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        case SDL_CONTROLLERAXISMOTION: {
          Device device = { DeviceType_Gamepad, (uint32) sdl.caxis.which };
          float value = Clamp((float) sdl.caxis.value / 32767.0f, -1.0f, 1.0f);
          SDL_GameControllerAxis axis = (SDL_GameControllerAxis) sdl.caxis.axis;
          if (axis == SDL_CONTROLLER_AXIS_LEFTY || axis == SDL_CONTROLLER_AXIS_RIGHTY)
            value = -value;

          event.device = device;
          event.button = Button_FromSDLControllerAxis(axis);
          event.value  = value;
          event.state  = State_Changed | Input_DetermineButtonState(event);
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        case SDL_CONTROLLERDEVICEADDED: {
          if (SDL_IsGameController(sdl.cdevice.which) == SDL_TRUE) {
            SDL_GameController* sdlController = SDL_GameControllerOpen(sdl.cdevice.which);
            if (sdlController == 0) {
              Warn("Input_Update: SDL_GameControllerOpen failed");
            }
            else {
              SDL_Joystick* sdlJoystick = SDL_GameControllerGetJoystick(sdlController);
              uint32        id          = (uint32) SDL_JoystickInstanceID(sdlJoystick);
              Device        device      = { DeviceType_Gamepad, id };
              DeviceState*  deviceState = Input_EnsureDeviceState(device);
              deviceState->isConnected  = true;
            }
          }
        }
        break;

        case SDL_CONTROLLERDEVICEREMOVED: {
          /* NOTE : SDL already sends events to zero out all game controller
           *        input so there's no need to do it manually. */

          Device        device      = { DeviceType_Gamepad, (uint32) sdl.cdevice.which };
          DeviceState*  deviceState = Input_GetDeviceState(device);
          deviceState->isConnected  = false;

          SDL_GameController* sdlController = SDL_GameControllerFromInstanceID(sdl.cdevice.which);
          if (sdlController)
            SDL_GameControllerClose(sdlController);
        }
        break;

        /* TODO : Maybe we should release all input then re-set it? */
        case SDL_CONTROLLERDEVICEREMAPPED: {
          SDL_GameController* sdlController = SDL_GameControllerFromInstanceID(sdl.cdevice.which);
          Device              device        = { DeviceType_Gamepad, (uint32) sdl.cdevice.which };
          DeviceState*        deviceState   = Input_GetDeviceState(device);

          for (int32 iBtn = Button_Gamepad_Button_First; iBtn <= Button_Gamepad_Button_Last; iBtn++) {
            float value = (float) SDL_GameControllerGetButton(sdlController, Button_ToSDLControllerButton(iBtn));
            if (value != deviceState->axes[iBtn]) {
              event.device = device;
              event.button = iBtn;
              event.value  = value;
              event.state  = State_Changed | Input_DetermineButtonState(event);
              Input_SetButton(event);
              Input_AppendEvent(event);
            }
          }

          for (int32 iAxis = Button_Gamepad_Axis_First; iAxis <= Button_Gamepad_Axis_Last; iAxis++) {
            float value = (float) SDL_GameControllerGetAxis(sdlController, Button_ToSDLControllerAxis(iAxis));
            value = Clamp(value / 32767.0f, -1.0f, 1.0f);
            if (iAxis == Button_Gamepad_LStickY || iAxis == Button_Gamepad_RStickY)
              value = -value;

            if (value != deviceState->axes[iAxis]) {
              event.device = device;
              event.button = iAxis;
              event.value  = value;
              event.state  = State_Changed | Input_DetermineButtonState(event);
              Input_SetButton(event);
              Input_AppendEvent(event);
            }
          }
        }
        break;

        /* DeviceType_Null */
        case SDL_QUIT: {
          Device device = { DeviceType_Null, 0 };

          event.device = device;
          event.button = Button_System_Exit;
          event.value  = 0.0f;
          event.state  = State_Changed | State_Pressed | State_Down;
          Input_SetButton(event);
          Input_AppendEvent(event);
        }
        break;

        case SDL_WINDOWEVENT: {
          if (sdl.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
            SDL_CaptureMouse(SDL_TRUE);

          /* TODO : Test button release on focus loss */
          if (sdl.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
            SDL_CaptureMouse(SDL_FALSE);

            /* OPTIMIZE : Do this without incurring the cost of the search and
             *            removes in SetButton */
            ArrayList_ForEach(self.downButtons, InputEvent, down) {
              down->timestamp = sdl.common.timestamp;
              down->value     = 0.0f;
              down->state     = State_Changed | Input_DetermineButtonState(event);
              Input_SetButton(*down);
              Input_AppendEvent(*down);
            }
          }
        }
        break;
      }
    }
  }

  FRAME_END;
}

void Input_LoadGamepadDatabase (cstr name) {
  cstr path = Resource_GetPath(ResourceType_Other, name);
  int result = SDL_GameControllerAddMappingsFromFile(path);
  if (result == -1)
    Fatal("Input_Init: Failed to add gamepad mappings");
}

/* --- Direct Query API (Automatic Device) ---------------------------------- */

bool Input_GetPressed (Button button) {
  Device device = { Button_ToDeviceType(button), 0 };
  return Input_GetDevicePressedImpl(device, button);
}

bool Input_GetDown (Button button) {
  Device device = { Button_ToDeviceType(button), 0 };
  return Input_GetDeviceDownImpl(device, button);
}

bool Input_GetReleased (Button button) {
  Device device = { Button_ToDeviceType(button), 0 };
  return Input_GetDeviceReleasedImpl(device, button);
}

float Input_GetValue (Button button) {
  Device device = { Button_ToDeviceType(button), 0 };
  return Input_GetDeviceValueImpl(device, button);
}

float Input_GetIdleTime () {
  return (self.lastTimestamp - self.lastEventTimestamp) / 1000.0f;
}

/* --- Direct Query API (Active Device) ------------------------------------- */

void Input_GetActiveDevice (Device* device) {
  *device = self.activeDevice;
}

DeviceType Input_GetActiveDeviceType () {
  return self.activeDevice.type;
}

uint32 Input_GetActiveDeviceID () {
  return self.activeDevice.id;
}

float Input_GetActiveDeviceIdleTime () {
  return Input_GetDeviceIdleTimeImpl(self.activeDevice);
}

/* --- Direct Query API (Specified Device) ---------------------------------- */

bool Input_GetDevicePressed (Device* device, Button button) {
  if (!Input_GetDeviceExists(*device))
    return false;
  return Input_GetDevicePressedImpl(*device, button);
}

bool Input_GetDeviceDown (Device* device, Button button) {
  if (!Input_GetDeviceExists(*device))
    return false;
  return Input_GetDeviceDownImpl(*device, button);
}

bool Input_GetDeviceReleased (Device* device, Button button) {
  if (!Input_GetDeviceExists(*device))
    return false;
  return Input_GetDeviceReleasedImpl(*device, button);
}

float Input_GetDeviceValue (Device* device, Button button) {
  if (!Input_GetDeviceExists(*device))
    return 0.0f;
  return Input_GetDeviceValueImpl(*device, button);
}

float Input_GetDeviceIdleTime (Device* device) {
  if (!Input_GetDeviceExists(*device))
    return FLT_MAX;
  return Input_GetDeviceIdleTimeImpl(*device);
}

/* --- Direct Query API (Mouse Device) -------------------------------------- */

void Input_GetMouseDelta (Vec2i* delta) {
  delta->x = (int) Input_GetValue(Button_Mouse_X) - self.lastMousePosition.x;
  delta->y = (int) Input_GetValue(Button_Mouse_Y) - self.lastMousePosition.y;
}

float Input_GetMouseIdleTime () {
  Device device = { DeviceType_Mouse, 0 };
  return Input_GetDeviceIdleTimeImpl(device);
}

void Input_GetMousePosition (Vec2i* position) {
  Device device = { DeviceType_Mouse, 0 };
  position->x = (int) Input_GetDeviceValueImpl(device, Button_Mouse_X);
  position->y = (int) Input_GetDeviceValueImpl(device, Button_Mouse_Y);
}

void Input_GetMouseScroll (Vec2i* scroll) {
  Device device = { DeviceType_Mouse, 0 };
  scroll->x = (int) Input_GetDeviceValueImpl(device, Button_Mouse_ScrollX);
  scroll->y = (int) Input_GetDeviceValueImpl(device, Button_Mouse_ScrollY);
}

void Input_SetMousePosition (Vec2i* position) {
  SDL_WarpMouseInWindow(0, position->x, position->y);
}

void Input_SetMouseVisible (bool visible) {
  self.autoHideMouse = false;
  SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
}

void Input_SetMouseVisibleAuto () {
  self.autoHideMouse = true;
  Input_SetActiveDevice(self.activeDevice);
}

void Input_SetMouseScroll (Vec2i* scroll) {
  uint32 timestamp = (uint32) SDL_GetTicks();
  Device device    = { DeviceType_Mouse, 0 };

  InputEvent event = {};

  event.timestamp = timestamp;
  event.device    = device;
  event.button    = Button_Mouse_ScrollX;
  event.value     = (float) scroll->x;
  event.state     = State_Changed | Input_DetermineButtonState(event);
  if (event.value != Input_GetDeviceValueImpl(event.device, event.button))
    Input_InjectEvent(event);

  event.timestamp = timestamp;
  event.device    = device;
  event.button    = Button_Mouse_ScrollY;
  event.value     = (float) scroll->y;
  event.state     = State_Changed | Input_DetermineButtonState(event);
  if (event.value != Input_GetDeviceValueImpl(event.device, event.button))
    Input_InjectEvent(event);
}

/* --- Direct Query API (Keyboard Device) ----------------------------------- */

float Input_GetKeyboardIdleTime () {
  Device device = { DeviceType_Keyboard, 0 };
  return Input_GetDeviceIdleTimeImpl(device);
}

bool Input_GetKeyboardMod (Modifier modifier) {
  bool hasMod = true;
  if (HAS_FLAG(modifier, Modifier_Alt))   hasMod &= Input_GetKeyboardAlt();
  if (HAS_FLAG(modifier, Modifier_Ctrl))  hasMod &= Input_GetKeyboardCtrl();
  if (HAS_FLAG(modifier, Modifier_Shift)) hasMod &= Input_GetKeyboardShift();
  return hasMod;
}

bool Input_GetKeyboardAlt () {
  Device device = { DeviceType_Keyboard, 0 };
  DeviceState* deviceState = Input_GetDeviceState(device);
  return deviceState->buttons[Button_Keyboard_LAlt] || deviceState->buttons[Button_Keyboard_RAlt];
}

bool Input_GetKeyboardCtrl () {
  Device device = { DeviceType_Keyboard, 0 };
  DeviceState* deviceState = Input_GetDeviceState(device);
  return deviceState->buttons[Button_Keyboard_LCtrl] || deviceState->buttons[Button_Keyboard_RCtrl];
}

bool Input_GetKeyboardShift () {
  Device device = { DeviceType_Keyboard, 0 };
  DeviceState* deviceState = Input_GetDeviceState(device);
  return deviceState->buttons[Button_Keyboard_LShift] || deviceState->buttons[Button_Keyboard_RShift];
}

/* --- Direct Query API (Gamepad Device) ------------------------------------ */

float Input_GetGamepadIdleTime (uint32 id) {
  Device device = { DeviceType_Gamepad, id };
  if (!Input_GetDeviceExists(device))
    return FLT_MAX;
  return Input_GetDeviceIdleTimeImpl(device);
}

bool Input_GetGamepadPressed (uint32 id, Button button) {
  Device device = { DeviceType_Gamepad, id };
  if (!Input_GetDeviceExists(device))
    return false;
  return Input_GetDevicePressedImpl(device, button);
}

bool Input_GetGamepadDown (uint32 id, Button button) {
  Device device = { DeviceType_Gamepad, id };
  if (!Input_GetDeviceExists(device))
    return false;
  return Input_GetDeviceDownImpl(device, button);
}

bool Input_GetGamepadReleased (uint32 id, Button button) {
  Device device = { DeviceType_Gamepad, id };
  if (!Input_GetDeviceExists(device))
    return false;
  return Input_GetDeviceReleasedImpl(device, button);
}

float Input_GetGamepadValue (uint32 id, Button button) {
  Device device = { DeviceType_Gamepad, id };
  if (!Input_GetDeviceExists(device))
    return 0.0f;
  return Input_GetDeviceValueImpl(device, button);
}

/* --- Event Loop API ------------------------------------------------------- */

int32 Input_GetEventCount () {
  return ArrayList_GetSize(self.events);
}

bool Input_GetNextEvent (InputEvent* event) {
  if (ArrayList_GetSize(self.events) == 0) return false;
  FRAME_BEGIN;

  *event = ArrayList_Get(self.events, 0);
  ArrayList_RemoveAt(self.events, 0);

  FRAME_END;
  return true;
}

/* NOTE : Controller instance ID's are signed (sdl.cbutton/caxis/cdevice.which)
 *        while mouse instance ID's are unsigned (sdl.button/motion/wheel.which).
 *        This is a bit annoying since we can store both in Device.id. I've
 *        opted to store them as uint32's since they're used as array indices
 *        here (and presumably in SDL as well). */

/* NOTE : We keep a permanent DeviceState for every joystick instance id which
 *        is montonically increasing each time a joystick is connected. This is
 *        kind of leaky in a sense, but realistically seems like it's never
 *        going to be a problem. */

/* NOTE : SDL GameController Info
 *        SDL_CONTROLLERDEVICEADDED is always sent when a controller is connected
 *        SDL_CONTROLLERDEVICEREMOVED is only sent if the controller was opened
 *        SDL sends events to zero all controller input right before device removal
 *        SDL does not send any events to fix up input when a controller is remapped */

/* TODO : I changed instantaneous events such as Button_System_Exit to be
 *        pressed in the current frame then auto-released in the following
 *        frame. This probably makes sense for Button_Mouse_ScrollX/Y because we
 *        want GetValue queries to return the scroll value throughout the entire
 *        frame, but maybe makes less sense for Exit where the value is not
 *        terribly important and GetPressed will already return true. Supporting
 *        this, any random button that is pressed and released in the same frame
 *        already behave that way: value will be 0 and pressed will be true. */


text:
#include "OpenGL.h"
#include "Vec2.h"
#include "Viewport.h"

#define MAX_STACK_DEPTH 16

/* TODO : This is a low-level mechanism and probably not for use outside of
 *        RenderTarget. Should likely be folded into RenderTarget. */

struct VP {
  int x, y, sx, sy;
  bool isWindow;
};

static int vpIndex = -1;
static VP vp[MAX_STACK_DEPTH];

static void Viewport_Set (VP const* self) {
  GLCALL(glViewport(self->x, self->y, self->sx, self->sy))
  GLCALL(glMatrixMode(GL_PROJECTION))
  GLCALL(glLoadIdentity())

  /* GL's window coordinates and texture coordinates have opposite vertical
   * orientation. Automatically compensate via the projection matrix. */
  if (self->isWindow) {
    GLCALL(glTranslatef(-1.0, 1.0, 0.0))
    GLCALL(glScalef(2.0f / self->sx, -2.0f / self->sy, 1.0f))
  } else {
    GLCALL(glTranslatef(-1.0, -1.0, 0.0))
    GLCALL(glScalef(2.0f / self->sx,  2.0f / self->sy, 1.0f))
  }
}

float Viewport_GetAspect () {
  if (vpIndex < 0)
    Fatal("Viewport_GetAspect: Viewport stack is empty");
  return (float)vp[vpIndex].sx / (float)vp[vpIndex].sy;
}

void Viewport_GetSize (Vec2i* out) {
  if (vpIndex < 0)
    Fatal("Viewport_GetSize: Viewport stack is empty");
  out->x = vp[vpIndex].sx;
  out->y = vp[vpIndex].sy;
}

void Viewport_Push (int x, int y, int sx, int sy, bool isWindow) {
  if (vpIndex + 1 >= MAX_STACK_DEPTH)
    Fatal("Viewport_Push: Maximum viewport stack depth exceeded");
  vpIndex++;
  VP* self = vp + vpIndex;
  self->x = x;
  self->y = y;
  self->sx = sx;
  self->sy = sy;
  self->isWindow = isWindow;
  Viewport_Set(self);
}

void Viewport_Pop () {
  if (vpIndex < 0)
    Fatal("Viewport_Pop: Viewport stack is empty");
  vpIndex--;
  if (vpIndex >= 0)
    Viewport_Set(vp + vpIndex);
}


text:
#include "Common.h"
#include "Lua.h"
#include "PhxMemory.h"
#include "PhxString.h"
#include "Profiler.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef WINDOWS
  #include <windows.h>

  static void Fatal_Output (cstr message) {
    /* TODO : We want this in Visual Studio where stdout/err don't go to the
     *       Console window, but not in VS Code where it does. */
    //OutputDebugStringA(message);
    fprintf(stderr, "%s\n", message);

    #ifndef BUILD_DISTRIBUTABLE
      int result = MessageBox(0,
        StrAdd(message, "\n\nBreak into debugger? (Cancel to continue execution)"),
        "PHX Engine: Fatal Error", MB_YESNOCANCEL | MB_ICONWARNING | MB_DEFBUTTON2);
      switch(result) {
        case IDYES:
          Profiler_Backtrace();
          Lua_Backtrace();
          DEBUG_BREAK;
          exit(1);
          break;

        case IDNO:
          abort();
          break;

        case IDCANCEL:
          break;
      }
    #else
      abort();
    #endif
  }
#else
  static void Fatal_Output (cstr message) {
    puts(message);
    abort();
  }
#endif

void Fatal (cstr format, ...) {
  va_list args;
  va_start(args, format);
  int len = vsnprintf(0, 0, format, args) + 1;
  va_end(args);

  char* message = MemNewArray(char, len);
  va_start(args, format);
  vsnprintf(message, len, format, args);
  va_end(args);

  Fatal_Output(message);
}

void Warn (cstr format, ...) {
  va_list args;
  va_start(args, format);
  int len = vsnprintf(0, 0, format, args) + 1;
  va_end(args);

  char* message = MemNewArray(char, len);
  va_start(args, format);
  vsnprintf(message, len, format, args);
  va_end(args);

  fprintf(stdout, "%s\n", message);
  fflush(stdout);
  free(message);
}


text:
#include "Bullet.h"
#include "CollisionShape.h"
#include "PhxMath.h"
#include "Physics.h"
#include "PhysicsDefs.h"
#include "Quat.h"
#include "Ray.h"
#include "RenderState.h"
#include "RigidBody.h"
#include "Shader.h"
#include "Sphere.h"
#include "Trigger.h"

struct Physics {
  btDefaultCollisionConfiguration*     collisionConfiguration;
  btCollisionDispatcher*               dispatcher;
  btBroadphaseInterface*               broadphase;
  btSequentialImpulseConstraintSolver* solver;
  btDiscreteDynamicsWorld*             dynamicsWorld;
  DebugDrawer*                         debugDrawer;
  btGhostPairCallback*                 ghostCallback;
  BroadphaseFilterCallback*            broadphaseFilterCallback;
  ArrayList(Trigger*,                  triggers);
};

Physics* Physics_Create () {
  Physics* self = MemNewZero(Physics);

  /* NOTE : btSequentialImpulseConstraintSolver is Projected Gauss Seidel
            btDbvtBroadphase is dynamic bounding volume tree */
  self->collisionConfiguration   = new btDefaultCollisionConfiguration();
  self->dispatcher               = new btCollisionDispatcher(self->collisionConfiguration);
  self->broadphase               = new btDbvtBroadphase();
  self->solver                   = new btSequentialImpulseConstraintSolver();
  self->dynamicsWorld            = new btDiscreteDynamicsWorld(self->dispatcher, self->broadphase, self->solver, self->collisionConfiguration);
  self->debugDrawer              = new DebugDrawer();
  self->ghostCallback            = new btGhostPairCallback();
  self->broadphaseFilterCallback = new BroadphaseFilterCallback();
  ArrayList_Reserve(self->triggers, 32);

  self->dynamicsWorld->setDebugDrawer(self->debugDrawer);
  self->dynamicsWorld->setGravity(btVector3(0, 0, 0));
  self->dynamicsWorld->setWorldUserInfo(self);

  btContactSolverInfo& solverInfo = self->dynamicsWorld->getSolverInfo();
  solverInfo.m_numIterations = 4;

  btOverlappingPairCache* pairCache = self->broadphase->getOverlappingPairCache();
  pairCache->setInternalGhostPairCallback(self->ghostCallback);
  pairCache->setOverlapFilterCallback(self->broadphaseFilterCallback);

  return self;
}

void Physics_Free (Physics* self) {
  /* NOTE: Collision objects have a world index and broadphase proxy that need
           to be cleared so actually have to remove them all. */

  btCollisionObjectArray& collisionObjects = self->dynamicsWorld->getCollisionObjectArray();
  for (int i = collisionObjects.size() - 1; i >= 0; i--) {
    btCollisionObject* collisionObject = collisionObjects[i];
    void*              userPointer     = collisionObject->getUserPointer();

    PhysicsType type = *((PhysicsType*) userPointer);
    switch (type) {
      case PhysicsType_RigidBody: {
        RigidBody* rigidBody = (RigidBody*) userPointer;
        rigidBody->physics = 0;
        self->dynamicsWorld->removeRigidBody(rigidBody->handle);
        break;
      }

      case PhysicsType_Trigger: {
        Trigger* trigger = (Trigger*) userPointer;
        trigger->physics = 0;
        self->dynamicsWorld->removeCollisionObject(trigger->handle);
        break;
      }
    }
  }

  ArrayList_Free(self->triggers);
  delete self->broadphaseFilterCallback;
  delete self->ghostCallback;
  delete self->debugDrawer;
  delete self->dynamicsWorld;
  delete self->solver;
  delete self->broadphase;
  delete self->dispatcher;
  delete self->collisionConfiguration;
  MemFree(self);
}

void Physics_AddRigidBody (Physics* self, RigidBody* rigidBody) {
  /* TODO: I'd rather fatal here, but RigidBody.lua adds indiscriminately. */
  if (RigidBody_IsChild(rigidBody)) return;

  if (rigidBody->physics)
    Fatal("Physics_Add: Object is already added to physics.");

  typedef btCollisionObject::CollisionFlags Flags;
  int flags = rigidBody->handle->getCollisionFlags();
  bool collidable = !HAS_FLAG(flags, Flags::CF_NO_CONTACT_RESPONSE);
  int group = collidable ? rigidBody->collisionGroup : 0;

  rigidBody->physics = self;
  self->dynamicsWorld->addRigidBody(rigidBody->handle, group, rigidBody->collisionMask);

  RigidBody* child = rigidBody;
  while (child) {
    Trigger* trigger = child->triggers;
    while (trigger) {
      Physics_AddTrigger(self, trigger);
      trigger = trigger->next;
    }
    child = child->next;
  }
}

void Physics_RemoveRigidBody (Physics* self, RigidBody* rigidBody) {
  /* TODO: I'd rather fatal here, but RigidBody.lua removes indiscriminately. */
  if (RigidBody_IsChild(rigidBody)) return;

  if (!rigidBody->physics)
    Fatal("Physics_Remove: Object is not added to physics.");
  if (rigidBody->physics != self)
    Fatal("Physics_Remove: Object is not added to this physics world.");

  rigidBody->physics = 0;
  self->dynamicsWorld->removeRigidBody(rigidBody->handle);

  RigidBody* child = rigidBody;
  while (child) {
    Trigger* trigger = child->triggers;
    while (trigger) {
      Physics_RemoveTrigger(self, trigger);
      trigger = trigger->next;
    }
    child = child->next;
  }
}

void Physics_AddTrigger (Physics* self, Trigger* trigger) {
  if (trigger->physics)
    Fatal("Physics_AddTrigger: Object is already added to physics.");

  ArrayList_Append(self->triggers, trigger);
  trigger->physics = self;
  self->dynamicsWorld->addCollisionObject(trigger->handle, trigger->collisionGroup, trigger->collisionMask);
}

void Physics_RemoveTrigger (Physics* self, Trigger* trigger) {
  if (!trigger->physics)
    Fatal("Physics_RemoveTrigger: Object is not added to physics.");
  if (trigger->physics != self)
    Fatal("Physics_RemoveTrigger: Object is not added to this physics world.");

  ArrayList_RemoveFast(self->triggers, trigger);
  trigger->physics = 0;
  self->dynamicsWorld->removeCollisionObject(trigger->handle);
}

bool Physics_GetNextCollision (Physics* self, Collision* it) {
  int collisionCount = self->dispatcher->getNumManifolds();
  while (it->index < collisionCount) {
    btPersistentManifold* manifold = self->dispatcher->getManifoldByIndexInternal(it->index++);

    if (manifold->getNumContacts() > 0) {
      btManifoldPoint point = manifold->getContactPoint(0);
      it->count++;
      it->body0 = (RigidBody*) manifold->getBody0()->getUserPointer();
      it->body1 = (RigidBody*) manifold->getBody1()->getUserPointer();
      if (RigidBody_IsCompound(it->body0)) it->body0 = RigidBody_GetPart(it->body0, point.m_index0);
      if (RigidBody_IsCompound(it->body1)) it->body1 = RigidBody_GetPart(it->body1, point.m_index1);
      return true;
    }
  }
  it->body0 = 0;
  it->body1 = 0;
  return false;
}

void Physics_Update (Physics* self, float dt) {
  for (int i = 0; i < ArrayList_GetSize(self->triggers); i++) {
    Trigger* trigger = ArrayList_Get(self->triggers, i);
    Trigger_Update(trigger);
  }
  self->dynamicsWorld->stepSimulation(dt, 0);
}

void Physics_RayCast (Physics* self, Ray* ray, RayCastResult* result) {
  typedef btTriangleRaycastCallback::EFlags Flags;

  Vec3f from; Ray_GetPoint(ray, ray->tMin, &from);
  Vec3f to;   Ray_GetPoint(ray, ray->tMax, &to);
  btVector3 btFrom = Vec3f_ToBullet(&from);
  btVector3 btTo   = Vec3f_ToBullet(&to);

  *result = {};
  RayCastCallback callback = {};
  callback.ray                    = ray;
  callback.result                 = result;
  callback.m_flags                = Flags::kF_FilterBackfaces | Flags::kF_UseSubSimplexConvexCastRaytest;
  callback.m_collisionFilterGroup = CollisionGroup_Default;
  callback.m_collisionFilterMask  = CollisionMask_All;
  self->dynamicsWorld->rayTest(btFrom, btTo, callback);
}

inline static void Physics_ShapeCast (Physics* self, btConvexShape* shape, Vec3f* pos, Quat* rot, ShapeCastResult* result) {
  btTransform btPos = btTransform(Quat_ToBullet(rot), Vec3f_ToBullet(pos));
  *result = {};

  btCollisionObject* proxy = new btCollisionObject();
  proxy->setCollisionShape(shape);
  proxy->setWorldTransform(btPos);

  ShapeCastCallback callback = {};
  callback.castShape              = proxy;
  callback.result                 = result;
  callback.m_collisionFilterGroup = CollisionGroup_Default;
  callback.m_collisionFilterMask  = CollisionMask_All;
  self->dynamicsWorld->contactTest(proxy, callback);
}

void Physics_SphereCast (Physics* self, Sphere* sphere, ShapeCastResult* result) {
  btSphereShape btSphere = btSphereShape(sphere->r);
  Quat rot; Quat_Identity(&rot);
  Physics_ShapeCast(self, &btSphere, &sphere->p, &rot, result);
}

void Physics_BoxCast (Physics* self, Vec3f* pos, Quat* rot, Vec3f* halfExtents, ShapeCastResult* result) {
  btBoxShape btBox = btBoxShape(Vec3f_ToBullet(halfExtents));
  Physics_ShapeCast(self, &btBox, pos, rot, result);
}

inline static bool Physics_ShapeOverlap (Physics* self, btConvexShape* shape, Vec3f* pos, Quat* rot) {
  btTransform btPos = btTransform(Quat_ToBullet(rot), Vec3f_ToBullet(pos));

  btCollisionObject* proxy = new btCollisionObject();
  proxy->setCollisionShape(shape);
  proxy->setWorldTransform(btPos);

  ShapeOverlapCallback callback = {};
  callback.castShape              = proxy;
  callback.m_collisionFilterGroup = CollisionGroup_Default;
  callback.m_collisionFilterMask  = CollisionMask_All;
  self->dynamicsWorld->contactTest(proxy, callback);
  return callback.foundHit;
}

bool Physics_SphereOverlap (Physics* self, Sphere* sphere) {
  btSphereShape btSphere = btSphereShape(sphere->r);
  Quat rot; Quat_Identity(&rot);
  return Physics_ShapeOverlap(self, &btSphere, &sphere->p, &rot);
}

bool Physics_BoxOverlap (Physics* self, Vec3f* pos, Quat* rot, Vec3f* halfExtents) {
  btBoxShape btBox = btBoxShape(Vec3f_ToBullet(halfExtents));
  return Physics_ShapeOverlap(self, &btBox, pos, rot);
}

void Physics_PrintProfiling (Physics* self) {
  if (!self->dynamicsWorld) return;

  int numActive = 0;
  btCollisionObjectArray& collisionObjects = self->dynamicsWorld->getCollisionObjectArray();
  for (int i = 0; i < collisionObjects.size(); i++) {
    btCollisionObject* collisionObject = collisionObjects[i];
    if (collisionObject->isActive())
      numActive++;
  }
  Warn("Active %i / Total %i", numActive, collisionObjects.size());

  CProfileManager::dumpAll();
}

void Physics_DrawBoundingBoxesLocal (Physics* self) {
  RenderState_PushWireframe(true);
  btCollisionObjectArray& collisionObjects = self->dynamicsWorld->getCollisionObjectArray();
  for (int i = 0; i < collisionObjects.size(); i++) {
    btCollisionObject* collisionObject = collisionObjects[i];
    void*              userPointer     = collisionObject->getUserPointer();

    PhysicsType type = *((PhysicsType*) userPointer);
    if (type == PhysicsType_RigidBody) {
      RigidBody* rigidBody = (RigidBody*) userPointer;

      if (RigidBody_IsParent(rigidBody)) {
        Box3f box; RigidBody_GetBoundingBoxLocalCompound(rigidBody, &box);
        Vec3f pos; RigidBody_GetPos(rigidBody, &pos);
        Quat  rot; RigidBody_GetRot(rigidBody, &rot);
        Matrix* mat = Matrix_FromPosRot(&pos, &rot);
        Shader_SetMatrix("mWorld", mat);
        Draw_Box3(&box);
        Matrix_Free(mat);
      }

      while (rigidBody) {
        Box3f box; RigidBody_GetBoundingBoxLocal(rigidBody, &box);
        Vec3f pos; RigidBody_GetPos(rigidBody, &pos);
        Quat  rot; RigidBody_GetRot(rigidBody, &rot);
        Matrix* mat = Matrix_FromPosRot(&pos, &rot);
        Shader_SetMatrix("mWorld", mat);
        Draw_Box3(&box);
        Matrix_Free(mat);
        rigidBody = rigidBody->next;
      }
    }
  }
  RenderState_PopWireframe();
}

void Physics_DrawBoundingBoxesWorld (Physics* self) {
  RenderState_PushWireframe(true);
  btCollisionObjectArray& collisionObjects = self->dynamicsWorld->getCollisionObjectArray();
  for (int i = 0; i < collisionObjects.size(); i++) {
    btCollisionObject* collisionObject = collisionObjects[i];
    void*              userPointer     = collisionObject->getUserPointer();

    PhysicsType type = *((PhysicsType*) userPointer);
    if (type == PhysicsType_RigidBody) {
      RigidBody* rigidBody = (RigidBody*) userPointer;

      if (RigidBody_IsParent(rigidBody)) {
        Box3f box; RigidBody_GetBoundingBoxCompound(rigidBody, &box);
        Draw_Box3(&box);
      }

      while (rigidBody) {
        Box3f box; RigidBody_GetBoundingBox(rigidBody, &box);
        Draw_Box3(&box);
        rigidBody = rigidBody->next;
      }
    }
  }
  RenderState_PopWireframe();
}

void Physics_DrawTriggers (Physics* self) {
  RenderState_PushWireframe(true);
  btCollisionObjectArray& collisionObjects = self->dynamicsWorld->getCollisionObjectArray();
  for (int i = 0; i < collisionObjects.size(); i++) {
    btCollisionObject* collisionObject = collisionObjects[i];
    void*              userPointer     = collisionObject->getUserPointer();

    PhysicsType type = *((PhysicsType*) userPointer);
    if (type == PhysicsType_Trigger) {
      Trigger* trigger = (Trigger*) userPointer;

      Box3f box; Trigger_GetBoundingBox(trigger, &box);
      Draw_Box3(&box);
    }
  }
  RenderState_PopWireframe();
}

void Physics_DrawWireframes (Physics* self) {
  self->debugDrawer->setDebugMode(btIDebugDraw::DBG_DrawWireframe);

  btCollisionObjectArray& collisionObjects = self->dynamicsWorld->getCollisionObjectArray();
  for (int i = 0; i < collisionObjects.size(); i++) {
    btCollisionObject* collisionObject = collisionObjects[i];
    void*              userPointer     = collisionObject->getUserPointer();

    PhysicsType type = *((PhysicsType*) userPointer);
    if (type == PhysicsType_RigidBody) {
      RigidBody* rigidBody = (RigidBody*) userPointer;

      while (rigidBody) {
        /* TODO: Do better. */
        btTransform RigidBody_GetWorldTransform (RigidBody*);

        btTransform       transform = RigidBody_GetWorldTransform(rigidBody);
        CollisionShape*   shape     = CollisionShape_GetCached(rigidBody->iShape);
        if (shape->type == CollisionShapeType_Hull) {
          /* HACK: Drawing a btUniformScalingShape does nothing. The underlying
                   hull has a scale of 1. So we draw the hull directly and hack
                   the scale into the transform. Inefficiently because scaled
                   creates a copy. */
          btVector3 scale = btVector3(REPEAT3(shape->scale));
          transform.setBasis(transform.getBasis().scaled(scale));
          self->dynamicsWorld->debugDrawObject(transform, shape->hull.hullHandle, btVector3());
        } else {
          self->dynamicsWorld->debugDrawObject(transform, shape->base.handle, btVector3());
        }

        rigidBody = rigidBody->next;
      }
    }
  }
}

void Physics_FlushCachedRigidBodyData (Physics* self, RigidBody* rigidBody) {
  /* NOTE: Broadphase implementations have a 'pair cache' internally. Each pair
           contains data for the two objects that are touching, plus an
           indicator for which collision algorithm should be used. The algorithm
           chosen depends on the shape of each object. If a new pair is added
           and there is an existing pair for the same objects (I'm not sure if
           one or both objects need to match) the algorithm will be copied from
           that, instead of determined from scratch. Further, the algorithm is
           cached for the duration of the contact (I think). Thus, if the shape
           of an object changes while it is part of any pairs Bullet may apply
           the wrong algorithm. This leads to crashes and other bad behavior
           when Bullet later casts the btCollisionShape* stored in the pair to a
           specific shape expected by the algorithm and tries to use it. */

  btBroadphaseProxy*      bpProxy   = rigidBody->handle->getBroadphaseHandle();
  btOverlappingPairCache* pairCache = self->broadphase->getOverlappingPairCache();
  pairCache->cleanProxyFromPairs(bpProxy, self->dispatcher);
}


text:
#include "Key.h"
#include "SDL.h"

const Key Key_A           = SDL_SCANCODE_A;
const Key Key_B           = SDL_SCANCODE_B;
const Key Key_C           = SDL_SCANCODE_C;
const Key Key_D           = SDL_SCANCODE_D;
const Key Key_E           = SDL_SCANCODE_E;
const Key Key_F           = SDL_SCANCODE_F;
const Key Key_G           = SDL_SCANCODE_G;
const Key Key_H           = SDL_SCANCODE_H;
const Key Key_I           = SDL_SCANCODE_I;
const Key Key_J           = SDL_SCANCODE_J;
const Key Key_K           = SDL_SCANCODE_K;
const Key Key_L           = SDL_SCANCODE_L;
const Key Key_M           = SDL_SCANCODE_M;
const Key Key_N           = SDL_SCANCODE_N;
const Key Key_O           = SDL_SCANCODE_O;
const Key Key_P           = SDL_SCANCODE_P;
const Key Key_Q           = SDL_SCANCODE_Q;
const Key Key_R           = SDL_SCANCODE_R;
const Key Key_S           = SDL_SCANCODE_S;
const Key Key_T           = SDL_SCANCODE_T;
const Key Key_U           = SDL_SCANCODE_U;
const Key Key_V           = SDL_SCANCODE_V;
const Key Key_W           = SDL_SCANCODE_W;
const Key Key_X           = SDL_SCANCODE_X;
const Key Key_Y           = SDL_SCANCODE_Y;
const Key Key_Z           = SDL_SCANCODE_Z;

const Key Key_N0          = SDL_SCANCODE_0;
const Key Key_N1          = SDL_SCANCODE_1;
const Key Key_N2          = SDL_SCANCODE_2;
const Key Key_N3          = SDL_SCANCODE_3;
const Key Key_N4          = SDL_SCANCODE_4;
const Key Key_N5          = SDL_SCANCODE_5;
const Key Key_N6          = SDL_SCANCODE_6;
const Key Key_N7          = SDL_SCANCODE_7;
const Key Key_N8          = SDL_SCANCODE_8;
const Key Key_N9          = SDL_SCANCODE_9;

const Key Key_F1          = SDL_SCANCODE_F1;
const Key Key_F2          = SDL_SCANCODE_F2;
const Key Key_F3          = SDL_SCANCODE_F3;
const Key Key_F4          = SDL_SCANCODE_F4;
const Key Key_F5          = SDL_SCANCODE_F5;
const Key Key_F6          = SDL_SCANCODE_F6;
const Key Key_F7          = SDL_SCANCODE_F7;
const Key Key_F8          = SDL_SCANCODE_F8;
const Key Key_F9          = SDL_SCANCODE_F9;
const Key Key_F10         = SDL_SCANCODE_F10;
const Key Key_F11         = SDL_SCANCODE_F11;
const Key Key_F12         = SDL_SCANCODE_F12;
const Key Key_F13         = SDL_SCANCODE_F13;
const Key Key_F14         = SDL_SCANCODE_F14;
const Key Key_F15         = SDL_SCANCODE_F15;
const Key Key_F16         = SDL_SCANCODE_F16;
const Key Key_F17         = SDL_SCANCODE_F17;
const Key Key_F18         = SDL_SCANCODE_F18;
const Key Key_F19         = SDL_SCANCODE_F19;
const Key Key_F20         = SDL_SCANCODE_F20;
const Key Key_F21         = SDL_SCANCODE_F21;
const Key Key_F22         = SDL_SCANCODE_F22;
const Key Key_F23         = SDL_SCANCODE_F23;
const Key Key_F24         = SDL_SCANCODE_F24;

const Key Key_KP0         = SDL_SCANCODE_KP_0;
const Key Key_KP1         = SDL_SCANCODE_KP_1;
const Key Key_KP2         = SDL_SCANCODE_KP_2;
const Key Key_KP3         = SDL_SCANCODE_KP_3;
const Key Key_KP4         = SDL_SCANCODE_KP_4;
const Key Key_KP5         = SDL_SCANCODE_KP_5;
const Key Key_KP6         = SDL_SCANCODE_KP_6;
const Key Key_KP7         = SDL_SCANCODE_KP_7;
const Key Key_KP8         = SDL_SCANCODE_KP_8;
const Key Key_KP9         = SDL_SCANCODE_KP_9;

const Key Key_KPNumLock   = SDL_SCANCODE_NUMLOCKCLEAR;
const Key Key_KPDivide    = SDL_SCANCODE_KP_DIVIDE;
const Key Key_KPMultiply  = SDL_SCANCODE_KP_MULTIPLY;
const Key Key_KPSubtract  = SDL_SCANCODE_KP_MINUS;
const Key Key_KPAdd       = SDL_SCANCODE_KP_PLUS;
const Key Key_KPEnter     = SDL_SCANCODE_KP_ENTER;
const Key Key_KPDecimal   = SDL_SCANCODE_KP_DECIMAL;

const Key Key_Backspace   = SDL_SCANCODE_BACKSPACE;
const Key Key_Escape      = SDL_SCANCODE_ESCAPE;
const Key Key_Return      = SDL_SCANCODE_RETURN;
const Key Key_Space       = SDL_SCANCODE_SPACE;
const Key Key_Tab         = SDL_SCANCODE_TAB;
const Key Key_Backtick    = SDL_SCANCODE_GRAVE;
const Key Key_CapsLock    = SDL_SCANCODE_CAPSLOCK;

const Key Key_Minus       = SDL_SCANCODE_MINUS;
const Key Key_Equals      = SDL_SCANCODE_EQUALS;
const Key Key_LBracket    = SDL_SCANCODE_LEFTBRACKET;
const Key Key_RBracket    = SDL_SCANCODE_RIGHTBRACKET;
const Key Key_Backslash   = SDL_SCANCODE_BACKSLASH;
const Key Key_Semicolon   = SDL_SCANCODE_SEMICOLON;
const Key Key_Apostrophe  = SDL_SCANCODE_APOSTROPHE;
const Key Key_Comma       = SDL_SCANCODE_COMMA;
const Key Key_Period      = SDL_SCANCODE_PERIOD;
const Key Key_Slash       = SDL_SCANCODE_SLASH;

const Key Key_PrintScreen = SDL_SCANCODE_PRINTSCREEN;
const Key Key_ScrollLock  = SDL_SCANCODE_SCROLLLOCK;
const Key Key_Pause       = SDL_SCANCODE_PAUSE;
const Key Key_Insert      = SDL_SCANCODE_INSERT;
const Key Key_Home        = SDL_SCANCODE_HOME;
const Key Key_PageUp      = SDL_SCANCODE_PAGEUP;
const Key Key_PageDown    = SDL_SCANCODE_PAGEDOWN;
const Key Key_Delete      = SDL_SCANCODE_DELETE;
const Key Key_Right       = SDL_SCANCODE_RIGHT;
const Key Key_Left        = SDL_SCANCODE_LEFT;
const Key Key_Down        = SDL_SCANCODE_DOWN;
const Key Key_Up          = SDL_SCANCODE_UP;

const Key Key_LCtrl       = SDL_SCANCODE_LCTRL;
const Key Key_LShift      = SDL_SCANCODE_LSHIFT;
const Key Key_LAlt        = SDL_SCANCODE_LALT;
const Key Key_LMeta       = SDL_SCANCODE_LGUI;
const Key Key_RCtrl       = SDL_SCANCODE_RCTRL;
const Key Key_RShift      = SDL_SCANCODE_RSHIFT;
const Key Key_RAlt        = SDL_SCANCODE_RALT;
const Key Key_RMeta       = SDL_SCANCODE_RGUI;


text:
#include "Box3.h"
#include "Matrix.h"
#include "MatrixDef.h"
#include "PhxMemory.h"
#include "Quat.h"
#include "PhxMath.h"
#include "Vec3.h"
#include "Vec4.h"

#include <stdio.h>

Matrix* Matrix_Clone (Matrix const* self) {
  Matrix* clone = MemNew(Matrix);
  *clone = *self;
  return clone;
}

void Matrix_Free (Matrix* self) {
  MemFree(self);
}

static void Matrix_IOInverse (Matrix const* NO_ALIAS in, Matrix* NO_ALIAS out) {
  float const* src = (float const*)in;
  float* dst       = (float*)out;

  dst[0] =
    src[ 5] * src[10] * src[15] - src[ 5] * src[11] * src[14] -
    src[ 9] * src[ 6] * src[15] + src[ 9] * src[ 7] * src[14] +
    src[13] * src[ 6] * src[11] - src[13] * src[ 7] * src[10];

  dst[4] =
   -src[ 4] * src[10] * src[15] + src[ 4] * src[11] * src[14] +
    src[ 8] * src[ 6] * src[15] - src[ 8] * src[ 7] * src[14] -
    src[12] * src[ 6] * src[11] + src[12] * src[ 7] * src[10];

  dst[8] =
    src[ 4] * src[ 9] * src[15] - src[ 4] * src[11] * src[13] -
    src[ 8] * src[ 5] * src[15] + src[ 8] * src[ 7] * src[13] +
    src[12] * src[ 5] * src[11] - src[12] * src[ 7] * src[9];

  dst[12] =
   -src[ 4] * src[ 9] * src[14] + src[ 4] * src[10] * src[13] +
    src[ 8] * src[ 5] * src[14] - src[ 8] * src[ 6] * src[13] -
    src[12] * src[ 5] * src[10] + src[12] * src[ 6] * src[ 9];

  dst[1] =
   -src[ 1] * src[10] * src[15] + src[ 1] * src[11] * src[14] +
    src[ 9] * src[ 2] * src[15] - src[ 9] * src[ 3] * src[14] -
    src[13] * src[ 2] * src[11] + src[13] * src[ 3] * src[10];

  dst[5] =
    src[ 0] * src[10] * src[15] - src[ 0] * src[11] * src[14] -
    src[ 8] * src[ 2] * src[15] + src[ 8] * src[ 3] * src[14] +
    src[12] * src[ 2] * src[11] - src[12] * src[ 3] * src[10];

  dst[9] =
   -src[ 0] * src[ 9] * src[15] + src[ 0] * src[11] * src[13] +
    src[ 8] * src[ 1] * src[15] - src[ 8] * src[ 3] * src[13] -
    src[12] * src[ 1] * src[11] + src[12] * src[ 3] * src[ 9];

  dst[13] =
    src[ 0] * src[ 9] * src[14] - src[ 0] * src[10] * src[13] -
    src[ 8] * src[ 1] * src[14] + src[ 8] * src[ 2] * src[13] +
    src[12] * src[ 1] * src[10] - src[12] * src[ 2] * src[ 9];

  dst[2] =
    src[ 1] * src[ 6] * src[15] - src[ 1] * src[ 7] * src[14] -
    src[ 5] * src[ 2] * src[15] + src[ 5] * src[ 3] * src[14] +
    src[13] * src[ 2] * src[ 7] - src[13] * src[ 3] * src[ 6];

  dst[6] =
   -src[ 0] * src[ 6] * src[15] + src[ 0] * src[ 7] * src[14] +
    src[ 4] * src[ 2] * src[15] - src[ 4] * src[ 3] * src[14] -
    src[12] * src[ 2] * src[ 7] + src[12] * src[ 3] * src[ 6];

  dst[10] =
    src[ 0] * src[ 5] * src[15] - src[ 0] * src[ 7] * src[13] -
    src[ 4] * src[ 1] * src[15] + src[ 4] * src[ 3] * src[13] +
    src[12] * src[ 1] * src[ 7] - src[12] * src[ 3] * src[ 5];

  dst[14] =
   -src[ 0] * src[ 5] * src[14] + src[ 0] * src[ 6] * src[13] +
    src[ 4] * src[ 1] * src[14] - src[ 4] * src[ 2] * src[13] -
    src[12] * src[ 1] * src[ 6] + src[12] * src[ 2] * src[ 5];

  dst[3] =
   -src[ 1] * src[ 6] * src[11] + src[ 1] * src[ 7] * src[10] +
    src[ 5] * src[ 2] * src[11] - src[ 5] * src[ 3] * src[10] -
    src[ 9] * src[ 2] * src[ 7] + src[ 9] * src[ 3] * src[ 6];

  dst[7] =
    src[ 0] * src[ 6] * src[11] - src[ 0] * src[ 7] * src[10] -
    src[ 4] * src[ 2] * src[11] + src[ 4] * src[ 3] * src[10] +
    src[ 8] * src[ 2] * src[ 7] - src[ 8] * src[ 3] * src[ 6];

  dst[11] =
   -src[ 0] * src[ 5] * src[11] + src[ 0] * src[ 7] * src[ 9] +
    src[ 4] * src[ 1] * src[11] - src[ 4] * src[ 3] * src[ 9] -
    src[ 8] * src[ 1] * src[ 7] + src[ 8] * src[ 3] * src[ 5];

  dst[15] =
    src[ 0] * src[ 5] * src[10] - src[ 0] * src[ 6] * src[ 9] -
    src[ 4] * src[ 1] * src[10] + src[ 4] * src[ 2] * src[ 9] +
    src[ 8] * src[ 1] * src[ 6] - src[ 8] * src[ 2] * src[ 5];

  float det = 1.0f /
    (src[0] * dst[0] + src[1] * dst[4] + src[2] * dst[8] + src[3] * dst[12]);
  for (int i = 0; i < 16; ++i)
    dst[i] *= det;
}

static void Matrix_IOTranspose (Matrix const* NO_ALIAS in, Matrix* NO_ALIAS out) {
  float const* src = (float const*)in;
  float* dst       = (float*)out;
  dst[ 0] = src[ 0]; dst[ 1] = src[ 4]; dst[ 2] = src[ 8]; dst[ 3] = src[12];
  dst[ 4] = src[ 1]; dst[ 5] = src[ 5]; dst[ 6] = src[ 9]; dst[ 7] = src[13];
  dst[ 8] = src[ 2]; dst[ 9] = src[ 6]; dst[10] = src[10]; dst[11] = src[14];
  dst[12] = src[ 3]; dst[13] = src[ 7]; dst[14] = src[11]; dst[15] = src[15];
}

bool Matrix_Equal (Matrix const* a, Matrix const* b) {
  for (int i = 0; i < 16; i++) {
    if (a->m[i] != b->m[i])
      return false;
  }
  return true;
}

bool Matrix_ApproximatelyEqual (Matrix const* a, Matrix const* b) {
  for (int i = 0; i < 16; i++) {
    if (!Float_ApproximatelyEqual(a->m[i], b->m[i]))
      return false;
  }
  return true;
}

Matrix* Matrix_Inverse (Matrix const* self) {
  Matrix result;
  Matrix_IOInverse(self, &result);
  return Matrix_Clone(&result);
}

Matrix* Matrix_InverseTranspose (Matrix const* self) {
  Matrix inverse, result;
  Matrix_IOInverse(self, &inverse);
  Matrix_IOTranspose(&inverse, &result);
  return Matrix_Clone(&result);
}

Matrix* Matrix_Sum (Matrix const* a, Matrix const* b) {
  Matrix result;
  for (int i = 0; i < 16; ++i) {
    result.m[i] = a->m[i] + b->m[i];
  }
  return Matrix_Clone(&result);
}

Matrix* Matrix_Transpose (Matrix const* self) {
  Matrix result;
  Matrix_IOTranspose(self, &result);
  return Matrix_Clone(&result);
}

void Matrix_IInverse (Matrix* self) {
  Matrix result;
  Matrix_IOInverse(self, &result);
  *self = result;
}

void Matrix_IScale (Matrix* self, float scale) {
  float* m = self->m;
  m[0] *= scale; m[1] *= scale; m[ 2] *= scale;
  m[4] *= scale; m[5] *= scale; m[ 6] *= scale;
  m[8] *= scale; m[9] *= scale; m[10] *= scale;
}

void Matrix_ITranspose (Matrix* self) {
  Matrix result;
  Matrix_IOTranspose(self, &result);
  *self = result;
}

Matrix* Matrix_Identity () {
  Matrix const identity = {
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1,
  };
  return Matrix_Clone(&identity);
}

Matrix* Matrix_LookAt (Vec3f const* pos, Vec3f const* at, Vec3f const* up) {
  Vec3f z = Vec3f_Normalize(Vec3f_Sub(*pos, *at));
  Vec3f x = Vec3f_Normalize(Vec3f_Cross(*up, z));
  Vec3f y = Vec3f_Cross(z, x);
  Matrix result = {
     x.x, y.x, z.x, pos->x,
     x.y, y.y, z.y, pos->y,
     x.z, y.z, z.z, pos->z,
       0,   0,   0,      1,
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_LookUp (Vec3f const* pos, Vec3f const* look, Vec3f const* up) {
  Vec3f z = Vec3f_Normalize(Vec3f_Muls(*look, -1.0f));
  Vec3f x = Vec3f_Normalize(Vec3f_Cross(*up, z));
  Vec3f y = Vec3f_Cross(z, x);
  Matrix result = {
     x.x, y.x, z.x, pos->x,
     x.y, y.y, z.y, pos->y,
     x.z, y.z, z.z, pos->z,
       0,   0,   0,      1,
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_Perspective (float degreesFovy, float aspect, float N, float F) {
  double rads = Pi * degreesFovy / 360.0;
  double cot = 1.0 / Tan(rads);
  Matrix result = {
    (float)(cot / aspect), 0, 0, 0,
    0, (float)cot, 0, 0,
    0, 0, (N + F) / (N - F), (float)(2.0 * (F * N) / (N - F)),
    0, 0, -1.0f, 0,
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_Product (Matrix const* a, Matrix const* b) {
  Matrix result;
  float* pResult = result.m;
  for (int i = 0; i < 4; ++i)
  for (int j = 0; j < 4; ++j) {
    float sum = 0.0f;
    for (int k = 0; k < 4; ++k)
      sum += a->m[4 * i + k] * b->m[4 * k + j];
    *pResult++ = sum;
  }
  return Matrix_Clone(&result);
}

Matrix* Matrix_RotationX (float rads) {
  float c = Cos(rads);
  float s = Sin(rads);
  Matrix result = {
    1,  0,  0,  0,
    0,  c, -s,  0,
    0,  s,  c,  0,
    0,  0,  0,  1,
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_RotationY (float rads) {
  float c = Cos(rads);
  float s = Sin(rads);
  Matrix result = {
    c,  0,  s,  0,
    0,  1,  0,  0,
   -s,  0,  c,  0,
    0,  0,  0,  1,
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_RotationZ (float rads) {
  float c = Cos(rads);
  float s = Sin(rads);
  Matrix result = {
    c, -s,  0,  0,
    s,  c,  0,  0,
    0,  0,  1,  0,
    0,  0,  0,  1,
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_Scaling (float sx, float sy, float sz) {
  Matrix result = {
    sx,  0,  0, 0,
     0, sy,  0, 0,
     0,  0, sz, 0,
     0,  0,  0, 1,
  };
  return Matrix_Clone(&result);
}

/* TODO : Be less lazy. */
Matrix* Matrix_SRT (
  float sx, float sy, float sz,
  float ry, float rp, float rr,
  float tx, float ty, float tz)
{
  Matrix* S = Matrix_Scaling(sx, sy, sz);
  Matrix* R = Matrix_YawPitchRoll(ry, rp, rr);
  Matrix* T = Matrix_Translation(tx, ty, tz);
  Matrix* TR = Matrix_Product(T, R);
  Matrix* TRS = Matrix_Product(TR, S);
  Matrix_Free(S);
  Matrix_Free(R);
  Matrix_Free(T);
  Matrix_Free(TR);
  return TRS;
}

Matrix* Matrix_Translation (float tx, float ty, float tz) {
  Matrix result = {
     1,  0,  0, tx,
     0,  1,  0, ty,
     0,  0,  1, tz,
     0,  0,  0,  1,
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_YawPitchRoll (float yaw, float pitch, float roll) {
  float ca = Cos(roll);
  float sa = Sin(roll);
  float cb = Cos(yaw);
  float sb = Sin(yaw);
  float cy = Cos(pitch);
  float sy = Sin(pitch);
  Matrix result = {
    ca * cb, ca * sb * sy - sa * cy, ca * sb * cy + sa * sy, 0,
    sa * cb, sa * sb * sy + ca * cy, sa * sb * cy - ca * sy, 0,
    -sb, cb * sy, cb * cy, 0,
    0, 0, 0, 1,
  };
  return Matrix_Clone(&result);
}

void Matrix_MulBox (Matrix const* self, Box3f* NO_ALIAS out, Box3f const* NO_ALIAS in) {
  Vec3f const corners[] = {
    { in->lower.x, in->lower.y, in->lower.z },
    { in->upper.x, in->lower.y, in->lower.z },
    { in->lower.x, in->upper.y, in->lower.z },
    { in->upper.x, in->upper.y, in->lower.z },
    { in->lower.x, in->lower.y, in->upper.z },
    { in->upper.x, in->lower.y, in->upper.z },
    { in->lower.x, in->upper.y, in->upper.z },
    { in->upper.x, in->upper.y, in->upper.z },
  };

  Vec3f result;
  Matrix_MulPoint(self, &result, corners[0].x, corners[0].y, corners[0].z);
  out->lower = result;
  out->upper = result;

  for (int i = 1; i < 8; ++i) {
    Matrix_MulPoint(self, &result, corners[i].x, corners[i].y, corners[i].z);
    out->lower = Vec3f_Min(out->lower, result);
    out->upper = Vec3f_Max(out->upper, result);
  }
}

void Matrix_MulDir (Matrix const* self, Vec3f* NO_ALIAS out, float x, float y, float z) {
  float const* m = self->m;
  out->x = m[ 0] * x + m[ 1] * y + m[ 2] * z;
  out->y = m[ 4] * x + m[ 5] * y + m[ 6] * z;
  out->z = m[ 8] * x + m[ 9] * y + m[10] * z;
}

void Matrix_MulPoint (Matrix const* self, Vec3f* NO_ALIAS out, float x, float y, float z) {
  float const* m = self->m;
  out->x = m[ 0] * x + m[ 1] * y + m[ 2] * z + m[ 3];
  out->y = m[ 4] * x + m[ 5] * y + m[ 6] * z + m[ 7];
  out->z = m[ 8] * x + m[ 9] * y + m[10] * z + m[11];
}

void Matrix_MulVec (Matrix const* self, Vec4f* NO_ALIAS out, float x, float y, float z, float w) {
  float const* m = self->m;
  out->x = m[ 0] * x + m[ 1] * y + m[ 2] * z + m[ 3] * w;
  out->y = m[ 4] * x + m[ 5] * y + m[ 6] * z + m[ 7] * w;
  out->z = m[ 8] * x + m[ 9] * y + m[10] * z + m[11] * w;
  out->w = m[12] * x + m[13] * y + m[14] * z + m[15] * w;
}

void Matrix_GetForward (Matrix const* self, Vec3f* out) {
  out->x = -self->m[2];
  out->y = -self->m[6];
  out->z = -self->m[10];
}

void Matrix_GetRight (Matrix const* self, Vec3f* out) {
  out->x = self->m[0];
  out->y = self->m[4];
  out->z = self->m[8];
}

void Matrix_GetUp (Matrix const* self, Vec3f* out) {
  out->x = self->m[1];
  out->y = self->m[5];
  out->z = self->m[9];
}

void Matrix_GetPos (Matrix const* self, Vec3f* out) {
  out->x = self->m[3];
  out->y = self->m[7];
  out->z = self->m[11];
}

void Matrix_GetRow (Matrix const* self, Vec4f* out, int row) {
  out->x = self->m[4 * row + 0];
  out->y = self->m[4 * row + 1];
  out->z = self->m[4 * row + 2];
  out->w = self->m[4 * row + 3];
}

Matrix* Matrix_FromBasis (Vec3f const* x, Vec3f const* y, Vec3f const* z) {
  Matrix result = {
    x->x, y->x, z->x, 0,
    x->y, y->y, z->y, 0,
    x->z, y->z, z->z, 0,
      0,   0,   0,    1
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_FromPosRot (Vec3f const* pos, Quat const* rot) {
  Vec3f x; Quat_GetAxisX(rot, &x);
  Vec3f y; Quat_GetAxisY(rot, &y);
  Vec3f z; Quat_GetAxisZ(rot, &z);
  Matrix result = {
    x.x, y.x, z.x, pos->x,
    x.y, y.y, z.y, pos->y,
    x.z, y.z, z.z, pos->z,
      0,   0,   0,      1
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_FromPosRotScale (Vec3f const* pos, Quat const* rot, float scale) {
  Vec3f x; Quat_GetAxisX(rot, &x);
  Vec3f y; Quat_GetAxisY(rot, &y);
  Vec3f z; Quat_GetAxisZ(rot, &z);
  Matrix result = {
    scale*x.x, scale*y.x, scale*z.x, pos->x,
    scale*x.y, scale*y.y, scale*z.y, pos->y,
    scale*x.z, scale*y.z, scale*z.z, pos->z,
            0,         0,         0,      1
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_FromPosBasis (Vec3f const* pos, Vec3f const* x, Vec3f const* y, Vec3f const* z) {
  Matrix result = {
    x->x, y->x, z->x, pos->x,
    x->y, y->y, z->y, pos->y,
    x->z, y->z, z->z, pos->z,
       0,    0,    0,      1
  };
  return Matrix_Clone(&result);
}

Matrix* Matrix_FromQuat (Quat const* q) {
  Vec3f x; Quat_GetAxisX(q, &x);
  Vec3f y; Quat_GetAxisY(q, &y);
  Vec3f z; Quat_GetAxisZ(q, &z);
  Matrix result = {
    x.x, y.x, z.x, 0,
    x.y, y.y, z.y, 0,
    x.z, y.z, z.z, 0,
      0,   0,   0, 1
  };
  return Matrix_Clone(&result);
}

void Matrix_ToQuat (Matrix const* self, Quat* q) {
  float const* m = (float const*) self;
  Vec3f x = { m[0], m[4], m[ 8] };
  Vec3f y = { m[1], m[5], m[ 9] };
  Vec3f z = { m[2], m[6], m[10] };
  Quat_FromBasis(&x, &y, &z, q);
}

void Matrix_Print (Matrix const* self) {
  for (int i = 0; i < 4; ++i) {
    for (int j = 0; j < 4; ++j)
      printf("%f ", self->m[4 * i + j]);
    printf("\n");
  }
}

cstr Matrix_ToString (Matrix const* self) {
  static char buffer[512];

  const float* m = self->m;
  snprintf(buffer, (size_t) Array_GetSize(buffer),
      "[%+.2f, %+.2f, %+.2f, %+.2f]"
    "\n[%+.2f, %+.2f, %+.2f, %+.2f]"
    "\n[%+.2f, %+.2f, %+.2f, %+.2f]"
    "\n[%+.2f, %+.2f, %+.2f, %+.2f]",
    m[ 0], m[ 1], m[ 2], m[ 3],
    m[ 4], m[ 5], m[ 6], m[ 7],
    m[ 8], m[ 9], m[10], m[11],
    m[12], m[13], m[14], m[15]
  );
  return buffer;
}


text:
#include "ArrayList.h"
#include "Box3.h"
#include "Bytes.h"
#include "Matrix.h"
#include "Mesh.h"
#include "Metric.h"
#include "OpenGL.h"
#include "RefCounted.h"
#include "Resource.h"
#include "SDF.h"
#include "Triangle.h"
#include "Vertex.h"

#include <float.h>

struct Computed {
  Box3f bound;
  float radius;
};

struct Mesh {
  RefCounted;
  uint vbo;
  uint ibo;
  uint64 version;
  uint64 versionBuffers;
  uint64 versionInfo;
  Computed info;
  ArrayList(int32, index);
  ArrayList(Vertex, vertex);
};

static void Mesh_UpdateInfo (Mesh* self) {
  if (self->versionInfo == self->version)
    return;
  self->info.bound.lower = Vec3f_Create( FLT_MAX,  FLT_MAX,  FLT_MAX);
  self->info.bound.upper = Vec3f_Create(-FLT_MAX, -FLT_MAX, -FLT_MAX);

  /* Compute bounding box. */
  ArrayList_ForEach(self->vertex, Vertex, v) {
    Box3f_Add(&self->info.bound, v->p);
  }

  Vec3f center = Box3f_Center(self->info.bound);

  /* Compute radius in second pass after bound is. Note that radius is relative
   * to bounding box center, NOT centroid! */ {
    double r2 = 0.0;
    ArrayList_ForEach(self->vertex, Vertex, v) {
      double dx = v->p.x - center.x;
      double dy = v->p.y - center.y;
      double dz = v->p.z - center.z;
      r2 = Max(r2, dx*dx + dy*dy + dz*dz);
    }
    self->info.radius = (float)Sqrt(r2);
  }
  self->versionInfo = self->version;
}

Mesh* Mesh_Create () {
  Mesh* self = MemNew(Mesh);
  RefCounted_Init(self);
  self->vbo = 0;
  self->ibo = 0;
  self->version = 1;
  self->versionBuffers = 0;
  self->versionInfo = 0;
  ArrayList_Init(self->vertex);
  ArrayList_Init(self->index);
  return self;
}

Mesh* Mesh_Clone (Mesh* other) {
  Mesh* self = Mesh_Create();
  ArrayList_Reserve(self->index, other->index_size);
  ArrayList_Reserve(self->vertex, other->vertex_size);
  self->index_size = other->index_size;
  self->vertex_size = other->vertex_size;
  MemCpy(self->index_data, other->index_data, sizeof(int) * other->index_size);
  MemCpy(self->vertex_data, other->vertex_data, sizeof(Vertex) * other->vertex_size);
  return self;
}

Mesh* Mesh_Load (cstr name) {
  Bytes* bytes = Resource_LoadBytes(ResourceType_Mesh, name);
  Mesh* self = Mesh_FromBytes(bytes);
  Bytes_Free(bytes);
  return self;
}

void Mesh_Acquire (Mesh* self) {
  RefCounted_Acquire(self);
}

void Mesh_Free (Mesh* self) {
  RefCounted_Free(self) {
    ArrayList_Free(self->vertex);
    ArrayList_Free(self->index);
    if (self->vbo) {
      GLCALL(glDeleteBuffers(1, &self->vbo))
      GLCALL(glDeleteBuffers(1, &self->ibo))
    }
    MemFree(self);
  }
}

Bytes* Mesh_ToBytes (Mesh* mesh) {
  int32 vertexCount = Mesh_GetVertexCount(mesh);
  int32 indexCount = Mesh_GetIndexCount(mesh);
  uint32 size = 2 * sizeof(int32) +
    vertexCount * sizeof(Vertex) +
    indexCount * sizeof(int32);

  Bytes* self = Bytes_Create(size);
  Bytes_WriteI32(self, vertexCount);
  Bytes_WriteI32(self, indexCount);
  Bytes_Write(self, ArrayList_GetData(mesh->vertex), vertexCount * sizeof(Vertex));
  Bytes_Write(self, ArrayList_GetData(mesh->index), indexCount * sizeof(int32));
  return self;
}

Mesh* Mesh_FromBytes (Bytes* buf) {
  Mesh* self = Mesh_Create();
  int32 vertexCount = Bytes_ReadI32(buf);
  int32 indexCount = Bytes_ReadI32(buf);
  Mesh_ReserveVertexData(self, vertexCount);
  Mesh_ReserveIndexData(self, indexCount);
  Bytes_Read(buf, ArrayList_GetData(self->vertex), vertexCount * sizeof(Vertex));
  Bytes_Read(buf, ArrayList_GetData(self->index), indexCount * sizeof(int32));
  self->vertex_size = vertexCount;
  self->index_size = indexCount;
  return self;
}

Mesh* Mesh_FromSDF (SDF* sdf) {
  return SDF_ToMesh(sdf);
}

void Mesh_AddIndex (Mesh* self, int newIndex) {
  ArrayList_Grow(self->index);
  self->index_data[self->index_size++] = newIndex;
  self->version++;
}

void Mesh_AddMesh (Mesh* self, Mesh* other) {
  int indexOffset = self->vertex_size;
  for (int i = 0; i < other->vertex_size; ++i)
    Mesh_AddVertexRaw(self, other->vertex_data + i);
  for (int i = 0; i < other->index_size; ++i)
    Mesh_AddIndex(self, other->index_data[i] + indexOffset);
}

void Mesh_AddQuad (Mesh* self, int i1, int i2, int i3, int i4) {
  Mesh_AddTri(self, i1, i2, i3);
  Mesh_AddTri(self, i1, i3, i4);
}

void Mesh_AddTri (Mesh* self, int i1, int i2, int i3) {
  Mesh_AddIndex(self, i1);
  Mesh_AddIndex(self, i2);
  Mesh_AddIndex(self, i3);
}

void Mesh_AddVertex (Mesh* self,
  float px, float py, float pz,
  float nx, float ny, float nz,
  float u, float v)
{
  ArrayList_Grow(self->vertex);
  Vertex* newVertex = self->vertex_data + (self->vertex_size++);
  newVertex->p = Vec3f_Create(px, py, pz);
  newVertex->n = Vec3f_Create(nx, ny, nz);
  newVertex->uv = Vec2f_Create(u, v);
  self->version++;
}

void Mesh_AddVertexRaw (Mesh* self, Vertex const* vertex) {
  ArrayList_Grow(self->vertex);
  *(self->vertex_data + (self->vertex_size++)) = *vertex;
  self->version++;
}

void Mesh_DrawBind (Mesh* self) {
  /* Release cached GL buffers if the mesh has changed since we built them. */
  if (self->vbo && self->version != self->versionBuffers) {
    GLCALL(glDeleteBuffers(1, &self->vbo))
    GLCALL(glDeleteBuffers(1, &self->ibo))
    self->vbo = 0;
    self->ibo = 0;
  }

  /* Generate cached GL buffers for fast drawing. */
  if (!self->vbo) {
    GLCALL(glGenBuffers(1, &self->vbo))
    GLCALL(glGenBuffers(1, &self->ibo))
    GLCALL(glBindBuffer(GL_ARRAY_BUFFER, self->vbo))
    GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->ibo))
    GLCALL(glBufferData(
      GL_ARRAY_BUFFER,
      self->vertex_size * sizeof(Vertex),
      self->vertex_data,
      GL_STATIC_DRAW))

    /* TODO : 16-bit index optimization */
    /* TODO : Check if 8-bit indices are supported by hardware. IIRC they
     *        weren't last time I checked. */

    GLCALL(glBufferData(
      GL_ELEMENT_ARRAY_BUFFER,
      self->index_size * sizeof(int),
      self->index_data,
      GL_STATIC_DRAW))

    self->versionBuffers = self->version;
  }

  GLCALL(glBindBuffer(GL_ARRAY_BUFFER, self->vbo))
  GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->ibo))

  GLCALL(glEnableVertexAttribArray(0))
  GLCALL(glEnableVertexAttribArray(1))
  GLCALL(glEnableVertexAttribArray(2))

  GLCALL(glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void const*)OFFSET_OF(Vertex, p)))
  GLCALL(glVertexAttribPointer(1, 3, GL_FLOAT, false, sizeof(Vertex), (void const*)OFFSET_OF(Vertex, n)))
  GLCALL(glVertexAttribPointer(2, 2, GL_FLOAT, false, sizeof(Vertex), (void const*)OFFSET_OF(Vertex, uv)))
}

void Mesh_DrawBound (Mesh* self) {
  Metric_AddDraw(self->index_size / 3, self->index_size / 3, self->vertex_size);
  GLCALL(glDrawElements(GL_TRIANGLES, self->index_size, GL_UNSIGNED_INT, 0))
}

void Mesh_DrawUnbind (Mesh*) {
  GLCALL(glDisableVertexAttribArray(0))
  GLCALL(glDisableVertexAttribArray(1))
  GLCALL(glDisableVertexAttribArray(2))
  GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0))
  GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0))
}

void Mesh_Draw (Mesh* self) {
  Mesh_DrawBind(self);
  Mesh_DrawBound(self);
  Mesh_DrawUnbind(self);
}

void Mesh_DrawNormals (Mesh* self, float scale) {
  glBegin(GL_LINES);
  ArrayList_ForEach(self->vertex, Vertex, v) {
    glVertex3f(v->p.x, v->p.y, v->p.z);
    glVertex3f(
      v->p.x + scale * v->n.x,
      v->p.y + scale * v->n.y,
      v->p.z + scale * v->n.z);
  }
  GLCALL(glEnd())
}

void Mesh_GetBound (Mesh* self, Box3f* out) {
  Mesh_UpdateInfo(self);
  *out = self->info.bound;
}

void Mesh_GetCenter (Mesh* self, Vec3f* out) {
  Mesh_UpdateInfo(self);
  *out = Box3f_Center(self->info.bound);
}

int Mesh_GetIndexCount (Mesh* self) {
  return self->index_size;
}

int* Mesh_GetIndexData (Mesh* self) {
  return self->index_data;
}

float Mesh_GetRadius (Mesh* self) {
  Mesh_UpdateInfo(self);
  return self->info.radius;
}

uint64 Mesh_GetVersion (Mesh* self) {
  return self->version;
}

void Mesh_IncVersion (Mesh* self) {
  self->version++;
}

Error Mesh_Validate (Mesh* self) {
  int32   indexLen   = Mesh_GetIndexCount(self);
  int32*  indexData  = Mesh_GetIndexData(self);
  Vertex* vertexData = Mesh_GetVertexData(self);

  if (indexLen % 3 != 0) return Error_Index | Error_BadCount;

  for (int32 i = 0; i < indexLen; i += 3) {
    int32 i0 = indexData[i + 0];
    int32 i1 = indexData[i + 1];
    int32 i2 = indexData[i + 2];

    Triangle triangle;
    triangle.vertices[0] = vertexData[i0].p;
    triangle.vertices[1] = vertexData[i1].p;
    triangle.vertices[2] = vertexData[i2].p;

    Error e = Triangle_Validate(&triangle);
    if (e != Error_None) return Error_VertPos | e;
  }

  ArrayList_ForEach(self->vertex, Vertex const, v) {
    Error e;
    if ((e = Vec3f_Validate(v->p))  != Error_None) return Error_VertPos  | e;
    if ((e = Vec3f_Validate(v->n))  != Error_None) return Error_VertNorm | e;
    if ((e = Vec2f_Validate(v->uv)) != Error_None) return Error_VertUV   | e;
  }

  return Error_None;
}

Vertex* Mesh_GetVertex (Mesh* self, int index) {
  return self->vertex_data + index;
}

int Mesh_GetVertexCount (Mesh* self) {
  return self->vertex_size;
}

Vertex* Mesh_GetVertexData (Mesh* self) {
  return self->vertex_data;
}

void Mesh_ReserveIndexData (Mesh* self, int capacity) {
  ArrayList_Reserve(self->index, capacity);
}

void Mesh_ReserveVertexData (Mesh* self, int capacity) {
  ArrayList_Reserve(self->vertex, capacity);
}

Mesh* Mesh_Center (Mesh* self) {
  Vec3f c; Mesh_GetCenter(self, &c);
  Mesh_Translate(self, -c.x, -c.y, -c.z);
  return self;
}

Mesh* Mesh_Invert (Mesh* self) {
  for (int i = 0; i < self->index_size; i += 3)
    Swap(self->index_data[i + 1], self->index_data[i + 2]);
  self->version++;
  return self;
}

Mesh* Mesh_RotateX (Mesh* self, float rads) {
  Matrix* matrix = Matrix_RotationX(rads);
  Mesh_Transform(self, matrix);
  Matrix_Free(matrix);
  return self;
}

Mesh* Mesh_RotateY (Mesh* self, float rads) {
  Matrix* matrix = Matrix_RotationY(rads);
  Mesh_Transform(self, matrix);
  Matrix_Free(matrix);
  return self;
}

Mesh* Mesh_RotateZ (Mesh* self, float rads) {
  Matrix* matrix = Matrix_RotationZ(rads);
  Mesh_Transform(self, matrix);
  Matrix_Free(matrix);
  return self;
}

Mesh* Mesh_RotateYPR (Mesh* self, float yaw, float pitch, float roll) {
  Matrix* matrix = Matrix_YawPitchRoll(yaw, pitch, roll);
  Mesh_Transform(self, matrix);
  Matrix_Free(matrix);
  return self;
}

Mesh* Mesh_Scale (Mesh* self, float x, float y, float z) {
  ArrayList_ForEach(self->vertex, Vertex, v) {
    v->p.x *= x;
    v->p.y *= y;
    v->p.z *= z;
  }
  self->version++;
  return self;
}

Mesh* Mesh_ScaleUniform (Mesh* self, float s) {
  Mesh_Scale(self, s, s, s);
  return self;
}

Mesh* Mesh_Translate (Mesh* self, float x, float y, float z) {
  ArrayList_ForEach(self->vertex, Vertex, v) {
    v->p.x += x;
    v->p.y += y;
    v->p.z += z;
  }
  self->version++;
  return self;
}

Mesh* Mesh_Transform (Mesh* self, Matrix* NO_ALIAS matrix) {
  ArrayList_ForEach(self->vertex, Vertex, v)
    Matrix_MulPoint(matrix, &v->p, v->p.x, v->p.y, v->p.z);
  self->version++;
  return self;
}

void Mesh_ComputeNormals (Mesh* self) {
  ArrayList_ForEach(self->vertex, Vertex, v) {
    v->n.x = 0;
    v->n.y = 0;
    v->n.z = 0;
  }

  for (int i = 0; i < self->index_size; i += 3) {
    Vertex* v1 = ArrayList_GetPtr(self->vertex, ArrayList_Get(self->index, i + 0));
    Vertex* v2 = ArrayList_GetPtr(self->vertex, ArrayList_Get(self->index, i + 1));
    Vertex* v3 = ArrayList_GetPtr(self->vertex, ArrayList_Get(self->index, i + 2));

    Vec3f e1 = Vec3f_Sub(v2->p, v1->p);
    Vec3f e2 = Vec3f_Sub(v3->p, v2->p);
    Vec3f en = Vec3f_Cross(e1, e2);
    v1->n = Vec3f_Add(v1->n, en);
    v2->n = Vec3f_Add(v2->n, en);
    v3->n = Vec3f_Add(v3->n, en);
  }

  ArrayList_ForEach(self->vertex, Vertex, v)
    v->n = Vec3f_Normalize(v->n);

  self->version++;
}

void Mesh_SplitNormals (Mesh* self, float minDot) {
  ArrayList_ForEach(self->vertex, Vertex, v)
    v->n = Vec3f_Create(0, 0, 0);

  for (int i = 0; i < self->index_size; i += 3) {
    int32* index[3] = {
      self->index_data + i + 0,
      self->index_data + i + 1,
      self->index_data + i + 2,
    };

    Vertex* v[3] = {
      self->vertex_data + *index[0],
      self->vertex_data + *index[1],
      self->vertex_data + *index[2],
    };

    Vec3f face = Vec3f_Cross(
      Vec3f_Sub(v[1]->p, v[0]->p),
      Vec3f_Sub(v[2]->p, v[0]->p));

    for (int j = 0; j < 3; ++j) {
      Vec3f* cn = &self->vertex_data[*index[j]].n;
      if (Vec3f_LengthSquared(*cn) > 0.0f) {
        float cDot = Vec3f_Dot(Vec3f_Normalize(face), Vec3f_Normalize(*cn));
        if (cDot < minDot) {
          ArrayList_Grow(self->vertex);
          Vertex* nv = self->vertex_data + (self->vertex_size++);
          *nv = ArrayList_Get(self->vertex, *index[j]);
          nv->n = face;
          *index[j] = ArrayList_GetSize(self->vertex) - 1;
        } else {
          Vec3f_IAdd(cn, face);
        }
      } else {
        Vec3f_IAdd(cn, face);
      }
    }
  }

  ArrayList_ForEach(self->vertex, Vertex, v)
    v->n = Vec3f_Normalize(v->n);
}