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/real_type.h b/src/real_type.h index ec56fdf7..ed042f81 100644 --- a/src/real_type.h +++ b/src/real_type.h @@ -8,6 +8,7 @@ #ifdef USE_FLOAT_REALS typedef float GMLReal; +typedef int32_t GMLReal_int; #define GMLReal_sin sinf #define GMLReal_cos cosf @@ -31,6 +32,7 @@ typedef float GMLReal; #else typedef double GMLReal; +typedef int64_t GMLReal_int; #define GMLReal_sin sin #define GMLReal_cos cos @@ -56,12 +58,12 @@ typedef double GMLReal; // Round-half-to-even (banker's rounding). // While the original runner uses "llrint(double)", we use our own banker's rounding implementation to avoid quirks in specific platforms (like the PlayStation 2) having different llrint rounding implementations. static inline GMLReal GMLReal_bankersRound(GMLReal v) { - if (isnan(v) || isinf(v)) return v; GMLReal f = GMLReal_floor(v); GMLReal frac = v - f; - if (0.5 > frac) return f; - if (frac > 0.5) return f + 1.0; - // Exactly halfway: round to the even neighbor. - int64_t fi = (int64_t) f; - return (fi & 1) == 0 ? f : f + 1.0; + + if (frac == 0.5) { + GMLReal_int fi = (GMLReal_int) f; + return ((fi & 1) == 0) ? f : (f + 1.0); + } + return (frac > 0.5) ? (f + 1.0) : f; } 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;