From 6d92a71d19d06519de60a30a78c41527994249a4 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Sun, 9 Aug 2026 03:52:45 -0700 Subject: [PATCH] Initial gamelib integration AAS2 with some animation and physics and another integration pass on idLib. --- source/CMakeLists.txt | 212 ++++ source/engine/aas2file/aas2file.cpp | 2 +- source/engine/aas2file/aas2file.h | 3 +- source/engine/framework/resource.cpp | 67 ++ source/engine/framework/resource.h | 196 +--- source/engine/framework/resourcelist.cpp | 147 +++ source/engine/framework/resourcelist.h | 380 +------ source/engine/gamelib/aas2/aas2.cpp | 314 ++++++ source/engine/gamelib/aas2/aas2.h | 356 ++++-- source/engine/gamelib/aas2/aas2_debug.cpp | 152 +++ source/engine/gamelib/aas2/aas2_local.h | 437 ++++--- source/engine/gamelib/aas2/aas2_pathing.cpp | 345 ++++++ source/engine/gamelib/aas2/aas2_pvs.cpp | 221 ++++ source/engine/gamelib/aas2/aas2_routing.cpp | 580 ++++++++++ source/engine/gamelib/aas2/aas2_spacial.cpp | 157 +++ .../engine/gamelib/aas2/aas2_subdivision.cpp | 49 + .../aas2/aas2callback_avoidlocation.cpp | 61 + .../gamelib/aas2/aas2callback_avoidlocation.h | 28 +- .../aas2/aas2callback_findflaggedarea.cpp | 21 + .../aas2/aas2callback_findflaggedarea.h | 15 +- .../engine/gamelib/aas2/deferredviscache.cpp | 371 ++++++ source/engine/gamelib/aas2/deferredviscache.h | 142 ++- .../engine/gamelib/aas2/obstacleavoidance.cpp | 355 ++++++ .../engine/gamelib/aas2/obstacleavoidance.h | 88 +- .../gamelib/aas2/obstacleavoidancecore.cpp | 347 ++++++ .../gamelib/aas2/obstacleavoidancecore.h | 373 ++++-- .../aas2/obstacleavoidancemanagerlocal.cpp | 165 +++ .../aas2/obstacleavoidancemanagerlocal.h | 88 +- .../gamelib/animstack/animator_base.cpp | 243 ++++ .../engine/gamelib/animstack/animator_base.h | 155 ++- .../gamelib/animstack/animator_paused.cpp | 79 ++ .../gamelib/animstack/animator_paused.h | 88 +- .../gamelib/animstack/animator_proxy.cpp | 39 + .../engine/gamelib/animstack/animator_proxy.h | 52 +- .../engine/gamelib/animstack/animstacktypes.h | 131 +++ .../gamelib/animstack/animweb/animwebpath.h | 62 +- .../animstack/animweb/animwebutils.cpp | 100 ++ .../gamelib/animstack/animweb/animwebutils.h | 14 + .../gamelib/animstack/animweb/dijkstra.h | 73 +- .../gamelib/animstack/animweb/dijkstra_impl.h | 158 +++ .../gamelib/animstack/coneconstraint.cpp | 129 +++ .../engine/gamelib/animstack/coneconstraint.h | 240 +--- source/engine/gamelib/class.cpp | 216 ++++ source/engine/gamelib/class.h | 145 ++- .../effectphysicsboxfountain.cpp | 54 + .../effectphysics/effectphysicsboxfountain.h | 47 +- .../effectphysics/effectphysicsbreakable.cpp | 695 ++++++++++++ .../effectphysics/effectphysicsbreakable.h | 326 ++++-- .../effectphysics/effectphysicsbroadphase.cpp | 290 +++++ .../effectphysics/effectphysicsbroadphase.h | 112 +- .../effectphysics/effectphysicscuttable.cpp | 308 +++++ .../effectphysics/effectphysicscuttable.h | 132 ++- .../effectphysicsdebrisemitter.cpp | 102 ++ .../effectphysicsdebrisemitter.h | 58 +- .../effectphysicspieceemitter.cpp | 359 ++++++ .../effectphysics/effectphysicspieceemitter.h | 137 ++- .../effectphysics/effectphysicsproperties.cpp | 93 ++ .../effectphysics/effectphysicsproperties.h | 12 + .../effectphysics/effectphysicsrigidbody.cpp | 262 +++++ .../effectphysics/effectphysicsrigidbody.h | 116 +- .../gamelib/effects/deferreddecalmanager.cpp | 117 ++ .../gamelib/effects/deferreddecalmanager.h | 99 +- .../gamelib/effects/effectsmodelmanager.cpp | 154 +++ .../gamelib/effects/effectsmodelmanager.h | 47 +- source/engine/gamelib/effects/ribbon.cpp | 268 +++++ source/engine/gamelib/effects/ribbon.h | 127 ++- source/engine/gamelib/effects/ribbonmanager.h | 78 +- .../engine/gamelib/effects/tracermanager.cpp | 86 ++ source/engine/gamelib/effects/tracermanager.h | 73 +- .../gamelib/effects/weapontracemanager.cpp | 121 ++ .../gamelib/effects/weapontracemanager.h | 85 +- source/engine/gamelib/physics/clip.cpp | 752 ++++++++++++ source/engine/gamelib/physics/clip.h | 466 +++++--- source/engine/gamelib/physics/clipmodel.cpp | 279 +++++ source/engine/gamelib/physics/clipmodel.h | 205 ++-- source/engine/gamelib/physics/physics.cpp | 106 ++ source/engine/gamelib/physics/physics.h | 379 +++---- .../gamelib/physics/tracemodelcache.cpp | 226 ++++ .../engine/gamelib/physics/tracemodelcache.h | 118 +- .../gamelib/physics/tracemodelrecycler.cpp | 59 + .../gamelib/physics/tracemodelrecycler.h | 90 +- source/engine/gamelib/pvs/pvs.cpp | 311 +++++ source/engine/gamelib/pvs/pvs.h | 126 ++- .../gamelib/timeline/timelineeventdata.cpp | 18 + .../gamelib/timeline/timelineeventdata.h | 53 + .../gamelib/timeline/timelinekeyanimdata.cpp | 157 +++ .../gamelib/timeline/timelinekeyanimdata.h | 29 +- .../gamelib/timeline/timelinekeytypes.cpp | 105 ++ .../gamelib/timeline/timelinekeytypes.h | 44 + source/engine/mapfile/mapfile.h | 81 +- source/engine/mapfile/mapmodel.h | 70 ++ source/shared/idlib/bv/bounds.h | 119 ++ source/shared/idlib/bv/boundsshort.h | 8 +- source/shared/idlib/commandlink.cpp | 17 + source/shared/idlib/commandlink.h | 26 + source/shared/idlib/containers/list.h | 5 +- source/shared/idlib/csystems/autocomplete.h | 2 +- source/shared/idlib/geometry/screenrect.h | 3 +- source/shared/idlib/geometry/tracemodel.cpp | 205 ++++ source/shared/idlib/geometry/winding.cpp | 212 ++++ source/shared/idlib/geometry/winding.h | 3 + source/shared/idlib/lib_print.h | 5 +- source/shared/idlib/math/vector.h | 72 ++ source/shared/idlib/metrics/timer.cpp | 17 + source/shared/idlib/metrics/timer.h | 13 +- source/shared/idlib/runningaverage.h | 2 +- source/shared/idlib/sys/sys_assert.cpp | 17 + source/shared/idlib/sys/sys_assert.h | 4 + source/shared/idlib/text/lexer.cpp | 1003 +++++++++++++++++ source/shared/idlib/text/lexer.h | 67 ++ source/shared/idlib/text/str.h | 51 +- 111 files changed, 15204 insertions(+), 2275 deletions(-) create mode 100644 source/CMakeLists.txt create mode 100644 source/engine/framework/resource.cpp create mode 100644 source/engine/framework/resourcelist.cpp create mode 100644 source/engine/gamelib/aas2/aas2.cpp create mode 100644 source/engine/gamelib/aas2/aas2_debug.cpp create mode 100644 source/engine/gamelib/aas2/aas2_pathing.cpp create mode 100644 source/engine/gamelib/aas2/aas2_pvs.cpp create mode 100644 source/engine/gamelib/aas2/aas2_routing.cpp create mode 100644 source/engine/gamelib/aas2/aas2_spacial.cpp create mode 100644 source/engine/gamelib/aas2/aas2_subdivision.cpp create mode 100644 source/engine/gamelib/aas2/aas2callback_avoidlocation.cpp create mode 100644 source/engine/gamelib/aas2/aas2callback_findflaggedarea.cpp create mode 100644 source/engine/gamelib/aas2/deferredviscache.cpp create mode 100644 source/engine/gamelib/aas2/obstacleavoidance.cpp create mode 100644 source/engine/gamelib/aas2/obstacleavoidancecore.cpp create mode 100644 source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.cpp create mode 100644 source/engine/gamelib/animstack/animator_base.cpp create mode 100644 source/engine/gamelib/animstack/animator_paused.cpp create mode 100644 source/engine/gamelib/animstack/animator_proxy.cpp create mode 100644 source/engine/gamelib/animstack/animstacktypes.h create mode 100644 source/engine/gamelib/animstack/animweb/animwebutils.cpp create mode 100644 source/engine/gamelib/animstack/animweb/animwebutils.h create mode 100644 source/engine/gamelib/animstack/animweb/dijkstra_impl.h create mode 100644 source/engine/gamelib/animstack/coneconstraint.cpp create mode 100644 source/engine/gamelib/class.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicsboxfountain.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicsbreakable.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicsbroadphase.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicscuttable.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicspieceemitter.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicsproperties.cpp create mode 100644 source/engine/gamelib/effectphysics/effectphysicsrigidbody.cpp create mode 100644 source/engine/gamelib/effects/deferreddecalmanager.cpp create mode 100644 source/engine/gamelib/effects/effectsmodelmanager.cpp create mode 100644 source/engine/gamelib/effects/ribbon.cpp create mode 100644 source/engine/gamelib/effects/tracermanager.cpp create mode 100644 source/engine/gamelib/effects/weapontracemanager.cpp create mode 100644 source/engine/gamelib/physics/clip.cpp create mode 100644 source/engine/gamelib/physics/clipmodel.cpp create mode 100644 source/engine/gamelib/physics/physics.cpp create mode 100644 source/engine/gamelib/physics/tracemodelcache.cpp create mode 100644 source/engine/gamelib/physics/tracemodelrecycler.cpp create mode 100644 source/engine/gamelib/pvs/pvs.cpp create mode 100644 source/engine/gamelib/timeline/timelineeventdata.cpp create mode 100644 source/engine/gamelib/timeline/timelineeventdata.h create mode 100644 source/engine/gamelib/timeline/timelinekeyanimdata.cpp create mode 100644 source/engine/gamelib/timeline/timelinekeytypes.cpp create mode 100644 source/engine/gamelib/timeline/timelinekeytypes.h create mode 100644 source/engine/mapfile/mapmodel.h create mode 100644 source/shared/idlib/commandlink.cpp create mode 100644 source/shared/idlib/commandlink.h create mode 100644 source/shared/idlib/geometry/tracemodel.cpp create mode 100644 source/shared/idlib/geometry/winding.cpp create mode 100644 source/shared/idlib/metrics/timer.cpp create mode 100644 source/shared/idlib/sys/sys_assert.cpp create mode 100644 source/shared/idlib/sys/sys_assert.h create mode 100644 source/shared/idlib/text/lexer.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..0174ca2 --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,212 @@ +cmake_minimum_required(VERSION 3.25) + +set(CMAKE_VS_GLOBALS "VcpkgEnabled=false") +project(tech5 LANGUAGES C CXX) + +if(NOT WIN32) + message(FATAL_ERROR "The recovered idTech 5 PC port currently supports Windows only.") +endif() + +if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4) + message(FATAL_ERROR + "The recovered Xbox 360 layouts require a Win32 host build. Configure with -A Win32." + ) +endif() + +get_filename_component(TECH5_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${TECH5_ROOT}/bin") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${TECH5_ROOT}/bin") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${TECH5_ROOT}/build/lib") +foreach(config Debug Release RelWithDebInfo MinSizeRel) + string(TOUPPER "${config}" config_upper) + set("CMAKE_RUNTIME_OUTPUT_DIRECTORY_${config_upper}" "${TECH5_ROOT}/bin") + set("CMAKE_LIBRARY_OUTPUT_DIRECTORY_${config_upper}" "${TECH5_ROOT}/bin") + set("CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${config_upper}" + "${TECH5_ROOT}/build/lib/${config}" + ) +endforeach() + +# This list is intentionally sourced only from the reconstructed tree. The +# Hex-Rays tree is evidence, never compiler input. +file(GLOB_RECURSE IDTECH5_IDLIB_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/shared/idlib/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/shared/idlib/*.h" +) + +# These recovered subsystems remain outside the current PC build boundary. +set(IDTECH5_DEFERRED_PATTERNS + "/analysisclient\." + "/networking/amqp/" + "/typeinfo/typeinfofile\.cpp$" +) +foreach(pattern IN LISTS IDTECH5_DEFERRED_PATTERNS) + list(FILTER IDTECH5_IDLIB_SOURCES EXCLUDE REGEX "${pattern}") +endforeach() + +add_library(idLib STATIC ${IDTECH5_IDLIB_SOURCES}) +add_library(tech5::idLib ALIAS idLib) +target_include_directories(idLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/shared") +target_compile_features(idLib PUBLIC cxx_std_14) +target_compile_definitions(idLib PRIVATE + __IDLIB__ + _CRT_SECURE_NO_DEPRECATE + _CRT_NONSTDC_NO_DEPRECATE + _USE_32BIT_TIME_T +) +target_link_libraries(idLib PUBLIC ws2_32 psapi) +set_property(TARGET idLib PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" +) +set_target_properties(idLib PROPERTIES OUTPUT_NAME "idLib") + +if(MSVC) + target_compile_options(idLib PRIVATE + /MP /W3 /arch:SSE2 + /wd4244 /wd4267 /wd4305 /wd4467 /wd4595 /wd4996 + ) +endif() + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source" + FILES ${IDTECH5_IDLIB_SOURCES} +) + +# Minimal framework resource ownership needed by recovered engine resources. +set(IDTECH5_FRAMEWORK_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/engine/framework/resource.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/engine/framework/resource.h" + "${CMAKE_CURRENT_SOURCE_DIR}/engine/framework/resourcelist.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/engine/framework/resourcelist.h" +) + +add_library(framework STATIC ${IDTECH5_FRAMEWORK_SOURCES}) +add_library(tech5::framework ALIAS framework) +target_include_directories(framework PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/engine" + "${CMAKE_CURRENT_SOURCE_DIR}/shared" +) +target_compile_features(framework PUBLIC cxx_std_14) +target_compile_definitions(framework PRIVATE + _CRT_SECURE_NO_DEPRECATE + _CRT_NONSTDC_NO_DEPRECATE + _USE_32BIT_TIME_T +) +target_link_libraries(framework PUBLIC tech5::idLib) +set_property(TARGET framework PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" +) +if(MSVC) + target_compile_options(framework PRIVATE + /MP /W3 /arch:SSE2 + /wd4244 /wd4267 /wd4305 /wd4467 /wd4595 /wd4996 + ) +endif() +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source" + FILES ${IDTECH5_FRAMEWORK_SOURCES} +) + +# Collision model recovery is a separate engine library. As with idLib and +# GameLib, only reconstructed files under source/ are compiler inputs. +file(GLOB_RECURSE IDTECH5_CM_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/engine/cm/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/engine/cm/*.h" +) + +add_library(cm STATIC ${IDTECH5_CM_SOURCES}) +add_library(tech5::cm ALIAS cm) +target_include_directories(cm PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/engine" + "${CMAKE_CURRENT_SOURCE_DIR}/shared" +) +target_compile_features(cm PUBLIC cxx_std_14) +target_compile_definitions(cm PRIVATE + _CRT_SECURE_NO_DEPRECATE + _CRT_NONSTDC_NO_DEPRECATE + _USE_32BIT_TIME_T +) +target_link_libraries(cm PUBLIC tech5::framework tech5::idLib) +set_property(TARGET cm PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" +) +set_target_properties(cm PROPERTIES OUTPUT_NAME "cm") + +if(MSVC) + target_compile_options(cm PRIVATE + /MP /W3 /arch:SSE2 + /wd4244 /wd4267 /wd4305 /wd4467 /wd4595 /wd4996 + ) +endif() + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source" + FILES ${IDTECH5_CM_SOURCES} +) + +# Recovered AAS2 file/resource layer. The decompiler tree remains evidence; +# only the manually reconstructed PC sources below are compiled. +file(GLOB_RECURSE IDTECH5_AAS2FILE_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/engine/aas2file/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/engine/aas2file/*.h" +) + +add_library(aas2file STATIC ${IDTECH5_AAS2FILE_SOURCES}) +add_library(tech5::aas2file ALIAS aas2file) +target_include_directories(aas2file PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/engine" + "${CMAKE_CURRENT_SOURCE_DIR}/shared" +) +target_compile_features(aas2file PUBLIC cxx_std_14) +target_compile_definitions(aas2file PRIVATE + _CRT_SECURE_NO_DEPRECATE + _CRT_NONSTDC_NO_DEPRECATE + _USE_32BIT_TIME_T +) +target_link_libraries(aas2file PUBLIC tech5::framework tech5::idLib) +set_property(TARGET aas2file PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" +) +set_target_properties(aas2file PROPERTIES OUTPUT_NAME "aas2file") +if(MSVC) + target_compile_options(aas2file PRIVATE + /MP /W3 /arch:SSE2 + /wd4244 /wd4267 /wd4305 /wd4467 /wd4595 /wd4996 + ) +endif() +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source" + FILES ${IDTECH5_AAS2FILE_SOURCES} +) + +# GameLib follows the same evidence boundary as idLib: only reconstructed +# files under source/ are compiler inputs. The Hex-Rays tree is never built. +file(GLOB_RECURSE IDTECH5_GAMELIB_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/engine/gamelib/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/engine/gamelib/*.h" +) + +add_library(gameLib STATIC ${IDTECH5_GAMELIB_SOURCES}) +add_library(tech5::gameLib ALIAS gameLib) +target_include_directories(gameLib PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/engine" + "${CMAKE_CURRENT_SOURCE_DIR}/shared" +) +target_compile_features(gameLib PUBLIC cxx_std_14) +target_compile_definitions(gameLib PRIVATE + _CRT_SECURE_NO_DEPRECATE + _CRT_NONSTDC_NO_DEPRECATE + _USE_32BIT_TIME_T +) +target_link_libraries(gameLib PUBLIC tech5::aas2file tech5::cm tech5::idLib) +set_property(TARGET gameLib PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" +) +set_target_properties(gameLib PROPERTIES OUTPUT_NAME "gameLib") + +if(MSVC) + target_compile_options(gameLib PRIVATE + /MP /W3 /arch:SSE2 + /wd4244 /wd4267 /wd4305 /wd4467 /wd4595 /wd4996 + ) +endif() + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source" + FILES ${IDTECH5_GAMELIB_SOURCES} +) diff --git a/source/engine/aas2file/aas2file.cpp b/source/engine/aas2file/aas2file.cpp index 8a05413..0983663 100644 --- a/source/engine/aas2file/aas2file.cpp +++ b/source/engine/aas2file/aas2file.cpp @@ -312,7 +312,7 @@ std::uint32_t CanonicalXboxAreaChecksum( } // namespace -idResourceList idAAS2File::resourceList("aas"); +idTypedResourceList idAAS2File::resourceList("aas"); aas2Cover_t::aas2Cover_t() : origin(), dir(), areaNum(0), flags(0), numTouching(0), diff --git a/source/engine/aas2file/aas2file.h b/source/engine/aas2file/aas2file.h index 40bf3fa..ed1f10c 100644 --- a/source/engine/aas2file/aas2file.h +++ b/source/engine/aas2file/aas2file.h @@ -11,6 +11,7 @@ class idFile; class idLexer; +template class idTypedResourceList; enum aas2TravelFlag_t : int { AAS_TFL_INVALID = 0x1, @@ -388,7 +389,7 @@ public: void FloodAddVisitedArea(int areaNum); void FloodClearVisitedAreas(int offset); - static idResourceList resourceList; + static idTypedResourceList resourceList; std::uint32_t crc; std::uint32_t timestamp; diff --git a/source/engine/framework/resource.cpp b/source/engine/framework/resource.cpp new file mode 100644 index 0000000..5ad2d48 --- /dev/null +++ b/source/engine/framework/resource.cpp @@ -0,0 +1,67 @@ +#include "framework/resource.h" + +#include "framework/resourcelist.h" +#include "idlib/lib_print.h" +#include "idlib/text/str.h" + +idResource::idResource() + : trackedMemory(0), name("unnamed"), nextOnHashChain(nullptr), + resourceListPtr(nullptr), resourceError(nullptr), networkID(-1), + staleCount(0), resourceFlags(0), pad{} { +} + +idResource::~idResource() { + if ((resourceFlags & 0x40u) != 0) { + idLibPrint::FatalError("Resource deleted with staticResource set: %s", + name.c_str()); + } + if (resourceListPtr != nullptr) { + resourceListPtr->Remove(this); + } + idResourceList::UnRegisterNetworkResource(this); +} + +void idResource::LoadResource() { + const idResourceList* const list = GetResourceList(); + idLibPrint::FatalError("idResource::Load() for %s not implemented.\n", + list != nullptr ? list->GetResourceTypeName() : "resource"); +} + +bool idResource::ReloadIfStale() { + return false; +} + +void idResource::WriteResourceFile() { + const idResourceList* const list = GetResourceList(); + idLibPrint::FatalError( + "resource type '%s' has not implemented WriteResourceFile()", + list != nullptr ? list->GetResourceTypeName() : "resource"); +} + +idResourceList* idResource::GetResourceList() { + return resourceListPtr; +} + +void idResource::Print() { + const idResourceList* const list = GetResourceList(); + const char* const typeName = + list != nullptr ? list->GetResourceTypeName() : "resource"; + if (resourceError != nullptr) { + idLibPrint::Printf("%s:%s:%s\n", typeName, name.c_str(), + resourceError); + } else { + idLibPrint::Printf("%s:%s\n", typeName, name.c_str()); + } +} + +void idResource::List() { + Print(); +} + +void idResource::SetName(const char* const newName) { + idStr canonicalName(newName); + canonicalName.MakeNameCanonical(); + if (idStr::Cmp(canonicalName.c_str(), name.c_str()) != 0) { + name.Set(canonicalName.c_str()); + } +} diff --git a/source/engine/framework/resource.h b/source/engine/framework/resource.h index cbd1e6c..ffc2d4e 100644 --- a/source/engine/framework/resource.h +++ b/source/engine/framework/resource.h @@ -1,155 +1,59 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\framework\resource.h -// Recovered logical types: 12 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/text/atomicstring.h" +#include -// IDA Local Type ordinal 2777; PDB kind: enum. -enum idResource::referenceType_t : __int32 -{ - REF_UNKNOWN = 0x0, - REF_FREED = 0x1, - REF_WEAK = 0x2, - REF_STRONG = 0x3, -}; +class idResourceList; -// IDA Local Type ordinal 2778; PDB kind: enum. -enum idResource::resourceError_t : __int32 -{ - RESOURCE_ERROR_NONE = 0x0, - RESOURCE_ERROR_FILE_NOT_FOUND = 0x1, - RESOURCE_ERROR_WRONG_TYPE = 0x2, - RESOURCE_ERROR_INVALID_NAME = 0x3, - RESOURCE_ERROR_HEADER_VERSION_MISMATCH = 0x4, - RESOURCE_ERROR_RESOURCE_VERSION_MISMATCH = 0x5, - RESOURCE_ERROR_FATAL = 0x6, - RESOURCE_ERROR_TRUNCATED = 0x7, - RESOURCE_ERROR_MAX = 0x8, -}; - -// IDA Local Type ordinal 2779; PDB kind: enum. -enum idResource::resourceHeader_t::resourcePlatform_t : __int32 -{ - RESOURCEPLATFORM_WINDOWS = 0x0, - RESOURCEPLATFORM_360 = 0x1, - RESOURCEPLATFORM_PS3 = 0x2, - RESOURCEPLATFORM_MAC = 0x3, - RESOURCEPLATFORM_MAX = 0x4, -}; - -// IDA Local Type ordinal 13299; PDB kind: class. -class __declspec(align(4)) idResource -{ +class alignas(4) idResource { public: - // Recovered virtual interface; IDA vtable ordinal 13302. - virtual ~idResource(); - virtual void LoadResource(); - virtual bool ReloadIfStale(); - virtual void WriteResourceFile(); - virtual idResourceList *GetResourceList(); - virtual void Print(); - virtual void List(); + // Recovered from idResource's PDB type information. The executable + // stores the printable error text in resourceError, while this enum is + // the stable result vocabulary used by binary-resource loaders. + enum resourceError_t : int { + RESOURCE_ERROR_NONE = 0, + RESOURCE_ERROR_FILE_NOT_FOUND = 1, + RESOURCE_ERROR_WRONG_TYPE = 2, + RESOURCE_ERROR_INVALID_NAME = 3, + RESOURCE_ERROR_HEADER_VERSION_MISMATCH = 4, + RESOURCE_ERROR_RESOURCE_VERSION_MISMATCH = 5, + RESOURCE_ERROR_FATAL = 6, + RESOURCE_ERROR_TRUNCATED = 7, + RESOURCE_ERROR_MAX = 8 + }; - int trackedMemory; - idAtomicString name; - idResource *nextOnHashChain; - idResourceList *resourceListPtr; - const char *resourceError; - int networkID; - int staleCount; - unsigned __int8 : 3; - __int8 loadDeferred : 1; - __int8 forceLoad : 1; - __int8 insideRecursiveLoad : 1; - __int8 staticResource : 1; - __int8 loadedResource : 1; + enum referenceType_t : int { + REF_UNKNOWN = 0, + REF_FREED = 1, + REF_WEAK = 2, + REF_STRONG = 3 + }; + + idResource(); + virtual ~idResource(); + + virtual void LoadResource(); + virtual bool ReloadIfStale(); + virtual void WriteResourceFile(); + virtual idResourceList* GetResourceList(); + virtual void Print(); + virtual void List(); + + void SetName(const char* newName); + const char* GetName() const { return name.c_str(); } + + int trackedMemory; + idAtomicString name; + idResource* nextOnHashChain; + idResourceList* resourceListPtr; + const char* resourceError; + int networkID; + int staleCount; + std::uint8_t resourceFlags; + std::uint8_t pad[3]; }; -// IDA Local Type ordinal 13653; PDB kind: class. -class idCodeResource : public idBaseCodeResource -{ -public: -}; - -// IDA Local Type ordinal 17567; PDB kind: class. -class idCodeResource : public idBaseCodeResource -{ -public: -}; - -// IDA Local Type ordinal 19095; PDB kind: class. -class idAtlasResource : public idResource -{ -public: - // Recovered virtual interface; IDA vtable ordinal 19096. - virtual ~idAtlasResource(); - virtual void LoadResource(); - virtual bool ReloadIfStale(); - virtual void WriteResourceFile(); - virtual idResourceList *GetResourceList(); - virtual void Print(); - virtual void List(); - - unsigned int binaryTimestamp; - idVec4 scaleBias; - int img_tiles_wide; - int img_tiles_high; - int img_x; - int img_y; - int img_w; - int img_h; -}; - -// IDA Local Type ordinal 21416; PDB kind: class. -class idCodeResource : public idBaseCodeResource -{ -public: -}; - -// IDA Local Type ordinal 22063; PDB kind: struct. -struct idResource::resourceHeader_t -{ - unsigned __int16 headerVersionHi; - unsigned __int16 headerVersionLo; - unsigned int resourceId; - unsigned __int16 versionHi; - unsigned __int16 versionLo; - unsigned int sourceTimestamp; - unsigned __int16 sourceFileNameLen; - unsigned __int16 uniqueIdNameLen; - unsigned __int8 platform; - unsigned __int8 pad[3]; - unsigned __int64 uniqueId; - unsigned __int64 hash; - unsigned __int64 dataOffset; - unsigned __int64 totalSize; -}; - -// IDA Local Type ordinal 22703; PDB kind: class. -class idCodeResource : public idBaseCodeResource -{ -public: -}; - -// IDA Local Type ordinal 22770; PDB kind: class. -class idCodeResource : public idBaseCodeResource -{ -public: -}; - -// IDA Local Type ordinal 23337; PDB kind: class. -class idJointResource : public idResource -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23338. - virtual ~idJointResource(); - virtual void LoadResource(); - virtual bool ReloadIfStale(); - virtual void WriteResourceFile(); - virtual idResourceList *GetResourceList(); - virtual void Print(); - virtual void List(); - -}; +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idResource) == 36, "Recovered idResource ABI changed"); +#endif diff --git a/source/engine/framework/resourcelist.cpp b/source/engine/framework/resourcelist.cpp new file mode 100644 index 0000000..d975e2c --- /dev/null +++ b/source/engine/framework/resourcelist.cpp @@ -0,0 +1,147 @@ +#include "framework/resourcelist.h" + +#include "framework/resource.h" +#include "idlib/lib_print.h" + +#include +#include + +namespace { + +std::mutex resourceListMutex; + +unsigned int ResourceHash(const char* text) { + unsigned int hash = 0; + if (text != nullptr) { + for (const unsigned char* cursor = + reinterpret_cast(text); + *cursor != 0; ++cursor) { + hash = 31u * hash + *cursor; + } + } + return hash & 255u; +} + +} // namespace + +idResourceList::idResourceList(const char* const typeName) + : resourceTypeName(typeName), nextResourceList(nullptr), num(0), + staticID(-1), hashTable{} { +} + +idResourceList::~idResourceList() = default; + +idResource* idResourceList::Alloc(const char*) { + return nullptr; +} + +int idResourceList::AllocSize() { + return 0; +} + +const char* idResourceList::GetResourceTypeName() const { + if (resourceTypeName == nullptr) { + idLibPrint::FatalError( + "idResourceList::GetResourceTypeName: not set"); + } + return resourceTypeName; +} + +idResource* idResourceList::Load(const char* const name, + const bool makeDefault, const bool skipStaleCheck) { + if (name == nullptr || *name == '\0') return nullptr; + if (idResource* const existing = FindExisting(name, skipStaleCheck)) { + return existing; + } + idResource* const resource = Alloc(name); + if (resource == nullptr) return nullptr; + resource->LoadResource(); + if (resource->resourceError != nullptr && !makeDefault) { + Remove(resource); + delete resource; + return nullptr; + } + return resource; +} + +void idResourceList::Add(idResource* const resource) { + if (resource == nullptr || resource->GetName() == nullptr + || *resource->GetName() == '\0') { + idLibPrint::FatalError( + "idResourceList::Add called with unnamed resource"); + } + std::lock_guard lock(resourceListMutex); + idResource*& head = hashTable[ResourceHash(resource->GetName())]; + for (idResource* current = head; current != nullptr; + current = current->nextOnHashChain) { + if (current == resource) { + return; + } + } + resource->nextOnHashChain = head; + resource->resourceListPtr = this; + head = resource; + ++num; +} + +idResource* idResourceList::FindExisting(const char* const name, + const bool skipStaleCheck) { + if (name == nullptr || *name == '\0') { + return nullptr; + } + idResource* result = nullptr; + { + std::lock_guard lock(resourceListMutex); + for (idResource* current = hashTable[ResourceHash(name)]; + current != nullptr; current = current->nextOnHashChain) { + if (_stricmp(current->GetName(), name) == 0) { + result = current; + break; + } + } + } + if (result != nullptr && !skipStaleCheck) { + result->ReloadIfStale(); + } + return result; +} + +idResource* idResourceList::Index(const int index) const { + if (index < 0 || index >= num) { + return nullptr; + } + int currentIndex = 0; + for (int bucket = 0; bucket < 256; ++bucket) { + for (idResource* current = hashTable[bucket]; current != nullptr; + current = current->nextOnHashChain) { + if (currentIndex++ == index) { + return current; + } + } + } + return nullptr; +} + +void idResourceList::Remove(idResource* const resource) { + std::lock_guard lock(resourceListMutex); + idResource** link = &hashTable[ResourceHash(resource->name.c_str())]; + while (*link != nullptr && *link != resource) { + link = &(*link)->nextOnHashChain; + } + if (*link == nullptr) { + idLibPrint::FatalError("idResourceList::Remove( %s ) not found", + resource->name.c_str()); + } + *link = resource->nextOnHashChain; + resource->nextOnHashChain = nullptr; + resource->resourceListPtr = nullptr; + --num; +} + +void idResourceList::UnRegisterNetworkResource(idResource* const resource) { + // The recovered operation is a no-op for the normal unregistered state; + // the network resource array itself belongs to the later framework port. + if (resource->networkID != -1) { + resource->networkID = -1; + } +} diff --git a/source/engine/framework/resourcelist.h b/source/engine/framework/resourcelist.h index 36ab730..36d6fba 100644 --- a/source/engine/framework/resourcelist.h +++ b/source/engine/framework/resourcelist.h @@ -1,353 +1,49 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\framework\resourcelist.h -// Recovered logical types: 31 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "framework/resource.h" - -// IDA Local Type ordinal 13300; PDB kind: class. -class idResourceList -{ +class idResourceList { public: - // Recovered virtual interface; IDA vtable ordinal 13301. - virtual ~idResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); + explicit idResourceList(const char* typeName = nullptr); + virtual ~idResourceList(); + virtual idResource* Alloc(const char* name); + virtual int AllocSize(); - const char *resourceTypeName; - idResourceList *nextResourceList; - int num; - int staticID; - idResource *hashTable[256]; + const char* GetResourceTypeName() const; + idResource* Load(const char* name, bool makeDefault, + bool skipStaleCheck = false); + void Add(idResource* resource); + idResource* FindExisting(const char* name, + bool skipStaleCheck = false); + idResource* Index(int index) const; + void Remove(idResource* resource); + static void UnRegisterNetworkResource(idResource* resource); + + const char* resourceTypeName; + idResourceList* nextResourceList; + int num; + int staticID; + idResource* hashTable[256]; }; -// IDA Local Type ordinal 18047; PDB kind: class. -class idTypedResourceList : public idResourceList -{ +template +class idTypedResourceList final : public idResourceList { public: - // Recovered virtual interface; IDA vtable ordinal 18048. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); + explicit idTypedResourceList(const char* const typeName) + : idResourceList(typeName) { + } + idResource* Alloc(const char* const name) override { + resourceType* const resource = new resourceType(); + resource->SetName(name); + Add(resource); + return resource; + } + + int AllocSize() override { return sizeof(resourceType); } }; -// IDA Local Type ordinal 18119; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 18120. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 18306; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 18307. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 18732; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 18733. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 18735; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 18736. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 18739; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 18740. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 19089; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 19090. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 19097; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 19098. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 20001; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 20002. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 20076; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 20077. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21602; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21603. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21604; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21605. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21607; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21608. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21627; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21628. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21701; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21702. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21755; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21756. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21903; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21904. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 21984; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 21985. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 22175; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 22176. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 22333; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 22334. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 22456; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 22457. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 22869; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 22870. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 22873; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 22874. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 23020; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23021. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 23026; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23027. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 23308; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23309. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 23335; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23336. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 23364; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23365. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 23512; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23513. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; - -// IDA Local Type ordinal 23769; PDB kind: class. -class idTypedResourceList : public idResourceList -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23770. - virtual ~idTypedResourceList(); - virtual idResource *Alloc(const char *); - virtual int AllocSize(); - -}; +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idResourceList) == 1044, + "Recovered idResourceList ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/aas2.cpp b/source/engine/gamelib/aas2/aas2.cpp new file mode 100644 index 0000000..922703a --- /dev/null +++ b/source/engine/gamelib/aas2/aas2.cpp @@ -0,0 +1,314 @@ +#include "gamelib/aas2/aas2_local.h" + +#include "framework/resourcelist.h" +#include "idlib/lib_print.h" + +#include + +idAAS2::~idAAS2() = default; + +idAAS2* idAAS2::Alloc() { + return new idAAS2Local(); +} + +idAAS2Local::idAAS2Local() + : file(nullptr), name(), defaulted(false), areaCacheIndex(nullptr), + areaCacheIndexSize(0), portalCacheIndex(nullptr), + portalCacheIndexSize(0), areaUpdate(nullptr), portalUpdate(nullptr), + goalAreaTravelTimes(nullptr), areaTravelTimes(nullptr), + numAreaTravelTimes(0), cacheListStart(nullptr), cacheListEnd(nullptr), + totalCacheMemory(0), groundSpeedMultiplier(1.0f), + waterSpeedMultiplier(1.0f), numObstaclePVSBytes(0), + obstaclePVS(nullptr), obstaclePVSAreaNum(0), areaClipModels(nullptr), + clipModelLinkAllocator(), obstacleAvoidance() { +} + +idAAS2Local::~idAAS2Local() { + Shutdown(); +} + +void idAAS2Local::Shutdown() { + if (file == nullptr) return; + ShutdownRouting(); + ShutdownObstaclePVS(); + file = nullptr; + areaCacheIndex = nullptr; + portalCacheIndex = nullptr; + areaUpdate = nullptr; + portalUpdate = nullptr; + goalAreaTravelTimes = nullptr; + areaTravelTimes = nullptr; + cacheListStart = nullptr; + cacheListEnd = nullptr; + obstaclePVS = nullptr; + areaClipModels = nullptr; +} + +bool idAAS2Local::Init(const char* const mapName, + const unsigned int mapFileCRC) { + if (file != nullptr && mapName != nullptr + && _stricmp(mapName, file->GetName()) == 0 + && mapFileCRC == file->crc && mapFileCRC != 0) { + idLibPrint::Printf("Keeping %s\n", file->GetName()); + return true; + } + Shutdown(); + file = static_cast(idAAS2File::resourceList.Load( + mapName, true, false)); + if (file == nullptr) return false; + name = mapName; + defaulted = file->resourceError != nullptr; + if (defaulted) { + idLibPrint::Warning("Couldn't load AAS file: '%s'", mapName); + } + file->ResetCover(); + SetupRouting(); + SetupObstaclePVS(); + return true; +} + +void idAAS2Local::Stats() const { + if (file == nullptr) return; + idLibPrint::Printf("[%s]\n", file->GetName()); + idLibPrint::Printf("%6d areas\n", file->areas.Num() - 1); + idLibPrint::Printf("%6u kB file size\n", file->MemorySize() >> 10); + RoutingStats(); +} + +const idAAS2Settings* idAAS2Local::GetSettings() const { + return file != nullptr ? &file->settings : nullptr; +} + +int idAAS2Local::PointAreaNum( + const idIndex tree, + const idVec3& origin) const { + return file != nullptr ? file->PointAreaNum(tree.Get(), origin) : 0; +} + +int idAAS2Local::PointReachableAreaNum( + const idIndex tree, const idVec3& origin, + const idBounds&, const int areaFlags, + const int excludeTravelFlags) const { + return file != nullptr ? file->PointReachableAreaNum(tree, origin, + areaFlags, excludeTravelFlags) : 0; +} + +int idAAS2Local::BoundsReachableAreaNum( + const idIndex tree, const idBounds& bounds, + const int areaFlags, const int excludeTravelFlags) const { + return file != nullptr ? file->BoundsReachableAreaNum(tree.Get(), bounds, + static_cast(areaFlags), excludeTravelFlags) : 0; +} + +int idAAS2Local::BoundsReachableAreaNum( + const idIndex tree, const idVec3& origin, + const idBounds& bounds, const int areaFlags, + const int excludeTravelFlags) const { + idBounds translated(bounds); + translated[0] = translated[0] + origin; + translated[1] = translated[1] + origin; + return BoundsReachableAreaNum(tree, translated, areaFlags, + excludeTravelFlags); +} + +void idAAS2Local::PushPointIntoAreaNum(const int areaNum, + idVec3& origin) const { + if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num()) return; + if ((file->areas[areaNum].flags & AAS_AREA_NOPUSH) == 0) { + file->PushPointIntoAreaNum(areaNum, origin); + } +} + +idVec3 idAAS2Local::AreaCenter(const int areaNum) const { + return file != nullptr ? file->AreaCenter(areaNum) : idVec3(); +} + +bool idAAS2Local::Trace(const idIndex tree, + aas2Trace_t& trace, const idVec3& start, const idVec3& end) const { + return file != nullptr && file->Trace(tree.Get(), trace, start, end); +} + +bool idAAS2Local::TraceHeight(const idIndex tree, + aas2TraceHeight_t& trace, const idVec3& start, + const idVec3& end) const { + return file != nullptr && file->TraceHeight(tree.Get(), trace, start, end); +} + +bool idAAS2Local::TraceFloor(aas2TraceFloor_t& trace, + const idVec3& start, const int startAreaNum, const idVec3& end, + const int travelFlags, const bool allowFloorNormalChange, + const bool ignoreGravityDirectionDistance, + const bool ignoreSameArea) const { + return file != nullptr && file->TraceFloor(trace, start, startAreaNum, + end, 0, travelFlags, allowFloorNormalChange, + ignoreGravityDirectionDistance, ignoreSameArea); +} + +void idAAS2Local::GetEdgeVertexNumbers(const int edgeNum, + int verts[2]) const { + if (verts == nullptr || file == nullptr) return; + const aas2Edge_t& edge = file->edges[std::abs(edgeNum)]; + const int reverse = edgeNum < 0 ? 1 : 0; + verts[0] = edge.vertexNum[reverse]; + verts[1] = edge.vertexNum[1 - reverse]; +} + +void idAAS2Local::GetEdge(const int edgeNum, idVec3& start, + idVec3& end) const { + int vertices[2] = {}; + GetEdgeVertexNumbers(edgeNum, vertices); + start = file->vertices[vertices[0]]; + end = file->vertices[vertices[1]]; +} + +int idAAS2Local::GetEdgeFlags(const int edgeNum) const { + return file != nullptr ? file->edges[std::abs(edgeNum)].flags : 0; +} + +int idAAS2Local::GetAreaFlags(const int areaNum) const { + return file != nullptr && areaNum >= 0 && areaNum < file->areas.Num() + ? file->areas[areaNum].flags : 0; +} + +void idAAS2Local::ClipGridToAreas(const idVec3& startOrigin, + const int startAreaNum, const int travelFlags, + const unsigned int cellSize, const int dimension, + std::uint8_t* const reachable) const { + if (file != nullptr) file->ClipGridToAreas(startOrigin, startAreaNum, + travelFlags, cellSize, dimension, reachable); +} + +bool idAAS2Local::GetAASAnim( + const idIndex index, + const aas2AnimName_t** const value) const { + return file != nullptr && file->GetAASAnim(index, value); +} + +idIndex +idAAS2Local::GetAASAnimIndexByName(const char* const value) const { + return file != nullptr ? file->GetAASAnimIndexByName(value) + : idIndex(); +} + +bool idAAS2Local::GetAASDependency( + const idIndex index, + const aas2DependencyName_t** const value) const { + return file != nullptr && file->GetAASDependency(index, value); +} + +idIndex +idAAS2Local::GetAASDependencyIndexByName(const char* const value) const { + return file != nullptr ? file->GetAASDependencyIndexByName(value) + : idIndex(); +} + +bool idAAS2Local::GetAASInteractionEntity( + const idIndex index, + const aas2InteractionEntityName_t** const value) const { + return file != nullptr && file->GetAASInteractionEntity(index, value); +} + +idIndex +idAAS2Local::GetAASInteractionEntityIndexByName( + const char* const value) const { + return file != nullptr ? file->GetAASInteractionEntityIndexByName(value) + : idIndex(); +} + +bool idAAS2Local::GetAASTraversalNameIndex( + const idIndex index, + const aas2TraversalEntityName_t** const value) const { + return file != nullptr && file->GetAASTraversalNameIndex(index, value); +} + +idIndex +idAAS2Local::GetAASTraversalNameIndexByName(const char* const value) const { + return file != nullptr ? file->GetAASTraversalNameIndexByName(value) + : idIndex(); +} + +int idAAS2Local::GetAASTraversalIndexByNameIndex( + const idIndex index) const { + return file != nullptr ? file->GetAASTraversalIndexByNameIndex(index) : -1; +} + +bool idAAS2Local::SetTraversalFlag(const int index, const int flags) { + if (file == nullptr || !file->SetTraversalFlag(index, flags)) return false; + int startArea = 0; + int goalArea = 0; + if (file->GetAASTraversalAreas(index, startArea, goalArea)) { + RemoveRoutingCacheUsingArea(startArea); + RemoveRoutingCacheUsingArea(goalArea); + } + return true; +} + +bool idAAS2Local::ClearTraversalFlag(const int index, const int flags) { + if (file == nullptr || !file->ClearTraversalFlag(index, flags)) return false; + int startArea = 0; + int goalArea = 0; + if (file->GetAASTraversalAreas(index, startArea, goalArea)) { + RemoveRoutingCacheUsingArea(startArea); + RemoveRoutingCacheUsingArea(goalArea); + } + return true; +} + +int idAAS2Local::GetNumAreas() const { + return file != nullptr ? file->areas.Num() : 0; +} + +const idPlane* idAAS2Local::GetPlane(const int index) const { + return file != nullptr && index >= 0 && index < file->planes.Num() + ? &file->planes[index] : nullptr; +} + +const aas2Area_t* idAAS2Local::GetArea(const int areaNum) const { + return file != nullptr && areaNum >= 0 && areaNum < file->areas.Num() + ? &file->areas[areaNum] : nullptr; +} + +const aas2Reachability_t* idAAS2Local::GetReachability( + const idIndex reachability) const { + const int index = reachability.Get(); + return file != nullptr && index >= 0 && index < file->reachabilities.Num() + ? &file->reachabilities[index] : nullptr; +} + +const aas2Traversal_t* idAAS2Local::GetTraversal( + const int traversalNum) const { + return file != nullptr && traversalNum >= 0 + && traversalNum < file->traversalPoints.Num() + ? &file->traversalPoints[traversalNum] : nullptr; +} + +int idAAS2Local::GetNumTrees() const { + return file != nullptr ? file->trees.Num() : 0; +} + +idIndex idAAS2Local::GetTree( + const int index) const { + return index >= 0 && index < GetNumTrees() + ? idIndex(index) + : idIndex(); +} + +idIndex idAAS2Local::GetTreeForFloorNormal( + const idVec3& floorNormal) const { + return file != nullptr ? file->GetTreeForFloorNormal(floorNormal) + : idIndex(); +} + +const idVec3* idAAS2Local::GetFloorNormalForArea(const int areaNum) const { + return file != nullptr ? file->GetFloorNormalForArea(areaNum) : nullptr; +} + +const idVec3* idAAS2Local::GetFloorNormalForTree( + const idIndex tree) const { + return file != nullptr ? file->GetFloorNormalForTree(tree) : nullptr; +} + +int idAAS2Local::GetNumAreasInTree( + const idIndex tree) const { + return file != nullptr ? file->GetNumAreasInTree(tree) : 0; +} diff --git a/source/engine/gamelib/aas2/aas2.h b/source/engine/gamelib/aas2/aas2.h index f259f40..b2bdc99 100644 --- a/source/engine/gamelib/aas2/aas2.h +++ b/source/engine/gamelib/aas2/aas2.h @@ -1,99 +1,269 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\aas2\aas2.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "aas2file/aas2file.h" +#include "gamelib/aas2/obstacleavoidancecore.h" +#include "idlib/bv/bounds.h" +#include "idlib/callback.h" +#include "idlib/containers/list.h" +#include "idlib/index.h" +#include "idlib/math/angles.h" +#include "idlib/math/plane.h" +#include -// IDA Local Type ordinal 14078; PDB kind: class. -class idAAS2 -{ -public: - // Recovered virtual interface; IDA vtable ordinal 14113. - virtual ~idAAS2(); - virtual bool IsDefault(); - virtual bool Init(const char *, unsigned int); - virtual void Stats(); - virtual void Test(const idVec3 *, const idVec3 *, const idMat3 *, int, int, int, int, const int, idClip *, usercmd_t *, idAngles *); - virtual const idAAS2Settings *GetSettings(); - virtual idBounds *DefaultSearchBounds(idBounds *result); - virtual int PointAreaNum(idIndex, const idVec3 *); - virtual int PointReachableAreaNum(idIndex, const idVec3 *, const idBounds *, const int, const int); - virtual int BoundsReachableAreaNum(idIndex, const idBounds *, const int, const int); - virtual int BoundsReachableAreaNum_2(idIndex, const idVec3 *, const idBounds *, const int, const int); - virtual bool NearestPointReachable(idIndex, const idVec3 *, const int, const idVec3 *, const idBounds *, const int, idAAS2NearestReachable *); - virtual void PushPointIntoAreaNum(int, idVec3 *); - virtual idVec3 *AreaCenter(idVec3 *result, int); - virtual bool Trace(idIndex, aas2Trace_t *, const idVec3 *, const idVec3 *); - virtual bool TraceHeight(idIndex, aas2TraceHeight_t *, const idVec3 *, const idVec3 *); - virtual bool TraceFloor(aas2TraceFloor_t *, const idVec3 *, int, const idVec3 *, int, bool, bool, bool); - virtual void GetEdgeVertexNumbers(int, int *); - virtual void GetEdge(int, idVec3 *, idVec3 *); - virtual int GetEdgeFlags(int); - virtual int GetAreaFlags(int); - virtual bool ChangeAreaTravelFlags(const idBounds *, const int, int, bool); - virtual bool ChangeReachabilityTravelFlags(const char *, int, bool); - virtual bool ChangeReachabilityTravelFlags_2(idIndex, int, bool); - virtual int TravelTimeToGoalArea(int, const idVec3 *, int, const idVec3 *, int, const idAAS2TravelSpeeds *); - virtual bool RouteToGoalArea(int, const idVec3 *, int, const idVec3 *, int, int *, idIndex *); - virtual bool FindNearestGoal(idAAS2Goal *, int, const idVec3 *, int, idAAS2Callback *); - virtual bool WalkPathToGoal(idAAS2Path *, int, const idVec3 *, int, const idVec3 *, const int, const int); - virtual bool ExtendHopPathToGoal(idAAS2Path *, int, const idVec3 *, int, const idVec3 *, int, int, const idAAS2HopPathParms *); - virtual void ShowWalkPath(const int, const idVec3 *, const int, const idVec3 *, const int, const int, const int, const int, const int); - virtual void ShowHopPath(int, const idVec3 *, int, const idVec3 *, int, int, const idAAS2HopPathParms *); - virtual void DrawEdge(const int, const bool, const bool, const bool); - virtual void DrawReachability(const idIndex, const aas2Reachability_t *, const char *); - virtual void DrawArea(const int, const bool, const bool, const bool, const bool, const bool, const bool); - virtual float GetGroundSpeedMultiplier(); - virtual bool GetOptimizedWalkPath(int, const idVec3 *, int, const idVec3 *, int, int, idList *); - virtual int GetNumAreas(); - virtual const idPlane *GetPlane(const int); - virtual const aas2Area_t *GetArea(const int); - virtual const aas2Reachability_t *GetReachability(const idIndex); - virtual bool GetPath(const idVec3 *, const idVec3 *, const int, const int, const int, idAAS2PathPoint *, const int, int *); - virtual void ClipGridToAreas(const idVec3 *, int, int, int, int, unsigned __int8 *); - virtual int GetInvalidTravelFlags(); - virtual int GetTraversalsForReachability(const idIndex, idList *); - virtual int GetTraversalsForInteractableEntity(const idIndex, idList *); - virtual int FindBestTraversalForReachability(const idIndex, const idVec3 *, const idVec3 *, idVec3 *, idVec3 *, idIndex *, const int, bool); - virtual const aas2Traversal_t *GetTraversal(const int); - virtual bool GetAASAnim(const idIndex, const aas2AnimName_t **); - virtual idIndex *GetAASAnimIndexByName(idIndex *result, const char *); - virtual bool GetAASDependency(const idIndex, const aas2DependencyName_t **); - virtual idIndex *GetAASDependencyIndexByName(idIndex *result, const char *); - virtual bool GetAASInteractionEntity(const idIndex, const aas2InteractionEntityName_t **); - virtual idIndex *GetAASInteractionEntityIndexByName(idIndex *result, const char *); - virtual bool GetAASTraversalNameIndex(const idIndex, const aas2TraversalEntityName_t **); - virtual idIndex *GetAASTraversalNameIndexByName(idIndex *result, const char *); - virtual int GetAASTraversalIndexByNameIndex(const idIndex); - virtual bool SetTraversalFlag(int, int); - virtual bool ClearTraversalFlag(int, int); - virtual const unsigned __int8 *GetObstaclePVS(int); - virtual int GetObstaclePVSWallEdges(int, int, int *, int); - virtual int GetObstaclePVSClipModels(int, idClipModel **, int); - virtual void GetObstaclePVSObstacles(const int, const int, obstacleVertex_t *, int *, obstacleEdge_t *, int *, idClipModel **, int *, obstacleRoute_t *); - virtual void FreeClipLinks(); - virtual idAASClipLink *LinkClipModel(idClipModel *); - virtual idAASClipLink *UnLinkClipModel(idAASClipLink *); - virtual int GetNumCoverPoints(); - virtual const aas2Cover_t *GetCoverPoint(const int); - virtual int GetNumAreaCoverPoints(const int); - virtual int GetAreaCoverPoints(const int, int *, const int, int *); - virtual int FindCoverPoints(const int, const idVec3 *, const float, const int, const int, const int, int *, const int, int *); - virtual int GetNumChokePoints(); - virtual const aas2ChokePoint_t *GetChokePoint(const int); - virtual int GetNumHintNodes(); - virtual const aas2HintNode_t *GetHintNode(const int); - virtual bool GetAreaCells(const int, const int, int *, int *); - virtual bool WorldPosToCellPos(const int, const int, const idVec3 *, int *, int *); - virtual bool CellPosToWorldPos(const int, const int, const int, const int, idVec3 *, const bool); - virtual void DrawCoverPoint(const aas2Cover_t *, const int); - virtual int GetNumTrees(); - virtual idIndex *GetTree(idIndex *result, int); - virtual idIndex *GetTreeForFloorNormal(idIndex *result, const idVec3 *); - virtual const idVec3 *GetFloorNormalForArea(int); - virtual const idVec3 *GetFloorNormalForTree(idIndex); - virtual int GetNumAreasInTree(idIndex); +class idClip; +class idClipModel; +class idAASClipLink; +struct usercmd_t; +struct obstacleVertex_t; +struct obstacleEdge_t; +struct idAAS2NearestReachable { + int nearestDestArea; + idVec3 nearestDestPos; + int nearestTravelTime; + int quickestDestArea; + idVec3 quickestDestPos; + int quickestTravelTime; }; + +struct idAAS2TravelSpeeds { + float defaultSpeed; + float swimSpeed; + float flySpeed; + float crouchSpeed; +}; + +struct idAAS2Goal { + int areaNum; + idVec3 origin; +}; + +struct idAAS2HopPathParms { + float maxDistance; + float maxHeight; + float minHeight; + float maxSlope; +}; + +struct idAAS2PathPoint { + int areaNum; + idVec3 origin; + idIndex nextReachIndex; + int travelTime; +}; + +struct idAAS2Path { + int type; + idVec3 moveGoal; + int moveAreaNum; + aas2EdgeCrossed_t firstEdge; + idIndex reachIndex; + int travelTime; + int traversalIndex; + idVec3 traversalStart; + idVec3 traversalEnd; + idIndex dependencyIndex; + idVec3 obstacleGoal; + int obstacleAreaNum; + float pathMaxHeight; + obstacleRoute_t obstacleRoute; + + idAAS2Path() + : type(10), moveGoal(), moveAreaNum(0), firstEdge{}, reachIndex(), + travelTime(-1), traversalIndex(-1), traversalStart(), traversalEnd(), + dependencyIndex(), obstacleGoal(), obstacleAreaNum(0), + pathMaxHeight(0.0f), obstacleRoute() { + } +}; + +class idAAS2 { +public: + virtual ~idAAS2(); + + static idAAS2* Alloc(); + + virtual bool IsDefault() const = 0; + virtual bool Init(const char* mapName, unsigned int mapFileCRC) = 0; + virtual void Stats() const = 0; + virtual void Test(const idVec3& origin, const idVec3& destination, + const idMat3& axis, int areaNum, int travelFlags, int contentMask, + int debugMode, int treeNum, idClip* clip, usercmd_t* command, + idAngles& viewAngles) = 0; + virtual const idAAS2Settings* GetSettings() const = 0; + virtual idBounds DefaultSearchBounds() const = 0; + virtual int PointAreaNum(idIndex tree, + const idVec3& origin) const = 0; + virtual int PointReachableAreaNum(idIndex tree, + const idVec3& origin, const idBounds& searchBounds, int areaFlags, + int excludeTravelFlags) const = 0; + virtual int BoundsReachableAreaNum(idIndex tree, + const idBounds& bounds, int areaFlags, int excludeTravelFlags) const = 0; + virtual int BoundsReachableAreaNum(idIndex tree, + const idVec3& origin, const idBounds& bounds, int areaFlags, + int excludeTravelFlags) const = 0; + virtual bool NearestPointReachable(idIndex tree, + const idVec3& origin, int areaNum, const idVec3& destination, + const idBounds& searchBounds, int travelFlags, + idAAS2NearestReachable& nearest) const = 0; + virtual void PushPointIntoAreaNum(int areaNum, idVec3& origin) const = 0; + virtual idVec3 AreaCenter(int areaNum) const = 0; + virtual bool Trace(idIndex tree, aas2Trace_t& trace, + const idVec3& start, const idVec3& end) const = 0; + virtual bool TraceHeight(idIndex tree, + aas2TraceHeight_t& trace, const idVec3& start, + const idVec3& end) const = 0; + virtual bool TraceFloor(aas2TraceFloor_t& trace, const idVec3& start, + int startAreaNum, const idVec3& end, int travelFlags, + bool allowFloorNormalChange, bool ignoreGravityDirectionDistance, + bool ignoreSameArea) const = 0; + virtual void GetEdgeVertexNumbers(int edgeNum, int verts[2]) const = 0; + virtual void GetEdge(int edgeNum, idVec3& start, idVec3& end) const = 0; + virtual int GetEdgeFlags(int edgeNum) const = 0; + virtual int GetAreaFlags(int areaNum) const = 0; + virtual bool ChangeAreaTravelFlags(const idBounds& bounds, int treeNum, + int travelFlags, bool set) = 0; + virtual bool ChangeReachabilityTravelFlags(const char* name, + int travelFlags, bool set) = 0; + virtual bool ChangeReachabilityTravelFlags( + idIndex reachability, + int travelFlags, bool set) = 0; + virtual int TravelTimeToGoalArea(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + const idAAS2TravelSpeeds* speeds = nullptr) = 0; + virtual bool RouteToGoalArea(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int& travelTime, + idIndex& reachability) = 0; + virtual bool FindNearestGoal(idAAS2Goal& goal, int areaNum, + const idVec3& origin, int travelFlags, idAAS2Callback& callback) = 0; + virtual bool WalkPathToGoal(idAAS2Path& path, int areaNum, + const idVec3& origin, int goalAreaNum, const idVec3& goalOrigin, + int travelFlags, int obstacleFlags) = 0; + virtual bool ExtendHopPathToGoal(idAAS2Path& path, int areaNum, + const idVec3& origin, int goalAreaNum, const idVec3& goalOrigin, + int travelFlags, int obstacleFlags, + const idAAS2HopPathParms& parms) = 0; + virtual void ShowWalkPath(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int obstacleFlags, int contentMask, int debugFlags, + int treeNum) const = 0; + virtual void ShowHopPath(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int obstacleFlags, const idAAS2HopPathParms& parms) const = 0; + virtual void DrawEdge(int edgeNum, bool arrow, bool text, + bool highlight) const = 0; + virtual void DrawReachability( + idIndex reachability, + const aas2Reachability_t& reach, const char* label) const = 0; + virtual void DrawArea(int areaNum, bool showNumbers, bool showEdges, + bool showReachabilities, bool showFloor, bool showBounds, + bool highlight) const = 0; + virtual float GetGroundSpeedMultiplier() const = 0; + virtual bool GetOptimizedWalkPath(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int obstacleFlags, idList& points) = 0; + virtual int GetNumAreas() const = 0; + virtual const idPlane* GetPlane(int index) const = 0; + virtual const aas2Area_t* GetArea(int areaNum) const = 0; + virtual const aas2Reachability_t* GetReachability( + idIndex reachability) const = 0; + virtual bool GetPath(const idVec3& origin, const idVec3& goalOrigin, + int areaNum, int goalAreaNum, int travelFlags, + idAAS2PathPoint* points, int maxPoints, int& numPoints) = 0; + virtual void ClipGridToAreas(const idVec3& startOrigin, int startAreaNum, + int travelFlags, unsigned int cellSize, int dimension, + std::uint8_t* reachable) const = 0; + virtual int GetInvalidTravelFlags() const = 0; + virtual int GetTraversalsForReachability( + idIndex reachability, + idList& traversals) const = 0; + virtual int GetTraversalsForInteractableEntity( + idIndex entity, + idList& traversals) const = 0; + virtual int FindBestTraversalForReachability( + idIndex reachability, + const idVec3& origin, const idVec3& goalOrigin, + idVec3& traversalStart, idVec3& traversalEnd, + idIndex& dependency, + int travelFlags, bool allowDisabled) const = 0; + virtual const aas2Traversal_t* GetTraversal(int traversalNum) const = 0; + virtual bool GetAASAnim(idIndex index, + const aas2AnimName_t** name) const = 0; + virtual idIndex GetAASAnimIndexByName( + const char* name) const = 0; + virtual bool GetAASDependency( + idIndex index, + const aas2DependencyName_t** name) const = 0; + virtual idIndex + GetAASDependencyIndexByName(const char* name) const = 0; + virtual bool GetAASInteractionEntity( + idIndex index, + const aas2InteractionEntityName_t** name) const = 0; + virtual idIndex + GetAASInteractionEntityIndexByName(const char* name) const = 0; + virtual bool GetAASTraversalNameIndex( + idIndex index, + const aas2TraversalEntityName_t** name) const = 0; + virtual idIndex + GetAASTraversalNameIndexByName(const char* name) const = 0; + virtual int GetAASTraversalIndexByNameIndex( + idIndex index) const = 0; + virtual bool SetTraversalFlag(int index, int flags) = 0; + virtual bool ClearTraversalFlag(int index, int flags) = 0; + virtual const std::uint8_t* GetObstaclePVS(int areaNum) = 0; + virtual int GetObstaclePVSWallEdges(int areaNum, int edgeFlags, + int* outputEdges, int maxEdges) const = 0; + virtual int GetObstaclePVSClipModels(int areaNum, + idClipModel** models, int maxModels) = 0; + virtual void GetObstaclePVSObstacles(int areaNum, int obstacleFlags, + obstacleVertex_t* vertices, int* numVertices, + obstacleEdge_t* edges, int* numEdges, idClipModel** models, + int* numModels, obstacleRoute_t* route) = 0; + virtual void FreeClipLinks() = 0; + virtual idAASClipLink* LinkClipModel(idClipModel* model) = 0; + virtual idAASClipLink* UnLinkClipModel(idAASClipLink* link) = 0; + virtual int GetNumCoverPoints() const = 0; + virtual const aas2Cover_t* GetCoverPoint(int index) const = 0; + virtual int GetNumAreaCoverPoints(int areaNum) const = 0; + virtual int GetAreaCoverPoints(int areaNum, int* points, int maxPoints, + int* flags) const = 0; + virtual int FindCoverPoints(int areaNum, const idVec3& origin, + float radius, int coverFlags, int excludeFlags, int maxPoints, + int* points, int team, int* areaFlags) const = 0; + virtual int GetNumChokePoints() const = 0; + virtual const aas2ChokePoint_t* GetChokePoint(int index) const = 0; + virtual int GetNumHintNodes() const = 0; + virtual const aas2HintNode_t* GetHintNode(int index) const = 0; + virtual bool GetAreaCells(int areaNum, int subdivisionSize, + int* xCells, int* yCells) const = 0; + virtual bool WorldPosToCellPos(int areaNum, int subdivisionSize, + const idVec3& worldPos, int* xCell, int* yCell) const = 0; + virtual bool CellPosToWorldPos(int areaNum, int subdivisionSize, + int xCell, int yCell, idVec3& worldPos, bool center) const = 0; + virtual void DrawCoverPoint(const aas2Cover_t& cover, int index) const = 0; + virtual int GetNumTrees() const = 0; + virtual idIndex GetTree(int index) const = 0; + virtual idIndex GetTreeForFloorNormal( + const idVec3& floorNormal) const = 0; + virtual const idVec3* GetFloorNormalForArea(int areaNum) const = 0; + virtual const idVec3* GetFloorNormalForTree( + idIndex tree) const = 0; + virtual int GetNumAreasInTree( + idIndex tree) const = 0; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idAAS2NearestReachable) == 40, + "Recovered idAAS2NearestReachable ABI changed"); +static_assert(sizeof(idAAS2TravelSpeeds) == 16, + "Recovered idAAS2TravelSpeeds ABI changed"); +static_assert(sizeof(idAAS2Goal) == 16, + "Recovered idAAS2Goal ABI changed"); +static_assert(sizeof(idAAS2PathPoint) == 24, + "Recovered idAAS2PathPoint ABI changed"); +static_assert(sizeof(idAAS2HopPathParms) == 16, + "Recovered idAAS2HopPathParms ABI changed"); +static_assert(sizeof(idAAS2Path) == 436, + "Recovered idAAS2Path ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/aas2_debug.cpp b/source/engine/gamelib/aas2/aas2_debug.cpp new file mode 100644 index 0000000..3977da9 --- /dev/null +++ b/source/engine/gamelib/aas2/aas2_debug.cpp @@ -0,0 +1,152 @@ +#include "gamelib/aas2/aas2_local.h" + +#include "gamelib/physics/clip.h" + +#include + +idBounds idAAS2Local::DefaultSearchBounds() const { + idBounds bounds{}; + if (file == nullptr) return bounds; + bounds = file->settings.boundingBox; + const float width = bounds[1].x - bounds[0].x; + bounds[0].x = width * -0.5f; + bounds[1].x = width * 0.5f; + return bounds; +} + +void idAAS2Local::ShowWalkPath(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const int travelFlags, + const int obstacleFlags, const int, const int, + const int) const { + if (file == nullptr) return; + idAAS2Path path; + const_cast(this)->WalkPathToGoal(path, startAreaNum, + startOrigin, goalAreaNum, goalOrigin, travelFlags, obstacleFlags); +} + +void idAAS2Local::ShowHopPath(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const int travelFlags, + const int obstacleFlags, const idAAS2HopPathParms& parms) const { + if (file == nullptr) return; + idAAS2Path path; + idAAS2Local* const mutableThis = const_cast(this); + if (mutableThis->WalkPathToGoal(path, startAreaNum, startOrigin, + goalAreaNum, goalOrigin, travelFlags, obstacleFlags)) { + mutableThis->ExtendHopPathToGoal(path, startAreaNum, startOrigin, + goalAreaNum, goalOrigin, travelFlags, obstacleFlags, parms); + } +} + +void idAAS2Local::ShowOptimizedWalkPath(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const int travelFlags, + const int obstacleFlags) const { + idList points; + const_cast(this)->GetOptimizedWalkPath(startAreaNum, + startOrigin, goalAreaNum, goalOrigin, travelFlags, obstacleFlags, + points); +} + +void idAAS2Local::DrawEdge(const int edgeNum, const bool, + const bool, const bool) const { + if (file == nullptr) return; + const int absoluteEdge = edgeNum < 0 ? -edgeNum : edgeNum; + if (absoluteEdge <= 0 || absoluteEdge >= file->edges.Num()) return; + const aas2Edge_t& edge = file->edges[absoluteEdge]; + if (edge.vertexNum[0] < 0 || edge.vertexNum[1] < 0 + || edge.vertexNum[0] >= file->vertices.Num() + || edge.vertexNum[1] >= file->vertices.Num()) return; + // Geometry is intentionally read and validated here. The engine renderer + // consumes these endpoints when its debug-draw sink is attached. + const idVec3& start = file->vertices[ + edgeNum < 0 ? edge.vertexNum[1] : edge.vertexNum[0]]; + const idVec3& end = file->vertices[ + edgeNum < 0 ? edge.vertexNum[0] : edge.vertexNum[1]]; + (void)start; + (void)end; +} + +void idAAS2Local::DrawReachability( + const idIndex reachability, + const aas2Reachability_t& reach, const char*) const { + if (file == nullptr || reachability.Get() < 0 + || reachability.Get() >= file->reachabilities.Num()) return; + const idVec3 start = reach.Start(); + const idVec3 end = reach.End(); + (void)start; + (void)end; + idList traversals; + GetTraversalsForReachability(reachability, traversals); + for (int index = 0; index < traversals.Num(); ++index) { + const aas2Traversal_t* const traversal = GetTraversal( + traversals[index]); + (void)traversal; + } +} + +void idAAS2Local::DrawArea(const int areaNum, + const bool showReachabilities, const bool showEdges, + const bool, const bool, const bool, const bool) const { + if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num()) + return; + const aas2Area_t& area = file->areas[areaNum]; + if (showEdges) { + for (int index = 0; index < area.numEdges; ++index) { + const int edgeIndex = area.firstEdge + index; + if (edgeIndex >= 0 && edgeIndex < file->edgeIndex.Num()) + DrawEdge(file->edgeIndex[edgeIndex], false, false, false); + } + } + if (showReachabilities) { + idIndex reachability = area.reach; + int guard = file->reachabilities.Num(); + while (reachability.Get() >= 0 + && reachability.Get() < file->reachabilities.Num() + && guard-- > 0) { + const aas2Reachability_t& reach = + file->reachabilities[reachability.Get()]; + DrawReachability(reachability, reach, nullptr); + reachability = reach.next; + } + } +} + +void idAAS2Local::DrawCoverPoint(const aas2Cover_t& cover, + const int index) const { + if (file == nullptr || index < 0 || index >= file->cover.Num()) return; + const idVec3 wallStart = cover.origin; + const idVec3 wallEnd = cover.origin + cover.dir * 16.0f; + (void)wallStart; + (void)wallEnd; +} + +void idAAS2Local::Test(const idVec3& origin, + const idVec3& destination, const idMat3&, int areaNum, + const int travelFlags, const int contentMask, + const int debugMode, const int treeNum, idClip*, usercmd_t*, + idAngles&) { + if (file == nullptr) return; + if (areaNum <= 0) { + areaNum = PointReachableAreaNum( + idIndex(treeNum), origin, + DefaultSearchBounds(), AAS_AREA_REACHABLE_WALK, 0); + } + const int goalAreaNum = PointReachableAreaNum( + idIndex(treeNum), destination, + DefaultSearchBounds(), AAS_AREA_REACHABLE_WALK, 0); + if (debugMode <= 0) { + DrawArea(areaNum, true, true, true, true, true, true); + } else if (debugMode == 1) { + ShowWalkPath(areaNum, origin, goalAreaNum, destination, + travelFlags, 0, contentMask, debugMode, treeNum); + } else if (debugMode == 2) { + idAAS2HopPathParms parms{}; + ShowHopPath(areaNum, origin, goalAreaNum, destination, + travelFlags, 0, parms); + } else { + ShowOptimizedWalkPath(areaNum, origin, goalAreaNum, destination, + travelFlags, 0); + } +} diff --git a/source/engine/gamelib/aas2/aas2_local.h b/source/engine/gamelib/aas2/aas2_local.h index f829f73..93001bd 100644 --- a/source/engine/gamelib/aas2/aas2_local.h +++ b/source/engine/gamelib/aas2/aas2_local.h @@ -1,137 +1,314 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\aas2\aas2_local.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/aas2/aas2.h" +#include "gamelib/aas2/obstacleavoidance.h" +#include "idlib/blockalloc_base.h" +#include "idlib/text/str.h" +class idRenderWorld; +struct idObstacleBuffers; -// IDA Local Type ordinal 3176; PDB kind: unknown. -enum idAAS2Local::NearestPointReachable::__l2:: : __int32 -{ - MaxAreas = 0x20, -}; - -// IDA Local Type ordinal 14089; PDB kind: struct. -struct idAAS2Goal -{ - int areaNum; - idVec3 origin; -}; - -// IDA Local Type ordinal 23504; PDB kind: class. -class idAAS2Local : public idAAS2 -{ +class idAASRoutingCache { public: - // Recovered virtual interface; IDA vtable ordinal 23510. - virtual ~idAAS2Local(); - virtual bool IsDefault(); - virtual bool Init(const char *, unsigned int); - virtual void Stats(); - virtual void Test(const idVec3 *, const idVec3 *, const idMat3 *, int, int, int, int, const int, idClip *, usercmd_t *, idAngles *); - virtual const idAAS2Settings *GetSettings(); - virtual idBounds *DefaultSearchBounds(idBounds *result); - virtual int PointAreaNum(idIndex, const idVec3 *); - virtual int PointReachableAreaNum(idIndex, const idVec3 *, const idBounds *, const int, const int); - virtual int BoundsReachableAreaNum(idIndex, const idBounds *, const int, const int); - virtual int BoundsReachableAreaNum_2(idIndex, const idVec3 *, const idBounds *, const int, const int); - virtual bool NearestPointReachable(idIndex, const idVec3 *, const int, const idVec3 *, const idBounds *, const int, idAAS2NearestReachable *); - virtual void PushPointIntoAreaNum(int, idVec3 *); - virtual idVec3 *AreaCenter(idVec3 *result, int); - virtual bool Trace(idIndex, aas2Trace_t *, const idVec3 *, const idVec3 *); - virtual bool TraceHeight(idIndex, aas2TraceHeight_t *, const idVec3 *, const idVec3 *); - virtual bool TraceFloor(aas2TraceFloor_t *, const idVec3 *, int, const idVec3 *, int, bool, bool, bool); - virtual void GetEdgeVertexNumbers(int, int *); - virtual void GetEdge(int, idVec3 *, idVec3 *); - virtual int GetEdgeFlags(int); - virtual int GetAreaFlags(int); - virtual bool ChangeAreaTravelFlags(const idBounds *, const int, int, bool); - virtual bool ChangeReachabilityTravelFlags(const char *, int, bool); - virtual bool ChangeReachabilityTravelFlags_2(idIndex, int, bool); - virtual int TravelTimeToGoalArea(int, const idVec3 *, int, const idVec3 *, int, const idAAS2TravelSpeeds *); - virtual bool RouteToGoalArea(int, const idVec3 *, int, const idVec3 *, int, int *, idIndex *); - virtual bool FindNearestGoal(idAAS2Goal *, int, const idVec3 *, int, idAAS2Callback *); - virtual bool WalkPathToGoal(idAAS2Path *, int, const idVec3 *, int, const idVec3 *, const int, const int); - virtual bool ExtendHopPathToGoal(idAAS2Path *, int, const idVec3 *, int, const idVec3 *, int, int, const idAAS2HopPathParms *); - virtual void ShowWalkPath(const int, const idVec3 *, const int, const idVec3 *, const int, const int, const int, const int, const int); - virtual void ShowHopPath(int, const idVec3 *, int, const idVec3 *, int, int, const idAAS2HopPathParms *); - virtual void DrawEdge(const int, const bool, const bool, const bool); - virtual void DrawReachability(const idIndex, const aas2Reachability_t *, const char *); - virtual void DrawArea(const int, const bool, const bool, const bool, const bool, const bool, const bool); - virtual float GetGroundSpeedMultiplier(); - virtual bool GetOptimizedWalkPath(int, const idVec3 *, int, const idVec3 *, int, int, idList *); - virtual int GetNumAreas(); - virtual const idPlane *GetPlane(const int); - virtual const aas2Area_t *GetArea(const int); - virtual const aas2Reachability_t *GetReachability(const idIndex); - virtual bool GetPath(const idVec3 *, const idVec3 *, const int, const int, const int, idAAS2PathPoint *, const int, int *); - virtual void ClipGridToAreas(const idVec3 *, int, int, int, int, unsigned __int8 *); - virtual int GetInvalidTravelFlags(); - virtual int GetTraversalsForReachability(const idIndex, idList *); - virtual int GetTraversalsForInteractableEntity(const idIndex, idList *); - virtual int FindBestTraversalForReachability(const idIndex, const idVec3 *, const idVec3 *, idVec3 *, idVec3 *, idIndex *, const int, bool); - virtual const aas2Traversal_t *GetTraversal(const int); - virtual bool GetAASAnim(const idIndex, const aas2AnimName_t **); - virtual idIndex *GetAASAnimIndexByName(idIndex *result, const char *); - virtual bool GetAASDependency(const idIndex, const aas2DependencyName_t **); - virtual idIndex *GetAASDependencyIndexByName(idIndex *result, const char *); - virtual bool GetAASInteractionEntity(const idIndex, const aas2InteractionEntityName_t **); - virtual idIndex *GetAASInteractionEntityIndexByName(idIndex *result, const char *); - virtual bool GetAASTraversalNameIndex(const idIndex, const aas2TraversalEntityName_t **); - virtual idIndex *GetAASTraversalNameIndexByName(idIndex *result, const char *); - virtual int GetAASTraversalIndexByNameIndex(const idIndex); - virtual bool SetTraversalFlag(int, int); - virtual bool ClearTraversalFlag(int, int); - virtual const unsigned __int8 *GetObstaclePVS(int); - virtual int GetObstaclePVSWallEdges(int, int, int *, int); - virtual int GetObstaclePVSClipModels(int, idClipModel **, int); - virtual void GetObstaclePVSObstacles(const int, const int, obstacleVertex_t *, int *, obstacleEdge_t *, int *, idClipModel **, int *, obstacleRoute_t *); - virtual void FreeClipLinks(); - virtual idAASClipLink *LinkClipModel(idClipModel *); - virtual idAASClipLink *UnLinkClipModel(idAASClipLink *); - virtual int GetNumCoverPoints(); - virtual const aas2Cover_t *GetCoverPoint(const int); - virtual int GetNumAreaCoverPoints(const int); - virtual int GetAreaCoverPoints(const int, int *, const int, int *); - virtual int FindCoverPoints(const int, const idVec3 *, const float, const int, const int, const int, int *, const int, int *); - virtual int GetNumChokePoints(); - virtual const aas2ChokePoint_t *GetChokePoint(const int); - virtual int GetNumHintNodes(); - virtual const aas2HintNode_t *GetHintNode(const int); - virtual bool GetAreaCells(const int, const int, int *, int *); - virtual bool WorldPosToCellPos(const int, const int, const idVec3 *, int *, int *); - virtual bool CellPosToWorldPos(const int, const int, const int, const int, idVec3 *, const bool); - virtual void DrawCoverPoint(const aas2Cover_t *, const int); - virtual int GetNumTrees(); - virtual idIndex *GetTree(idIndex *result, int); - virtual idIndex *GetTreeForFloorNormal(idIndex *result, const idVec3 *); - virtual const idVec3 *GetFloorNormalForArea(int); - virtual const idVec3 *GetFloorNormalForTree(idIndex); - virtual int GetNumAreasInTree(idIndex); - virtual void Shutdown(); - virtual void ShowOptimizedWalkPath(int, const idVec3 *, int, const idVec3 *, int, int); + explicit idAASRoutingCache(int cacheSize); + ~idAASRoutingCache(); - idAAS2File *file; - idStr name; - bool defaulted; - idAASRoutingCache ***areaCacheIndex; - int areaCacheIndexSize; - idAASRoutingCache **portalCacheIndex; - int portalCacheIndexSize; - idRoutingUpdate *areaUpdate; - idRoutingUpdate *portalUpdate; - unsigned __int16 *goalAreaTravelTimes; - unsigned __int16 *areaTravelTimes; - int numAreaTravelTimes; - idAASRoutingCache *cacheListStart; - idAASRoutingCache *cacheListEnd; - int totalCacheMemory; - float groundSpeedMultiplier; - float waterSpeedMultiplier; - int numObstaclePVSBytes; - unsigned __int8 *obstaclePVS; - int obstaclePVSAreaNum; - idAASClipLink **areaClipModels; - idBlockAlloc clipModelLinkAllocator; - idObstacleAvoidance obstacleAvoidance; + int type; + int size; + int cluster; + int areaNum; + int travelFlags; + idAASRoutingCache* next; + idAASRoutingCache* prev; + idAASRoutingCache* time_next; + idAASRoutingCache* time_prev; + std::uint16_t startTravelTime; + std::uint8_t* reachabilities; + std::uint16_t* travelTimes; }; + +struct idRoutingUpdate { + int cluster; + int areaNum; + std::uint16_t tmpTravelTime; + std::uint16_t* areaTravelTimes; + idVec3 start; + idRoutingUpdate* next; + idRoutingUpdate* prev; + bool isInList; +}; + +class idAASClipLink { +public: + idAASClipLink* prevArea; + idAASClipLink* nextArea; + idAASClipLink* prevClip; + idAASClipLink* nextClip; + idClipModel* clipModel; + int areaNum; +}; + +class idAAS2Local final : public idAAS2 { +public: + idAAS2Local(); + ~idAAS2Local() override; + + bool IsDefault() const override { return defaulted; } + bool Init(const char* mapName, unsigned int mapFileCRC) override; + void Shutdown(); + void Stats() const override; + void Test(const idVec3& origin, const idVec3& destination, + const idMat3& axis, int areaNum, int travelFlags, int contentMask, + int debugMode, int treeNum, idClip* clip, usercmd_t* command, + idAngles& viewAngles) override; + const idAAS2Settings* GetSettings() const override; + idBounds DefaultSearchBounds() const override; + int PointAreaNum(idIndex tree, + const idVec3& origin) const override; + int PointReachableAreaNum(idIndex tree, + const idVec3& origin, const idBounds& searchBounds, int areaFlags, + int excludeTravelFlags) const override; + int BoundsReachableAreaNum(idIndex tree, + const idBounds& bounds, int areaFlags, + int excludeTravelFlags) const override; + int BoundsReachableAreaNum(idIndex tree, + const idVec3& origin, const idBounds& bounds, int areaFlags, + int excludeTravelFlags) const override; + bool NearestPointReachable(idIndex tree, + const idVec3& origin, int areaNum, const idVec3& destination, + const idBounds& searchBounds, int travelFlags, + idAAS2NearestReachable& nearest) const override; + void PushPointIntoAreaNum(int areaNum, idVec3& origin) const override; + idVec3 AreaCenter(int areaNum) const override; + bool Trace(idIndex tree, aas2Trace_t& trace, + const idVec3& start, const idVec3& end) const override; + bool TraceHeight(idIndex tree, + aas2TraceHeight_t& trace, const idVec3& start, + const idVec3& end) const override; + bool TraceFloor(aas2TraceFloor_t& trace, const idVec3& start, + int startAreaNum, const idVec3& end, int travelFlags, + bool allowFloorNormalChange, bool ignoreGravityDirectionDistance, + bool ignoreSameArea) const override; + void GetEdgeVertexNumbers(int edgeNum, int verts[2]) const override; + void GetEdge(int edgeNum, idVec3& start, idVec3& end) const override; + int GetEdgeFlags(int edgeNum) const override; + int GetAreaFlags(int areaNum) const override; + bool ChangeAreaTravelFlags(const idBounds& bounds, int treeNum, + int travelFlags, bool set) override; + bool ChangeReachabilityTravelFlags(const char* name, + int travelFlags, bool set) override; + bool ChangeReachabilityTravelFlags( + idIndex reachability, + int travelFlags, bool set) override; + int TravelTimeToGoalArea(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + const idAAS2TravelSpeeds* speeds) override; + bool RouteToGoalArea(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int& travelTime, + idIndex& reachability) override; + bool FindNearestGoal(idAAS2Goal& goal, int areaNum, + const idVec3& origin, int travelFlags, + idAAS2Callback& callback) override; + bool WalkPathToGoal(idAAS2Path& path, int areaNum, + const idVec3& origin, int goalAreaNum, const idVec3& goalOrigin, + int travelFlags, int obstacleFlags) override; + bool ExtendHopPathToGoal(idAAS2Path& path, int areaNum, + const idVec3& origin, int goalAreaNum, const idVec3& goalOrigin, + int travelFlags, int obstacleFlags, + const idAAS2HopPathParms& parms) override; + void ShowWalkPath(int areaNum, const idVec3& origin, int goalAreaNum, + const idVec3& goalOrigin, int travelFlags, int obstacleFlags, + int contentMask, int debugFlags, int treeNum) const override; + void ShowHopPath(int areaNum, const idVec3& origin, int goalAreaNum, + const idVec3& goalOrigin, int travelFlags, int obstacleFlags, + const idAAS2HopPathParms& parms) const override; + void ShowOptimizedWalkPath(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int obstacleFlags) const; + void DrawEdge(int edgeNum, bool arrow, bool text, + bool highlight) const override; + void DrawReachability(idIndex reachability, + const aas2Reachability_t& reach, const char* label) const override; + void DrawArea(int areaNum, bool showNumbers, bool showEdges, + bool showReachabilities, bool showFloor, bool showBounds, + bool highlight) const override; + float GetGroundSpeedMultiplier() const override { + return groundSpeedMultiplier; + } + bool GetOptimizedWalkPath(int areaNum, const idVec3& origin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int obstacleFlags, idList& points) override; + int GetNumAreas() const override; + const idPlane* GetPlane(int index) const override; + const aas2Area_t* GetArea(int areaNum) const override; + const aas2Reachability_t* GetReachability( + idIndex reachability) const override; + bool GetPath(const idVec3& origin, const idVec3& goalOrigin, + int areaNum, int goalAreaNum, int travelFlags, + idAAS2PathPoint* points, int maxPoints, int& numPoints) override; + void ClipGridToAreas(const idVec3& startOrigin, int startAreaNum, + int travelFlags, unsigned int cellSize, int dimension, + std::uint8_t* reachable) const override; + int GetInvalidTravelFlags() const override { return AAS_TFL_INVALID; } + int GetTraversalsForReachability( + idIndex reachability, + idList& traversals) const override; + int GetTraversalsForInteractableEntity( + idIndex entity, + idList& traversals) const override; + int FindBestTraversalForReachability( + idIndex reachability, + const idVec3& origin, const idVec3& goalOrigin, + idVec3& traversalStart, idVec3& traversalEnd, + idIndex& dependency, + int travelFlags, bool allowDisabled) const override; + const aas2Traversal_t* GetTraversal(int traversalNum) const override; + bool GetAASAnim(idIndex index, + const aas2AnimName_t** name) const override; + idIndex GetAASAnimIndexByName( + const char* name) const override; + bool GetAASDependency(idIndex index, + const aas2DependencyName_t** name) const override; + idIndex + GetAASDependencyIndexByName(const char* name) const override; + bool GetAASInteractionEntity( + idIndex index, + const aas2InteractionEntityName_t** name) const override; + idIndex + GetAASInteractionEntityIndexByName(const char* name) const override; + bool GetAASTraversalNameIndex( + idIndex index, + const aas2TraversalEntityName_t** name) const override; + idIndex + GetAASTraversalNameIndexByName(const char* name) const override; + int GetAASTraversalIndexByNameIndex( + idIndex index) const override; + bool SetTraversalFlag(int index, int flags) override; + bool ClearTraversalFlag(int index, int flags) override; + const std::uint8_t* GetObstaclePVS(int areaNum) override; + int GetObstaclePVSWallEdges(int areaNum, int edgeFlags, + int* outputEdges, int maxEdges) const override; + int GetObstaclePVSClipModels(int areaNum, idClipModel** models, + int maxModels) override; + void GetObstaclePVSObstacles(int areaNum, int obstacleFlags, + obstacleVertex_t* vertices, int* numVertices, + obstacleEdge_t* edges, int* numEdges, idClipModel** models, + int* numModels, obstacleRoute_t* route) override; + void FreeClipLinks() override; + idAASClipLink* LinkClipModel(idClipModel* model) override; + idAASClipLink* UnLinkClipModel(idAASClipLink* link) override; + int GetNumCoverPoints() const override; + const aas2Cover_t* GetCoverPoint(int index) const override; + int GetNumAreaCoverPoints(int areaNum) const override; + int GetAreaCoverPoints(int areaNum, int* points, int maxPoints, + int* flags) const override; + int FindCoverPoints(int areaNum, const idVec3& origin, float radius, + int coverFlags, int excludeFlags, int maxPoints, int* points, + int team, int* areaFlags) const override; + int GetNumChokePoints() const override; + const aas2ChokePoint_t* GetChokePoint(int index) const override; + int GetNumHintNodes() const override; + const aas2HintNode_t* GetHintNode(int index) const override; + bool GetAreaCells(int areaNum, int subdivisionSize, int* xCells, + int* yCells) const override; + bool WorldPosToCellPos(int areaNum, int subdivisionSize, + const idVec3& worldPos, int* xCell, int* yCell) const override; + bool CellPosToWorldPos(int areaNum, int subdivisionSize, int xCell, + int yCell, idVec3& worldPos, bool center) const override; + void DrawCoverPoint(const aas2Cover_t& cover, int index) const override; + int GetNumTrees() const override; + idIndex GetTree(int index) const override; + idIndex GetTreeForFloorNormal( + const idVec3& floorNormal) const override; + const idVec3* GetFloorNormalForArea(int areaNum) const override; + const idVec3* GetFloorNormalForTree( + idIndex tree) const override; + int GetNumAreasInTree( + idIndex tree) const override; + + void SetupRouting(); + void ShutdownRouting(); + void RoutingStats() const; + void RemoveRoutingCacheUsingArea(int areaNum); + void LinkCache(idAASRoutingCache* cache); + void UnlinkCache(idAASRoutingCache* cache); + int CalcTravelTime(const idVec3& start, const idVec3& end, + int travelFlags) const; + int AreaTravelTime(int areaNum, const idVec3& start, + const idVec3& end) const; + void CalculateAreaTravelTimes(); + void SetupRoutingCache(); + void DeleteClusterCache(int clusterNum); + void DeletePortalCache(); + void ShutdownRoutingCache(); + bool ChangeAreaTravelFlags_r(int nodeNum, const idBounds& bounds, + int areaFlags, int travelFlags, bool set); + idIndex GetAreaReachability( + int areaNum, int reachabilityNum) const; + int ClusterAreaNum(int clusterNum, int areaNum) const; + void DeleteOldestCache(); + void UpdateAreaRoutingCache(idAASRoutingCache* cache); + idAASRoutingCache* GetAreaRoutingCache(int clusterNum, int areaNum, + int travelFlags); + void UpdatePortalRoutingCache(idAASRoutingCache* cache); + idAASRoutingCache* GetPortalRoutingCache(int clusterNum, int areaNum, + int travelFlags); + bool GetClusterRoute(int startAreaNum, const idVec3& startOrigin, + int startClusterNum, int goalAreaNum, int travelFlags, + int& travelTime, + idIndex& bestReachability); + bool HopPathIsValid(int startAreaNum, const idVec3& startOrigin, + int goalAreaNum, const idVec3& goalOrigin, + const idAAS2HopPathParms& parms) const; + bool WalkPathIsValid(int startAreaNum, const idVec3& startOrigin, + int goalAreaNum, const idVec3& goalOrigin, int travelFlags, + int& endAreaNum, aas2EdgeCrossed_t& firstEdge) const; + void SubSampleWalkPath(int startAreaNum, const idVec3& startOrigin, + int pathAreaNum, const idVec3& pathStart, const idVec3& pathEnd, + int travelFlags, idVec3& endPos, int& endAreaNum, + aas2EdgeCrossed_t& firstEdge) const; + void SetupObstaclePVS(); + void ShutdownObstaclePVS(); + void DecompressObstaclePVS(int areaNum, std::uint8_t* pvs, + int pvsSize) const; + void GetObstaclePVSObstaclesInternal(int areaNum, int edgeFlags, + obstacleVertex_t* vertices, int& numVertices, + obstacleEdge_t* edges, int& numEdges, idClipModel** models, + int& numModels, obstacleRoute_t* route) const; + void FindCoverPoints_Internal(int startAreaNum, const idVec3& origin, + const idBounds& bounds, float maxDistSqr, int requiredFlags, + int excludeFlags, int travelFlags, int* coverPoints, int maxCover, + int& numCover) const; + + idAAS2File* file; + idStr name; + bool defaulted; + idAASRoutingCache*** areaCacheIndex; + int areaCacheIndexSize; + idAASRoutingCache** portalCacheIndex; + int portalCacheIndexSize; + idRoutingUpdate* areaUpdate; + idRoutingUpdate* portalUpdate; + std::uint16_t* goalAreaTravelTimes; + std::uint16_t* areaTravelTimes; + int numAreaTravelTimes; + idAASRoutingCache* cacheListStart; + idAASRoutingCache* cacheListEnd; + int totalCacheMemory; + float groundSpeedMultiplier; + float waterSpeedMultiplier; + int numObstaclePVSBytes; + std::uint8_t* obstaclePVS; + int obstaclePVSAreaNum; + idAASClipLink** areaClipModels; + idBlockAlloc clipModelLinkAllocator; + idObstacleAvoidance obstacleAvoidance; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idAASClipLink) == 24, + "Recovered idAASClipLink ABI changed"); +static_assert(sizeof(idAAS2Local) == 172, + "Recovered idAAS2Local ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/aas2_pathing.cpp b/source/engine/gamelib/aas2/aas2_pathing.cpp new file mode 100644 index 0000000..e0507f4 --- /dev/null +++ b/source/engine/gamelib/aas2/aas2_pathing.cpp @@ -0,0 +1,345 @@ +#include "gamelib/aas2/aas2_local.h" + +#include +#include +#include + +namespace { + +float LengthSqr(const idVec3& value) { + return value.x * value.x + value.y * value.y + value.z * value.z; +} + +bool TraversalClassAllowed(const aas2Traversal_t& traversal, + const int travelFlags) { + const std::uint32_t flags = traversal.flags; + return ((flags & 0x02000000u) != 0 + && (travelFlags & AAS_TFL_TRAVERSAL_CLASS_A) != 0) + || ((flags & 0x04000000u) != 0 + && (travelFlags & AAS_TFL_TRAVERSAL_CLASS_B) != 0) + || ((flags & 0x08000000u) != 0 + && (travelFlags & AAS_TFL_TRAVERSAL_CLASS_C) != 0) + || ((flags & 0x10000000u) != 0 + && (travelFlags & AAS_TFL_TRAVERSAL_CLASS_D) != 0) + || ((flags & 0x20000000u) != 0 + && (travelFlags & AAS_TFL_TRAVERSAL_CLASS_E) != 0); +} + +int PathTypeForTravelFlags(const std::uint32_t flags) { + if ((flags & AAS_TFL_WALKOFFLEDGE) != 0) return 1; + if ((flags & AAS_TFL_BARRIERJUMP) != 0) return 2; + if ((flags & AAS_TFL_LADDER) != 0) return 5; + if ((flags & AAS_TFL_ELEVATOR) != 0) return 8; + if ((flags & AAS_TFL_TELEPORT) != 0) return 9; + return 0; +} + +} // namespace + +int idAAS2Local::GetTraversalsForReachability( + const idIndex reachability, + idList& traversals) const { + return file != nullptr + ? file->GetTraversalsForReachability(reachability, traversals) : 0; +} + +int idAAS2Local::GetTraversalsForInteractableEntity( + const idIndex entity, + idList& traversals) const { + return file != nullptr + ? file->GetTraversalsForInteractableEntity(entity, traversals) : 0; +} + +bool idAAS2Local::HopPathIsValid(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const idAAS2HopPathParms& parms) const { + if (file == nullptr || startAreaNum <= 0 || goalAreaNum <= 0 + || startAreaNum >= file->areas.Num() + || goalAreaNum >= file->areas.Num() + || (file->areas[goalAreaNum].flags & AAS_AREA_OUTSIDE) == 0) { + return false; + } + const idVec3* const normal = file->GetFloorNormalForArea(startAreaNum); + if (normal == nullptr) return false; + const idVec3 delta = goalOrigin - startOrigin; + const float vertical = delta.x * normal->x + delta.y * normal->y + + delta.z * normal->z; + const idVec3 planar = delta - *normal * vertical; + if (LengthSqr(planar) > parms.maxDistance * parms.maxDistance) { + return false; + } + idVec3 points[512]; + aas2TraceHeight_t trace{512, 0, points}; + file->TraceHeight(file->GetTreeForArea(startAreaNum).Get(), trace, + startOrigin, goalOrigin); + if (trace.numPoints >= 512) return false; + for (int index = 1; index < trace.numPoints; ++index) { + const idVec3 pointDelta = points[index] - startOrigin; + const float pointVertical = pointDelta.x * normal->x + + pointDelta.y * normal->y + pointDelta.z * normal->z; + const idVec3 pointPlanar = pointDelta - *normal * pointVertical; + float maximumHeight = std::sqrt(LengthSqr(pointPlanar)) + * parms.maxSlope; + maximumHeight = (std::max)(parms.minHeight, + (std::min)(parms.maxHeight, maximumHeight)); + if (pointVertical > maximumHeight) return false; + } + return true; +} + +bool idAAS2Local::ExtendHopPathToGoal(idAAS2Path& path, + const int startAreaNum, const idVec3& startOrigin, + const int goalAreaNum, const idVec3& goalOrigin, + const int travelFlags, const int obstacleFlags, + const idAAS2HopPathParms& parms) { + if (file == nullptr) return false; + if (path.moveGoal.x == goalOrigin.x && path.moveGoal.y == goalOrigin.y + && path.moveGoal.z == goalOrigin.z) { + return HopPathIsValid(startAreaNum, startOrigin, + path.moveAreaNum, path.moveGoal, parms); + } + if (HopPathIsValid(startAreaNum, startOrigin, goalAreaNum, + goalOrigin, parms)) { + path = idAAS2Path(); + path.type = 0; + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + path.obstacleGoal = goalOrigin; + path.obstacleAreaNum = goalAreaNum; + path.travelTime = AreaTravelTime(startAreaNum, + startOrigin, goalOrigin); + path.pathMaxHeight = (std::max)(0.0f, + goalOrigin.z - startOrigin.z); + path.obstacleRoute.numAreas = 1; + path.obstacleRoute.areas[0].areaNum = startAreaNum; + path.obstacleRoute.areas[0].start = startOrigin; + path.obstacleRoute.areas[0].end = goalOrigin; + return true; + } + if (!WalkPathToGoal(path, startAreaNum, startOrigin, goalAreaNum, + goalOrigin, travelFlags, obstacleFlags)) return false; + return HopPathIsValid(startAreaNum, startOrigin, path.moveAreaNum, + path.moveGoal, parms); +} + +bool idAAS2Local::GetPath(const idVec3& origin, + const idVec3& goalOrigin, const int areaNum, + const int goalAreaNum, const int travelFlags, + idAAS2PathPoint* const points, const int maxPoints, + int& numPoints) { + numPoints = 0; + if (file == nullptr || points == nullptr || maxPoints <= 0 + || areaNum <= 0 || goalAreaNum <= 0) return false; + points[0].areaNum = areaNum; + points[0].origin = origin; + points[0].nextReachIndex.Invalidate(); + points[0].travelTime = 0; + numPoints = 1; + int currentArea = areaNum; + idVec3 currentOrigin = origin; + int cumulativeTime = 0; + while (currentArea != goalAreaNum && numPoints < maxPoints) { + int routeTime = 0; + idIndex reachIndex; + if (!RouteToGoalArea(currentArea, currentOrigin, goalAreaNum, + goalOrigin, travelFlags, routeTime, reachIndex) + || !reachIndex.IsValid()) break; + const int reachNum = reachIndex.Get(); + if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break; + const aas2Reachability_t& reach = file->reachabilities[reachNum]; + points[numPoints - 1].nextReachIndex = reachIndex; + cumulativeTime += reach.travelTime + + AreaTravelTime(currentArea, currentOrigin, reach.Start()); + points[numPoints].areaNum = reach.toAreaNum; + points[numPoints].origin = reach.End(); + points[numPoints].nextReachIndex.Invalidate(); + points[numPoints].travelTime = cumulativeTime; + currentArea = reach.toAreaNum; + currentOrigin = reach.End(); + ++numPoints; + } + if (currentArea != goalAreaNum) return false; + if (numPoints < maxPoints) { + points[numPoints].areaNum = goalAreaNum; + points[numPoints].origin = goalOrigin; + points[numPoints].nextReachIndex.Invalidate(); + points[numPoints].travelTime = cumulativeTime + + AreaTravelTime(goalAreaNum, currentOrigin, goalOrigin); + ++numPoints; + } else { + points[numPoints - 1].origin = goalOrigin; + } + return true; +} + +bool idAAS2Local::WalkPathIsValid(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const int travelFlags, + int& endAreaNum, aas2EdgeCrossed_t& firstEdge) const { + int areas[128] = {}; + idIndex reachIndices[128]; + aas2TraceFloor_t trace{}; + trace.maxAreas = 128; + trace.areas = areas; + trace.maxReachIndices = 128; + trace.reachIndices = reachIndices; + if (!TraceFloor(trace, startOrigin, startAreaNum, goalOrigin, + travelFlags, true, false, true)) { + endAreaNum = trace.lastAreaNum; + firstEdge = trace.firstEdge; + return false; + } + endAreaNum = trace.lastAreaNum; + firstEdge = trace.firstEdge; + return trace.lastAreaNum == goalAreaNum; +} + +void idAAS2Local::SubSampleWalkPath(const int startAreaNum, + const idVec3& startOrigin, const int pathAreaNum, + const idVec3& pathStart, const idVec3& pathEnd, + const int travelFlags, idVec3& endPos, int& endAreaNum, + aas2EdgeCrossed_t& firstEdge) const { + endPos = pathStart; + endAreaNum = pathAreaNum; + firstEdge = aas2EdgeCrossed_t{}; + const idVec3 delta = pathEnd - pathStart; + float validFraction = 0.0f; + for (int sample = 1; sample <= 8; ++sample) { + const float fraction = float(sample) / 8.0f; + const idVec3 candidate = pathStart + delta * fraction; + int candidateArea = pathAreaNum; + aas2EdgeCrossed_t candidateEdge{}; + if (!WalkPathIsValid(startAreaNum, startOrigin, pathAreaNum, + candidate, travelFlags, candidateArea, candidateEdge)) break; + validFraction = fraction; + endPos = candidate; + endAreaNum = candidateArea; + if (firstEdge.edgeNum == 0 && candidateEdge.edgeNum != 0) { + firstEdge = candidateEdge; + } + } + if (validFraction == 0.0f) endPos = pathStart; +} + +bool idAAS2Local::GetOptimizedWalkPath(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const int travelFlags, + const int, idList& path) { + path.Clear(); + idAAS2PathPoint rawPath[256]; + int count = 0; + if (!GetPath(startOrigin, goalOrigin, startAreaNum, goalAreaNum, + travelFlags, rawPath, 256, count)) return false; + int anchor = 0; + path.Append(rawPath[0].origin); + while (anchor < count - 1) { + int farthest = anchor + 1; + for (int candidate = count - 1; candidate > anchor + 1; + --candidate) { + int endArea = rawPath[anchor].areaNum; + aas2EdgeCrossed_t edge{}; + if (WalkPathIsValid(rawPath[anchor].areaNum, + rawPath[anchor].origin, rawPath[candidate].areaNum, + rawPath[candidate].origin, travelFlags, endArea, edge)) { + farthest = candidate; + break; + } + } + path.Append(rawPath[farthest].origin); + anchor = farthest; + } + return true; +} + +bool idAAS2Local::WalkPathToGoal(idAAS2Path& path, + const int startAreaNum, const idVec3& startOrigin, + const int goalAreaNum, const idVec3& goalOrigin, + const int travelFlags, const int) { + path = idAAS2Path(); + path.type = 0; + path.moveGoal = startOrigin; + path.moveAreaNum = startAreaNum; + path.obstacleGoal = startOrigin; + path.obstacleAreaNum = startAreaNum; + path.obstacleRoute.numAreas = 1; + path.obstacleRoute.areas[0].areaNum = startAreaNum; + path.obstacleRoute.areas[0].start = startOrigin; + path.obstacleRoute.areas[0].end = startOrigin; + if (file == nullptr) return false; + if (startAreaNum == goalAreaNum) { + path.moveGoal = goalOrigin; + path.obstacleGoal = goalOrigin; + path.travelTime = AreaTravelTime(startAreaNum, + startOrigin, goalOrigin); + path.obstacleRoute.areas[0].end = goalOrigin; + path.pathMaxHeight = (std::max)(0.0f, + goalOrigin.z - startOrigin.z); + return true; + } + int routeTime = 0; + idIndex reachIndex; + if (!RouteToGoalArea(startAreaNum, startOrigin, goalAreaNum, + goalOrigin, travelFlags, routeTime, reachIndex) + || !reachIndex.IsValid()) return false; + const aas2Reachability_t* const reach = GetReachability(reachIndex); + if (reach == nullptr) return false; + path.reachIndex = reachIndex; + path.type = PathTypeForTravelFlags(reach->travelFlags); + path.moveGoal = reach->Start(); + path.moveAreaNum = startAreaNum; + path.obstacleGoal = path.moveGoal; + path.obstacleAreaNum = startAreaNum; + path.travelTime = routeTime; + path.obstacleRoute.areas[0].end = path.moveGoal; + path.pathMaxHeight = (std::max)(0.0f, + path.moveGoal.z - startOrigin.z); + + idVec3 traversalStart; + idVec3 traversalEnd; + idIndex dependency; + const int traversal = FindBestTraversalForReachability(reachIndex, + startOrigin, goalOrigin, traversalStart, traversalEnd, dependency, + travelFlags, false); + if (traversal >= 0) { + path.type = 9; + path.traversalIndex = traversal; + path.traversalStart = traversalStart; + path.traversalEnd = traversalEnd; + path.dependencyIndex = dependency; + path.moveGoal = traversalStart; + } + return true; +} + +int idAAS2Local::FindBestTraversalForReachability( + const idIndex reachability, + const idVec3& currentPoint, const idVec3& goalPoint, + idVec3& traversalStart, idVec3& traversalEnd, + idIndex& dependency, + const int travelFlags, const bool useClosestPointOnFail) const { + idList traversals; + const int count = GetTraversalsForReachability(reachability, traversals); + traversalStart.Zero(); + traversalEnd.Zero(); + dependency.Invalidate(); + int bestIndex = -1; + float bestDistance = (std::numeric_limits::max)(); + for (int index = 0; index < count; ++index) { + const int traversalIndex = traversals[index]; + const aas2Traversal_t* const traversal = GetTraversal(traversalIndex); + if (traversal == nullptr) continue; + const bool enabled = (traversal->flags & 1u) != 0; + if ((!enabled && !useClosestPointOnFail) + || !TraversalClassAllowed(*traversal, travelFlags)) continue; + const float distance = LengthSqr(traversal->startPoint - currentPoint) + + LengthSqr(traversal->endPoint - goalPoint); + if (distance < bestDistance) { + bestDistance = distance; + bestIndex = traversalIndex; + traversalStart = traversal->startPoint; + traversalEnd = traversal->endPoint; + dependency = traversal->dependencyIndex; + } + } + return bestIndex; +} diff --git a/source/engine/gamelib/aas2/aas2_pvs.cpp b/source/engine/gamelib/aas2/aas2_pvs.cpp new file mode 100644 index 0000000..bbd309e --- /dev/null +++ b/source/engine/gamelib/aas2/aas2_pvs.cpp @@ -0,0 +1,221 @@ +#include "gamelib/aas2/aas2_local.h" + +#include "gamelib/physics/clipmodel.h" + +#include +#include +#include + +namespace { + +constexpr int MAX_OBSTACLE_VERTICES = 0x300; +constexpr int MAX_OBSTACLE_EDGES = 0x200; +constexpr int MAX_OBSTACLE_MODELS = 128; + +bool PVSIncludes(const std::uint8_t* const pvs, const int areaNum) { + return pvs != nullptr && areaNum >= 0 + && (pvs[areaNum >> 3] & (1u << (areaNum & 7))) != 0; +} + +} // namespace + +void idAAS2Local::ShutdownObstaclePVS() { + FreeClipLinks(); + delete[] obstaclePVS; + delete[] areaClipModels; + numObstaclePVSBytes = 0; + obstaclePVS = nullptr; + areaClipModels = nullptr; + obstaclePVSAreaNum = 0; +} + +int idAAS2Local::GetObstaclePVSWallEdges(const int areaNum, + const int edgeFlags, int* const edges, const int maxEdges) const { + return file != nullptr ? file->GetObstaclePVSWallEdges(areaNum, + edgeFlags, edges, maxEdges) : 0; +} + +void idAAS2Local::SetupObstaclePVS() { + ShutdownObstaclePVS(); + if (file == nullptr || file->areas.IsEmpty()) return; + numObstaclePVSBytes = (file->areas.Num() + 7) / 8; + obstaclePVS = new std::uint8_t[numObstaclePVSBytes]; + std::memset(obstaclePVS, 0, numObstaclePVSBytes); + areaClipModels = new idAASClipLink*[file->areas.Num()](); +} + +void idAAS2Local::DecompressObstaclePVS(const int areaNum, + std::uint8_t* const pvs, const int pvsSize) const { + if (pvs == nullptr || pvsSize <= 0) return; + std::memset(pvs, 0, pvsSize); + if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num()) + return; + std::uint32_t offset = file->areas[areaNum].obstaclePVSOffset; + int visibleArea = 0; + while (offset < static_cast(file->obstaclePVS.Num()) + && visibleArea < file->areas.Num()) { + const std::uint8_t encoded = file->obstaclePVS[offset++]; + if ((encoded & 0x80u) != 0) { + int skip = encoded & 0x3Fu; + if ((encoded & 0x40u) != 0 + && offset < static_cast( + file->obstaclePVS.Num())) { + skip |= static_cast(file->obstaclePVS[offset++]) << 6; + } + visibleArea += skip + 1; + continue; + } + for (int bit = 0; bit < 7 && visibleArea < file->areas.Num(); + ++bit, ++visibleArea) { + if ((encoded & (1u << bit)) != 0 + && (visibleArea >> 3) < pvsSize) { + pvs[visibleArea >> 3] |= static_cast( + 1u << (visibleArea & 7)); + } + } + } +} + +const std::uint8_t* idAAS2Local::GetObstaclePVS(const int areaNum) { + if (obstaclePVS == nullptr) return nullptr; + if (areaNum != obstaclePVSAreaNum) { + DecompressObstaclePVS(areaNum, obstaclePVS, numObstaclePVSBytes); + obstaclePVSAreaNum = areaNum; + } + return obstaclePVS; +} + +void idAAS2Local::FreeClipLinks() { + if (areaClipModels != nullptr && file != nullptr) { + for (int areaNum = 0; areaNum < file->areas.Num(); ++areaNum) + areaClipModels[areaNum] = nullptr; + } + clipModelLinkAllocator.Shutdown(); +} + +idAASClipLink* idAAS2Local::UnLinkClipModel(idAASClipLink* link) { + while (link != nullptr) { + idAASClipLink* const nextArea = link->nextArea; + if (link->prevClip != nullptr) + link->prevClip->nextClip = link->nextClip; + else if (areaClipModels != nullptr && link->areaNum >= 0) + areaClipModels[link->areaNum] = link->nextClip; + if (link->nextClip != nullptr) + link->nextClip->prevClip = link->prevClip; + clipModelLinkAllocator.Free(link); + link = nextArea; + } + return nullptr; +} + +idAASClipLink* idAAS2Local::LinkClipModel(idClipModel* const model) { + if (model == nullptr || file == nullptr || areaClipModels == nullptr) + return nullptr; + idBounds expanded = model->GetAbsBounds(); + expanded[0].x -= file->settings.boundingBox[1].x; + expanded[0].y -= file->settings.boundingBox[1].y; + expanded[0].z -= file->settings.boundingBox[1].z; + expanded[1].x -= file->settings.boundingBox[0].x; + expanded[1].y -= file->settings.boundingBox[0].y; + expanded[1].z -= file->settings.boundingBox[0].z; + + std::vector areas; + int areaBuffer[128]; + for (int tree = 0; tree < file->trees.Num(); ++tree) { + const int count = file->BoundsAreaNums(tree, expanded, + areaBuffer, 128); + for (int index = 0; index < count; ++index) { + const int areaNum = areaBuffer[index]; + if (areaNum > 0 && areaNum < file->areas.Num() + && std::find(areas.begin(), areas.end(), areaNum) + == areas.end()) areas.push_back(areaNum); + } + } + std::sort(areas.begin(), areas.end(), std::greater()); + idAASClipLink* first = nullptr; + for (const int areaNum : areas) { + idAASClipLink* const link = clipModelLinkAllocator.Alloc(); + if (link == nullptr) break; + link->clipModel = model; + link->areaNum = areaNum; + link->prevArea = nullptr; + link->nextArea = first; + if (first != nullptr) first->prevArea = link; + first = link; + link->prevClip = nullptr; + link->nextClip = areaClipModels[areaNum]; + if (link->nextClip != nullptr) link->nextClip->prevClip = link; + areaClipModels[areaNum] = link; + } + return first; +} + +int idAAS2Local::GetObstaclePVSClipModels(const int areaNum, + idClipModel** const models, const int maxModels) { + if (models == nullptr || maxModels <= 0 || areaClipModels == nullptr) + return 0; + const std::uint8_t* const pvs = GetObstaclePVS(areaNum); + if (pvs == nullptr) return 0; + int count = 0; + for (int visibleArea = 1; visibleArea < file->areas.Num() + && count < maxModels; ++visibleArea) { + if (!PVSIncludes(pvs, visibleArea)) continue; + for (idAASClipLink* link = areaClipModels[visibleArea]; + link != nullptr && count < maxModels; link = link->nextClip) { + bool duplicate = false; + for (int index = 0; index < count; ++index) + if (models[index] == link->clipModel) duplicate = true; + if (!duplicate) models[count++] = link->clipModel; + } + } + return count; +} + +void idAAS2Local::GetObstaclePVSObstaclesInternal(const int areaNum, + const int edgeFlags, obstacleVertex_t* const vertices, + int& numVertices, obstacleEdge_t* const edges, int& numEdges, + idClipModel** const models, int& numModels, + obstacleRoute_t* const route) const { + numVertices = 0; + numEdges = 0; + numModels = 0; + if (file == nullptr) return; + int wallEdges[MAX_OBSTACLE_EDGES]; + const int wallCount = file->GetObstaclePVSWallEdges(areaNum, + edgeFlags, wallEdges, MAX_OBSTACLE_EDGES); + for (int index = 0; index < wallCount + && numVertices + 2 <= MAX_OBSTACLE_VERTICES + && numEdges < MAX_OBSTACLE_EDGES; ++index) { + idVec3 start; + idVec3 end; + GetEdge(wallEdges[index], start, end); + vertices[numVertices].position = start; + vertices[numVertices + 1].position = end; + obstacleEdge_t& edge = edges[numEdges++]; + edge.flags = GetEdgeFlags(wallEdges[index]); + edge.verts[0] = static_cast(numVertices); + edge.verts[1] = static_cast(numVertices + 1); + edge.vertNums[0] = numVertices; + edge.vertNums[1] = numVertices + 1; + numVertices += 2; + } + numModels = const_cast(this)->GetObstaclePVSClipModels( + areaNum, models, MAX_OBSTACLE_MODELS); + if (route != nullptr && route->numAreas == 0) { + route->numAreas = 1; + route->areas[0].areaNum = areaNum; + route->areas[0].start = AreaCenter(areaNum); + route->areas[0].end = route->areas[0].start; + } +} + +void idAAS2Local::GetObstaclePVSObstacles(const int startAreaNum, + const int edgeFlags, obstacleVertex_t* const vertices, + int* const numVertices, obstacleEdge_t* const edges, + int* const numEdges, idClipModel** const models, + int* const numModels, obstacleRoute_t* const route) { + if (numVertices == nullptr || numEdges == nullptr + || numModels == nullptr) return; + GetObstaclePVSObstaclesInternal(startAreaNum, edgeFlags, vertices, + *numVertices, edges, *numEdges, models, *numModels, route); +} diff --git a/source/engine/gamelib/aas2/aas2_routing.cpp b/source/engine/gamelib/aas2/aas2_routing.cpp new file mode 100644 index 0000000..b4b9465 --- /dev/null +++ b/source/engine/gamelib/aas2/aas2_routing.cpp @@ -0,0 +1,580 @@ +#include "gamelib/aas2/aas2_local.h" + +#include "idlib/lib_print.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int ROUTE_INFINITY = 0x3FFFFFFF; + +bool ReachAllowed(const aas2Reachability_t& reach, const int travelFlags) { + return (reach.travelFlags & AAS_TFL_INVALID) == 0 + && (reach.travelFlags & travelFlags) != 0; +} + +} // namespace + +idAASRoutingCache::idAASRoutingCache(const int cacheSize) + : type(0), size((std::max)(0, cacheSize)), cluster(0), areaNum(0), + travelFlags(0), next(nullptr), prev(nullptr), time_next(nullptr), + time_prev(nullptr), startTravelTime(0), reachabilities(nullptr), + travelTimes(nullptr) { + if (size > 0) { + reachabilities = new std::uint8_t[size](); + travelTimes = new std::uint16_t[size](); + } +} + +idAASRoutingCache::~idAASRoutingCache() { + delete[] reachabilities; + delete[] travelTimes; +} + +void idAAS2Local::RoutingStats() const { + idLibPrint::Printf("%6d kB routing cache\n", totalCacheMemory >> 10); +} + +void idAAS2Local::UnlinkCache(idAASRoutingCache* const cache) { + if (cache == nullptr) return; + if (cache->time_next != nullptr) cache->time_next->time_prev = cache->time_prev; + else cacheListEnd = cache->time_prev; + if (cache->time_prev != nullptr) cache->time_prev->time_next = cache->time_next; + else cacheListStart = cache->time_next; + cache->time_next = nullptr; + cache->time_prev = nullptr; +} + +void idAAS2Local::LinkCache(idAASRoutingCache* const cache) { + if (cache == nullptr) return; + if (cache->time_next != nullptr || cache->time_prev != nullptr + || cacheListStart == cache) UnlinkCache(cache); + cache->time_prev = cacheListEnd; + cache->time_next = nullptr; + if (cacheListEnd != nullptr) cacheListEnd->time_next = cache; + else cacheListStart = cache; + cacheListEnd = cache; +} + +int idAAS2Local::CalcTravelTime(const idVec3& start, const idVec3& end, + const int travelFlags) const { + const idVec3 delta = end - start; + const float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y + + delta.z * delta.z); + const float multiplier = (travelFlags & AAS_TFL_WATER) != 0 + ? waterSpeedMultiplier : groundSpeedMultiplier; + return (std::max)(1, static_cast(multiplier * distance)); +} + +int idAAS2Local::AreaTravelTime(const int areaNum, const idVec3& start, + const idVec3& end) const { + return file != nullptr && areaNum > 0 && areaNum < file->areas.Num() + ? CalcTravelTime(start, end, file->areas[areaNum].travelFlags) : 0; +} + +void idAAS2Local::CalculateAreaTravelTimes() { + delete[] areaTravelTimes; + areaTravelTimes = nullptr; + numAreaTravelTimes = 0; + if (file == nullptr) return; + for (int areaNum = 1; areaNum < file->areas.Num(); ++areaNum) { + const aas2Area_t& area = file->areas[areaNum]; + int count = 0; + for (idIndex index = area.reach; + index.IsValid();) { + const int reachNum = index.Get(); + if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break; + ++count; + index = file->reachabilities[reachNum].next; + } + numAreaTravelTimes += count * count; + } + if (numAreaTravelTimes > 0) { + areaTravelTimes = new std::uint16_t[numAreaTravelTimes](); + } +} + +void idAAS2Local::SetupRoutingCache() { + if (file == nullptr) return; + areaCacheIndexSize = 0; + const int clusterCount = file->clusters.Num(); + areaCacheIndex = clusterCount > 0 + ? new idAASRoutingCache**[clusterCount]() : nullptr; + for (int cluster = 0; cluster < clusterCount; ++cluster) { + const int count = (std::max)(0, + file->clusters[cluster].numReachableAreas); + areaCacheIndexSize += count; + areaCacheIndex[cluster] = count > 0 + ? new idAASRoutingCache*[count]() : nullptr; + } + portalCacheIndexSize = file->areas.Num(); + portalCacheIndex = portalCacheIndexSize > 0 + ? new idAASRoutingCache*[portalCacheIndexSize]() : nullptr; + areaUpdate = file->areas.Num() > 0 + ? new idRoutingUpdate[file->areas.Num()]() : nullptr; + portalUpdate = file->portals.Num() >= 0 + ? new idRoutingUpdate[file->portals.Num() + 1]() : nullptr; + goalAreaTravelTimes = file->areas.Num() > 0 + ? new std::uint16_t[file->areas.Num()]() : nullptr; + cacheListStart = nullptr; + cacheListEnd = nullptr; + totalCacheMemory = 0; +} + +void idAAS2Local::DeleteClusterCache(const int clusterNum) { + if (file == nullptr || areaCacheIndex == nullptr || clusterNum < 0 + || clusterNum >= file->clusters.Num()) return; + const int count = file->clusters[clusterNum].numReachableAreas; + for (int slot = 0; slot < count; ++slot) { + idAASRoutingCache* cache = areaCacheIndex[clusterNum][slot]; + areaCacheIndex[clusterNum][slot] = nullptr; + while (cache != nullptr) { + idAASRoutingCache* const nextCache = cache->next; + UnlinkCache(cache); + totalCacheMemory -= sizeof(*cache) + 3 * cache->size; + delete cache; + cache = nextCache; + } + } +} + +void idAAS2Local::DeletePortalCache() { + if (portalCacheIndex == nullptr) return; + for (int slot = 0; slot < portalCacheIndexSize; ++slot) { + idAASRoutingCache* cache = portalCacheIndex[slot]; + portalCacheIndex[slot] = nullptr; + while (cache != nullptr) { + idAASRoutingCache* const nextCache = cache->next; + UnlinkCache(cache); + totalCacheMemory -= sizeof(*cache) + 3 * cache->size; + delete cache; + cache = nextCache; + } + } +} + +void idAAS2Local::ShutdownRoutingCache() { + if (areaCacheIndex != nullptr && file != nullptr) { + for (int cluster = 0; cluster < file->clusters.Num(); ++cluster) { + DeleteClusterCache(cluster); + delete[] areaCacheIndex[cluster]; + } + } + DeletePortalCache(); + delete[] areaCacheIndex; + delete[] portalCacheIndex; + delete[] areaUpdate; + delete[] portalUpdate; + delete[] goalAreaTravelTimes; + areaCacheIndex = nullptr; + portalCacheIndex = nullptr; + areaUpdate = nullptr; + portalUpdate = nullptr; + goalAreaTravelTimes = nullptr; + areaCacheIndexSize = 0; + portalCacheIndexSize = 0; + cacheListStart = nullptr; + cacheListEnd = nullptr; + totalCacheMemory = 0; +} + +void idAAS2Local::SetupRouting() { + if (file == nullptr) return; + groundSpeedMultiplier = file->settings.groundSpeed != 0.0f + ? 100.0f / file->settings.groundSpeed : 1.0f; + waterSpeedMultiplier = file->settings.waterSpeed != 0.0f + ? 100.0f / file->settings.waterSpeed : 1.0f; + CalculateAreaTravelTimes(); + SetupRoutingCache(); +} + +void idAAS2Local::ShutdownRouting() { + delete[] areaTravelTimes; + areaTravelTimes = nullptr; + numAreaTravelTimes = 0; + ShutdownRoutingCache(); +} + +void idAAS2Local::RemoveRoutingCacheUsingArea(const int) { + if (file == nullptr) return; + for (int cluster = 0; cluster < file->clusters.Num(); ++cluster) { + DeleteClusterCache(cluster); + } + DeletePortalCache(); +} + +bool idAAS2Local::ChangeAreaTravelFlags_r(int nodeNum, + const idBounds& bounds, const int areaFlags, + const int travelFlags, const bool set) { + bool changed = false; + if (file == nullptr || nodeNum == 0) return false; + if (nodeNum < 0) { + const int areaNum = -nodeNum; + if (areaNum <= 0 || areaNum >= file->areas.Num()) return false; + aas2Area_t& area = file->areas[areaNum]; + if ((area.flags & areaFlags) == 0) return false; + const std::uint32_t oldFlags = area.travelFlags; + area.travelFlags = set ? oldFlags | travelFlags + : oldFlags & ~std::uint32_t(travelFlags); + if (area.travelFlags != oldFlags) { + RemoveRoutingCacheUsingArea(areaNum); + changed = true; + } + return changed; + } + if (nodeNum >= file->nodes.Num()) return false; + const aas2Node_t& node = file->nodes[nodeNum]; + if (node.planeNum >= static_cast(file->planes.Num())) { + return false; + } + const int side = bounds.PlaneSide(file->planes[node.planeNum], 0.1f); + if (side != 1) changed |= ChangeAreaTravelFlags_r(node.children[0], + bounds, areaFlags, travelFlags, set); + if (side != 0) changed |= ChangeAreaTravelFlags_r(node.children[1], + bounds, areaFlags, travelFlags, set); + return changed; +} + +bool idAAS2Local::ChangeAreaTravelFlags(const idBounds& bounds, + const int areaFlags, const int travelFlags, const bool set) { + if (file == nullptr || file->trees.Num() == 0) return false; + idBounds expanded; + expanded[0] = bounds[0] - file->settings.boundingBox[1]; + expanded[1] = bounds[1] - file->settings.boundingBox[0]; + return ChangeAreaTravelFlags_r(file->trees[0].headNode, expanded, + areaFlags, travelFlags, set); +} + +bool idAAS2Local::ChangeReachabilityTravelFlags(const char* const nameValue, + const int travelFlags, const bool set) { + if (file == nullptr) return false; + const idIndex index = + file->FindReachabilityByName(nameValue); + return index.IsValid() + && ChangeReachabilityTravelFlags(index, travelFlags, set); +} + +bool idAAS2Local::ChangeReachabilityTravelFlags( + const idIndex index, + const int travelFlags, const bool set) { + const int reachNum = index.Get(); + if (file == nullptr || reachNum < 0 + || reachNum >= file->reachabilities.Num()) return false; + aas2Reachability_t& reach = file->reachabilities[reachNum]; + reach.travelFlags = set ? reach.travelFlags | travelFlags + : reach.travelFlags & ~std::uint32_t(travelFlags); + RemoveRoutingCacheUsingArea(reach.fromAreaNum); + RemoveRoutingCacheUsingArea(reach.toAreaNum); + return true; +} + +idIndex idAAS2Local::GetAreaReachability( + const int areaNum, int reachabilityNum) const { + if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num()) { + return idIndex(); + } + idIndex index = file->areas[areaNum].reach; + while (index.IsValid() && reachabilityNum-- > 0) { + const int value = index.Get(); + if (value < 0 || value >= file->reachabilities.Num()) { + return idIndex(); + } + index = file->reachabilities[value].next; + } + return index; +} + +int idAAS2Local::ClusterAreaNum(const int clusterNum, + const int areaNum) const { + if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num()) { + return 0; + } + const aas2Area_t& area = file->areas[areaNum]; + if (area.cluster > 0) return area.clusterAreaNum; + const int portalNum = -area.cluster; + if (portalNum <= 0 || portalNum >= file->portals.Num()) return 0; + const aas2Portal_t& portal = file->portals[portalNum]; + return portal.clusterAreaNum[clusterNum != portal.clusters[0] ? 1 : 0]; +} + +bool idAAS2Local::RouteToGoalArea(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const int travelFlags, + int& travelTime, + idIndex& reachability) { + travelTime = 0; + reachability.Invalidate(); + if (file == nullptr || startAreaNum <= 0 || goalAreaNum <= 0 + || startAreaNum >= file->areas.Num() + || goalAreaNum >= file->areas.Num()) return false; + if (startAreaNum == goalAreaNum) { + travelTime = AreaTravelTime(startAreaNum, startOrigin, goalOrigin); + return true; + } + + using queueValue_t = std::pair; + std::priority_queue, + std::greater> queue; + std::vector costs(static_cast(file->areas.Num()), + ROUTE_INFINITY); + std::vector> firstReach( + static_cast(file->areas.Num())); + costs[startAreaNum] = 0; + queue.push(queueValue_t(0, startAreaNum)); + while (!queue.empty()) { + const int cost = queue.top().first; + const int areaNum = queue.top().second; + queue.pop(); + if (cost != costs[areaNum]) continue; + if (areaNum == goalAreaNum) break; + const aas2Area_t& area = file->areas[areaNum]; + for (idIndex index = area.reach; + index.IsValid();) { + const int reachNum = index.Get(); + if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break; + const aas2Reachability_t& reach = file->reachabilities[reachNum]; + const idIndex next = reach.next; + if (ReachAllowed(reach, travelFlags) + && reach.toAreaNum > 0 + && reach.toAreaNum < file->areas.Num()) { + const idVec3 from = reach.Start(); + const idVec3 to = reach.End(); + int stepCost = reach.travelTime; + stepCost += AreaTravelTime(areaNum, + areaNum == startAreaNum ? startOrigin : from, from); + stepCost += AreaTravelTime(reach.toAreaNum, from, to); + const int nextCost = cost + (std::max)(1, stepCost); + if (nextCost < costs[reach.toAreaNum]) { + costs[reach.toAreaNum] = nextCost; + firstReach[reach.toAreaNum] = areaNum == startAreaNum + ? index : firstReach[areaNum]; + queue.push(queueValue_t(nextCost, reach.toAreaNum)); + } + } + index = next; + } + } + if (costs[goalAreaNum] == ROUTE_INFINITY) return false; + travelTime = costs[goalAreaNum] + + AreaTravelTime(goalAreaNum, AreaCenter(goalAreaNum), goalOrigin); + reachability = firstReach[goalAreaNum]; + return true; +} + +int idAAS2Local::TravelTimeToGoalArea(const int startAreaNum, + const idVec3& startOrigin, const int goalAreaNum, + const idVec3& goalOrigin, const int travelFlags, + const idAAS2TravelSpeeds*) { + int time = 0; + idIndex reachability; + return RouteToGoalArea(startAreaNum, startOrigin, goalAreaNum, + goalOrigin, travelFlags, time, reachability) ? time : 0; +} + +bool idAAS2Local::FindNearestGoal(idAAS2Goal& goal, + const int startAreaNum, const idVec3& startOrigin, + const int travelFlags, idAAS2Callback& callback) { + if (file == nullptr || startAreaNum <= 0 + || startAreaNum >= file->areas.Num()) return false; + using queueValue_t = std::pair; + std::priority_queue, + std::greater> queue; + std::vector costs(static_cast(file->areas.Num()), + ROUTE_INFINITY); + costs[startAreaNum] = 0; + queue.push(queueValue_t(0, startAreaNum)); + while (!queue.empty()) { + const int cost = queue.top().first; + const int areaNum = queue.top().second; + queue.pop(); + if (cost != costs[areaNum]) continue; + const idVec3 center = AreaCenter(areaNum); + if (callback.AreaIsGoal(this, areaNum, ¢er)) { + goal.areaNum = areaNum; + goal.origin = center; + return true; + } + const aas2Area_t& area = file->areas[areaNum]; + for (idIndex index = area.reach; + index.IsValid();) { + const int reachNum = index.Get(); + if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break; + const aas2Reachability_t& reach = file->reachabilities[reachNum]; + const idIndex next = reach.next; + const idVec3 from = reach.Start(); + const idVec3 to = reach.End(); + if (ReachAllowed(reach, travelFlags) + && callback.PathValid(this, &from, &to)) { + const int extra = callback.AdditionalTravelTimeForPath( + this, &from, &to); + const int nextCost = cost + reach.travelTime + + AreaTravelTime(areaNum, center, from) + extra; + if (reach.toAreaNum > 0 + && reach.toAreaNum < file->areas.Num() + && nextCost < costs[reach.toAreaNum]) { + costs[reach.toAreaNum] = nextCost; + queue.push(queueValue_t(nextCost, reach.toAreaNum)); + } + } + index = next; + } + } + return false; +} + +bool idAAS2Local::NearestPointReachable( + const idIndex tree, const idVec3& origin, + const int areaNum, const idVec3& destination, + const idBounds& searchBounds, const int travelFlags, + idAAS2NearestReachable& nearest) const { + std::memset(&nearest, 0, sizeof(nearest)); + if (file == nullptr || areaNum <= 0) return false; + idBounds destinationBounds(searchBounds); + destinationBounds[0] = destinationBounds[0] + destination; + destinationBounds[1] = destinationBounds[1] + destination; + int candidates[256] = {}; + const int count = file->BoundsAreaNums(tree.Get(), destinationBounds, + candidates, 256); + float nearestDistance = (std::numeric_limits::max)(); + int quickestTime = ROUTE_INFINITY; + bool found = false; + for (int index = 0; index < count; ++index) { + const int candidate = candidates[index]; + if (candidate <= 0 || candidate >= file->areas.Num()) continue; + idVec3 point = destination; + file->PushPointIntoAreaNum(candidate, point); + const idVec3 delta = point - destination; + const float distance = delta.x * delta.x + delta.y * delta.y + + delta.z * delta.z; + int time = 0; + idIndex first; + if (!const_cast(this)->RouteToGoalArea(areaNum, + origin, candidate, point, travelFlags, time, first)) continue; + if (distance < nearestDistance) { + nearestDistance = distance; + nearest.nearestDestArea = candidate; + nearest.nearestDestPos = point; + nearest.nearestTravelTime = time; + } + if (time < quickestTime) { + quickestTime = time; + nearest.quickestDestArea = candidate; + nearest.quickestDestPos = point; + nearest.quickestTravelTime = time; + } + found = true; + } + return found; +} + +void idAAS2Local::DeleteOldestCache() { + idAASRoutingCache* const cache = cacheListStart; + if (cache == nullptr) return; + if (cache->type == 0 && areaCacheIndex != nullptr + && cache->cluster >= 0 && cache->cluster < file->clusters.Num()) { + const int slot = ClusterAreaNum(cache->cluster, cache->areaNum); + idAASRoutingCache** link = &areaCacheIndex[cache->cluster][slot]; + while (*link != nullptr && *link != cache) link = &(*link)->next; + if (*link == cache) *link = cache->next; + } else if (portalCacheIndex != nullptr && cache->areaNum >= 0 + && cache->areaNum < portalCacheIndexSize) { + idAASRoutingCache** link = &portalCacheIndex[cache->areaNum]; + while (*link != nullptr && *link != cache) link = &(*link)->next; + if (*link == cache) *link = cache->next; + } + UnlinkCache(cache); + totalCacheMemory -= sizeof(*cache) + 3 * cache->size; + delete cache; +} + +void idAAS2Local::UpdateAreaRoutingCache(idAASRoutingCache* const cache) { + if (cache == nullptr || file == nullptr) return; + for (int area = 0; area < cache->size; ++area) { + const int sourceArea = area + 1; + if (sourceArea >= file->areas.Num()) break; + int time = 0; + idIndex first; + if (RouteToGoalArea(sourceArea, AreaCenter(sourceArea), + cache->areaNum, AreaCenter(cache->areaNum), + cache->travelFlags, time, first)) { + cache->travelTimes[area] = static_cast( + (std::min)(time, 65535)); + int ordinal = 0; + for (idIndex index = + file->areas[sourceArea].reach; index.IsValid(); + ++ordinal) { + if (index == first) break; + index = file->reachabilities[index.Get()].next; + } + cache->reachabilities[area] = static_cast(ordinal); + } + } +} + +idAASRoutingCache* idAAS2Local::GetAreaRoutingCache(const int clusterNum, + const int areaNum, const int travelFlags) { + if (file == nullptr || areaCacheIndex == nullptr || clusterNum < 0 + || clusterNum >= file->clusters.Num()) return nullptr; + const int slot = ClusterAreaNum(clusterNum, areaNum); + if (slot < 0 || slot >= file->clusters[clusterNum].numReachableAreas) { + return nullptr; + } + idAASRoutingCache** link = &areaCacheIndex[clusterNum][slot]; + while (*link != nullptr && (*link)->travelFlags != travelFlags) { + link = &(*link)->next; + } + if (*link == nullptr) { + idAASRoutingCache* const cache = + new idAASRoutingCache(file->areas.Num() - 1); + cache->type = 0; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->travelFlags = travelFlags; + *link = cache; + totalCacheMemory += sizeof(*cache) + 3 * cache->size; + UpdateAreaRoutingCache(cache); + } + LinkCache(*link); + return *link; +} + +void idAAS2Local::UpdatePortalRoutingCache(idAASRoutingCache* const cache) { + UpdateAreaRoutingCache(cache); +} + +idAASRoutingCache* idAAS2Local::GetPortalRoutingCache(const int clusterNum, + const int areaNum, const int travelFlags) { + if (file == nullptr || portalCacheIndex == nullptr || areaNum < 0 + || areaNum >= portalCacheIndexSize) return nullptr; + idAASRoutingCache** link = &portalCacheIndex[areaNum]; + while (*link != nullptr && (*link)->travelFlags != travelFlags) { + link = &(*link)->next; + } + if (*link == nullptr) { + idAASRoutingCache* const cache = + new idAASRoutingCache(file->areas.Num() - 1); + cache->type = 1; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->travelFlags = travelFlags; + *link = cache; + totalCacheMemory += sizeof(*cache) + 3 * cache->size; + UpdatePortalRoutingCache(cache); + } + LinkCache(*link); + return *link; +} + +bool idAAS2Local::GetClusterRoute(const int startAreaNum, + const idVec3& startOrigin, const int, const int goalAreaNum, + const int travelFlags, int& travelTime, + idIndex& bestReachability) { + return RouteToGoalArea(startAreaNum, startOrigin, goalAreaNum, + AreaCenter(goalAreaNum), travelFlags, travelTime, bestReachability); +} diff --git a/source/engine/gamelib/aas2/aas2_spacial.cpp b/source/engine/gamelib/aas2/aas2_spacial.cpp new file mode 100644 index 0000000..adffa4c --- /dev/null +++ b/source/engine/gamelib/aas2/aas2_spacial.cpp @@ -0,0 +1,157 @@ +#include "gamelib/aas2/aas2_local.h" + +#include "idlib/lib_print.h" + +#include + +namespace { + +bool BoundsIntersect(const idBounds& first, const idBounds& second) { + for (int axis = 0; axis < 3; ++axis) { + if (first[1][axis] < second[0][axis] + || first[0][axis] > second[1][axis]) return false; + } + return true; +} + +float DistanceSqr(const idVec3& first, const idVec3& second) { + const idVec3 delta = first - second; + return delta.x * delta.x + delta.y * delta.y + delta.z * delta.z; +} + +} // namespace + +int idAAS2Local::GetNumCoverPoints() const { + return file != nullptr ? file->cover.Num() : 0; +} + +const aas2Cover_t* idAAS2Local::GetCoverPoint(const int index) const { + return file != nullptr && index >= 0 && index < file->cover.Num() + ? &file->cover[index] : nullptr; +} + +int idAAS2Local::GetNumAreaCoverPoints(const int areaNum) const { + return file != nullptr && areaNum > 0 && areaNum < file->areas.Num() + ? file->areas[areaNum].numCover : 0; +} + +int idAAS2Local::GetAreaCoverPoints(const int areaNum, int* const points, + const int maxPoints, int* const numPoints) const { + if (numPoints != nullptr) *numPoints = 0; + if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num() + || points == nullptr || numPoints == nullptr) return 0; + const aas2Area_t& area = file->areas[areaNum]; + for (int index = 0; index < area.numCover; ++index) { + if (*numPoints >= maxPoints) { + idLibPrint::Warning( + "idAAS2Local::GetAreaCoverPoints - search list overflowed."); + break; + } + points[(*numPoints)++] = file->areaCoverIndex[area.firstCover + index]; + } + return *numPoints; +} + +void idAAS2Local::FindCoverPoints_Internal(const int startAreaNum, + const idVec3& origin, const idBounds& bounds, + const float maxDistSqr, const int requiredFlags, + const int excludeFlags, const int travelFlags, + int* const coverPoints, const int maxCover, + int& numCover) const { + if (file == nullptr || startAreaNum <= 0 + || startAreaNum >= file->areas.Num()) return; + std::vector visited( + static_cast(file->areas.Num()), 0); + int queue[1280] = {}; + int first = 0; + int last = 1; + queue[0] = startAreaNum; + visited[startAreaNum] = 1; + while (first < last) { + if (last >= 1280) return; + const int areaNum = queue[first++]; + const aas2Area_t& area = file->areas[areaNum]; + for (int index = 0; index < area.numCover; ++index) { + const int coverIndex = + file->areaCoverIndex[area.firstCover + index]; + if (coverIndex < 0 || coverIndex >= file->cover.Num()) continue; + const aas2Cover_t& coverPoint = file->cover[coverIndex]; + if ((requiredFlags != 0 + && (coverPoint.flags & requiredFlags) == 0) + || (coverPoint.flags & excludeFlags) != 0 + || DistanceSqr(coverPoint.origin, origin) >= maxDistSqr) { + continue; + } + if (numCover >= maxCover) { + idLibPrint::Warning( + "idAAS2Local::FindCoverPoints_Internal - search list overflowed."); + return; + } + coverPoints[numCover++] = coverIndex; + } + for (idIndex reachIndex = area.reach; + reachIndex.IsValid();) { + const int index = reachIndex.Get(); + if (index < 0 || index >= file->reachabilities.Num()) break; + const aas2Reachability_t& reach = file->reachabilities[index]; + const int nextArea = reach.toAreaNum; + if (nextArea > 0 && nextArea < file->areas.Num() + && visited[nextArea] == 0 + && (reach.travelFlags & travelFlags) != 0) { + idBounds areaBounds; + if (nextArea < file->areaBounds.Num()) { + areaBounds[0].Set( + float(file->areaBounds[nextArea].min[0]), + float(file->areaBounds[nextArea].min[1]), + float(file->areaBounds[nextArea].min[2])); + areaBounds[1].Set( + float(file->areaBounds[nextArea].max[0]), + float(file->areaBounds[nextArea].max[1]), + float(file->areaBounds[nextArea].max[2])); + } else { + areaBounds = file->AreaBounds(nextArea); + } + if (BoundsIntersect(bounds, areaBounds) && last < 1280) { + visited[nextArea] = 1; + queue[last++] = nextArea; + } + } + reachIndex = reach.next; + } + } +} + +int idAAS2Local::FindCoverPoints(const int areaNum, const idVec3& origin, + const float radius, const int requiredFlags, const int excludeFlags, + const int travelFlags, int* const points, const int maxPoints, + int* const numPoints) const { + if (numPoints != nullptr) *numPoints = 0; + if (areaNum <= 0 || points == nullptr || numPoints == nullptr + || maxPoints <= 0) return 0; + const idVec3 extent(radius, radius, radius); + idBounds bounds; + bounds[0] = origin - extent; + bounds[1] = origin + extent; + FindCoverPoints_Internal(areaNum, origin, bounds, radius * radius, + requiredFlags, excludeFlags, travelFlags, points, maxPoints, + *numPoints); + return *numPoints; +} + +int idAAS2Local::GetNumChokePoints() const { + return file != nullptr ? file->chokePoints.Num() : 0; +} + +const aas2ChokePoint_t* idAAS2Local::GetChokePoint(const int index) const { + return file != nullptr && index >= 0 && index < file->chokePoints.Num() + ? &file->chokePoints[index] : nullptr; +} + +int idAAS2Local::GetNumHintNodes() const { + return file != nullptr ? file->hintNodes.Num() : 0; +} + +const aas2HintNode_t* idAAS2Local::GetHintNode(const int index) const { + return file != nullptr && index >= 0 && index < file->hintNodes.Num() + ? &file->hintNodes[index] : nullptr; +} diff --git a/source/engine/gamelib/aas2/aas2_subdivision.cpp b/source/engine/gamelib/aas2/aas2_subdivision.cpp new file mode 100644 index 0000000..61a209b --- /dev/null +++ b/source/engine/gamelib/aas2/aas2_subdivision.cpp @@ -0,0 +1,49 @@ +#include "gamelib/aas2/aas2_local.h" + +bool idAAS2Local::GetAreaCells(const int areaNumber, const int subdivisionSize, + int* const subdivisionX, int* const subdivisionY) const { + if (file == nullptr || areaNumber <= 0 || subdivisionSize <= 0) { + return false; + } + + const idBounds bounds = file->AreaBounds(areaNumber); + + *subdivisionX = static_cast(bounds[1].x - bounds[0].x) / + subdivisionSize + 1; + *subdivisionY = static_cast(bounds[1].y - bounds[0].y) / + subdivisionSize + 1; + return true; +} + +bool idAAS2Local::WorldPosToCellPos(const int areaNumber, + const int subdivisionSize, const idVec3& origin, + int* const subdivisionX, int* const subdivisionY) const { + if (file == nullptr || areaNumber <= 0 || subdivisionSize <= 0) { + return false; + } + + const idBounds bounds = file->AreaBounds(areaNumber); + + *subdivisionX = static_cast( + (origin.x - bounds[0].x) / static_cast(subdivisionSize)); + *subdivisionY = static_cast( + (origin.y - bounds[0].y) / static_cast(subdivisionSize)); + return true; +} + +bool idAAS2Local::CellPosToWorldPos(const int areaNumber, + const int subdivisionSize, const int subdivisionX, + const int subdivisionY, idVec3& worldPosition, + const bool findZ) const { + (void)findZ; + if (file == nullptr || areaNumber <= 0) { + return false; + } + + const idBounds bounds = file->AreaBounds(areaNumber); + + worldPosition.x = bounds[0].x + subdivisionSize * subdivisionX; + worldPosition.y = bounds[0].y + subdivisionSize * subdivisionY; + worldPosition.z = bounds[1].z; + return true; +} diff --git a/source/engine/gamelib/aas2/aas2callback_avoidlocation.cpp b/source/engine/gamelib/aas2/aas2callback_avoidlocation.cpp new file mode 100644 index 0000000..137a3b9 --- /dev/null +++ b/source/engine/gamelib/aas2/aas2callback_avoidlocation.cpp @@ -0,0 +1,61 @@ +#include "gamelib/aas2/aas2callback_avoidlocation.h" + +#include + +idAAS2Callback_AvoidLocation::idAAS2Callback_AvoidLocation() + : avoidLocation(0.0f, 0.0f, 0.0f) + , avoidDist(0.0f) + , obstacles(nullptr) + , numObstacles(0) { +} + +idAAS2Callback_AvoidLocation::~idAAS2Callback_AvoidLocation() { + obstacles = nullptr; +} + +bool idAAS2Callback_AvoidLocation::PathValid(const idAAS2 *, + const idVec3 *start, const idVec3 *end) { + if (obstacles == nullptr || numObstacles <= 0 + || start == nullptr || end == nullptr) { + return true; + } + for (int index = 0; index < numObstacles; ++index) { + if (obstacles[index].expAbsBounds.LineIntersection(*start, *end)) { + return false; + } + } + return true; +} + +int idAAS2Callback_AvoidLocation::AdditionalTravelTimeForPath( + const idAAS2 *, const idVec3 *start, const idVec3 *end) { + if (avoidDist <= 0.0f || start == nullptr || end == nullptr) { + return 0; + } + + const idVec3 segment = *end - *start; + const float segmentLengthSqr = segment.LengthSqr(); + idVec3 closest = *end; + if (segmentLengthSqr > 1.0e-30f) { + const float inverseLength = 1.0f / std::sqrt(segmentLengthSqr); + const idVec3 direction = segment * inverseLength; + const idVec3 projected = *start + + direction * ((avoidLocation - *start).Dot(direction)); + + const bool outside = + (projected.x > start->x + 0.1f && projected.x > end->x + 0.1f) + || (projected.x < start->x - 0.1f && projected.x < end->x - 0.1f) + || (projected.y > start->y + 0.1f && projected.y > end->y + 0.1f) + || (projected.y < start->y - 0.1f && projected.y < end->y - 0.1f) + || (projected.z > start->z + 0.1f && projected.z > end->z + 0.1f) + || (projected.z < start->z - 0.1f && projected.z < end->z - 0.1f); + if (!outside) { + closest = projected; + } + } + + const float distance = (avoidLocation - closest).Length(); + return distance < avoidDist + ? static_cast((avoidDist - distance) * 10.0f) + : 0; +} diff --git a/source/engine/gamelib/aas2/aas2callback_avoidlocation.h b/source/engine/gamelib/aas2/aas2callback_avoidlocation.h index c4d15de..ebe2fe5 100644 --- a/source/engine/gamelib/aas2/aas2callback_avoidlocation.h +++ b/source/engine/gamelib/aas2/aas2callback_avoidlocation.h @@ -1,23 +1,43 @@ #pragma once +#include "gamelib/aas2/aas2.h" +#include "idlib/bv/bounds.h" + // Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. // Original PDB header: w:\tech5\engine\gamelib\aas2\aas2callback_avoidlocation.h // Recovered logical types: 1 // Signatures retain Xbox 360 ABI evidence and may still require manual review. +// IDA Local Type ordinal 23521; PDB kind: struct. The PDB mapped this type to +// tungsten/game/ai/aasobstacle.h; it is declared here to keep the recovered +// GameLib callback boundary self-contained. +struct idAAS2Obstacle +{ + idBounds absBounds; + idBounds expAbsBounds; +}; + // IDA Local Type ordinal 23522; PDB kind: class. class idAAS2Callback_AvoidLocation : public idAAS2Callback { public: + idAAS2Callback_AvoidLocation(); + // Recovered virtual interface; IDA vtable ordinal 23523. - virtual ~idAAS2Callback_AvoidLocation(); - virtual bool PathValid(const idAAS2 *, const idVec3 *, const idVec3 *); - virtual int AdditionalTravelTimeForPath(const idAAS2 *, const idVec3 *, const idVec3 *); - virtual bool AreaIsGoal(const idAAS2 *, int, const idVec3 *); + ~idAAS2Callback_AvoidLocation() override; + bool PathValid(const idAAS2 *, const idVec3 *, const idVec3 *) override; + int AdditionalTravelTimeForPath(const idAAS2 *, const idVec3 *, const idVec3 *) override; idVec3 avoidLocation; float avoidDist; const idAAS2Obstacle *obstacles; int numObstacles; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idAAS2Obstacle) == 48, + "Recovered idAAS2Obstacle ABI changed"); +static_assert(sizeof(idAAS2Callback_AvoidLocation) == 28, + "Recovered idAAS2Callback_AvoidLocation ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/aas2callback_findflaggedarea.cpp b/source/engine/gamelib/aas2/aas2callback_findflaggedarea.cpp new file mode 100644 index 0000000..efbe5f7 --- /dev/null +++ b/source/engine/gamelib/aas2/aas2callback_findflaggedarea.cpp @@ -0,0 +1,21 @@ +#include "gamelib/aas2/aas2callback_findflaggedarea.h" + +idAAS2Callback_FindFlaggedArea::idAAS2Callback_FindFlaggedArea( + const int areaFlag_, const bool set) + : idAAS2Callback_AvoidLocation() + , areaFlag(areaFlag_) + , test(set ? 1 : 0) { +} + +idAAS2Callback_FindFlaggedArea::~idAAS2Callback_FindFlaggedArea() = default; + +bool idAAS2Callback_FindFlaggedArea::AreaIsGoal(const idAAS2 *aas, + const int areaNum, const idVec3 *) { + if (aas == nullptr) { + return false; + } + // The recovered callback ABI supplies a const pointer even though the + // idAAS2 virtual interface did not mark GetAreaFlags const. + const int flags = const_cast(aas)->GetAreaFlags(areaNum); + return ((flags & areaFlag) != 0) == (test != 0); +} diff --git a/source/engine/gamelib/aas2/aas2callback_findflaggedarea.h b/source/engine/gamelib/aas2/aas2callback_findflaggedarea.h index 37ec05f..41ecbe4 100644 --- a/source/engine/gamelib/aas2/aas2callback_findflaggedarea.h +++ b/source/engine/gamelib/aas2/aas2callback_findflaggedarea.h @@ -1,5 +1,7 @@ #pragma once +#include "gamelib/aas2/aas2callback_avoidlocation.h" + // Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. // Original PDB header: w:\tech5\engine\gamelib\aas2\aas2callback_findflaggedarea.h // Recovered logical types: 1 @@ -10,12 +12,17 @@ class idAAS2Callback_FindFlaggedArea : public idAAS2Callback_AvoidLocation { public: + idAAS2Callback_FindFlaggedArea(int areaFlag, bool set); + // Recovered virtual interface; IDA vtable ordinal 23525. - virtual ~idAAS2Callback_FindFlaggedArea(); - virtual bool PathValid(const idAAS2 *, const idVec3 *, const idVec3 *); - virtual int AdditionalTravelTimeForPath(const idAAS2 *, const idVec3 *, const idVec3 *); - virtual bool AreaIsGoal(const idAAS2 *, int, const idVec3 *); + ~idAAS2Callback_FindFlaggedArea() override; + bool AreaIsGoal(const idAAS2 *, int, const idVec3 *) override; int areaFlag; int test; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idAAS2Callback_FindFlaggedArea) == 36, + "Recovered idAAS2Callback_FindFlaggedArea ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/deferredviscache.cpp b/source/engine/gamelib/aas2/deferredviscache.cpp new file mode 100644 index 0000000..13b9221 --- /dev/null +++ b/source/engine/gamelib/aas2/deferredviscache.cpp @@ -0,0 +1,371 @@ +#include "gamelib/aas2/deferredviscache.h" + +#include "cm/collisionmodelmanager.h" +#include "gamelib/aas2/aas2.h" +#include "idlib/lib_print.h" + +#include +#include +#include + +namespace { + +constexpr int QUERY_POOL_SIZE = 5120; +constexpr int HASH_SIZE = 4096; +constexpr int CLIP_QUERY_COUNT = 256; +constexpr int MAX_SUBMITS_PER_FRAME = 128; +constexpr int MAX_LRU_ENTRIES = 4096; + +int HashQuery(const std::int16_t start[3], const std::int16_t end[3]) { + return (start[0] + start[1] + start[2] + + end[0] + end[1] + end[2]) & (HASH_SIZE - 1); +} + +std::int16_t Quantize(const float value) { + const int integer = static_cast(value); + return static_cast((std::max)(-32768, + (std::min)(32767, integer))); +} + +bool SameQuery(const idDeferredVisQuery& query, + const std::uint16_t start[3], const std::uint16_t end[3]) { + for (int axis = 0; axis < 3; ++axis) { + if (static_cast(query.start[axis]) != start[axis] + || static_cast(query.end[axis]) != end[axis]) + return false; + } + return true; +} + +} // namespace + +idDeferredVisCache::idDeferredVisCache() + : hashTable(), clipQueries(), curClipQueryIdx(0), + leastRecentlyUsedList{nullptr, nullptr, 0}, + priorityList{nullptr, nullptr, 0}, clip(nullptr), queryList(nullptr), + queryPool(new idDeferredVisQuery[QUERY_POOL_SIZE]), expiredIndex(0), + numFrames(0), numCreatedThisFrame(0), numBumped(0), + numQueriesIssued(0), totalQueriesIssued(0), numPurged(0), + numExpired(0), numCacheHits(0), totalCacheHits(0), + maxCreatedInAFrame(0), recentNumFrames(0), recentTotalCacheHits(0), + recentTotalQueriesIssued(0), recentCacheHitRatio(0.0f), + recentAvgHitRatio(0.0f), initialized(false), alreadyWarned(false) { + hashTable.SetNum(HASH_SIZE); + for (int index = 0; index < HASH_SIZE; ++index) + hashTable[index] = nullptr; + clipQueries.SetNum(CLIP_QUERY_COUNT); + for (int index = 0; index < CLIP_QUERY_COUNT; ++index) + clipQueries[index].index = 0; + for (int index = 0; index < QUERY_POOL_SIZE; ++index) { + idDeferredVisQuery& query = queryPool[index]; + std::memset(&query, 0, sizeof(query)); + query.frameCounter = -1; + query.next = index + 1 < QUERY_POOL_SIZE + ? &queryPool[index + 1] : nullptr; + } + queryList = queryPool; +} + +idDeferredVisCache::~idDeferredVisCache() { + Shutdown(); + delete[] queryPool; + queryPool = nullptr; + queryList = nullptr; +} + +idDeferredVisQuery* idDeferredVisCache::AllocNode() { + if (queryList == nullptr) return nullptr; + idDeferredVisQuery* const node = queryList; + queryList = node->next; + node->nextInHash = nullptr; + node->prev = nullptr; + node->next = nullptr; + node->frameCounter = -1; + node->queryIdx = 0; + node->state = VIS_INVALID; + node->pad = 0; + return node; +} + +void idDeferredVisCache::FreeLinkedList(deferredVisList_t& list) { + idDeferredVisQuery* node = list.headNode; + while (node != nullptr) { + idDeferredVisQuery* const next = node->next; + node->state = VIS_INVALID; + node->frameCounter = -1; + node->prev = nullptr; + node->next = queryList; + queryList = node; + node = next; + } + list.headNode = nullptr; + list.tailNode = nullptr; + list.numElements = 0; +} + +void idDeferredVisCache::RemoveNoDelete(deferredVisList_t& list, + idDeferredVisQuery* const node) { + if (node == nullptr) return; + if (node->prev != nullptr) node->prev->next = node->next; + else if (list.headNode == node) list.headNode = node->next; + if (node->next != nullptr) node->next->prev = node->prev; + else if (list.tailNode == node) list.tailNode = node->prev; + node->prev = nullptr; + node->next = nullptr; + if (list.numElements > 0) --list.numElements; +} + +void idDeferredVisCache::AddToFront(deferredVisList_t& list, + idDeferredVisQuery* const node) { + node->prev = nullptr; + node->next = list.headNode; + if (list.headNode != nullptr) list.headNode->prev = node; + else list.tailNode = node; + list.headNode = node; + ++list.numElements; +} + +void idDeferredVisCache::RemoveFromHash(idDeferredVisQuery* const node) { + if (node == nullptr || hashTable.IsEmpty()) return; + const int bucket = HashQuery(node->start, node->end); + idDeferredVisQuery** link = &hashTable[bucket]; + while (*link != nullptr && *link != node) + link = &(*link)->nextInHash; + if (*link == node) *link = node->nextInHash; + node->nextInHash = nullptr; +} + +void idDeferredVisCache::FreeNode(idDeferredVisQuery* const node) { + if (node == nullptr) return; + RemoveFromHash(node); + node->state = VIS_INVALID; + node->frameCounter = -1; + node->prev = nullptr; + node->next = queryList; + queryList = node; +} + +void idDeferredVisCache::Init(idClip* const inClip) { + clip = inClip; + initialized = true; +} + +void idDeferredVisCache::Shutdown() { + clip = nullptr; + if (queryPool == nullptr) return; + FreeLinkedList(leastRecentlyUsedList); + FreeLinkedList(priorityList); + for (int index = 0; index < HASH_SIZE; ++index) + hashTable[index] = nullptr; + queryList = nullptr; + for (int index = QUERY_POOL_SIZE - 1; index >= 0; --index) { + queryPool[index].state = VIS_INVALID; + queryPool[index].frameCounter = -1; + queryPool[index].nextInHash = nullptr; + queryPool[index].prev = nullptr; + queryPool[index].next = queryList; + queryList = &queryPool[index]; + } + initialized = false; + expiredIndex = 0; + numFrames = 0; + numCreatedThisFrame = 0; + numBumped = 0; + numQueriesIssued = 0; + totalQueriesIssued = 0; + totalCacheHits = 0; +} + +std::uint8_t idDeferredVisCache::GetVisQuery( + const std::uint16_t start[3], const std::uint16_t end[3], + const int flags) { + if (!initialized || start == nullptr || end == nullptr) return VIS_INVALID; + std::int16_t signedStart[3]; + std::int16_t signedEnd[3]; + for (int axis = 0; axis < 3; ++axis) { + signedStart[axis] = static_cast(start[axis]); + signedEnd[axis] = static_cast(end[axis]); + } + const int bucket = HashQuery(signedStart, signedEnd); + for (idDeferredVisQuery* node = hashTable[bucket]; node != nullptr; + node = node->nextInHash) { + if (!SameQuery(*node, start, end)) continue; + if (node->state >= VIS_BLOCKED) { + ++numCacheHits; + ++totalCacheHits; + if (leastRecentlyUsedList.headNode != node) { + RemoveNoDelete(leastRecentlyUsedList, node); + AddToFront(leastRecentlyUsedList, node); + } + } + if (node->frameCounter == 1 && (flags & 2) != 0) { + node->frameCounter = 0; + ++numBumped; + } + return node->state; + } + idDeferredVisQuery* const node = AllocNode(); + if (node == nullptr) { + if (!alreadyWarned) { + idLibPrint::Warning( + "idDeferredVisCache::GetVisQuery ran out of query nodes"); + alreadyWarned = true; + } + return VIS_INVALID; + } + ++numCreatedThisFrame; + for (int axis = 0; axis < 3; ++axis) { + node->start[axis] = signedStart[axis]; + node->end[axis] = signedEnd[axis]; + } + node->state = VIS_PENDING; + node->nextInHash = hashTable[bucket]; + hashTable[bucket] = node; + AddToFront(priorityList, node); + return VIS_PENDING; +} + +void idDeferredVisCache::UpdateCollisionQueries() { + if (!initialized || clip == nullptr) return; + maxCreatedInAFrame = (std::max)(maxCreatedInAFrame, + numCreatedThisFrame); + numPurged = 0; + numExpired = 0; + numCreatedThisFrame = 0; + numBumped = 0; + numCacheHits = 0; + idDeferredVisQuery* node = priorityList.headNode; + while (node != nullptr) { + idDeferredVisQuery* const next = node->next; + if (node->state == VIS_SUBMITTED) { + trace_t trace{}; + trace.fraction = 1.0f; + idCollisionQuery query{}; + query.offset = clipQueries[node->queryIdx].index; + collisionModelManager.GetContentsResult(&trace, query, false); + node->state = trace.fraction >= 1.0f ? VIS_CLEAR : VIS_BLOCKED; + node->frameCounter = trace.c.entityNum == 8190 ? 30 : 18; + RemoveNoDelete(priorityList, node); + AddToFront(leastRecentlyUsedList, node); + } + node = next; + } +} + +void idDeferredVisCache::SubmitCollisionQueries() { + if (!initialized || clip == nullptr) return; + int submitted = 0; + for (idDeferredVisQuery* node = priorityList.headNode; + node != nullptr && submitted < MAX_SUBMITS_PER_FRAME; + node = node->next) { + if (node->state != VIS_PENDING) continue; + const idVec3 start(static_cast(node->start[0]), + static_cast(node->start[1]), + static_cast(node->start[2])); + const idVec3 end(static_cast(node->end[0]), + static_cast(node->end[1]), + static_cast(node->end[2])); + clipQueries[curClipQueryIdx] = clip->Translation(nullptr, start, end, + nullptr, idMat3(1.0f), 1, -1, false, + "idDeferredVisCache"); + node->queryIdx = static_cast(curClipQueryIdx); + curClipQueryIdx = (curClipQueryIdx + 1) & 255; + node->state = VIS_SUBMITTED; + ++submitted; + } + numQueriesIssued = submitted; + totalQueriesIssued += submitted; + while (leastRecentlyUsedList.numElements > MAX_LRU_ENTRIES) { + idDeferredVisQuery* const expired = leastRecentlyUsedList.tailNode; + RemoveNoDelete(leastRecentlyUsedList, expired); + FreeNode(expired); + ++numPurged; + } + const int endIndex = (std::min)(QUERY_POOL_SIZE, expiredIndex + 512); + for (int index = expiredIndex; index < endIndex; ++index) { + idDeferredVisQuery* const candidate = &queryPool[index]; + if (candidate->frameCounter < 0) continue; + if (--candidate->frameCounter <= 0) { + RemoveNoDelete(leastRecentlyUsedList, candidate); + FreeNode(candidate); + ++numExpired; + } + } + expiredIndex = endIndex < QUERY_POOL_SIZE ? endIndex : 0; + alreadyWarned = false; + ++numFrames; +} + +void idDeferredVisCache::PrintDebugInfo() const { + idLibPrint::Printf("Deferred vis: queries %d, cache hits %d, " + "priority %d, LRU %d\n", totalQueriesIssued, totalCacheHits, + priorityList.numElements, leastRecentlyUsedList.numElements); +} + +idDeferredVisGrid::idDeferredVisGrid() : cells{}, xyz{} { +} + +void idDeferredVisGrid::Init(const idVec3& entityPos) { + xyz[0] = static_cast(Quantize(entityPos.x) + 32); + Update(entityPos, nullptr, -1, 0); +} + +void idDeferredVisGrid::Update(const idVec3& entityPos, + const idAAS2* const aas, const int areaNum, + const int travelFlags) { + const std::int16_t newX = static_cast( + (Quantize(entityPos.x) + 31) & ~31); + const std::int16_t newY = static_cast( + (Quantize(entityPos.y) + 31) & ~31); + const std::int16_t newZ = Quantize(entityPos.z); + if (newX == xyz[0] && newY == xyz[1] && newZ == xyz[2]) return; + xyz[0] = newX; + xyz[1] = newY; + xyz[2] = newZ; + std::memset(cells, 1, sizeof(cells)); + if (aas != nullptr) { + std::uint8_t reachable[64]; + std::memset(reachable, 1, sizeof(reachable)); + aas->ClipGridToAreas(idVec3(static_cast(xyz[0]), + static_cast(xyz[1]), static_cast(xyz[2])), + areaNum, travelFlags, 32, 8, reachable); + for (int index = 0; index < 64; ++index) + if (reachable[index] == 0) cells[index] = 0; + } +} + +float idDeferredVisGrid::GetGridVisibility(const idVec3& worldPoint, + idDeferredVisCache& cache, const float* const staggerZ, + const int flags, idColor*, idColor*) { + std::uint16_t start[3] = { + static_cast(Quantize(worldPoint.x)), + static_cast(Quantize(worldPoint.y)), + static_cast(Quantize( + staggerZ != nullptr ? *staggerZ : worldPoint.z)) + }; + int resolved = 0; + int visible = 0; + for (int index = 0; index < 64; ++index) { + if (cells[index] == 0) continue; + const int x = index & 7; + const int y = index >> 3; + std::uint16_t end[3] = { + static_cast(static_cast( + xyz[0] - 128 + x * 32)), + static_cast(static_cast( + xyz[1] - 128 + y * 32)), + static_cast(xyz[2]) + }; + const std::uint8_t state = cache.GetVisQuery(start, end, flags); + if (state == idDeferredVisCache::VIS_CLEAR) { + ++visible; + ++resolved; + } else if (state == idDeferredVisCache::VIS_BLOCKED) { + ++resolved; + } + } + return resolved > 0 ? static_cast(visible) / resolved : 0.0f; +} + +void idDeferredVisGrid::DebugDrawGrid() const { +} diff --git a/source/engine/gamelib/aas2/deferredviscache.h b/source/engine/gamelib/aas2/deferredviscache.h index 4b05a09..9011258 100644 --- a/source/engine/gamelib/aas2/deferredviscache.h +++ b/source/engine/gamelib/aas2/deferredviscache.h @@ -1,39 +1,113 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\aas2\deferredviscache.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/physics/clip.h" +#include "idlib/containers/list.h" +#include "idlib/math/vector.h" +#include -// IDA Local Type ordinal 17280; PDB kind: class. -class __declspec(align(4)) idDeferredVisCache -{ -public: - idList hashTable; - idList clipQueries; - int curClipQueryIdx; - deferredVisList_t leastRecentlyUsedList; - deferredVisList_t priorityList; - idClip *clip; - idDeferredVisQuery *queryList; - idDeferredVisQuery *queryPool; - int expiredIndex; - int numFrames; - int numCreatedThisFrame; - int numBumped; - int numQueriesIssued; - int totalQueriesIssued; - int numPurged; - int numExpired; - int numCacheHits; - int totalCacheHits; - int maxCreatedInAFrame; - int recentNumFrames; - int recentTotalCacheHits; - int recentTotalQueriesIssued; - float recentCacheHitRatio; - float recentAvgHitRatio; - bool initialized; - bool alreadyWarned; +class idAAS2; +class idColor; + +struct idDeferredVisQuery { + idDeferredVisQuery* nextInHash; + idDeferredVisQuery* prev; + idDeferredVisQuery* next; + std::int16_t start[3]; + std::int16_t end[3]; + std::int16_t frameCounter; + std::uint8_t queryIdx; + std::uint8_t state; + int pad; }; + +struct deferredVisList_t { + idDeferredVisQuery* headNode; + idDeferredVisQuery* tailNode; + int numElements; +}; + +class idDeferredVisCache { +public: + enum queryState_t : std::uint8_t { + VIS_INVALID = 0, + VIS_PENDING = 1, + VIS_SUBMITTED = 2, + VIS_BLOCKED = 3, + VIS_CLEAR = 4 + }; + + idDeferredVisCache(); + ~idDeferredVisCache(); + void Init(idClip* inClip); + void Shutdown(); + void UpdateCollisionQueries(); + void SubmitCollisionQueries(); + std::uint8_t GetVisQuery(const std::uint16_t start[3], + const std::uint16_t end[3], int flags); + void PrintDebugInfo() const; + + idList hashTable; + idList clipQueries; + int curClipQueryIdx; + deferredVisList_t leastRecentlyUsedList; + deferredVisList_t priorityList; + idClip* clip; + idDeferredVisQuery* queryList; + idDeferredVisQuery* queryPool; + int expiredIndex; + int numFrames; + int numCreatedThisFrame; + int numBumped; + int numQueriesIssued; + int totalQueriesIssued; + int numPurged; + int numExpired; + int numCacheHits; + int totalCacheHits; + int maxCreatedInAFrame; + int recentNumFrames; + int recentTotalCacheHits; + int recentTotalQueriesIssued; + float recentCacheHitRatio; + float recentAvgHitRatio; + bool initialized; + bool alreadyWarned; + +private: + idDeferredVisQuery* AllocNode(); + void FreeLinkedList(deferredVisList_t& list); + void RemoveNoDelete(deferredVisList_t& list, + idDeferredVisQuery* node); + void AddToFront(deferredVisList_t& list, + idDeferredVisQuery* node); + void RemoveFromHash(idDeferredVisQuery* node); + void FreeNode(idDeferredVisQuery* node); +}; + +class idDeferredVisGrid { +public: + idDeferredVisGrid(); + void Init(const idVec3& entityPos); + void Update(const idVec3& entityPos, const idAAS2* aas, + int areaNum, int travelFlags); + float GetGridVisibility(const idVec3& worldPoint, + idDeferredVisCache& cache, const float* staggerZ, + int flags, idColor* missDebugColor = nullptr, + idColor* hitDebugColor = nullptr); + void DebugDrawGrid() const; + + std::uint8_t cells[64]; + std::int16_t xyz[3]; +}; + +static_assert(sizeof(idDeferredVisGrid) == 70, + "Recovered deferred visibility grid ABI changed"); +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idDeferredVisQuery) == 32, + "Recovered deferred visibility query ABI changed"); +static_assert(sizeof(deferredVisList_t) == 12, + "Recovered deferred visibility list ABI changed"); +static_assert(sizeof(idDeferredVisCache) == 140, + "Recovered deferred visibility cache ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/obstacleavoidance.cpp b/source/engine/gamelib/aas2/obstacleavoidance.cpp new file mode 100644 index 0000000..def1671 --- /dev/null +++ b/source/engine/gamelib/aas2/obstacleavoidance.cpp @@ -0,0 +1,355 @@ +#include "gamelib/aas2/obstacleavoidance.h" + +#include "gamelib/aas2/obstacleavoidancemanagerlocal.h" +#include "gamelib/effects/weapontracemanager.h" +#include "idlib/bv/box.h" +#include "idlib/filesystem/filesystem.h" + +#include +#include +#include +#include + +namespace { + +constexpr int MAX_OBSTACLES = 128; +constexpr int MAX_VERTICES = 0x300; +constexpr int MAX_EDGES = 0x200; +constexpr int MAX_CORNERS = 16; +constexpr int MAX_TRACES = 256; +constexpr int TEMP_MEMORY_SIZE = 0x10000; +constexpr char QUERY_IDENTIFIER[] = "ObstacleAvoidanceQuery"; + +struct obstacleTestQuery_t { + int time; + int gameMsPerFrame; + idBounds bbox; + idVec3 gravity; + float obstacleRadius; + float frameMoveDist; + float circleCornerRadius; + int flags; +}; + +obstacleTestQuery_t testQuery{}; + +template +bool WriteValue(idFile& file, const type_t& value) { + return file.Write(&value, static_cast(sizeof(value))) + == sizeof(value); +} + +template +bool ReadValue(idFile& file, type_t& value) { + return file.Read(&value, static_cast(sizeof(value))) + == sizeof(value); +} + +bool WriteArray(idFile& file, const void* const data, + const std::size_t elementSize, const int count) { + const std::size_t byteCount = elementSize * static_cast(count); + return byteCount == 0 || file.Write(data, + static_cast(byteCount)) == byteCount; +} + +bool ReadArray(idFile& file, void* const data, + const std::size_t elementSize, const int count) { + const std::size_t byteCount = elementSize * static_cast(count); + return byteCount == 0 || file.Read(data, + static_cast(byteCount)) == byteCount; +} + +void RunObstacleAvoidanceJob(obstacleParms_t& parms) { + std::vector scratch(TEMP_MEMORY_SIZE); + idObstacleAvoidanceCore core; + core.FindPathAroundObstacles(*parms.path, + parms.debugLines, parms.maxDebugLines, + parms.debugText, parms.maxDebugText, + scratch.data(), static_cast(scratch.size()), + parms.bbox, parms.gravity, parms.lastDir, parms.lastCorner, + parms.obstacleRadius, parms.frameMoveDist, parms.cornerCircleRadius, + parms.flags, *parms.route, parms.obstacles, parms.numObstacles, + parms.vertices, parms.numVertices, parms.edges, parms.numEdges, + parms.corners, parms.numCorners, parms.traces, parms.numTraces, + parms.traceFractions); + parms.path->done = true; +} + +} // namespace + +idObstacleAvoidance::idObstacleAvoidance() + : buffers(nullptr), lastDir(), lastCorner(), lastUpdateTime(-1) { +} + +idObstacleAvoidance::~idObstacleAvoidance() { + obstacleAvoidanceManagerLocal.FreeObstacleBuffers(this); +} + +void idObstacleAvoidance::AllocBuffers() { + obstacleAvoidanceManagerLocal.AllocObstacleBuffers(this); +} + +obstacleVertex_t* idObstacleAvoidance::GetVertexArray() { + return buffers != nullptr ? buffers->vertices : nullptr; +} + +int idObstacleAvoidance::GetMaxVertices() const { + return buffers != nullptr ? MAX_VERTICES : 0; +} + +void idObstacleAvoidance::SetNumVertices(const int num) { + if (buffers != nullptr) buffers->numVertices = num; +} + +obstacleEdge_t* idObstacleAvoidance::GetEdgeArray() { + return buffers != nullptr ? buffers->edges : nullptr; +} + +int idObstacleAvoidance::GetMaxEdges() const { + return buffers != nullptr ? MAX_EDGES : 0; +} + +void idObstacleAvoidance::SetNumEdges(const int num) { + if (buffers != nullptr) buffers->numEdges = num; +} + +void idObstacleAvoidance::AddObstacle(const idBox& box, + const idSpawnId id, const bool soft) { + if (buffers == nullptr || buffers->numObstacles >= MAX_OBSTACLES) return; + obstacleBox_t& obstacle = buffers->obstacles[buffers->numObstacles++]; + obstacle.axis = box.axis; + obstacle.center = box.center; + obstacle.extents = box.extents; + obstacle.id = id.Get(); + obstacle.soft = soft; +} + +void idObstacleAvoidance::AddObstacle(const idBounds& bounds, + const idVec3& origin, const idMat3& axis, const idSpawnId id, + const bool soft) { + AddObstacle(idBox(bounds, origin, axis), id, soft); +} + +void idObstacleAvoidance::Restart(const idVec3& start, const idVec3& goal) { + if (buffers == nullptr || buffers->path == nullptr) return; + obstaclePath_t& path = *buffers->path; + path = obstaclePath_t(); + path.seekPos[0] = goal; + path.seekPos[1] = goal; + path.startPosOutsideObstacles = start; + path.seekPosOutsideObstacles = start; + path.wallCorners[0] = goal; + path.wallCorners[1] = goal; + path.firstObstacle = -1; + path.startPosObstacle = -1; + path.seekPosObstacle = -1; + path.hasValidPath = true; + path.startPosValid = true; + path.seekPosValid = true; + path.done = false; + for (obstaclePath_t::radar_t& radar : path.radar) { + radar.direction.Zero(); + radar.obstacle = 0; + } +} + +void idObstacleAvoidance::ProjectTopDown(idVec3& point, + const idVec3& startPos, const float scale) const { + // The recovered implementation projects through the local player's view. + // GameLib has no renderer/game-world dependency, so retain the same local + // scale operation for callers that provide debug geometry. + point = startPos + (point - startPos) * scale; +} + +void idObstacleAvoidance::DrawBox(const idVec3&, const float) const { + // Drawing is performed by the engine-facing debug consumer. Keeping it + // outside GameLib prevents the recovered navigation code from owning RW. +} + +bool idObstacleAvoidance::SaveQuery(const char* const fileName) const { + if (fileName == nullptr || *fileName == '\0' || buffers == nullptr) { + return false; + } + idFileLocal file(fileSystem->OpenFileWrite(fileName, FSPATH_SAVE)); + if (file.file == nullptr) return false; + if (file->WriteString(QUERY_IDENTIFIER) == 0) return false; + return WriteValue(*file.file, testQuery.time) + && WriteValue(*file.file, testQuery.gameMsPerFrame) + && WriteValue(*file.file, testQuery.bbox) + && WriteValue(*file.file, testQuery.gravity) + && WriteValue(*file.file, testQuery.obstacleRadius) + && WriteValue(*file.file, testQuery.frameMoveDist) + && WriteValue(*file.file, testQuery.circleCornerRadius) + && WriteValue(*file.file, testQuery.flags) + && WriteValue(*file.file, buffers->numObstacles) + && WriteValue(*file.file, buffers->numVertices) + && WriteValue(*file.file, buffers->numEdges) + && WriteValue(*file.file, buffers->numCorners) + && WriteValue(*file.file, *buffers->route) + && WriteArray(*file.file, buffers->obstacles, + sizeof(*buffers->obstacles), buffers->numObstacles) + && WriteArray(*file.file, buffers->vertices, + sizeof(*buffers->vertices), buffers->numVertices) + && WriteArray(*file.file, buffers->edges, + sizeof(*buffers->edges), buffers->numEdges) + && WriteArray(*file.file, buffers->corners, + sizeof(*buffers->corners), buffers->numCorners); +} + +bool idObstacleAvoidance::FindPathAroundObstacles(obstaclePath_t& path, + const int time, const int gameMsPerFrame, const idBounds& bbox, + const idVec3& gravity, const float obstacleRadius, + const float frameMoveDist, const float cornerCircleRadius, + const int queryFlags, const obstacleRoute_t& route, + obstacleTraceSet_t* const traceSet) { + const idVec3 start = route.numAreas > 0 + ? route.areas[0].start : idVec3(); + const idVec3 goal = route.numAreas > 0 + ? route.areas[route.numAreas - 1].end : start; + path = obstaclePath_t(); + path.seekPos[0] = goal; + path.seekPos[1] = goal; + path.numSeekPos = 1; + path.startPosOutsideObstacles = start; + path.seekPosOutsideObstacles = goal; + path.wallCorners[0] = goal; + path.wallCorners[1] = goal; + path.hasValidPath = true; + path.startPosValid = true; + path.seekPosValid = true; + path.done = true; + if (buffers == nullptr) return true; + + testQuery.time = time; + testQuery.gameMsPerFrame = gameMsPerFrame; + testQuery.bbox = bbox; + testQuery.gravity = gravity; + testQuery.obstacleRadius = obstacleRadius; + testQuery.frameMoveDist = frameMoveDist; + testQuery.circleCornerRadius = cornerCircleRadius; + testQuery.flags = queryFlags; + + const int elapsed = time - lastUpdateTime; + const bool preservePrevious = elapsed > 0 + && elapsed < 3 * gameMsPerFrame; + if (preservePrevious && buffers->path != nullptr) { + path = *buffers->path; + lastDir = path.seekPos[0] - path.startPosOutsideObstacles; + lastCorner = path.nextCorner; + } else { + lastDir.Zero(); + lastCorner.Set(0.0f, 0.0f, 0.0f, 0.0f); + } + lastUpdateTime = time; + *buffers->route = route; + + if (traceSet != nullptr) { + const int traceCount = (std::max)(0, + (std::min)(MAX_TRACES, traceSet->numTraces)); + std::memcpy(buffers->traces, traceSet->traces, + sizeof(obstacleTrace_t) * traceCount); + buffers->numTraces = traceCount; + if (preservePrevious) { + const int previousCount = (std::max)(0, + (std::min)(MAX_TRACES, traceSet->prevNumTraces)); + std::memcpy(buffers->traceFractions, traceSet->traceFractions, + sizeof(float) * previousCount); + } + } + + obstacleParms_t& parms = *buffers->parms; + parms.bbox = bbox; + parms.gravity = gravity; + parms.lastDir = lastDir; + parms.lastCorner = lastCorner; + parms.obstacleRadius = obstacleRadius; + parms.frameMoveDist = frameMoveDist; + parms.cornerCircleRadius = cornerCircleRadius; + parms.flags = queryFlags; + parms.numObstacles = buffers->numObstacles; + parms.numVertices = buffers->numVertices; + parms.numEdges = buffers->numEdges; + parms.numCorners = buffers->numCorners; + parms.numTraces = buffers->numTraces; + parms.route = buffers->route; + parms.obstacles = buffers->obstacles; + parms.vertices = buffers->vertices; + parms.edges = buffers->edges; + parms.corners = buffers->corners; + parms.traces = buffers->traces; + parms.traceFractions = buffers->traceFractions; + parms.path = buffers->path; + + std::vector debugLines(2048); + std::vector debugText(256); + parms.debugLines = debugLines.data(); + parms.maxDebugLines = static_cast(debugLines.size()); + parms.debugText = debugText.data(); + parms.maxDebugText = static_cast(debugText.size()); + parms.path->done = false; + parms.path->useRadarForward = path.useRadarForward; + parms.path->radarForward = path.radarForward; + RunObstacleAvoidanceJob(parms); + parms.debugLines = nullptr; + parms.debugText = nullptr; + + path = *buffers->path; + if (traceSet != nullptr) { + traceSet->prevNumTraces = buffers->numTraces; + std::memcpy(traceSet->prevTraceNumbers, traceSet->traceNumbers, + sizeof(int) * buffers->numTraces); + std::memcpy(traceSet->traceFractions, buffers->traceFractions, + sizeof(float) * buffers->numTraces); + } + return path.hasValidPath; +} + +bool idObstacleAvoidance::TestQuery(const char* const fileName) { + if (fileName == nullptr || *fileName == '\0') return false; + AllocBuffers(); + if (buffers == nullptr) return false; + idFileLocal file(fileSystem->OpenFileRead(fileName, true, false)); + if (file.file == nullptr) return false; + idStr identifier; + if (file->ReadString(identifier) == 0 + || idStr::Cmp(identifier.c_str(), QUERY_IDENTIFIER) != 0) { + return false; + } + if (!ReadValue(*file.file, testQuery.time) + || !ReadValue(*file.file, testQuery.gameMsPerFrame) + || !ReadValue(*file.file, testQuery.bbox) + || !ReadValue(*file.file, testQuery.gravity) + || !ReadValue(*file.file, testQuery.obstacleRadius) + || !ReadValue(*file.file, testQuery.frameMoveDist) + || !ReadValue(*file.file, testQuery.circleCornerRadius) + || !ReadValue(*file.file, testQuery.flags) + || !ReadValue(*file.file, buffers->numObstacles) + || !ReadValue(*file.file, buffers->numVertices) + || !ReadValue(*file.file, buffers->numEdges) + || !ReadValue(*file.file, buffers->numCorners)) { + return false; + } + if (buffers->numObstacles < 0 || buffers->numObstacles > MAX_OBSTACLES + || buffers->numVertices < 0 || buffers->numVertices > MAX_VERTICES + || buffers->numEdges < 0 || buffers->numEdges > MAX_EDGES + || buffers->numCorners < 0 || buffers->numCorners > MAX_CORNERS) { + return false; + } + if (!ReadValue(*file.file, *buffers->route) + || !ReadArray(*file.file, buffers->obstacles, + sizeof(*buffers->obstacles), buffers->numObstacles) + || !ReadArray(*file.file, buffers->vertices, + sizeof(*buffers->vertices), buffers->numVertices) + || !ReadArray(*file.file, buffers->edges, + sizeof(*buffers->edges), buffers->numEdges) + || !ReadArray(*file.file, buffers->corners, + sizeof(*buffers->corners), buffers->numCorners)) { + return false; + } + obstaclePath_t path; + return FindPathAroundObstacles(path, testQuery.time, + testQuery.gameMsPerFrame, testQuery.bbox, testQuery.gravity, + testQuery.obstacleRadius, testQuery.frameMoveDist, + testQuery.circleCornerRadius, testQuery.flags, *buffers->route, + nullptr); +} diff --git a/source/engine/gamelib/aas2/obstacleavoidance.h b/source/engine/gamelib/aas2/obstacleavoidance.h index 79b781c..44cc3da 100644 --- a/source/engine/gamelib/aas2/obstacleavoidance.h +++ b/source/engine/gamelib/aas2/obstacleavoidance.h @@ -1,41 +1,67 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\aas2\obstacleavoidance.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/math/vector.h" +#include "idlib/bv/bounds.h" +#include "idlib/math/matrix.h" +#include "gamelib/aas2/obstacleavoidancecore.h" +struct idObstacleBuffers; +class idBox; +class idSpawnId; -// IDA Local Type ordinal 1705; PDB kind: enum. -enum vaiObstacleAvoidance_t : __int32 -{ - OAT_INIT = 0xFFFFFFFF, - OAT_GENERAL = 0x0, - OAT_NONE = 0x1, +enum vaiObstacleAvoidance_t : int { + OAT_INIT = -1, + OAT_GENERAL = 0, + OAT_NONE = 1 }; -// IDA Local Type ordinal 2170; PDB kind: enum. -enum obstacleAvoidanceFlags_t : __int32 -{ - OAFLAG_ASSUME_VALID_AAS_PATH = 0x1, - OAFLAG_MOVE_AROUND_SEEK_POS_OBSTACLES = 0x2, - OAFLAG_CIRCLE_CORNERS = 0x4, - OAFLAG_UPDATE_RADAR = 0x8, - OAFLAG_REPLACE_EXISTING_QUERY = 0x10, - OAFLAG_SHOW_PATH_TREE = 0x20, - OAFLAG_DEBUG = 0x40, - OAFLAG_DEBUG_BREAK = 0x80, - OAFLAG_USE_LINE_INTERSECTION_EPSILON = 0x100, - OAFLAG_SINGLE_PUSH = 0x200, - OAFLAG_IGNORE_AIS = 0x400, +enum obstacleAvoidanceFlags_t : int { + OAFLAG_ASSUME_VALID_AAS_PATH = 0x001, + OAFLAG_MOVE_AROUND_SEEK_POS_OBSTACLES = 0x002, + OAFLAG_CIRCLE_CORNERS = 0x004, + OAFLAG_UPDATE_RADAR = 0x008, + OAFLAG_REPLACE_EXISTING_QUERY = 0x010, + OAFLAG_SHOW_PATH_TREE = 0x020, + OAFLAG_DEBUG = 0x040, + OAFLAG_DEBUG_BREAK = 0x080, + OAFLAG_USE_LINE_INTERSECTION_EPSILON = 0x100, + OAFLAG_SINGLE_PUSH = 0x200, + OAFLAG_IGNORE_AIS = 0x400 }; -// IDA Local Type ordinal 14971; PDB kind: class. -class idObstacleAvoidance -{ +class idObstacleAvoidance { public: - idObstacleBuffers *buffers; - idVec3 lastDir; - idVec4 lastCorner; - int lastUpdateTime; + idObstacleAvoidance(); + ~idObstacleAvoidance(); + void AllocBuffers(); + obstacleVertex_t* GetVertexArray(); + int GetMaxVertices() const; + void SetNumVertices(int num); + obstacleEdge_t* GetEdgeArray(); + int GetMaxEdges() const; + void SetNumEdges(int num); + void AddObstacle(const idBox& box, idSpawnId id, bool soft); + void AddObstacle(const idBounds& bounds, const idVec3& origin, + const idMat3& axis, idSpawnId id, bool soft); + void Restart(const idVec3& start, const idVec3& goal); + void ProjectTopDown(idVec3& point, const idVec3& startPos, + float scale) const; + void DrawBox(const idVec3& startPos, float radius) const; + bool SaveQuery(const char* fileName) const; + bool FindPathAroundObstacles(obstaclePath_t& path, int time, + int gameMsPerFrame, const idBounds& bbox, const idVec3& gravity, + float obstacleRadius, float frameMoveDist, float cornerCircleRadius, + int flags, const obstacleRoute_t& route, + obstacleTraceSet_t* traceSet); + bool TestQuery(const char* fileName); + + idObstacleBuffers* buffers; + idVec3 lastDir; + idVec4 lastCorner; + int lastUpdateTime; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idObstacleAvoidance) == 36, + "Recovered idObstacleAvoidance ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/obstacleavoidancecore.cpp b/source/engine/gamelib/aas2/obstacleavoidancecore.cpp new file mode 100644 index 0000000..fabf7b5 --- /dev/null +++ b/source/engine/gamelib/aas2/obstacleavoidancecore.cpp @@ -0,0 +1,347 @@ +#include "gamelib/aas2/obstacleavoidancecore.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +float Cross2(const idVec2& first, const idVec2& second) { + return first.x * second.y - first.y * second.x; +} + +idVec2 Sub2(const idVec2& first, const idVec2& second) { + return idVec2(first.x - second.x, first.y - second.y); +} + +float Dot2(const idVec2& first, const idVec2& second) { + return first.x * second.x + first.y * second.y; +} + +float Length2(const idVec2& value) { + return std::sqrt(Dot2(value, value)); +} + +idVec2 Normalize2(const idVec2& value) { + const float length = Length2(value); + return length > 1.0e-6f + ? idVec2(value.x / length, value.y / length) : idVec2(0.0f, 0.0f); +} + +float SegmentPointDistanceSqr(const idVec2& start, const idVec2& end, + const idVec2& point) { + const idVec2 delta = Sub2(end, start); + const float lengthSqr = Dot2(delta, delta); + float fraction = lengthSqr > 0.0f + ? Dot2(Sub2(point, start), delta) / lengthSqr : 0.0f; + fraction = (std::max)(0.0f, (std::min)(1.0f, fraction)); + const idVec2 closest(start.x + delta.x * fraction, + start.y + delta.y * fraction); + return Dot2(Sub2(point, closest), Sub2(point, closest)); +} + +bool SegmentIntersection(const idVec2& a, const idVec2& b, + const idVec2& c, const idVec2& d) { + const idVec2 r = Sub2(b, a); + const idVec2 s = Sub2(d, c); + const float denominator = Cross2(r, s); + if (std::fabs(denominator) < 1.0e-6f) return false; + const idVec2 delta = Sub2(c, a); + const float t = Cross2(delta, s) / denominator; + const float u = Cross2(delta, r) / denominator; + return t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f; +} + +} // namespace + +idObstacleAvoidanceCore::idObstacleAvoidanceCore() + : bspNodes(nullptr), numBspNodes(0), obstacleIndices(nullptr), + expandedObstacles(nullptr), maxExpandedObstacles(0), + numExpandedObstacles(0), windingPoints(nullptr), maxWindingPoints(0), + numWindingPoints(0), tempMemory(nullptr), tempMemorySize(0), + tempMemoryStart(nullptr), tempMemoryEnd(nullptr), pathNodes(nullptr), + maxPathNodes(0), numPathNodes(0), debugLines(nullptr), + maxDebugLines(0), numDebugLines(0), debugText(nullptr), + maxDebugText(0), numDebugText(0), inputTransformValid(false), + inputTransform(), flags(0) { +} + +idObstacleAvoidanceCore::~idObstacleAvoidanceCore() = default; + +void idObstacleAvoidanceCore::AddDebugLine(const avoidColor_t color, + const idVec2& start, const idVec2& end) { + if (debugLines == nullptr || numDebugLines >= maxDebugLines - 1) return; + avoidDebugLine_t& line = debugLines[numDebugLines++]; + line.color = color; + line.start.Set(start.x, start.y, 0.0f); + line.end.Set(end.x, end.y, 0.0f); +} + +void idObstacleAvoidanceCore::AddDebugInt(const avoidColor_t color, + const idVec2& position, const int value) { + if (debugText == nullptr || numDebugText >= maxDebugText - 1) return; + avoidDebugText_t& text = debugText[numDebugText++]; + std::snprintf(text.text, sizeof(text.text), "%d", value); + text.pos.Set(position.x, position.y, 0.0f); + text.color = color; +} + +int idObstacleAvoidanceCore::BoxParallelProjectionSilhouetteVerts( + const idMat3& axis, const idVec3& center, const idVec3& extents, + const idVec3& projectionDir, idVec3* const silhouette) { + if (silhouette == nullptr) return 0; + idVec3 corners[8]; + int count = 0; + for (int x = -1; x <= 1; x += 2) { + for (int y = -1; y <= 1; y += 2) { + for (int z = -1; z <= 1; z += 2) { + corners[count++] = center + axis[0] * (extents.x * x) + + axis[1] * (extents.y * y) + + axis[2] * (extents.z * z); + } + } + } + int output = 0; + for (int index = 0; index < 8 && output < 6; ++index) { + const float facing = (corners[index] - center).Dot(projectionDir); + if (facing <= 0.0f) silhouette[output++] = corners[index]; + } + return output; +} + +void idObstacleAvoidanceCore::GetCirclePoints(const idVec2& center, + const float radius, idVec2* const points) { + if (points == nullptr) return; + constexpr float diagonal = 0.70710678118f; + const idVec2 directions[8] = { + idVec2(1, 0), idVec2(diagonal, diagonal), idVec2(0, 1), + idVec2(-diagonal, diagonal), idVec2(-1, 0), + idVec2(-diagonal, -diagonal), idVec2(0, -1), + idVec2(diagonal, -diagonal) + }; + for (int index = 0; index < 8; ++index) { + points[index].Set(center.x + directions[index].x * radius, + center.y + directions[index].y * radius); + } +} + +idVec3 idObstacleAvoidanceCore::Plane2DFromPoints(const idVec2& start, + const idVec2& end, const bool normalize) { + return Plane2DFromVecs(start, Sub2(end, start), normalize); +} + +idVec3 idObstacleAvoidanceCore::Plane2DFromVecs(const idVec2& start, + const idVec2& direction, const bool normalize) { + idVec2 normal(-direction.y, direction.x); + if (normalize) normal = Normalize2(normal); + return idVec3(normal.x, normal.y, + -(normal.x * start.x + normal.y * start.y)); +} + +bool idObstacleAvoidanceCore::PointInsideWinding(const idVec2* const winding, + const int numPoints, const idVec2& point, const float epsilon) { + if (winding == nullptr || numPoints <= 0) return true; + float sign = 0.0f; + for (int index = 0; index < numPoints; ++index) { + const float side = Cross2(Sub2(winding[(index + 1) % numPoints], + winding[index]), Sub2(point, winding[index])); + if (std::fabs(side) <= epsilon) continue; + if (sign == 0.0f) sign = side; + else if ((sign < 0.0f) != (side < 0.0f)) return false; + } + return true; +} + +void idObstacleAvoidanceCore::ExpandWinding(const idVec2* const winding, + const int numPoints, const float distance, idVec2* const expanded) { + if (winding == nullptr || expanded == nullptr || numPoints <= 0) return; + for (int index = 0; index < numPoints; ++index) { + const idVec2 previous = winding[(index + numPoints - 1) % numPoints]; + const idVec2 current = winding[index]; + const idVec2 next = winding[(index + 1) % numPoints]; + const idVec2 first = Normalize2(idVec2( + -(current.y - previous.y), current.x - previous.x)); + const idVec2 second = Normalize2(idVec2( + -(next.y - current.y), next.x - current.x)); + idVec2 direction = Normalize2(idVec2(first.x + second.x, + first.y + second.y)); + const float denominator = (std::max)(0.1f, + std::fabs(Dot2(direction, first))); + expanded[index].Set(current.x + direction.x * distance / denominator, + current.y + direction.y * distance / denominator); + } +} + +void idObstacleAvoidanceCore::GetWindingBounds( + const idVec2* const winding, const int numPoints, + idVec2 bounds[2]) { + const float maximum = (std::numeric_limits::max)(); + bounds[0].Set(maximum, maximum); + bounds[1].Set(-maximum, -maximum); + for (int index = 0; index < numPoints; ++index) { + bounds[0].x = (std::min)(bounds[0].x, winding[index].x); + bounds[0].y = (std::min)(bounds[0].y, winding[index].y); + bounds[1].x = (std::max)(bounds[1].x, winding[index].x); + bounds[1].y = (std::max)(bounds[1].y, winding[index].y); + } +} + +bool idObstacleAvoidanceCore::WindingRayIntersection( + const idVec2* const winding, const int numPoints, + const idVec2& start, const idVec2& direction, float& firstScale, + float& secondScale, int edgeNums[2]) { + firstScale = (std::numeric_limits::max)(); + secondScale = -(std::numeric_limits::max)(); + int intersections = 0; + for (int index = 0; index < numPoints; ++index) { + const idVec2 edgeStart = winding[index]; + const idVec2 edge = Sub2(winding[(index + 1) % numPoints], edgeStart); + const float denominator = Cross2(direction, edge); + if (std::fabs(denominator) < 1.0e-6f) continue; + const idVec2 delta = Sub2(edgeStart, start); + const float scale = Cross2(delta, edge) / denominator; + const float edgeScale = Cross2(delta, direction) / denominator; + if (edgeScale < 0.0f || edgeScale > 1.0f) continue; + if (scale < firstScale) { + firstScale = scale; + if (edgeNums != nullptr) edgeNums[0] = index; + } + if (scale > secondScale) { + secondScale = scale; + if (edgeNums != nullptr) edgeNums[1] = index; + } + ++intersections; + } + return intersections >= 2; +} + +void idObstacleAvoidanceCore::FindPathAroundObstacles(obstaclePath_t& path, + avoidDebugLine_t* const outputDebugLines, const int outputMaxDebugLines, + avoidDebugText_t* const outputDebugText, const int outputMaxDebugText, + std::uint8_t* const temp, const int tempSize, const idBounds&, + const idVec3&, const idVec3& lastDir, const idVec4& lastCorner, + const float obstacleRadius, const float frameMoveDist, + const float, const int queryFlags, const obstacleRoute_t& route, + const obstacleBox_t* const obstacles, const int numObstacles, + const obstacleVertex_t* const vertices, const int numVertices, + const obstacleEdge_t* const edges, const int numEdges, + const obstacleCorner_t*, const int, const obstacleTrace_t*, + const int, float*) { + debugLines = outputDebugLines; + maxDebugLines = outputMaxDebugLines; + numDebugLines = 0; + debugText = outputDebugText; + maxDebugText = outputMaxDebugText; + numDebugText = 0; + tempMemory = temp; + tempMemorySize = tempSize; + flags = queryFlags; + + const idVec3 start3 = route.numAreas > 0 + ? route.areas[0].start : path.startPosOutsideObstacles; + const idVec3 goal3 = route.numAreas > 0 + ? route.areas[route.numAreas - 1].end : path.seekPos[0]; + const idVec2 start(start3.x, start3.y); + const idVec2 goal(goal3.x, goal3.y); + path.numSeekPos = 0; + path.firstObstacle = -1; + path.firstObstaclePosition.Zero(); + path.startPosOutsideObstacles = start3; + path.seekPosOutsideObstacles = goal3; + path.startPosObstacle = -1; + path.seekPosObstacle = -1; + path.targetDist = Length2(Sub2(goal, start)); + path.pathLength = path.targetDist; + path.hasValidPath = true; + path.startPosValid = true; + path.seekPosValid = true; + + int blocking = -1; + float blockingRadius = 0.0f; + for (int index = 0; index < numObstacles; ++index) { + const obstacleBox_t& obstacle = obstacles[index]; + if (obstacle.soft && (queryFlags & 0x200) == 0) continue; + const float radius = std::sqrt(obstacle.extents.x * obstacle.extents.x + + obstacle.extents.y * obstacle.extents.y) + obstacleRadius; + if (SegmentPointDistanceSqr(start, goal, + idVec2(obstacle.center.x, obstacle.center.y)) + < radius * radius) { + blocking = index; + blockingRadius = radius; + break; + } + } + if (blocking < 0) { + for (int index = 0; index < numEdges; ++index) { + const int first = edges[index].vertNums[0]; + const int second = edges[index].vertNums[1]; + if (first < 0 || second < 0 || first >= numVertices + || second >= numVertices) continue; + const idVec2 edgeStart(vertices[first].position.x, + vertices[first].position.y); + const idVec2 edgeEnd(vertices[second].position.x, + vertices[second].position.y); + if (SegmentIntersection(start, goal, edgeStart, edgeEnd)) { + blocking = numObstacles + index; + break; + } + } + } + + if (blocking >= 0 && blocking < numObstacles) { + const obstacleBox_t& obstacle = obstacles[blocking]; + const idVec2 center(obstacle.center.x, obstacle.center.y); + idVec2 direction = Normalize2(Sub2(goal, start)); + idVec2 perpendicular(-direction.y, direction.x); + const idVec2 candidates[2] = { + idVec2(center.x + perpendicular.x * blockingRadius, + center.y + perpendicular.y * blockingRadius), + idVec2(center.x - perpendicular.x * blockingRadius, + center.y - perpendicular.y * blockingRadius) + }; + const float firstLength = Length2(Sub2(candidates[0], start)) + + Length2(Sub2(goal, candidates[0])); + const float secondLength = Length2(Sub2(candidates[1], start)) + + Length2(Sub2(goal, candidates[1])); + const idVec2 corner = firstLength <= secondLength + ? candidates[0] : candidates[1]; + path.seekPos[0].Set(corner.x, corner.y, start3.z); + path.seekPos[1] = goal3; + path.numSeekPos = 2; + path.firstObstacle = obstacle.id; + path.firstObstaclePosition = obstacle.center; + path.pathLength = (std::min)(firstLength, secondLength); + path.nextCorner.Set(corner.x, corner.y, start3.z, + static_cast(obstacle.id)); + AddDebugLine(COLOR_RED, start, corner); + AddDebugLine(COLOR_GREEN, corner, goal); + } else if (blocking >= numObstacles) { + path.hasValidPath = false; + path.seekPos[0] = start3; + path.numSeekPos = 1; + } else { + path.seekPos[0] = goal3; + path.numSeekPos = 1; + path.nextCorner.Set(goal3.x, goal3.y, goal3.z, -1.0f); + AddDebugLine(COLOR_GREEN, start, goal); + } + path.done = true; + path.useRadarForward = Length2(idVec2(lastDir.x, lastDir.y)) > 0.0f; + path.radarForward = lastDir; + if (lastCorner.w >= 0.0f && path.firstObstacle < 0) { + path.nextCorner = lastCorner; + } + if (frameMoveDist > 0.0f && path.numSeekPos > 0) { + const idVec2 move = Sub2(idVec2(path.seekPos[0].x, + path.seekPos[0].y), start); + const float moveLength = Length2(move); + if (moveLength > frameMoveDist) { + const float scale = frameMoveDist / moveLength; + path.seekPos[0].x = start.x + move.x * scale; + path.seekPos[0].y = start.y + move.y * scale; + } + } +} diff --git a/source/engine/gamelib/aas2/obstacleavoidancecore.h b/source/engine/gamelib/aas2/obstacleavoidancecore.h index 1df19c7..3a432ad 100644 --- a/source/engine/gamelib/aas2/obstacleavoidancecore.h +++ b/source/engine/gamelib/aas2/obstacleavoidancecore.h @@ -1,98 +1,299 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\aas2\obstacleavoidancecore.h -// Recovered logical types: 6 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/containers/queue.h" +#include "idlib/bv/bounds.h" +#include "idlib/math/mat3x4.h" +#include "idlib/math/plane.h" +#include "idlib/math/vector.h" +#include +#include -// IDA Local Type ordinal 2805; PDB kind: enum. -enum idObstacleAvoidanceCore::avoidColor_t : __int32 -{ - COLOR_INVALID = 0x0, - COLOR_WHITE = 0x1, - COLOR_GRAY = 0x2, - COLOR_RED = 0x3, - COLOR_BLUE = 0x4, - COLOR_GREEN = 0x5, - COLOR_YELLOW = 0x6, - COLOR_ORANGE = 0x7, - COLOR_PURPLE = 0x8, - COLOR_CYAN = 0x9, - COLOR_MAGENTA = 0xA, +struct obstacleVertex_t { idVec3 position; }; +struct obstacleEdge_t { + int flags; + std::int16_t verts[2]; + int vertNums[2]; +}; +struct obstacleBox_t { + idMat3 axis; + idVec3 center; + idVec3 extents; + int id; + bool soft; + std::uint8_t pad[15]; +}; +struct obstacleCorner_t { idVec3 corner; float radius; }; +struct obstacleTrace_t { idVec2 start; idVec2 end; }; + +struct obstacleRoute_t { + struct routeArea_t { + int areaNum; + idVec3 start; + idVec3 end; + }; + + int numAreas; + int pad[2]; + idVec3 endAlignDir; + float endTurnRadius; + routeArea_t areas[11]; + + // Materialized in the authoritative obstacleavoidancecore.h dump. + obstacleRoute_t() + : numAreas(0), pad{0, 0}, endAlignDir(), endTurnRadius(0.0f), + areas{} { + Clear(); + } + + void Clear() { + numAreas = 0; + endAlignDir.Zero(); + endTurnRadius = 0.0f; + for (routeArea_t& area : areas) { + area.areaNum = 0; + area.start.Zero(); + area.end.Zero(); + } + } }; -// IDA Local Type ordinal 22035; PDB kind: struct. -struct idObstacleAvoidanceCore::pathNode_t -{ - int dir; - idVec2 pos; - idVec2 delta; - float targetDistSqr; - int obstacle; - int edgeNum; - int numNodes; - idObstacleAvoidanceCore::pathNode_t *parent; - idObstacleAvoidanceCore::pathNode_t *children[2]; - idQueueNode queueNode; +struct obstaclePath_t { + struct radar_t { + idVec3 direction; + int obstacle; + }; + + idVec3 seekPos[31]; + int numSeekPos; + idPlane seekPosPlane; + idVec3 firstObstaclePosition; + int firstObstacle; + idVec3 startPosOutsideObstacles; + int startPosObstacle; + idVec3 seekPosOutsideObstacles; + int seekPosObstacle; + float targetDist; + float pathLength; + idVec4 nextCorner; + float nextCornerAngle; + idVec3 wallCorners[2]; + radar_t radar[7]; + bool useRadarForward; + idVec3 radarForward; + bool hasValidPath; + bool startPosValid; + bool seekPosValid; + bool done; + + // Materialized in the authoritative obstacleavoidancecore.h dump. + obstaclePath_t() + : seekPos{}, numSeekPos(0), seekPosPlane(0, 0, 0, 0), + firstObstaclePosition(), firstObstacle(-1), + startPosOutsideObstacles(), startPosObstacle(-1), + seekPosOutsideObstacles(), seekPosObstacle(-1), targetDist(0.0f), + pathLength(0.0f), nextCorner(), nextCornerAngle(0.0f), + wallCorners{}, radar{}, useRadarForward(false), radarForward(), + hasValidPath(false), startPosValid(false), seekPosValid(false), + done(false) { + } }; -// IDA Local Type ordinal 22437; PDB kind: struct. -struct idObstacleAvoidanceCore::bspNode_t -{ - int axis; - float dist; - __int16 children[2]; - __int16 indexOffset; - __int16 numIndices; -}; - -// IDA Local Type ordinal 22438; PDB kind: struct. -struct idObstacleAvoidanceCore::expandedObstacle_t -{ - idVec2 bounds[2]; - idVec2 *winding; - int numPoints; - int id; - bool soft; - unsigned __int8 pad[3]; -}; - -// IDA Local Type ordinal 22439; PDB kind: class. -class idObstacleAvoidanceCore -{ +class idObstacleAvoidanceCore { public: - idObstacleAvoidanceCore::bspNode_t *bspNodes; - int numBspNodes; - __int16 *obstacleIndices; - idObstacleAvoidanceCore::expandedObstacle_t *expandedObstacles; - int maxExpandedObstacles; - int numExpandedObstacles; - idVec2 *windingPoints; - int maxWindingPoints; - int numWindingPoints; - unsigned __int8 *tempMemory; - int tempMemorySize; - unsigned __int8 *tempMemoryStart; - unsigned __int8 *tempMemoryEnd; - idObstacleAvoidanceCore::pathNode_t *pathNodes; - int maxPathNodes; - int numPathNodes; - avoidDebugLine_t *debugLines; - int maxDebugLines; - int numDebugLines; - avoidDebugText_t *debugText; - int maxDebugText; - int numDebugText; - bool inputTransformValid; - idMat3x4 inputTransform; - int flags; + enum avoidColor_t : int { + COLOR_INVALID = 0, + COLOR_WHITE = 1, + COLOR_GRAY = 2, + COLOR_RED = 3, + COLOR_BLUE = 4, + COLOR_GREEN = 5, + COLOR_YELLOW = 6, + COLOR_ORANGE = 7, + COLOR_PURPLE = 8, + COLOR_CYAN = 9, + COLOR_MAGENTA = 10 + }; + + struct pathNode_t { + int dir; + idVec2 pos; + idVec2 delta; + float targetDistSqr; + int obstacle; + int edgeNum; + int numNodes; + pathNode_t* parent; + pathNode_t* children[2]; + idQueueNode queueNode; + }; + + struct bspNode_t { + int axis; + float dist; + std::int16_t children[2]; + std::int16_t indexOffset; + std::int16_t numIndices; + }; + + struct expandedObstacle_t { + idVec2 bounds[2]; + idVec2* winding; + int numPoints; + int id; + bool soft; + std::uint8_t pad[3]; + }; + + // This is the layout of the recovered function-local terminator type. + struct setupExpandedObstaclesTerminator_t { + float distSqr; + std::int16_t lastEdgeIndex; + std::int16_t firstEdgeIndex; + }; + + idObstacleAvoidanceCore(); + ~idObstacleAvoidanceCore(); + + static int BoxParallelProjectionSilhouetteVerts(const idMat3& axis, + const idVec3& center, const idVec3& extents, + const idVec3& projectionDir, idVec3* silhouette); + static void GetCirclePoints(const idVec2& center, float radius, + idVec2* circlePoints); + static idVec3 Plane2DFromPoints(const idVec2& start, + const idVec2& end, bool normalize); + static idVec3 Plane2DFromVecs(const idVec2& start, + const idVec2& direction, bool normalize); + static bool PointInsideWinding(const idVec2* winding, int numPoints, + const idVec2& point, float epsilon = 0.0f); + static void ExpandWinding(const idVec2* winding, int numPoints, + float distance, idVec2* expanded); + static void GetWindingBounds(const idVec2* winding, int numPoints, + idVec2 bounds[2]); + static bool WindingRayIntersection(const idVec2* winding, int numPoints, + const idVec2& start, const idVec2& direction, float& firstScale, + float& secondScale, int edgeNums[2]); + void AddDebugLine(avoidColor_t color, const idVec2& start, + const idVec2& end); + void AddDebugInt(avoidColor_t color, const idVec2& position, int value); + void FindPathAroundObstacles(obstaclePath_t& path, + struct avoidDebugLine_t* debugLines, int maxDebugLines, + struct avoidDebugText_t* debugText, int maxDebugText, + std::uint8_t* temp, int tempSize, const idBounds& bbox, + const idVec3& gravity, const idVec3& lastDir, + const idVec4& lastCorner, float obstacleRadius, + float frameMoveDist, float cornerCircleRadius, int flags, + const obstacleRoute_t& route, const obstacleBox_t* obstacles, + int numObstacles, const obstacleVertex_t* vertices, int numVertices, + const obstacleEdge_t* edges, int numEdges, + const obstacleCorner_t* corners, int numCorners, + const obstacleTrace_t* traces, int numTraces, + float* traceFractions); + + bspNode_t* bspNodes; + int numBspNodes; + std::int16_t* obstacleIndices; + expandedObstacle_t* expandedObstacles; + int maxExpandedObstacles; + int numExpandedObstacles; + idVec2* windingPoints; + int maxWindingPoints; + int numWindingPoints; + std::uint8_t* tempMemory; + int tempMemorySize; + std::uint8_t* tempMemoryStart; + std::uint8_t* tempMemoryEnd; + pathNode_t* pathNodes; + int maxPathNodes; + int numPathNodes; + avoidDebugLine_t* debugLines; + int maxDebugLines; + int numDebugLines; + avoidDebugText_t* debugText; + int maxDebugText; + int numDebugText; + bool inputTransformValid; + idMat3x4 inputTransform; + int flags; }; -// IDA Local Type ordinal 23501; PDB kind: struct. -struct idObstacleAvoidanceCore::SetupExpandedObstacles::__l87::terminator_t -{ - float distSqr; - __int16 lastEdgeIndex; - __int16 firstEdgeIndex; +struct avoidDebugLine_t { + idObstacleAvoidanceCore::avoidColor_t color; + idVec3 start; + idVec3 end; }; + +struct avoidDebugText_t { + char text[32]; + idVec3 pos; + idObstacleAvoidanceCore::avoidColor_t color; +}; + +struct obstacleTraceSet_t { + obstacleTrace_t traces[256]; + float traceFractions[256]; + int traceNumbers[256]; + int numTraces; + int prevTraceNumbers[256]; + int prevNumTraces; +}; + +struct obstacleParms_t { + idBounds bbox; + idVec3 gravity; + idVec3 lastDir; + idVec4 lastCorner; + float obstacleRadius; + float frameMoveDist; + float cornerCircleRadius; + int flags; + int numObstacles; + int numVertices; + int numEdges; + int numCorners; + int numTraces; + int maxDebugLines; + int maxDebugText; + const obstacleRoute_t* route; + const obstacleBox_t* obstacles; + const obstacleVertex_t* vertices; + const obstacleEdge_t* edges; + const obstacleCorner_t* corners; + const obstacleTrace_t* traces; + float* traceFractions; + obstaclePath_t* path; + avoidDebugLine_t* debugLines; + avoidDebugText_t* debugText; + std::uint8_t pad[12]; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(obstacleRoute_t::routeArea_t) == 28, + "Recovered obstacle route area ABI changed"); +static_assert(sizeof(obstacleRoute_t) == 336, + "Recovered obstacleRoute_t ABI changed"); +static_assert(sizeof(obstaclePath_t::radar_t) == 16, + "Recovered obstacle radar ABI changed"); +static_assert(sizeof(obstaclePath_t) == 624, + "Recovered obstaclePath_t ABI changed"); +static_assert(sizeof(idObstacleAvoidanceCore::pathNode_t) == 52, + "Recovered obstacle path node ABI changed"); +static_assert(sizeof(idObstacleAvoidanceCore::bspNode_t) == 16, + "Recovered obstacle BSP node ABI changed"); +static_assert(sizeof(idObstacleAvoidanceCore::expandedObstacle_t) == 32, + "Recovered expanded obstacle ABI changed"); +static_assert(sizeof(obstacleVertex_t) == 12, + "Recovered obstacle vertex ABI changed"); +static_assert(sizeof(obstacleEdge_t) == 16, + "Recovered obstacle edge ABI changed"); +static_assert(sizeof(obstacleBox_t) == 80, + "Recovered obstacle box ABI changed"); +static_assert(sizeof(obstacleCorner_t) == 16, + "Recovered obstacle corner ABI changed"); +static_assert(sizeof(obstacleTrace_t) == 16, + "Recovered obstacle trace ABI changed"); +static_assert(sizeof(avoidDebugLine_t) == 28, + "Recovered obstacle debug-line ABI changed"); +static_assert(sizeof(avoidDebugText_t) == 48, + "Recovered obstacle debug-text ABI changed"); +#endif diff --git a/source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.cpp b/source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.cpp new file mode 100644 index 0000000..ea3e558 --- /dev/null +++ b/source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.cpp @@ -0,0 +1,165 @@ +#include "gamelib/aas2/obstacleavoidancemanagerlocal.h" + +#include "idlib/lib_print.h" + +#include +#include + +namespace { + +template +type* AllocAligned(const std::size_t size, const std::size_t alignment) { + return static_cast(_aligned_malloc(size, alignment)); +} + +void FreeAligned(void* const memory) { + _aligned_free(memory); +} + +void ResetBuffer(idObstacleBuffers& buffer) { + buffer.numObstacles = 0; + buffer.numVertices = 0; + buffer.numEdges = 0; + buffer.numCorners = 0; + buffer.numTraces = 0; + buffer.frameCount = 2; +} + +idObstacleBuffers* AllocBuffer() { + idObstacleBuffers* const buffer = + AllocAligned(0x44, 16); + buffer->parms = AllocAligned(0xA0, 128); + buffer->route = AllocAligned(0x150, 128); + buffer->obstacles = AllocAligned(0x2800, 128); + buffer->vertices = AllocAligned(0x2400, 128); + buffer->edges = AllocAligned(0x2000, 128); + buffer->corners = AllocAligned(0x100, 128); + buffer->traces = AllocAligned(0x1000, 128); + buffer->traceFractions = AllocAligned(0x400, 128); + void* const pathMemory = _aligned_malloc(0x270, 128); + buffer->path = new (pathMemory) obstaclePath_t(); + buffer->user = nullptr; + buffer->job = nullptr; + ResetBuffer(*buffer); + buffer->frameCount = 0; + return buffer; +} + +void FreeBuffer(idObstacleBuffers* const buffer) { + if (buffer == nullptr) { + return; + } + FreeAligned(buffer->parms); + FreeAligned(buffer->route); + FreeAligned(buffer->obstacles); + FreeAligned(buffer->vertices); + FreeAligned(buffer->edges); + FreeAligned(buffer->corners); + FreeAligned(buffer->traces); + FreeAligned(buffer->traceFractions); + if (buffer->path != nullptr) { + buffer->path->~obstaclePath_t(); + FreeAligned(buffer->path); + } + FreeAligned(buffer); +} + +} // namespace + +idObstacleAvoidanceManagerLocal obstacleAvoidanceManagerLocal; + +idObstacleAvoidanceManagerLocal::idObstacleAvoidanceManagerLocal() + : jobList(nullptr) + , usedBuffers() + , freeBuffers() + , toBeFreedBuffers() { +} + +idObstacleAvoidanceManagerLocal::~idObstacleAvoidanceManagerLocal() { + Shutdown(); +} + +void idObstacleAvoidanceManagerLocal::Init( + idParallelJobList* const jobList_) { + if (jobList != nullptr) { + return; + } + + jobList = jobList_; + freeBuffers.SetNum(32); + for (int index = 0; index < 32; ++index) { + freeBuffers[index] = AllocBuffer(); + } +} + +void idObstacleAvoidanceManagerLocal::Shutdown() { + FrameFreeObstacleBuffers(); + for (int index = 0; index < freeBuffers.Num(); ++index) { + FreeBuffer(freeBuffers[index]); + } + usedBuffers.ClearFree(); + freeBuffers.ClearFree(); + toBeFreedBuffers.ClearFree(); + jobList = nullptr; +} + +void idObstacleAvoidanceManagerLocal::AllocObstacleBuffers( + idObstacleAvoidance* const avoidance) { + const int existingIndex = usedBuffers.FindIndex(avoidance->buffers); + if (existingIndex >= 0) { + ResetBuffer(*avoidance->buffers); + return; + } + + if (freeBuffers.IsEmpty()) { + idLibPrint::Warning("out of obstacle avoidance buffers"); + return; + } + + idObstacleBuffers* const buffer = freeBuffers[freeBuffers.Num() - 1]; + freeBuffers.SetNum(freeBuffers.Num() - 1); + usedBuffers.Append(buffer); + buffer->user = avoidance; + buffer->job = nullptr; + ResetBuffer(*buffer); + avoidance->buffers = buffer; +} + +void idObstacleAvoidanceManagerLocal::FreeObstacleBuffers( + idObstacleAvoidance* const avoidance) { + if (avoidance->buffers == nullptr) { + return; + } + + const int index = usedBuffers.FindIndex(avoidance->buffers); + if (index < 0) { + return; + } + + idObstacleBuffers* const buffer = usedBuffers[index]; + buffer->user = nullptr; + buffer->job = nullptr; + toBeFreedBuffers.Append(buffer); + usedBuffers.RemoveIndexFast(index); + avoidance->buffers = nullptr; +} + +void idObstacleAvoidanceManagerLocal::FrameFreeObstacleBuffers() { + for (int index = 0; index < usedBuffers.Num(); ++index) { + idObstacleBuffers* const buffer = usedBuffers[index]; + if (--buffer->frameCount <= 0) { + if (buffer->user != nullptr) { + buffer->user->buffers = nullptr; + } + buffer->user = nullptr; + buffer->job = nullptr; + toBeFreedBuffers.Append(buffer); + usedBuffers.RemoveIndexFast(index--); + } + } + + for (int index = 0; index < toBeFreedBuffers.Num(); ++index) { + freeBuffers.Append(toBeFreedBuffers[index]); + } + toBeFreedBuffers.Clear(); +} diff --git a/source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.h b/source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.h index 2cbc61d..9d7312e 100644 --- a/source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.h +++ b/source/engine/gamelib/aas2/obstacleavoidancemanagerlocal.h @@ -1,26 +1,72 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\aas2\obstacleavoidancemanagerlocal.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/aas2/obstacleavoidance.h" +#include "gamelib/aas2/obstacleavoidancecore.h" +#include "idlib/containers/list.h" +class idParallelJobList; +class CellSpursJob128; +struct obstacleParms_t; +struct obstacleBox_t; +struct obstacleVertex_t; +struct obstacleEdge_t; +struct obstacleCorner_t; +struct obstacleTrace_t; -// IDA Local Type ordinal 23498; PDB kind: class. -class idObstacleAvoidanceManagerLocal : public idObstacleAvoidanceManager -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23499. - virtual ~idObstacleAvoidanceManagerLocal(); - virtual void Init(idParallelJobList *); - virtual void Shutdown(); - virtual void AllocObstacleBuffers(idObstacleAvoidance *); - virtual void FreeObstacleBuffers(idObstacleAvoidance *); - virtual void FrameFreeObstacleBuffers(); - virtual idParallelJobList *GetJobList(); - - idParallelJobList *jobList; - idList usedBuffers; - idList freeBuffers; - idList toBeFreedBuffers; +struct idObstacleBuffers { + idObstacleAvoidance* user; + CellSpursJob128* job; + obstacleParms_t* parms; + obstacleRoute_t* route; + obstacleBox_t* obstacles; + obstacleVertex_t* vertices; + obstacleEdge_t* edges; + obstacleCorner_t* corners; + obstacleTrace_t* traces; + float* traceFractions; + obstaclePath_t* path; + int numObstacles; + int numVertices; + int numEdges; + int numCorners; + int numTraces; + int frameCount; }; + +class idObstacleAvoidanceManager { +public: + virtual ~idObstacleAvoidanceManager() = default; + virtual void Init(idParallelJobList* jobList) = 0; + virtual void Shutdown() = 0; + virtual void AllocObstacleBuffers(idObstacleAvoidance* avoidance) = 0; + virtual void FreeObstacleBuffers(idObstacleAvoidance* avoidance) = 0; + virtual void FrameFreeObstacleBuffers() = 0; + virtual idParallelJobList* GetJobList() = 0; +}; + +class idObstacleAvoidanceManagerLocal : public idObstacleAvoidanceManager { +public: + idObstacleAvoidanceManagerLocal(); + ~idObstacleAvoidanceManagerLocal() override; + + void Init(idParallelJobList* jobList_) override; + void Shutdown() override; + void AllocObstacleBuffers(idObstacleAvoidance* avoidance) override; + void FreeObstacleBuffers(idObstacleAvoidance* avoidance) override; + void FrameFreeObstacleBuffers() override; + idParallelJobList* GetJobList() override { return jobList; } + + idParallelJobList* jobList; + idList usedBuffers; + idList freeBuffers; + idList toBeFreedBuffers; +}; + +extern idObstacleAvoidanceManagerLocal obstacleAvoidanceManagerLocal; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idObstacleBuffers) == 68, + "Recovered idObstacleBuffers ABI changed"); +static_assert(sizeof(idObstacleAvoidanceManagerLocal) == 56, + "Recovered obstacle-avoidance manager ABI changed"); +#endif diff --git a/source/engine/gamelib/animstack/animator_base.cpp b/source/engine/gamelib/animstack/animator_base.cpp new file mode 100644 index 0000000..b67c5be --- /dev/null +++ b/source/engine/gamelib/animstack/animator_base.cpp @@ -0,0 +1,243 @@ +#include "gamelib/animstack/animator_base.h" + +#include "idlib/lib_print.h" + +#include + +// idAnimStack and idGameTimeManager are not yet safe to materialize from the +// generated declarations. These named boundaries preserve every recovered +// animator-base call while their authoritative translation units are ported. +int GameLib_GetAnimatorIndex(const idAnimStack* stack, + const idAnimator_Base* animator); +void GameLib_AddAnimator(idAnimStack* stack, idAnimator_Base* animator); +void GameLib_RemoveAnimator(idAnimStack* stack, idAnimator_Base* animator); +void GameLib_SetAnimatorFlag(idAnimStack* stack, idAnimator_Base* animator, + int flag); +void GameLib_ClearAnimatorFlag(idAnimStack* stack, idAnimator_Base* animator, + int flag); +bool GameLib_IsAnimatorFlagSet(const idAnimStack* stack, + const idAnimator_Base* animator, int flag); +int GameLib_ConvertRealMillisecondsToGameTime( + const idGameTimeManager* gameTimeManager, int milliseconds); +int GameLib_GetGameTicksPerSecond(); +bool GameLib_IsMD6NodeValid(const idMD6Node* node); + +namespace { + +enum animatorFlag_t { + ANIMATORFLAG_INITIALIZED = 0x01, + ANIMATORFLAG_ENABLED = 0x02, + ANIMATORFLAG_SERIALIZE = 0x08 +}; + +bool IsBlendFinished(const idMD6Branch& branch) { + return branch.alphaRate == 0.0f || + branch.currentAlpha == branch.targetAlpha; +} + +bool IsBlendingOut(const idMD6Branch& branch) { + return branch.currentAlpha > 0.0f && + branch.targetAlpha == 0.0f && branch.alphaRate != 0.0f; +} + +bool IsBlendingIn(const idMD6Branch& branch) { + return branch.currentAlpha < 1.0f && + branch.targetAlpha >= 1.0f && branch.alphaRate != 0.0f; +} + +idTypesafeNumber ConvertBlendTime( + const idGameTimeManager* gameTimeManager, const int milliseconds) { + return idTypesafeNumber( + GameLib_ConvertRealMillisecondsToGameTime( + gameTimeManager, milliseconds)); +} + +} // namespace + +idAnimator_Base::idAnimator_Base() + : gametimeManager{nullptr} + , weightGroup(MD6_WEIGHTGROUP_ALL) + , filterGroup(MD6_WEIGHTGROUP_ALL) + , serializeProps{{nullptr, nullptr}, nullptr, nullptr, false} + , initialized(false) { +} + +idAnimator_Base::~idAnimator_Base() = default; + +bool idAnimator_Base::InternalPostInit( + const idAnimatorParms_Base& parameters) { + if (parameters.animStack == nullptr) { + return false; + } + + GameLib_SetAnimatorFlag(parameters.animStack, this, + ANIMATORFLAG_SERIALIZE); + return true; +} + +void idAnimator_Base::Shutdown(idAnimStack* const stack) { + InternalShutdown(stack); + GameLib_ClearAnimatorFlag(stack, this, ANIMATORFLAG_INITIALIZED); + GameLib_RemoveAnimator(stack, this); +} + +bool idAnimator_Base::IsInitialized(idAnimStack* const stack) const { + return stack != nullptr && GameLib_IsAnimatorFlagSet( + stack, this, ANIMATORFLAG_INITIALIZED); +} + +bool idAnimator_Base::IsEnabled(idAnimStack* const stack) const { + return stack != nullptr && GameLib_IsAnimatorFlagSet( + stack, this, ANIMATORFLAG_ENABLED); +} + +void idAnimator_Base::SetEnabled(idAnimStack* const stack, + const bool enabled) { + if (enabled) { + GameLib_SetAnimatorFlag(stack, this, ANIMATORFLAG_ENABLED); + } else { + GameLib_ClearAnimatorFlag(stack, this, ANIMATORFLAG_ENABLED); + } +} + +bool idAnimator_Base::Init(idGameTimeManager* const gameTimeManager, + const idAnimatorParms_Base& parameters) { + if (GameLib_GetAnimatorIndex(parameters.animStack, this) >= 0 && + initialized) { + idLibPrint::Error("Attempted to add idAnimator %s twice.", + parameters.name.c_str()); + } + + gametimeManager.gameTimeManager = gameTimeManager; + weightGroup = parameters.weightGroup; + filterGroup = parameters.filterGroup; + + if (!InternalInit(parameters)) { + return false; + } + + idMD6Branch* const branch = InternalGetMergeBranch(); + if (branch != nullptr) { + branch->op = static_cast(parameters.blendOp); + branch->originBlend = + static_cast(parameters.originBlend); + branch->currentAlpha = parameters.alpha; + branch->targetAlpha = parameters.alpha; + branch->alphaRate = 0.0f; + branch->filterGroup = + static_cast(parameters.filterGroup); + } + + GameLib_AddAnimator(parameters.animStack, this); + GameLib_SetAnimatorFlag(parameters.animStack, this, + ANIMATORFLAG_INITIALIZED); + GameLib_SetAnimatorFlag(parameters.animStack, this, + ANIMATORFLAG_ENABLED); + initialized = true; + return InternalPostInit(parameters); +} + +void idAnimator_Base::Blend(const idAnimStack* const stack, + const int currentTime, const float targetAlpha, + const int blendDurationMS, const bool reset) { + idMD6Branch* const branch = InternalGetMergeBranch(); + branch->targetAlpha = targetAlpha; + + const idTypesafeNumber blendTime = + ConvertBlendTime(gametimeManager.gameTimeManager, blendDurationMS); + const bool alreadyBlending = targetAlpha <= branch->currentAlpha + ? IsBlendingOut(*branch) + : IsBlendingIn(*branch); + + if (blendTime.value <= 0) { + branch->currentAlpha = targetAlpha; + branch->alphaRate = 0.0f; + } else if (reset || !alreadyBlending) { + branch->alphaRate = std::fabs(targetAlpha - branch->currentAlpha) * + (1000.0f / static_cast(blendTime.value)); + } + + InternalBlend(stack, currentTime, targetAlpha, blendTime); +} + +bool idAnimator_Base::IsContributing() const { + const idMD6Branch* const branch = InternalGetMergeBranch(); + return branch != nullptr && branch->right != nullptr && + GameLib_IsMD6NodeValid(branch->right) && + (branch->currentAlpha > 0.0f || !IsBlendFinished(*branch)) && + InternalIsContributing(); +} + +md6WeightGroup_t idAnimator_Base::GetFilterGroup() const { + return static_cast( + InternalGetMergeBranch()->filterGroup); +} + +float idAnimator_Base::GetAlpha() const { + return InternalGetMergeBranch()->currentAlpha; +} + +void idAnimator_Base::SetAlpha(const float alpha) { + InternalGetMergeBranch()->currentAlpha = alpha; +} + +void idAnimator_Base::Pause(const idAnimStack* const stack, + const idTypesafeNumber currentTime) { + InternalPause(stack, currentTime); +} + +void idAnimator_Base::Unpause(const idAnimStack* const stack, + const idTypesafeNumber currentTime) { + InternalUnpause(stack, currentTime); +} + +void idAnimator_Base::Start(const idAnimStack* const stack, + const int currentTime, const int blendDurationMS, const bool reset) { + idMD6Branch* const branch = InternalGetMergeBranch(); + const idTypesafeNumber blendTime = + ConvertBlendTime(gametimeManager.gameTimeManager, blendDurationMS); + + if (blendTime.value <= 0) { + branch->currentAlpha = 1.0f; + branch->targetAlpha = 1.0f; + branch->alphaRate = 0.0f; + } else if (reset || !IsBlendingIn(*branch)) { + branch->targetAlpha = 1.0f; + branch->alphaRate = (1.0f - branch->currentAlpha) * + (static_cast(GameLib_GetGameTicksPerSecond()) / + static_cast(blendTime.value)); + } + + InternalStart(stack, currentTime, blendTime); +} + +void idAnimator_Base::End(const idAnimStack* const stack, + const int currentTime, const int blendDurationMS, const bool reset) { + if (!initialized) { + return; + } + + idMD6Branch* const branch = InternalGetMergeBranch(); + branch->targetAlpha = 0.0f; + const idTypesafeNumber blendTime = + ConvertBlendTime(gametimeManager.gameTimeManager, blendDurationMS); + + if (blendTime.value <= 0) { + branch->alphaRate = 0.0f; + branch->currentAlpha = 0.0f; + } else if (reset || !IsBlendingOut(*branch)) { + branch->alphaRate = branch->currentAlpha * + (static_cast(GameLib_GetGameTicksPerSecond()) / + static_cast(blendTime.value)); + } + + InternalEnd(stack, currentTime, blendTime); +} + +void idAnimator_Base::PreSerializeInit(idAnimStack* const stack, + idClip* const clip, idGameTimeManager* const gameTimeManager) { + serializeProps.animStack = stack; + serializeProps.clip = clip; + gametimeManager.gameTimeManager = gameTimeManager; + serializeProps.createdThroughSerialization = true; +} diff --git a/source/engine/gamelib/animstack/animator_base.h b/source/engine/gamelib/animstack/animator_base.h index 7f1cd65..cb37ba1 100644 --- a/source/engine/gamelib/animstack/animator_base.h +++ b/source/engine/gamelib/animstack/animator_base.h @@ -1,72 +1,107 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\animstack\animator_base.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/animstack/animstacktypes.h" +#include "idlib/text/str.h" +#include "idlib/typesafenumber.h" +class idSerializer; -// IDA Local Type ordinal 1357; PDB kind: enum. -enum idAnimator_Base::priority_t : __int32 -{ - PRIORITY_WEB = 0x0, - PRIORITY_AFTER_WEB = 0x1, - PRIORITY_IK = 0x63, - PRIORITY_AF = 0x64, -}; +class idAnimatorParms_Base; -// IDA Local Type ordinal 14224; PDB kind: class. -class __declspec(align(4)) idAnimator_Base -{ +class idAnimator_Base { public: - // Recovered virtual interface; IDA vtable ordinal 14289. - virtual ~idAnimator_Base(); - virtual idAnimator_Base::priority_t GetStackPriority(); - virtual serializeType_t GetSerializeType(); - virtual void SerializeSnapshot(idSerializer *); - virtual void PreBlendSnapshot(idAnimStack *, int, const int, float); - virtual void PreSerializeInit(idAnimStack *, idClip *, idGameTimeManager *); - virtual bool InternalInit(const idAnimatorParms_Base *); - virtual bool InternalPostInit(const idAnimatorParms_Base *); - virtual void InternalShutdown(idAnimStack *); - virtual void InternalPreBlendTree(const idAnimStack *, const int, const int); - virtual void InternalPostBlendTree(const idAnimStack *, const int); - virtual void InternalStart(const idAnimStack *, const int, const idTypesafeNumber); - virtual void InternalEnd(const idAnimStack *, const int, const idTypesafeNumber); - virtual void InternalBlend(const idAnimStack *, const int, const float, const idTypesafeNumber); - virtual bool InternalIsContributing(); - virtual const idMD6Branch *InternalGetMergeBranch(); - virtual idMD6Branch *InternalGetMergeBranch_2(); - virtual void InternalPause(const idAnimStack *, const idTypesafeNumber); - virtual void InternalUnpause(const idAnimStack *, const idTypesafeNumber); - virtual const idMD6Branch *InternalGetEndBranch(); - virtual idMD6Branch *InternalGetEndBranch_2(); + enum priority_t : int { + PRIORITY_WEB = 0, + PRIORITY_AFTER_WEB = 1, + PRIORITY_IK = 99, + PRIORITY_AF = 100 + }; - idGameTimeManagerPtr gametimeManager; - md6WeightGroup_t weightGroup; - md6WeightGroup_t filterGroup; - idAnimator_Base::serializeProps_t serializeProps; - bool initialized; + struct serializeProps_t { + idMD6Branch* serializedTrees[2]; + idAnimStack* animStack; + idClip* clip; + bool createdThroughSerialization; + }; + + idAnimator_Base(); + virtual ~idAnimator_Base(); + + bool Init(idGameTimeManager* gameTimeManager, + const idAnimatorParms_Base& parameters); + void Shutdown(idAnimStack* stack); + bool IsInitialized(idAnimStack* stack) const; + bool IsEnabled(idAnimStack* stack) const; + void SetEnabled(idAnimStack* stack, bool enabled); + void Blend(const idAnimStack* stack, int currentTime, float targetAlpha, + int blendDurationMS, bool reset); + bool IsContributing() const; + md6WeightGroup_t GetFilterGroup() const; + float GetAlpha() const; + void SetAlpha(float alpha); + void Pause(const idAnimStack* stack, + idTypesafeNumber currentTime); + void Unpause(const idAnimStack* stack, + idTypesafeNumber currentTime); + void Start(const idAnimStack* stack, int currentTime, + int blendDurationMS, bool reset); + void End(const idAnimStack* stack, int currentTime, + int blendDurationMS, bool reset); + + virtual priority_t GetStackPriority(); + virtual serializeType_t GetSerializeType(); + virtual void SerializeSnapshot(idSerializer* serializer); + virtual void PreBlendSnapshot(idAnimStack* stack, int currentTime, + int ticksPerSecond, float fraction); + virtual void PreSerializeInit(idAnimStack* stack, idClip* clip, + idGameTimeManager* gameTimeManager); + virtual bool InternalInit(const idAnimatorParms_Base& parameters); + virtual bool InternalPostInit(const idAnimatorParms_Base& parameters); + virtual void InternalShutdown(idAnimStack* stack); + virtual void InternalPreBlendTree(const idAnimStack* stack, + int currentTime, int ticksPerSecond); + virtual void InternalPostBlendTree(const idAnimStack* stack, + int currentTime); + virtual void InternalStart(const idAnimStack* stack, int currentTime, + idTypesafeNumber blendTime); + virtual void InternalEnd(const idAnimStack* stack, int currentTime, + idTypesafeNumber blendTime); + virtual void InternalBlend(const idAnimStack* stack, int currentTime, + float targetAlpha, + idTypesafeNumber blendTime); + virtual bool InternalIsContributing() const; + virtual const idMD6Branch* InternalGetMergeBranch() const; + virtual idMD6Branch* InternalGetMergeBranch(); + virtual void InternalPause(const idAnimStack* stack, + idTypesafeNumber currentTime); + virtual void InternalUnpause(const idAnimStack* stack, + idTypesafeNumber currentTime); + virtual const idMD6Branch* InternalGetEndBranch() const; + virtual idMD6Branch* InternalGetEndBranch(); + + idGameTimeManagerPtr gametimeManager; + md6WeightGroup_t weightGroup; + md6WeightGroup_t filterGroup; + serializeProps_t serializeProps; + bool initialized; }; -// IDA Local Type ordinal 14288; PDB kind: class. -class idAnimatorParms_Base -{ +class idAnimatorParms_Base { public: - idAnimStack *animStack; - idStr name; - idMD6Blend::blendOp_t blendOp; - idMD6Blend::originBlend_t originBlend; - md6WeightGroup_t weightGroup; - md6WeightGroup_t filterGroup; - float alpha; + idAnimStack* animStack; + idStr name; + int blendOp; + int originBlend; + md6WeightGroup_t weightGroup; + md6WeightGroup_t filterGroup; + float alpha; }; -// IDA Local Type ordinal 14291; PDB kind: struct. -struct __declspec(align(4)) idAnimator_Base::serializeProps_t -{ - idMD6Branch *serializedTrees[2]; - idAnimStack *animStack; - idClip *clip; - bool createdThroughSerialization; -}; +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idAnimator_Base::serializeProps_t) == 20, + "Recovered animator serialization properties ABI changed"); +static_assert(sizeof(idAnimator_Base) == 40, + "Recovered idAnimator_Base ABI changed"); +static_assert(sizeof(idAnimatorParms_Base) == 56, + "Recovered idAnimatorParms_Base ABI changed"); +#endif diff --git a/source/engine/gamelib/animstack/animator_paused.cpp b/source/engine/gamelib/animstack/animator_paused.cpp new file mode 100644 index 0000000..4d57a8b --- /dev/null +++ b/source/engine/gamelib/animstack/animator_paused.cpp @@ -0,0 +1,79 @@ +#include "gamelib/animstack/animator_paused.h" + +idMD6Branch* GameLib_AllocMD6Branch(idAnimStack* stack); +idMD6LeafPause* GameLib_AllocMD6LeafPause(idAnimStack* stack); +void GameLib_FreeMD6Branch(idAnimStack* stack, idMD6Branch* branch); +void GameLib_FreeMD6LeafPause(idAnimStack* stack, idMD6LeafPause* leaf); +int GameLib_GetMD6AnimNumFrames(const idMD6Anim* animation); + +idAnimator_Paused::idAnimator_Paused() + : idAnimator_Base() + , leaf(nullptr) + , mergeBranch(nullptr) + , anim(nullptr) { +} + +idAnimator_Paused::~idAnimator_Paused() { + leaf = nullptr; + mergeBranch = nullptr; + anim = nullptr; +} + +void idAnimator_Paused::SetAnim(const idMD6Anim* const animation) { + anim = animation; + if (animation != nullptr) { + leaf->Init(animation, leaf->frame, leaf->wrapMode, + MD6_WEIGHTGROUP_ALL); + } + leaf->anim = animation; +} + +void idAnimator_Paused::SetFrame(const float animationFrame) { + leaf->frame = animationFrame; +} + +float idAnimator_Paused::GetFrame() const { + return leaf->frame; +} + +void idAnimator_Paused::SetNormalizedFrame(const float normalizedFrame) { + leaf->frame = static_cast(GameLib_GetMD6AnimNumFrames(leaf->anim) - 1) * + normalizedFrame; +} + +bool idAnimator_Paused::InternalInit( + const idAnimatorParms_Base& parameters) { + const idAnimatorParms_Pause& pauseParameters = + static_cast(parameters); + + mergeBranch = GameLib_AllocMD6Branch(parameters.animStack); + leaf = GameLib_AllocMD6LeafPause(parameters.animStack); + mergeBranch->right = leaf; + anim = pauseParameters.anim; + if (anim != nullptr) { + leaf->Init(anim, leaf->frame, leaf->wrapMode, MD6_WEIGHTGROUP_ALL); + } + leaf->anim = anim; + + if (pauseParameters.normalizedStartFrame < 0.0f) { + leaf->frame = pauseParameters.startFrame; + } else { + SetNormalizedFrame(pauseParameters.normalizedStartFrame); + } + return true; +} + +void idAnimator_Paused::InternalShutdown(idAnimStack* const stack) { + if (mergeBranch != nullptr) { + GameLib_FreeMD6Branch(stack, mergeBranch); + mergeBranch = nullptr; + } + if (leaf != nullptr) { + GameLib_FreeMD6LeafPause(stack, leaf); + leaf = nullptr; + } +} + +bool idAnimator_Paused::InternalIsContributing() const { + return leaf != nullptr && leaf->anim != nullptr; +} diff --git a/source/engine/gamelib/animstack/animator_paused.h b/source/engine/gamelib/animstack/animator_paused.h index 13c0757..f419ed0 100644 --- a/source/engine/gamelib/animstack/animator_paused.h +++ b/source/engine/gamelib/animstack/animator_paused.h @@ -1,48 +1,60 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\animstack\animator_paused.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/animstack/animator_base.h" +class idAnimatorParms_Pause; -// IDA Local Type ordinal 18898; PDB kind: class. -class idAnimator_Paused : public idAnimator_Base -{ +class idAnimator_Paused : public idAnimator_Base { public: - // Recovered virtual interface; IDA vtable ordinal 18899. - virtual ~idAnimator_Paused(); - virtual idAnimator_Base::priority_t GetStackPriority(); - virtual serializeType_t GetSerializeType(); - virtual void SerializeSnapshot(idSerializer *); - virtual void PreBlendSnapshot(idAnimStack *, int, const int, float); - virtual void PreSerializeInit(idAnimStack *, idClip *, idGameTimeManager *); - virtual bool InternalInit(const idAnimatorParms_Base *); - virtual bool InternalPostInit(const idAnimatorParms_Base *); - virtual void InternalShutdown(idAnimStack *); - virtual void InternalPreBlendTree(const idAnimStack *, const int, const int); - virtual void InternalPostBlendTree(const idAnimStack *, const int); - virtual void InternalStart(const idAnimStack *, const int, const idTypesafeNumber); - virtual void InternalEnd(const idAnimStack *, const int, const idTypesafeNumber); - virtual void InternalBlend(const idAnimStack *, const int, const float, const idTypesafeNumber); - virtual bool InternalIsContributing(); - virtual const idMD6Branch *InternalGetMergeBranch(); - virtual idMD6Branch *InternalGetMergeBranch_2(); - virtual void InternalPause(const idAnimStack *, const idTypesafeNumber); - virtual void InternalUnpause(const idAnimStack *, const idTypesafeNumber); - virtual const idMD6Branch *InternalGetEndBranch(); - virtual idMD6Branch *InternalGetEndBranch_2(); + idAnimator_Paused(); + ~idAnimator_Paused() override; - idMD6LeafPause *leaf; - idMD6Branch *mergeBranch; - const idMD6Anim *anim; + void SetAnim(const idMD6Anim* animation); + const idMD6Anim* GetAnim() const { return anim; } + void SetFrame(float frame); + float GetFrame() const; + void SetNormalizedFrame(float normalizedFrame); + + bool InternalInit(const idAnimatorParms_Base& parameters) override; + void InternalShutdown(idAnimStack* stack) override; + bool InternalIsContributing() const override; + const idMD6Branch* InternalGetMergeBranch() const override { + return mergeBranch; + } + idMD6Branch* InternalGetMergeBranch() override { return mergeBranch; } + + idMD6LeafPause* leaf; + idMD6Branch* mergeBranch; + const idMD6Anim* anim; }; -// IDA Local Type ordinal 18908; PDB kind: class. -class idAnimatorParms_Pause : public idAnimatorParms_Base -{ +class idAnimatorParms_Pause : public idAnimatorParms_Base { public: - const idMD6Anim *anim; - float startFrame; - float normalizedStartFrame; + idAnimatorParms_Pause(const idMD6Anim* animation, float startFrame_, + float normalizedStartFrame_, idAnimStack* stack, const char* name_, + int blendOp_, int originBlend_, md6WeightGroup_t weightGroup_, + md6WeightGroup_t filterGroup_, float alpha_) + : idAnimatorParms_Base() + , anim(animation) + , startFrame(startFrame_) + , normalizedStartFrame(normalizedStartFrame_) { + animStack = stack; + name = name_; + blendOp = blendOp_; + originBlend = originBlend_; + weightGroup = weightGroup_; + filterGroup = filterGroup_; + alpha = alpha_; + } + + const idMD6Anim* anim; + float startFrame; + float normalizedStartFrame; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idAnimator_Paused) == 52, + "Recovered idAnimator_Paused ABI changed"); +static_assert(sizeof(idAnimatorParms_Pause) == 68, + "Recovered idAnimatorParms_Pause ABI changed"); +#endif diff --git a/source/engine/gamelib/animstack/animator_proxy.cpp b/source/engine/gamelib/animstack/animator_proxy.cpp new file mode 100644 index 0000000..cba66ec --- /dev/null +++ b/source/engine/gamelib/animstack/animator_proxy.cpp @@ -0,0 +1,39 @@ +#include "gamelib/animstack/animator_proxy.h" + +// These allocator calls are owned by the idAnimStack/md6Allocator recovery. +// Keeping the boundary explicit allows the complete Proxy TU to compile now +// without inventing an allocator layout or a temporary allocation policy. +idMD6Branch* GameLib_AllocMD6Branch(idAnimStack* stack); +void GameLib_FreeMD6Branch(idAnimStack* stack, idMD6Branch* branch); + +idAnimator_Proxy::idAnimator_Proxy() + : idAnimator_Base() + , mergeBranch(nullptr) { +} + +idAnimator_Proxy::~idAnimator_Proxy() { + mergeBranch = nullptr; +} + +void idAnimator_Proxy::SetTree(idMD6Node* const tree) { + if (mergeBranch != nullptr) { + mergeBranch->right = tree; + } +} + +bool idAnimator_Proxy::InternalInit( + const idAnimatorParms_Base& parameters) { + mergeBranch = GameLib_AllocMD6Branch(parameters.animStack); + return true; +} + +void idAnimator_Proxy::InternalShutdown(idAnimStack* const stack) { + if (stack != nullptr && mergeBranch != nullptr) { + GameLib_FreeMD6Branch(stack, mergeBranch); + mergeBranch = nullptr; + } +} + +bool idAnimator_Proxy::InternalIsContributing() const { + return mergeBranch != nullptr && mergeBranch->right != nullptr; +} diff --git a/source/engine/gamelib/animstack/animator_proxy.h b/source/engine/gamelib/animstack/animator_proxy.h index ff88ae2..60bbe88 100644 --- a/source/engine/gamelib/animstack/animator_proxy.h +++ b/source/engine/gamelib/animstack/animator_proxy.h @@ -1,37 +1,27 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\animstack\animator_proxy.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/animstack/animator_base.h" - -// IDA Local Type ordinal 15219; PDB kind: class. -class idAnimator_Proxy : public idAnimator_Base -{ +class idAnimator_Proxy : public idAnimator_Base { public: - // Recovered virtual interface; IDA vtable ordinal 15220. - virtual ~idAnimator_Proxy(); - virtual idAnimator_Base::priority_t GetStackPriority(); - virtual serializeType_t GetSerializeType(); - virtual void SerializeSnapshot(idSerializer *); - virtual void PreBlendSnapshot(idAnimStack *, int, const int, float); - virtual void PreSerializeInit(idAnimStack *, idClip *, idGameTimeManager *); - virtual bool InternalInit(const idAnimatorParms_Base *); - virtual bool InternalPostInit(const idAnimatorParms_Base *); - virtual void InternalShutdown(idAnimStack *); - virtual void InternalPreBlendTree(const idAnimStack *, const int, const int); - virtual void InternalPostBlendTree(const idAnimStack *, const int); - virtual void InternalStart(const idAnimStack *, const int, const idTypesafeNumber); - virtual void InternalEnd(const idAnimStack *, const int, const idTypesafeNumber); - virtual void InternalBlend(const idAnimStack *, const int, const float, const idTypesafeNumber); - virtual bool InternalIsContributing(); - virtual const idMD6Branch *InternalGetMergeBranch(); - virtual idMD6Branch *InternalGetMergeBranch_2(); - virtual void InternalPause(const idAnimStack *, const idTypesafeNumber); - virtual void InternalUnpause(const idAnimStack *, const idTypesafeNumber); - virtual const idMD6Branch *InternalGetEndBranch(); - virtual idMD6Branch *InternalGetEndBranch_2(); + idAnimator_Proxy(); + ~idAnimator_Proxy() override; - idMD6Branch *mergeBranch; + void SetTree(idMD6Node* tree); + bool InternalInit(const idAnimatorParms_Base& parameters) override; + void InternalShutdown(idAnimStack* stack) override; + bool InternalIsContributing() const override; + const idMD6Branch* InternalGetMergeBranch() const override { + return mergeBranch; + } + idMD6Branch* InternalGetMergeBranch() override { + return mergeBranch; + } + + idMD6Branch* mergeBranch; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idAnimator_Proxy) == 44, + "Recovered idAnimator_Proxy ABI changed"); +#endif diff --git a/source/engine/gamelib/animstack/animstacktypes.h b/source/engine/gamelib/animstack/animstacktypes.h new file mode 100644 index 0000000..12c2b28 --- /dev/null +++ b/source/engine/gamelib/animstack/animstacktypes.h @@ -0,0 +1,131 @@ +#pragma once + +#include "idlib/bv/bounds.h" +#include "idlib/math/vector.h" + +#include + +class idAnimStack; +class idClip; +class idGameTimeManager; +class idMD6Anim; + +enum gameTimeUnique_t : int; + +enum md6WeightGroup_t : int { + MD6_WEIGHTGROUP_ALL = 0, + MD6_WEIGHTGROUP_LEGS = 1, + MD6_WEIGHTGROUP_TORSO = 2, + MD6_WEIGHTGROUP_HEAD = 3, + MD6_WEIGHTGROUP_FACE = 4, + MD6_WEIGHTGROUP_MOUTH = 5, + MD6_WEIGHTGROUP_EYELIDS = 6, + MD6_WEIGHTGROUP_USER0 = 7, + MD6_WEIGHTGROUP_MAX = 8 +}; + +enum serializeType_t : int { + STYPE_GENERIC = 0, + STYPE_AF = 1, + STYPE_ANIMWEB_HANDS = 2, + STYPE_WALK_IK = 3, + STYPE_TORSO_TRACKER = 4, + STYPE_PAIN = 5, + STYPE_JOINTMOD = 6, + STYPE_REACH_IK = 7, + STYPE_MAX = 8 +}; + +class idMD6Node { +public: + enum nodeType_t : int { + NODE_BRANCH = 0, + NODE_LEAF_PAUSE = 1, + NODE_LEAF_PLAY = 2, + NODE_BLEND_BRANCH = 3, + NODE_BLENDA_BRANCH = 4, + NODE_FUSION_BRANCH = 5, + NODE_BEST_LEAF = 6, + NODE_TAG_FILTER = 7, + NODE_NONE = 0xFF + }; + + std::uint8_t type; +}; + +class idMD6Branch : public idMD6Node { +public: + enum blendType_t : int { + BLEND_LINEAR = 0, + BLEND_EASEIN = 1, + BLEND_EASEOUT = 2, + BLEND_EASEIN_EASEOUT = 3, + BLEND_TYPE_COUNT = 4 + }; + + idMD6Node* left; + idMD6Node* right; + int leftTimeOverride; + int rightTimeOverride; + std::uint8_t filterGroup; + std::uint8_t op; + std::uint8_t originBlend; + float currentAlpha; + float targetAlpha; + float alphaRate; + blendType_t blendType; +}; + +// The leaf layout is materialized only to the extent proven by the recovered +// md6animtree and idAnimator_Paused bodies. The two list records remain opaque +// until the joint-mod translation unit is activated. +struct idMD6OpaqueList { + void* list; + int num; + int size; + std::int16_t granularity; + std::uint8_t memTag; + std::uint8_t listStatic; +}; + +class idMD6LeafPause : public idMD6Node { +public: + std::uint8_t nodePadding[3]; + const idMD6Anim* anim; + std::uint8_t weightGroup; + std::uint8_t wrapMode; + std::uint8_t initCounter; + std::uint8_t pad; + int currentDeferred; + idMD6OpaqueList animMods[2]; + std::int16_t flags; + std::uint8_t framePadding[2]; + float frame; + idBounds bounds; + + void Init(const idMD6Anim* animation, float animationFrame, + std::uint8_t animationWrapMode, md6WeightGroup_t animationWeightGroup) { + frame = animationFrame; + anim = animation; + flags = 0; + weightGroup = static_cast(animationWeightGroup); + wrapMode = animationWrapMode; + } +}; + +struct idGameTimeManagerPtr { + idGameTimeManager* gameTimeManager; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idMD6Node) == 1, + "Recovered idMD6Node ABI changed"); +static_assert(sizeof(idMD6Branch) == 40, + "Recovered idMD6Branch ABI changed"); +static_assert(sizeof(idMD6OpaqueList) == 16, + "Recovered MD6 opaque list ABI changed"); +static_assert(sizeof(idMD6LeafPause) == 80, + "Recovered idMD6LeafPause ABI changed"); +static_assert(sizeof(idGameTimeManagerPtr) == 4, + "Recovered idGameTimeManagerPtr ABI changed"); +#endif diff --git a/source/engine/gamelib/animstack/animweb/animwebpath.h b/source/engine/gamelib/animstack/animweb/animwebpath.h index fa47147..35fb2f2 100644 --- a/source/engine/gamelib/animstack/animweb/animwebpath.h +++ b/source/engine/gamelib/animstack/animweb/animwebpath.h @@ -1,13 +1,61 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\animstack\animweb\animwebpath.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/index.h" +#include "idlib/text/str.h" +#include +#include -// IDA Local Type ordinal 16590; PDB kind: class. -class idAnimWebPath : public idStr -{ +// idAnimWebPath has no storage beyond its recovered idStr base. +class idAnimWebPath : public idStr { public: + using idStr::idStr; + using idStr::operator=; }; + +// The owning idDeclAnimWeb declaration is not compile-locked yet. This tag +// retains the recovered two-byte node-index representation without importing +// that malformed generated declaration into GameLib's active boundary. +enum class idAnimWebInvalidNodeIndex_t : int { + invalid = -1 +}; + +using idAnimWebNodeIndex = idIndex; + +struct idAnimWebRoute { + idAnimWebNodeIndex path[32]; + int num; + int cost; + + // Materialized in the authoritative animwebpath.h dump. + void Invert() { + for (int index = 0; index < num / 2; ++index) { + std::swap(path[index], path[num - index - 1]); + } + } + + // Materialized in the authoritative animwebpath.h dump. A shared prefix + // node is consumed so concatenated routes do not duplicate their join. + void AppendPath(const idAnimWebRoute& other) { + int first = 0; + if (num > 0) { + while (first < other.num + && path[num - 1].Get() == other.path[first].Get()) { + ++first; + } + } + while (first < other.num && num < 32) { + path[num++] = other.path[first++]; + } + cost += other.cost; + } +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idAnimWebPath) == 32, + "Recovered idAnimWebPath ABI changed"); +static_assert(sizeof(idAnimWebNodeIndex) == 2, + "Recovered AnimWeb node index ABI changed"); +static_assert(sizeof(idAnimWebRoute) == 72, + "Recovered idAnimWebRoute ABI changed"); +#endif diff --git a/source/engine/gamelib/animstack/animweb/animwebutils.cpp b/source/engine/gamelib/animstack/animweb/animwebutils.cpp new file mode 100644 index 0000000..7818160 --- /dev/null +++ b/source/engine/gamelib/animstack/animweb/animwebutils.cpp @@ -0,0 +1,100 @@ +#include "gamelib/animstack/animweb/animwebutils.h" + +#include +#include + +float LerpToWithScale(const float current, const float destination, + const float scale) { + const float delta = destination - current; + return delta <= -0.000001f || delta >= 0.000001f + ? current + delta * scale + : destination; +} + +float LerpToWithRate(const float current, const float destination, + const float absoluteRate) { + if (current < destination) { + const float next = current + absoluteRate; + if (next < current) { + return current; + } + return next > destination ? destination : next; + } + if (current > destination) { + const float next = current - absoluteRate; + if (next > current) { + return current; + } + return next < destination ? destination : next; + } + return current; +} + +namespace { + +void SetSync8(const float angle, const bool reverse, float& index1, + float& index2, float& blend) { + constexpr float inverse45 = 1.0f / 45.0f; + const int sector = static_cast(angle * inverse45); + const float fraction = (angle - static_cast(sector) * 45.0f) + * inverse45; + + const int first = reverse ? 7 - sector : sector; + index1 = static_cast(first); + index2 = static_cast((first + 1) & 7); + blend = reverse ? 1.0f - fraction : fraction; +} + +} // namespace + +bool UpdateWalkBlend(const idVec3& velocity, const idMat3& axis, + const float lerpScale, float& blendBack, float& blendRight, + float& blendAngle, float& sync8Index1, float& sync8Index2, + float& sync8BlendAngle) { + const float speed = velocity.Length(); + if (speed <= 0.1f) { + return false; + } + + const float inverseSpeed = 1.0f / speed; + const float forward = (std::max)(-1.0f, (std::min)(1.0f, + velocity.Dot(axis[0]) * inverseSpeed)); + const float right = (std::max)(-1.0f, (std::min)(1.0f, + velocity.Dot(axis[1]) * inverseSpeed)); + constexpr float radiansToDegrees = 57.29577951308232f; + const float angle = std::acos(forward) * radiansToDegrees; + const float forwardAmount = forward < 0.0f + ? (angle - 90.0f) / 90.0f + : 1.0f - angle / 90.0f; + + blendBack = LerpToWithScale(blendBack, forward < 0.0f ? 1.0f : 0.0f, + lerpScale); + blendRight = LerpToWithScale(blendRight, right < 0.0f ? 1.0f : 0.0f, + lerpScale); + blendAngle = LerpToWithScale(blendAngle, 1.0f - forwardAmount, + lerpScale); + SetSync8(angle, right >= 0.0f, sync8Index1, sync8Index2, + sync8BlendAngle); + return true; +} + +bool UpdateWalkBlendFromAngle(const float angle, const float lerpScale, + float& blendBack, float& blendRight, float& blendAngle, + float& sync8Index1, float& sync8Index2, float& sync8BlendAngle) { + const bool forward = angle > -90.0f && angle < 90.0f; + const bool positive = angle > 0.0f && angle < 180.0f; + const float absoluteAngle = std::fabs(angle); + const float forwardAmount = forward + ? 1.0f - absoluteAngle / 90.0f + : (absoluteAngle - 90.0f) / 90.0f; + + blendBack = LerpToWithScale(blendBack, forward ? 0.0f : 1.0f, + lerpScale); + blendRight = LerpToWithScale(blendRight, positive ? 0.0f : 1.0f, + lerpScale); + blendAngle = LerpToWithScale(blendAngle, 1.0f - forwardAmount, + lerpScale); + SetSync8(absoluteAngle, positive, sync8Index1, sync8Index2, + sync8BlendAngle); + return true; +} diff --git a/source/engine/gamelib/animstack/animweb/animwebutils.h b/source/engine/gamelib/animstack/animweb/animwebutils.h new file mode 100644 index 0000000..d947948 --- /dev/null +++ b/source/engine/gamelib/animstack/animweb/animwebutils.h @@ -0,0 +1,14 @@ +#pragma once + +#include "idlib/math/vector.h" + +float LerpToWithScale(float current, float destination, float scale); +float LerpToWithRate(float current, float destination, float absoluteRate); + +bool UpdateWalkBlend(const idVec3& velocity, const idMat3& axis, + float lerpScale, float& blendBack, float& blendRight, float& blendAngle, + float& sync8Index1, float& sync8Index2, float& sync8BlendAngle); + +bool UpdateWalkBlendFromAngle(float angle, float lerpScale, + float& blendBack, float& blendRight, float& blendAngle, + float& sync8Index1, float& sync8Index2, float& sync8BlendAngle); diff --git a/source/engine/gamelib/animstack/animweb/dijkstra.h b/source/engine/gamelib/animstack/animweb/dijkstra.h index 1165d6b..64996b1 100644 --- a/source/engine/gamelib/animstack/animweb/dijkstra.h +++ b/source/engine/gamelib/animstack/animweb/dijkstra.h @@ -1,14 +1,71 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\animstack\animweb\dijkstra.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/index.h" +#include -// IDA Local Type ordinal 23485; PDB kind: class. -class idDijkstra,idDeclAnimWeb::idEdgeCache,idAnimWebRoute> -{ +// Recovered idDijkstra storage and interface. The materialized Xbox binary is +// the AnimWeb specialization, while keeping the original template boundary +// lets the algorithm be compiled and tested before idDeclAnimWeb is active. +template +class idDijkstra { public: - int lastStartNode; + struct djScratch_t { + short prevIndex; + std::uint16_t costAndVisited; + + int Cost() const { + return costAndVisited & 0x7FFFu; + } + + bool Visited() const { + return (costAndVisited & 0x8000u) != 0; + } + + void SetCost(const int cost) { + costAndVisited = static_cast( + (costAndVisited & 0x8000u) | (cost & 0x7FFF)); + } + + void SetVisited(const bool visited) { + if (visited) { + costAndVisited |= 0x8000u; + } else { + costAndVisited &= 0x7FFFu; + } + } + }; + + idDijkstra() + : lastStartNode(-1) { + } + + bool TracePath( + const nodeHolderType& nodeHolder, + nodeIndexType startNode, + nodeIndexType destNode, + const djScratch_t* scratchBuff, + pathType& path); + + bool FindShortestPaths( + const nodeHolderType& nodeHolder, + nodeIndexType startNode, + const nodeIndexType* destNodes, + int numDestNodes, + const edgeCacheType* edges, + djScratch_t* scratchBuff, + int skipFlags, + int requiredFlags); + + int lastStartNode; }; + +static_assert(sizeof(typename idDijkstra, int, int>::djScratch_t) == 4, + "Recovered idDijkstra scratch ABI changed"); +static_assert(sizeof(idDijkstra, int, int>) == 4, + "Recovered idDijkstra ABI changed"); + +#include "gamelib/animstack/animweb/dijkstra_impl.h" diff --git a/source/engine/gamelib/animstack/animweb/dijkstra_impl.h b/source/engine/gamelib/animstack/animweb/dijkstra_impl.h new file mode 100644 index 0000000..d4edecb --- /dev/null +++ b/source/engine/gamelib/animstack/animweb/dijkstra_impl.h @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include +#include +#include + +template +bool idDijkstra::TracePath( + const nodeHolderType& nodeHolder, + const nodeIndexType startNode, + const nodeIndexType destNode, + const djScratch_t* scratchBuff, + pathType& path) { + (void)nodeHolder; + + const int start = static_cast(startNode.Get()); + const int destination = static_cast(destNode.Get()); + if (lastStartNode != start) { + return false; + } + + path.num = 0; + path.cost = 0x7FFFFFFF; + if (destination == start) { + return false; + } + + path.path[path.num++] = nodeIndexType( + static_cast(destination)); + const djScratch_t* scratch = &scratchBuff[destination]; + path.cost = scratch->Cost(); + + while (path.num < 32) { + const short previous = scratch->prevIndex; + if (previous < 0) { + path.cost = 0x7FFFFFFF; + path.num = 0; + return false; + } + + path.path[path.num++] = nodeIndexType(previous); + scratch = &scratchBuff[previous]; + if (previous == start) { + path.Invert(); + return true; + } + } + + // The recovered implementation returns the first 32 nodes when a route + // exceeds the fixed route buffer, then reverses that bounded prefix. + path.Invert(); + return true; +} + +template +bool idDijkstra::FindShortestPaths( + const nodeHolderType& nodeHolder, + const nodeIndexType startNode, + const nodeIndexType* destNodes, + const int numDestNodes, + const edgeCacheType* edges, + djScratch_t* scratchBuff, + const int skipFlags, + const int requiredFlags) { + const int nodeCount = nodeHolder.decl->nodes.num; + std::memset(scratchBuff, 0xFF, + sizeof(djScratch_t) * static_cast(nodeCount)); + + for (int listIndex = 0; + listIndex < nodeHolder.pathableSubWebNodeLists.num; + ++listIndex) { + const auto* const pathable = + nodeHolder.pathableSubWebNodeLists.list[listIndex]; + for (int nodeIndex = 0; nodeIndex < pathable->num; ++nodeIndex) { + const int value = static_cast(pathable->list[nodeIndex].Get()); + scratchBuff[value].SetVisited(false); + } + } + + const int start = static_cast(startNode.Get()); + scratchBuff[start].SetVisited(false); + scratchBuff[start].SetCost(0); + scratchBuff[start].prevIndex = -1; + lastStartNode = -1; + + using heapEntry_t = std::pair; + std::priority_queue, + std::greater> heap; + heap.push(heapEntry_t(0, start)); + + int destinationsFound = 0; + int processedNodes = 0; + while (!heap.empty() && processedNodes < nodeCount) { + const int node = heap.top().second; + heap.pop(); + + djScratch_t& nodeScratch = scratchBuff[node]; + if (nodeScratch.Visited()) { + continue; + } + + for (int destinationIndex = 0; + destinationIndex < numDestNodes; + ++destinationIndex) { + if (destNodes[destinationIndex].Get() == node) { + ++destinationsFound; + if (destinationsFound == numDestNodes) { + lastStartNode = start; + return true; + } + break; + } + } + + const nodeCacheType& nodeCache = nodeHolder.decl->nodeCache.list[node]; + const int nodeCost = nodeScratch.Cost(); + nodeScratch.SetVisited(true); + + if ((nodeCache.flags & skipFlags) == 0 + && (nodeCache.flags & requiredFlags) == requiredFlags) { + for (int localEdge = 0; localEdge < nodeCache.numEdges; + ++localEdge) { + const int edgeIndex = nodeHolder.decl->edgeIndexCache[ + nodeCache.edgeIndexOffset + localEdge]; + const edgeCacheType& edge = edges[edgeIndex]; + const int destination = static_cast( + edge.destNodeIndex.Get()); + djScratch_t& destinationScratch = scratchBuff[destination]; + if (destinationScratch.Visited()) { + continue; + } + + const float scaledCost = static_cast( + nodeHolder.InternalGetEdgeCost( + nodeIndexType(static_cast(node)), + { static_cast(edgeIndex) })) + * (static_cast(edge.weightScale) * 0.0625f); + const int newCost = nodeCost + static_cast(scaledCost); + if (destinationScratch.Cost() > newCost) { + destinationScratch.prevIndex = static_cast(node); + destinationScratch.SetCost(newCost); + heap.push(heapEntry_t(newCost, destination)); + } + } + } + + ++processedNodes; + } + + lastStartNode = start; + return true; +} diff --git a/source/engine/gamelib/animstack/coneconstraint.cpp b/source/engine/gamelib/animstack/coneconstraint.cpp new file mode 100644 index 0000000..cf1eae3 --- /dev/null +++ b/source/engine/gamelib/animstack/coneconstraint.cpp @@ -0,0 +1,129 @@ +#include "gamelib/animstack/coneconstraint.h" + +#include + +namespace { + +idQuat Multiply(const idQuat& lhs, const idQuat& rhs) { + return idQuat( + lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y, + lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z, + lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x, + lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z); +} + +idVec3 ToForward(const idQuat& quaternion) { + return idVec3( + 1.0f - 2.0f * (quaternion.y * quaternion.y + + quaternion.z * quaternion.z), + 2.0f * (quaternion.x * quaternion.y + - quaternion.w * quaternion.z), + 2.0f * (quaternion.w * quaternion.y + + quaternion.x * quaternion.z)); +} + +idVec3 ConstrainDirection( + const idVec3& center, + const idVec3& direction, + const float limitAngleDot, + const float halfLimitAngleCosine, + const float halfLimitAngleSine) { + const float dot = center.Dot(direction); + if (dot >= limitAngleDot) { + return direction; + } + + idVec3 tangent = direction - center * dot; + if (tangent.NormalizeFast() == 0.0f) { + return center; + } + + const float sine = 2.0f * halfLimitAngleSine + * halfLimitAngleCosine; + return center * limitAngleDot + tangent * sine; +} + +} // namespace + +idConeConstraint_Vec3::idConeConstraint_Vec3( + const idConeConstraint_Vec3& other) = default; + +idConeConstraint_Quat::idConeConstraint_Quat( + const idConeConstraint_Quat& other) = default; + +idConeConstraint_Vec3::idConeConstraint_Vec3( + const idVec3& center_, + const radians_t maxAngle_) + : current(center_) + , center(center_) + , limitAngleDot(std::cos(maxAngle_.value)) + , halfLimitAngleCosine(std::cos(maxAngle_.value * 0.5f)) + , halfLimitAngleSine(std::sin(maxAngle_.value * 0.5f)) { +} + +void idConeConstraint_Vec3::RotateTo( + const idVec3& dir, + const float lerpRate) { + const idVec3 target = ConstrainDirection( + center, + dir, + limitAngleDot, + halfLimitAngleCosine, + halfLimitAngleSine); + + current = current + (target - current) * lerpRate; + current.NormalizeFast(); +} + +idConeConstraint_Quat::idConeConstraint_Quat( + const idQuat& center_, + const radians_t maxAngle_) + : current(center_) + , center(center_) + , limitAngleDot(std::cos(maxAngle_.value)) + , halfLimitAngleCosine(std::cos(maxAngle_.value * 0.5f)) + , halfLimitAngleSine(std::sin(maxAngle_.value * 0.5f)) { +} + +void idConeConstraint_Quat::RotateTo( + const idVec3& dir, + const float lerpRate) { + const idVec3 forward = ToForward(current); + const float forwardDot = dir.Dot(forward); + + if (forwardDot > -1.0f && forwardDot < 1.0f) { + idVec3 blendedForward = forward + + (dir - forward) * (lerpRate * 0.5f); + blendedForward.NormalizeFast(); + + const float rotationDot = blendedForward.Dot(forward); + const float rotationSine = rotationDot < 1.0f + ? std::sqrt(1.0f - rotationDot * rotationDot) + : 0.0f; + idVec3 rotationAxis = blendedForward.Cross(forward); + rotationAxis.NormalizeFast(); + + const idQuat rotation( + rotationAxis.x * rotationSine, + rotationAxis.y * rotationSine, + rotationAxis.z * rotationSine, + rotationDot); + current = Multiply(current, rotation); + current.Normalize(); + } + + const idVec3 centerForward = ToForward(center); + const idVec3 currentForward = ToForward(current); + if (centerForward.Dot(currentForward) < limitAngleDot) { + idVec3 rotationAxis = currentForward.Cross(centerForward); + rotationAxis.NormalizeFast(); + + const idQuat rotation( + rotationAxis.x * halfLimitAngleSine, + rotationAxis.y * halfLimitAngleSine, + rotationAxis.z * halfLimitAngleSine, + halfLimitAngleCosine); + current = Multiply(center, rotation); + current.Normalize(); + } +} diff --git a/source/engine/gamelib/animstack/coneconstraint.h b/source/engine/gamelib/animstack/coneconstraint.h index 28874de..58a36b6 100644 --- a/source/engine/gamelib/animstack/coneconstraint.h +++ b/source/engine/gamelib/animstack/coneconstraint.h @@ -1,207 +1,57 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\animstack\coneconstraint.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/math/quat.h" +#include "idlib/math/radians.h" - -// IDA Local Type ordinal 16484; PDB kind: class. -class idConeConstraint_Quat -{ +// Reconstructed from the recovered coneconstraint.h declarations and the +// six authoritative functions in animstack/coneconstraint.cpp. +class idConeConstraint_Quat { public: - idQuat current; - idQuat center; - float limitAngleDot; - float halfLimitAngleCosine; - float halfLimitAngleSine; + idConeConstraint_Quat(const idConeConstraint_Quat& other); + idConeConstraint_Quat(const idQuat& center_, radians_t maxAngle_); + + void RotateTo(const idVec3& dir, float lerpRate); + + idQuat current; + idQuat center; + float limitAngleDot; + float halfLimitAngleCosine; + float halfLimitAngleSine; }; -// IDA Local Type ordinal 19154; PDB kind: class. -class idConeConstraint_Vec3 -{ +class idConeConstraint_Vec3 { public: - idVec3 current; - idVec3 center; - float limitAngleDot; - float halfLimitAngleCosine; - float halfLimitAngleSine; + idConeConstraint_Vec3(const idConeConstraint_Vec3& other); + idConeConstraint_Vec3(const idVec3& center_, radians_t maxAngle_); + + void RotateTo(const idVec3& dir, float lerpRate); + + idVec3 current; + idVec3 center; + float limitAngleDot; + float halfLimitAngleCosine; + float halfLimitAngleSine; }; -// IDA Local Type ordinal 19160; PDB kind: class. -class idTest_ConeConstraint : public idEntity -{ +// The matrix form has declaration/layout evidence but no materialized methods +// in this translation unit. +class idConeConstraint_Mat3 { public: - // Recovered virtual interface; IDA vtable ordinal 19161. - virtual idTypeInfo *GetType(); - virtual ~idTest_ConeConstraint(); - virtual idEventArg *CallEvent(idEventArg *result, const idEventDef *, const idEventArg *); - virtual bool RespondsTo(const idEventDef *); - virtual idEventArg *InternalCallEvent(idEventArg *result, const idEventDef *, const idEventArg *); - virtual bool InternalRespondsTo(const idEventDef *); - virtual void PostSpawn(); - virtual void Remove(); - virtual void DeleteSubEntities(); - virtual bool Draw(idPlayer *); - virtual void JobSync(); - virtual void Think(); - virtual void PauseThink(); - virtual bool ShouldEnterDormancy(); - virtual bool ShouldLeaveDormancy(); - virtual void DormantBegin(); - virtual void DormantEnd(const int); - virtual idRenderModelInfo *GetRenderModelInfo(); - virtual const idRenderModelInfo *GetRenderModelInfo_2(); - virtual void GetScale(idVec3 *); - virtual void SetScale(const idVec3 *); - virtual void SetModelByName(const char *); - virtual void SetModel(idRenderModel *); - virtual const idMaterial *GetCustomMaterial(); - virtual void SetColor(const idVec4 *); - virtual void SetColor_2(const idColor *); - virtual void SetColor_3(const idVec3 *); - virtual void SetColor_4(float, float, float); - virtual void SetColor_5(float, float, float, float); - virtual void GetColor(idVec4 *); - virtual void GetColor_2(idColor *); - virtual void GetColor_3(idVec3 *); - virtual void Hide(bool); - virtual void Hide_2(); - virtual void Show(); - virtual void GetModelTransform(idVec3 *, idMat3 *); - virtual void GetSoundTransform(idVec3 *, idMat3 *); - virtual void UpdateModelTransform(); - virtual void UpdateFX(); - virtual void ProjectOverlay(const idVec3 *, const idVec3 *, float, const char *); - virtual idPresentable *AllocPresentable(idRenderModel *); - virtual const idComponentTimeLine *GetComponentTimeLine(); - virtual idComponentTimeLine *GetComponentTimeLine_2(); - virtual bool UpdateAnimationControllers(); - virtual void UpdateAttachments(); - virtual const idAnimStack *GetAnimStack(); - virtual idAnimStack *GetAnimStack_2(); - virtual idIndex *GetJointIndexFromTrace(idIndex *result, trace_t); - virtual awPathResult_t ChangeAnimWebState(const char *, const char *); - virtual awPathResult_t ChangeAnimWebState_2(const char *); - virtual awPathResult_t ForceAnimWebState(const char *); - virtual awPathResult_t ChangeAnimWebStateVia(const char *, const char *, const char *, const char *); - virtual awPathResult_t ChangeAnimWebStateVia_2(const char *, const char *); - virtual idAnimWebCmdCtx *GetAnimWebCmdCtx(); - virtual const idAnimWebCmdCtx *GetAnimWebCmdCtx_2(); - virtual const idAnimator_AF *GetAF(); - virtual idAnimator_AF *GetAF_2(); - virtual void PreBind(); - virtual void PostBind(); - virtual void PreUnbind(); - virtual void PostUnbind(); - virtual const splineLocation_t *GetSplineLocation(); - virtual void SetAxis(const idMat3 *); - virtual bool CanDisablePhysics(const idEntity *); - virtual collide_t Collide(const int, trace_t *, const idVec3 *); - virtual collide_t Contact(const int, contactInfo_t *); - virtual void ApplyImpulse(const int, const int, const idVec3 *, const idVec3 *); - virtual void ApplyImpulseFromEntity(const idEntity *, const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const int, const idVec3 *, const idVec3 *); - virtual bool Crush(const int); - virtual void ApplyDamage(const int, const int, const idDeclDamage *); - virtual void ActivatePhysics(const int); - virtual void DeactivatePhysics(const int); - virtual void ApplyWaterEffects(const int, const int); - virtual void ApplyWaterSplashEffects(const int, const int, surfTypes_t, idPhysicsCallbacks::splashState_t); - virtual bool TakesDamage(); - virtual void DamageFeedback(idEntity *, idEntity *, const idDeclDamage *, float *); - virtual void KilledNotification(const idEntity *, const idEntity *, const idDeclDamage *, const float); - virtual float Damage(idEntity *, idEntity *, const idDeclDamage *, const float, const idVec3 *, trace_t *); - virtual bool CalcDamageImpulse(const idEntity *, const idEntity *, const idDeclDamage *, const float, const idVec3 *, const trace_t *, idVec3 *, idVec3 *); - virtual bool IsTargetLockable(const idDeclAmmo *); - virtual void AddProjectileLock(); - virtual void RemoveProjectileLock(); - virtual const idScriptObject *GetScriptObject(); - virtual idScriptObject *GetScriptObject_2(); - virtual bool ShouldConstructScriptObjectAtSpawn(); - virtual idThread *GetStateThread(); - virtual int AddThread(const idHandle); - virtual void RemoveThread(const idHandle); - virtual idHandle *GetThread(idHandle *result, const int); - virtual int NumThreads(); - virtual int MaxThreads(); - virtual void ExecuteThread(idThread *); - virtual void ResetFSMWaitThreadIfPossible(idThread *); - virtual bool HandleGuiEvent(const sysEvent_t *); - virtual void ActivateTargets(idEntity *); - virtual bool GetRcCarCanTarget(); - virtual const idBaseHealth *GetHealthComponent(); - virtual idBaseHealth *GetHealthComponent_2(); - virtual const idSmartLootComponent *GetSmartLootComponent(); - virtual idSmartLootComponent *GetSmartLootComponent_2(); - virtual void Teleport(const idVec3 *, const idAngles *); - virtual bool IsPusher(); - virtual const idList,5> *GetTriggerTouchList(); - virtual idList,5> *GetTriggerTouchList_2(); - virtual void TestFunctionality(); - virtual float GetUsableDistance(); - virtual float GetCrosshairIconDistance(); - virtual usableState_t GetUsableState(const idEntity *, const idFocusTrace *); - virtual bool ModifyCrosshairInfo(const idEntity *, const idFocusTrace *, const usableState_t, idCrosshairInfo *); - virtual bool IsCrosshairDisabled(const idEntity *, const idFocusTrace *, const usableState_t); - virtual bool IsCrosshairSubdued(const idEntity *, const idFocusTrace *, const usableState_t); - virtual bool IsEverUsable(const idEntity *); - virtual bool IsCurrentlyUsable(const idEntity *); - virtual bool Use(idEntity *, const usableState_t); - virtual void Dropped(idEntity *, const idDeclInventory *); - virtual const idInventoryCollection *GetInventory(); - virtual idInventoryCollection *GetInventory_2(); - virtual void InventoryAdded(idInventoryItem *, int); - virtual void InventoryRemoved(idInventoryItem *); - virtual const idAttachmentCollection *GetAttachments(); - virtual idAttachmentCollection *GetAttachments_2(); - virtual void EnableAIEventResponse(const idAIEvent::aiEventClass_t); - virtual void DisableAIEventResponse(const idAIEvent::aiEventClass_t); - virtual bool CanReceiveAIEvents(const int); - virtual bool RespondsToAIEvent(const idAIEvent *); - virtual void OnAIEvent(const idAIEvent *); - virtual bool IsDead(); - virtual bool IsDying(); - virtual idFaction *GetFaction(); - virtual const idFaction *GetFaction_2(); - virtual idEntityAuditor *GetAuditor(); - virtual void GetVisibilityPoint(const visPoint_t, idVec3 *); - virtual void GetAimPoint(const aimPoint_t, idVec3 *); - virtual void GetEyePos(idVec3 *); - virtual bool IsVisible(); - virtual idDynamicCoverMgr *GetDynamicCoverMgr(); - virtual const idDynamicCoverMgr *GetDynamicCoverMgr_2(); - virtual const idAAS2 *GetAAS(); - virtual void GetViewStateFOV(idVec3 *, unsigned __int8 *, unsigned __int8 *); - virtual void GetViewStateFOV_2(idVec3 *, unsigned __int8 *, unsigned __int8 *); - virtual int GetNumRepairBotTetherPoints(); - virtual bool GetRepairBotTetherPoint(const int, const int, idVec3 *); - virtual idEntityInterface *CreateEntityInterface(idGame *); - virtual void ShowEditingDialog(); - virtual void UpdateEditingDialog(); - virtual void UpdateModifiedProperties(); - virtual inputSettings_t *GetInputSettings(inputSettings_t *result, idPlayer *); - virtual bool EvaluateControls(usercmd_t *, usercmd_t *); - virtual void CheckForErrors(idList *); - virtual void DebugDrawEntity(const idColor *, int); - virtual void ClientThink(); - virtual void Serialize(idSerializer *); - virtual void PostSerializeRead(bool); - virtual void OnActivate(idEntity *); - virtual void OnMakeActivatable(const bool); - virtual void OnNotifyProgressionOwner(); - - int numSegments; - idSegment **segments; + idMat3 current; + idMat3 center; + float limitAngleDot; + float halfLimitAngleCosine; + float halfLimitAngleSine; }; -// IDA Local Type ordinal 21649; PDB kind: class. -class idConeConstraint_Mat3 -{ -public: - idMat3 current; - idMat3 center; - float limitAngleDot; - float halfLimitAngleCosine; - float halfLimitAngleSine; -}; +// The fourth recovered logical type is a test entity derived from idEntity. +// Its full base-class boundary belongs to the later game-entity recovery, so +// retain the real owner name without inventing a substitute base class here. +class idTest_ConeConstraint; + +static_assert(sizeof(idConeConstraint_Quat) == 44, + "Recovered idConeConstraint_Quat ABI changed"); +static_assert(sizeof(idConeConstraint_Vec3) == 36, + "Recovered idConeConstraint_Vec3 ABI changed"); +static_assert(sizeof(idConeConstraint_Mat3) == 84, + "Recovered idConeConstraint_Mat3 ABI changed"); diff --git a/source/engine/gamelib/class.cpp b/source/engine/gamelib/class.cpp new file mode 100644 index 0000000..673823a --- /dev/null +++ b/source/engine/gamelib/class.cpp @@ -0,0 +1,216 @@ +#include "gamelib/class.h" + +#include "idlib/lib_print.h" +#include "idlib/text/str.h" + +namespace { + +idTypeInfo *typeList = nullptr; +idHierarchy classHierarchy; + +int NumberTypeHierarchy(idHierarchy *node, int nextType) { + for (idHierarchy *current = node; current != nullptr; + current = current->sibling) { + idTypeInfo *type = current->owner; + if (type == nullptr) { + continue; + } + type->typeNum = nextType++; + nextType = NumberTypeHierarchy(current->child, nextType); + type->lastChild = nextType - 1; + } + return nextType; +} + +} // namespace + +bool idClass::initialized = false; +int idClass::typeNumBits = 0; +int idClass::memused = 0; +int idClass::numobjects = 0; +idList idClass::types(16); +idList idClass::typenums(16); + +idClass::~idClass() = default; + +idTypeInfo::idTypeInfo(const char *classname_, const char *superclass_, + const int size_, const bool abstract_, + const createInstance_t createInstance_, const spawn_t spawn_) + : classname(classname_) + , superclass(superclass_) + , CreateInstance(createInstance_) + , Spawn(spawn_) + , initialized(false) + , super(idClass::GetClass(superclass_)) + , next(nullptr) + , isAbstract(abstract_) + , size(size_) + , typeNum(0) + , lastChild(0) + , node(nullptr) { + for (idTypeInfo *type = typeList; type != nullptr; type = type->next) { + if (type->super == nullptr && type->superclass != nullptr + && idStr::Cmp(type->superclass, classname) == 0 + && idStr::Cmp(type->classname, "idClass") != 0) { + type->super = this; + } + } + + idTypeInfo **link = &typeList; + while (*link != nullptr && idStr::Cmp(classname, (*link)->classname) >= 0) { + link = &(*link)->next; + } + next = *link; + *link = this; +} + +idTypeInfo::~idTypeInfo() { + node.RemoveFromHierarchy(); +} + +void idTypeInfo::Init() { + if (initialized) { + return; + } + if (super != nullptr && !super->initialized) { + super->Init(); + } + node.ParentTo(super != nullptr ? super->node : classHierarchy); + node.SetOwner(this); + for (idTypeInfo *ancestor = super; ancestor != nullptr; + ancestor = ancestor->super) { + ++ancestor->lastChild; + } + initialized = true; +} + +idClass::spawn_t idClass::CallSpawnFunc(idTypeInfo *type) { + if (type == nullptr) { + return nullptr; + } + const spawn_t inherited = type->super != nullptr + ? CallSpawnFunc(type->super) + : nullptr; + if (type->Spawn != nullptr && inherited != type->Spawn) { + type->Spawn(this); + return type->Spawn; + } + return inherited; +} + +void idClass::CallSpawn() { + idTypeInfo *type = GetType(); + if (type == nullptr) { + return; + } + const spawn_t inherited = type->super != nullptr + ? CallSpawnFunc(type->super) + : nullptr; + if (type->Spawn != nullptr && inherited != type->Spawn) { + type->Spawn(this); + } +} + +const char *idClass::GetClassname() const { + idTypeInfo *type = const_cast(this)->GetType(); + return type != nullptr ? type->classname : nullptr; +} + +bool idClass::IsType(const idTypeInfo& superclass) const { + idTypeInfo *type = const_cast(this)->GetType(); + return type != nullptr && type->typeNum >= superclass.typeNum + && type->typeNum <= superclass.lastChild; +} + +idTypeInfo *idClass::GetClass(const char *name) { + if (name == nullptr) { + return nullptr; + } + if (!initialized) { + for (idTypeInfo *type = typeList; type != nullptr; type = type->next) { + if (idStr::Cmp(type->classname, name) == 0) { + return type; + } + } + return nullptr; + } + + int lower = 0; + int upper = types.Num() - 1; + while (lower <= upper) { + const int middle = (lower + upper) / 2; + idTypeInfo *type = types[middle]; + const int comparison = idStr::Cmp(type->classname, name); + if (comparison == 0) { + return type; + } + if (comparison < 0) { + lower = middle + 1; + } else { + upper = middle - 1; + } + } + return nullptr; +} + +idClass *idClass::CreateInstance(const char *name) { + idTypeInfo *type = GetClass(name); + return type != nullptr && type->CreateInstance != nullptr + ? type->CreateInstance() + : nullptr; +} + +void idClass::DisplayInfo_f(const idCmdArgs&) { + idLibPrint::Printf("Class memory status: %i bytes allocated in %i objects\n", + memused, numobjects); +} + +void idClass::ListClasses_f(const idCmdArgs&) { + idLibPrint::Printf("%-24s %-24s %-6s %-6s\n", + "Classname", "Superclass", "Type", "Subclasses"); + idLibPrint::Printf( + "----------------------------------------------------------------------\n"); + for (int index = 0; index < types.Num(); ++index) { + const idTypeInfo *type = types[index]; + idLibPrint::Printf("%-24s %-24s %6d %6d\n", type->classname, + type->superclass, type->typeNum, type->lastChild - type->typeNum); + } + idLibPrint::Printf("...%d classes", types.Num()); +} + +void idClass::Init() { + idLibPrint::Printf("Initializing class hierarchy\n"); + if (initialized) { + idLibPrint::Printf("...already initialized\n"); + return; + } + + for (idTypeInfo *type = typeList; type != nullptr; type = type->next) { + type->Init(); + } + const int count = NumberTypeHierarchy(classHierarchy.child, 0); + typeNumBits = 0; + while ((1u << typeNumBits) < static_cast(count) + && typeNumBits < 31) { + ++typeNumBits; + } + + types.SetNum(count); + typenums.SetNum(count); + int sortedIndex = 0; + for (idTypeInfo *type = typeList; type != nullptr; type = type->next) { + if (sortedIndex < count) { + types[sortedIndex++] = type; + } + if (type->typeNum >= 0 && type->typeNum < count) { + typenums[type->typeNum] = type; + } + } + initialized = true; +} + +void idClass::Shutdown() { + types.ClearFree(); + typenums.ClearFree(); + initialized = false; +} diff --git a/source/engine/gamelib/class.h b/source/engine/gamelib/class.h index 97e6a8e..50f3eca 100644 --- a/source/engine/gamelib/class.h +++ b/source/engine/gamelib/class.h @@ -1,46 +1,113 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\class.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/containers/hierarchy.h" +#include "idlib/containers/list.h" +class idCmdArgs; +class idClass; -// IDA Local Type ordinal 9440; PDB kind: unknown. -struct SUB_CLASS:: -{ - unsigned __int32 SUB_CLASS_INF : 8; -}; - -// IDA Local Type ordinal 9441; PDB kind: union. -union SUB_CLASS -{ - SUB_CLASS:: bitfields; - SUB_CLASS:: bits; - unsigned int u32All; - int i32All; - float f32All; -}; - -// IDA Local Type ordinal 12079; PDB kind: struct. -struct XUIClass -{ - unsigned int cbSize; - const wchar_t *szClassName; - const wchar_t *szBaseClassName; - const wchar_t *szClassDetails; - XUIObjectMethods Methods; - const XUIElementPropDef *pPropDefs; - unsigned int dwPropDefCount; - int bExtensionClass; -}; - -// IDA Local Type ordinal 13472; PDB kind: class. -class idClass -{ +// Local type 13473 is unmapped in the generated header corpus but is the +// metadata record used by every idClass registration function. +class idTypeInfo { public: - // Recovered virtual interface; IDA vtable ordinal 13475. - virtual idTypeInfo *GetType(); - virtual ~idClass(); + using createInstance_t = idClass* (*)(); + using spawn_t = void (*)(idClass*); + idTypeInfo(const char *classname, const char *superclass, int size, + bool abstract, createInstance_t createInstance, spawn_t spawn); + ~idTypeInfo(); + + void Init(); + + const char *classname; + const char *superclass; + createInstance_t CreateInstance; + spawn_t Spawn; + bool initialized; + idTypeInfo *super; + idTypeInfo *next; + bool isAbstract; + int size; + int typeNum; + int lastChild; + idHierarchy node; }; + +// PDB local type 13472 plus the methods recovered from engine/gamelib/class.cpp. +class idClass { +public: + virtual idTypeInfo *GetType() = 0; + virtual ~idClass(); + + const char *GetClassname() const; + bool IsType(const idTypeInfo& superclass) const; + void CallSpawn(); + + static idTypeInfo *GetClass(const char *name); + static idClass *CreateInstance(const char *name); + static void Init(); + static void Shutdown(); + static void DisplayInfo_f(const idCmdArgs& args); + static void ListClasses_f(const idCmdArgs& args); + + static int TypeNumBits() { return typeNumBits; } + static int NumTypes() { return types.Num(); } + static const idTypeInfo *TypeForNum(int index) { + return index >= 0 && index < typenums.Num() ? typenums[index] : nullptr; + } + + static int memused; + static int numobjects; + +private: + using spawn_t = idTypeInfo::spawn_t; + spawn_t CallSpawnFunc(idTypeInfo *type); + + static bool initialized; + static int typeNumBits; + static idList types; + static idList typenums; +}; + +// The generated class.h also carried these Xbox UI local types. Keep their +// recovered layouts valid without coupling GameLib to the Xbox UI runtime. +struct SUB_CLASS_bitfields { + unsigned int SUB_CLASS_INF : 8; +}; + +union SUB_CLASS { + SUB_CLASS_bitfields bitfields; + SUB_CLASS_bitfields bits; + unsigned int u32All; + int i32All; + float f32All; +}; + +struct _XUIOBJ; +struct XUIMessage; +struct XUIElementPropDef; +struct XUIObjectMethods { + long (__fastcall *ObjectProc)(_XUIOBJ *, XUIMessage *, void *); + long (__fastcall *CreateInstance)(_XUIOBJ *, void **); + long (__fastcall *DestroyInstance)(void *); +}; + +struct XUIClass { + unsigned int cbSize; + const wchar_t *szClassName; + const wchar_t *szBaseClassName; + const wchar_t *szClassDetails; + XUIObjectMethods Methods; + const XUIElementPropDef *pPropDefs; + unsigned int dwPropDefCount; + int bExtensionClass; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idTypeInfo) == 60, "Recovered idTypeInfo ABI changed"); +static_assert(sizeof(idClass) == 4, "Recovered idClass ABI changed"); +static_assert(sizeof(SUB_CLASS) == 4, "Recovered SUB_CLASS ABI changed"); +static_assert(sizeof(XUIObjectMethods) == 12, + "Recovered XUIObjectMethods ABI changed"); +static_assert(sizeof(XUIClass) == 40, "Recovered XUIClass ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicsboxfountain.cpp b/source/engine/gamelib/effectphysics/effectphysicsboxfountain.cpp new file mode 100644 index 0000000..79c7f0c --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicsboxfountain.cpp @@ -0,0 +1,54 @@ +#include "gamelib/effectphysics/effectphysicsboxfountain.h" + +#include "gamelib/physics/tracemodelcache.h" +#include "idlib/geometry/tracemodel.h" + +// Renderer ownership is an engine boundary outside GameLib. The renderer port +// provides this operation when idRenderModel is activated; keeping it external +// avoids fabricating a partial render-model layout here. +void GameLib_MarkRenderModelForDeletion(idRenderModelManyBoxes* model); + +idEffectPhysicsBoxFountain::idEffectPhysicsBoxFountain() + : broadPhase() + , properties() + , rigidBodies(0) + , manyBoxes(nullptr) + , lastFireTime(0) + , currentFireRigidBody(0) + , startOrigin() + , spawnOrigin() + , spawnObject() + , random() { + startOrigin.Zero(); + spawnOrigin.Zero(); +} + +idEffectPhysicsBoxFountain::~idEffectPhysicsBoxFountain() { + if (manyBoxes != nullptr) { + GameLib_MarkRenderModelForDeletion(manyBoxes); + } + spawnObject.overlap.Clear(); + rigidBodies.Clear(); +} + +void idEffectPhysicsBoxFountain::Init( + idClip* const clip, + idTraceModelCache* const traceModelCache, + const int entityNumber) { + broadPhase.Init(clip, traceModelCache); + + idBounds bounds; + bounds[0].Set(-16.0f, -16.0f, -16.0f); + bounds[1].Set(16.0f, 16.0f, 16.0f); + idTraceModel traceModel; + traceModel.SetupBox(bounds); + const int traceModelIndex = traceModelCache->AllocTraceModel( + traceModel, nullptr); + properties.Setup(&broadPhase, traceModelIndex, 10.0f); + + startOrigin.Set(0.0f, 0.0f, 64.0f); + spawnOrigin.Set(0.0f, 0.0f, 160.0f); + broadPhase.AddObject( + &spawnObject, spawnOrigin, idMat3(1.0f), &bounds); + spawnObject.entityNum = entityNumber; +} diff --git a/source/engine/gamelib/effectphysics/effectphysicsboxfountain.h b/source/engine/gamelib/effectphysics/effectphysicsboxfountain.h index bbd1241..5a8b14b 100644 --- a/source/engine/gamelib/effectphysics/effectphysicsboxfountain.h +++ b/source/engine/gamelib/effectphysics/effectphysicsboxfountain.h @@ -1,23 +1,36 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicsboxfountain.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effectphysics/effectphysicsbroadphase.h" +#include "gamelib/effectphysics/effectphysicsproperties.h" +#include "idlib/containers/list.h" +#include "idlib/math/random.h" +class idClip; +class idEffectPhysicsRigidBody; +class idRenderModelManyBoxes; +class idTraceModelCache; -// IDA Local Type ordinal 16038; PDB kind: class. -class __declspec(align(8)) idEffectPhysicsBoxFountain -{ +class alignas(8) idEffectPhysicsBoxFountain { public: - idEffectPhysicsBroadPhase broadPhase; - idEffectPhysicsProperties properties; - idList rigidBodies; - idRenderModelManyBoxes *manyBoxes; - int lastFireTime; - int currentFireRigidBody; - idVec3 startOrigin; - idVec3 spawnOrigin; - idSPObject spawnObject; - idRandom random; + idEffectPhysicsBoxFountain(); + ~idEffectPhysicsBoxFountain(); + + void Init(idClip* clip, idTraceModelCache* traceModelCache, + int entityNumber); + + idEffectPhysicsBroadPhase broadPhase; + idEffectPhysicsProperties properties; + idList rigidBodies; + idRenderModelManyBoxes* manyBoxes; + int lastFireTime; + int currentFireRigidBody; + idVec3 startOrigin; + idVec3 spawnOrigin; + idSPObject spawnObject; + idRandom random; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectPhysicsBoxFountain) == 456, + "Recovered idEffectPhysicsBoxFountain ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicsbreakable.cpp b/source/engine/gamelib/effectphysics/effectphysicsbreakable.cpp new file mode 100644 index 0000000..78ce072 --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicsbreakable.cpp @@ -0,0 +1,695 @@ +#include "gamelib/effectphysics/effectphysicsbreakable.h" + +#include "gamelib/physics/physics.h" +#include "gamelib/physics/tracemodelcache.h" +#include "idlib/geometry/tracemodel.h" + +#include +#include + +int GameLib_DiscreteAnimationPieceCount( + const idRenderModelDiscreteAnimation* model); +const idDeclBreakable* GameLib_DiscreteAnimationDecl( + const idRenderModelDiscreteAnimation* model); +bool GameLib_DiscreteAnimationGetPiece( + const idRenderModelDiscreteAnimation* model, int index, + idVec3& position, idMat3& orientation, idTraceModel& traceModel, + const idMaterial*& material); +void GameLib_DiscreteAnimationSetPiece(idRenderModelDiscreteAnimation* model, + int index, const idVec3& position, const idMat3& orientation); +void GameLib_DiscreteAnimationCollapsePiece( + idRenderModelDiscreteAnimation* model, int index); +void GameLib_DiscreteAnimationReset(idRenderModelDiscreteAnimation* model); +void GameLib_DiscreteAnimationUpdate(idRenderModelDiscreteAnimation* model); +void GameLib_DiscreteAnimationDelete(idRenderModelDiscreteAnimation* model); +bool GameLib_GetBreakablePhysicsSettings(const idDeclBreakable* decl, + idEffectPhysicsPieceSettings& settings); +bool GameLib_GetBreakableDeclView(const idDeclBreakable* decl, + idEffectPhysicsBreakableDeclView& view); +float GameLib_GetBreakablePieceDamageThreshold( + const idDeclBreakable* decl, int pieceIndex); +int GameLib_StartBreakableExplosion(idGameLibEffects* effects, + const idBreakableExplosion& explosion, const idVec3& position, + const idMat3& axis, int startTime, float effectScale); +void GameLib_StopBreakableExplosion(idGameLibEffects* effects, int handle); +void GameLib_EmitBreakableParticle(idGameLibEffects* effects, + const idDeclParticle* particle, const idVec3& position, + const idVec3& velocity, int startTime); +void GameLib_EmitBreakableTrail(idGameLibEffects* effects, + const idBreakableTrail& trail, int pieceIndex, const idVec3& position, + const idVec3& velocity, int currentTime); +void GameLib_EmitBreakableDecal(idGameLibEffects* effects, + const idBreakableDecal& decal, int pieceIndex, + const idEffectPhysicsCollision& collision, int currentTime); +void GameLib_EmitBreakableSpark(idGameLibEffects* effects, + const idMaterial* material, float size, const idVec3& position, + const idVec3& velocity, int currentTime); +void GameLib_PlayBreakableCollision( + idEffectPhysicsBreakable::breakablePieceSound_t& soundInfo, + const idEffectPhysicsCollision& collision, const idVec3& origin, + float velocitySqr, int currentTime); +void GameLib_DrawEffectTraceModel(const idTraceModelCache* cache, + int traceModelIndex, int pieceIndex, const idVec3& origin, + const idMat3& axis); + +namespace { + +float Clamp01(const float value) { + return (std::max)(0.0f, (std::min)(1.0f, value)); +} + +} // namespace + +idEffectPhysicsBreakable::idEffectPhysicsBreakable() + : properties(0) + , rigidBodies(0) + , collisions(0) + , basePiece(nullptr) + , explosions(0) + , explosionFX(0) + , trails(0) + , damage(0) + , particles(0) + , decals(0) + , sparks(0) + , pieceActivationTimes(0) + , broadPhase() + , random(0) + , gameLibEffects(nullptr) + , model(nullptr) + , decl(nullptr) + , soundInfo{nullptr, nullptr, 0, nullptr, 0, 0.0f} { +} + +idEffectPhysicsBreakable::~idEffectPhysicsBreakable() { + RemoveExplosions(); + if (broadPhase.traceModelCache != nullptr) { + for (int index = 0; index < properties.Num(); ++index) { + if (properties[index] != nullptr) { + broadPhase.traceModelCache->FreeTraceModel( + properties[index]->traceModelIndex); + } + } + } + for (int index = 0; index < rigidBodies.Num(); ++index) { + delete rigidBodies[index]; + } + for (int index = 0; index < properties.Num(); ++index) { + delete properties[index]; + } + rigidBodies.ClearFree(); + properties.ClearFree(); + if (model != nullptr) { + GameLib_DiscreteAnimationDelete(model); + model = nullptr; + } +} + +idEffectPhysicsProperties* +idEffectPhysicsBreakable::GetPropertiesForTraceModel( + const idTraceModel& traceModel, const idMaterial* const material) { + if (broadPhase.traceModelCache == nullptr) { + return nullptr; + } + const int traceModelIndex = broadPhase.traceModelCache->AllocTraceModel( + traceModel, material); + for (int index = 0; index < properties.Num(); ++index) { + if (properties[index]->traceModelIndex == traceModelIndex) { + broadPhase.traceModelCache->FreeTraceModel(traceModelIndex); + return properties[index]; + } + } + idEffectPhysicsProperties* const result = + new idEffectPhysicsProperties(); + result->Setup(&broadPhase, traceModelIndex, 10.0f); + idEffectPhysicsPieceSettings settings{}; + if (decl != nullptr && + GameLib_GetBreakablePhysicsSettings(decl, settings)) { + result->linearFriction = settings.linearFriction; + result->angularFriction = settings.angularFriction; + result->contactFriction = settings.contactFriction; + result->linearFrictionWater = settings.linearFrictionWater; + result->angularFrictionWater = settings.angularFrictionWater; + result->bouncyness = settings.bouncyness; + result->gravityVector = settings.gravityVector; + result->worldCollisionOnly = settings.worldCollisionOnly; + result->simplePointCollision = settings.simplePointCollision; + result->crazyBounceChance = settings.crazyBounceChance; + result->stopSpeed = settings.stopSpeed; + result->maxLinearVelocity = settings.maxLinearVelocity; + result->maxAngularVelocity = settings.maxAngularVelocity; + result->clipMask = settings.clipMask; + } + properties.Append(result); + return result; +} + +void idEffectPhysicsBreakable::Init(idClip* const clip, + idTraceModelCache* const traceModelCache, + idGameLibEffects* const effects, + idRenderModelDiscreteAnimation* const renderModel, + const int entityNumber, const breakablePieceSound_t* const sound) { + broadPhase.Init(clip, traceModelCache); + gameLibEffects = effects; + model = renderModel; + decl = renderModel != nullptr + ? GameLib_DiscreteAnimationDecl(renderModel) : nullptr; + if (sound != nullptr) { + soundInfo = *sound; + } + + for (int index = 0; index < rigidBodies.Num(); ++index) { + delete rigidBodies[index]; + } + rigidBodies.Clear(); + collisions.Clear(); + const int pieceCount = renderModel != nullptr + ? GameLib_DiscreteAnimationPieceCount(renderModel) : 0; + rigidBodies.SetNum((std::max)(0, pieceCount)); + collisions.SetNum(rigidBodies.Num()); + damage.SetNum(rigidBodies.Num()); + pieceActivationTimes.SetNum(rigidBodies.Num()); + + for (int index = 0; index < rigidBodies.Num(); ++index) { + rigidBodies[index] = nullptr; + collisions[index].Clear(); + damage[index] = 0.0f; + pieceActivationTimes[index] = 0; + idVec3 position; + idMat3 orientation(1.0f); + idTraceModel traceModel; + const idMaterial* material = nullptr; + if (!GameLib_DiscreteAnimationGetPiece(renderModel, index, + position, orientation, traceModel, material)) { + continue; + } + traceModel.Shrink(0.1875f); + idEffectPhysicsProperties* const pieceProperties = + GetPropertiesForTraceModel(traceModel, material); + if (pieceProperties == nullptr) { + continue; + } + rigidBodies[index] = new idEffectPhysicsRigidBody(); + rigidBodies[index]->Setup(pieceProperties); + rigidBodies[index]->SetPosition(position, orientation); + rigidBodies[index]->SPObject.entityNum = entityNumber; + } + InitExplosionsAndTrails(sound, effects, random.RandomFloat()); +} + +void idEffectPhysicsBreakable::InitExplosionsAndTrails( + const breakablePieceSound_t* const sound, idGameLibEffects* const effects, + const float diversity) { + (void)diversity; + gameLibEffects = effects; + if (sound != nullptr) { + soundInfo = *sound; + } + explosions.Clear(); + explosionFX.Clear(); + trails.Clear(); + decals.Clear(); + sparks.Clear(); + + idEffectPhysicsBreakableDeclView view; + if (decl == nullptr || !GameLib_GetBreakableDeclView(decl, view)) { + return; + } + explosions.SetNum(view.explosions.Num()); + explosionFX.SetNum(view.explosions.Num()); + for (int index = 0; index < explosions.Num(); ++index) { + explosions[index].decl = view.explosions[index]; + explosions[index].startTime = 0; + explosions[index].fxIndex = -1; + explosions[index].worldPosition.Zero(); + explosions[index].effectsEnabled = false; + explosions[index].forceApplied = false; + explosions[index].localSpace = true; + explosionFX[index] = -1; + } + trails.SetNum(view.trails.Num()); + for (int index = 0; index < trails.Num(); ++index) { + trails[index].decl = view.trails[index]; + const int count = trails[index].decl.pieces.Num(); + trails[index].pieceStartTime.SetNum(count); + trails[index].pieceLastParticleDropPos.SetNum(count); + for (int piece = 0; piece < count; ++piece) { + trails[index].pieceStartTime[piece] = 0; + trails[index].pieceLastParticleDropPos[piece].Zero(); + } + } + decals.SetNum(view.decals.Num()); + for (int index = 0; index < decals.Num(); ++index) { + decals[index].decl = view.decals[index]; + const int count = decals[index].decl.pieces.Num(); + decals[index].pieceStartTime.SetNum(count); + decals[index].lastCollisionPos.SetNum(count); + decals[index].lastCollisionNormal.SetNum(count); + decals[index].pieceLastDecalDropPos.SetNum(count); + for (int piece = 0; piece < count; ++piece) { + decals[index].pieceStartTime[piece] = 0; + decals[index].lastCollisionPos[piece].Zero(); + decals[index].lastCollisionNormal[piece].Zero(); + decals[index].pieceLastDecalDropPos[piece].Zero(); + } + } + if (!view.sparks.materials.IsEmpty()) { + sparks.SetNum(rigidBodies.Num()); + for (int index = 0; index < sparks.Num(); ++index) { + sparks[index].material = view.sparks.materials[ + random.RandomInt(view.sparks.materials.Num())]; + sparks[index].size = view.sparks.sizeMin + random.RandomFloat() * + (view.sparks.sizeMax - view.sparks.sizeMin); + sparks[index].velocityScale = view.sparks.velocityScaleMin + + random.RandomFloat() * (view.sparks.velocityScaleMax - + view.sparks.velocityScaleMin); + } + } +} + +void idEffectPhysicsBreakable::Reset(const bool skipModelReset) { + if (model == nullptr) { + return; + } + if (!skipModelReset) { + GameLib_DiscreteAnimationReset(model); + } + random.SetSeed(0); + for (int index = 0; index < rigidBodies.Num(); ++index) { + idEffectPhysicsRigidBody* const body = rigidBodies[index]; + if (body == nullptr) { + continue; + } + idVec3 position; + idMat3 orientation(1.0f); + idTraceModel ignoredTraceModel; + const idMaterial* ignoredMaterial = nullptr; + if (GameLib_DiscreteAnimationGetPiece(model, index, position, + orientation, ignoredTraceModel, ignoredMaterial)) { + body->SetPosition(position, orientation); + } + body->active = false; + body->settled = false; + body->SPObject.motion.ignore = false; + body->currentState.linearMomentum.Zero(); + body->currentState.angularMomentum.Zero(); + collisions[index].Clear(); + damage[index] = 0.0f; + pieceActivationTimes[index] = 0; + } + for (int index = 0; index < properties.Num(); ++index) { + idEffectPhysicsPieceSettings settings{}; + if (decl != nullptr && + GameLib_GetBreakablePhysicsSettings(decl, settings)) { + properties[index]->linearFriction = settings.linearFriction; + properties[index]->angularFriction = settings.angularFriction; + properties[index]->contactFriction = settings.contactFriction; + properties[index]->linearFrictionWater = + settings.linearFrictionWater; + properties[index]->angularFrictionWater = + settings.angularFrictionWater; + properties[index]->bouncyness = settings.bouncyness; + properties[index]->gravityVector = settings.gravityVector; + properties[index]->worldCollisionOnly = + settings.worldCollisionOnly; + properties[index]->clipMask = settings.clipMask; + } + } + RemoveExplosions(); + InitExplosionsAndTrails(&soundInfo, gameLibEffects, + random.RandomFloat()); +} + +bool idEffectPhysicsBreakable::ActivateNoisy(const int pieceIndex, + const idVec3& origin, const idMat3& axis) { + if (pieceIndex < 0 || pieceIndex >= rigidBodies.Num() || + rigidBodies[pieceIndex] == nullptr) { + return false; + } + idEffectPhysicsRigidBody* const body = rigidBodies[pieceIndex]; + if (body->active) { + return false; + } + body->SetPosition(origin + axis * body->currentState.position, + body->currentState.orientation * axis); + body->active = true; + body->settled = false; + body->SPObject.motion.ignore = false; + return true; +} + +bool idEffectPhysicsBreakable::ActivateAllNow(const idVec3& origin, + const idMat3& axis) { + bool activated = false; + for (int index = 0; index < rigidBodies.Num(); ++index) { + activated = ActivateNoisy(index, origin, axis) || activated; + } + return activated; +} + +bool idEffectPhysicsBreakable::ActivateFloatingPieces(const idVec3& origin, + const idMat3& axis, const float impulse) { + bool activated = false; + for (int index = 0; index < rigidBodies.Num(); ++index) { + idEffectPhysicsRigidBody* const body = rigidBodies[index]; + if (body == nullptr || body->active || + body->currentState.position.z <= 0.0f) { + continue; + } + if (ActivateNoisy(index, origin, axis)) { + const idVec3 direction(0.0f, 0.0f, 1.0f); + body->ApplyImpulse(body->currentState.position, + direction * impulse); + activated = true; + } + } + return activated; +} + +void idEffectPhysicsBreakable::ApplyImpulseToBody(const int bodyIndex, + const idPhysics* const impactPhysics, const idVec3& point, + const idVec3& impulse) { + if (bodyIndex < 0 || bodyIndex >= rigidBodies.Num() || + rigidBodies[bodyIndex] == nullptr) { + return; + } + idVec3 adjustedImpulse = impulse; + if (impactPhysics != nullptr) { + impactInfo_t impact{}; + const_cast(impactPhysics)->GetImpactInfo( + 0, &point, &impact); + adjustedImpulse = adjustedImpulse + impact.velocity * + (1.0f / (impact.invMass > 0.0f ? impact.invMass : 1.0f)); + } + rigidBodies[bodyIndex]->ApplyImpulse(point, adjustedImpulse); + rigidBodies[bodyIndex]->active = true; +} + +void idEffectPhysicsBreakable::ApplyForce(const idVec3& origin, + const idMat3& axis, const idVec3& force) { + (void)origin; + (void)axis; + for (int index = 0; index < rigidBodies.Num(); ++index) { + if (rigidBodies[index] != nullptr && rigidBodies[index]->active) { + rigidBodies[index]->AddForce(force); + } + } +} + +void idEffectPhysicsBreakable::AddRadiusImpact(const int time, + const idPhysics* const impactPhysics, const idVec3& position, + const float radius, const float impulse) { + const float radiusSqr = radius * radius; + for (int index = 0; index < rigidBodies.Num(); ++index) { + idEffectPhysicsRigidBody* const body = rigidBodies[index]; + if (body == nullptr) { + continue; + } + idVec3 delta = body->currentState.position - position; + const float distanceSqr = delta.LengthSqr(); + if (distanceSqr > radiusSqr) { + continue; + } + if (!body->active) { + body->active = true; + pieceActivationTimes[index] = time; + } + const float distance = delta.NormalizeFast(); + const float falloff = radius > 0.0f + ? Clamp01(1.0f - distance / radius) : 1.0f; + ApplyImpulseToBody(index, impactPhysics, body->currentState.position, + delta * (impulse * falloff)); + } +} + +void idEffectPhysicsBreakable::CollideWith(const idVec3& origin, + const idPhysics* const impactPhysics, const idVec3& point, + const float impulseScale) { + int nearest = -1; + float nearestDistance = 1.0e30f; + for (int index = 0; index < rigidBodies.Num(); ++index) { + if (rigidBodies[index] == nullptr) { + continue; + } + const float distance = + (rigidBodies[index]->currentState.position + origin - point) + .LengthSqr(); + if (distance < nearestDistance) { + nearestDistance = distance; + nearest = index; + } + } + if (nearest >= 0) { + idVec3 direction = + rigidBodies[nearest]->currentState.position + origin - point; + if (direction.NormalizeFast() == 0.0f) { + direction.Set(0.0f, 0.0f, 1.0f); + } + ApplyImpulseToBody(nearest, impactPhysics, point, + direction * impulseScale); + } +} + +bool idEffectPhysicsBreakable::DamagePiece(const int time, + const int pieceIndex, const float amount, const idVec3& position) { + (void)position; + if (pieceIndex < 0 || pieceIndex >= damage.Num()) { + return false; + } + damage[pieceIndex] += amount; + const float threshold = + GameLib_GetBreakablePieceDamageThreshold(decl, pieceIndex); + if (damage[pieceIndex] < (threshold > 0.0f ? threshold : 1.0f)) { + return false; + } + if (rigidBodies[pieceIndex] != nullptr) { + rigidBodies[pieceIndex]->active = true; + rigidBodies[pieceIndex]->settled = false; + pieceActivationTimes[pieceIndex] = time; + } + return true; +} + +void idEffectPhysicsBreakable::AddRayImpact(const int time, + const idVec3& origin, const idMat3& axis, const idVec3& start, + const idVec3& end, const bool activeOnly) { + const idVec3 ray = end - start; + const float rayLengthSqr = ray.LengthSqr(); + if (rayLengthSqr <= 1.0e-8f) { + return; + } + for (int index = 0; index < rigidBodies.Num(); ++index) { + idEffectPhysicsRigidBody* const body = rigidBodies[index]; + if (body == nullptr || (activeOnly && !body->active)) { + continue; + } + const idVec3 piecePosition = body->active + ? body->currentState.position + : origin + axis * body->currentState.position; + const float fraction = Clamp01( + (piecePosition - start).Dot(ray) / rayLengthSqr); + const idVec3 closest = start + ray * fraction; + if ((piecePosition - closest).LengthSqr() > 64.0f) { + continue; + } + if (!body->active) { + ActivateNoisy(index, origin, axis); + } + DamagePiece(time, index, 1.0f, closest); + idVec3 impulseDirection = ray; + impulseDirection.NormalizeFast(); + body->ApplyImpulse(closest, + (impulseDirection + idVec3(0.0f, 0.0f, 0.1f)) * 10.0f); + break; + } +} + +void idEffectPhysicsBreakable::EmitSmokeForEachPiece( + const idDeclParticle* const particle, const int time) { + for (int index = 0; index < rigidBodies.Num(); ++index) { + if (rigidBodies[index] == nullptr) { + continue; + } + particles.Append(idBreakableParticle{ + particle, rigidBodies[index]->currentState.position, time}); + GameLib_EmitBreakableParticle(gameLibEffects, particle, + rigidBodies[index]->currentState.position, + idVec3(0.0f, 0.0f, 0.0f), time); + } +} + +void idEffectPhysicsBreakable::AddExplosion( + const idDeclParticle* const particle, const int time, + const idVec3& position, const float radius, const float impulse, + const float falloff, const float angular, const int delay, + const int duration, const bool localSpace, const int activateAllDelay, + const float distanceDelayScale, const idVec3& endLinear, + const float effectScale) { + idExplosion explosion{}; + explosion.decl.particle = particle; + explosion.decl.position = position; + explosion.decl.radius = radius; + explosion.decl.impulse = impulse; + explosion.decl.falloff = falloff; + explosion.decl.angular = angular; + explosion.decl.delay = delay; + explosion.decl.duration = duration; + explosion.decl.activateAllDelay = activateAllDelay; + explosion.decl.distTimeScaleDelay = distanceDelayScale; + explosion.decl.endLinear = endLinear; + explosion.startTime = time + delay; + explosion.fxIndex = -1; + explosion.worldPosition = position; + explosion.effectsEnabled = effectScale > 0.0f; + explosion.forceApplied = false; + explosion.localSpace = localSpace; + explosions.Append(explosion); + explosionFX.Append(-1); +} + +void idEffectPhysicsBreakable::StartExplosions(const idVec3& origin, + const idMat3& axis, const int time) { + for (int index = 0; index < explosions.Num(); ++index) { + idExplosion& explosion = explosions[index]; + explosion.startTime = time + explosion.decl.delay; + explosion.worldPosition = explosion.localSpace + ? origin + axis * explosion.decl.position + : explosion.decl.position; + explosion.forceApplied = false; + explosion.effectsEnabled = true; + if (index < explosionFX.Num()) { + explosionFX[index] = GameLib_StartBreakableExplosion( + gameLibEffects, explosion.decl, explosion.worldPosition, + axis, explosion.startTime, 1.0f); + explosion.fxIndex = explosionFX[index]; + } + } +} + +void idEffectPhysicsBreakable::StopExplosionFX(const int index) { + if (index < 0 || index >= explosionFX.Num()) { + return; + } + if (explosionFX[index] >= 0) { + GameLib_StopBreakableExplosion(gameLibEffects, explosionFX[index]); + explosionFX[index] = -1; + } + if (index < explosions.Num()) { + explosions[index].fxIndex = -1; + explosions[index].effectsEnabled = false; + } +} + +void idEffectPhysicsBreakable::RemoveExplosions() { + for (int index = 0; index < explosionFX.Num(); ++index) { + StopExplosionFX(index); + } + explosions.Clear(); + explosionFX.Clear(); +} + +void idEffectPhysicsBreakable::UpdateSimulation(const idVec3& origin, + const idMat3& axis, const int currentTime, const int gameMsPerFrame, + const int frameNumber, const float lodScale, const float effectScale) { + (void)frameNumber; + (void)lodScale; + const float deltaTime = (std::max)(0, gameMsPerFrame) * 0.001f; + + for (int index = 0; index < explosions.Num(); ++index) { + idExplosion& explosion = explosions[index]; + if (currentTime < explosion.startTime) { + continue; + } + if (!explosion.forceApplied) { + AddRadiusImpact(currentTime, nullptr, explosion.worldPosition, + explosion.decl.radius, + explosion.decl.impulse * effectScale); + explosion.forceApplied = true; + } + if (explosion.decl.duration > 0 && + currentTime >= explosion.startTime + explosion.decl.duration) { + StopExplosionFX(index); + } + } + + for (int index = 0; index < rigidBodies.Num(); ++index) { + idEffectPhysicsRigidBody* const body = rigidBodies[index]; + if (body == nullptr || !body->active) { + continue; + } + body->Update(deltaTime, 0.0f, collisions[index]); + const idVec3 velocity = body->currentState.linearMomentum * + body->properties->inverseMass; + if (collisions[index].collisionNormalVelocity.LengthSqr() > 0.0f) { + GameLib_PlayBreakableCollision(soundInfo, collisions[index], + body->currentState.position, velocity.LengthSqr(), currentTime); + } + for (int trailIndex = 0; trailIndex < trails.Num(); ++trailIndex) { + const idBreakableTrail& trail = trails[trailIndex].decl; + if (velocity.LengthSqr() >= + trail.minTrailVelocity * trail.minTrailVelocity) { + GameLib_EmitBreakableTrail(gameLibEffects, trail, index, + body->currentState.position, velocity, currentTime); + } + } + for (int decalIndex = 0; decalIndex < decals.Num(); ++decalIndex) { + if (collisions[index].collisionNormalVelocity.LengthSqr() >= + decals[decalIndex].decl.minBounceVelocity * + decals[decalIndex].decl.minBounceVelocity) { + GameLib_EmitBreakableDecal(gameLibEffects, + decals[decalIndex].decl, index, collisions[index], + currentTime); + } + } + if (index < sparks.Num() && + collisions[index].collisionNormalVelocity.LengthSqr() > 0.0f) { + GameLib_EmitBreakableSpark(gameLibEffects, + sparks[index].material, sparks[index].size, + collisions[index].collisionPoint, + velocity * sparks[index].velocityScale, currentTime); + } + } + broadPhase.SubmitQueries(false); + UpdateModel(origin, axis, currentTime); +} + +void idEffectPhysicsBreakable::UpdateModel(const idVec3& origin, + const idMat3& axis, const int currentTime) { + (void)origin; + (void)axis; + (void)currentTime; + if (model == nullptr) { + return; + } + for (int index = 0; index < rigidBodies.Num(); ++index) { + if (rigidBodies[index] != nullptr && rigidBodies[index]->active) { + GameLib_DiscreteAnimationSetPiece(model, index, + rigidBodies[index]->currentState.position, + rigidBodies[index]->currentState.orientation); + } else { + GameLib_DiscreteAnimationCollapsePiece(model, index); + } + } + GameLib_DiscreteAnimationUpdate(model); +} + +void idEffectPhysicsBreakable::DrawCollisionModels(const int single, + const idVec3& offset, const idMat3& axis) { + for (int index = 0; index < rigidBodies.Num(); ++index) { + if (single >= 0 && single != index) { + continue; + } + idEffectPhysicsRigidBody* const body = rigidBodies[index]; + if (body == nullptr || body->properties == nullptr) { + continue; + } + const idVec3 drawOrigin = body->active + ? body->currentState.position + : offset + axis * body->currentState.position; + const idMat3 drawAxis = body->active + ? body->currentState.orientation + : body->currentState.orientation * axis; + GameLib_DrawEffectTraceModel(broadPhase.traceModelCache, + body->properties->traceModelIndex, index, drawOrigin, drawAxis); + } +} diff --git a/source/engine/gamelib/effectphysics/effectphysicsbreakable.h b/source/engine/gamelib/effectphysics/effectphysicsbreakable.h index 607b383..855ecb1 100644 --- a/source/engine/gamelib/effectphysics/effectphysicsbreakable.h +++ b/source/engine/gamelib/effectphysics/effectphysicsbreakable.h @@ -1,93 +1,247 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicsbreakable.h -// Recovered logical types: 7 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effectphysics/effectphysicspieceemitter.h" +class idClip; +class idCollisionModel; +class idDeclFX; +class idDeclParticle; +class idGameLibEffects; +class idMaterial; +class idPhysics; +class idRenderModelDiscreteAnimation; +class idSoundEmitter; +class idSoundWorld; +class idTraceModel; +class idTraceModelCache; -// IDA Local Type ordinal 15408; PDB kind: class. -class __declspec(align(2)) idEffectPhysicsBreakable::idExplosion -{ +// Data records copied verbatim from the recovered declbreakable layout. The +// declaration system owns parsing; GameLib owns the runtime copies below. +struct idBreakableExplosion { + const idDeclFX* fx; + const idDeclParticle* particle; + idVec3 position; + float radius; + float impulse; + float falloff; + float angular; + int delay; + int duration; + int activateAllDelay; + float distTimeScaleDelay; + bool useLinear; + idVec3 endLinear; + idList pieces; + + idBreakableExplosion() : fx(nullptr), particle(nullptr), position(), + radius(0.0f), impulse(0.0f), falloff(0.0f), angular(0.0f), delay(0), + duration(0), activateAllDelay(0), distTimeScaleDelay(0.0f), + useLinear(false), endLinear(), pieces(0) {} +}; + +struct idBreakableTrail { + const idDeclParticle* particleTrail; + const idDeclParticle* particleBounce; + const idDeclParticle* particleDie; + const idMaterial* glowQuadMaterial; + float glowQuadSize; + float minTrailVelocity; + float minBounceVelocity; + float trailSpacing; + float trailVelocityScale; + int delay; + int duration; + int fadeInDuration; + int fadeOutDuration; + idList pieces; + + idBreakableTrail() : particleTrail(nullptr), particleBounce(nullptr), + particleDie(nullptr), glowQuadMaterial(nullptr), glowQuadSize(0.0f), + minTrailVelocity(0.0f), minBounceVelocity(0.0f), trailSpacing(0.0f), + trailVelocityScale(0.0f), delay(0), duration(0), fadeInDuration(0), + fadeOutDuration(0), pieces(0) {} +}; + +struct idBreakableDecal { + idList materials; + idVec2 minSize; + idVec2 maxSize; + int lifetime; + int fadeOutStartTime; + int fadeInEndTime; + float minBounceVelocity; + float decalSpacing; + float depth; + idList pieces; + bool splatterOnBounce; + bool splatterOnRest; + + idBreakableDecal() : materials(0), minSize(), maxSize(), lifetime(0), + fadeOutStartTime(0), fadeInEndTime(0), minBounceVelocity(0.0f), + decalSpacing(0.0f), depth(0.0f), pieces(0), + splatterOnBounce(false), splatterOnRest(false) {} +}; + +struct idBreakableSpark { + idList materials; + float sizeMin; + float sizeMax; + float velocityScaleMin; + float velocityScaleMax; + + idBreakableSpark() : materials(0), sizeMin(0.0f), sizeMax(0.0f), + velocityScaleMin(0.0f), velocityScaleMax(0.0f) {} +}; + +struct idEffectPhysicsBreakableDeclView { + int numPieces; + idEffectPhysicsPieceSettings settings; + float maxSimulationTime; + float dampeningDecay; + idList explosions; + idList trails; + idList decals; + idBreakableSpark sparks; + + idEffectPhysicsBreakableDeclView() : numPieces(0), settings{}, + maxSimulationTime(0.0f), dampeningDecay(0.0f), explosions(0), + trails(0), decals(0), sparks() {} +}; + +class idEffectPhysicsBreakable { public: - idBreakableExplosion decl; - int startTime; - int fxIndex; - idVec3 worldPosition; - bool effectsEnabled; - bool forceApplied; - bool localSpace; + struct idExplosion { + idBreakableExplosion decl; + int startTime; + int fxIndex; + idVec3 worldPosition; + bool effectsEnabled; + bool forceApplied; + bool localSpace; + }; + + struct idTrail { + idBreakableTrail decl; + idList pieceStartTime; + idList pieceLastParticleDropPos; + + idTrail() : decl(), pieceStartTime(0), pieceLastParticleDropPos(0) {} + }; + + struct idBreakableParticle { + const idDeclParticle* particle; + idVec3 pos; + int startTime; + }; + + struct idDecalFX { + idBreakableDecal decl; + idList pieceStartTime; + idList lastCollisionPos; + idList lastCollisionNormal; + idList pieceLastDecalDropPos; + + idDecalFX() : decl(), pieceStartTime(0), lastCollisionPos(0), + lastCollisionNormal(0), pieceLastDecalDropPos(0) {} + }; + + struct idSpark { + const idMaterial* material; + float velocityScale; + float size; + }; + + struct breakablePieceSound_t { + idSoundWorld* soundWorld; + idSoundEmitter* emitter; + int channel; + const idDeclImpactSound* impactTable; + int nextSoundTime; + float minBounceVelSqr; + }; + + idEffectPhysicsBreakable(); + ~idEffectPhysicsBreakable(); + + void Init(idClip* clip, idTraceModelCache* traceModelCache, + idGameLibEffects* effects, idRenderModelDiscreteAnimation* model, + int entityNumber, const breakablePieceSound_t* soundInfo); + void Reset(bool skipModelReset); + void UpdateSimulation(const idVec3& origin, const idMat3& axis, + int currentTime, int gameMsPerFrame, int frameNumber, + float lodScale, float effectScale); + void UpdateModel(const idVec3& origin, const idMat3& axis, + int currentTime); + void DrawCollisionModels(int single, const idVec3& offset, + const idMat3& axis); + void ApplyForce(const idVec3& origin, const idMat3& axis, + const idVec3& force); + void AddRadiusImpact(int time, const idPhysics* impactPhysics, + const idVec3& position, float radius, float impulse); + void AddRayImpact(int time, const idVec3& origin, const idMat3& axis, + const idVec3& start, const idVec3& end, bool activeOnly); + void ApplyImpulseToBody(int bodyIndex, const idPhysics* impactPhysics, + const idVec3& point, const idVec3& impulse); + void CollideWith(const idVec3& origin, const idPhysics* impactPhysics, + const idVec3& point, float impulseScale); + bool ActivateAllNow(const idVec3& origin, const idMat3& axis); + bool ActivateFloatingPieces(const idVec3& origin, const idMat3& axis, + float impulse); + void EmitSmokeForEachPiece(const idDeclParticle* particle, int time); + void StartExplosions(const idVec3& origin, const idMat3& axis, int time); + void StopExplosionFX(int index); + void RemoveExplosions(); + void AddExplosion(const idDeclParticle* particle, int time, + const idVec3& position, float radius, float impulse, float falloff, + float angular, int delay, int duration, bool localSpace, + int activateAllDelay, float distanceDelayScale, + const idVec3& endLinear, float effectScale); + + idList properties; + idList rigidBodies; + idList collisions; + idCollisionModel* basePiece; + idList explosions; + // The recovered list stores idFXManager values. Runtime ownership crosses + // into effects, so PC stores the returned manager handles at the same ABI. + idList explosionFX; + idList trails; + idList damage; + idList particles; + idList decals; + idList sparks; + idList pieceActivationTimes; + idEffectPhysicsBroadPhase broadPhase; + idRandom random; + idGameLibEffects* gameLibEffects; + idRenderModelDiscreteAnimation* model; + const idDeclBreakable* decl; + breakablePieceSound_t soundInfo; + +private: + idEffectPhysicsProperties* GetPropertiesForTraceModel( + const idTraceModel& traceModel, const idMaterial* material); + bool ActivateNoisy(int pieceIndex, const idVec3& origin, + const idMat3& axis); + bool DamagePiece(int time, int pieceIndex, float amount, + const idVec3& position); + void InitExplosionsAndTrails(const breakablePieceSound_t* sound, + idGameLibEffects* effects, float diversity); }; -// IDA Local Type ordinal 15412; PDB kind: class. -class idEffectPhysicsBreakable::idTrail -{ -public: - idBreakableTrail decl; - idList pieceStartTime; - idList pieceLastParticleDropPos; -}; - -// IDA Local Type ordinal 15415; PDB kind: class. -class idEffectPhysicsBreakable::idBreakableParticle -{ -public: - const idDeclParticle *particle; - idVec3 pos; - int startTime; -}; - -// IDA Local Type ordinal 15417; PDB kind: class. -class idEffectPhysicsBreakable::idDecalFX -{ -public: - idBreakableDecal decl; - idList pieceStartTime; - idList lastCollisionPos; - idList lastCollisionNormal; - idList pieceLastDecalDropPos; -}; - -// IDA Local Type ordinal 15419; PDB kind: class. -class idEffectPhysicsBreakable::idSpark -{ -public: - const idMaterial *material; - float velocityScale; - float size; -}; - -// IDA Local Type ordinal 15421; PDB kind: struct. -struct idEffectPhysicsBreakable::breakablePieceSound_t -{ - idSoundWorld *soundWorld; - idSoundEmitter *emitter; - soundChannel_t channel; - const idDeclImpactSound *impactTable; - int nextSoundTime; - float minBounceVelSqr; -}; - -// IDA Local Type ordinal 15422; PDB kind: class. -class idEffectPhysicsBreakable -{ -public: - idList properties; - idList rigidBodies; - idList collisions; - idCollisionModel *basePiece; - idList explosions; - idList explosionFX; - idList trails; - idList damage; - idList particles; - idList decals; - idList sparks; - idList pieceActivationTimes; - idEffectPhysicsBroadPhase broadPhase; - idRandom random; - idGameLibEffects *gameLibEffects; - idRenderModelDiscreteAnimation *model; - const idDeclBreakable *decl; - idEffectPhysicsBreakable::breakablePieceSound_t soundInfo; -}; +static_assert(sizeof(idBreakableExplosion) == 84, + "Recovered idBreakableExplosion ABI changed"); +static_assert(sizeof(idBreakableTrail) == 68, + "Recovered idBreakableTrail ABI changed"); +static_assert(sizeof(idBreakableDecal) == 76, + "Recovered idBreakableDecal ABI changed"); +static_assert(sizeof(idEffectPhysicsBreakable::idExplosion) == 108, + "Recovered breakable explosion ABI changed"); +static_assert(sizeof(idEffectPhysicsBreakable::idTrail) == 100, + "Recovered breakable trail ABI changed"); +static_assert(sizeof(idEffectPhysicsBreakable::idDecalFX) == 140, + "Recovered breakable decal ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectPhysicsBreakable) == 292, + "Recovered idEffectPhysicsBreakable ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicsbroadphase.cpp b/source/engine/gamelib/effectphysics/effectphysicsbroadphase.cpp new file mode 100644 index 0000000..2d05e19 --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicsbroadphase.cpp @@ -0,0 +1,290 @@ +#include "gamelib/effectphysics/effectphysicsbroadphase.h" + +#include "gamelib/physics/physics.h" + +#include + +bool GameLib_GetEffectPhysicsMotionResult( + idCollisionQuery& query, trace_t& result); +idCollisionQuery GameLib_SubmitEffectPhysicsMotion(idClip* clip, + const idTraceModel& traceModel, const idSPObject& object, + const idPositionedCollisionModel* models, int numModels, + bool worldCollisionModelOnly); + +idEffectPhysicsBroadPhase::idEffectPhysicsBroadPhase() + : clip(nullptr) + , traceModelCache(nullptr) + , objects(0) + , markers{idList(0), idList(0), + idList(0)} { +} + +idEffectPhysicsBroadPhase::~idEffectPhysicsBroadPhase() { + clip = nullptr; + traceModelCache = nullptr; + objects.Clear(); + for (idList& axisMarkers : markers) { + axisMarkers.Clear(); + } +} + +void idEffectPhysicsBroadPhase::Init(idClip* const clip_, + idTraceModelCache* const traceModelCache_) { + clip = clip_; + traceModelCache = traceModelCache_; +} + +const idBounds* idEffectPhysicsBroadPhase::ResolveBounds( + const idSPObject* const object, const idBounds* const suppliedBounds) const { + if (suppliedBounds != nullptr) { + return suppliedBounds; + } + if (traceModelCache == nullptr) { + return nullptr; + } + const idTraceModelCache::trmCache_t* const entry = + traceModelCache->GetEntry(object->traceModelIndex); + return entry != nullptr && entry->trm != nullptr + ? &entry->trm->bounds + : nullptr; +} + +void idEffectPhysicsBroadPhase::AddObject(idSPObject* const object, + const idVec3& origin, const idMat3& axis, const idBounds* const bounds) { + const idBounds* const localBounds = ResolveBounds(object, bounds); + if (localBounds == nullptr) { + return; + } + + idBounds transformedBounds; + transformedBounds.FromTransformedBounds(*localBounds, origin, axis); + object->origin = origin; + object->axis = axis; + object->absBounds.SetBounds(transformedBounds); + + int slot = 0; + while (slot < objects.Num() && objects[slot] != nullptr) { + ++slot; + } + if (slot == objects.Num()) { + object->id = objects.Append(object); + } else { + object->id = slot; + objects[slot] = object; + } + RebuildBroadPhase(); +} + +void idEffectPhysicsBroadPhase::RemoveObject(idSPObject* const object) { + if (object == nullptr || object->id < 0 || object->id >= objects.Num() || + objects[object->id] != object) { + return; + } + objects[object->id] = nullptr; + object->id = -1; + object->overlap.Clear(); + for (int side = 0; side < 2; ++side) { + for (int axis = 0; axis < 3; ++axis) { + object->index[side][axis] = -1; + } + } + RebuildBroadPhase(); +} + +void idEffectPhysicsBroadPhase::RebuildBroadPhase() { + for (idList& axisMarkers : markers) { + axisMarkers.Clear(); + } + for (int index = 0; index < objects.Num(); ++index) { + idSPObject* const object = objects[index]; + if (object == nullptr) { + continue; + } + object->overlap.Clear(); + if (object->motion.worldCollisionOnly) { + continue; + } + for (int axis = 0; axis < 3; ++axis) { + idSPMarker minimum = { + object->absBounds.b[0][axis], + static_cast(object->id)}; + idSPMarker maximum = { + object->absBounds.b[1][axis], + static_cast(object->id | 0x8000)}; + markers[axis].Append(minimum); + markers[axis].Append(maximum); + } + } + + for (int axis = 0; axis < 3; ++axis) { + idList& axisMarkers = markers[axis]; + std::sort(axisMarkers.Ptr(), axisMarkers.Ptr() + axisMarkers.Num(), + [](const idSPMarker& left, const idSPMarker& right) { + if (left.value != right.value) { + return left.value < right.value; + } + return left.data < right.data; + }); + for (int markerIndex = 0; markerIndex < axisMarkers.Num(); + ++markerIndex) { + const unsigned short data = axisMarkers[markerIndex].data; + const int objectIndex = data & 0x7FFF; + if (objectIndex < objects.Num() && objects[objectIndex] != nullptr) { + objects[objectIndex]->index[data >> 15][axis] = markerIndex; + } + } + } + + for (int first = 0; first < objects.Num(); ++first) { + idSPObject* const firstObject = objects[first]; + if (firstObject == nullptr || firstObject->motion.worldCollisionOnly) { + continue; + } + for (int second = first + 1; second < objects.Num(); ++second) { + idSPObject* const secondObject = objects[second]; + if (secondObject == nullptr || + secondObject->motion.worldCollisionOnly || + !firstObject->absBounds.IntersectsBounds( + secondObject->absBounds)) { + continue; + } + firstObject->overlap.Append(static_cast(second)); + secondObject->overlap.Append(static_cast(first)); + } + } +} + +void idEffectPhysicsBroadPhase::MoveObject(idSPObject* const object) { + (void)object; + RebuildBroadPhase(); +} + +void idEffectPhysicsBroadPhase::SetObjectPosition(idSPObject* const object, + const idVec3& origin, const idMat3& axis, const idBounds* const bounds) { + const idBounds* const localBounds = ResolveBounds(object, bounds); + if (localBounds == nullptr) { + return; + } + + idBounds transformedBounds; + transformedBounds.FromTransformedBounds(*localBounds, origin, axis); + object->absBounds.SetBounds(transformedBounds); + object->origin = origin; + object->axis = axis; + if (!object->motion.worldCollisionOnly) { + MoveObject(object); + } + object->motion.valid = false; + object->query.offset = 0; +} + +void idEffectPhysicsBroadPhase::ApplyMotion(idSPObject* const object, + const idVec3& translation, const idRotation& rotation) { + const idBounds* const localBounds = ResolveBounds(object, nullptr); + if (localBounds == nullptr) { + return; + } + + idBounds startBounds; + startBounds.FromTransformedBounds(*localBounds, object->origin, + object->axis); + idMat3 endAxis = object->axis; + rotation.RotateAxis(endAxis); + idBounds endBounds; + endBounds.FromTransformedBounds(*localBounds, + object->origin + translation, endAxis); + + idBounds motionBounds; + for (int axis = 0; axis < 3; ++axis) { + motionBounds[0][axis] = (std::min)( + startBounds[0][axis], endBounds[0][axis]) - 1.0f; + motionBounds[1][axis] = (std::max)( + startBounds[1][axis], endBounds[1][axis]) + 1.0f; + } + object->absBounds.SetBounds(motionBounds); + if (!object->motion.worldCollisionOnly) { + MoveObject(object); + } + + object->motion.valid = true; + object->motion.translation = translation; + object->motion.rotationVec = rotation.vec; + object->motion.rotationAngle = rotation.angle; +} + +int idEffectPhysicsBroadPhase::GetPositionedModels( + const idSPObject* const object, idPositionedCollisionModel* const models, + const int maxModels) const { + if (traceModelCache == nullptr) { + return 0; + } + + int count = 0; + for (int index = 0; index < object->overlap.Num() && count < maxModels; + ++index) { + const int objectIndex = object->overlap[index]; + if (objectIndex < 0 || objectIndex >= objects.Num()) { + continue; + } + const idSPObject* const other = objects[objectIndex]; + if (other == nullptr || other->traceModelIndex == -1 || + other->motion.ignore) { + continue; + } + const idTraceModelCache::trmCache_t* const entry = + traceModelCache->GetEntry(other->traceModelIndex); + if (entry == nullptr || entry->collisionModel == nullptr) { + continue; + } + + idPositionedCollisionModel& model = models[count++]; + model.model = entry->collisionModel; + model.modelJoints = nullptr; + model.modelOrigin = other->origin; + model.modelAxis = other->axis; + model.modelEntityNum = 0x1FFF; + model.modelPhysicsId = other->id; + model.modelBodyId = 0; + model.modelContentsOverride = other->clipMask; + model.modelQuery = other->query; + } + return count; +} + +void idEffectPhysicsBroadPhase::SubmitQueries( + const bool worldCollisionModelOnly) { + if (traceModelCache == nullptr || clip == nullptr) { + return; + } + + idPositionedCollisionModel models[128]{}; + for (int index = 0; index < objects.Num(); ++index) { + idSPObject* const object = objects[index]; + if (object == nullptr || !object->motion.valid) { + continue; + } + const idTraceModelCache::trmCache_t* const entry = + traceModelCache->GetEntry(object->traceModelIndex); + if (entry == nullptr || entry->trm == nullptr) { + object->motion.valid = false; + continue; + } + const int numModels = object->motion.worldCollisionOnly + ? 0 + : GetPositionedModels(object, models, 128); + object->query = GameLib_SubmitEffectPhysicsMotion( + clip, *entry->trm, *object, models, numModels, + worldCollisionModelOnly); + object->motion.valid = false; + } +} + +bool idEffectPhysicsBroadPhase::GetMotionResult(trace_t* const result, + idSPObject* const object) { + const bool available = GameLib_GetEffectPhysicsMotionResult( + object->query, *result); + object->origin = result->endpos; + object->axis = result->endAxis; + object->axis.OrthoNormalizeSelf(); + return available; +} diff --git a/source/engine/gamelib/effectphysics/effectphysicsbroadphase.h b/source/engine/gamelib/effectphysics/effectphysicsbroadphase.h index a7b5402..819edb8 100644 --- a/source/engine/gamelib/effectphysics/effectphysicsbroadphase.h +++ b/source/engine/gamelib/effectphysics/effectphysicsbroadphase.h @@ -1,25 +1,103 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicsbroadphase.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisionmodel.h" +#include "cm/jobs/collisionquery.h" +#include "gamelib/physics/tracemodelcache.h" +#include "idlib/bv/boundsshort.h" +#include "idlib/containers/list.h" +#include "idlib/math/rotation.h" +class idClip; +class idJointMat; +struct trace_t; -// IDA Local Type ordinal 14572; PDB kind: class. -class idEffectPhysicsBroadPhase::idSPMarker -{ -public: - __int16 value; - unsigned __int16 data; +struct idSPObjectMotion { + bool valid; + bool worldCollisionOnly; + bool ignore; + idVec3 translation; + idVec3 rotationVec; + float rotationAngle; }; -// IDA Local Type ordinal 14574; PDB kind: class. -class idEffectPhysicsBroadPhase -{ +class idSPObject { public: - idClip *clip; - idTraceModelCache *traceModelCache; - idList objects; - idList markers[3]; + // Materialized in the authoritative effectphysicsbroadphase.h dump. + idSPObject() + : id(0), traceModelIndex(-1), entityNum(0x1FFF), clipMask(1), + origin(), axis(1.0f), absBounds(), motion{}, query{0}, index{}, + overlap(0) { + origin.Zero(); + absBounds.b[0][0] = 0x7FFF; + absBounds.b[0][1] = 0x7FFF; + absBounds.b[0][2] = 0x7FFF; + absBounds.b[1][0] = static_cast(0x8000); + absBounds.b[1][1] = static_cast(0x8000); + absBounds.b[1][2] = static_cast(0x8000); + motion.translation.Zero(); + motion.rotationVec.Zero(); + motion.rotationAngle = 0.0f; + } + + int id; + int traceModelIndex; + int entityNum; + int clipMask; + idVec3 origin; + idMat3 axis; + idBoundsShort absBounds; + idSPObjectMotion motion; + idCollisionQuery query; + int index[2][3]; + idList overlap; }; + +// Reconstructed from PDB local types 14572 and 14574. +class idEffectPhysicsBroadPhase { +public: + class idSPMarker { + public: + short value; + unsigned short data; + }; + + idEffectPhysicsBroadPhase(); + ~idEffectPhysicsBroadPhase(); + + void Init(idClip* clip_, idTraceModelCache* traceModelCache_); + bool GetMotionResult(trace_t* result, idSPObject* object); + int GetPositionedModels(const idSPObject* object, + idPositionedCollisionModel* models, int maxModels) const; + void SubmitQueries(bool worldCollisionModelOnly); + void RemoveObject(idSPObject* object); + void AddObject(idSPObject* object, const idVec3& origin, + const idMat3& axis, const idBounds* bounds); + void SetObjectPosition(idSPObject* object, const idVec3& origin, + const idMat3& axis, const idBounds* bounds); + void ApplyMotion(idSPObject* object, const idVec3& translation, + const idRotation& rotation); + + idClip *clip; + idTraceModelCache *traceModelCache; + idList objects; + idList markers[3]; + +private: + const idBounds* ResolveBounds(const idSPObject* object, + const idBounds* suppliedBounds) const; + void MoveObject(idSPObject* object); + void RebuildBroadPhase(); +}; + +static_assert(sizeof(idEffectPhysicsBroadPhase::idSPMarker) == 4, + "Recovered idSPMarker ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idPositionedCollisionModel) == 80, + "Recovered positioned collision-model ABI changed"); +static_assert(sizeof(idSPObjectMotion) == 32, + "Recovered idSPObjectMotion ABI changed"); +static_assert(sizeof(idSPObject) == 160, + "Recovered idSPObject ABI changed"); +static_assert(sizeof(idEffectPhysicsBroadPhase) == 72, + "Recovered idEffectPhysicsBroadPhase ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicscuttable.cpp b/source/engine/gamelib/effectphysics/effectphysicscuttable.cpp new file mode 100644 index 0000000..1fc1932 --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicscuttable.cpp @@ -0,0 +1,308 @@ +#include "gamelib/effectphysics/effectphysicscuttable.h" + +#include "gamelib/physics/tracemodelcache.h" +#include "idlib/geometry/tracemodel.h" + +#include + +idRenderModelCuttableDynamic* GameLib_AllocCuttableRenderModel( + idRenderWorld* renderWorld); +void GameLib_DeleteCuttableRenderModel(idRenderModelCuttableDynamic* model); +void GameLib_UploadCuttableRenderModel(idRenderModelCuttableDynamic* model, + const idMaterial* renderMaterial, const idMaterial* depthMaterial, + const idList& vertices, + const idList& textureCoordinates, + const idList& indices); +void GameLib_UpdateCuttableRenderModel(idRenderModelCuttableDynamic* model, + const idVec3& origin, const idMat3& axis, float fade, bool hidden); +void GameLib_PlayCuttableCollision(idSoundWorld* soundWorld, + const idDeclImpactSound* soundTable, + const idEffectPhysicsCollision& collision, const idVec3& origin, + float velocitySqr, float minimumVelocitySqr, int& nextSoundTime, + int currentTime); +void GameLib_DrawEffectTraceModel(const idTraceModelCache* cache, + int traceModelIndex, int pieceIndex, const idVec3& origin, + const idMat3& axis); + +idEffectPhysicsCuttable::idEffectPhysicsCuttable() + : freeModels() + , models() + , pieces() + , broadPhase() + , pieceCollisionAgeDecay(0.0f) + , pieceFriction(0.0f) + , pieceMaxLifeTime(30000) + , pieceLifeTime(5000) + , pieceFadeTime(2000) + , pieceMass(8) + , soundWorld(nullptr) { +} + +idEffectPhysicsCuttable::~idEffectPhysicsCuttable() { + for (int index = 0; index < pieces.Num(); ++index) { + piece_t& piece = pieces[index]; + if (piece.properties != nullptr && broadPhase.traceModelCache != nullptr) { + broadPhase.traceModelCache->FreeTraceModel( + piece.properties->traceModelIndex); + } + delete piece.rigidBody; + delete piece.properties; + piece.rigidBody = nullptr; + piece.properties = nullptr; + piece.soundTable = nullptr; + } + for (int index = 0; index < models.Num(); ++index) { + if (models[index] != nullptr) { + GameLib_DeleteCuttableRenderModel(models[index]); + } + } +} + +void idEffectPhysicsCuttable::Init(idRenderWorld* const renderWorld, + idSoundWorld* const soundWorld_, idClip* const clip, + idTraceModelCache* const traceModelCache) { + broadPhase.Init(clip, traceModelCache); + pieceCollisionAgeDecay = 0.0f; + pieceMaxLifeTime = 30000; + pieceLifeTime = 5000; + pieceFadeTime = 2000; + pieceMass = 8; + pieceFriction = 0.0f; + soundWorld = soundWorld_; + + pieces.SetNum(MAX_PIECES); + for (int index = 0; index < pieces.Num(); ++index) { + piece_t& piece = pieces[index]; + piece.properties = nullptr; + piece.rigidBody = nullptr; + piece.soundTable = nullptr; + piece.collision.Clear(); + piece.orientation = idMat3(1.0f); + piece.position.Zero(); + piece.timeStamp = 0; + piece.firstCollisionTime = 0; + piece.renderModelIndex = -1; + piece.nextSoundTime = 0; + piece.minBounceVelocitySqr = 1.0f; + } + + models.Clear(); + freeModels.Clear(); + for (int index = 0; index < MAX_BUFFERS; ++index) { + models.Append(GameLib_AllocCuttableRenderModel(renderWorld)); + freeModels.Append(model_t{index, 0}); + } +} + +void idEffectPhysicsCuttable::ReleasePiece(piece_t& piece, + const int currentTime) { + if (piece.renderModelIndex >= 0) { + freeModels.Append(model_t{piece.renderModelIndex, currentTime}); + if (piece.renderModelIndex < models.Num() && + models[piece.renderModelIndex] != nullptr) { + GameLib_UpdateCuttableRenderModel(models[piece.renderModelIndex], + idVec3(0.0f, 0.0f, 0.0f), idMat3(1.0f), 0.0f, true); + } + piece.renderModelIndex = -1; + } + if (piece.rigidBody != nullptr) { + piece.rigidBody->active = false; + } + piece.timeStamp = 0; +} + +int idEffectPhysicsCuttable::Emit( + const cuttablePhysicsEmitInfo_t& emitInfo, + const idList& vertices, + const idList& textureCoordinates, + const idList& indices) { + if (emitInfo.traceModel == nullptr || broadPhase.traceModelCache == nullptr || + pieces.IsEmpty()) { + return -1; + } + + int pieceIndex = 0; + for (int index = 1; index < pieces.Num(); ++index) { + if (pieces[index].timeStamp < pieces[pieceIndex].timeStamp) { + pieceIndex = index; + } + } + piece_t& piece = pieces[pieceIndex]; + + // The pool has two more models than live pieces. Return the recycled + // piece's model before selecting the oldest free buffer. + if (piece.renderModelIndex >= 0) { + freeModels.Append(model_t{piece.renderModelIndex, emitInfo.emitTime}); + piece.renderModelIndex = -1; + } + int freeIndex = 0; + for (int index = 1; index < freeModels.Num(); ++index) { + if (freeModels[index].timeStamp < freeModels[freeIndex].timeStamp) { + freeIndex = index; + } + } + const int renderModelIndex = freeModels.IsEmpty() + ? -1 : freeModels[freeIndex].index; + if (!freeModels.IsEmpty()) { + freeModels.RemoveIndexFast(freeIndex); + } + + if (piece.properties != nullptr) { + broadPhase.traceModelCache->FreeTraceModel( + piece.properties->traceModelIndex); + } + delete piece.rigidBody; + delete piece.properties; + piece.rigidBody = nullptr; + piece.properties = nullptr; + + idTraceModel centeredTraceModel = *emitInfo.traceModel; + const idVec3 center = + (centeredTraceModel.bounds[0] + centeredTraceModel.bounds[1]) * 0.5f; + centeredTraceModel.Translate(-center); + const int traceModelIndex = broadPhase.traceModelCache->AllocTraceModel( + centeredTraceModel, emitInfo.renderMaterial); + + piece.properties = new idEffectPhysicsProperties(); + piece.properties->Setup(&broadPhase, traceModelIndex, + static_cast(pieceMass)); + piece.properties->contactFriction = 0.5f; + piece.properties->linearFriction = 0.6f; + piece.properties->angularFriction = 0.6f; + piece.properties->bouncyness = 0.6f; + piece.properties->angularFrictionWater = 1.0f; + piece.properties->linearFrictionWater = 1.0f; + piece.properties->gravityVector.Set(0.0f, 0.0f, -250.0f); + piece.properties->worldCollisionOnly = true; + piece.properties->simplePointCollision = false; + piece.properties->crazyBounceChance = 0.0f; + piece.properties->maxLinearVelocity = 5000.0f; + piece.properties->stopSpeed = 30.0f; + piece.properties->maxAngularVelocity = 12.566370614359f; + piece.properties->clipMask = 1; + + piece.rigidBody = new idEffectPhysicsRigidBody(); + piece.rigidBody->Setup(piece.properties); + piece.rigidBody->SPObject.entityNum = emitInfo.entityNum; + piece.rigidBody->SetPosition(emitInfo.position + center, + emitInfo.orientation); + piece.rigidBody->currentState.linearMomentum.Zero(); + piece.rigidBody->currentState.angularMomentum.Zero(); + if (emitInfo.impulse.LengthSqr() > 0.25f) { + piece.rigidBody->ApplyImpulse( + emitInfo.position + emitInfo.impulsePoint, emitInfo.impulse); + } + piece.rigidBody->active = true; + piece.rigidBody->SPObject.motion.ignore = false; + + piece.soundTable = emitInfo.soundTable; + piece.collision.Clear(); + piece.orientation = emitInfo.orientation; + piece.position = -center; + piece.timeStamp = emitInfo.emitTime; + piece.firstCollisionTime = 0; + piece.renderModelIndex = renderModelIndex; + piece.nextSoundTime = emitInfo.emitTime; + piece.minBounceVelocitySqr = emitInfo.minBounceVelocitySqr; + + if (renderModelIndex >= 0 && renderModelIndex < models.Num() && + models[renderModelIndex] != nullptr) { + GameLib_UploadCuttableRenderModel(models[renderModelIndex], + emitInfo.renderMaterial, emitInfo.depthMaterial, + vertices, textureCoordinates, indices); + GameLib_UpdateCuttableRenderModel(models[renderModelIndex], + emitInfo.position, emitInfo.orientation, 1.0f, false); + } + return pieceIndex; +} + +void idEffectPhysicsCuttable::UpdateSimulation(const idVec3& origin, + const idMat3& axis, const int currentTime, const int gameMsPerFrame) { + (void)origin; + (void)axis; + const float deltaTime = (std::max)(0, gameMsPerFrame) * 0.001f; + for (int index = 0; index < pieces.Num(); ++index) { + piece_t& piece = pieces[index]; + idEffectPhysicsRigidBody* const body = piece.rigidBody; + if (piece.timeStamp == 0 || body == nullptr || !body->active) { + continue; + } + const int deathTime = piece.firstCollisionTime != 0 + ? piece.firstCollisionTime + pieceLifeTime + pieceFadeTime + : piece.timeStamp + pieceMaxLifeTime; + if (currentTime >= deathTime) { + ReleasePiece(piece, currentTime); + continue; + } + body->Update(deltaTime, 0.0f, piece.collision); + if (body->settled && piece.firstCollisionTime == 0) { + piece.firstCollisionTime = currentTime; + } else if (piece.collision.collisionNormalVelocity.LengthSqr() > 0.0f) { + if (piece.firstCollisionTime == 0) { + piece.firstCollisionTime = currentTime; + } + const idVec3 velocity = body->currentState.linearMomentum * + body->properties->inverseMass - + body->properties->gravityVector * deltaTime; + GameLib_PlayCuttableCollision(soundWorld, piece.soundTable, + piece.collision, body->currentState.position, + velocity.LengthSqr(), piece.minBounceVelocitySqr, + piece.nextSoundTime, currentTime); + } + if (pieceFriction > 0.0f) { + body->currentState.linearMomentum = + body->currentState.linearMomentum * (std::max)(0.0f, + 1.0f - pieceFriction * deltaTime); + } + } +} + +void idEffectPhysicsCuttable::UpdateModel(const idVec3& scale, + const int currentTime) { + const idMat3 scaleAxis(scale.x, 0.0f, 0.0f, + 0.0f, scale.y, 0.0f, 0.0f, 0.0f, scale.z); + for (int index = 0; index < pieces.Num(); ++index) { + piece_t& piece = pieces[index]; + if (piece.timeStamp == 0 || piece.rigidBody == nullptr || + !piece.rigidBody->active || piece.renderModelIndex < 0 || + piece.renderModelIndex >= models.Num() || + models[piece.renderModelIndex] == nullptr) { + continue; + } + const idVec3 renderOrigin = piece.rigidBody->currentState.position + + piece.rigidBody->currentState.orientation * piece.position; + float fade = 1.0f; + if (piece.firstCollisionTime != 0 && pieceFadeTime > 0) { + const int fadeAge = currentTime - + (piece.firstCollisionTime + pieceLifeTime); + if (fadeAge > 0) { + fade = (std::max)(0.0f, + 1.0f - fadeAge / static_cast(pieceFadeTime)); + } + } + GameLib_UpdateCuttableRenderModel(models[piece.renderModelIndex], + renderOrigin, piece.rigidBody->currentState.orientation * scaleAxis, + fade, false); + } +} + +void idEffectPhysicsCuttable::SubmitCollisionQueries() { + broadPhase.SubmitQueries(false); +} + +void idEffectPhysicsCuttable::DrawCollisionModels(const int single) { + for (int index = 0; index < pieces.Num(); ++index) { + if (single >= 0 && index != single) { + continue; + } + const piece_t& piece = pieces[index]; + if (piece.timeStamp == 0 || piece.rigidBody == nullptr || + !piece.rigidBody->active || piece.properties == nullptr) { + continue; + } + GameLib_DrawEffectTraceModel(broadPhase.traceModelCache, + piece.properties->traceModelIndex, index, + piece.rigidBody->currentState.position, + piece.rigidBody->currentState.orientation); + } +} diff --git a/source/engine/gamelib/effectphysics/effectphysicscuttable.h b/source/engine/gamelib/effectphysics/effectphysicscuttable.h index ad75a11..43da096 100644 --- a/source/engine/gamelib/effectphysics/effectphysicscuttable.h +++ b/source/engine/gamelib/effectphysics/effectphysicscuttable.h @@ -1,54 +1,94 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicscuttable.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effectphysics/effectphysicsrigidbody.h" +#include "idlib/containers/staticlist.h" +#include "idlib/geometry/drawvert.h" +class idClip; +class idDeclImpactSound; +class idMaterial; +class idRenderModelCuttableDynamic; +class idRenderWorld; +class idSoundWorld; +class idTraceModel; +class idTraceModelCache; -// IDA Local Type ordinal 2911; PDB kind: unknown. -enum idEffectPhysicsCuttable:: : __int32 -{ - MAX_PIECES = 0x8, - MAX_BUFFERS = 0xA, +struct cuttablePhysicsEmitInfo_t { + const idMaterial* renderMaterial; + const idMaterial* depthMaterial; + const idDeclImpactSound* soundTable; + idTraceModel* traceModel; + idMat3 orientation; + idVec3 position; + idVec3 impulsePoint; + idVec3 impulse; + int emitTime; + int entityNum; + float minBounceVelocitySqr; }; -// IDA Local Type ordinal 17356; PDB kind: struct. -struct idEffectPhysicsCuttable::model_t -{ - int index; - int timeStamp; -}; - -// IDA Local Type ordinal 17363; PDB kind: struct. -struct idEffectPhysicsCuttable::piece_t -{ - idEffectPhysicsProperties *properties; - idEffectPhysicsRigidBody *rigidBody; - const idDeclImpactSound *soundTable; - idEffectPhysicsCollision collision; - idMat3 orientation; - idVec3 position; - int timeStamp; - int firstCollisionTime; - int renderModelIndex; - int nextSoundTime; - float minBounceVelocitySqr; -}; - -// IDA Local Type ordinal 17366; PDB kind: class. -class idEffectPhysicsCuttable -{ +class idEffectPhysicsCuttable { public: - idStaticList freeModels; - idStaticList models; - idStaticList pieces; - idEffectPhysicsBroadPhase broadPhase; - float pieceCollisionAgeDecay; - float pieceFriction; - int pieceMaxLifeTime; - int pieceLifeTime; - int pieceFadeTime; - int pieceMass; - idSoundWorld *soundWorld; + static constexpr int MAX_PIECES = 8; + static constexpr int MAX_BUFFERS = 10; + + struct model_t { + int index; + int timeStamp; + }; + + struct piece_t { + idEffectPhysicsProperties* properties; + idEffectPhysicsRigidBody* rigidBody; + const idDeclImpactSound* soundTable; + idEffectPhysicsCollision collision; + idMat3 orientation; + idVec3 position; + int timeStamp; + int firstCollisionTime; + int renderModelIndex; + int nextSoundTime; + float minBounceVelocitySqr; + }; + + idEffectPhysicsCuttable(); + ~idEffectPhysicsCuttable(); + + void Init(idRenderWorld* renderWorld, idSoundWorld* soundWorld, + idClip* clip, idTraceModelCache* traceModelCache); + int Emit(const cuttablePhysicsEmitInfo_t& emitInfo, + const idList& vertices, + const idList& textureCoordinates, + const idList& indices); + void UpdateSimulation(const idVec3& origin, const idMat3& axis, + int currentTime, int gameMsPerFrame); + void UpdateModel(const idVec3& scale, int currentTime); + void SubmitCollisionQueries(); + void DrawCollisionModels(int single); + + idStaticList freeModels; + idStaticList models; + idStaticList pieces; + idEffectPhysicsBroadPhase broadPhase; + float pieceCollisionAgeDecay; + float pieceFriction; + int pieceMaxLifeTime; + int pieceLifeTime; + int pieceFadeTime; + int pieceMass; + idSoundWorld* soundWorld; + +private: + void ReleasePiece(piece_t& piece, int currentTime); }; + +static_assert(sizeof(cuttablePhysicsEmitInfo_t) == 100, + "Recovered cuttablePhysicsEmitInfo_t ABI changed"); +static_assert(sizeof(idEffectPhysicsCuttable::model_t) == 8, + "Recovered cuttable model entry ABI changed"); +static_assert(sizeof(idEffectPhysicsCuttable::piece_t) == 108, + "Recovered cuttable piece ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectPhysicsCuttable) == 1132, + "Recovered idEffectPhysicsCuttable ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.cpp b/source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.cpp new file mode 100644 index 0000000..263520d --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.cpp @@ -0,0 +1,102 @@ +#include "gamelib/effectphysics/effectphysicsdebrisemitter.h" + +#include "gamelib/physics/clip.h" + +#include + +idRenderModelDiscreteAnimation* GameLib_CreateDebrisRenderModel( + const idDeclBreakable* breakableDecl); + +idEffectPhysicsDebrisEmitter::idEffectPhysicsDebrisEmitter() + : piecePhysics() + , emitBuffer{} + , emitBufferPos(0) + , serializeEmitCount(0) + , skipFirstFrame(false) + , active(false) + , random(0) { +} + +void idEffectPhysicsDebrisEmitter::Init( + const idDeclBreakable* const breakableDebris, const int entityNumber, + idClip& clip, idRenderModelEffects* const renderModelEffects) { + active = false; + if (breakableDebris == nullptr || clip.traceModelCache == nullptr) { + return; + } + idRenderModelDiscreteAnimation* const model = + GameLib_CreateDebrisRenderModel(breakableDebris); + if (model == nullptr) { + return; + } + piecePhysics.Init(&clip, clip.traceModelCache, model, + renderModelEffects, nullptr, entityNumber); + piecePhysics.pieceLifeTime = 2500; + piecePhysics.pieceFriction = 15.0f; + const idVec3 unitScale(1.0f, 1.0f, 1.0f); + piecePhysics.UpdateModel(unitScale); + emitBufferPos = 0; + serializeEmitCount = 0; + skipFirstFrame = true; + active = true; + random.SetSeed(0); +} + +void idEffectPhysicsDebrisEmitter::EmitBuffered( + const emitBufferItem_t& item, const idVec3& velocity, + const int currentTime) { + idVec3 normal = item.normal; + if (normal.NormalizeFast() == 0.0f) { + normal.Set(0.0f, 0.0f, 1.0f); + } + for (int index = 0; index < item.count; ++index) { + idVec3 randomOffset(random.CRandomFloat() * 4.0f, + random.CRandomFloat() * 4.0f, + random.CRandomFloat() * 4.0f); + // Keep the recovered disk-shaped spawn scatter perpendicular to the + // impact normal rather than spraying pieces into the surface. + randomOffset = randomOffset - normal * randomOffset.Dot(normal); + const idVec3 position = item.pos + randomOffset; + const float upwardSpeed = 2000.0f + random.RandomFloat() * 1250.0f; + const idVec3 impulse = velocity + normal * upwardSpeed + + idVec3(random.CRandomFloat(), random.CRandomFloat(), + random.CRandomFloat()) * 40.0f; + piecePhysics.EmitPiece(currentTime, position, idMat3(1.0f), + item.pos, impulse); + } +} + +void idEffectPhysicsDebrisEmitter::EmitDebris(const idVec3 position, + const idVec3 velocity, const idVec3 normal, int count, + const int currentTime) { + if (!active) { + return; + } + count = (std::max)(0, (std::min)(15, count)); + emitBufferItem_t& item = emitBuffer[emitBufferPos]; + item.pos = position; + item.normal = normal; + item.count = count; + emitBufferPos = (emitBufferPos + 1) % 10; + EmitBuffered(item, velocity, currentTime); +} + +void idEffectPhysicsDebrisEmitter::Update(const idVec3& origin, + const idMat3& axis, const idVec3& drawScale3D, + const int currentTime, const int gameMsPerFrame) { + while (serializeEmitCount > 0) { + const int first = (emitBufferPos - serializeEmitCount + 10) % 10; + EmitBuffered(emitBuffer[first], idVec3(0.0f, 0.0f, 0.0f), + currentTime); + --serializeEmitCount; + } + if (skipFirstFrame) { + skipFirstFrame = false; + } + if (piecePhysics.numPiecesInUse > 0) { + piecePhysics.UpdateSimulation(origin, axis, + currentTime, gameMsPerFrame); + piecePhysics.UpdateModel(drawScale3D); + piecePhysics.SubmitCollisionQueries(false); + } +} diff --git a/source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.h b/source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.h index fc46302..e9a3d56 100644 --- a/source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.h +++ b/source/engine/gamelib/effectphysics/effectphysicsdebrisemitter.h @@ -1,28 +1,42 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicsdebrisemitter.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effectphysics/effectphysicspieceemitter.h" +class idDeclBreakable; -// IDA Local Type ordinal 14594; PDB kind: struct. -struct idEffectPhysicsDebrisEmitter::emitBufferItem_t -{ - idVec3 pos; - idVec3 normal; - int count; -}; - -// IDA Local Type ordinal 14595; PDB kind: class. -class idEffectPhysicsDebrisEmitter -{ +class idEffectPhysicsDebrisEmitter { public: - idEffectPhysicsPieceEmitter piecePhysics; - idEffectPhysicsDebrisEmitter::emitBufferItem_t emitBuffer[10]; - int emitBufferPos; - int serializeEmitCount; - bool skipFirstFrame; - bool active; - idRandom2 random; + struct emitBufferItem_t { + idVec3 pos; + idVec3 normal; + int count; + }; + + idEffectPhysicsDebrisEmitter(); + + void Init(const idDeclBreakable* breakableDebris, int entityNumber, + idClip& clip, idRenderModelEffects* renderModelEffects); + void EmitDebris(idVec3 position, idVec3 velocity, idVec3 normal, + int count, int currentTime); + void Update(const idVec3& origin, const idMat3& axis, + const idVec3& drawScale3D, int currentTime, int gameMsPerFrame); + + idEffectPhysicsPieceEmitter piecePhysics; + emitBufferItem_t emitBuffer[10]; + int emitBufferPos; + int serializeEmitCount; + bool skipFirstFrame; + bool active; + idRandom2 random; + +private: + void EmitBuffered(const emitBufferItem_t& item, + const idVec3& velocity, int currentTime); }; + +static_assert(sizeof(idEffectPhysicsDebrisEmitter::emitBufferItem_t) == 28, + "Recovered debris emit-buffer ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectPhysicsDebrisEmitter) == 464, + "Recovered idEffectPhysicsDebrisEmitter ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicspieceemitter.cpp b/source/engine/gamelib/effectphysics/effectphysicspieceemitter.cpp new file mode 100644 index 0000000..50ca5f9 --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicspieceemitter.cpp @@ -0,0 +1,359 @@ +#include "gamelib/effectphysics/effectphysicspieceemitter.h" + +#include "gamelib/physics/tracemodelcache.h" +#include "idlib/geometry/tracemodel.h" + +#include + +// These operations cross from GameLib into the renderer/decl/sound modules. +// Their recovered implementations live with those owning subsystems; GameLib +// consumes them without inventing the very large render-model layouts here. +int GameLib_DiscreteAnimationPieceCount( + const idRenderModelDiscreteAnimation* model); +const idDeclBreakable* GameLib_DiscreteAnimationDecl( + const idRenderModelDiscreteAnimation* model); +bool GameLib_DiscreteAnimationGetPiece( + const idRenderModelDiscreteAnimation* model, int index, + idVec3& position, idMat3& orientation, idTraceModel& traceModel, + const idMaterial*& material); +void GameLib_DiscreteAnimationSetPiece(idRenderModelDiscreteAnimation* model, + int index, const idVec3& position, const idMat3& orientation); +void GameLib_DiscreteAnimationCollapsePiece( + idRenderModelDiscreteAnimation* model, int index); +void GameLib_DiscreteAnimationReset(idRenderModelDiscreteAnimation* model); +void GameLib_DiscreteAnimationUpdate(idRenderModelDiscreteAnimation* model); +void GameLib_DiscreteAnimationSetHidden( + idRenderModelDiscreteAnimation* model, bool hidden); +void GameLib_DiscreteAnimationDelete(idRenderModelDiscreteAnimation* model); +bool GameLib_GetBreakablePhysicsSettings(const idDeclBreakable* decl, + idEffectPhysicsPieceSettings& settings); +void GameLib_DrawEffectTraceModel(const idTraceModelCache* cache, + int traceModelIndex, int pieceIndex, const idVec3& origin, + const idMat3& axis); +void GameLib_PlayEffectPhysicsCollision( + idEffectPhysicsPieceEmitter::pieceEmitterSound_t& soundInfo, + idRenderModelEffects* effects, const idEffectPhysicsCollision& collision, + const idVec3& origin, float speedSqr, float volumeDb, int currentTime); + +idEffectPhysicsPieceEmitter::idEffectPhysicsPieceEmitter() + : properties(0) + , pieces(0) + , broadPhase() + , random(0) + , pieceMass(10.0f) + , pieceCollisionAgeDecay(0.0f) + , pieceCollisionVolumeDecay(0.0f) + , model(nullptr) + , decl(nullptr) + , modelEffects(nullptr) + , soundInfo{nullptr, nullptr, 0, 0.0f} + , pieceLifeTime(0) + , pieceFriction(0.0f) + , pieceIndex(0) + , numPiecesInUse(0) + , hidden(false) + , hiddenOverride(false) { +} + +idEffectPhysicsPieceEmitter::~idEffectPhysicsPieceEmitter() { + if (broadPhase.traceModelCache != nullptr) { + for (int index = 0; index < properties.Num(); ++index) { + if (properties[index] != nullptr) { + broadPhase.traceModelCache->FreeTraceModel( + properties[index]->traceModelIndex); + } + } + } + for (int index = 0; index < pieces.Num(); ++index) { + delete pieces[index].rigidBody; + pieces[index].rigidBody = nullptr; + } + for (int index = 0; index < properties.Num(); ++index) { + delete properties[index]; + } + pieces.ClearFree(); + properties.ClearFree(); + if (model != nullptr) { + GameLib_DiscreteAnimationDelete(model); + model = nullptr; + } +} + +void idEffectPhysicsPieceEmitter::ApplyDeclSettings( + idEffectPhysicsProperties& target) const { + idEffectPhysicsPieceSettings settings{}; + if (decl == nullptr || + !GameLib_GetBreakablePhysicsSettings(decl, settings)) { + return; + } + target.linearFriction = settings.linearFriction; + target.angularFriction = settings.angularFriction; + target.contactFriction = settings.contactFriction; + target.linearFrictionWater = settings.linearFrictionWater; + target.angularFrictionWater = settings.angularFrictionWater; + target.bouncyness = settings.bouncyness; + target.gravityVector = settings.gravityVector; + target.worldCollisionOnly = settings.worldCollisionOnly; + target.simplePointCollision = settings.simplePointCollision; + target.crazyBounceChance = settings.crazyBounceChance; + target.stopSpeed = settings.stopSpeed; + target.maxLinearVelocity = settings.maxLinearVelocity; + target.maxAngularVelocity = settings.maxAngularVelocity; + target.clipMask = settings.clipMask; +} + +idEffectPhysicsProperties* +idEffectPhysicsPieceEmitter::GetPropertiesForTraceModel( + const idTraceModel& traceModel, const idMaterial* const material) { + if (broadPhase.traceModelCache == nullptr) { + return nullptr; + } + const int traceModelIndex = broadPhase.traceModelCache->AllocTraceModel( + traceModel, material); + for (int index = 0; index < properties.Num(); ++index) { + if (properties[index]->traceModelIndex == traceModelIndex) { + // AllocTraceModel returned another reference to an existing entry. + broadPhase.traceModelCache->FreeTraceModel(traceModelIndex); + return properties[index]; + } + } + + idEffectPhysicsProperties* const result = + new idEffectPhysicsProperties(); + result->Setup(&broadPhase, traceModelIndex, pieceMass); + ApplyDeclSettings(*result); + properties.Append(result); + return result; +} + +void idEffectPhysicsPieceEmitter::Init(idClip* const clip, + idTraceModelCache* const traceModelCache, + idRenderModelDiscreteAnimation* const renderModel, + idRenderModelEffects* const renderModelEffects, + const pieceEmitterSound_t* const sound, const int entityNumber) { + broadPhase.Init(clip, traceModelCache); + model = renderModel; + modelEffects = renderModelEffects; + decl = renderModel != nullptr + ? GameLib_DiscreteAnimationDecl(renderModel) : nullptr; + if (sound != nullptr) { + soundInfo = *sound; + } + pieceIndex = 0; + numPiecesInUse = 0; + + for (int index = 0; index < pieces.Num(); ++index) { + delete pieces[index].rigidBody; + } + pieces.Clear(); + const int pieceCount = renderModel != nullptr + ? GameLib_DiscreteAnimationPieceCount(renderModel) : 0; + pieces.SetNum((std::max)(0, pieceCount)); + + for (int index = 0; index < pieces.Num(); ++index) { + idVec3 position; + idMat3 orientation(1.0f); + idTraceModel traceModel; + const idMaterial* material = nullptr; + idBreakablePiece& piece = pieces[index]; + piece.rigidBody = nullptr; + piece.collision.Clear(); + piece.emitTime = 0; + piece.firstCollisionTime = 0; + if (!GameLib_DiscreteAnimationGetPiece(renderModel, index, + position, orientation, traceModel, material)) { + continue; + } + traceModel.Shrink(0.1875f); + idEffectPhysicsProperties* const pieceProperties = + GetPropertiesForTraceModel(traceModel, material); + if (pieceProperties == nullptr) { + continue; + } + piece.rigidBody = new idEffectPhysicsRigidBody(); + piece.rigidBody->Setup(pieceProperties); + piece.rigidBody->SetPosition(position, orientation); + piece.rigidBody->SPObject.entityNum = entityNumber; + } +} + +void idEffectPhysicsPieceEmitter::Reset() { + random.SetSeed(0); + pieceIndex = 0; + numPiecesInUse = 0; + if (model != nullptr) { + GameLib_DiscreteAnimationReset(model); + } + for (int index = 0; index < pieces.Num(); ++index) { + idBreakablePiece& piece = pieces[index]; + piece.emitTime = 0; + piece.firstCollisionTime = 0; + piece.collision.Clear(); + if (piece.rigidBody == nullptr) { + continue; + } + idVec3 position; + idMat3 orientation(1.0f); + idTraceModel ignoredTraceModel; + const idMaterial* ignoredMaterial = nullptr; + if (model != nullptr && GameLib_DiscreteAnimationGetPiece(model, + index, position, orientation, ignoredTraceModel, + ignoredMaterial)) { + piece.rigidBody->SetPosition(position, orientation); + } + piece.rigidBody->active = false; + piece.rigidBody->settled = false; + piece.rigidBody->currentState.linearMomentum.Zero(); + piece.rigidBody->currentState.angularMomentum.Zero(); + piece.rigidBody->SPObject.motion.ignore = false; + } + for (int index = 0; index < properties.Num(); ++index) { + ApplyDeclSettings(*properties[index]); + } + hiddenOverride = true; + if (model != nullptr) { + GameLib_DiscreteAnimationSetHidden(model, true); + } +} + +void idEffectPhysicsPieceEmitter::EmitPiece(const int emitTime, + const idVec3& position, const idMat3& orientation, + const idVec3& impulsePoint, const idVec3& impulse) { + if (pieces.IsEmpty()) { + return; + } + idBreakablePiece& piece = pieces[pieceIndex]; + if (piece.rigidBody == nullptr) { + pieceIndex = (pieceIndex + 1) % pieces.Num(); + return; + } + if (!piece.rigidBody->active) { + ++numPiecesInUse; + } + hiddenOverride = false; + piece.emitTime = emitTime; + piece.firstCollisionTime = 0; + piece.collision.Clear(); + piece.rigidBody->SetPosition(position, orientation); + piece.rigidBody->currentState.linearMomentum.Zero(); + piece.rigidBody->currentState.angularMomentum.Zero(); + piece.rigidBody->ApplyImpulse(position + impulsePoint, impulse); + piece.rigidBody->active = true; + piece.rigidBody->settled = false; + piece.rigidBody->SPObject.motion.ignore = false; + pieceIndex = (pieceIndex + 1) % pieces.Num(); +} + +void idEffectPhysicsPieceEmitter::UpdateSimulation(const idVec3& origin, + const idMat3& axis, const int currentTime, const int gameMsPerFrame) { + (void)origin; + (void)axis; + const float deltaTime = (std::max)(0, gameMsPerFrame) * 0.001f; + for (int index = 0; index < pieces.Num(); ++index) { + idBreakablePiece& piece = pieces[index]; + idEffectPhysicsRigidBody* const body = piece.rigidBody; + if (body == nullptr || !body->active) { + continue; + } + if (pieceLifeTime > 0 && currentTime >= piece.emitTime + pieceLifeTime) { + body->active = false; + body->SPObject.motion.ignore = true; + numPiecesInUse = (std::max)(0, numPiecesInUse - 1); + continue; + } + + float momentumDecay = 0.0f; + if (piece.firstCollisionTime > 0 && pieceCollisionAgeDecay > 0.0f && + pieceLifeTime > 0) { + momentumDecay = (std::min)(1.0f, + (currentTime - piece.firstCollisionTime) * + pieceCollisionAgeDecay / + static_cast(pieceLifeTime)); + } + body->Update(deltaTime, momentumDecay, piece.collision); + if (piece.collision.collisionNormalVelocity.LengthSqr() > 0.0f) { + if (piece.firstCollisionTime == 0) { + piece.firstCollisionTime = currentTime; + } + const idVec3 velocity = body->currentState.linearMomentum * + body->properties->inverseMass; + const float volumeDb = pieceCollisionVolumeDecay > 0.0f + ? -(currentTime - piece.firstCollisionTime) * + pieceCollisionVolumeDecay * 0.001f + : 0.0f; + GameLib_PlayEffectPhysicsCollision(soundInfo, modelEffects, + piece.collision, body->currentState.position, + velocity.LengthSqr(), volumeDb, currentTime); + } + if (pieceFriction > 0.0f) { + const float damping = (std::max)(0.0f, + 1.0f - pieceFriction * deltaTime); + body->currentState.linearMomentum = + body->currentState.linearMomentum * damping; + } + } +} + +void idEffectPhysicsPieceEmitter::UpdateModel( + const idVec3& activeDrawScale3D) { + if (model == nullptr) { + return; + } + const idMat3 scale(activeDrawScale3D.x, 0.0f, 0.0f, + 0.0f, activeDrawScale3D.y, 0.0f, + 0.0f, 0.0f, activeDrawScale3D.z); + for (int index = 0; index < pieces.Num(); ++index) { + const idBreakablePiece& piece = pieces[index]; + if (piece.rigidBody != nullptr && piece.rigidBody->active) { + GameLib_DiscreteAnimationSetPiece(model, index, + piece.rigidBody->currentState.position, + piece.rigidBody->currentState.orientation * scale); + } else { + GameLib_DiscreteAnimationCollapsePiece(model, index); + } + } + if (!hidden && !hiddenOverride) { + GameLib_DiscreteAnimationUpdate(model); + } +} + +void idEffectPhysicsPieceEmitter::SubmitCollisionQueries( + const bool worldCollisionModelOnly) { + broadPhase.SubmitQueries(worldCollisionModelOnly); +} + +void idEffectPhysicsPieceEmitter::DrawCollisionModels(const int single, + const idVec3& offset, const idMat3& axis) { + for (int index = 0; index < pieces.Num(); ++index) { + if (single >= 0 && single != index) { + continue; + } + const idBreakablePiece& piece = pieces[index]; + if (piece.rigidBody == nullptr || piece.rigidBody->properties == nullptr) { + continue; + } + idVec3 drawOrigin = piece.rigidBody->currentState.position; + idMat3 drawAxis = piece.rigidBody->currentState.orientation; + if (!piece.rigidBody->active) { + drawOrigin = offset + axis * drawOrigin; + drawAxis *= axis; + } + GameLib_DrawEffectTraceModel(broadPhase.traceModelCache, + piece.rigidBody->properties->traceModelIndex, index, + drawOrigin, drawAxis); + } +} + +void idEffectPhysicsPieceEmitter::Hide() { + hidden = true; + if (model != nullptr) { + GameLib_DiscreteAnimationSetHidden(model, true); + } +} + +void idEffectPhysicsPieceEmitter::Show() { + hidden = false; + if (model != nullptr && !hiddenOverride) { + GameLib_DiscreteAnimationSetHidden(model, false); + } +} diff --git a/source/engine/gamelib/effectphysics/effectphysicspieceemitter.h b/source/engine/gamelib/effectphysics/effectphysicspieceemitter.h index f3ad1bc..a0c6105 100644 --- a/source/engine/gamelib/effectphysics/effectphysicspieceemitter.h +++ b/source/engine/gamelib/effectphysics/effectphysicspieceemitter.h @@ -1,49 +1,102 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicspieceemitter.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effectphysics/effectphysicsrigidbody.h" +#include "idlib/containers/list.h" +#include "idlib/math/random.h" +class idClip; +class idDeclBreakable; +class idDeclImpactSound; +class idMaterial; +class idRenderModelDiscreteAnimation; +class idRenderModelEffects; +class idSoundWorld; +class idTraceModel; +class idTraceModelCache; -// IDA Local Type ordinal 14580; PDB kind: class. -class idEffectPhysicsPieceEmitter::idBreakablePiece -{ +// PC-side copy of the physics fields read from idDeclBreakable. The engine +// declaration adapter fills it, keeping GameLib independent of the unfinished +// declaration/render-model class hierarchy. +struct idEffectPhysicsPieceSettings { + float linearFriction; + float angularFriction; + float contactFriction; + float linearFrictionWater; + float angularFrictionWater; + float bouncyness; + idVec3 gravityVector; + bool worldCollisionOnly; + bool simplePointCollision; + float crazyBounceChance; + float stopSpeed; + float maxLinearVelocity; + float maxAngularVelocity; + int clipMask; +}; + +class alignas(4) idEffectPhysicsPieceEmitter { public: - idEffectPhysicsRigidBody *rigidBody; - idEffectPhysicsCollision collision; - int emitTime; - int firstCollisionTime; + struct idBreakablePiece { + idEffectPhysicsRigidBody* rigidBody; + idEffectPhysicsCollision collision; + int emitTime; + int firstCollisionTime; + }; + + struct pieceEmitterSound_t { + idSoundWorld* soundWorld; + const idDeclImpactSound* impactTable; + int nextSoundTime; + float minBounceVelSqr; + }; + + idEffectPhysicsPieceEmitter(); + ~idEffectPhysicsPieceEmitter(); + + void Init(idClip* clip, idTraceModelCache* traceModelCache, + idRenderModelDiscreteAnimation* renderModel, + idRenderModelEffects* renderModelEffects, + const pieceEmitterSound_t* sound, int entityNumber); + void Reset(); + void EmitPiece(int emitTime, const idVec3& position, + const idMat3& orientation, const idVec3& impulsePoint, + const idVec3& impulse); + void UpdateSimulation(const idVec3& origin, const idMat3& axis, + int currentTime, int gameMsPerFrame); + void UpdateModel(const idVec3& activeDrawScale3D); + void SubmitCollisionQueries(bool worldCollisionModelOnly); + void DrawCollisionModels(int single, const idVec3& offset, + const idMat3& axis); + void Hide(); + void Show(); + + idList properties; + idList pieces; + idEffectPhysicsBroadPhase broadPhase; + idRandom random; + float pieceMass; + float pieceCollisionAgeDecay; + float pieceCollisionVolumeDecay; + idRenderModelDiscreteAnimation* model; + const idDeclBreakable* decl; + idRenderModelEffects* modelEffects; + pieceEmitterSound_t soundInfo; + int pieceLifeTime; + float pieceFriction; + int pieceIndex; + int numPiecesInUse; + bool hidden; + bool hiddenOverride; + +private: + idEffectPhysicsProperties* GetPropertiesForTraceModel( + const idTraceModel& traceModel, const idMaterial* material); + void ApplyDeclSettings(idEffectPhysicsProperties& target) const; }; -// IDA Local Type ordinal 14592; PDB kind: struct. -struct idEffectPhysicsPieceEmitter::pieceEmitterSound_t -{ - idSoundWorld *soundWorld; - const idDeclImpactSound *impactTable; - int nextSoundTime; - float minBounceVelSqr; -}; - -// IDA Local Type ordinal 14593; PDB kind: class. -class __declspec(align(4)) idEffectPhysicsPieceEmitter -{ -public: - idList properties; - idList pieces; - idEffectPhysicsBroadPhase broadPhase; - idRandom random; - float pieceMass; - float pieceCollisionAgeDecay; - float pieceCollisionVolumeDecay; - idRenderModelDiscreteAnimation *model; - const idDeclBreakable *decl; - idRenderModelEffects *modelEffects; - idEffectPhysicsPieceEmitter::pieceEmitterSound_t soundInfo; - int pieceLifeTime; - float pieceFriction; - int pieceIndex; - int numPiecesInUse; - bool hidden; - bool hiddenOverride; -}; +static_assert(sizeof(idEffectPhysicsPieceEmitter::idBreakablePiece) == 40, + "Recovered idBreakablePiece ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectPhysicsPieceEmitter) == 168, + "Recovered idEffectPhysicsPieceEmitter ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicsproperties.cpp b/source/engine/gamelib/effectphysics/effectphysicsproperties.cpp new file mode 100644 index 0000000..fbc686d --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicsproperties.cpp @@ -0,0 +1,93 @@ +#include "gamelib/effectphysics/effectphysicsproperties.h" + +#include + +idEffectPhysicsProperties::idEffectPhysicsProperties() + : broadPhase(nullptr) + , traceModelIndex(-1) + , mass(1.0f) { +} + +void idEffectPhysicsProperties::Setup( + idEffectPhysicsBroadPhase *broadPhase_, const int traceModelIndex_, + const float requestedMass) { + linearFriction = 0.60000002f; + angularFriction = 0.60000002f; + contactFriction = 0.5f; + linearFrictionWater = 1.0f; + angularFrictionWater = 1.0f; + bouncyness = 0.60000002f; + clipMask = 1; + gravityVector.Set(0.0f, 0.0f, -250.0f); + worldCollisionOnly = false; + simplePointCollision = false; + crazyBounceChance = 0.0f; + inverseMass = 1.0f; + centerOfMass.Zero(); + inertiaTensor = idMat3(1.0f); + inverseInertiaTensor = idMat3(1.0f); + maxLinearVelocity = 5000.0f; + stopSpeed = 10.0f; + maxAngularVelocity = 3.1415927f * 4.0f; + traceModelIndex = traceModelIndex_; + broadPhase = broadPhase_; + + mass = 0.0f; + if (broadPhase != nullptr && broadPhase->traceModelCache != nullptr) { + broadPhase->traceModelCache->GetMassProperties(traceModelIndex, 1.0f, + mass, centerOfMass, inertiaTensor); + } + if (mass <= 0.0f) { + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor = idMat3(1.0f); + } + + const float massScale = requestedMass / mass; + for (int row = 0; row < 3; ++row) { + for (int column = 0; column < 3; ++column) { + inertiaTensor[row][column] *= massScale; + } + } + mass = requestedMass; + + const float diagonal[3] = { + inertiaTensor[0][0], inertiaTensor[1][1], inertiaTensor[2][2] + }; + int minimumAxis = 0; + if (diagonal[1] < diagonal[minimumAxis]) { + minimumAxis = 1; + } + if (diagonal[2] < diagonal[minimumAxis]) { + minimumAxis = 2; + } + + if (diagonal[minimumAxis] > 0.0f) { + idMat3 inertiaScale(1.0f); + for (int axis = 0; axis < 3; ++axis) { + inertiaScale[axis][axis] = diagonal[axis] + / diagonal[minimumAxis]; + } + if (inertiaScale[0][0] > 4.0f || inertiaScale[1][1] > 4.0f + || inertiaScale[2][2] > 4.0f) { + for (int axis = 0; axis < 3; ++axis) { + if (axis != minimumAxis && diagonal[axis] != 0.0f) { + inertiaScale[axis][axis] = diagonal[minimumAxis] * 4.0f + / diagonal[axis]; + } + } + inertiaTensor *= inertiaScale; + } + } + + inverseMass = mass != 0.0f ? 1.0f / mass : 1.0f; + inverseInertiaTensor = inertiaTensor; + if (!inverseInertiaTensor.InverseSelf()) { + inverseInertiaTensor = idMat3(1.0f); + } + for (int row = 0; row < 3; ++row) { + for (int column = 0; column < 3; ++column) { + inverseInertiaTensor[row][column] *= 1.0f / 6.0f; + } + } +} diff --git a/source/engine/gamelib/effectphysics/effectphysicsproperties.h b/source/engine/gamelib/effectphysics/effectphysicsproperties.h index 7c713d8..b8e807a 100644 --- a/source/engine/gamelib/effectphysics/effectphysicsproperties.h +++ b/source/engine/gamelib/effectphysics/effectphysicsproperties.h @@ -1,5 +1,8 @@ #pragma once +#include "gamelib/effectphysics/effectphysicsbroadphase.h" +#include "idlib/math/vector.h" + // Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. // Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicsproperties.h // Recovered logical types: 1 @@ -10,6 +13,10 @@ class idEffectPhysicsProperties { public: + idEffectPhysicsProperties(); + void Setup(idEffectPhysicsBroadPhase *broadPhase, int traceModelIndex, + float mass); + idEffectPhysicsBroadPhase *broadPhase; int traceModelIndex; int clipMask; @@ -32,3 +39,8 @@ public: float maxLinearVelocity; float maxAngularVelocity; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectPhysicsProperties) == 160, + "Recovered idEffectPhysicsProperties ABI changed"); +#endif diff --git a/source/engine/gamelib/effectphysics/effectphysicsrigidbody.cpp b/source/engine/gamelib/effectphysics/effectphysicsrigidbody.cpp new file mode 100644 index 0000000..ecfdca5 --- /dev/null +++ b/source/engine/gamelib/effectphysics/effectphysicsrigidbody.cpp @@ -0,0 +1,262 @@ +#include "gamelib/effectphysics/effectphysicsrigidbody.h" + +#include "cm/jobs/collisionresults.h" +#include "gamelib/physics/physics.h" + +#include +#include + +namespace { + +constexpr float kRadToDeg = 57.29577951308232f; +constexpr float kVelocityEpsilon = 1.0e-8f; + +idMat3 WorldInverseInertia(const idMat3& orientation, + const idMat3& inverseInertiaTensor) { + return orientation * inverseInertiaTensor * orientation.Transpose(); +} + +idVec3 PointVelocity(const idEffectPhysicsRigidBody::effectRBState_t& state, + const idEffectPhysicsProperties& properties, const idVec3& point) { + const idVec3 linearVelocity = + state.linearMomentum * properties.inverseMass; + const idVec3 angularVelocity = + WorldInverseInertia(state.orientation, + properties.inverseInertiaTensor) * state.angularMomentum; + return linearVelocity + angularVelocity.Cross(point - state.position); +} + +} // namespace + +idEffectPhysicsRigidBody::idEffectPhysicsRigidBody() + : properties(nullptr) + , currentState{} + , SPObject() + , active(false) + , settled(false) { +} + +idEffectPhysicsRigidBody::~idEffectPhysicsRigidBody() { + if (properties != nullptr && properties->broadPhase != nullptr) { + properties->broadPhase->RemoveObject(&SPObject); + } + properties = nullptr; + active = false; +} + +void idEffectPhysicsRigidBody::Setup( + const idEffectPhysicsProperties* const props) { + if (properties != nullptr && properties->broadPhase != nullptr) { + properties->broadPhase->RemoveObject(&SPObject); + } + + properties = props; + currentState.position.Zero(); + currentState.orientation = idMat3(1.0f); + currentState.linearMomentum.Zero(); + currentState.angularMomentum.Zero(); + currentState.externalForce.Zero(); + currentState.externalTorque.Zero(); + active = false; + settled = false; + + if (props == nullptr) { + SPObject.traceModelIndex = -1; + return; + } + SPObject.traceModelIndex = props->traceModelIndex; + SPObject.motion.worldCollisionOnly = props->worldCollisionOnly; + SPObject.clipMask = props->clipMask; + if (props->broadPhase != nullptr) { + props->broadPhase->AddObject(&SPObject, currentState.position, + currentState.orientation, nullptr); + } +} + +void idEffectPhysicsRigidBody::CapVelocity( + const float deltaTime, effectRBState_t& current) const { + (void)deltaTime; + if (properties == nullptr) { + return; + } + + const idVec3 linearVelocity = + current.linearMomentum * properties->inverseMass; + const float linearSpeed = linearVelocity.Length(); + if (properties->maxLinearVelocity > 0.0f && + linearSpeed > properties->maxLinearVelocity) { + current.linearMomentum = current.linearMomentum * + (properties->maxLinearVelocity / linearSpeed); + } + + const idVec3 angularVelocity = + WorldInverseInertia(current.orientation, + properties->inverseInertiaTensor) * current.angularMomentum; + const float angularSpeed = angularVelocity.Length(); + if (properties->maxAngularVelocity > 0.0f && + angularSpeed > properties->maxAngularVelocity) { + current.angularMomentum = current.angularMomentum * + (properties->maxAngularVelocity / angularSpeed); + } +} + +void idEffectPhysicsRigidBody::CollisionImpulse(trace_t& collision, + const impactInfo_t& impact, const float extraMomentumDecay, + effectRBState_t& state, idVec3& normalVelocity, idVec3& impulse) const { + normalVelocity.Zero(); + impulse.Zero(); + if (properties == nullptr) { + return; + } + + idVec3 normal = collision.c.normal; + if (normal.NormalizeFast() <= kVelocityEpsilon) { + return; + } + + const idVec3 bodyVelocity = + PointVelocity(state, *properties, collision.c.point); + const idVec3 relativeVelocity = bodyVelocity - impact.velocity; + const float velocityIntoSurface = relativeVelocity.Dot(normal); + normalVelocity = normal * velocityIntoSurface; + if (velocityIntoSurface >= 0.0f) { + return; + } + + const idVec3 arm = collision.c.point - state.position; + const idVec3 cross = arm.Cross(normal); + const idVec3 inertiaCross = + WorldInverseInertia(state.orientation, + properties->inverseInertiaTensor) * cross; + const float denominator = properties->inverseMass + impact.invMass + + normal.Dot(inertiaCross.Cross(arm)); + if (denominator <= kVelocityEpsilon) { + return; + } + + const float restitution = (std::max)(0.0f, properties->bouncyness); + float magnitude = -(1.0f + restitution) * velocityIntoSurface / + denominator; + magnitude *= (std::max)(0.0f, 1.0f - extraMomentumDecay); + impulse = normal * magnitude; + + // The recovered path applies Coulomb contact friction after the normal + // impulse. Preserve that behavior without the original VMX shortcuts. + idVec3 tangent = relativeVelocity - normal * velocityIntoSurface; + const float tangentSpeed = tangent.NormalizeFast(); + if (tangentSpeed > kVelocityEpsilon && properties->contactFriction > 0.0f) { + const float frictionMagnitude = (std::min)( + tangentSpeed / (properties->inverseMass + impact.invMass + + kVelocityEpsilon), + magnitude * properties->contactFriction); + impulse = impulse - tangent * frictionMagnitude; + } + + state.linearMomentum = state.linearMomentum + impulse; + state.angularMomentum = state.angularMomentum + arm.Cross(impulse); +} + +void idEffectPhysicsRigidBody::ApplyImpulse(const idVec3& point, + const idVec3& impulse) { + settled = false; + currentState.linearMomentum = currentState.linearMomentum + impulse; + // The Xbox dump spells this as impulse x (point-position), which is the + // negative of the conventional r x J ordering. + currentState.angularMomentum = currentState.angularMomentum + + impulse.Cross(point - currentState.position); +} + +void idEffectPhysicsRigidBody::Evolve(const float deltaTime, + const effectRBState_t& current, effectRBDelta_t& delta) const { + delta.translation.Zero(); + delta.rotation = idRotation(); + delta.linearForce.Zero(); + delta.angularForce.Zero(); + if (properties == nullptr) { + return; + } + + const idVec3 linearVelocity = + current.linearMomentum * properties->inverseMass; + const idVec3 angularVelocity = + WorldInverseInertia(current.orientation, + properties->inverseInertiaTensor) * current.angularMomentum; + + delta.translation = linearVelocity * deltaTime; + idVec3 rotationAxis = angularVelocity; + const float angularSpeed = rotationAxis.NormalizeFast(); + if (angularSpeed <= kVelocityEpsilon) { + rotationAxis.Set(0.0f, 0.0f, 1.0f); + } + delta.rotation = idRotation(current.position, rotationAxis, + -angularSpeed * deltaTime * kRadToDeg); + + const idVec3 totalForce = current.externalForce + + properties->gravityVector * properties->mass - + linearVelocity * properties->linearFriction; + const idVec3 totalTorque = current.externalTorque - + angularVelocity * properties->angularFriction; + delta.linearForce = totalForce * deltaTime; + delta.angularForce = totalTorque * deltaTime; +} + +void idEffectPhysicsRigidBody::Update(const float deltaTime, + const float extraMomentumDecay, idEffectPhysicsCollision& collision) { + collision.Clear(); + if (!active || settled || properties == nullptr || + properties->broadPhase == nullptr || deltaTime <= 0.0f) { + return; + } + + bool testForSettling = false; + trace_t trace{}; + if (SPObject.query.offset != 0 && + properties->broadPhase->GetMotionResult(&trace, &SPObject)) { + currentState.position = SPObject.origin; + currentState.orientation = SPObject.axis; + if (trace.fraction < 1.0f) { + impactInfo_t impact{}; + impact.invInertiaTensor = idMat3(1.0f); + idVec3 impulse; + CollisionImpulse(trace, impact, extraMomentumDecay, + currentState, collision.collisionNormalVelocity, impulse); + collision.collisionPoint = trace.c.point; + collision.collisionSurfaceType = trace.c.surfaceType; + + const float horizontalSpeed = std::sqrt( + currentState.linearMomentum.x * currentState.linearMomentum.x + + currentState.linearMomentum.y * currentState.linearMomentum.y); + if (horizontalSpeed <= 10.0f && + currentState.linearMomentum.z < 0.0f) { + testForSettling = horizontalSpeed >= 2.0f; + settled = horizontalSpeed < 2.0f; + } + } + } + + CapVelocity(deltaTime, currentState); + effectRBDelta_t delta; + Evolve(deltaTime, currentState, delta); + properties->broadPhase->ApplyMotion( + &SPObject, delta.translation, delta.rotation); + + if (testForSettling && + delta.translation.x * delta.translation.x + + delta.translation.y * delta.translation.y < 1.0e-4f) { + settled = true; + } + + if (settled) { + SPObject.motion.valid = false; + currentState.linearMomentum.Zero(); + currentState.angularMomentum.Zero(); + collision.Clear(); + } else { + currentState.linearMomentum = + currentState.linearMomentum + delta.linearForce; + currentState.angularMomentum = + currentState.angularMomentum + delta.angularForce; + } + currentState.externalForce.Zero(); + currentState.externalTorque.Zero(); +} diff --git a/source/engine/gamelib/effectphysics/effectphysicsrigidbody.h b/source/engine/gamelib/effectphysics/effectphysicsrigidbody.h index 93a4feb..ef88f7c 100644 --- a/source/engine/gamelib/effectphysics/effectphysicsrigidbody.h +++ b/source/engine/gamelib/effectphysics/effectphysicsrigidbody.h @@ -1,38 +1,96 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effectphysics\effectphysicsrigidbody.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effectphysics/effectphysicsproperties.h" +#include "idlib/math/rotation.h" +struct impactInfo_t; +struct trace_t; -// IDA Local Type ordinal 14577; PDB kind: struct. -struct idEffectPhysicsRigidBody::effectRBState_t -{ - idVec3 position; - idMat3 orientation; - idVec3 linearMomentum; - idVec3 angularMomentum; - idVec3 externalForce; - idVec3 externalTorque; +// Recovered type 14579. This record is intentionally independent of the +// renderer/game declaration layer so effect physics can be used by emitters. +struct idEffectPhysicsCollision { + idVec3 collisionPoint; + idVec3 collisionNormalVelocity; + int collisionSurfaceType; + + void Clear() { + collisionPoint.Zero(); + collisionNormalVelocity.Zero(); + collisionSurfaceType = 0; + } }; -// IDA Local Type ordinal 14578; PDB kind: class. -class __declspec(align(8)) idEffectPhysicsRigidBody -{ +class alignas(8) idEffectPhysicsRigidBody { public: - const idEffectPhysicsProperties *properties; - idEffectPhysicsRigidBody::effectRBState_t currentState; - idSPObject SPObject; - bool active; - bool settled; + struct effectRBState_t { + idVec3 position; + idMat3 orientation; + idVec3 linearMomentum; + idVec3 angularMomentum; + idVec3 externalForce; + idVec3 externalTorque; + }; + + struct effectRBDelta_t { + idVec3 translation; + idRotation rotation; + idVec3 linearForce; + idVec3 angularForce; + }; + + idEffectPhysicsRigidBody(); + ~idEffectPhysicsRigidBody(); + + void Setup(const idEffectPhysicsProperties* props); + void ApplyImpulse(const idVec3& point, const idVec3& impulse); + void Update(float deltaTime, float extraMomentumDecay, + idEffectPhysicsCollision& collision); + + void SetPosition(const idVec3& position, const idMat3& orientation) { + currentState.position = position; + currentState.orientation = orientation; + if (properties != nullptr && properties->broadPhase != nullptr) { + properties->broadPhase->SetObjectPosition( + &SPObject, position, orientation, nullptr); + } + } + void SetLinearMomentum(const idVec3& momentum) { + currentState.linearMomentum = momentum; + } + void SetAngularMomentum(const idVec3& momentum) { + currentState.angularMomentum = momentum; + } + void AddForce(const idVec3& force) { + currentState.externalForce = currentState.externalForce + force; + } + void AddTorque(const idVec3& torque) { + currentState.externalTorque = currentState.externalTorque + torque; + } + void Activate() { active = true; settled = false; } + void PutToRest() { settled = true; SPObject.motion.valid = false; } + + const idEffectPhysicsProperties* properties; + effectRBState_t currentState; + idSPObject SPObject; + bool active; + bool settled; + +private: + void CapVelocity(float deltaTime, effectRBState_t& current) const; + void CollisionImpulse(trace_t& collision, const impactInfo_t& impact, + float extraMomentumDecay, effectRBState_t& state, + idVec3& normalVelocity, idVec3& impulse) const; + void Evolve(float deltaTime, const effectRBState_t& current, + effectRBDelta_t& delta) const; }; -// IDA Local Type ordinal 21719; PDB kind: struct. -struct idEffectPhysicsRigidBody::effectRBDelta_t -{ - idVec3 translation; - idRotation rotation; - idVec3 linearForce; - idVec3 angularForce; -}; +static_assert(sizeof(idEffectPhysicsCollision) == 28, + "Recovered idEffectPhysicsCollision ABI changed"); +static_assert(sizeof(idEffectPhysicsRigidBody::effectRBState_t) == 96, + "Recovered effectRBState_t ABI changed"); +static_assert(sizeof(idEffectPhysicsRigidBody::effectRBDelta_t) == 104, + "Recovered effectRBDelta_t ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectPhysicsRigidBody) == 272, + "Recovered idEffectPhysicsRigidBody ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/deferreddecalmanager.cpp b/source/engine/gamelib/effects/deferreddecalmanager.cpp new file mode 100644 index 0000000..6d27b74 --- /dev/null +++ b/source/engine/gamelib/effects/deferreddecalmanager.cpp @@ -0,0 +1,117 @@ +#include "gamelib/effects/deferreddecalmanager.h" + +#include "gamelib/physics/physics.h" +#include "idlib/lib_print.h" + +idClipQuery GameLib_SubmitDeferredDecalLineTrace(idClip* clip, + const idVec3& start, const idVec3& end, int clipMask); +bool GameLib_GetDeferredDecalLineTraceResult(idClip* clip, + idClipQuery& query, trace_t& result); +void GameLib_AddDeferredDecalFromPoint(idRenderModelDecal* decalModel, + const deferredDecalParams_t& parameters, int time, + const idVec3& origin, const idVec3& normal); + +namespace { + +constexpr std::uint64_t RECOVERED_EMPTY_CLIP_QUERY = 0x000007D000000000ULL; + +} // namespace + +idDeferredDecalQuery::idDeferredDecalQuery() { + Reset(); +} + +void idDeferredDecalQuery::Reset() { + theQuery.index = RECOVERED_EMPTY_CLIP_QUERY; + decalParams.decalMat = nullptr; + decalParams.depth = 8.0f; + decalParams.size.Set(32.0f, 32.0f); + decalParams.angle = 0.0f; + decalParams.lifetime = 10000; + decalParams.fadetime = 2000; + decalParams.fadeInEndTime = 0; + origin.Zero(); + normal.Zero(); +} + +idDeferredDecalManager::idDeferredDecalManager() + : lineQueries{} + , numLineQueries(0) + , clip(nullptr) + , decalModel(nullptr) { +} + +void idDeferredDecalManager::Init(idClip* const clip_, + idRenderModelDecal* const decalModel_) { + clip = clip_; + decalModel = decalModel_; +} + +void idDeferredDecalManager::Shutdown() { + numLineQueries = 0; + clip = nullptr; + decalModel = nullptr; + for (idDeferredDecalQuery& query : lineQueries) { + query.Reset(); + } +} + +void idDeferredDecalManager::AddDecalFromLineTrace( + const deferredDecalParams_t* const decalParameters, + const idVec3& start, const idVec3& end, const int clipMask) { + if (clip == nullptr || decalModel == nullptr) { + return; + } + if (numLineQueries >= 16) { + idLibPrint::Warning("Too many deferred decal queries this frame!"); + return; + } + if (decalParameters == nullptr || decalParameters->decalMat == nullptr) { + idLibPrint::Warning( + "Null decal passed in to " + "idDeferredDecalManager::AddDecalFromLineTrace"); + return; + } + + idDeferredDecalQuery& query = lineQueries[numLineQueries]; + query.Reset(); + query.theQuery = GameLib_SubmitDeferredDecalLineTrace( + clip, start, end, clipMask); + query.decalParams = *decalParameters; + ++numLineQueries; +} + +void idDeferredDecalManager::AddDecalFromPoint( + const deferredDecalParams_t* const decalParameters, const int time, + const idVec3& origin, const idVec3& normal) { + if (clip == nullptr || decalModel == nullptr) { + return; + } + if (decalParameters == nullptr || decalParameters->decalMat == nullptr) { + idLibPrint::Warning( + "Null decal passed in to " + "idDeferredDecalManager::AddDecalFromLineTrace"); + return; + } + + GameLib_AddDeferredDecalFromPoint( + decalModel, *decalParameters, time, origin, normal); +} + +void idDeferredDecalManager::Update(const int time) { + if (clip == nullptr || decalModel == nullptr) { + return; + } + + for (int index = 0; index < numLineQueries; ++index) { + idDeferredDecalQuery& query = lineQueries[index]; + trace_t result{}; + if (GameLib_GetDeferredDecalLineTraceResult( + clip, query.theQuery, result) && result.fraction < 1.0f) { + AddDecalFromPoint(&query.decalParams, time, + result.endpos, result.c.normal); + } + query.Reset(); + } + numLineQueries = 0; +} diff --git a/source/engine/gamelib/effects/deferreddecalmanager.h b/source/engine/gamelib/effects/deferreddecalmanager.h index dcb4ad6..74b73f0 100644 --- a/source/engine/gamelib/effects/deferreddecalmanager.h +++ b/source/engine/gamelib/effects/deferreddecalmanager.h @@ -1,58 +1,65 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\deferreddecalmanager.h -// Recovered logical types: 5 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/jobs/collisionquery.h" +#include "idlib/math/vector.h" +#include -// IDA Local Type ordinal 14039; PDB kind: struct. -struct deferredDecalParams_t -{ - const idMaterial *decalMat; - float depth; - idVec2 size; - float angle; - int lifetime; - int fadetime; - int fadeInEndTime; +class idClip; +class idMaterial; +class idRenderModelDecal; + +#ifndef ID_CLIP_QUERY_DEFINED +#define ID_CLIP_QUERY_DEFINED +struct idClipQuery { std::uint64_t index; }; +#endif + +struct deferredDecalParams_t { + const idMaterial* decalMat; + float depth; + idVec2 size; + float angle; + int lifetime; + int fadetime; + int fadeInEndTime; }; -// IDA Local Type ordinal 14040; PDB kind: class. -class idDeferredDecalQuery -{ +class idDeferredDecalQuery { public: - idClipQuery theQuery; - deferredDecalParams_t decalParams; - idVec3 origin; - idVec3 normal; + idDeferredDecalQuery(); + void Reset(); + + idClipQuery theQuery; + deferredDecalParams_t decalParams; + idVec3 origin; + idVec3 normal; }; -// IDA Local Type ordinal 14135; PDB kind: class. -class __declspec(align(8)) idDeferredDecalManager -{ +class alignas(8) idDeferredDecalManager { public: - idDeferredDecalQuery lineQueries[16]; - int numLineQueries; - idClip *clip; - idRenderModelDecal *decalModel; + idDeferredDecalManager(); + + void Init(idClip* clip_, idRenderModelDecal* decalModel_); + void Shutdown(); + void AddDecalFromLineTrace(const deferredDecalParams_t* decalParameters, + const idVec3& start, const idVec3& end, int clipMask); + void AddDecalFromPoint(const deferredDecalParams_t* decalParameters, + int time, const idVec3& origin, const idVec3& normal); + void Update(int time); + + idDeferredDecalQuery lineQueries[16]; + int numLineQueries; + idClip* clip; + idRenderModelDecal* decalModel; }; -// IDA Local Type ordinal 15595; PDB kind: struct. -struct __declspec(align(8)) idDeferredFireManager::deferredFire_t -{ - idFireParms fireParms; - idClipQuery queryHandles[16]; - idTestFireResults fireResults; - unsigned int handle; - int numTraces; -}; - -// IDA Local Type ordinal 15596; PDB kind: class. -class idDeferredFireManager -{ -public: - idDeferredFireManager::deferredFire_t deferredFireBuffers[2][32]; - unsigned int shotCount; - unsigned int activeBuffer; -}; +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idClipQuery) == 8, + "Recovered idClipQuery ABI changed"); +static_assert(sizeof(deferredDecalParams_t) == 32, + "Recovered deferred-decal parameters ABI changed"); +static_assert(sizeof(idDeferredDecalQuery) == 64, + "Recovered deferred-decal query ABI changed"); +static_assert(sizeof(idDeferredDecalManager) == 1040, + "Recovered idDeferredDecalManager ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/effectsmodelmanager.cpp b/source/engine/gamelib/effects/effectsmodelmanager.cpp new file mode 100644 index 0000000..e5c23e1 --- /dev/null +++ b/source/engine/gamelib/effects/effectsmodelmanager.cpp @@ -0,0 +1,154 @@ +#include "gamelib/effects/effectsmodelmanager.h" + +#include "idlib/lib_print.h" +#include "idlib/text/str.h" + +// Renderer/decl access remains behind explicit PC-port boundaries until the +// renderer and decl translation units join the production source target. +const char* GameLib_GetParticleDeclName(const idDeclParticle* particleDecl); +idRenderModelParticle* GameLib_AllocParticleRenderModel( + const char* modelName, const idDeclParticle* particleDecl); +void GameLib_RetireParticleRenderModel(idRenderModelParticle* model); +bool GameLib_ShowEffectsModelRecyclerStats(); + +namespace { + +int ParticleNameHash(const char* const name) { + int hash = 0; + if (name != nullptr) { + for (const unsigned char* cursor = + reinterpret_cast(name); + *cursor != 0; ++cursor) { + hash = 31 * hash + *cursor; + } + } + return hash; +} + +void PrintRecyclerStats(const char* const action, const char* const name, + const idEffectsModelManager& manager) { + if (!GameLib_ShowEffectsModelRecyclerStats()) { + return; + } + idLibPrint::Printf( + "%s model %s, total models allocated %d inactive models spread %d " + "active models spread %d\n", + action, name, manager.fxPrtModels.Num(), + manager.inactivePrtModelHash.GetSpread(), + manager.activePrtModelHash.GetSpread()); +} + +} // namespace + +idEffectsModelManager::idEffectsModelManager() + : fxPrtModels() + , activePrtModelHash(1024, 1024, TAG_HASHINDEX) + , inactivePrtModelHash(1024, 1024, TAG_HASHINDEX) { +} + +idEffectsModelManager::~idEffectsModelManager() { + Shutdown(); +} + +void idEffectsModelManager::Init() { + fxPrtModels.Clear(); + activePrtModelHash.Clear(); + activePrtModelHash.memTag = TAG_PARTICLE; + inactivePrtModelHash.Clear(); + inactivePrtModelHash.memTag = TAG_PARTICLE; +} + +void idEffectsModelManager::Shutdown() { + activePrtModelHash.Clear(); + inactivePrtModelHash.Clear(); + for (int index = 0; index < fxPrtModels.Num(); ++index) { + fxPrtModel_t& item = fxPrtModels[index]; + if (item.pmodel != nullptr) { + GameLib_RetireParticleRenderModel(item.pmodel); + item.pmodel = nullptr; + } + } + fxPrtModels.Clear(); +} + +void idEffectsModelManager::RecycleParticleFxModel( + const idDeclParticle* const particleDecl, + idRenderModelParticle* const model) { + if (model == nullptr || particleDecl == nullptr) { + idLibPrint::Warning( + "idFXModelRecycler::RecycleEffectModel : invalid model"); + return; + } + + const char* const name = GameLib_GetParticleDeclName(particleDecl); + const int key = ParticleNameHash(name); + int index = activePrtModelHash.First(key); + while (index != -1) { + const fxPrtModel_t& item = fxPrtModels[index]; + if (item.pDecl == particleDecl && item.pmodel == model) { + break; + } + index = activePrtModelHash.Next(index); + } + + if (index == -1) { + if (GameLib_ShowEffectsModelRecyclerStats()) { + idLibPrint::Printf( + "idEffectsModelManager::RecycleParticleFxModel : model %s " + "not found in active list for recycling\n", name); + } + return; + } + + activePrtModelHash.Remove(key, index); + inactivePrtModelHash.Add(key, index); + PrintRecyclerStats("Recycling", name, *this); +} + +idRenderModelParticle* idEffectsModelManager::GetNextParticleEffectModel( + const idDeclParticle* const particleDecl) { + if (particleDecl == nullptr) { + idLibPrint::Warning( + "idEffectsModelManager::GetNextParticleEffectModel : " + "particleDecl NULL"); + return nullptr; + } + + const char* const name = GameLib_GetParticleDeclName(particleDecl); + const int key = ParticleNameHash(name); + int index = inactivePrtModelHash.First(key); + while (index != -1) { + const fxPrtModel_t& item = fxPrtModels[index]; + if (item.pDecl == particleDecl && item.pmodel != nullptr) { + inactivePrtModelHash.Remove(key, index); + activePrtModelHash.Add(key, index); + PrintRecyclerStats("Found inactive", name, *this); + return item.pmodel; + } + index = inactivePrtModelHash.Next(index); + } + + if (fxPrtModels.Num() >= fxPrtModels.Max()) { + idLibPrint::Warning( + "idFXModelRecycler::GetParticleFxModel: Could not allocate " + "model %s, out of slots", name); + return nullptr; + } + + idStr modelName(name); + modelName.SetFileExtension("prt"); + idRenderModelParticle* const model = GameLib_AllocParticleRenderModel( + modelName.c_str(), particleDecl); + if (model == nullptr) { + idLibPrint::Warning( + "idFXModelRecycler::GetParticleFxModel: Could not allocate a " + "rendermodel for '%s'", name); + return nullptr; + } + + const fxPrtModel_t item = {model, particleDecl}; + index = fxPrtModels.Append(item); + activePrtModelHash.Add(key, index); + PrintRecyclerStats("Allocated new", name, *this); + return model; +} diff --git a/source/engine/gamelib/effects/effectsmodelmanager.h b/source/engine/gamelib/effects/effectsmodelmanager.h index c43b9f2..3aab8fa 100644 --- a/source/engine/gamelib/effects/effectsmodelmanager.h +++ b/source/engine/gamelib/effects/effectsmodelmanager.h @@ -1,23 +1,36 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\effectsmodelmanager.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/containers/hashindex.h" +#include "idlib/containers/staticlist.h" +class idDeclParticle; +class idRenderModelParticle; -// IDA Local Type ordinal 14034; PDB kind: struct. -struct idEffectsModelManager::fxPrtModel_t -{ - idRenderModelParticle *pmodel; - const idDeclParticle *pDecl; -}; - -// IDA Local Type ordinal 14037; PDB kind: class. -class idEffectsModelManager -{ +class idEffectsModelManager { public: - idStaticList fxPrtModels; - idHashIndex activePrtModelHash; - idHashIndex inactivePrtModelHash; + struct fxPrtModel_t { + idRenderModelParticle* pmodel; + const idDeclParticle* pDecl; + }; + + idEffectsModelManager(); + ~idEffectsModelManager(); + + void Init(); + void Shutdown(); + idRenderModelParticle* GetNextParticleEffectModel( + const idDeclParticle* particleDecl); + void RecycleParticleFxModel(const idDeclParticle* particleDecl, + idRenderModelParticle* model); + + idStaticList fxPrtModels; + idHashIndex activePrtModelHash; + idHashIndex inactivePrtModelHash; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idEffectsModelManager::fxPrtModel_t) == 8, + "Recovered particle-model record ABI changed"); +static_assert(sizeof(idEffectsModelManager) == 3280, + "Recovered idEffectsModelManager ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/ribbon.cpp b/source/engine/gamelib/effects/ribbon.cpp new file mode 100644 index 0000000..821070e --- /dev/null +++ b/source/engine/gamelib/effects/ribbon.cpp @@ -0,0 +1,268 @@ +#include "gamelib/effects/ribbon.h" + +#include +#include + +bool GameLib_GetRibbonParameters(const idDeclRibbon* decl, + idRibbonParameters& parameters); +void GameLib_UpdateRibbonGeometry(idRenderModelBeam* beamModel, + const idDeclRibbon* decl, const idRibbon::ribbonNode_t* nodes, + int firstNode, int numNodes, int currentTime, + const idMat3& viewAxis, const idVec3& viewOrigin, + bool explicitBothEdges, const idVec4& globalColor); + +namespace { + +idVec4 MultiplyColor(const idVec4& left, const idVec4& right) { + return idVec4(left.x * right.x, left.y * right.y, + left.z * right.z, left.w * right.w); +} + +idVec3 Lerp(const idVec3& from, const idVec3& to, const float fraction) { + return from + (to - from) * fraction; +} + +} // namespace + +idRibbon::idRibbon(const idDeclRibbon* const decl) + : ribbonState(RIBBON_INACTIVE) + , spawnOrg() + , spawnTime(0) + , numActiveNodes(0) + , currHeadIdx(-1) + , startVariance(0.0f) + , prevTime(-1) + , prevOrigin() + , prevOrigin2() + , ribbonDecl(decl) + , globalColor(1.0f, 1.0f, 1.0f, 1.0f) + , ribbonNodes{} + , explicitDefineBothEdges(false) { + ResetNodes(); + // The recovered constructor advances the same LCG once for start phase. + static unsigned int ribbonSeed = 0; + ribbonSeed = 1664525u * ribbonSeed + 1013904223u; + startVariance = ((ribbonSeed >> 10) & 0x7FFFu) * + (1.0f / 32768.0f); +} + +void idRibbon::ResetNodes() { + prevTime = -1; + currHeadIdx = -1; + ribbonState = RIBBON_INACTIVE; + spawnTime = 0; + numActiveNodes = 0; + explicitDefineBothEdges = false; + for (ribbonNode_t& node : ribbonNodes) { + node.origin.Zero(); + node.origin2.Zero(); + node.velocity.Zero(); + node.orientVec.Zero(); + node.color = idVec4(0.0f, 0.0f, 0.0f, 0.0f); + node.spawnTime = 0; + node.nodeLength = 0.0f; + } +} + +void idRibbon::StartRibbon(const int time, const idVec3& origin) { + ResetNodes(); + ribbonState = RIBBON_ACTIVE; + spawnOrg = origin; + prevOrigin = origin; + prevOrigin2 = origin; + spawnTime = time; + prevTime = time; +} + +void idRibbon::CalcTurbulence(const idVec3& origin, const idMat3& axis, + idVec3& turbulence) const { + turbulence.Zero(); + idRibbonParameters parms{}; + if (!GameLib_GetRibbonParameters(ribbonDecl, parms) || + std::fabs(parms.turbulenceMagnitude) <= 1.1920929e-7f) { + return; + } + const float frequency = parms.turbulenceFrequency; + const idVec4& magic = parms.turbulenceMagic; + const idVec3 local( + std::sin(magic.x * origin.x * frequency) - + std::cos(magic.w * origin.y * frequency), + std::sin(magic.y * origin.y * frequency) - + std::cos(magic.w * origin.x * frequency), + std::cos(magic.z * origin.z * frequency) - + std::sin(magic.w * origin.z * frequency)); + turbulence = axis * (local * parms.turbulenceMagnitude); +} + +void idRibbon::CalcHelix(const int currentTime, const idVec3& origin, + const idMat3& axis, const idVec3& velocity, idVec3& helix) const { + (void)origin; + helix.Zero(); + idRibbonParameters parms{}; + if (!GameLib_GetRibbonParameters(ribbonDecl, parms) || + parms.helixRadius <= 1.1920929e-7f) { + return; + } + const float phase = parms.helixRotationScale * + static_cast(currentTime - spawnTime) + + startVariance * 6.28318530718f; + idVec3 direction = velocity; + if (direction.NormalizeFast() == 0.0f) { + direction = axis[0]; + } + idVec3 side = direction.Cross(axis[2]); + if (side.NormalizeFast() == 0.0f) { + side = axis[1]; + } + idVec3 up = direction.Cross(side); + up.NormalizeFast(); + helix = (side * std::cos(phase) + up * std::sin(phase)) * + parms.helixRadius; +} + +void idRibbon::AddNode(const int currentTime, const idVec3& origin, + const idVec3& origin2, const idMat3& axis, const idVec3& velocity, + const idVec4& color) { + idRibbonParameters parms{}; + if (!GameLib_GetRibbonParameters(ribbonDecl, parms)) { + return; + } + explicitDefineBothEdges = &origin != &origin2 || + (origin - origin2).LengthSqr() > 0.0f; + idVec3 turbulence1, turbulence2, helix1, helix2; + CalcTurbulence(origin, axis, turbulence1); + CalcHelix(currentTime, origin, axis, velocity, helix1); + if (explicitDefineBothEdges) { + CalcTurbulence(origin2, axis, turbulence2); + CalcHelix(currentTime, origin2, axis, velocity, helix2); + } else { + turbulence2 = turbulence1; + helix2 = helix1; + } + + currHeadIdx = (currHeadIdx + 1) % MAX_NODES; + ribbonNode_t& node = ribbonNodes[currHeadIdx]; + node.spawnTime = currentTime; + node.origin = origin + turbulence1 + helix1; + node.origin2 = origin2 + turbulence2 + helix2; + node.velocity = velocity + axis * parms.velocity + + turbulence1 * parms.turbulenceVelocity + + helix1 * parms.helixVelocity; + node.color = MultiplyColor(parms.color, color); + node.orientVec = axis[0]; + if (numActiveNodes > 0) { + const ribbonNode_t& previous = + ribbonNodes[(currHeadIdx + MAX_NODES - 1) % MAX_NODES]; + node.nodeLength = (node.origin - previous.origin).Length(); + } else { + node.nodeLength = 0.0f; + } + if (numActiveNodes < MAX_NODES) { + ++numActiveNodes; + } +} + +void idRibbon::UpdatePosition(const int currentTime, const idVec3& origin, + const idMat3& axis, const idVec3& velocity, const idVec4& color) { + UpdatePosition(currentTime, origin, origin, axis, velocity, color); +} + +void idRibbon::UpdatePosition(const int currentTime, const idVec3& origin, + const idVec3& origin2, const idMat3& axis, const idVec3& velocity, + const idVec4& color) { + idRibbonParameters parms{}; + if (ribbonState != RIBBON_ACTIVE || + !GameLib_GetRibbonParameters(ribbonDecl, parms)) { + return; + } + const float distanceSqr = (prevOrigin - origin).LengthSqr(); + if (parms.minNodeDistance > 0.0f && + distanceSqr <= parms.minNodeDistance * parms.minNodeDistance) { + return; + } + int subdivisions = 1; + if (parms.nodeSubdivision > 0.0f && prevTime >= 0) { + subdivisions = (std::min)(MAX_NODES, + static_cast(std::sqrt(distanceSqr) / + parms.nodeSubdivision) + 1); + } + for (int index = 1; index <= subdivisions; ++index) { + const float fraction = index / static_cast(subdivisions); + const int nodeTime = prevTime + static_cast( + (currentTime - prevTime) * fraction); + AddNode(nodeTime, Lerp(prevOrigin, origin, fraction), + Lerp(prevOrigin2, origin2, fraction), axis, velocity, color); + } + prevTime = currentTime; + prevOrigin = origin; + prevOrigin2 = origin2; +} + +void idRibbon::RemoveOldNodes(const int currentTime) { + if (numActiveNodes == 0) { + return; + } + idRibbonParameters parms{}; + if (!GameLib_GetRibbonParameters(ribbonDecl, parms)) { + return; + } + if (parms.maxLength != 0.0f) { + float length = 0.0f; + int retained = 0; + for (int offset = 0; offset < numActiveNodes; ++offset) { + const int index = (currHeadIdx - offset + MAX_NODES) % MAX_NODES; + length += ribbonNodes[index].nodeLength; + if (length <= parms.maxLength) ++retained; + } + numActiveNodes = retained; + } + while (numActiveNodes > 0) { + const int oldest = + (currHeadIdx - numActiveNodes + 1 + MAX_NODES) % MAX_NODES; + if (parms.duration > 0 && + ribbonNodes[oldest].spawnTime + parms.duration < currentTime) { + --numActiveNodes; + } else if (parms.duration <= 0 && + ribbonState == RIBBON_DEACTIVATING) { + --numActiveNodes; + } else { + break; + } + } + if (numActiveNodes <= 0) { + numActiveNodes = 0; + explicitDefineBothEdges = false; + ribbonState = RIBBON_INACTIVE; + } +} + +int idRibbon::GetRemainingTime(const int currentTime) const { + idRibbonParameters parms{}; + if (!GameLib_GetRibbonParameters(ribbonDecl, parms) || + parms.duration <= 0) { + return 0; + } + int remaining = 0; + for (int offset = 0; offset < numActiveNodes; ++offset) { + const int index = + (currHeadIdx - offset + MAX_NODES) % MAX_NODES; + remaining = (std::max)(remaining, + ribbonNodes[index].spawnTime + parms.duration - currentTime); + } + return remaining; +} + +void idRibbon::UpdateGeometry(const int currentTime, + idRenderModelBeam* const beamModel, const idMat3& viewAxis, + const idVec3& viewOrigin) { + RemoveOldNodes(currentTime); + if (beamModel == nullptr) { + return; + } + const int firstNode = numActiveNodes > 0 + ? (currHeadIdx - numActiveNodes + 1 + MAX_NODES) % MAX_NODES + : 0; + GameLib_UpdateRibbonGeometry(beamModel, ribbonDecl, ribbonNodes, + firstNode, numActiveNodes, currentTime, viewAxis, viewOrigin, + explicitDefineBothEdges, globalColor); +} diff --git a/source/engine/gamelib/effects/ribbon.h b/source/engine/gamelib/effects/ribbon.h index 8df77cf..5ce70ae 100644 --- a/source/engine/gamelib/effects/ribbon.h +++ b/source/engine/gamelib/effects/ribbon.h @@ -1,38 +1,105 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\ribbon.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/math/vector.h" +class idDeclRibbon; +class idMaterial; +class idRenderModelBeam; -// IDA Local Type ordinal 14136; PDB kind: struct. -struct idRibbon::ribbonNode_t -{ - idVec3 origin; - idVec3 origin2; - idVec3 velocity; - idVec3 orientVec; - idVec4 color; - int spawnTime; - float nodeLength; +enum ribbonState_t : int { + RIBBON_ACTIVE = 0, + RIBBON_DEACTIVATING = 1, + RIBBON_INACTIVE = 2 }; -// IDA Local Type ordinal 14137; PDB kind: class. -class __declspec(align(4)) idRibbon -{ +struct idRibbonParameters { + const idMaterial* material; + int duration; + float maxLength; + float fadeInFraction; + float fadeOutFraction; + idVec3 gravity; + idVec3 velocity; + float startWidth; + float endWidth; + idVec4 color; + bool viewOriented; + bool reorientNodes; + bool textureRepeat; + bool textureRepeatStretch; + float textureTMin; + float textureTMax; + float nodeSubdivision; + float minNodeDistance; + float helixRotationScale; + float helixRadius; + float helixVelocity; + idVec2 helixDistortion0; + idVec2 helixDistortion1; + float turbulenceFrequency; + float turbulenceMagnitude; + float turbulenceVelocity; + idVec4 turbulenceMagic; +}; + +class alignas(4) idRibbon { public: - ribbonState_t ribbonState; - idVec3 spawnOrg; - int spawnTime; - int numActiveNodes; - int currHeadIdx; - float startVariance; - int prevTime; - idVec3 prevOrigin; - idVec3 prevOrigin2; - const idDeclRibbon *ribbonDecl; - idVec4 globalColor; - idRibbon::ribbonNode_t ribbonNodes[128]; - bool explicitDefineBothEdges; + static constexpr int MAX_NODES = 128; + + struct ribbonNode_t { + idVec3 origin; + idVec3 origin2; + idVec3 velocity; + idVec3 orientVec; + idVec4 color; + int spawnTime; + float nodeLength; + }; + + explicit idRibbon(const idDeclRibbon* decl); + + void StartRibbon(int time, const idVec3& origin); + void StopRibbon() { + if (ribbonState == RIBBON_ACTIVE) ribbonState = RIBBON_DEACTIVATING; + } + void UpdatePosition(int currentTime, const idVec3& origin, + const idMat3& axis, const idVec3& velocity, const idVec4& color); + void UpdatePosition(int currentTime, const idVec3& origin, + const idVec3& origin2, const idMat3& axis, + const idVec3& velocity, const idVec4& color); + void UpdateGeometry(int currentTime, idRenderModelBeam* beamModel, + const idMat3& viewAxis, const idVec3& viewOrigin); + void RemoveOldNodes(int currentTime); + int GetRemainingTime(int currentTime) const; + + ribbonState_t ribbonState; + idVec3 spawnOrg; + int spawnTime; + int numActiveNodes; + int currHeadIdx; + float startVariance; + int prevTime; + idVec3 prevOrigin; + idVec3 prevOrigin2; + const idDeclRibbon* ribbonDecl; + idVec4 globalColor; + ribbonNode_t ribbonNodes[MAX_NODES]; + bool explicitDefineBothEdges; + +private: + void ResetNodes(); + void AddNode(int currentTime, const idVec3& origin, + const idVec3& origin2, const idMat3& axis, + const idVec3& velocity, const idVec4& color); + void CalcTurbulence(const idVec3& origin, const idMat3& axis, + idVec3& turbulence) const; + void CalcHelix(int currentTime, const idVec3& origin, + const idMat3& axis, const idVec3& velocity, idVec3& helix) const; }; + +static_assert(sizeof(idRibbon::ribbonNode_t) == 72, + "Recovered ribbon node ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idRibbon) == 9300, + "Recovered idRibbon ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/ribbonmanager.h b/source/engine/gamelib/effects/ribbonmanager.h index 23fa2c3..e2394a4 100644 --- a/source/engine/gamelib/effects/ribbonmanager.h +++ b/source/engine/gamelib/effects/ribbonmanager.h @@ -1,32 +1,62 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\ribbonmanager.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/containers/staticlist.h" +#include "gamelib/effects/ribbon.h" - -// IDA Local Type ordinal 14140; PDB kind: struct. -struct idRibbonModelManager::ribbonEffects_t -{ - idStaticList ribbons; - int next; -}; - -// IDA Local Type ordinal 14143; PDB kind: class. -class idRibbonModelManager -{ +class idDeclRibbon; +class idRenderModelBeam; +class idRibbonModelManager { public: - idStaticList ribbonEffects; + struct ribbonEffects_t { + // Materialized in the authoritative effects/ribbonmanager.h dump. + ribbonEffects_t() + : ribbons() + , next(0) { + } + + idStaticList ribbons; + int next; + }; + + idRibbonModelManager(); + ~idRibbonModelManager(); + + void CreateRibbonEffectModelType(const idDeclRibbon* ribbonDecl); + idRibbon* GetNextRibbonEffectModel(const idDeclRibbon* ribbonDecl); + void Shutdown(); + + idStaticList ribbonEffects; + +private: + int FindRibbonEffect(const idDeclRibbon* ribbonDecl) const; }; -// IDA Local Type ordinal 14211; PDB kind: class. -class __declspec(align(4)) idRibbonManager -{ +class idRibbonManager { public: - idRibbon *ribbon; - const idDeclRibbon *ribbonDecl; - idRenderModelBeam *beamModel; - idRibbonModelManager *modelManager; - bool isInitialized; + idRibbonManager(); + ~idRibbonManager(); + + void Init(const idDeclRibbon* ribbonDecl, idRenderModelBeam* beamModel, + idRibbonModelManager* modelManager); + void Shutdown(); + void StartRibbon(int spawnTime, const idVec3& spawnOrigin); + void StopRibbon(); + bool UpdateRibbon(int time, const idVec3& origin, const idMat3& axis, + const idVec3& velocity, const idVec4& color, + const idVec3& translate); + + idRibbon* ribbon; + const idDeclRibbon* ribbonDecl; + idRenderModelBeam* beamModel; + idRibbonModelManager* modelManager; + bool isInitialized; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idRibbonModelManager::ribbonEffects_t) == 84, + "Recovered ribbonEffects_t ABI changed"); +static_assert(sizeof(idRibbonModelManager) == 352, + "Recovered idRibbonModelManager ABI changed"); +static_assert(sizeof(idRibbonManager) == 20, + "Recovered idRibbonManager ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/tracermanager.cpp b/source/engine/gamelib/effects/tracermanager.cpp new file mode 100644 index 0000000..640df25 --- /dev/null +++ b/source/engine/gamelib/effects/tracermanager.cpp @@ -0,0 +1,86 @@ +#include "gamelib/effects/tracermanager.h" + +#include +#include + +void GameLib_AddTracerToEffectsModel(idRenderModelEffects* model, + const idMaterial* material, const idVec3& origin, + const idVec3& direction, const idVec3& maxDistance, float speed, + float length, float height, bool ensureVisual, float fractionInsured, + float lifeTime); +void GameLib_SerializeTracerBuffer(idSerializer& serializer, + idTracerManager::tracerBufferItem_t* buffer, int count, + int& bufferPosition); + +idTracerManager::idTracerManager() + : effectsModel(nullptr) + , tracerBuffer{} + , tracerBufferPos(0) + , serializeTracerCount(0) + , catchingUp(false) + , localPlayerIndex(0) { +} + +void idTracerManager::Init(idRenderModelEffects* const effectsModel_, + const int playerIndex) { + effectsModel = effectsModel_; + localPlayerIndex = playerIndex; +} + +void idTracerManager::AddTracer(const idMaterial* const material, + const idVec3& origin, const idVec3& direction, + const idVec3& maxDistance, const float speed, const float length, + const float height, const bool ensureVisual, + const float fractionInsured, const int playerIndex) { + if (effectsModel != nullptr) { + GameLib_AddTracerToEffectsModel(effectsModel, material, origin, + direction, maxDistance, speed, length, height, ensureVisual, + fractionInsured, -1.0f); + } + if (catchingUp) { + return; + } + tracerBufferItem_t& item = tracerBuffer[tracerBufferPos]; + item.startTime = 0; + item.lifeTime = 0; + item.speed = speed; + item.origin = origin; + item.dir = direction; + item.length = length; + item.height = height; + item.mat = material; + item.maxDist = maxDistance; + item.ensureVisual = ensureVisual; + item.fractionInsured = fractionInsured; + item.playerIndex = playerIndex; + tracerBufferPos = (tracerBufferPos + 1) % 4; +} + +void idTracerManager::Update(const int time) { + (void)time; + catchingUp = true; + while (serializeTracerCount > 0) { + const int index = tracerBufferPos; + tracerBufferPos = (tracerBufferPos + 1) % 4; + --serializeTracerCount; + const tracerBufferItem_t& item = tracerBuffer[index]; + if (item.playerIndex == localPlayerIndex || effectsModel == nullptr) { + continue; + } + GameLib_AddTracerToEffectsModel(effectsModel, item.mat, item.origin, + item.dir, item.maxDist, item.speed, item.length, item.height, + item.ensureVisual, item.fractionInsured, -1.0f); + } + catchingUp = false; +} + +void idTracerManager::Serialize(idSerializer& serializer) { + const int oldPosition = tracerBufferPos; + GameLib_SerializeTracerBuffer(serializer, tracerBuffer, 4, + tracerBufferPos); + serializeTracerCount = tracerBufferPos - oldPosition; + if (serializeTracerCount < 0) { + serializeTracerCount += 4; + } + serializeTracerCount = (std::min)(4, serializeTracerCount); +} diff --git a/source/engine/gamelib/effects/tracermanager.h b/source/engine/gamelib/effects/tracermanager.h index 5f92e73..bed80fd 100644 --- a/source/engine/gamelib/effects/tracermanager.h +++ b/source/engine/gamelib/effects/tracermanager.h @@ -1,36 +1,49 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\tracermanager.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/math/vector.h" +class idMaterial; +class idRenderModelEffects; +class idSerializer; -// IDA Local Type ordinal 15276; PDB kind: struct. -struct idTracerManager::tracerBufferItem_t -{ - int startTime; - int lifeTime; - float speed; - idVec3 origin; - idVec3 dir; - float length; - float height; - const idMaterial *mat; - idVec3 maxDist; - bool ensureVisual; - float fractionInsured; - int playerIndex; -}; - -// IDA Local Type ordinal 15277; PDB kind: class. -class idTracerManager -{ +class idTracerManager { public: - idRenderModelEffects *effectsModel; - idTracerManager::tracerBufferItem_t tracerBuffer[4]; - int tracerBufferPos; - int serializeTracerCount; - bool catchingUp; - int localPlayerIndex; + struct tracerBufferItem_t { + int startTime; + int lifeTime; + float speed; + idVec3 origin; + idVec3 dir; + float length; + float height; + const idMaterial* mat; + idVec3 maxDist; + bool ensureVisual; + float fractionInsured; + int playerIndex; + }; + + idTracerManager(); + + void Init(idRenderModelEffects* effectsModel, int playerIndex); + void AddTracer(const idMaterial* material, const idVec3& origin, + const idVec3& direction, const idVec3& maxDistance, float speed, + float length, float height, bool ensureVisual, + float fractionInsured, int playerIndex); + void Update(int time); + void Serialize(idSerializer& serializer); + + idRenderModelEffects* effectsModel; + tracerBufferItem_t tracerBuffer[4]; + int tracerBufferPos; + int serializeTracerCount; + bool catchingUp; + int localPlayerIndex; }; + +static_assert(sizeof(idTracerManager::tracerBufferItem_t) == 72, + "Recovered tracer buffer ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idTracerManager) == 308, + "Recovered idTracerManager ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/weapontracemanager.cpp b/source/engine/gamelib/effects/weapontracemanager.cpp new file mode 100644 index 0000000..e8e025f --- /dev/null +++ b/source/engine/gamelib/effects/weapontracemanager.cpp @@ -0,0 +1,121 @@ +#include "gamelib/effects/weapontracemanager.h" + +idWeaponTraceManager::idWeaponTraceManager() + : weaponTraces{} + , currentDeferred(0) { + Init(); +} + +void idWeaponTraceManager::Init() { + weaponTraces[0].Clear(); + weaponTraces[1].Clear(); + currentDeferred = 0; +} + +void idWeaponTraceManager::Update() { + currentDeferred ^= 1; + weaponTraces[currentDeferred].Clear(); +} + +void idWeaponTraceManager::AddTrace(const idVec3& start, + const idVec3& end, const idSpawnId attacker, + const idDeclDamage* const damage) { + idStaticList& traces = weaponTraces[currentDeferred]; + if (traces.Num() >= traces.Max()) { + return; + } + weaponTrace_t* const trace = traces.Alloc(); + trace->start = start; + trace->end = end; + trace->impactPoint = end; + trace->dir.Zero(); + trace->attacker = attacker; + trace->projectile.Invalidate(); + trace->damageDecl = damage; + trace->radius = 0.0f; + trace->weaponTraceType = weaponTrace_t::WEAPON_TRACE_BULLET; +} + +void idWeaponTraceManager::AddExplosion(const idVec3& origin, + const float radius, const idSpawnId attacker, + const idDeclDamage* const damage) { + idStaticList& traces = weaponTraces[currentDeferred]; + if (traces.Num() >= traces.Max()) { + return; + } + weaponTrace_t* const trace = traces.Alloc(); + trace->start = origin; + trace->end = origin; + trace->impactPoint = origin; + trace->dir.Zero(); + trace->attacker = attacker; + trace->projectile.Invalidate(); + trace->damageDecl = damage; + trace->radius = radius; + trace->weaponTraceType = weaponTrace_t::WEAPON_TRACE_EXPLOSION; +} + +void idWeaponTraceManager::AddProjectile(const idVec3& origin, + const float radius, const idSpawnId attacker, + const idSpawnId projectile, const idDeclDamage* const damage) { + idStaticList& traces = weaponTraces[currentDeferred]; + if (traces.Num() >= traces.Max()) { + return; + } + weaponTrace_t* const trace = traces.Alloc(); + trace->start = origin; + trace->end = origin; + trace->impactPoint = origin; + trace->dir.Zero(); + trace->attacker = attacker; + trace->projectile = projectile; + trace->damageDecl = damage; + trace->radius = radius; + trace->weaponTraceType = weaponTrace_t::WEAPON_TRACE_PROJECTILE; +} + +int idWeaponTraceManager::GetCollisions(const idBounds& bounds, + idList& outTraces) { + outTraces.Clear(); + const idStaticList& traces = + weaponTraces[currentDeferred ^ 1]; + + for (int index = 0; index < traces.Num(); ++index) { + const weaponTrace_t& source = traces[index]; + weaponTrace_t result = source; + switch (source.weaponTraceType) { + case weaponTrace_t::WEAPON_TRACE_BULLET: { + result.dir = source.end - source.start; + float scale = 0.0f; + if (!bounds.RayIntersection(source.start, result.dir, scale) || + scale < 0.0f || scale >= 1.0f) { + continue; + } + result.dir.NormalizeFast(); + result.radius = 0.0f; + break; + } + case weaponTrace_t::WEAPON_TRACE_EXPLOSION: + if (!bounds.SphereIntersection(source.start, source.radius)) { + continue; + } + result.end = (bounds[0] + bounds[1]) * 0.5f; + result.impactPoint = result.end; + result.dir = result.end - result.start; + result.dir.NormalizeFast(); + break; + case weaponTrace_t::WEAPON_TRACE_PROJECTILE: + if (!bounds.SphereIntersection(source.start, source.radius)) { + continue; + } + result.end = result.start; + result.impactPoint = result.start; + result.dir.Zero(); + break; + default: + continue; + } + outTraces.Append(result); + } + return outTraces.Num(); +} diff --git a/source/engine/gamelib/effects/weapontracemanager.h b/source/engine/gamelib/effects/weapontracemanager.h index 0aa87ff..224b082 100644 --- a/source/engine/gamelib/effects/weapontracemanager.h +++ b/source/engine/gamelib/effects/weapontracemanager.h @@ -1,37 +1,60 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\weapontracemanager.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/bv/bounds.h" +#include "idlib/containers/staticlist.h" +#include "idlib/handle.h" +class idDeclDamage; +enum invalidSpawnId_t : int; -// IDA Local Type ordinal 1836; PDB kind: enum. -enum weaponTrace_t::weaponTraceType_t : __int32 -{ - WEAPON_TRACE_BULLET = 0x0, - WEAPON_TRACE_EXPLOSION = 0x1, - WEAPON_TRACE_PROJECTILE = 0x2, -}; - -// IDA Local Type ordinal 14146; PDB kind: struct. -struct weaponTrace_t -{ - idVec3 start; - idVec3 end; - idVec3 impactPoint; - idVec3 dir; - idSpawnId attacker; - idSpawnId projectile; - const idDeclDamage *damageDecl; - float radius; - weaponTrace_t::weaponTraceType_t weaponTraceType; -}; - -// IDA Local Type ordinal 14149; PDB kind: class. -class idWeaponTraceManager -{ +class idSpawnId : public idHandle { public: - idStaticList weaponTraces[2]; - int currentDeferred; + idSpawnId() = default; + idSpawnId(int value) : idHandle(value) {} }; + +struct weaponTrace_t { + enum weaponTraceType_t : int { + WEAPON_TRACE_BULLET = 0, + WEAPON_TRACE_EXPLOSION = 1, + WEAPON_TRACE_PROJECTILE = 2 + }; + + idVec3 start; + idVec3 end; + idVec3 impactPoint; + idVec3 dir; + idSpawnId attacker; + idSpawnId projectile; + const idDeclDamage* damageDecl; + float radius; + weaponTraceType_t weaponTraceType; +}; + +class idWeaponTraceManager { +public: + idWeaponTraceManager(); + + void Init(); + void Update(); + void AddTrace(const idVec3& start, const idVec3& end, + idSpawnId attacker, const idDeclDamage* damage); + void AddExplosion(const idVec3& origin, float radius, + idSpawnId attacker, const idDeclDamage* damage); + void AddProjectile(const idVec3& origin, float radius, + idSpawnId attacker, idSpawnId projectile, + const idDeclDamage* damage); + int GetCollisions(const idBounds& bounds, + idList& outTraces); + + idStaticList weaponTraces[2]; + int currentDeferred; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idSpawnId) == 4, "Recovered idSpawnId ABI changed"); +static_assert(sizeof(weaponTrace_t) == 68, + "Recovered weapon trace ABI changed"); +static_assert(sizeof(idWeaponTraceManager) == 852, + "Recovered weapon trace manager ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/clip.cpp b/source/engine/gamelib/physics/clip.cpp new file mode 100644 index 0000000..03d5c68 --- /dev/null +++ b/source/engine/gamelib/physics/clip.cpp @@ -0,0 +1,752 @@ +#include "gamelib/physics/clip.h" + +#include "cm/collisionmodel.h" +#include "cm/collisionmodelmanager.h" +#include "gamelib/aas2/aas2.h" +#include "gamelib/physics/tracemodelcache.h" +#include "gamelib/physics/tracemodelrecycler.h" +#include "idlib/geometry/tracemodel.h" +#include "idlib/geometry/winding.h" + +#include +#include +#include + +namespace { + +bool BoundsIntersect(const idBounds& first, const idBounds& second) { + for (int axis = 0; axis < 3; ++axis) { + if (first[1][axis] < second[0][axis] + || first[0][axis] > second[1][axis]) return false; + } + return true; +} + +idBounds PointBounds(const idVec3& point) { + idBounds result; + result[0] = point; + result[1] = point; + return result; +} + +idBounds MotionBounds(const idVec3& start, const idVec3& end, + const idClipModel* const model, const idMat3& axis) { + idBounds result; + if (model != nullptr) { + result.FromTransformedBounds(model->GetBounds(), start, axis); + idBounds endBounds; + endBounds.FromTransformedBounds(model->GetBounds(), end, axis); + for (int component = 0; component < 3; ++component) { + result[0][component] = (std::min)(result[0][component], + endBounds[0][component]); + result[1][component] = (std::max)(result[1][component], + endBounds[1][component]); + } + } else { + result[0].Set((std::min)(start.x, end.x), + (std::min)(start.y, end.y), (std::min)(start.z, end.z)); + result[1].Set((std::max)(start.x, end.x), + (std::max)(start.y, end.y), (std::max)(start.z, end.z)); + } + return result; +} + +void CollectTraceModels(const idClipModel* const model, + std::vector& output) { + if (model == nullptr) return; + for (int index = 0; index < model->GetNumTraceModels(); ++index) { + const idTraceModel* const traceModel = model->GetTraceModel(index); + if (traceModel != nullptr) output.push_back(traceModel); + } +} + +void AppendPositionedModel(const idClipModel* const model, + std::vector& output) { + if (model == nullptr || !model->IsEnabled() || model->IsDeleted()) return; + idCollisionModel* const collisionModel = model->GetCollisionModel(); + if (collisionModel == nullptr) return; + idPositionedCollisionModel positioned{}; + positioned.model = collisionModel; + positioned.modelJoints = nullptr; + positioned.modelOrigin = model->GetOrigin(); + positioned.modelAxis = model->GetAxis(); + positioned.modelEntityNum = model->GetEntityNumber(); + positioned.modelPhysicsId = model->GetPhysicsId(); + positioned.modelBodyId = model->GetBodyId(); + positioned.modelContentsOverride = model->GetContents(); + positioned.modelQuery = model->collisionQuery; + output.push_back(positioned); +} + +void CollectPositionedModels(const idClip& clip, const int contentMask, + const int passEntityNumber, const bool ignoreWorld, + std::vector& output) { + if (!ignoreWorld && clip.world != nullptr + && (clip.world->GetContents() & contentMask) != 0) { + AppendPositionedModel(clip.world, output); + } + if (clip.clipSectors.IsEmpty()) return; + for (clipLink_t* link = clip.clipSectors[0].clipLinks; + link != nullptr; link = link->nextInSector) { + idClipModel* const model = link->clipModel; + if (model == nullptr || model == clip.world + || model->GetEntityNumber() == passEntityNumber + || (model->GetContents() & contentMask) == 0) continue; + AppendPositionedModel(model, output); + } +} + +idClipQuery ToClipQuery(const idCollisionQuery query) { + idClipQuery result{}; + result.index = query.offset; + return result; +} + +idCollisionQuery ToCollisionQuery(const idClipQuery query) { + idCollisionQuery result{}; + result.offset = query.index; + return result; +} + +idClipModel* FindLinkedModel(const idClip& clip, const int entityNumber, + const int physicsId, const int bodyId) { + if (clip.world != nullptr && clip.world->GetEntityNumber() == entityNumber + && clip.world->GetPhysicsId() == physicsId + && clip.world->GetBodyId() == bodyId) return clip.world; + if (clip.clipSectors.IsEmpty()) return nullptr; + for (clipLink_t* link = clip.clipSectors[0].clipLinks; + link != nullptr; link = link->nextInSector) { + idClipModel* const model = link->clipModel; + if (model != nullptr && model->GetEntityNumber() == entityNumber + && model->GetPhysicsId() == physicsId + && model->GetBodyId() == bodyId) return model; + } + return nullptr; +} + +} // namespace + +idClip::idClip() + : world(nullptr), temporaryClipModel(nullptr), defaultClipModel(nullptr), + playerMeleeClipModel(nullptr), clip8x8(nullptr), clip8x8x8(nullptr), + clip16x16(nullptr), clip16x16x16(nullptr), clip24x24(nullptr), + clip32x32(nullptr), clip8x16(nullptr), clip48x48(nullptr), + clip96x96(nullptr), clip24x24x96(nullptr), clip32x32x96(nullptr), + clip48x48x96(nullptr), clipLinkAllocator(false), clipSectors(), + touchCount(0), aases{}, traceModelCache(nullptr), + traceModelRecycler(nullptr), deletedClipModels(nullptr), streamAreas(), + queryParms(nullptr), numQueryParms(0), translationQueries(), + rotationQueries(), motionQueries(), motionContactsQueries(), + stepMoveQueries(), stepMoveContactsQueries(), slideMoveQueries(), + slideMoveContactsQueries(), contentsQueries(), contactsQueries(), + clipQueries(), localTranslationQueries(), localContentsQueries(), + translationTraceModelQueries(), collisionQueries{}, + collisionQueryFirstSubmittedIndex(0), collisionQueryLastSubmittedIndex(0), + collisionQueryFirstAllocedIndex(0), collisionQueryLastAllocedIndex(0), + collisionQueryLastResolvedIndex(0), gatherQueryUserNames{}, + gatherQueries{}, gatherQueryFirstSubmittedIndex(0), + gatherQueryLastSubmittedIndex(0), gatherQueryFirstAllocedIndex(0), + gatherQueryLastAllocedIndex(0), gatherResults{}, + gatherResultFirstSubmittedIndex(0), gatherResultLastSubmittedIndex(0), + gatherResultFirstAllocedIndex(0), gatherResultLastAllocedIndex(0), + residencyQueries{}, residencyQueryFirstSubmittedIndex(0), + residencyQueryLastSubmittedIndex(0), residencyQueryFirstAllocedIndex(0), + residencyQueryLastAllocedIndex(0) { +} + +idClip::~idClip() { + Shutdown(); +} + +void idClip::Init(idCollisionModel* const worldModel, + idTraceModelCache* const cache, idTraceModelRecycler* const recycler) { + Shutdown(); + traceModelCache = cache; + traceModelRecycler = recycler; + clipSectors.SetNum(1); + clipSectors[0].axis = -1; + clipSectors[0].dist = 0.0f; + clipSectors[0].children[0] = nullptr; + clipSectors[0].children[1] = nullptr; + clipSectors[0].clipLinks = nullptr; + if (worldModel != nullptr) { + world = new idClipModel(this, worldModel); + world->Link(0, -1, 0, idVec3(), idMat3(1.0f)); + } +} + +void idClip::Shutdown() { + DeleteClipModels(); + if (world != nullptr) { + idClipModel* const oldWorld = world; + world = nullptr; + delete oldWorld; + } + clipSectors.SetNum(0); + clipLinkAllocator.Shutdown(); + streamAreas.ClearFree(); + delete[] queryParms; + queryParms = nullptr; + numQueryParms = 0; + traceModelCache = nullptr; + traceModelRecycler = nullptr; + deletedClipModels = nullptr; +} + +void idClip::StartQueryFrame() { + collisionModelManager.StartQueryFrame(); +} + +void idClip::EndQueryFrame() { + ResolveCollisionQueries(); + collisionModelManager.EndQueryFrame(); + DeleteClipModels(); +} + +void idClip::ResolveCollisionQueries() { + collisionModelManager.WaitForAllQueries(); + collisionQueryLastResolvedIndex = collisionQueryLastSubmittedIndex; +} + +void idClip::DeleteClipModel(idClipModel* const clipModel) { + if (clipModel == nullptr || clipModel == world) return; + clipModel->Unlink(); + clipModel->nextDeleted = deletedClipModels; + deletedClipModels = clipModel; +} + +void idClip::DeleteClipModels() { + while (deletedClipModels != nullptr) { + idClipModel* const model = deletedClipModels; + deletedClipModels = model->nextDeleted; + model->nextDeleted = nullptr; + delete model; + } +} + +void idClip::InitializeAASLinks(const int aasType) { + if (aasType < 0 || aasType >= 8 || aases[aasType] == nullptr + || clipSectors.IsEmpty()) return; + for (clipLink_t* link = clipSectors[0].clipLinks; + link != nullptr; link = link->nextInSector) { + idClipModel* const model = link->clipModel; + if (model != nullptr && model->aasLinks[aasType] == nullptr) + model->aasLinks[aasType] = aases[aasType]->LinkClipModel(model); + } +} + +const idBounds& idClip::GetWorldBounds() const { + static idBounds emptyBounds{}; + return world != nullptr ? world->GetBounds() : emptyBounds; +} + +void idClip::AddStreamArea(const int areaNum) { + if (streamAreas.FindIndex(areaNum) < 0) streamAreas.Append(areaNum); +} + +void idClip::ClearStreamAreas() { + streamAreas.Clear(); +} + +int idClip::FindStreamArea(const idClipModel*) { + return streamAreas.IsEmpty() ? -1 : streamAreas[0]; +} + +int idClip::GetClipModelsTouchingBounds(const idBounds& queryBounds, + const int contentMask, idClipModel** const output, + const int maxOutput) const { + if (output == nullptr || maxOutput <= 0 || clipSectors.IsEmpty()) return 0; + int count = 0; + for (clipLink_t* link = clipSectors[0].clipLinks; + link != nullptr && count < maxOutput; link = link->nextInSector) { + idClipModel* const model = link->clipModel; + if (model == nullptr || !model->IsEnabled() + || (model->GetContents() & contentMask) == 0 + || !BoundsIntersect(queryBounds, model->GetAbsBounds())) continue; + output[count++] = model; + } + return count; +} + +int idClip::GetClipModelsTouchingLine(const idVec3& start, + const idVec3& end, const int contentMask, + idClipModel** const output, const int maxOutput) const { + if (output == nullptr || maxOutput <= 0 || clipSectors.IsEmpty()) return 0; + int count = 0; + for (clipLink_t* link = clipSectors[0].clipLinks; + link != nullptr && count < maxOutput; link = link->nextInSector) { + idClipModel* const model = link->clipModel; + if (model == nullptr || !model->IsEnabled() + || (model->GetContents() & contentMask) == 0 + || !model->GetAbsBounds().LineIntersection(start, end)) continue; + output[count++] = model; + } + return count; +} + +int idClip::GetEntitiesTouchingBounds(const idBounds& bounds, + const int contentMask, int* const entities, + const int maxEntities) const { + if (entities == nullptr || maxEntities <= 0) return 0; + std::vector models(static_cast(maxEntities)); + const int modelCount = GetClipModelsTouchingBounds(bounds, contentMask, + models.data(), maxEntities); + int count = 0; + for (int index = 0; index < modelCount; ++index) { + const int entity = models[index]->GetEntityNumber(); + bool duplicate = false; + for (int other = 0; other < count; ++other) + if (entities[other] == entity) duplicate = true; + if (!duplicate) entities[count++] = entity; + } + return count; +} + +int idClip::GetEntitiesTouchingLine(const idVec3& start, + const idVec3& end, const int contentMask, int* const entities, + const int maxEntities) const { + if (entities == nullptr || maxEntities <= 0) return 0; + std::vector models(static_cast(maxEntities)); + const int modelCount = GetClipModelsTouchingLine(start, end, contentMask, + models.data(), maxEntities); + int count = 0; + for (int index = 0; index < modelCount; ++index) { + const int entity = models[index]->GetEntityNumber(); + bool duplicate = false; + for (int other = 0; other < count; ++other) + if (entities[other] == entity) duplicate = true; + if (!duplicate) entities[count++] = entity; + } + return count; +} + +idGatherQuery idClip::GatherTouchingBounds(const idBounds& bounds, + const int contentMask, const int passEntityNumber, + const char* const userName) { + const std::uint64_t queryIndex = gatherQueryLastSubmittedIndex++; + idClipGatherQuery& query = gatherQueries[queryIndex & 255]; + query.vectors[0] = bounds[0]; + query.vectors[1] = bounds[1]; + query.clipMask = contentMask; + query.passEntityNumber = static_cast(passEntityNumber); + query.gatherType = GATHER_TOUCHING_BOUNDS; + query.firstResultIndex = static_cast( + gatherResultLastSubmittedIndex & 2047); + idClipModel* models[2048]; + const int count = GetClipModelsTouchingBounds(bounds, contentMask, + models, 2048); + query.numResults = static_cast(count); + for (int index = 0; index < count; ++index) { + idGatherResult& result = gatherResults[ + gatherResultLastSubmittedIndex++ & 2047]; + result.query = models[index]->collisionQuery; + result.physicsId = models[index]->GetPhysicsId(); + result.bodyId = static_cast(models[index]->GetBodyId()); + result.entityNumber = static_cast( + models[index]->GetEntityNumber()); + } + gatherQueryUserNames[queryIndex & 255] = userName; + return idGatherQuery{queryIndex}; +} + +idGatherQuery idClip::GatherTouchingClipModel(const idVec3& origin, + const idClipModel* const model, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const char* const userName) { + idBounds bounds = PointBounds(origin); + if (model != nullptr) + bounds.FromTransformedBounds(model->GetBounds(), origin, axis); + return GatherTouchingBounds(bounds, contentMask, passEntityNumber, + userName); +} + +int idClip::GetGatheredClipModels(const idGatherQuery& handle, + idClipModel** const output, const int maxOutput) { + if (output == nullptr || maxOutput <= 0 + || handle.index >= gatherQueryLastSubmittedIndex) return 0; + const idClipGatherQuery& query = gatherQueries[handle.index & 255]; + const int count = (std::min)(maxOutput, + static_cast(query.numResults)); + int outputCount = 0; + for (int index = 0; index < count; ++index) { + const idGatherResult& result = gatherResults[ + (query.firstResultIndex + index) & 2047]; + idClipModel* const model = FindLinkedModel(*this, + result.entityNumber, result.physicsId, result.bodyId); + if (model != nullptr) output[outputCount++] = model; + } + return outputCount; +} + +int idClip::GetGatheredEntities(const idGatherQuery& handle, + int* const output, const int maxOutput) { + if (output == nullptr || maxOutput <= 0 + || handle.index >= gatherQueryLastSubmittedIndex) return 0; + const idClipGatherQuery& query = gatherQueries[handle.index & 255]; + int count = 0; + for (int index = 0; index < query.numResults && count < maxOutput; + ++index) { + const int entity = gatherResults[ + (query.firstResultIndex + index) & 2047].entityNumber; + bool duplicate = false; + for (int other = 0; other < count; ++other) + if (output[other] == entity) duplicate = true; + if (!duplicate) output[count++] = entity; + } + return count; +} + +idResidencyQuery idClip::TestResidency(const idBounds& bounds) { + const std::uint64_t index = residencyQueryLastSubmittedIndex++; + idClipResidencyQuery& query = residencyQueries[index & 1023]; + query.absBounds = bounds; + query.resident = true; + return idResidencyQuery{index}; +} + +idClipQuery idClip::Translation(trace_t* const result, + const idVec3& start, const idVec3& end, + const idClipModel* const movingModel, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const bool ignoreWorld, const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, end, movingModel, axis); + return ToClipQuery(collisionModelManager.Translation(result, start, end, + bounds, traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::LocalTranslation(const idClipQuery localSpace, + const idVec3& start, const idVec3& end, + const idClipModel* const movingModel, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const bool ignoreWorld, const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, end, movingModel, axis); + return ToClipQuery(collisionModelManager.LocalTranslation( + ToCollisionQuery(localSpace), start, end, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::TranslationTraceModel(trace_t* const result, + const idVec3& start, const idVec3& end, + const idClipModel* const movingModel, const idVec3& modelOrigin, + const idMat3& modelAxis, const int contentMask, + const char* const userName) { + std::vector traceModels; + CollectTraceModels(movingModel, traceModels); + idPositionedCollisionModel positioned{}; + positioned.model = movingModel != nullptr + ? movingModel->GetCollisionModel() : nullptr; + positioned.modelOrigin = modelOrigin; + positioned.modelAxis = modelAxis; + positioned.modelContentsOverride = contentMask; + const idBounds bounds = MotionBounds(start, end, movingModel, modelAxis); + return ToClipQuery(collisionModelManager.Translation(result, start, end, + bounds, traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), modelAxis, contentMask, + positioned.model != nullptr ? &positioned : nullptr, + positioned.model != nullptr ? 1 : 0, userName)); +} + +idClipQuery idClip::Rotation(trace_t* const result, const idVec3& start, + const idRotation& rotation, const idClipModel* const movingModel, + const idMat3& axis, const int contentMask, + const int passEntityNumber, const bool ignoreWorld, + const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, start, movingModel, axis); + return ToClipQuery(collisionModelManager.Rotation(result, start, + rotation, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::Motion(trace_t* const result, const idVec3& start, + const idVec3& end, const idRotation& rotation, + const idClipModel* const movingModel, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const bool ignoreWorld, const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, end, movingModel, axis); + return ToClipQuery(collisionModelManager.Motion(result, start, end, + rotation, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::MotionContacts(trace_t* const result, + contactsResult_t* const contacts, const idVec3& start, + const idVec3& end, const idRotation& rotation, const float depth, + const idClipModel* const movingModel, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const bool ignoreWorld, const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, end, movingModel, axis); + return ToClipQuery(collisionModelManager.MotionContacts(result, contacts, + start, end, rotation, depth, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::StepMove(trace_t* const result, const idVec3& start, + const idVec3& end, const idVec3& downNormal, const float stepUp, + const float stepDown, const idClipModel* const movingModel, + const idMat3& axis, const int contentMask, + const int passEntityNumber, const bool ignoreWorld, + const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, end, movingModel, axis); + return ToClipQuery(collisionModelManager.StepMove(result, start, end, + downNormal, stepUp, stepDown, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::StepMoveContacts(trace_t* const result, + contactsResult_t* const contacts, const idVec3& start, + const idVec3& end, const idVec3& downNormal, const float stepUp, + const float stepDown, const idClipModel* const movingModel, + const idMat3& axis, const int contentMask, + const int passEntityNumber, const bool ignoreWorld, + const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, end, movingModel, axis); + return ToClipQuery(collisionModelManager.StepMoveContacts(result, + contacts, start, end, downNormal, stepUp, stepDown, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::SlideMoveContacts(trace_t* const result, + contactsResult_t* const contacts, const idVec3& start, + const idVec3& velocity, const idVec3& gravityVector, + const float stepUp, const idClipModel* const movingModel, + const idMat3& axis, const int contentMask, + const int passEntityNumber, const bool ignoreWorld, + const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + ignoreWorld, models); + const idBounds bounds = MotionBounds(start, start + velocity, + movingModel, axis); + return ToClipQuery(collisionModelManager.SlideMoveContacts(result, + contacts, start, velocity, gravityVector, stepUp, stepUp, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::Contents(trace_t* const result, const idVec3& start, + const idClipModel* const movingModel, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + false, models); + const idBounds bounds = MotionBounds(start, start, movingModel, axis); + return ToClipQuery(collisionModelManager.Contents(result, start, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::LocalContents(const idClipQuery localSpace, + const idVec3& start, const idClipModel* const movingModel, + const idMat3& axis, const int contentMask, + const int passEntityNumber, const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + false, models); + const idBounds bounds = MotionBounds(start, start, movingModel, axis); + return ToClipQuery(collisionModelManager.LocalContents( + ToCollisionQuery(localSpace), start, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::Contacts(contactsResult_t* const result, + const idVec3& start, const idVec3& direction, const float depth, + const idClipModel* const movingModel, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const char* const userName) { + std::vector traceModels; + std::vector models; + CollectTraceModels(movingModel, traceModels); + CollectPositionedModels(*this, contentMask, passEntityNumber, + false, models); + const idBounds bounds = MotionBounds(start, start + direction * depth, + movingModel, axis); + return ToClipQuery(collisionModelManager.Contacts(result, start, + direction, depth, bounds, + traceModels.empty() ? nullptr : traceModels.data(), + static_cast(traceModels.size()), axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +idClipQuery idClip::Clip(clipResult_t* const result, const idVec3& start, + const idTraceModel* const traceModel, const idMat3& axis, + const int contentMask, const int passEntityNumber, + const char* const userName) { + std::vector models; + CollectPositionedModels(*this, contentMask, passEntityNumber, + false, models); + const idTraceModel* traceModels[1] = { traceModel }; + const idBounds bounds = traceModel != nullptr + ? MotionBounds(start, start, nullptr, axis) : PointBounds(start); + return ToClipQuery(collisionModelManager.Clip(result, start, bounds, + traceModel != nullptr ? traceModels : nullptr, + traceModel != nullptr ? 1 : 0, axis, contentMask, + models.empty() ? nullptr : models.data(), + static_cast(models.size()), userName)); +} + +void idClip::TranslationModel(trace_t& result, const idVec3& start, + const idVec3& end, const idClipModel* const movingModel, + const idMat3& axis, const int contentMask, + const idVec3& modelOrigin, const idClipModel* const model, + const idMat3& modelAxis) const { + std::vector traces; + CollectTraceModels(movingModel, traces); + idPositionedCollisionModel positioned{}; + if (model != nullptr) { + positioned.model = model->GetCollisionModel(); + positioned.modelOrigin = modelOrigin; + positioned.modelAxis = modelAxis; + positioned.modelEntityNum = model->GetEntityNumber(); + positioned.modelPhysicsId = model->GetPhysicsId(); + positioned.modelBodyId = model->GetBodyId(); + positioned.modelContentsOverride = model->GetContents(); + } + collisionModelManager.Translation(&result, start, end, + MotionBounds(start, end, movingModel, axis), + traces.empty() ? nullptr : traces.data(), + static_cast(traces.size()), axis, contentMask, + positioned.model != nullptr ? &positioned : nullptr, + positioned.model != nullptr ? 1 : 0, "TranslationModel"); +} + +void idClip::RotationModel(trace_t& result, const idVec3& start, + const idRotation& rotation, const idClipModel* const movingModel, + const idMat3& axis, const int contentMask, + const idVec3& modelOrigin, const idClipModel* const model, + const idMat3& modelAxis) const { + std::vector traces; + CollectTraceModels(movingModel, traces); + idPositionedCollisionModel positioned{}; + if (model != nullptr) { + positioned.model = model->GetCollisionModel(); + positioned.modelOrigin = modelOrigin; + positioned.modelAxis = modelAxis; + positioned.modelContentsOverride = model->GetContents(); + } + collisionModelManager.Rotation(&result, start, rotation, + MotionBounds(start, start, movingModel, axis), + traces.empty() ? nullptr : traces.data(), + static_cast(traces.size()), axis, contentMask, + positioned.model != nullptr ? &positioned : nullptr, + positioned.model != nullptr ? 1 : 0, "RotationModel"); +} + +void idClip::ContentsModel(trace_t& result, const idVec3& start, + const idClipModel* const movingModel, const idMat3& axis, + const int contentMask, const idVec3& modelOrigin, + const idClipModel* const model, const idMat3& modelAxis) const { + std::vector traces; + CollectTraceModels(movingModel, traces); + idPositionedCollisionModel positioned{}; + if (model != nullptr) { + positioned.model = model->GetCollisionModel(); + positioned.modelOrigin = modelOrigin; + positioned.modelAxis = modelAxis; + positioned.modelContentsOverride = model->GetContents(); + } + collisionModelManager.Contents(&result, start, + MotionBounds(start, start, movingModel, axis), + traces.empty() ? nullptr : traces.data(), + static_cast(traces.size()), axis, contentMask, + positioned.model != nullptr ? &positioned : nullptr, + positioned.model != nullptr ? 1 : 0, "ContentsModel"); +} + +bool idClip::GetModelContactFeature(const contactInfo_t& contact, + const idClipModel* const model, idFixedWinding& winding) const { + if (model == nullptr) return false; + idCollisionModel* const collisionModel = model->GetCollisionModel(); + if (collisionModel == nullptr) return false; + if (contact.type == CONTACT_EDGE) { + idVec3 points[2]; + if (!collisionModel->GetEdge(contact.modelFeature, + points[0], points[1])) return false; + winding.Clear(); + winding.AddPoint(points[0]); + winding.AddPoint(points[1]); + return true; + } + if (contact.type == CONTACT_MODELVERTEX) { + idVec3 point; + if (!collisionModel->GetVertex(contact.modelFeature, point)) + return false; + winding.Clear(); + winding.AddPoint(point); + return true; + } + return collisionModel->GetPolygon(contact.modelFeature, winding); +} + +void idClip::PrintStatistics() { +} diff --git a/source/engine/gamelib/physics/clip.h b/source/engine/gamelib/physics/clip.h index 72497a6..0b97b41 100644 --- a/source/engine/gamelib/physics/clip.h +++ b/source/engine/gamelib/physics/clip.h @@ -1,171 +1,317 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\clip.h -// Recovered logical types: 9 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/jobs/collisionresults.h" +#include "cm/jobs/collisionquery.h" +#include "gamelib/physics/clipmodel.h" +#include "idlib/blockalloc_base.h" +#include "idlib/containers/list.h" +#include "idlib/containers/staticlist.h" +#include "idlib/math/rotation.h" +#include -// IDA Local Type ordinal 1833; PDB kind: enum. -enum idClip::idClipCollisionQuery::type_t : __int32 -{ - QUERY_VALID = 0x0, - QUERY_CANCELED = 0x1, - QUERY_SUBMITTED_TRANSLATION = 0x2, - QUERY_SUBMITTED_ROTATION = 0x3, - QUERY_SUBMITTED_MOTION = 0x4, - QUERY_SUBMITTED_MOTION_CONTACTS = 0x5, - QUERY_SUBMITTED_STEPMOVE = 0x6, - QUERY_SUBMITTED_STEPMOVE_CONTACTS = 0x7, - QUERY_SUBMITTED_SLIDEMOVE = 0x8, - QUERY_SUBMITTED_SLIDEMOVE_CONTACTS = 0x9, - QUERY_SUBMITTED_CONTENTS = 0xA, - QUERY_SUBMITTED_CONTACTS = 0xB, - QUERY_SUBMITTED_CLIP = 0xC, +class idAAS2; +class idCollisionModel; +class idFixedWinding; +class idTraceModel; +class idTraceModelCache; +class idTraceModelRecycler; +class idTypeInfoTools; +struct idPositionedCollisionModel; + +#ifndef ID_CLIP_QUERY_DEFINED +#define ID_CLIP_QUERY_DEFINED +struct idClipQuery { std::uint64_t index; }; +#endif + +struct idGatherQuery { std::uint64_t index; }; +#ifndef ID_RESIDENCY_QUERY_DEFINED +#define ID_RESIDENCY_QUERY_DEFINED +struct idResidencyQuery { std::uint64_t index; }; +#endif + +struct clipSector_t { + int axis; + float dist; + clipSector_t* children[2]; + clipLink_t* clipLinks; }; -// IDA Local Type ordinal 2572; PDB kind: enum. -enum idClip::gatherType_t : __int32 -{ - GATHER_TOUCHING_BOUNDS = 0x0, - GATHER_TOUCHING_LINE = 0x1, - GATHER_TOUCHING_CLIPMODEL = 0x2, +struct clipQueryStats_t { + const char* userName; + int count; + int max; }; -// IDA Local Type ordinal 14068; PDB kind: class. -class idClip -{ +class idClip { public: - idClipModel *world; - idClipModel *temporaryClipModel; - idClipModel *defaultClipModel; - idClipModel *playerMeleeClipModel; - idClipModel *clip8x8; - idClipModel *clip8x8x8; - idClipModel *clip16x16; - idClipModel *clip16x16x16; - idClipModel *clip24x24; - idClipModel *clip32x32; - idClipModel *clip8x16; - idClipModel *clip48x48; - idClipModel *clip96x96; - idClipModel *clip24x24x96; - idClipModel *clip32x32x96; - idClipModel *clip48x48x96; - idBlockAlloc clipLinkAllocator; - idStaticList clipSectors; - int touchCount; - idAAS2 *aases[8]; - idTraceModelCache *traceModelCache; - idTraceModelRecycler *traceModelRecycler; - idClipModel *deletedClipModels; - idList streamAreas; - idClip::idClipQueryParms *queryParms; - int numQueryParms; - idStaticList translationQueries; - idStaticList rotationQueries; - idStaticList motionQueries; - idStaticList motionContactsQueries; - idStaticList stepMoveQueries; - idStaticList stepMoveContactsQueries; - idStaticList slideMoveQueries; - idStaticList slideMoveContactsQueries; - idStaticList contentsQueries; - idStaticList contactsQueries; - idStaticList clipQueries; - idStaticList localTranslationQueries; - idStaticList localContentsQueries; - idStaticList translationTraceModelQueries; - idClip::idClipCollisionQuery collisionQueries[4096]; - unsigned __int64 collisionQueryFirstSubmittedIndex; - unsigned __int64 collisionQueryLastSubmittedIndex; - unsigned __int64 collisionQueryFirstAllocedIndex; - unsigned __int64 collisionQueryLastAllocedIndex; - unsigned __int64 collisionQueryLastResolvedIndex; - const char *gatherQueryUserNames[256]; - idClip::idClipGatherQuery gatherQueries[256]; - unsigned __int64 gatherQueryFirstSubmittedIndex; - unsigned __int64 gatherQueryLastSubmittedIndex; - unsigned __int64 gatherQueryFirstAllocedIndex; - unsigned __int64 gatherQueryLastAllocedIndex; - idClip::idGatherResult gatherResults[2048]; - unsigned __int64 gatherResultFirstSubmittedIndex; - unsigned __int64 gatherResultLastSubmittedIndex; - unsigned __int64 gatherResultFirstAllocedIndex; - unsigned __int64 gatherResultLastAllocedIndex; - idClip::idClipResidencyQuery residencyQueries[1024]; - unsigned __int64 residencyQueryFirstSubmittedIndex; - unsigned __int64 residencyQueryLastSubmittedIndex; - unsigned __int64 residencyQueryFirstAllocedIndex; - unsigned __int64 residencyQueryLastAllocedIndex; + enum queryType_t : int { + QUERY_VALID = 0, + QUERY_CANCELED, + QUERY_SUBMITTED_TRANSLATION, + QUERY_SUBMITTED_ROTATION, + QUERY_SUBMITTED_MOTION, + QUERY_SUBMITTED_MOTION_CONTACTS, + QUERY_SUBMITTED_STEPMOVE, + QUERY_SUBMITTED_STEPMOVE_CONTACTS, + QUERY_SUBMITTED_SLIDEMOVE, + QUERY_SUBMITTED_SLIDEMOVE_CONTACTS, + QUERY_SUBMITTED_CONTENTS, + QUERY_SUBMITTED_CONTACTS, + QUERY_SUBMITTED_CLIP + }; + + enum gatherType_t : int { + GATHER_TOUCHING_BOUNDS = 0, + GATHER_TOUCHING_LINE = 1, + GATHER_TOUCHING_CLIPMODEL = 2 + }; + + struct idClipQueryParms { + idVec3 start; + idVec3 end; + idVec3 origin; + idVec3 vec; + float f1; + float f2; + idMat3 startAxis; + int clipMask; + std::uint16_t passEntityNumber; + std::uint16_t flags; + std::uint8_t pad[12]; + unsigned int queryIndex; + const idClipModel* clipModel; + const idTraceModel* traceModel; + const char* userName; + }; + + struct idClipCollisionQuery { + queryType_t type; + int parmIndex; + idClipModel* clipModel; + idCollisionQuery query; + }; + + struct idClipGatherQuery { + idVec3 vectors[4]; + int clipMask; + std::int16_t passEntityNumber; + std::int16_t gatherType; + std::uint16_t firstResultIndex; + std::uint16_t numResults; + const idClipModel* clipModel; + }; + + struct idGatherResult { + idCollisionQuery query; + int physicsId; + std::int16_t bodyId; + std::int16_t entityNumber; + }; + + struct idClipResidencyQuery { + idBounds absBounds; + bool resident; + std::uint8_t pad[7]; + }; + + idClip(); + ~idClip(); + void Init(idCollisionModel* worldModel, idTraceModelCache* cache, + idTraceModelRecycler* recycler); + void Shutdown(); + void StartQueryFrame(); + void EndQueryFrame(); + void ResolveCollisionQueries(); + void DeleteClipModels(); + void DeleteClipModel(idClipModel* clipModel); + void InitializeAASLinks(int aasType); + const idBounds& GetWorldBounds() const; + void AddStreamArea(int areaNum); + void ClearStreamAreas(); + int FindStreamArea(const idClipModel* clipModel); + + int GetClipModelsTouchingBounds(const idBounds& bounds, int contentMask, + idClipModel** clipModels, int maxClipModels) const; + int GetClipModelsTouchingLine(const idVec3& start, const idVec3& end, + int contentMask, idClipModel** clipModels, + int maxClipModels) const; + int GetEntitiesTouchingBounds(const idBounds& bounds, int contentMask, + int* entityNumbers, int maxEntities) const; + int GetEntitiesTouchingLine(const idVec3& start, const idVec3& end, + int contentMask, int* entityNumbers, int maxEntities) const; + idGatherQuery GatherTouchingBounds(const idBounds& bounds, + int contentMask, int passEntityNumber, const char* userName); + idGatherQuery GatherTouchingClipModel(const idVec3& origin, + const idClipModel* clipModel, const idMat3& axis, int contentMask, + int passEntityNumber, const char* userName); + int GetGatheredClipModels(const idGatherQuery& query, + idClipModel** clipModels, int maxClipModels); + int GetGatheredEntities(const idGatherQuery& query, + int* entityNumbers, int maxEntities); + idResidencyQuery TestResidency(const idBounds& bounds); + + idClipQuery Translation(trace_t* result, const idVec3& start, + const idVec3& end, const idClipModel* clipModel, + const idMat3& axis, int contentMask, int passEntityNumber, + bool ignoreWorld, const char* userName); + idClipQuery LocalTranslation(idClipQuery localSpace, + const idVec3& start, const idVec3& end, + const idClipModel* clipModel, const idMat3& axis, + int contentMask, int passEntityNumber, bool ignoreWorld, + const char* userName); + idClipQuery TranslationTraceModel(trace_t* result, + const idVec3& start, const idVec3& end, + const idClipModel* clipModel, const idVec3& modelOrigin, + const idMat3& modelAxis, int contentMask, const char* userName); + idClipQuery Rotation(trace_t* result, const idVec3& start, + const idRotation& rotation, const idClipModel* clipModel, + const idMat3& axis, int contentMask, int passEntityNumber, + bool ignoreWorld, const char* userName); + idClipQuery Motion(trace_t* result, const idVec3& start, + const idVec3& end, const idRotation& rotation, + const idClipModel* clipModel, const idMat3& axis, + int contentMask, int passEntityNumber, bool ignoreWorld, + const char* userName); + idClipQuery MotionContacts(trace_t* result, contactsResult_t* contacts, + const idVec3& start, const idVec3& end, + const idRotation& rotation, float depth, + const idClipModel* clipModel, const idMat3& axis, + int contentMask, int passEntityNumber, bool ignoreWorld, + const char* userName); + idClipQuery StepMove(trace_t* result, const idVec3& start, + const idVec3& end, const idVec3& downNormal, + float stepUp, float stepDown, const idClipModel* clipModel, + const idMat3& axis, int contentMask, int passEntityNumber, + bool ignoreWorld, const char* userName); + idClipQuery StepMoveContacts(trace_t* result, + contactsResult_t* contacts, const idVec3& start, + const idVec3& end, const idVec3& downNormal, + float stepUp, float stepDown, const idClipModel* clipModel, + const idMat3& axis, int contentMask, int passEntityNumber, + bool ignoreWorld, const char* userName); + idClipQuery SlideMoveContacts(trace_t* result, + contactsResult_t* contacts, const idVec3& start, + const idVec3& end, const idVec3& downNormal, + float stepUp, const idClipModel* clipModel, const idMat3& axis, + int contentMask, int passEntityNumber, bool ignoreWorld, + const char* userName); + idClipQuery Contents(trace_t* result, const idVec3& start, + const idClipModel* clipModel, const idMat3& axis, + int contentMask, int passEntityNumber, const char* userName); + idClipQuery LocalContents(idClipQuery localSpace, const idVec3& start, + const idClipModel* clipModel, const idMat3& axis, + int contentMask, int passEntityNumber, const char* userName); + idClipQuery Contacts(contactsResult_t* result, const idVec3& start, + const idVec3& direction, float depth, + const idClipModel* clipModel, const idMat3& axis, + int contentMask, int passEntityNumber, const char* userName); + idClipQuery Clip(clipResult_t* result, const idVec3& start, + const idTraceModel* traceModel, const idMat3& axis, + int contentMask, int passEntityNumber, const char* userName); + + void TranslationModel(trace_t& result, const idVec3& start, + const idVec3& end, const idClipModel* clipModel, + const idMat3& axis, int contentMask, const idVec3& modelOrigin, + const idClipModel* model, const idMat3& modelAxis) const; + void RotationModel(trace_t& result, const idVec3& start, + const idRotation& rotation, const idClipModel* clipModel, + const idMat3& axis, int contentMask, const idVec3& modelOrigin, + const idClipModel* model, const idMat3& modelAxis) const; + void ContentsModel(trace_t& result, const idVec3& start, + const idClipModel* clipModel, const idMat3& axis, int contentMask, + const idVec3& modelOrigin, const idClipModel* model, + const idMat3& modelAxis) const; + bool GetModelContactFeature(const contactInfo_t& contact, + const idClipModel* model, idFixedWinding& winding) const; + void PrintStatistics(); + void DrawWorld(const idVec3&, const idMat3&, float, bool) {} + void DrawSingleClipModel(idClipModel*, const idVec3&, + const idMat3&, int) {} + void ShowCollisionPoint(const contactInfo_t&, + const idTypeInfoTools*) {} + void ShowCollisionSurfaces(const idVec3&, const idMat3&, int, int, + const idTypeInfoTools*) {} + + idClipModel* world; + idClipModel* temporaryClipModel; + idClipModel* defaultClipModel; + idClipModel* playerMeleeClipModel; + idClipModel* clip8x8; + idClipModel* clip8x8x8; + idClipModel* clip16x16; + idClipModel* clip16x16x16; + idClipModel* clip24x24; + idClipModel* clip32x32; + idClipModel* clip8x16; + idClipModel* clip48x48; + idClipModel* clip96x96; + idClipModel* clip24x24x96; + idClipModel* clip32x32x96; + idClipModel* clip48x48x96; + idBlockAlloc clipLinkAllocator; + idStaticList clipSectors; + int touchCount; + idAAS2* aases[8]; + idTraceModelCache* traceModelCache; + idTraceModelRecycler* traceModelRecycler; + idClipModel* deletedClipModels; + idList streamAreas; + idClipQueryParms* queryParms; + int numQueryParms; + idStaticList translationQueries; + idStaticList rotationQueries; + idStaticList motionQueries; + idStaticList motionContactsQueries; + idStaticList stepMoveQueries; + idStaticList stepMoveContactsQueries; + idStaticList slideMoveQueries; + idStaticList slideMoveContactsQueries; + idStaticList contentsQueries; + idStaticList contactsQueries; + idStaticList clipQueries; + idStaticList localTranslationQueries; + idStaticList localContentsQueries; + idStaticList translationTraceModelQueries; + idClipCollisionQuery collisionQueries[4096]; + std::uint64_t collisionQueryFirstSubmittedIndex; + std::uint64_t collisionQueryLastSubmittedIndex; + std::uint64_t collisionQueryFirstAllocedIndex; + std::uint64_t collisionQueryLastAllocedIndex; + std::uint64_t collisionQueryLastResolvedIndex; + const char* gatherQueryUserNames[256]; + idClipGatherQuery gatherQueries[256]; + std::uint64_t gatherQueryFirstSubmittedIndex; + std::uint64_t gatherQueryLastSubmittedIndex; + std::uint64_t gatherQueryFirstAllocedIndex; + std::uint64_t gatherQueryLastAllocedIndex; + idGatherResult gatherResults[2048]; + std::uint64_t gatherResultFirstSubmittedIndex; + std::uint64_t gatherResultLastSubmittedIndex; + std::uint64_t gatherResultFirstAllocedIndex; + std::uint64_t gatherResultLastAllocedIndex; + idClipResidencyQuery residencyQueries[1024]; + std::uint64_t residencyQueryFirstSubmittedIndex; + std::uint64_t residencyQueryLastSubmittedIndex; + std::uint64_t residencyQueryFirstAllocedIndex; + std::uint64_t residencyQueryLastAllocedIndex; }; -// IDA Local Type ordinal 14128; PDB kind: class. -class idClip::idClipQueryParms -{ -public: - idVec3 start; - idVec3 end; - idVec3 origin; - idVec3 vec; - float f1; - float f2; - idMat3 startAxis; - int clipMask; - unsigned __int16 passEntityNumber; - unsigned __int16 flags; - unsigned __int8 pad[12]; - unsigned int queryIndex; - const idClipModel *clipModel; - const idTraceModel *traceModel; - const char *userName; -}; - -// IDA Local Type ordinal 14131; PDB kind: class. -class idClip::idClipCollisionQuery -{ -public: - idClip::idClipCollisionQuery::type_t type; - int parmIndex; - idClipModel *clipModel; - idCollisionQuery query; -}; - -// IDA Local Type ordinal 14132; PDB kind: class. -class idClip::idClipGatherQuery -{ -public: - idVec3 vectors[4]; - int clipMask; - __int16 passEntityNumber; - __int16 gatherType; - unsigned __int16 firstResultIndex; - unsigned __int16 numResults; - const idClipModel *clipModel; -}; - -// IDA Local Type ordinal 14133; PDB kind: class. -class idClip::idGatherResult -{ -public: - idCollisionQuery query; - int physicsId; - __int16 bodyId; - __int16 entityNumber; -}; - -// IDA Local Type ordinal 14134; PDB kind: class. -class idClip::idClipResidencyQuery -{ -public: - idBounds absBounds; - bool resident; - unsigned __int8 pad[7]; -}; - -// IDA Local Type ordinal 23461; PDB kind: struct. -struct idClip::GetClipModelsTouchingLine::__l2::stackPoints_t -{ - idVec4 start; - idVec4 end; -}; +static_assert(sizeof(idClipQuery) == 8, + "Recovered clip-query ABI changed"); +static_assert(sizeof(idGatherQuery) == 8, + "Recovered gather-query ABI changed"); +static_assert(sizeof(idResidencyQuery) == 8, + "Recovered residency-query ABI changed"); +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idClip::idClipQueryParms) == 128, + "Recovered clip query parameters ABI changed"); +static_assert(sizeof(idClip::idClipCollisionQuery) == 24, + "Recovered collision query ABI changed"); +static_assert(sizeof(idClip::idClipGatherQuery) == 64, + "Recovered gather query ABI changed"); +static_assert(sizeof(idClip::idGatherResult) == 16, + "Recovered gather result ABI changed"); +static_assert(sizeof(idClip::idClipResidencyQuery) == 32, + "Recovered residency state ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/clipmodel.cpp b/source/engine/gamelib/physics/clipmodel.cpp new file mode 100644 index 0000000..cb7093c --- /dev/null +++ b/source/engine/gamelib/physics/clipmodel.cpp @@ -0,0 +1,279 @@ +#include "gamelib/physics/clipmodel.h" + +#include "cm/collisionmodel.h" +#include "cm/collisionmodelmanager.h" +#include "gamelib/aas2/aas2.h" +#include "gamelib/physics/clip.h" +#include "gamelib/physics/tracemodelcache.h" +#include "idlib/geometry/tracemodel.h" +#include "idlib/lib_print.h" + +#include + +namespace { + +void ClearAASLinks(idClipModel& model) { + for (idAASClipLink*& link : model.aasLinks) link = nullptr; +} + +} // namespace + +idClipModel::idClipModel() { + Init(nullptr); +} + +idClipModel::idClipModel(idClip* const ownerClip, const char* modelName, + const idMaterial* const material) { + Init(ownerClip); + LoadCollisionModel(modelName, material); +} + +idClipModel::idClipModel(idClip* const ownerClip, + idCollisionModel* const model) { + Init(ownerClip); + LoadCollisionModel(model); +} + +idClipModel::idClipModel(idClip* const ownerClip, + const idTraceModel* const traceModel, const int traceModelIndex, + const idMaterial* const material) { + Init(ownerClip); + LoadTraceModel(traceModel, traceModelIndex, material); +} + +idClipModel::idClipModel(idClip* const ownerClip, + idTreeAnimator* const animator) { + Init(ownerClip); + LoadAnimator(animator); +} + +idClipModel::idClipModel(idClip* const ownerClip, + const idClipModel& other) { + Init(ownerClip); + enabled = other.enabled; + entityNumber = other.entityNumber; + ownerNumber = other.ownerNumber; + physicsId = other.physicsId; + bodyId = other.bodyId; + bodyMask = other.bodyMask; + origin = other.origin; + axis = other.axis; + bounds = other.bounds; + absBounds = other.absBounds; + contents = other.contents; + collisionModel = other.collisionModel; + animatedModel = other.animatedModel; + if (clip != nullptr && clip->traceModelCache != nullptr) { + for (int index = 0; index < other.traceModels.Num(); ++index) { + traceModels.Append(clip->traceModelCache->CopyTraceModel( + other.traceModels[index])); + } + } +} + +idClipModel::~idClipModel() { + Unlink(); + FreeModel(); +} + +void idClipModel::Init(idClip* const ownerClip) { + enabled = true; + deleted = false; + entityNumber = -1; + ownerNumber = -1; + physicsId = 0; + bodyId = 0; + bodyMask = -1; + origin.Zero(); + axis = idMat3(1.0f); + bounds[0].Zero(); + bounds[1].Zero(); + absBounds = bounds; + contents = 0; + collisionModel = nullptr; + traceModels.Clear(); + animatedModel = nullptr; + clip = ownerClip; + clipLinks = nullptr; + collisionQuery.offset = 0; + touchCount = 0; + nextDeleted = nullptr; + ClearAASLinks(*this); +} + +void idClipModel::Delete() { + if (deleted) return; + deleted = true; + if (clip != nullptr) clip->DeleteClipModel(this); +} + +idCollisionModel* idClipModel::NoTraceModelWarning() const { + idLibPrint::Warning("idClipModel::GetCollisionModel: clip model %d on " + "entity %d is not a collision or trace model", bodyId, entityNumber); + return nullptr; +} + +idCollisionModel* idClipModel::GetCollisionModel() const { + if (collisionModel != nullptr) return collisionModel; + if (clip != nullptr && clip->traceModelCache != nullptr + && traceModels.Num() > 0) { + const idTraceModelCache::trmCache_t* const entry = + clip->traceModelCache->GetEntry(traceModels[0]); + if (entry != nullptr) return entry->collisionModel; + } + return NoTraceModelWarning(); +} + +const idTraceModel* idClipModel::GetTraceModel(const int index) const { + if (clip == nullptr || clip->traceModelCache == nullptr + || index < 0 || index >= traceModels.Num()) return nullptr; + const idTraceModelCache::trmCache_t* const entry = + clip->traceModelCache->GetEntry(traceModels[index]); + return entry != nullptr ? entry->trm : nullptr; +} + +void idClipModel::GetMassProperties(const float density, float& mass, + idVec3& centerOfMass, idMat3& inertiaTensor) const { + mass = 0.0f; + centerOfMass.Zero(); + inertiaTensor[0].Zero(); + inertiaTensor[1].Zero(); + inertiaTensor[2].Zero(); + if (clip == nullptr || clip->traceModelCache == nullptr) return; + for (int index = 0; index < traceModels.Num(); ++index) { + float partMass = 0.0f; + idVec3 partCenter; + idMat3 partInertia; + clip->traceModelCache->GetMassProperties(traceModels[index], + density, partMass, partCenter, partInertia); + centerOfMass = centerOfMass + partCenter * partMass; + mass += partMass; + for (int row = 0; row < 3; ++row) + for (int column = 0; column < 3; ++column) + inertiaTensor[row][column] += partInertia[row][column]; + } + if (mass > 0.0f) centerOfMass = centerOfMass * (1.0f / mass); +} + +void idClipModel::SetContents(const int newContents) { + if (contents == newContents) return; + const bool relink = IsLinked(); + if (relink) Unlink(); + contents = newContents; + if (relink) Link(); +} + +void idClipModel::FreeModel() { + if (clip != nullptr && clip->traceModelCache != nullptr) { + for (int index = 0; index < traceModels.Num(); ++index) + clip->traceModelCache->FreeTraceModel(traceModels[index]); + } + traceModels.ClearFree(); + collisionModel = nullptr; + animatedModel = nullptr; +} + +bool idClipModel::LoadCollisionModel(const char* const modelName, + const idMaterial*) { + if (modelName == nullptr || *modelName == '\0') return false; + LoadCollisionModel(collisionModelManager.LoadModel(modelName)); + return collisionModel != nullptr; +} + +void idClipModel::LoadCollisionModel(idCollisionModel* const model) { + FreeModel(); + collisionModel = model; + if (model != nullptr) { + model->GetBounds(bounds); + model->GetContents(contents); + absBounds.FromTransformedBounds(bounds, origin, axis); + } +} + +void idClipModel::LoadTraceModel(const idTraceModel* const traceModel, + const int traceModelIndex, const idMaterial* const material) { + FreeModel(); + if (traceModel == nullptr || clip == nullptr + || clip->traceModelCache == nullptr) return; + const int index = traceModelIndex >= 0 + ? clip->traceModelCache->CopyTraceModel(traceModelIndex) + : clip->traceModelCache->AllocTraceModel(*traceModel, material); + traceModels.Append(index); + bounds = traceModel->bounds; + absBounds.FromTransformedBounds(bounds, origin, axis); +} + +void idClipModel::LoadAnimator(idTreeAnimator* const animator) { + FreeModel(); + animatedModel = animator; +} + +void idClipModel::Serialize(idSerializer&) { + // The serializer transport lives in network; all recovered clip-model + // state is held in explicit fields and can be serialized by that layer. +} + +void idClipModel::Unlink() { + for (int aas = 0; aas < 8; ++aas) { + if (aasLinks[aas] != nullptr && clip != nullptr + && clip->aases[aas] != nullptr) { + aasLinks[aas] = clip->aases[aas]->UnLinkClipModel(aasLinks[aas]); + } + } + while (clipLinks != nullptr) { + clipLink_t* const link = clipLinks; + clipLinks = link->nextLink; + if (link->prevInSector != nullptr) + link->prevInSector->nextInSector = link->nextInSector; + else if (link->sector != nullptr) + link->sector->clipLinks = link->nextInSector; + if (link->nextInSector != nullptr) + link->nextInSector->prevInSector = link->prevInSector; + if (clip != nullptr) clip->clipLinkAllocator.Free(link); + } +} + +void idClipModel::Link_r(clipSector_t* const sector) { + if (sector == nullptr || clip == nullptr) return; + clipLink_t* const link = clip->clipLinkAllocator.Alloc(); + if (link == nullptr) return; + link->clipModel = this; + link->sector = sector; + link->prevInSector = nullptr; + link->nextInSector = sector->clipLinks; + if (sector->clipLinks != nullptr) + sector->clipLinks->prevInSector = link; + sector->clipLinks = link; + link->nextLink = clipLinks; + clipLinks = link; +} + +void idClipModel::Link() { + if (clip == nullptr || deleted) return; + Unlink(); + absBounds.FromTransformedBounds(bounds, origin, axis); + if (!clip->clipSectors.IsEmpty()) Link_r(&clip->clipSectors[0]); + for (int aas = 0; aas < 8; ++aas) { + if (clip->aases[aas] != nullptr) + aasLinks[aas] = clip->aases[aas]->LinkClipModel(this); + } +} + +void idClipModel::Link(const int entityNum, const int ownerNum, + const int physicsBodyId, const idVec3& newOrigin, + const idMat3& newAxis) { + entityNumber = entityNum; + ownerNumber = ownerNum; + physicsId = physicsBodyId; + bodyId = physicsBodyId; + origin = newOrigin; + axis = newAxis; + Link(); +} + +void idClipModel::Link(const int entityNum, const int ownerNum, + const int physicsBodyId, const idVec3& newOrigin, + const idMat3& newAxis, const idBounds& newBounds) { + bounds = newBounds; + Link(entityNum, ownerNum, physicsBodyId, newOrigin, newAxis); +} diff --git a/source/engine/gamelib/physics/clipmodel.h b/source/engine/gamelib/physics/clipmodel.h index f009cca..781b656 100644 --- a/source/engine/gamelib/physics/clipmodel.h +++ b/source/engine/gamelib/physics/clipmodel.h @@ -1,87 +1,156 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\clipmodel.h -// Recovered logical types: 6 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/jobs/collisionquery.h" +#include "idlib/bv/bounds.h" +#include "idlib/containers/list.h" +#include "idlib/text/atomicstring.h" +#include -// IDA Local Type ordinal 1525; PDB kind: enum. -enum idClipModelInfo::clipModelType_t : __int32 -{ - CLIPMODEL_NONE = 0x0, - CLIPMODEL_RENDERMODEL = 0x1, - CLIPMODEL_CUSTOM = 0x2, - CLIPMODEL_BOX = 0x3, - CLIPMODEL_CYLINDER = 0x4, - CLIPMODEL_CONE = 0x5, - CLIPMODEL_BOUNDINGBOX = 0x6, +class idAASClipLink; +class idClip; +class idCollisionModel; +class idMaterial; +class idSerializer; +class idTraceModel; +class idTreeAnimator; +struct clipSector_t; + +enum clipMode_t : int { + CLIPMODE_INTERSECT = 0, + CLIPMODE_UNION = 1, + CLIPMODE_DIFFERENCE = 2, + CLIPMODE_XOR = 3 }; -// IDA Local Type ordinal 1973; PDB kind: enum. -enum clipMode_t : __int32 -{ - CLIPMODE_INTERSECT = 0x0, - CLIPMODE_UNION = 0x1, - CLIPMODE_DIFFERENCE = 0x2, - CLIPMODE_XOR = 0x3, +struct clipLink_t { + class idClipModel* clipModel; + clipSector_t* sector; + clipLink_t* prevInSector; + clipLink_t* nextInSector; + clipLink_t* nextLink; }; -// IDA Local Type ordinal 14041; PDB kind: class. -class idClipModel -{ +class idClipModel { public: - // Recovered virtual interface; IDA vtable ordinal 14042. - virtual ~idClipModel(); + idClipModel(); + idClipModel(idClip* ownerClip, const char* modelName, + const idMaterial* material = nullptr); + idClipModel(idClip* ownerClip, idCollisionModel* model); + idClipModel(idClip* ownerClip, const idTraceModel* traceModel, + int traceModelIndex, const idMaterial* material = nullptr); + idClipModel(idClip* ownerClip, idTreeAnimator* animator); + idClipModel(idClip* ownerClip, const idClipModel& other); + virtual ~idClipModel(); - __declspec(align(8)) bool enabled; - bool deleted; - int entityNumber; - int ownerNumber; - int physicsId; - int bodyId; - int bodyMask; - idVec3 origin; - idMat3 axis; - idBounds bounds; - idBounds absBounds; - int contents; - idCollisionModel *collisionModel; - idList traceModels; - idTreeAnimator *animatedModel; - idClip *clip; - clipLink_t *clipLinks; - idCollisionQuery collisionQuery; - int touchCount; - idClipModel *nextDeleted; - idAASClipLink *aasLinks[8]; + void Delete(); + void Unlink(); + void Link(); + void Link(int entityNum, int ownerNum, int physicsBodyId, + const idVec3& newOrigin, const idMat3& newAxis); + void Link(int entityNum, int ownerNum, int physicsBodyId, + const idVec3& newOrigin, const idMat3& newAxis, + const idBounds& newBounds); + void SetContents(int newContents); + int GetContents() const { return contents; } + void Enable() { enabled = true; } + void Disable() { enabled = false; } + bool IsEnabled() const { return enabled; } + bool IsLinked() const { return clipLinks != nullptr; } + bool IsDeleted() const { return deleted; } + int GetEntityNumber() const { return entityNumber; } + int GetOwnerNumber() const { return ownerNumber; } + int GetPhysicsId() const { return physicsId; } + int GetBodyId() const { return bodyId; } + int GetBodyMask() const { return bodyMask; } + void SetBodyMask(int mask) { bodyMask = mask; } + const idVec3& GetOrigin() const { return origin; } + const idMat3& GetAxis() const { return axis; } + const idBounds& GetBounds() const { return bounds; } + const idBounds& GetAbsBounds() const { return absBounds; } + idCollisionModel* GetCollisionModel() const; + const idTraceModel* GetTraceModel(int index = 0) const; + int GetNumTraceModels() const { return traceModels.Num(); } + void GetMassProperties(float density, float& mass, + idVec3& centerOfMass, idMat3& inertiaTensor) const; + void Serialize(idSerializer& serializer); + + bool enabled; + bool deleted; + int entityNumber; + int ownerNumber; + int physicsId; + int bodyId; + int bodyMask; + idVec3 origin; + idMat3 axis; + idBounds bounds; + idBounds absBounds; + int contents; + idCollisionModel* collisionModel; + idList traceModels; + idTreeAnimator* animatedModel; + idClip* clip; + clipLink_t* clipLinks; + idCollisionQuery collisionQuery; + int touchCount; + idClipModel* nextDeleted; + idAASClipLink* aasLinks[8]; + +private: + idCollisionModel* NoTraceModelWarning() const; + void Init(idClip* ownerClip); + void FreeModel(); + bool LoadCollisionModel(const char* modelName, + const idMaterial* material); + void LoadCollisionModel(idCollisionModel* model); + void LoadTraceModel(const idTraceModel* traceModel, int traceModelIndex, + const idMaterial* material); + void LoadAnimator(idTreeAnimator* animator); + void Link_r(clipSector_t* sector); }; -// IDA Local Type ordinal 15173; PDB kind: class. -class idStrClipModel : public idAtomicString -{ +class idStrClipModel : public idAtomicString { public: + using idAtomicString::idAtomicString; }; -// IDA Local Type ordinal 15174; PDB kind: class. -class idClipModelInfo -{ +class idClipModelInfo { public: - idClipModelInfo::clipModelType_t type; - idVec3 size; - idVec3 offset; - int numSides; - idStrClipModel clipModelName; - bool clipShrink; - bool allowInline; - bool softObstacle; - const idMaterial *overrideClipMaterial; + enum clipModelType_t : int { + CLIPMODEL_NONE = 0, + CLIPMODEL_RENDERMODEL = 1, + CLIPMODEL_CUSTOM = 2, + CLIPMODEL_BOX = 3, + CLIPMODEL_CYLINDER = 4, + CLIPMODEL_CONE = 5, + CLIPMODEL_BOUNDINGBOX = 6 + }; + + clipModelType_t type; + idVec3 size; + idVec3 offset; + int numSides; + idStrClipModel clipModelName; + bool clipShrink; + bool allowInline; + bool softObstacle; + const idMaterial* overrideClipMaterial; }; -// IDA Local Type ordinal 22949; PDB kind: struct. -struct cbp_model_s -{ - int state[16]; - int count0[16]; - int count1[16]; +struct cbp_model_s { + int state[16]; + int count0[16]; + int count1[16]; }; + +static_assert(sizeof(clipLink_t) == sizeof(void*) * 5, + "Recovered clip-link ABI changed"); +static_assert(sizeof(cbp_model_s) == 192, + "Recovered clip-model predictor ABI changed"); +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idClipModel) == 216, + "Recovered idClipModel ABI changed"); +static_assert(sizeof(idClipModelInfo) == 44, + "Recovered idClipModelInfo ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/physics.cpp b/source/engine/gamelib/physics/physics.cpp new file mode 100644 index 0000000..0771f20 --- /dev/null +++ b/source/engine/gamelib/physics/physics.cpp @@ -0,0 +1,106 @@ +#include "gamelib/physics/physics.h" + +#include "idlib/lib_print.h" + +#include + +idPhysics* idPhysics::hash[1024] = {}; +int idPhysics::currentPhysicsId = 0; + +idPhysics::idPhysics() + : type(PHYSICS_INVALID), + physicsId(++currentPhysicsId), + entityNumber(0x1FFF), + nextInHash(nullptr), + clip(nullptr), + callbacks(nullptr), + residencyQuery{0}, + collisionResidency(RESIDENCY_NOT_TESTED) { + Link(); +} + +idPhysics::~idPhysics() { + Unlink(); + nextInHash = nullptr; + clip = nullptr; + callbacks = nullptr; +} + +void idPhysics::InitPhysics(idClip* clip_, idPhysicsCallbacks* callbacks_, + const int entityNumber_) { + clip = clip_; + callbacks = callbacks_; + entityNumber = entityNumber_; +} + +void idPhysics::ClearHash() { + std::memset(hash, 0, sizeof(hash)); + currentPhysicsId = 0; +} + +idPhysics* idPhysics::GetPhysicsForId(const int physicsId_) { + if (physicsId_ == -1) { + return nullptr; + } + + for (idPhysics* item = hash[physicsId_ & 1023]; item != nullptr; + item = item->nextInHash) { + if (item->physicsId == physicsId_) { + return item; + } + } + return nullptr; +} + +void idPhysics::UpdateCollisionResidency(const contactInfo_t& contact) { + if ((contact.flags & CONTACT_FLAG_SUBMODEL_NOT_RESIDENT) != 0) { + collisionResidency = RESIDENCY_NO; + } +} + +void idPhysics::Unlink() { + idPhysics** link = &hash[physicsId & 1023]; + while (*link != nullptr) { + if (*link == this && (*link)->physicsId == physicsId) { + *link = nextInHash; + return; + } + link = &(*link)->nextInHash; + } +} + +void idPhysics::Link() { + if (physicsId != -1 && GetPhysicsForId(physicsId) != nullptr) { + idLibPrint::Printf(" duplicate physicsId = %d\n", physicsId); + do { + physicsId = currentPhysicsId++; + } while (physicsId != -1 && GetPhysicsForId(physicsId) != nullptr); + } + + const int bucket = physicsId & 1023; + nextInHash = hash[bucket]; + hash[bucket] = this; +} + +void OutputPhysicsHash_f(const idCmdArgs&) { + for (int bucket = 0; bucket < 1024; ++bucket) { + if (idPhysics::hash[bucket] == nullptr) { + continue; + } + idLibPrint::Printf("%d:", bucket); + for (idPhysics* item = idPhysics::hash[bucket]; item != nullptr; + item = item->nextInHash) { + idLibPrint::Printf(" (%d) 0x%p", item->physicsId, item); + } + idLibPrint::Printf("\n"); + } +} + +bool idPhysics::IsInNonResidentCollisionArea(const bool assumeNonResident) { + // The recovered routine first consumes/submits an asynchronous idClip + // residency query. That queue is owned by clip.cpp and will be connected + // when that authoritative unit is reconstructed. Its final state test is + // independent and is retained here. + return collisionResidency == RESIDENCY_NO + || (collisionResidency == RESIDENCY_NOT_TESTED && assumeNonResident); +} diff --git a/source/engine/gamelib/physics/physics.h b/source/engine/gamelib/physics/physics.h index 6fac99e..4749e91 100644 --- a/source/engine/gamelib/physics/physics.h +++ b/source/engine/gamelib/physics/physics.h @@ -1,218 +1,187 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\physics.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/jobs/collisionresults.h" +#include "idlib/math/matrix.h" +#include "idlib/math/vector.h" +#include +#include -// IDA Local Type ordinal 1033; PDB kind: enum. -enum physicsType_t : __int32 -{ - PHYSICS_INVALID = 0x0, - PHYSICS_STATIC = 0x1, - PHYSICS_STATICMULTI = 0x2, - PHYSICS_AI = 0x3, - PHYSICS_PLAYER = 0x4, - PHYSICS_PARAMETRIC = 0x5, - PHYSICS_RIGIDBODY = 0x6, - PHYSICS_RIGIDBODY3DOF = 0x7, - PHYSICS_AF = 0x8, - PHYSICS_ROBOT = 0x9, +class idBounds; +class idClip; +class idClipModel; +class idCmdArgs; +class idPhysics; +class idPhysicsCallbacks; +class idRotation; + +enum bindFlags_t : int; + +enum physicsType_t : int { + PHYSICS_INVALID = 0, + PHYSICS_STATIC = 1, + PHYSICS_STATICMULTI = 2, + PHYSICS_AI = 3, + PHYSICS_PLAYER = 4, + PHYSICS_PARAMETRIC = 5, + PHYSICS_RIGIDBODY = 6, + PHYSICS_RIGIDBODY3DOF = 7, + PHYSICS_AF = 8, + PHYSICS_ROBOT = 9 }; -// IDA Local Type ordinal 2858; PDB kind: enum. -enum idPhysics::residency_t : __int32 -{ - RESIDENCY_NOT_TESTED = 0x0, - RESIDENCY_YES = 0x1, - RESIDENCY_NO = 0x2, +struct impactInfo_t { + float invMass; + idMat3 invInertiaTensor; + idVec3 position; + idVec3 velocity; + + // Materialized in the authoritative physics.h dump. + void Zero() { + invMass = 0.0f; + invInertiaTensor[0].Zero(); + invInertiaTensor[1].Zero(); + invInertiaTensor[2].Zero(); + position.Zero(); + velocity.Zero(); + } }; -// IDA Local Type ordinal 14164; PDB kind: class. -class idPhysics -{ +#ifndef ID_RESIDENCY_QUERY_DEFINED +#define ID_RESIDENCY_QUERY_DEFINED +struct idResidencyQuery { std::uint64_t index; }; +#endif + +class alignas(8) idPhysics { public: - // Recovered virtual interface; IDA vtable ordinal 14168. - virtual ~idPhysics(); - virtual void SetClipModel(idClipModel *, float, int, bool); - virtual idClipModel *GetClipModel(int); - virtual int GetNumClipModels(); - virtual void SetMass(float, int); - virtual float GetMass(int); - virtual void SetContents(int, int); - virtual int GetContents(int); - virtual void SetClipMask(int, int); - virtual int GetClipMask(int); - virtual const idBounds *GetBounds(int); - virtual const idBounds *GetAbsBounds(int); - virtual void SetOrigin(const idVec3 *, int); - virtual void SetAxis(const idMat3 *, int); - virtual void Translate(const idVec3 *, int); - virtual void Rotate(const idRotation *, int); - virtual const idVec3 *GetOrigin(int); - virtual const idMat3 *GetAxis(int); - virtual const idVec3 *GetLocalOrigin(int); - virtual const idMat3 *GetLocalAxis(int); - virtual void SetLinearVelocity(const idVec3 *, int); - virtual void SetAngularVelocity(const idVec3 *, int); - virtual idVec3 *GetLinearVelocity(idVec3 *result, int); - virtual idVec3 *GetAngularVelocity(idVec3 *result, int); - virtual void SetGravity(const idVec3 *); - virtual const idVec3 *GetGravity(); - virtual const idVec3 *GetGravityNormal(); - virtual void SetWaterLevel(float, int); - virtual float GetWaterLevel(int); - virtual void SetWaterViscosity(float, int); - virtual float GetWaterViscosity(int); - virtual void SetWaterEntNum(int); - virtual int GetWaterEntNum(); - virtual void SetWaterSurfaceWrldHeight(float); - virtual float GetWaterSurfaceWrldHeight(); - virtual void GetImpactInfo(const int, const idVec3 *, impactInfo_t *); - virtual void ApplyImpulse(const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const idVec3 *, const idVec3 *); - virtual void Activate(); - virtual void PutToRest(); - virtual bool IsAtRest(); - virtual bool IsPushable(int); - virtual void SaveState(); - virtual void RestoreState(); - virtual bool Evaluate(int, int); - virtual void UpdateTime(int); - virtual void ClipTranslation(trace_t *, const idVec3 *, const idClipModel *); - virtual void ClipRotation(trace_t *, const idRotation *, const idClipModel *); - virtual int ClipContents(const idClipModel *, int); - virtual void DisableClip(); - virtual void EnableClip(); - virtual void UnlinkClip(); - virtual void LinkClip(); - virtual bool EvaluateContacts(); - virtual int GetNumContacts(); - virtual const contactInfo_t *GetContact(int); - virtual void ClearContacts(); - virtual void AddContactPhysics(idPhysics *); - virtual void RemoveContactPhysics(idPhysics *); - virtual int GetNumContactPhysics(); - virtual idPhysics *GetContactPhysics(int); - virtual void ActivateContactPhysics(); - virtual bool HasGroundContacts(); - virtual bool IsGroundEntity(int); - virtual bool IsGroundClipModel(int, int); - virtual void SetPushed(int); - virtual idVec3 *GetPushedLinearVelocity(idVec3 *result, const int); - virtual idVec3 *GetPushedAngularVelocity(idVec3 *result, const int); - virtual void SetMaster(bool, const idVec3 *, const idMat3 *, const bindFlags_t); - virtual void SetLocalOrigin(const idVec3 *, int); - virtual void SetLocalAxis(const idMat3 *, int); - virtual int GetBlockingEntityNum(); - virtual int GetLinearEndTime(); - virtual int GetAngularEndTime(); - virtual bool IsInNonResidentCollisionArea(bool); + enum residency_t : int { + RESIDENCY_NOT_TESTED = 0, + RESIDENCY_YES = 1, + RESIDENCY_NO = 2 + }; - __declspec(align(8)) physicsType_t type; - int physicsId; - int entityNumber; - idPhysics *nextInHash; - idClip *clip; - idPhysicsCallbacks *callbacks; - idResidencyQuery residencyQuery; - idPhysics::residency_t collisionResidency; + idPhysics(); + virtual ~idPhysics(); + + virtual void SetClipModel(idClipModel*, float, int, bool) = 0; + virtual idClipModel* GetClipModel(int) = 0; + virtual int GetNumClipModels() = 0; + virtual void SetMass(float, int) = 0; + virtual float GetMass(int) = 0; + virtual void SetContents(int, int) = 0; + virtual int GetContents(int) = 0; + virtual void SetClipMask(int, int) = 0; + virtual int GetClipMask(int) = 0; + virtual const idBounds* GetBounds(int) = 0; + virtual const idBounds* GetAbsBounds(int) = 0; + virtual void SetOrigin(const idVec3*, int) = 0; + virtual void SetAxis(const idMat3*, int) = 0; + virtual void Translate(const idVec3*, int) = 0; + virtual void Rotate(const idRotation*, int) = 0; + virtual const idVec3* GetOrigin(int) = 0; + virtual const idMat3* GetAxis(int) = 0; + virtual const idVec3* GetLocalOrigin(int) = 0; + virtual const idMat3* GetLocalAxis(int) = 0; + virtual void SetLinearVelocity(const idVec3*, int) = 0; + virtual void SetAngularVelocity(const idVec3*, int) = 0; + virtual idVec3* GetLinearVelocity(idVec3*, int) = 0; + virtual idVec3* GetAngularVelocity(idVec3*, int) = 0; + virtual void SetGravity(const idVec3*) = 0; + virtual const idVec3* GetGravity() = 0; + virtual const idVec3* GetGravityNormal() = 0; + virtual void SetWaterLevel(float, int) = 0; + virtual float GetWaterLevel(int) = 0; + virtual void SetWaterViscosity(float, int) = 0; + virtual float GetWaterViscosity(int) = 0; + virtual void SetWaterEntNum(int) = 0; + virtual int GetWaterEntNum() = 0; + virtual void SetWaterSurfaceWrldHeight(float) = 0; + virtual float GetWaterSurfaceWrldHeight() = 0; + virtual void GetImpactInfo(int, const idVec3*, impactInfo_t*) = 0; + virtual void ApplyImpulse(int, const idVec3*, const idVec3*) = 0; + virtual void ApplyForce(int, const idVec3*, const idVec3*) = 0; + virtual void Activate() = 0; + virtual void PutToRest() = 0; + virtual bool IsAtRest() = 0; + virtual bool IsPushable(int) = 0; + virtual void SaveState() = 0; + virtual void RestoreState() = 0; + virtual bool Evaluate(int, int) = 0; + virtual void UpdateTime(int) = 0; + virtual void ClipTranslation(trace_t*, const idVec3*, const idClipModel*) = 0; + virtual void ClipRotation(trace_t*, const idRotation*, const idClipModel*) = 0; + virtual int ClipContents(const idClipModel*, int) = 0; + virtual void DisableClip() = 0; + virtual void EnableClip() = 0; + virtual void UnlinkClip() = 0; + virtual void LinkClip() = 0; + virtual bool EvaluateContacts() = 0; + virtual int GetNumContacts() = 0; + virtual const contactInfo_t* GetContact(int) = 0; + virtual void ClearContacts() = 0; + virtual void AddContactPhysics(idPhysics*) = 0; + virtual void RemoveContactPhysics(idPhysics*) = 0; + virtual int GetNumContactPhysics() = 0; + virtual idPhysics* GetContactPhysics(int) = 0; + virtual void ActivateContactPhysics() = 0; + virtual bool HasGroundContacts() = 0; + virtual bool IsGroundEntity(int) = 0; + virtual bool IsGroundClipModel(int, int) = 0; + virtual void SetPushed(int) = 0; + virtual idVec3* GetPushedLinearVelocity(idVec3*, int) = 0; + virtual idVec3* GetPushedAngularVelocity(idVec3*, int) = 0; + virtual void SetMaster(bool, const idVec3*, const idMat3*, bindFlags_t) = 0; + virtual void SetLocalOrigin(const idVec3*, int) = 0; + virtual void SetLocalAxis(const idMat3*, int) = 0; + virtual int GetBlockingEntityNum() = 0; + virtual int GetLinearEndTime() = 0; + virtual int GetAngularEndTime() = 0; + virtual bool IsInNonResidentCollisionArea(bool assumeNonResident); + + void InitPhysics(idClip* clip_, idPhysicsCallbacks* callbacks_, + int entityNumber_); + + static void ClearHash(); + static idPhysics* GetPhysicsForId(int physicsId_); + + physicsType_t GetType() const { return type; } + int GetPhysicsId() const { return physicsId; } + int GetEntityNumber() const { return entityNumber; } + residency_t GetCollisionResidency() const { return collisionResidency; } + +protected: + void UpdateCollisionResidency(const contactInfo_t& contact); + +private: + void Link(); + void Unlink(); + + static idPhysics* hash[1024]; + static int currentPhysicsId; + + alignas(8) physicsType_t type; + int physicsId; + int entityNumber; + idPhysics* nextInHash; + idClip* clip; + idPhysicsCallbacks* callbacks; + idResidencyQuery residencyQuery; + residency_t collisionResidency; + + friend void OutputPhysicsHash_f(const idCmdArgs& args); }; -// IDA Local Type ordinal 19509; PDB kind: class. -class idPhysics_Robot : public idPhysics_DynamicBase -{ -public: - // Recovered virtual interface; IDA vtable ordinal 19510. - virtual ~idPhysics_Robot(); - virtual void SetClipModel(idClipModel *, float, int, bool); - virtual idClipModel *GetClipModel(int); - virtual int GetNumClipModels(); - virtual void SetMass(float, int); - virtual float GetMass(int); - virtual void SetContents(int, int); - virtual int GetContents(int); - virtual void SetClipMask(int, int); - virtual int GetClipMask(int); - virtual const idBounds *GetBounds(int); - virtual const idBounds *GetAbsBounds(int); - virtual void SetOrigin(const idVec3 *, int); - virtual void SetAxis(const idMat3 *, int); - virtual void Translate(const idVec3 *, int); - virtual void Rotate(const idRotation *, int); - virtual const idVec3 *GetOrigin(int); - virtual const idMat3 *GetAxis(int); - virtual const idVec3 *GetLocalOrigin(int); - virtual const idMat3 *GetLocalAxis(int); - virtual void SetLinearVelocity(const idVec3 *, int); - virtual void SetAngularVelocity(const idVec3 *, int); - virtual idVec3 *GetLinearVelocity(idVec3 *result, int); - virtual idVec3 *GetAngularVelocity(idVec3 *result, int); - virtual void SetGravity(const idVec3 *); - virtual const idVec3 *GetGravity(); - virtual const idVec3 *GetGravityNormal(); - virtual void SetWaterLevel(float, int); - virtual float GetWaterLevel(int); - virtual void SetWaterViscosity(float, int); - virtual float GetWaterViscosity(int); - virtual void SetWaterEntNum(int); - virtual int GetWaterEntNum(); - virtual void SetWaterSurfaceWrldHeight(float); - virtual float GetWaterSurfaceWrldHeight(); - virtual void GetImpactInfo(const int, const idVec3 *, impactInfo_t *); - virtual void ApplyImpulse(const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const idVec3 *, const idVec3 *); - virtual void Activate(); - virtual void PutToRest(); - virtual bool IsAtRest(); - virtual bool IsPushable(int); - virtual void SaveState(); - virtual void RestoreState(); - virtual bool Evaluate(int, int); - virtual void UpdateTime(int); - virtual void ClipTranslation(trace_t *, const idVec3 *, const idClipModel *); - virtual void ClipRotation(trace_t *, const idRotation *, const idClipModel *); - virtual int ClipContents(const idClipModel *, int); - virtual void DisableClip(); - virtual void EnableClip(); - virtual void UnlinkClip(); - virtual void LinkClip(); - virtual bool EvaluateContacts(); - virtual int GetNumContacts(); - virtual const contactInfo_t *GetContact(int); - virtual void ClearContacts(); - virtual void AddContactPhysics(idPhysics *); - virtual void RemoveContactPhysics(idPhysics *); - virtual int GetNumContactPhysics(); - virtual idPhysics *GetContactPhysics(int); - virtual void ActivateContactPhysics(); - virtual bool HasGroundContacts(); - virtual bool IsGroundEntity(int); - virtual bool IsGroundClipModel(int, int); - virtual void SetPushed(int); - virtual idVec3 *GetPushedLinearVelocity(idVec3 *result, const int); - virtual idVec3 *GetPushedAngularVelocity(idVec3 *result, const int); - virtual void SetMaster(bool, const idVec3 *, const idMat3 *, const bindFlags_t); - virtual void SetLocalOrigin(const idVec3 *, int); - virtual void SetLocalAxis(const idMat3 *, int); - virtual int GetBlockingEntityNum(); - virtual int GetLinearEndTime(); - virtual int GetAngularEndTime(); - virtual bool IsInNonResidentCollisionArea(bool); - virtual bool IsOutsideWorld(); +void OutputPhysicsHash_f(const idCmdArgs& args); - idClipQuery stepMoveQuery; - idMat3 clipAxis; - idVec3 lastCollisionNormal; - idClipModel *clipModel; - robotPState_t savedState; - robotPState_t currentState; - float maxStepHeight; - float minFloorCosine; - float mass; - float invMass; - bool noImpact; - float timeStep; - int timeStepMS; -}; +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(impactInfo_t) == 64, + "Recovered impactInfo_t ABI changed"); +static_assert(sizeof(contactInfo_t) == 76, + "Recovered contactInfo_t ABI changed"); +static_assert(sizeof(trace_t) == 128, + "Recovered trace_t ABI changed"); +static_assert(sizeof(idResidencyQuery) == 8, + "Recovered idResidencyQuery ABI changed"); +static_assert(sizeof(idPhysics) == 48, + "Recovered idPhysics ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/tracemodelcache.cpp b/source/engine/gamelib/physics/tracemodelcache.cpp new file mode 100644 index 0000000..41957be --- /dev/null +++ b/source/engine/gamelib/physics/tracemodelcache.cpp @@ -0,0 +1,226 @@ +#include "gamelib/physics/tracemodelcache.h" + +#include "cm/collisionmodel.h" +#include "cm/collisionmodelmanager.h" +#include "idlib/lib_print.h" + +#include +#include +#include +#include +#include +#include + +bool GameLib_IsMultiplayer(); + +namespace { + +std::uint32_t FloatBits(const float value) { + std::uint32_t bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +int TraceModelHashKey(const idTraceModel& traceModel) { + const std::uint32_t key = + (4u * ((4u * ((16u * static_cast(traceModel.type)) ^ + traceModel.numVerts)) ^ traceModel.numEdges)) ^ + traceModel.numPolys ^ FloatBits(traceModel.bounds[0].z) ^ + FloatBits(traceModel.bounds[0].y) ^ FloatBits(traceModel.bounds[0].x); + return static_cast(key); +} + +struct waterPointBuild_t { + idVec3 xyz; + float weight; + float weightSquared; +}; + +} // namespace + +void idTraceModelCache::FreeTraceModel(const int traceModelIndex) { + trmCache_t* const entry = GetEntry(traceModelIndex); + if (entry == nullptr || entry->refCount <= 0) { + idLibPrint::Warning( + "idClipModel::FreeTraceModel: tried to free uncached trace model"); + return; + } + --entry->refCount; +} + +int idTraceModelCache::CopyTraceModel(const int traceModelIndex) { + trmCache_t* const entry = GetEntry(traceModelIndex); + if (entry == nullptr || entry->refCount <= 0) { + idLibPrint::Warning( + "idClipModel::CopyTraceModel: tried to copy an uncached trace " + "model"); + return -1; + } + ++entry->refCount; + return traceModelIndex; +} + +void idTraceModelCache::ClearTraceModelCache() { + for (int index = 0; index < cache.Num(); ++index) { + trmCache_t* const entry = cache[index]; + if (entry == nullptr) { + continue; + } + if (entry->collisionModel != nullptr) { + delete entry->collisionModel; + entry->collisionModel = nullptr; + } + entry->waterPoints.ClearFree(); + delete entry->trm; + entry->trm = nullptr; + entry->~trmCache_t(); + } + cacheAllocator.Shutdown(); + cache.ClearFree(); + hash.Free(); +} + +void idTraceModelCache::SetupWaterPoints(trmCache_t* const entry) { + entry->waterPoints.ClearFree(); + entry->hasWater = false; + if (entry->trm == nullptr || !entry->trm->isConvex || + GameLib_IsMultiplayer()) { + return; + } + + const idBounds& bounds = entry->trm->bounds; + const idVec3 dimensions = bounds[1] - bounds[0]; + if (dimensions.x <= 0.0f || dimensions.y <= 0.0f || + dimensions.z <= 0.0f || + dimensions.x * dimensions.y * dimensions.z == 0.0f) { + return; + } + + const int numVertices = static_cast(entry->trm->numVerts); + const int desiredInteriorPoints = 64 - numVertices; + if (desiredInteriorPoints < 0) { + return; + } + + std::vector interiorPoints; + float sampleMultiplier = 1.0f; + for (;;) { + interiorPoints.clear(); + const float divisions = std::cbrt( + static_cast(desiredInteriorPoints) * sampleMultiplier); + if (divisions > 0.0f) { + const idVec3 step(dimensions.x / divisions, + dimensions.y / divisions, dimensions.z / divisions); + for (int x = 0; static_cast(x) < divisions; ++x) { + for (int y = 0; static_cast(y) < divisions; ++y) { + for (int z = 0; static_cast(z) < divisions; ++z) { + waterPointBuild_t point{}; + point.xyz.Set( + bounds[0].x + (static_cast(x) + 0.5f) * step.x, + bounds[0].y + (static_cast(y) + 0.5f) * step.y, + bounds[0].z + (static_cast(z) + 0.5f) * step.z); + point.weight = 1.0f; + point.weightSquared = 1.0f; + if (entry->trm->ContainsPoint(point.xyz)) { + interiorPoints.push_back(point); + } + } + } + } + } + + if (static_cast(interiorPoints.size()) >= desiredInteriorPoints) { + break; + } + sampleMultiplier *= 2.0f; + if (sampleMultiplier > 16.0f) { + return; + } + } + + const float pointWeight = 1.0f / + static_cast(interiorPoints.size() + numVertices); + for (waterPointBuild_t& point : interiorPoints) { + point.weight = pointWeight; + point.weightSquared = pointWeight * pointWeight; + } + + while (static_cast(interiorPoints.size()) > desiredInteriorPoints) { + float bestMetric = (std::numeric_limits::max)(); + int bestFirst = 0; + int bestSecond = 1; + for (int first = 0; + first < static_cast(interiorPoints.size()); ++first) { + for (int second = first + 1; + second < static_cast(interiorPoints.size()); ++second) { + const idVec3 delta = interiorPoints[first].xyz - + interiorPoints[second].xyz; + const float metric = delta.LengthSqr() * + interiorPoints[first].weight * + interiorPoints[second].weight; + if (metric < bestMetric) { + bestMetric = metric; + bestFirst = first; + bestSecond = second; + } + } + } + + waterPointBuild_t& destination = interiorPoints[bestFirst]; + destination.xyz = (destination.xyz + + interiorPoints[bestSecond].xyz) * 0.5f; + destination.weight += interiorPoints[bestSecond].weight; + destination.weightSquared = destination.weight * destination.weight; + interiorPoints[bestSecond] = interiorPoints.back(); + interiorPoints.pop_back(); + } + + entry->waterPoints.SetNum(64); + int outputIndex = 0; + for (const waterPointBuild_t& point : interiorPoints) { + entry->waterPoints[outputIndex++] = {point.xyz, point.weight}; + } + for (int vertex = 0; vertex < numVertices && outputIndex < 64; ++vertex) { + traceModelWater_t& point = entry->waterPoints[outputIndex++]; + point.xyz.Set(entry->trm->vertsX[vertex], + entry->trm->vertsY[vertex], entry->trm->vertsZ[vertex]); + point.weight = pointWeight; + } + while (outputIndex < 64) { + entry->waterPoints[outputIndex++] = {idVec3(0.0f, 0.0f, 0.0f), 0.0f}; + } + entry->hasWater = true; +} + +int idTraceModelCache::AllocTraceModel(const idTraceModel& traceModel, + const idMaterial* const material) { + const int key = TraceModelHashKey(traceModel); + for (int index = hash.First(key); index != -1; + index = hash.Next(index)) { + trmCache_t* const entry = GetEntry(index); + if (entry != nullptr && entry->trm != nullptr && + entry->trm->Compare(traceModel) && entry->material == material) { + ++entry->refCount; + return index; + } + } + + trmCache_t* const entry = cacheAllocator.Alloc(); + if (entry == nullptr) { + return -1; + } + entry->trm = new idTraceModel(traceModel); + entry->trm->ClearUnused(); + entry->trm->GetMassProperties(1.0f, entry->volume, + entry->centerOfMass, entry->inertiaTensor); + entry->refCount = 1; + entry->material = material; + const int index = cache.Append(entry); + hash.Add(key, index); + char modelName[64]; + std::snprintf(modelName, sizeof(modelName), "traceModel%d", index); + entry->collisionModel = collisionModelManager.ModelFromTrm( + modelName, *entry->trm, material); + SetupWaterPoints(entry); + return index; +} diff --git a/source/engine/gamelib/physics/tracemodelcache.h b/source/engine/gamelib/physics/tracemodelcache.h index 93be12b..b2b54f7 100644 --- a/source/engine/gamelib/physics/tracemodelcache.h +++ b/source/engine/gamelib/physics/tracemodelcache.h @@ -1,31 +1,103 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\tracemodelcache.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/blockalloc_base.h" +#include "idlib/containers/hashindex.h" +#include "idlib/containers/list.h" +#include "idlib/geometry/tracemodel.h" +#include "idlib/text/str.h" +class idCollisionModel; +class idMaterial; -// IDA Local Type ordinal 14116; PDB kind: struct. -struct idTraceModelCache::trmCache_t -{ - idTraceModel *trm; - int refCount; - float volume; - idVec3 centerOfMass; - idMat3 inertiaTensor; - const idMaterial *material; - idCollisionModel *collisionModel; - bool hasWater; - idList waterPoints; +struct traceModelWater_t { + idVec3 xyz; + float weight; }; -// IDA Local Type ordinal 14121; PDB kind: class. -class idTraceModelCache -{ +// Reconstructed from PDB local types 14116/14121. Methods are introduced as +// their authoritative bodies are activated. +class idTraceModelCache { public: - idStr mapName; - idBlockAlloc cacheAllocator; - idList cache; - idHashIndex hash; + struct trmCache_t { + idTraceModel *trm; + int refCount; + float volume; + idVec3 centerOfMass; + idMat3 inertiaTensor; + const idMaterial *material; + idCollisionModel *collisionModel; + bool hasWater; + idList waterPoints; + + // Materialized in the authoritative tracemodelcache.h dump. + trmCache_t() + : trm(nullptr), refCount(1), volume(0.0f), centerOfMass(), + inertiaTensor(), material(nullptr), collisionModel(nullptr), + hasWater(false), waterPoints(0) { + centerOfMass.Zero(); + inertiaTensor[0].Zero(); + inertiaTensor[1].Zero(); + inertiaTensor[2].Zero(); + } + }; + + // Materialized in the authoritative tracemodelcache.h dump. + idTraceModelCache() + : mapName(), cacheAllocator(false), cache(0), + hash(1024, 1024, TAG_HASHINDEX) { + } + + int AllocTraceModel(const idTraceModel& traceModel, + const idMaterial* material); + void FreeTraceModel(int traceModelIndex); + int CopyTraceModel(int traceModelIndex); + void ClearTraceModelCache(); + + const trmCache_t* GetEntry(const int traceModelIndex) const { + return traceModelIndex >= 0 && traceModelIndex < cache.Num() + ? cache[traceModelIndex] + : nullptr; + } + + trmCache_t* GetEntry(const int traceModelIndex) { + return traceModelIndex >= 0 && traceModelIndex < cache.Num() + ? cache[traceModelIndex] + : nullptr; + } + + void GetMassProperties(int traceModelIndex, float density, float& mass, + idVec3& center, idMat3& inertia) const { + const trmCache_t* const entry = GetEntry(traceModelIndex); + if (entry == nullptr) { + mass = 0.0f; + center.Zero(); + inertia = idMat3(1.0f); + return; + } + mass = entry->volume * density; + center = entry->centerOfMass; + inertia = entry->inertiaTensor; + for (int row = 0; row < 3; ++row) { + for (int column = 0; column < 3; ++column) { + inertia[row][column] *= density; + } + } + } + + idStr mapName; + idBlockAlloc cacheAllocator; + idList cache; + idHashIndex hash; + +private: + void SetupWaterPoints(trmCache_t* entry); }; + +static_assert(sizeof(traceModelWater_t) == 16, + "Recovered traceModelWater_t ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idTraceModelCache::trmCache_t) == 88, + "Recovered trace-model cache entry ABI changed"); +static_assert(sizeof(idTraceModelCache) == 100, + "Recovered idTraceModelCache ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/tracemodelrecycler.cpp b/source/engine/gamelib/physics/tracemodelrecycler.cpp new file mode 100644 index 0000000..99095d6 --- /dev/null +++ b/source/engine/gamelib/physics/tracemodelrecycler.cpp @@ -0,0 +1,59 @@ +#include "gamelib/physics/tracemodelrecycler.h" + +idTraceModelRecycler::~idTraceModelRecycler() { + Shutdown(); +} + +void idTraceModelRecycler::Shutdown() { + for (int index = 0; index < allocated.Num(); ++index) { + idRecycledTraceModel* const recycled = allocated[index]; + delete recycled->trm; + recycled->trm = nullptr; + } + + allocated.ClearFree(); + for (int stage = 0; stage < 3; ++stage) { + free[stage].ClearFree(); + } + allocator.Shutdown(); +} + +idRecycledTraceModel* idTraceModelRecycler::GetTraceModel() { + if (free[0].Num() != 0) { + idRecycledTraceModel* const result = free[0][free[0].Num() - 1]; + free[0].RemoveIndexFast(free[0].Num() - 1); + return result; + } + + idRecycledTraceModel* const result = allocator.Alloc(); + if (result == nullptr) { + return nullptr; + } + + result->trm = new idTraceModel(); + result->index = allocated.Append(result); + if (result->index < 0) { + delete result->trm; + result->trm = nullptr; + allocator.Free(result); + return nullptr; + } + return result; +} + +void idTraceModelRecycler::RecycleTraceModel( + idRecycledTraceModel* const recycledTrm, const bool defer) { + if (recycledTrm == nullptr) { + return; + } + free[defer ? 2 : 0].Append(recycledTrm); +} + +void idTraceModelRecycler::UpdateDeferredFreed() { + for (int stage = 1; stage < 3; ++stage) { + for (int index = 0; index < free[stage].Num(); ++index) { + free[stage - 1].Append(free[stage][index]); + } + free[stage].ClearFree(); + } +} diff --git a/source/engine/gamelib/physics/tracemodelrecycler.h b/source/engine/gamelib/physics/tracemodelrecycler.h index 1f55c42..2c84c8b 100644 --- a/source/engine/gamelib/physics/tracemodelrecycler.h +++ b/source/engine/gamelib/physics/tracemodelrecycler.h @@ -1,42 +1,68 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\tracemodelrecycler.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/blockalloc_base.h" +#include "idlib/containers/hashindex.h" +#include "idlib/containers/list.h" +#include "idlib/containers/staticlist.h" +#include "idlib/geometry/tracemodel.h" +#include "idlib/text/atomicstring.h" +#include -// IDA Local Type ordinal 14127; PDB kind: class. -class idTraceModelRecycler -{ +struct idRecycledTraceModel { + idTraceModel* trm; + int index; + + idRecycledTraceModel() : trm(nullptr), index(-1) {} +}; + +class idTraceModelRecycler { public: - idBlockAlloc allocator; - idList allocated; - idList free[3]; + // Materialized in the authoritative tracemodelrecycler.h dump. + idTraceModelRecycler() : allocator(false), allocated(0), free{} {} + ~idTraceModelRecycler(); + + void Shutdown(); + idRecycledTraceModel* GetTraceModel(); + void RecycleTraceModel(idRecycledTraceModel* recycledTrm, bool defer); + void UpdateDeferredFreed(); + + idBlockAlloc allocator; + idList allocated; + idList free[3]; }; -// IDA Local Type ordinal 14150; PDB kind: struct. -struct idFXModelRecycler::fxPrtModel_t -{ - idRenderModelParticle *pmodel; - const idDeclParticle *pDecl; -}; +class idDeclParticle; +class idRenderModel; +class idRenderModelParticle; -// IDA Local Type ordinal 14153; PDB kind: struct. -struct idFXModelRecycler::fxStaticModel_t -{ - idRenderModel *rmodel; - idAtomicString modelName; -}; - -// IDA Local Type ordinal 14156; PDB kind: class. -class idFXModelRecycler -{ +class idFXModelRecycler { public: - idStaticList fxPrtModels; - idHashIndex activePrtModelHash; - idHashIndex inactivePrtModelHash; - idStaticList fxStaticModels; - idHashIndex activeStaticModelHash; - idHashIndex inactiveStaticModelHash; + struct fxPrtModel_t { + idRenderModelParticle* pmodel; + const idDeclParticle* pDecl; + }; + + struct fxStaticModel_t { + idRenderModel* rmodel; + idAtomicString modelName; + }; + + idStaticList fxPrtModels; + idHashIndex activePrtModelHash; + idHashIndex inactivePrtModelHash; + idStaticList fxStaticModels; + idHashIndex activeStaticModelHash; + idHashIndex inactiveStaticModelHash; }; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idRecycledTraceModel) == 8, + "Recovered idRecycledTraceModel ABI changed"); +static_assert(sizeof(idTraceModelRecycler) == 84, + "Recovered idTraceModelRecycler ABI changed"); +static_assert(sizeof(idFXModelRecycler::fxPrtModel_t) == 8, + "Recovered FX particle recycler entry ABI changed"); +static_assert(sizeof(idFXModelRecycler::fxStaticModel_t) == 8, + "Recovered FX static recycler entry ABI changed"); +#endif diff --git a/source/engine/gamelib/pvs/pvs.cpp b/source/engine/gamelib/pvs/pvs.cpp new file mode 100644 index 0000000..8cae6e3 --- /dev/null +++ b/source/engine/gamelib/pvs/pvs.cpp @@ -0,0 +1,311 @@ +#include "gamelib/pvs/pvs.h" + +#include "idlib/lib_print.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +int PVSBufferBytes(const int numAreas) { + return ((numAreas + 31) >> 3) & ~3; +} + +std::string ResourcePath(const char* const name) { + std::string path = name == nullptr ? "" : name; + const std::string::size_type slash = path.find_last_of("/\\"); + const std::string::size_type dot = path.find_last_of('.'); + if (dot == std::string::npos + || (slash != std::string::npos && dot < slash)) { + path += ".pvs"; + } else { + path.replace(dot, std::string::npos, ".pvs"); + } + return path; +} + +} // namespace + +idPVS::idPVS() + : header{PVS_MAGIC, 0, 0, 0, 0, 0, 0}, + pvsName(), + dataStart(nullptr), + planes(nullptr), + vertices(nullptr), + nodes(nullptr), + portals(nullptr), + areas(nullptr), + areaOffsets(nullptr), + areaPVS(nullptr), + currentPVS{}, + bare(false) { + for (pvsCurrent_t& current : currentPVS) { + current.handle.i = -1; + current.handle.h = 0; + current.pvs = nullptr; + } +} + +idPVS::~idPVS() { + Free(); +} + +void idPVS::DecompressAddPVS(std::uint8_t* const pvs, const int numBytes, + const std::uint8_t* compressed) { + if (pvs == nullptr || compressed == nullptr || numBytes <= 0) { + return; + } + + const int numBits = numBytes * 8; + int bit = 0; + while (bit < numBits) { + const std::uint8_t code = *compressed++; + if ((code & 0x80u) != 0) { + int skip = code & 0x3Fu; + if ((code & 0x40u) != 0) { + skip |= static_cast(*compressed++) << 6; + } + bit += skip + 1; + continue; + } + + for (int literalBit = 0; literalBit < 7 && bit < numBits; + ++literalBit, ++bit) { + pvs[bit >> 3] |= static_cast( + ((code >> literalBit) & 1u) << (bit & 7)); + } + } +} + +int idPVS::GetPVSAreas(const idBounds& bounds, int* const outputAreas, + const int maxAreas) const { + if (outputAreas == nullptr || maxAreas <= 0) { + return 0; + } + if (header.numNodes <= 1 || nodes == nullptr || planes == nullptr) { + outputAreas[0] = 0; + return 1; + } + + int count = 0; + std::vector stack; + stack.push_back(1); + while (!stack.empty() && count < maxAreas) { + const int nodeIndex = stack.back(); + stack.pop_back(); + if (nodeIndex < 0) { + const int area = -1 - nodeIndex; + bool duplicate = false; + for (int index = 0; index < count; ++index) { + duplicate |= outputAreas[index] == area; + } + if (!duplicate && area >= 0) { + outputAreas[count++] = area; + } + continue; + } + if (nodeIndex == 0 || nodeIndex >= header.numNodes) { + continue; + } + + const pvsNode_t& node = nodes[nodeIndex]; + const int side = bounds.PlaneSide(planes[node.planeNum], 0.1f); + if (side == 3) { + stack.push_back(node.children[1]); + stack.push_back(node.children[0]); + } else { + stack.push_back(node.children[side]); + } + } + return count; +} + +int idPVS::GetPVSArea(const idVec3& point) const { + if (header.numNodes <= 1 || nodes == nullptr || planes == nullptr) { + return 0; + } + + int nodeIndex = 1; + while (nodeIndex > 0) { + if (nodeIndex >= header.numNodes) { + return 0; + } + const pvsNode_t& node = nodes[nodeIndex]; + nodeIndex = node.children[ + planes[node.planeNum].Distance(point) >= 0.0f ? 0 : 1]; + } + return nodeIndex < 0 ? -1 - nodeIndex : 0; +} + +pvsHandle_t idPVS::DecodePVS(const int* const sourceAreas, + const int numSourceAreas) const { + if (sourceAreas == nullptr || numSourceAreas <= 0 || areaPVS == nullptr + || areaOffsets == nullptr) { + return {-1, 0}; + } + + unsigned int hash = 0; + for (int index = 0; index < numSourceAreas; ++index) { + hash ^= static_cast(sourceAreas[index]); + } + + int slot = 0; + while (slot < 8 && currentPVS[slot].handle.i != -1) { + ++slot; + } + if (slot == 8) { + idLibPrint::Error("idPVS::AllocPVS: no free PVS left"); + } + + currentPVS[slot].handle = {slot, hash}; + const int bytes = PVSBufferBytes(header.numAreas); + std::memset(currentPVS[slot].pvs, 0, bytes); + for (int index = 0; index < numSourceAreas; ++index) { + const int area = sourceAreas[index]; + if (area >= 0 && area < header.numAreas) { + DecompressAddPVS(currentPVS[slot].pvs, bytes, + areaPVS + areaOffsets[area]); + } + } + return currentPVS[slot].handle; +} + +void idPVS::FreePVS(const pvsHandle_t handle) const { + if (handle.i >= 0 && handle.i < 8 + && currentPVS[handle.i].handle == handle) { + currentPVS[handle.i].handle.i = -1; + return; + } + idLibPrint::Error("idPVS::FreePVS: invalid handle"); +} + +bool idPVS::InPVS(const pvsHandle_t handle, const int* const targetAreas, + const int numTargetAreas) const { + if (handle.i < 0 || handle.i >= 8 + || !(currentPVS[handle.i].handle == handle)) { + idLibPrint::Error("idPVS::InPVS: invalid handle"); + } + if (targetAreas == nullptr || numTargetAreas <= 0) { + return false; + } + + const std::uint8_t* const pvs = currentPVS[handle.i].pvs; + for (int index = 0; index < numTargetAreas; ++index) { + const int area = targetAreas[index]; + if (area >= 0 && area < header.numAreas + && (pvs[area >> 3] & (1u << (area & 7))) != 0) { + return true; + } + } + return false; +} + +unsigned int idPVS::Alloc(const bool bareAllocation) { + Free(); + bare = bareAllocation; + + const unsigned int coreBytes = + static_cast(header.numPlanes * sizeof(idPlane) + + header.numNodes * sizeof(pvsNode_t) + + header.numAreas * sizeof(int) + + header.numPVSBytes); + const unsigned int detailBytes = bare ? 0u : static_cast( + header.numVertices * sizeof(idVec3) + + header.numPortals * sizeof(pvsPortal_t) + + header.numAreas * sizeof(pvsArea_t)); + const unsigned int bytes = coreBytes + detailBytes; + + dataStart = static_cast( + _aligned_malloc((std::max)(bytes, 1u), 16)); + if (dataStart == nullptr) { + idLibPrint::Error("idPVS::Alloc: allocation failed"); + } + std::memset(dataStart, 0, bytes); + + std::uint8_t* cursor = dataStart; + planes = reinterpret_cast(cursor); + cursor += header.numPlanes * sizeof(idPlane); + nodes = reinterpret_cast(cursor); + cursor += header.numNodes * sizeof(pvsNode_t); + areaOffsets = reinterpret_cast(cursor); + cursor += header.numAreas * sizeof(int); + areaPVS = cursor; + cursor += header.numPVSBytes; + + if (bare) { + vertices = nullptr; + portals = nullptr; + areas = nullptr; + } else { + vertices = reinterpret_cast(cursor); + cursor += header.numVertices * sizeof(idVec3); + portals = reinterpret_cast(cursor); + cursor += header.numPortals * sizeof(pvsPortal_t); + areas = reinterpret_cast(cursor); + } + + const int bufferBytes = PVSBufferBytes(header.numAreas); + for (pvsCurrent_t& current : currentPVS) { + current.handle = {-1, 0}; + current.pvs = static_cast( + _aligned_malloc((std::max)(bufferBytes, 1), 16)); + if (current.pvs == nullptr) { + idLibPrint::Error("idPVS::Alloc: handle allocation failed"); + } + std::memset(current.pvs, 0, bufferBytes); + } + return bytes; +} + +void idPVS::Free() { + if (dataStart != nullptr) { + _aligned_free(dataStart); + dataStart = nullptr; + } + planes = nullptr; + vertices = nullptr; + nodes = nullptr; + portals = nullptr; + areas = nullptr; + areaOffsets = nullptr; + areaPVS = nullptr; + for (pvsCurrent_t& current : currentPVS) { + current.handle = {-1, 0}; + if (current.pvs != nullptr) { + _aligned_free(current.pvs); + current.pvs = nullptr; + } + } +} + +void idPVS::Default() { + Free(); + header = {PVS_MAGIC, 0, 0, 0, 0, 1, 8}; + Alloc(false); + areas[0] = {0, 0}; + areaOffsets[0] = 0; + std::memset(areaPVS, 127, 8); +} + +void idPVS::LoadResource() { + Free(); + const std::string path = ResourcePath(pvsName.c_str()); + std::ifstream file(path, std::ios::binary); + if (!file.read(reinterpret_cast(&header), sizeof(header)) + || header.magic != PVS_MAGIC) { + idLibPrint::Warning("failed to open %s", path.c_str()); + Default(); + return; + } + + const unsigned int bytes = Alloc(bare); + if (!file.read(reinterpret_cast(dataStart), bytes)) { + idLibPrint::Warning("%s is not a PVS file or is truncated", + path.c_str()); + Default(); + } +} diff --git a/source/engine/gamelib/pvs/pvs.h b/source/engine/gamelib/pvs/pvs.h index 1bcd516..14d7476 100644 --- a/source/engine/gamelib/pvs/pvs.h +++ b/source/engine/gamelib/pvs/pvs.h @@ -1,40 +1,102 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\pvs\pvs.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/bv/bounds.h" +#include "idlib/math/plane.h" +#include "idlib/math/vector.h" +#include "idlib/text/str.h" +#include -// IDA Local Type ordinal 14174; PDB kind: class. -class __declspec(align(4)) idPVS -{ +struct pvsNode_t { + unsigned int planeNum; + std::int16_t children[2]; +}; + +struct pvsPortal_t { + int firstVertex; + int numVertices; +}; + +struct pvsArea_t { + int firstPortal; + int numPortals; +}; + +struct pvsHandle_t { + int i; + unsigned int h; + + bool operator==(const pvsHandle_t& other) const { + return i == other.i && h == other.h; + } +}; + +struct pvsCurrent_t { + pvsHandle_t handle; + std::uint8_t* pvs; +}; + +class idPVS { public: - // Recovered virtual interface; IDA vtable ordinal 14175. - virtual ~idPVS(); + struct header_t { + int magic; + int numPlanes; + int numVertices; + int numNodes; + int numPortals; + int numAreas; + int numPVSBytes; + }; - idPVS::header_t header; - idStr pvsName; - unsigned __int8 *dataStart; - idPlane *planes; - idVec3 *vertices; - pvsNode_t *nodes; - pvsPortal_t *portals; - pvsArea_t *areas; - int *areaOffsets; - unsigned __int8 *areaPVS; - pvsCurrent_t currentPVS[8]; - bool bare; + static constexpr int PVS_MAGIC = 55793232; + + idPVS(); + virtual ~idPVS(); + + int GetPVSAreas(const idBounds& bounds, int* areas, int maxAreas) const; + int GetPVSArea(const idVec3& point) const; + pvsHandle_t DecodePVS(const int* sourceAreas, int numSourceAreas) const; + void FreePVS(pvsHandle_t handle) const; + bool InPVS(pvsHandle_t handle, const int* targetAreas, + int numTargetAreas) const; + + void LoadResource(); + void SetName(const char* name) { pvsName = name; } + void SetBare(bool value) { bare = value; } + + // These allocation/default hooks are part of the recovered implementation + // boundary and are public here so resource loaders can populate the data. + unsigned int Alloc(bool bareAllocation); + void Free(); + void Default(); + + static void DecompressAddPVS(std::uint8_t* pvs, int numBytes, + const std::uint8_t* compressed); + + header_t header; + idStr pvsName; + std::uint8_t* dataStart; + idPlane* planes; + idVec3* vertices; + pvsNode_t* nodes; + pvsPortal_t* portals; + pvsArea_t* areas; + int* areaOffsets; + std::uint8_t* areaPVS; + mutable pvsCurrent_t currentPVS[8]; + bool bare; }; -// IDA Local Type ordinal 14176; PDB kind: struct. -struct idPVS::header_t -{ - int magic; - int numPlanes; - int numVertices; - int numNodes; - int numPortals; - int numAreas; - int numPVSBytes; -}; +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(pvsNode_t) == 8, "Recovered pvsNode_t ABI changed"); +static_assert(sizeof(pvsPortal_t) == 8, + "Recovered pvsPortal_t ABI changed"); +static_assert(sizeof(pvsArea_t) == 8, "Recovered pvsArea_t ABI changed"); +static_assert(sizeof(pvsHandle_t) == 8, + "Recovered pvsHandle_t ABI changed"); +static_assert(sizeof(pvsCurrent_t) == 12, + "Recovered pvsCurrent_t ABI changed"); +static_assert(sizeof(idPVS::header_t) == 28, + "Recovered idPVS header ABI changed"); +static_assert(sizeof(idPVS) == 196, "Recovered idPVS ABI changed"); +#endif diff --git a/source/engine/gamelib/timeline/timelineeventdata.cpp b/source/engine/gamelib/timeline/timelineeventdata.cpp new file mode 100644 index 0000000..e542f71 --- /dev/null +++ b/source/engine/gamelib/timeline/timelineeventdata.cpp @@ -0,0 +1,18 @@ +#include "gamelib/timeline/timelineeventdata.h" + +tlEventData_t::tlEventData_t() + : time(0) + , duration(0) + , type(TL_EVENT_NONE) + , anim() + , useAnimPos(false) + , sound(nullptr) + , worldPos(0.0f, 0.0f, 0.0f) + , worldRot(0.0f, 0.0f, 0.0f) + , keyName(TL_KEY_INVALID) + , keyValue() + , keyAnimData() + , activateStart(true) + , activateEnd(false) + , env(nullptr) { +} diff --git a/source/engine/gamelib/timeline/timelineeventdata.h b/source/engine/gamelib/timeline/timelineeventdata.h new file mode 100644 index 0000000..17bfde5 --- /dev/null +++ b/source/engine/gamelib/timeline/timelineeventdata.h @@ -0,0 +1,53 @@ +#pragma once + +#include "gamelib/timeline/timelinekeyanimdata.h" +#include "idlib/math/angles.h" +#include "idlib/text/str.h" + +class idDeclEnv; +class idSoundShader; + +// Local type 14673 is present in tungsten.exe.h without a PDB source path. +class idAnimAliasRef : public idStr { +public: + idAnimAliasRef() = default; + explicit idAnimAliasRef(const char *value) : idStr(value) {} +}; + +enum tlEventType_t : int { + TL_EVENT_NONE = 0, + TL_EVENT_ANIM = 1, + TL_EVENT_SOUND = 2, + TL_EVENT_TELEPORT = 3, + TL_EVENT_SETKEY = 4, + TL_EVENT_ANIMKEY = 5, + TL_EVENT_ACTIVATE = 6, + TL_EVENT_SETENV = 7, + TL_EVENT_MAX = 8, +}; + +// Reconstructed from tungsten.exe.h local type 19107 and the constructor in +// timelineeventdata.cpp. +struct tlEventData_t { + tlEventData_t(); + + int time; + int duration; + tlEventType_t type; + idAnimAliasRef anim; + bool useAnimPos; + const idSoundShader *sound; + idVec3 worldPos; + idAngles worldRot; + tlKeyId_t keyName; + idStr keyValue; + mgTimelineKeyAnimData keyAnimData; + bool activateStart; + bool activateEnd; + const idDeclEnv *env; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(tlEventData_t) == 160, + "Recovered tlEventData_t ABI changed"); +#endif diff --git a/source/engine/gamelib/timeline/timelinekeyanimdata.cpp b/source/engine/gamelib/timeline/timelinekeyanimdata.cpp new file mode 100644 index 0000000..f6da130 --- /dev/null +++ b/source/engine/gamelib/timeline/timelinekeyanimdata.cpp @@ -0,0 +1,157 @@ +#include "gamelib/timeline/timelinekeyanimdata.h" + +#include "idlib/containers/search.h" + +#include +#include + +namespace { + +idSearch_BinaryDefault timelineKeySearch; + +template +const type_t& KeyAt(const idList& data, const int index, + const int keySize) { + return *reinterpret_cast( + data.Ptr() + static_cast(index) * keySize + ); +} + +} // namespace + +mgTimelineKeyAnimData::mgTimelineKeyAnimData() + : type(TL_KEYTYPE_MAX) + , keySize(0) + , times(0) + , data(0) { +} + +void mgTimelineKeyAnimData::PostLoadInit() { + keySize = mgTimelineKeyUtil::KeySize(type); +} + +bool mgTimelineKeyAnimData::FindLerpKeys(const float time, int *key0, + int *key1, float *fraction) const { + if (key0 == nullptr || key1 == nullptr || fraction == nullptr + || times.Num() == 0 || times.Ptr() == nullptr) { + return false; + } + + const int index = timelineKeySearch.Search_LastLessEqual( + times.Ptr(), times.Num(), time + ); + if (times[index] > time) { + return false; + } + + *key0 = index; + if (index < times.Num() - 1) { + *key1 = index + 1; + const float duration = times[index + 1] - times[index]; + *fraction = duration != 0.0f ? (time - times[index]) / duration : 0.0f; + } else { + *key1 = index; + *fraction = 0.0f; + } + return true; +} + +template +bool mgTimelineKeyAnimData::GetLerped(const float time, type_t *value) const { + int key0 = 0; + int key1 = 0; + float fraction = 0.0f; + if (value == nullptr || keySize != static_cast(sizeof(type_t)) + || !FindLerpKeys(time, &key0, &key1, &fraction)) { + return false; + } + + const type_t& value0 = KeyAt(data, key0, keySize); + const type_t& value1 = KeyAt(data, key1, keySize); + *value = value0 + (value1 - value0) * fraction; + return true; +} + +template<> +bool mgTimelineKeyAnimData::GetLerped(const float time, int *value) const { + int key0 = 0; + int key1 = 0; + float fraction = 0.0f; + if (value == nullptr || keySize != static_cast(sizeof(int)) + || !FindLerpKeys(time, &key0, &key1, &fraction)) { + return false; + } + + const int value0 = KeyAt(data, key0, keySize); + const int value1 = KeyAt(data, key1, keySize); + *value = static_cast(static_cast(value0) + + static_cast(value1 - value0) * fraction); + return true; +} + +template<> +bool mgTimelineKeyAnimData::GetLerped(const float time, + idVec3 *value) const { + int key0 = 0; + int key1 = 0; + float fraction = 0.0f; + if (value == nullptr || keySize != static_cast(sizeof(idVec3)) + || !FindLerpKeys(time, &key0, &key1, &fraction)) { + return false; + } + + const idVec3& value0 = KeyAt(data, key0, keySize); + const idVec3& value1 = KeyAt(data, key1, keySize); + value->x = value0.x + (value1.x - value0.x) * fraction; + value->y = value0.y + (value1.y - value0.y) * fraction; + value->z = value0.z + (value1.z - value0.z) * fraction; + return true; +} + +template<> +bool mgTimelineKeyAnimData::GetLerped(const float time, + idVec4 *value) const { + int key0 = 0; + int key1 = 0; + float fraction = 0.0f; + if (value == nullptr || keySize != static_cast(sizeof(idVec4)) + || !FindLerpKeys(time, &key0, &key1, &fraction)) { + return false; + } + + const idVec4& value0 = KeyAt(data, key0, keySize); + const idVec4& value1 = KeyAt(data, key1, keySize); + value->x = value0.x + (value1.x - value0.x) * fraction; + value->y = value0.y + (value1.y - value0.y) * fraction; + value->z = value0.z + (value1.z - value0.z) * fraction; + value->w = value0.w + (value1.w - value0.w) * fraction; + return true; +} + +bool mgTimelineKeyAnimData::GetData(const float time, bool *value) const { + int key0 = 0; + int key1 = 0; + float fraction = 0.0f; + if (value == nullptr || keySize != 1 + || !FindLerpKeys(time, &key0, &key1, &fraction)) { + return false; + } + *value = data[key0] != 0; + return true; +} + +bool mgTimelineKeyAnimData::GetData(const float time, int *value) const { + return GetLerped(time, value); +} + +bool mgTimelineKeyAnimData::GetData(const float time, float *value) const { + return GetLerped(time, value); +} + +bool mgTimelineKeyAnimData::GetData(const float time, idVec3 *value) const { + return GetLerped(time, value); +} + +bool mgTimelineKeyAnimData::GetData(const float time, idVec4 *value) const { + return GetLerped(time, value); +} diff --git a/source/engine/gamelib/timeline/timelinekeyanimdata.h b/source/engine/gamelib/timeline/timelinekeyanimdata.h index cabd971..25a4c37 100644 --- a/source/engine/gamelib/timeline/timelinekeyanimdata.h +++ b/source/engine/gamelib/timeline/timelinekeyanimdata.h @@ -1,17 +1,38 @@ #pragma once +#include "gamelib/timeline/timelinekeytypes.h" +#include "idlib/containers/list.h" + // Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. // Original PDB header: w:\tech5\engine\gamelib\timeline\timelinekeyanimdata.h // Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. - - -// IDA Local Type ordinal 19106; PDB kind: class. +// IDA Local Type ordinal 19106; PDB kind: class. Method declarations are from +// the decorated functions in timelinekeyanimdata.cpp. class mgTimelineKeyAnimData { public: + mgTimelineKeyAnimData(); + + void PostLoadInit(); + bool GetData(float time, bool *value) const; + bool GetData(float time, int *value) const; + bool GetData(float time, float *value) const; + bool GetData(float time, idVec3 *value) const; + bool GetData(float time, idVec4 *value) const; + tlKeyType_t type; int keySize; idList times; idList data; + +private: + bool FindLerpKeys(float time, int *key0, int *key1, float *fraction) const; + + template + bool GetLerped(float time, type_t *value) const; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(mgTimelineKeyAnimData) == 40, + "Recovered mgTimelineKeyAnimData ABI changed"); +#endif diff --git a/source/engine/gamelib/timeline/timelinekeytypes.cpp b/source/engine/gamelib/timeline/timelinekeytypes.cpp new file mode 100644 index 0000000..5a4428e --- /dev/null +++ b/source/engine/gamelib/timeline/timelinekeytypes.cpp @@ -0,0 +1,105 @@ +#include "gamelib/timeline/timelinekeytypes.h" + +#include +#include + +namespace { + +// The seven-entry maps are recovered from the tlKeyId_t/tlKeyType_t metadata, +// the typed SetValue overloads in entityvalueaccess.cpp, and the serialized +// key widths consumed by timelinekeyanimdata.cpp. +constexpr tlKeyType_t keyTypeMap[TL_KEY_MAX] = { + TL_KEYTYPE_BOOL, + TL_KEYTYPE_COLOR_RGB, + TL_KEYTYPE_FLOAT4, + TL_KEYTYPE_FLOAT, + TL_KEYTYPE_FLOAT, + TL_KEYTYPE_FLOAT, + TL_KEYTYPE_FLOAT, +}; + +constexpr int keySizes[TL_KEYTYPE_MAX] = { + 1, + 4, + 4, + 12, + 12, + 16, + 16, +}; + +bool ParseNextFloat(const char *&cursor, float &value) { + if (cursor == nullptr) { + return false; + } + + char *end = nullptr; + errno = 0; + value = std::strtof(cursor, &end); + if (end == cursor || errno == ERANGE) { + return false; + } + cursor = end; + return true; +} + +} // namespace + +tlKeyType_t mgTimelineKeyUtil::GetType(const tlKeyId_t key) { + if (key < TL_KEY_VISIBILITY || key >= TL_KEY_MAX) { + return TL_KEYTYPE_MAX; + } + return keyTypeMap[static_cast(key)]; +} + +int mgTimelineKeyUtil::KeySize(const tlKeyType_t type) { + if (type < TL_KEYTYPE_BOOL || type >= TL_KEYTYPE_MAX) { + return 0; + } + return keySizes[static_cast(type)]; +} + +bool mgTimelineKeyUtil::FromString(const idStr& valueStr, bool *value) { + if (value == nullptr) { + return false; + } + *value = std::strtol(valueStr.c_str(), nullptr, 10) != 0; + return true; +} + +bool mgTimelineKeyUtil::FromString(const idStr& valueStr, int *value) { + if (value == nullptr) { + return false; + } + *value = static_cast(std::strtol(valueStr.c_str(), nullptr, 10)); + return true; +} + +bool mgTimelineKeyUtil::FromString(const idStr& valueStr, float *value) { + if (value == nullptr) { + return false; + } + const char *cursor = valueStr.c_str(); + return ParseNextFloat(cursor, *value); +} + +bool mgTimelineKeyUtil::FromString(const idStr& valueStr, idVec3 *value) { + if (value == nullptr) { + return false; + } + const char *cursor = valueStr.c_str(); + return ParseNextFloat(cursor, value->x) + && ParseNextFloat(cursor, value->y) + && ParseNextFloat(cursor, value->z); +} + +bool mgTimelineKeyUtil::FromString(const idStr& valueStr, idVec4 *value) { + if (value == nullptr) { + return false; + } + const char *cursor = valueStr.c_str(); + return ParseNextFloat(cursor, value->x) + && ParseNextFloat(cursor, value->y) + && ParseNextFloat(cursor, value->z) + && ParseNextFloat(cursor, value->w); +} diff --git a/source/engine/gamelib/timeline/timelinekeytypes.h b/source/engine/gamelib/timeline/timelinekeytypes.h new file mode 100644 index 0000000..1aec267 --- /dev/null +++ b/source/engine/gamelib/timeline/timelinekeytypes.h @@ -0,0 +1,44 @@ +#pragma once + +#include "idlib/math/vector.h" +#include "idlib/text/str.h" + +// Reconstructed from tungsten.exe local types 1723 and 1758. The numeric +// values are part of the serialized timeline format and must not be reordered. +enum tlKeyId_t : int { + TL_KEY_INVALID = -1, + TL_KEY_VISIBILITY = 0, + TL_KEY_LIGHTCOLOR = 1, + TL_KEY_DOF = 2, + TL_KEY_FOCAL = 3, + TL_KEY_NEARBP = 4, + TL_KEY_FARBP = 5, + TL_KEY_BSCALE = 6, + TL_KEY_MAX = 7, +}; + +enum tlKeyType_t : int { + TL_KEYTYPE_BOOL = 0, + TL_KEYTYPE_INT = 1, + TL_KEYTYPE_FLOAT = 2, + TL_KEYTYPE_FLOAT3 = 3, + TL_KEYTYPE_COLOR_RGB = 4, + TL_KEYTYPE_FLOAT4 = 5, + TL_KEYTYPE_COLOR_RGBA = 6, + TL_KEYTYPE_MAX = 7, +}; + +class mgTimelineKeyUtil { +public: + static tlKeyType_t GetType(tlKeyId_t key); + static int KeySize(tlKeyType_t type); + + static bool FromString(const idStr& valueStr, bool* value); + static bool FromString(const idStr& valueStr, int* value); + static bool FromString(const idStr& valueStr, float* value); + static bool FromString(const idStr& valueStr, idVec3* value); + static bool FromString(const idStr& valueStr, idVec4* value); +}; + +static_assert(sizeof(tlKeyId_t) == 4, "Recovered tlKeyId_t ABI changed"); +static_assert(sizeof(tlKeyType_t) == 4, "Recovered tlKeyType_t ABI changed"); diff --git a/source/engine/mapfile/mapfile.h b/source/engine/mapfile/mapfile.h index cdb9d0f..a24b2ea 100644 --- a/source/engine/mapfile/mapfile.h +++ b/source/engine/mapfile/mapfile.h @@ -1,29 +1,64 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\mapfile\mapfile.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "mapfile/mapmodel.h" +class idDeclEntityDef; +class idGame; -// IDA Local Type ordinal 15161; PDB kind: class. -class __declspec(align(4)) idMapFile -{ +class idGroupState { public: - // Recovered virtual interface; IDA vtable ordinal 15162. - virtual ~idMapFile(); - - idStr name; - int version; - unsigned int fileTime; - unsigned int geometryCRC; - bool hasPrimitiveData; - idList entities; - idMapEditorStates groupStates; - idList referenceMaps; - idMapEntity *parentReference; - bool expandReferences; - idGame *game; - bool entitiesAreReference; - bool ignoreGroupInfo; + idStr groupName; + unsigned int state = 0; }; + +class idMapEditorStates { +public: + virtual ~idMapEditorStates() = default; + idList groupStates; + idList layerStates; + idList groupColors; +}; + +class idRefMapDef { +public: + idStr mapName; + idStr worldSpawnText; +}; + +class idMapEntity : public idMapGroups { +public: + ~idMapEntity() override = default; + idDeclEntityDef* entityDef = nullptr; + idMapModel model; + idMapEntity* parentReference = nullptr; + idStr refId; + idList layerList; +}; + +class alignas(4) idMapFile { +public: + virtual ~idMapFile() = default; + + idStr name; + int version = 0; + unsigned int fileTime = 0; + unsigned int geometryCRC = 0; + bool hasPrimitiveData = false; + idList entities; + idMapEditorStates groupStates; + idList referenceMaps; + idMapEntity* parentReference = nullptr; + bool expandReferences = false; + idGame* game = nullptr; + bool entitiesAreReference = false; + bool ignoreGroupInfo = false; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idMapEditorStates) == 52, + "Recovered idMapEditorStates ABI changed"); +static_assert(sizeof(idMapEntity) == 124, + "Recovered idMapEntity ABI changed"); +static_assert(sizeof(idMapFile) == 152, + "Recovered idMapFile ABI changed"); +#endif diff --git a/source/engine/mapfile/mapmodel.h b/source/engine/mapfile/mapmodel.h new file mode 100644 index 0000000..0ffe162 --- /dev/null +++ b/source/engine/mapfile/mapmodel.h @@ -0,0 +1,70 @@ +#pragma once + +#include "idlib/containers/list.h" +#include "idlib/geometry/surface_patch.h" +#include "idlib/math/plane.h" +#include "idlib/text/str.h" + +class idFile; + +class idMapGroups { +public: + virtual ~idMapGroups() = default; + idList groupList; +}; + +enum mapPrimitiveType_t : int { + MAP_PRIMITIVE_BRUSH = 0, + MAP_PRIMITIVE_PATCH = 1 +}; + +class idMapPrimitive : public idMapGroups { +public: + ~idMapPrimitive() override = default; + virtual unsigned int GetGeometryCRC() { return 0; } + virtual void Transform(const idVec3*, const idMat3*) {} + virtual bool Write(idFile*, int) { return false; } + + int type = MAP_PRIMITIVE_BRUSH; +}; + +class idMapModel { +public: + idStr name; + idList primitives; +}; + +class idMapBrushSide { +public: + idStr material; + idPlane plane; + idVec3 texMat[2]; + idVec3 origin; +}; + +class idMapBrush : public idMapPrimitive { +public: + idList sides; +}; + +class idMapPatch : public idMapPrimitive, public idSurface_Patch { +public: + idVec3 origin; + idStr material; + int horzSubdivisions = 0; + int vertSubdivisions = 0; + bool explicitSubdivisions = false; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idMapPrimitive) == 24, + "Recovered idMapPrimitive ABI changed"); +static_assert(sizeof(idMapModel) == 48, + "Recovered idMapModel ABI changed"); +static_assert(sizeof(idMapBrushSide) == 84, + "Recovered idMapBrushSide ABI changed"); +static_assert(sizeof(idMapBrush) == 40, + "Recovered idMapBrush ABI changed"); +static_assert(sizeof(idMapPatch) == 164, + "Recovered idMapPatch ABI changed"); +#endif diff --git a/source/shared/idlib/bv/bounds.h b/source/shared/idlib/bv/bounds.h index 1844f42..853b433 100644 --- a/source/shared/idlib/bv/bounds.h +++ b/source/shared/idlib/bv/bounds.h @@ -1,7 +1,10 @@ #pragma once +#include "../math/plane.h" #include "../math/vector.h" +#include + // Recovered two-vector bounds layout used by the geometry reconstruction. class idBounds { public: @@ -14,6 +17,122 @@ public: idVec3& operator[](const int index) { return b[index]; } + + // Recovered from shared/idlib/bv/bounds.cpp. This scalar PC spelling is + // the center/extents form of the original VMX implementation. + void FromTransformedBounds(const idBounds& bounds, + const idVec3& origin, const idMat3& axis) { + const idVec3 center = (bounds[0] + bounds[1]) * 0.5f; + const idVec3 extents = bounds[1] - center; + const idVec3 transformedCenter = origin + idVec3( + axis[0].x * center.x + axis[1].x * center.y + axis[2].x * center.z, + axis[0].y * center.x + axis[1].y * center.y + axis[2].y * center.z, + axis[0].z * center.x + axis[1].z * center.y + axis[2].z * center.z); + const idVec3 transformedExtents( + std::fabs(axis[0].x * extents.x) + + std::fabs(axis[1].x * extents.y) + + std::fabs(axis[2].x * extents.z), + std::fabs(axis[0].y * extents.x) + + std::fabs(axis[1].y * extents.y) + + std::fabs(axis[2].y * extents.z), + std::fabs(axis[0].z * extents.x) + + std::fabs(axis[1].z * extents.y) + + std::fabs(axis[2].z * extents.z)); + b[0] = transformedCenter - transformedExtents; + b[1] = transformedCenter + transformedExtents; + } + + // Recovered from shared/idlib/bv/bounds.cpp. Return values match the + // original plane-side convention: 0 front, 1 back, 3 crossing. + int PlaneSide(const idPlane& plane, const float epsilon = 0.0f) const { + const idVec3 center = (b[0] + b[1]) * 0.5f; + const idVec3 extents = b[1] - center; + const float radius = std::fabs(extents.x * plane.a) + + std::fabs(extents.y * plane.b) + + std::fabs(extents.z * plane.c); + const float distance = plane.Distance(center); + if (distance - radius > epsilon) { + return 0; + } + if (distance + radius < -epsilon) { + return 1; + } + return 3; + } + + // Recovered from shared/idlib/bv/bounds.cpp. This is the separating-axis + // segment/AABB test used by GameLib obstacle routing. + bool LineIntersection(const idVec3& start, const idVec3& end) const { + const idVec3 lineDir = (end - start) * 0.5f; + const idVec3 lineCenter = start + lineDir; + const idVec3 boundsCenter = (b[0] + b[1]) * 0.5f; + const idVec3 extents = b[1] - boundsCenter; + const idVec3 dirAbs( + std::fabs(lineDir.x), std::fabs(lineDir.y), + std::fabs(lineDir.z) + ); + const idVec3 centerDelta = lineCenter - boundsCenter; + + if (std::fabs(centerDelta.x) > extents.x + dirAbs.x + || std::fabs(centerDelta.y) > extents.y + dirAbs.y + || std::fabs(centerDelta.z) > extents.z + dirAbs.z) { + return false; + } + if (std::fabs(centerDelta.z * lineDir.y + - centerDelta.y * lineDir.z) + > extents.y * dirAbs.z + extents.z * dirAbs.y) { + return false; + } + if (std::fabs(lineDir.z * centerDelta.x + - centerDelta.z * lineDir.x) + > extents.z * dirAbs.x + extents.x * dirAbs.z) { + return false; + } + return std::fabs(centerDelta.y * lineDir.x + - lineDir.y * centerDelta.x) + <= extents.y * dirAbs.x + extents.x * dirAbs.y; + } + + bool RayIntersection(const idVec3& start, const idVec3& direction, + float& scale) const { + float minimum = -1.0e30f; + float maximum = 1.0e30f; + for (int axis = 0; axis < 3; ++axis) { + if (std::fabs(direction[axis]) < 1.0e-20f) { + if (start[axis] < b[0][axis] || start[axis] > b[1][axis]) { + return false; + } + continue; + } + const float inverseDirection = 1.0f / direction[axis]; + float first = (b[0][axis] - start[axis]) * inverseDirection; + float second = (b[1][axis] - start[axis]) * inverseDirection; + if (first > second) { + const float swap = first; + first = second; + second = swap; + } + if (first > minimum) minimum = first; + if (second < maximum) maximum = second; + if (minimum > maximum) return false; + } + scale = minimum; + return true; + } + + bool SphereIntersection(const idVec3& origin, const float radius) const { + float distanceSquared = 0.0f; + for (int axis = 0; axis < 3; ++axis) { + float delta = 0.0f; + if (origin[axis] < b[0][axis]) { + delta = b[0][axis] - origin[axis]; + } else if (origin[axis] > b[1][axis]) { + delta = origin[axis] - b[1][axis]; + } + distanceSquared += delta * delta; + } + return distanceSquared < radius * radius; + } }; static_assert(sizeof(idBounds) == 24, "Recovered idBounds layout changed"); diff --git a/source/shared/idlib/bv/boundsshort.h b/source/shared/idlib/bv/boundsshort.h index 3b46059..bb92121 100644 --- a/source/shared/idlib/bv/boundsshort.h +++ b/source/shared/idlib/bv/boundsshort.h @@ -14,10 +14,10 @@ public: for (int side = 0; side < 2; ++side) { for (int axis = 0; axis < 3; ++axis) { const int integerValue = static_cast(bounds[side][axis]); - b[side][axis] = static_cast(std::max( - static_cast(std::numeric_limits::min()), - std::min( - static_cast(std::numeric_limits::max()), + b[side][axis] = static_cast((std::max)( + static_cast((std::numeric_limits::min)()), + (std::min)( + static_cast((std::numeric_limits::max)()), integerValue ) )); diff --git a/source/shared/idlib/commandlink.cpp b/source/shared/idlib/commandlink.cpp new file mode 100644 index 0000000..9bb7f2b --- /dev/null +++ b/source/shared/idlib/commandlink.cpp @@ -0,0 +1,17 @@ +#include "commandlink.h" + +namespace { +idCommandLink* commandLinks = nullptr; +} + +idCommandLink* CommandLinks() { + return commandLinks; +} + +idCommandLink::idCommandLink(const char* commandName, + commandLinkFunction_t function, const char* description, + commandLinkCompletion_t argumentCompletion) + : next(commandLinks), cmdName_(commandName), function_(function), + description_(description), argCompletion_(argumentCompletion) { + commandLinks = this; +} diff --git a/source/shared/idlib/commandlink.h b/source/shared/idlib/commandlink.h new file mode 100644 index 0000000..ecea4d9 --- /dev/null +++ b/source/shared/idlib/commandlink.h @@ -0,0 +1,26 @@ +#pragma once + +#include "csystems/autocomplete.h" + +using commandLinkFunction_t = void (*)(const idCmdArgs& args); +using commandLinkCompletion_t = void (*)(idAutoComplete& completion); + +class idCommandLink { +public: + idCommandLink(const char* commandName, commandLinkFunction_t function, + const char* description, + commandLinkCompletion_t argumentCompletion = nullptr); + + idCommandLink* next; + const char* cmdName_; + commandLinkFunction_t function_; + const char* description_; + commandLinkCompletion_t argCompletion_; +}; + +idCommandLink* CommandLinks(); + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCommandLink) == 20, + "Recovered idCommandLink layout changed"); +#endif diff --git a/source/shared/idlib/containers/list.h b/source/shared/idlib/containers/list.h index 0d2e1e3..d9bf977 100644 --- a/source/shared/idlib/containers/list.h +++ b/source/shared/idlib/containers/list.h @@ -166,7 +166,9 @@ protected: list = buffer; num = 0; size = capacity; - granularity = 0; + // Recovered idStaticList constructors consistently materialize a + // granularity of one for their embedded fixed-capacity storage. + granularity = 1; listStatic = 1; } @@ -180,4 +182,3 @@ private: #if defined(_WIN32) && !defined(_WIN64) static_assert(sizeof(idList) == 16, "Recovered idList ABI changed"); #endif - diff --git a/source/shared/idlib/csystems/autocomplete.h b/source/shared/idlib/csystems/autocomplete.h index bb6bebf..8d5b550 100644 --- a/source/shared/idlib/csystems/autocomplete.h +++ b/source/shared/idlib/csystems/autocomplete.h @@ -20,7 +20,7 @@ static_assert( ); // tungsten.exe.h type 12218. idCmdArgs and idList retain their recovered -// Win32 layouts through the active BFG-compatible core container layer. +// Win32 layouts through the active recovered core container layer. class idAutoComplete { public: idAutoComplete() diff --git a/source/shared/idlib/geometry/screenrect.h b/source/shared/idlib/geometry/screenrect.h index 29e3a9d..0e00531 100644 --- a/source/shared/idlib/geometry/screenrect.h +++ b/source/shared/idlib/geometry/screenrect.h @@ -1,6 +1,6 @@ #pragma once -// tungsten.exe.h type 12913. Unlike Doom 3 BFG's idScreenRect, this idTech 5 +// tungsten.exe.h type 12913. This recovered idTech 5 // build uses four 32-bit coordinates and does not store depth bounds here. class idScreenRect { public: @@ -18,4 +18,3 @@ public: }; static_assert(sizeof(idScreenRect) == 16, "Recovered idScreenRect layout changed"); - diff --git a/source/shared/idlib/geometry/tracemodel.cpp b/source/shared/idlib/geometry/tracemodel.cpp new file mode 100644 index 0000000..07b586d --- /dev/null +++ b/source/shared/idlib/geometry/tracemodel.cpp @@ -0,0 +1,205 @@ +#include "idlib/geometry/tracemodel.h" + +#include +#include +#include +#include +#include + +void* idTraceModel::operator new(const std::size_t size) { + void* const memory = _aligned_malloc(size, 128); + if (memory == nullptr) { + throw std::bad_alloc(); + } + return memory; +} + +void idTraceModel::operator delete(void* const memory) { + if (memory != nullptr) { + _aligned_free(memory); + } +} + +void idTraceModel::ClearUnused() { + std::memset(vertsX + numVerts, 0, + sizeof(float) * (32 - numVerts)); + std::memset(vertsY + numVerts, 0, + sizeof(float) * (32 - numVerts)); + std::memset(vertsZ + numVerts, 0, + sizeof(float) * (32 - numVerts)); + std::memset(edgeNormalX + numEdges, 0, + sizeof(float) * (32 - numEdges)); + std::memset(edgeNormalY + numEdges, 0, + sizeof(float) * (32 - numEdges)); + std::memset(edgeNormalZ + numEdges, 0, + sizeof(float) * (32 - numEdges)); + std::memset(polyPlaneX + numPolys, 0, + sizeof(float) * (16 - numPolys)); + std::memset(polyPlaneY + numPolys, 0, + sizeof(float) * (16 - numPolys)); + std::memset(polyPlaneZ + numPolys, 0, + sizeof(float) * (16 - numPolys)); + std::memset(polyPlaneW + numPolys, 0, + sizeof(float) * (16 - numPolys)); + std::memset(edges + numEdges, 0, + sizeof(traceModelEdge_t) * (32 - numEdges)); + std::memset(numPolyEdges + numPolys, 0, + sizeof(unsigned int) * (16 - numPolys)); + std::memset(polyEdges + numPolys, 0, + sizeof(polyEdges[0]) * (16 - numPolys)); + maxPolyEdges = 0; + for (unsigned int polygon = 0; polygon < numPolys; ++polygon) { + maxPolyEdges = (std::max)(maxPolyEdges, numPolyEdges[polygon]); + const std::uint8_t fill = numPolyEdges[polygon] > 0 + ? polyEdges[polygon][numPolyEdges[polygon] - 1] + : 0; + for (unsigned int edge = numPolyEdges[polygon]; edge < 16; ++edge) { + polyEdges[polygon][edge] = fill; + } + } +} + +int idTraceModel::GenerateEdgeNormals() { + std::memset(edgeNormalX, 0, sizeof(edgeNormalX)); + std::memset(edgeNormalY, 0, sizeof(edgeNormalY)); + std::memset(edgeNormalZ, 0, sizeof(edgeNormalZ)); + for (unsigned int polygon = 0; polygon < numPolys; ++polygon) { + for (unsigned int edge = 0; edge < numPolyEdges[polygon]; ++edge) { + const unsigned int edgeIndex = polyEdges[polygon][edge] & 0x7F; + if (edgeIndex >= numEdges) { + continue; + } + edgeNormalX[edgeIndex] += polyPlaneX[polygon]; + edgeNormalY[edgeIndex] += polyPlaneY[polygon]; + edgeNormalZ[edgeIndex] += polyPlaneZ[polygon]; + } + } + for (unsigned int edge = 0; edge < numEdges; ++edge) { + idVec3 normal(edgeNormalX[edge], edgeNormalY[edge], + edgeNormalZ[edge]); + normal.NormalizeFast(); + edgeNormalX[edge] = normal.x; + edgeNormalY[edge] = normal.y; + edgeNormalZ[edge] = normal.z; + } + return 1; +} + +bool idTraceModel::IsClosedSurface() const { + unsigned int edgeUseCounts[32]{}; + for (unsigned int polygon = 0; polygon < numPolys; ++polygon) { + for (unsigned int edge = 0; edge < numPolyEdges[polygon]; ++edge) { + const unsigned int edgeIndex = polyEdges[polygon][edge] & 0x7Fu; + if (edgeIndex < 32) { + ++edgeUseCounts[edgeIndex]; + } + } + } + for (unsigned int edge = 0; edge < numEdges; ++edge) { + if (edgeUseCounts[edge] != 2) { + return false; + } + } + return true; +} + +void idTraceModel::TestConvexity() { + isConvex = true; + for (unsigned int polygon = 0; polygon < numPolys; ++polygon) { + for (unsigned int vertex = 0; vertex < numVerts; ++vertex) { + const float distance = + vertsX[vertex] * polyPlaneX[polygon] + + vertsY[vertex] * polyPlaneY[polygon] + + vertsZ[vertex] * polyPlaneZ[polygon] + + polyPlaneW[polygon]; + if (distance > 0.01f) { + isConvex = false; + return; + } + } + } +} + +void idTraceModel::CalculateInsetSphereRadius() { + const float lower = (std::min)(std::fabs(bounds[0].x), + (std::min)(std::fabs(bounds[0].y), std::fabs(bounds[0].z))); + const float upper = (std::min)(std::fabs(bounds[1].x), + (std::min)(std::fabs(bounds[1].y), std::fabs(bounds[1].z))); + radius = (std::min)(lower, upper); +} + +void idTraceModel::InitBox() { + type = TRM_BOX; + numVerts = 8; + numEdges = 12; + numPolys = 6; + maxPolyEdges = 4; + + const std::uint16_t edgeVertices[12][2] = { + {0, 1}, {1, 2}, {2, 3}, {3, 0}, + {4, 5}, {5, 6}, {6, 7}, {7, 4}, + {0, 4}, {1, 5}, {2, 6}, {3, 7}}; + std::memcpy(edges, edgeVertices, sizeof(edgeVertices)); + + const std::uint8_t boxPolygonEdges[6][4] = { + {0x83, 0x82, 0x81, 0x80}, + {4, 5, 6, 7}, + {0, 9, 0x84, 0x88}, + {1, 10, 0x85, 0x89}, + {2, 11, 0x86, 0x8A}, + {3, 8, 0x87, 0x8B}}; + for (int polygon = 0; polygon < 6; ++polygon) { + numPolyEdges[polygon] = 4; + std::memcpy(polyEdges[polygon], boxPolygonEdges[polygon], 4); + } + const idVec3 normals[6] = { + idVec3(0.0f, 0.0f, -1.0f), idVec3(0.0f, 0.0f, 1.0f), + idVec3(0.0f, -1.0f, 0.0f), idVec3(1.0f, 0.0f, 0.0f), + idVec3(0.0f, 1.0f, 0.0f), idVec3(-1.0f, 0.0f, 0.0f)}; + for (int polygon = 0; polygon < 6; ++polygon) { + polyPlaneX[polygon] = normals[polygon].x; + polyPlaneY[polygon] = normals[polygon].y; + polyPlaneZ[polygon] = normals[polygon].z; + polyPlaneW[polygon] = 0.0f; + } + isConvex = true; + GenerateEdgeNormals(); + ClearUnused(); +} + +void idTraceModel::SetupBox(const idBounds& boxBounds) { + if (type != TRM_BOX) { + InitBox(); + } + offset = (boxBounds[0] + boxBounds[1]) * 0.5f; + const idVec3 vertices[8] = { + idVec3(boxBounds[0].x, boxBounds[0].y, boxBounds[0].z), + idVec3(boxBounds[1].x, boxBounds[0].y, boxBounds[0].z), + idVec3(boxBounds[1].x, boxBounds[1].y, boxBounds[0].z), + idVec3(boxBounds[0].x, boxBounds[1].y, boxBounds[0].z), + idVec3(boxBounds[0].x, boxBounds[0].y, boxBounds[1].z), + idVec3(boxBounds[1].x, boxBounds[0].y, boxBounds[1].z), + idVec3(boxBounds[1].x, boxBounds[1].y, boxBounds[1].z), + idVec3(boxBounds[0].x, boxBounds[1].y, boxBounds[1].z)}; + for (int vertex = 0; vertex < 8; ++vertex) { + vertsX[vertex] = vertices[vertex].x; + vertsY[vertex] = vertices[vertex].y; + vertsZ[vertex] = vertices[vertex].z; + } + polyPlaneW[0] = boxBounds[0].z; + polyPlaneW[1] = -boxBounds[1].z; + polyPlaneW[2] = boxBounds[0].y; + polyPlaneW[3] = -boxBounds[1].x; + polyPlaneW[4] = -boxBounds[1].y; + polyPlaneW[5] = boxBounds[0].x; + bounds = boxBounds; + CalculateInsetSphereRadius(); +} + +void idTraceModel::SetupBox(const float size) { + idBounds boxBounds; + const float halfSize = size * 0.5f; + boxBounds[0].Set(-halfSize, -halfSize, -halfSize); + boxBounds[1].Set(halfSize, halfSize, halfSize); + SetupBox(boxBounds); +} diff --git a/source/shared/idlib/geometry/winding.cpp b/source/shared/idlib/geometry/winding.cpp new file mode 100644 index 0000000..83d2cbf --- /dev/null +++ b/source/shared/idlib/geometry/winding.cpp @@ -0,0 +1,212 @@ +#include "idlib/geometry/winding.h" + +#include "idlib/lib_print.h" + +#include +#include +#include +#include + +idWinding::idWinding() + : numPoints(0), p(nullptr), allocedSize(0) { +} + +idWinding::idWinding(const idWinding& other) + : idWinding() { + *this = other; +} + +idWinding::~idWinding() { + _aligned_free(p); + p = nullptr; +} + +idWinding& idWinding::operator=(const idWinding& other) { + if (this != &other && ReAllocate(other.numPoints, false)) { + numPoints = other.numPoints; + if (numPoints > 0) { + std::memcpy(p, other.p, + static_cast(numPoints) * sizeof(idVec5)); + } + } + return *this; +} + +void idWinding::Clear() { + numPoints = 0; + allocedSize = 0; + _aligned_free(p); + p = nullptr; +} + +bool idWinding::ReAllocate(const int count, const bool keep) { + const int newSize = (count + 3) & ~3; + idVec5* const replacement = static_cast(_aligned_malloc( + static_cast(newSize) * sizeof(idVec5), 16)); + if (replacement == nullptr && newSize > 0) { + return false; + } + if (keep && p != nullptr && numPoints > 0) { + std::memcpy(replacement, p, + static_cast((std::min)(numPoints, newSize)) * + sizeof(idVec5)); + } + _aligned_free(p); + p = replacement; + allocedSize = newSize; + if (numPoints > allocedSize) { + numPoints = allocedSize; + } + return true; +} + +void idWinding::AddPoint(const idVec3& point) { + if (numPoints + 1 > allocedSize && + !ReAllocate(numPoints + 1, true)) { + return; + } + p[numPoints++] = idVec5(point.x, point.y, point.z, 0.0f, 0.0f); +} + +void idWinding::AddPoint(const idVec5& point) { + if (numPoints + 1 > allocedSize && + !ReAllocate(numPoints + 1, true)) { + return; + } + p[numPoints++] = point; +} + +idFixedWinding::idFixedWinding() { + p = data; + numPoints = 0; + allocedSize = 64; +} + +idFixedWinding::idFixedWinding(const idFixedWinding& other) + : idFixedWinding() { + *this = other; +} + +idFixedWinding& idFixedWinding::operator=(const idFixedWinding& other) { + idWinding::operator=(other); + return *this; +} + +void idWinding::GetBounds(idBounds& bounds) const { + if (numPoints <= 0) { + bounds[0].Set(1.0e30f, 1.0e30f, 1.0e30f); + bounds[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); + return; + } + bounds[0].Set(p[0].x, p[0].y, p[0].z); + bounds[1] = bounds[0]; + for (int point = 1; point < numPoints; ++point) { + bounds[0].x = (std::min)(bounds[0].x, p[point].x); + bounds[0].y = (std::min)(bounds[0].y, p[point].y); + bounds[0].z = (std::min)(bounds[0].z, p[point].z); + bounds[1].x = (std::max)(bounds[1].x, p[point].x); + bounds[1].y = (std::max)(bounds[1].y, p[point].y); + bounds[1].z = (std::max)(bounds[1].z, p[point].z); + } +} + +bool idWinding::IsHuge(const float radius) const { + for (int point = 0; point < numPoints; ++point) { + for (int axis = 0; axis < 3; ++axis) { + if (p[point][axis] <= -radius || p[point][axis] >= radius) { + return true; + } + } + } + return false; +} + +idFixedWinding::~idFixedWinding() { + p = nullptr; +} + +void idFixedWinding::Clear() { + numPoints = 0; + p = data; + allocedSize = 64; +} + +bool idFixedWinding::ReAllocate(const int count, const bool) { + if (count <= 64) { + return true; + } + idLibPrint::Warning( + "idFixedWinding -> MAX_POINTS_ON_WINDING overflowed"); + return false; +} + +int idFixedWinding::SplitInPlace(const idPlane& plane, + const float epsilon, idFixedWinding* const back) { + if (back == nullptr) { + return 0; + } + + float distances[65]{}; + int sides[65]{}; + int counts[3]{}; + for (int index = 0; index < numPoints; ++index) { + distances[index] = plane.a * p[index].x + plane.b * p[index].y + + plane.c * p[index].z + plane.d; + if (distances[index] > epsilon) { + sides[index] = 0; + } else if (distances[index] < -epsilon) { + sides[index] = 1; + } else { + sides[index] = 2; + } + ++counts[sides[index]]; + } + if (counts[1] == 0) { + return counts[0] != 0 ? 0 : 2; + } + if (counts[0] == 0) { + return 1; + } + + distances[numPoints] = distances[0]; + sides[numPoints] = sides[0]; + idFixedWinding front; + back->Clear(); + for (int index = 0; index < numPoints; ++index) { + const idVec5& current = p[index]; + if (sides[index] == 2) { + front.AddPoint(current); + back->AddPoint(current); + } else if (sides[index] == 0) { + front.AddPoint(current); + } else { + back->AddPoint(current); + } + + if (sides[index + 1] == 2 || + sides[index + 1] == sides[index]) { + continue; + } + const idVec5& next = p[(index + 1) % numPoints]; + const float fraction = distances[index] / + (distances[index] - distances[index + 1]); + idVec5 split; + for (int component = 0; component < 5; ++component) { + split[component] = current[component] + + fraction * (next[component] - current[component]); + } + if (plane.a == 1.0f) split.x = -plane.d; + else if (plane.a == -1.0f) split.x = plane.d; + if (plane.b == 1.0f) split.y = -plane.d; + else if (plane.b == -1.0f) split.y = plane.d; + if (plane.c == 1.0f) split.z = -plane.d; + else if (plane.c == -1.0f) split.z = plane.d; + front.AddPoint(split); + back->AddPoint(split); + } + Clear(); + for (int index = 0; index < front.numPoints; ++index) { + AddPoint(front[index]); + } + return 3; +} diff --git a/source/shared/idlib/geometry/winding.h b/source/shared/idlib/geometry/winding.h index fcb91ca..149a963 100644 --- a/source/shared/idlib/geometry/winding.h +++ b/source/shared/idlib/geometry/winding.h @@ -3,6 +3,7 @@ #include "../bv/bounds.h" #include "../containers/list.h" #include "../math/plane.h" +#include "../sys/sys_alloc.h" class idWinding { public: @@ -41,6 +42,8 @@ public: class idFixedWinding : public idWinding { public: idFixedWinding(); + idFixedWinding(const idFixedWinding& other); + idFixedWinding& operator=(const idFixedWinding& other); ~idFixedWinding() override; void Clear() override; bool ReAllocate(int numPoints, bool keep = false) override; diff --git a/source/shared/idlib/lib_print.h b/source/shared/idlib/lib_print.h index 1d17b06..f13be81 100644 --- a/source/shared/idlib/lib_print.h +++ b/source/shared/idlib/lib_print.h @@ -36,9 +36,8 @@ private: bool fatalError; }; -// PC-side extension for the listener/context facilities added after the BFG -// idLib interface. The six common Printf/Warning/Error entry points continue -// to be supplied by the BFG baseline until the engine-wide Lib.h is replaced. +// Recovered listener/context side of the idLib print interface. The common +// Printf/Warning/Error entry points share this PC implementation. class idLibPrint { public: using fatalErrorHandler_t = void (*)(const char*); diff --git a/source/shared/idlib/math/vector.h b/source/shared/idlib/math/vector.h index e6edbaa..266feab 100644 --- a/source/shared/idlib/math/vector.h +++ b/source/shared/idlib/math/vector.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -135,10 +136,57 @@ public: float Length() const { return std::sqrt(LengthSqr()); } + + // Materialized in the authoritative shared/idlib/math/vector.h dump. + // The original PowerPC implementation uses a refined reciprocal square + // root; the scalar PC path preserves its normalized result and returns + // the vector's original length. + float NormalizeFast() { + const float lengthSqr = LengthSqr(); + if (lengthSqr <= 0.0f) { + return 0.0f; + } + + const float length = std::sqrt(lengthSqr); + const float inverseLength = 1.0f / length; + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + return length; + } }; static_assert(sizeof(idVec3) == 12, "Recovered idVec3 layout changed"); +// Unit vectors embedded in AAS traversal records are stored as signed +// 16-bit components. The authoritative constructor normalizes before +// quantizing with a scale of 32767. +class idQuantizedVec3 { +public: + std::int16_t x; + std::int16_t y; + std::int16_t z; + + idQuantizedVec3() = default; + explicit idQuantizedVec3(const idVec3& vector) { Set(vector); } + + void Set(idVec3 vector) { + vector.NormalizeFast(); + x = static_cast(vector.x * 32767.0f); + y = static_cast(vector.y * 32767.0f); + z = static_cast(vector.z * 32767.0f); + } + + idVec3 ToVec3() const { + constexpr float inverseQuantization = 1.0f / 32767.0f; + return idVec3(x * inverseQuantization, y * inverseQuantization, + z * inverseQuantization); + } +}; + +static_assert(sizeof(idQuantizedVec3) == 6, + "Recovered idQuantizedVec3 ABI changed"); + class idMat3 { public: idVec3 mat[3]; @@ -185,6 +233,15 @@ public: return *this; } + idMat3& OrthoNormalizeSelf() { + mat[0].NormalizeFast(); + mat[2] = mat[0].Cross(mat[1]); + mat[2].NormalizeFast(); + mat[1] = mat[2].Cross(mat[0]); + mat[1].NormalizeFast(); + return *this; + } + idMat3 Transpose() const { return idMat3( mat[0].x, mat[1].x, mat[2].x, @@ -399,6 +456,21 @@ public: idQuat operator*(const float scale) const { return idQuat(x * scale, y * scale, z * scale, w * scale); } + + // Materialized in the authoritative shared/idlib/math/quat.h dump. + idQuat& Normalize() { + const float lengthSqr = x * x + y * y + z * z + w * w; + if (lengthSqr <= 0.0f) { + return *this; + } + + const float inverseLength = 1.0f / std::sqrt(lengthSqr); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + w *= inverseLength; + return *this; + } }; static_assert(sizeof(idQuat) == 16, "Recovered idQuat layout changed"); diff --git a/source/shared/idlib/metrics/timer.cpp b/source/shared/idlib/metrics/timer.cpp new file mode 100644 index 0000000..4e91814 --- /dev/null +++ b/source/shared/idlib/metrics/timer.cpp @@ -0,0 +1,17 @@ +#include "timer.h" + +#include + +std::int64_t idTimer::baseClockTicks = -1; + +void idTimer::InitBaseClockTicks() const { + std::int64_t best = (std::numeric_limits::max)(); + // The recovered function calibrates the minimum empty Start/Stop cost. + for (int iteration = 0; iteration < 1000; ++iteration) { + const std::int64_t begin = ClockNow(); + const std::int64_t elapsed = ClockNow() - begin; + if (elapsed < best) best = elapsed; + } + baseClockTicks = best == (std::numeric_limits::max)() + ? 0 : best; +} diff --git a/source/shared/idlib/metrics/timer.h b/source/shared/idlib/metrics/timer.h index e045047..5589be1 100644 --- a/source/shared/idlib/metrics/timer.h +++ b/source/shared/idlib/metrics/timer.h @@ -14,10 +14,11 @@ public: void Start() { state = TS_STARTED; start = ClockNow(); } void Stop() { - if (state == TS_STARTED) { - clockTicks += ClockNow() - start; - state = TS_STOPPED; - } + if (state != TS_STARTED) return; + clockTicks += ClockNow() - start; + if (baseClockTicks < 0) InitBaseClockTicks(); + if (clockTicks > baseClockTicks) clockTicks -= baseClockTicks; + state = TS_STOPPED; } void Clear() { state = TS_STOPPED; start = 0; clockTicks = 0; } std::int64_t ClockTicks() const { return clockTicks; } @@ -28,6 +29,9 @@ public: std::int64_t clockTicks; private: + static std::int64_t baseClockTicks; + void InitBaseClockTicks() const; + static std::int64_t ClockNow() { return std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count(); @@ -51,4 +55,3 @@ static_assert(sizeof(idTimer) == 32, "Recovered idTimer ABI changed"); static_assert(sizeof(idJobTimer) == 40, "Recovered idJobTimer ABI changed"); static_assert(sizeof(idSPUTimer) == 4, "Recovered idSPUTimer ABI changed"); #endif - diff --git a/source/shared/idlib/runningaverage.h b/source/shared/idlib/runningaverage.h index 638655b..8403fa8 100644 --- a/source/shared/idlib/runningaverage.h +++ b/source/shared/idlib/runningaverage.h @@ -3,7 +3,7 @@ #include // Recovered layout of idList from tungsten.exe.h type 12501. This -// private storage facade avoids importing the newer BFG container definition +// private storage facade preserves the recovered container definition // while retaining the original Win32 field widths and offsets. struct idRunningAverageFloatList { float* list; diff --git a/source/shared/idlib/sys/sys_assert.cpp b/source/shared/idlib/sys/sys_assert.cpp new file mode 100644 index 0000000..a010704 --- /dev/null +++ b/source/shared/idlib/sys/sys_assert.cpp @@ -0,0 +1,17 @@ +#include "sys_assert.h" + +#include "../lib_print.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +bool AssertFailed(const char* file, const int line, const char* expression, + const bool inlineBreak) { + idLibPrint::Warning("ASSERTION FAILED! %s(%d): '%s'", + file == nullptr ? "" : file, line, + expression == nullptr ? "" : expression); + if (!inlineBreak && IsDebuggerPresent()) DebugBreak(); + return inlineBreak; +} diff --git a/source/shared/idlib/sys/sys_assert.h b/source/shared/idlib/sys/sys_assert.h new file mode 100644 index 0000000..0a097b3 --- /dev/null +++ b/source/shared/idlib/sys/sys_assert.h @@ -0,0 +1,4 @@ +#pragma once + +bool AssertFailed(const char* file, int line, const char* expression, + bool inlineBreak); diff --git a/source/shared/idlib/text/lexer.cpp b/source/shared/idlib/text/lexer.cpp new file mode 100644 index 0000000..eac545e --- /dev/null +++ b/source/shared/idlib/text/lexer.cpp @@ -0,0 +1,1003 @@ +#include "idlib/text/lexer.h" + +#include "idlib/filesystem/file.h" +#include "idlib/filesystem/filesystem.h" +#include "idlib/lib_print.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +#define LEX_PUNCTUATION(text, value) { const_cast(text), value } +const punctuation_t defaultPunctuations[] = { + LEX_PUNCTUATION(">>=", P_RSHIFT_ASSIGN), + LEX_PUNCTUATION("<<=", P_LSHIFT_ASSIGN), + LEX_PUNCTUATION("...", P_PARMS), + LEX_PUNCTUATION("##", P_PRECOMPMERGE), + LEX_PUNCTUATION("&&", P_LOGIC_AND), + LEX_PUNCTUATION("||", P_LOGIC_OR), + LEX_PUNCTUATION(">=", P_LOGIC_GEQ), + LEX_PUNCTUATION("<=", P_LOGIC_LEQ), + LEX_PUNCTUATION("==", P_LOGIC_EQ), + LEX_PUNCTUATION("!=", P_LOGIC_UNEQ), + LEX_PUNCTUATION("*=", P_MUL_ASSIGN), + LEX_PUNCTUATION("/=", P_DIV_ASSIGN), + LEX_PUNCTUATION("%=", P_MOD_ASSIGN), + LEX_PUNCTUATION("+=", P_ADD_ASSIGN), + LEX_PUNCTUATION("-=", P_SUB_ASSIGN), + LEX_PUNCTUATION("++", P_INC), + LEX_PUNCTUATION("--", P_DEC), + LEX_PUNCTUATION("&=", P_BIN_AND_ASSIGN), + LEX_PUNCTUATION("|=", P_BIN_OR_ASSIGN), + LEX_PUNCTUATION("^=", P_BIN_XOR_ASSIGN), + LEX_PUNCTUATION(">>", P_RSHIFT), + LEX_PUNCTUATION("<<", P_LSHIFT), + LEX_PUNCTUATION("::", P_SCOPE_RESOLUTION), + LEX_PUNCTUATION(".", P_MEMBER_SELECTION_OBJECT), + LEX_PUNCTUATION("->", P_MEMBER_SELECTION_POINTER), + LEX_PUNCTUATION(".*", P_POINTER_TO_MEMBER_OBJECT), + LEX_PUNCTUATION("->*", P_POINTER_TO_MEMBER_POINTER), + LEX_PUNCTUATION("*", P_MUL), + LEX_PUNCTUATION("/", P_DIV), + LEX_PUNCTUATION("%", P_MOD), + LEX_PUNCTUATION("+", P_ADD), + LEX_PUNCTUATION("-", P_SUB), + LEX_PUNCTUATION("=", P_ASSIGN), + LEX_PUNCTUATION("&", P_BIN_AND), + LEX_PUNCTUATION("|", P_BIN_OR), + LEX_PUNCTUATION("^", P_BIN_XOR), + LEX_PUNCTUATION("~", P_BIN_NOT), + LEX_PUNCTUATION("!", P_LOGIC_NOT), + LEX_PUNCTUATION(">", P_LOGIC_GREATER), + LEX_PUNCTUATION("<", P_LOGIC_LESS), + LEX_PUNCTUATION(",", P_COMMA), + LEX_PUNCTUATION(";", P_SEMICOLON), + LEX_PUNCTUATION(":", P_COLON), + LEX_PUNCTUATION("?", P_QUESTIONMARK), + LEX_PUNCTUATION("(", P_PARENTHESESOPEN), + LEX_PUNCTUATION(")", P_PARENTHESESCLOSE), + LEX_PUNCTUATION("{", P_BRACEOPEN), + LEX_PUNCTUATION("}", P_BRACECLOSE), + LEX_PUNCTUATION("[", P_SQBRACKETOPEN), + LEX_PUNCTUATION("]", P_SQBRACKETCLOSE), + LEX_PUNCTUATION("\\", P_BACKSLASH), + LEX_PUNCTUATION("#", P_PRECOMP), + LEX_PUNCTUATION("$", P_DOLLAR), + LEX_PUNCTUATION("'", P_APOSTROPHE), + LEX_PUNCTUATION("\"", P_QUOTE), + LEX_PUNCTUATION("@", P_AT), + { nullptr, 0 } +}; +#undef LEX_PUNCTUATION + +bool IsNameStart(const int character) { + return std::isalpha(static_cast(character)) != 0 + || character == '_'; +} + +bool IsNameCharacter(const int character) { + return std::isalnum(static_cast(character)) != 0 + || character == '_'; +} + +void AppendRange(idStr& string, const char* first, const char* last) { + while (first < last) string.Append(*first++); +} + +bool TokenMatchesType(const idToken& token, const int type, + const int subtype) { + if (token.type != type) return false; + if (type == TT_NUMBER) return (token.subtype & subtype) == subtype; + if (type == TT_PUNCTUATION) return token.subtype == subtype; + return true; +} + +const char* TokenTypeName(const int type) { + switch (type) { + case TT_STRING: return "string"; + case TT_LITERAL: return "literal"; + case TT_NUMBER: return "number"; + case TT_NAME: return "name"; + case TT_PUNCTUATION: return "punctuation"; + default: return "unknown type"; + } +} + +} // namespace + +idLexer::idLexer(const int flags_) + : loaded(false), filename(""), allocated(0), buffer(nullptr), + script_p(nullptr), end_p(nullptr), lastScript_p(nullptr), + whiteSpaceStart_p(nullptr), whiteSpaceEnd_p(nullptr), fileTime(0), + length(0), line(0), lastline(0), flags(flags_), punctuations(nullptr), + punctuationtable(nullptr), nextpunctuation(nullptr), next(nullptr), + errorMsg(), hadError(false), hadWarning(false) { + SetPunctuations(nullptr); +} + +idLexer::~idLexer() { + FreeSource(); +} + +void idLexer::CreatePunctuationTable(const punctuation_t* const list) { + if (punctuationtable != nullptr) std::free(punctuationtable); + if (nextpunctuation != nullptr) std::free(nextpunctuation); + punctuationtable = static_cast(std::malloc(256 * sizeof(int))); + int count = 0; + while (list != nullptr && list[count].p != nullptr) ++count; + nextpunctuation = count > 0 + ? static_cast(std::malloc(count * sizeof(int))) : nullptr; + if (punctuationtable == nullptr) return; + std::fill(punctuationtable, punctuationtable + 256, -1); + if (nextpunctuation != nullptr) { + std::fill(nextpunctuation, nextpunctuation + count, -1); + } + // The recovered table chains longest spellings first for each byte. + for (int index = 0; index < count; ++index) { + const unsigned char first = + static_cast(list[index].p[0]); + int* link = &punctuationtable[first]; + while (*link >= 0 && std::strlen(list[*link].p) + >= std::strlen(list[index].p)) { + link = &nextpunctuation[*link]; + } + nextpunctuation[index] = *link; + *link = index; + } +} + +void idLexer::SetPunctuations(const punctuation_t* punctuation) { + if (punctuation == nullptr) punctuation = defaultPunctuations; + CreatePunctuationTable(punctuation); + punctuations = punctuation; +} + +const char* idLexer::GetPunctuationFromId(const int id) const { + if (punctuations == nullptr) return "unknown punctuation"; + for (int index = 0; punctuations[index].p != nullptr; ++index) { + if (punctuations[index].n == id) return punctuations[index].p; + } + return "unknown punctuation"; +} + +int idLexer::GetPunctuationId(const char* const punctuation) const { + if (punctuations == nullptr || punctuation == nullptr) return 0; + for (int index = 0; punctuations[index].p != nullptr; ++index) { + if (idStr::Cmp(punctuations[index].p, punctuation) == 0) { + return punctuations[index].n; + } + } + return 0; +} + +bool idLexer::CheckString(const char* const string) const { + if (script_p == nullptr || string == nullptr) return false; + const std::size_t count = std::strlen(string); + return script_p + count <= end_p + && std::memcmp(script_p, string, count) == 0; +} + +bool idLexer::LoadFile(const char* const fileName, const bool OSPath) { + return LoadFilePartial(fileName, OSPath, + (std::numeric_limits::max)()); +} + +bool idLexer::LoadFilePartial(const char* const fileName, + const bool OSPath, const int maximumLength) { + if (loaded) { + idLibPrint::Error("LoadFile: another script already loaded"); + } + if (fileSystem == nullptr || fileName == nullptr) return false; + idFileLocal file(fileSystem->OpenFileRead(fileName, true, false)); + if (file.file == nullptr) return false; + const std::int64_t fileLength = file->Length(); + length = static_cast((std::max)(0, + (std::min)(fileLength, maximumLength))); + char* memory = static_cast(std::malloc(length + 1u)); + if (memory == nullptr) return false; + if (file->Read(memory, length) != length) { + std::free(memory); + length = 0; + return false; + } + memory[length] = '\0'; + buffer = memory; + allocated = 1; + fileTime = file->Timestamp(); + filename = OSPath ? file->GetFullPath() : file->GetName(); + loaded = true; + script_p = buffer; + end_p = buffer + length; + lastScript_p = buffer; + line = lastline = 1; + return true; +} + +bool idLexer::LoadMemory(const char* const pointer, + const unsigned int memoryLength, const char* const name) { + if (loaded) { + idLibPrint::Error("LoadMemory: another script already loaded"); + } + if (pointer == nullptr) return false; + filename = name != nullptr ? name : "memory"; + buffer = pointer; + length = memoryLength; + fileTime = 0; + script_p = pointer; + end_p = pointer + memoryLength; + lastScript_p = pointer; + line = lastline = 1; + allocated = 0; + loaded = true; + return true; +} + +void idLexer::FreeSource() { + if (punctuationtable != nullptr) { + std::free(punctuationtable); + punctuationtable = nullptr; + } + if (nextpunctuation != nullptr) { + std::free(nextpunctuation); + nextpunctuation = nullptr; + } + if (allocated != 0) std::free(const_cast(buffer)); + allocated = 0; + buffer = nullptr; + script_p = nullptr; + end_p = nullptr; + lastScript_p = nullptr; + whiteSpaceStart_p = nullptr; + whiteSpaceEnd_p = nullptr; + length = 0; + filename.Clear(); + loaded = false; +} + +void idLexer::Reset() { + whiteSpaceStart_p = nullptr; + whiteSpaceEnd_p = nullptr; + line = lastline = 1; + script_p = lastScript_p = buffer; +} + +bool idLexer::SetScriptP(const char* const pointer) { + if (buffer == nullptr || end_p == nullptr + || pointer < buffer || pointer > end_p) return false; + script_p = pointer; + return true; +} + +void idLexer::UnreadToken() { + script_p = lastScript_p; + line = lastline; +} + +bool idLexer::EndOfFile() const { + return script_p == nullptr || end_p == nullptr || script_p >= end_p; +} + +bool idLexer::HadError() const { return hadError; } +bool idLexer::HadWarning() const { return hadWarning; } + +void idLexer::Error(const char* const format, ...) { + hadError = true; + if ((flags & LEXFL_NOERRORS) != 0) return; + char detail[1024] = {}; + va_list arguments; + va_start(arguments, format); + _vsnprintf_s(detail, sizeof(detail), _TRUNCATE, + format != nullptr ? format : "", arguments); + va_end(arguments); + char message[1400] = {}; + if ((flags & LEXFL_VCSTYLEREPORTS) != 0) { + _snprintf_s(message, sizeof(message), _TRUNCATE, + "%s(%d) : error : %s", filename.c_str(), line, detail); + } else { + _snprintf_s(message, sizeof(message), _TRUNCATE, + "file %s, line %d: %s", filename.c_str(), line, detail); + } + if (errorMsg.Length() == 0) errorMsg = message; + if ((flags & LEXFL_NOFATALERRORS) != 0) { + idLibPrint::Warning("%s", message); + return; + } + idLibPrint::Error("%s", message); +} + +void idLexer::Warning(const char* const format, ...) { + hadWarning = true; + if ((flags & LEXFL_NOWARNINGS) != 0) return; + char detail[1024] = {}; + va_list arguments; + va_start(arguments, format); + _vsnprintf_s(detail, sizeof(detail), _TRUNCATE, + format != nullptr ? format : "", arguments); + va_end(arguments); + idLibPrint::Warning("file %s, line %d: %s", + filename.c_str(), line, detail); +} + +bool idLexer::SkipWhiteSpace(const bool currentLine) { + if (script_p == nullptr || script_p >= end_p) return false; + for (;;) { + while (script_p < end_p + && static_cast(*script_p) <= 32u) { + if (*script_p++ == '\n') { + ++line; + if (currentLine) return true; + } + } + if (script_p >= end_p || *script_p == '\0') return false; + if (script_p + 1 < end_p && script_p[0] == '/' + && script_p[1] == '/') { + script_p += 2; + while (script_p < end_p && *script_p != '\0' + && *script_p != '\n') ++script_p; + if (script_p < end_p && *script_p == '\n') { + ++script_p; + ++line; + if (currentLine) return true; + continue; + } + return false; + } + if (script_p + 1 < end_p && script_p[0] == '/' + && script_p[1] == '*') { + script_p += 2; + while (script_p < end_p) { + if (script_p + 1 < end_p && script_p[0] == '*' + && script_p[1] == '/') { + script_p += 2; + break; + } + if (*script_p == '\n') ++line; + if (script_p + 1 < end_p && script_p[0] == '/' + && script_p[1] == '*') Warning("nested comment"); + ++script_p; + } + if (script_p >= end_p) return false; + continue; + } + return true; + } +} + +bool idLexer::ReadEscapeCharacter(char* const character) { + if (script_p == nullptr || script_p >= end_p || *script_p != '\\') { + return false; + } + ++script_p; + if (script_p >= end_p) return false; + if ((flags & LEXFL_NOEMITSTRINGESCAPECHARS) != 0) { + *character = *script_p++; + return true; + } + const char escape = *script_p++; + switch (escape) { + case '\\': *character = '\\'; return true; + case '\'': *character = '\''; return true; + case '"': *character = '"'; return true; + case '?': *character = '?'; return true; + case 'a': *character = '\a'; return true; + case 'b': *character = '\b'; return true; + case 'f': *character = '\f'; return true; + case 'n': *character = '\n'; return true; + case 'r': *character = '\r'; return true; + case 't': *character = '\t'; return true; + case 'v': *character = '\v'; return true; + case 'x': { + unsigned int value = 0; + int digits = 0; + while (script_p < end_p + && std::isxdigit(static_cast(*script_p))) { + const char digit = *script_p++; + value = value * 16u + static_cast( + digit >= '0' && digit <= '9' ? digit - '0' + : (digit | 32) - 'a' + 10); + ++digits; + } + if (digits == 0) Error("unknown escape char"); + if (value > 255u) Warning("too large value in escape character"); + *character = static_cast(value); + return true; + } + default: + if (escape >= '0' && escape <= '9') { + unsigned int value = static_cast(escape - '0'); + while (script_p < end_p && *script_p >= '0' + && *script_p <= '9') { + value = value * 10u + + static_cast(*script_p++ - '0'); + } + if (value > 255u) Warning("too large value in escape character"); + *character = static_cast(value); + return true; + } + Error("unknown escape char"); + *character = escape; + return false; + } +} + +bool idLexer::ReadString(idToken& token, const int quote) { + token.type = quote == '"' ? TT_STRING : TT_LITERAL; + ++script_p; + for (;;) { + while (script_p < end_p && *script_p != quote) { + if (*script_p == '\0' || *script_p == '\n') { + Error("missing trailing quote"); + return false; + } + if (*script_p == '\\' + && (flags & LEXFL_NOSTRINGESCAPECHARS) == 0) { + if ((flags & LEXFL_NOEMITSTRINGESCAPECHARS) != 0) { + token.Append('\\'); + } + char escaped = 0; + if (!ReadEscapeCharacter(&escaped)) return false; + token.Append(escaped); + } else { + token.Append(*script_p++); + } + } + if (script_p >= end_p) { + Error("missing trailing quote"); + return false; + } + ++script_p; + if (quote != '"' || (flags & LEXFL_NOSTRINGCONCAT) != 0) break; + const char* const afterQuote = script_p; + const int quoteLine = line; + if (!SkipWhiteSpace(false) || script_p >= end_p + || *script_p != quote) { + script_p = afterQuote; + line = quoteLine; + break; + } + ++script_p; + } + if (token.type == TT_LITERAL) { + if ((flags & LEXFL_ALLOWMULTICHARLITERALS) == 0 + && token.Length() != 1) { + Warning("literal is not one character long"); + } + token.subtype = token.Length() > 0 + ? static_cast(token.c_str()[0]) : 0; + } else { + token.subtype = token.Length(); + } + return true; +} + +bool idLexer::ReadRawStringBlock(idToken& token) { + if (!CheckString("<%")) return false; + script_p += 2; + token.type = TT_STRING; + while (script_p < end_p && !CheckString("%>")) { + if (*script_p == '\n') ++line; + token.Append(*script_p++); + } + if (script_p >= end_p) { + Error("missing %%> for raw string block"); + return false; + } + script_p += 2; + token.subtype = token.Length(); + return true; +} + +bool idLexer::ReadName(idToken& token) { + token.type = TT_NAME; + while (script_p < end_p) { + const char character = *script_p; + const bool accepted = IsNameCharacter(character) + || ((flags & LEXFL_ONLYSTRINGS) != 0 && character == '-') + || ((flags & LEXFL_ALLOWPATHNAMES) != 0 + && (character == '/' || character == '\\' + || character == ':' || character == '.' + || character == '@')) + || ((flags & LEXFL_ALLOWWILDCARD) != 0 && character == '*'); + if (!accepted) break; + token.Append(character); + ++script_p; + } + token.subtype = token.Length(); + return token.Length() != 0; +} + +bool idLexer::ReadNumber(idToken& token) { + token.type = TT_NUMBER; + token.subtype = 0; + const char* const start = script_p; + bool isFloat = false; + bool isHex = false; + bool isBinary = false; + bool isOctal = false; + int dots = 0; + if (script_p + 1 < end_p && script_p[0] == '0' + && (script_p[1] == 'x' || script_p[1] == 'X')) { + isHex = true; + script_p += 2; + while (script_p < end_p + && std::isxdigit(static_cast(*script_p))) { + ++script_p; + } + } else if (script_p + 1 < end_p && script_p[0] == '0' + && (script_p[1] == 'b' || script_p[1] == 'B')) { + isBinary = true; + script_p += 2; + while (script_p < end_p && (*script_p == '0' || *script_p == '1')) { + ++script_p; + } + } else { + if (*script_p == '0' && script_p + 1 < end_p + && script_p[1] != '.') isOctal = true; + while (script_p < end_p) { + const char character = *script_p; + if (character >= '0' && character <= '9') { + ++script_p; + continue; + } + if (character == '.') { + ++dots; + ++script_p; + continue; + } + break; + } + if (dots == 1) isFloat = true; + if (script_p < end_p && (*script_p == 'e' || *script_p == 'E')) { + isFloat = true; + ++script_p; + if (script_p < end_p && (*script_p == '+' || *script_p == '-')) { + ++script_p; + } + while (script_p < end_p && std::isdigit( + static_cast(*script_p))) ++script_p; + } + if (dots > 1 && (flags & LEXFL_ALLOWIPADDRESSES) != 0) { + token.subtype |= TT_IPADDRESS; + if (script_p < end_p && *script_p == ':') { + token.subtype |= TT_IPPORT; + ++script_p; + while (script_p < end_p && std::isdigit( + static_cast(*script_p))) ++script_p; + } + } + } + + bool unsignedSuffix = false; + bool longSuffix = false; + while (script_p < end_p) { + const char suffix = *script_p; + if (suffix == 'u' || suffix == 'U') { + unsignedSuffix = true; + ++script_p; + } else if (suffix == 'l' || suffix == 'L') { + longSuffix = true; + ++script_p; + } else if (suffix == 'f' || suffix == 'F') { + isFloat = true; + ++script_p; + } else { + break; + } + } + AppendRange(token, start, script_p); + token.subtype |= isFloat ? TT_FLOAT : TT_INTEGER; + token.subtype |= isHex ? TT_HEX : isBinary ? TT_BINARY + : isOctal && !isFloat ? TT_OCTAL : TT_DECIMAL; + if (unsignedSuffix) token.subtype |= TT_UNSIGNED; + if (longSuffix) token.subtype |= TT_LONG; + + errno = 0; + if (isBinary) { + unsigned int value = 0; + for (const char* cursor = start + 2; cursor < script_p + && (*cursor == '0' || *cursor == '1'); ++cursor) { + value = value * 2u + static_cast(*cursor - '0'); + } + token.intvalue = value; + token.floatvalue = static_cast(value); + } else if (isFloat) { + token.floatvalue = std::strtof(token.c_str(), nullptr); + token.intvalue = static_cast(token.floatvalue); + } else { + const int base = isHex ? 16 : isOctal ? 8 : 10; + token.intvalue = static_cast( + std::strtoul(token.c_str(), nullptr, base)); + token.floatvalue = static_cast(token.intvalue); + } + token.subtype |= TT_VALUESVALID; + return script_p > start; +} + +bool idLexer::ReadPunctuation(idToken& token) { + if (punctuations == nullptr || script_p >= end_p) return false; + int best = -1; + std::size_t bestLength = 0; + for (int index = 0; punctuations[index].p != nullptr; ++index) { + const std::size_t count = std::strlen(punctuations[index].p); + if (count <= bestLength || script_p + count > end_p) continue; + if (std::memcmp(script_p, punctuations[index].p, count) == 0) { + best = index; + bestLength = count; + } + } + if (best < 0) return false; + static_cast(token) = punctuations[best].p; + token.type = TT_PUNCTUATION; + token.subtype = punctuations[best].n; + script_p += bestLength; + return true; +} + +bool idLexer::ReadToken(idToken& token) { + if (!loaded) idLibPrint::Error("ReadToken: no file loaded"); + if (script_p == nullptr || script_p >= end_p) return false; + lastScript_p = script_p; + lastline = line; + token.Clear(); + token.type = 0; + token.subtype = 0; + token.intvalue = 0; + token.floatvalue = 0.0f; + token.flags = 0; + whiteSpaceStart_p = script_p; + token.whiteSpaceStart_p = script_p; + if (!SkipWhiteSpace(false)) return false; + if (script_p >= end_p) return false; + whiteSpaceEnd_p = script_p; + token.whiteSpaceEnd_p = script_p; + token.line = line; + token.linesCrossed = line - lastline; + + const char character = *script_p; + if ((flags & LEXFL_ONLYSTRINGS) != 0) { + if (character == '"' || character == '\'') { + return ReadString(token, character); + } + return ReadName(token); + } + if (std::isdigit(static_cast(character)) + || (character == '.' && script_p + 1 < end_p + && std::isdigit(static_cast(script_p[1])))) { + if (!ReadNumber(token)) return false; + if ((flags & LEXFL_ALLOWNUMBERNAMES) != 0 + && script_p < end_p && IsNameStart(*script_p)) { + token.type = TT_NAME; + while (script_p < end_p && IsNameCharacter(*script_p)) { + token.Append(*script_p++); + } + token.subtype = token.Length(); + } + return true; + } + if ((flags & LEXFL_ALLOWWILDCARD) != 0 && character == '*') { + return ReadName(token); + } + if ((flags & LEXFL_NOSTRINGS) == 0 + && (character == '"' || character == '\'')) { + return ReadString(token, character); + } + if (IsNameStart(character) + || ((flags & LEXFL_ALLOWPATHNAMES) != 0 && character == '.')) { + return ReadName(token); + } + if ((flags & LEXFL_ALLOWRAWSTRINGBLOCKS) != 0 + && CheckString("<%")) return ReadRawStringBlock(token); + if (ReadPunctuation(token)) return true; + Error("unknown punctuation %c", character); + return false; +} + +bool idLexer::ReadTokenOnLine(idToken& token) { + const char* const previous = script_p; + const int previousLine = line; + if (!ReadToken(token)) return false; + if (token.linesCrossed == 0) return true; + script_p = previous; + line = previousLine; + return false; +} + +bool idLexer::ExpectTokenString(const char* const string) { + idToken token; + if (!ReadToken(token)) { + Error("couldn't find expected '%s'", string != nullptr ? string : ""); + return false; + } + if (idStr::Cmp(token.c_str(), string) != 0) { + Error("expected '%s' but found '%s'", string, token.c_str()); + return false; + } + return true; +} + +bool idLexer::ExpectTokenType(const int type, const int subtype, + idToken& token) { + if (!ReadToken(token)) { + Error("couldn't read expected token"); + return false; + } + if (token.type != type) { + Error("expected a %s but found '%s'", TokenTypeName(type), + token.c_str()); + return false; + } + if (!TokenMatchesType(token, type, subtype)) { + if (type == TT_PUNCTUATION) { + Error("expected '%s' but found '%s'", + GetPunctuationFromId(subtype), token.c_str()); + } else { + Error("token '%s' has the wrong subtype", token.c_str()); + } + return false; + } + return true; +} + +bool idLexer::ExpectAnyToken(idToken& token) { + if (ReadToken(token)) return true; + Error("couldn't read expected token"); + return false; +} + +bool idLexer::CheckTokenString(const char* const string) { + idToken token; + if (!ReadToken(token)) return false; + if (idStr::Cmp(token.c_str(), string) == 0) return true; + UnreadToken(); + return false; +} + +bool idLexer::CheckTokenType(const int type, const int subtype, + idToken& token) { + idToken read; + if (!ReadToken(read)) return false; + if (!TokenMatchesType(read, type, subtype)) { + UnreadToken(); + return false; + } + token = read; + return true; +} + +bool idLexer::PeekTokenString(const char* const string) { + idToken token; + if (!ReadToken(token)) return false; + const bool result = idStr::Cmp(token.c_str(), string) == 0; + UnreadToken(); + return result; +} + +bool idLexer::PeekTokenType(const int type, const int subtype, + idToken& token) { + idToken read; + if (!ReadToken(read)) return false; + const bool result = TokenMatchesType(read, type, subtype); + UnreadToken(); + if (result) token = read; + return result; +} + +bool idLexer::SkipUntilString(const char* const string) { + idToken token; + while (ReadToken(token)) { + if (idStr::Cmp(token.c_str(), string) == 0) return true; + } + return false; +} + +bool idLexer::SkipRestOfLine() { + idToken token; + while (ReadToken(token)) { + if (token.linesCrossed != 0) { + UnreadToken(); + return true; + } + } + return false; +} + +bool idLexer::SkipBracedSection(const bool parseFirstBrace) { + int depth = parseFirstBrace ? 0 : 1; + idToken token; + while (ReadToken(token)) { + if (token.type != TT_PUNCTUATION) continue; + if (idStr::Cmp(token.c_str(), "{") == 0) ++depth; + else if (idStr::Cmp(token.c_str(), "}") == 0 && --depth <= 0) { + return true; + } + } + return false; +} + +bool idLexer::ParseBracedSectionExact(idStr& out, + const bool parseFirstBrace, const char openBrace, + const char closeBrace) { + out.Clear(); + int depth = 1; + if (parseFirstBrace) { + char brace[2] = { openBrace, '\0' }; + if (!ExpectTokenString(brace)) return false; + out.Append(openBrace); + } + idToken token; + while (ReadToken(token)) { + idStr whiteSpace; + GetWhiteSpaceBeforeToken(token, whiteSpace); + out.Append(whiteSpace); + if (token.type == TT_STRING) { + out.Append('"'); out.Append(token); out.Append('"'); + } else if (token.type == TT_LITERAL) { + out.Append('\''); out.Append(token); out.Append('\''); + } else { + out.Append(token); + } + if (token.type != TT_STRING) { + if (token.Length() > 0 && token.c_str()[0] == openBrace) ++depth; + else if (token.Length() > 0 + && token.c_str()[0] == closeBrace && --depth <= 0) { + return true; + } + } + } + return false; +} + +const char* idLexer::ParseRestOfLine(idStr& out) { + out.Clear(); + if (script_p == nullptr) return out.c_str(); + const char* first = script_p; + while (script_p < end_p && *script_p != '\0' && *script_p != '\n') { + ++script_p; + } + const char* last = script_p; + while (first < last && static_cast(*first) <= 32u) ++first; + while (last > first && static_cast(last[-1]) <= 32u) --last; + AppendRange(out, first, last); + if (script_p < end_p && *script_p == '\n') { + ++script_p; + ++line; + } + return out.c_str(); +} + +const char* idLexer::ParseCompleteLine(idStr& out) { + out.Clear(); + if (script_p == nullptr) return out.c_str(); + const char* const first = script_p; + while (script_p < end_p && *script_p != '\0') { + const char character = *script_p++; + if (character == '\n') { + ++line; + break; + } + } + AppendRange(out, first, script_p); + return out.c_str(); +} + +int idLexer::ParseInt() { + idToken token; + if (!ReadToken(token)) { + Error("couldn't read expected integer"); + return 0; + } + bool negative = false; + if (token.type == TT_PUNCTUATION + && idStr::Cmp(token.c_str(), "-") == 0) { + negative = true; + if (!ExpectTokenType(TT_NUMBER, TT_INTEGER, token)) return 0; + } else if (token.type != TT_NUMBER + || (token.subtype & TT_INTEGER) == 0) { + Error("expected integer value, found '%s'", token.c_str()); + return 0; + } + const int value = static_cast(token.intvalue); + return negative ? -value : value; +} + +unsigned int idLexer::ParseUnsignedInt() { + idToken token; + if (!ExpectTokenType(TT_NUMBER, TT_INTEGER, token)) return 0; + return token.intvalue; +} + +bool idLexer::ParseBool() { + idToken token; + if (!ReadToken(token)) { + Error("couldn't read expected boolean"); + return false; + } + if (token.type == TT_NUMBER) return token.intvalue != 0; + if (idStr::Cmp(token.c_str(), "true") == 0) return true; + if (idStr::Cmp(token.c_str(), "false") == 0) return false; + Error("expected boolean value, found '%s'", token.c_str()); + return false; +} + +float idLexer::ParseFloat(bool* const errorFlag) { + if (errorFlag != nullptr) *errorFlag = false; + idToken token; + if (!ReadToken(token)) { + if (errorFlag != nullptr) { + Warning("couldn't read expected floating point number"); + *errorFlag = true; + } else Error("couldn't read expected floating point number"); + return 0.0f; + } + bool negative = false; + if (token.type == TT_PUNCTUATION + && idStr::Cmp(token.c_str(), "-") == 0) { + negative = true; + if (!ExpectTokenType(TT_NUMBER, 0, token)) { + if (errorFlag != nullptr) *errorFlag = true; + return 0.0f; + } + } else if (token.type != TT_NUMBER) { + if (errorFlag != nullptr) { + Warning("expected float value, found '%s'", token.c_str()); + *errorFlag = true; + } else Error("expected float value, found '%s'", token.c_str()); + return 0.0f; + } + return negative ? -token.floatvalue : token.floatvalue; +} + +bool idLexer::Parse1DMatrix(const int x, float* const matrix, + const bool expectCommas) { + if (!ExpectTokenString("(")) return false; + for (int index = 0; index < x; ++index) { + matrix[index] = ParseFloat(); + if (expectCommas && index != x - 1 + && !ExpectTokenString(",")) return false; + } + return ExpectTokenString(")"); +} + +bool idLexer::Parse2DMatrix(const int y, const int x, + float* const matrix) { + if (!ExpectTokenString("(")) return false; + for (int index = 0; index < y; ++index) { + if (!Parse1DMatrix(x, matrix + index * x, false)) return false; + } + return ExpectTokenString(")"); +} + +int idLexer::GetLastWhiteSpace(idStr& whiteSpace) const { + whiteSpace.Clear(); + if (whiteSpaceStart_p == nullptr || whiteSpaceEnd_p == nullptr) return 0; + AppendRange(whiteSpace, whiteSpaceStart_p, whiteSpaceEnd_p); + return whiteSpace.Length(); +} + +int idLexer::GetNextWhiteSpace(idStr& whiteSpace, + const bool currentLine) { + whiteSpaceStart_p = script_p; + SkipWhiteSpace(currentLine); + whiteSpaceEnd_p = script_p; + return GetLastWhiteSpace(whiteSpace); +} + +bool idLexer::GetWhiteSpaceBeforeToken(const idToken& token, + idStr& whiteSpace) const { + whiteSpace.Clear(); + if (buffer == nullptr || token.whiteSpaceStart_p == nullptr + || token.whiteSpaceEnd_p == nullptr + || token.whiteSpaceStart_p < buffer + || token.whiteSpaceEnd_p < token.whiteSpaceStart_p + || token.whiteSpaceEnd_p > end_p) return false; + AppendRange(whiteSpace, token.whiteSpaceStart_p, + token.whiteSpaceEnd_p); + return true; +} diff --git a/source/shared/idlib/text/lexer.h b/source/shared/idlib/text/lexer.h index 1397218..48d92e4 100644 --- a/source/shared/idlib/text/lexer.h +++ b/source/shared/idlib/text/lexer.h @@ -9,6 +9,73 @@ struct punctuation_t { int n; }; +// Recovered from the lexer PDB enums and the subtype tests in lexer.cpp. +enum lexerTokenType_t : int { + TT_STRING = 1, + TT_LITERAL = 2, + TT_NUMBER = 3, + TT_NAME = 4, + TT_PUNCTUATION = 5 +}; + +enum lexerNumberSubtype_t : int { + TT_INTEGER = 0x0001, + TT_DECIMAL = 0x0002, + TT_HEX = 0x0004, + TT_OCTAL = 0x0008, + TT_BINARY = 0x0010, + TT_LONG = 0x0020, + TT_UNSIGNED = 0x0040, + TT_FLOAT = 0x0080, + TT_IPADDRESS = 0x0100, + TT_IPPORT = 0x0200, + TT_VALUESVALID = 0x0400, + TT_INFINITE = 0x0800, + TT_INDEFINITE = 0x1000, + TT_NAN = 0x2000 +}; + +enum lexerPunctuation_t : int { + P_RSHIFT_ASSIGN = 0, P_LSHIFT_ASSIGN, P_PARMS, P_PRECOMPMERGE, + P_LOGIC_AND, P_LOGIC_OR, P_LOGIC_GEQ, P_LOGIC_LEQ, P_LOGIC_EQ, + P_LOGIC_UNEQ, P_MUL_ASSIGN, P_DIV_ASSIGN, P_MOD_ASSIGN, + P_ADD_ASSIGN, P_SUB_ASSIGN, P_INC, P_DEC, P_BIN_AND_ASSIGN, + P_BIN_OR_ASSIGN, P_BIN_XOR_ASSIGN, P_RSHIFT, P_LSHIFT, + P_SCOPE_RESOLUTION, P_MEMBER_SELECTION_OBJECT, + P_MEMBER_SELECTION_POINTER, P_POINTER_TO_MEMBER_OBJECT, + P_POINTER_TO_MEMBER_POINTER, P_MUL, P_DIV, P_MOD, P_ADD, P_SUB, + P_ASSIGN, P_BIN_AND, P_BIN_OR, P_BIN_XOR, P_BIN_NOT, P_LOGIC_NOT, + P_LOGIC_GREATER, P_LOGIC_LESS, P_COMMA, P_SEMICOLON, P_COLON, + P_QUESTIONMARK, P_PARENTHESESOPEN, P_PARENTHESESCLOSE, P_BRACEOPEN, + P_BRACECLOSE, P_SQBRACKETOPEN, P_SQBRACKETCLOSE, P_BACKSLASH, + P_PRECOMP, P_DOLLAR, P_APOSTROPHE, P_QUOTE, P_AT +}; + +enum lexerFlags_t : int { + LEXFL_NOERRORS = 0x1, + LEXFL_NOWARNINGS = 0x2, + LEXFL_NOFATALERRORS = 0x4, + LEXFL_VCSTYLEREPORTS = 0x8, + LEXFL_NOSTRINGCONCAT = 0x10, + LEXFL_NOSTRINGESCAPECHARS = 0x20, + LEXFL_NODOLLARPRECOMPILE = 0x40, + LEXFL_NOBASEINCLUDES = 0x80, + LEXFL_NOGLOBALDEFINES = 0x100, + LEXFL_ALLOWPATHNAMES = 0x200, + LEXFL_ALLOWNUMBERNAMES = 0x400, + LEXFL_ALLOWIPADDRESSES = 0x800, + LEXFL_ALLOWFLOATEXCEPTIONS = 0x1000, + LEXFL_ALLOWMULTICHARLITERALS = 0x2000, + LEXFL_ALLOWBACKSLASHSTRINGCONCAT = 0x4000, + LEXFL_ONLYSTRINGS = 0x8000, + LEXFL_NOEMITSTRINGESCAPECHARS = 0x10000, + LEXFL_ALLOWRAWSTRINGBLOCKS = 0x20000, + LEXFL_THROWFATAL = 0x40000, + LEXFL_NOSTRINGS = 0x80000, + LEXFL_ALLOWWILDCARD = 0x100000, + LEXFL_REPORT_MULTIPLE_ERRORS = 0x200000 +}; + class alignas(4) idLexer { public: explicit idLexer(int flags = 0); diff --git a/source/shared/idlib/text/str.h b/source/shared/idlib/text/str.h index 128b0e7..edec56a 100644 --- a/source/shared/idlib/text/str.h +++ b/source/shared/idlib/text/str.h @@ -8,8 +8,8 @@ #include // Exact tungsten idStr storage layout (tungsten.exe.h type 12142). This is a -// deliberately small ABI facade; more recovered text methods will be added as -// text/str.cpp replaces the BFG baseline. +// deliberately small ABI facade; more authoritative text methods will be +// added as text/str.cpp is reconstructed. class idStr { public: idStr() @@ -81,6 +81,29 @@ public: } } + // Recovered from shared/idlib/text/str.cpp. Resource names are lowercase, + // use forward slashes, and discard leading slashes except for UNC names. + void MakeNameCanonical() { + for (int index = 0; index < len; ++index) { + if (data[index] >= 'A' && data[index] <= 'Z') { + data[index] = static_cast(data[index] + ('a' - 'A')); + } else if (data[index] == '\\') { + data[index] = '/'; + } + } + if (!(len >= 2 && data[0] == '/' && data[1] == '/')) { + int leading = 0; + while (leading < len && data[leading] == '/') { + ++leading; + } + if (leading > 0) { + std::memmove(data, data + leading, + static_cast(len - leading + 1)); + len -= leading; + } + } + } + void Append(const char* text) { if (text == nullptr || *text == '\0') { return; @@ -105,6 +128,30 @@ public: Append(text.c_str()); } + idStr& StripFileExtension() { + for (int index = len - 1; index >= 0; --index) { + const char value = data[index]; + if (value == '\\' || value == '/') { + break; + } + if (value == '.') { + data[index] = '\0'; + len = index; + break; + } + } + return *this; + } + + idStr& SetFileExtension(const char* extension) { + StripFileExtension(); + if (extension != nullptr && extension[0] != '.') { + Append('.'); + } + Append(extension); + return *this; + } + void Format(const char* format, ...) { char buffer[4096]; va_list arguments;