mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-03 02:53:57 +00:00
Some checks failed
CI / clang-format (push) Has been cancelled
CI / ${{ matrix.name }} (false, --toolchain /usr/local/vitasdk/share/vita.toolchain.cmake, false, false, Ninja, Vita, ubuntu-latest, true, true) (push) Has been cancelled
CI / ${{ matrix.name }} (false, -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0, false, false, Visual Studio 17 2022, true, Xbox One, windows-latest, amd64, false, true) (push) Has been cancelled
CI / ${{ matrix.name }} (false, -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake, false, devkitpro/devkitarm:latest, false, Ninja, true, Nintendo 3DS, ubuntu-latest, true) (push) Has been cancelled
CI / ${{ matrix.name }} (false, -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake, false, devkitpro/devkita64:latest, false, Ninja, Nintendo Switch, true, ubuntu-latest, true) (push) Has been cancelled
CI / ${{ matrix.name }} (false, emcmake, false, false, true, Ninja, Emscripten, ubuntu-latest, true) (push) Has been cancelled
CI / ${{ matrix.name }} (false, false, false, Ninja, true, MSVC (arm64), windows-latest, amd64_arm64, false) (push) Has been cancelled
CI / ${{ matrix.name }} (false, false, true, Ninja, true, MSVC (x86), windows-latest, amd64_x86, false) (push) Has been cancelled
CI / ${{ matrix.name }} (false, true, false, Ninja, true, MSVC (x64), windows-latest, amd64, false) (push) Has been cancelled
CI / ${{ matrix.name }} (false, true, true, false, Ninja, true, MSVC (x64 Debug), windows-latest, amd64, false) (push) Has been cancelled
CI / ${{ matrix.name }} (true, false, -DCMAKE_SYSTEM_NAME=iOS, false, false, Xcode, true, iOS, macos-15, true) (push) Has been cancelled
CI / ${{ matrix.name }} (true, false, false, false, Ninja, Android, ubuntu-latest, true) (push) Has been cancelled
CI / ${{ matrix.name }} (true, false, true, false, Ninja, macOS, macos-latest, true) (push) Has been cancelled
CI / ${{ matrix.name }} (true, true, false, Ninja, true, mingw-w64-x86_64, mingw64, msys2 mingw64, windows-latest, msys2 {0}, true) (push) Has been cancelled
CI / ${{ matrix.name }} (true, true, true, false, Ninja, true, Linux (Debug), ubuntu-latest, true) (push) Has been cancelled
CI / ${{ matrix.name }} (true, true, true, false, Ninja, true, Linux, ubuntu-latest, true) (push) Has been cancelled
CI / Flatpak (${{ matrix.arch }}) (aarch64, ubuntu-22.04-arm) (push) Has been cancelled
CI / Flatpak (${{ matrix.arch }}) (x86_64, ubuntu-latest) (push) Has been cancelled
CI / C++ (push) Has been cancelled
Docker / Publish web port (push) Has been cancelled
CI / Release (push) Has been cancelled
Introduces a third person camera system with orbit camera, input handling (mouse/keyboard/touch/gamepad), display actor cloning, and camera-relative movement. Includes shared character utilities (animator, cloner, customizer) and an IExtraAnimHandler interface for optional animation extensions. Also includes generic base game fixes and extension system improvements.
271 lines
6.7 KiB
C++
271 lines
6.7 KiB
C++
#include "extensions/common/animutils.h"
|
|
|
|
#include "anim/legoanim.h"
|
|
#include "legoanimpresenter.h"
|
|
#include "legoworld.h"
|
|
#include "misc.h"
|
|
#include "misc/legotree.h"
|
|
#include "roi/legoroi.h"
|
|
|
|
#include <SDL3/SDL_stdinc.h>
|
|
#include <vector>
|
|
|
|
using namespace Extensions::Common;
|
|
|
|
// Mirrors the game's UpdateStructMapAndROIIndex: assigns ROI indices at runtime
|
|
// via SetROIIndex() since m_roiIndex starts at 0 for all animation nodes.
|
|
//
|
|
// Intentional divergences from LegoAnimPresenter::BuildROIMap (legoanimpresenter.cpp:413-530):
|
|
// 1. No variable substitution -- we bypass the streaming pipeline, so the variable
|
|
// table lacks our entries. Direct name comparison instead.
|
|
// 2. *-prefixed nodes search extraROIs -- the original's GetActorName() depends on
|
|
// presenter action context (m_action->GetUnknown24()). We search created extra
|
|
// ROIs directly.
|
|
// 3. No LegoAnimStructMap dedup -- sequential indices, functionally correct.
|
|
// Look up an animation node name in the alias map (case-insensitive).
|
|
static LegoROI* FindAlias(const char* p_name, const AnimUtils::ROIAlias* p_aliases, int p_aliasCount)
|
|
{
|
|
for (int i = 0; i < p_aliasCount; i++) {
|
|
if (p_aliases[i].animName && !SDL_strcasecmp(p_name, p_aliases[i].animName)) {
|
|
return p_aliases[i].roi;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static void AssignROIIndices(
|
|
LegoTreeNode* p_node,
|
|
LegoROI* p_parentROI,
|
|
LegoROI* p_rootROI,
|
|
LegoROI** p_extraROIs,
|
|
int p_extraROICount,
|
|
const AnimUtils::ROIAlias* p_aliases,
|
|
int p_aliasCount,
|
|
MxU32& p_nextIndex,
|
|
std::vector<LegoROI*>& p_entries,
|
|
bool& p_rootClaimed
|
|
)
|
|
{
|
|
LegoROI* roi = p_parentROI;
|
|
LegoAnimNodeData* data = (LegoAnimNodeData*) p_node->GetData();
|
|
const char* name = data ? data->GetName() : nullptr;
|
|
|
|
if (name != nullptr && *name != '-') {
|
|
LegoROI* matchedROI = nullptr;
|
|
|
|
if (*name == '*' || p_parentROI == nullptr) {
|
|
roi = p_rootROI;
|
|
|
|
const char* searchName = (*name == '*') ? name + 1 : name;
|
|
bool matchedExtra = false;
|
|
|
|
// Check aliases first (participant ROIs mapped by character name).
|
|
// Claiming root prevents subsequent sibling nodes from also claiming it.
|
|
matchedROI = FindAlias(searchName, p_aliases, p_aliasCount);
|
|
if (matchedROI) {
|
|
roi = matchedROI;
|
|
matchedExtra = true;
|
|
p_rootClaimed = true;
|
|
}
|
|
|
|
// Then check extra ROIs by name.
|
|
// This handles cases like BIKESY appearing before SY in the tree:
|
|
// BIKESY should match the vehicle extra, not claim the root.
|
|
if (!matchedExtra && p_extraROICount > 0) {
|
|
for (int e = 0; e < p_extraROICount; e++) {
|
|
matchedROI = p_extraROIs[e]->FindChildROI(searchName, p_extraROIs[e]);
|
|
if (matchedROI != nullptr) {
|
|
roi = matchedROI;
|
|
matchedExtra = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!matchedExtra) {
|
|
if (!p_rootClaimed) {
|
|
matchedROI = p_rootROI;
|
|
p_rootClaimed = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
matchedROI = p_parentROI->FindChildROI(name, p_parentROI);
|
|
if (matchedROI == nullptr) {
|
|
// Check aliases — also update roi so children resolve against the alias ROI
|
|
matchedROI = FindAlias(name, p_aliases, p_aliasCount);
|
|
if (matchedROI) {
|
|
roi = matchedROI;
|
|
}
|
|
}
|
|
if (matchedROI == nullptr) {
|
|
for (int e = 0; e < p_extraROICount; e++) {
|
|
matchedROI = p_extraROIs[e]->FindChildROI(name, p_extraROIs[e]);
|
|
if (matchedROI != nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Mirrors original game (legoanimpresenter.cpp:486-490):
|
|
// If FindChildROI fails, the node might be a top-level actor that isn't
|
|
// a child of the current parent. Re-run this node with p_parentROI=NULL
|
|
// so it enters the root-claiming / top-level search path instead.
|
|
if (matchedROI == nullptr) {
|
|
bool isTopLevel = false;
|
|
// Check aliases for top-level match
|
|
if (FindAlias(name, p_aliases, p_aliasCount) != nullptr) {
|
|
isTopLevel = true;
|
|
}
|
|
if (!isTopLevel && !p_rootClaimed && p_rootROI->GetName() &&
|
|
!SDL_strcasecmp(name, p_rootROI->GetName())) {
|
|
isTopLevel = true;
|
|
}
|
|
if (!isTopLevel) {
|
|
for (int e = 0; e < p_extraROICount; e++) {
|
|
if (p_extraROIs[e]->GetName() && !SDL_strcasecmp(name, p_extraROIs[e]->GetName())) {
|
|
isTopLevel = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (isTopLevel) {
|
|
AssignROIIndices(
|
|
p_node,
|
|
nullptr,
|
|
p_rootROI,
|
|
p_extraROIs,
|
|
p_extraROICount,
|
|
p_aliases,
|
|
p_aliasCount,
|
|
p_nextIndex,
|
|
p_entries,
|
|
p_rootClaimed
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matchedROI != nullptr) {
|
|
data->SetROIIndex(p_nextIndex);
|
|
p_entries.push_back(matchedROI);
|
|
p_nextIndex++;
|
|
}
|
|
else {
|
|
data->SetROIIndex(0);
|
|
}
|
|
}
|
|
|
|
for (MxS32 i = 0; i < p_node->GetNumChildren(); i++) {
|
|
AssignROIIndices(
|
|
p_node->GetChild(i),
|
|
roi,
|
|
p_rootROI,
|
|
p_extraROIs,
|
|
p_extraROICount,
|
|
p_aliases,
|
|
p_aliasCount,
|
|
p_nextIndex,
|
|
p_entries,
|
|
p_rootClaimed
|
|
);
|
|
}
|
|
}
|
|
|
|
void AnimUtils::BuildROIMap(
|
|
LegoAnim* p_anim,
|
|
LegoROI* p_rootROI,
|
|
LegoROI** p_extraROIs,
|
|
int p_extraROICount,
|
|
LegoROI**& p_roiMap,
|
|
MxU32& p_roiMapSize,
|
|
const ROIAlias* p_aliases,
|
|
int p_aliasCount
|
|
)
|
|
{
|
|
if (!p_anim || !p_rootROI) {
|
|
return;
|
|
}
|
|
|
|
LegoTreeNode* root = p_anim->GetRoot();
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
MxU32 nextIndex = 1;
|
|
std::vector<LegoROI*> entries;
|
|
bool rootClaimed = false;
|
|
AssignROIIndices(
|
|
root,
|
|
nullptr,
|
|
p_rootROI,
|
|
p_extraROIs,
|
|
p_extraROICount,
|
|
p_aliases,
|
|
p_aliasCount,
|
|
nextIndex,
|
|
entries,
|
|
rootClaimed
|
|
);
|
|
|
|
if (entries.empty()) {
|
|
return;
|
|
}
|
|
|
|
// 1-indexed; index 0 reserved as NULL
|
|
p_roiMapSize = entries.size() + 1;
|
|
p_roiMap = new LegoROI*[p_roiMapSize];
|
|
p_roiMap[0] = nullptr;
|
|
for (MxU32 i = 0; i < entries.size(); i++) {
|
|
p_roiMap[i + 1] = entries[i];
|
|
}
|
|
}
|
|
|
|
AnimUtils::AnimCache* AnimUtils::GetOrBuildAnimCache(
|
|
std::map<std::string, AnimCache>& p_cacheMap,
|
|
LegoROI* p_roi,
|
|
const char* p_animName
|
|
)
|
|
{
|
|
if (!p_animName || !p_roi) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Check if already cached
|
|
auto it = p_cacheMap.find(p_animName);
|
|
if (it != p_cacheMap.end()) {
|
|
return &it->second;
|
|
}
|
|
|
|
// Look up the animation presenter in the current world
|
|
LegoWorld* world = CurrentWorld();
|
|
if (!world) {
|
|
return nullptr;
|
|
}
|
|
|
|
MxCore* presenter = world->Find("LegoAnimPresenter", p_animName);
|
|
if (!presenter) {
|
|
return nullptr;
|
|
}
|
|
|
|
LegoAnim* anim = static_cast<LegoAnimPresenter*>(presenter)->GetAnimation();
|
|
if (!anim) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Build and cache
|
|
AnimCache& cache = p_cacheMap[p_animName];
|
|
cache.anim = anim;
|
|
BuildROIMap(anim, p_roi, nullptr, 0, cache.roiMap, cache.roiMapSize);
|
|
|
|
return &cache;
|
|
}
|
|
|
|
void AnimUtils::ApplyTree(LegoAnim* p_anim, MxMatrix& p_transform, LegoTime p_time, LegoROI** p_roiMap)
|
|
{
|
|
LegoTreeNode* root = p_anim->GetRoot();
|
|
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
|
LegoROI::ApplyAnimationTransformation(root->GetChild(i), p_transform, p_time, p_roiMap);
|
|
}
|
|
}
|