Add ability to shoot and delete entities

This commit is contained in:
Doyle Thai 2016-11-24 18:57:44 +11:00
parent 6069ed8415
commit 42ba86f05a
9 changed files with 256 additions and 140 deletions

Binary file not shown.

View File

@ -183,6 +183,8 @@ v2 *createAsteroidVertexList(MemoryArena_ *arena, i32 iterations,
result[i] = V2(((math_cosf(iterationAngle * i) + 1) * asteroidRadius),
((math_sinf(iterationAngle * i) + 1) * asteroidRadius));
ASSERT(result[i].x >= 0 && result[i].y >= 0);
#if 1
f32 displacementDist = 0.50f * asteroidRadius;
i32 vertexDisplacement =
@ -284,8 +286,9 @@ b32 checkEdgeProjectionOverlap(v2 *vertexList, i32 listSize,
return result;
}
b32 moveEntity(GameState *state, Entity *entity, i32 entityIndex, v2 ddP,
f32 dt, f32 ddPSpeed)
INTERNAL b32 moveEntity(World *world, MemoryArena_ *transientArena,
Entity *entity, i32 entityIndex, v2 ddP, f32 dt,
f32 ddPSpeed)
{
ASSERT(ABS(ddP.x) <= 1.0f && ABS(ddP.y) <= 1.0f);
/*
@ -295,7 +298,7 @@ b32 moveEntity(GameState *state, Entity *entity, i32 entityIndex, v2 ddP,
newPos = (a*t^2)/2 + oldVelocity*t + oldPos
*/
ddP = v2_scale(ddP, state->pixelsPerMeter * ddPSpeed);
ddP = v2_scale(ddP, world->pixelsPerMeter * ddPSpeed);
v2 oldDp = entity->dP;
v2 resistance = v2_scale(oldDp, 2.0f);
ddP = v2_sub(ddP, resistance);
@ -313,59 +316,59 @@ b32 moveEntity(GameState *state, Entity *entity, i32 entityIndex, v2 ddP,
// TODO(doyle): Collision for rects, (need to create vertex list for it)
#if 1
if (entity->renderMode == rendermode_polygon && entity->collides)
for (i32 i = entityIndex + 1; i < world->entityIndex; i++)
{
for (i32 i = entityIndex + 1; i < state->entityIndex; i++)
Entity *checkEntity = &world->entityList[i];
ASSERT(checkEntity->id != entity->id);
if (world->collisionTable[entity->type][checkEntity->type])
{
Entity *checkEntity = &state->entityList[i];
ASSERT(checkEntity->id != entity->id);
ASSERT(entity->vertexPoints);
ASSERT(checkEntity->vertexPoints);
if (checkEntity->renderMode == rendermode_polygon &&
checkEntity->collides)
/* Create entity edge lists */
v2 *entityVertexListOffsetToP = entity_generateUpdatedVertexList(
transientArena, entity);
v2 *checkEntityVertexListOffsetToP =
entity_generateUpdatedVertexList(transientArena,
checkEntity);
v2 *entityEdgeList = createNormalEdgeList(transientArena,
entityVertexListOffsetToP,
entity->numVertexPoints);
v2 *checkEntityEdgeList = createNormalEdgeList(
transientArena, checkEntityVertexListOffsetToP,
checkEntity->numVertexPoints);
/* Combine both edge lists into one */
i32 totalNumEdges =
checkEntity->numVertexPoints + entity->numVertexPoints;
v2 *edgeList =
memory_pushBytes(transientArena, totalNumEdges * sizeof(v2));
for (i32 i = 0; i < entity->numVertexPoints; i++)
{
/* Create entity edge lists */
v2 *entityVertexListOffsetToP =
entity_createVertexList(&state->transientArena, entity);
v2 *checkEntityVertexListOffsetToP = entity_createVertexList(
&state->transientArena, checkEntity);
v2 *entityEdgeList = createNormalEdgeList(
&state->transientArena, entityVertexListOffsetToP,
entity->numVertexPoints);
v2 *checkEntityEdgeList = createNormalEdgeList(
&state->transientArena, checkEntityVertexListOffsetToP,
checkEntity->numVertexPoints);
/* Combine both edge lists into one */
i32 totalNumEdges =
checkEntity->numVertexPoints + entity->numVertexPoints;
v2 *edgeList = memory_pushBytes(&state->transientArena,
totalNumEdges * sizeof(v2));
for (i32 i = 0; i < entity->numVertexPoints; i++)
{
edgeList[i] = entityEdgeList[i];
}
for (i32 i = 0; i < checkEntity->numVertexPoints; i++)
{
edgeList[i + entity->numVertexPoints] =
checkEntityEdgeList[i];
}
if (checkEdgeProjectionOverlap(
entityVertexListOffsetToP, entity->numVertexPoints,
checkEntityVertexListOffsetToP,
checkEntity->numVertexPoints, edgeList, totalNumEdges))
{
willCollide = TRUE;
}
edgeList[i] = entityEdgeList[i];
}
if (willCollide) {
break;
for (i32 i = 0; i < checkEntity->numVertexPoints; i++)
{
edgeList[i + entity->numVertexPoints] = checkEntityEdgeList[i];
}
if (checkEdgeProjectionOverlap(
entityVertexListOffsetToP, entity->numVertexPoints,
checkEntityVertexListOffsetToP,
checkEntity->numVertexPoints, edgeList, totalNumEdges))
{
willCollide = TRUE;
}
}
if (willCollide)
{
break;
}
}
#endif
@ -389,68 +392,106 @@ b32 moveEntity(GameState *state, Entity *entity, i32 entityIndex, v2 ddP,
return willCollide;
}
INTERNAL void addAsteroid(GameState *state, v2 windowSize)
INTERNAL void addAsteroid(World *world, v2 windowSize)
{
Entity *asteroid = &state->entityList[state->entityIndex];
asteroid->id = state->entityIndex++;
Entity *asteroid = &world->entityList[world->entityIndex++];
asteroid->id = world->entityIdCounter++;
i32 randValue = rand();
i32 randX = (randValue % (i32)windowSize.w);
i32 randY = (randValue % (i32)windowSize.h);
asteroid->pos = V2i(randX, randY);
asteroid->size = V2(75.0f, 75.0f);
asteroid->size = V2(100.0f, 100.0f);
asteroid->hitbox = asteroid->size;
asteroid->offset = V2(asteroid->size.w * -0.5f, 0);
asteroid->scale = 1;
asteroid->rotation = 45;
asteroid->offset = v2_scale(asteroid->size, -0.5f);
asteroid->type = entitytype_asteroid;
asteroid->direction = direction_null;
asteroid->renderMode = rendermode_polygon;
asteroid->numVertexPoints = 10;
asteroid->vertexPoints = createAsteroidVertexList(
&state->persistentArena, asteroid->numVertexPoints,
(i32)(asteroid->size.x * 0.5f));
&world->entityArena, asteroid->numVertexPoints,
(i32)(asteroid->size.w * 0.5f));
asteroid->tex = NULL;
asteroid->collides = TRUE;
asteroid->color = V4(0.0f, 0.5f, 0.5f, 1.0f);
}
INTERNAL void addBullet(World *world, Entity *shooter)
{
Entity *bullet = &world->entityList[world->entityIndex++];
bullet->id = world->entityIdCounter++;
bullet->offset = v2_scale(bullet->size, -0.5f);
bullet->pos = v2_add(shooter->pos, bullet->offset);
bullet->hitbox = bullet->size;
bullet->size = V2(2.0f, 20.0f);
bullet->rotation = shooter->rotation;
bullet->renderMode = rendermode_quad;
// TODO(doyle): Figure out how to free this memory on entity delete. A free list?
bullet->vertexPoints =
memory_pushBytes(&world->entityArena, sizeof(v2) * 4);
bullet->vertexPoints[0] = V2(0, bullet->size.h);
bullet->vertexPoints[1] = V2(0, 0);
bullet->vertexPoints[2] = V2(bullet->size.w, 0);
bullet->vertexPoints[3] = bullet->size;
bullet->numVertexPoints = 4;
bullet->type = entitytype_bullet;
bullet->color = V4(1.0f, 1.0f, 0, 1.0f);
}
INTERNAL void setCollisionRule(World *world, enum EntityType a,
enum EntityType b, b32 rule)
{
ASSERT(a <= entitytype_count);
ASSERT(b <= entitytype_count);
world->collisionTable[a][b] = rule;
world->collisionTable[b][a] = rule;
}
void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
v2 windowSize, f32 dt)
{
MemoryIndex globalTransientArenaSize =
(MemoryIndex)((f32)memory->transientSize * 0.5f);
memory_arenaInit(&state->transientArena, memory->transient,
memory->transientSize);
globalTransientArenaSize);
World *world = &state->world;
if (!state->init)
{
srand((u32)time(NULL));
memory_arenaInit(&state->persistentArena, memory->persistent,
memory->persistentSize);
initAssetManager(state);
initRenderer(state, windowSize);
state->pixelsPerMeter = 70.0f;
world->pixelsPerMeter = 70.0f;
MemoryIndex entityArenaSize =
(MemoryIndex)((f32)memory->transientSize * 0.5f);
u8 *arenaBase = state->transientArena.base + state->transientArena.size;
memory_arenaInit(&world->entityArena, arenaBase, entityArenaSize);
{ // Init asteroid entities
i32 numAsteroids = 15;
for (i32 i = 0; i < numAsteroids; i++)
addAsteroid(state, windowSize);
addAsteroid(world, windowSize);
}
#if 1
{ // Init ship entity
Entity *ship = &state->entityList[state->entityIndex];
ship->id = state->entityIndex++;
Entity *ship = &world->entityList[world->entityIndex++];
ship->id = world->entityIdCounter++;
ship->pos = V2(100, 100);
ship->size = V2(25.0f, 50.0f);
ship->hitbox = ship->size;
ship->offset = v2_scale(ship->size, 0.5f);
ship->offset = v2_scale(ship->size, -0.5f);
ship->numVertexPoints = 3;
ship->vertexPoints = memory_pushBytes(
&state->persistentArena, sizeof(v2) * ship->numVertexPoints);
&world->entityArena, sizeof(v2) * ship->numVertexPoints);
v2 triangleBaseP = V2(0, 0);
v2 triangleTopP = V2(ship->size.w * 0.5f, ship->size.h);
@ -464,19 +505,21 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
ship->type = entitytype_ship;
ship->direction = direction_null;
ship->renderMode = rendermode_polygon;
ship->tex = NULL;
ship->collides = TRUE;
ship->color = V4(1.0f, 0.5f, 0.5f, 1.0f);
}
#endif
{ // Global Collision Rules
setCollisionRule(world, entitytype_ship, entitytype_asteroid, TRUE);
setCollisionRule(world, entitytype_bullet, entitytype_asteroid,
TRUE);
}
world->camera.min = V2(0, 0);
world->camera.max = state->renderer.size;
world->worldSize = windowSize;
state->camera.min = V2(0, 0);
state->camera.max = state->renderer.size;
state->init = TRUE;
state->worldSize = windowSize;
debug_init(&state->persistentArena, windowSize,
state->assetManager.font);
}
@ -509,26 +552,26 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
if (getKeyStatus(&state->input.keys[keycode_left_square_bracket],
readkeytype_repeat, 0.2f, dt))
{
addAsteroid(state, windowSize);
addAsteroid(world, windowSize);
}
for (i32 i = 0; i < state->entityIndex; i++)
for (i32 i = 0; i < world->entityIndex; i++)
{
Entity *entity = &state->entityList[i];
Entity *entity = &world->entityList[i];
ASSERT(entity->type != entitytype_invalid);
v2 pivotPoint = {0};
// Loop entity around world
if (entity->pos.y >= state->worldSize.h)
if (entity->pos.y >= world->worldSize.h)
entity->pos.y = 0;
else if (entity->pos.y < 0)
entity->pos.y = state->worldSize.h;
entity->pos.y = world->worldSize.h;
if (entity->pos.x >= state->worldSize.w)
if (entity->pos.x >= world->worldSize.w)
entity->pos.x = 0;
else if (entity->pos.x < 0)
entity->pos.x = state->worldSize.w;
entity->pos.x = world->worldSize.w;
f32 ddPSpeedInMs = 0;
v2 ddP = {0};
@ -560,6 +603,12 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
entity->rotation -= (rotationsPerSecond) * dt;
}
if (getKeyStatus(&state->input.keys[keycode_space],
readkeytype_delayedRepeat, 0.0f, dt))
{
addBullet(world, entity);
}
if (ddP.x > 0.0f && ddP.y > 0.0f)
{
// NOTE(doyle): Cheese it and pre-compute the vector for
@ -572,6 +621,11 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
DEBUG_PUSH_VAR("Pos: %5.2f, %5.2f", entity->pos, "v2");
DEBUG_PUSH_VAR("Velocity: %5.2f, %5.2f", entity->dP, "v2");
DEBUG_PUSH_VAR("Rotation: %5.2f", entity->rotation, "f32");
renderer_rect(&state->renderer, world->camera, entity->pos,
V2(5, 5), V2(0, 0),
DEGREES_TO_RADIANS(entity->rotation), NULL,
V4(1.0f, 1.0f, 1.0f, 1.0f), renderflag_no_texture);
}
else if (entity->type == entitytype_asteroid)
{
@ -648,26 +702,43 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
f32 dirOffset = ((randValue % 10) + 1) / 100.0f;
v2_scale(ddP, dirOffset);
// NOTE(doyle): Make asteroids start and move at constant speed
// NOTE(doyle): Make asteroids start and move at constant speed by
// ensuring that dP is "refreshed" with non-decaying acceleration
ddPSpeedInMs = 1;
entity->dP = v2_scale(ddP, state->pixelsPerMeter * ddPSpeedInMs);
entity->dP = v2_scale(ddP, world->pixelsPerMeter * ddPSpeedInMs);
entity->rotation += (60 * dt);
#endif
}
else if (entity->type == entitytype_bullet)
{
ddPSpeedInMs = 5;
Radians rotation = DEGREES_TO_RADIANS((entity->rotation + 90.0f));
ddP = V2(math_cosf(rotation), math_sinf(rotation));
entity->dP = v2_scale(ddP, world->pixelsPerMeter * ddPSpeedInMs);
}
b32 willCollide = moveEntity(state, entity, i, ddP, dt, ddPSpeedInMs);
v4 entityColor = V4(1.0f, 1.0f, 1.0f, 1.0f);
#if 1
b32 willCollide = moveEntity(world, &state->transientArena, entity, i,
ddP, dt, ddPSpeedInMs);
v4 collideColor = {0};
if (willCollide)
{
entityColor = V4(1.0f, 1.0f, 0, 1.0f);
collideColor = V4(1.0f, 0, 0, 0.5f);
}
if (entity->type == entitytype_bullet)
{
if (!math_pointInRect(world->camera, entity->pos))
{
world->entityList[i] = world->entityList[--world->entityIndex];
i--;
continue;
}
}
#endif
RenderFlags flags = renderflag_wireframe | renderflag_no_texture;
renderer_entity(&state->renderer, &state->transientArena, state->camera,
renderer_entity(&state->renderer, &state->transientArena, world->camera,
entity, V2(0, 0), 0,
entityColor, flags);
collideColor, flags);
}
#if 1

