From 77519332e8ed232619417ebbf6eb01e4b58236f5 Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Wed, 11 Sep 2024 07:18:07 -0400 Subject: [PATCH 01/17] - deactivate appimage for now, don't have enough interest looking into what's causing it to fail --- {.github => .github-inactive}/workflows/appimage.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {.github => .github-inactive}/workflows/appimage.yml (100%) diff --git a/.github/workflows/appimage.yml b/.github-inactive/workflows/appimage.yml similarity index 100% rename from .github/workflows/appimage.yml rename to .github-inactive/workflows/appimage.yml From a663d97961e736a56d362c1b09622114cb4504ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Wed, 14 Aug 2024 04:53:49 -0300 Subject: [PATCH 02/17] load id24res if present --- src/d_iwad.cpp | 37 ++++++++++++++++++++++++++++---- src/d_main.h | 3 +++ wadsrc_extra/static/iwadinfo.txt | 20 +++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 7f9bc60fe..3ad6ed004 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -111,6 +111,12 @@ void FIWadManager::ParseIWadInfo(const char *fn, const char *data, int datasize, iwad->prio = sc.Number; } } + else if (sc.Compare("SupportWAD")) + { + sc.MustGetStringName("="); + sc.MustGetString(); + iwad->SupportWAD = sc.String; + } else if (sc.Compare("Config")) { sc.MustGetStringName("="); @@ -562,6 +568,18 @@ void FIWadManager::ValidateIWADs() static bool havepicked = false; + +FString FIWadManager::IWADPathFileSearch(const FString &file) +{ + for(const FString &path : mSearchPaths) + { + FString f = path + "/" + file; + if(FileExists(f)) return f; + } + + return ""; +} + int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char *iwad, const char *zdoom_wad, const char *optional_wad) { const char *iwadparm = Args->CheckValue ("-iwad"); @@ -802,11 +820,11 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char D_AddFile (wadfiles, zdoom_wad, true, -1, GameConfig); // [SP] Load non-free assets if available. This must be done before the IWAD. - int iwadnum; + int iwadnum = 1; if (D_AddFile(wadfiles, optional_wad, true, -1, GameConfig)) - iwadnum = 2; - else - iwadnum = 1; + { + iwadnum++; + } fileSystem.SetIwadNum(iwadnum); if (picks[pick].mRequiredPath.IsNotEmpty()) @@ -818,6 +836,17 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char fileSystem.SetMaxIwadNum(iwadnum); auto info = mIWadInfos[picks[pick].mInfoIndex]; + + if(info.SupportWAD.IsNotEmpty()) + { + FString supportWAD = IWADPathFileSearch(info.SupportWAD); + + if(supportWAD.IsNotEmpty()) + { + D_AddFile(wadfiles, supportWAD.GetChars(), true, -1, GameConfig); + } + } + // Load additional resources from the same directory as the IWAD itself. for (unsigned i=0; i < info.Load.Size(); i++) { diff --git a/src/d_main.h b/src/d_main.h index 49e9846be..0d4a33079 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -76,6 +76,7 @@ struct FIWADInfo FString Name; // Title banner text for this IWAD FString Autoname; // Name of autoload ini section for this IWAD FString IWadname; // Default name this game would use - this is for IWAD detection in GAMEINFO. + FString SupportWAD; // Optional support WAD, load if present (initially implemented for id24) int prio = 0; // selection priority for given IWAD name. FString Configname; // Name of config section for this IWAD FString Required; // Requires another IWAD @@ -133,7 +134,9 @@ class FIWadManager void CollectSearchPaths(); void AddIWADCandidates(const char *dir); void ValidateIWADs(); + FString IWADPathFileSearch(const FString &file); public: + FIWadManager(const char *fn, const char *fnopt); const FIWADInfo *FindIWAD(std::vector& wadfiles, const char *iwad, const char *basewad, const char *optionalwad); const FString *GetAutoname(unsigned int num) const diff --git a/wadsrc_extra/static/iwadinfo.txt b/wadsrc_extra/static/iwadinfo.txt index 8eb7f1522..985df8b5d 100644 --- a/wadsrc_extra/static/iwadinfo.txt +++ b/wadsrc_extra/static/iwadinfo.txt @@ -338,6 +338,7 @@ IWad "DMENUPIC", "M_ACPT", "M_CAN", "M_EXITO", "M_CHG" BannerColors = "54 54 54", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -356,6 +357,7 @@ IWad Load = "extras.wad" BannerColors = "00 7c 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -374,6 +376,7 @@ IWad Load = "extras.wad", "soundtrack" BannerColors = "00 7c 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -388,6 +391,7 @@ IWad MustContain = "SMOOSHED", "ANIMDEFS", "LANGUAGE", "MAPINFO", "ENDOOM", "M_DOOM", "TITLEPIC", "TEXTURES" BannerColors = "a8 00 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -404,6 +408,7 @@ IWad "DPHOOF","BFGGA0","HEADA1","CYBRA1","SPIDA1D1", "E4M2", "E1M10", "SEWERS" BannerColors = "18 18 18", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -420,6 +425,7 @@ IWad "DPHOOF","BFGGA0","HEADA1","CYBRA1","SPIDA1D1", "E4M2" BannerColors = "54 54 54", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -436,6 +442,7 @@ IWad "DPHOOF","BFGGA0","HEADA1","CYBRA1","SPIDA1D1" BannerColors = "54 54 54", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -449,6 +456,7 @@ IWad MustContain = "E1M1" BannerColors = "54 54 54", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -463,6 +471,7 @@ IWad MustContain = "MAP01", "REDTNT2", "DMAPINFO" BannerColors = "00 7c 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -478,6 +487,7 @@ IWad BannerColors = "a8 00 00", "a8 a8 a8" Load = "extras.wad" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -492,6 +502,7 @@ IWad MustContain = "MAP01", "REDTNT2" BannerColors = "a8 00 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -506,6 +517,7 @@ IWad MustContain = "MAP01", "CAMO1", "DMAPINFO" BannerColors = "00 7c 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -521,6 +533,7 @@ IWad BannerColors = "a8 00 00", "a8 a8 a8" Load = "extras.wad" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -535,6 +548,7 @@ IWad MustContain = "MAP01", "CAMO1" BannerColors = "a8 00 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -550,6 +564,7 @@ IWad BannerColors = "a8 00 00", "a8 a8 a8" Load = "nerve.wad" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } IWad @@ -565,6 +580,7 @@ IWad BannerColors = "00 7c 00", "a8 a8 a8" IgnoreTitlePatches = 1 Load = "extras.wad" + SupportWAD = "id24res.wad" } IWad @@ -580,6 +596,7 @@ IWad BannerColors = "00 7c 00", "a8 a8 a8" IgnoreTitlePatches = 1 Load = "nerveunity.wad", "extras.wad", "soundtrack" + SupportWAD = "id24res.wad" } IWad @@ -596,6 +613,7 @@ IWad IgnoreTitlePatches = 1 Load = "nerve.wad" DeleteLumps = "M_EPI1", "M_EPI2", "M_EPISOD" + SupportWAD = "id24res.wad" } IWad @@ -609,6 +627,7 @@ IWad Compatibility = "Shorttex", "nosectionmerge" MustContain = "MAP01", "WIOBJ" BannerColors = "ff ff ff", "a8 00 00" + SupportWAD = "id24res.wad" } // Doom 2 must be last to be checked because MAP01 is its only requirement @@ -624,6 +643,7 @@ IWad MustContain = "MAP01" BannerColors = "a8 00 00", "a8 a8 a8" IgnoreTitlePatches = 1 + SupportWAD = "id24res.wad" } From ce24849adf2a0a94db1e073abb3e6bb67507158f Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Mon, 19 Aug 2024 15:00:27 +0600 Subject: [PATCH 03/17] Add support for fire textures in ANIMDEFS --- src/CMakeLists.txt | 1 + src/common/textures/firetexture.cpp | 139 +++++++++++++++++++++++++++ src/common/textures/firetexture.h | 17 ++++ src/gamedata/textures/animations.cpp | 110 +++++++++++++++++++++ src/gamedata/textures/animations.h | 10 ++ 5 files changed, 277 insertions(+) create mode 100644 src/common/textures/firetexture.cpp create mode 100644 src/common/textures/firetexture.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37d5d4839..789bea954 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1022,6 +1022,7 @@ set (PCH_SOURCES common/textures/multipatchtexturebuilder.cpp common/textures/skyboxtexture.cpp common/textures/animtexture.cpp + common/textures/firetexture.cpp common/textures/v_collection.cpp common/textures/formats/automaptexture.cpp common/textures/formats/brightmaptexture.cpp diff --git a/src/common/textures/firetexture.cpp b/src/common/textures/firetexture.cpp new file mode 100644 index 000000000..4a89622f7 --- /dev/null +++ b/src/common/textures/firetexture.cpp @@ -0,0 +1,139 @@ +/* +** firetexture.cpp +** PSX/N64-style fire texture implementation +** +**--------------------------------------------------------------------------- +** Copyright Cacodemon345 2024 +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +/* Algorithm based on https://github.com/fabiensanglard/DoomFirePSX */ + +#include "bitmap.h" +#include "firetexture.h" +#include "m_random.h" +#include "imagehelpers.h" + +#include "engineerrors.h" + +static constexpr int32_t FIRESKY_W = 64; +static constexpr int32_t FIRESKY_H = 128; + +FireTexture::FireTexture() +{ + SetSize(FIRESKY_W, FIRESKY_H); + Image.Clear(); + Image.AppendFill(0, Width * Height); +} + +void FireTexture::SetPalette(TArray& colors) +{ + Palette.Clear(); + Palette.Append(colors); + + /* This shouldn't happen in any circumstances. */ + if (Palette.Size() > 256) + { + I_FatalError("Fire palette too big!"); + } + + for (unsigned int i = 0; i < Width; i++) { + Image[(Height - 1) * Width + i] = (uint8_t)(Palette.Size() - 1); + } +} + +void FireTexture::Update() +{ + for (unsigned int y = 1; y < Height; y++) + { + for (unsigned int x = 0; x < Width; x++) + { + uint8_t srcPixel = Image[y * Width + x]; + + if (srcPixel == 0) + { + Image[(y - 1) * Width + x] = 0; + } + else + { + int XRand = M_Random() & 3; + int destRand = M_Random() & 1; + + Image[(y - 1) * Width + ((x + 1 - XRand) % Width)] = srcPixel - destRand; + } + } + } +} + +FBitmap FireTexture::GetBgraBitmap(const PalEntry* remap, int* trans) +{ + FBitmap bitmap; + bitmap.Create(Width, Height); + uint32_t* pixels = (uint32_t*)bitmap.GetPixels(); + + for (unsigned int y = 0; y < Height; y++) + { + for (unsigned int x = 0; x < Width; x++) + { + pixels[y * Width + x] = Palette[Image[y * Width + x]]; + } + } + + return bitmap; +} + +TArray FireTexture::Get8BitPixels(bool alphatex) +{ + FBitmap bitmap = GetBgraBitmap(nullptr, nullptr); + const uint8_t* data = bitmap.GetPixels(); + + uint8_t* dest_p; + int dest_adv = Height; + int dest_rew = Width * Height - 1; + + TArray Pixels(Width * Height); + dest_p = Pixels.Data(); + + bool doalpha = alphatex; + // Convert the source image from row-major to column-major format and remap it + for (int y = Height; y != 0; --y) + { + for (int x = Width; x != 0; --x) + { + int b = *data++; + int g = *data++; + int r = *data++; + int a = *data++; + if (a < 128) *dest_p = 0; + else *dest_p = ImageHelpers::RGBToPalette(doalpha, r, g, b); + dest_p += dest_adv; + } + dest_p -= dest_rew; + } + return Pixels; +} diff --git a/src/common/textures/firetexture.h b/src/common/textures/firetexture.h new file mode 100644 index 000000000..3ea6d967d --- /dev/null +++ b/src/common/textures/firetexture.h @@ -0,0 +1,17 @@ +#pragma once + +#include "textures.h" + + +class FireTexture : public FTexture +{ + TArray Image; + TArray Palette; + +public: + FireTexture(); + void SetPalette(TArray& colors); + void Update(); + virtual FBitmap GetBgraBitmap(const PalEntry* remap, int* trans) override; + virtual TArray Get8BitPixels(bool alphatex) override; +}; \ No newline at end of file diff --git a/src/gamedata/textures/animations.cpp b/src/gamedata/textures/animations.cpp index d40f2c7c5..6df649789 100644 --- a/src/gamedata/textures/animations.cpp +++ b/src/gamedata/textures/animations.cpp @@ -62,7 +62,12 @@ static FRandom pr_animatepictures ("AnimatePics"); void FTextureAnimator::DeleteAll() { + for (unsigned int i = 0; i < mFireTextures.Size(); i++) + { + mFireTextures[i].texture->CleanHardwareData(true); + } mAnimations.Clear(); + mFireTextures.Clear(); for (unsigned i = 0; i < mSwitchDefs.Size(); i++) { @@ -348,6 +353,10 @@ void FTextureAnimator::InitAnimDefs () TexMan.GameTexture(picnum)->SetSkyOffset(sc.Number); } } + else if (sc.Compare("firetexture")) + { + ParseFireTexture (sc); + } else { sc.ScriptError (NULL); @@ -782,6 +791,84 @@ void FTextureAnimator::ParseCameraTexture(FScanner &sc) viewer->SetDisplaySize((float)fitwidth, (float)fitheight); } +void FTextureAnimator::ParseFireTexture(FScanner& sc) +{ + FString picname; + FFireTexture fireTexture; + TArray palette; + uint32_t duration; + + sc.MustGetString(); + picname = sc.String; + sc.MustGetStringName("tics"); + sc.MustGetValue(true); + duration = uint32_t(sc.Float * 1000 / TICRATE); + + FGameTexture* gametex = MakeGameTexture(new FireTexture(), picname.GetChars(), ETextureType::Wall); + // No decals here. + gametex->SetNoDecals(true); + if (sc.GetString()) + { + if (sc.Compare("allowdecals")) + { + gametex->SetNoDecals(false); + } + else + { + sc.UnGet(); + } + } + while (sc.GetString()) + { + if (sc.Compare("color")) + { + uint8_t r, g, b, a; + sc.MustGetValue(false); + r = sc.Number; + sc.MustGetValue(false); + g = sc.Number; + sc.MustGetValue(false);; + b = sc.Number; + sc.MustGetValue(false); + a = sc.Number; + palette.Push(PalEntry(a, r, g, b)); + if (a != 255 && a != 0); + gametex->SetTranslucent(true); + + if (palette.Size() > 256) + { + sc.ScriptError("Too many colors specified!"); + } + } + else if (sc.Compare("palette")) + { + uint8_t index = 0; + sc.MustGetValue(false); + index = sc.Number; + PalEntry pal = GPalette.BaseColors[index]; + pal.a = pal.isBlack() ? 0 : 255; + palette.Push(pal); + + if (palette.Size() > 256) + { + sc.ScriptError("Too many colors specified!"); + } + } + else + { + sc.UnGet(); + break; + } + } + + fireTexture.texture = gametex; + fireTexture.Duration = duration; + fireTexture.SwitchTime = 0; + static_cast(gametex->GetTexture())->SetPalette(palette); + mFireTextures.Push(fireTexture); + TexMan.AddGameTexture(gametex); +} + //========================================================================== // // FTextureAnimator :: FixAnimations @@ -1040,6 +1127,29 @@ FTextureID FTextureAnimator::UpdateStandaloneAnimation(FStandaloneAnimation &ani void FTextureAnimator::UpdateAnimations (uint64_t mstime) { + for (unsigned int i = 0; i < mFireTextures.Size(); i++) + { + FFireTexture* fire = &mFireTextures[i]; + bool updated = false; + + if (fire->SwitchTime == 0) + { + fire->SwitchTime = mstime + fire->Duration; + } + else while (fire->SwitchTime <= mstime) + { + static_cast(fire->texture->GetTexture())->Update(); + fire->SwitchTime = mstime + fire->Duration; + updated = true; + } + + if (updated) + { + fire->texture->CleanHardwareData(); + delete fire->texture->GetSoftwareTexture(); + fire->texture->SetSoftwareTexture(nullptr); + } + } for (unsigned int j = 0; j < mAnimations.Size(); ++j) { FAnimDef *anim = &mAnimations[j]; diff --git a/src/gamedata/textures/animations.h b/src/gamedata/textures/animations.h index aa6b00e4e..1b3a11dbc 100644 --- a/src/gamedata/textures/animations.h +++ b/src/gamedata/textures/animations.h @@ -5,6 +5,7 @@ #include "textureid.h" #include "tarray.h" #include "s_soundinternal.h" +#include "firetexture.h" struct FStandaloneAnimation { @@ -17,6 +18,13 @@ struct FStandaloneAnimation static_assert(sizeof(FStandaloneAnimation) == sizeof(uint64_t)*2); +struct FFireTexture +{ + uint32_t Duration; // Duration before updates. + uint64_t SwitchTime; // Absolute time before update. + FGameTexture* texture; +}; + struct FAnimDef { struct FAnimFrame @@ -77,6 +85,7 @@ class FTextureAnimator TArray mAnimations; TArray mSwitchDefs; TArray mAnimatedDoors; + TArray mFireTextures; void ParseAnim(FScanner& sc, ETextureType usetype); FAnimDef* ParseRangeAnim(FScanner& sc, FTextureID picnum, ETextureType usetype, bool missing); @@ -84,6 +93,7 @@ class FTextureAnimator void ParseWarp(FScanner& sc); void ParseCanvasTexture(FScanner& sc); void ParseCameraTexture(FScanner& sc); + void ParseFireTexture(FScanner& sc); FTextureID ParseFramenum(FScanner& sc, FTextureID basepicnum, ETextureType usetype, bool allowMissing); void ParseTime(FScanner& sc, uint32_t& min, uint32_t& max); From 38290b6615848c0c52ff8b71051869fce5153fbe Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Thu, 12 Sep 2024 00:25:52 +0600 Subject: [PATCH 04/17] Truecolor sky: Draw opaque black background on transparent texels Fixes HOM effect --- src/common/textures/image.cpp | 3 ++- src/gamedata/textures/animations.cpp | 5 ++++- src/rendering/swrenderer/drawers/r_draw_sky32.h | 16 ++++++++++++++++ .../swrenderer/drawers/r_draw_sky32_sse2.h | 16 ++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/common/textures/image.cpp b/src/common/textures/image.cpp index 83f7450eb..95ee7d23c 100644 --- a/src/common/textures/image.cpp +++ b/src/common/textures/image.cpp @@ -306,7 +306,8 @@ void FImageSource::EndPrecaching() void FImageSource::RegisterForPrecache(FImageSource *img, bool requiretruecolor) { - img->CollectForPrecache(precacheInfo, requiretruecolor); + if (img) + img->CollectForPrecache(precacheInfo, requiretruecolor); } //========================================================================== diff --git a/src/gamedata/textures/animations.cpp b/src/gamedata/textures/animations.cpp index 6df649789..7b7a1049b 100644 --- a/src/gamedata/textures/animations.cpp +++ b/src/gamedata/textures/animations.cpp @@ -1146,7 +1146,10 @@ void FTextureAnimator::UpdateAnimations (uint64_t mstime) if (updated) { fire->texture->CleanHardwareData(); - delete fire->texture->GetSoftwareTexture(); + + if (fire->texture->GetSoftwareTexture()) + delete fire->texture->GetSoftwareTexture(); + fire->texture->SetSoftwareTexture(nullptr); } } diff --git a/src/rendering/swrenderer/drawers/r_draw_sky32.h b/src/rendering/swrenderer/drawers/r_draw_sky32.h index ea9dff4f7..ab11948bd 100644 --- a/src/rendering/swrenderer/drawers/r_draw_sky32.h +++ b/src/rendering/swrenderer/drawers/r_draw_sky32.h @@ -64,6 +64,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; + if (*dest == 0) + *dest |= 0xff000000; dest += pitch; frac += fracstep; } @@ -90,6 +92,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; + if (fg == 0) + fg |= 0xff000000; uint32_t alpha = max(min(frac >> (16 - start_fade), 256), 0); uint32_t inv_alpha = 256 - alpha; @@ -110,6 +114,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; + if (*dest == 0) + *dest |= 0xff000000; frac += fracstep; dest += pitch; @@ -121,6 +127,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; + if (fg == 0) + fg |= 0xff000000; uint32_t alpha = max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0); uint32_t inv_alpha = 256 - alpha; @@ -189,6 +197,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } *dest = fg; @@ -222,6 +232,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } uint32_t alpha = max(min(frac >> (16 - start_fade), 256), 0); @@ -245,6 +257,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } *dest = fg; @@ -262,6 +276,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } uint32_t alpha = max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0); diff --git a/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h b/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h index bc2411b83..739bf298e 100644 --- a/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h +++ b/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h @@ -64,6 +64,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; + if (*dest == 0) + *dest |= 0xff000000; dest += pitch; frac += fracstep; } @@ -90,6 +92,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; + if (fg == 0) + fg |= 0xff000000; __m128i alpha = _mm_set1_epi16(max(min(frac >> (16 - start_fade), 256), 0)); __m128i inv_alpha = _mm_sub_epi16(_mm_set1_epi16(256), alpha); @@ -108,6 +112,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; + if (*dest == 0) + *dest |= 0xff000000; frac += fracstep; dest += pitch; @@ -119,6 +125,8 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; + if (fg == 0) + fg |= 0xff000000; __m128i alpha = _mm_set1_epi16(max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0)); __m128i inv_alpha = _mm_sub_epi16(_mm_set1_epi16(256), alpha); @@ -173,6 +181,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } *dest = fg; @@ -218,6 +228,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } __m128i alpha = _mm_set1_epi16(max(min(frac >> (16 - start_fade), 256), 0)); @@ -241,6 +253,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } *dest = fg; @@ -258,6 +272,8 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; + if (fg == 0) + fg |= 0xff000000; } __m128i alpha = _mm_set1_epi16(max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0)); From ed39413532ad13c6e875c3b9b73047cbdff2ab11 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 12 Sep 2024 08:47:00 +0200 Subject: [PATCH 05/17] Render the bottom sky layer always with full opacity in the SW true color renderer. No need to add explicit checks for color (0,0,0,0) here. --- .../swrenderer/drawers/r_draw_sky32.h | 24 +++++++------------ .../swrenderer/drawers/r_draw_sky32_sse2.h | 24 +++++++------------ 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/rendering/swrenderer/drawers/r_draw_sky32.h b/src/rendering/swrenderer/drawers/r_draw_sky32.h index ab11948bd..5a1d0ec53 100644 --- a/src/rendering/swrenderer/drawers/r_draw_sky32.h +++ b/src/rendering/swrenderer/drawers/r_draw_sky32.h @@ -64,8 +64,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; - if (*dest == 0) - *dest |= 0xff000000; + *dest |= 0xff000000; dest += pitch; frac += fracstep; } @@ -92,8 +91,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; uint32_t alpha = max(min(frac >> (16 - start_fade), 256), 0); uint32_t inv_alpha = 256 - alpha; @@ -114,8 +112,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; - if (*dest == 0) - *dest |= 0xff000000; + *dest |= 0xff000000; frac += fracstep; dest += pitch; @@ -127,8 +124,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; uint32_t alpha = max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0); uint32_t inv_alpha = 256 - alpha; @@ -197,8 +193,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } *dest = fg; @@ -232,8 +227,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } uint32_t alpha = max(min(frac >> (16 - start_fade), 256), 0); @@ -257,8 +251,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } *dest = fg; @@ -276,8 +269,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } uint32_t alpha = max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0); diff --git a/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h b/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h index 739bf298e..487ac924c 100644 --- a/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h +++ b/src/rendering/swrenderer/drawers/r_draw_sky32_sse2.h @@ -64,8 +64,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; - if (*dest == 0) - *dest |= 0xff000000; + *dest |= 0xff000000; dest += pitch; frac += fracstep; } @@ -92,8 +91,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; __m128i alpha = _mm_set1_epi16(max(min(frac >> (16 - start_fade), 256), 0)); __m128i inv_alpha = _mm_sub_epi16(_mm_set1_epi16(256), alpha); @@ -112,8 +110,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; *dest = source0[sample_index]; - if (*dest == 0) - *dest |= 0xff000000; + *dest |= 0xff000000; frac += fracstep; dest += pitch; @@ -125,8 +122,7 @@ namespace swrenderer { uint32_t sample_index = (((((uint32_t)frac) << 8) >> FRACBITS) * textureheight0) >> FRACBITS; uint32_t fg = source0[sample_index]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; __m128i alpha = _mm_set1_epi16(max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0)); __m128i inv_alpha = _mm_sub_epi16(_mm_set1_epi16(256), alpha); @@ -181,8 +177,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } *dest = fg; @@ -228,8 +223,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } __m128i alpha = _mm_set1_epi16(max(min(frac >> (16 - start_fade), 256), 0)); @@ -253,8 +247,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } *dest = fg; @@ -272,8 +265,7 @@ namespace swrenderer { uint32_t sample_index2 = min(sample_index, maxtextureheight1); fg = source1[sample_index2]; - if (fg == 0) - fg |= 0xff000000; + fg |= 0xff000000; } __m128i alpha = _mm_set1_epi16(max(min(((2 << 24) - frac) >> (16 - start_fade), 256), 0)); From 17d7006d72069942a9a05168d9d1efe48c53e369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Tue, 10 Sep 2024 23:10:08 -0300 Subject: [PATCH 06/17] replace naive interpolation logic with more exact logic --- src/playsim/actor.h | 4 ++-- src/playsim/p_actionfunctions.cpp | 12 ++++++++--- src/r_data/models.cpp | 33 +++++++++++++++++-------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 5aa23b573..37e9eb645 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -702,8 +702,8 @@ enum EViewPosFlags // [MC] Flags for SetViewPos. enum EAnimOverrideFlags { - ANIMOVERRIDE_NONE = 1 << 0, // no animation - ANIMOVERRIDE_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends + ANIMOVERRIDE_NONE = 1 << 0, // no animation + ANIMOVERRIDE_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends }; struct AnimOverride diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 4a647af4e..1612c7516 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -5128,7 +5128,7 @@ enum ESetAnimationFlags SAF_NOOVERRIDE = 1 << 2, }; -extern double getCurrentFrame(const AnimOverride &anim, double tic); +extern double getCurrentFrame(const AnimOverride &anim, double tic, bool *looped); void SetAnimationInternal(AActor * self, FName animName, double framerate, int startFrame, int loopFrame, int endFrame, int interpolateTics, int flags, double ticFrac) { @@ -5218,7 +5218,13 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s if(!(flags & SAF_INSTANT)) { - self->modelData->prevAnim.startFrame = getCurrentFrame(self->modelData->prevAnim, tic); + bool looped = false; + self->modelData->prevAnim.startFrame = getCurrentFrame(self->modelData->prevAnim, tic, &looped); + + if(!looped) + { + self->modelData->prevAnim.flags &= ~ANIMOVERRIDE_LOOP; + } int startTic = floor(tic) + interpolateTics; self->modelData->curAnim.startTic = startTic; @@ -5272,7 +5278,7 @@ void SetAnimationFrameRateInternal(AActor * self, double framerate, double ticFr return; } - double frame = getCurrentFrame(self->modelData->curAnim, tic); + double frame = getCurrentFrame(self->modelData->curAnim, tic, nullptr); self->modelData->curAnim.startFrame = frame; self->modelData->curAnim.startTic = tic; diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index 866e538c3..3c2ed56d7 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -258,7 +258,7 @@ void RenderHUDModel(FModelRenderer *renderer, DPSprite *psp, FVector3 translatio renderer->EndDrawHUDModel(playermo->RenderStyle, smf_flags); } -double getCurrentFrame(const AnimOverride &anim, double tic) +double getCurrentFrame(const AnimOverride &anim, double tic, bool *looped) { if(anim.framerate <= 0) return anim.startFrame; @@ -268,6 +268,7 @@ double getCurrentFrame(const AnimOverride &anim, double tic) if((anim.flags & ANIMOVERRIDE_LOOP) && frame >= duration) { + if(looped) *looped = true; frame = frame - duration; return fmod(frame, anim.lastFrame - anim.loopFrame) + anim.loopFrame; } @@ -277,15 +278,26 @@ double getCurrentFrame(const AnimOverride &anim, double tic) } } -static void calcFrame(const AnimOverride &anim, double tic, double &inter, int &prev, int &next) +static void fixFrame(const AnimOverride &anim, double frame, double &inter, int &prev, int &next, bool looped) { - double frame = getCurrentFrame(anim, tic); - prev = int(floor(frame)); + int startFrame = (looped ? anim.startFrame : anim.loopFrame); + + if(prev < startFrame) prev = anim.lastFrame; + inter = frame - prev; next = int(ceil(frame)); + + if(next > anim.lastFrame) next = startFrame; +} + +static void calcFrame(const AnimOverride &anim, double tic, double &inter, int &prev, int &next) +{ + bool looped = false; + double frame = getCurrentFrame(anim, tic, &looped); + fixFrame(anim, frame, inter, prev, next, looped); } void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, FTranslationID translation, AActor* actor) @@ -325,17 +337,8 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr { inter = (tic - (actor->modelData->curAnim.startTic - actor->modelData->curAnim.switchOffset)) / actor->modelData->curAnim.switchOffset; - double nextFrame = actor->modelData->curAnim.startFrame; - - double prevFrame = actor->modelData->prevAnim.startFrame; - - decoupled_next_prev_frame = floor(nextFrame); - decoupled_next_frame = ceil(nextFrame); - inter_next = nextFrame - floor(nextFrame); - - decoupled_main_prev_frame = floor(prevFrame); - decoupled_main_frame = ceil(prevFrame); - inter_main = prevFrame - floor(prevFrame); + fixFrame(actor->modelData->curAnim, actor->modelData->curAnim.startFrame, inter_next, decoupled_next_prev_frame, decoupled_next_frame, false); + fixFrame(actor->modelData->prevAnim, actor->modelData->prevAnim.startFrame, inter_main, decoupled_main_prev_frame, decoupled_main_frame, actor->modelData->prevAnim.flags & ANIMOVERRIDE_LOOP); } else { From b1318e4b22c15eb08f402e0a55b6bf4046e75e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Fri, 13 Sep 2024 12:12:15 -0300 Subject: [PATCH 07/17] quick fix for interpolation, TODO better fix --- src/playsim/p_actionfunctions.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 1612c7516..bfed28fcb 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -5176,9 +5176,23 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s return; } + + if(self->modelData->curAnim.startTic > tic) + { // force instant switch if interpolating + flags |= SAF_INSTANT; + } + if(!(flags & SAF_INSTANT)) { self->modelData->prevAnim = self->modelData->curAnim; + + bool looped = false; + self->modelData->prevAnim.startFrame = getCurrentFrame(self->modelData->prevAnim, tic, &looped); + + if(!looped) + { + self->modelData->prevAnim.flags &= ~ANIMOVERRIDE_LOOP; + } } int animEnd = mdl->FindLastFrame(animName); @@ -5218,14 +5232,6 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s if(!(flags & SAF_INSTANT)) { - bool looped = false; - self->modelData->prevAnim.startFrame = getCurrentFrame(self->modelData->prevAnim, tic, &looped); - - if(!looped) - { - self->modelData->prevAnim.flags &= ~ANIMOVERRIDE_LOOP; - } - int startTic = floor(tic) + interpolateTics; self->modelData->curAnim.startTic = startTic; self->modelData->curAnim.switchOffset = startTic - tic; From 1429e224411ec139adfa2906bb0f0ec939f2ce64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Fri, 13 Sep 2024 15:09:57 -0300 Subject: [PATCH 08/17] rework interpolation to allow for precalculated frames --- src/common/engine/serializer.h | 4 +- src/common/models/bonecomponents.h | 36 ++++++++++++++ src/common/models/model.h | 5 +- src/common/models/model_iqm.h | 3 +- src/common/models/models_iqm.cpp | 24 +++++++++- src/playsim/actor.h | 22 +-------- src/playsim/p_actionfunctions.cpp | 36 +++++++------- src/playsim/p_mobj.cpp | 64 +++++++++++++++++++++++-- src/r_data/models.cpp | 76 ++++++++++++++---------------- 9 files changed, 186 insertions(+), 84 deletions(-) diff --git a/src/common/engine/serializer.h b/src/common/engine/serializer.h index ae25853d9..99ffb72dc 100644 --- a/src/common/engine/serializer.h +++ b/src/common/engine/serializer.h @@ -9,6 +9,7 @@ #include "palentry.h" #include "name.h" #include "dictionary.h" +#include "bonecomponents.h" extern bool save_full; @@ -247,7 +248,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def); FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &mo, struct ModelOverride *def); FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimModelOverride &mo, struct AnimModelOverride *def); -FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &ao, struct AnimOverride *def); +FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnim &ao, ModelAnim *def); +FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnimFrame &ao, ModelAnimFrame *def); FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval); void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p); diff --git a/src/common/models/bonecomponents.h b/src/common/models/bonecomponents.h index 1e65389ea..556b28769 100644 --- a/src/common/models/bonecomponents.h +++ b/src/common/models/bonecomponents.h @@ -4,6 +4,8 @@ #include "TRS.h" #include "matrix.h" +#include + class DBoneComponents : public DObject { @@ -14,3 +16,37 @@ public: DBoneComponents() = default; }; + +struct ModelAnimFrameInterp +{ + float inter = -1.0f; + int frame1 = -1; + int frame2 = -1; +}; + +struct ModelAnimFramePrecalculatedIQM +{ + TArray precalcBones; +}; + +enum EModelAnimFlags +{ + MODELANIM_NONE = 1 << 0, // no animation + MODELANIM_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends +}; + +struct ModelAnim +{ + int firstFrame = 0; + int lastFrame = 0; + int loopFrame = 0; + float framerate = 0; + double startFrame = 0; + int flags = MODELANIM_NONE; + double startTic = 0; // when the current animation started (changing framerates counts as restarting) (or when animation starts if interpolating from previous animation) + double switchOffset = 0; // when the animation was changed -- where to interpolate the switch from +}; + +static_assert(sizeof(ModelAnim) == sizeof(double) * 6); + +using ModelAnimFrame = std::variant; diff --git a/src/common/models/model.h b/src/common/models/model.h index 7d5746e33..f25a3df1a 100644 --- a/src/common/models/model.h +++ b/src/common/models/model.h @@ -9,6 +9,8 @@ #include "tarray.h" #include "name.h" +#include "bonecomponents.h" + class DBoneComponents; class FModelRenderer; class FGameTexture; @@ -94,7 +96,8 @@ public: virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) = 0; virtual float getAspectFactor(float vscale) { return 1.f; } virtual const TArray* AttachAnimationData() { return nullptr; }; - virtual const TArray CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* bones, int index) { return {}; }; + + virtual const TArray CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) { return {}; }; void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; } IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; } diff --git a/src/common/models/model_iqm.h b/src/common/models/model_iqm.h index 464f5c13a..c0c4916eb 100644 --- a/src/common/models/model_iqm.h +++ b/src/common/models/model_iqm.h @@ -120,7 +120,8 @@ public: void BuildVertexBuffer(FModelRenderer* renderer) override; void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override; const TArray* AttachAnimationData() override; - const TArray CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* bones, int index) override; + const TArray CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* bones, int index); + const TArray CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) override; private: void LoadGeometry(); diff --git a/src/common/models/models_iqm.cpp b/src/common/models/models_iqm.cpp index 891a19647..047d3226d 100644 --- a/src/common/models/models_iqm.cpp +++ b/src/common/models/models_iqm.cpp @@ -560,7 +560,29 @@ static TRS InterpolateBone(const TRS &from, const TRS &to, float t, float invt) return bone; } -const TArray IQMModel::CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* boneComponentData, int index) +#include "printf.h" + +const TArray IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) +{ + if(inter <= 0 || !(std::holds_alternative(from) || std::holds_alternative(from))) + { + return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, animationData, bones, index); + } + else if(std::holds_alternative(from)) + { + auto &from_interp = std::get(from); + + Printf("CalculateBones({%d, %d, %f}, {%d, %d, %f}, %f)\n", from_interp.frame1, from_interp.frame2, from_interp.inter, to.frame1, to.frame2, to.inter, inter); + + return CalculateBonesIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, animationData, bones, index); + } + else if(std::holds_alternative(from)) + { //TODO + return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, animationData, bones, index); + } +} + +const TArray IQMModel::CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* boneComponentData, int index) { const TArray& animationFrames = animationData ? *animationData : TRSData; if (Joints.Size() > 0) diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 37e9eb645..bbf2eaf98 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -700,24 +700,6 @@ enum EViewPosFlags // [MC] Flags for SetViewPos. VPSF_ORTHOGRAPHIC = 1 << 4, // Use orthographic projection (hardware renderer only). }; -enum EAnimOverrideFlags -{ - ANIMOVERRIDE_NONE = 1 << 0, // no animation - ANIMOVERRIDE_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends -}; - -struct AnimOverride -{ - int firstFrame; - int lastFrame; - int loopFrame; - double startFrame; - int flags = ANIMOVERRIDE_NONE; - float framerate; - double startTic; // when the current animation started (changing framerates counts as restarting) (or when animation starts if interpolating from previous animation) - double switchOffset; // when the animation was changed -- where to interpolate the switch from -}; - struct ModelOverride { int modelID; @@ -753,8 +735,8 @@ public: int overrideFlagsSet; int overrideFlagsClear; - AnimOverride curAnim; - AnimOverride prevAnim; // used for interpolation when switching anims + ModelAnim curAnim; + ModelAnimFrame prevAnim; // used for interpolation when switching anims DActorModelData() = default; virtual void Serialize(FSerializer& arc) override; diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index bfed28fcb..1b780a194 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -5128,7 +5128,8 @@ enum ESetAnimationFlags SAF_NOOVERRIDE = 1 << 2, }; -extern double getCurrentFrame(const AnimOverride &anim, double tic, bool *looped); +double getCurrentFrame(const ModelAnim &anim, double tic, bool *looped); +void calcFrame(const ModelAnim &anim, double tic, ModelAnimFrameInterp &inter); void SetAnimationInternal(AActor * self, FName animName, double framerate, int startFrame, int loopFrame, int endFrame, int interpolateTics, int flags, double ticFrac) { @@ -5150,7 +5151,7 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s if(animName == NAME_None) { - self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + self->modelData->curAnim.flags = MODELANIM_NONE; return; } @@ -5165,12 +5166,12 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s int animStart = mdl->FindFirstFrame(animName); if(animStart == FErr_NotFound) { - self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + self->modelData->curAnim.flags = MODELANIM_NONE; Printf("Could not find animation %s\n", animName.GetChars()); return; } - if((flags & SAF_NOOVERRIDE) && self->modelData->curAnim.flags != ANIMOVERRIDE_NONE && self->modelData->curAnim.firstFrame == animStart) + if((flags & SAF_NOOVERRIDE) && self->modelData->curAnim.flags != MODELANIM_NONE && self->modelData->curAnim.firstFrame == animStart) { //same animation as current, skip setting it return; @@ -5184,14 +5185,15 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s if(!(flags & SAF_INSTANT)) { - self->modelData->prevAnim = self->modelData->curAnim; - - bool looped = false; - self->modelData->prevAnim.startFrame = getCurrentFrame(self->modelData->prevAnim, tic, &looped); - - if(!looped) + if(self->modelData->curAnim.startTic > tic) { - self->modelData->prevAnim.flags &= ~ANIMOVERRIDE_LOOP; + //TODO + } + else + { + self->modelData->prevAnim = ModelAnimFrameInterp{}; + + calcFrame(self->modelData->curAnim, tic, std::get(self->modelData->prevAnim)); } } @@ -5206,19 +5208,19 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s if(startFrame >= len) { - self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + self->modelData->curAnim.flags = MODELANIM_NONE; Printf("frame %d (startFrame) is past the end of animation %s\n", startFrame, animName.GetChars()); return; } else if(loopFrame >= len) { - self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + self->modelData->curAnim.flags = MODELANIM_NONE; Printf("frame %d (loopFrame) is past the end of animation %s\n", startFrame, animName.GetChars()); return; } else if(endFrame >= len) { - self->modelData->curAnim.flags = ANIMOVERRIDE_NONE; + self->modelData->curAnim.flags = MODELANIM_NONE; Printf("frame %d (endFrame) is past the end of animation %s\n", endFrame, animName.GetChars()); return; } @@ -5227,12 +5229,12 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s self->modelData->curAnim.lastFrame = endFrame < 0 ? animEnd - 1 : animStart + endFrame; self->modelData->curAnim.startFrame = startFrame < 0 ? animStart : animStart + startFrame; self->modelData->curAnim.loopFrame = loopFrame < 0 ? animStart : animStart + loopFrame; - self->modelData->curAnim.flags = (flags&SAF_LOOP) ? ANIMOVERRIDE_LOOP : 0; + self->modelData->curAnim.flags = (flags & SAF_LOOP) ? MODELANIM_LOOP : 0; self->modelData->curAnim.framerate = (float)framerate; if(!(flags & SAF_INSTANT)) { - int startTic = floor(tic) + interpolateTics; + int startTic = int(floor(tic)) + interpolateTics; self->modelData->curAnim.startTic = startTic; self->modelData->curAnim.switchOffset = startTic - tic; } @@ -5264,7 +5266,7 @@ void SetAnimationFrameRateInternal(AActor * self, double framerate, double ticFr EnsureModelData(self); - if(self->modelData->curAnim.flags & ANIMOVERRIDE_NONE) return; + if(self->modelData->curAnim.flags & MODELANIM_NONE) return; if(framerate < 0) { diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 442f101f5..e8a6c247a 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -1443,7 +1443,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, AnimModelOverride &amo return arc; } -FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &ao, struct AnimOverride *def) +FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelAnim &ao, struct ModelAnim *def) { arc.BeginObject(key); arc("firstFrame", ao.firstFrame); @@ -1458,6 +1458,64 @@ FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &a return arc; } +FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnimFrame &ao, ModelAnimFrame *def) +{ + arc.BeginObject(key); + if(arc.isReading()) + { + if(arc.HasKey("firstFrame")) + { // legacy save, clear interpolation + ao = nullptr; + } + else + { + FString type = "nullptr"; + arc("type", type); + if(type.Compare("nullptr") == 0) + { + ao = nullptr; + } + else if(type.Compare("interp") == 0) + { + ModelAnimFrameInterp tmp; + arc("inter", tmp.inter); + arc("frame1", tmp.frame1); + arc("frame2", tmp.frame2); + ao = tmp; + } + else if(type.Compare("precalcIQM") == 0) + { + //TODO, unreachable + ao = nullptr; + } + } + } + else // if(arc.isWriting()) + { + if(std::holds_alternative(ao)) + { + FString tmp = "nullptr"; + arc("type", tmp); + } + else if(std::holds_alternative(ao)) + { + FString type = "interp"; + arc("type", type); + arc("inter", std::get(ao).inter); + arc("frame1", std::get(ao).frame1); + arc("frame2", std::get(ao).frame2); + } + else if(std::holds_alternative(ao)) + { + //TODO + FString type = "nullptr"; + arc("type", type); + } + } + arc.EndObject(); + return arc; +} + void DActorModelData::Serialize(FSerializer& arc) { Super::Serialize(arc); @@ -3862,7 +3920,7 @@ void AActor::Tick () special2++; } - if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & ANIMOVERRIDE_NONE)) + if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & MODELANIM_NONE)) { modelData->curAnim.startTic += 1; } @@ -3915,7 +3973,7 @@ void AActor::Tick () special2++; } - if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & ANIMOVERRIDE_NONE)) + if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & MODELANIM_NONE)) { modelData->curAnim.startTic += 1; } diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index 3c2ed56d7..9a710cd9a 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -258,7 +258,7 @@ void RenderHUDModel(FModelRenderer *renderer, DPSprite *psp, FVector3 translatio renderer->EndDrawHUDModel(playermo->RenderStyle, smf_flags); } -double getCurrentFrame(const AnimOverride &anim, double tic, bool *looped) +double getCurrentFrame(const ModelAnim &anim, double tic, bool *looped) { if(anim.framerate <= 0) return anim.startFrame; @@ -266,7 +266,7 @@ double getCurrentFrame(const AnimOverride &anim, double tic, bool *looped) double duration = double(anim.lastFrame) - anim.startFrame; - if((anim.flags & ANIMOVERRIDE_LOOP) && frame >= duration) + if((anim.flags & MODELANIM_LOOP) && frame >= duration) { if(looped) *looped = true; frame = frame - duration; @@ -278,26 +278,37 @@ double getCurrentFrame(const AnimOverride &anim, double tic, bool *looped) } } -static void fixFrame(const AnimOverride &anim, double frame, double &inter, int &prev, int &next, bool looped) +void calcFrame(const ModelAnim &anim, double tic, ModelAnimFrameInterp &inter) { - prev = int(floor(frame)); + bool looped = false; + + double frame = getCurrentFrame(anim, tic, &looped); + + inter.frame1 = int(floor(frame)); + + inter.inter = frame - inter.frame1; + + inter.frame2 = int(ceil(frame)); int startFrame = (looped ? anim.startFrame : anim.loopFrame); - if(prev < startFrame) prev = anim.lastFrame; - - inter = frame - prev; - - next = int(ceil(frame)); - - if(next > anim.lastFrame) next = startFrame; + if(inter.frame1 < startFrame) inter.frame1 = anim.lastFrame; + if(inter.frame2 > anim.lastFrame) inter.frame2 = startFrame; } -static void calcFrame(const AnimOverride &anim, double tic, double &inter, int &prev, int &next) +void calcFrames(const ModelAnim &curAnim, double tic, ModelAnimFrameInterp &to, float &inter) { - bool looped = false; - double frame = getCurrentFrame(anim, tic, &looped); - fixFrame(anim, frame, inter, prev, next, looped); + if(curAnim.startTic > tic) + { + inter = (tic - (curAnim.startTic - curAnim.switchOffset)) / curAnim.switchOffset; + + calcFrame(curAnim, curAnim.startTic, to); + } + else + { + inter = -1.0f; + calcFrame(curAnim, tic, to); + } } void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, FTranslationID translation, AActor* actor) @@ -308,17 +319,11 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr int smf_flags = smf->getFlags(actor->modelData); const FSpriteModelFrame * smfNext = nullptr; - double inter = 0.; - double inter_main = -1.f; - double inter_next = -1.f; + float inter = 0.; bool is_decoupled = (actor->flags9 & MF9_DECOUPLEDANIMATIONS); - int decoupled_main_prev_frame = -1; - int decoupled_next_prev_frame = -1; - - int decoupled_main_frame = -1; - int decoupled_next_frame = -1; + ModelAnimFrameInterp decoupled_frame; // if prev_frame == -1: interpolate(main_frame, next_frame, inter), else: interpolate(interpolate(main_prev_frame, main_frame, inter_main), interpolate(next_prev_frame, next_frame, inter_next), inter) // 4-way interpolation is needed to interpolate animation switches between animations that aren't 35hz @@ -326,24 +331,15 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr if(is_decoupled) { smfNext = smf = &BaseSpriteModelFrames[actor->GetClass()]; - if(actor->modelData && !(actor->modelData->curAnim.flags & ANIMOVERRIDE_NONE)) + if(actor->modelData && !(actor->modelData->curAnim.flags & MODELANIM_NONE)) { double tic = actor->Level->totaltime; if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !actor->isFrozen()) { tic += I_GetTimeFrac(); } - if(actor->modelData->curAnim.startTic > tic) - { - inter = (tic - (actor->modelData->curAnim.startTic - actor->modelData->curAnim.switchOffset)) / actor->modelData->curAnim.switchOffset; - fixFrame(actor->modelData->curAnim, actor->modelData->curAnim.startFrame, inter_next, decoupled_next_prev_frame, decoupled_next_frame, false); - fixFrame(actor->modelData->prevAnim, actor->modelData->prevAnim.startFrame, inter_main, decoupled_main_prev_frame, decoupled_main_frame, actor->modelData->prevAnim.flags & ANIMOVERRIDE_LOOP); - } - else - { - calcFrame(actor->modelData->curAnim, tic, inter, decoupled_main_frame, decoupled_next_frame); - } + calcFrames(actor->modelData->curAnim, tic, decoupled_frame, inter); } } else if (gl_interpolate_model_frames && !(smf_flags & MDL_NOINTERPOLATION)) @@ -543,14 +539,14 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr if(is_decoupled) { - if(decoupled_main_frame != -1) + if(decoupled_frame.frame1 != -1) { - boneData = animation->CalculateBones(decoupled_main_frame, decoupled_next_frame, inter, decoupled_main_prev_frame, inter_main, decoupled_next_prev_frame, inter_next, animationData, actor->boneComponentData, i); + boneData = animation->CalculateBones(actor->modelData->prevAnim, decoupled_frame, inter, animationData, actor->boneComponentData, i); } } else { - boneData = animation->CalculateBones(modelframe, modelframenext, nextFrame ? inter : -1.f, 0, -1.f, 0, -1.f, animationData, actor->boneComponentData, i); + boneData = animation->CalculateBones(nullptr, {nextFrame ? inter : -1.0f, modelframe, modelframenext}, -1.0f, animationData, actor->boneComponentData, i); } boneStartingPosition = renderer->SetupFrame(animation, 0, 0, 0, boneData, -1); evaluatedSingle = true; @@ -559,14 +555,14 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr { if(is_decoupled) { - if(decoupled_main_frame != -1) + if(decoupled_frame.frame1 != -1) { - boneData = mdl->CalculateBones(decoupled_main_frame, decoupled_next_frame, inter, decoupled_main_prev_frame, inter_main, decoupled_next_prev_frame, inter_next, nullptr, actor->boneComponentData, i); + boneData = mdl->CalculateBones(actor->modelData->prevAnim, decoupled_frame, inter, nullptr, actor->boneComponentData, i); } } else { - boneData = mdl->CalculateBones(modelframe, modelframenext, nextFrame ? inter : -1.f, 0, -1.f, 0, -1.f, nullptr, actor->boneComponentData, i); + boneData = mdl->CalculateBones(nullptr, {nextFrame ? inter : -1.0f, modelframe, modelframenext}, -1.0f, nullptr, actor->boneComponentData, i); } boneStartingPosition = renderer->SetupFrame(mdl, 0, 0, 0, boneData, -1); evaluatedSingle = true; From 79dacdf1b73e10147f6a2c629dbc13b410ee0f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Fri, 13 Sep 2024 16:56:07 -0300 Subject: [PATCH 09/17] Properly fix double interpolation for IQM models --- src/common/models/bonecomponents.h | 4 ++ src/common/models/model.h | 2 + src/common/models/model_iqm.h | 6 +- src/common/models/models_iqm.cpp | 108 ++++++++++++++++++++++++++--- src/playsim/p_actionfunctions.cpp | 43 +++++++++--- src/r_data/models.cpp | 39 ++++------- 6 files changed, 154 insertions(+), 48 deletions(-) diff --git a/src/common/models/bonecomponents.h b/src/common/models/bonecomponents.h index 556b28769..313b0832e 100644 --- a/src/common/models/bonecomponents.h +++ b/src/common/models/bonecomponents.h @@ -50,3 +50,7 @@ struct ModelAnim static_assert(sizeof(ModelAnim) == sizeof(double) * 6); using ModelAnimFrame = std::variant; + +double getCurrentFrame(const ModelAnim &anim, double tic, bool *looped); +void calcFrame(const ModelAnim &anim, double tic, ModelAnimFrameInterp &inter); +void calcFrames(const ModelAnim &curAnim, double tic, ModelAnimFrameInterp &to, float &inter); diff --git a/src/common/models/model.h b/src/common/models/model.h index f25a3df1a..2be1f54cc 100644 --- a/src/common/models/model.h +++ b/src/common/models/model.h @@ -97,6 +97,8 @@ public: virtual float getAspectFactor(float vscale) { return 1.f; } virtual const TArray* AttachAnimationData() { return nullptr; }; + virtual ModelAnimFrame PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) { return nullptr; }; + virtual const TArray CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) { return {}; }; void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; } diff --git a/src/common/models/model_iqm.h b/src/common/models/model_iqm.h index c0c4916eb..c16e8372a 100644 --- a/src/common/models/model_iqm.h +++ b/src/common/models/model_iqm.h @@ -120,9 +120,13 @@ public: void BuildVertexBuffer(FModelRenderer* renderer) override; void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override; const TArray* AttachAnimationData() override; - const TArray CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* bones, int index); + + ModelAnimFrame PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) override; const TArray CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) override; + ModelAnimFramePrecalculatedIQM CalculateFrameIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray* animationData, DBoneComponents* bones, int index); + const TArray CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray* animationData, DBoneComponents* bones, int index); + private: void LoadGeometry(); void UnloadGeometry(); diff --git a/src/common/models/models_iqm.cpp b/src/common/models/models_iqm.cpp index 047d3226d..1384debce 100644 --- a/src/common/models/models_iqm.cpp +++ b/src/common/models/models_iqm.cpp @@ -562,27 +562,106 @@ static TRS InterpolateBone(const TRS &from, const TRS &to, float t, float invt) #include "printf.h" -const TArray IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) + +ModelAnimFrame IQMModel::PrecalculateFrame(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) { - if(inter <= 0 || !(std::holds_alternative(from) || std::holds_alternative(from))) + if(inter <= 0) { - return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, animationData, bones, index); + return CalculateFrameIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index); } else if(std::holds_alternative(from)) { auto &from_interp = std::get(from); - Printf("CalculateBones({%d, %d, %f}, {%d, %d, %f}, %f)\n", from_interp.frame1, from_interp.frame2, from_interp.inter, to.frame1, to.frame2, to.inter, inter); - - return CalculateBonesIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, animationData, bones, index); + return CalculateFrameIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, nullptr, animationData, bones, index); } else if(std::holds_alternative(from)) - { //TODO - return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, animationData, bones, index); + { + return CalculateFrameIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get(from), animationData, bones, index); + } + else + { + return CalculateFrameIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index); } } -const TArray IQMModel::CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray* animationData, DBoneComponents* boneComponentData, int index) +const TArray IQMModel::CalculateBones(const ModelAnimFrame &from, const ModelAnimFrameInterp &to, float inter, const TArray* animationData, DBoneComponents* bones, int index) +{ + if(inter <= 0) + { + return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index); + } + else if(std::holds_alternative(from)) + { + auto &from_interp = std::get(from); + + return CalculateBonesIQM(from_interp.frame2, to.frame2, inter, from_interp.frame1, from_interp.inter, to.frame1, to.inter, nullptr, animationData, bones, index); + } + else if(std::holds_alternative(from)) + { + return CalculateBonesIQM(0, to.frame2, inter, 0, -1.f, to.frame1, to.inter, &std::get(from), animationData, bones, index); + } + else + { + return CalculateBonesIQM(to.frame1, to.frame2, to.inter, 0, -1.f, 0, -1.f, nullptr, animationData, bones, index); + } +} + +ModelAnimFramePrecalculatedIQM IQMModel::CalculateFrameIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray* animationData, DBoneComponents* boneComponentData, int index) +{ + ModelAnimFramePrecalculatedIQM out; + const TArray& animationFrames = animationData ? *animationData : TRSData; + + out.precalcBones.Resize(Joints.Size()); + + if (Joints.Size() > 0) + { + int numbones = Joints.SSize(); + + int offset1 = frame1 * numbones; + int offset2 = frame2 * numbones; + + int offset1_1 = frame1_prev * numbones; + int offset2_1 = frame2_prev * numbones; + + float invt = 1.0f - inter; + float invt1 = 1.0f - inter1_prev; + float invt2 = 1.0f - inter2_prev; + + for (int i = 0; i < numbones; i++) + { + TRS prev; + + if(precalculated) + { + prev = precalculated->precalcBones[i]; + } + else + { + if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0)) + { + prev = inter1_prev <= 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1); + } + } + + TRS next; + + if(frame2 >= 0 && (frame2_prev >= 0 || inter2_prev < 0)) + { + next = inter2_prev <= 0 ? animationFrames[offset2 + i] : InterpolateBone(animationFrames[offset2_1 + i], animationFrames[offset2 + i], inter2_prev, invt2); + } + + if(frame1 >= 0 || inter < 0) + { + out.precalcBones[i] = inter < 0 ? animationFrames[offset1 + i] : InterpolateBone(prev, next , inter, invt); + } + } + } + + return out; +} + +const TArray IQMModel::CalculateBonesIQM(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const ModelAnimFramePrecalculatedIQM* precalculated, const TArray* animationData, DBoneComponents* boneComponentData, int index) { const TArray& animationFrames = animationData ? *animationData : TRSData; if (Joints.Size() > 0) @@ -619,9 +698,16 @@ const TArray IQMModel::CalculateBonesIQM(int frame1, int frame2, float { TRS prev; - if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0)) + if(precalculated) { - prev = inter1_prev <= 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1); + prev = precalculated->precalcBones[i]; + } + else + { + if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0)) + { + prev = inter1_prev <= 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1); + } } TRS next; diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 1b780a194..ea7090c1d 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -5128,9 +5128,6 @@ enum ESetAnimationFlags SAF_NOOVERRIDE = 1 << 2, }; -double getCurrentFrame(const ModelAnim &anim, double tic, bool *looped); -void calcFrame(const ModelAnim &anim, double tic, ModelAnimFrameInterp &inter); - void SetAnimationInternal(AActor * self, FName animName, double framerate, int startFrame, int loopFrame, int endFrame, int interpolateTics, int flags, double ticFrac) { if(!self) ThrowAbortException(X_READ_NIL, "In function parameter self"); @@ -5177,17 +5174,39 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s return; } - - if(self->modelData->curAnim.startTic > tic) - { // force instant switch if interpolating - flags |= SAF_INSTANT; - } - if(!(flags & SAF_INSTANT)) { if(self->modelData->curAnim.startTic > tic) { - //TODO + ModelAnimFrameInterp to; + float inter; + + calcFrames(self->modelData->curAnim, tic, to, inter); + + const TArray* animationData = nullptr; + + int animationid = -1; + + const FSpriteModelFrame * smf = &BaseSpriteModelFrames[self->GetClass()]; + + if (self->modelData->animationIDs.Size() > 0 && self->modelData->animationIDs[0] >= 0) + { + animationid = self->modelData->animationIDs[0]; + } + else if(smf->modelsAmount > 0) + { + animationid = smf->animationIDs[0]; + } + + FModel* animation = mdl; + + if (animationid >= 0) + { + animation = Models[animationid]; + animationData = animation->AttachAnimationData(); + } + + self->modelData->prevAnim = animation->PrecalculateFrame(self->modelData->prevAnim, to, inter, animationData, self->boneComponentData, 0); } else { @@ -5196,6 +5215,10 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s calcFrame(self->modelData->curAnim, tic, std::get(self->modelData->prevAnim)); } } + else + { + self->modelData->prevAnim = nullptr; + } int animEnd = mdl->FindLastFrame(animName); diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index 9a710cd9a..41166a0e8 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -530,43 +530,30 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr } // [RL0] while per-model animations aren't done, DECOUPLEDANIMATIONS does the same as MODELSAREATTACHMENTS - if ((!(smf_flags & MDL_MODELSAREATTACHMENTS) && !is_decoupled) || !evaluatedSingle) + if(!evaluatedSingle) { + FModel* animation = mdl; + const TArray* animationData = nullptr; + if (animationid >= 0) { - FModel* animation = Models[animationid]; + animation = Models[animationid]; const TArray* animationData = animation->AttachAnimationData(); + } - if(is_decoupled) + if(is_decoupled) + { + if(decoupled_frame.frame1 != -1) { - if(decoupled_frame.frame1 != -1) - { - boneData = animation->CalculateBones(actor->modelData->prevAnim, decoupled_frame, inter, animationData, actor->boneComponentData, i); - } + boneData = animation->CalculateBones(actor->modelData->prevAnim, decoupled_frame, inter, animationData, actor->boneComponentData, i); } - else - { - boneData = animation->CalculateBones(nullptr, {nextFrame ? inter : -1.0f, modelframe, modelframenext}, -1.0f, animationData, actor->boneComponentData, i); - } - boneStartingPosition = renderer->SetupFrame(animation, 0, 0, 0, boneData, -1); - evaluatedSingle = true; } else { - if(is_decoupled) - { - if(decoupled_frame.frame1 != -1) - { - boneData = mdl->CalculateBones(actor->modelData->prevAnim, decoupled_frame, inter, nullptr, actor->boneComponentData, i); - } - } - else - { - boneData = mdl->CalculateBones(nullptr, {nextFrame ? inter : -1.0f, modelframe, modelframenext}, -1.0f, nullptr, actor->boneComponentData, i); - } - boneStartingPosition = renderer->SetupFrame(mdl, 0, 0, 0, boneData, -1); - evaluatedSingle = true; + boneData = animation->CalculateBones(nullptr, {nextFrame ? inter : -1.0f, modelframe, modelframenext}, -1.0f, animationData, actor->boneComponentData, i); } + boneStartingPosition = renderer->SetupFrame(animation, 0, 0, 0, boneData, -1); + evaluatedSingle = (smf_flags & MDL_MODELSAREATTACHMENTS) || is_decoupled; } mdl->RenderFrame(renderer, tex, modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : -1.f, translation, ssidp, boneData, boneStartingPosition); From b79deabab1b2e3ab38c9699be3ff35412b839346 Mon Sep 17 00:00:00 2001 From: "Dileep V. Reddy" Date: Mon, 16 Sep 2024 09:43:15 -0600 Subject: [PATCH 10/17] Prevent console from laying open when intro cutscene plays on some systems. --- src/g_game.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/g_game.cpp b/src/g_game.cpp index 105b16aa7..e80a5cfd2 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1236,6 +1236,7 @@ void G_Ticker () case ga_intro: gamestate = GS_INTRO; gameaction = ga_nothing; + C_HideConsole(); // On some systems, console is open during intro break; From faead1c7339a9fb20dd18e26608ddb05ff8855ae Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Sat, 10 Aug 2024 20:27:12 -0700 Subject: [PATCH 11/17] Fix bug where down arrow does nothing after clicking the main menu This issue was previously fixed in 0c2ed71cdd2676af6aecf6e26b0ab1a692e66d71, but that change added a bug that would trigger an infinite loop on scrolling in a list without any selectable items. Then 4fdbe81a132c472532985d31143c0dc26605283a restored the old behavior, reintroducing the issue. This new fix does handle lists without any selectable items correctly. --- wadsrc/static/zscript/engine/ui/menu/listmenu.zs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wadsrc/static/zscript/engine/ui/menu/listmenu.zs b/wadsrc/static/zscript/engine/ui/menu/listmenu.zs index ed3c2ef16..2489e41a4 100644 --- a/wadsrc/static/zscript/engine/ui/menu/listmenu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/listmenu.zs @@ -188,11 +188,12 @@ class ListMenu : Menu override bool MenuEvent (int mkey, bool fromcontroller) { int oldSelect = mDesc.mSelectedItem; - int startedAt = max(0, mDesc.mSelectedItem); + int startedAt; switch (mkey) { case MKEY_Up: + startedAt = mDesc.mSelectedItem < 0 ? 0 : mDesc.mSelectedItem; do { if (--mDesc.mSelectedItem < 0) mDesc.mSelectedItem = mDesc.mItems.Size()-1; @@ -203,6 +204,7 @@ class ListMenu : Menu return true; case MKEY_Down: + startedAt = mDesc.mSelectedItem < 0 ? mDesc.mItems.Size()-1 : mDesc.mSelectedItem; do { if (++mDesc.mSelectedItem >= mDesc.mItems.Size()) mDesc.mSelectedItem = 0; From c8fbe43dc0fbadf8c00ed4d92d27f6dad9474a1e Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Sat, 10 Aug 2024 21:13:21 -0700 Subject: [PATCH 12/17] Fix load/save menus not starting scrolled to the selected entry Fixes https://github.com/ZDoom/gzdoom/issues/2645 --- wadsrc/static/zscript/engine/ui/menu/loadsavemenu.zs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wadsrc/static/zscript/engine/ui/menu/loadsavemenu.zs b/wadsrc/static/zscript/engine/ui/menu/loadsavemenu.zs index e15ca266f..82b5ea4bf 100644 --- a/wadsrc/static/zscript/engine/ui/menu/loadsavemenu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/loadsavemenu.zs @@ -483,8 +483,8 @@ class SaveMenu : LoadSaveMenu { Super.Init(parent, desc); manager.InsertNewSaveNode(); - TopItem = 0; Selected = manager.ExtractSaveData (-1); + TopItem = MAX(0, Selected - listboxRows + 1); UpdateSaveComment(); } @@ -623,8 +623,8 @@ class LoadMenu : LoadSaveMenu override void Init(Menu parent, ListMenuDescriptor desc) { Super.Init(parent, desc); - TopItem = 0; Selected = manager.ExtractSaveData (-1); + TopItem = MAX(0, Selected - listboxRows + 1); UpdateSaveComment(); } From 0c3ebf451fafd4df372185715f81deeaef6200cc Mon Sep 17 00:00:00 2001 From: nashmuhandes Date: Tue, 17 Sep 2024 05:44:01 +0800 Subject: [PATCH 13/17] Fix missing serialization for `skyspeed1` and `skyspeed2` --- src/p_saveg.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index e58124759..970cc9aa5 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -986,6 +986,8 @@ void FLevelLocals::Serialize(FSerializer &arc, bool hubload) ("flags2", flags2) ("flags3", flags3) ("fadeto", fadeto) + ("skyspeed1", skyspeed1) + ("skyspeed2", skyspeed2) ("found_secrets", found_secrets) ("found_items", found_items) ("killed_monsters", killed_monsters) From 712dd1133d18436ee7e9a5a4c3208e57ddfe0c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Tue, 17 Sep 2024 14:12:45 -0300 Subject: [PATCH 14/17] Fix typo --- src/r_data/models.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index 41166a0e8..780d420fb 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -290,7 +290,7 @@ void calcFrame(const ModelAnim &anim, double tic, ModelAnimFrameInterp &inter) inter.frame2 = int(ceil(frame)); - int startFrame = (looped ? anim.startFrame : anim.loopFrame); + int startFrame = (looped ? anim.loopFrame : anim.startFrame); if(inter.frame1 < startFrame) inter.frame1 = anim.lastFrame; if(inter.frame2 > anim.lastFrame) inter.frame2 = startFrame; From 5347cdf56919c6e15ea5ef01a88df4b61759104b Mon Sep 17 00:00:00 2001 From: Gene Date: Wed, 18 Sep 2024 16:16:24 -0700 Subject: [PATCH 15/17] Fix billboard facing camera only taking effect when used with XY billboarding --- src/rendering/hwrenderer/scene/hw_sprites.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index 05207f8c4..205ccb121 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -425,7 +425,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) float yy = -center.Y + y; float zz = -center.Z + z; // [Nash] check for special sprite drawing modes - if (drawWithXYBillboard || isWallSprite) + if (drawWithXYBillboard || drawBillboardFacingCamera || isWallSprite) { mat.MakeIdentity(); mat.Translate(center.X, center.Z, center.Y); // move to sprite center From de87493c41394afb45b648d702b2422aa8df3cd0 Mon Sep 17 00:00:00 2001 From: Gene Date: Wed, 18 Sep 2024 17:25:27 -0700 Subject: [PATCH 16/17] Fix incorrect boolean logic for `drawBillboardFacingCamera` --- src/rendering/hwrenderer/scene/hw_sprites.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index 205ccb121..aebe8a9b0 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -404,8 +404,8 @@ bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) const bool drawBillboardFacingCamera = hw_force_cambbpref ? gl_billboard_faces_camera : gl_billboard_faces_camera - && ((actor && (!(actor->renderflags2 & RF2_BILLBOARDNOFACECAMERA) || (actor->renderflags2 & RF2_BILLBOARDFACECAMERA))) - || (particle && particle->texture.isValid() && (!(particle->flags & SPF_NOFACECAMERA) || (particle->flags & SPF_FACECAMERA)))); + || ((actor && (!(actor->renderflags2 & RF2_BILLBOARDNOFACECAMERA) && (actor->renderflags2 & RF2_BILLBOARDFACECAMERA))) + || (particle && particle->texture.isValid() && (!(particle->flags & SPF_NOFACECAMERA) && (particle->flags & SPF_FACECAMERA)))); // [Nash] has +ROLLSPRITE const bool drawRollSpriteActor = (actor != nullptr && actor->renderflags & RF_ROLLSPRITE); From 3524d06813cd4841056901111bf649b023274255 Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Mon, 29 Jan 2024 16:55:59 -0500 Subject: [PATCH 17/17] - export `FFont::GetChar()` to ZScript --- src/common/scripting/interface/vmnatives.cpp | 20 ++++++++++++++++++++ wadsrc/static/zscript/engine/base.zs | 1 + 2 files changed, 21 insertions(+) diff --git a/src/common/scripting/interface/vmnatives.cpp b/src/common/scripting/interface/vmnatives.cpp index 730465a34..8a5674afd 100644 --- a/src/common/scripting/interface/vmnatives.cpp +++ b/src/common/scripting/interface/vmnatives.cpp @@ -770,6 +770,26 @@ DEFINE_ACTION_FUNCTION_NATIVE(FFont, GetDisplayTopOffset, GetDisplayTopOffset) ACTION_RETURN_FLOAT(GetDisplayTopOffset(self, code)); } +static int GetChar(FFont* font, int c) +{ + int texc = 0; + auto getch = font->GetChar(c, CR_UNDEFINED, nullptr); + if (getch) + texc = getch->GetID().GetIndex(); + return texc; +} + +DEFINE_ACTION_FUNCTION_NATIVE(FFont, GetChar, ::GetChar) +{ + PARAM_SELF_STRUCT_PROLOGUE(FFont); + PARAM_INT(mchar); + + if (numret > 0) ret[0].SetInt(::GetChar(self, mchar)); + if (numret > 1) ret[1].SetInt(self->GetCharWidth(mchar)); + return min(2, numret); +} + + //========================================================================== // // file system diff --git a/wadsrc/static/zscript/engine/base.zs b/wadsrc/static/zscript/engine/base.zs index f24b71d01..f55919116 100644 --- a/wadsrc/static/zscript/engine/base.zs +++ b/wadsrc/static/zscript/engine/base.zs @@ -657,6 +657,7 @@ struct Font native native BrokenLines BreakLines(String text, int maxlen); native int GetGlyphHeight(int code); native int GetDefaultKerning(); + native TextureID, int GetChar(int c); } struct Console native