mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
Add 6 prop-only animations and scene ROI cloning for missing models
Manually override 6 spectator-only animations (557, 596, 709-711, 754) as cam anims in the catalog, with 754's location set to Hospital. For animations referencing world-placed models (BIRD, SHARK, CAT) whose LOD data isn't independently registered in the ViewLODListManager, add a scene ROI cloning fallback in SetupROIs. DeepCloneROI recursively clones the ROI hierarchy sharing LOD geometry via refcount, producing independent copies safe for concurrent playback. Moved to AnimUtils as a reusable utility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
734623cdd7
commit
964013203a
@ -143,6 +143,7 @@ class ROI {
|
||||
|
||||
// FUNCTION: BETA10 0x10027110
|
||||
const CompoundObject* GetComp() const { return comp; }
|
||||
void SetComp(CompoundObject* p_comp) { comp = p_comp; }
|
||||
|
||||
// FUNCTION: BETA10 0x10049e10
|
||||
unsigned char GetVisibility() { return m_visible; }
|
||||
|
||||
@ -93,6 +93,10 @@ inline void EnsureROIMapVisibility(LegoROI** p_roiMap, MxU32 p_roiMapSize)
|
||||
// Apply animation transformation to all root children of an animation tree.
|
||||
void ApplyTree(LegoAnim* p_anim, MxMatrix& p_transform, LegoTime p_time, LegoROI** p_roiMap);
|
||||
|
||||
// Deep-clone a ROI hierarchy, sharing LOD geometry via refcount.
|
||||
// Each clone gets its own transform, safe for concurrent animation playback.
|
||||
LegoROI* DeepCloneROI(LegoROI* p_source, const char* p_name);
|
||||
|
||||
// Strip trailing digits and underscores from a name to get the LOD base name.
|
||||
// Mirrors the digit-trimming in LegoAnimPresenter::CreateManagedActors/CreateSceneROIs.
|
||||
std::string TrimLODSuffix(const std::string& p_name);
|
||||
|
||||
@ -92,6 +92,9 @@ class ScenePlayer {
|
||||
// Props created for the animation (cloned characters and prop models)
|
||||
std::vector<LegoROI*> m_propROIs;
|
||||
|
||||
// ROIs cloned from scene (created by sharing LOD data, not registered in CharacterManager)
|
||||
std::vector<LegoROI*> m_clonedSceneROIs;
|
||||
|
||||
bool m_hasCamAnim;
|
||||
bool m_observerMode;
|
||||
std::vector<LegoROI*> m_ptAtCamROIs;
|
||||
|
||||
@ -2,10 +2,12 @@
|
||||
|
||||
#include "anim/legoanim.h"
|
||||
#include "legoanimpresenter.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "legoworld.h"
|
||||
#include "misc.h"
|
||||
#include "misc/legotree.h"
|
||||
#include "roi/legoroi.h"
|
||||
#include "viewmanager/viewlodlist.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <algorithm>
|
||||
@ -311,6 +313,39 @@ void AnimUtils::ApplyTree(LegoAnim* p_anim, MxMatrix& p_transform, LegoTime p_ti
|
||||
}
|
||||
}
|
||||
|
||||
LegoROI* AnimUtils::DeepCloneROI(LegoROI* p_source, const char* p_name)
|
||||
{
|
||||
Tgl::Renderer* renderer = VideoManager()->GetRenderer();
|
||||
ViewLODList* lodList = reinterpret_cast<ViewLODList*>(const_cast<LODListBase*>(p_source->GetLODs()));
|
||||
|
||||
LegoROI* clone;
|
||||
if (lodList && lodList->Size() > 0) {
|
||||
clone = new LegoROI(renderer, lodList);
|
||||
}
|
||||
else {
|
||||
clone = new LegoROI(renderer);
|
||||
}
|
||||
|
||||
clone->SetName(p_name);
|
||||
clone->SetBoundingSphere(p_source->GetBoundingSphere());
|
||||
|
||||
const CompoundObject* children = p_source->GetComp();
|
||||
if (children && !children->empty()) {
|
||||
CompoundObject* clonedChildren = new CompoundObject();
|
||||
for (CompoundObject::const_iterator it = children->begin(); it != children->end(); it++) {
|
||||
LegoROI* childSource = (LegoROI*) *it;
|
||||
const char* childName = childSource->GetName() ? childSource->GetName() : "";
|
||||
LegoROI* childClone = DeepCloneROI(childSource, childName);
|
||||
if (childClone) {
|
||||
clonedChildren->push_back(childClone);
|
||||
}
|
||||
}
|
||||
clone->SetComp(clonedChildren);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
std::string AnimUtils::TrimLODSuffix(const std::string& p_name)
|
||||
{
|
||||
std::string result(p_name);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "extensions/multiplayer/animation/catalog.h"
|
||||
|
||||
#include "actions/isle_actions.h"
|
||||
#include "decomp.h"
|
||||
#include "legoactors.h"
|
||||
#include "legoanimationmanager.h"
|
||||
@ -85,7 +86,18 @@ void Catalog::Refresh(LegoAnimationManager* p_am)
|
||||
static const uint64_t NAMED_CHARACTER_MASK = ((uint64_t(1) << 48) - 1) | (uint64_t(0xF) << 54);
|
||||
bool hasNamedPerformer = (entry.performerMask & NAMED_CHARACTER_MASK) != 0;
|
||||
|
||||
if (!hasNamedPerformer) {
|
||||
// Manual overrides for prop-only animations that have no character
|
||||
// performers but are valid scene animations with spectator-only slots.
|
||||
MxU32 objectId = m_animsBase[i].m_objectId;
|
||||
if (objectId == IsleScript::c_snsx31sh_RunAnim || objectId == IsleScript::c_fpz166p1_RunAnim ||
|
||||
objectId == IsleScript::c_nic002pr_RunAnim || objectId == IsleScript::c_nic003pr_RunAnim ||
|
||||
objectId == IsleScript::c_nic004pr_RunAnim || objectId == IsleScript::c_prp101pr_RunAnim) {
|
||||
if (objectId == IsleScript::c_prp101pr_RunAnim) {
|
||||
entry.location = 11; // Hospital
|
||||
}
|
||||
entry.category = e_camAnim;
|
||||
}
|
||||
else if (!hasNamedPerformer) {
|
||||
entry.category = e_otherAnim;
|
||||
}
|
||||
else if (entry.location == -1) {
|
||||
|
||||
@ -16,13 +16,13 @@
|
||||
#include "mxgeometry/mxgeometry3d.h"
|
||||
#include "realtime/realtime.h"
|
||||
#include "roi/legoroi.h"
|
||||
#include "viewmanager/viewmanager.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
using namespace Multiplayer::Animation;
|
||||
@ -81,12 +81,16 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
||||
|
||||
std::vector<bool> participantMatched(m_participants.size(), false);
|
||||
|
||||
// Register an alias mapping an animation actor name to an ROI whose actual
|
||||
// name differs (e.g. a participant's unique name, or a cloned scene ROI).
|
||||
auto addAlias = [&](const std::string& p_name, LegoROI* p_roi) {
|
||||
aliasNames.push_back(p_name);
|
||||
aliases.push_back({aliasNames.back().c_str(), p_roi});
|
||||
m_actorAliases.push_back({p_name, p_roi});
|
||||
};
|
||||
|
||||
// Create a prop ROI from a registered LOD name. Returns nullptr if the
|
||||
// LOD isn't in the ViewLODListManager.
|
||||
auto createProp = [&](const std::string& p_name, const char* p_lodName) -> LegoROI* {
|
||||
char uniqueName[64];
|
||||
SDL_snprintf(uniqueName, sizeof(uniqueName), "npc_prop_%s", p_name.c_str());
|
||||
@ -98,6 +102,32 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
||||
return roi;
|
||||
};
|
||||
|
||||
// Clone a scene ROI by name. Creates an independent deep copy (shared LOD
|
||||
// geometry via refcount) with a unique name and an alias for the ROI map.
|
||||
auto cloneSceneROI = [&](const std::string& p_name) -> LegoROI* {
|
||||
const CompoundObject& sceneROIs = VideoManager()->Get3DManager()->GetLego3DView()->GetViewManager()->GetROIs();
|
||||
for (CompoundObject::const_iterator it = sceneROIs.begin(); it != sceneROIs.end(); it++) {
|
||||
LegoROI* source = (LegoROI*) *it;
|
||||
if (!source->GetName() || SDL_strcasecmp(source->GetName(), p_name.c_str())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
static uint32_t s_counter = 0;
|
||||
char uniqueName[64];
|
||||
SDL_snprintf(uniqueName, sizeof(uniqueName), "npc_scene_%s_%u", p_name.c_str(), s_counter++);
|
||||
|
||||
LegoROI* clone = AnimUtils::DeepCloneROI(source, uniqueName);
|
||||
if (clone) {
|
||||
clone->SetVisibility(FALSE);
|
||||
VideoManager()->Get3DManager()->Add(*clone);
|
||||
m_clonedSceneROIs.push_back(clone);
|
||||
addAlias(p_name, clone);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
for (LegoU32 i = 0; i < numActors; i++) {
|
||||
const char* actorName = m_currentData->anim->GetActorName(i);
|
||||
LegoU32 actorType = m_currentData->anim->GetActorType(i);
|
||||
@ -111,6 +141,7 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(), ::tolower);
|
||||
|
||||
if (actorType == LegoAnimActorEntry::e_managedLegoActor) {
|
||||
// Character actor: match to a participant or clone as NPC
|
||||
bool matched = false;
|
||||
|
||||
for (size_t p = 0; p < m_participants.size(); p++) {
|
||||
@ -126,18 +157,15 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// No participant matched — create a clone
|
||||
char uniqueName[64];
|
||||
SDL_snprintf(uniqueName, sizeof(uniqueName), "npc_char_%s", lowered.c_str());
|
||||
LegoROI* roi = CharacterCloner::Clone(CharacterManager(), uniqueName, lowered.c_str());
|
||||
if (roi) {
|
||||
roi->SetName(lowered.c_str());
|
||||
VideoManager()->Get3DManager()->Add(*roi);
|
||||
createdROIs.push_back(roi);
|
||||
if (!matched) {
|
||||
char uniqueName[64];
|
||||
SDL_snprintf(uniqueName, sizeof(uniqueName), "npc_char_%s", lowered.c_str());
|
||||
LegoROI* roi = CharacterCloner::Clone(CharacterManager(), uniqueName, lowered.c_str());
|
||||
if (roi) {
|
||||
roi->SetName(lowered.c_str());
|
||||
VideoManager()->Get3DManager()->Add(*roi);
|
||||
createdROIs.push_back(roi);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (actorType == LegoAnimActorEntry::e_managedInvisibleRoiTrimmed || actorType == LegoAnimActorEntry::e_sceneRoi1 || actorType == LegoAnimActorEntry::e_sceneRoi2) {
|
||||
@ -147,10 +175,11 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
||||
createProp(lowered, lowered.c_str());
|
||||
}
|
||||
else {
|
||||
// Type 0/1: check if this is a vehicle actor via ModelInfo flag
|
||||
// Type 0/1: scene actor, vehicle, or prop
|
||||
LegoROI* roi = nullptr;
|
||||
bool isVehicleActor = false;
|
||||
|
||||
// Check if this is a vehicle actor via ModelInfo flag
|
||||
bool isVehicleActor = false;
|
||||
for (uint8_t m = 0; m < p_animInfo->m_modelCount; m++) {
|
||||
if (p_animInfo->m_models[m].m_name &&
|
||||
!SDL_strcasecmp(lowered.c_str(), p_animInfo->m_models[m].m_name) &&
|
||||
@ -182,11 +211,17 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Try creating as prop
|
||||
// Try creating from a registered LOD
|
||||
if (!roi) {
|
||||
roi = createProp(lowered, AnimUtils::TrimLODSuffix(lowered).c_str());
|
||||
}
|
||||
|
||||
// Fallback: clone an existing scene ROI (for models like BIRD
|
||||
// whose LOD data is embedded in the world, not registered separately)
|
||||
if (!roi) {
|
||||
roi = cloneSceneROI(lowered);
|
||||
}
|
||||
|
||||
// Final fallback: borrow local player's vehicle via alias
|
||||
if (!roi && m_participants[0].vehicleROI && !m_vehicleROI) {
|
||||
m_vehicleROI = m_participants[0].vehicleROI;
|
||||
@ -226,6 +261,9 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
||||
for (auto* propROI : m_propROIs) {
|
||||
extras.push_back(propROI);
|
||||
}
|
||||
for (auto* clonedROI : m_clonedSceneROIs) {
|
||||
extras.push_back(clonedROI);
|
||||
}
|
||||
if (m_vehicleROI) {
|
||||
extras.push_back(m_vehicleROI);
|
||||
}
|
||||
@ -571,4 +609,12 @@ void ScenePlayer::CleanupProps()
|
||||
}
|
||||
}
|
||||
m_propROIs.clear();
|
||||
|
||||
for (auto* clonedROI : m_clonedSceneROIs) {
|
||||
if (clonedROI) {
|
||||
VideoManager()->Get3DManager()->Remove(*clonedROI);
|
||||
delete clonedROI;
|
||||
}
|
||||
}
|
||||
m_clonedSceneROIs.clear();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user