View File

@ -273,19 +273,27 @@ void debug_drawUi(GameState *state, f32 dt)
updateAndRenderDebugStack(&state->renderer, &state->transientArena, dt);
renderConsole(&state->renderer, &state->transientArena);
MemoryArena_ *transient = &state->transientArena;
i32 transientSizeInKbs = transient->size / 1024;
i32 transientUsedInKbs = transient->used / 1024;
v2 transientUsage = V2i(transientUsedInKbs, transientSizeInKbs);
DEBUG_PUSH_VAR("Transient Size: %.0f", transient->size, "f32");
DEBUG_PUSH_VAR("Transient Usage: %.0f/%.0f", transientUsage, "v2");
{ // Print Memory Arena Info
DEBUG_PUSH_STRING("== MEMORY ARENAS ==");
MemoryArena_ *transient = &state->transientArena;
i32 transientSizeInKbs = transient->size / 1024;
i32 transientUsedInKbs = transient->used / 1024;
v2 transientUsage = V2i(transientUsedInKbs, transientSizeInKbs);
DEBUG_PUSH_VAR("Transient Usage: %.0f/%.0f", transientUsage, "v2");
MemoryArena_ *persistent = &state->persistentArena;
i32 persistentSizeInKbs = persistent->size / 1024;
i32 persistentUsedInKbs = persistent->used / 1024;
v2 persistentUsage = V2i(persistentUsedInKbs, persistentSizeInKbs);
DEBUG_PUSH_VAR("Permanent Size: %.0f", persistent->size, "f32");
DEBUG_PUSH_VAR("Permanent Usage: %.0f/%.0f", persistentUsage, "v2");
MemoryArena_ *persistent = &state->persistentArena;
i32 persistentSizeInKbs = persistent->size / 1024;
i32 persistentUsedInKbs = persistent->used / 1024;
v2 persistentUsage = V2i(persistentUsedInKbs, persistentSizeInKbs);
DEBUG_PUSH_VAR("Permanent Usage: %.0f/%.0f", persistentUsage, "v2");
MemoryArena_ *entityArena = &state->world.entityArena;
i32 entitySizeInKbs = entityArena->size / 1024;
i32 entityUsedInKbs = entityArena->used / 1024;
v2 entityUsage = V2i(entityUsedInKbs, entitySizeInKbs);
DEBUG_PUSH_VAR("Entity Usage: %.0f/%.0f", entityUsage, "v2");
DEBUG_PUSH_STRING("== ==");
}
DEBUG_PUSH_VAR("Num Vertex: %d",
GLOBAL_debug.callCount[debugcount_numVertex], "i32");

