1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | internal bool32 TestWall(real32 WallX, real32 RelX, real32 RelY, real32 PlayerDeltaX, real32 PlayerDeltaY, real32 *tMin, real32 MinY, real32 MaxY) { bool32 Hit = false; real32 tEpsilon = 0.00001f; if(PlayerDeltaX != 0.0f) { real32 tResult = (WallX - RelX) / PlayerDeltaX; real32 Y = RelY + tResult*PlayerDeltaY; if((tResult >= 0.0f) && (*tMin > tResult)) { if((Y >= MinY) && (Y <= MaxY)) { *tMin = Maximum(0.0f, tResult - tEpsilon); Hit = true; } } } return(Hit); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | real32 tRemaining = 1.0f; for(uint32 Iteration = 0; (Iteration < 4) && (tRemaining > 0.0f); ++Iteration) { real32 tMin = 1.0f; v2 WallNormal = {}; Assert((MaxTileX - MinTileX) < 32); Assert((MaxTileY - MinTileY) < 32); for(uint32 AbsTileY = MinTileY; AbsTileY <= MaxTileY; ++AbsTileY) { for(uint32 AbsTileX = MinTileX; AbsTileX <= MaxTileX; ++AbsTileX) { tile_map_position TestTileP = CenteredTilePoint(AbsTileX, AbsTileY, AbsTileZ); uint32 TileValue = GetTileValue(TileMap, TestTileP); if(!IsTileValueEmpty(TileValue)) { real32 DiameterW = TileMap->TileSideInMeters + Entity->Width; real32 DiameterH = TileMap->TileSideInMeters + Entity->Height; v2 MinCorner = -0.5f*v2{DiameterW, DiameterH}; v2 MaxCorner = 0.5f*v2{DiameterW, DiameterH}; tile_map_difference RelOldPlayerP = Subtract(TileMap, &Entity->P, &TestTileP); v2 Rel = RelOldPlayerP.dXY; if(TestWall(MinCorner.X, Rel.X, Rel.Y, PlayerDelta.X, PlayerDelta.Y, &tMin, MinCorner.Y, MaxCorner.Y)) { WallNormal = v2{-1, 0}; } if(TestWall(MaxCorner.X, Rel.X, Rel.Y, PlayerDelta.X, PlayerDelta.Y, &tMin, MinCorner.Y, MaxCorner.Y)) { WallNormal = v2{1, 0}; } if(TestWall(MinCorner.Y, Rel.Y, Rel.X, PlayerDelta.Y, PlayerDelta.X, &tMin, MinCorner.X, MaxCorner.X)) { WallNormal = v2{0, -1}; } if(TestWall(MaxCorner.Y, Rel.Y, Rel.X, PlayerDelta.Y, PlayerDelta.X, &tMin, MinCorner.X, MaxCorner.X)) { WallNormal = v2{0, 1}; } } } } Entity->P = Offset(TileMap, Entity->P, tMin*PlayerDelta); Entity->dP = Entity->dP - 1*Inner(Entity->dP, WallNormal)*WallNormal; PlayerDelta = PlayerDelta - 1*Inner(PlayerDelta, WallNormal)*WallNormal; tRemaining -= tMin*tRemaining; } |
1 2 3 4 5 6 7 8 9 10 11 12 | real32 Edge0 = Inner(PixelP - Origin, -Perp(XAxis)); real32 Edge1 = Inner(PixelP - (Origin + XAxis), -Perp(YAxis)); real32 Edge2 = Inner(PixelP - (Origin + XAxis + YAxis), Perp(XAxis)); real32 Edge3 = Inner(PixelP - (Origin + YAxis), Perp(YAxis)); if((Edge0 < 0) && (Edge1 < 0) && (Edge2 < 0) && (Edge3 < 0)) { *Pixel = Color32; } |
mrmixer
I don't know exactly what Casey meant, but I think the idea is to use the inner products to determine if there is a collision, and only if there is one, try to compute the intersection.
mrmixer
If you're trying to get some general collision code, you can look at the Separating Axis Theorem (SAT) or Gilbert Johnson Keerthi (GJK) + Expanding Polytope Algorithm (EPA).
Grid
Those inner product edge tests can only tell if a point is inside a rectangle but not produce the intersection point of a line segment and one of the edges?
mrmixer
- If you do the dot product of two vectors, the resulting scalar tells you if the two vectors point in the same general direction:
--- if the value is positive they are pointing in the same general direction
--- if the value is negative they are pointing in opposite general direction
--- if the value is 0 they are perpendicular.
Grid
- If moving too fast, an entity can go directly through obstacles
- Even if they don't go completely though when moving fast, if they get far enough into an obstacle then they are pushed to the wrong side of it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | while ( game_running ) { r32 delta_time = ...; r32 physics_timestep = 0.005f; /* Balance this value for "correctness" vs "performance". */ r32 p_dt = 0; while ( p_dt + physics_timestep < delta_time ) { simulate( physics_timestep, ... ); p_dt += physics_timestep; } /* Note that at this point p_dt is less than delta_time. So there is delta_time - p_dt time left to simulate. You can do another step with that time but it will change the predictability of the fixed timestep. You can simulate it in the next frame, ignore it, do something else. I don't really know about that. */ } |
Grid
- Entities can't glide slopes nicely, since step 4 will zero their velocity so they glide downward slopes very slowly
Grid
2 - Test entity's new position against all other entities one at a time (this produces the mtv)
mrmixer
Instead of setting the velocity to 0, you need to change the direction of the entity into the direction of the slope, and probably remove a bit of the velocity (how much you went into the obstacle).
You'll need to figure out on which side of that axis you need to go (probably use the dot product of the velocity with the axis to find out).
mrmixer
This is probably OK at first, but if you got a lot of entities, or shapes with a lot of edges it will most likely take a lot of time. If you run into that, you can do a "broad phase" where you do a simple test to see if a collision is possible and create a smaller list of entities pairs that could collide. The simple test is for example "entities that are more than 1 meter apart can't collide", or testing axis aligned bounding boxes.