diff --git a/src/collision.h b/src/collision.h index e6f690fe..7a87da79 100644 --- a/src/collision.h +++ b/src/collision.h @@ -18,11 +18,6 @@ static inline bool Collision_matchesTarget(DataWin* dataWin, Instance* inst, int return VM_isObjectOrDescendant(dataWin, inst->objectIndex, target); } -typedef struct { - GMLReal left, right, top, bottom; - bool valid; -} InstanceBBox; - // Returns the collision sprite for an instance (mask sprite if set, else display sprite) static inline Sprite* Collision_getSprite(DataWin* dataWin, Instance* inst) { int32_t sprIdx = (inst->maskIndex >= 0) ? inst->maskIndex : inst->spriteIndex; @@ -32,8 +27,15 @@ static inline Sprite* Collision_getSprite(DataWin* dataWin, Instance* inst) { // Computes the axis-aligned bounding box for an instance using its collision sprite static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) { + // Fast path: return cached AABB when nothing bbox-affecting has changed. + if (inst->bboxCacheValid) return inst->cachedBBox; + Sprite* spr = Collision_getSprite(runner->dataWin, inst); - if (spr == nullptr) return (InstanceBBox){0, 0, 0, 0, false}; + if (spr == nullptr) { + inst->cachedBBox = (InstanceBBox){0, 0, 0, 0, false}; + inst->bboxCacheValid = true; + return inst->cachedBBox; + } GMLReal marginL = (GMLReal) spr->marginLeft; GMLReal marginR = (GMLReal) (spr->marginRight + 1); @@ -42,6 +44,7 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) GMLReal originX = (GMLReal) spr->originX; GMLReal originY = (GMLReal) spr->originY; + InstanceBBox result; GMLReal left, right, top, bottom; if (GMLReal_fabs(inst->imageAngle) > 0.0001) { // Compute rotated AABB: transform the 4 corners of the unrotated bbox @@ -62,13 +65,10 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) cx[2] = cs * lx0 + sn * ly1; cy[2] = -sn * lx0 + cs * ly1; cx[3] = cs * lx1 + sn * ly1; cy[3] = -sn * lx1 + cs * ly1; - GMLReal minX = cx[0], maxX = cx[0], minY = cy[0], maxY = cy[0]; - for (int c = 1; 4 > c; c++) { - if (minX > cx[c]) minX = cx[c]; - if (cx[c] > maxX) maxX = cx[c]; - if (minY > cy[c]) minY = cy[c]; - if (cy[c] > maxY) maxY = cy[c]; - } + GMLReal minX = GMLReal_fmin(GMLReal_fmin(cx[0], cx[1]), GMLReal_fmin(cx[2], cx[3])); + GMLReal maxX = GMLReal_fmax(GMLReal_fmax(cx[0], cx[1]), GMLReal_fmax(cx[2], cx[3])); + GMLReal minY = GMLReal_fmin(GMLReal_fmin(cy[0], cy[1]), GMLReal_fmin(cy[2], cy[3])); + GMLReal maxY = GMLReal_fmax(GMLReal_fmax(cy[0], cy[1]), GMLReal_fmax(cy[2], cy[3])); left = inst->x + minX; right = inst->x + maxX; @@ -82,8 +82,14 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) bottom = inst->y + inst->imageYscale * (marginB - originY); // Normalize if negative scale - if (left > right) { GMLReal tmp = left; left = right; right = tmp; } - if (top > bottom) { GMLReal tmp = top; top = bottom; bottom = tmp; } + GMLReal tmp_left = GMLReal_fmin(left, right); + GMLReal tmp_right = GMLReal_fmax(left, right); + GMLReal tmp_top = GMLReal_fmin(top, bottom); + GMLReal tmp_bottom = GMLReal_fmax(top, bottom); + left = tmp_left; + right = tmp_right; + top = tmp_top; + bottom = tmp_bottom; } if (runner->collisionCompatibilityMode) { @@ -93,7 +99,11 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) bottom = GMLReal_bankersRound(bottom); } - return (InstanceBBox){left, right, top, bottom, true}; + result = (InstanceBBox){left, right, top, bottom, true}; + + inst->cachedBBox = result; + inst->bboxCacheValid = true; + return result; } static inline bool Collision_hasFrameMasks(Sprite* sprite) { diff --git a/src/collision_types.h b/src/collision_types.h new file mode 100644 index 00000000..d3de55b3 --- /dev/null +++ b/src/collision_types.h @@ -0,0 +1,7 @@ +#pragma once +#include "real_type.h" + +typedef struct { + GMLReal left, right, top, bottom; + bool valid; +} InstanceBBox; diff --git a/src/instance.c b/src/instance.c index 4af3e4aa..572e3d09 100644 --- a/src/instance.c +++ b/src/instance.c @@ -47,6 +47,7 @@ Instance* Instance_create(uint32_t instanceId, int32_t objectIndex, GMLReal x, G inst->gravityDirection = 270.0f; inst->pathIndex = -1; inst->pathScale = 1.0f; + inst->bboxCacheValid = false; inst->timelineIndex = -1; inst->timelinePosition = 0.0f; inst->timelineSpeed = 1.0f; diff --git a/src/instance.h b/src/instance.h index 5876fce2..6e91ec3a 100644 --- a/src/instance.h +++ b/src/instance.h @@ -1,6 +1,7 @@ #pragma once #include "common.h" +#include "collision_types.h" #include #include "rvalue.h" #include "gml_array.h" @@ -62,6 +63,10 @@ struct Instance { float pathXStart; // origin for relative paths float pathYStart; + // AABB cache for fast path reuse + InstanceBBox cachedBBox; + bool bboxCacheValid; + int32_t alarm[GML_ALARM_COUNT]; // Timeline following state diff --git a/src/spatial_grid.c b/src/spatial_grid.c index 66c86155..dc04b5d5 100644 --- a/src/spatial_grid.c +++ b/src/spatial_grid.c @@ -103,6 +103,8 @@ void SpatialGrid_markInstanceAsDirty(SpatialGrid* grid, Instance* dirtyInstance) return; } + dirtyInstance->bboxCacheValid = false; + if (dirtyInstance->spatialGridDirty) return;