View File

@ -89,8 +89,12 @@ void entity_addAnim(AssetManager *const assetManager, Entity *const entity,
DEBUG_LOG("No more free entity animation slots");
}
v2 *entity_createVertexList(MemoryArena_ *transientArena, Entity *entity)
v2 *entity_generateUpdatedVertexList(MemoryArena_ *transientArena,
Entity *entity)
{
ASSERT(entity->vertexPoints);
ASSERT(entity->numVertexPoints >= 3);
v2 *result =
memory_pushBytes(transientArena, entity->numVertexPoints * sizeof(v2));
@ -98,9 +102,10 @@ v2 *entity_createVertexList(MemoryArena_ *transientArena, Entity *entity)
{
result[i] = v2_add(entity->vertexPoints[i], entity->offset);
result[i] = v2_add(result[i], entity->pos);
}
math_applyRotationToVertexes(result[0], entity->offset,
math_applyRotationToVertexes(entity->pos, V2(0 ,0),
DEGREES_TO_RADIANS(entity->rotation), result,
entity->numVertexPoints);

View File

@ -574,11 +574,15 @@ void renderer_entity(Renderer *renderer, MemoryArena_ *transientArena,
renderTex.texRect = texRect;
}
// TODO(doyle): Proper blending
v4 renderColor = color;
if (v4_equals(color, V4(0, 0, 0, 0))) renderColor = entity->color;
if (entity->renderMode == rendermode_quad)
{
renderer_rect(renderer, camera, entity->pos, entity->size,
v2_add(entity->offset, pivotPoint), totalRotation,
&renderTex, color, flags);
&renderTex, entity->color, flags);
}
else if (entity->renderMode == rendermode_polygon)
{
@ -586,12 +590,12 @@ void renderer_entity(Renderer *renderer, MemoryArena_ *transientArena,
ASSERT(entity->vertexPoints);
v2 *offsetVertexPoints =
entity_createVertexList(transientArena, entity);
entity_generateUpdatedVertexList(transientArena, entity);
renderer_polygon(renderer, camera, offsetVertexPoints,
entity->numVertexPoints,
v2_add(entity->offset, pivotPoint), totalRotation,
&renderTex, color, flags);
&renderTex, renderColor, flags);
}
else
{

View File

@ -172,8 +172,13 @@ i32 main(void)
memory.transientSize = transientSize;
memory.transient = PLATFORM_MEM_ALLOC_(NULL, transientSize, u8);
GameState gameState = {0};
glfwSetWindowUserPointer(window, CAST(void *)(&gameState));
MemoryArena_ gameArena = {0};
memory_arenaInit(&gameArena, memory.persistent, memory.persistentSize);
GameState *gameState = MEMORY_PUSH_STRUCT(&gameArena, GameState);
gameState->persistentArena = gameArena;
glfwSetWindowUserPointer(window, CAST(void *)(gameState));
/*
*******************
@ -203,7 +208,7 @@ i32 main(void)
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
asteroid_gameUpdateAndRender(&gameState, &memory, windowSize,
asteroid_gameUpdateAndRender(gameState, &memory, windowSize,
secondsElapsed);
GL_CHECK_ERROR();
@ -233,7 +238,7 @@ i32 main(void)
char textBuffer[256];
snprintf(textBuffer, ARRAY_COUNT(textBuffer),
"Dengine | %f ms/f | %f fps | Entity Count: %d",
msPerFrame, framesPerSecond, gameState.entityIndex);
msPerFrame, framesPerSecond, gameState->world.entityIndex);
glfwSetWindowTitle(window, textBuffer);
titleUpdateFrequencyInSeconds = 0.5f;

View File

@ -8,23 +8,35 @@
#include "Dengine/Platform.h"
#include "Dengine/Renderer.h"
typedef struct World
{
MemoryArena_ entityArena;
v2 *entityVertexListCache[entitytype_count];
Entity entityList[1024];
i32 entityIndex;
u32 entityIdCounter;
f32 pixelsPerMeter;
v2 worldSize;
Rect camera;
// TODO(doyle): Ensure we change this if it gets too big
b32 collisionTable[entitytype_count][entitytype_count];
} World;
typedef struct GameState {
b32 init;
Entity entityList[1024];
i32 entityIndex;
f32 pixelsPerMeter;
v2 worldSize;
Rect camera;
World world;
AssetManager assetManager;
KeyInput input;
MemoryArena_ transientArena;
MemoryArena_ persistentArena;
Renderer renderer;
} GameState;

View File

@ -28,6 +28,7 @@ enum EntityType
entitytype_invalid,
entitytype_ship,
entitytype_asteroid,
entitytype_bullet,
entitytype_count,
};
@ -42,10 +43,7 @@ typedef struct EntityAnim
typedef struct Entity
{
i32 id;
i32 childIds[8];
i32 numChilds;
u32 id;
v2 pos;
v2 dP;
@ -53,11 +51,11 @@ typedef struct Entity
v2 hitbox;
v2 size;
// NOTE(doyle): Offset from origin point to the entity's considered "center"
// point, all operations work from this point, i.e. rotation, movement,
// collision detection
// If this is a polygon, the offset should be from the 1st vertex point
// specified
/*
NOTE(doyle): Offset is the vector to shift the polygons "origin" to the
world origin. In our case this should strictly be negative as we have
a requirement that all vertex points must be strictly positive.
*/
v2 offset;
enum RenderMode renderMode;
@ -70,17 +68,11 @@ typedef struct Entity
enum EntityType type;
enum Direction direction;
v4 color;
Texture *tex;
b32 flipX;
b32 flipY;
// TODO(doyle): Two collision flags, we want certain entities to collide
// with certain types of entities only (i.e. projectile from hero to enemy,
// only collides with enemy). Having two flags is redundant, but! it does
// allow for early-exit in collision check if the entity doesn't collide at
// all
b32 collides;
EntityAnim animList[16];
i32 animListIndex;
@ -95,5 +87,6 @@ void entity_updateAnim(Entity *const entity, const f32 dt);
void entity_addAnim(AssetManager *const assetManager, Entity *const entity,
const char *const animName);
v2 *entity_createVertexList(MemoryArena_ *transientArena, Entity *entity);
v2 *entity_generateUpdatedVertexList(MemoryArena_ *transientArena,
Entity *entity);
#endif

View File

@ -410,5 +410,23 @@ INTERNAL inline void math_applyRotationToVertexes(v2 pos, v2 pivotPoint,
vertexList[i] = newP;
}
}
INTERNAL inline f32 math_lerp(f32 a, f32 t, f32 b)
{
/*
Linear blend between two values. We having a starting point "a", and
the distance to "b" is defined as (b - a). Then we can say
a + t(b - a)
As our linear blend fn. We start from "a" and choosing a t from 0->1
will vary the value of (b - a) towards b. If we expand this, this
becomes
a + (t * b) - (a * t) == (1 - t)a + t*b
*/
f32 result = ((1 - t) * a) + (t * b);
return result;
}
#endif