From 5b5917fbab3e724b3ac1dfd4e70f17f4b0e1f7b5 Mon Sep 17 00:00:00 2001 From: Nathan Fulton Date: Wed, 31 Aug 2016 08:59:47 -0400 Subject: [PATCH] Implement SdlAnimSprite --- game/sdl/htsdl.xcodeproj/project.pbxproj | 8 + game/sdl/sdlanimsprite.cpp | 327 +++++++++++++++++++++++ game/sdl/sdlanimsprite.h | 61 +++++ game/sdl/sdlspritemgr.h | 6 +- 4 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 game/sdl/sdlanimsprite.cpp create mode 100644 game/sdl/sdlanimsprite.h diff --git a/game/sdl/htsdl.xcodeproj/project.pbxproj b/game/sdl/htsdl.xcodeproj/project.pbxproj index f46571c..e7a414c 100644 --- a/game/sdl/htsdl.xcodeproj/project.pbxproj +++ b/game/sdl/htsdl.xcodeproj/project.pbxproj @@ -346,6 +346,8 @@ D813C0A01D6E3CDF007132EE /* NavBackSmall.png in Resources */ = {isa = PBXBuildFile; fileRef = D813C0591D6E3B2F007132EE /* NavBackSmall.png */; }; D813C0A11D6E3CDF007132EE /* NavForwardSmall.png in Resources */ = {isa = PBXBuildFile; fileRef = D813C05A1D6E3B2F007132EE /* NavForwardSmall.png */; }; D813C0A21D6E3CDF007132EE /* webview.mm in Sources */ = {isa = PBXBuildFile; fileRef = D813C05C1D6E3B2F007132EE /* webview.mm */; }; + D8408C041D770C0900304E05 /* sdlanimsprite.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D8408C021D770C0900304E05 /* sdlanimsprite.cpp */; }; + D8408C051D770C2D00304E05 /* sdlanimsprite.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D8408C021D770C0900304E05 /* sdlanimsprite.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -685,6 +687,8 @@ D813C08A1D6E3C30007132EE /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/CoreAudio.framework; sourceTree = DEVELOPER_DIR; }; D813C08C1D6E3C46007132EE /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; D813C08E1D6E3C4E007132EE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; + D8408C021D770C0900304E05 /* sdlanimsprite.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sdlanimsprite.cpp; sourceTree = ""; }; + D8408C031D770C0900304E05 /* sdlanimsprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sdlanimsprite.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1105,6 +1109,8 @@ 55C5AC511390627C003B15C5 /* htplatform.h */, 55C5AC651390627C003B15C5 /* main.cpp */, 55C5AC791390627C003B15C5 /* savegame.cpp */, + D8408C021D770C0900304E05 /* sdlanimsprite.cpp */, + D8408C031D770C0900304E05 /* sdlanimsprite.h */, C169F5B013A43CD800B4491D /* sdleventserver.h */, 55C5AC7E1390627C003B15C5 /* sdlpackfile.cpp */, 55C5AC7F1390627C003B15C5 /* sdlpackfile.h */, @@ -1374,6 +1380,7 @@ 5501E616139076E20048AAFE /* main.cpp in Sources */, 551C91601390A40F0018ECBA /* yajl.c in Sources */, 551C91611390A40F0018ECBA /* yajl_buf.c in Sources */, + D8408C041D770C0900304E05 /* sdlanimsprite.cpp in Sources */, 551C91621390A40F0018ECBA /* yajl_encode.c in Sources */, 551C91641390A40F0018ECBA /* yajl_lex.c in Sources */, 551C91651390A40F0018ECBA /* yajl_parser.c in Sources */, @@ -1552,6 +1559,7 @@ D813C01C1D6E3A5E007132EE /* selectserver.cpp in Sources */, D813C01D1D6E3A5E007132EE /* socket.cpp in Sources */, D813C01E1D6E3A5E007132EE /* socketaddress.cpp in Sources */, + D8408C051D770C2D00304E05 /* sdlanimsprite.cpp in Sources */, D813C01F1D6E3A5E007132EE /* socketserver.cpp in Sources */, D813C0201D6E3A5E007132EE /* thread.cpp in Sources */, D813C0211D6E3A5E007132EE /* tick.cpp in Sources */, diff --git a/game/sdl/sdlanimsprite.cpp b/game/sdl/sdlanimsprite.cpp new file mode 100644 index 0000000..2e5dbbc --- /dev/null +++ b/game/sdl/sdlanimsprite.cpp @@ -0,0 +1,327 @@ +#include "base/tick.h" +#include "base/thread.h" +#include "game/sdl/sdlanimsprite.h" +#include "game/sdl/sysmessages.h" + +namespace wi { + +SdlAnimSprite::SdlAnimSprite(SpriteManager *psprm) { + hash_ = 0; + cx_ = 0; + cy_ = 0; + xOrigin_ = 0; + yOrigin_ = 0; + texture_ = NULL; + surface_ = NULL; + nScale_ = 1.0f; + x_ = 0; + y_ = 0; + fVisible_ = true; + psprm_ = psprm; +} + +SdlAnimSprite::~SdlAnimSprite() { + psprm_->Remove(this); + + crit_.Enter(); + SDL_DestroyTexture(texture_); + texture_ = NULL; + SDL_FreeSurface(surface_); + surface_ = NULL; + crit_.Leave(); +} + +void SdlAnimSprite::CaptureFrame(UnitGob *pgob) { + // Surface already created? + + dword hash = pgob->GetAnimationHash(); + if (hash == hash_) { + return; + } + + // Enter critical section since drawing occurs on the main thread + + crit_.Enter(); + hash_ = hash; + + // Remember UIBounds + + UnitConsts *puntc = GetUnitConsts(pgob->GetType()); + rcUI_.FromWorldRect(&puntc->wrcUIBounds); + + // Create surface + + bool fSuccess = CreateSurface(pgob); + crit_.Leave(); + if (!fSuccess) { + return; + } + + // Add this sprite + psprm_->Add(this); +} + +bool SdlAnimSprite::CreateSurface(UnitGob *pgob) { + // Get height/width + Rect rcAni; + pgob->GetAnimationBounds(&rcAni, false); + cx_ = (rcAni.Width() + 1) & ~1; + cy_ = rcAni.Height(); + + // Remember the bounds of the base. This'll be used later for y + // positioning. + + pgob->GetAnimationBounds(&rcBase_, true); + + // rcAni has negative left and top. This identifies the origin. + xOrigin_ = rcAni.left; + yOrigin_ = rcAni.top; + + // Alloc the 8bpp buffer. + int cp = cx_ * cy_; + byte *pb8 = new byte[cp]; + if (pb8 == NULL) { + return false; + } + + // Draw the animation into an 8 bit DibBitmap + // The 8->32 conversion palette has been tweaked so that 255 + // will map to RGBA transparent on output. + + DibBitmap bm; + bm.Init(pb8, cx_, cy_); + memset(pb8, 255, cp); + + // Subvert the TBitmap shdowing to our purpose. + // The background is 255. Force TBitmap shadowing to turn this into 254. + // 254 has been to RGBA color with appropriate alpha, when the 8->32bpp + // conversion occurs. + + byte bSav = gmpiclriclrShadow[255]; + gmpiclriclrShadow[255] = 254; + pgob->DrawAnimation(&bm, -xOrigin_, -yOrigin_); + gmpiclriclrShadow[255] = bSav; + + // Alloc the 32bpp buffer. + dword *pdw32 = new dword[cp]; + if (pdw32 == NULL) { + delete[] pb8; + return false; + } + + // Convert to 32bpp. Sdl will rotate at draw time. + byte *pbT = pb8; + dword *pdwT = pdw32; + int cpT = cp; + while (cpT-- != 0) { + *pdwT++ = mp8bpp32bpp_[*pbT++]; + } + delete[] pb8; + + // Create the appropriate masks + + dword rmask = 0xff000000; + dword gmask = 0x00ff0000; + dword bmask = 0x0000ff00; + dword amask = 0x000000ff; + + surface_ = SDL_CreateRGBSurfaceFrom(pdw32, cx_, cy_, 32, (cx_ * sizeof(dword)), bmask, gmask, rmask, amask); + + if (surface_ == NULL) { + delete[] pdw32; + return false; + } + + return true; +} + +void SdlAnimSprite::Draw(void *pv, Size *psiz) { + if (!fVisible_) { + return; + } + + // This gets called on the drawing thread. + // The coordinate space pre-rotated to be the game coordinate space but at + // sector 0 (0, 0 is at bottom left). As always, xOrigin_ and yOrigin_ + // are negative. + + crit_.Enter(); + + Rect rcBounds; + GetBounds(&rcBounds); + + SDL_Rect rc = { + x_ + rcBounds.left, + y_ + rcBounds.top, + rcBounds.Width() , + rcBounds.Height() + }; + + if (surface_ != NULL) { + // Create a texture from the surface + texture_ = SDL_CreateTextureFromSurface((SDL_Renderer *)pv, surface_); + + // Render the texture + SDL_RenderCopy((SDL_Renderer *)pv, texture_, NULL, &rc); + } + + crit_.Leave(); +} + +void SdlAnimSprite::GetBounds(Rect *prc) { + base::CritScope cs(&crit_); + + // Scale the UI rect, centered on the non-scaled UI rect + + Rect rcUIScaled = rcUI_; + rcUIScaled.Inflate(rcUI_.Width() * (nScale_ - 1.0f) / 2, + rcUI_.Height() * (nScale_ - 1.0f) / 2); + + // Center the height within the scaled UI rect, so it is centered under + // the finger. Use the base height, since that doesn't change as other + // parts animate. Offset by the base origin so the base stays in one + // place (even if the height changes). Finally, use the same percentage + // offset from the UI rect to position correctly. + + int xNew = rcUIScaled.left + (xOrigin_ - rcUI_.left) * nScale_; + int yNew = (rcUIScaled.top + rcUIScaled.bottom - + rcBase_.Height() * nScale_) / 2 - rcBase_.top * nScale_ + + yOrigin_ * nScale_; + int cxNew = (float)cx_ * nScale_; + int cyNew = (float)cy_ * nScale_; + + prc->left = xNew; + prc->top = yNew; + prc->right = xNew + cxNew; + prc->bottom = yNew + cyNew; +} + +void SdlAnimSprite::SetPalette(Palette *ppal) { + // Set the palette mapping table + // Note the AMXs use the first 131 colors of the palette. + for (int n = 0; n < BigWord(ppal->cEntries); n++) { + byte *pb = (byte *)&mp8bpp32bpp_[n]; + *pb++ = 255; + *pb++ = ppal->argb[n][0]; + *pb++ = ppal->argb[n][1]; + *pb++ = ppal->argb[n][2]; + } + + // Make the last color of the palete RGBA for transparent. + // We'll fill the 8bpp image with this, so that at 8->32 conversion, + // we get transparency. + + mp8bpp32bpp_[255] = 0; + + // Make the second to last 40% black. AMX transparent will map to this + // with clever remapping. 40% is equivalent to tbitmap shadowmap. + + byte *pb = (byte *)&mp8bpp32bpp_[254]; + *pb++ = 102; + *pb++ = 0; + *pb++ = 0; + *pb++ = 0; +} + +void SdlAnimSprite::SetScale(float nScale) { + if (nScale_ == nScale) { + return; + } + nScale_ = nScale; + psprm_->Update(this); +} + +void SdlAnimSprite::SetPosition(int x, int y) { + if (x_ == x && y_ == y) { + return; + } + x_ = x; + y_ = y; + psprm_->Update(this); +} + +void SdlAnimSprite::Show(bool fShow) { + if (fVisible_ == fShow) { + return; + } + fVisible_ = fShow; + psprm_->Update(this); +} + +void SdlAnimSprite::SetScaleAnimation(float nScaleStart, float nScaleEnd, + dword cms, dword cmsRate, bool fAutoDestroy) { +#if 1 + SetScale(nScaleEnd); + return; +#endif + + // If the current scale is inside the requested range, advance the + // animation timing to that point. For example if while it is scaling + // up there is a request to scale it down, this keeps the animation + // stepping constant. + + if (nScale_ >= nScaleStart && nScale_ <= nScaleEnd) { + float nPercent = (float)(nScaleEnd - nScale_) / + (float)(nScaleEnd - nScaleStart); + cms *= nPercent; + nScaleStart = nScale_; + } + nScaleStart_ = nScaleStart; + nScaleEnd_ = nScaleEnd; + msAnimateStart_ = base::GetMillisecondCount(); + cmsAnimate_ = cms; + cmsAnimationRate_ = cmsRate; + fAutoDestroy_ = fAutoDestroy; + + thread_.Clear(this, kidmAnimationTimer); + base::Message msg; + msg.handler = this; + msg.id = kidmAnimationTimer; + thread_.PostDelayed(&msg, cmsAnimationRate_ / 10); + + SetScale(nScaleStart_); + psprm_->Update(this); +} + +void SdlAnimSprite::OnMessage(base::Message *pmsg) { + if (pmsg->id == kidmAnimationTimer) { + long msCurrent = base::GetMillisecondCount(); + float nPercent = (float)(msCurrent - msAnimateStart_) / + (float)cmsAnimate_; + + bool fFinished = false; + if (nPercent >= 1.0f) { + nPercent = 1.0f; + fFinished = true; + } + SetScale(nScaleStart_ + (nScaleEnd_ - nScaleStart_) * nPercent); + psprm_->Update(this); + + if (fFinished) { + if (fAutoDestroy_) { + // Delete after the next timer, so the current frame shows + + base::Message msg; + msg.handler = this; + msg.id = kidmDestroyAfterAnimation; + thread_.PostDelayed(&msg, cmsAnimationRate_ / 10); + } + return; + } + + // Schedule the next timer + + base::Message msg; + msg.handler = this; + msg.id = kidmAnimationTimer; + thread_.PostDelayed(&msg, cmsAnimationRate_ / 10); + } + + if (pmsg->id == kidmDestroyAfterAnimation) { + // This deletes asynchronously - very convenient + + thread_.Dispose(this); + } +} + +} // namespace wi diff --git a/game/sdl/sdlanimsprite.h b/game/sdl/sdlanimsprite.h new file mode 100644 index 0000000..da7413b --- /dev/null +++ b/game/sdl/sdlanimsprite.h @@ -0,0 +1,61 @@ +#ifndef __SDLANIMSPRITE_H__ +#define __SDLANIMSPRITE_H__ + +#include "base/criticalsection.h" +#include "base/messagehandler.h" +#include "inc/basictypes.h" +#include "game/ht.h" + +namespace wi { + +class SdlAnimSprite : public AnimSprite, base::MessageHandler { +public: + SdlAnimSprite(SpriteManager *psprm); + ~SdlAnimSprite(); + + // AnimationSprite + virtual void SetPalette(Palette *ppal); + virtual void CaptureFrame(UnitGob *pgob); + virtual void SetScaleAnimation(float nScaleStart, float nScaleEnd, + dword cms, dword cmsRate, bool fAutoDestroy); + + // Sprite + virtual SpriteManager *GetManager() { return psprm_; } + virtual void SetScale(float scale); + virtual void SetPosition(int x, int y); + virtual void Show(bool fShow); + virtual bool IsVisible() { return fVisible_; } + virtual void GetBounds(Rect *prc); + + // PlatformSprite + virtual void Draw(void *pv, Size *psiz); + +private: + bool CreateSurface(UnitGob *pgob); + + // MessageHandler + virtual void OnMessage(base::Message *pmsg); + + base::CriticalSection crit_; + Rect rcUI_; + int x_, y_; + Rect rcBase_; + float nScale_; + bool fVisible_; + dword hash_; + int cx_, cy_; + int xOrigin_, yOrigin_; + SDL_Texture *texture_; + SDL_Surface *surface_; + SpriteManager *psprm_; + dword mp8bpp32bpp_[256]; + float nScaleStart_, nScaleEnd_; + long msAnimateStart_; + dword cmsAnimate_; + dword cmsAnimationRate_; + bool fAutoDestroy_; +}; + +} // namespace wi + +#endif // __SDLANIMSPRITE_H__ diff --git a/game/sdl/sdlspritemgr.h b/game/sdl/sdlspritemgr.h index 6b71cb4..842f6e5 100644 --- a/game/sdl/sdlspritemgr.h +++ b/game/sdl/sdlspritemgr.h @@ -3,6 +3,7 @@ #include "game/sprite.h" #include "game/sdl/sdlselectionsprite.h" +#include "game/sdl/sdlanimsprite.h" namespace wi { @@ -12,9 +13,8 @@ public: ~SdlSpriteManager(); virtual void SetClipRects(wi::Rect *prc1, wi::Rect *prc2); - virtual wi::AnimSprite *CreateAnimSprite() { - LOG() << "SdlSpriteManager::CreateAnimSprite not implemented yet"; - return NULL; + virtual wi::SdlAnimSprite *CreateAnimSprite() { + return new SdlAnimSprite(this); } virtual wi::SelectionSprite *CreateSelectionSprite() { return new SdlSelectionSprite(this);