From bb5b3f39bbc01cbde737b250d2be877d3e1472cd Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Sun, 12 Jan 2025 03:23:35 +0300 Subject: [PATCH] Engine: backport sprite rotation code update from ags4 * Fix Math to use double math when converting between radians and degrees * Implement RotateSize() helper function, and use it in DynamicSprite_Rotate(), instead of a in-place code. * Move angle to fixed-point value conversion into the Bitmap::RotateBlt(), for convenience of use. --- Common/gfx/allegrobitmap.cpp | 6 +++++- Common/gfx/allegrobitmap.h | 4 ++-- Common/util/geometry.cpp | 16 ++++++++++++++++ Common/util/geometry.h | 5 +++++ Common/util/math.h | 16 ++++++++++++---- Engine/ac/dynamicsprite.cpp | 23 ++++++----------------- Engine/ac/math.cpp | 7 ++++--- 7 files changed, 50 insertions(+), 27 deletions(-) diff --git a/Common/gfx/allegrobitmap.cpp b/Common/gfx/allegrobitmap.cpp index 2aaa2a3d23b..ef6827c6ec3 100644 --- a/Common/gfx/allegrobitmap.cpp +++ b/Common/gfx/allegrobitmap.cpp @@ -379,12 +379,16 @@ void Bitmap::FlipBlt(Bitmap *src, int dst_x, int dst_y, GraphicFlip flip) void Bitmap::RotateBlt(Bitmap *src, int dst_x, int dst_y, fixed_t angle) { + // convert to allegro angle + fixed_t al_angle = itofix((angle * 256) / 360); BITMAP *al_src_bmp = src->_alBitmap; rotate_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, angle); } void Bitmap::RotateBlt(Bitmap *src, int dst_x, int dst_y, int pivot_x, int pivot_y, fixed_t angle) -{ +{ + // convert to allegro angle + fixed_t al_angle = itofix((angle * 256) / 360); BITMAP *al_src_bmp = src->_alBitmap; pivot_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, pivot_x, pivot_y, angle); } diff --git a/Common/gfx/allegrobitmap.h b/Common/gfx/allegrobitmap.h index 577e9b0af5b..46d31edeca7 100644 --- a/Common/gfx/allegrobitmap.h +++ b/Common/gfx/allegrobitmap.h @@ -222,8 +222,8 @@ class Bitmap void LitBlendBlt(Bitmap *src, int dst_x, int dst_y, int light_amount); // TODO: generic "draw transformed" function? What about mask option? void FlipBlt(Bitmap *src, int dst_x, int dst_y, GraphicFlip flip); - void RotateBlt(Bitmap *src, int dst_x, int dst_y, fixed_t angle); - void RotateBlt(Bitmap *src, int dst_x, int dst_y, int pivot_x, int pivot_y, fixed_t angle); + void RotateBlt(Bitmap *src, int dst_x, int dst_y, int angle); + void RotateBlt(Bitmap *src, int dst_x, int dst_y, int pivot_x, int pivot_y, int angle); //========================================================================= // Pixel operations diff --git a/Common/util/geometry.cpp b/Common/util/geometry.cpp index 88f19524574..4f3c712bc0d 100644 --- a/Common/util/geometry.cpp +++ b/Common/util/geometry.cpp @@ -141,5 +141,21 @@ Rect IntersectRects(const Rect &r1, const Rect &r2) std::min(r1.Right, r2.Right), std::min(r1.Bottom, r2.Bottom)); } +Size RotateSize(Size sz, int degrees) +{ + // 1 degree = 181 degrees in terms of x/y size, so % 180 + int fixangle = degrees % 180; + // and 0..90 is the same as 180..90 + if (fixangle > 90) + fixangle = 180 - fixangle; + // useAngle is now between 0 and 90 (otherwise the sin/cos stuff doesn't work) + const double rads = AGSMath::DegreesToRadians(fixangle); + const double sinv = sin(rads); + const double cosv = cos(rads); + const int width = (int)(cosv * (double)sz.Width + sinv * (double)sz.Height); + const int height = (int)(sinv * (double)sz.Width + cosv * (double)sz.Height); + return Size(width, height); +} + //} // namespace Common //} // namespace AGS diff --git a/Common/util/geometry.h b/Common/util/geometry.h index b37a21e972b..5acd8b732cc 100644 --- a/Common/util/geometry.h +++ b/Common/util/geometry.h @@ -469,6 +469,11 @@ Rect PlaceInRect(const Rect &place, const Rect &item, const RectPlacement &place Rect SumRects(const Rect &r1, const Rect &r2); // Intersect two rectangles, the resolt is the rectangle bounding their intersection Rect IntersectRects(const Rect &r1, const Rect &r2); + +// Calculates the size of a rectangle necessary to accomodate the rect of original size +// if it were rotated by the given angle (in degrees) +Size RotateSize(Size sz, int degrees); + //} // namespace Common //} // namespace AGS diff --git a/Common/util/math.h b/Common/util/math.h index 441ade1aea4..5b9ff485085 100644 --- a/Common/util/math.h +++ b/Common/util/math.h @@ -77,14 +77,22 @@ namespace Math static_cast(val) : def; } - inline float RadiansToDegrees(float rads) + inline double RadiansToDegrees(const double rads) { - return rads * (float)(180.0 / M_PI); + return rads * (180.0 / M_PI); } - inline float DegreesToRadians(float deg) + inline double DegreesToRadians(const double deg) { - return deg * (float)(M_PI / 180.0); + return deg * (M_PI / 180.0); + } + + // Wraps the angle in degrees into [0;360) range + inline double ClampAngle360(const double degrees) + { + if (degrees >= 0.0) + return std::fmod(degrees, 360.0); + return std::fmod(360.0 + degrees, 360.0); } } // namespace Math diff --git a/Engine/ac/dynamicsprite.cpp b/Engine/ac/dynamicsprite.cpp index 7abb8b5602e..ef5de75f8f0 100644 --- a/Engine/ac/dynamicsprite.cpp +++ b/Engine/ac/dynamicsprite.cpp @@ -209,28 +209,17 @@ void DynamicSprite_Rotate(ScriptDynamicSprite *sds, int angle, int width, int he if (sds->slot == 0) quit("!DynamicSprite.Rotate: sprite has been deleted"); + const int src_width = game.SpriteInfos[sds->slot].Width; + const int src_height = game.SpriteInfos[sds->slot].Height; if ((width == SCR_NO_VALUE) || (height == SCR_NO_VALUE)) { - // calculate the new image size automatically - // 1 degree = 181 degrees in terms of x/y size, so % 180 - int useAngle = angle % 180; - // and 0..90 is the same as 180..90 - if (useAngle > 90) - useAngle = 180 - useAngle; - // useAngle is now between 0 and 90 (otherwise the sin/cos stuff doesn't work) - double angleInRadians = (double)useAngle * (M_PI / 180.0); - double sinVal = sin(angleInRadians); - double cosVal = cos(angleInRadians); - - width = (cosVal * (double)game.SpriteInfos[sds->slot].Width + sinVal * (double)game.SpriteInfos[sds->slot].Height); - height = (sinVal * (double)game.SpriteInfos[sds->slot].Width + cosVal * (double)game.SpriteInfos[sds->slot].Height); + Size rot_sz = RotateSize(Size(src_width, src_height), angle); + width = rot_sz.Width; + height = rot_sz.Height; } else { data_to_game_coords(&width, &height); } - // convert to allegro angle - angle = (angle * 256) / 360; - // resize the sprite to the requested size Bitmap *sprite = spriteset[sds->slot]; std::unique_ptr new_pic(BitmapHelper::CreateTransparentBitmap(width, height, sprite->GetColorDepth())); @@ -238,7 +227,7 @@ void DynamicSprite_Rotate(ScriptDynamicSprite *sds, int angle, int width, int he // rotate the sprite about its centre // (+ width%2 fixes one pixel offset problem) new_pic->RotateBlt(sprite, width / 2 + width % 2, height / 2, - sprite->GetWidth() / 2, sprite->GetHeight() / 2, itofix(angle)); + src_width / 2, src_height / 2, angle); // replace the bitmap in the sprite set add_dynamic_sprite(sds->slot, std::move(new_pic), (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0); diff --git a/Engine/ac/math.cpp b/Engine/ac/math.cpp index 364f0387fa5..24bbdd8f0f4 100644 --- a/Engine/ac/math.cpp +++ b/Engine/ac/math.cpp @@ -11,12 +11,13 @@ // https://opensource.org/license/artistic-2-0/ // //============================================================================= - #include "ac/math.h" #include #include "ac/common.h" // quit #include "util/math.h" +using namespace AGS::Common; + int FloatToInt(float value, int roundDirection) { switch (roundDirection) @@ -115,12 +116,12 @@ float Math_RaiseToPower(float base, float exp) float Math_DegreesToRadians(float value) { - return static_cast(value * (M_PI / 180.0)); + return static_cast(Math::DegreesToRadians(value)); } float Math_RadiansToDegrees(float value) { - return static_cast(value * (180.0 / M_PI)); + return static_cast(Math::RadiansToDegrees(value)); } float Math_GetPi()