From d49811358a7540836bb1a2cc179b7482ccf8d477 Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Fri, 25 Nov 2016 20:43:43 +1100 Subject: [PATCH] Fix bullet rendering artifacts On delete entity, old entries were not being cleared out causing bullets being the most commonly spawned object to inherit unusual behaviour from "dead" entities. Fixed by ensuring on entity delete the entity entry is cleared out. Also add rendering guard against malformed vertex points. The renderer has a strict requirement that all polygons passed in are CCW. Bullets were being formed with the mindset of a triangle strip causing it to render incorrectly. Now renderer checks ordering of polygons points and asserts if incorrect. It works based off calculating the bounding area polygons, where in CCW order this will produce a negative result and positive for CW order. --- src/Asteroid.c | 69 ++++++++++++++++++++++++++++++-------------------- src/Renderer.c | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/src/Asteroid.c b/src/Asteroid.c index eb53c61..14c936d 100644 --- a/src/Asteroid.c +++ b/src/Asteroid.c @@ -495,24 +495,24 @@ 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->pos = shooter->pos; bullet->size = V2(2.0f, 20.0f); + bullet->offset = v2_scale(bullet->size, -0.5f); + bullet->hitbox = bullet->size; bullet->rotation = shooter->rotation; - bullet->renderMode = rendermode_quad; + bullet->renderMode = rendermode_polygon; if (!world->bulletVertexCache) { world->bulletVertexCache = - memory_pushBytes(&world->entityArena, sizeof(v2) * 4); + MEMORY_PUSH_ARRAY(&world->entityArena, 4, v2); world->bulletVertexCache[0] = V2(0, bullet->size.h); world->bulletVertexCache[1] = V2(0, 0); world->bulletVertexCache[2] = V2(bullet->size.w, 0); world->bulletVertexCache[3] = bullet->size; } - bullet->vertexPoints = world->bulletVertexCache; + bullet->vertexPoints = world->bulletVertexCache; bullet->numVertexPoints = 4; bullet->type = entitytype_bullet; @@ -542,6 +542,19 @@ INTERNAL AudioRenderer *getFreeAudioRenderer(World *world) return NULL; } +INTERNAL void deleteEntity(World *world, i32 entityIndex) +{ + ASSERT(entityIndex > 0); + ASSERT(entityIndex < ARRAY_COUNT(world->entityList)); + + /* Last entity replaces the entity to delete */ + world->entityList[entityIndex] = world->entityList[world->entityIndex - 1]; + + /* Make sure the replaced entity from end of list is cleared out */ + Entity emptyEntity = {0}; + world->entityList[--world->entityIndex] = emptyEntity; +} + void asteroid_gameUpdateAndRender(GameState *state, Memory *memory, v2 windowSize, f32 dt) { @@ -649,7 +662,7 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory, Entity *entity = &world->entityList[i]; ASSERT(entity->type != entitytype_invalid); - v2 pivotPoint = {0}; + v2 pivotPoint = {0}; f32 ddPSpeedInMs = 0; v2 ddP = {0}; if (entity->type == entitytype_ship) @@ -662,24 +675,10 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory, // is right facing for trig to work Radians rotation = DEGREES_TO_RADIANS((entity->rotation + 90.0f)); - v2 direction = V2(math_cosf(rotation), math_sinf(rotation)); ddP = direction; } - Degrees rotationsPerSecond = 180.0f; - if (platform_queryKey(&state->input.keys[keycode_left], - readkeytype_repeat, 0.0f)) - { - entity->rotation += (rotationsPerSecond)*dt; - } - - if (platform_queryKey(&state->input.keys[keycode_right], - readkeytype_repeat, 0.0f)) - { - entity->rotation -= (rotationsPerSecond)*dt; - } - if (platform_queryKey(&state->input.keys[keycode_space], readkeytype_one_shot, KEY_DELAY_NONE)) { @@ -698,6 +697,20 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory, } } + Degrees rotationsPerSecond = 180.0f; + if (platform_queryKey(&state->input.keys[keycode_left], + readkeytype_repeat, 0.0f)) + { + entity->rotation += (rotationsPerSecond)*dt; + } + + if (platform_queryKey(&state->input.keys[keycode_right], + readkeytype_repeat, 0.0f)) + { + entity->rotation -= (rotationsPerSecond)*dt; + } + entity->rotation = (f32)((i32)entity->rotation); + ddPSpeedInMs = 25; DEBUG_PUSH_VAR("Pos: %5.2f, %5.2f", entity->pos, "v2"); DEBUG_PUSH_VAR("Velocity: %5.2f, %5.2f", entity->dP, "v2"); @@ -792,14 +805,13 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory, { if (!math_pointInRect(world->camera, entity->pos)) { - world->entityList[i--] = - world->entityList[--world->entityIndex]; + deleteEntity(world, i--); continue; } Radians rotation = DEGREES_TO_RADIANS((entity->rotation + 90.0f)); v2 localDp = V2(math_cosf(rotation), math_sinf(rotation)); - entity->dP = v2_scale(localDp, world->pixelsPerMeter * 10); + entity->dP = v2_scale(localDp, world->pixelsPerMeter * 5); } /* Loop entity around world */ @@ -862,11 +874,12 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory, } ASSERT(colliderB->type == entitytype_bullet); - world->entityList[collisionIndex] = - world->entityList[--world->entityIndex]; - world->entityList[i--] = - world->entityList[--world->entityIndex]; + + deleteEntity(world, collisionIndex); + + deleteEntity(world, i--); world->asteroidCounter--; + ASSERT(world->asteroidCounter >= 0); AudioRenderer *audioRenderer = getFreeAudioRenderer(world); diff --git a/src/Renderer.c b/src/Renderer.c index 259d935..b697a6e 100644 --- a/src/Renderer.c +++ b/src/Renderer.c @@ -443,6 +443,54 @@ void renderer_polygon(Renderer *const renderer, Rect camera, { ASSERT(numPoints >= 3); + { // Validate polygon is CCW + /* + NOTE(doyle): Polygon vertexes must be specified in a CCW order! + This check utilises the equation for calculating the bounding area + of a polygon by decomposing the shapes into line segments and + calculating the area under the segment. On cartesian plane, if the + polygon is CCW, then by creating these "area" calculatings in + sequential order, we'll produce negative valued areas since we + determine line segment length by subtracting x2-x1. + + Better explanation over here + http://blog.element84.com/polygon-winding.html + */ + + f32 areaSum = 0.0f; + for (i32 i = 0; i < numPoints - 1; i++) + { + f32 lengthX = polygonPoints[i + 1].x - polygonPoints[i].x; + + // NOTE(doyle): The height of a line segment is actually (y1 + y2)/2 + // But since the (1/2) is a constant factor we can get rid of for + // checking the winding order.. + // i.e. a negative number halved is still always negative. + f32 lengthY = polygonPoints[i + 1].y + polygonPoints[i].y; + + areaSum += (lengthX * lengthY); + } + + f32 lengthX = polygonPoints[0].x - polygonPoints[numPoints - 1].x; + f32 lengthY = polygonPoints[0].y + polygonPoints[numPoints - 1].y; + areaSum += (lengthX * lengthY); + + if (areaSum < 0) + { + // NOTE(doyle): Is counter clockwise + } + else if (areaSum > 0) + { + // NOTE(doyle): Is clockwise + ASSERT(INVALID_CODE_PATH); + } + else + { + // NOTE(doyle): CW + CCW combination, i.e. figure 8 shape + ASSERT(INVALID_CODE_PATH); + } + } + for (i32 i = 0; i < numPoints; i++) polygonPoints[i] = v2_sub(polygonPoints[i], camera.min);