Collision checks now support spatial stuff.

This commit is contained in:
Kapendev 2025-03-14 16:15:36 +02:00
parent 557622f64f
commit dcb49d6cdd
3 changed files with 97 additions and 52 deletions

View file

@ -2090,7 +2090,7 @@ void drawLine(Line area, float size, Color color = white) {
void drawTextureArea(Texture texture, Rect area, Vec2 position, DrawOptions options = DrawOptions()) {
if (texture.isEmpty || area.size.x <= 0.0f || area.size.y <= 0.0f) return;
auto target = Rect(position, area.size * options.scale.abs());
auto origin = options.origin == Vec2() ? target.origin(options.hook) : options.origin;
auto origin = options.origin.isZero ? target.origin(options.hook) : options.origin;
auto flip = options.flip;
if (options.scale.x < 0.0f && options.scale.y < 0.0f) {
flip = oppositeFlip(flip, Flip.xy);
@ -2253,7 +2253,7 @@ void drawViewport(Viewport viewport, Vec2 position, DrawOptions options = DrawOp
void drawRune(Font font, dchar rune, Vec2 position, DrawOptions options = DrawOptions()) {
if (font.isEmpty) return;
auto rect = toParin(rl.GetGlyphAtlasRec(font.data, rune));
auto origin = options.origin == Vec2() ? rect.origin(options.hook) : options.origin;
auto origin = options.origin.isZero ? rect.origin(options.hook) : options.origin;
rl.rlPushMatrix();
if (isPixelSnapped || isPixelPerfect) {
rl.rlTranslatef(position.x.floor(), position.y.floor(), 0.0f);

View file

@ -111,7 +111,7 @@ struct TileMap {
if (!has(row, col)) {
assert(0, "Tile `[{}, {}]` does not exist.".format(row, col));
}
mixin("tiles[colCount * row + col]", op, "= rhs;");
mixin("data[colCount * row + col]", op, "= rhs;");
}
void opIndexOpAssign(IStr op)(T rhs, IVec2 position) {
@ -122,6 +122,18 @@ struct TileMap {
return data.opDollar!dim();
}
Sz length() {
return data.length;
}
short* ptr() {
return data.ptr;
}
Sz capacity() {
return data.capacity;
}
bool isEmpty() {
return data.isEmpty;
}

View file

@ -9,7 +9,7 @@
// TODO: Update all the doc comments here.
// TODO: Add one-way collision support for moving walls.
// TODO: Add spatial partitioning. Just check the cell a box is in and the cells that are near it.
// NOTE: Was working on spatial partitioning. The grid is done, just need to add values in it.
// NOTE: Was working on spatial partitioning. I need to update the ids in the grid and then everything should work?
/// The `platformer` module provides a pixel-perfect physics engine.
module parin.platformer;
@ -21,18 +21,19 @@ import joka.types;
@safe @nogc nothrow:
alias BaseBoxId = uint;
alias BaseBoxFlags = ubyte;
alias WallBoxId = BaseBoxId;
alias WallBoxFlags = BaseBoxFlags;
alias ActorBoxId = BaseBoxId;
alias ActorBoxFlags = BaseBoxFlags;
alias TaggedBaseBoxId = BaseBoxId;
alias TaggedBaseBoxIdGroup = FixedList!(TaggedBaseBoxId, 510);
alias OneWaySide = RideSide;
alias BaseBoxId = uint;
alias BaseBoxFlags = ubyte;
alias WallBoxId = BaseBoxId;
alias WallBoxFlags = BaseBoxFlags;
alias ActorBoxId = BaseBoxId;
alias ActorBoxFlags = BaseBoxFlags;
alias TaggedBoxId = BaseBoxId;
alias TaggedBoxIdGroup = FixedList!(TaggedBoxId, 254);
alias OneWaySide = RideSide;
enum wallBoxTag = 0;
enum actorBoxTag = 1;
enum taggedBoxTagBit = 1 << 31;
enum boxPassableFlag = 0x1;
enum boxRidingFlag = 0x2;
@ -140,7 +141,7 @@ struct BoxWorld {
List!ActorBoxProperties actorsProperties;
List!ActorBoxId squishedIdsBuffer;
List!BaseBoxId collisionIdsBuffer;
Grid!TaggedBaseBoxIdGroup grid;
Grid!TaggedBoxIdGroup grid;
int gridTileWidth;
int gridTileHeight;
@ -154,12 +155,12 @@ struct BoxWorld {
foreach (i, ref properties; wallsProperties) {
auto id = cast(BaseBoxId) (i + 1);
auto point = getWallGridPoint(id);
grid[point.y, point.x].append(id & ~(1 << 31));
grid[point.y, point.x].append(id & ~taggedBoxTagBit);
}
foreach (i, ref properties; actorsProperties) {
auto id = cast(BaseBoxId) (i + 1);
auto point = getActorGridPoint(id);
grid[point.y, point.x].append(id | (1 << 31));
grid[point.y, point.x].append(id | taggedBoxTagBit);
}
}
@ -169,6 +170,13 @@ struct BoxWorld {
grid.clear();
}
IVec2 getGridPoint(IRect box) {
return IVec2(
box.position.x / gridTileWidth - (box.position.x < 0),
box.position.y / gridTileHeight - (box.position.y < 0),
);
}
ref IRect getWall(WallBoxId id) {
if (id == 0) assert(0, "ID `0` is always invalid and represents a box that was never created.");
return walls[id - 1];
@ -181,11 +189,7 @@ struct BoxWorld {
IVec2 getWallGridPoint(WallBoxId id) {
if (id == 0) assert(0, "ID `0` is always invalid and represents a box that was never created.");
auto i = id - 1;
return IVec2(
walls[i].position.x / gridTileWidth - (walls[i].position.x < 0),
walls[i].position.y / gridTileHeight - (walls[i].position.y < 0),
);
return getGridPoint(walls[id - 1]);
}
ref IRect getActor(ActorBoxId id) {
@ -200,62 +204,95 @@ struct BoxWorld {
IVec2 getActorGridPoint(ActorBoxId id) {
if (id == 0) assert(0, "ID `0` is always invalid and represents a box that was never created.");
auto i = id - 1;
return IVec2(
actors[i].position.x / gridTileWidth - (actors[i].position.x < 0),
actors[i].position.y / gridTileHeight - (actors[i].position.y < 0),
);
return getGridPoint(actors[id - 1]);
}
WallBoxId appendWall(IRect box, OneWaySide oneWaySide = OneWaySide.none) {
walls.append(box);
wallsProperties.append(WallBoxProperties());
wallsProperties[$ - 1].oneWaySide = oneWaySide;
return cast(BaseBoxId) walls.length;
auto id = cast(BaseBoxId) walls.length;
if (grid.length) {
auto point = getGridPoint(box);
if (box.position.x < 0 || box.position.y < 0 || !grid.has(point.y, point.x)) return id;
grid[point.y, point.x].append(id & ~taggedBoxTagBit);
}
return id;
}
ActorBoxId appendActor(IRect box, RideSide rideSide = RideSide.none) {
actors.append(box);
actorsProperties.append(ActorBoxProperties());
actorsProperties[$ - 1].rideSide = rideSide;
return cast(BaseBoxId) actors.length;
}
WallBoxId hasWallCollision(IRect box) {
foreach (i, wall; walls) {
if (wall.hasIntersection(box) && ~wallsProperties[i].flags & boxPassableFlag) return cast(BaseBoxId) (i + 1);
auto id = cast(BaseBoxId) actors.length;
if (grid.length) {
auto point = getGridPoint(box);
if (box.position.x < 0 || box.position.y < 0 || !grid.has(point.y, point.x)) return id;
grid[point.y, point.x].append(id | taggedBoxTagBit);
}
return 0;
}
ActorBoxId hasActorCollision(IRect box) {
foreach (i, actor; actors) {
if (actor.hasIntersection(box) && ~actorsProperties[i].flags & boxPassableFlag) return cast(BaseBoxId) (i + 1);
}
return 0;
return id;
}
WallBoxId[] getWallCollisions(IRect box) {
collisionIdsBuffer.clear();
foreach (i, wall; walls) {
if (wall.hasIntersection(box) && ~wallsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
// TODO: Try not going over every neighboring cell please...
if (grid.length) {
auto point = getGridPoint(box);
foreach (y; -1 .. 2) { foreach (x; -1 .. 2) {
auto otherPoint = IVec2(point.x + x, point.y + y);
if (otherPoint.x < 0 || otherPoint.y < 0 || !grid.has(otherPoint.y, otherPoint.x)) continue;
foreach (taggedId; grid[point.y + y, point.x + x]) {
auto i = (taggedId & ~taggedBoxTagBit) - 1;
auto isActor = taggedId & taggedBoxTagBit;
if (isActor) continue;
if (walls[i].hasIntersection(box) && ~wallsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
}}
} else {
foreach (i, wall; walls) {
if (wall.hasIntersection(box) && ~wallsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
}
return collisionIdsBuffer[];
}
ActorBoxId[] getActorCollisions(IRect box) {
collisionIdsBuffer.clear();
foreach (i, actor; actors) {
if (actor.hasIntersection(box) && ~actorsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
// TODO: Try not going over every neighboring cell please...
if (grid.length) {
auto point = getGridPoint(box);
foreach (y; -1 .. 2) { foreach (x; -1 .. 2) {
auto otherPoint = IVec2(point.x + x, point.y + y);
if (otherPoint.x < 0 || otherPoint.y < 0 || !grid.has(otherPoint.y, otherPoint.x)) continue;
foreach (taggedId; grid[point.y + y, point.x + x]) {
auto i = (taggedId & ~taggedBoxTagBit) - 1;
auto isWall = !(taggedId & taggedBoxTagBit);
if (isWall) continue;
if (actors[i].hasIntersection(box) && ~actorsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
}}
} else {
foreach (i, actor; actors) {
if (actor.hasIntersection(box) && ~actorsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
}
return collisionIdsBuffer[];
}
WallBoxId hasWallCollision(IRect box) {
auto boxes = getWallCollisions(box);
return boxes.length ? boxes[0] : 0;
}
ActorBoxId hasActorCollision(IRect box) {
auto boxes = getActorCollisions(box);
return boxes.length ? boxes[0] : 0;
}
WallBoxId moveActorX(ActorBoxId id, float amount) {
auto actor = &getActor(id);
auto properties = &getActorProperties(id);
properties.remainder.x += amount;
auto move = cast(int) properties.remainder.x.round();
if (move == 0) return false;
@ -309,7 +346,6 @@ struct BoxWorld {
auto actor = &getActor(id);
auto properties = &getActorProperties(id);
properties.remainder.y += amount;
auto move = cast(int) properties.remainder.y.round();
if (move == 0) return false;
@ -413,13 +449,9 @@ struct BoxWorld {
ActorBoxId[] moveWall(WallBoxId id, Vec2 amount) {
auto wall = &getWall(id);
auto properties = &getWallProperties(id);
if (properties.oneWaySide) assert(0, "One-way collisions are not yet supported for moving walls.");
properties.remainder += amount;
// NOTE: Will be removed when I want to work on that...
if (properties.oneWaySide) {
assert(0, "One-way collisions are not yet supported for moving walls.");
}
squishedIdsBuffer.clear();
auto move = properties.remainder.round().toIVec();
if (move.x != 0 || move.y != 0) {
@ -437,6 +469,7 @@ struct BoxWorld {
actorProperties.flags |= wall.hasIntersection(rideBox) ? boxRidingFlag : 0x0;
}
}
if (move.x != 0) {
wall.position.x += move.x;
properties.remainder.x -= move.x;