From 600bf0351476a5a21aabb5429132ddf7f52ac0b9 Mon Sep 17 00:00:00 2001 From: aap Date: Wed, 15 May 2019 16:52:37 +0200 Subject: [PATCH] first commit --- README.md | 101 ++ premake5.cmd | 1 + premake5.exe | Bin 0 -> 1156608 bytes premake5.lua | 38 + src/Camera.cpp | 63 ++ src/Camera.h | 420 +++++++ src/Clock.cpp | 116 ++ src/Clock.h | 27 + src/Collision.cpp | 1629 ++++++++++++++++++++++++++++ src/Collision.h | 152 +++ src/CullZones.cpp | 323 ++++++ src/CullZones.h | 93 ++ src/Game.cpp | 5 + src/Game.h | 15 + src/General.h | 15 + src/Glass.cpp | 15 + src/Glass.h | 10 + src/Lists.cpp | 26 + src/Lists.h | 130 +++ src/MenuManager.cpp | 4 + src/MenuManager.h | 5 + src/NodeName.cpp | 15 + src/NodeName.h | 3 + src/Pad.cpp | 37 + src/Pad.h | 116 ++ src/ParticleObject.cpp | 5 + src/ParticleObject.h | 31 + src/PathFind.cpp | 591 ++++++++++ src/PathFind.h | 130 +++ src/Placeable.cpp | 81 ++ src/Placeable.h | 26 + src/Pools.cpp | 19 + src/Pools.h | 34 + src/References.cpp | 22 + src/References.h | 17 + src/RwHelper.cpp | 19 + src/RwHelper.h | 3 + src/Streaming.cpp | 10 + src/Streaming.h | 54 + src/SurfaceTable.cpp | 44 + src/SurfaceTable.h | 99 ++ src/Timecycle.cpp | 92 ++ src/Timecycle.h | 111 ++ src/Timer.cpp | 14 + src/Timer.h | 19 + src/TxdStore.cpp | 158 +++ src/TxdStore.h | 34 + src/Weather.cpp | 27 + src/Weather.h | 35 + src/World.cpp | 39 + src/World.h | 66 ++ src/Zones.cpp | 614 +++++++++++ src/Zones.h | 107 ++ src/common.h | 97 ++ src/config.h | 56 + src/debugmenu_public.h | 154 +++ src/entities/Building.cpp | 7 + src/entities/Building.h | 15 + src/entities/CutsceneHead.cpp | 2 + src/entities/CutsceneHead.h | 10 + src/entities/CutsceneObject.cpp | 2 + src/entities/CutsceneObject.h | 9 + src/entities/Entity.cpp | 391 +++++++ src/entities/Entity.h | 146 +++ src/entities/Object.cpp | 9 + src/entities/Object.h | 50 + src/entities/Ped.h | 35 + src/entities/Physical.cpp | 916 ++++++++++++++++ src/entities/Physical.h | 137 +++ src/entities/Treadable.cpp | 7 + src/entities/Treadable.h | 16 + src/entities/Vehicle.h | 21 + src/main.cpp | 98 ++ src/math/Matrix.h | 245 +++++ src/math/Rect.h | 31 + src/math/Vector.h | 82 ++ src/math/Vector2D.h | 37 + src/modelinfo/BaseModelInfo.cpp | 117 ++ src/modelinfo/BaseModelInfo.h | 66 ++ src/modelinfo/ClumpModelInfo.cpp | 156 +++ src/modelinfo/ClumpModelInfo.h | 60 + src/modelinfo/ModelIndices.cpp | 32 + src/modelinfo/ModelIndices.h | 224 ++++ src/modelinfo/ModelInfo.cpp | 124 +++ src/modelinfo/ModelInfo.h | 35 + src/modelinfo/PedModelInfo.cpp | 197 ++++ src/modelinfo/PedModelInfo.h | 47 + src/modelinfo/SimpleModelInfo.cpp | 174 +++ src/modelinfo/SimpleModelInfo.h | 57 + src/modelinfo/TimeModelInfo.cpp | 36 + src/modelinfo/TimeModelInfo.h | 18 + src/modelinfo/VehicleModelInfo.cpp | 917 ++++++++++++++++ src/modelinfo/VehicleModelInfo.h | 115 ++ src/patcher.cpp | 22 + src/patcher.h | 171 +++ src/render/2dEffect.h | 39 + src/render/Clouds.cpp | 430 ++++++++ src/render/Clouds.h | 20 + src/render/Coronas.cpp | 10 + src/render/Coronas.h | 13 + src/render/Draw.cpp | 6 + src/render/Draw.h | 16 + src/render/Lights.cpp | 171 +++ src/render/Lights.h | 9 + src/render/Particle.cpp | 10 + src/render/Particle.h | 82 ++ src/render/RenderBuffer.cpp | 59 + src/render/RenderBuffer.h | 10 + src/render/Renderer.cpp | 1165 ++++++++++++++++++++ src/render/Renderer.h | 59 + src/render/Sprite.cpp | 553 ++++++++++ src/render/Sprite.h | 37 + src/render/VisibilityPlugins.cpp | 849 +++++++++++++++ src/render/VisibilityPlugins.h | 129 +++ src/rw.cpp | 285 +++++ src/templates.h | 179 +++ 116 files changed, 15132 insertions(+) create mode 100644 README.md create mode 100644 premake5.cmd create mode 100644 premake5.exe create mode 100644 premake5.lua create mode 100644 src/Camera.cpp create mode 100644 src/Camera.h create mode 100644 src/Clock.cpp create mode 100644 src/Clock.h create mode 100644 src/Collision.cpp create mode 100644 src/Collision.h create mode 100644 src/CullZones.cpp create mode 100644 src/CullZones.h create mode 100644 src/Game.cpp create mode 100644 src/Game.h create mode 100644 src/General.h create mode 100644 src/Glass.cpp create mode 100644 src/Glass.h create mode 100644 src/Lists.cpp create mode 100644 src/Lists.h create mode 100644 src/MenuManager.cpp create mode 100644 src/MenuManager.h create mode 100644 src/NodeName.cpp create mode 100644 src/NodeName.h create mode 100644 src/Pad.cpp create mode 100644 src/Pad.h create mode 100644 src/ParticleObject.cpp create mode 100644 src/ParticleObject.h create mode 100644 src/PathFind.cpp create mode 100644 src/PathFind.h create mode 100644 src/Placeable.cpp create mode 100644 src/Placeable.h create mode 100644 src/Pools.cpp create mode 100644 src/Pools.h create mode 100644 src/References.cpp create mode 100644 src/References.h create mode 100644 src/RwHelper.cpp create mode 100644 src/RwHelper.h create mode 100644 src/Streaming.cpp create mode 100644 src/Streaming.h create mode 100644 src/SurfaceTable.cpp create mode 100644 src/SurfaceTable.h create mode 100644 src/Timecycle.cpp create mode 100644 src/Timecycle.h create mode 100644 src/Timer.cpp create mode 100644 src/Timer.h create mode 100644 src/TxdStore.cpp create mode 100644 src/TxdStore.h create mode 100644 src/Weather.cpp create mode 100644 src/Weather.h create mode 100644 src/World.cpp create mode 100644 src/World.h create mode 100644 src/Zones.cpp create mode 100644 src/Zones.h create mode 100644 src/common.h create mode 100644 src/config.h create mode 100644 src/debugmenu_public.h create mode 100644 src/entities/Building.cpp create mode 100644 src/entities/Building.h create mode 100644 src/entities/CutsceneHead.cpp create mode 100644 src/entities/CutsceneHead.h create mode 100644 src/entities/CutsceneObject.cpp create mode 100644 src/entities/CutsceneObject.h create mode 100644 src/entities/Entity.cpp create mode 100644 src/entities/Entity.h create mode 100644 src/entities/Object.cpp create mode 100644 src/entities/Object.h create mode 100644 src/entities/Ped.h create mode 100644 src/entities/Physical.cpp create mode 100644 src/entities/Physical.h create mode 100644 src/entities/Treadable.cpp create mode 100644 src/entities/Treadable.h create mode 100644 src/entities/Vehicle.h create mode 100644 src/main.cpp create mode 100644 src/math/Matrix.h create mode 100644 src/math/Rect.h create mode 100644 src/math/Vector.h create mode 100644 src/math/Vector2D.h create mode 100644 src/modelinfo/BaseModelInfo.cpp create mode 100644 src/modelinfo/BaseModelInfo.h create mode 100644 src/modelinfo/ClumpModelInfo.cpp create mode 100644 src/modelinfo/ClumpModelInfo.h create mode 100644 src/modelinfo/ModelIndices.cpp create mode 100644 src/modelinfo/ModelIndices.h create mode 100644 src/modelinfo/ModelInfo.cpp create mode 100644 src/modelinfo/ModelInfo.h create mode 100644 src/modelinfo/PedModelInfo.cpp create mode 100644 src/modelinfo/PedModelInfo.h create mode 100644 src/modelinfo/SimpleModelInfo.cpp create mode 100644 src/modelinfo/SimpleModelInfo.h create mode 100644 src/modelinfo/TimeModelInfo.cpp create mode 100644 src/modelinfo/TimeModelInfo.h create mode 100644 src/modelinfo/VehicleModelInfo.cpp create mode 100644 src/modelinfo/VehicleModelInfo.h create mode 100644 src/patcher.cpp create mode 100644 src/patcher.h create mode 100644 src/render/2dEffect.h create mode 100644 src/render/Clouds.cpp create mode 100644 src/render/Clouds.h create mode 100644 src/render/Coronas.cpp create mode 100644 src/render/Coronas.h create mode 100644 src/render/Draw.cpp create mode 100644 src/render/Draw.h create mode 100644 src/render/Lights.cpp create mode 100644 src/render/Lights.h create mode 100644 src/render/Particle.cpp create mode 100644 src/render/Particle.h create mode 100644 src/render/RenderBuffer.cpp create mode 100644 src/render/RenderBuffer.h create mode 100644 src/render/Renderer.cpp create mode 100644 src/render/Renderer.h create mode 100644 src/render/Sprite.cpp create mode 100644 src/render/Sprite.h create mode 100644 src/render/VisibilityPlugins.cpp create mode 100644 src/render/VisibilityPlugins.h create mode 100644 src/rw.cpp create mode 100644 src/templates.h diff --git a/README.md b/README.md new file mode 100644 index 00000000..812b9c83 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# Intro + +The aim of this project is to reverse GTA III for PC by replacing +parts of the game [one by one](https://en.wikipedia.org/wiki/Ship_of_Theseus) +such that we have a working game at all times. + +# Strategy + +A good approach is to start at the fringes of the code base, +i.e. classes that don't depend on code that we don't have reversed yet. +If a function uses only few unreversed functions that would be inconvenient +to reverse at the time, calling the original functions is acceptable. + +# Progress + +This is a list of some things that have been reversed to some non-trivial extent. +Not everything is listed, check the code. + +``` +CPool +CTxdStore +CVector +CVector2D +CMatrix +CModelInfo +CBaseModelInfo +CSimpleModelInfo +CTimeModelInfo +CClumpModelInfo +CPedModelInfo +CVehicleModelInfo +CVisibilityPlugins +CRenderer +CEntity +CPhysical +CCollision +CCullZones +CTheZones +CPathFind +``` + +# Low hanging fruit + +There are a couple of things that have been reversed for other projects +already that could probably be put into this project without too much effort. +Again, the list is not complete: + +* Animation (https://github.com/aap/iii_anim) +* File Loader (https://github.com/aap/librwgta/tree/master/tools/IIItest) +* ... + +# Coding style + +I started writing in [Plan 9 style](http://man.cat-v.org/plan_9/6/style), +but realize that this is not the most popular style, so I'm willing to compromise. +Try not to deviate too much so the code will look similar across the whole project. + +To give examples, these two styles (or anything in between) are fine: + +``` +type +functionname(args) +{ + if(a == b){ + s1; + s2; + } + if(x != y) + s3; +} + +type functionname(args) +{ + if (a == b) { + s1; + s2; + } + if (x != y) + s3; +} +``` + +This one (or anything more extreme) is heavily discouraged: + +``` +type functionname ( args ) +{ + if ( a == b ) + { + s1; + s2; + } + if ( x != y ) + { + s3; + } +} +``` + +Indentation is done with TABS. + diff --git a/premake5.cmd b/premake5.cmd new file mode 100644 index 00000000..6d0726fa --- /dev/null +++ b/premake5.cmd @@ -0,0 +1 @@ +premake5 vs2015 \ No newline at end of file diff --git a/premake5.exe b/premake5.exe new file mode 100644 index 0000000000000000000000000000000000000000..a4bd472486aa22d0e20062dda6863e8ab35fa0bb GIT binary patch literal 1156608 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%0ApkNFxjL} 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 z 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 literal 0 HcmV?d00001 diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 00000000..f7e54d31 --- /dev/null +++ b/premake5.lua @@ -0,0 +1,38 @@ +workspace "re3" + configurations { "Release","Debug" } + location "build" + + files { "src/*.*" } + files { "src/math/*.*" } + files { "src/modelinfo/*.*" } + files { "src/entities/*.*" } + files { "src/render/*.*" } + + includedirs { "src", "src/modelinfo" } + includedirs { "src", "src/entities" } + includedirs { "src", "src/render" } + includedirs { os.getenv("RWSDK33") } + +project "re3" + kind "SharedLib" + language "C++" + targetname "re3" + targetdir "bin/%{cfg.buildcfg}" + targetextension ".dll" + characterset ("MBCS") + + filter "configurations:Debug" + defines { "DEBUG" } + flags { "StaticRuntime" } + symbols "On" + debugdir "C:/Users/aap/games/gta3_re" + debugcommand "C:/Users/aap/games/gta3_re/gta3.exe" + postbuildcommands "copy /y \"$(TargetPath)\" \"C:\\Users\\aap\\games\\gta3_re\\plugins\\re3.dll\"" + + filter "configurations:Release" + defines { "NDEBUG" } + optimize "On" + flags { "StaticRuntime" } + debugdir "C:/Users/aap/games/gta3_re" + debugcommand "C:/Users/aap/games/gta3_re/gta3.exe" + postbuildcommands "copy /y \"$(TargetPath)\" \"C:\\Users\\aap\\games\\gta3_re\\plugins\\re3.dll\"" diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 00000000..f4cea966 --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,63 @@ +#include "common.h" +#include "patcher.h" +#include "Draw.h" +#include "Camera.h" + +CCamera &TheCamera = *(CCamera*)0x6FACF8; + +bool +CCamera::IsSphereVisible(const CVector ¢er, float radius, const CMatrix *mat) +{ + RwV3d c; + c = *(RwV3d*)¢er; + RwV3dTransformPoints(&c, &c, 1, &mat->m_matrix); + if(c.y + radius < CDraw::GetNearClipZ()) return false; + if(c.y - radius > CDraw::GetFarClipZ()) return false; + if(c.x*m_vecFrustumNormals[0].x + c.y*m_vecFrustumNormals[0].y > radius) return false; + if(c.x*m_vecFrustumNormals[1].x + c.y*m_vecFrustumNormals[1].y > radius) return false; + if(c.y*m_vecFrustumNormals[2].y + c.z*m_vecFrustumNormals[2].z > radius) return false; + if(c.y*m_vecFrustumNormals[3].y + c.z*m_vecFrustumNormals[3].z > radius) return false; + return true; +} + +bool +CCamera::IsPointVisible(const CVector ¢er, const CMatrix *mat) +{ + RwV3d c; + c = *(RwV3d*)¢er; + RwV3dTransformPoints(&c, &c, 1, &mat->m_matrix); + if(c.y < CDraw::GetNearClipZ()) return false; + if(c.y > CDraw::GetFarClipZ()) return false; + if(c.x*m_vecFrustumNormals[0].x + c.y*m_vecFrustumNormals[0].y > 0.0f) return false; + if(c.x*m_vecFrustumNormals[1].x + c.y*m_vecFrustumNormals[1].y > 0.0f) return false; + if(c.y*m_vecFrustumNormals[2].y + c.z*m_vecFrustumNormals[2].z > 0.0f) return false; + if(c.y*m_vecFrustumNormals[3].y + c.z*m_vecFrustumNormals[3].z > 0.0f) return false; + return true; +} + +bool +CCamera::IsBoxVisible(RwV3d *box, const CMatrix *mat) +{ + int i; + int frustumTests[6] = { 0 }; + RwV3dTransformPoints(box, box, 8, &mat->m_matrix); + + for(i = 0; i < 8; i++){ + if(box[i].y < CDraw::GetNearClipZ()) frustumTests[0]++; + if(box[i].y > CDraw::GetFarClipZ()) frustumTests[1]++; + if(box[i].x*m_vecFrustumNormals[0].x + box[i].y*m_vecFrustumNormals[0].y > 0.0f) frustumTests[2]++; + if(box[i].x*m_vecFrustumNormals[1].x + box[i].y*m_vecFrustumNormals[1].y > 0.0f) frustumTests[3]++; +// Why not test z? +// if(box[i].y*m_vecFrustumNormals[2].y + box[i].z*m_vecFrustumNormals[2].z > 0.0f) frustumTests[4]++; +// if(box[i].y*m_vecFrustumNormals[3].y + box[i].z*m_vecFrustumNormals[3].z > 0.0f) frustumTests[5]++; + } + for(i = 0; i < 6; i++) + if(frustumTests[i] == 8) + return false; // Box is completely outside of one plane + return true; +} + + +STARTPATCHES + InjectHook(0x42C760, &CCamera::IsSphereVisible, PATCH_JUMP); +ENDPATCHES diff --git a/src/Camera.h b/src/Camera.h new file mode 100644 index 00000000..46396ec4 --- /dev/null +++ b/src/Camera.h @@ -0,0 +1,420 @@ +#pragma once + +#include "Placeable.h" + +class CEntity; +class CPed; +class CAutomobile; + +#define NUMBER_OF_VECTORS_FOR_AVERAGE 2 + +struct CCam +{ + enum CamMode + { + MODE_TOPDOWN1 = 1, + MODE_TOPDOWN2, + MODE_BEHINDCAR, + MODE_FOLLOWPED, + MODE_AIMING, + MODE_DEBUG, + MODE_SNIPER, + MODE_ROCKET, + MODE_MODELVIEW, + MODE_BILL, + MODE_SYPHON, + MODE_CIRCLE, + MODE_CHEESYZOOM, + MODE_WHEELCAM, + MODE_FIXED, + MODE_FIRSTPERSON, + MODE_FLYBY, + MODE_CAMONASTRING, + MODE_REACTIONCAM, + MODE_FOLLOWPEDWITHBINDING, + MODE_CHRISWITHBINDINGPLUSROTATION, + MODE_BEHINDBOAT, + MODE_PLAYERFALLENWATER, + MODE_CAMONTRAINROOF, + MODE_CAMRUNNINGSIDETRAIN, + MODE_BLOODONTHETRACKS, + MODE_IMTHEPASSENGERWOOWOO, + MODE_SYPHONCRIMINFRONT, + MODE_PEDSDEADBABY, + MODE_CUSHYPILLOWSARSE, + MODE_LOOKATCARS, + MODE_ARRESTCAMONE, + MODE_ARRESTCAMTWO, + MODE_M16FIRSTPERSON_34, + MODE_SPECIALFIXEDFORSYPHON, + MODE_FIGHT, + MODE_TOPDOWNPED, + MODE_FIRSTPERSONPEDONPC_38, + MODE_FIRSTPERSONPEDONPC_39, + MODE_FIRSTPERSONPEDONPC_40, + MODE_FIRSTPERSONPEDONPC_41, + MODE_FIRSTPERSONPEDONPC_42, + MODE_EDITOR, + MODE_M16FIRSTPERSON_44 + }; + + bool bBelowMinDist; //used for follow ped mode + bool bBehindPlayerDesired; //used for follow ped mode + bool m_bCamLookingAtVector; + bool m_bCollisionChecksOn; + bool m_bFixingBeta; //used for camera on a string + bool m_bTheHeightFixerVehicleIsATrain; + bool LookBehindCamWasInFront; + bool LookingBehind; + bool LookingLeft; // 32 + bool LookingRight; + bool ResetStatics; //for interpolation type stuff to work + bool Rotating; + + int16 Mode; // CameraMode + uint32 m_uiFinishTime; // 52 + + int m_iDoCollisionChecksOnFrameNum; + int m_iDoCollisionCheckEveryNumOfFrames; + int m_iFrameNumWereAt; // 64 + int m_iRunningVectorArrayPos; + int m_iRunningVectorCounter; + int DirectionWasLooking; + + float f_max_role_angle; //=DEGTORAD(5.0f); + float f_Roll; //used for adding a slight roll to the camera in the + float f_rollSpeed; + float m_fSyphonModeTargetZOffSet; +float m_unknownZOffset; + float m_fAmountFractionObscured; + float m_fAlphaSpeedOverOneFrame; // 100 + float m_fBetaSpeedOverOneFrame; + float m_fBufferedTargetBeta; + float m_fBufferedTargetOrientation; + float m_fBufferedTargetOrientationSpeed; + float m_fCamBufferedHeight; + float m_fCamBufferedHeightSpeed; + float m_fCloseInPedHeightOffset; + float m_fCloseInPedHeightOffsetSpeed; // 132 + float m_fCloseInCarHeightOffset; + float m_fCloseInCarHeightOffsetSpeed; + float m_fDimensionOfHighestNearCar; + float m_fDistanceBeforeChanges; + float m_fFovSpeedOverOneFrame; + float m_fMinDistAwayFromCamWhenInterPolating; + float m_fPedBetweenCameraHeightOffset; + float m_fPlayerInFrontSyphonAngleOffSet; // 164 + float m_fRadiusForDead; + float m_fRealGroundDist; //used for follow ped mode + float m_fTargetBeta; + float m_fTimeElapsedFloat; + + float m_fTransitionBeta; + float m_fTrueBeta; + float m_fTrueAlpha; // 200 + float m_fInitialPlayerOrientation; //used for first person + + float Alpha; + float AlphaSpeed; + float FOV; + float FOVSpeed; + float Beta; + float BetaSpeed; + float Distance; // 232 + float DistanceSpeed; + float CA_MIN_DISTANCE; + float CA_MAX_DISTANCE; + float SpeedVar; + + // ped onfoot zoom distance + float m_fTargetZoomGroundOne; + float m_fTargetZoomGroundTwo; // 256 + float m_fTargetZoomGroundThree; + // ped onfoot alpha angle offset + float m_fTargetZoomOneZExtra; + float m_fTargetZoomTwoZExtra; + float m_fTargetZoomThreeZExtra; + + float m_fTargetZoomZCloseIn; + float m_fMinRealGroundDist; + float m_fTargetCloseInDist; + + CVector m_cvecTargetCoorsForFudgeInter; // 360 + CVector m_cvecCamFixedModeVector; // 372 + CVector m_cvecCamFixedModeSource; // 384 + CVector m_cvecCamFixedModeUpOffSet; // 396 + CVector m_vecLastAboveWaterCamPosition; //408 //helper for when the player has gone under the water + CVector m_vecBufferedPlayerBodyOffset; // 420 + + // The three vectors that determine this camera for this frame + CVector Front; // 432 // Direction of looking in + CVector Source; // Coors in world space + CVector SourceBeforeLookBehind; + CVector Up; // Just that + CVector m_arrPreviousVectors[NUMBER_OF_VECTORS_FOR_AVERAGE]; // used to average stuff + CEntity *CamTargetEntity; + + float m_fCameraDistance; + float m_fIdealAlpha; + float m_fPlayerVelocity; + CAutomobile *m_pLastCarEntered; // So interpolation works + CPed *m_pLastPedLookedAt;// So interpolation works + bool m_bFirstPersonRunAboutActive; + + + void Process_Debug(float *vec, float a, float b, float c); + void Process_Kalvin(float*, float, float, float); + void GetVectorsReadyForRW(void); +}; +static_assert(sizeof(CCam) == 0x1A4, "CCam: wrong size"); +static_assert(offsetof(CCam, Alpha) == 0xA8, "CCam: error"); +static_assert(offsetof(CCam, Front) == 0x140, "CCam: error"); + +struct CCamPathSplines +{ + float m_arr_PathData[800]; +}; + +struct CTrainCamNode +{ + CVector m_cvecCamPosition; + CVector m_cvecPointToLookAt; + CVector m_cvecMinPointInRange; + CVector m_cvecMaxPointInRange; + float m_fDesiredFOV; + float m_fNearClip; +}; + +struct CQueuedMode +{ + int16 Mode; + float Duration; + int16 MinZoom; + int16 MaxZoom; +}; + +enum +{ + LOOKING_BEHIND, + LOOKING_LEFT, + LOOKING_RIGHT, + LOOKING_FORWARD, +}; + +struct CCamera : public CPlaceable +{ + bool m_bAboveGroundTrainNodesLoaded; + bool m_bBelowGroundTrainNodesLoaded; + bool m_bCamDirectlyBehind; + bool m_bCamDirectlyInFront; + bool m_bCameraJustRestored; + bool m_bcutsceneFinished; + bool m_bCullZoneChecksOn; + bool m_bFirstPersonBeingUsed; + bool m_bJustJumpedOutOf1stPersonBecauseOfTarget; + bool m_bIdleOn; + bool m_bInATunnelAndABigVehicle; + bool m_bInitialNodeFound; + bool m_bInitialNoNodeStaticsSet; + bool m_bIgnoreFadingStuffForMusic; + bool m_bPlayerIsInGarage; + bool m_bJustCameOutOfGarage; + bool m_bJustInitalised; + bool m_bJust_Switched; + bool m_bLookingAtPlayer; + bool m_bLookingAtVector; + bool m_bMoveCamToAvoidGeom; + bool m_bObbeCinematicPedCamOn; + bool m_bObbeCinematicCarCamOn; + bool m_bRestoreByJumpCut; + bool m_bUseNearClipScript; + bool m_bStartInterScript; + bool m_bStartingSpline; + bool m_bTargetJustBeenOnTrain; + bool m_bTargetJustCameOffTrain; + bool m_bUseSpecialFovTrain; + bool m_bUseTransitionBeta; + bool m_bUseScriptZoomValuePed; + bool m_bUseScriptZoomValueCar; + bool m_bWaitForInterpolToFinish; + bool m_bItsOkToLookJustAtThePlayer; + bool m_bWantsToSwitchWidescreenOff; + bool m_WideScreenOn; + bool m_1rstPersonRunCloseToAWall; + bool m_bHeadBob; + bool m_bFailedCullZoneTestPreviously; + +bool m_FadeTargetIsSplashScreen; + + bool WorldViewerBeingUsed; + uint8 ActiveCam; + uint32 m_uiCamShakeStart; + uint32 m_uiFirstPersonCamLastInputTime; +// where are those? +//bool m_bVehicleSuspenHigh; +//bool m_bEnable1rstPersonCamCntrlsScript; +//bool m_bAllow1rstPersonWeaponsCamera; + + uint32 m_uiLongestTimeInMill; + uint32 m_uiNumberOfTrainCamNodes; + uint8 m_uiTransitionJUSTStarted; + uint8 m_uiTransitionState; // 0:one mode 1:transition + + uint32 m_uiTimeLastChange; + uint32 m_uiTimeWeEnteredIdle; + uint32 m_uiTimeTransitionStart; + uint32 m_uiTransitionDuration; + int m_BlurBlue; + int m_BlurGreen; + int m_BlurRed; + int m_BlurType; + +uint32 unknown; + int m_iWorkOutSpeedThisNumFrames; + int m_iNumFramesSoFar; + + + int m_iCurrentTrainCamNode; + int m_motionBlur; + int m_imotionBlurAddAlpha; + int m_iCheckCullZoneThisNumFrames; + int m_iZoneCullFrameNumWereAt; + int WhoIsInControlOfTheCamera; + + float CamFrontXNorm; + float CamFrontYNorm; + float CarZoomIndicator; + float CarZoomValue; + float CarZoomValueSmooth; + + float DistanceToWater; + float FOVDuringInter; + float LODDistMultiplier; + float GenerationDistMultiplier; + float m_fAlphaSpeedAtStartInter; + float m_fAlphaWhenInterPol; + float m_fAlphaDuringInterPol; + float m_fBetaDuringInterPol; + float m_fBetaSpeedAtStartInter; + float m_fBetaWhenInterPol; + float m_fFOVWhenInterPol; + float m_fFOVSpeedAtStartInter; + float m_fStartingBetaForInterPol; + float m_fStartingAlphaForInterPol; + float m_PedOrientForBehindOrInFront; + float m_CameraAverageSpeed; + float m_CameraSpeedSoFar; + float m_fCamShakeForce; + float m_fCarZoomValueScript; + float m_fFovForTrain; + float m_fFOV_Wide_Screen; + float m_fNearClipScript; + float m_fOldBetaDiff; + float m_fPedZoomValue; + + float m_fPedZoomValueScript; + float m_fPedZoomValueSmooth; + float m_fPositionAlongSpline; + float m_ScreenReductionPercentage; + float m_ScreenReductionSpeed; + float m_AlphaForPlayerAnim1rstPerson; + float Orientation; + float PedZoomIndicator; + float PlayerExhaustion; + float SoundDistUp, SoundDistLeft, SoundDistRight; + float SoundDistUpAsRead, SoundDistLeftAsRead, SoundDistRightAsRead; + float SoundDistUpAsReadOld, SoundDistLeftAsReadOld, SoundDistRightAsReadOld; + float m_fWideScreenReductionAmount; + float m_fStartingFOVForInterPol; + + // not static yet + float m_fMouseAccelHorzntl;// acceleration multiplier for 1st person controls + float m_fMouseAccelVertical;// acceleration multiplier for 1st person controls + float m_f3rdPersonCHairMultX; + float m_f3rdPersonCHairMultY; + + + CCam Cams[3]; + void *pToGarageWeAreIn; + void *pToGarageWeAreInForHackAvoidFirstPerson; + CQueuedMode m_PlayerMode; + CQueuedMode PlayerWeaponMode; + CVector m_PreviousCameraPosition; + CVector m_RealPreviousCameraPosition; + CVector m_cvecAimingTargetCoors; + CVector m_vecFixedModeVector; + + // one of those has to go + CVector m_vecFixedModeSource; + CVector m_vecFixedModeUpOffSet; +// CVector m_vecCutSceneOffset; + CVector m_cvecStartingSourceForInterPol; + CVector m_cvecStartingTargetForInterPol; + CVector m_cvecStartingUpForInterPol; + CVector m_cvecSourceSpeedAtStartInter; + CVector m_cvecTargetSpeedAtStartInter; + CVector m_cvecUpSpeedAtStartInter; + CVector m_vecSourceWhenInterPol; + CVector m_vecTargetWhenInterPol; + CVector m_vecUpWhenInterPol; + CVector m_vecClearGeometryVec; + + CVector m_vecGameCamPos; + CVector SourceDuringInter; + CVector TargetDuringInter; + CVector UpDuringInter; + RwCamera *m_pRwCamera; + CEntity *pTargetEntity; + CCamPathSplines m_arrPathArray[4]; + CTrainCamNode m_arrTrainCamNode[800]; + CMatrix m_cameraMatrix; + bool m_bGarageFixedCamPositionSet; + bool m_vecDoingSpecialInterPolation; + bool m_bScriptParametersSetForInterPol; + bool m_bFading; + bool m_bMusicFading; + CMatrix m_viewMatrix; + CVector m_vecFrustumNormals[4]; + CVector m_vecOldSourceForInter; + CVector m_vecOldFrontForInter; + CVector m_vecOldUpForInter; + + float m_vecOldFOVForInter; + float m_fFLOATingFade; + float m_fFLOATingFadeMusic; + float m_fTimeToFadeOut; + float m_fTimeToFadeMusic; + float m_fFractionInterToStopMovingTarget; + float m_fFractionInterToStopCatchUpTarget; + float m_fGaitSwayBuffer; + float m_fScriptPercentageInterToStopMoving; + float m_fScriptPercentageInterToCatchUp; + +uint32 m_fScriptTimeForInterPolation; + + +int16 m_iFadingDirection; +int m_iModeObbeCamIsInForCar; + int16 m_iModeToGoTo; + int16 m_iMusicFadingDirection; + int16 m_iTypeOfSwitch; + + uint32 m_uiFadeTimeStarted; + uint32 m_uiFadeTimeStartedMusic; + + + CMatrix &GetCameraMatrix(void) { return m_cameraMatrix; } + CVector &GetGameCamPosition(void) { return m_vecGameCamPos; } + bool IsPointVisible(const CVector ¢er, const CMatrix *mat); + bool IsSphereVisible(const CVector ¢er, float radius, const CMatrix *mat); + bool IsBoxVisible(RwV3d *box, const CMatrix *mat); +}; +static_assert(offsetof(CCamera, m_WideScreenOn) == 0x70, "CCamera: error"); +static_assert(offsetof(CCamera, WorldViewerBeingUsed) == 0x75, "CCamera: error"); +static_assert(offsetof(CCamera, m_uiNumberOfTrainCamNodes) == 0x84, "CCamera: error"); +static_assert(offsetof(CCamera, m_uiTransitionState) == 0x89, "CCamera: error"); +static_assert(offsetof(CCamera, m_uiTimeTransitionStart) == 0x94, "CCamera: error"); +static_assert(offsetof(CCamera, m_BlurBlue) == 0x9C, "CCamera: error"); +static_assert(offsetof(CCamera, Cams) == 0x1A4, "CCamera: error"); +static_assert(sizeof(CCamera) == 0xE9D8, "CCamera: wrong size"); +extern CCamera &TheCamera; diff --git a/src/Clock.cpp b/src/Clock.cpp new file mode 100644 index 00000000..dc8e1599 --- /dev/null +++ b/src/Clock.cpp @@ -0,0 +1,116 @@ +#include "common.h" +#include "patcher.h" +#include "Timer.h" +#include "Pad.h" +#include "Clock.h" + +uint8 &CClock::ms_nGameClockHours = *(uint8*)0x95CDA6; +uint8 &CClock::ms_nGameClockMinutes = *(uint8*)0x95CDC8; +uint16 &CClock::ms_nGameClockSeconds = *(uint16*)0x95CC7C; +uint8 &CClock::ms_Stored_nGameClockHours = *(uint8*)0x95CD7B; +uint8 &CClock::ms_Stored_nGameClockMinutes = *(uint8*)0x95CD9B; +uint16 &CClock::ms_Stored_nGameClockSeconds = *(uint16*)0x95CC9C; +uint32 &CClock::ms_nMillisecondsPerGameMinute = *(uint32*)0x8F2C64; +int32 &CClock::ms_nLastClockTick = *(int32*)0x9430E4; +bool &CClock::ms_bClockHasBeenStored = *(bool*)0x95CD82; + +void +CClock::Initialise(uint32 scale) +{ + debug("Initialising CClock...\n"); + ms_nGameClockHours = 12; + ms_nGameClockMinutes = 0; + ms_nGameClockSeconds = 0; + ms_nMillisecondsPerGameMinute = scale; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + ms_bClockHasBeenStored = false; + debug("CClock ready\n"); +} + +void +CClock::Update(void) +{ + if(CPad::GetPad(1)->NewState.r1){ + ms_nGameClockMinutes += 8; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + if(ms_nGameClockMinutes >= 60){ + ms_nGameClockHours++; + ms_nGameClockMinutes = 0; + if(ms_nGameClockHours >= 24) + ms_nGameClockHours = 0; + } + }else + if(CTimer::GetTimeInMilliseconds() - ms_nLastClockTick > + ms_nMillisecondsPerGameMinute){ + ms_nGameClockMinutes++; + ms_nLastClockTick += ms_nMillisecondsPerGameMinute; + if(ms_nGameClockMinutes >= 60){ + ms_nGameClockHours++; + ms_nGameClockMinutes = 0; + if(ms_nGameClockHours >= 24) + ms_nGameClockHours = 0; + // TODO: stats days passed + } + } + ms_nGameClockSeconds += + 60 + * (CTimer::GetTimeInMilliseconds() - ms_nLastClockTick) + / ms_nMillisecondsPerGameMinute; +} + +void +CClock::SetGameClock(uint8 h, uint8 m) +{ + ms_nGameClockHours = h; + ms_nGameClockMinutes = m; + ms_nGameClockSeconds = 0; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); +} + +int32 +CClock::GetGameClockMinutesUntil(uint8 h, uint8 m) +{ + int32 now, then; + now = ms_nGameClockHours*60 + ms_nGameClockMinutes; + then = h*60 + m; + if(then < now) + then += 24*60; + return then-now; +} + +bool +CClock::GetIsTimeInRange(uint8 h1, uint8 h2) +{ + if(h1 > h2) + return ms_nGameClockHours >= h1 || ms_nGameClockHours < h2; + else + return ms_nGameClockHours >= h1 && ms_nGameClockHours < h2; +} + +void +CClock::StoreClock(void) +{ + ms_Stored_nGameClockHours = ms_nGameClockHours; + ms_Stored_nGameClockMinutes = ms_nGameClockMinutes; + ms_Stored_nGameClockSeconds = ms_nGameClockSeconds; + ms_bClockHasBeenStored = true; +} + +void +CClock::RestoreClock(void) +{ + ms_nGameClockHours = ms_Stored_nGameClockHours; + ms_nGameClockMinutes = ms_Stored_nGameClockMinutes; + ms_nGameClockSeconds = ms_Stored_nGameClockSeconds; +} + + +STARTPATCHES + InjectHook(0x473370, CClock::Initialise, PATCH_JUMP); + InjectHook(0x473460, CClock::Update, PATCH_JUMP); + InjectHook(0x4733C0, CClock::SetGameClock, PATCH_JUMP); + InjectHook(0x4733F0, CClock::GetGameClockMinutesUntil, PATCH_JUMP); + InjectHook(0x473420, CClock::GetIsTimeInRange, PATCH_JUMP); + InjectHook(0x473540, CClock::StoreClock, PATCH_JUMP); + InjectHook(0x473570, CClock::RestoreClock, PATCH_JUMP); +ENDPATCHES diff --git a/src/Clock.h b/src/Clock.h new file mode 100644 index 00000000..0b6ba304 --- /dev/null +++ b/src/Clock.h @@ -0,0 +1,27 @@ +#pragma once + +class CClock +{ + static uint8 &ms_nGameClockHours; + static uint8 &ms_nGameClockMinutes; + static uint16 &ms_nGameClockSeconds; + static uint8 &ms_Stored_nGameClockHours; + static uint8 &ms_Stored_nGameClockMinutes; + static uint16 &ms_Stored_nGameClockSeconds; + static uint32 &ms_nMillisecondsPerGameMinute; + static int32 &ms_nLastClockTick; + static bool &ms_bClockHasBeenStored; +public: + + static void Initialise(uint32 scale); + static void Update(void); + static void SetGameClock(uint8 h, uint8 m); + static int32 GetGameClockMinutesUntil(uint8 h, uint8 m); + static bool GetIsTimeInRange(uint8 h1, uint8 h2); + static void StoreClock(void); + static void RestoreClock(void); + + static int8 GetHours(void) { return ms_nGameClockHours; } + static int8 GetMinutes(void) { return ms_nGameClockMinutes; } + static int16 GetSeconds(void) { return ms_nGameClockSeconds; } +}; diff --git a/src/Collision.cpp b/src/Collision.cpp new file mode 100644 index 00000000..14b3adcd --- /dev/null +++ b/src/Collision.cpp @@ -0,0 +1,1629 @@ +#include "common.h" +#include "patcher.h" +#include "Game.h" +#include "General.h" +#include "RenderBuffer.h" +#include "SurfaceTable.h" +#include "Collision.h" + +enum Direction +{ + DIR_X_POS, + DIR_X_NEG, + DIR_Y_POS, + DIR_Y_NEG, + DIR_Z_POS, + DIR_Z_NEG, +}; + +eLevelName &CCollision::ms_collisionInMemory = *(eLevelName*)0x8F6250; +CLinkList &CCollision::ms_colModelCache = *(CLinkList*)0x95CB58; + +#if 0 + +void +CCollision::Init(void) +{ + ms_colModelCache.Init(NUMCOLCACHELINKS); + ms_collisionInMemory = LEVEL_NONE; +} + +void +CCollision::Update(void) +{ + CVector pos = FindPlayerCoors(); + eLevelName level = CTheZones::m_CurrLevel; + bool changeLevel = false; + + // hardcode a level if there are no zones + if(level == LEVEL_NONE){ + if(CGame::currLevel == LEVEL_INDUSTRIAL && + pos.x < 400.0f){ + level = LEVEL_COMMERCIAL; + changeLevel = true; + }else if(CGame::currLevel == LEVEL_SUBURBAN && + pos.x > -450.0f && pos.y < -1400.0f){ + level = LEVEL_COMMERCIAL; + changeLevel = true; + }else{ + if(pos.x > 800.0f){ + level = LEVEL_INDUSTRIAL; + changeLevel = true; + }else if(pos.x < -800.0f){ + level = LEVEL_SUBURBAN; + changeLevel = true; + } + } + } + if(level != LEVEL_NONE && level != CGame::currLevel){ + debug("changing level %d -> %d\n", CGame::currLevel, level); + CGame::currLevel = level; + } + if(ms_collisionInMemory != CGame::currLevel) + LoadCollisionWhenINeedIt(changeLevel); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); +} + +void +CCollision::LoadCollisionWhenINeedIt(bool changeLevel) +{ + eLevelName level; + level = LEVEL_NONE; + if(!changeLevel){ + //assert(0 && "unimplemented"); + } + + if(level != CGame::currLevel || changeLevel){ + CTimer::Stop(); + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + ms_collisionInMemory = CGame::currLevel; + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + CTimer::Update(); + } +} + +#endif + +// +// Test +// + + +bool +CCollision::TestSphereSphere(const CColSphere &s1, const CColSphere &s2) +{ + float d = s1.radius + s2.radius; + return (s1.center - s2.center).MagnitudeSqr() < d*d; +} + +bool +CCollision::TestSphereBox(const CColSphere &sph, const CColBox &box) +{ + if(sph.center.x + sph.radius < box.min.x) return false; + if(sph.center.x - sph.radius > box.max.x) return false; + if(sph.center.y + sph.radius < box.min.y) return false; + if(sph.center.y - sph.radius > box.max.y) return false; + if(sph.center.z + sph.radius < box.min.z) return false; + if(sph.center.z - sph.radius > box.max.z) return false; + return true; +} + +bool +CCollision::TestLineBox(const CColLine &line, const CColBox &box) +{ + float t, x, y, z; + // If either line point is in the box, we have a collision + if(line.p0.x > box.min.x && line.p0.x < box.max.x && + line.p0.y > box.min.y && line.p0.y < box.max.y && + line.p0.z > box.min.z && line.p0.z < box.max.z) + return true; + if(line.p1.x > box.min.x && line.p1.x < box.max.x && + line.p1.y > box.min.y && line.p1.y < box.max.y && + line.p1.z > box.min.z && line.p1.z < box.max.z) + return true; + + // check if points are on opposite sides of min x plane + if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){ + // parameter along line where we intersect + t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x); + // y of intersection + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + // z of intersection + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // same test with max x plane + if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){ + t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x); + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // min y plne + if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){ + t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // max y plane + if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){ + t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // min z plne + if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){ + t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + return true; + } + } + + // max z plane + if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){ + t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + return true; + } + } + return false; +} + +bool +CCollision::TestVerticalLineBox(const CColLine &line, const CColBox &box) +{ + if(line.p0.x <= box.min.x) return false; + if(line.p0.y <= box.min.y) return false; + if(line.p0.x >= box.max.x) return false; + if(line.p0.y >= box.max.y) return false; + if(line.p0.z < line.p1.z){ + if(line.p0.z > box.max.z) return false; + if(line.p1.z < box.min.z) return false; + }else{ + if(line.p1.z > box.max.z) return false; + if(line.p0.z < box.min.z) return false; + } + return true; +} + +bool +CCollision::TestLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ + float t; + CVector normal; + plane.GetNormal(normal); + + // if points are on the same side, no collision + if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / DotProduct(line.p1 - line.p0, normal); + // find point of intersection + CVector p = line.p0 + (line.p1-line.p0)*t; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + CVector2D vec1, vec2, vec3, vect; + + // We do the test in 2D. With the plane direction we + // can figure out how to project the vectors. + // normal = (c-a) x (b-a) + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + // This is our triangle: + // 3-------2 + // \ P / + // \ / + // \ / + // 1 + // We can use the "2d cross product" to check on which side + // a vector is of another. Test is true if point is inside of all edges. + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + return true; +} + +// Test if line segment intersects with sphere. +// If the first point is inside the sphere this test does not register a collision! +// The code is reversed from the original code and rather ugly, see Process for a clear version. +// TODO: actually rewrite this mess +bool +CCollision::TestLineSphere(const CColLine &line, const CColSphere &sph) +{ + CVector v01 = line.p1 - line.p0; // vector from p0 to p1 + CVector v0c = sph.center - line.p0; // vector from p0 to center + float linesq = v01.MagnitudeSqr(); + // I leave in the strange -2 factors even though they serve no real purpose + float projline = -2.0f * DotProduct(v01, v0c); // project v0c onto line + // Square of tangent from p0 multiplied by line length so we can compare with projline. + // The length of the tangent would be this: sqrt((c-p0)^2 - r^2). + // Negative if p0 is inside the sphere! This breaks the test! + float tansq = 4.0f * linesq * + (sph.center.MagnitudeSqr() - 2.0f*DotProduct(sph.center, line.p0) + line.p0.MagnitudeSqr() - sph.radius*sph.radius); + float diffsq = projline*projline - tansq; + // if diffsq < 0 that means the line is a passant, so no intersection + if(diffsq < 0.0f) + return false; + // projline (negative in GTA for some reason) is the point on the line + // in the middle of the two intersection points (startin from p0). + // sqrt(diffsq) somehow works out to be the distance from that + // midpoint to the intersection points. + // So subtract that and get rid of the awkward scaling: + float f = (-projline - sqrt(diffsq)) / (2.0f*linesq); + // f should now be in range [0, 1] for [p0, p1] + return f >= 0.0f && f <= 1.0f; +} + +bool +CCollision::TestSphereTriangle(const CColSphere &sphere, + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ + // If sphere and plane don't intersect, no collision + if(fabs(plane.CalcPoint(sphere.center)) > sphere.radius) + return false; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + + // calculate two orthogonal basis vectors for the triangle + CVector vec2 = vb - va; + float len = vec2.Magnitude(); + vec2 = vec2 * (1.0f/len); + CVector vec1 = CrossProduct(vec2, plane.normal); + + // We know A has local coordinate [0,0] and B has [0,len]. + // Now calculate coordinates on triangle for these two vectors: + CVector vac = vc - va; + CVector vas = sphere.center - va; + CVector2D b(0.0f, len); + CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac)); + CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas)); + + // The three triangle lines partition the space into 6 sectors, + // find out in which the center lies. + int insideAB = CrossProduct2D(s, b) >= 0.0f; + int insideAC = CrossProduct2D(c, s) >= 0.0f; + int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f; + + int testcase = insideAB + insideAC + insideBC; + float dist = 0.0f; + if(testcase == 1){ + // closest to a vertex + if(insideAB) dist = (sphere.center - vc).Magnitude(); + else if(insideAC) dist = (sphere.center - vb).Magnitude(); + else if(insideBC) dist = (sphere.center - va).Magnitude(); + else assert(0); + }else if(testcase == 2){ + // closest to an edge + if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center); + else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center); + else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center); + else assert(0); + }else if(testcase == 3){ + // center is in triangle + return true; + }else + assert(0); // front fell off + + return dist < sphere.radius; +} + +bool +CCollision::TestLineOfSight(CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSurf78) +{ + static CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CColLine newline(matTransform * line.p0, matTransform * line.p1); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(newline, model.boundingBox)) + return false; + + for(i = 0; i < model.numSpheres; i++) + if(!ignoreSurf78 || model.spheres[i].surface != 7 && model.spheres[i].surface != 8) + if(TestLineSphere(newline, model.spheres[i])) + return true; + + for(i = 0; i < model.numBoxes; i++) + if(!ignoreSurf78 || model.boxes[i].surface != 7 && model.boxes[i].surface != 8) + if(TestLineBox(newline, model.boxes[i])) + return true; + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++) + if(!ignoreSurf78 || model.triangles[i].surface != 7 && model.triangles[i].surface != 8) + if(TestLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i])) + return true; + + return false; +} + + +// +// Process +// + +// For Spheres mindist is the squared distance to its center +// For Lines mindist is between [0,1] + +bool +CCollision::ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq) +{ + CVector dist = s1.center - s2.center; + float d = dist.Magnitude() - s2.radius; // distance from s1's center to s2 + float dc = d < 0.0f ? 0.0f : d; // clamp to zero, i.e. if s1's center is inside s2 + // no collision if sphere is not close enough + if(mindistsq <= dc*dc || s1.radius <= dc) + return false; + dist.Normalise(); + point.point = s1.center - dist*dc; + point.normal = dist; + point.surfaceA = s1.surface; + point.pieceA = s1.piece; + point.surfaceB = s2.surface; + point.pieceB = s2.piece; + point.depth = s1.radius - d; // sphere overlap + mindistsq = dc*dc; // collision radius + return true; +} + +bool +CCollision::ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq) +{ + CVector p; + CVector dist; + + // GTA's code is too complicated, uses a huge 3x3x3 if statement + // we can simplify the structure a lot + + // first make sure we have a collision at all + if(sph.center.x + sph.radius < box.min.x) return false; + if(sph.center.x - sph.radius > box.max.x) return false; + if(sph.center.y + sph.radius < box.min.y) return false; + if(sph.center.y - sph.radius > box.max.y) return false; + if(sph.center.z + sph.radius < box.min.z) return false; + if(sph.center.z - sph.radius > box.max.z) return false; + + // Now find out where the sphere center lies in relation to all the sides + int xpos = sph.center.x < box.min.x ? 1 : + sph.center.x > box.max.x ? 2 : + 0; + int ypos = sph.center.y < box.min.y ? 1 : + sph.center.y > box.max.y ? 2 : + 0; + int zpos = sph.center.z < box.min.z ? 1 : + sph.center.z > box.max.z ? 2 : + 0; + + if(xpos == 0 && ypos == 0 && zpos == 0){ + // sphere is inside the box + p = (box.min + box.max)*0.5f; + + dist = sph.center - p; + float lensq = dist.MagnitudeSqr(); + if(lensq < mindistsq){ + point.normal = dist * (1.0f/sqrt(lensq)); + point.point = sph.center - point.normal; + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; + + // find absolute distance to the closer side in each dimension + float dx = dist.x > 0.0f ? + box.max.x - sph.center.x : + sph.center.x - box.min.x; + float dy = dist.y > 0.0f ? + box.max.y - sph.center.y : + sph.center.y - box.min.y; + float dz = dist.z > 0.0f ? + box.max.z - sph.center.z : + sph.center.z - box.min.z; + // collision depth is maximum of that: + if(dx > dy && dx > dz) + point.depth = dx; + else if(dy > dz) + point.depth = dy; + else + point.depth = dz; + return true; + } + }else{ + // sphere is outside. + // closest point on box: + p.x = xpos == 1 ? box.min.x : + xpos == 2 ? box.max.x : + sph.center.x; + p.y = ypos == 1 ? box.min.y : + ypos == 2 ? box.max.y : + sph.center.y; + p.z = zpos == 1 ? box.min.z : + zpos == 2 ? box.max.z : + sph.center.z; + + dist = sph.center - p; + float lensq = dist.MagnitudeSqr(); + if(lensq < mindistsq){ + float len = sqrt(lensq); + point.point = p; + point.normal = dist * (1.0f/len); + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; + point.depth = sph.radius - len; + mindistsq = lensq; + return true; + } + } + return false; +} + +bool +CCollision::ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist) +{ + float mint, t, x, y, z; + CVector normal; + CVector p; + + mint = 1.0f; + // check if points are on opposite sides of min x plane + if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){ + // parameter along line where we intersect + t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x); + // y of intersection + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + // z of intersection + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(box.min.x, y, z); + normal = CVector(-1.0f, 0.0f, 0.0f); + } + } + } + + // max x plane + if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){ + t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x); + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(box.max.x, y, z); + normal = CVector(1.0f, 0.0f, 0.0f); + } + } + } + + // min y plne + if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){ + t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(x, box.min.y, z); + normal = CVector(0.0f, -1.0f, 0.0f); + } + } + } + + // max y plane + if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){ + t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(x, box.max.y, z); + normal = CVector(0.0f, 1.0f, 0.0f); + } + } + } + + // min z plne + if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){ + t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + if(t < mint){ + mint = t; + p = CVector(x, y, box.min.z); + normal = CVector(0.0f, 0.0f, -1.0f); + } + } + } + + // max z plane + if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){ + t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + if(t < mint){ + mint = t; + p = CVector(x, y, box.max.z); + normal = CVector(0.0f, 0.0f, 1.0f); + } + } + } + + if(mint >= mindist) + return false; + + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = box.surface; + point.pieceB = box.piece; + mindist = mint; + + return true; +} + +// If line.p0 lies inside sphere, no collision is registered. +bool +CCollision::ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist) +{ + CVector v01 = line.p1 - line.p0; + CVector v0c = sphere.center - line.p0; + float linesq = v01.MagnitudeSqr(); + // project v0c onto v01, scaled by |v01| this is the midpoint of the two intersections + float projline = DotProduct(v01, v0c); + // tangent of p0 to sphere, scaled by linesq just like projline^2 + float tansq = (v0c.MagnitudeSqr() - sphere.radius*sphere.radius) * linesq; + // this works out to be the square of the distance between the midpoint and the intersections + float diffsq = projline*projline - tansq; + // no intersection + if(diffsq < 0.0f) + return false; + // point of first intersection, in range [0,1] between p0 and p1 + float t = (projline - sqrt(diffsq)) / linesq; + // if not on line or beyond mindist, no intersection + if(t < 0.0f || t > 1.0f || t >= mindist) + return false; + point.point = line.p0 + v01*t; + point.normal = point.point - sphere.center; + point.normal.Normalise(); + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = sphere.surface; + point.pieceB = sphere.piece; + mindist = t; + return true; +} + +bool +CCollision::ProcessVerticalLineTriangle(const CColLine &line, + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist, CStoredCollPoly *poly) +{ + float t; + CVector normal; + + const CVector &p0 = line.p0; + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + + // early out bound rect test + if(p0.x < va.x && p0.x < vb.x && p0.x < vc.x) return false; + if(p0.x > va.x && p0.x > vb.x && p0.x > vc.x) return false; + if(p0.y < va.y && p0.y < vb.y && p0.y < vc.y) return false; + if(p0.y > va.y && p0.y > vb.y && p0.y > vc.y) return false; + + plane.GetNormal(normal); + // if points are on the same side, no collision + if(plane.CalcPoint(p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + // intersection parameter on line + float h = (line.p1 - p0).z; + t = -plane.CalcPoint(p0) / (h * normal.z); + // early out if we're beyond the mindist + if(t >= mindist) + return false; + CVector p(p0.x, p0.y, p0.z + h*t); + + CVector2D vec1, vec2, vec3, vect; + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = tri.surface; + point.pieceB = 0; + if(poly){ + poly->verts[0] = va; + poly->verts[1] = vb; + poly->verts[2] = vc; + poly->valid = true; + } + mindist = t; + return true; +} + +bool +CCollision::ProcessLineTriangle(const CColLine &line , + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist) +{ + float t; + CVector normal; + plane.GetNormal(normal); + + // if points are on the same side, no collision + if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / DotProduct(line.p1 - line.p0, normal); + // early out if we're beyond the mindist + if(t >= mindist) + return false; + // find point of intersection + CVector p = line.p0 + (line.p1-line.p0)*t; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + CVector2D vec1, vec2, vec3, vect; + + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = tri.surface; + point.pieceB = 0; + mindist = t; + return true; +} + +bool +CCollision::ProcessSphereTriangle(const CColSphere &sphere, + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindistsq) +{ + // If sphere and plane don't intersect, no collision + float planedist = plane.CalcPoint(sphere.center); + float distsq = planedist*planedist; + if(fabs(planedist) > sphere.radius || distsq > mindistsq) + return false; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + + // calculate two orthogonal basis vectors for the triangle + CVector normal; + plane.GetNormal(normal); + CVector vec2 = vb - va; + float len = vec2.Magnitude(); + vec2 = vec2 * (1.0f/len); + CVector vec1 = CrossProduct(vec2, normal); + + // We know A has local coordinate [0,0] and B has [0,len]. + // Now calculate coordinates on triangle for these two vectors: + CVector vac = vc - va; + CVector vas = sphere.center - va; + CVector2D b(0.0f, len); + CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac)); + CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas)); + + // The three triangle lines partition the space into 6 sectors, + // find out in which the center lies. + int insideAB = CrossProduct2D(s, b) >= 0.0f; + int insideAC = CrossProduct2D(c, s) >= 0.0f; + int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f; + + int testcase = insideAB + insideAC + insideBC; + float dist = 0.0f; + CVector p; + if(testcase == 1){ + // closest to a vertex + if(insideAB) p = vc; + else if(insideAC) p = vb; + else if(insideBC) p = va; + else assert(0); + dist = (sphere.center - p).Magnitude(); + }else if(testcase == 2){ + // closest to an edge + if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center, p); + else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center, p); + else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center, p); + else assert(0); + }else if(testcase == 3){ + // center is in triangle + dist = fabs(planedist); + p = sphere.center - normal*planedist; + }else + assert(0); // front fell off + + if(dist >= sphere.radius || dist*dist >= mindistsq) + return false; + + point.point = p; + point.normal = sphere.center - p; + point.normal.Normalise(); + point.surfaceA = sphere.surface; + point.pieceA = sphere.piece; + point.surfaceB = tri.surface; + point.pieceB = 0; + point.depth = sphere.radius - dist; + mindistsq = dist*dist; + return true; +} + +bool +CCollision::ProcessLineOfSight(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSurf78) +{ + static CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CColLine newline(matTransform * line.p0, matTransform * line.p1); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(newline, model.boundingBox)) + return false; + + float coldist = mindist; + for(i = 0; i < model.numSpheres; i++) + if(!ignoreSurf78 || model.spheres[i].surface != 7 && model.spheres[i].surface != 8) + ProcessLineSphere(newline, model.spheres[i], point, coldist); + + for(i = 0; i < model.numBoxes; i++) + if(!ignoreSurf78 || model.boxes[i].surface != 7 && model.boxes[i].surface != 8) + ProcessLineBox(newline, model.boxes[i], point, coldist); + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++) + if(!ignoreSurf78 || model.triangles[i].surface != 7 && model.triangles[i].surface != 8) + ProcessLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist); + + if(coldist < mindist){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + mindist = coldist; + return true; + } + return false; +} + +bool +CCollision::ProcessVerticalLine(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSurf78, CStoredCollPoly *poly) +{ + static CStoredCollPoly TempStoredPoly; + int i; + + // transform line to model space + // Why does the game seem to do this differently than above? + CColLine newline(MultiplyInverse(matrix, line.p0), MultiplyInverse(matrix, line.p1)); + newline.p1.x = newline.p0.x; + newline.p1.y = newline.p0.y; + + if(!TestVerticalLineBox(newline, model.boundingBox)) + return false; + + float coldist = mindist; + for(i = 0; i < model.numSpheres; i++) + if(!ignoreSurf78 || model.spheres[i].surface != 7 && model.spheres[i].surface != 8) + ProcessLineSphere(newline, model.spheres[i], point, coldist); + + for(i = 0; i < model.numBoxes; i++) + if(!ignoreSurf78 || model.boxes[i].surface != 7 && model.boxes[i].surface != 8) + ProcessLineBox(newline, model.boxes[i], point, coldist); + + CalculateTrianglePlanes(&model); + TempStoredPoly.valid = false; + for(i = 0; i < model.numTriangles; i++) + if(!ignoreSurf78 || model.triangles[i].surface != 7 && model.triangles[i].surface != 8) + ProcessVerticalLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist, &TempStoredPoly); + + if(coldist < mindist){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + if(poly && TempStoredPoly.valid){ + *poly = TempStoredPoly; + poly->verts[0] = matrix * poly->verts[0]; + poly->verts[1] = matrix * poly->verts[1]; + poly->verts[2] = matrix * poly->verts[2]; + } + mindist = coldist; + return true; + } + return false; +} + +enum { + MAXNUMSPHERES = 128, + MAXNUMBOXES = 32, + MAXNUMLINES = 16, + MAXNUMTRIS = 600 +}; + +// This checks model A's spheres and lines against model B's spheres, boxes and triangles. +// Returns the number of A's spheres that collide. +// Returned ColPoints are in world space. +// NB: lines do not seem to be supported very well, use with caution +int32 +CCollision::ProcessColModels(const CMatrix &matrixA, CColModel &modelA, + const CMatrix &matrixB, CColModel &modelB, + CColPoint *spherepoints, CColPoint *linepoints, float *linedists) +{ + static int aSphereIndicesA[MAXNUMSPHERES]; + static int aLineIndicesA[MAXNUMLINES]; + static int aSphereIndicesB[MAXNUMSPHERES]; + static int aBoxIndicesB[MAXNUMBOXES]; + static int aTriangleIndicesB[MAXNUMTRIS]; + static bool aCollided[MAXNUMLINES]; + static CColSphere aSpheresA[MAXNUMSPHERES]; + static CColLine aLinesA[MAXNUMLINES]; + static CMatrix matAB, matBA; + CColSphere s; + int i, j; + + assert(modelA.numSpheres <= MAXNUMSPHERES); + assert(modelA.numLines <= MAXNUMLINES); + + // From model A space to model B space + matAB = Invert(matrixB, matAB) * matrixA; + + CColSphere bsphereAB; // bounding sphere of A in B space + bsphereAB.Set(modelA.boundingSphere.radius, matAB * modelA.boundingSphere.center); + if(!TestSphereBox(bsphereAB, modelB.boundingBox)) + return 0; + // B to A space + matBA = Invert(matrixA, matBA) * matrixB; + + // transform modelA's spheres and lines to B space + for(i = 0; i < modelA.numSpheres; i++){ + CColSphere &s = modelA.spheres[i]; + aSpheresA[i].Set(s.radius, matAB * s.center, s.surface, s.piece); + } + for(i = 0; i < modelA.numLines; i++) + aLinesA[i].Set(matAB * modelA.lines[i].p0, matAB * modelA.lines[i].p1); + + // Test them against model B's bounding volumes + int numSpheresA = 0; + int numLinesA = 0; + for(i = 0; i < modelA.numSpheres; i++) + if(TestSphereBox(aSpheresA[i], modelB.boundingBox)) + aSphereIndicesA[numSpheresA++] = i; + // no actual check??? + for(i = 0; i < modelA.numLines; i++) + aLineIndicesA[numLinesA++] = i; + // No collision + if(numSpheresA == 0 && numLinesA == 0) + return 0; + + // Check model B against A's bounding volumes + int numSpheresB = 0; + int numBoxesB = 0; + int numTrianglesB = 0; + for(i = 0; i < modelB.numSpheres; i++){ + s.Set(modelB.spheres[i].radius, matBA * modelB.spheres[i].center); + if(TestSphereBox(s, modelA.boundingBox)) + aSphereIndicesB[numSpheresB++] = i; + } + for(i = 0; i < modelB.numBoxes; i++) + if(TestSphereBox(bsphereAB, modelB.boxes[i])) + aBoxIndicesB[numBoxesB++] = i; + CalculateTrianglePlanes(&modelB); + for(i = 0; i < modelB.numTriangles; i++) + if(TestSphereTriangle(bsphereAB, modelB.vertices, modelB.triangles[i], modelB.trianglePlanes[i])) + aTriangleIndicesB[numTrianglesB++] = i; + assert(numSpheresB <= MAXNUMSPHERES); + assert(numBoxesB <= MAXNUMBOXES); + assert(numTrianglesB <= MAXNUMTRIS); + // No collision + if(numSpheresB == 0 && numBoxesB == 0 && numTrianglesB == 0) + return 0; + + // We now have the collision volumes in A and B that are worth processing. + + // Process A's spheres against B's collision volumes + int numCollisions = 0; + for(i = 0; i < numSpheresA; i++){ + float coldist = 1.0e24f; + bool hasCollided = false; + + for(j = 0; j < numSpheresB; j++) + hasCollided |= ProcessSphereSphere( + aSpheresA[aSphereIndicesA[i]], + modelB.spheres[aSphereIndicesB[j]], + spherepoints[numCollisions], coldist); + for(j = 0; j < numBoxesB; j++) + hasCollided |= ProcessSphereBox( + aSpheresA[aSphereIndicesA[i]], + modelB.boxes[aBoxIndicesB[j]], + spherepoints[numCollisions], coldist); + for(j = 0; j < numTrianglesB; j++) + hasCollided |= ProcessSphereTriangle( + aSpheresA[aSphereIndicesA[i]], + modelB.vertices, + modelB.triangles[aTriangleIndicesB[j]], + modelB.trianglePlanes[aTriangleIndicesB[j]], + spherepoints[numCollisions], coldist); + if(hasCollided) + numCollisions++; + } + for(i = 0; i < numCollisions; i++){ + spherepoints[i].point = matrixB * spherepoints[i].point; + spherepoints[i].normal = Multiply3x3(matrixB, spherepoints[i].normal); + } + + // And the same thing for the lines in A + for(i = 0; i < numLinesA; i++){ + aCollided[i] = false; + + for(j = 0; j < numSpheresB; j++) + aCollided[i] |= ProcessLineSphere( + aLinesA[aLineIndicesA[i]], + modelB.spheres[aSphereIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + for(j = 0; j < numBoxesB; j++) + aCollided[i] |= ProcessLineBox( + aLinesA[aLineIndicesA[i]], + modelB.boxes[aBoxIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + for(j = 0; j < numTrianglesB; j++) + aCollided[i] |= ProcessLineTriangle( + aLinesA[aLineIndicesA[i]], + modelB.vertices, + modelB.triangles[aTriangleIndicesB[j]], + modelB.trianglePlanes[aTriangleIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + } + for(i = 0; i < numLinesA; i++) + if(aCollided[i]){ + j = aLineIndicesA[i]; + linepoints[j].point = matrixB * linepoints[j].point; + linepoints[j].normal = Multiply3x3(matrixB, linepoints[j].normal); + } + + return numCollisions; // sphere collisions +} + + +// +// Misc +// + +float +CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point) +{ + float lensq = (*l1 - *l0).MagnitudeSqr(); + float dot = DotProduct(*point - *l0, *l1 - *l0); + // Between 0 and len we're above the line. + // if not, calculate distance to endpoint + if(dot <= 0.0f) + return (*point - *l0).Magnitude(); + if(dot >= lensq) + return (*point - *l1).Magnitude(); + // distance to line + return sqrt((*point - *l0).MagnitudeSqr() - dot*dot/lensq); +} + +// same as above but also return the point on the line +float +CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest) +{ + float lensq = (*l1 - *l0).MagnitudeSqr(); + float dot = DotProduct(*point - *l0, *l1 - *l0); + // find out which point we're closest to + if(dot <= 0.0f) + closest = *l0; + else if(dot >= lensq) + closest = *l1; + else + closest = *l0 + (*l1 - *l0)*(dot/lensq); + // this is the distance + return (*point - closest).Magnitude(); +} + +void +CCollision::CalculateTrianglePlanes(CColModel *model) +{ + if(model->numTriangles == 0) + return; + + CLink *lptr; + if(model->trianglePlanes){ + // re-insert at front so it's not removed again soon + lptr = model->GetLinkPtr(); + lptr->Remove(); + ms_colModelCache.head.Insert(lptr); + }else{ + assert(model); + lptr = ms_colModelCache.Insert(model); + if(lptr == nil){ + // make room if we have to, remove last in list + lptr = ms_colModelCache.tail.prev; + assert(lptr); + assert(lptr->item); + lptr->item->RemoveTrianglePlanes(); + ms_colModelCache.Remove(lptr); + // now this cannot fail + lptr = ms_colModelCache.Insert(model); + assert(lptr); + } + model->CalculateTrianglePlanes(); + model->SetLinkPtr(lptr); + } +} + +void +CCollision::DrawColModel(const CMatrix &mat, const CColModel &colModel) +{ +} + +void +CCollision::DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id) +{ + int i; + int s; + float f; + CVector verts[8]; + CVector min, max; + int r, g, b; + RwImVertexIndex *iptr; + RwIm3DVertex *vptr; + + RenderBuffer::ClearRenderBuffer(); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + for(i = 0; i < colModel.numTriangles; i++){ + colModel.GetTrianglePoint(verts[0], colModel.triangles[i].a); + colModel.GetTrianglePoint(verts[1], colModel.triangles[i].b); + colModel.GetTrianglePoint(verts[2], colModel.triangles[i].c); + verts[0] = mat * verts[0]; + verts[1] = mat * verts[1]; + verts[2] = mat * verts[2]; + + // TODO: surface + r = 255; + g = 128; + b = 0; + + s = colModel.triangles[i].surface; + f = (s & 0xF)/32.0f + 0.5f; + switch(CSurfaceTable::GetAdhesionGroup(s)){ + case ADHESIVE_RUBBER: + r = f * 255.0f; + g = 0; + b = 0; + break; + case ADHESIVE_HARD: + r = f*255.0f; + g = f*255.0f; + b = f*128.0f; + break; + case ADHESIVE_ROAD: + r = f*128.0f; + g = f*128.0f; + b = f*128.0f; + break; + case ADHESIVE_LOOSE: + r = 0; + g = f * 255.0f; + b = 0; + break; + case ADHESIVE_WET: + r = 0; + g = 0; + b = f * 255.0f; + break; + default: + // this doesn't make much sense + r *= f; + g *= f; + b *= f; + } + + // TODO: make some surface types flicker? + + if(s > SURFACE_32){ + r = CGeneral::GetRandomNumber(); + g = CGeneral::GetRandomNumber(); + b = CGeneral::GetRandomNumber(); + printf("Illegal surfacetype:%d on MI:%d\n", s, id); + } + + RenderBuffer::StartStoring(6, 3, &iptr, &vptr); + RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255); + RwIm3DVertexSetU(&vptr[0], 0.0f); + RwIm3DVertexSetV(&vptr[0], 0.0f); + RwIm3DVertexSetU(&vptr[1], 0.0f); + RwIm3DVertexSetV(&vptr[1], 1.0f); + RwIm3DVertexSetU(&vptr[2], 1.0f); + RwIm3DVertexSetV(&vptr[2], 1.0f); + RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z); + RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z); + RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z); + iptr[0] = 0; iptr[1] = 1; iptr[2] = 2; + iptr[3] = 0; iptr[4] = 2; iptr[5] = 1; + RenderBuffer::StopStoring(); + } + + for(i = 0; i < colModel.numBoxes; i++){ + min = colModel.boxes[i].min; + max = colModel.boxes[i].max; + + verts[0] = mat * CVector(min.x, min.y, min.z); + verts[1] = mat * CVector(min.x, min.y, max.z); + verts[2] = mat * CVector(min.x, max.y, min.z); + verts[3] = mat * CVector(min.x, max.y, max.z); + verts[4] = mat * CVector(max.x, min.y, min.z); + verts[5] = mat * CVector(max.x, min.y, max.z); + verts[6] = mat * CVector(max.x, max.y, min.z); + verts[7] = mat * CVector(max.x, max.y, max.z); + + s = colModel.boxes[i].surface; + f = (s & 0xF)/32.0f + 0.5f; + switch(CSurfaceTable::GetAdhesionGroup(s)){ + case ADHESIVE_RUBBER: + r = f * 255.0f; + g = 0; + b = 0; + break; + case ADHESIVE_HARD: + r = f*255.0f; + g = f*255.0f; + b = f*128.0f; + break; + case ADHESIVE_ROAD: + r = f*128.0f; + g = f*128.0f; + b = f*128.0f; + break; + case ADHESIVE_LOOSE: + r = 0; + g = f * 255.0f; + b = 0; + break; + case ADHESIVE_WET: + r = 0; + g = 0; + b = f * 255.0f; + break; + default: + // this doesn't make much sense + r *= f; + g *= f; + b *= f; + } + + // TODO: make some surface types flicker? + + RenderBuffer::StartStoring(36, 8, &iptr, &vptr); + RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[3], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[4], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[5], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[6], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[7], r, g, b, 255); + RwIm3DVertexSetU(&vptr[0], 0.0f); + RwIm3DVertexSetV(&vptr[0], 0.0f); + RwIm3DVertexSetU(&vptr[1], 0.0f); + RwIm3DVertexSetV(&vptr[1], 1.0f); + RwIm3DVertexSetU(&vptr[2], 1.0f); + RwIm3DVertexSetV(&vptr[2], 1.0f); + RwIm3DVertexSetU(&vptr[3], 0.0f); + RwIm3DVertexSetV(&vptr[3], 0.0f); + RwIm3DVertexSetU(&vptr[4], 0.0f); + RwIm3DVertexSetV(&vptr[4], 1.0f); + RwIm3DVertexSetU(&vptr[5], 1.0f); + RwIm3DVertexSetV(&vptr[5], 1.0f); + RwIm3DVertexSetU(&vptr[6], 0.0f); + RwIm3DVertexSetV(&vptr[6], 1.0f); + RwIm3DVertexSetU(&vptr[7], 1.0f); + RwIm3DVertexSetV(&vptr[7], 1.0f); + RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z); + RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z); + RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z); + RwIm3DVertexSetPos(&vptr[3], verts[3].x, verts[3].y, verts[3].z); + RwIm3DVertexSetPos(&vptr[4], verts[4].x, verts[4].y, verts[4].z); + RwIm3DVertexSetPos(&vptr[5], verts[5].x, verts[5].y, verts[5].z); + RwIm3DVertexSetPos(&vptr[6], verts[6].x, verts[6].y, verts[6].z); + RwIm3DVertexSetPos(&vptr[7], verts[7].x, verts[7].y, verts[7].z); + iptr[0] = 0; iptr[1] = 1; iptr[2] = 2; + iptr[3] = 1; iptr[4] = 3; iptr[5] = 2; + iptr[6] = 1; iptr[7] = 5; iptr[8] = 7; + iptr[9] = 1; iptr[10] = 7; iptr[11] = 3; + iptr[12] = 2; iptr[13] = 3; iptr[14] = 7; + iptr[15] = 2; iptr[16] = 7; iptr[17] = 6; + iptr[18] = 0; iptr[19] = 5; iptr[20] = 1; + iptr[21] = 0; iptr[22] = 4; iptr[23] = 5; + iptr[24] = 0; iptr[25] = 2; iptr[26] = 4; + iptr[27] = 2; iptr[28] = 6; iptr[29] = 4; + iptr[30] = 4; iptr[31] = 6; iptr[32] = 7; + iptr[33] = 4; iptr[34] = 7; iptr[35] = 5; + RenderBuffer::StopStoring(); + } + + RenderBuffer::RenderStuffInBuffer(); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); +} + + +/* + * ColModel code + */ + +void +CColSphere::Set(float radius, const CVector ¢er, uint8 surf, uint8 piece) +{ + this->radius = radius; + this->center = center; + this->surface = surf; + this->piece = piece; +} + +void +CColBox::Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece) +{ + this->min = min; + this->max = max; + this->surface = surf; + this->piece = piece; +} + +void +CColLine::Set(const CVector &p0, const CVector &p1) +{ + this->p0 = p0; + this->p1 = p1; +} + +void +CColTriangle::Set(const CVector *, int a, int b, int c, uint8 surf, uint8 piece) +{ + this->a = a; + this->b = b; + this->c = c; + this->surface = surf; +} + +void +CColTrianglePlane::Set(const CVector *v, CColTriangle &tri) +{ + const CVector &va = v[tri.a]; + const CVector &vb = v[tri.b]; + const CVector &vc = v[tri.c]; + + normal = CrossProduct(vc-va, vb-va); + normal.Normalise(); + dist = DotProduct(normal, va); + CVector an(fabs(normal.x), fabs(normal.y), fabs(normal.z)); + // find out largest component and its direction + if(an.x > an.y && an.x > an.z) + dir = normal.x < 0.0f ? DIR_X_NEG : DIR_X_POS; + else if(an.y > an.z) + dir = normal.y < 0.0f ? DIR_Y_NEG : DIR_Y_POS; + else + dir = normal.z < 0.0f ? DIR_Z_NEG : DIR_Z_POS; +} + +CColModel::CColModel(void) +{ + numSpheres = 0; + spheres = nil; + numLines = 0; + lines = nil; + numBoxes = 0; + boxes = nil; + numTriangles = 0; + vertices = nil; + triangles = nil; + trianglePlanes = nil; + level = CGame::currLevel; + ownsCollisionVolumes = true; +} + +CColModel::~CColModel(void) +{ + RemoveCollisionVolumes(); + RemoveTrianglePlanes(); +} + +void +CColModel::RemoveCollisionVolumes(void) +{ + if(ownsCollisionVolumes){ + RwFree(spheres); + RwFree(lines); + RwFree(boxes); + RwFree(vertices); + RwFree(triangles); + } + numSpheres = 0; + numLines = 0; + numBoxes = 0; + numTriangles = 0; + spheres = nil; + lines = nil; + boxes = nil; + vertices = nil; + triangles = nil; +} + +void +CColModel::CalculateTrianglePlanes(void) +{ + // HACK: allocate space for one more element to stuff the link pointer into + trianglePlanes = (CColTrianglePlane*)RwMalloc(sizeof(CColTrianglePlane) * (numTriangles+1)); + for(int i = 0; i < numTriangles; i++) + trianglePlanes[i].Set(vertices, triangles[i]); +} + +void +CColModel::RemoveTrianglePlanes(void) +{ + RwFree(trianglePlanes); + trianglePlanes = nil; +} + +void +CColModel::SetLinkPtr(CLink *lptr) +{ + assert(trianglePlanes); + *(CLink**)ALIGNPTR(&trianglePlanes[numTriangles]) = lptr; +} + +CLink* +CColModel::GetLinkPtr(void) +{ + assert(trianglePlanes); + return *(CLink**)ALIGNPTR(&trianglePlanes[numTriangles]); +} + +void +CColModel::GetTrianglePoint(CVector &v, int i) const +{ + v = vertices[i]; +} + +STARTPATCHES + InjectHook(0x4B9C30, (CMatrix& (*)(const CMatrix &src, CMatrix &dst))Invert, PATCH_JUMP); + + InjectHook(0x40BB70, CCollision::TestSphereBox, PATCH_JUMP); + InjectHook(0x40E130, CCollision::TestLineBox, PATCH_JUMP); + InjectHook(0x40E5C0, CCollision::TestVerticalLineBox, PATCH_JUMP); + InjectHook(0x40EC10, CCollision::TestLineTriangle, PATCH_JUMP); + InjectHook(0x40DAA0, CCollision::TestLineSphere, PATCH_JUMP); + InjectHook(0x40C580, CCollision::TestSphereTriangle, PATCH_JUMP); + InjectHook(0x40F720, CCollision::TestLineOfSight, PATCH_JUMP); + + InjectHook(0x40B9F0, CCollision::ProcessSphereSphere, PATCH_JUMP); + InjectHook(0x40BC00, CCollision::ProcessSphereBox, PATCH_JUMP); + InjectHook(0x40E670, CCollision::ProcessLineBox, PATCH_JUMP); + InjectHook(0x40DE80, CCollision::ProcessLineSphere, PATCH_JUMP); + InjectHook(0x40FB50, CCollision::ProcessVerticalLineTriangle, PATCH_JUMP); + InjectHook(0x40F140, CCollision::ProcessLineTriangle, PATCH_JUMP); + InjectHook(0x40CE30, CCollision::ProcessSphereTriangle, PATCH_JUMP); + + InjectHook(0x40F910, CCollision::ProcessLineOfSight, PATCH_JUMP); + InjectHook(0x410120, CCollision::ProcessVerticalLine, PATCH_JUMP); + InjectHook(0x410BE0, CCollision::ProcessColModels, PATCH_JUMP); + + InjectHook(0x40B960, CCollision::CalculateTrianglePlanes, PATCH_JUMP); + InjectHook(0x411640, &CLink::Remove, PATCH_JUMP); + InjectHook(0x411620, &CLink::Insert, PATCH_JUMP); + InjectHook(0x4115C0, &CLinkList::Insert, PATCH_JUMP); + InjectHook(0x411600, &CLinkList::Remove, PATCH_JUMP); +// InjectHook(0x411530, &CLinkList::Init, PATCH_JUMP); + + InjectHook(0x411E40, (void (CColSphere::*)(float, const CVector&, uint8, uint8))&CColSphere::Set, PATCH_JUMP); + InjectHook(0x40B2A0, &CColBox::Set, PATCH_JUMP); + InjectHook(0x40B320, &CColLine::ctor, PATCH_JUMP); + InjectHook(0x40B350, &CColLine::Set, PATCH_JUMP); + InjectHook(0x411E70, &CColTriangle::Set, PATCH_JUMP); + + InjectHook(0x411EA0, &CColTrianglePlane::Set, PATCH_JUMP); + InjectHook(0x412140, &CColTrianglePlane::GetNormal, PATCH_JUMP); + + InjectHook(0x411680, &CColModel::ctor, PATCH_JUMP); + InjectHook(0x4116E0, &CColModel::dtor, PATCH_JUMP); + InjectHook(0x411D80, &CColModel::RemoveCollisionVolumes, PATCH_JUMP); + InjectHook(0x411CB0, &CColModel::CalculateTrianglePlanes, PATCH_JUMP); + InjectHook(0x411D10, &CColModel::RemoveTrianglePlanes, PATCH_JUMP); + InjectHook(0x411D40, &CColModel::SetLinkPtr, PATCH_JUMP); + InjectHook(0x411D60, &CColModel::GetLinkPtr, PATCH_JUMP); +ENDPATCHES diff --git a/src/Collision.h b/src/Collision.h new file mode 100644 index 00000000..9d23524f --- /dev/null +++ b/src/Collision.h @@ -0,0 +1,152 @@ +#pragma once + +#include "templates.h" +#include "Game.h" // for eLevelName + +struct CColSphere +{ + CVector center; + float radius; + uint8 surface; + uint8 piece; + + void Set(float radius, const CVector ¢er, uint8 surf, uint8 piece); + void Set(float radius, const CVector ¢er) { this->center = center; this->radius = radius; } +}; + +struct CColBox +{ + CVector min; + CVector max; + uint8 surface; + uint8 piece; + + void Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece); +}; + +struct CColLine +{ + CVector p0; + int pad0; + CVector p1; + int pad1; + + CColLine(void) { }; + CColLine(const CVector &p0, const CVector &p1) { this->p0 = p0; this->p1 = p1; }; + void Set(const CVector &p0, const CVector &p1); + + CColLine *ctor(CVector *p0, CVector *p1) { return ::new (this) CColLine(*p0, *p1); } +}; + +struct CColTriangle +{ + uint16 a; + uint16 b; + uint16 c; + uint8 surface; + + void Set(const CVector *v, int a, int b, int c, uint8 surf, uint8 piece); +}; + +struct CColTrianglePlane +{ + CVector normal; + float dist; + uint8 dir; + + void Set(const CVector *v, CColTriangle &tri); + void GetNormal(CVector &n) const { n = normal; } + float CalcPoint(const CVector &v) const { return DotProduct(normal, v) - dist; }; +}; + +struct CColPoint +{ + CVector point; + int pad1; + // the surface normal on the surface of point + CVector normal; + int pad2; + uint8 surfaceA; + uint8 pieceA; + uint8 surfaceB; + uint8 pieceB; + float depth; +}; + +struct CStoredCollPoly +{ + CVector verts[3]; + bool valid; +}; + +struct CColModel +{ + CColSphere boundingSphere; + CColBox boundingBox; + short numSpheres; + short numLines; + short numBoxes; + short numTriangles; + int level; + bool ownsCollisionVolumes; + CColSphere *spheres; + CColLine *lines; + CColBox *boxes; + CVector *vertices; + CColTriangle *triangles; + CColTrianglePlane *trianglePlanes; + + CColModel(void); + ~CColModel(void); + void RemoveCollisionVolumes(void); + void CalculateTrianglePlanes(void); + void RemoveTrianglePlanes(void); + CLink *GetLinkPtr(void); + void SetLinkPtr(CLink*); + void GetTrianglePoint(CVector &v, int i) const; + + CColModel *ctor(void) { return ::new (this) CColModel(); } + void dtor(void) { this->CColModel::~CColModel(); } +}; + +class CCollision +{ +public: + static eLevelName &ms_collisionInMemory; + static CLinkList &ms_colModelCache; + + static void Init(void); + static void Update(void); + static void LoadCollisionWhenINeedIt(bool changeLevel); + static void DrawColModel(const CMatrix &mat, const CColModel &colModel); + static void DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id); + + static void CalculateTrianglePlanes(CColModel *model); + + // all these return true if there's a collision + static bool TestSphereSphere(const CColSphere &s1, const CColSphere &s2); + static bool TestSphereBox(const CColSphere &sph, const CColBox &box); + static bool TestLineBox(const CColLine &line, const CColBox &box); + static bool TestVerticalLineBox(const CColLine &line, const CColBox &box); + static bool TestLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane); + static bool TestLineSphere(const CColLine &line, const CColSphere &sph); + static bool TestSphereTriangle(const CColSphere &sphere, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane); + static bool TestLineOfSight(CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSurf78); + + static bool ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq); + static bool ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq); + static bool ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist); + static bool ProcessVerticalLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist, CStoredCollPoly *poly); + static bool ProcessLineTriangle(const CColLine &line , const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist); + static bool ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist); + static bool ProcessSphereTriangle(const CColSphere &sph, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindistsq); + static bool ProcessLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSurf78); + static bool ProcessVerticalLine(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSurf78, CStoredCollPoly *poly); + static int32 ProcessColModels(const CMatrix &matrix1, CColModel &model1, const CMatrix &matrix2, CColModel &model2, CColPoint *point1, CColPoint *point2, float *linedists); + + // TODO: + // CCollision::IsStoredPolyStillValidVerticalLine + + static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point); + static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest); +}; diff --git a/src/CullZones.cpp b/src/CullZones.cpp new file mode 100644 index 00000000..dc30790b --- /dev/null +++ b/src/CullZones.cpp @@ -0,0 +1,323 @@ +#include "common.h" +#include "patcher.h" +#include "Building.h" +#include "Treadable.h" +#include "Pools.h" +#include "Timer.h" +#include "Camera.h" +#include "World.h" +#include "CullZones.h" + +int32 &CCullZones::NumCullZones = *(int*)0x8F2564; +CCullZone *CCullZones::aZones = (CCullZone*)0x864750; // [NUMCULLZONES]; +int32 &CCullZones::NumAttributeZones = *(int*)0x8E29D0; +CAttributeZone *CCullZones::aAttributeZones = (CAttributeZone*)0x709C60; // [NUMATTRIBZONES]; +uint16 *CCullZones::aIndices = (uint16*)0x847330; // [NUMZONEINDICES]; +int16 *CCullZones::aPointersToBigBuildingsForBuildings = (int16*)0x86C9D0; // [NUMBUILDINGS]; +int16 *CCullZones::aPointersToBigBuildingsForTreadables = (int16*)0x8F1B8C; // [NUMTREADABLES]; + +int32 &CCullZones::CurrentWantedLevelDrop_Player = *(int32*)0x880DA8; +int32 &CCullZones::CurrentFlags_Camera = *(int32*)0x940718; +int32 &CCullZones::CurrentFlags_Player = *(int32*)0x9415F0; +int32 &CCullZones::OldCullZone = *(int32*)0x8E2C90; +int32 &CCullZones::EntityIndicesUsed = *(int32*)0x8F2508; +bool &CCullZones::bCurrentSubwayIsInvisible = *(bool*)0x95CDA5; +bool &CCullZones::bCullZonesDisabled = *(bool*)0x95CD4A; + + +void +CCullZones::Init(void) +{ + int i; + + NumAttributeZones = 0; + NumCullZones = 0; + CurrentWantedLevelDrop_Player = 0; + CurrentFlags_Camera = 0; + CurrentFlags_Player = 0; + OldCullZone = -1; + EntityIndicesUsed = 0; + bCurrentSubwayIsInvisible = false; + + for(i = 0; i < NUMBUILDINGS; i++) + aPointersToBigBuildingsForBuildings[i] = -1; + for(i = 0; i < NUMTREADABLES; i++) + aPointersToBigBuildingsForTreadables[i] = -1; +} + +void +CCullZones::Update(void) +{ + bool invisible; + CVector v; + + if(bCullZonesDisabled) + return; + + switch(CTimer::GetFrameCounter() & 7){ + case 0: + case 4: + /* Update Cull zone */ + ForceCullZoneCoors(TheCamera.GetGameCamPosition()); + break; + + case 2: + /* Update camera attributes */ + CurrentFlags_Camera = FindAttributesForCoors(TheCamera.GetGameCamPosition(), nil); + invisible = (CurrentFlags_Camera & ATTRZONE_SUBWAYVISIBLE) == 0; + if(invisible != bCurrentSubwayIsInvisible){ + MarkSubwayAsInvisible(!invisible); + bCurrentSubwayIsInvisible = invisible; + } + break; + + case 6: + /* Update player attributes */ + CurrentFlags_Player = FindAttributesForCoors(FindPlayerCoors(v), + &CurrentWantedLevelDrop_Player); + break; + } +} + +void +CCullZones::ForceCullZoneCoors(CVector coors) +{ + int32 z; + z = FindCullZoneForCoors(coors); + if(z != OldCullZone){ + if(OldCullZone >= 0) + aZones[OldCullZone].DoStuffLeavingZone(); + if(z >= 0) + aZones[z].DoStuffEnteringZone(); + OldCullZone = z; + } +} + +int32 +CCullZones::FindCullZoneForCoors(CVector coors) +{ + int i; + + for(i = 0; i < NumCullZones; i++) + if(coors.x >= aZones[i].minx && coors.x <= aZones[i].maxx && + coors.y >= aZones[i].miny && coors.y <= aZones[i].maxy && + coors.z >= aZones[i].minz && coors.z <= aZones[i].maxz) + return i; + return -1; +} + +int32 +CCullZones::FindAttributesForCoors(CVector coors, int32 *wantedLevel) +{ + int i; + int32 attribs; + + attribs = 0; + for(i = 0; i < NumAttributeZones; i++) + if(coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx && + coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy && + coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz){ + attribs |= aAttributeZones[i].attributes; + if(wantedLevel && *wantedLevel <= aAttributeZones[i].wantedLevel) + *wantedLevel = aAttributeZones[i].wantedLevel; + } + return attribs; +} + +CAttributeZone* +CCullZones::FindZoneWithStairsAttributeForPlayer(void) +{ + int i; + CVector coors; + + FindPlayerCoors(coors); + for(i = 0; i < NumAttributeZones; i++) + if(aAttributeZones[i].attributes & ATTRZONE_STAIRS && + coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx && + coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy && + coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz) + return &aAttributeZones[i]; + return nil; +} + +WRAPPER void +CCullZones::MarkSubwayAsInvisible(bool visible) +{ EAXJMP(0x525AF0); +} + +void +CCullZones::AddCullZone(CVector const &position, + float minx, float maxx, + float miny, float maxy, + float minz, float maxz, + uint16 flag, int16 wantedLevel) +{ + CCullZone *cull; + CAttributeZone *attrib; + + CVector v; + if((flag & ATTRZONE_NOTCULLZONE) == 0){ + cull = &aZones[NumCullZones++]; + v = position; + // WTF is this? + if((v-CVector(1032.14f, -624.255f, 24.93f)).Magnitude() < 1.0f) + v = CVector(1061.7f, -613.0f, 19.0f); + if((v-CVector(1029.48f, -495.757f, 21.98f)).Magnitude() < 1.0f) + v = CVector(1061.4f, -506.0f, 18.5f); + cull->position.x = clamp(v.x, minx, maxx); + cull->position.y = clamp(v.y, miny, maxy); + cull->position.z = clamp(v.z, minz, maxz); + cull->minx = minx; + cull->maxx = maxx; + cull->miny = miny; + cull->maxy = maxy; + cull->minz = minz; + cull->maxz = maxz; + cull->unk2 = 0; + cull->unk3 = 0; + cull->unk4 = 0; + cull->m_indexStart = 0; + } + if(flag & ~ATTRZONE_NOTCULLZONE){ + attrib = &aAttributeZones[NumAttributeZones++]; + attrib->minx = minx; + attrib->maxx = maxx; + attrib->miny = miny; + attrib->maxy = maxy; + attrib->minz = minz; + attrib->maxz = maxz; + attrib->attributes = flag; + attrib->wantedLevel = wantedLevel; + } +} + + + +void +CCullZone::DoStuffLeavingZone(void) +{ + int i; + + for(i = 0; i < m_numBuildings; i++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables ; i++) + DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[m_indexStart + i]); +} + +void +CCullZone::DoStuffLeavingZone_OneBuilding(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetBuildingPool()->GetSlot(i)->m_bZoneCulled = false; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->m_bZoneCulled = false; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffLeavingZone_OneTreadableBoth(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetTreadablePool()->GetSlot(i)->m_bZoneCulled = false; + CPools::GetTreadablePool()->GetSlot(i)->m_bZoneCulled2 = false; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->m_bZoneCulled = false; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone(void) +{ + int i; + + for(i = 0; i < m_numBuildings; i++) + DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m; i++) + DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables; i++) + DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[m_indexStart + i]); +} + +void +CCullZone::DoStuffEnteringZone_OneBuilding(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetBuildingPool()->GetSlot(i)->m_bZoneCulled = true; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->m_bZoneCulled = true; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadablePlus10m(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetTreadablePool()->GetSlot(i)->m_bZoneCulled = true;; + CPools::GetTreadablePool()->GetSlot(i)->m_bZoneCulled2 = true;; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->m_bZoneCulled = true; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadable(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetTreadablePool()->GetSlot(i)->m_bZoneCulled = true;; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->m_bZoneCulled = true; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +STARTPATCHES + InjectHook(0x524BC0, &CCullZones::Init, PATCH_JUMP); + InjectHook(0x524F80, &CCullZones::Update, PATCH_JUMP); + InjectHook(0x525370, &CCullZones::AddCullZone, PATCH_JUMP); + InjectHook(0x5250D0, &CCullZones::ForceCullZoneCoors, PATCH_JUMP); + InjectHook(0x525130, &CCullZones::FindCullZoneForCoors, PATCH_JUMP); + InjectHook(0x5251C0, &CCullZones::FindAttributesForCoors, PATCH_JUMP); + InjectHook(0x525290, &CCullZones::FindZoneWithStairsAttributeForPlayer, PATCH_JUMP); + + InjectHook(0x525610, &CCullZone::DoStuffLeavingZone, PATCH_JUMP); + InjectHook(0x525810, &CCullZone::DoStuffEnteringZone, PATCH_JUMP); +ENDPATCHES diff --git a/src/CullZones.h b/src/CullZones.h new file mode 100644 index 00000000..4641f6ad --- /dev/null +++ b/src/CullZones.h @@ -0,0 +1,93 @@ +class CCullZone +{ +public: + CVector position; + float minx; + float maxx; + float miny; + float maxy; + float minz; + float maxz; + + // TODO: figure these out: + int32 m_indexStart; + int16 unk2; + int16 unk3; + int16 unk4; + int16 m_numBuildings; + int16 m_numTreadablesPlus10m; + int16 m_numTreadables; + + void DoStuffLeavingZone(void); + static void DoStuffLeavingZone_OneBuilding(uint16 i); + static void DoStuffLeavingZone_OneTreadableBoth(uint16 i); + void DoStuffEnteringZone(void); + static void DoStuffEnteringZone_OneBuilding(uint16 i); + static void DoStuffEnteringZone_OneTreadablePlus10m(uint16 i); + static void DoStuffEnteringZone_OneTreadable(uint16 i); +}; + +enum eZoneAttribs +{ + ATTRZONE_CAMCLOSEIN = 1, + ATTRZONE_STAIRS = 2, + ATTRZONE_1STPERSON = 4, + ATTRZONE_NORAIN = 8, + ATTRZONE_NOPOLICE = 0x10, + ATTRZONE_NOTCULLZONE = 0x20, + ATTRZONE_DOINEEDCOLLISION = 0x40, + ATTRZONE_SUBWAYVISIBLE = 0x80, +}; + +struct CAttributeZone +{ + float minx; + float maxx; + float miny; + float maxy; + float minz; + float maxz; + int16 attributes; + int16 wantedLevel; +}; + +class CCullZones +{ +public: + static int32 &NumCullZones; + static CCullZone *aZones; // [NUMCULLZONES]; + static int32 &NumAttributeZones; + static CAttributeZone *aAttributeZones; // [NUMATTRIBZONES]; + static uint16 *aIndices; // [NUMZONEINDICES]; + static int16 *aPointersToBigBuildingsForBuildings; // [NUMBUILDINGS]; + static int16 *aPointersToBigBuildingsForTreadables; // [NUMTREADABLES]; + + static int32 &CurrentWantedLevelDrop_Player; + static int32 &CurrentFlags_Camera; + static int32 &CurrentFlags_Player; + static int32 &OldCullZone; + static int32 &EntityIndicesUsed; + static bool &bCurrentSubwayIsInvisible; + static bool &bCullZonesDisabled; + + static void Init(void); + static void Update(void); + static void ForceCullZoneCoors(CVector coors); + static int32 FindCullZoneForCoors(CVector coors); + static int32 FindAttributesForCoors(CVector coors, int32 *wantedLevel); + static CAttributeZone *FindZoneWithStairsAttributeForPlayer(void); + static void MarkSubwayAsInvisible(bool visible); + static void AddCullZone(CVector const &position, + float minx, float maxx, + float miny, float maxy, + float minz, float maxz, + uint16 flag, int16 wantedLevel); + static bool CamCloseInForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_CAMCLOSEIN) != 0; } + static bool CamStairsForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_STAIRS) != 0; } + static bool Cam1stPersonForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_1STPERSON) != 0; } + static bool NoPolice(void) { return (CurrentFlags_Player & ATTRZONE_NOPOLICE) != 0; } + static bool DoINeedToLoadCollision(void) { return (CurrentFlags_Player & ATTRZONE_DOINEEDCOLLISION) != 0; } + static bool PlayerNoRain(void) { return (CurrentFlags_Player & ATTRZONE_NORAIN) != 0; } + static bool CamNoRain(void) { return (CurrentFlags_Camera & ATTRZONE_NORAIN) != 0; } + static int32 GetWantedLevelDrop(void) { return CurrentWantedLevelDrop_Player; } +}; diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 00000000..7f9d86a0 --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,5 @@ +#include "common.h" +#include "patcher.h" +#include "Game.h" + +int &CGame::currLevel = *(int*)0x941514; diff --git a/src/Game.h b/src/Game.h new file mode 100644 index 00000000..83a51fab --- /dev/null +++ b/src/Game.h @@ -0,0 +1,15 @@ +#pragma once + +enum eLevelName +{ + LEVEL_NONE = 0, + LEVEL_INDUSTRIAL, + LEVEL_COMMERCIAL, + LEVEL_SUBURBAN +}; + +class CGame +{ +public: + static int &currLevel; +}; diff --git a/src/General.h b/src/General.h new file mode 100644 index 00000000..7aacee39 --- /dev/null +++ b/src/General.h @@ -0,0 +1,15 @@ +class CGeneral +{ +public: + static float GetATanOfXY(float x, float y){ + if(y >= 0.0f) return atan2(x, y); + return atan2(x, y) + 2*M_PI; + } + + // not too sure about all these... + static uint16 GetRandomNumber(void) + { return myrand() & 0xFFFF; } + // Probably don't want to ever reach high + static float GetRandomNumberInRange(float low, float high) + { return low + (high - low)*(GetRandomNumber()/65536.0f); } +}; diff --git a/src/Glass.cpp b/src/Glass.cpp new file mode 100644 index 00000000..7b02cb6c --- /dev/null +++ b/src/Glass.cpp @@ -0,0 +1,15 @@ +#include "common.h" +#include "patcher.h" +#include "Glass.h" + +WRAPPER void +CGlass::WindowRespondsToCollision(CEntity *ent, float amount, CVector speed, CVector point, bool foo) +{ + EAXJMP(0x503F10); +} + +WRAPPER void +CGlass::WindowRespondsToSoftCollision(CEntity *ent, float amount) +{ + EAXJMP(0x504630); +} diff --git a/src/Glass.h b/src/Glass.h new file mode 100644 index 00000000..5347467b --- /dev/null +++ b/src/Glass.h @@ -0,0 +1,10 @@ +#pragma once + +class CEntity; + +class CGlass +{ +public: + static void WindowRespondsToCollision(CEntity *ent, float amount, CVector speed, CVector point, bool foo); + static void WindowRespondsToSoftCollision(CEntity *ent, float amount); +}; diff --git a/src/Lists.cpp b/src/Lists.cpp new file mode 100644 index 00000000..448a0ff1 --- /dev/null +++ b/src/Lists.cpp @@ -0,0 +1,26 @@ +#include "common.h" +#include "Pools.h" +#include "Lists.h" + +void* +CPtrNode::operator new(size_t){ + CPtrNode *node = CPools::GetPtrNodePool()->New(); + assert(node); + return node; +} + +void +CPtrNode::operator delete(void *p, size_t){ + CPools::GetPtrNodePool()->Delete((CPtrNode*)p); +} + +void* +CEntryInfoNode::operator new(size_t){ + CEntryInfoNode *node = CPools::GetEntryInfoNodePool()->New(); + assert(node); + return node; +} +void +CEntryInfoNode::operator delete(void *p, size_t){ + CPools::GetEntryInfoNodePool()->Delete((CEntryInfoNode*)p); +} diff --git a/src/Lists.h b/src/Lists.h new file mode 100644 index 00000000..7572e882 --- /dev/null +++ b/src/Lists.h @@ -0,0 +1,130 @@ +#pragma once + +class CPtrNode +{ +public: + void *item; + CPtrNode *prev; + CPtrNode *next; + + void *operator new(size_t); + void operator delete(void *p, size_t); +}; + +class CPtrList +{ +public: + CPtrNode *first; + + CPtrList(void) { first = nil; } + ~CPtrList(void) { Flush(); } + CPtrNode *FindItem(void *item){ + CPtrNode *node; + for(node = first; node; node = node->next) + if(node->item == item) + return node; + return nil; + } + CPtrNode *InsertNode(CPtrNode *node){ + node->prev = nil; + node->next = first; + if(first) + first->prev = node; + first = node; + return node; + } + CPtrNode *InsertItem(void *item){ + CPtrNode *node = new CPtrNode; + node->item = item; + InsertNode(node); + return node; + } + void RemoveNode(CPtrNode *node){ + if(node == first) + first = node->next; + if(node->prev) + node->prev->next = node->next; + if(node->next) + node->next->prev = node->prev; + } + void DeleteNode(CPtrNode *node){ + RemoveNode(node); + delete node; + } + void RemoveItem(void *item){ + CPtrNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + if(node->item == item) + DeleteNode(node); + } + } + void Flush(void){ + CPtrNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + DeleteNode(node); + } + } +}; + +class CSector; + +// This records in which sector list a Physical is +class CEntryInfoNode +{ +public: + CPtrList *list; // list in sector + CPtrNode *listnode; // node in list + CSector *sector; + + CEntryInfoNode *prev; + CEntryInfoNode *next; + + void *operator new(size_t); + void operator delete(void *p, size_t); +}; + +class CEntryInfoList +{ +public: + CEntryInfoNode *first; + + CEntryInfoList(void) { first = nil; } + ~CEntryInfoList(void) { Flush(); } + CEntryInfoNode *InsertNode(CEntryInfoNode *node){ + node->prev = nil; + node->next = first; + if(first) + first->prev = node; + first = node; + return node; + } + CEntryInfoNode *InsertItem(CPtrList *list, CPtrNode *listnode, CSector *sect){ + CEntryInfoNode *node = new CEntryInfoNode; + node->list = list; + node->listnode = listnode; + node->sector = sect; + InsertNode(node); + return node; + } + void RemoveNode(CEntryInfoNode *node){ + if(node == first) + first = node->next; + if(node->prev) + node->prev->next = node->next; + if(node->next) + node->next->prev = node->prev; + } + void DeleteNode(CEntryInfoNode *node){ + RemoveNode(node); + delete node; + } + void Flush(void){ + CEntryInfoNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + DeleteNode(node); + } + } +}; diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp new file mode 100644 index 00000000..0e335546 --- /dev/null +++ b/src/MenuManager.cpp @@ -0,0 +1,4 @@ +#include "common.h" +#include "MenuManager.h" + +int &CMenuManager::m_PrefsBrightness = *(int*)0x5F2E50; diff --git a/src/MenuManager.h b/src/MenuManager.h new file mode 100644 index 00000000..803e4d8d --- /dev/null +++ b/src/MenuManager.h @@ -0,0 +1,5 @@ +class CMenuManager +{ +public: + static int &m_PrefsBrightness; +}; diff --git a/src/NodeName.cpp b/src/NodeName.cpp new file mode 100644 index 00000000..c2ffe048 --- /dev/null +++ b/src/NodeName.cpp @@ -0,0 +1,15 @@ +#include "common.h" +#include "patcher.h" +#include "NodeName.h" + +int &gPluginOffset = *(int*)0x64C610; + +#define NODENAMEEXT(o) (RWPLUGINOFFSET(char, o, gPluginOffset)) + +char* +GetFrameNodeName(RwFrame *frame) +{ + if(gPluginOffset < 0) + return nil; + return NODENAMEEXT(frame); +} diff --git a/src/NodeName.h b/src/NodeName.h new file mode 100644 index 00000000..a4dcf0cf --- /dev/null +++ b/src/NodeName.h @@ -0,0 +1,3 @@ +#pragma once + +char *GetFrameNodeName(RwFrame *frame); diff --git a/src/Pad.cpp b/src/Pad.cpp new file mode 100644 index 00000000..ab6bf154 --- /dev/null +++ b/src/Pad.cpp @@ -0,0 +1,37 @@ +#include "common.h" +#include "Pad.h" + +CPad *CPad::Pads = (CPad*)0x6F0360; + +uint16 *CPad::OldKeyState = (uint16*)0x6F1E70; +uint16 *CPad::NewKeyState = (uint16*)0x6E60D0; +uint16 *CPad::TempKeyState = (uint16*)0x774DE8; + +CMouseControllerState &CPad::OldMouseControllerState = *(CMouseControllerState*)0x8472A0; +CMouseControllerState &CPad::NewMouseControllerState = *(CMouseControllerState*)0x8809F0; +CMouseControllerState &CPad::PCTempMouseControllerState = *(CMouseControllerState*)0x6F1E60; + +void +CControllerState::Clear(void) +{ + leftX = 0; + leftY = 0; + rightX = 0; + rightY = 0; + l1 = 0; + l2 = 0; + r1 = 0; + r2 = 0; + up = 0; + down = 0; + left = 0; + right = 0; + start = 0; + select = 0; + square = 0; + triangle = 0; + cross = 0; + circle = 0; + leftshock = 0; + rightshock = 0; +} diff --git a/src/Pad.h b/src/Pad.h new file mode 100644 index 00000000..ddcfad8d --- /dev/null +++ b/src/Pad.h @@ -0,0 +1,116 @@ +#pragma once + +// same as RW skeleton +enum Key +{ + // ascii... + + KEY_ESC = 128, + + KEY_F1 = 129, + KEY_F2 = 130, + KEY_F3 = 131, + KEY_F4 = 132, + KEY_F5 = 133, + KEY_F6 = 134, + KEY_F7 = 135, + KEY_F8 = 136, + KEY_F9 = 137, + KEY_F10 = 138, + KEY_F11 = 139, + KEY_F12 = 140, + + KEY_INS = 141, + KEY_DEL = 142, + KEY_HOME = 143, + KEY_END = 144, + KEY_PGUP = 145, + KEY_PGDN = 146, + + KEY_UP = 147, + KEY_DOWN = 148, + KEY_LEFT = 149, + KEY_RIGHT = 150, + + // some stuff ommitted + + KEY_BACKSP = 168, + KEY_TAB = 169, + KEY_CAPSLK = 170, + KEY_ENTER = 171, + KEY_LSHIFT = 172, + KEY_RSHIFT = 173, + KEY_LCTRL = 174, + KEY_RCTRL = 175, + KEY_LALT = 176, + KEY_RALT = 177, + + KEY_NULL, // unused + KEY_NUMKEYS, +}; + + +class CControllerState +{ +public: + int16 leftX, leftY; + int16 rightX, rightY; + int16 l1, l2; + int16 r1, r2; + int16 up, down, left, right; + int16 start, select; + int16 square, triangle, cross, circle; + int16 leftshock, rightshock; + int16 networktalk; + float getLeftX(void) { return leftX/32767.0f; }; + float getLeftY(void) { return leftY/32767.0f; }; + float getRightX(void) { return rightX/32767.0f; }; + float getRightY(void) { return rightY/32767.0f; }; + + void Clear(void); +}; +static_assert(sizeof(CControllerState) == 0x2A, "CControllerState: error"); + +struct CMouseControllerState +{ + uint32 btns; // bit 0-2 button 1-3 + int x, y; +}; + +class CPad +{ +public: + CControllerState NewState; + CControllerState OldState; + CControllerState PCTempKeyState; + CControllerState PCTempJoyState; + CControllerState PCTempMouseState; + // straight out of my IDB + int16 Phase; + int16 Mode; + int16 ShakeDur; + int8 ShakeFreq; + int8 bHornHistory[5]; + int8 iCurrHornHistory; + int8 DisablePlayerControls; + int8 JustOutOfFrontEnd; + int8 bApplyBrakes; + int32 unk[3]; + int32 LastTimeTouched; + int32 AverageWeapon; + int32 AverageEntries; + + static CPad *Pads; //[2]; + static uint16 *OldKeyState; //[KEY_NUMKEYS]; + static uint16 *NewKeyState; //[KEY_NUMKEYS]; + static uint16 *TempKeyState; //[KEY_NUMKEYS]; + static CMouseControllerState &OldMouseControllerState; + static CMouseControllerState &NewMouseControllerState; + static CMouseControllerState &PCTempMouseControllerState; + + static CPad *GetPad(int n) { return &Pads[n]; } +}; +static_assert(sizeof(CPad) == 0xFC, "CPad: error"); + +#define IsButtonJustDown(pad, btn) \ + (!(pad)->OldState.btn && (pad)->NewState.btn) diff --git a/src/ParticleObject.cpp b/src/ParticleObject.cpp new file mode 100644 index 00000000..fc15c2f7 --- /dev/null +++ b/src/ParticleObject.cpp @@ -0,0 +1,5 @@ +#include "common.h" +#include "patcher.h" +#include "ParticleObject.h" + +void CParticleObject::AddObject(uint16, const CVector &pos, bool remove) { EAXJMP(0x4BC4D0); } diff --git a/src/ParticleObject.h b/src/ParticleObject.h new file mode 100644 index 00000000..b2cfadb8 --- /dev/null +++ b/src/ParticleObject.h @@ -0,0 +1,31 @@ +#pragma once + +enum eParticleObjectType +{ + POBJECT_PAVEMENT_STEAM, + POBJECT_PAVEMENT_STEAM_SLOWMOTION, + POBJECT_WALL_STEAM, + POBJECT_WALL_STEAM_SLOWMOTION, + POBJECT_DARK_SMOKE, + POBJECT_FIRE_HYDRANT, + POBJECT_CAR_WATER_SPLASH, + POBJECT_PED_WATER_SPLASH, + POBJECT_SPLASHES_AROUND, + POBJECT_SMALL_FIRE, + POBJECT_BIG_FIRE, + POBJECT_DRY_ICE, + POBJECT_DRY_ICE_SLOWMOTION, + POBJECT_FIRE_TRAIL, + POBJECT_SMOKE_TRAIL, + POBJECT_FIREBALL_AND_SMOKE, + POBJECT_ROCKET_TRAIL, + POBJECT_EXPLOSION_ONCE, + POBJECT_CATALINAS_GUNFLASH, + POBJECT_CATALINAS_SHOTGUNFLASH, +}; + +class CParticleObject +{ +public: + static void AddObject(uint16, const CVector &pos, bool remove); +}; diff --git a/src/PathFind.cpp b/src/PathFind.cpp new file mode 100644 index 00000000..c337ca88 --- /dev/null +++ b/src/PathFind.cpp @@ -0,0 +1,591 @@ +#include "common.h" +#include "patcher.h" +#include "PathFind.h" + +CPathFind &ThePaths = *(CPathFind*)0x8F6754; + +int TempListLength; + +enum +{ + NodeTypeExtern = 1, + NodeTypeIntern = 2, + + PathTypeCar = 0, + PathTypePed = 1, + + PathNodeFlag1 = 1, // used? + PathNodeFlag2 = 2, + PathNodeDeadEnd = 4, + PathNodeDisabled = 8, + PathNodeBetweenLevels = 0x10, +}; + +// link flags: +// 1: crosses road +// 2: ped traffic light +// pathnode flags: +// 1: +// 2: +// 4: dead end +// 8: switched off +// 10: road between levels?? +// navi node flags: +// 1: bridge light +// object flags: +// 1 +// 2 east/west road(?) + +CPathInfoForObject *&InfoForTileCars = *(CPathInfoForObject**)0x8F1A8C; +CPathInfoForObject *&InfoForTilePeds = *(CPathInfoForObject**)0x8F1AE4; +// unused +CTempDetachedNode *&DetachedNodesCars = *(CTempDetachedNode**)0x8E2824; +CTempDetachedNode *&DetachedNodesPeds = *(CTempDetachedNode**)0x8E28A0; + +void +CPathFind::PreparePathData(void) +{ + int i, j, k; + int numExtern, numIntern, numLanes; + float maxX, maxY; + CTempNode *tempNodes; + + printf("PreparePathData\n"); + // UNUSED: CPathFind::LoadPathFindData + if(InfoForTileCars && InfoForTilePeds && + DetachedNodesCars && DetachedNodesPeds){ + tempNodes = new CTempNode[4000]; + + m_numLinks = 0; + for(i = 0; i < PATHNODESIZE; i++) + m_pathNodes[i].flags &= ~(PathNodeFlag1 | PathNodeFlag2); + + for(i = 0; i < PATHNODESIZE; i++){ + numExtern = 0; + numIntern = 0; + for(j = 0; j < 12; j++){ + if(InfoForTileCars[i*12 + j].type == NodeTypeExtern) + numExtern++; + if(InfoForTileCars[i*12 + j].type == NodeTypeIntern) + numIntern++; + } + if(numIntern > 1 && numExtern != 2) + printf("ILLEGAL BLOCK. MORE THAN 1 INTERNALS AND NOT 2 EXTERNALS (Modelindex:%d)\n", i); + } + + for(i = 0; i < PATHNODESIZE; i++) + for(j = 0; j < 12; j++) + if(InfoForTileCars[i*12 + j].type == NodeTypeExtern){ + if(InfoForTileCars[i*12 + j].numLeftLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(InfoForTileCars[i*12 + j].numRightLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(InfoForTileCars[i*12 + j].numLeftLanes + InfoForTileCars[i*12 + j].numRightLanes <= 0) + printf("ILLEGAL BLOCK. NO LANES IN NODE (Obj:%d)\n", i); + } + + m_numPathNodes = 0; + PreparePathDataForType(PathTypeCar, tempNodes, InfoForTileCars, 1.0f, DetachedNodesCars, 100); + m_numCarPathNodes = m_numPathNodes; + PreparePathDataForType(PathTypePed, tempNodes, InfoForTilePeds, 1.0f, DetachedNodesPeds, 50); + m_numPedPathNodes = m_numPathNodes - m_numCarPathNodes; + + // TODO: figure out what exactly is going on here + // Some roads seem to get a west/east flag + for(i = 0; i < m_numMapObjects; i++){ + numExtern = 0; + numIntern = 0; + numLanes = 0; + maxX = 0.0f; + maxY = 0.0f; + for(j = 0; j < 12; j++){ + k = i*12 + j; + if(InfoForTileCars[k].type == NodeTypeExtern){ + numExtern++; + if(InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes > numLanes) + numLanes = InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes; + maxX = max(maxX, fabs(InfoForTileCars[k].x)); + maxY = max(maxY, fabs(InfoForTileCars[k].y)); + }else if(InfoForTileCars[k].type == NodeTypeIntern) + numIntern++; + } + + if(numIntern == 1 && numExtern == 2){ + if(numLanes < 4){ + if((i & 7) == 4){ // WHAT? + m_objectFlags[i] |= 1; + if(maxX > maxY) + m_objectFlags[i] |= 2; + else + m_objectFlags[i] &= ~2; + } + }else{ + m_objectFlags[i] |= 1; + if(maxX > maxY) + m_objectFlags[i] |= 2; + else + m_objectFlags[i] &= ~2; + } + } + } + + delete[] tempNodes; + + CountFloodFillGroups(PathTypeCar); + CountFloodFillGroups(PathTypePed); + + delete[] InfoForTileCars; + InfoForTileCars = nil; + delete[] InfoForTilePeds; + InfoForTilePeds = nil; + delete[] DetachedNodesCars; + DetachedNodesCars = nil; + delete[] DetachedNodesPeds; + DetachedNodesPeds = nil; + } + printf("Done with PreparePathData\n"); +} + +/* String together connected nodes in a list by a flood fill algorithm */ +void +CPathFind::CountFloodFillGroups(uint8 type) +{ + int start, end; + int i, l; + uint16 n; + CPathNode *node, *prev; + + switch(type){ + case PathTypeCar: + start = 0; + end = m_numCarPathNodes; + break; + case PathTypePed: + start = m_numCarPathNodes; + end = start + m_numPedPathNodes; + break; + } + + for(i = start; i < end; i++) + m_pathNodes[i].group = 0; + + n = 0; + for(;;){ + n++; + if(n > 1500){ + for(i = start; m_pathNodes[i].group && i < end; i++); + printf("NumNodes:%d Accounted for:%d\n", end - start, i - start); + } + + // Look for unvisited node + for(i = start; m_pathNodes[i].group && i < end; i++); + if(i == end) + break; + + node = &m_pathNodes[i]; + node->next = nil; + node->group = n; + + if(node->numLinks == 0){ + if(type == PathTypeCar) + printf("Single car node: %f %f %f (%d)\n", + node->pos.x, node->pos.y, node->pos.z, + m_mapObjects[node->objectIndex]->m_modelIndex); + else + printf("Single ped node: %f %f %f\n", + node->pos.x, node->pos.y, node->pos.z); + } + + while(node){ + prev = node; + node = node->next; + for(i = 0; i < prev->numLinks; i++){ + l = m_linkTo[prev->firstLink + i]; + if(m_pathNodes[l].group == 0){ + m_pathNodes[l].group = n; + if(m_pathNodes[l].group == 0) + m_pathNodes[l].group = 0x80; // ??? + m_pathNodes[l].next = node; + node = &m_pathNodes[l]; + } + } + } + } + + m_numGroups[type] = n-1; + printf("GraphType:%d. FloodFill groups:%d\n", type, n); +} + +void +CPathFind::PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, + float maxdist, CTempDetachedNode *detachednodes, int unused) +{ + static CVector CoorsXFormed; + int i, j, k, l; + int l1, l2; + int start, typeoff; + float posx, posy; + float dx, dy, mag; + float nearestDist; + int nearestId; + int next; + int oldNumPathNodes, oldNumLinks; + CVector dist; + int iseg, jseg; + int istart, jstart; + int done, cont; + + typeoff = 12*type; + oldNumPathNodes = m_numPathNodes; + oldNumLinks = m_numLinks; + + // Initialize map objects + for(i = 0; i < m_numMapObjects; i++) + for(j = 0; j < 12; j++) + m_mapObjects[i]->m_nodeIndicesCars[typeoff + j] = -1; + + // Calculate internal nodes, store them and connect them to defining object + for(i = 0; i < m_numMapObjects; i++){ + start = 12*m_mapObjects[i]->m_modelIndex; + for(j = 0; j < 12; j++){ + if(objectpathinfo[start + j].type != NodeTypeIntern) + continue; + CalcNodeCoors( + objectpathinfo[start + j].x, + objectpathinfo[start + j].y, + objectpathinfo[start + j].z, + i, + &CoorsXFormed); + m_pathNodes[m_numPathNodes].pos = CoorsXFormed; + m_pathNodes[m_numPathNodes].objectIndex = i; + m_pathNodes[m_numPathNodes].flags |= 1; + m_mapObjects[i]->m_nodeIndicesCars[typeoff + j] = m_numPathNodes++; + } + } + + // Insert external nodes into TempList + TempListLength = 0; + for(i = 0; i < m_numMapObjects; i++){ + start = 12*m_mapObjects[i]->m_modelIndex; + + for(j = 0; j < 12; j++){ + if(objectpathinfo[start + j].type != NodeTypeExtern) + continue; + CalcNodeCoors( + objectpathinfo[start + j].x, + objectpathinfo[start + j].y, + objectpathinfo[start + j].z, + i, + &CoorsXFormed); + + // find closest unconnected node + nearestId = -1; + nearestDist = maxdist; + for(k = 0; k < TempListLength; k++){ + if(tempnodes[k].linkState != 1) + continue; + dx = tempnodes[k].pos.x - CoorsXFormed.x; + if(fabs(dx) < nearestDist){ + dy = tempnodes[k].pos.y - CoorsXFormed.y; + if(fabs(dy) < nearestDist){ + nearestDist = max(fabs(dx), fabs(dy)); + nearestId = k; + } + } + } + + if(nearestId < 0){ + // None found, add this one to temp list + tempnodes[TempListLength].pos = CoorsXFormed; + next = objectpathinfo[start + j].next; + if(next < 0){ + // no link from this node, find link to this node + next = 0; + for(k = start; j != objectpathinfo[k].next; k++) + next++; + } + // link to connecting internal node + tempnodes[TempListLength].link1 = m_mapObjects[i]->m_nodeIndicesCars[typeoff + next]; + if(type == PathTypeCar){ + tempnodes[TempListLength].numLeftLanes = objectpathinfo[start + j].numLeftLanes; + tempnodes[TempListLength].numRightLanes = objectpathinfo[start + j].numRightLanes; + } + tempnodes[TempListLength++].linkState = 1; + }else{ + // Found nearest, connect it to our neighbour + next = objectpathinfo[start + j].next; + if(next < 0){ + // no link from this node, find link to this node + next = 0; + for(k = start; j != objectpathinfo[k].next; k++) + next++; + } + tempnodes[nearestId].link2 = m_mapObjects[i]->m_nodeIndicesCars[typeoff + next]; + tempnodes[nearestId].linkState = 2; + + // collapse this node with nearest we found + dx = m_pathNodes[tempnodes[nearestId].link1].pos.x - m_pathNodes[tempnodes[nearestId].link2].pos.x; + dy = m_pathNodes[tempnodes[nearestId].link1].pos.y - m_pathNodes[tempnodes[nearestId].link2].pos.y; + tempnodes[nearestId].pos.x = (tempnodes[nearestId].pos.x + CoorsXFormed.x)*0.5f; + tempnodes[nearestId].pos.y = (tempnodes[nearestId].pos.y + CoorsXFormed.y)*0.5f; + tempnodes[nearestId].pos.z = (tempnodes[nearestId].pos.z + CoorsXFormed.z)*0.5f; + mag = sqrt(dx*dx + dy*dy); + tempnodes[nearestId].dirX = dx/mag; + tempnodes[nearestId].dirY = dy/mag; + // do something when number of lanes doesn't agree + if(type == PathTypeCar) + if(tempnodes[nearestId].numLeftLanes != 0 && tempnodes[nearestId].numRightLanes != 0 && + (objectpathinfo[start + j].numLeftLanes == 0 || objectpathinfo[start + j].numRightLanes == 0)){ + tempnodes[nearestId].numLeftLanes = objectpathinfo[start + j].numLeftLanes; + tempnodes[nearestId].numRightLanes = objectpathinfo[start + j].numRightLanes; + } + } + } + } + + // Loop through previously added internal nodes and link them + for(i = oldNumPathNodes; i < m_numPathNodes; i++){ + // Init link + m_pathNodes[i].numLinks = 0; + m_pathNodes[i].firstLink = m_numLinks; + + // See if node connects to external nodes + for(j = 0; j < TempListLength; j++){ + if(tempnodes[j].linkState != 2) + continue; + + // Add link to other side of the external + if(tempnodes[j].link1 == i) + m_linkTo[m_numLinks] = tempnodes[j].link2; + else if(tempnodes[j].link2 == i) + m_linkTo[m_numLinks] = tempnodes[j].link1; + else + continue; + + dist = m_pathNodes[i].pos - m_pathNodes[m_linkTo[m_numLinks]].pos; + m_distTo[m_numLinks] = dist.Magnitude(); + m_linkFlags[m_numLinks] = 0; + + if(type == PathTypeCar){ + // IMPROVE: use a goto here + // Find existing navi node + for(k = 0; k < m_numNaviNodes; k++){ + if(m_naviNodes[k].dirX == tempnodes[j].dirX && + m_naviNodes[k].dirY == tempnodes[j].dirY && + m_naviNodes[k].posX == tempnodes[j].pos.x && + m_naviNodes[k].posY == tempnodes[j].pos.y){ + m_naviNodeLinks[m_numLinks] = k; + k = m_numNaviNodes; + } + } + // k is m_numNaviNodes+1 if we found one + if(k == m_numNaviNodes){ + m_naviNodes[m_numNaviNodes].dirX = tempnodes[j].dirX; + m_naviNodes[m_numNaviNodes].dirY = tempnodes[j].dirY; + m_naviNodes[m_numNaviNodes].posX = tempnodes[j].pos.x; + m_naviNodes[m_numNaviNodes].posY = tempnodes[j].pos.y; + m_naviNodes[m_numNaviNodes].pathNodeIndex = i; + m_naviNodes[m_numNaviNodes].numLeftLanes = tempnodes[j].numLeftLanes; + m_naviNodes[m_numNaviNodes].numRightLanes = tempnodes[j].numRightLanes; + m_naviNodes[m_numNaviNodes].trafficLightType = 0; + m_naviNodeLinks[m_numLinks] = m_numNaviNodes++; + } + } + + m_pathNodes[i].numLinks++; + m_numLinks++; + } + + // Find i inside path segment + iseg = 0; + for(j = max(oldNumPathNodes, i-12); j < i; j++) + if(m_pathNodes[j].objectIndex == m_pathNodes[i].objectIndex) + iseg++; + + istart = 12*m_mapObjects[m_pathNodes[i].objectIndex]->m_modelIndex; + // Add links to other internal nodes + for(j = max(oldNumPathNodes, i-12); j < min(m_numPathNodes, i+12); j++){ + if(m_pathNodes[i].objectIndex != m_pathNodes[j].objectIndex || i == j) + continue; + // N.B.: in every path segment, the externals have to be at the end + jseg = j-i + iseg; + + jstart = 12*m_mapObjects[m_pathNodes[j].objectIndex]->m_modelIndex; + if(objectpathinfo[istart + iseg].next == jseg || + objectpathinfo[jstart + jseg].next == iseg){ + // Found a link between i and j + m_linkTo[m_numLinks] = j; + dist = m_pathNodes[i].pos - m_pathNodes[j].pos; + m_distTo[m_numLinks] = dist.Magnitude(); + + if(type == PathTypeCar){ + posx = (m_pathNodes[i].pos.x + m_pathNodes[j].pos.x)*0.5f; + posy = (m_pathNodes[i].pos.y + m_pathNodes[j].pos.y)*0.5f; + dx = m_pathNodes[j].pos.x - m_pathNodes[i].pos.x; + dy = m_pathNodes[j].pos.y - m_pathNodes[i].pos.y; + mag = sqrt(dx*dx + dy*dy); + dx /= mag; + dy /= mag; + if(i < j){ + dx = -dx; + dy = -dy; + } + // IMPROVE: use a goto here + // Find existing navi node + for(k = 0; k < m_numNaviNodes; k++){ + if(m_naviNodes[k].dirX == dx && + m_naviNodes[k].dirY == dy && + m_naviNodes[k].posX == posx && + m_naviNodes[k].posY == posy){ + m_naviNodeLinks[m_numLinks] = k; + k = m_numNaviNodes; + } + } + // k is m_numNaviNodes+1 if we found one + if(k == m_numNaviNodes){ + m_naviNodes[m_numNaviNodes].dirX = dx; + m_naviNodes[m_numNaviNodes].dirY = dy; + m_naviNodes[m_numNaviNodes].posX = posx; + m_naviNodes[m_numNaviNodes].posY = posy; + m_naviNodes[m_numNaviNodes].pathNodeIndex = i; + m_naviNodes[m_numNaviNodes].numLeftLanes = -1; + m_naviNodes[m_numNaviNodes].numRightLanes = -1; + m_naviNodes[m_numNaviNodes].trafficLightType = 0; + m_naviNodeLinks[m_numLinks] = m_numNaviNodes++; + } + }else{ + // Crosses road + if(objectpathinfo[istart + iseg].next == jseg && objectpathinfo[istart + iseg].flag & 1 || + objectpathinfo[jstart + jseg].next == iseg && objectpathinfo[jstart + jseg].flag & 1) + m_linkFlags[m_numLinks] |= 1; + else + m_linkFlags[m_numLinks] &= ~1; + } + + m_pathNodes[i].numLinks++; + m_numLinks++; + } + } + } + + if(type == PathTypeCar){ + done = 0; + // Set number of lanes for all nodes somehow + // very strange code + for(k = 0; !done && k < 10; k++){ + done = 1; + for(i = 0; i < m_numPathNodes; i++){ + if(m_pathNodes[i].numLinks != 2) + continue; + l1 = m_naviNodeLinks[m_pathNodes[i].firstLink]; + l2 = m_naviNodeLinks[m_pathNodes[i].firstLink+1]; + + if(m_naviNodes[l1].numLeftLanes == -1 && + m_naviNodes[l2].numLeftLanes != -1){ + done = 0; + if(m_naviNodes[l2].pathNodeIndex == i){ + // why switch left and right here? + m_naviNodes[l1].numLeftLanes = m_naviNodes[l2].numRightLanes; + m_naviNodes[l1].numRightLanes = m_naviNodes[l2].numLeftLanes; + }else{ + m_naviNodes[l1].numLeftLanes = m_naviNodes[l2].numLeftLanes; + m_naviNodes[l1].numRightLanes = m_naviNodes[l2].numRightLanes; + } + m_naviNodes[l1].pathNodeIndex = i; + }else if(m_naviNodes[l1].numLeftLanes != -1 && + m_naviNodes[l2].numLeftLanes == -1){ + done = 0; + if(m_naviNodes[l1].pathNodeIndex == i){ + // why switch left and right here? + m_naviNodes[l2].numLeftLanes = m_naviNodes[l1].numRightLanes; + m_naviNodes[l2].numRightLanes = m_naviNodes[l1].numLeftLanes; + }else{ + m_naviNodes[l2].numLeftLanes = m_naviNodes[l1].numLeftLanes; + m_naviNodes[l2].numRightLanes = m_naviNodes[l1].numRightLanes; + } + m_naviNodes[l2].pathNodeIndex = i; + }else if(m_naviNodes[l1].numLeftLanes == -1 && + m_naviNodes[l2].numLeftLanes == -1) + done = 0; + } + } + + // Fall back to default values for number of lanes + for(i = 0; i < m_numPathNodes; i++) + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + k = m_naviNodeLinks[m_pathNodes[i].firstLink + j]; + if(m_naviNodes[k].numLeftLanes < 0) + m_naviNodes[k].numLeftLanes = 1; + if(m_naviNodes[k].numRightLanes < 0) + m_naviNodes[k].numRightLanes = 1; + } + } + + // Set flags for car nodes + if(type == PathTypeCar){ + do{ + cont = 0; + for(i = 0; i < m_numPathNodes; i++){ + m_pathNodes[i].flags &= ~PathNodeDisabled; + m_pathNodes[i].flags &= ~PathNodeBetweenLevels; + // See if node is a dead end, if so, we're not done yet + if((m_pathNodes[i].flags & PathNodeDeadEnd) == 0){ + k = 0; + for(j = 0; j < m_pathNodes[i].numLinks; j++) + if((m_pathNodes[m_linkTo[m_pathNodes[i].firstLink + j]].flags & PathNodeDeadEnd) == 0) + k++; + if(k < 2){ + m_pathNodes[i].flags |= PathNodeDeadEnd; + cont = 1; + } + } + } + }while(cont); + } + + // Remove isolated ped nodes + if(type == PathTypePed) + for(i = oldNumPathNodes; i < m_numPathNodes; i++){ + if(m_pathNodes[i].numLinks != 0) + continue; + + // Remove node + for(j = i; j < m_numPathNodes-1; j++) + m_pathNodes[j] = m_pathNodes[j+1]; + + // Fix links + for(j = oldNumLinks; j < m_numLinks; j++) + if(m_linkTo[j] >= i) + m_linkTo[j]--; + + // Also in treadables + for(j = 0; j < m_numMapObjects; j++) + for(k = 0; k < 12; k++){ + if(m_mapObjects[j]->m_nodeIndicesPeds[k] == i){ + // remove this one + for(l = k; l < 12-1; l++) + m_mapObjects[j]->m_nodeIndicesPeds[l] = m_mapObjects[j]->m_nodeIndicesPeds[l+1]; + m_mapObjects[j]->m_nodeIndicesPeds[11] = -1; + }else if(m_mapObjects[j]->m_nodeIndicesPeds[k] > i) + m_mapObjects[j]->m_nodeIndicesPeds[k]--; + } + + i--; + m_numPathNodes--; + } +} + +void +CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int id, CVector *out) +{ + CVector pos; + pos.x = x / 16.0f; + pos.y = y / 16.0f; + pos.z = z / 16.0f; + *out = m_mapObjects[id]->GetMatrix() * pos; +} + +STARTPATCHES + InjectHook(0x429610, &CPathFind::PreparePathData, PATCH_JUMP); + InjectHook(0x429C20, &CPathFind::PreparePathDataForType, PATCH_JUMP); +ENDPATCHES diff --git a/src/PathFind.h b/src/PathFind.h new file mode 100644 index 00000000..f4857ce2 --- /dev/null +++ b/src/PathFind.h @@ -0,0 +1,130 @@ +#pragma once + +#include "Treadable.h" + +struct CPathNode +{ + CVector pos; + CPathNode *prev; //? + CPathNode *next; + int16 unknown; + int16 objectIndex; + int16 firstLink; + uint8 numLinks; + uint8 flags; + uint8 group; +/* VC: + int16 unk1; + int16 nextIndex; + int16 x; + int16 y; + int16 z; + int16 unknown; + int16 firstLink; + int8 width; + int8 group; + int8 numLinks : 4; + int8 bDeadEnd : 1; + int8 bTurnedOff : 1; // flag 8 in node info + int8 flagA40 : 1; // flag 20 in node info + int8 flagA80 : 1; // flag 4 in node info + int8 flagB1 : 1; // flag 10 in node info + int8 flagB2 : 1; // flag 2 in node info + int8 flagB4 : 1; + int8 speedLimit : 2; // speed limit + int8 flagB20 : 1; + int8 flagB40 : 1; + int8 flagB80 : 1; + int8 spawnRate : 4; + int8 flagsC : 4; +*/ +}; + +// TODO: name? +struct NaviNode +{ + float posX; + float posY; + float dirX; + float dirY; + int16 pathNodeIndex; + int8 numLeftLanes; + int8 numRightLanes; + int8 trafficLightType; + // probably only padding + int8 field15; + int8 field16; + int8 field17; +}; + +struct CPathInfoForObject +{ + int16 x; + int16 y; + int16 z; + int8 type; + int8 next; + int8 numLeftLanes; + int8 numRightLanes; + uint8 flag; +}; + +struct CTempNode +{ + CVector pos; + float dirX; + float dirY; + int16 link1; + int16 link2; + int8 numLeftLanes; + int8 numRightLanes; + int8 linkState; + // probably padding + int8 field1B; +}; + +struct CTempDetachedNode // unused +{ + uint8 foo[20]; +}; + +class CPathFind +{ +public: +/* For reference VC: + CPathNode pathNodes[9650]; + NaviNode naviNodes[3500]; + CBuilding *mapObjects[1250]; + // 0x8000 is cross road flag + // 0x4000 is traffic light flag + uint16 linkTo[20400]; + uint8 distTo[20400]; + int16 naviNodeLinks[20400]; +*/ + CPathNode m_pathNodes[4930]; + NaviNode m_naviNodes[2076]; + CTreadable *m_mapObjects[1250]; + uint8 m_objectFlags[1250]; + int16 m_linkTo[10260]; + int16 m_distTo[10260]; + uint8 m_linkFlags[10260]; + int16 m_naviNodeLinks[10260]; + int32 m_numPathNodes; + int32 m_numCarPathNodes; + int32 m_numPedPathNodes; + int16 m_numMapObjects; + int16 m_numLinks; + int32 m_numNaviNodes; + int32 h; + uint8 m_numGroups[2]; + CPathNode m_aExtraPaths[872]; + + void PreparePathData(void); + void CountFloodFillGroups(uint8 type); + void PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, + float unk, CTempDetachedNode *detachednodes, int unused); + void CalcNodeCoors(int16 x, int16 y, int16 z, int32 id, CVector *out); +}; +static_assert(sizeof(CPathFind) == 0x4c8f4, "CPathFind: error"); + +extern CPathFind &ThePaths; diff --git a/src/Placeable.cpp b/src/Placeable.cpp new file mode 100644 index 00000000..43708d3e --- /dev/null +++ b/src/Placeable.cpp @@ -0,0 +1,81 @@ +#include "common.h" +#include "Placeable.h" +#include "patcher.h" + +CPlaceable::CPlaceable(void) +{ + m_matrix.SetScale(1.0f); +} + +CPlaceable::~CPlaceable(void) { } + +void +CPlaceable::SetHeading(float angle) +{ + CVector pos = GetPosition(); + m_matrix.SetRotateZ(angle); + GetPosition() += pos; +} + +bool +CPlaceable::IsWithinArea(float x1, float y1, float x2, float y2) +{ + float x, xmin, xmax; + float y, ymin, ymax; + xmin = x1; + xmax = x2; + ymin = y1; + ymax = y2; + if(x2 > x1){ + xmin = x2; + xmax = x1; + } + if(y2 > y1){ + ymin = y2; + ymax = y1; + } + x = GetPosition().x; + y = GetPosition().y; + return xmin <= x && x <= xmax && + ymin <= y && y <= ymax; +} + +bool +CPlaceable::IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2) +{ + float x, xmin, xmax; + float y, ymin, ymax; + float z, zmin, zmax; + xmin = x1; + xmax = x2; + ymin = y1; + ymax = y2; + zmin = z1; + zmax = z2; + if(x2 > x1){ + xmin = x2; + xmax = x1; + } + if(y2 > y1){ + ymin = y2; + ymax = y1; + } + if(z2 > z1){ + zmin = z2; + zmax = z1; + } + x = GetPosition().x; + y = GetPosition().y; + z = GetPosition().z; + return xmin <= x && x <= xmax && + ymin <= y && y <= ymax && + zmin <= z && z <= zmax; +} + +STARTPATCHES + InjectHook(0x49F9A0, &CPlaceable::ctor, PATCH_JUMP); + InjectHook(0x49F9E0, &CPlaceable::dtor, PATCH_JUMP); + InjectHook(0x49FA00, &CPlaceable::SetHeading, PATCH_JUMP); + InjectHook(0x49FA50, (bool (CPlaceable::*)(float, float, float, float))&CPlaceable::IsWithinArea, PATCH_JUMP); + InjectHook(0x49FAF0, (bool (CPlaceable::*)(float, float, float, float, float, float))&CPlaceable::IsWithinArea, PATCH_JUMP); +ENDPATCHES diff --git a/src/Placeable.h b/src/Placeable.h new file mode 100644 index 00000000..bca9462d --- /dev/null +++ b/src/Placeable.h @@ -0,0 +1,26 @@ +#pragma once + +class CPlaceable +{ + // disable allocation + static void *operator new(size_t) { assert(0); return nil; } + static void operator delete(void*, size_t) { assert(0); } +public: + CMatrix m_matrix; + + CPlaceable(void); + virtual ~CPlaceable(void); + CVector &GetPosition(void) { return *m_matrix.GetPosition(); } + CVector &GetRight(void) { return *m_matrix.GetRight(); } + CVector &GetForward(void) { return *m_matrix.GetForward(); } + CVector &GetUp(void) { return *m_matrix.GetUp(); } + CMatrix &GetMatrix(void) { return m_matrix; } + void SetTransform(RwMatrix *m) { m_matrix = CMatrix(m, false); } + void SetHeading(float angle); + bool IsWithinArea(float x1, float y1, float x2, float y2); + bool IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2); + + CPlaceable *ctor(void) { return ::new (this) CPlaceable(); } + void dtor(void) { this->CPlaceable::~CPlaceable(); } +}; +static_assert(sizeof(CPlaceable) == 0x4C, "CPlaceable: error"); diff --git a/src/Pools.cpp b/src/Pools.cpp new file mode 100644 index 00000000..106715e4 --- /dev/null +++ b/src/Pools.cpp @@ -0,0 +1,19 @@ +#include "common.h" +#include "Pools.h" + +CCPtrNodePool *&CPools::ms_pPtrNodePool = *(CCPtrNodePool**)0x943044; +CEntryInfoNodePool *&CPools::ms_pEntryInfoNodePool = *(CEntryInfoNodePool**)0x941448; +CBuildingPool *&CPools::ms_pBuildingPool = *(CBuildingPool**)0x8F2C04; +CTreadablePool *&CPools::ms_pTreadablePool = *(CTreadablePool**)0x8F2568; +CObjectPool *&CPools::ms_pObjectPool = *(CObjectPool**)0x880E28; + +void +CPools::Initialise(void) +{ + // TODO: unused right now + ms_pPtrNodePool = new CCPtrNodePool(NUMPTRNODES); + ms_pEntryInfoNodePool = new CEntryInfoNodePool(NUMENTRYINFOS); + ms_pBuildingPool = new CBuildingPool(NUMBUILDINGS); + ms_pTreadablePool = new CTreadablePool(NUMTREADABLES); + ms_pObjectPool = new CObjectPool(NUMOBJECTS); +} diff --git a/src/Pools.h b/src/Pools.h new file mode 100644 index 00000000..aa804788 --- /dev/null +++ b/src/Pools.h @@ -0,0 +1,34 @@ +#pragma once + +#include "templates.h" +#include "Lists.h" +#include "Treadable.h" +#include "Object.h" +#include "CutsceneHead.h" + +typedef CPool CCPtrNodePool; +typedef CPool CEntryInfoNodePool; +typedef CPool CBuildingPool; +typedef CPool CTreadablePool; +typedef CPool CObjectPool; + +class CPools +{ + static CCPtrNodePool *&ms_pPtrNodePool; + static CEntryInfoNodePool *&ms_pEntryInfoNodePool; + // ms_pPedPool + // ms_pVehiclePool + static CBuildingPool *&ms_pBuildingPool; + static CTreadablePool *&ms_pTreadablePool; + static CObjectPool *&ms_pObjectPool; + // ms_pDummyPool + // ms_pAudioScriptObjectPool +public: + static CCPtrNodePool *GetPtrNodePool(void) { return ms_pPtrNodePool; } + static CEntryInfoNodePool *GetEntryInfoNodePool(void) { return ms_pEntryInfoNodePool; } + static CBuildingPool *GetBuildingPool(void) { return ms_pBuildingPool; } + static CTreadablePool *GetTreadablePool(void) { return ms_pTreadablePool; } + static CObjectPool *GetObjectPool(void) { return ms_pObjectPool; } + + static void Initialise(void); +}; diff --git a/src/References.cpp b/src/References.cpp new file mode 100644 index 00000000..187f4ffa --- /dev/null +++ b/src/References.cpp @@ -0,0 +1,22 @@ +#include "common.h" +#include "patcher.h" +#include "References.h" + +CReference *CReferences::aRefs = (CReference*)0x70BBE0; //[NUMREFERENCES]; +CReference *&CReferences::pEmptyList = *(CReference**)0x8F1AF8; + +void +CReferences::Init(void) +{ + int i; + pEmptyList = &aRefs[0]; + for(i = 0; i < NUMREFERENCES; i++){ + aRefs[i].pentity = nil; + aRefs[i].next = &aRefs[i+1]; + } + aRefs[NUMREFERENCES-1].next = nil; +} + +STARTPATCHES + InjectHook(0x4A7350, CReferences::Init, PATCH_JUMP); +ENDPATCHES diff --git a/src/References.h b/src/References.h new file mode 100644 index 00000000..5ee20d38 --- /dev/null +++ b/src/References.h @@ -0,0 +1,17 @@ +#pragma once + +class CEntity; + +struct CReference +{ + CReference *next; + CEntity **pentity; +}; + +class CReferences +{ + static CReference *aRefs; //[NUMREFERENCES]; +public: + static CReference *&pEmptyList; + static void Init(void); +}; diff --git a/src/RwHelper.cpp b/src/RwHelper.cpp new file mode 100644 index 00000000..c8782f9e --- /dev/null +++ b/src/RwHelper.cpp @@ -0,0 +1,19 @@ +#include "common.h" + + +RwObject* +GetFirstObjectCallback(RwObject *object, void *data) +{ + *(RwObject**)data = object; + return nil; +} + +RwObject* +GetFirstObject(RwFrame *frame) +{ + RwObject *obj; + + obj = nil; + RwFrameForAllObjects(frame, GetFirstObjectCallback, &obj); + return obj; +} diff --git a/src/RwHelper.h b/src/RwHelper.h new file mode 100644 index 00000000..90852b08 --- /dev/null +++ b/src/RwHelper.h @@ -0,0 +1,3 @@ +#pragma once + +RwObject *GetFirstObject(RwFrame *frame); diff --git a/src/Streaming.cpp b/src/Streaming.cpp new file mode 100644 index 00000000..47258c9d --- /dev/null +++ b/src/Streaming.cpp @@ -0,0 +1,10 @@ +#include "common.h" +#include "patcher.h" +#include "Streaming.h" + +bool &CStreaming::ms_disableStreaming = *(bool*)0x95CD6E; +int32 &CStreaming::ms_numModelsRequested = *(int32*)0x8E2C10; +CStreamingInfo *CStreaming::ms_aInfoForModel = (CStreamingInfo*)0x6C7088; + +WRAPPER void CStreaming::RemoveModel(int32 id) { EAXJMP(0x408830); } +WRAPPER void CStreaming::RequestModel(int32 model, int32 flags) { EAXJMP(0x407EA0); } diff --git a/src/Streaming.h b/src/Streaming.h new file mode 100644 index 00000000..1acfc261 --- /dev/null +++ b/src/Streaming.h @@ -0,0 +1,54 @@ +#pragma once + +enum { + STREAM_OFFSET_MODEL = 0, + STREAM_OFFSET_TXD = STREAM_OFFSET_MODEL+MODELINFOSIZE, + NUMSTREAMINFO = STREAM_OFFSET_TXD+TXDSTORESIZE +}; + +enum StreamFlags +{ + STREAM_DONT_REMOVE = 0x01, + STREAM_SCRIPTOWNED = 0x02, + STREAM_DEPENDENCY = 0x04, + STREAM_PRIORITY = 0x08, + STREAM_NOFADE = 0x10, +}; + +enum StreamLoadState +{ + STREAM_NOTLOADED = 0, + STREAM_LOADED = 1, + STREAM_INQUEUE = 2, + STREAM_READING = 3, // what is this? + STREAM_BIGFILE = 4, +}; + +class CStreamingInfo +{ +public: + CStreamingInfo *m_next; + CStreamingInfo *m_prev; + uint8 m_loadState; + uint8 m_flags; + + int16 m_nextID; + uint32 m_position; + uint32 m_size; + +// bool GetCdPosnAndSize(uint32 *pos, uint32 *size); +// void SetCdPosnAndSize(uint32 pos, uint32 size); +// void AddToList(CStreamingInfo *link); +// void RemoveFromList(void); +}; + +class CStreaming +{ +public: + static bool &ms_disableStreaming; + static int32 &ms_numModelsRequested; + static CStreamingInfo *ms_aInfoForModel; //[NUMSTREAMINFO] + + static void RemoveModel(int32 id); + static void RequestModel(int32 model, int32 flags); +}; diff --git a/src/SurfaceTable.cpp b/src/SurfaceTable.cpp new file mode 100644 index 00000000..e9ef459a --- /dev/null +++ b/src/SurfaceTable.cpp @@ -0,0 +1,44 @@ +#include "common.h" +#include "patcher.h" +#include "SurfaceTable.h" + +int +CSurfaceTable::GetAdhesionGroup(uint8 surfaceType) +{ + switch(surfaceType){ + case SURFACE_0: return ADHESIVE_ROAD; + case SURFACE_1: return ADHESIVE_ROAD; + case SURFACE_2: return ADHESIVE_LOOSE; + case SURFACE_3: return ADHESIVE_LOOSE; + case SURFACE_4: return ADHESIVE_HARD; + case SURFACE_5: return ADHESIVE_ROAD; + case SURFACE_6: return ADHESIVE_HARD; + case SURFACE_7: return ADHESIVE_HARD; + case SURFACE_8: return ADHESIVE_HARD; + case SURFACE_9: return ADHESIVE_HARD; + case SURFACE_10: return ADHESIVE_HARD; + case SURFACE_11: return ADHESIVE_HARD; + case SURFACE_12: return ADHESIVE_HARD; + case SURFACE_13: return ADHESIVE_HARD; + case SURFACE_14: return ADHESIVE_HARD; + case SURFACE_15: return ADHESIVE_HARD; + case SURFACE_16: return ADHESIVE_HARD; + case SURFACE_17: return ADHESIVE_RUBBER; + case SURFACE_18: return ADHESIVE_LOOSE; + case SURFACE_19: return ADHESIVE_WET; + case SURFACE_20: return ADHESIVE_ROAD; + case SURFACE_21: return ADHESIVE_ROAD; + case SURFACE_22: return ADHESIVE_ROAD; + case SURFACE_23: return ADHESIVE_RUBBER; + case SURFACE_24: return ADHESIVE_HARD; + case SURFACE_25: return ADHESIVE_LOOSE; + case SURFACE_26: return ADHESIVE_LOOSE; + case SURFACE_27: return ADHESIVE_HARD; + case SURFACE_28: return ADHESIVE_HARD; + case SURFACE_29: return ADHESIVE_RUBBER; + case SURFACE_30: return ADHESIVE_LOOSE; + case SURFACE_31: return ADHESIVE_HARD; + case SURFACE_32: return ADHESIVE_HARD; + default: return ADHESIVE_ROAD; + } +} diff --git a/src/SurfaceTable.h b/src/SurfaceTable.h new file mode 100644 index 00000000..556a6e04 --- /dev/null +++ b/src/SurfaceTable.h @@ -0,0 +1,99 @@ +#pragma once + + +enum +{ + SURFACE_0, + SURFACE_1, + SURFACE_2, + SURFACE_3, + SURFACE_4, + SURFACE_5, + SURFACE_6, + SURFACE_7, + SURFACE_8, + SURFACE_9, + SURFACE_10, + SURFACE_11, + SURFACE_12, + SURFACE_13, + SURFACE_14, + SURFACE_15, + SURFACE_16, + SURFACE_17, + SURFACE_18, + SURFACE_19, + SURFACE_20, + SURFACE_21, + SURFACE_22, + SURFACE_23, + SURFACE_24, + SURFACE_25, + SURFACE_26, + SURFACE_27, + SURFACE_28, + SURFACE_29, + SURFACE_30, + SURFACE_31, + SURFACE_32, + + NUMSURFACETYPES +}; + +// From nick +// TODO: check and use this +enum eSurfaceType +{ + SURFACE_ROAD0, + SURFACE_ROAD1, + SURFACE_GRASS, + SURFACE_DIRT, + SURFACE_MUD, + SURFACE_PAVEMENT, + SURFACE_METAL6, + SURFACE_GLASS, + SURFACE_HARD8, + SURFACE_METAL_DOOR, + SURFACE_METAL10, + SURFACE_METAL11, + SURFACE_METAL12, + SURFACE_METAL13, + SURFACE_METAL14, + SURFACE_METAL15, + SURFACE_METAL_FENCE, + SURFACE_FLESH, + SURFACE_SAND18, + SURFACE_WATER, + SURFACE_WOOD, + SURFACE_WOOD_BOX, + SURFACE_WOOD_PLANK, + SURFACE_TIRE, + SURFACE_HARD24, + SURFACE_HEDGE, + SURFACE_STONE, + SURFACE_METAL27, + SURFACE_METAL28, + SURFACE_RUBBER29, + SURFACE_LOOSE30, + SURFACE_BOLLARD, + SURFACE_GATE, + SURFACE_SAND33, + SURFACE_ROAD34, +}; + +enum +{ + ADHESIVE_RUBBER, + ADHESIVE_HARD, + ADHESIVE_ROAD, + ADHESIVE_LOOSE, + ADHESIVE_WET, + + NUMADHESIVEGROUPS +}; + +class CSurfaceTable +{ +public: + static int GetAdhesionGroup(uint8 surfaceType); +}; diff --git a/src/Timecycle.cpp b/src/Timecycle.cpp new file mode 100644 index 00000000..10fd6bf6 --- /dev/null +++ b/src/Timecycle.cpp @@ -0,0 +1,92 @@ +#include "common.h" +#include "Timecycle.h" + +int (*CTimeCycle::m_nAmbientRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x86AF78; +int (*CTimeCycle::m_nAmbientGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x665308; +int (*CTimeCycle::m_nAmbientBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x72CF88; +int (*CTimeCycle::m_nDirectionalRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6FAB78; +int (*CTimeCycle::m_nDirectionalGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6F4528; +int (*CTimeCycle::m_nDirectionalBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x83CE58; +int (*CTimeCycle::m_nSkyTopRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87FB90; +int (*CTimeCycle::m_nSkyTopGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x8460A8; +int (*CTimeCycle::m_nSkyTopBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87B158; +int (*CTimeCycle::m_nSkyBottomRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6FA960; +int (*CTimeCycle::m_nSkyBottomGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x70D6A8; +int (*CTimeCycle::m_nSkyBottomBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x83D288; +int (*CTimeCycle::m_nSunCoreRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x878360; +int (*CTimeCycle::m_nSunCoreGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6EE088; +int (*CTimeCycle::m_nSunCoreBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x773A68; +int (*CTimeCycle::m_nSunCoronaRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x664B60; +int (*CTimeCycle::m_nSunCoronaGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6F01E0; +int (*CTimeCycle::m_nSunCoronaBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6E6340; +float (*CTimeCycle::m_fSunSize)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x733510; +float (*CTimeCycle::m_fSpriteSize)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x87F820; +float (*CTimeCycle::m_fSpriteBrightness)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x6E96F0; +short (*CTimeCycle::m_nShadowStrength)[NUMWEATHERS] = (short(*)[NUMWEATHERS])0x83CFD8; +short (*CTimeCycle::m_nLightShadowStrength)[NUMWEATHERS] = (short(*)[NUMWEATHERS])0x72B0F8; +short (*CTimeCycle::m_nTreeShadowStrength)[NUMWEATHERS] = (short(*)[NUMWEATHERS])0x733450; +float (*CTimeCycle::m_fFogStart)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x8806C8; +float (*CTimeCycle::m_fFarClip)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x8804E0; +float (*CTimeCycle::m_fLightsOnGroundBrightness)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x83D108; +int (*CTimeCycle::m_nLowCloudsRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x726770; +int (*CTimeCycle::m_nLowCloudsGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87BF08; +int (*CTimeCycle::m_nLowCloudsBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87FA10; +int (*CTimeCycle::m_nFluffyCloudsTopRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x70F2B0; +int (*CTimeCycle::m_nFluffyCloudsTopGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x72D288; +int (*CTimeCycle::m_nFluffyCloudsTopBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x86B108; +int (*CTimeCycle::m_nFluffyCloudsBottomRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6E8DA8; +int (*CTimeCycle::m_nFluffyCloudsBottomGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x715AA8; +int (*CTimeCycle::m_nFluffyCloudsBottomBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6EE2D0; +float (*CTimeCycle::m_fBlurRed)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x87C7E0; +float (*CTimeCycle::m_fBlurGreen)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x774C10; +float (*CTimeCycle::m_fBlurBlue)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x8784E0; +float (*CTimeCycle::m_fBlurAlpha)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x733690; + +float &CTimeCycle::m_fCurrentAmbientRed = *(float*)0x8F29B4; +float &CTimeCycle::m_fCurrentAmbientGreen = *(float*)0x94144C; +float &CTimeCycle::m_fCurrentAmbientBlue = *(float*)0x942FC0; +float &CTimeCycle::m_fCurrentDirectionalRed = *(float*)0x8F29D8; +float &CTimeCycle::m_fCurrentDirectionalGreen = *(float*)0x940594; +float &CTimeCycle::m_fCurrentDirectionalBlue = *(float*)0x942FAC; +int &CTimeCycle::m_nCurrentSkyTopRed = *(int*)0x9403C0; +int &CTimeCycle::m_nCurrentSkyTopGreen = *(int*)0x943074; +int &CTimeCycle::m_nCurrentSkyTopBlue = *(int*)0x8F29B8; +int &CTimeCycle::m_nCurrentSkyBottomRed = *(int*)0x9414D0; +int &CTimeCycle::m_nCurrentSkyBottomGreen = *(int*)0x8F2BD0; +int &CTimeCycle::m_nCurrentSkyBottomBlue = *(int*)0x8F625C; +int &CTimeCycle::m_nCurrentSunCoreRed = *(int*)0x8F2534; +int &CTimeCycle::m_nCurrentSunCoreGreen = *(int*)0x8F6264; +int &CTimeCycle::m_nCurrentSunCoreBlue = *(int*)0x94149C; +int &CTimeCycle::m_nCurrentSunCoronaRed = *(int*)0x8F2C1C; +int &CTimeCycle::m_nCurrentSunCoronaGreen = *(int*)0x885B54; +int &CTimeCycle::m_nCurrentSunCoronaBlue = *(int*)0x880F60; +float &CTimeCycle::m_fCurrentSunSize = *(float*)0x940588; +float &CTimeCycle::m_fCurrentSpriteSize = *(float*)0x8F1AA8; +float &CTimeCycle::m_fCurrentSpriteBrightness = *(float*)0x8F5FDC; +int &CTimeCycle::m_nCurrentShadowStrength = *(int*)0x95CC76; +int &CTimeCycle::m_nCurrentLightShadowStrength = *(int*)0x95CC66; +int &CTimeCycle::m_nCurrentTreeShadowStrength = *(int*)0x95CC86; +float &CTimeCycle::m_fCurrentFogStart = *(float*)0x8F1AE0; +float &CTimeCycle::m_fCurrentFarClip = *(float*)0x8F5FD8; +float &CTimeCycle::m_fCurrentLightsOnGroundBrightness = *(float*)0x8F1B60; +int &CTimeCycle::m_nCurrentLowCloudsRed = *(int*)0x95CB54; +int &CTimeCycle::m_nCurrentLowCloudsGreen = *(int*)0x95CB48; +int &CTimeCycle::m_nCurrentLowCloudsBlue = *(int*)0x95CC1C; +int &CTimeCycle::m_nCurrentFluffyCloudsTopRed = *(int*)0x8F2550; +int &CTimeCycle::m_nCurrentFluffyCloudsTopGreen = *(int*)0x8F59CC; +int &CTimeCycle::m_nCurrentFluffyCloudsTopBlue = *(int*)0x941434; +int &CTimeCycle::m_nCurrentFluffyCloudsBottomRed = *(int*)0x8F1A38; +int &CTimeCycle::m_nCurrentFluffyCloudsBottomGreen = *(int*)0x8E28B8; +int &CTimeCycle::m_nCurrentFluffyCloudsBottomBlue = *(int*)0x8F3960; +float &CTimeCycle::m_fCurrentBlurRed = *(float*)0x8F6000; +float &CTimeCycle::m_fCurrentBlurGreen = *(float*)0x9405A0; +float &CTimeCycle::m_fCurrentBlurBlue = *(float*)0x8F250C; +float &CTimeCycle::m_fCurrentBlurAlpha = *(float*)0x940728; +int &CTimeCycle::m_nCurrentFogColourRed = *(int*)0x940714; +int &CTimeCycle::m_nCurrentFogColourGreen = *(int*)0x8E2A60; +int &CTimeCycle::m_nCurrentFogColourBlue = *(int*)0x8F57EC; + +int &CTimeCycle::m_FogReduction = *(int*)0x880FB8; + +int &CTimeCycle::m_CurrentStoredValue = *(int*)0x94057C; +CVector *CTimeCycle::m_VectorToSun = (CVector*)0x665548; // [16] diff --git a/src/Timecycle.h b/src/Timecycle.h new file mode 100644 index 00000000..6946cc7c --- /dev/null +++ b/src/Timecycle.h @@ -0,0 +1,111 @@ +class CTimeCycle +{ + static int (*m_nAmbientRed)[NUMWEATHERS]; + static int (*m_nAmbientGreen)[NUMWEATHERS]; + static int (*m_nAmbientBlue)[NUMWEATHERS]; + + static int (*m_nDirectionalRed)[NUMWEATHERS]; + static int (*m_nDirectionalGreen)[NUMWEATHERS]; + static int (*m_nDirectionalBlue)[NUMWEATHERS]; + static int (*m_nSkyTopRed)[NUMWEATHERS]; + static int (*m_nSkyTopGreen)[NUMWEATHERS]; + static int (*m_nSkyTopBlue)[NUMWEATHERS]; + static int (*m_nSkyBottomRed)[NUMWEATHERS]; + static int (*m_nSkyBottomGreen)[NUMWEATHERS]; + static int (*m_nSkyBottomBlue)[NUMWEATHERS]; + static int (*m_nSunCoreRed)[NUMWEATHERS]; + static int (*m_nSunCoreGreen)[NUMWEATHERS]; + static int (*m_nSunCoreBlue)[NUMWEATHERS]; + static int (*m_nSunCoronaRed)[NUMWEATHERS]; + static int (*m_nSunCoronaGreen)[NUMWEATHERS]; + static int (*m_nSunCoronaBlue)[NUMWEATHERS]; + static float (*m_fSunSize)[NUMWEATHERS]; + static float (*m_fSpriteSize)[NUMWEATHERS]; + static float (*m_fSpriteBrightness)[NUMWEATHERS]; + static short (*m_nShadowStrength)[NUMWEATHERS]; + static short (*m_nLightShadowStrength)[NUMWEATHERS]; + static short (*m_nTreeShadowStrength)[NUMWEATHERS]; + static float (*m_fFogStart)[NUMWEATHERS]; + static float (*m_fFarClip)[NUMWEATHERS]; + static float (*m_fLightsOnGroundBrightness)[NUMWEATHERS]; + static int (*m_nLowCloudsRed)[NUMWEATHERS]; + static int (*m_nLowCloudsGreen)[NUMWEATHERS]; + static int (*m_nLowCloudsBlue)[NUMWEATHERS]; + static int (*m_nFluffyCloudsTopRed)[NUMWEATHERS]; + static int (*m_nFluffyCloudsTopGreen)[NUMWEATHERS]; + static int (*m_nFluffyCloudsTopBlue)[NUMWEATHERS]; + static int (*m_nFluffyCloudsBottomRed)[NUMWEATHERS]; + static int (*m_nFluffyCloudsBottomGreen)[NUMWEATHERS]; + static int (*m_nFluffyCloudsBottomBlue)[NUMWEATHERS]; + static float (*m_fBlurRed)[NUMWEATHERS]; + static float (*m_fBlurGreen)[NUMWEATHERS]; + static float (*m_fBlurBlue)[NUMWEATHERS]; + static float (*m_fBlurAlpha)[NUMWEATHERS]; + + static float &m_fCurrentAmbientRed; + static float &m_fCurrentAmbientGreen; + static float &m_fCurrentAmbientBlue; + static float &m_fCurrentDirectionalRed; + static float &m_fCurrentDirectionalGreen; + static float &m_fCurrentDirectionalBlue; + static int &m_nCurrentSkyTopRed; + static int &m_nCurrentSkyTopGreen; + static int &m_nCurrentSkyTopBlue; + static int &m_nCurrentSkyBottomRed; + static int &m_nCurrentSkyBottomGreen; + static int &m_nCurrentSkyBottomBlue; + static int &m_nCurrentSunCoreRed; + static int &m_nCurrentSunCoreGreen; + static int &m_nCurrentSunCoreBlue; + static int &m_nCurrentSunCoronaRed; + static int &m_nCurrentSunCoronaGreen; + static int &m_nCurrentSunCoronaBlue; + static float &m_fCurrentSunSize; + static float &m_fCurrentSpriteSize; + static float &m_fCurrentSpriteBrightness; + static int &m_nCurrentShadowStrength; + static int &m_nCurrentLightShadowStrength; + static int &m_nCurrentTreeShadowStrength; + static float &m_fCurrentFogStart; + static float &m_fCurrentFarClip; + static float &m_fCurrentLightsOnGroundBrightness; + static int &m_nCurrentLowCloudsRed; + static int &m_nCurrentLowCloudsGreen; + static int &m_nCurrentLowCloudsBlue; + static int &m_nCurrentFluffyCloudsTopRed; + static int &m_nCurrentFluffyCloudsTopGreen; + static int &m_nCurrentFluffyCloudsTopBlue; + static int &m_nCurrentFluffyCloudsBottomRed; + static int &m_nCurrentFluffyCloudsBottomGreen; + static int &m_nCurrentFluffyCloudsBottomBlue; + static float &m_fCurrentBlurRed; + static float &m_fCurrentBlurGreen; + static float &m_fCurrentBlurBlue; + static float &m_fCurrentBlurAlpha; + static int &m_nCurrentFogColourRed; + static int &m_nCurrentFogColourGreen; + static int &m_nCurrentFogColourBlue; + + static int &m_FogReduction; + +public: + static int &m_CurrentStoredValue; + static CVector *m_VectorToSun; // [16] + + static float GetAmbientRed(void) { return m_fCurrentAmbientRed; } + static float GetAmbientGreen(void) { return m_fCurrentAmbientGreen; } + static float GetAmbientBlue(void) { return m_fCurrentAmbientBlue; } + static float GetDirectionalRed(void) { return m_fCurrentDirectionalRed; } + static float GetDirectionalGreen(void) { return m_fCurrentDirectionalGreen; } + static float GetDirectionalBlue(void) { return m_fCurrentDirectionalBlue; } + static int GetLowCloudsRed(void) { return m_nCurrentLowCloudsRed; } + static int GetLowCloudsGreen(void) { return m_nCurrentLowCloudsGreen; } + static int GetLowCloudsBlue(void) { return m_nCurrentLowCloudsBlue; } + static int GetFluffyCloudsTopRed(void) { return m_nCurrentFluffyCloudsTopRed; } + static int GetFluffyCloudsTopGreen(void) { return m_nCurrentFluffyCloudsTopGreen; } + static int GetFluffyCloudsTopBlue(void) { return m_nCurrentFluffyCloudsTopBlue; } + static int GetFluffyCloudsBottomRed(void) { return m_nCurrentFluffyCloudsBottomRed; } + static int GetFluffyCloudsBottomGreen(void) { return m_nCurrentFluffyCloudsBottomGreen; } + static int GetFluffyCloudsBottomBlue(void) { return m_nCurrentFluffyCloudsBottomBlue; } + +}; diff --git a/src/Timer.cpp b/src/Timer.cpp new file mode 100644 index 00000000..9cc35e42 --- /dev/null +++ b/src/Timer.cpp @@ -0,0 +1,14 @@ +#include "common.h" +#include "patcher.h" +#include "Timer.h" + +uint32 &CTimer::m_snTimeInMilliseconds = *(uint32*)0x885B48; +uint32 &CTimer::m_snTimeInMillisecondsPauseMode = *(uint32*)0x5F7614; +uint32 &CTimer::m_snTimeInMillisecondsNonClipped = *(uint32*)0x9412E8; +uint32 &CTimer::m_snPreviousTimeInMilliseconds = *(uint32*)0x8F29E4; +uint32 &CTimer::m_FrameCounter = *(uint32*)0x9412EC; +float &CTimer::ms_fTimeScale = *(float*)0x8F2C20; +float &CTimer::ms_fTimeStep = *(float*)0x8E2CB4; +float &CTimer::ms_fTimeStepNonClipped = *(float*)0x8E2C4C; +bool &CTimer::m_UserPause = *(bool*)0x95CD7C; +bool &CTimer::m_CodePause = *(bool*)0x95CDB1; diff --git a/src/Timer.h b/src/Timer.h new file mode 100644 index 00000000..a96574ce --- /dev/null +++ b/src/Timer.h @@ -0,0 +1,19 @@ +#pragma once + +class CTimer +{ + static uint32 &m_snTimeInMilliseconds; + static uint32 &m_snTimeInMillisecondsPauseMode; + static uint32 &m_snTimeInMillisecondsNonClipped; + static uint32 &m_snPreviousTimeInMilliseconds; + static uint32 &m_FrameCounter; + static float &ms_fTimeScale; + static float &ms_fTimeStep; + static float &ms_fTimeStepNonClipped; + static bool &m_UserPause; + static bool &m_CodePause; +public: + static float GetTimeStep(void) { return ms_fTimeStep; } + static uint32 GetFrameCounter(void) { return m_FrameCounter; } + static uint32 GetTimeInMilliseconds(void) { return m_snTimeInMilliseconds; } +}; diff --git a/src/TxdStore.cpp b/src/TxdStore.cpp new file mode 100644 index 00000000..ba8eecb9 --- /dev/null +++ b/src/TxdStore.cpp @@ -0,0 +1,158 @@ +#include "common.h" +#include "patcher.h" +#include "templates.h" +#include "Streaming.h" +#include "TxdStore.h" + +CPool *&CTxdStore::ms_pTxdPool = *(CPool**)0x8F5FB8; +RwTexDictionary *&CTxdStore::ms_pStoredTxd = *(RwTexDictionary**)0x9405BC; + +void +CTxdStore::Initialize(void) +{ + if(ms_pTxdPool == nil) + ms_pTxdPool = new CPool(TXDSTORESIZE); +} + +void +CTxdStore::Shutdown(void) +{ + if(ms_pTxdPool) + delete ms_pTxdPool; +} + +int +CTxdStore::AddTxdSlot(const char *name) +{ + TxdDef *def = ms_pTxdPool->New(); + assert(def); + def->texDict = nil; + def->refCount = 0; + strcpy(def->name, name); + return ms_pTxdPool->GetJustIndex(def); +} + +int +CTxdStore::FindTxdSlot(const char *name) +{ + char *defname; + int size = ms_pTxdPool->GetSize(); + for(int i = 0; i < size; i++){ + defname = GetTxdName(i); + if(defname && _strcmpi(defname, name) == 0) + return i; + } + return -1; +} + +char* +CTxdStore::GetTxdName(int slot) +{ + TxdDef *def = GetSlot(slot); + return def ? def->name : nil; +} + +void +CTxdStore::PushCurrentTxd(void) +{ + ms_pStoredTxd = RwTexDictionaryGetCurrent(); +} + +void +CTxdStore::PopCurrentTxd(void) +{ + RwTexDictionarySetCurrent(ms_pStoredTxd); + ms_pStoredTxd = nil; +} + +void +CTxdStore::SetCurrentTxd(int slot) +{ + TxdDef *def = GetSlot(slot); + if(def) + RwTexDictionarySetCurrent(def->texDict); +} + +void +CTxdStore::Create(int slot) +{ + GetSlot(slot)->texDict = RwTexDictionaryCreate(); +} + +void +CTxdStore::AddRef(int slot) +{ + GetSlot(slot)->refCount++; +} + +void +CTxdStore::RemoveRef(int slot) +{ + if(--GetSlot(slot)->refCount <= 0) + CStreaming::RemoveModel(slot + STREAM_OFFSET_TXD); +} + +void +CTxdStore::RemoveRefWithoutDelete(int slot) +{ + GetSlot(slot)->refCount--; +} + +/* +bool +CTxdStore::LoadTxd(int slot, RwStream *stream) +{ + TxdDef *def = GetSlot(slot); + if(!rw::findChunk(stream, rw::ID_TEXDICTIONARY, nil, nil)){ + return false; + }else{ + def->texDict = rw::TexDictionary::streamRead(stream); + convertTxd(def->texDict); + return def->texDict != nil; + } +} + +bool +CTxdStore::LoadTxd(int slot, const char *filename) +{ + rw::StreamFile stream; + if(stream.open(getPath(filename), "rb")){ + LoadTxd(slot, &stream); + stream.close(); + return true; + } + printf("Failed to open TXD\n"); + return false; +} +*/ + +void +CTxdStore::RemoveTxd(int slot) +{ + TxdDef *def = GetSlot(slot); + if(def->texDict) + RwTexDictionaryDestroy(def->texDict); + def->texDict = nil; +} + +//bool +//CTxdStore::isTxdLoaded(int slot) +//{ +// return GetSlot(slot)->texDict != nil; +//} + +STARTPATCHES + InjectHook(0x527440, CTxdStore::Initialize, PATCH_JUMP); + InjectHook(0x527470, CTxdStore::Shutdown, PATCH_JUMP); + InjectHook(0x5274E0, CTxdStore::AddTxdSlot, PATCH_JUMP); + InjectHook(0x5275D0, CTxdStore::FindTxdSlot, PATCH_JUMP); + InjectHook(0x527590, CTxdStore::GetTxdName, PATCH_JUMP); + InjectHook(0x527900, CTxdStore::PushCurrentTxd, PATCH_JUMP); + InjectHook(0x527910, CTxdStore::PopCurrentTxd, PATCH_JUMP); + InjectHook(0x5278C0, CTxdStore::SetCurrentTxd, PATCH_JUMP); + InjectHook(0x527830, CTxdStore::Create, PATCH_JUMP); + InjectHook(0x527930, CTxdStore::AddRef, PATCH_JUMP); + InjectHook(0x527970, CTxdStore::RemoveRef, PATCH_JUMP); + InjectHook(0x5279C0, CTxdStore::RemoveRefWithoutDelete, PATCH_JUMP); + InjectHook(0x527870, CTxdStore::RemoveTxd, PATCH_JUMP); +ENDPATCHES diff --git a/src/TxdStore.h b/src/TxdStore.h new file mode 100644 index 00000000..2cfef0e0 --- /dev/null +++ b/src/TxdStore.h @@ -0,0 +1,34 @@ +#pragma once + +#include "templates.h" + +struct TxdDef { + RwTexDictionary *texDict; + int refCount; + char name[20]; +}; + +class CTxdStore +{ + static CPool *&ms_pTxdPool; + static RwTexDictionary *&ms_pStoredTxd; +public: + static void Initialize(void); + static void Shutdown(void); + static int AddTxdSlot(const char *name); + static int FindTxdSlot(const char *name); + static char *GetTxdName(int slot); + static void PushCurrentTxd(void); + static void PopCurrentTxd(void); + static void SetCurrentTxd(int slot); + static void Create(int slot); + static void AddRef(int slot); + static void RemoveRef(int slot); + static void RemoveRefWithoutDelete(int slot); + static bool LoadTxd(int slot, RwStream *stream); + static bool LoadTxd(int slot, const char *filename); + static void RemoveTxd(int slot); + + static TxdDef *GetSlot(int slot) { return ms_pTxdPool->GetSlot(slot); } + static bool isTxdLoaded(int slot); +}; diff --git a/src/Weather.cpp b/src/Weather.cpp new file mode 100644 index 00000000..73421932 --- /dev/null +++ b/src/Weather.cpp @@ -0,0 +1,27 @@ +#include "common.h" +#include "Weather.h" + +int32 &CWeather::SoundHandle = *(int32*)0x5FFBC4; + +int32 &CWeather::WeatherTypeInList = *(int32*)0x8F626C; +int16 &CWeather::OldWeatherType = *(int16*)0x95CCEC; +int16 &CWeather::NewWeatherType = *(int16*)0x95CC70; +int16 &CWeather::ForcedWeatherType = *(int16*)0x95CC80; + +bool &CWeather::LightningFlash = *(bool*)0x95CDA3; +bool &CWeather::LightningBurst = *(bool*)0x95CDAC; +uint32 &CWeather::LightningStart = *(uint32*)0x8F5F84; +uint32 &CWeather::LightningFlashLastChange = *(uint32*)0x8E2C0C; +uint32 &CWeather::WhenToPlayLightningSound = *(uint32*)0x8F57E4; +uint32 &CWeather::LightningDuration = *(uint32*)0x940578; + +float &CWeather::Foggyness = *(float*)0x885AF4; +float &CWeather::CloudCoverage = *(float*)0x8E2818; +float &CWeather::Wind = *(float*)0x8E2BF8; +float &CWeather::Rain = *(float*)0x8E2BFC; +float &CWeather::InterpolationValue = *(float*)0x8F2520; +float &CWeather::WetRoads = *(float*)0x8F5FF8; +float &CWeather::Rainbow = *(float*)0x940598; + +bool &CWeather::bScriptsForceRain = *(bool*)0x95CD7D; +bool &CWeather::Stored_StateStored = *(bool*)0x95CDC1; diff --git a/src/Weather.h b/src/Weather.h new file mode 100644 index 00000000..41e07d93 --- /dev/null +++ b/src/Weather.h @@ -0,0 +1,35 @@ +enum { + WEATHER_SUNNY, + WEATHER_CLOUDY, + WEATHER_RAINY, + WEATHER_FOGGY +}; + +class CWeather +{ +public: + static int32 &SoundHandle; + + static int32 &WeatherTypeInList; + static int16 &OldWeatherType; + static int16 &NewWeatherType; + static int16 &ForcedWeatherType; + + static bool &LightningFlash; + static bool &LightningBurst; + static uint32 &LightningStart; + static uint32 &LightningFlashLastChange; + static uint32 &WhenToPlayLightningSound; + static uint32 &LightningDuration; + + static float &Foggyness; + static float &CloudCoverage; + static float &Wind; + static float &Rain; + static float &InterpolationValue; + static float &WetRoads; + static float &Rainbow; + + static bool &bScriptsForceRain; + static bool &Stored_StateStored; +}; diff --git a/src/World.cpp b/src/World.cpp new file mode 100644 index 00000000..761d8bf6 --- /dev/null +++ b/src/World.cpp @@ -0,0 +1,39 @@ +#include "common.h" +#include "patcher.h" +#include "Entity.h" +#include "World.h" + +CPtrList *CWorld::ms_bigBuildingsList = (CPtrList*)0x6FAB60; +CPtrList &CWorld::ms_listMovingEntityPtrs = *(CPtrList*)0x8F433C; +CSector (*CWorld::ms_aSectors)[NUMSECTORS_X] = (CSector (*)[NUMSECTORS_Y])0x665608; +uint16 &CWorld::ms_nCurrentScanCode = *(uint16*)0x95CC64; + +bool &CWorld::bNoMoreCollisionTorque = *(bool*)0x95CDCC; + +void +CWorld::ClearScanCodes(void) +{ + CPtrNode *node; + for(int i = 0; i < NUMSECTORS_Y; i++) + for(int j = 0; j < NUMSECTORS_X; j++){ + CSector *s = &ms_aSectors[i][j]; + for(node = s->m_lists[ENTITYLIST_BUILDINGS].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_VEHICLES].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_PEDS].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_OBJECTS].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_DUMMIES].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + } +} + +STARTPATCHES + InjectHook(0x4B1F60, CWorld::ClearScanCodes, PATCH_JUMP); +ENDPATCHES + +WRAPPER CVector &FindPlayerCoors(CVector &v) { EAXJMP(0x4A1030); } +WRAPPER CVehicle *FindPlayerVehicle(void) { EAXJMP(0x4A10C0); } +WRAPPER CVehicle *FindPlayerTrain(void) { EAXJMP(0x4A1120); } diff --git a/src/World.h b/src/World.h new file mode 100644 index 00000000..ecd8feb3 --- /dev/null +++ b/src/World.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Game.h" +#include "Lists.h" + +/* Sectors span from -2000 to 2000 in x and y. + * With 100x100 sectors, each is 40x40 units. */ + +#define NUMSECTORS_X 100 +#define NUMSECTORS_Y 100 + +enum +{ + ENTITYLIST_BUILDINGS, + ENTITYLIST_BUILDINGS_OVERLAP, + ENTITYLIST_OBJECTS, + ENTITYLIST_OBJECTS_OVERLAP, + ENTITYLIST_VEHICLES, + ENTITYLIST_VEHICLES_OVERLAP, + ENTITYLIST_PEDS, + ENTITYLIST_PEDS_OVERLAP, + ENTITYLIST_DUMMIES, + ENTITYLIST_DUMMIES_OVERLAP, + + NUMSECTORENTITYLISTS +}; + +class CSector +{ +public: + CPtrList m_lists[NUMSECTORENTITYLISTS]; +}; +static_assert(sizeof(CSector) == 0x28, "CSector: error"); + +class CWorld +{ + static CPtrList *ms_bigBuildingsList; // [4]; + static CPtrList &ms_listMovingEntityPtrs; + static CSector (*ms_aSectors)[NUMSECTORS_X]; // [NUMSECTORS_Y][NUMSECTORS_X]; + static uint16 &ms_nCurrentScanCode; + +public: + static bool &bNoMoreCollisionTorque; + + static CSector *GetSector(int x, int y) { return &ms_aSectors[y][x]; } + static CPtrList &GetBigBuildingList(eLevelName i) { return ms_bigBuildingsList[i]; } + static CPtrList &GetMovingEntityList(void) { return ms_listMovingEntityPtrs; } + static uint16 GetCurrentScanCode(void) { return ms_nCurrentScanCode; } + static void AdvanceCurrentScanCode(void){ + if(++CWorld::ms_nCurrentScanCode == 0){ + CWorld::ClearScanCodes(); + CWorld::ms_nCurrentScanCode = 1; + } + } + static void ClearScanCodes(void); + + static float GetSectorX(float f) { return ((f + 2000.0f)/40.0f); } + static float GetSectorY(float f) { return ((f + 2000.0f)/40.0f); } + static int GetSectorIndexX(float f) { return (int)GetSectorX(f); } + static int GetSectorIndexY(float f) { return (int)GetSectorY(f); } +}; + +CVector &FindPlayerCoors(CVector &v); +class CVehicle; +CVehicle *FindPlayerVehicle(void); +CVehicle *FindPlayerTrain(void); diff --git a/src/Zones.cpp b/src/Zones.cpp new file mode 100644 index 00000000..6c8f66ce --- /dev/null +++ b/src/Zones.cpp @@ -0,0 +1,614 @@ +#include "common.h" +#include "patcher.h" +#include "World.h" +#include "Clock.h" +#include "Zones.h" + +eLevelName &CTheZones::m_CurrLevel = *(eLevelName*)0x8F2BC8; +CZone *&CTheZones::m_pPlayersZone = *(CZone**)0x8F254C; +int16 &CTheZones::FindIndex = *(int16*)0x95CC40; + +uint16 &CTheZones::NumberOfAudioZones = *(uint16*)0x95CC84; +int16 *CTheZones::AudioZoneArray = (int16*)0x713BC0; +uint16 &CTheZones::TotalNumberOfMapZones = *(uint16*)0x95CC74; +uint16 &CTheZones::TotalNumberOfZones = *(uint16*)0x95CC36; +CZone *CTheZones::ZoneArray = (CZone*)0x86BEE0; +CZone *CTheZones::MapZoneArray = (CZone*)0x663EC0; +uint16 &CTheZones::TotalNumberOfZoneInfos = *(uint16*)0x95CC3C; +CZoneInfo *CTheZones::ZoneInfoArray = (CZoneInfo*)0x714400; + +#define SWAPF(a, b) { float t; t = a; a = b; b = t; } + + +void +CTheZones::Init(void) +{ + int i; + for(i = 0; i < NUMAUDIOZONES; i++) + AudioZoneArray[i] = -1; + NumberOfAudioZones = 0; + + CZoneInfo *zonei; + int x = 1000/6; + for(i = 0; i < 2*NUMZONES; i++){ + zonei = &ZoneInfoArray[i]; + zonei->carDensity = 10; + zonei->carThreshold[0] = x; + zonei->carThreshold[1] = zonei->carThreshold[0] + x; + zonei->carThreshold[2] = zonei->carThreshold[1] + x; + zonei->carThreshold[3] = zonei->carThreshold[2] + x; + zonei->carThreshold[4] = zonei->carThreshold[3]; + zonei->carThreshold[5] = zonei->carThreshold[4]; + zonei->copThreshold = zonei->carThreshold[5] + x; + zonei->gangThreshold[0] = zonei->copThreshold; + zonei->gangThreshold[1] = zonei->gangThreshold[0]; + zonei->gangThreshold[2] = zonei->gangThreshold[1]; + zonei->gangThreshold[3] = zonei->gangThreshold[2]; + zonei->gangThreshold[4] = zonei->gangThreshold[3]; + zonei->gangThreshold[5] = zonei->gangThreshold[4]; + zonei->gangThreshold[6] = zonei->gangThreshold[5]; + zonei->gangThreshold[7] = zonei->gangThreshold[6]; + zonei->gangThreshold[8] = zonei->gangThreshold[7]; + } + TotalNumberOfZoneInfos = 1; // why 1? + + for(i = 0; i < NUMZONES; i++) + memset(&ZoneArray[i], 0, sizeof(CZone)); + strcpy(ZoneArray[0].name, "CITYZON"); + ZoneArray[0].minx = -4000.0f; + ZoneArray[0].miny = -4000.0f; + ZoneArray[0].minz = -500.0f; + ZoneArray[0].maxx = 4000.0f; + ZoneArray[0].maxy = 4000.0f; + ZoneArray[0].maxz = 500.0f; + ZoneArray[0].level = LEVEL_NONE; + TotalNumberOfZones = 1; + + m_CurrLevel = LEVEL_NONE; + m_pPlayersZone = &ZoneArray[0]; + + for(i = 0; i < NUMMAPZONES; i++){ + memset(&MapZoneArray[i], 0, sizeof(CZone)); + MapZoneArray[i].type = ZONE_MAPZONE; + } + strcpy(MapZoneArray[0].name, "THEMAP"); + MapZoneArray[0].minx = -4000.0f; + MapZoneArray[0].miny = -4000.0f; + MapZoneArray[0].minz = -500.0f; + MapZoneArray[0].maxx = 4000.0f; + MapZoneArray[0].maxy = 4000.0f; + MapZoneArray[0].maxz = 500.0f; + MapZoneArray[0].level = LEVEL_NONE; + TotalNumberOfMapZones = 1; +} + +void +CTheZones::Update(void) +{ + CVector pos; + FindPlayerCoors(pos); + m_pPlayersZone = FindSmallestZonePosition(&pos); + m_CurrLevel = GetLevelFromPosition(pos); +} + +void +CTheZones::CreateZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level) +{ + CZone *zone; + char *p; + + if(minx > maxx) SWAPF(minx, maxx); + if(miny > maxy) SWAPF(miny, maxy); + if(minz > maxz) SWAPF(minz, maxz); + + // make upper case + for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p); + + // add zone + zone = &ZoneArray[TotalNumberOfZones++]; + strncpy(zone->name, name, 7); + zone->name[7] = '\0'; + zone->type = type; + zone->minx = minx; + zone->miny = miny; + zone->minz = minz; + zone->maxx = maxx; + zone->maxy = maxy; + zone->maxz = maxz; + zone->level = level; + if(type == ZONE_AUDIO || type == ZONE_TYPE1 || type == ZONE_TYPE2){ + zone->zoneinfoDay = TotalNumberOfZoneInfos++; + zone->zoneinfoNight = TotalNumberOfZoneInfos++; + } +} + +void +CTheZones::CreateMapZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level) +{ + CZone *zone; + char *p; + + if(minx > maxx) SWAPF(minx, maxx); + if(miny > maxy) SWAPF(miny, maxy); + if(minz > maxz) SWAPF(minz, maxz); + + // make upper case + for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p); + + // add zone + zone = &MapZoneArray[TotalNumberOfMapZones++]; + strncpy(zone->name, name, 7); + zone->name[7] = '\0'; + zone->type = type; + zone->minx = minx; + zone->miny = miny; + zone->minz = minz; + zone->maxx = maxx; + zone->maxy = maxy; + zone->maxz = maxz; + zone->level = level; +} + +void +CTheZones::PostZoneCreation(void) +{ + int i; + for(i = 1; i < TotalNumberOfZones; i++) + InsertZoneIntoZoneHierarchy(&ZoneArray[i]); + InitialiseAudioZoneArray(); +} + +void +CTheZones::InsertZoneIntoZoneHierarchy(CZone *zone) +{ + zone->child = nil; + zone->parent = nil; + zone->next = nil; + InsertZoneIntoZoneHierRecursive(zone, &ZoneArray[0]); +} + +bool +CTheZones::InsertZoneIntoZoneHierRecursive(CZone *inner, CZone *outer) +{ + int n; + CZone *child, *next, *insert; + + // return false if inner was not inserted into outer + if(outer == nil || + !ZoneIsEntirelyContainedWithinOtherZone(inner, outer)) + return false; + + // try to insert inner into children of outer + for(child = outer->child; child; child = child->next) + if(InsertZoneIntoZoneHierRecursive(inner, child)) + return true; + + // insert inner as child of outer + // count number of outer's children contained within inner + n = 0; + for(child = outer->child; child; child = child->next) + if(ZoneIsEntirelyContainedWithinOtherZone(child, inner)) + n++; + inner->next = outer->child; + inner->parent = outer; + outer->child = inner; + // move children from outer to inner + if(n){ + insert = inner; + for(child = inner->next; child; child = next){ + next = child->next; + if(ZoneIsEntirelyContainedWithinOtherZone(child,inner)){ + insert->next = child->next; + child->parent = inner; + child->next = inner->child; + inner->child = child; + }else + insert = child; + } + } + + return true; +} + +bool +CTheZones::ZoneIsEntirelyContainedWithinOtherZone(CZone *inner, CZone *outer) +{ + char tmp[100]; + + if(inner->minx < outer->minx || + inner->maxx > outer->maxx || + inner->miny < outer->miny || + inner->maxy > outer->maxy || + inner->minz < outer->minz || + inner->maxz > outer->maxz){ + CVector vmin(inner->minx, inner->miny, inner->minz); + if(PointLiesWithinZone(vmin, outer)) + sprintf(tmp, "Overlapping zones %s and %s\n", + inner->name, outer->name); + CVector vmax(inner->maxx, inner->maxy, inner->maxz); + if(PointLiesWithinZone(vmax, outer)) + sprintf(tmp, "Overlapping zones %s and %s\n", + inner->name, outer->name); + return false; + } + return true; +} + +bool +CTheZones::PointLiesWithinZone(const CVector &v, CZone *zone) +{ + return zone->minx <= v.x && v.x <= zone->maxx && + zone->miny <= v.y && v.y <= zone->maxy && + zone->minz <= v.z && v.z <= zone->maxz; +} + +eLevelName +CTheZones::GetLevelFromPosition(CVector const &v) +{ + int i; +// char tmp[116]; +// if(!PointLiesWithinZone(v, &MapZoneArray[0])) +// sprintf(tmp, "x = %.3f y= %.3f z = %.3f\n", v.x, v.y, v.z); + for(i = 1; i < TotalNumberOfMapZones; i++) + if(PointLiesWithinZone(v, &MapZoneArray[i])) + return MapZoneArray[i].level; + return MapZoneArray[0].level; +} + +CZone* +CTheZones::FindSmallestZonePosition(const CVector *v) +{ + CZone *best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(*v, zone)){ + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +CZone* +CTheZones::FindSmallestZonePositionType(const CVector *v, eZoneType type) +{ + CZone *best = nil; + if(ZoneArray[0].type == type) + best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(*v, zone)){ + if(zone->type == type) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +CZone* +CTheZones::FindSmallestZonePositionILN(const CVector *v) +{ + CZone *best = nil; + if(ZoneArray[0].type == ZONE_AUDIO || + ZoneArray[0].type == ZONE_TYPE1 || + ZoneArray[0].type == ZONE_TYPE2) + best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(*v, zone)){ + if(zone->type == ZONE_AUDIO || + zone->type == ZONE_TYPE1 || + zone->type == ZONE_TYPE2) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +int16 +CTheZones::FindZoneByLabelAndReturnIndex(char *name) +{ + char str[8]; + memset(str, 0, 8); + strncpy(str, name, 8); + for(FindIndex = 0; FindIndex < TotalNumberOfZones; FindIndex++) + if(strcmp(GetZone(FindIndex)->name, name) == 0) + return FindIndex; + return -1; +} + +CZoneInfo* +CTheZones::GetZoneInfo(const CVector *v, uint8 day) +{ + CZone *zone; + zone = FindSmallestZonePositionILN(v); + if(zone == nil) + return &ZoneInfoArray[0]; + return &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; +} + +void +CTheZones::GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info) +{ + CZoneInfo *day, *night; + float d, n; + + day = GetZoneInfo(pos, 1); + night = GetZoneInfo(pos, 0); + + if(CClock::GetIsTimeInRange(8, 19)) + *info = *day; + else if(CClock::GetIsTimeInRange(22, 5)) + *info = *night; + else{ + if(CClock::GetIsTimeInRange(19, 22)){ + n = (CClock::GetHours() - 19) / 3.0f; + d = n - 1.0f; + }else{ + d = (CClock::GetHours() - 5) / 3.0f; + n = d - 1.0f; + } + info->carDensity = day->carDensity * d + night->carDensity * n; + info->carThreshold[0] = day->carThreshold[0] * d + night->carThreshold[0] * n; + info->carThreshold[1] = day->carThreshold[1] * d + night->carThreshold[1] * n; + info->carThreshold[2] = day->carThreshold[2] * d + night->carThreshold[2] * n; + info->carThreshold[3] = day->carThreshold[3] * d + night->carThreshold[3] * n; + info->carThreshold[4] = day->carThreshold[4] * d + night->carThreshold[4] * n; + info->carThreshold[5] = day->carThreshold[5] * d + night->carThreshold[5] * n; + info->copThreshold = day->copThreshold * d + night->copThreshold * n; + info->gangThreshold[0] = day->gangThreshold[0] * d + night->gangThreshold[0] * n; + info->gangThreshold[1] = day->gangThreshold[1] * d + night->gangThreshold[1] * n; + info->gangThreshold[2] = day->gangThreshold[2] * d + night->gangThreshold[2] * n; + info->gangThreshold[3] = day->gangThreshold[3] * d + night->gangThreshold[3] * n; + info->gangThreshold[4] = day->gangThreshold[4] * d + night->gangThreshold[4] * n; + info->gangThreshold[5] = day->gangThreshold[5] * d + night->gangThreshold[5] * n; + info->gangThreshold[6] = day->gangThreshold[6] * d + night->gangThreshold[6] * n; + info->gangThreshold[7] = day->gangThreshold[7] * d + night->gangThreshold[7] * n; + info->gangThreshold[8] = day->gangThreshold[8] * d + night->gangThreshold[8] * n; + + info->pedDensity = day->pedDensity * d + night->pedDensity * n; + info->copDensity = day->copDensity * d + night->copDensity * n; + info->gangDensity[0] = day->gangDensity[0] * d + night->gangDensity[0] * n; + info->gangDensity[1] = day->gangDensity[1] * d + night->gangDensity[1] * n; + info->gangDensity[2] = day->gangDensity[2] * d + night->gangDensity[2] * n; + info->gangDensity[3] = day->gangDensity[3] * d + night->gangDensity[3] * n; + info->gangDensity[4] = day->gangDensity[4] * d + night->gangDensity[4] * n; + info->gangDensity[5] = day->gangDensity[5] * d + night->gangDensity[5] * n; + info->gangDensity[6] = day->gangDensity[6] * d + night->gangDensity[6] * n; + info->gangDensity[7] = day->gangDensity[7] * d + night->gangDensity[7] * n; + info->gangDensity[8] = day->gangDensity[8] * d + night->gangDensity[8] * n; + } + if(CClock::GetIsTimeInRange(5, 19)) + info->pedGroup = day->pedGroup; + else + info->pedGroup = night->pedGroup; +} + +void +CTheZones::SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity, + int16 gang0Num, int16 gang1Num, int16 gang2Num, + int16 gang3Num, int16 gang4Num, int16 gang5Num, + int16 gang6Num, int16 gang7Num, int16 gang8Num, + int16 copNum, + int16 car0Num, int16 car1Num, int16 car2Num, + int16 car3Num, int16 car4Num, int16 car5Num) +{ + CZone *zone; + CZoneInfo *info; + zone = GetZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + + if(carDensity != -1) info->carDensity = carDensity; + int16 oldCar1Num = info->carThreshold[1] - info->carThreshold[0]; + int16 oldCar2Num = info->carThreshold[2] - info->carThreshold[1]; + int16 oldCar3Num = info->carThreshold[3] - info->carThreshold[2]; + int16 oldCar4Num = info->carThreshold[4] - info->carThreshold[3]; + int16 oldCar5Num = info->carThreshold[5] - info->carThreshold[4]; + int16 oldCopNum = info->copThreshold - info->carThreshold[5]; + int16 oldGang0Num = info->gangThreshold[0] - info->copThreshold; + int16 oldGang1Num = info->gangThreshold[1] - info->gangThreshold[0]; + int16 oldGang2Num = info->gangThreshold[2] - info->gangThreshold[1]; + int16 oldGang3Num = info->gangThreshold[3] - info->gangThreshold[2]; + int16 oldGang4Num = info->gangThreshold[4] - info->gangThreshold[3]; + int16 oldGang5Num = info->gangThreshold[5] - info->gangThreshold[4]; + int16 oldGang6Num = info->gangThreshold[6] - info->gangThreshold[5]; + int16 oldGang7Num = info->gangThreshold[7] - info->gangThreshold[6]; + int16 oldGang8Num = info->gangThreshold[8] - info->gangThreshold[7]; + + if(car0Num != -1) info->carThreshold[0] = car0Num; + if(car1Num != -1) info->carThreshold[1] = info->carThreshold[0] + car1Num; + else info->carThreshold[1] = info->carThreshold[0] + oldCar1Num; + if(car2Num != -1) info->carThreshold[2] = info->carThreshold[1] + car2Num; + else info->carThreshold[2] = info->carThreshold[1] + oldCar2Num; + if(car3Num != -1) info->carThreshold[3] = info->carThreshold[2] + car3Num; + else info->carThreshold[3] = info->carThreshold[2] + oldCar3Num; + if(car4Num != -1) info->carThreshold[4] = info->carThreshold[3] + car4Num; + else info->carThreshold[4] = info->carThreshold[3] + oldCar4Num; + if(car5Num != -1) info->carThreshold[5] = info->carThreshold[4] + car5Num; + else info->carThreshold[5] = info->carThreshold[4] + oldCar5Num; + if(copNum != -1) info->copThreshold = info->carThreshold[5] + copNum; + else info->copThreshold = info->carThreshold[5] + oldCopNum; + if(gang0Num != -1) info->gangThreshold[0] = info->copThreshold + gang0Num; + else info->gangThreshold[0] = info->copThreshold + oldGang0Num; + if(gang1Num != -1) info->gangThreshold[1] = info->gangThreshold[0] + gang1Num; + else info->gangThreshold[1] = info->gangThreshold[0] + oldGang1Num; + if(gang2Num != -1) info->gangThreshold[2] = info->gangThreshold[1] + gang2Num; + else info->gangThreshold[2] = info->gangThreshold[1] + oldGang2Num; + if(gang3Num != -1) info->gangThreshold[3] = info->gangThreshold[2] + gang3Num; + else info->gangThreshold[3] = info->gangThreshold[2] + oldGang3Num; + if(gang4Num != -1) info->gangThreshold[4] = info->gangThreshold[3] + gang4Num; + else info->gangThreshold[4] = info->gangThreshold[3] + oldGang4Num; + if(gang5Num != -1) info->gangThreshold[5] = info->gangThreshold[4] + gang5Num; + else info->gangThreshold[5] = info->gangThreshold[4] + oldGang5Num; + if(gang6Num != -1) info->gangThreshold[6] = info->gangThreshold[5] + gang6Num; + else info->gangThreshold[6] = info->gangThreshold[5] + oldGang6Num; + if(gang7Num != -1) info->gangThreshold[7] = info->gangThreshold[6] + gang7Num; + else info->gangThreshold[7] = info->gangThreshold[6] + oldGang7Num; + if(gang8Num != -1) info->gangThreshold[8] = info->gangThreshold[7] + gang8Num; + else info->gangThreshold[8] = info->gangThreshold[7] + oldGang8Num; +} + +void +CTheZones::SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity, + int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density, + int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density, + int16 gang8Density, int16 copDensity) +{ + CZone *zone; + CZoneInfo *info; + zone = GetZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + if(pedDensity != -1) info->pedDensity = pedDensity; + if(copDensity != -1) info->copDensity = copDensity; + if(gang0Density != -1) info->gangThreshold[0] = gang0Density; + if(gang1Density != -1) info->gangThreshold[1] = gang1Density; + if(gang2Density != -1) info->gangThreshold[2] = gang2Density; + if(gang3Density != -1) info->gangThreshold[3] = gang3Density; + if(gang4Density != -1) info->gangThreshold[4] = gang4Density; + if(gang5Density != -1) info->gangThreshold[5] = gang5Density; + if(gang6Density != -1) info->gangThreshold[6] = gang6Density; + if(gang7Density != -1) info->gangThreshold[7] = gang7Density; + if(gang8Density != -1) info->gangThreshold[8] = gang8Density; +} + +void +CTheZones::SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity) +{ + CZone *zone; + zone = GetZone(zoneid); + if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].carDensity = cardensity; +} + +void +CTheZones::SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity) +{ + CZone *zone; + zone = GetZone(zoneid); + if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedDensity = peddensity; +} + +void +CTheZones::SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup) +{ + CZone *zone; + zone = GetZone(zoneid); + if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedGroup = pedgroup; +} + +int16 +CTheZones::FindAudioZone(CVector *pos) +{ + int i; + + for(i = 0; i < NumberOfAudioZones; i++) + if(PointLiesWithinZone(*pos, GetZone(AudioZoneArray[i]))) + return i; + return -1; +} + +eLevelName +CTheZones::FindZoneForPoint(const CVector &pos) +{ + if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("IND_ZON")))) + return LEVEL_INDUSTRIAL; + if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("COM_ZON")))) + return LEVEL_COMMERCIAL; + if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("SUB_ZON")))) + return LEVEL_SUBURBAN; + return LEVEL_NONE; +} + +void +CTheZones::AddZoneToAudioZoneArray(CZone *zone) +{ + int i, z; + + if(zone->type != ZONE_AUDIO) + return; + + /* This is a bit stupid */ + z = -1; + for(i = 0; i < NUMZONES; i++) + if(&ZoneArray[i] == zone) + z = i; + AudioZoneArray[NumberOfAudioZones++] = z; +} + +void +CTheZones::InitialiseAudioZoneArray(void) +{ + bool gonext; + CZone *zone; + + gonext = false; + zone = &ZoneArray[0]; + // Go deep first, + // set gonext when backing up a level to visit the next child + while(zone) + if(gonext){ + AddZoneToAudioZoneArray(zone); + if(zone->next){ + gonext = false; + zone = zone->next; + }else + zone = zone->parent; + }else if(zone->child) + zone = zone->child; + else{ + AddZoneToAudioZoneArray(zone); + if(zone->next) + zone = zone->next; + else{ + gonext = true; + zone = zone->parent; + } + } +} + +STARTPATCHES + InjectHook(0x4B5DE0, CTheZones::Init, PATCH_JUMP); + InjectHook(0x4B61D0, CTheZones::Update, PATCH_JUMP); + InjectHook(0x4B6210, CTheZones::CreateZone, PATCH_JUMP); + InjectHook(0x4B6380, CTheZones::CreateMapZone, PATCH_JUMP); + InjectHook(0x4B64C0, CTheZones::PostZoneCreation, PATCH_JUMP); + InjectHook(0x4B6500, CTheZones::InsertZoneIntoZoneHierarchy, PATCH_JUMP); + InjectHook(0x4B6530, CTheZones::InsertZoneIntoZoneHierRecursive, PATCH_JUMP); + InjectHook(0x4B65F0, CTheZones::ZoneIsEntirelyContainedWithinOtherZone, PATCH_JUMP); + InjectHook(0x4B6710, CTheZones::PointLiesWithinZone, PATCH_JUMP); + InjectHook(0x4B6910, CTheZones::GetLevelFromPosition, PATCH_JUMP); + InjectHook(0x4B69B0, CTheZones::FindSmallestZonePosition, PATCH_JUMP); + InjectHook(0x4B6790, CTheZones::FindSmallestZonePositionType, PATCH_JUMP); + InjectHook(0x4B6890, CTheZones::FindSmallestZonePositionILN, PATCH_JUMP); + InjectHook(0x4B6800, CTheZones::FindZoneByLabelAndReturnIndex, PATCH_JUMP); + InjectHook(0x4B6A10, CTheZones::GetZoneInfo, PATCH_JUMP); + InjectHook(0x4B6FB0, CTheZones::GetZoneInfoForTimeOfDay, PATCH_JUMP); + InjectHook(0x4B6A50, CTheZones::SetZoneCarInfo, PATCH_JUMP); + InjectHook(0x4B6DC0, CTheZones::SetZonePedInfo, PATCH_JUMP); + InjectHook(0x4B6EB0, CTheZones::SetCarDensity, PATCH_JUMP); + InjectHook(0x4B6F00, CTheZones::SetPedDensity, PATCH_JUMP); + InjectHook(0x4B6F50, CTheZones::SetPedGroup, PATCH_JUMP); + InjectHook(0x4B83E0, CTheZones::FindAudioZone, PATCH_JUMP); + InjectHook(0x4B8430, CTheZones::FindZoneForPoint, PATCH_JUMP); + InjectHook(0x4B8340, CTheZones::AddZoneToAudioZoneArray, PATCH_JUMP); + InjectHook(0x4B8380, CTheZones::InitialiseAudioZoneArray, PATCH_JUMP); +ENDPATCHES diff --git a/src/Zones.h b/src/Zones.h new file mode 100644 index 00000000..c7745e29 --- /dev/null +++ b/src/Zones.h @@ -0,0 +1,107 @@ +#pragma once + +#include "Game.h" + +enum eZoneType +{ + ZONE_AUDIO, + ZONE_TYPE1, // this should be NAVIG + ZONE_TYPE2, // this should be INFO...but all except MAPINFO get zoneinfo?? + ZONE_MAPZONE, +}; + +class CZone +{ +public: + char name[8]; + float minx; + float miny; + float minz; + float maxx; + float maxy; + float maxz; + eZoneType type; + eLevelName level; + int16 zoneinfoDay; + int16 zoneinfoNight; + CZone *child; + CZone *parent; + CZone *next; +}; + +class CZoneInfo +{ +public: + // Car data + uint16 carDensity; + uint16 carThreshold[6]; + uint16 copThreshold; + uint16 gangThreshold[9]; + + // Ped data + uint16 pedDensity; + uint16 copDensity; + uint16 gangDensity[9]; + uint16 pedGroup; +}; + + +class CTheZones +{ +public: + static eLevelName &m_CurrLevel; + static CZone *&m_pPlayersZone; + static int16 &FindIndex; + + static uint16 &NumberOfAudioZones; + static int16 *AudioZoneArray; //[NUMAUDIOZONES]; + static uint16 &TotalNumberOfMapZones; + static uint16 &TotalNumberOfZones; + static CZone *ZoneArray; //[NUMZONES]; + static CZone *MapZoneArray; //[NUMMAPZONES]; + static uint16 &TotalNumberOfZoneInfos; + static CZoneInfo *ZoneInfoArray; //[2*NUMZONES]; + + static void Init(void); + static void Update(void); + static void CreateZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level); + static void CreateMapZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level); + static CZone *GetZone(uint16 i) { return &ZoneArray[i]; } + static void PostZoneCreation(void); + static void InsertZoneIntoZoneHierarchy(CZone *zone); + static bool InsertZoneIntoZoneHierRecursive(CZone *z1, CZone *z2); + static bool ZoneIsEntirelyContainedWithinOtherZone(CZone *z1, CZone *z2); + static bool PointLiesWithinZone(const CVector &v, CZone *zone); + static eLevelName GetLevelFromPosition(CVector const &v); + static CZone *FindSmallestZonePosition(const CVector *v); + static CZone *FindSmallestZonePositionType(const CVector *v, eZoneType type); + static CZone *FindSmallestZonePositionILN(const CVector *v); + static int16 FindZoneByLabelAndReturnIndex(char *name); + static CZoneInfo *GetZoneInfo(const CVector *v, uint8 day); + static void GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info); + static void SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity, + int16 gang0Num, int16 gang1Num, int16 gang2Num, + int16 gang3Num, int16 gang4Num, int16 gang5Num, + int16 gang6Num, int16 gang7Num, int16 gang8Num, + int16 copNum, + int16 car0Num, int16 car1Num, int16 car2Num, + int16 car3Num, int16 car4Num, int16 car5Num); + static void SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity, + int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density, + int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density, + int16 gang8Density, int16 copDensity); + static void SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity); + static void SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity); + static void SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup); + static int16 FindAudioZone(CVector *pos); + static eLevelName FindZoneForPoint(const CVector &pos); + static CZone *GetPointerForZoneIndex(int32 i) { return i == -1 ? nil : &ZoneArray[i]; } + static void AddZoneToAudioZoneArray(CZone *zone); + static void InitialiseAudioZoneArray(void); +}; diff --git a/src/common.h b/src/common.h new file mode 100644 index 00000000..e55e5b52 --- /dev/null +++ b/src/common.h @@ -0,0 +1,97 @@ +#pragma once + +#define _CRT_SECURE_NO_WARNINGS +#define _USE_MATH_DEFINES +#pragma warning(disable: 4244) // int to float +#pragma warning(disable: 4800) // int to bool +#pragma warning(disable: 4838) // narrowing conversion + +#include +#include +#include +#include + +#include +#include + +#define rwVENDORID_ROCKSTAR 0x0253F2 + +typedef uint8_t uint8; +typedef int8_t int8; +typedef uint16_t uint16; +typedef int16_t int16; +typedef uint32_t uint32; +typedef int32_t int32; +typedef uintptr_t uintptr; + +#define nil NULL + +#include "config.h" + +#define ALIGNPTR(p) (void*)((((uintptr)(void*)p) + sizeof(void*)-1) & ~(sizeof(void*)-1)) + +// little hack +extern void **rwengine; +#define RwEngineInstance (*rwengine) + +// TODO +struct RsInputDevice +{ + int inputDeviceType; + int used; + void *inputEventHandler; +}; + +struct RsGlobalType +{ + const char *appName; + int width; + int height; + int maximumWidth; + int maximumHeight; + int maxFPS; + int quit; + void *ps; + RsInputDevice keyboard; + RsInputDevice mouse; + RsInputDevice pad; +}; +extern RsGlobalType &RsGlobal; + +#define SCREENW (RsGlobal.maximumWidth) +#define SCREENH (RsGlobal.maximumHeight) + +struct GlobalScene +{ + RpWorld *world; + RwCamera *camera; +}; +extern GlobalScene &Scene; + +#include "math/Vector.h" +#include "math/Vector2D.h" +#include "math/Matrix.h" +#include "math/Rect.h" + +class CRGBA +{ +public: + uint8 r, g, b, a; + CRGBA(void) { } + CRGBA(uint8 r, uint8 g, uint8 b, uint8 a) : r(r), g(g), b(b), a(a) { } +}; + +inline float +clamp(float v, float min, float max){ return vmax ? max : v; } +inline float +sq(float x) { return x*x; } +#define PI M_PI +#define DEGTORAD(d) (d/180.0f*PI) + +int myrand(void); +void mysrand(unsigned int seed); + +#define debug printf + +//#define min(a, b) ((a) < (b) ? (a) : (b)) +//#define max(a, b) ((a) > (b) ? (a) : (b)) diff --git a/src/config.h b/src/config.h new file mode 100644 index 00000000..df99487f --- /dev/null +++ b/src/config.h @@ -0,0 +1,56 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +enum Config { + NUMCDIMAGES = 50, // was 12 + + MODELINFOSIZE = 5500, + TXDSTORESIZE = 850, + EXTRADIRSIZE = 128, + + SIMPLEMODELSIZE = 5000, + TIMEMODELSIZE = 30, + CLUMPMODELSIZE = 5, + PEDMODELSIZE = 90, + VEHICLEMODELSIZE = 120, + TWODFXSIZE = 2000, + + NUMOBJECTINFO = 168, // object.dat + + // Pool sizes + NUMPTRNODES = 30000, // 26000 on PS2 + NUMENTRYINFOS = 5400, // 3200 on PS2 + NUMPEDS = 140, // 90 on PS2 + NUMVEHICLES = 110, // 70 on PS2 + NUMBUILDINGS = 5500, // 4915 on PS2 + NUMTREADABLES = 1214, + NUMOBJECTS = 450, + NUMDUMMIES = 2802, // 2368 on PS2 + NUMAUDIOSCRIPTOBJECTS = 256, + + // Link list lengths + // TODO: alpha list + NUMCOLCACHELINKS = 200, + NUMREFERENCES = 800, + + // Zones + NUMAUDIOZONES = 36, + NUMZONES = 50, + NUMMAPZONES = 25, + + // Cull zones + NUMCULLZONES = 512, + NUMATTRIBZONES = 288, + NUMZONEINDICES = 55000, + + + NUMPEDSTATS = 35, + NUMHANDLINGS = 57, + + PATHNODESIZE = 4500, + + NUMWEATHERS = 4, + NUMHOURS = 24, +}; + +#endif diff --git a/src/debugmenu_public.h b/src/debugmenu_public.h new file mode 100644 index 00000000..3671caca --- /dev/null +++ b/src/debugmenu_public.h @@ -0,0 +1,154 @@ + +extern "C" { + +typedef void (*TriggerFunc)(void); + +struct DebugMenuEntry; + +typedef DebugMenuEntry *(*DebugMenuAddInt8_TYPE)(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddInt16_TYPE)(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddInt32_TYPE)(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddInt64_TYPE)(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt8_TYPE)(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt16_TYPE)(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt32_TYPE)(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt64_TYPE)(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddFloat32_TYPE)(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound); +typedef DebugMenuEntry *(*DebugMenuAddFloat64_TYPE)(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound); +typedef DebugMenuEntry *(*DebugMenuAddCmd_TYPE)(const char *path, const char *name, TriggerFunc triggerFunc); +typedef void (*DebugMenuEntrySetWrap_TYPE)(DebugMenuEntry *e, bool wrap); +typedef void (*DebugMenuEntrySetStrings_TYPE)(DebugMenuEntry *e, const char **strings); +typedef void (*DebugMenuEntrySetAddress_TYPE)(DebugMenuEntry *e, void *addr); + +struct DebugMenuAPI +{ + bool isLoaded; + HMODULE module; + DebugMenuAddInt8_TYPE addint8; + DebugMenuAddInt16_TYPE addint16; + DebugMenuAddInt32_TYPE addint32; + DebugMenuAddInt64_TYPE addint64; + DebugMenuAddUInt8_TYPE adduint8; + DebugMenuAddUInt16_TYPE adduint16; + DebugMenuAddUInt32_TYPE adduint32; + DebugMenuAddUInt64_TYPE adduint64; + DebugMenuAddFloat32_TYPE addfloat32; + DebugMenuAddFloat64_TYPE addfloat64; + DebugMenuAddCmd_TYPE addcmd; + DebugMenuEntrySetWrap_TYPE setwrap; + DebugMenuEntrySetStrings_TYPE setstrings; + DebugMenuEntrySetAddress_TYPE setaddress; +}; +extern DebugMenuAPI gDebugMenuAPI; + +inline DebugMenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound) +{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } +inline DebugMenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound) +{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } +inline DebugMenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc) +{ return gDebugMenuAPI.addcmd(path, name, triggerFunc); } +inline void DebugMenuEntrySetWrap(DebugMenuEntry *e, bool wrap) +{ gDebugMenuAPI.setwrap(e, wrap); } +inline void DebugMenuEntrySetStrings(DebugMenuEntry *e, const char **strings) +{ gDebugMenuAPI.setstrings(e, strings); } +inline void DebugMenuEntrySetAddress(DebugMenuEntry *e, void *addr) +{ gDebugMenuAPI.setaddress(e, addr); } + +inline bool DebugMenuLoad(void) +{ + if(gDebugMenuAPI.isLoaded) + return true; + HMODULE mod = LoadLibraryA("debugmenu"); + if(mod == 0){ + char modulePath[MAX_PATH]; + HMODULE dllModule; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)&gDebugMenuAPI, &dllModule); + GetModuleFileNameA(dllModule, modulePath, MAX_PATH); + char *p = strchr(modulePath, '\\'); + if(p) p[1] = '\0'; + strcat(modulePath, "debugmenu"); + mod = LoadLibraryA(modulePath); + } + if(mod == 0) + return false; + gDebugMenuAPI.addint8 = (DebugMenuAddInt8_TYPE)GetProcAddress(mod, "DebugMenuAddInt8"); + gDebugMenuAPI.addint16 = (DebugMenuAddInt16_TYPE)GetProcAddress(mod, "DebugMenuAddInt16"); + gDebugMenuAPI.addint32 = (DebugMenuAddInt32_TYPE)GetProcAddress(mod, "DebugMenuAddInt32"); + gDebugMenuAPI.addint64 = (DebugMenuAddInt64_TYPE)GetProcAddress(mod, "DebugMenuAddInt64"); + gDebugMenuAPI.adduint8 = (DebugMenuAddUInt8_TYPE)GetProcAddress(mod, "DebugMenuAddUInt8"); + gDebugMenuAPI.adduint16 = (DebugMenuAddUInt16_TYPE)GetProcAddress(mod, "DebugMenuAddUInt16"); + gDebugMenuAPI.adduint32 = (DebugMenuAddUInt32_TYPE)GetProcAddress(mod, "DebugMenuAddUInt32"); + gDebugMenuAPI.adduint64 = (DebugMenuAddUInt64_TYPE)GetProcAddress(mod, "DebugMenuAddUInt64"); + gDebugMenuAPI.addfloat32 = (DebugMenuAddFloat32_TYPE)GetProcAddress(mod, "DebugMenuAddFloat32"); + gDebugMenuAPI.addfloat64 = (DebugMenuAddFloat64_TYPE)GetProcAddress(mod, "DebugMenuAddFloat64"); + gDebugMenuAPI.addcmd = (DebugMenuAddCmd_TYPE)GetProcAddress(mod, "DebugMenuAddCmd"); + gDebugMenuAPI.setwrap = (DebugMenuEntrySetWrap_TYPE)GetProcAddress(mod, "DebugMenuEntrySetWrap"); + gDebugMenuAPI.setstrings = (DebugMenuEntrySetStrings_TYPE)GetProcAddress(mod, "DebugMenuEntrySetStrings"); + gDebugMenuAPI.setaddress = (DebugMenuEntrySetAddress_TYPE)GetProcAddress(mod, "DebugMenuEntrySetAddress"); + gDebugMenuAPI.isLoaded = true; + gDebugMenuAPI.module = mod; + return true; +} + +} + +// Also overload them for simplicity + +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound) +{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound) +{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } + +inline DebugMenuEntry *DebugMenuAddVarBool32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} +inline DebugMenuEntry *DebugMenuAddVarBool16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} +inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} diff --git a/src/entities/Building.cpp b/src/entities/Building.cpp new file mode 100644 index 00000000..e8f19b01 --- /dev/null +++ b/src/entities/Building.cpp @@ -0,0 +1,7 @@ +#include "common.h" +#include "rpworld.h" +#include "Building.h" +#include "Pools.h" + +void *CBuilding::operator new(size_t sz) { return CPools::GetBuildingPool()->New(); } +void CBuilding::operator delete(void *p, size_t sz) { CPools::GetBuildingPool()->Delete((CBuilding*)p); } diff --git a/src/entities/Building.h b/src/entities/Building.h new file mode 100644 index 00000000..d33aaa4f --- /dev/null +++ b/src/entities/Building.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Entity.h" + +class CBuilding : public CEntity +{ +public: + // TODO: ReplaceWithNewModel + // TODO: ctor + static void *operator new(size_t); + static void operator delete(void*, size_t); + + virtual bool GetIsATreadable(void) { return false; } +}; +static_assert(sizeof(CBuilding) == 0x64, "CBuilding: error"); diff --git a/src/entities/CutsceneHead.cpp b/src/entities/CutsceneHead.cpp new file mode 100644 index 00000000..6a8874f5 --- /dev/null +++ b/src/entities/CutsceneHead.cpp @@ -0,0 +1,2 @@ +#include "common.h" +#include "CutsceneHead.h" diff --git a/src/entities/CutsceneHead.h b/src/entities/CutsceneHead.h new file mode 100644 index 00000000..5784ffc9 --- /dev/null +++ b/src/entities/CutsceneHead.h @@ -0,0 +1,10 @@ +#pragma once + +#include "CutsceneObject.h" + +class CCutsceneHead : public CCutsceneObject +{ +public: + RwFrame *m_pHeadNode; +}; +static_assert(sizeof(CCutsceneHead) == 0x19C, "CCutsceneHead: error"); diff --git a/src/entities/CutsceneObject.cpp b/src/entities/CutsceneObject.cpp new file mode 100644 index 00000000..6aa0f4b3 --- /dev/null +++ b/src/entities/CutsceneObject.cpp @@ -0,0 +1,2 @@ +#include "common.h" +#include "CutsceneObject.h" diff --git a/src/entities/CutsceneObject.h b/src/entities/CutsceneObject.h new file mode 100644 index 00000000..c5cbf83f --- /dev/null +++ b/src/entities/CutsceneObject.h @@ -0,0 +1,9 @@ +#pragma once + +#include "Object.h" + +class CCutsceneObject : public CObject +{ +public: +}; +static_assert(sizeof(CCutsceneObject) == 0x198, "CCutsceneObject: error"); diff --git a/src/entities/Entity.cpp b/src/entities/Entity.cpp new file mode 100644 index 00000000..11fa9ab2 --- /dev/null +++ b/src/entities/Entity.cpp @@ -0,0 +1,391 @@ +#include "common.h" +#include "rpworld.h" +#include "Placeable.h" +#include "Entity.h" +#include "Lights.h" +#include "World.h" +#include "Camera.h" +#include "References.h" +#include "TxdStore.h" +#include "Zones.h" +#include "patcher.h" + +int gBuildings; + +void +CEntity::GetBoundCentre(CVector &out) +{ + out = m_matrix * CModelInfo::GetModelInfo(m_modelIndex)->GetColModel()->boundingSphere.center; +}; + +bool +CEntity::GetIsTouching(CVector const ¢er, float radius) +{ + return sq(GetBoundRadius()+radius) > (GetBoundCentre()-center).MagnitudeSqr(); +} + +bool +CEntity::GetIsOnScreen(void) +{ + return TheCamera.IsSphereVisible(GetBoundCentre(), GetBoundRadius(), + &TheCamera.GetCameraMatrix()); +} + +bool +CEntity::GetIsOnScreenComplex(void) +{ + RwV3d boundBox[8]; + + if(TheCamera.IsPointVisible(GetBoundCentre(), &TheCamera.GetCameraMatrix())) + return true; + + CRect rect = GetBoundRect(); + CColModel *colmodel = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel(); + float z = GetPosition().z; + float minz = z + colmodel->boundingBox.min.z; + float maxz = z + colmodel->boundingBox.max.z; + boundBox[0].x = rect.left; + boundBox[0].y = rect.top; + boundBox[0].z = minz; + boundBox[1].x = rect.left; + boundBox[1].y = rect.bottom; + boundBox[1].z = minz; + boundBox[2].x = rect.right; + boundBox[2].y = rect.top; + boundBox[2].z = minz; + boundBox[3].x = rect.right; + boundBox[3].y = rect.bottom; + boundBox[3].z = minz; + boundBox[4].x = rect.left; + boundBox[4].y = rect.top; + boundBox[4].z = maxz; + boundBox[5].x = rect.left; + boundBox[5].y = rect.bottom; + boundBox[5].z = maxz; + boundBox[6].x = rect.right; + boundBox[6].y = rect.top; + boundBox[6].z = maxz; + boundBox[7].x = rect.right; + boundBox[7].y = rect.bottom; + boundBox[7].z = maxz; + + return TheCamera.IsBoxVisible(boundBox, &TheCamera.GetCameraMatrix()); +} + +void +CEntity::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.bottom); + yend = CWorld::GetSectorIndexY(bounds.top); + ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES]; + break; + }else switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + break; + } + list->InsertItem(this); + } +} + +void +CEntity::Remove(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.bottom); + yend = CWorld::GetSectorIndexY(bounds.top); + ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES]; + break; + }else switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + break; + } + list->RemoveItem(this); + } +} + +void +CEntity::CreateRwObject(void) +{ + CBaseModelInfo *mi; + + mi = CModelInfo::GetModelInfo(m_modelIndex); + m_rwObject = mi->CreateInstance(); + if(m_rwObject){ + if(IsBuilding()) + gBuildings++; + if(RwObjectGetType(m_rwObject) == rpATOMIC) + m_matrix.AttachRW(RwFrameGetMatrix(RpAtomicGetFrame(m_rwObject)), false); + else if(RwObjectGetType(m_rwObject) == rpCLUMP) + m_matrix.AttachRW(RwFrameGetMatrix(RpClumpGetFrame(m_rwObject)), false); + mi->AddRef(); + } +} + +void +CEntity::DeleteRwObject(void) +{ + RwFrame *f; + + m_matrix.Detach(); + if(m_rwObject){ + if(RwObjectGetType(m_rwObject) == rpATOMIC){ + f = RpAtomicGetFrame(m_rwObject); + RpAtomicDestroy((RpAtomic*)m_rwObject); + RwFrameDestroy(f); + }else if(RwObjectGetType(m_rwObject) == rpCLUMP) + RpClumpDestroy((RpClump*)m_rwObject); + m_rwObject = nil; + CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef(); + if(IsBuilding()) + gBuildings--; + } +} + +void +CEntity::UpdateRwFrame(void) +{ + if(m_rwObject){ + if(RwObjectGetType(m_rwObject) == rpATOMIC) + RwFrameUpdateObjects(RpAtomicGetFrame(m_rwObject)); + else if(RwObjectGetType(m_rwObject) == rpCLUMP) + RwFrameUpdateObjects(RpClumpGetFrame(m_rwObject)); + } +} + +void +CEntity::SetupBigBuilding(void) +{ + CSimpleModelInfo *mi; + + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_modelIndex); + bIsBIGBuilding = true; + m_flagC20 = true; + bUsesCollision = false; + m_level = CTheZones::GetLevelFromPosition(GetPosition()); + if(m_level == LEVEL_NONE){ + if(mi->GetTxdSlot() != CTxdStore::FindTxdSlot("generic")){ + mi->SetTexDictionary("generic"); + printf("%d:%s txd has been set to generic\n", m_modelIndex, mi->GetName()); + } + } + if(mi->m_lodDistances[0] > 2000.0f) + m_level = LEVEL_NONE; +} + +CRect +CEntity::GetBoundRect(void) +{ + CRect rect; + CVector v; + CColModel *col = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel(); + + rect.ContainPoint(m_matrix * col->boundingBox.min); + rect.ContainPoint(m_matrix * col->boundingBox.max); + + v = col->boundingBox.min; + v.x = col->boundingBox.max.x; + rect.ContainPoint(m_matrix * v); + + v = col->boundingBox.max; + v.x = col->boundingBox.min.x; + rect.ContainPoint(m_matrix * v); + + return rect; +} + +void +CEntity::PreRender(void) +{ +} + +void +CEntity::Render(void) +{ + if(m_rwObject){ + bImBeingRendered = true; + if(RwObjectGetType(m_rwObject) == rpATOMIC) + RpAtomicRender((RpAtomic*)m_rwObject); + else + RpClumpRender((RpClump*)m_rwObject); + bImBeingRendered = false; + } +} + +bool +CEntity::SetupLighting(void) +{ + DeActivateDirectional(); + SetAmbientColours(); + return false; +} + +void +CEntity::RegisterReference(CEntity **pent) +{ + if(IsBuilding()) + return; + CReference *ref; + // check if already registered + for(ref = m_pFirstReference; ref; ref = ref->next) + if(ref->pentity == pent) + return; + // have to allocate new reference + ref = CReferences::pEmptyList; + if(ref){ + CReferences::pEmptyList = ref->next; + + ref->pentity = pent; + ref->next = m_pFirstReference; + m_pFirstReference = ref; + return; + } + return; +} + +// Clear all references to this entity +void +CEntity::ResolveReferences(void) +{ + CReference *ref; + // clear pointers to this entity + for(ref = m_pFirstReference; ref; ref = ref->next) + if(*ref->pentity == this) + *ref->pentity = nil; + // free list + if(m_pFirstReference){ + for(ref = m_pFirstReference; ref->next; ref = ref->next) + ; + ref->next = CReferences::pEmptyList; + CReferences::pEmptyList = ref; + m_pFirstReference = nil; + } +} + +// Free all references that no longer point to this entity +void +CEntity::PruneReferences(void) +{ + CReference *ref, *next, **lastnextp; + lastnextp = &m_pFirstReference; + for(ref = m_pFirstReference; ref; ref = next){ + next = ref->next; + if(*ref->pentity == this) + lastnextp = &ref->next; + else{ + *lastnextp = ref->next; + ref->next = CReferences::pEmptyList; + CReferences::pEmptyList = ref; + } + } +} + +STARTPATCHES + InjectHook(0x4742C0, (void (CEntity::*)(CVector&))&CEntity::GetBoundCentre, PATCH_JUMP); + InjectHook(0x474310, &CEntity::GetBoundRadius, PATCH_JUMP); + InjectHook(0x474C10, &CEntity::GetIsTouching, PATCH_JUMP); + InjectHook(0x474CC0, &CEntity::GetIsOnScreen, PATCH_JUMP); + InjectHook(0x474D20, &CEntity::GetIsOnScreenComplex, PATCH_JUMP); + InjectHook(0x474CA0, &CEntity::IsVisible, PATCH_JUMP); + InjectHook(0x474330, &CEntity::UpdateRwFrame, PATCH_JUMP); + InjectHook(0x4755E0, &CEntity::SetupBigBuilding, PATCH_JUMP); + InjectHook(0x4A7480, &CEntity::RegisterReference, PATCH_JUMP); + InjectHook(0x4A74E0, &CEntity::ResolveReferences, PATCH_JUMP); + InjectHook(0x4A7530, &CEntity::PruneReferences, PATCH_JUMP); + + InjectHook(0x475080, &CEntity::Add_, PATCH_JUMP); + InjectHook(0x475310, &CEntity::Remove_, PATCH_JUMP); + InjectHook(0x473EA0, &CEntity::CreateRwObject_, PATCH_JUMP); + InjectHook(0x473F90, &CEntity::DeleteRwObject_, PATCH_JUMP); + InjectHook(0x474000, &CEntity::GetBoundRect_, PATCH_JUMP); + InjectHook(0x474BD0, &CEntity::Render_, PATCH_JUMP); + InjectHook(0x4A7C60, &CEntity::SetupLighting_, PATCH_JUMP); +ENDPATCHES diff --git a/src/entities/Entity.h b/src/entities/Entity.h new file mode 100644 index 00000000..8bcd7348 --- /dev/null +++ b/src/entities/Entity.h @@ -0,0 +1,146 @@ +#pragma once + +#include "ModelInfo.h" +#include "Placeable.h" + +struct CReference; + +enum eEntityType +{ + ENTITY_TYPE_NOTHING = 0, + ENTITY_TYPE_BUILDING, + ENTITY_TYPE_VEHICLE, + ENTITY_TYPE_PED, + ENTITY_TYPE_OBJECT, + ENTITY_TYPE_DUMMY, + ENTITY_TYPE_6, + ENTITY_TYPE_7, +}; + +enum eEntityStatus +{ + // from SA MTA! let's hope they didn't change from III + STATUS_PLAYER = 0, + STATUS_PLAYER_PLAYBACKFROMBUFFER, + STATUS_SIMPLE, + STATUS_PHYSICS, + STATUS_ABANDONED, + STATUS_WRECKED, + STATUS_TRAIN_MOVING, + STATUS_TRAIN_NOT_MOVING, + STATUS_HELI, + STATUS_PLANE, + STATUS_PLAYER_REMOTE, + STATUS_PLAYER_DISABLED, + //STATUS_TRAILER, + //STATUS_SIMPLE_TRAILER +}; + +class CEntity : public CPlaceable +{ +public: + RwObject *m_rwObject; + uint32 m_type : 3; + uint32 m_status : 5; + + // flagsA + uint32 bUsesCollision : 1; + uint32 bCollisionProcessed : 1; + uint32 bIsStatic : 1; + uint32 bHasContacted : 1; + uint32 bPedPhysics : 1; + uint32 bIsStuck : 1; + uint32 bIsInSafePosition : 1; + uint32 bUseCollisionRecords : 1; + + // flagsB + uint32 bWasPostponed : 1; + uint32 m_flagB2 : 1; // explosion proof? + uint32 bIsVisible : 1; + uint32 bHasCollided : 1; // + uint32 bRenderScorched : 1; + uint32 m_flagB20 : 1; // bFlashing? + uint32 bIsBIGBuilding : 1; + // VC inserts one more flag here: if drawdist <= 2000 + uint32 bRenderDamaged : 1; + + // flagsC + uint32 m_flagC1 : 1; // bullet proof? + uint32 m_flagC2 : 1; // fire proof? + uint32 m_flagC4 : 1; // collision proof? + uint32 m_flagC8 : 1; // melee proof? + uint32 m_flagC10 : 1; // bOnlyDamagedByPlayer? + uint32 m_flagC20 : 1; + uint32 m_bZoneCulled : 1; + uint32 m_bZoneCulled2 : 1; // only treadables+10m + + // flagsD + uint32 bRemoveFromWorld : 1; + uint32 bHasHitWall : 1; + uint32 bImBeingRendered : 1; + uint32 m_flagD8 : 1; + uint32 m_flagD10 : 1; + uint32 bDrawLast : 1; + uint32 m_flagD40 : 1; + uint32 m_flagD80 : 1; + + // flagsE + uint32 bDistanceFade : 1; + uint32 m_flagE2 : 1; + + uint16 m_scanCode; + int16 m_randomSeed; + int16 m_modelIndex; + uint16 m_level; // int16 + CReference *m_pFirstReference; + + virtual void Add(void); + virtual void Remove(void); + virtual void SetModelIndex(uint32 i) { m_modelIndex = i; CreateRwObject(); } + virtual void SetModelIndexNoCreate(uint32 i) { m_modelIndex = i; } + virtual void CreateRwObject(void); + virtual void DeleteRwObject(void); + virtual CRect GetBoundRect(void); + virtual void ProcessControl(void) {} + virtual void ProcessCollision(void) {} + virtual void ProcessShift(void) {} + virtual void Teleport(CVector v) {} + virtual void PreRender(void); + virtual void Render(void); + virtual bool SetupLighting(void); + virtual void RemoveLighting(bool) {} + virtual void FlagToDestroyWhenNextProcessed(void) {} + + bool IsBuilding(void) { return m_type == ENTITY_TYPE_BUILDING; } + bool IsVehicle(void) { return m_type == ENTITY_TYPE_VEHICLE; } + bool IsPed(void) { return m_type == ENTITY_TYPE_PED; } + bool IsObject(void) { return m_type == ENTITY_TYPE_OBJECT; } + bool IsDummy(void) { return m_type == ENTITY_TYPE_DUMMY; } + + void GetBoundCentre(CVector &out); + CVector GetBoundCentre(void) { CVector v; GetBoundCentre(v); return v; } + float GetBoundRadius(void) { return CModelInfo::GetModelInfo(m_modelIndex)->GetColModel()->boundingSphere.radius; } + bool GetIsTouching(CVector const ¢er, float r); + bool GetIsOnScreen(void); + bool GetIsOnScreenComplex(void); + bool IsVisible(void) { return m_rwObject && bIsVisible && GetIsOnScreen(); } + bool IsVisibleComplex(void) { return m_rwObject && bIsVisible && GetIsOnScreenComplex(); } + int GetModelIndex(void) { return m_modelIndex; } + void UpdateRwFrame(void); + void SetupBigBuilding(void); + + void RegisterReference(CEntity **pent); + void ResolveReferences(void); + void PruneReferences(void); + + + // to make patching virtual functions possible + void Add_(void) { CEntity::Add(); } + void Remove_(void) { CEntity::Remove(); } + void CreateRwObject_(void) { CEntity::CreateRwObject(); } + void DeleteRwObject_(void) { CEntity::DeleteRwObject(); } + CRect GetBoundRect_(void) { return CEntity::GetBoundRect(); } + void Render_(void) { CEntity::Render(); } + bool SetupLighting_(void) { return CEntity::SetupLighting(); } +}; +static_assert(sizeof(CEntity) == 0x64, "CEntity: error"); diff --git a/src/entities/Object.cpp b/src/entities/Object.cpp new file mode 100644 index 00000000..8ce1250f --- /dev/null +++ b/src/entities/Object.cpp @@ -0,0 +1,9 @@ +#include "common.h" +#include "patcher.h" +#include "Object.h" +#include "Pools.h" + +void *CObject::operator new(size_t sz) { return CPools::GetObjectPool()->New(); } +void CObject::operator delete(void *p, size_t sz) { CPools::GetObjectPool()->Delete((CObject*)p); } + +WRAPPER void CObject::ObjectDamage(float amount) { EAXJMP(0x4BB240); } diff --git a/src/entities/Object.h b/src/entities/Object.h new file mode 100644 index 00000000..6992b92d --- /dev/null +++ b/src/entities/Object.h @@ -0,0 +1,50 @@ +#pragma once + +#include "Physical.h" + +enum { + GAME_OBJECT = 1, + MISSION_OBJECT = 2, + TEMP_OBJECT = 3, +}; + +class CObject : public CPhysical +{ +public: + CMatrix m_objectMatrix; + float m_fUprootLimit; + int8 ObjectCreatedBy; +// int8 m_nObjectFlags; + int8 m_obj_flag1 : 1; + int8 m_obj_flag2 : 1; + int8 m_obj_flag4 : 1; + int8 m_obj_flag8 : 1; + int8 m_obj_flag10 : 1; + int8 bHasBeenDamaged : 1; + int8 m_obj_flag40 : 1; + int8 m_obj_flag80 : 1; + int8 field_172; + int8 field_173; + float m_fCollisionDamageMultiplier; + int8 m_nCollisionDamageEffect; + int8 m_bSpecialCollisionResponseCases; + int8 m_bCameraToAvoidThisObject; + int8 field_17B; + int8 field_17C; + int8 field_17D; + int8 field_17E; + int8 field_17F; + int32 m_nEndOfLifeTime; + int16 m_nRefModelIndex; + int8 field_186; + int8 field_187; + CEntity *m_pCurSurface; + CEntity *field_18C; + int8 m_colour1, m_colour2; + + static void *operator new(size_t); + static void operator delete(void*, size_t); + + void ObjectDamage(float amount); +}; +static_assert(sizeof(CObject) == 0x198, "CObject: error"); diff --git a/src/entities/Ped.h b/src/entities/Ped.h new file mode 100644 index 00000000..fd71b616 --- /dev/null +++ b/src/entities/Ped.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Physical.h" + +enum PedAction +{ + PED_PASSENGER = 44, +}; + +class CVehicle; + +class CPed : public CPhysical +{ +public: + // 0x128 + uint8 stuff1[252]; + int32 m_nPedState; + uint8 stuff2[196]; + CEntity *m_pCurrentPhysSurface; + CVector m_vecOffsetFromPhysSurface; + CEntity *m_pCurSurface; + uint8 stuff3[16]; + CVehicle *m_pMyVehicle; + bool bInVehicle; + uint8 stuff4[23]; + int32 m_nPedType; + uint8 stuff5[528]; + + bool IsPlayer(void) { return m_nPedType == 0 || m_nPedType== 1 || m_nPedType == 2 || m_nPedType == 3; } +}; +static_assert(offsetof(CPed, m_nPedState) == 0x224, "CPed: error"); +static_assert(offsetof(CPed, m_pCurSurface) == 0x2FC, "CPed: error"); +static_assert(offsetof(CPed, m_pMyVehicle) == 0x310, "CPed: error"); +static_assert(offsetof(CPed, m_nPedType) == 0x32C, "CPed: error"); +static_assert(sizeof(CPed) == 0x540, "CPed: error"); diff --git a/src/entities/Physical.cpp b/src/entities/Physical.cpp new file mode 100644 index 00000000..f235cb42 --- /dev/null +++ b/src/entities/Physical.cpp @@ -0,0 +1,916 @@ +#include "common.h" +#include "patcher.h" +#include "World.h" +#include "Timer.h" +#include "ModelIndices.h" +#include "Vehicle.h" +#include "Ped.h" +#include "Object.h" +#include "Glass.h" +#include "ParticleObject.h" +#include "Particle.h" +#include "SurfaceTable.h" +#include "Physical.h" + +void +CPhysical::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.bottom); + yend = CWorld::GetSectorIndexY(bounds.top); + ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + default: + assert(0); + }else switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + default: + assert(0); + } + CPtrNode *node = list->InsertItem(this); + assert(node); + m_entryInfoList.InsertItem(list, node, s); + } +} + +void +CPhysical::Remove(void) +{ + CEntryInfoNode *node, *next; + for(node = m_entryInfoList.first; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} + +void +CPhysical::RemoveAndAdd(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.bottom); + yend = CWorld::GetSectorIndexY(bounds.top); + ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + // we'll try to recycle nodes from here + CEntryInfoNode *next = m_entryInfoList.first; + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + }else switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + } + if(next){ + // If we still have old nodes, use them + next->list->RemoveNode(next->listnode); + list->InsertNode(next->listnode); + next->list = list; + next->sector = s; + next = next->next; + }else{ + CPtrNode *node = list->InsertItem(this); + m_entryInfoList.InsertItem(list, node, s); + } + } + + // Remove old nodes we no longer need + CEntryInfoNode *node; + for(node = next; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} + +CRect +CPhysical::GetBoundRect(void) +{ + CVector center; + float radius; + GetBoundCentre(center); + radius = GetBoundRadius(); + return CRect(center.x-radius, center.y-radius, center.x+radius, center.y+radius); +} + +void +CPhysical::AddToMovingList(void) +{ + m_movingListNode = CWorld::GetMovingEntityList().InsertItem(this); +} + +void +CPhysical::RemoveFromMovingList(void) +{ + if(m_movingListNode){ + CWorld::GetMovingEntityList().DeleteNode(m_movingListNode); + m_movingListNode = nil; + } +} + + +/* + * Some quantities (german in parens): + * + * acceleration: distance/time^2: a + * velocity: distance/time: v (GTA: speed) + * momentum (impuls): velocity*mass: p + * impulse (kraftstoss): delta momentum, force*time: J + * + * angular equivalents: + * velocity -> angular velocity (GTA: turn speed) + * momentum -> angular momentum (drehimpuls): L = r cross p + * force -> torque (drehmoment): tau = r cross F + * mass -> moment of inertia, angular mass (drehmoment, drehmasse): I = L/omega (GTA: turn mass) + */ + +CVector +CPhysical::GetSpeed(const CVector &r) +{ + return m_vecMoveSpeed + m_vecMoveFriction + CrossProduct(m_vecTurnFriction + m_vecTurnSpeed, r); +} + +void +CPhysical::ApplyMoveSpeed(void) +{ + GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep(); +} + +void +CPhysical::ApplyTurnSpeed(void) +{ + // Move the coordinate axes by their speed + // Note that this denormalizes the matrix + CVector turnvec = m_vecTurnSpeed*CTimer::GetTimeStep(); + GetRight() += CrossProduct(turnvec, GetRight()); + GetForward() += CrossProduct(turnvec, GetForward()); + GetUp() += CrossProduct(turnvec, GetUp()); +} + +void +CPhysical::ApplyMoveForce(float jx, float jy, float jz) +{ + m_vecMoveSpeed += CVector(jx, jy, jz)*(1.0f/m_fMass); +} + +void +CPhysical::ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz) +{ + CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz)); + m_vecTurnSpeed += turnimpulse*(1.0f/m_fTurnMass); +} + +void +CPhysical::ApplyFrictionMoveForce(float jx, float jy, float jz) +{ + m_vecMoveFriction += CVector(jx, jy, jz)*(1.0f/m_fMass); +} + +void +CPhysical::ApplyFrictionTurnForce(float jx, float jy, float jz, float px, float py, float pz) +{ + CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz)); + m_vecTurnFriction += turnimpulse*(1.0f/m_fTurnMass); +} + +void +CPhysical::ApplySpringCollision(float f1, CVector &v, CVector &p, float f2, float f3) +{ + if(1.0f - f2 <= 0.0f) + return; + float step = min(CTimer::GetTimeStep(), 3.0f); + float strength = -0.008f*m_fMass*2.0f*step * f1 * (1.0f-f2) * f3; + ApplyMoveForce(v.x*strength, v.y*strength, v.z*strength); + ApplyTurnForce(v.x*strength, v.y*strength, v.z*strength, p.x, p.y, p.z); +} + +void +CPhysical::ApplyGravity(void) +{ + if(bAffectedByGravity) + m_vecMoveSpeed.z -= 0.008f * CTimer::GetTimeStep(); +} + +void +CPhysical::ApplyFriction(void) +{ + m_vecMoveSpeed += m_vecMoveFriction; + m_vecTurnSpeed += m_vecTurnFriction; + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); +} + +void +CPhysical::ApplyAirResistance(void) +{ + if(m_fAirResistance > 0.1f){ + float f = powf(m_fAirResistance, CTimer::GetTimeStep()); + m_vecMoveSpeed *= f; + m_vecTurnSpeed *= f; + }else{ + float f = powf(1.0f/(m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr() + 1.0f), CTimer::GetTimeStep()); + m_vecMoveSpeed *= f; + m_vecTurnSpeed *= 0.99f; + } +} + + +bool +CPhysical::ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB) +{ + float eA, eB; + CPhysical *A = this; + CObject *Bobj = (CObject*)B; + + bool ispedcontactA = false; + bool ispedcontactB = false; + + float timestepA; + if(B->bPedPhysics){ + timestepA = 10.0f; + if(B->IsPed() && ((CPed*)B)->m_pCurrentPhysSurface == A) + ispedcontactA = true; + }else + timestepA = A->m_phy_flagA1 ? 2.0f : 1.0f; + + float timestepB; + if(A->bPedPhysics){ + if(A->IsPed() && ((CPed*)A)->IsPlayer() && B->IsVehicle() && + (B->m_status == STATUS_ABANDONED || B->m_status == STATUS_WRECKED || A->bHasHitWall)) + timestepB = 2200.0f / B->m_fMass; + else + timestepB = 10.0f; + + if(A->IsPed() && ((CPed*)A)->m_pCurrentPhysSurface == B) + ispedcontactB = true; + }else + timestepB = B->m_phy_flagA1 ? 2.0f : 1.0f; + + float speedA, speedB; + if(B->bIsStatic){ + if(A->bPedPhysics){ + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + if(speedA < 0.0f){ + if(B->IsObject()){ + impulseA = -speedA * A->m_fMass; + impulseB = impulseA; + if(impulseA > Bobj->m_fUprootLimit){ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false); + else if(!B->bInfiniteMass) + B->bIsStatic = false; + }else{ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToSoftCollision(B, impulseA); + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA); + return true; + } + }else if(!B->bInfiniteMass) + B->bIsStatic = false; + + if(B->bInfiniteMass){ + impulseA = -speedA * A->m_fMass; + impulseB = 0.0f; + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA); + return true; + } + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + if(speedA < 0.0f){ + if(B->IsObject()){ + if(A->bHasHitWall) + eA = -1.0f; + else + eA = -(1.0f + A->m_fElasticity); + impulseA = eA * speedA * A->GetMass(pointposA, colpoint.normal); + impulseB = impulseA; + + if(Bobj->m_nCollisionDamageEffect && impulseA > 20.0f){ + Bobj->ObjectDamage(impulseA); + if(!B->bUsesCollision){ + if(!A->bInfiniteMass){ + A->ApplyMoveForce(colpoint.normal*0.2f*impulseA); + A->ApplyTurnForce(colpoint.normal*0.2f*impulseA, pointposA); + } + return false; + } + } + + if((impulseA > Bobj->m_fUprootLimit || A->bIsStuck) && + !B->bInfiniteMass){ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false); + else + B->bIsStatic = false; + int16 model = B->GetModelIndex(); + if(model == MI_FIRE_HYDRANT && !Bobj->bHasBeenDamaged){ + CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, B->GetPosition() - CVector(0.0f, 0.0f, 0.5f), true); + Bobj->bHasBeenDamaged = true; + }else if(B->IsObject() && model != MI_EXPLODINGBARREL && model != MI_PETROLPUMP) + Bobj->bHasBeenDamaged = true; + }else{ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToSoftCollision(B, impulseA); + CVector f = colpoint.normal * impulseA; + if(A->IsVehicle() && colpoint.normal.z < 0.7f) + f.z *= 0.3f; + if(!A->bInfiniteMass){ + A->ApplyMoveForce(f); + if(!A->IsVehicle() || !CWorld::bNoMoreCollisionTorque) + A->ApplyTurnForce(f, pointposA); + } + return true; + } + }else if(!B->bInfiniteMass) + B->bIsStatic = false; + } + } + + if(B->bIsStatic) + return false; + if(!B->bInfiniteMass) + B->AddToMovingList(); + } + + // B is not static + + if(A->bPedPhysics && B->bPedPhysics){ + // negative if A is moving towards B + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + // positive if B is moving towards A + // not interested in how much B moves into A apparently? + // only interested in cases where A collided into B + speedB = max(0.0f, DotProduct(B->m_vecMoveSpeed, colpoint.normal)); + // A has moved into B + if(speedA < speedB){ + if(!A->bHasHitWall) + speedB -= (speedA - speedB) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (speedB-speedA) * A->m_fMass * timestepA; + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*(impulseA/timestepA)); + return true; + } + }else if(A->bPedPhysics){ + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal); + + float a = A->m_fMass*timestepA; + float b = B->GetMassTime(pointposB, colpoint.normal, timestepB); + float speedSum = (b*speedB + a*speedA)/(a + b); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * a; + impulseB = -(eB - speedB) * b; + CVector fA = colpoint.normal*(impulseA/timestepA); + CVector fB = colpoint.normal*(-impulseB/timestepB); + if(!A->bInfiniteMass){ + if(fA.z < 0.0f) fA.z = 0.0f; + if(ispedcontactB){ + fA.x *= 2.0f; + fA.y *= 2.0f; + } + A->ApplyMoveForce(fA); + } + if(!B->bInfiniteMass && !ispedcontactB){ + B->ApplyMoveForce(fB); + B->ApplyTurnForce(fB, pointposB); + } + return true; + } + }else if(B->bPedPhysics){ + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + + float a = A->GetMassTime(pointposA, colpoint.normal, timestepA); + float b = B->m_fMass*timestepB; + float speedSum = (b*speedB + a*speedA)/(a + b); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * a; + impulseB = -(eB - speedB) * b; + CVector fA = colpoint.normal*(impulseA/timestepA); + CVector fB = colpoint.normal*(-impulseB/timestepB); + if(!A->bInfiniteMass && !ispedcontactA){ + if(fA.z < 0.0f) fA.z = 0.0f; + A->ApplyMoveForce(fA); + A->ApplyTurnForce(fA, pointposA); + } + if(!B->bInfiniteMass){ + if(fB.z < 0.0f){ + fB.z = 0.0f; + if(fabs(speedA) < 0.01f) + fB *= 0.5f; + } + if(ispedcontactA){ + fB.x *= 2.0f; + fB.y *= 2.0f; + } + B->ApplyMoveForce(fB); + } + return true; + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal); + float a = A->GetMassTime(pointposA, colpoint.normal, timestepA); + float b = B->GetMassTime(pointposB, colpoint.normal, timestepB); + float speedSum = (b*speedB + a*speedA)/(a + b); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * a; + impulseB = -(eB - speedB) * b; + CVector fA = colpoint.normal*(impulseA/timestepA); + CVector fB = colpoint.normal*(-impulseB/timestepB); + if(A->IsVehicle() && !A->bHasHitWall){ + fA.x *= 1.4f; + fA.y *= 1.4f; + if(colpoint.normal.z < 0.7f) + fA.z *= 0.3f; + if(A->m_status == STATUS_PLAYER) + pointposA *= 0.8f; + if(CWorld::bNoMoreCollisionTorque){ + A->ApplyFrictionMoveForce(fA*-0.3f); + A->ApplyFrictionTurnForce(fA*-0.3f, pointposA); + } + } + if(B->IsVehicle() && !B->bHasHitWall){ + fB.x *= 1.4f; + fB.y *= 1.4f; + if(colpoint.normal.z < 0.7f) + fB.z *= 0.3f; + if(B->m_status == STATUS_PLAYER) + pointposB *= 0.8f; + if(CWorld::bNoMoreCollisionTorque){ + // BUG: the game actually uses A here, but this can't be right + B->ApplyFrictionMoveForce(fB*-0.3f); + B->ApplyFrictionTurnForce(fB*-0.3f, pointposB); + } + } + if(!A->bInfiniteMass){ + A->ApplyMoveForce(fA); + A->ApplyTurnForce(fA, pointposA); + } + if(!B->bInfiniteMass){ + if(B->bIsInSafePosition) + B->UnsetIsInSafePosition(); + B->ApplyMoveForce(fB); + B->ApplyTurnForce(fB, pointposB); + } + return true; + } + } + return false; +} + +bool +CPhysical::ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed) +{ + float normalSpeed; + float e; + CVector speed; + CVector vImpulse; + + if(bPedPhysics){ + normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal); + if(normalSpeed < 0.0f){ + impulse = -normalSpeed * m_fMass; + ApplyMoveForce(colpoint.normal * impulse); + return true; + } + }else{ + CVector pointpos = colpoint.point - GetPosition(); + speed = GetSpeed(pointpos); + normalSpeed = DotProduct(speed, colpoint.normal); + if(normalSpeed < 0.0f){ + float minspeed = 0.0104f * CTimer::GetTimeStep(); + if((IsObject() || IsVehicle() && GetUp().z < -0.3f) && + !bHasContacted && + fabs(m_vecMoveSpeed.x) < minspeed && + fabs(m_vecMoveSpeed.y) < minspeed && + fabs(m_vecMoveSpeed.z) < minspeed*2.0f) + e = -1.0f; + else + e = -(m_fElasticity + 1.0f); + impulse = normalSpeed * e * GetMass(pointpos, colpoint.normal); + + // ApplyMoveForce + vImpulse = colpoint.normal*impulse; + if(IsVehicle() && + (!bHasHitWall || + !(m_vecMoveSpeed.MagnitudeSqr() > 0.1 || !(B->IsBuilding() || ((CPhysical*)B)->bInfiniteMass)))) + moveSpeed += vImpulse * 1.2f * (1.0f/m_fMass); + else + moveSpeed += vImpulse * (1.0f/m_fMass); + + // ApplyTurnForce + CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(pointpos-com, vImpulse); + turnSpeed += turnimpulse*(1.0f/m_fTurnMass); + + return true; + } + } + return false; +} + +bool +CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint) +{ + CVector speedA, speedB; + float normalSpeedA, normalSpeedB; + CVector vOtherSpeedA, vOtherSpeedB; + float fOtherSpeedA, fOtherSpeedB; + float speedSum; + CVector frictionDir; + float impulseA, impulseB; + float impulseLimit; + CPhysical *A = this; + + if(A->bPedPhysics && B->bPedPhysics){ + normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA; + vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); + speedSum = (B->m_fMass*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(B->m_fMass + A->m_fMass); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; + impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; // BUG: game has A's clamp again here, but this can't be right + A->ApplyFrictionMoveForce(frictionDir*impulseA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + return true; + } + }else if(A->bPedPhysics){ + if(B->IsVehicle()) + return false; + CVector pointposB = colpoint.point - B->GetPosition(); + speedB = B->GetSpeed(pointposB); + + normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + normalSpeedB = DotProduct(speedB, colpoint.normal); + vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA; + vOtherSpeedB = speedB - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); + float massB = B->GetMass(pointposB, frictionDir); + speedSum = (massB*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(massB + A->m_fMass); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; + impulseB = (speedSum - fOtherSpeedB) * massB; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB); + return true; + } + }else if(B->bPedPhysics){ + if(A->IsVehicle()) + return false; + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = A->GetSpeed(pointposA); + + normalSpeedA = DotProduct(speedA, colpoint.normal); + normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + vOtherSpeedA = speedA - colpoint.normal*normalSpeedA; + vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); + float massA = A->GetMass(pointposA, frictionDir); + speedSum = (B->m_fMass*fOtherSpeedB + massA*fOtherSpeedA)/(B->m_fMass + massA); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * massA; + impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + return true; + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = A->GetSpeed(pointposA); + speedB = B->GetSpeed(pointposB); + + normalSpeedA = DotProduct(speedA, colpoint.normal); + normalSpeedB = DotProduct(speedB, colpoint.normal); + vOtherSpeedA = speedA - colpoint.normal*normalSpeedA; + vOtherSpeedB = speedB - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); + float massA = A->GetMass(pointposA, frictionDir); + float massB = B->GetMass(pointposB, frictionDir); + speedSum = (massB*fOtherSpeedB + massA*fOtherSpeedA)/(massB + massA); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * massA; + impulseB = (speedSum - fOtherSpeedB) * massB; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB); + return true; + } + } + return false; +} + +bool +CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint) +{ + CVector speed; + float normalSpeed; + CVector vOtherSpeed; + float fOtherSpeed; + CVector frictionDir; + float fImpulse; + float impulseLimit; + + if(bPedPhysics){ + normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal); + vOtherSpeed = m_vecMoveSpeed - colpoint.normal*normalSpeed; + + fOtherSpeed = vOtherSpeed.Magnitude(); + if(fOtherSpeed > 0.0f){ + frictionDir = vOtherSpeed * (1.0f/fOtherSpeed); + // not really impulse but speed + // maybe use ApplyFrictionMoveForce instead? + fImpulse = -fOtherSpeed; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass; + if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; + CVector vImpulse = frictionDir*fImpulse; + m_vecMoveFriction += CVector(vImpulse.x, vImpulse.y, 0.0f); + return true; + } + }else{ + CVector pointpos = colpoint.point - GetPosition(); + speed = GetSpeed(pointpos); + normalSpeed = DotProduct(speed, colpoint.normal); + vOtherSpeed = speed - colpoint.normal*normalSpeed; + + fOtherSpeed = vOtherSpeed.Magnitude(); + if(fOtherSpeed > 0.0f){ + frictionDir = vOtherSpeed * (1.0f/fOtherSpeed); + fImpulse = -fOtherSpeed * m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5f; + if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; + ApplyFrictionMoveForce(frictionDir*fImpulse); + ApplyFrictionTurnForce(frictionDir*fImpulse, pointpos); + + if(fOtherSpeed > 0.1f && + colpoint.surfaceB != SURFACE_2 && colpoint.surfaceB != SURFACE_4 && + CSurfaceTable::GetAdhesionGroup(colpoint.surfaceA) == ADHESIVE_HARD){ + CVector v = frictionDir * fOtherSpeed * 0.25f; + for(int i = 0; i < 4; i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpoint.point, v); + } + return true; + } + } + return false; +} + + +void +CPhysical::AddCollisionRecord(CEntity *ent) +{ + AddCollisionRecord_Treadable(ent); + this->bHasCollided = true; + ent->bHasCollided = true; + if(IsVehicle() && ent->IsVehicle()){ + if(((CVehicle*)this)->m_nAlarmState == -1) + ((CVehicle*)this)->m_nAlarmState = 15000; + if(((CVehicle*)ent)->m_nAlarmState == -1) + ((CVehicle*)ent)->m_nAlarmState = 15000; + } + if(bUseCollisionRecords){ + int i; + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] == ent) + return; + if(m_nCollisionRecords < PHYSICAL_MAX_COLLISIONRECORDS) + m_aCollisionRecords[m_nCollisionRecords++] = ent; + m_nLastTimeCollided = CTimer::GetTimeInMilliseconds(); + } +} + +void +CPhysical::AddCollisionRecord_Treadable(CEntity *ent) +{ + if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){ + CTreadable *t = (CTreadable*)ent; + if(t->m_nodeIndicesPeds[0] >= 0 || + t->m_nodeIndicesPeds[1] >= 0 || + t->m_nodeIndicesPeds[2] >= 0 || + t->m_nodeIndicesPeds[3] >= 0) + m_pedTreadable = t; + if(t->m_nodeIndicesCars[0] >= 0 || + t->m_nodeIndicesCars[1] >= 0 || + t->m_nodeIndicesCars[2] >= 0 || + t->m_nodeIndicesCars[3] >= 0) + m_carTreadable = t; + } +} + +bool +CPhysical::GetHasCollidedWith(CEntity *ent) +{ + int i; + if(bUseCollisionRecords) + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] == ent) + return true; + return false; +} + +void +CPhysical::ProcessControl(void) +{ + if(!IsPed()) + m_phy_flagA8 = false; + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + + if(m_status == STATUS_SIMPLE) + return; + + m_nCollisionRecords = 0; + bHasCollided = false; + m_nCollisionPieceType = 0; + m_fCollisionImpulse = 0.0f; + m_pCollidingEntity = nil; + + if(!bIsStuck){ + if(IsObject() || + IsPed() && !bPedPhysics){ + m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f; + m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f; + float step = CTimer::GetTimeStep() * 0.003; + if(m_vecMoveSpeedAvg.MagnitudeSqr() < step*step && + m_vecTurnSpeedAvg.MagnitudeSqr() < step*step){ + m_nStaticFrames++; + if(m_nStaticFrames > 10){ + m_nStaticFrames = 10; + bIsStatic = true; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = m_vecMoveSpeed; + m_vecTurnFriction = m_vecTurnSpeed; + return; + } + }else + m_nStaticFrames = 0; + } + } + ApplyGravity(); + ApplyFriction(); + ApplyAirResistance(); +} + +STARTPATCHES + InjectHook(0x4951F0, &CPhysical::Add_, PATCH_JUMP); + InjectHook(0x4954B0, &CPhysical::Remove_, PATCH_JUMP); + InjectHook(0x495540, &CPhysical::RemoveAndAdd, PATCH_JUMP); + InjectHook(0x495F10, &CPhysical::ProcessControl_, PATCH_JUMP); + InjectHook(0x4958F0, &CPhysical::AddToMovingList, PATCH_JUMP); + InjectHook(0x495940, &CPhysical::RemoveFromMovingList, PATCH_JUMP); + + InjectHook(0x497180, &CPhysical::AddCollisionRecord, PATCH_JUMP); + InjectHook(0x4970C0, &CPhysical::AddCollisionRecord_Treadable, PATCH_JUMP); + InjectHook(0x497240, &CPhysical::GetHasCollidedWith, PATCH_JUMP); + +#define F3 float, float, float + InjectHook(0x495B10, &CPhysical::ApplyMoveSpeed, PATCH_JUMP); + InjectHook(0x497280, &CPhysical::ApplyTurnSpeed, PATCH_JUMP); + InjectHook(0x4959A0, (void (CPhysical::*)(F3))&CPhysical::ApplyMoveForce, PATCH_JUMP); + InjectHook(0x495A10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyTurnForce, PATCH_JUMP); + InjectHook(0x495D90, (void (CPhysical::*)(F3))&CPhysical::ApplyFrictionMoveForce, PATCH_JUMP); + InjectHook(0x495E10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyFrictionTurnForce, PATCH_JUMP); + InjectHook(0x499890, &CPhysical::ApplySpringCollision, PATCH_JUMP); + InjectHook(0x495B50, &CPhysical::ApplyGravity, PATCH_JUMP); + InjectHook(0x495B80, (void (CPhysical::*)(void))&CPhysical::ApplyFriction, PATCH_JUMP); + InjectHook(0x495C20, &CPhysical::ApplyAirResistance, PATCH_JUMP); + + InjectHook(0x4973A0, &CPhysical::ApplyCollision, PATCH_JUMP); + InjectHook(0x4992A0, &CPhysical::ApplyCollisionAlt, PATCH_JUMP); + InjectHook(0x499BE0, (bool (CPhysical::*)(float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP); + InjectHook(0x49A180, (bool (CPhysical::*)(CPhysical*, float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP); +ENDPATCHES diff --git a/src/entities/Physical.h b/src/entities/Physical.h new file mode 100644 index 00000000..681ab5c8 --- /dev/null +++ b/src/entities/Physical.h @@ -0,0 +1,137 @@ +#pragma once + +#include "Lists.h" +#include "Entity.h" +#include "Treadable.h" + +enum { + PHYSICAL_MAX_COLLISIONRECORDS = 6 +}; + +class CPhysical : public CEntity +{ +public: + // The not properly indented fields haven't been checked properly yet + + int uAudioEntityId; + float unk1; + CTreadable *m_carTreadable; + CTreadable *m_pedTreadable; + uint32 m_nLastTimeCollided; + CVector m_vecMoveSpeed; // velocity + CVector m_vecTurnSpeed; // angular velocity + CVector m_vecMoveFriction; + CVector m_vecTurnFriction; + CVector m_vecMoveSpeedAvg; + CVector m_vecTurnSpeedAvg; + float m_fMass; + float m_fTurnMass; // moment of inertia + float fForceMultiplier; + float m_fAirResistance; + float m_fElasticity; + float fPercentSubmerged; + CVector m_vecCentreOfMass; + CEntryInfoList m_entryInfoList; + CPtrNode *m_movingListNode; + + char field_EC; + uint8 m_nStaticFrames; + uint8 m_nCollisionRecords; + char field_EF; + CEntity *m_aCollisionRecords[PHYSICAL_MAX_COLLISIONRECORDS]; + + float m_fDistanceTravelled; + + // damaged piece + float m_fCollisionImpulse; + CEntity *m_pCollidingEntity; + CVector m_vecCollisionDirection; + int16 m_nCollisionPieceType; + + uint8 m_phy_flagA1 : 1; + uint8 bAffectedByGravity : 1; + uint8 bInfiniteMass : 1; + uint8 m_phy_flagA8 : 1; + uint8 m_phy_flagA10 : 1; + uint8 m_phy_flagA20 : 1; + uint8 m_phy_flagA40 : 1; + uint8 m_phy_flagA80 : 1; + + uint8 m_phy_flagB1 : 1; + uint8 m_phy_flagB2 : 1; + uint8 m_phy_flagB4 : 1; + uint8 m_phy_flagB8 : 1; + uint8 m_phy_flagB10 : 1; + uint8 m_phy_flagB20 : 1; + uint8 m_phy_flagB40 : 1; + uint8 m_phy_flagB80 : 1; + + char byteLastCollType; + char byteZoneLevel; + int16 pad; + + + // from CEntity + void Add(void); + void Remove(void); + CRect GetBoundRect(void); + void ProcessControl(void); + + void RemoveAndAdd(void); + void AddToMovingList(void); + void RemoveFromMovingList(void); + + // get speed of point p relative to entity center + CVector GetSpeed(const CVector &r); + CVector GetSpeed(void) { return GetSpeed(CVector(0.0f, 0.0f, 0.0f)); } + float GetMass(const CVector &pos, const CVector &dir) { + return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/m_fTurnMass + + 1.0f/m_fMass); + } + float GetMassTime(const CVector &pos, const CVector &dir, float t) { + return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/(m_fTurnMass*t) + + 1.0f/(m_fMass*t)); + } + void UnsetIsInSafePosition(void) { + m_vecMoveSpeed *= -1.0f; + m_vecTurnSpeed *= -1.0f; + ApplyTurnSpeed(); + ApplyMoveSpeed(); + m_vecMoveSpeed *= -1.0f; + m_vecTurnSpeed *= -1.0f; + bIsInSafePosition = false; + } + + void ApplyMoveSpeed(void); + void ApplyTurnSpeed(void); + // Force actually means Impulse here + void ApplyMoveForce(float jx, float jy, float jz); + void ApplyMoveForce(const CVector &j) { ApplyMoveForce(j.x, j.y, j.z); } + // v(x,y,z) is direction of force, p(x,y,z) is point relative to model center where force is applied + void ApplyTurnForce(float jx, float jy, float jz, float rx, float ry, float rz); + // v is direction of force, p is point relative to model center where force is applied + void ApplyTurnForce(const CVector &j, const CVector &p) { ApplyTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); } + void ApplyFrictionMoveForce(float jx, float jy, float jz); + void ApplyFrictionMoveForce(const CVector &j) { ApplyFrictionMoveForce(j.x, j.y, j.z); } + void ApplyFrictionTurnForce(float jx, float jy, float jz, float rx, float ry, float rz); + void ApplyFrictionTurnForce(const CVector &j, const CVector &p) { ApplyFrictionTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); } + void ApplySpringCollision(float f1, CVector &v, CVector &p, float f2, float f3); + void ApplyGravity(void); + void ApplyFriction(void); + void ApplyAirResistance(void); + bool ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB); + bool ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed); + bool ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint); + bool ApplyFriction(float adhesiveLimit, CColPoint &colpoint); + + void AddCollisionRecord(CEntity *ent); + void AddCollisionRecord_Treadable(CEntity *ent); + bool GetHasCollidedWith(CEntity *ent); + + // to make patching virtual functions possible + void Add_(void) { CPhysical::Add(); } + void Remove_(void) { CPhysical::Remove(); } + CRect GetBoundRect_(void) { return CPhysical::GetBoundRect(); } + void ProcessControl_(void) { CPhysical::ProcessControl(); } +}; +static_assert(sizeof(CPhysical) == 0x128, "CPhysical: error"); diff --git a/src/entities/Treadable.cpp b/src/entities/Treadable.cpp new file mode 100644 index 00000000..e2eca36a --- /dev/null +++ b/src/entities/Treadable.cpp @@ -0,0 +1,7 @@ +#include "common.h" +#include "rpworld.h" +#include "Treadable.h" +#include "Pools.h" + +void *CTreadable::operator new(size_t sz) { return CPools::GetTreadablePool()->New(); } +void CTreadable::operator delete(void *p, size_t sz) { CPools::GetTreadablePool()->Delete((CTreadable*)p); } diff --git a/src/entities/Treadable.h b/src/entities/Treadable.h new file mode 100644 index 00000000..df5c9ee0 --- /dev/null +++ b/src/entities/Treadable.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Building.h" + +class CTreadable : public CBuilding +{ +public: + static void *operator new(size_t); + static void operator delete(void*, size_t); + + int16 m_nodeIndicesCars[12]; + int16 m_nodeIndicesPeds[12]; + + virtual bool GetIsATreadable(void) { return true; } +}; +static_assert(sizeof(CTreadable) == 0x94, "CTreadable: error"); diff --git a/src/entities/Vehicle.h b/src/entities/Vehicle.h new file mode 100644 index 00000000..598b4a57 --- /dev/null +++ b/src/entities/Vehicle.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Physical.h" + +class CPed; + +class CVehicle : public CPhysical +{ +public: + // 0x128 + uint8 stuff1[120]; + int16 m_nAlarmState; + CPed *pDriver; + CPed *pPassengers[8]; + uint8 stuff2[24]; + CEntity *m_pCurSurface; + uint8 stuff3[160]; + int32 m_vehType; +}; +static_assert(sizeof(CVehicle) == 0x288, "CVehicle: error"); +static_assert(offsetof(CVehicle, m_pCurSurface) == 0x1E0, "CVehicle: error"); diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..b17a30a3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,98 @@ +#include "common.h" +#include +#include "patcher.h" +#include "Renderer.h" +#include "debugmenu_public.h" + +void **rwengine = *(void***)0x5A10E1; + +RsGlobalType &RsGlobal = *(RsGlobalType*)0x8F4360; + +GlobalScene &Scene = *(GlobalScene*)0x726768; + +DebugMenuAPI gDebugMenuAPI; + +WRAPPER void *gtanew(uint32 sz) { EAXJMP(0x5A0690); } +WRAPPER void gtadelete(void *p) { EAXJMP(0x5A07E0); } + +// overload our own new/delete with GTA's functions +void *operator new(size_t sz) { return gtanew(sz); } +void operator delete(void *ptr) noexcept { gtadelete(ptr); } + +// Use our own implementation of rand, stolen from PS2 + +unsigned __int64 myrand_seed = 1; + +int +myrand(void) +{ + myrand_seed = 0x5851F42D4C957F2D * myrand_seed + 1; + return ((myrand_seed >> 32) & 0x7FFFFFFF); +} + +void +mysrand(unsigned int seed) +{ + myrand_seed = seed; +} + + +int (*open_script_orig)(const char *path, const char *mode); +int +open_script(const char *path, const char *mode) +{ + if(GetAsyncKeyState('D') & 0x8000) + return open_script_orig("main_d.scm", mode); +// if(GetAsyncKeyState('R') & 0x8000) + return open_script_orig("main_freeroam.scm", mode); + return open_script_orig(path, mode); +} + +int (*RsEventHandler_orig)(int a, int b); +int +delayedPatches10(int a, int b) +{ + if(DebugMenuLoad()){ + DebugMenuAddVarBool8("Debug", "Show Ped Road Groups", (int8*)&gbShowPedRoadGroups, nil); + DebugMenuAddVarBool8("Debug", "Show Car Road Groups", (int8*)&gbShowCarRoadGroups, nil); + DebugMenuAddVarBool8("Debug", "Show Collision Polys", (int8*)&gbShowCollisionPolys, nil); + DebugMenuAddVarBool8("Debug", "Don't render Buildings", (int8*)&gbDontRenderBuildings, nil); + DebugMenuAddVarBool8("Debug", "Don't render Big Buildings", (int8*)&gbDontRenderBigBuildings, nil); + DebugMenuAddVarBool8("Debug", "Don't render Peds", (int8*)&gbDontRenderPeds, nil); + DebugMenuAddVarBool8("Debug", "Don't render Objects", (int8*)&gbDontRenderObjects, nil); + } + + return RsEventHandler_orig(a, b); +} + +void +patch() +{ + StaticPatcher::Apply(); + + Patch(0x46BC61+6, 1.0f); // car distance + InjectHook(0x59E460, printf, PATCH_JUMP); + + InterceptCall(&open_script_orig, open_script, 0x438869); + + InterceptCall(&RsEventHandler_orig, delayedPatches10, 0x58275E); +} + +BOOL WINAPI +DllMain(HINSTANCE hInst, DWORD reason, LPVOID) +{ + if(reason == DLL_PROCESS_ATTACH){ + + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + + if (*(DWORD*)0x5C1E75 == 0xB85548EC) // 1.0 + patch(); + else + return FALSE; + } + + return TRUE; +} diff --git a/src/math/Matrix.h b/src/math/Matrix.h new file mode 100644 index 00000000..cc15da09 --- /dev/null +++ b/src/math/Matrix.h @@ -0,0 +1,245 @@ +#pragma once + +class CMatrix +{ +public: + RwMatrix m_matrix; + RwMatrix *m_attachment; + bool m_hasRwMatrix; // are we the owner? + + CMatrix(void){ + m_attachment = nil; + m_hasRwMatrix = false; + } + CMatrix(CMatrix const &m){ + m_attachment = nil; + m_hasRwMatrix = false; + *this = m; + } + CMatrix(RwMatrix *matrix, bool attach){ + m_attachment = nil; + Attach(matrix, attach); + } + ~CMatrix(void){ + if(m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + } + void Attach(RwMatrix *matrix, bool attach){ + if(m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + m_attachment = matrix; + m_hasRwMatrix = attach; + Update(); + } + void AttachRW(RwMatrix *matrix, bool attach){ + if(m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + m_attachment = matrix; + m_hasRwMatrix = attach; + UpdateRW(); + } + void Detach(void){ + if(m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + m_attachment = nil; + } + void Update(void){ + m_matrix = *m_attachment; + } + void UpdateRW(void){ + if(m_attachment){ + *m_attachment = m_matrix; + RwMatrixUpdate(m_attachment); + } + } + void operator=(CMatrix const &rhs){ + m_matrix = rhs.m_matrix; + if(m_attachment) + UpdateRW(); + } + + CVector *GetPosition(void){ return (CVector*)&m_matrix.pos; } + CVector *GetRight(void) { return (CVector*)&m_matrix.right; } + CVector *GetForward(void) { return (CVector*)&m_matrix.up; } + CVector *GetUp(void) { return (CVector*)&m_matrix.at; } + void SetScale(float s){ + m_matrix.right.x = s; + m_matrix.right.y = 0.0f; + m_matrix.right.z = 0.0f; + + m_matrix.up.x = 0.0f; + m_matrix.up.y = s; + m_matrix.up.z = 0.0f; + + m_matrix.at.x = 0.0f; + m_matrix.at.y = 0.0f; + m_matrix.at.z = s; + + m_matrix.pos.x = 0.0f; + m_matrix.pos.y = 0.0f; + m_matrix.pos.z = 0.0f; + } + void SetRotateXOnly(float angle){ + float c = cos(angle); + float s = sin(angle); + + m_matrix.right.x = 1.0f; + m_matrix.right.y = 0.0f; + m_matrix.right.z = 0.0f; + + m_matrix.up.x = 0.0f; + m_matrix.up.y = c; + m_matrix.up.z = s; + + m_matrix.at.x = 0.0f; + m_matrix.at.y = -s; + m_matrix.at.z = c; + } + void SetRotateX(float angle){ + SetRotateXOnly(angle); + m_matrix.pos.x = 0.0f; + m_matrix.pos.y = 0.0f; + m_matrix.pos.z = 0.0f; + } + void SetRotateYOnly(float angle){ + float c = cos(angle); + float s = sin(angle); + + m_matrix.right.x = c; + m_matrix.right.y = 0.0f; + m_matrix.right.z = -s; + + m_matrix.up.x = 0.0f; + m_matrix.up.y = 1.0f; + m_matrix.up.z = 0.0f; + + m_matrix.at.x = s; + m_matrix.at.y = 0.0f; + m_matrix.at.z = c; + } + void SetRotateY(float angle){ + SetRotateYOnly(angle); + m_matrix.pos.x = 0.0f; + m_matrix.pos.y = 0.0f; + m_matrix.pos.z = 0.0f; + } + void SetRotateZOnly(float angle){ + float c = cos(angle); + float s = sin(angle); + + m_matrix.right.x = c; + m_matrix.right.y = s; + m_matrix.right.z = 0.0f; + + m_matrix.up.x = -s; + m_matrix.up.y = c; + m_matrix.up.z = 0.0f; + + m_matrix.at.x = 0.0f; + m_matrix.at.y = 0.0f; + m_matrix.at.z = 1.0f; + } + void SetRotateZ(float angle){ + SetRotateZOnly(angle); + m_matrix.pos.x = 0.0f; + m_matrix.pos.y = 0.0f; + m_matrix.pos.z = 0.0f; + } + void Reorthogonalise(void){ + CVector &r = *GetRight(); + CVector &f = *GetForward(); + CVector &u = *GetUp(); + u = CrossProduct(r, f); + u.Normalise(); + r = CrossProduct(f, u); + r.Normalise(); + f = CrossProduct(u, r); + } +}; + +inline CMatrix& +Invert(const CMatrix &src, CMatrix &dst) +{ + // GTA handles this as a raw 4x4 orthonormal matrix + // and trashes the RW flags, let's not do that + // actual copy of librw code: + RwMatrix *d = &dst.m_matrix; + const RwMatrix *s = &src.m_matrix; + d->right.x = s->right.x; + d->right.y = s->up.x; + d->right.z = s->at.x; + d->up.x = s->right.y; + d->up.y = s->up.y; + d->up.z = s->at.y; + d->at.x = s->right.z; + d->at.y = s->up.z; + d->at.z = s->at.z; + d->pos.x = -(s->pos.x*s->right.x + + s->pos.y*s->right.y + + s->pos.z*s->right.z); + d->pos.y = -(s->pos.x*s->up.x + + s->pos.y*s->up.y + + s->pos.z*s->up.z); + d->pos.z = -(s->pos.x*s->at.x + + s->pos.y*s->at.y + + s->pos.z*s->at.z); + d->flags = rwMATRIXTYPEORTHONORMAL; + return dst; +} + +inline CMatrix +Invert(const CMatrix &matrix) +{ + CMatrix inv; + return Invert(matrix, inv); +} + +inline CVector +operator*(const CMatrix &mat, const CVector &vec) +{ + return CVector( + mat.m_matrix.right.x * vec.x + mat.m_matrix.up.x * vec.y + mat.m_matrix.at.x * vec.z + mat.m_matrix.pos.x, + mat.m_matrix.right.y * vec.x + mat.m_matrix.up.y * vec.y + mat.m_matrix.at.y * vec.z + mat.m_matrix.pos.y, + mat.m_matrix.right.z * vec.x + mat.m_matrix.up.z * vec.y + mat.m_matrix.at.z * vec.z + mat.m_matrix.pos.z); +} + +inline CMatrix +operator*(const CMatrix &m1, const CMatrix &m2) +{ + CMatrix out; + RwMatrix *dst = &out.m_matrix; + const RwMatrix *src1 = &m1.m_matrix; + const RwMatrix *src2 = &m2.m_matrix; + dst->right.x = src1->right.x*src2->right.x + src1->up.x*src2->right.y + src1->at.x*src2->right.z; + dst->right.y = src1->right.y*src2->right.x + src1->up.y*src2->right.y + src1->at.y*src2->right.z; + dst->right.z = src1->right.z*src2->right.x + src1->up.z*src2->right.y + src1->at.z*src2->right.z; + dst->up.x = src1->right.x*src2->up.x + src1->up.x*src2->up.y + src1->at.x*src2->up.z; + dst->up.y = src1->right.y*src2->up.x + src1->up.y*src2->up.y + src1->at.y*src2->up.z; + dst->up.z = src1->right.z*src2->up.x + src1->up.z*src2->up.y + src1->at.z*src2->up.z; + dst->at.x = src1->right.x*src2->at.x + src1->up.x*src2->at.y + src1->at.x*src2->at.z; + dst->at.y = src1->right.y*src2->at.x + src1->up.y*src2->at.y + src1->at.y*src2->at.z; + dst->at.z = src1->right.z*src2->at.x + src1->up.z*src2->at.y + src1->at.z*src2->at.z; + dst->pos.x = src1->right.x*src2->pos.x + src1->up.x*src2->pos.y + src1->at.x*src2->pos.z + src1->pos.x; + dst->pos.y = src1->right.y*src2->pos.x + src1->up.y*src2->pos.y + src1->at.y*src2->pos.z + src1->pos.y; + dst->pos.z = src1->right.z*src2->pos.x + src1->up.z*src2->pos.y + src1->at.z*src2->pos.z + src1->pos.z; + return out; +} + +inline CVector +MultiplyInverse(const CMatrix &mat, const CVector &vec) +{ + CVector v(vec.x - mat.m_matrix.pos.x, vec.y - mat.m_matrix.pos.y, vec.z - mat.m_matrix.pos.z); + return CVector( + mat.m_matrix.right.x * v.x + mat.m_matrix.right.y * v.y + mat.m_matrix.right.z * v.z, + mat.m_matrix.up.x * v.x + mat.m_matrix.up.y * v.y + mat.m_matrix.up.z * v.z, + mat.m_matrix.at.x * v.x + mat.m_matrix.at.y * v.y + mat.m_matrix.at.z * v.z); +} + +inline CVector +Multiply3x3(const CMatrix &mat, const CVector &vec) +{ + return CVector( + mat.m_matrix.right.x * vec.x + mat.m_matrix.up.x * vec.y + mat.m_matrix.at.x * vec.z, + mat.m_matrix.right.y * vec.x + mat.m_matrix.up.y * vec.y + mat.m_matrix.at.y * vec.z, + mat.m_matrix.right.z * vec.x + mat.m_matrix.up.z * vec.y + mat.m_matrix.at.z * vec.z); +} diff --git a/src/math/Rect.h b/src/math/Rect.h new file mode 100644 index 00000000..212645fa --- /dev/null +++ b/src/math/Rect.h @@ -0,0 +1,31 @@ +#pragma once + +#pragma once + +class CRect +{ +public: + float left; // x min + float top; // y max + float right; // x max + float bottom; // y min + + CRect(void){ + left = 1000000.0f; + bottom = 1000000.0f; + right = -1000000.0f; + top = -1000000.0f; + } + CRect(float l, float b, float r, float t){ + left = l; + bottom = b; + right = r; + top = t; + } + void ContainPoint(CVector const &v){ + if(v.x < left) left = v.x; + if(v.x > right) right = v.x; + if(v.y < bottom) bottom = v.y; + if(v.y > top) top = v.y; + } +}; diff --git a/src/math/Vector.h b/src/math/Vector.h new file mode 100644 index 00000000..98c3f0ec --- /dev/null +++ b/src/math/Vector.h @@ -0,0 +1,82 @@ +#pragma once + +class CVector +{ +public: + float x, y, z; + CVector(void) {} + CVector(float x, float y, float z) : x(x), y(y), z(z) {} +// CVector(rw::V3d const &v) : x(v.x), y(v.y), z(v.z) {} + float Magnitude(void) const { return sqrt(x*x + y*y + z*z); } + float MagnitudeSqr(void) const { return x*x + y*y + z*z; } + float Magnitude2D(void) const { return sqrt(x*x + y*y); } + void Normalise(void){ + float sq = MagnitudeSqr(); + if(sq > 0.0f){ + float invsqrt = 1.0f/sqrt(sq); + x *= invsqrt; + y *= invsqrt; + z *= invsqrt; + }else + x = 1.0f; + } +// rw::V3d ToRW(void){ +// return rw::makeV3d(x, y, z); +// } +// void operator=(rw::V3d const &rhs){ +// x = rhs.x; +// y = rhs.y; +// z = rhs.z; +// } + CVector operator-(const CVector &rhs) const { + return CVector(x-rhs.x, y-rhs.y, z-rhs.z); + } + CVector operator+(const CVector &rhs) const { + return CVector(x+rhs.x, y+rhs.y, z+rhs.z); + } + CVector operator*(float t) const { + return CVector(x*t, y*t, z*t); + } + CVector operator/(float t) const { + return CVector(x/t, y/t, z/t); + } + CVector &operator-=(const CVector &rhs) { + this->x -= rhs.x; + this->y -= rhs.y; + this->z -= rhs.z; + return *this; + } + CVector &operator+=(const CVector &rhs) { + this->x += rhs.x; + this->y += rhs.y; + this->z += rhs.z; + return *this; + } + CVector &operator*=(float t) { + this->x *= t; + this->y *= t; + this->z *= t; + return *this; + } + CVector &operator/=(float t) { + this->x /= t; + this->y /= t; + this->z /= t; + return *this; + } +}; + +inline float +DotProduct(const CVector &v1, const CVector &v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + +inline CVector +CrossProduct(const CVector &v1, const CVector &v2) +{ + return CVector( + v1.y*v2.z - v1.z*v2.y, + v1.z*v2.x - v1.x*v2.z, + v1.x*v2.y - v1.y*v2.x); +} diff --git a/src/math/Vector2D.h b/src/math/Vector2D.h new file mode 100644 index 00000000..3c0013d4 --- /dev/null +++ b/src/math/Vector2D.h @@ -0,0 +1,37 @@ +#pragma once + +class CVector2D +{ +public: + float x, y; + CVector2D(void) {} + CVector2D(float x, float y) : x(x), y(y) {} + CVector2D(const CVector &v) : x(v.x), y(v.y) {} + float Magnitude(void) const { return sqrt(x*x + y*y); } + float MagnitudeSqr(void) const { return x*x + y*y; } + + void Normalise(void){ + float sq = MagnitudeSqr(); + if(sq > 0.0f){ + float invsqrt = 1.0f/sqrt(sq); + x *= invsqrt; + y *= invsqrt; + }else + x = 0.0f; + } + CVector2D operator-(const CVector2D &rhs) const { + return CVector2D(x-rhs.x, y-rhs.y); + } + CVector2D operator+(const CVector2D &rhs) const { + return CVector2D(x+rhs.x, y+rhs.y); + } + CVector2D operator*(float t) const { + return CVector2D(x*t, y*t); + } +}; + +inline float +CrossProduct2D(const CVector2D &v1, const CVector2D &v2) +{ + return v1.x*v2.y - v1.y*v2.x; +} diff --git a/src/modelinfo/BaseModelInfo.cpp b/src/modelinfo/BaseModelInfo.cpp new file mode 100644 index 00000000..f44c86b6 --- /dev/null +++ b/src/modelinfo/BaseModelInfo.cpp @@ -0,0 +1,117 @@ +#include "common.h" +#include "patcher.h" +#include "templates.h" +#include "TxdStore.h" +#include "2dEffect.h" +#include "BaseModelInfo.h" + + +CBaseModelInfo::CBaseModelInfo(ModeInfoType type) +{ + m_colModel = nil; + m_twodEffects = 0; + m_objectId = -1; + m_refCount = 0; + m_txdSlot = -1; + m_type = type; + m_num2dEffects = 0; + m_freeCol = false; +} + +void +CBaseModelInfo::Shutdown(void) +{ + DeleteCollisionModel(); + DeleteRwObject(); + m_twodEffects = 0; + m_num2dEffects = 0; + m_txdSlot = -1; +} + +void +CBaseModelInfo::DeleteCollisionModel(void) +{ + if(m_colModel && m_freeCol){ + if(m_colModel) + delete m_colModel; + m_colModel = nil; + } +} + +void +CBaseModelInfo::AddRef(void) +{ + m_refCount++; + AddTexDictionaryRef(); +} + +void +CBaseModelInfo::RemoveRef(void) +{ + m_refCount--; + RemoveTexDictionaryRef(); +} + +void +CBaseModelInfo::SetTexDictionary(const char *name) +{ + int slot = CTxdStore::FindTxdSlot(name); + if(slot < 0) + slot = CTxdStore::AddTxdSlot(name); + m_txdSlot = slot; +} + +void +CBaseModelInfo::AddTexDictionaryRef(void) +{ + CTxdStore::AddRef(m_txdSlot); +} + +void +CBaseModelInfo::RemoveTexDictionaryRef(void) +{ + CTxdStore::RemoveRef(m_txdSlot); +} + +void +CBaseModelInfo::Init2dEffects(void) +{ + m_twodEffects = nil; + m_num2dEffects = 0; +} + +void +CBaseModelInfo::Add2dEffect(C2dEffect *fx) +{ + if(m_twodEffects) + m_num2dEffects++; + else{ + m_twodEffects = fx; + m_num2dEffects = 1; + } +} + +C2dEffect* +CBaseModelInfo::Get2dEffect(int n) +{ + if(m_twodEffects) + return &m_twodEffects[n]; + else + return nil; +} + + +STARTPATCHES + // can't easily replace ctor at 4F6A50 + InjectHook(0x4F6A90, &CBaseModelInfo::Shutdown_, PATCH_JUMP); + InjectHook(0x4F6AC0, &CBaseModelInfo::DeleteCollisionModel, PATCH_JUMP); + InjectHook(0x4F6B70, &CBaseModelInfo::ClearTexDictionary, PATCH_JUMP); + InjectHook(0x4F6BA0, &CBaseModelInfo::AddRef, PATCH_JUMP); + InjectHook(0x4F6BB0, &CBaseModelInfo::RemoveRef, PATCH_JUMP); + InjectHook(0x4F6B40, &CBaseModelInfo::SetTexDictionary, PATCH_JUMP); + InjectHook(0x4F6B80, &CBaseModelInfo::AddTexDictionaryRef, PATCH_JUMP); + InjectHook(0x4F6B90, &CBaseModelInfo::RemoveTexDictionaryRef, PATCH_JUMP); + InjectHook(0x4F6B20, &CBaseModelInfo::Add2dEffect, PATCH_JUMP); + InjectHook(0x4F6AF0, &CBaseModelInfo::Init2dEffects, PATCH_JUMP); + InjectHook(0x4F6B00, &CBaseModelInfo::Get2dEffect, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/BaseModelInfo.h b/src/modelinfo/BaseModelInfo.h new file mode 100644 index 00000000..fadea18a --- /dev/null +++ b/src/modelinfo/BaseModelInfo.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Collision.h" + +enum ModeInfoType : uint8 +{ + MITYPE_NA = 0, + MITYPE_SIMPLE = 1, + MITYPE_MLO = 2, + MITYPE_TIME = 3, + MITYPE_CLUMP = 4, + MITYPE_VEHICLE = 5, + MITYPE_PED = 6, + MITYPE_XTRACOMPS = 7, +}; +static_assert(sizeof(ModeInfoType) == 1, "ModeInfoType: error"); + +class C2dEffect; + +class CBaseModelInfo +{ +protected: + // TODO?: make more things protected + char m_name[24]; + CColModel *m_colModel; + C2dEffect *m_twodEffects; + int16 m_objectId; +public: + uint16 m_refCount; + int16 m_txdSlot; + ModeInfoType m_type; + uint8 m_num2dEffects; + bool m_freeCol; + + CBaseModelInfo(ModeInfoType type); + virtual ~CBaseModelInfo() {} + virtual void Shutdown(void); + virtual void DeleteRwObject(void) = 0; + virtual RwObject *CreateInstance(RwMatrix *) = 0; + virtual RwObject *CreateInstance(void) = 0; + virtual RwObject *GetRwObject(void) = 0; + + bool IsSimple(void) { return m_type == MITYPE_SIMPLE || m_type == MITYPE_TIME; } + char *GetName(void) { return m_name; } + void SetName(const char *name) { strncpy(m_name, name, 24); } + void SetColModel(CColModel *col, bool free = false){ + m_colModel = col; m_freeCol = free; } + CColModel *GetColModel(void) { return m_colModel; } + void DeleteCollisionModel(void); + void ClearTexDictionary(void) { m_txdSlot = -1; } + short GetObjectID(void) { return m_objectId; } + void SetObjectID(short id) { m_objectId = id; } + short GetTxdSlot(void) { return m_txdSlot; } + void AddRef(void); + void RemoveRef(void); + void SetTexDictionary(const char *name); + void AddTexDictionaryRef(void); + void RemoveTexDictionaryRef(void); + void Init2dEffects(void); + void Add2dEffect(C2dEffect *fx); + C2dEffect *Get2dEffect(int n); + + void Shutdown_(void) { this->CBaseModelInfo::Shutdown(); } +}; + +static_assert(sizeof(CBaseModelInfo) == 0x30, "CBaseModelInfo: error"); diff --git a/src/modelinfo/ClumpModelInfo.cpp b/src/modelinfo/ClumpModelInfo.cpp new file mode 100644 index 00000000..4a19f1df --- /dev/null +++ b/src/modelinfo/ClumpModelInfo.cpp @@ -0,0 +1,156 @@ +#include "common.h" +#include "patcher.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" + +void +CClumpModelInfo::DeleteRwObject(void) +{ + if(m_clump){ + RpClumpDestroy(m_clump); + m_clump = nil; + RemoveTexDictionaryRef(); + } +} + +RwObject* +CClumpModelInfo::CreateInstance(void) +{ + if(m_clump) + return (RwObject*)RpClumpClone(m_clump); + return nil; +} + +RwObject* +CClumpModelInfo::CreateInstance(RwMatrix *m) +{ + if(m_clump){ + RpClump *clump = (RpClump*)CreateInstance(); + *RwFrameGetMatrix(RpClumpGetFrame(clump)) = *m; + return (RwObject*)clump; + } + return nil; +} + +RpAtomic* +CClumpModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::SetAtomicRenderCallback(atomic, (RpAtomicCallBackRender)data); + return atomic; +} + +void +CClumpModelInfo::SetClump(RpClump *clump) +{ + m_clump = clump; + CVisibilityPlugins::SetClumpModelInfo(m_clump, this); + AddTexDictionaryRef(); + RpClumpForAllAtomics(clump, SetAtomicRendererCB, nil); + if(strncmp(GetName(), "playerh", 8) == 0) + RpClumpForAllAtomics(clump, SetAtomicRendererCB, CVisibilityPlugins::RenderPlayerCB); +} + +void +CClumpModelInfo::SetFrameIds(RwObjectNameIdAssocation *assocs) +{ + int32 i; + RwObjectNameAssociation objname; + + for(i = 0; assocs[i].name; i++) + if((assocs[i].flags & CLUMP_FLAG_NO_HIERID) == 0){ + objname.frame = nil; + objname.name = assocs[i].name; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindFrameFromNameWithoutIdCB, &objname); + if(objname.frame) + CVisibilityPlugins::SetFrameHierarchyId(objname.frame, assocs[i].hierId); + } +} + +RwFrame* +CClumpModelInfo::FindFrameFromIdCB(RwFrame *frame, void *data) +{ + RwObjectIdAssociation *assoc = (RwObjectIdAssociation*)data; + + if(CVisibilityPlugins::GetFrameHierarchyId(frame) != assoc->id){ + RwFrameForAllChildren(frame, FindFrameFromIdCB, assoc); + return assoc->frame ? nil : frame; + }else{ + assoc->frame = frame; + return nil; + } +} + +RwFrame* +CClumpModelInfo::FindFrameFromNameCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(_strcmpi(GetFrameNodeName(frame), assoc->name) != 0){ + RwFrameForAllChildren(frame, FindFrameFromNameCB, assoc); + return assoc->frame ? nil : frame; + }else{ + assoc->frame = frame; + return nil; + } +} + +RwFrame* +CClumpModelInfo::FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(CVisibilityPlugins::GetFrameHierarchyId(frame) || + _strcmpi(GetFrameNodeName(frame), assoc->name) != 0){ + RwFrameForAllChildren(frame, FindFrameFromNameWithoutIdCB, assoc); + return assoc->frame ? nil : frame; + }else{ + assoc->frame = frame; + return nil; + } +} + +RwFrame* +CClumpModelInfo::FillFrameArrayCB(RwFrame *frame, void *data) +{ + int32 id; + RwFrame **frames = (RwFrame**)data; + id = CVisibilityPlugins::GetFrameHierarchyId(frame); + if(id > 0) + frames[id] = frame; + RwFrameForAllChildren(frame, FillFrameArrayCB, data); + return frame; +} + +void +CClumpModelInfo::FillFrameArray(RpClump *clump, RwFrame **frames) +{ + RwFrameForAllChildren(RpClumpGetFrame(clump), FillFrameArrayCB, frames); +} + +RwFrame* +CClumpModelInfo::GetFrameFromId(RpClump *clump, int32 id) +{ + RwObjectIdAssociation assoc; + assoc.id = id; + assoc.frame = nil; + RwFrameForAllChildren(RpClumpGetFrame(clump), FindFrameFromIdCB, &assoc); + return assoc.frame; +} + + +STARTPATCHES + InjectHook(0x4F8800, &CClumpModelInfo::DeleteRwObject_, PATCH_JUMP); + InjectHook(0x4F8920, &CClumpModelInfo::CreateInstance_1, PATCH_JUMP); + InjectHook(0x4F88A0, &CClumpModelInfo::CreateInstance_2, PATCH_JUMP); + InjectHook(0x50C1C0, &CClumpModelInfo::GetRwObject_, PATCH_JUMP); + InjectHook(0x4F8830, &CClumpModelInfo::SetClump_, PATCH_JUMP); + InjectHook(0x4F8940, &CClumpModelInfo::SetAtomicRendererCB, PATCH_JUMP); + InjectHook(0x4F8960, &CClumpModelInfo::FindFrameFromNameCB, PATCH_JUMP); + InjectHook(0x4F8A10, &CClumpModelInfo::FindFrameFromNameWithoutIdCB, PATCH_JUMP); + InjectHook(0x4F8AD0, &CClumpModelInfo::FindFrameFromIdCB, PATCH_JUMP); + InjectHook(0x4F8BB0, &CClumpModelInfo::SetFrameIds, PATCH_JUMP); + InjectHook(0x4F8B20, &CClumpModelInfo::FillFrameArrayCB, PATCH_JUMP); + InjectHook(0x4F8B90, &CClumpModelInfo::FillFrameArray, PATCH_JUMP); + InjectHook(0x4F8B50, &CClumpModelInfo::GetFrameFromId, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/ClumpModelInfo.h b/src/modelinfo/ClumpModelInfo.h new file mode 100644 index 00000000..909d241b --- /dev/null +++ b/src/modelinfo/ClumpModelInfo.h @@ -0,0 +1,60 @@ +#pragma once + +#include "BaseModelInfo.h" + +struct RwObjectNameIdAssocation +{ + char *name; + int32 hierId; + uint32 flags; +}; + +struct RwObjectNameAssociation +{ + char *name; + RwFrame *frame; +}; + +struct RwObjectIdAssociation +{ + int32 id; + RwFrame *frame; +}; + +enum { + CLUMP_FLAG_NO_HIERID = 0x1, +}; + + +class CClumpModelInfo : public CBaseModelInfo +{ +public: + RpClump *m_clump; + + CClumpModelInfo(void) : CBaseModelInfo(MITYPE_CLUMP) {} + CClumpModelInfo(ModeInfoType id) : CBaseModelInfo(id) {} + ~CClumpModelInfo() {} + void DeleteRwObject(void); + RwObject *CreateInstance(void); + RwObject *CreateInstance(RwMatrix *); + RwObject *GetRwObject(void) { return (RwObject*)m_clump; } + + virtual void SetClump(RpClump *); + + static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data); + void SetFrameIds(RwObjectNameIdAssocation *assocs); + static RwFrame *FindFrameFromNameCB(RwFrame *frame, void *data); + static RwFrame *FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data); + static RwFrame *FindFrameFromIdCB(RwFrame *frame, void *data); + static void FillFrameArray(RpClump *clump, RwFrame **frames); + static RwFrame *FillFrameArrayCB(RwFrame *frame, void *data); + static RwFrame *GetFrameFromId(RpClump *clump, int32 id); + + + void DeleteRwObject_(void) { this->CClumpModelInfo::DeleteRwObject(); } + RwObject *CreateInstance_1(void) { return this->CClumpModelInfo::CreateInstance(); } + RwObject *CreateInstance_2(RwMatrix *m) { return this->CClumpModelInfo::CreateInstance(m); } + RwObject *GetRwObject_(void) { return this->CClumpModelInfo::GetRwObject(); } + void SetClump_(RpClump *clump) { this->CClumpModelInfo::SetClump(clump); } +}; +static_assert(sizeof(CClumpModelInfo) == 0x34, "CClumpModelInfo: error"); diff --git a/src/modelinfo/ModelIndices.cpp b/src/modelinfo/ModelIndices.cpp new file mode 100644 index 00000000..9a8aaead --- /dev/null +++ b/src/modelinfo/ModelIndices.cpp @@ -0,0 +1,32 @@ +#include "common.h" +#include "patcher.h" +#include "ModelIndices.h" + +#define X(name, var, addr) int16 &var = *(int16*)addr; + MODELINDICES +#undef X + +void +InitModelIndices(void) +{ +#define X(name, var, addr) var = -1; + MODELINDICES +#undef X +} + +void +MatchModelString(const char *modelname, int16 id) +{ +#define X(name, var, addr) \ + if(strcmp(name, modelname) == 0){ \ + var = id; \ + return; \ + } + MODELINDICES +#undef X +} + +STARTPATCHES + InjectHook(0x48EB60, InitModelIndices, PATCH_JUMP); + InjectHook(0x48F030, MatchModelString, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/ModelIndices.h b/src/modelinfo/ModelIndices.h new file mode 100644 index 00000000..5de10558 --- /dev/null +++ b/src/modelinfo/ModelIndices.h @@ -0,0 +1,224 @@ +#define MODELINDICES \ + X("fire_hydrant", MI_FIRE_HYDRANT, 0x5F5A00) \ + X("bagelstnd02", MI_BAGELSTAND2, 0x5F59FC) \ + X("fish01", MI_FISHSTALL01, 0x5F59EC) \ + X("fishstall02", MI_FISHSTALL02, 0x5F59F0) \ + X("fishstall03", MI_FISHSTALL03, 0x5F59F4) \ + X("fishstall04", MI_FISHSTALL04, 0x5F59F8) \ + X("taxisign", MI_TAXISIGN, 0x5F59E8) \ + X("phonesign", MI_PHONESIGN, 0x5F59E4) \ + X("noparkingsign1", MI_NOPARKINGSIGN1, 0x5F59E0) \ + X("bussign1", MI_BUSSIGN1, 0x5F59DC) \ + X("roadworkbarrier1", MI_ROADWORKBARRIER1, 0x5F59D8) \ + X("dump1", MI_DUMP1, 0x5F59D4) \ + X("trafficcone", MI_TRAFFICCONE, 0x5F59D0) \ + X("newsstand1", MI_NEWSSTAND, 0x5F59CC) \ + X("postbox1", MI_POSTBOX1, 0x5F59C8) \ + X("bin1", MI_BIN, 0x5F59C4) \ + X("wastebin", MI_WASTEBIN, 0x5F59C0) \ + X("phonebooth1", MI_PHONEBOOTH1, 0x5F59BC) \ + X("parkingmeter", MI_PARKINGMETER, 0x5F59B8) \ + X("trafficlight1", MI_TRAFFICLIGHTS, 0x5F5958) \ + X("lamppost1", MI_SINGLESTREETLIGHTS1, 0x5F595C) \ + X("lamppost2", MI_SINGLESTREETLIGHTS2, 0x5F5960) \ + X("lamppost3", MI_SINGLESTREETLIGHTS3, 0x5F5964) \ + X("doublestreetlght1", MI_DOUBLESTREETLIGHTS, 0x5F5968) \ + X("rd_Road2A10", MI_ROADSFORROADBLOCKSSTART, 0x5F596C) \ + X("rd_Road1A30", MI_ROADSFORROADBLOCKSEND, 0x5F5970) \ + X("veg_tree1", MI_TREE1, 0x5F5974) \ + X("veg_tree3", MI_TREE2, 0x5F5978) \ + X("veg_treea1", MI_TREE3, 0x5F597C) \ + X("veg_treenew01", MI_TREE4, 0x5F5980) \ + X("veg_treenew05", MI_TREE5, 0x5F5984) \ + X("veg_treeb1", MI_TREE6, 0x5F5988) \ + X("veg_treenew10", MI_TREE7, 0x5F598C) \ + X("veg_treea3", MI_TREE8, 0x5F5990) \ + X("veg_treenew09", MI_TREE9, 0x5F5994) \ + X("veg_treenew08", MI_TREE10, 0x5F5998) \ + X("veg_treenew03", MI_TREE11, 0x5F599C) \ + X("veg_treenew16", MI_TREE12, 0x5F59A0) \ + X("veg_treenew17", MI_TREE13, 0x5F59A4) \ + X("veg_treenew06", MI_TREE14, 0x5F59A8) \ + X("doc_crane_cab", MODELID_CRANE_1, 0x5F59AC) \ + X("cranetopb", MODELID_CRANE_2, 0x5F59B0) \ + X("cranetopa", MODELID_CRANE_3, 0x5F59B4) \ + X("package1", MI_COLLECTABLE1, 0x5F5A04) \ + X("Money", MI_MONEY, 0x5F5A08) \ + X("barrel1", MI_CARMINE, 0x5F5A0C) \ + X("oddjgaragdoor", MI_GARAGEDOOR1, 0x5F5A10) \ + X("bombdoor", MI_GARAGEDOOR2, 0x5F5A14) \ + X("door_bombshop", MI_GARAGEDOOR3, 0x5F5A18) \ + X("vheistlocdoor", MI_GARAGEDOOR4, 0x5F5A1C) \ + X("door2_garage", MI_GARAGEDOOR5, 0x5F5A20) \ + X("ind_slidedoor", MI_GARAGEDOOR6, 0x5F5A24) \ + X("bankjobdoor", MI_GARAGEDOOR7, 0x5F5A28) \ + X("door_jmsgrage", MI_GARAGEDOOR9, 0x5F5A2C) \ + X("jamesgrge_kb", MI_GARAGEDOOR10, 0x5F5A30) \ + X("door_sfehousegrge", MI_GARAGEDOOR11, 0x5F5A34) \ + X("shedgaragedoor", MI_GARAGEDOOR12, 0x5F5A38) \ + X("door4_garage", MI_GARAGEDOOR13, 0x5F5A3C) \ + X("door_col_compnd_01", MI_GARAGEDOOR14, 0x5F5A40) \ + X("door_col_compnd_02", MI_GARAGEDOOR15, 0x5F5A44) \ + X("door_col_compnd_03", MI_GARAGEDOOR16, 0x5F5A48) \ + X("door_col_compnd_04", MI_GARAGEDOOR17, 0x5F5A4C) \ + X("door_col_compnd_05", MI_GARAGEDOOR18, 0x5F5A50) \ + X("impex_door", MI_GARAGEDOOR19, 0x5F5A54) \ + X("SalvGarage", MI_GARAGEDOOR20, 0x5F5A58) \ + X("door3_garage", MI_GARAGEDOOR21, 0x5F5A5C) \ + X("leveldoor2", MI_GARAGEDOOR22, 0x5F5A60) \ + X("double_garage_dr", MI_GARAGEDOOR23, 0x5F5A64) \ + X("amcogaragedoor", MI_GARAGEDOOR24, 0x5F5A68) \ + X("towergaragedoor1", MI_GARAGEDOOR25, 0x5F5A6C) \ + X("towergaragedoor2", MI_GARAGEDOOR26, 0x5F5A70) \ + X("towergaragedoor3", MI_GARAGEDOOR27, 0x5F5A74) \ + X("plysve_gragedoor", MI_GARAGEDOOR28, 0x5F5A78) \ + X("impexpsubgrgdoor", MI_GARAGEDOOR29, 0x5F5A7C) \ + X("Sub_sprayshopdoor", MI_GARAGEDOOR30, 0x5F5A80) \ + X("ind_plyrwoor", MI_GARAGEDOOR31, 0x5F5A84) \ + X("8ballsuburbandoor", MI_GARAGEDOOR32, 0x5F5A88) \ + X("barrel2", MI_NAUTICALMINE, 0x5F5A8C) \ + X("crushercrush", MI_CRUSHERBODY, 0x5F5A90) \ + X("crushertop", MI_CRUSHERLID, 0x5F5A94) \ + X("donkeymag", MI_DONKEYMAG, 0x5F5A98) \ + X("bullion", MI_BULLION, 0x5F5A9C) \ + X("floatpackge1", MI_FLOATPACKAGE1, 0x5F5AA0) \ + X("briefcase", MI_BRIEFCASE, 0x5F5AA4) \ + X("chinabanner1", MI_CHINABANNER1, 0x5F5AA8) \ + X("chinabanner2", MI_CHINABANNER2, 0x5F5AAC) \ + X("chinabanner3", MI_CHINABANNER3, 0x5F5AB0) \ + X("chinabanner4", MI_CHINABANNER4, 0x5F5AB4) \ + X("iten_chinatown5", MI_CHINABANNER5, 0x5F5AB8) \ + X("iten_chinatown7", MI_CHINABANNER6, 0x5F5ABC) \ + X("iten_chinatown3", MI_CHINABANNER7, 0x5F5AC0) \ + X("iten_chinatown2", MI_CHINABANNER8, 0x5F5AC4) \ + X("iten_chinatown4", MI_CHINABANNER9, 0x5F5AC8) \ + X("iten_washline01", MI_CHINABANNER10, 0x5F5ACC) \ + X("iten_washline02", MI_CHINABANNER11, 0x5F5AD0) \ + X("iten_washline03", MI_CHINABANNER12, 0x5F5AD4) \ + X("chinalanterns", MI_CHINALANTERN, 0x5F5AD8) \ + X("glassfx1", MI_GLASS1, 0x5F5ADC) \ + X("glassfx2", MI_GLASS2, 0x5F5AE0) \ + X("glassfx3", MI_GLASS3, 0x5F5AE4) \ + X("glassfx4", MI_GLASS4, 0x5F5AE8) \ + X("glassfx55", MI_GLASS5, 0x5F5AEC) \ + X("glassfxsub1", MI_GLASS6, 0x5F5AF0) \ + X("glassfxsub2", MI_GLASS7, 0x5F5AF4) \ + X("glassfx_composh", MI_GLASS8, 0x5F5AF8) \ + X("bridge_liftsec", MI_BRIDGELIFT, 0x5F5AFC) \ + X("bridge_liftweight", MI_BRIDGEWEIGHT, 0x5F5B00) \ + X("subbridge_lift", MI_BRIDGEROADSEGMENT, 0x5F5B04) \ + X("barrel4", MI_EXPLODINGBARREL, 0x5F5B08) \ + X("flagsitaly", MI_ITALYBANNER1, 0x5F5B0C) \ + X("adrenaline", MI_PICKUP_ADRENALINE, 0x5F5B10) \ + X("bodyarmour", MI_PICKUP_BODYARMOUR, 0x5F5B14) \ + X("info", MI_PICKUP_INFO, 0x5F5B18) \ + X("health", MI_PICKUP_HEALTH, 0x5F5B1C) \ + X("bonus", MI_PICKUP_BONUS, 0x5F5B20) \ + X("bribe", MI_PICKUP_BRIBE, 0x5F5B24) \ + X("killfrenzy", MI_PICKUP_KILLFRENZY, 0x5F5B28) \ + X("camerapickup", MI_PICKUP_CAMERA, 0x5F5B2C) \ + X("bollardlight", MI_BOLLARDLIGHT, 0x5F5B30) \ + X("magnet", MI_MAGNET, 0x5F5B34) \ + X("streetlamp1", MI_STREETLAMP1, 0x5F5B38) \ + X("streetlamp2", MI_STREETLAMP2, 0x5F5B3C) \ + X("railtrax_lo4b", MI_RAILTRACKS, 0x5F5B40) \ + X("bar_barrier10", MI_FENCE, 0x5F5B44) \ + X("bar_barrier12", MI_FENCE2, 0x5F5B48) \ + X("petrolpump", MI_PETROLPUMP, 0x5F5B4C) \ + X("bodycast", MI_BODYCAST, 0x5F5B50) \ + X("backdoor", MI_BACKDOOR, 0x5F5B54) \ + X("coffee", MI_COFFEE, 0x5F5B58) \ + X("bouy", MI_BUOY, 0x5F5B5C) \ + X("parktable1", MI_PARKTABLE, 0x5F5B60) \ + X("sbwy_tunl_start", MI_SUBWAY1, 0x5F5B64) \ + X("sbwy_tunl_bit", MI_SUBWAY2, 0x5F5B68) \ + X("sbwy_tunl_bend", MI_SUBWAY3, 0x5F5B6C) \ + X("sbwy_tunl_cstm6", MI_SUBWAY4, 0x5F5B70) \ + X("sbwy_tunl_cstm7", MI_SUBWAY5, 0x5F5B74) \ + X("sbwy_tunl_cstm8", MI_SUBWAY6, 0x5F5B78) \ + X("sbwy_tunl_cstm10", MI_SUBWAY7, 0x5F5B7C) \ + X("sbwy_tunl_cstm9", MI_SUBWAY8, 0x5F5B80) \ + X("sbwy_tunl_cstm11", MI_SUBWAY9, 0x5F5B84) \ + X("sbwy_tunl_cstm1", MI_SUBWAY10, 0x5F5B88) \ + X("sbwy_tunl_cstm2", MI_SUBWAY11, 0x5F5B8C) \ + X("sbwy_tunl_cstm4", MI_SUBWAY12, 0x5F5B90) \ + X("sbwy_tunl_cstm3", MI_SUBWAY13, 0x5F5B94) \ + X("sbwy_tunl_cstm5", MI_SUBWAY14, 0x5F5B98) \ + X("subplatform_n2", MI_SUBWAY15, 0x5F5B9C) \ + X("suby_tunl_start", MI_SUBWAY16, 0x5F5BA0) \ + X("sbwy_tunl_start2", MI_SUBWAY17, 0x5F5BA4) \ + X("indy_tunl_start", MI_SUBWAY18, 0x5F5BA8) \ + X("indsubway03", MI_SUBPLATFORM_IND, 0x5F5BAC) \ + X("comerside_subway", MI_SUBPLATFORM_COMS, 0x5F5BB0) \ + X("subplatform", MI_SUBPLATFORM_COMS2, 0x5F5BB4) \ + X("subplatform_n", MI_SUBPLATFORM_COMN, 0x5F5BB8) \ + X("Otherside_subway", MI_SUBPLATFORM_SUB, 0x5F5BBC) \ + X("subplatform_sub", MI_SUBPLATFORM_SUB2, 0x5F5BC0) \ + X("files", MI_FILES, 0x5F5BC4) + +#define X(name, var, addr) extern int16 &var; + MODELINDICES +#undef X + +// and some hardcoded ones +// expand as needed +enum +{ + MI_COP = 1, + MI_SWAT, + MI_FBI, + MI_ARMY, + MI_MEDIC, + MI_FIREMAN, + MI_MALE01, + MI_TAXI_D, + MI_PIMP, + MI_GANG01, + MI_GANG02, + MI_GANG03, + MI_GANG04, + MI_GANG05, + MI_GANG06, + MI_GANG07, + MI_GANG08, + MI_GANG09, + MI_GANG10, + MI_GANG11, + MI_GANG12, + MI_GANG13, + MI_GANG14, + MI_CRIMINAL01, + MI_CRIMINAL02, + MI_SPECIAL01, + MI_SPECIAL02, + MI_SPECIAL03, + MI_SPECIAL04, + MI_MALE02, + MI_MALE03, + MI_FATMALE01, + MI_FATMALE02, + MI_FEMALE01, + MI_FEMALE02, + MI_FEMALE03, + MI_FATFEMALE01, + MI_FATFEMALE02, + + MI_RHINO = 122, + MI_COACH = 127, +}; + +void InitModelIndices(void); +void MatchModelString(const char *name, int16 id); + +inline bool +IsGlass(int16 id) +{ + return id == MI_GLASS1 || + id == MI_GLASS2 || + id == MI_GLASS3 || + id == MI_GLASS4 || + id == MI_GLASS5 || + id == MI_GLASS6 || + id == MI_GLASS7 || + id == MI_GLASS8; +} diff --git a/src/modelinfo/ModelInfo.cpp b/src/modelinfo/ModelInfo.cpp new file mode 100644 index 00000000..89fcdee5 --- /dev/null +++ b/src/modelinfo/ModelInfo.cpp @@ -0,0 +1,124 @@ +#include "common.h" +#include "patcher.h" +#include "ModelInfo.h" + +CBaseModelInfo **CModelInfo::ms_modelInfoPtrs = (CBaseModelInfo**)0x83D408; + +//CStore &CModelInfo::ms_simpleModelStore = *(CStore*)0x885BB4; +//CStore &CModelInfo::ms_timeModelStore = *(CStore*)0x94076C; +//CStore &CModelInfo::ms_2dEffectStore = *(CStore*)0x9434F8; +CStore CModelInfo::ms_simpleModelStore; +CStore CModelInfo::ms_timeModelStore; +CStore CModelInfo::ms_clumpModelStore; +CStore CModelInfo::ms_pedModelStore; +CStore CModelInfo::ms_vehicleModelStore; +CStore CModelInfo::ms_2dEffectStore; + +void +CModelInfo::Initialise(void) +{ + int i; + for(i = 0; i < MODELINFOSIZE; i++) + ms_modelInfoPtrs[i] = nil; + ms_2dEffectStore.clear(); + ms_simpleModelStore.clear(); + ms_timeModelStore.clear(); + ms_clumpModelStore.clear(); + ms_pedModelStore.clear(); + ms_vehicleModelStore.clear(); +} + +void +CModelInfo::Shutdown(void) +{ + int i; + for(i = 0; i < ms_simpleModelStore.allocPtr; i++) + ms_simpleModelStore.store[i].Shutdown(); + for(i = 0; i < ms_timeModelStore.allocPtr; i++) + ms_timeModelStore.store[i].Shutdown(); + for(i = 0; i < ms_clumpModelStore.allocPtr; i++) + ms_clumpModelStore.store[i].Shutdown(); + for(i = 0; i < ms_pedModelStore.allocPtr; i++) + ms_pedModelStore.store[i].Shutdown(); + for(i = 0; i < ms_vehicleModelStore.allocPtr; i++) + ms_vehicleModelStore.store[i].Shutdown(); +} + +CSimpleModelInfo* +CModelInfo::AddSimpleModel(int id) +{ + CSimpleModelInfo *modelinfo; + modelinfo = CModelInfo::ms_simpleModelStore.alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->Init(); + return modelinfo; +} + +CTimeModelInfo* +CModelInfo::AddTimeModel(int id) +{ + CTimeModelInfo *modelinfo; + modelinfo = CModelInfo::ms_timeModelStore.alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->Init(); + return modelinfo; +} + +CClumpModelInfo* +CModelInfo::AddClumpModel(int id) +{ + CClumpModelInfo *modelinfo; + modelinfo = CModelInfo::ms_clumpModelStore.alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + return modelinfo; +} + +CPedModelInfo* +CModelInfo::AddPedModel(int id) +{ + CPedModelInfo *modelinfo; + modelinfo = CModelInfo::ms_pedModelStore.alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + return modelinfo; +} + +CVehicleModelInfo* +CModelInfo::AddVehicleModel(int id) +{ + CVehicleModelInfo *modelinfo; + modelinfo = CModelInfo::ms_vehicleModelStore.alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + modelinfo->m_vehicleType = -1; + modelinfo->m_wheelId = -1; + modelinfo->m_materials1[0] = nil; + modelinfo->m_materials2[0] = nil; + modelinfo->m_bikeSteerAngle = 999.99f; + return modelinfo; +} + +CBaseModelInfo* +CModelInfo::GetModelInfo(const char *name, int *id) +{ + CBaseModelInfo *modelinfo; + for(int i = 0; i < MODELINFOSIZE; i++){ + modelinfo = CModelInfo::ms_modelInfoPtrs[i]; + if(modelinfo && _strcmpi(modelinfo->GetName(), name) == 0){ + if(id) + *id = i; + return modelinfo; + } + } + return nil; +} + +STARTPATCHES +// InjectHook(0x50B920, CModelInfo::AddSimpleModel, PATCH_JUMP); +// InjectHook(0x50B9C0, CModelInfo::AddTimeModel, PATCH_JUMP); +// InjectHook(0x50BA10, CModelInfo::AddClumpModel, PATCH_JUMP); +// InjectHook(0x50BAD0, CModelInfo::AddPedModel, PATCH_JUMP); +// InjectHook(0x50BA60, CModelInfo::AddPedModel, PATCH_JUMP); + InjectHook(0x50B860, (CBaseModelInfo *(*)(const char*, int*))CModelInfo::GetModelInfo, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/ModelInfo.h b/src/modelinfo/ModelInfo.h new file mode 100644 index 00000000..a0d30dea --- /dev/null +++ b/src/modelinfo/ModelInfo.h @@ -0,0 +1,35 @@ +#pragma once + +#include "2dEffect.h" +#include "BaseModelInfo.h" +#include "SimpleModelInfo.h" +#include "TimeModelInfo.h" +#include "ClumpModelInfo.h" +#include "PedModelInfo.h" +#include "VehicleModelInfo.h" + +class CModelInfo +{ + static CBaseModelInfo **ms_modelInfoPtrs; //[MODELINFOSIZE]; + static CStore ms_simpleModelStore; + static CStore ms_timeModelStore; + static CStore ms_clumpModelStore; + static CStore ms_pedModelStore; + static CStore ms_vehicleModelStore; + static CStore ms_2dEffectStore; + +public: + static void Initialise(void); + static void Shutdown(void); + + static CSimpleModelInfo *AddSimpleModel(int id); + static CTimeModelInfo *AddTimeModel(int id); + static CClumpModelInfo *AddClumpModel(int id); + static CPedModelInfo *AddPedModel(int id); + static CVehicleModelInfo *AddVehicleModel(int id); + + static CBaseModelInfo *GetModelInfo(const char *name, int *id); + static CBaseModelInfo *GetModelInfo(int id){ + return ms_modelInfoPtrs[id]; + } +}; diff --git a/src/modelinfo/PedModelInfo.cpp b/src/modelinfo/PedModelInfo.cpp new file mode 100644 index 00000000..e095902e --- /dev/null +++ b/src/modelinfo/PedModelInfo.cpp @@ -0,0 +1,197 @@ +#include "common.h" +#include "patcher.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" + +void +CPedModelInfo::DeleteRwObject(void) +{ + CClumpModelInfo::DeleteRwObject(); + if(m_hitColModel) + delete m_hitColModel; + m_hitColModel = nil; +} + +RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[12] = { + { "Smid", PED_TORSO, 0, }, // that is strange... + { "Shead", PED_HEAD, 0, }, + { "Supperarml", PED_UPPERARML, 0, }, + { "Supperarmr", PED_UPPERARMR, 0, }, + { "SLhand", PED_HANDL, 0, }, + { "SRhand", PED_HANDR, 0, }, + { "Supperlegl", PED_UPPERLEGL, 0, }, + { "Supperlegr", PED_UPPERLEGR, 0, }, + { "Sfootl", PED_FOOTL, 0, }, + { "Sfootr", PED_FOOTR, 0, }, + { "Slowerlegr", PED_LOWERLEGR, 0, }, + { NULL, 0, 0, }, +}; + +void +CPedModelInfo::SetClump(RpClump *clump) +{ + CClumpModelInfo::SetClump(clump); + SetFrameIds(m_pPedIds); + if(m_hitColModel == nil) + CreateHitColModel(); + if(strncmp(GetName(), "player", 7) == 0) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, CVisibilityPlugins::RenderPlayerCB); +} + +RpAtomic* +CountAtomicsCB(RpAtomic *atomic, void *data) +{ + (*(int32*)data)++; + return atomic; +} + +RpAtomic* +GetAtomicListCB(RpAtomic *atomic, void *data) +{ + **(RpAtomic***)data = atomic; + (*(RpAtomic***)data)++; + return atomic; +} + +RwFrame* +FindPedFrameFromNameCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(_strcmpi(GetFrameNodeName(frame)+1, assoc->name+1) != 0){ + RwFrameForAllChildren(frame, FindPedFrameFromNameCB, assoc); + return assoc->frame ? nil : frame; + }else{ + assoc->frame = frame; + return nil; + } +} + +void +CPedModelInfo::SetLowDetailClump(RpClump *lodclump) +{ + RpAtomic *atomics[16]; + RpAtomic **pAtm; + int32 numAtm, numLodAtm; + int i; + RwObjectNameAssociation assoc; + + numAtm = 0; + numLodAtm = 0; + RpClumpForAllAtomics(m_clump, CountAtomicsCB, &numAtm); // actually unused + RpClumpForAllAtomics(lodclump, CountAtomicsCB, &numLodAtm); + + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, CVisibilityPlugins::RenderPedHiDetailCB); + RpClumpForAllAtomics(lodclump, SetAtomicRendererCB, CVisibilityPlugins::RenderPedLowDetailCB); + + pAtm = atomics; + RpClumpForAllAtomics(lodclump, GetAtomicListCB, &pAtm); + + for(i = 0; i < numLodAtm; i++){ + assoc.name = GetFrameNodeName(RpAtomicGetFrame(atomics[i])); + assoc.frame = nil; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindPedFrameFromNameCB, &assoc); + if(assoc.frame){ + RpAtomicSetFrame(atomics[i], assoc.frame); + RpClumpRemoveAtomic(lodclump, atomics[i]); + RpClumpAddAtomic(m_clump, atomics[i]); + } + } +} + +struct ColNodeInfo +{ + char *name; + int pedNode; + int pieceType; + float x, z; + float radius; +}; + +// TODO: find out piece types +#define NUMPEDINFONODES 8 +ColNodeInfo m_pColNodeInfos[NUMPEDINFONODES] = { + { NULL, PED_HEAD, 6, 0.0f, 0.05f, 0.2f }, + { "Storso", 0, 0, 0.0f, 0.15f, 0.2f }, + { "Storso", 0, 0, 0.0f, -0.05f, 0.3f }, + { NULL, PED_TORSO, 1, 0.0f, -0.07f, 0.3f }, + { NULL, PED_UPPERARML, 2, 0.07f, -0.1f, 0.2f }, + { NULL, PED_UPPERARMR, 3, -0.07f, -0.1f, 0.2f }, + { "Slowerlegl", 0, 4, 0.0f, 0.07f, 0.25f }, + { NULL, PED_LOWERLEGR, 5, 0.0f, 0.07f, 0.25f }, +}; + +RwObject* +FindHeadRadiusCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + *(float*)data = RpAtomicGetBoundingSphere(atomic)->radius; + return nil; +} + +void +CPedModelInfo::CreateHitColModel(void) +{ + RwObjectNameAssociation nameAssoc; + RwObjectIdAssociation idAssoc; + CVector center; + RwFrame *nodeFrame; + CColModel *colmodel = new CColModel; + CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere)); + RwFrame *root = RpClumpGetFrame(m_clump); + RwMatrix *mat = RwMatrixCreate(); + for(int i = 0; i < NUMPEDINFONODES; i++){ + nodeFrame = nil; + if(m_pColNodeInfos[i].name){ + nameAssoc.name = m_pColNodeInfos[i].name; + nameAssoc.frame = nil; + RwFrameForAllChildren(root, FindFrameFromNameCB, &nameAssoc); + nodeFrame = nameAssoc.frame; + }else{ + idAssoc.id = m_pColNodeInfos[i].pedNode; + idAssoc.frame = nil; + RwFrameForAllChildren(root, FindFrameFromIdCB, &idAssoc); + nodeFrame = idAssoc.frame; + } + if(nodeFrame){ + float radius = m_pColNodeInfos[i].radius; + if(m_pColNodeInfos[i].pieceType == 6) + RwFrameForAllObjects(nodeFrame, FindHeadRadiusCB, &radius); + RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEREPLACE); + const char *name = GetFrameNodeName(nodeFrame); + for(nodeFrame = RwFrameGetParent(nodeFrame); + nodeFrame; + nodeFrame = RwFrameGetParent(nodeFrame)){ + name = GetFrameNodeName(nodeFrame); + RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEPOSTCONCAT); + if(RwFrameGetParent(nodeFrame) == root) + break; + } + center.x = mat->pos.x + m_pColNodeInfos[i].x; + center.y = mat->pos.y + 0.0f; + center.z = mat->pos.z + m_pColNodeInfos[i].z; + spheres[i].Set(radius, center, 17, m_pColNodeInfos[i].pieceType); + } + } + RwMatrixDestroy(mat); + colmodel->spheres = spheres; + colmodel->numSpheres = NUMPEDINFONODES; + center.x = center.y = center.z = 0.0f; + colmodel->boundingSphere.Set(2.0f, center, 0, 0); + CVector min, max; + min.x = min.y = -0.5f; + min.z = -1.2f; + max.x = max.y = 0.5f; + max.z = 1.2f; + colmodel->boundingBox.Set(min, max, 0, 0); + colmodel->level = 0; + m_hitColModel = colmodel; +} + +STARTPATCHES + InjectHook(0x510210, &CPedModelInfo::SetClump_, PATCH_JUMP); + InjectHook(0x510280, &CPedModelInfo::DeleteRwObject_, PATCH_JUMP); + InjectHook(0x510390, &CPedModelInfo::SetLowDetailClump, PATCH_JUMP); + InjectHook(0x5104D0, &CPedModelInfo::CreateHitColModel, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/PedModelInfo.h b/src/modelinfo/PedModelInfo.h new file mode 100644 index 00000000..e917b6b2 --- /dev/null +++ b/src/modelinfo/PedModelInfo.h @@ -0,0 +1,47 @@ +#pragma once + +#include "ClumpModelInfo.h" + +enum PedNode { + PED_WAIST, + PED_TORSO, // Smid on PS2/PC, Storso on mobile/xbox + PED_HEAD, + PED_UPPERARML, + PED_UPPERARMR, + PED_HANDL, + PED_HANDR, + PED_UPPERLEGL, + PED_UPPERLEGR, + PED_FOOTL, + PED_FOOTR, + PED_LOWERLEGR, + // This is not valid apparently + PED_LOWERLEGL, +}; + +class CPedModelInfo : public CClumpModelInfo +{ +public: + void *m_animGroup; // TODO + int32 m_pedType; + int32 m_pedStatType; + uint32 m_carsCanDrive; + CColModel *m_hitColModel; + RpAtomic *m_head; + RpAtomic *m_lhand; + RpAtomic *m_rhand; + + static RwObjectNameIdAssocation m_pPedIds[12]; + + CPedModelInfo(void) : CClumpModelInfo(MITYPE_PED) { } + void DeleteRwObject(void); + void SetClump(RpClump *); + + void SetLowDetailClump(RpClump*); + void CreateHitColModel(void); + + + void DeleteRwObject_(void) { this->CPedModelInfo::DeleteRwObject(); } + void SetClump_(RpClump *clump) { this->CPedModelInfo::SetClump(clump); } +}; +static_assert(sizeof(CPedModelInfo) == 0x54, "CPedModelInfo: error"); diff --git a/src/modelinfo/SimpleModelInfo.cpp b/src/modelinfo/SimpleModelInfo.cpp new file mode 100644 index 00000000..a5853d6f --- /dev/null +++ b/src/modelinfo/SimpleModelInfo.cpp @@ -0,0 +1,174 @@ +#include "common.h" +#include "patcher.h" +#include "Camera.h" +#include "ModelInfo.h" + +#define LOD_DISTANCE (300.0f) + +void +CSimpleModelInfo::DeleteRwObject(void) +{ + int i; + RwFrame *f; + for(i = 0; i < m_numAtomics; i++) + if(m_atomics[i]){ + f = RpAtomicGetFrame(m_atomics[i]); + RpAtomicDestroy(m_atomics[i]); + RwFrameDestroy(f); + m_atomics[i] = nil; + RemoveTexDictionaryRef(); + } +} + +RwObject* +CSimpleModelInfo::CreateInstance(void) +{ + RpAtomic *atomic; + if(m_atomics[0] == nil) + return nil; + atomic = RpAtomicClone(m_atomics[0]); + RpAtomicSetFrame(atomic, RwFrameCreate()); + return (RwObject*)atomic; +} + +RwObject* +CSimpleModelInfo::CreateInstance(RwMatrix *matrix) +{ + RpAtomic *atomic; + RwFrame *frame; + + if(m_atomics[0] == nil) + return nil; + atomic = RpAtomicClone(m_atomics[0]); + frame = RwFrameCreate(); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + return (RwObject*)atomic; +} + +void +CSimpleModelInfo::Init(void) +{ + m_atomics[0] = nil; + m_atomics[1] = nil; + m_atomics[2] = nil; + m_numAtomics = 0; + m_furthest = 0; + m_normalCull = 0; + m_isDamaged = 0; + m_isBigBuilding = 0; + m_noFade = 0; + m_drawLast = 0; + m_additive = 0; + m_isSubway = 0; + m_ignoreLight = 0; + m_noZwrite = 0; +} + +void +CSimpleModelInfo::SetAtomic(int n, RpAtomic *atomic) +{ + AddTexDictionaryRef(); + m_atomics[n] = atomic; + if(m_ignoreLight){ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) & ~rpGEOMETRYLIGHT); + } +} + +void +CSimpleModelInfo::SetLodDistances(float *dist) +{ + m_lodDistances[0] = dist[0]; + m_lodDistances[1] = dist[1]; + m_lodDistances[2] = dist[2]; +} + +void +CSimpleModelInfo::IncreaseAlpha(void) +{ + if(m_alpha >= 0xEF) + m_alpha = 0xFF; + else + m_alpha += 0x10; +} + +float +CSimpleModelInfo::GetNearDistance(void) +{ + return m_lodDistances[2] * TheCamera.LODDistMultiplier; +} + +float +CSimpleModelInfo::GetLargestLodDistance(void) +{ + float d; + // TODO: what exactly is going on here? + if(m_furthest != 0 && !m_isDamaged) + d = m_lodDistances[m_furthest-1]; + else + d = m_lodDistances[m_numAtomics-1]; + return d * TheCamera.LODDistMultiplier; +} + +RpAtomic* +CSimpleModelInfo::GetAtomicFromDistance(float dist) +{ + int i; + i = 0; + // TODO: what exactly is going on here? + if(m_isDamaged) + i = m_furthest; + for(; i < m_numAtomics; i++) + if(dist < m_lodDistances[i] *TheCamera.LODDistMultiplier) + return m_atomics[i]; + return nil; +} + +void +CSimpleModelInfo::FindRelatedModel(void) +{ + int i; + CBaseModelInfo *mi; + for(i = 0; i < MODELINFOSIZE; i++){ + mi = CModelInfo::GetModelInfo(i); + if(mi && mi != this && + strcmp(GetName()+3, mi->GetName()+3) == 0){ + assert(mi->IsSimple()); + this->SetRelatedModel((CSimpleModelInfo*)mi); + return; + } + } +} + +void +CSimpleModelInfo::SetupBigBuilding(void) +{ + CSimpleModelInfo *related; + if(m_lodDistances[0] > LOD_DISTANCE && m_atomics[2] == nil){ + m_isBigBuilding = 1; + FindRelatedModel(); + related = GetRelatedModel(); + if(related) + m_lodDistances[2] = related->GetLargestLodDistance()/TheCamera.LODDistMultiplier; + else + m_lodDistances[2] = 100.0f; + } +} + + +STARTPATCHES + InjectHook(0x5179B0, &CSimpleModelInfo::DeleteRwObject_, PATCH_JUMP); + InjectHook(0x517B60, &CSimpleModelInfo::CreateInstance_1, PATCH_JUMP); + InjectHook(0x517AC0, &CSimpleModelInfo::CreateInstance_2, PATCH_JUMP); + InjectHook(0x4A9BA0, &CSimpleModelInfo::GetRwObject_, PATCH_JUMP); + InjectHook(0x517990, &CSimpleModelInfo::Init, PATCH_JUMP); + InjectHook(0x517C60, &CSimpleModelInfo::IncreaseAlpha, PATCH_JUMP); + InjectHook(0x517950, &CSimpleModelInfo::SetAtomic, PATCH_JUMP); + InjectHook(0x517AA0, &CSimpleModelInfo::SetLodDistances, PATCH_JUMP); + InjectHook(0x517A90, &CSimpleModelInfo::GetNearDistance, PATCH_JUMP); + InjectHook(0x517A60, &CSimpleModelInfo::GetLargestLodDistance, PATCH_JUMP); + InjectHook(0x517A00, &CSimpleModelInfo::GetAtomicFromDistance, PATCH_JUMP); + InjectHook(0x517C00, &CSimpleModelInfo::FindRelatedModel, PATCH_JUMP); + InjectHook(0x517B90, &CSimpleModelInfo::SetupBigBuilding, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/SimpleModelInfo.h b/src/modelinfo/SimpleModelInfo.h new file mode 100644 index 00000000..186a517c --- /dev/null +++ b/src/modelinfo/SimpleModelInfo.h @@ -0,0 +1,57 @@ +#pragma once + +#include "BaseModelInfo.h" + +class CSimpleModelInfo : public CBaseModelInfo +{ +public: + // atomics[2] is often a pointer to the non-LOD modelinfo + RpAtomic *m_atomics[3]; + // m_lodDistances[2] holds the near distance for LODs + float m_lodDistances[3]; + uint8 m_numAtomics; + uint8 m_alpha; + uint16 m_furthest : 2; // 0: numAtomics-1 is furthest visible + // 1: atomic 0 is furthest + // 2: atomic 1 is furthest + uint16 m_normalCull : 1; + uint16 m_isDamaged : 1; + uint16 m_isBigBuilding : 1; + uint16 m_noFade : 1; + uint16 m_drawLast : 1; + uint16 m_additive : 1; + uint16 m_isSubway : 1; + uint16 m_ignoreLight : 1; + uint16 m_noZwrite : 1; + + CSimpleModelInfo(void) : CBaseModelInfo(MITYPE_SIMPLE) {} + CSimpleModelInfo(ModeInfoType id) : CBaseModelInfo(id) {} + ~CSimpleModelInfo() {} + void DeleteRwObject(void); + RwObject *CreateInstance(void); + RwObject *CreateInstance(RwMatrix *); + RwObject *GetRwObject(void) { return (RwObject*)m_atomics[0]; } + + void Init(void); + void IncreaseAlpha(void); + void SetAtomic(int n, RpAtomic *atomic); + void SetLodDistances(float *dist); + float GetLodDistance(int i) { return m_lodDistances[i]; } + float GetNearDistance(void); + float GetLargestLodDistance(void); + RpAtomic *GetAtomicFromDistance(float dist); + void FindRelatedModel(void); + void SetupBigBuilding(void); + + void SetNumAtomics(int n) { m_numAtomics = n; } + CSimpleModelInfo *GetRelatedModel(void){ + return (CSimpleModelInfo*)m_atomics[2]; } + void SetRelatedModel(CSimpleModelInfo *m){ + m_atomics[2] = (RpAtomic*)m; } + + void DeleteRwObject_(void) { this->CSimpleModelInfo::DeleteRwObject(); } + RwObject *CreateInstance_1(void) { return this->CSimpleModelInfo::CreateInstance(); } + RwObject *CreateInstance_2(RwMatrix *m) { return this->CSimpleModelInfo::CreateInstance(m); } + RwObject *GetRwObject_(void) { return this->CSimpleModelInfo::GetRwObject(); } +}; +static_assert(sizeof(CSimpleModelInfo) == 0x4C, "CSimpleModelInfo: error"); diff --git a/src/modelinfo/TimeModelInfo.cpp b/src/modelinfo/TimeModelInfo.cpp new file mode 100644 index 00000000..3ab3e13a --- /dev/null +++ b/src/modelinfo/TimeModelInfo.cpp @@ -0,0 +1,36 @@ +#include "common.h" +#include "patcher.h" +#include "Camera.h" +#include "ModelInfo.h" + +CTimeModelInfo* +CTimeModelInfo::FindOtherTimeModel(void) +{ + char name[40]; + char *p; + int i; + + strcpy(name, GetName()); + // change _nt to _dy + if(p = strstr(name, "_nt")) + strncpy(p, "_dy", 4); + // change _dy to _nt + else if(p = strstr(name, "_dy")) + strncpy(p, "_nt", 4); + else + return nil; + + for(i = 0; i < MODELINFOSIZE; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(i); + if(mi && mi->m_type == MITYPE_TIME && + strncmp(name, mi->GetName(), 24) == 0){ + m_otherTimeModelID = i; + return (CTimeModelInfo*)mi; + } + } + return nil; +} + +STARTPATCHES + InjectHook(0x517C80, &CTimeModelInfo::FindOtherTimeModel, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/TimeModelInfo.h b/src/modelinfo/TimeModelInfo.h new file mode 100644 index 00000000..08e46bd3 --- /dev/null +++ b/src/modelinfo/TimeModelInfo.h @@ -0,0 +1,18 @@ +#pragma once + +#include "SimpleModelInfo.h" + +class CTimeModelInfo : public CSimpleModelInfo +{ + int32 m_timeOn; + int32 m_timeOff; + int32 m_otherTimeModelID; +public: + CTimeModelInfo(void) : CSimpleModelInfo(MITYPE_TIME) { m_otherTimeModelID = -1; } + + int32 GetTimeOn(void) { return m_timeOn; } + int32 GetTimeOff(void) { return m_timeOff; } + int32 GetOtherTimeModel(void) { return m_otherTimeModelID; } + CTimeModelInfo *FindOtherTimeModel(void); +}; +static_assert(sizeof(CTimeModelInfo) == 0x58, "CTimeModelInfo: error"); diff --git a/src/modelinfo/VehicleModelInfo.cpp b/src/modelinfo/VehicleModelInfo.cpp new file mode 100644 index 00000000..575d0360 --- /dev/null +++ b/src/modelinfo/VehicleModelInfo.cpp @@ -0,0 +1,917 @@ +#include "common.h" +#include +#include "patcher.h" +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "TxdStore.h" +#include "Weather.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" + +int8 *CVehicleModelInfo::ms_compsToUse = (int8*)0x5FF2EC; // -2, -2 +int8 *CVehicleModelInfo::ms_compsUsed = (int8*)0x95CCB2; +RwTexture **CVehicleModelInfo::ms_pEnvironmentMaps = (RwTexture **)0x8F1A30; +RwRGBA *CVehicleModelInfo::ms_vehicleColourTable = (RwRGBA*)0x86BA88; +RwTexture **CVehicleModelInfo::ms_colourTextureTable = (RwTexture**)0x711C40; + +RwTexture *&gpWhiteTexture = *(RwTexture**)0x64C4F8; +RwFrame *&pMatFxIdentityFrame = *(RwFrame**)0x64C510; + +// TODO This depends on handling +WRAPPER void CVehicleModelInfo::SetVehicleComponentFlags(RwFrame *frame, uint32 flags) { EAXJMP(0x5203C0); } + +enum { + CAR_WHEEL_RF = 1, + CAR_WHEEL_RM = 2, + CAR_WHEEL_RB = 3, + CAR_WHEEL_LF = 4, + CAR_WHEEL_LM = 5, + CAR_WHEEL_LB = 6, + CAR_BUMP_FRONT = 7, + CAR_BUMP_REAR = 8, + CAR_WING_RF = 9, + CAR_WING_RR = 10, + CAR_DOOR_RF = 11, + CAR_DOOR_RR = 12, + CAR_WING_LF = 13, + CAR_WING_LR = 14, + CAR_DOOR_LF = 15, + CAR_DOOR_LR = 16, + CAR_BONNET = 17, + CAR_BOOT = 18, + CAR_WINDSCREEN = 19, + + CAR_POS_HEADLIGHTS = 0, + CAR_POS_TAILLIGHTS = 1, + CAR_POS_FRONTSEAT = 2, + CAR_POS_BACKSEAT = 3, + CAR_POS_EXHAUST = 9, +}; + +enum { + VEHICLE_FLAG_COLLAPSE = 0x2, + VEHICLE_FLAG_ADD_WHEEL = 0x4, + VEHICLE_FLAG_POS = 0x8, + VEHICLE_FLAG_DOOR = 0x10, + VEHICLE_FLAG_LEFT = 0x20, + VEHICLE_FLAG_RIGHT = 0x40, + VEHICLE_FLAG_FRONT = 0x80, + VEHICLE_FLAG_REAR = 0x100, + VEHICLE_FLAG_COMP = 0x200, + VEHICLE_FLAG_DRAWLAST = 0x400, + VEHICLE_FLAG_WINDSCREEN = 0x800, + VEHICLE_FLAG_ANGLECULL = 0x1000, + VEHICLE_FLAG_REARDOOR = 0x2000, + VEHICLE_FLAG_FRONTDOOR = 0x4000, +}; + +RwObjectNameIdAssocation carIds[] = { + { "wheel_rf_dummy", CAR_WHEEL_RF, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_rm_dummy", CAR_WHEEL_RM, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_rb_dummy", CAR_WHEEL_RB, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lf_dummy", CAR_WHEEL_LF, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lm_dummy", CAR_WHEEL_LM, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lb_dummy", CAR_WHEEL_LB, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "bump_front_dummy", CAR_BUMP_FRONT, VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE }, + { "bonnet_dummy", CAR_BONNET, VEHICLE_FLAG_COLLAPSE }, + { "wing_rf_dummy", CAR_WING_RF, VEHICLE_FLAG_COLLAPSE }, + { "wing_rr_dummy", CAR_WING_RR, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_COLLAPSE }, + { "door_rf_dummy", CAR_DOOR_RF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "door_rr_dummy", CAR_DOOR_RR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "wing_lf_dummy", CAR_WING_LF, VEHICLE_FLAG_COLLAPSE }, + { "wing_lr_dummy", CAR_WING_LR, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "door_lf_dummy", CAR_DOOR_LF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "door_lr_dummy", CAR_DOOR_LR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "boot_dummy", CAR_BOOT, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE }, + { "bump_rear_dummy", CAR_BUMP_REAR, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE }, + { "windscreen_dummy", CAR_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE }, + + { "ped_frontseat", CAR_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_backseat", CAR_POS_BACKSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "headlights", CAR_POS_HEADLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "taillights", CAR_POS_TAILLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "exhaust", CAR_POS_EXHAUST, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation boatIds[] = { + { "boat_moving_hi", 1, VEHICLE_FLAG_COLLAPSE }, + { "boat_rudder_hi", 3, VEHICLE_FLAG_COLLAPSE }, + { "windscreen", 2, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_COLLAPSE }, + { "ped_frontseat", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation trainIds[] = { + { "door_lhs_dummy", 1, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "door_rhs_dummy", 2, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "light_front", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_rear", 1, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_left_entry", 2, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_mid_entry", 3, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_right_entry", 4, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation heliIds[] = { + { "chassis_dummy", 1, VEHICLE_FLAG_COLLAPSE }, + { "toprotor", 2, 0 }, + { "backrotor", 3, 0 }, + { "tail", 4, 0 }, + { "topknot", 5, 0 }, + { "skid_left", 6, 0 }, + { "skid_right", 7, 0 }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation planeIds[] = { + { "wheel_front_dummy", 2, 0 }, + { "wheel_rear_dummy", 3, 0 }, + { "light_tailplane", 2, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_left", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_right", 1, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation bikeIds[] = { + { "chassis_dummy", 1, 0 }, + { "forks_front", 2, 0 }, + { "forks_rear", 3, 0 }, + { "wheel_front", 4, 0 }, + { "wheel_rear", 5, 0 }, + { "mudguard", 6, 0 }, + { "ped_frontseat", 2, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "headlights", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "taillights", 1, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "exhaust", 9, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation *CVehicleModelInfo::ms_vehicleDescs[] = { + carIds, + boatIds, + trainIds, + heliIds, + planeIds, + bikeIds +}; + + +CVehicleModelInfo::CVehicleModelInfo(void) + : CClumpModelInfo(MITYPE_VEHICLE) +{ + int32 i; + for(i = 0; i < NUM_VEHICLE_POSITIONS; i++){ + m_positions[i].x = 0.0f; + m_positions[i].y = 0.0f; + m_positions[i].z = 0.0f; + } + m_numColours = 0; +} + +void +CVehicleModelInfo::DeleteRwObject(void) +{ + int32 i; + RwFrame *f; + + for(i = 0; i < m_numComps; i++){ + f = RpAtomicGetFrame(m_comps[i]); + RpAtomicDestroy(m_comps[i]); + RwFrameDestroy(f); + } + m_numComps = 0; + CClumpModelInfo::DeleteRwObject(); +} + +RwObject* +CVehicleModelInfo::CreateInstance(void) +{ + RpClump *clump; + RpAtomic *atomic; + RwFrame *clumpframe, *f; + int32 comp1, comp2; + + clump = (RpClump*)CClumpModelInfo::CreateInstance(); + if(m_numComps != 0){ + clumpframe = RpClumpGetFrame(clump); + + comp1 = ChooseComponent(); + if(comp1 != -1){ + atomic = RpAtomicClone(m_comps[comp1]); + f = RwFrameCreate(); + RwFrameTransform(f, + RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp1])), + rwCOMBINEREPLACE); + RpAtomicSetFrame(atomic, f); + RpClumpAddAtomic(clump, atomic); + RwFrameAddChild(clumpframe, f); + } + ms_compsUsed[0] = comp1; + + comp2 = ChooseSecondComponent(); + if(comp2 != -1){ + atomic = RpAtomicClone(m_comps[comp2]); + f = RwFrameCreate(); + RwFrameTransform(f, + RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp2])), + rwCOMBINEREPLACE); + RpAtomicSetFrame(atomic, f); + RpClumpAddAtomic(clump, atomic); + RwFrameAddChild(clumpframe, f); + } + ms_compsUsed[1] = comp2; + }else{ + ms_compsUsed[0] = -1; + ms_compsUsed[1] = -1; + } + return (RwObject*)clump; +} + +void +CVehicleModelInfo::SetClump(RpClump *clump) +{ + CClumpModelInfo::SetClump(clump); + SetAtomicRenderCallbacks(); + SetFrameIds(ms_vehicleDescs[m_vehicleType]); + PreprocessHierarchy(); + FindEditableMaterialList(); + m_envMap = nil; + SetEnvironmentMap(); +} + +RwFrame* +CVehicleModelInfo::CollapseFramesCB(RwFrame *frame, void *data) +{ + RwFrameForAllChildren(frame, CollapseFramesCB, data); + RwFrameForAllObjects(frame, MoveObjectsCB, data); + RwFrameDestroy(frame); + return frame; +} + +RwObject* +CVehicleModelInfo::MoveObjectsCB(RwObject *object, void *data) +{ + RpAtomicSetFrame((RpAtomic*)object, (RwFrame*)data); + return object; +} + +RpAtomic* +CVehicleModelInfo::HideDamagedAtomicCB(RpAtomic *atomic, void *data) +{ + if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_dam")){ + RpAtomicSetFlags(atomic, 0); + CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_DAM); + }else if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_ok")) + CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_OK); + return atomic; +} + +RpMaterial* +CVehicleModelInfo::HasAlphaMaterialCB(RpMaterial *material, void *data) +{ + if(RpMaterialGetColor(material)->alpha != 0xFF){ + *(bool*)data = true; + return nil; + } + return material; +} + + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data) +{ + RpClump *clump; + char *name; + bool alpha; + + clump = (RpClump*)data; + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi") || strncmp(name, "extra", 5) == 0){ + if(alpha || strncmp(name, "windscreen", 10) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB); + }else if(strstr(name, "_lo")){ + RpClumpRemoveAtomic(clump, atomic); + RpAtomicDestroy(atomic); + return atomic; // BUG: not done by gta + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data) +{ + char *name; + bool alpha; + + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi") || strncmp(name, "extra", 5) == 0){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle); + }else if(strstr(name, "_lo")){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle); + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Train(RpAtomic *atomic, void *data) +{ + char *name; + bool alpha; + + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi")){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailAlphaCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailCB); + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data) +{ + RpClump *clump; + char *name; + + clump = (RpClump*)data; + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + if(strcmp(name, "boat_hi") == 0 || strncmp(name, "extra", 5) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_Boat); + else if(strstr(name, "_hi")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB); + else if(strstr(name, "_lo")){ + RpClumpRemoveAtomic(clump, atomic); + RpAtomicDestroy(atomic); + return atomic; // BUG: not done by gta + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + return atomic; +} + +void +CVehicleModelInfo::SetAtomicRenderCallbacks(void) +{ + switch(m_vehicleType){ + case VEHICLE_TYPE_TRAIN: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Train, nil); + break; + case VEHICLE_TYPE_HELI: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Heli, nil); + break; + case VEHICLE_TYPE_PLANE: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_BigVehicle, nil); + break; + case VEHICLE_TYPE_BOAT: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Boat, m_clump); + break; + default: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, m_clump); + break; + } +} + +RpAtomic* +CVehicleModelInfo::SetAtomicFlagCB(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::SetAtomicFlag(atomic, (int)data); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::ClearAtomicFlagCB(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::ClearAtomicFlag(atomic, (int)data); + return atomic; +} + +RwObject* +GetOkAndDamagedAtomicCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_OK) + ((RpAtomic**)data)[0] = atomic; + else if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_DAM) + ((RpAtomic**)data)[1] = atomic; + return object; +} + +void +CVehicleModelInfo::PreprocessHierarchy(void) +{ + int32 i; + RwObjectNameIdAssocation *desc; + RwFrame *f; + RpAtomic *atomic; + RwV3d *rwvec; + + desc = ms_vehicleDescs[m_vehicleType]; + m_numDoors = 0; + m_numComps = 0; + + for(i = 0; desc[i].name; i++){ + RwObjectNameAssociation assoc; + + if((desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS)) == 0) + continue; + assoc.frame = nil; + assoc.name = desc[i].name; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), + FindFrameFromNameWithoutIdCB, &assoc); + if(assoc.frame == nil) + continue; + + if(desc[i].flags & VEHICLE_FLAG_DOOR) + m_numDoors++; + + if(desc[i].flags & VEHICLE_FLAG_POS){ + f = assoc.frame; + rwvec = (RwV3d*)&m_positions[desc[i].hierId]; + *rwvec = *RwMatrixGetPos(RwFrameGetMatrix(f)); + for(f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) + RwV3dTransformPoints(rwvec, rwvec, 1, RwFrameGetMatrix(f)); + RwFrameDestroy(assoc.frame); + }else{ + atomic = (RpAtomic*)GetFirstObject(assoc.frame); + RpClumpRemoveAtomic(m_clump, atomic); + RwFrameRemoveChild(assoc.frame); + SetVehicleComponentFlags(assoc.frame, desc[i].flags); + m_comps[m_numComps++] = atomic; + } + } + + for(i = 0; desc[i].name; i++){ + RwObjectIdAssociation assoc; + + if(desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS)) + continue; + assoc.frame = nil; + assoc.id = desc[i].hierId; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), + FindFrameFromIdCB, &assoc); + if(assoc.frame == nil) + continue; + + if(desc[i].flags & VEHICLE_FLAG_DOOR) + m_numDoors++; + + if(desc[i].flags & VEHICLE_FLAG_COLLAPSE){ + RpAtomic *okdam[2] = { nil, nil }; + RwFrameForAllChildren(assoc.frame, CollapseFramesCB, assoc.frame); + RwFrameUpdateObjects(assoc.frame); + RwFrameForAllObjects(assoc.frame, GetOkAndDamagedAtomicCB, okdam); + if(okdam[0] && okdam[1]) + RpAtomicSetRenderCallBack(okdam[1], RpAtomicGetRenderCallBack(okdam[0])); + } + + SetVehicleComponentFlags(assoc.frame, desc[i].flags); + + if(desc[i].flags & VEHICLE_FLAG_ADD_WHEEL){ + if(m_wheelId == -1) + RwFrameDestroy(assoc.frame); + else{ + RwV3d scale; + atomic = (RpAtomic*)CModelInfo::GetModelInfo(m_wheelId)->CreateInstance(); + RwFrameDestroy(RpAtomicGetFrame(atomic)); + RpAtomicSetFrame(atomic, assoc.frame); + RpClumpAddAtomic(m_clump, atomic); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, + CVisibilityPlugins::RenderWheelAtomicCB); + scale.x = m_wheelScale; + scale.y = m_wheelScale; + scale.z = m_wheelScale; + RwFrameScale(assoc.frame, &scale, rwCOMBINEPRECONCAT); + } + } + } +} + + +#define COMPRULE_RULE(comprule) (((comprule) >> 12) & 0xF) +#define COMPRULE_COMPS(comprule) ((comprule) & 0xFFF) +#define COMPRULE_COMPN(comps, n) (((comps) >> 4*(n)) & 0xF) +#define COMPRULE2_RULE(comprule) (((comprule) >> (12+16)) & 0xF) +#define COMPRULE2_COMPS(comprule) ((comprule >> 16) & 0xFFF) +#define COMPRULE2_COMPN(comps, n) (((comps >> 16) >> 4*(n)) & 0xF) + +bool +IsValidCompRule(int rule) +{ + if(rule == 2) + return CWeather::OldWeatherType == WEATHER_RAINY || + CWeather::NewWeatherType == WEATHER_RAINY; + return true; +} + +int32 +CountCompsInRule(int comps) +{ + int32 n; + for(n = 0; comps != 0; comps >>= 4) + if((comps & 0xF) != 0xF) + n++; + return n; +} + +int32 +ChooseComponent(int32 rule, int32 comps) +{ + int32 n; + switch(rule){ + // identical cases.... + case 1: + n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps)); + return COMPRULE_COMPN(comps, n); + case 2: + // only valid in rain + n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps)); + return COMPRULE_COMPN(comps, n); + } + return -1; +} + +int32 +GetListOfComponentsNotUsedByRules(uint32 comprules, int32 numComps, int32 *comps) +{ + int32 i, n; + int32 unused[6] = { 0, 1, 2, 3, 4, 5 }; + + // first comprule + if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules))) + for(i = 0; i < 3; i++){ + n = COMPRULE_COMPN(comprules, i); + if(n != 0xF) + unused[n] = 0xF; + } + // second comprule + comprules >>= 16; + if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules))) + for(i = 0; i < 3; i++){ + n = COMPRULE_COMPN(comprules, i); + if(n != 0xF) + unused[n] = 0xF; + } + + n = 0; + for(i = 0; i < numComps; i++) + if(unused[i] != 0xF) + comps[n++] = unused[i]; + return n; +} + +int32 wheelIds[] = { CAR_WHEEL_LF, CAR_WHEEL_LB, CAR_WHEEL_RF, CAR_WHEEL_RB }; + +void +CVehicleModelInfo::GetWheelPosn(int32 n, CVector &pos) +{ + RwMatrix *m = RwFrameGetMatrix(GetFrameFromId(m_clump, wheelIds[n])); + pos.x = RwMatrixGetPos(m)->x; + pos.y = RwMatrixGetPos(m)->y; + pos.z = RwMatrixGetPos(m)->z; +} + + +int32 +CVehicleModelInfo::ChooseComponent(void) +{ + int32 comp; + int32 comps[8]; + int32 n; + + comp = -1; + if(ms_compsToUse[0] == -2){ + if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules))) + comp = ::ChooseComponent(COMPRULE_RULE(m_compRules), COMPRULE_COMPS(m_compRules)); + else if(CGeneral::GetRandomNumberInRange(0, 3) < 2){ + n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps); + if(n) + comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)]; + } + }else{ + comp = ms_compsToUse[0]; + ms_compsToUse[0] = -2; + } + return comp; +} + +int32 +CVehicleModelInfo::ChooseSecondComponent(void) +{ + int32 comp; + int32 comps[8]; + int32 n; + + comp = -1; + if(ms_compsToUse[1] == -2){ + if(COMPRULE2_RULE(m_compRules) && IsValidCompRule(COMPRULE2_RULE(m_compRules))) + comp = ::ChooseComponent(COMPRULE2_RULE(m_compRules), COMPRULE2_COMPS(m_compRules)); + else if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules)) && + CGeneral::GetRandomNumberInRange(0, 3) < 2){ + + n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps); + if(n) + comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)]; + } + }else{ + comp = ms_compsToUse[1]; + ms_compsToUse[1] = -2; + } + return comp; +} + +struct editableMatCBData +{ + CVehicleModelInfo *vehicle; + int32 numMats1; + int32 numMats2; +}; + +RpMaterial* +CVehicleModelInfo::GetEditableMaterialListCB(RpMaterial *material, void *data) +{ + static RwRGBA white = { 255, 255, 255, 255 }; + RwRGBA *col; + editableMatCBData *cbdata; + + cbdata = (editableMatCBData*)data; + col = RpMaterialGetColor(material); + if(col->red == 0x3C && col->green == 0xFF && col->blue == 0){ + cbdata->vehicle->m_materials1[cbdata->numMats1++] = material; + RpMaterialSetColor(material, &white); + }else if(col->red == 0xFF && col->green == 0 && col->blue == 0xAF){ + cbdata->vehicle->m_materials2[cbdata->numMats2++] = material; + RpMaterialSetColor(material, &white); + } + return material; +} + +RpAtomic* +CVehicleModelInfo::GetEditableMaterialListCB(RpAtomic *atomic, void *data) +{ + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), GetEditableMaterialListCB, data); + return atomic; +} + +void +CVehicleModelInfo::FindEditableMaterialList(void) +{ + editableMatCBData cbdata; + int32 i; + + cbdata.vehicle = this; + cbdata.numMats1 = 0; + cbdata.numMats2 = 0; + RpClumpForAllAtomics(m_clump, GetEditableMaterialListCB, &cbdata); + for(i = 0; i < m_numComps; i++) + GetEditableMaterialListCB(m_comps[i], &cbdata); + m_materials1[cbdata.numMats1] = nil; + m_materials2[cbdata.numMats2] = nil; + m_currentColour1 = -1; + m_currentColour2 = -1; +} + +void +CVehicleModelInfo::SetVehicleColour(uint8 c1, uint8 c2) +{ + RwRGBA col, *colp; + RwTexture *coltex; + RpMaterial **matp; + + if(c1 != m_currentColour1){ + col = ms_vehicleColourTable[c1]; + coltex = ms_colourTextureTable[c1]; + for(matp = m_materials1; *matp; matp++){ + if(RpMaterialGetTexture(*matp) && RpMaterialGetTexture(*matp)->name[0] != '@'){ + colp = RpMaterialGetColor(*matp); + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + }else + RpMaterialSetTexture(*matp, coltex); + } + m_currentColour1 = c1; + } + + if(c2 != m_currentColour2){ + col = ms_vehicleColourTable[c2]; + coltex = ms_colourTextureTable[c2]; + for(matp = m_materials2; *matp; matp++){ + if(RpMaterialGetTexture(*matp) && RpMaterialGetTexture(*matp)->name[0] != '@'){ + colp = RpMaterialGetColor(*matp); + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + }else + RpMaterialSetTexture(*matp, coltex); + } + m_currentColour2 = c2; + } +} + + +RpMaterial* +CVehicleModelInfo::HasSpecularMaterialCB(RpMaterial *material, void *data) +{ + if(RpMaterialGetSurfaceProperties(material)->specular <= 0.0f) + return material; + *(bool*)data = true; + return nil; +} + +RpMaterial* +CVehicleModelInfo::SetEnvironmentMapCB(RpMaterial *material, void *data) +{ + float spec; + + spec = RpMaterialGetSurfaceProperties(material)->specular; + if(spec <= 0.0f) + RpMatFXMaterialSetEffects(material, rpMATFXEFFECTNULL); + else{ + if(RpMaterialGetTexture(material) == 0) + RpMaterialSetTexture(material, gpWhiteTexture); + RpMatFXMaterialSetEffects(material, rpMATFXEFFECTENVMAP); + spec *= 0.5f; // Tone down a bit for PC + RpMatFXMaterialSetupEnvMap(material, (RwTexture*)data, pMatFxIdentityFrame, false, spec); + } + return material; +} + +RpAtomic* +CVehicleModelInfo::SetEnvironmentMapCB(RpAtomic *atomic, void *data) +{ + bool hasSpec; + RpGeometry *geo; + + geo = RpAtomicGetGeometry(atomic); + hasSpec = 0; + RpGeometryForAllMaterials(geo, HasSpecularMaterialCB, &hasSpec); + if(hasSpec){ + RpGeometryForAllMaterials(geo, SetEnvironmentMapCB, data); + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpMatFXAtomicEnableEffects(atomic); + // PS2 sets of PS2Manager lighting CB here + } + return atomic; +} + +void +CVehicleModelInfo::SetEnvironmentMap(void) +{ + CSimpleModelInfo *wheelmi; + int32 i; + + if(pMatFxIdentityFrame == nil){ + pMatFxIdentityFrame = RwFrameCreate(); + RwMatrixSetIdentity(RwFrameGetMatrix(pMatFxIdentityFrame)); + RwFrameUpdateObjects(pMatFxIdentityFrame); + RwFrameGetLTM(pMatFxIdentityFrame); + } + + if(m_envMap != ms_pEnvironmentMaps[0]){ + m_envMap = ms_pEnvironmentMaps[0]; + RpClumpForAllAtomics(m_clump, SetEnvironmentMapCB, m_envMap); + if(m_wheelId != -1){ + wheelmi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_wheelId); + for(i = 0; i < wheelmi->m_numAtomics; i++) + SetEnvironmentMapCB(wheelmi->m_atomics[i], m_envMap); + } + } +} + +void +CVehicleModelInfo::LoadEnvironmentMaps(void) +{ + char *texnames[] = { + "reflection01", // only one used + "reflection02", + "reflection03", + "reflection04", + "reflection05", + "reflection06", + }; + int32 txdslot; + int32 i; + + txdslot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(txdslot); + for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++){ + ms_pEnvironmentMaps[i] = RwTextureRead(texnames[i], nil); + RwTextureSetFilterMode(ms_pEnvironmentMaps[i], rwFILTERLINEAR); + } + if(gpWhiteTexture == nil){ + gpWhiteTexture = RwTextureRead("white", nil); + gpWhiteTexture->name[0] = '@'; + RwTextureSetFilterMode(gpWhiteTexture, rwFILTERLINEAR); + } + CTxdStore::PopCurrentTxd(); +} + +void +CVehicleModelInfo::ShutdownEnvironmentMaps(void) +{ + int32 i; + + // ignoring "initialised" as that's a PS2 thing only + RwTextureDestroy(gpWhiteTexture); + gpWhiteTexture = nil; + for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++) + if(ms_pEnvironmentMaps[i]) + RwTextureDestroy(ms_pEnvironmentMaps[i]); + RwFrameDestroy(pMatFxIdentityFrame); + pMatFxIdentityFrame = nil; +} + +STARTPATCHES + InjectHook(0x51FDC0, &CVehicleModelInfo::DeleteRwObject_, PATCH_JUMP); + InjectHook(0x51FCB0, &CVehicleModelInfo::CreateInstance_, PATCH_JUMP); + InjectHook(0x51FC60, &CVehicleModelInfo::SetClump_, PATCH_JUMP); + + InjectHook(0x51FE10, &CVehicleModelInfo::CollapseFramesCB, PATCH_JUMP); + InjectHook(0x51FE50, &CVehicleModelInfo::MoveObjectsCB, PATCH_JUMP); + InjectHook(0x51FE70, &CVehicleModelInfo::HideDamagedAtomicCB, PATCH_JUMP); + InjectHook(0x51FEF0, &CVehicleModelInfo::HasAlphaMaterialCB, PATCH_JUMP); + + InjectHook(0x51FF10, &CVehicleModelInfo::SetAtomicRendererCB, PATCH_JUMP); + InjectHook(0x520030, &CVehicleModelInfo::SetAtomicRendererCB_BigVehicle, PATCH_JUMP); + InjectHook(0x520230, &CVehicleModelInfo::SetAtomicRendererCB_Train, PATCH_JUMP); + InjectHook(0x520120, &CVehicleModelInfo::SetAtomicRendererCB_Boat, PATCH_JUMP); + InjectHook(0x520210, &CVehicleModelInfo::SetAtomicRendererCB_Heli, PATCH_JUMP); + InjectHook(0x5202C0, &CVehicleModelInfo::SetAtomicRenderCallbacks, PATCH_JUMP); + + InjectHook(0x520340, &CVehicleModelInfo::SetAtomicFlagCB, PATCH_JUMP); + InjectHook(0x520360, &CVehicleModelInfo::ClearAtomicFlagCB, PATCH_JUMP); + + InjectHook(0x5204D0, &CVehicleModelInfo::PreprocessHierarchy, PATCH_JUMP); + + InjectHook(0x520840, &CVehicleModelInfo::GetWheelPosn, PATCH_JUMP); + + InjectHook(0x520880, IsValidCompRule, PATCH_JUMP); + InjectHook(0x520990, CountCompsInRule, PATCH_JUMP); + InjectHook(0x5209C0, ChooseComponent, PATCH_JUMP); + InjectHook(0x5208C0, GetListOfComponentsNotUsedByRules, PATCH_JUMP); + InjectHook(0x520AB0, &CVehicleModelInfo::ChooseComponent, PATCH_JUMP); + InjectHook(0x520BE0, &CVehicleModelInfo::ChooseSecondComponent, PATCH_JUMP); + + InjectHook(0x520DC0, (RpAtomic *(*)(RpAtomic*, void*))CVehicleModelInfo::GetEditableMaterialListCB, PATCH_JUMP); + InjectHook(0x520D30, (RpMaterial *(*)(RpMaterial*, void*))CVehicleModelInfo::GetEditableMaterialListCB, PATCH_JUMP); + InjectHook(0x520DE0, &CVehicleModelInfo::FindEditableMaterialList, PATCH_JUMP); + InjectHook(0x520E70, &CVehicleModelInfo::SetVehicleColour, PATCH_JUMP); + + InjectHook(0x521820, (RpAtomic *(*)(RpAtomic*, void*))CVehicleModelInfo::SetEnvironmentMapCB, PATCH_JUMP); + InjectHook(0x5217A0, (RpMaterial *(*)(RpMaterial*, void*))CVehicleModelInfo::SetEnvironmentMapCB, PATCH_JUMP); + InjectHook(0x521770, CVehicleModelInfo::HasSpecularMaterialCB, PATCH_JUMP); + InjectHook(0x521890, &CVehicleModelInfo::SetEnvironmentMap, PATCH_JUMP); + InjectHook(0x521680, CVehicleModelInfo::LoadEnvironmentMaps, PATCH_JUMP); + InjectHook(0x521720, CVehicleModelInfo::ShutdownEnvironmentMaps, PATCH_JUMP); +ENDPATCHES diff --git a/src/modelinfo/VehicleModelInfo.h b/src/modelinfo/VehicleModelInfo.h new file mode 100644 index 00000000..ccc46f73 --- /dev/null +++ b/src/modelinfo/VehicleModelInfo.h @@ -0,0 +1,115 @@ +#pragma once + +#include "ClumpModelInfo.h" + +enum { + NUM_VEHICLE_POSITIONS = 10, + NUM_FIRST_MATERIALS = 26, + NUM_SECOND_MATERIALS = 26, + NUM_VEHICLE_COLOURS = 8, + NUM_VEHICLE_ENVMAPS = 1 +}; + +enum { + ATOMIC_FLAG_OK = 0x1, + ATOMIC_FLAG_DAM = 0x2, + ATOMIC_FLAG_LEFT = 0x4, + ATOMIC_FLAG_RIGHT = 0x8, + ATOMIC_FLAG_FRONT = 0x10, + ATOMIC_FLAG_REAR = 0x20, + ATOMIC_FLAG_DRAWLAST = 0x40, + ATOMIC_FLAG_WINDSCREEN = 0x80, + ATOMIC_FLAG_ANGLECULL = 0x100, + ATOMIC_FLAG_REARDOOR = 0x200, + ATOMIC_FLAG_FRONTDOOR = 0x400, + ATOMIC_FLAG_NOCULL = 0x800, +}; + +enum { + VEHICLE_TYPE_CAR, + VEHICLE_TYPE_BOAT, + VEHICLE_TYPE_TRAIN, + VEHICLE_TYPE_HELI, + VEHICLE_TYPE_PLANE, + VEHICLE_TYPE_BIKE, + NUM_VEHICLE_TYPES +}; + +class CVehicleModelInfo : public CClumpModelInfo +{ +public: + uint8 m_lastColour1; + uint8 m_lastColour2; + char m_gameName[32]; + int32 m_vehicleType; + int32 m_wheelId; + float m_wheelScale; + int32 m_numDoors; + int32 m_handlingId; + int32 m_vehicleClass; + int32 m_level; + CVector m_positions[NUM_VEHICLE_POSITIONS]; + uint32 m_compRules; + float m_bikeSteerAngle; + RpMaterial *m_materials1[NUM_FIRST_MATERIALS]; + RpMaterial *m_materials2[NUM_SECOND_MATERIALS]; + uint8 m_colours1[NUM_VEHICLE_COLOURS]; + uint8 m_colours2[NUM_VEHICLE_COLOURS]; + uint8 m_numColours; + uint8 m_bLastColorVariation; // + uint8 m_currentColour1; + uint8 m_currentColour2; + RwTexture *m_envMap; + RpAtomic *m_comps[6]; + int32 m_numComps; + + static int8 *ms_compsToUse; // [2]; + static int8 *ms_compsUsed; // [2]; + static RwTexture **ms_pEnvironmentMaps; // [NUM_VEHICLE_ENVMAPS] + static RwRGBA *ms_vehicleColourTable; // [256] + static RwTexture **ms_colourTextureTable; // [256] + static RwObjectNameIdAssocation *ms_vehicleDescs[NUM_VEHICLE_TYPES]; + + CVehicleModelInfo(void); + void DeleteRwObject(void); + RwObject *CreateInstance(void); + void SetClump(RpClump *); + + static RwFrame *CollapseFramesCB(RwFrame *frame, void *data); + static RwObject *MoveObjectsCB(RwObject *object, void *data); + static RpAtomic *HideDamagedAtomicCB(RpAtomic *atomic, void *data); + static RpMaterial *HasAlphaMaterialCB(RpMaterial *material, void *data); + + static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Train(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data); + void SetAtomicRenderCallbacks(void); + + static RpAtomic *SetAtomicFlagCB(RpAtomic *atomic, void *data); + static RpAtomic *ClearAtomicFlagCB(RpAtomic *atomic, void *data); + void SetVehicleComponentFlags(RwFrame *frame, uint32 flags); + void PreprocessHierarchy(void); + void GetWheelPosn(int32 n, CVector &pos); + + int32 ChooseComponent(void); + int32 ChooseSecondComponent(void); + + static RpMaterial *GetEditableMaterialListCB(RpMaterial *material, void *data); + static RpAtomic *GetEditableMaterialListCB(RpAtomic *atomic, void *data); + void FindEditableMaterialList(void); + void SetVehicleColour(uint8 c1, uint8 c2); + + static RpAtomic *SetEnvironmentMapCB(RpAtomic *atomic, void *data); + static RpMaterial *SetEnvironmentMapCB(RpMaterial *material, void *data); + static RpMaterial *HasSpecularMaterialCB(RpMaterial *material, void *data); + void SetEnvironmentMap(void); + static void LoadEnvironmentMaps(void); + static void ShutdownEnvironmentMaps(void); + + void DeleteRwObject_(void) { this->CVehicleModelInfo::DeleteRwObject(); } + RwObject *CreateInstance_(void) { return this->CVehicleModelInfo::CreateInstance(); } + void SetClump_(RpClump *clump) { this->CVehicleModelInfo::SetClump(clump); } +}; +static_assert(sizeof(CVehicleModelInfo) == 0x1F8, "CVehicleModelInfo: error"); diff --git a/src/patcher.cpp b/src/patcher.cpp new file mode 100644 index 00000000..5fdbdf8b --- /dev/null +++ b/src/patcher.cpp @@ -0,0 +1,22 @@ +#include "common.h" +#include "patcher.h" + +StaticPatcher *StaticPatcher::ms_head; + +StaticPatcher::StaticPatcher(Patcher func) + : m_func(func) +{ + m_next = ms_head; + ms_head = this; +} + +void +StaticPatcher::Apply() +{ + StaticPatcher *current = ms_head; + while(current){ + current->Run(); + current = current->m_next; + } + ms_head = nil; +} diff --git a/src/patcher.h b/src/patcher.h new file mode 100644 index 00000000..4ac1111b --- /dev/null +++ b/src/patcher.h @@ -0,0 +1,171 @@ +#pragma once + +#define WRAPPER __declspec(naked) +#define DEPRECATED __declspec(deprecated) +#define EAXJMP(a) { _asm mov eax, a _asm jmp eax } +#define VARJMP(a) { _asm jmp a } +#define WRAPARG(a) UNREFERENCED_PARAMETER(a) + +#define NOVMT __declspec(novtable) +#define SETVMT(a) *((DWORD_PTR*)this) = (DWORD_PTR)a + +enum +{ + PATCH_CALL, + PATCH_JUMP, + PATCH_NOTHING, +}; + +enum +{ + III_10 = 1, + III_11, + III_STEAM, + VC_10, + VC_11, + VC_STEAM +}; + +extern int gtaversion; + +template +inline T AddressByVersion(uint32_t addressIII10, uint32_t addressIII11, uint32_t addressIIISteam, uint32_t addressvc10, uint32_t addressvc11, uint32_t addressvcSteam) +{ + if(gtaversion == -1){ + if(*(uint32_t*)0x5C1E75 == 0xB85548EC) gtaversion = III_10; + else if(*(uint32_t*)0x5C2135 == 0xB85548EC) gtaversion = III_11; + else if(*(uint32_t*)0x5C6FD5 == 0xB85548EC) gtaversion = III_STEAM; + else if(*(uint32_t*)0x667BF5 == 0xB85548EC) gtaversion = VC_10; + else if(*(uint32_t*)0x667C45 == 0xB85548EC) gtaversion = VC_11; + else if(*(uint32_t*)0x666BA5 == 0xB85548EC) gtaversion = VC_STEAM; + else gtaversion = 0; + } + switch(gtaversion){ + case III_10: + return (T)addressIII10; + case III_11: + return (T)addressIII11; + case III_STEAM: + return (T)addressIIISteam; + case VC_10: + return (T)addressvc10; + case VC_11: + return (T)addressvc11; + case VC_STEAM: + return (T)addressvcSteam; + default: + return (T)0; + } +} + +inline bool +is10(void) +{ + return gtaversion == III_10 || gtaversion == VC_10; +} + +inline bool +isIII(void) +{ + return gtaversion >= III_10 && gtaversion <= III_STEAM; +} + +inline bool +isVC(void) +{ + return gtaversion >= VC_10 && gtaversion <= VC_STEAM; +} + +#define PTRFROMCALL(addr) (uint32_t)(*(uint32_t*)((uint32_t)addr+1) + (uint32_t)addr + 5) +#define INTERCEPT(saved, func, a) \ +{ \ + saved = PTRFROMCALL(a); \ + InjectHook(a, func); \ +} + +template inline void +Patch(AT address, T value) +{ + DWORD dwProtect[2]; + VirtualProtect((void*)address, sizeof(T), PAGE_EXECUTE_READWRITE, &dwProtect[0]); + *(T*)address = value; + VirtualProtect((void*)address, sizeof(T), dwProtect[0], &dwProtect[1]); +} + +template inline void +Nop(AT address, unsigned int nCount) +{ + DWORD dwProtect[2]; + VirtualProtect((void*)address, nCount, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + memset((void*)address, 0x90, nCount); + VirtualProtect((void*)address, nCount, dwProtect[0], &dwProtect[1]); +} + +template inline void +InjectHook(AT address, HT hook, unsigned int nType=PATCH_NOTHING) +{ + DWORD dwProtect[2]; + switch ( nType ) + { + case PATCH_JUMP: + VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + *(BYTE*)address = 0xE9; + break; + case PATCH_CALL: + VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + *(BYTE*)address = 0xE8; + break; + default: + VirtualProtect((void*)((DWORD)address + 1), 4, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + break; + } + DWORD dwHook; + _asm + { + mov eax, hook + mov dwHook, eax + } + + *(ptrdiff_t*)((DWORD)address + 1) = (DWORD)dwHook - (DWORD)address - 5; + if ( nType == PATCH_NOTHING ) + VirtualProtect((void*)((DWORD)address + 1), 4, dwProtect[0], &dwProtect[1]); + else + VirtualProtect((void*)address, 5, dwProtect[0], &dwProtect[1]); +} + +inline void ExtractCall(void *dst, uint32_t a) +{ + *(uint32_t*)dst = (uint32_t)(*(uint32_t*)(a+1) + a + 5); +} +template +inline void InterceptCall(void *dst, T func, uint32_t a) +{ + ExtractCall(dst, a); + InjectHook(a, func); +} +template +inline void InterceptVmethod(void *dst, T func, uint32_t a) +{ + *(uint32_t*)dst = *(uint32_t*)a; + Patch(a, func); +} + + + +class StaticPatcher +{ +private: + using Patcher = void(*)(); + + Patcher m_func; + StaticPatcher *m_next; + static StaticPatcher *ms_head; + + void Run() { m_func(); } +public: + StaticPatcher(Patcher func); + static void Apply(); +}; + +#define STARTPATCHES static StaticPatcher Patcher([](){ +#define ENDPATCHES }); diff --git a/src/render/2dEffect.h b/src/render/2dEffect.h new file mode 100644 index 00000000..6cba85d1 --- /dev/null +++ b/src/render/2dEffect.h @@ -0,0 +1,39 @@ +class C2dEffect +{ +public: + struct Light { + float dist; + float outerRange; + float size; + float innerRange; + uint8 flash; + uint8 wet; + uint8 flare; + uint8 shadowIntens; + uint8 flag; + RwTexture *corona; + RwTexture *shadow; + }; + struct Particle { + int particleType; + float dir[3]; + float scale; + }; + struct Attractor { + CVector dir; + uint8 flag; + uint8 probability; + }; + + CVector pos; + RwRGBA col; + uint8 type; + union { + Light light; + Particle particle; + Attractor attractor; + }; + + C2dEffect(void) {} +}; +static_assert(sizeof(C2dEffect) == 0x34, "C2dEffect: error"); diff --git a/src/render/Clouds.cpp b/src/render/Clouds.cpp new file mode 100644 index 00000000..64bc93e8 --- /dev/null +++ b/src/render/Clouds.cpp @@ -0,0 +1,430 @@ +#include "common.h" +#include "patcher.h" +#include "Sprite.h" +#include "General.h" +#include "Coronas.h" +#include "Camera.h" +#include "TxdStore.h" +#include "Weather.h" +#include "Clock.h" +#include "Timer.h" +#include "Timecycle.h" +#include "Renderer.h" +#include "Clouds.h" + +#define SMALLSTRIPHEIGHT 4.0f +#define HORIZSTRIPHEIGHT 48.0f + +RwTexture **gpCloudTex = (RwTexture**)0x9411C0; //[5]; + +float &CClouds::CloudRotation = *(float*)0x8F5F40; +uint32 &CClouds::IndividualRotation = *(uint32*)0x943078; + +float &CClouds::ms_cameraRoll = *(float*)0x8F29CC; +float &CClouds::ms_horizonZ = *(float*)0x8F31C0; +CRGBA &CClouds::ms_colourTop = *(CRGBA*)0x94143C; +CRGBA &CClouds::ms_colourBottom = *(CRGBA*)0x8F2C38; + +void +CClouds::Init(void) +{ + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + gpCloudTex[0] = RwTextureRead("cloud1", nil); + gpCloudTex[1] = RwTextureRead("cloud2", nil); + gpCloudTex[2] = RwTextureRead("cloud3", nil); + gpCloudTex[3] = RwTextureRead("cloudhilit", nil); + gpCloudTex[4] = RwTextureRead("cloudmasked", nil); + CTxdStore::PopCurrentTxd(); + CloudRotation = 0.0f; +} + +void +CClouds::Update(void) +{ + float s = sin(TheCamera.Orientation - 0.85f); + CloudRotation += CWeather::Wind*s*0.0025f; + IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep() + 0.3f) * 60.0f; +} + + +void +CClouds::Render(void) +{ + int i; + float szx, szy; + RwV3d screenpos; + RwV3d worldpos; + + CCoronas::SunBlockedByClouds = false; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + CSprite::InitSpriteBuffer(); + + int minute = CClock::GetHours()*60 + CClock::GetMinutes(); + RwV3d campos = *(RwV3d*)&TheCamera.GetPosition(); + + float coverage = CWeather::CloudCoverage <= CWeather::Foggyness ? CWeather::Foggyness : CWeather::CloudCoverage; + + // Moon + int moonfadeout = abs(minute - 180); // fully visible at 3AM + if(moonfadeout < 180){ // fade in/out 3 hours + int brightness = (1.0f - coverage) * (180 - moonfadeout); + RwV3d pos = { 0.0f, -100.0f, 15.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[2]->raster); + if(CCoronas::bSmallMoon){ + szx *= 4.0f; + szy *= 4.0f; + }else{ + szx *= 10.0f; + szy *= 10.0f; + } + CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx, szy, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + + // The R* logo + int starintens = 0; + if(CClock::GetHours() < 22 && CClock::GetHours() > 5) + starintens = 0; + else if(CClock::GetHours() > 22 || CClock::GetHours() < 5) + starintens = 255; + else if(CClock::GetHours() == 22) + starintens = 255 * CClock::GetMinutes()/60.0f; + else if(CClock::GetHours() == 5) + starintens = 255 * (60 - CClock::GetMinutes())/60.0f; + if(starintens != 0){ + // R + static float StarCoorsX[9] = { 0.0f, 0.05f, 0.12f, 0.5f, 0.8f, 0.6f, 0.27f, 0.55f, 0.75f }; + static float StarCoorsY[9] = { 0.0f, 0.45f, 0.9f, 1.0f, 0.85f, 0.52f, 0.48f, 0.35f, 0.2f }; + static float StarSizes[9] = { 1.0f, 1.4f, 0.9f, 1.0f, 0.6f, 1.5f, 1.3f, 1.0f, 0.8f }; + int brightness = (1.0f - coverage) * starintens; + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[0]->raster); + for(i = 0; i < 11; i++){ + RwV3d pos = { 100.0f, 0.0f, 10.0f }; + if(i >= 9) pos.x = -pos.x; + RwV3dAdd(&worldpos, &campos, &pos); + worldpos.y -= 90.0f*StarCoorsX[i%9]; + worldpos.z += 80.0f*StarCoorsY[i%9]; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + float sz = 0.8f*StarSizes[i%9]; + CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx*sz, szy*sz, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + CSprite::FlushSpriteBuffer(); + + // * + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[0]->raster); + RwV3d pos = { 100.0f, 0.0f, 10.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + worldpos.y -= 90.0f; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + brightness *= (CGeneral::GetRandomNumber()&127) / 640.0f + 0.5f; + CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx*5.0f, szy*5.0f, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + + // Low clouds + static float LowCloudsX[12] = { 1.0f, 0.7f, 0.0f, -0.7f, -1.0f, -0.7f, + 0.0f, 0.7f, 0.8f, -0.8f, 0.4f, -0.4f }; + static float LowCloudsY[12] = { 0.0f, -0.7f, -1.0f, -0.7f, 0.0f, 0.7f, + 1.0f, 0.7f, 0.4f, 0.4f, -0.8f, -0.8f }; + static float LowCloudsZ[12] = { 0.0f, 1.0f, 0.5f, 0.0f, 1.0f, 0.3f, + 0.9f, 0.4f, 1.3f, 1.4f, 1.2f, 1.7f }; + float lowcloudintensity = 1.0f - coverage; + int r = CTimeCycle::GetLowCloudsRed() * lowcloudintensity; + int g = CTimeCycle::GetLowCloudsGreen() * lowcloudintensity; + int b = CTimeCycle::GetLowCloudsBlue() * lowcloudintensity; + for(int cloudtype = 0; cloudtype < 3; cloudtype++){ + for(i = cloudtype; i < 12; i += 3){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCloudTex[cloudtype]->raster); + RwV3d pos = { 800.0f*LowCloudsX[i], 800.0f*LowCloudsY[i], 60.0f*LowCloudsZ[i] }; + worldpos.x = campos.x + pos.x; + worldpos.y = campos.y + pos.y; + worldpos.z = 40.0f + pos.z; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)) + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(screenpos.x, screenpos.y, screenpos.z, + szx*320.0f, szy*40.0f, r, g, b, 255, 1.0f/screenpos.z, ms_cameraRoll, 255); + } + CSprite::FlushSpriteBuffer(); + } + + // Fluffy clouds + float rot_sin = sin(CloudRotation); + float rot_cos = cos(CloudRotation); + int fluffyalpha = 160 * (1.0f - CWeather::Foggyness); + if(fluffyalpha != 0){ + static float CoorsOffsetX[37] = { + 0.0f, 60.0f, 72.0f, 48.0f, 21.0f, 12.0f, + 9.0f, -3.0f, -8.4f, -18.0f, -15.0f, -36.0f, + -40.0f, -48.0f, -60.0f, -24.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, -30.0f, -20.0f, 10.0f, 30.0f, + 0.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f + }; + static float CoorsOffsetY[37] = { + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, -30.0f, 10.0f, + -25.0f, -5.0f, 28.0f, -10.0f, 10.0f, 0.0f, + 15.0f, 40.0f, -100.0f, -100.0f, -100.0f, -100.0f, + -100.0f, -40.0f, -20.0f, 0.0f, 10.0f, 30.0f, 35.0f + }; + static float CoorsOffsetZ[37] = { + 2.0f, 1.0f, 0.0f, 0.3f, 0.7f, 1.4f, + 1.7f, 0.24f, 0.7f, 1.3f, 1.6f, 1.0f, + 1.2f, 0.3f, 0.7f, 1.4f, 0.0f, 0.1f, + 0.5f, 0.4f, 0.55f, 0.75f, 1.0f, 1.4f, + 1.7f, 2.0f, 2.0f, 2.3f, 1.9f, 2.4f, + 2.0f, 2.0f, 1.5f, 1.2f, 1.7f, 1.5f, 2.1f + }; + static bool bCloudOnScreen[37]; + float hilight; + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCloudTex[4]->raster); + for(i = 0; i < 37; i++){ + RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f }; + worldpos.x = pos.x*rot_cos + pos.y*rot_sin + campos.x; + worldpos.y = pos.x*rot_sin - pos.y*rot_cos + campos.y; + worldpos.z = pos.z; + + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + float sundist = sqrt(sq(screenpos.x-CCoronas::SunScreenX) + sq(screenpos.y-CCoronas::SunScreenY)); + int tr = CTimeCycle::GetFluffyCloudsTopRed(); + int tg = CTimeCycle::GetFluffyCloudsTopGreen(); + int tb = CTimeCycle::GetFluffyCloudsTopBlue(); + int br = CTimeCycle::GetFluffyCloudsBottomRed(); + int bg = CTimeCycle::GetFluffyCloudsBottomGreen(); + int bb = CTimeCycle::GetFluffyCloudsBottomBlue(); + if(sundist < SCREENW/2){ + hilight = (1.0f - coverage) * (1.0f - sundist/(SCREENW/2)); + tr = tr*(1.0f-hilight) + 255*hilight; + tg = tg*(1.0f-hilight) + 190*hilight; + tb = tb*(1.0f-hilight) + 190*hilight; + br = br*(1.0f-hilight) + 255*hilight; + bg = bg*(1.0f-hilight) + 190*hilight; + bb = bb*(1.0f-hilight) + 190*hilight; + if(sundist < SCREENW/10) + CCoronas::SunBlockedByClouds = true; + }else + hilight = 0.0f; + CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(screenpos.x, screenpos.y, screenpos.z, + szx*55.0f, szy*55.0f, + tr, tg, tb, br, bg, bb, 0.0f, -1.0f, + 1.0f/screenpos.z, + IndividualRotation/65336.0f * 2*3.14f + ms_cameraRoll, + fluffyalpha); + bCloudOnScreen[i] = true; + }else + bCloudOnScreen[i] = false; + } + CSprite::FlushSpriteBuffer(); + + // Highlights + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCloudTex[3]->raster); + + for(i = 0; i < 37; i++){ + RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f }; + worldpos.x = campos.x*rot_cos + campos.y*rot_sin + pos.x; + worldpos.y = campos.x*rot_sin + campos.y*rot_cos + pos.y; + worldpos.z = pos.z; + if(bCloudOnScreen[i] && CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + // BUG: this is stupid....would have to do this for each cloud individually + if(hilight > 0.0f){ + CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(screenpos.x, screenpos.y, screenpos.z, + szx*30.0f, szy*30.0f, + 200*hilight, 0, 0, 255, 1.0f/screenpos.z, + 1.7f - CGeneral::GetATanOfXY(screenpos.x-CCoronas::SunScreenX, screenpos.y-CCoronas::SunScreenY) + CClouds::ms_cameraRoll, 255); + } + } + } + CSprite::FlushSpriteBuffer(); + } + + // Rainbow + if(CWeather::Rainbow != 0.0f){ + static uint8 BowRed[6] = { 30, 30, 30, 10, 0, 15 }; + static uint8 BowGreen[6] = { 0, 15, 30, 30, 0, 0 }; + static uint8 BowBlue[6] = { 0, 0, 0, 10, 30, 30 }; + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[0]->raster); + for(i = 0; i < 6; i++){ + RwV3d pos = { i*1.5f, 100.0f, 5.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)) + CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + 2.0f*szx, 50.0*szy, + BowRed[i]*CWeather::Rainbow, BowGreen[i]*CWeather::Rainbow, BowBlue[i]*CWeather::Rainbow, + 255, 1.0f/screenpos.z, 255); + + } + CSprite::FlushSpriteBuffer(); + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} + +bool +UseDarkBackground(void) +{ + return RwFrameGetLTM(RwCameraGetFrame(TheCamera.m_pRwCamera))->up.z < -0.9f || + gbShowCollisionPolys; +} + +void +CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue, + int16 botred, int16 botgreen, int16 botblue, int16 alpha) +{ + RwMatrix *mat = RwFrameGetLTM(RwCameraGetFrame(TheCamera.m_pRwCamera)); + float c = sqrt(mat->right.x * mat->right.x + mat->right.y * mat->right.y); + if(c > 1.0f) + c = 1.0f; + ms_cameraRoll = acos(c); + if(mat->right.z < 0.0f) + ms_cameraRoll = -ms_cameraRoll; + + if(UseDarkBackground()){ + ms_colourTop.r = 50; + ms_colourTop.g = 50; + ms_colourTop.b = 50; + ms_colourTop.a = 255; + if(gbShowCollisionPolys){ + if(CTimer::GetFrameCounter() & 1){ + ms_colourTop.r = 0; + ms_colourTop.g = 0; + ms_colourTop.b = 0; + }else{ + ms_colourTop.r = 255; + ms_colourTop.g = 255; + ms_colourTop.b = 255; + } + } + ms_colourBottom = ms_colourTop; + CRect r(0, 0, SCREENW, SCREENH); + CSprite2d::DrawRect(r, ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + }else{ + ms_horizonZ = CSprite::CalcHorizonCoors(); + + // Draw top/bottom gradient + float gradheight = SCREENH/2.0f; + float topedge = ms_horizonZ - gradheight; + float botpos, toppos; + if(ms_horizonZ > 0.0f && topedge < SCREENH){ + ms_colourTop.r = topred; + ms_colourTop.g = topgreen; + ms_colourTop.b = topblue; + ms_colourTop.a = alpha; + ms_colourBottom.r = botred; + ms_colourBottom.g = botgreen; + ms_colourBottom.b = botblue; + ms_colourBottom.a = alpha; + + if(ms_horizonZ < SCREENH) + botpos = ms_horizonZ; + else{ + float f = (ms_horizonZ - SCREENH)/gradheight; + ms_colourBottom.r = topred*f + (1.0f-f)*botred; + ms_colourBottom.g = topgreen*f + (1.0f-f)*botgreen; + ms_colourBottom.b = topblue*f + (1.0f-f)*botblue; + botpos = SCREENH; + } + if(topedge >= 0.0f) + toppos = topedge; + else{ + float f = (0.0f - topedge)/gradheight; + ms_colourTop.r = botred*f + (1.0f-f)*topred; + ms_colourTop.g = botgreen*f + (1.0f-f)*topgreen; + ms_colourTop.b = botblue*f + (1.0f-f)*topblue; + toppos = 0.0f; + } + CSprite2d::DrawRect(CRect(0, toppos, SCREENW, botpos), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + } + + // draw the small stripe (whatever it's supposed to be) + if(ms_horizonZ > -SMALLSTRIPHEIGHT && ms_horizonZ < SCREENH){ + // Same colour as fog + ms_colourTop.r = (topred + 2 * botred) / 3; + ms_colourTop.g = (topgreen + 2 * botgreen) / 3; + ms_colourTop.b = (topblue + 2 * botblue) / 3; + CSprite2d::DrawRect(CRect(0, ms_horizonZ, SCREENW, ms_horizonZ+SMALLSTRIPHEIGHT), + ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop); + } + + // Only top + if(topedge > 0.0f){ + ms_colourTop.r = topred; + ms_colourTop.g = topgreen; + ms_colourTop.b = topblue; + ms_colourTop.a = alpha; + ms_colourBottom.r = topred; + ms_colourBottom.g = topgreen; + ms_colourBottom.b = topblue; + ms_colourBottom.a = alpha; + + botpos = min(SCREENH, topedge); + CSprite2d::DrawRect(CRect(0, 0, SCREENW, botpos), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + } + + // Set both to fog colour for RenderHorizon + ms_colourTop.r = (topred + 2 * botred) / 3; + ms_colourTop.g = (topgreen + 2 * botgreen) / 3; + ms_colourTop.b = (topblue + 2 * botblue) / 3; + ms_colourBottom.r = (topred + 2 * botred) / 3; + ms_colourBottom.g = (topgreen + 2 * botgreen) / 3; + ms_colourBottom.b = (topblue + 2 * botblue) / 3; + } +} + +void +CClouds::RenderHorizon(void) +{ + if(UseDarkBackground()) + return; + + ms_colourBottom.a = 230; + ms_colourTop.a = 80; + + if(ms_horizonZ > SCREENH) + return; + + float z1 = min(ms_horizonZ + SMALLSTRIPHEIGHT, SCREENH); + CSprite2d::DrawRectXLU(CRect(0, ms_horizonZ, SCREENW, z1), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + + // This is just weird + float a = SCREENH/400.0f * HORIZSTRIPHEIGHT + + SCREENH/300.0f * max(TheCamera.GetPosition().z, 0.0f); + float b = TheCamera.GetUp().z < 0.0f ? + SCREENH : + SCREENH * fabs(TheCamera.GetRight().z); + float z2 = z1 + (a + b)*TheCamera.LODDistMultiplier; + z2 = min(z2, SCREENH); + CSprite2d::DrawRect(CRect(0, z1, SCREENW, z2), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); +} + +STARTPATCHES + InjectHook(0x4F6C10, CClouds::Init, PATCH_JUMP); + InjectHook(0x4F6CE0, CClouds::Update, PATCH_JUMP); + InjectHook(0x4F6D90, CClouds::Render, PATCH_JUMP); + InjectHook(0x4F7F00, CClouds::RenderBackground, PATCH_JUMP); + InjectHook(0x4F85F0, CClouds::RenderHorizon, PATCH_JUMP); +ENDPATCHES diff --git a/src/render/Clouds.h b/src/render/Clouds.h new file mode 100644 index 00000000..96f04bb1 --- /dev/null +++ b/src/render/Clouds.h @@ -0,0 +1,20 @@ +#pragma once + +class CClouds +{ +public: + static float &CloudRotation; + static uint32 &IndividualRotation; + + static float &ms_cameraRoll; + static float &ms_horizonZ; + static CRGBA &ms_colourTop; + static CRGBA &ms_colourBottom; + + static void Init(void); + static void Update(void); + static void Render(void); + static void RenderBackground(int16 topred, int16 topgreen, int16 topblue, + int16 botred, int16 botgreen, int16 botblue, int16 alpha); + static void RenderHorizon(void); +}; diff --git a/src/render/Coronas.cpp b/src/render/Coronas.cpp new file mode 100644 index 00000000..39b85246 --- /dev/null +++ b/src/render/Coronas.cpp @@ -0,0 +1,10 @@ +#include "common.h" +#include "Coronas.h" + +RwTexture **gpCoronaTexture = (RwTexture**)0x5FAF44; //[9] + +float &CCoronas::LightsMult = *(float*)0x5FB088; // 1.0 +float &CCoronas::SunScreenX = *(float*)0x8F4358; +float &CCoronas::SunScreenY = *(float*)0x8F4354; +bool &CCoronas::bSmallMoon = *(bool*)0x95CD49; +bool &CCoronas::SunBlockedByClouds = *(bool*)0x95CD73; diff --git a/src/render/Coronas.h b/src/render/Coronas.h new file mode 100644 index 00000000..4ec7dd3b --- /dev/null +++ b/src/render/Coronas.h @@ -0,0 +1,13 @@ +#pragma once + +extern RwTexture **gpCoronaTexture; //[9] + +class CCoronas +{ +public: + static float &LightsMult; + static float &SunScreenY; + static float &SunScreenX; + static bool &bSmallMoon; + static bool &SunBlockedByClouds; +}; diff --git a/src/render/Draw.cpp b/src/render/Draw.cpp new file mode 100644 index 00000000..f26f2ada --- /dev/null +++ b/src/render/Draw.cpp @@ -0,0 +1,6 @@ +#include "common.h" +#include "Draw.h" + +float &CDraw::ms_fNearClipZ = *(float*)0x8E2DC4; +float &CDraw::ms_fFarClipZ = *(float*)0x9434F0; +float &CDraw::ms_fFOV = *(float*)0x5FBC6C; diff --git a/src/render/Draw.h b/src/render/Draw.h new file mode 100644 index 00000000..62fe5193 --- /dev/null +++ b/src/render/Draw.h @@ -0,0 +1,16 @@ +#pragma once + +class CDraw +{ +private: + static float &ms_fNearClipZ; + static float &ms_fFarClipZ; + static float &ms_fFOV; +public: + static void SetNearClipZ(float nearclip) { ms_fNearClipZ = nearclip; } + static float GetNearClipZ(void) { return ms_fNearClipZ; } + static void SetFarClipZ(float farclip) { ms_fFarClipZ = farclip; } + static float GetFarClipZ(void) { return ms_fFarClipZ; } + static void SetFOV(float fov) { ms_fFOV = fov; } + static float GetFOV(void) { return ms_fFOV; } +}; diff --git a/src/render/Lights.cpp b/src/render/Lights.cpp new file mode 100644 index 00000000..6962af4d --- /dev/null +++ b/src/render/Lights.cpp @@ -0,0 +1,171 @@ +#include "common.h" +#include +#include +#include "patcher.h" +#include "Lights.h" +#include "Timecycle.h" +#include "Coronas.h" +#include "Weather.h" +#include "CullZones.h" +#include "MenuManager.h" + +RpLight *&pAmbient = *(RpLight**)0x885B6C; +RpLight *&pDirect = *(RpLight**)0x880F7C; + +RwRGBAReal &AmbientLightColourForFrame = *(RwRGBAReal*)0x6F46F8; +RwRGBAReal &AmbientLightColourForFrame_PedsCarsAndObjects = *(RwRGBAReal*)0x6F1D10; +RwRGBAReal &DirectionalLightColourForFrame = *(RwRGBAReal*)0x87C6B8; + +RwRGBAReal &AmbientLightColour = *(RwRGBAReal*)0x86B0F8; +RwRGBAReal &DirectionalLightColour = *(RwRGBAReal*)0x72E308; + +void +SetLightsWithTimeOfDayColour(RpWorld *) +{ + CVector vec1, vec2, vecsun; + RwMatrix mat; + + if(pAmbient){ + AmbientLightColourForFrame.red = CTimeCycle::GetAmbientRed() * CCoronas::LightsMult; + AmbientLightColourForFrame.green = CTimeCycle::GetAmbientGreen() * CCoronas::LightsMult; + AmbientLightColourForFrame.blue = CTimeCycle::GetAmbientBlue() * CCoronas::LightsMult; + if(CWeather::LightningFlash && !CCullZones::CamNoRain()){ + AmbientLightColourForFrame.red = 1.0f; + AmbientLightColourForFrame.green = 1.0f; + AmbientLightColourForFrame.blue = 1.0f; + } + AmbientLightColourForFrame_PedsCarsAndObjects.red = min(1.0f, AmbientLightColourForFrame.red*1.3f); + AmbientLightColourForFrame_PedsCarsAndObjects.green = min(1.0f, AmbientLightColourForFrame.green*1.3f); + AmbientLightColourForFrame_PedsCarsAndObjects.blue = min(1.0f, AmbientLightColourForFrame.blue*1.3f); + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + } + + if(pDirect){ + DirectionalLightColourForFrame.red = CTimeCycle::GetDirectionalRed() * CCoronas::LightsMult; + DirectionalLightColourForFrame.green = CTimeCycle::GetDirectionalGreen() * CCoronas::LightsMult; + DirectionalLightColourForFrame.blue = CTimeCycle::GetDirectionalBlue() * CCoronas::LightsMult; + RpLightSetColor(pDirect, &DirectionalLightColourForFrame); + + vecsun = CTimeCycle::m_VectorToSun[CTimeCycle::m_CurrentStoredValue]; + vec1 = CVector(0.0f, 0.0f, 1.0f); + vec2 = CrossProduct(vec1, vecsun); + vec2.Normalise(); + vec1 = CrossProduct(vec2, vecsun); + mat.at.x = -vecsun.x; + mat.at.y = -vecsun.y; + mat.at.z = -vecsun.z; + mat.right.x = vec1.x; + mat.right.y = vec1.y; + mat.right.z = vec1.z; + mat.up.x = vec2.x; + mat.up.y = vec2.y; + mat.up.z = vec2.z; + RwFrameTransform(RpLightGetFrame(pDirect), &mat, rwCOMBINEREPLACE); + } + + if(CMenuManager::m_PrefsBrightness > 256){ + float f1 = 2.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f; + float f2 = 3.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f; + + AmbientLightColourForFrame.red = min(1.0f, AmbientLightColourForFrame.red * f2); + AmbientLightColourForFrame.green = min(1.0f, AmbientLightColourForFrame.green * f2); + AmbientLightColourForFrame.blue = min(1.0f, AmbientLightColourForFrame.blue * f2); + AmbientLightColourForFrame_PedsCarsAndObjects.red = min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.red * f1); + AmbientLightColourForFrame_PedsCarsAndObjects.green = min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.green * f1); + AmbientLightColourForFrame_PedsCarsAndObjects.blue = min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.blue * f1); +#ifdef FIX_BUGS + DirectionalLightColourForFrame.red = min(1.0f, DirectionalLightColourForFrame.red * f1); + DirectionalLightColourForFrame.green = min(1.0f, DirectionalLightColourForFrame.green * f1); + DirectionalLightColourForFrame.blue = min(1.0f, DirectionalLightColourForFrame.blue * f1); +#else + DirectionalLightColourForFrame.red = min(1.0f, AmbientLightColourForFrame.red * f1); + DirectionalLightColourForFrame.green = min(1.0f, AmbientLightColourForFrame.green * f1); + DirectionalLightColourForFrame.blue = min(1.0f, AmbientLightColourForFrame.blue * f1); +#endif + } +} + +void +SetAmbientAndDirectionalColours(float f) +{ + AmbientLightColour.red = AmbientLightColourForFrame.red * f; + AmbientLightColour.green = AmbientLightColourForFrame.green * f; + AmbientLightColour.blue = AmbientLightColourForFrame.blue * f; + + DirectionalLightColour.red = DirectionalLightColourForFrame.red * f; + DirectionalLightColour.green = DirectionalLightColourForFrame.green * f; + DirectionalLightColour.blue = DirectionalLightColourForFrame.blue * f; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); +} + +void +SetBrightMarkerColours(float f) +{ + AmbientLightColour.red = 0.6f; + AmbientLightColour.green = 0.6f; + AmbientLightColour.blue = 0.6f; + + DirectionalLightColour.red = (1.0f - DirectionalLightColourForFrame.red) * 0.4f + DirectionalLightColourForFrame.red; + DirectionalLightColour.green = (1.0f - DirectionalLightColourForFrame.green) * 0.4f + DirectionalLightColourForFrame.green; + DirectionalLightColour.blue = (1.0f - DirectionalLightColourForFrame.blue) * 0.4f + DirectionalLightColourForFrame.blue; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); +} + +void +ReSetAmbientAndDirectionalColours(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + RpLightSetColor(pDirect, &DirectionalLightColourForFrame); +} + +void +DeActivateDirectional(void) +{ + RpLightSetFlags(pDirect, 0); +} + +void +ActivateDirectional(void) +{ + RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS); +} + +void +SetAmbientColours(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); +} + +void +SetAmbientColoursForPedsCarsAndObjects(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame_PedsCarsAndObjects); +} + +uint8 IndicateR[] = { 0, 255, 0, 0, 255, 255, 0 }; +uint8 IndicateG[] = { 0, 0, 255, 0, 255, 0, 255 }; +uint8 IndicateB[] = { 0, 0, 0, 255, 0, 255, 255 }; + +void +SetAmbientColoursToIndicateRoadGroup(int i) +{ + AmbientLightColour.red = IndicateR[i%7]/255.0f; + AmbientLightColour.green = IndicateG[i%7]/255.0f; + AmbientLightColour.blue = IndicateB[i%7]/255.0f; + RpLightSetColor(pAmbient, &AmbientLightColour); +} + +STARTPATCHES + InjectHook(0x526510, SetLightsWithTimeOfDayColour, PATCH_JUMP); + InjectHook(0x526DE0, SetAmbientAndDirectionalColours, PATCH_JUMP); + InjectHook(0x526E60, SetBrightMarkerColours, PATCH_JUMP); + InjectHook(0x526F10, ReSetAmbientAndDirectionalColours, PATCH_JUMP); + InjectHook(0x526F40, DeActivateDirectional, PATCH_JUMP); + InjectHook(0x526F50, ActivateDirectional, PATCH_JUMP); + InjectHook(0x526F60, SetAmbientColours, PATCH_JUMP); + InjectHook(0x526F80, SetAmbientColoursForPedsCarsAndObjects, PATCH_JUMP); +ENDPATCHES diff --git a/src/render/Lights.h b/src/render/Lights.h new file mode 100644 index 00000000..ca926eb8 --- /dev/null +++ b/src/render/Lights.h @@ -0,0 +1,9 @@ +void SetLightsWithTimeOfDayColour(RpWorld *); +void SetAmbientAndDirectionalColours(float f); +void SetBrightMarkerColours(float f); +void ReSetAmbientAndDirectionalColours(void); +void DeActivateDirectional(void); +void ActivateDirectional(void); +void SetAmbientColours(void); +void SetAmbientColoursForPedsCarsAndObjects(void); +void SetAmbientColoursToIndicateRoadGroup(int i); \ No newline at end of file diff --git a/src/render/Particle.cpp b/src/render/Particle.cpp new file mode 100644 index 00000000..9bbc4587 --- /dev/null +++ b/src/render/Particle.cpp @@ -0,0 +1,10 @@ +#include "common.h" +#include "patcher.h" +#include "Particle.h" + +WRAPPER void +CParticle::AddParticle(tParticleType, const CVector &pos, const CVector &velocity, CEntity *ent, + float size, int32 rotationStep, int32 rotation, int startFrame, int lifeSpan) +{ + EAXJMP(0x50D140); +} diff --git a/src/render/Particle.h b/src/render/Particle.h new file mode 100644 index 00000000..f711ecf1 --- /dev/null +++ b/src/render/Particle.h @@ -0,0 +1,82 @@ +#pragma once + +enum tParticleType +{ + PARTICLE_SPARK, + PARTICLE_SPARK_SMALL, + PARTICLE_WHEEL_DIRT, + PARTICLE_WHEEL_WATER, + PARTICLE_BLOOD, + PARTICLE_BLOOD_SMALL, + PARTICLE_BLOOD_SPURT, + PARTICLE_DEBRIS, + PARTICLE_DEBRIS2, + PARTICLE_WATER, + PARTICLE_FLAME, + PARTICLE_FIREBALL, + PARTICLE_GUNFLASH, + PARTICLE_GUNFLASH_NOANIM, + PARTICLE_GUNSMOKE, + PARTICLE_GUNSMOKE2, + PARTICLE_SMOKE, + PARTICLE_SMOKE_SLOWMOTION, + PARTICLE_GARAGEPAINT_SPRAY, + PARTICLE_SHARD, + PARTICLE_SPLASH, + PARTICLE_CARFLAME, + PARTICLE_STEAM, + PARTICLE_STEAM2, + PARTICLE_STEAM_NY, + PARTICLE_STEAM_NY_SLOWMOTION, + PARTICLE_ENGINE_STEAM, + PARTICLE_RAINDROP, + PARTICLE_RAINDROP_SMALL, + PARTICLE_RAIN_SPLASH, + PARTICLE_RAIN_SPLASH_BIGGROW, + PARTICLE_RAIN_SPLASHUP, + PARTICLE_WATERSPRAY, + PARTICLE_EXPLOSION_MEDIUM, + PARTICLE_EXPLOSION_LARGE, + PARTICLE_EXPLOSION_MFAST, + PARTICLE_EXPLOSION_LFAST, + PARTICLE_CAR_SPLASH, + PARTICLE_BOAT_SPLASH, + PARTICLE_BOAT_THRUSTJET, + PARTICLE_BOAT_WAKE, + PARTICLE_WATER_HYDRANT, + PARTICLE_WATER_CANNON, + PARTICLE_EXTINGUISH_STEAM, + PARTICLE_PED_SPLASH, + PARTICLE_PEDFOOT_DUST, + PARTICLE_HELI_DUST, + PARTICLE_HELI_ATTACK, + PARTICLE_ENGINE_SMOKE, + PARTICLE_ENGINE_SMOKE2, + PARTICLE_CARFLAME_SMOKE, + PARTICLE_FIREBALL_SMOKE, + PARTICLE_PAINT_SMOKE, + PARTICLE_TREE_LEAVES, + PARTICLE_CARCOLLISION_DUST, + PARTICLE_CAR_DEBRIS, + PARTICLE_HELI_DEBRIS, + PARTICLE_EXHAUST_FUMES, + PARTICLE_RUBBER_SMOKE, + PARTICLE_BURNINGRUBBER_SMOKE, + PARTICLE_BULLETHIT_SMOKE, + PARTICLE_GUNSHELL_FIRST, + PARTICLE_GUNSHELL, + PARTICLE_GUNSHELL_BUMP1, + PARTICLE_GUNSHELL_BUMP2, + PARTICLE_TEST, + PARTICLE_BIRD_FRONT, + PARTICLE_RAINDROP_2D, +}; + +class CEntity; + +class CParticle +{ +public: + static void AddParticle(tParticleType, const CVector &pos, const CVector &velocity, CEntity *ent = nil, + float size = 0.0, int32 rotationStep = 0, int32 rotation = 0, int startFrame = 0, int lifeSpan = 0); +}; diff --git a/src/render/RenderBuffer.cpp b/src/render/RenderBuffer.cpp new file mode 100644 index 00000000..9a1ed58d --- /dev/null +++ b/src/render/RenderBuffer.cpp @@ -0,0 +1,59 @@ +#include "common.h" +#include "patcher.h" +#include "RenderBuffer.h" + +int32 &TempBufferVerticesStored = *(int32*)0x8F5F78; +int32 &TempBufferIndicesStored = *(int32*)0x8F1A4C; + +RwIm3DVertex *TempVertexBuffer = (RwIm3DVertex*)0x862330; +RwImVertexIndex *TempBufferRenderIndexList = (RwImVertexIndex*)0x846288; + +int RenderBuffer::VerticesToBeStored; +int RenderBuffer::IndicesToBeStored; + +void +RenderBuffer::ClearRenderBuffer(void) +{ + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; +} + +void +RenderBuffer::StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart) +{ + if(TempBufferIndicesStored + numIndices >= 1024) + RenderStuffInBuffer(); + if(TempBufferVerticesStored + numVertices >= 256) + RenderStuffInBuffer(); + *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored]; + *vertexStart = &TempVertexBuffer[TempBufferVerticesStored]; + IndicesToBeStored = numIndices; + VerticesToBeStored = numVertices; +} + +void +RenderBuffer::StopStoring(void) +{ + int i; + for(i = TempBufferIndicesStored; i < TempBufferIndicesStored+IndicesToBeStored; i++) + TempBufferRenderIndexList[i] += TempBufferVerticesStored; + TempBufferIndicesStored += IndicesToBeStored; + TempBufferVerticesStored += VerticesToBeStored; +} + +void +RenderBuffer::RenderStuffInBuffer(void) +{ + if(TempBufferVerticesStored && RwIm3DTransform(TempVertexBuffer, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + ClearRenderBuffer(); +} + +STARTPATCHES + InjectHook(0x517620, RenderBuffer::ClearRenderBuffer, PATCH_JUMP); + InjectHook(0x517640, RenderBuffer::StartStoring, PATCH_JUMP); + InjectHook(0x5176B0, RenderBuffer::StopStoring, PATCH_JUMP); + InjectHook(0x5177C0, RenderBuffer::RenderStuffInBuffer, PATCH_JUMP); +ENDPATCHES diff --git a/src/render/RenderBuffer.h b/src/render/RenderBuffer.h new file mode 100644 index 00000000..66baa2d0 --- /dev/null +++ b/src/render/RenderBuffer.h @@ -0,0 +1,10 @@ +class RenderBuffer +{ +public: + static int VerticesToBeStored; + static int IndicesToBeStored; + static void ClearRenderBuffer(void); + static void StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart); + static void StopStoring(void); + static void RenderStuffInBuffer(void); +}; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp new file mode 100644 index 00000000..fd949cee --- /dev/null +++ b/src/render/Renderer.cpp @@ -0,0 +1,1165 @@ +#include "common.h" +#include "patcher.h" +#include "Lights.h" +#include "ModelInfo.h" +#include "Treadable.h" +#include "Ped.h" +#include "Vehicle.h" +#include "Object.h" +#include "PathFind.h" +#include "Collision.h" +#include "VisibilityPlugins.h" +#include "Clock.h" +#include "World.h" +#include "Camera.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "Renderer.h" + +bool gbShowPedRoadGroups; +bool gbShowCarRoadGroups; +bool gbShowCollisionPolys; + +bool gbDontRenderBuildings; +bool gbDontRenderBigBuildings; +bool gbDontRenderPeds; +bool gbDontRenderObjects; + +struct EntityInfo +{ + CEntity *ent; + float sort; +}; + +CLinkList &gSortedVehiclesAndPeds = *(CLinkList*)0x629AC0; + +int32 &CRenderer::ms_nNoOfVisibleEntities = *(int32*)0x940730; +CEntity **CRenderer::ms_aVisibleEntityPtrs = (CEntity**)0x6E9920; +int32 &CRenderer::ms_nNoOfInVisibleEntities = *(int32*)0x8F1B78; +CEntity **CRenderer::ms_aInVisibleEntityPtrs = (CEntity**)0x880B50; + +CVector &CRenderer::ms_vecCameraPosition = *(CVector*)0x8E2C3C; +CVehicle *&CRenderer::m_pFirstPersonVehicle = *(CVehicle**)0x885B80; +bool &CRenderer::m_loadingPriority = *(bool*)0x95CD86; + +void +CRenderer::Init(void) +{ + gSortedVehiclesAndPeds.Init(40); + SortBIGBuildings(); +} +void +CRenderer::RenderOneRoad(CEntity *e) +{ + if(gbDontRenderBuildings) + return; + if(gbShowCollisionPolys) + CCollision::DrawColModel_Coloured(e->GetMatrix(), + *CModelInfo::GetModelInfo(e->m_modelIndex)->GetColModel(), + e->m_modelIndex); + else + e->Render(); +} + +void +CRenderer::RenderOneNonRoad(CEntity *e) +{ + CPed *ped; + CVehicle *veh; + int i; + bool resetLights; + +#ifndef MASTER + if(gbShowCollisionPolys){ + if(!e->IsVehicle()){ + CCollision::DrawColModel_Coloured(e->GetMatrix(), + *CModelInfo::GetModelInfo(e->m_modelIndex)->GetColModel(), + e->m_modelIndex); + return; + } + }else if(e->IsBuilding()){ + if(e->bIsBIGBuilding){ + if(gbDontRenderBigBuildings) + return; + }else{ + if(gbDontRenderBuildings) + return; + } + }else +#endif + if(e->IsPed()){ +#ifndef MASTER + if(gbDontRenderPeds) + return; +#endif + ped = (CPed*)e; + if(ped->m_nPedState == PED_PASSENGER) + return; + } +#ifndef MASTER + else if(e->IsObject() || e->IsDummy()){ + if(gbDontRenderObjects) + return; + } +#endif + + resetLights = e->SetupLighting(); + + if(e->IsVehicle()) + CVisibilityPlugins::InitAlphaAtomicList(); + + // Render Peds in vehicle before vehicle itself + if(e->IsVehicle()){ + veh = (CVehicle*)e; + if(veh->pDriver && veh->pDriver->m_nPedState == PED_PASSENGER) + veh->pDriver->Render(); + for(i = 0; i < 8; i++) + if(veh->pPassengers[i] && veh->pPassengers[i]->m_nPedState == PED_PASSENGER) + veh->pPassengers[i]->Render(); + } + e->Render(); + + if(e->IsVehicle()){ + e->bImBeingRendered = true; + CVisibilityPlugins::RenderAlphaAtomics(); + e->bImBeingRendered = false; + } + + e->RemoveLighting(resetLights); +} + +void +CRenderer::RenderFirstPersonVehicle(void) +{ + if(m_pFirstPersonVehicle == nil) + return; + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RenderOneNonRoad(m_pFirstPersonVehicle); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); +} + +void +CRenderer::RenderRoads(void) +{ + int i; + CTreadable *t; + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + DeActivateDirectional(); + SetAmbientColours(); + + ThePaths.m_pathNodes[-1].group = 6; + + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + t = (CTreadable*)ms_aVisibleEntityPtrs[i]; + if(t->IsBuilding() && t->GetIsATreadable()){ +#ifndef MASTER + if(gbShowCarRoadGroups || gbShowPedRoadGroups){ + int ind = 0; + if(gbShowCarRoadGroups) + ind += ThePaths.m_pathNodes[t->m_nodeIndicesCars[0]].group; + if(gbShowPedRoadGroups) + ind += ThePaths.m_pathNodes[t->m_nodeIndicesPeds[0]].group; + SetAmbientColoursToIndicateRoadGroup(ind); + } +#endif + RenderOneRoad(t); +#ifndef MASTER + if(gbShowCarRoadGroups || gbShowPedRoadGroups) + ReSetAmbientAndDirectionalColours(); +#endif + } + } +} + +void +CRenderer::RenderEverythingBarRoads(void) +{ + int i; + CEntity *e; + CVector dist; + EntityInfo ei; + + gSortedVehiclesAndPeds.Clear(); + + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + e = ms_aVisibleEntityPtrs[i]; + + if(e->IsBuilding() && ((CBuilding*)e)->GetIsATreadable()) + continue; + + if(e->IsVehicle() || + e->IsPed() && CVisibilityPlugins::GetClumpAlpha((RpClump*)e->m_rwObject) != 255){ + if(e->IsVehicle() && ((CVehicle*)e)->m_vehType == VEHICLE_TYPE_BOAT){ + dist = ms_vecCameraPosition - e->GetPosition(); + if(!CVisibilityPlugins::InsertEntityIntoSortedList(e, dist.Magnitude())){ + printf("Ran out of space in alpha entity list"); + RenderOneNonRoad(e); + } + }else{ + ei.ent = e; + dist = ms_vecCameraPosition - e->GetPosition(); + ei.sort = dist.MagnitudeSqr(); + gSortedVehiclesAndPeds.InsertSorted(ei); + } + }else + RenderOneNonRoad(e); + } +} + +void +CRenderer::RenderVehiclesButNotBoats(void) +{ + CLink *node; + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev){ + CVehicle *v = (CVehicle*)node->item.ent; + if(v->IsVehicle() && v->m_vehType == VEHICLE_TYPE_BOAT) // BUG: missing in III + continue; + RenderOneNonRoad(v); + } +} + +void +CRenderer::RenderBoats(void) +{ + CLink *node; + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev){ + CVehicle *v = (CVehicle*)node->item.ent; + if(!v->IsVehicle()) // BUG: missing in III + continue; + if(v->m_vehType == VEHICLE_TYPE_BOAT) + RenderOneNonRoad(v); + } +} + +void +CRenderer::RenderFadingInEntities(void) +{ + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + DeActivateDirectional(); + SetAmbientColours(); + CVisibilityPlugins::RenderFadingEntities(); +} + +enum Visbility +{ + VIS_INVISIBLE, + VIS_VISIBLE, + VIS_OFFSCREEN, + VIS_STREAMME +}; + +#define LOD_DISTANCE 300.0f +#define FADE_DISTANCE 20.0f +#define STREAM_DISTANCE 30.0f + +// Time Objects can be time culled if +// other == -1 || CModelInfo::GetModelInfo(other)->GetRwObject() +// i.e. we have to draw even at the wrong time if +// other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil + +#define OTHERUNAVAILABLE (other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil) +#define CANTIMECULL (!OTHERUNAVAILABLE) + +int32 +CRenderer::SetupEntityVisibility(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex); + CTimeModelInfo *ti; + int32 other; + float dist; + + bool request = true; + if(mi->m_type == MITYPE_TIME){ + ti = (CTimeModelInfo*)mi; + other = ti->GetOtherTimeModel(); + if(CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff())){ + // don't fade in, or between time objects + if(CANTIMECULL) + ti->m_alpha = 255; + }else{ + // Hide if possible + if(CANTIMECULL) + return VIS_INVISIBLE; + // can't cull, so we'll try to draw this one, but don't request + // it since what we really want is the other one. + request = false; + } + }else{ + if(mi->m_type != MITYPE_SIMPLE){ + if(FindPlayerVehicle() == ent && + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIRSTPERSON){ + // Player's vehicle in first person mode + if(TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_FORWARD || + ent->GetModelIndex() == MI_RHINO || + ent->GetModelIndex() == MI_COACH || + TheCamera.m_bInATunnelAndABigVehicle){ + ent->m_flagD40 = true; + }else{ + m_pFirstPersonVehicle = (CVehicle*)ent; + ent->m_flagD40 = false; + } + return VIS_OFFSCREEN; + }else{ + // All sorts of Clumps + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + if(!ent->GetIsOnScreen()) + return VIS_OFFSCREEN; + if(ent->bDrawLast){ + dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + }else + return VIS_VISIBLE; + } + return VIS_INVISIBLE; + } + if(ent->m_type == ENTITY_TYPE_OBJECT && + ((CObject*)ent)->ObjectCreatedBy == TEMP_OBJECT){ + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + return ent->GetIsOnScreen() ? VIS_VISIBLE : VIS_OFFSCREEN; + } + } + + // Simple ModelInfo + + dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + + // This can only happen with multi-atomic models (e.g. railtracks) + // but why do we bump up the distance? can only be fading... + if(LOD_DISTANCE + STREAM_DISTANCE < dist && dist < mi->GetLargestLodDistance()) + dist = mi->GetLargestLodDistance(); + + if(ent->m_type == ENTITY_TYPE_OBJECT && ent->bRenderDamaged) + mi->m_isDamaged = true; + + RpAtomic *a = mi->GetAtomicFromDistance(dist); + if(a){ + mi->m_isDamaged = 0; + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + // Make sure our atomic uses the right geometry and not + // that of an atomic for another draw distance. + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0); + mi->IncreaseAlpha(); + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + + if(!ent->GetIsOnScreen()){ + mi->m_alpha = 255; + return VIS_OFFSCREEN; + } + + if(mi->m_alpha != 255){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_INVISIBLE; + } + + if(mi->m_drawLast || ent->bDrawLast){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + } + return VIS_VISIBLE; + } + + // Object is not loaded, figure out what to do + + if(mi->m_noFade){ + mi->m_isDamaged = false; + // request model + if(dist - STREAM_DISTANCE < mi->GetLargestLodDistance() && request) + return VIS_STREAMME; + return VIS_INVISIBLE; + } + + // We might be fading + + a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE); + mi->m_isDamaged = false; + if(a == nil){ + // request model + if(dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance() && request) + return VIS_STREAMME; + return VIS_INVISIBLE; + } + + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0); + mi->IncreaseAlpha(); + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + + if(!ent->GetIsOnScreen()){ + mi->m_alpha = 255; + return VIS_OFFSCREEN; + }else{ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_OFFSCREEN; // Why this? + } +} + +int32 +CRenderer::SetupBigBuildingVisibility(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex); + CTimeModelInfo *ti; + int32 other; + + if(mi->m_type == MITYPE_TIME){ + ti = (CTimeModelInfo*)mi; + other = ti->GetOtherTimeModel(); + // Hide objects not in time range if possible + if(CANTIMECULL) + if(!CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff())) + return 0; + // Draw like normal + }else if(mi->m_type == MITYPE_VEHICLE) + return ent->IsVisible(); + + float dist = (ms_vecCameraPosition-ent->GetPosition()).Magnitude(); + CSimpleModelInfo *nonLOD = mi->GetRelatedModel(); + + // Find out whether to draw below near distance. + // This is only the case if there is a non-LOD which is either not + // loaded or not completely faded in yet. + if(dist < mi->GetNearDistance() && dist < LOD_DISTANCE + STREAM_DISTANCE){ + // No non-LOD or non-LOD is completely visible. + if(nonLOD == nil || + nonLOD->GetRwObject() && nonLOD->m_alpha == 255) + return 0; + + // But if it is a time object, we'd rather draw the wrong + // non-LOD than the right LOD. + if(nonLOD->m_type == MITYPE_TIME){ + ti = (CTimeModelInfo*)nonLOD; + other = ti->GetOtherTimeModel(); + if(other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject()) + return 0; + } + } + + RpAtomic *a = mi->GetAtomicFromDistance(dist); + if(a){ + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + + // Make sure our atomic uses the right geometry and not + // that of an atomic for another draw distance. + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0); + if(!ent->IsVisibleComplex()) + return 0; + if(mi->m_drawLast){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = 0; + return 0; + } + return 1; + } + + if(mi->m_noFade){ + ent->DeleteRwObject(); + return 0; + } + + + // get faded atomic + a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE); + if(a == nil){ + ent->DeleteRwObject(); + return 0; + } + + // Fade... + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0); + if(ent->IsVisibleComplex()) + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + return 0; +} + +void +CRenderer::ConstructRenderList(void) +{ + ms_nNoOfVisibleEntities = 0; + ms_nNoOfInVisibleEntities = 0; + ms_vecCameraPosition = TheCamera.GetPosition(); + // TODO: blocked ranges, but unused + ScanWorld(); +} + +void +LimitFrustumVector(CVector &vec1, const CVector &vec2, float l) +{ + float f; + f = (l - vec2.z) / (vec1.z - vec2.z); + vec1.x = f*(vec1.x - vec2.x) + vec2.x; + vec1.y = f*(vec1.y - vec2.y) + vec2.y; + vec1.z = f*(vec1.z - vec2.z) + vec2.z; +} + +enum Corners +{ + CORNER_CAM = 0, + CORNER_FAR_TOPLEFT, + CORNER_FAR_TOPRIGHT, + CORNER_FAR_BOTRIGHT, + CORNER_FAR_BOTLEFT, + CORNER_LOD_LEFT, + CORNER_LOD_RIGHT, + CORNER_PRIO_LEFT, + CORNER_PRIO_RIGHT, +}; + +void +CRenderer::ScanWorld(void) +{ + float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera); + RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera); + CVector vectors[9]; + RwMatrix *cammatrix; + RwV2d poly[3]; + + memset(vectors, 0, sizeof(vectors)); + vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f; + vectors[CORNER_FAR_TOPLEFT].y = vw.y * f; + vectors[CORNER_FAR_TOPLEFT].z = f; + vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f; + vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f; + vectors[CORNER_FAR_TOPRIGHT].z = f; + vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f; + vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f; + vectors[CORNER_FAR_BOTRIGHT].z = f; + vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f; + vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f; + vectors[CORNER_FAR_BOTLEFT].z = f; + + cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + + m_pFirstPersonVehicle = nil; + CVisibilityPlugins::InitAlphaEntityList(); + CWorld::AdvanceCurrentScanCode(); + + if(cammatrix->at.z > 0.0f){ + // looking up, bottom corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f; + }else{ + // looking down, top corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f; + } + vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f; + vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f; + vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z; + vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f; + vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f; + vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z; + RwV3dTransformPoints((RwV3d*)vectors, (RwV3d*)vectors, 9, cammatrix); + + m_loadingPriority = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 || + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED){ + CRect rect; + int x1, x2, y1, y2; + LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]); + LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]); + x1 = CWorld::GetSectorIndexX(rect.left); + if(x1 < 0) x1 = 0; + x2 = CWorld::GetSectorIndexX(rect.right); + if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1; + y1 = CWorld::GetSectorIndexY(rect.bottom); + if(y1 < 0) y1 = 0; + y2 = CWorld::GetSectorIndexY(rect.top); + if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1; + for(; x1 <= x2; x1++) + for(int y = y1; y <= y2; y++) + ScanSectorList(CWorld::GetSector(x1, y)->m_lists); + }else{ + CVehicle *train = FindPlayerTrain(); + if(train && train->GetPosition().z < 0.0f){ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_Subway); + }else{ + if(f <= LOD_DISTANCE){ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPLEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPLEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPRIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPRIGHT].y); + }else{ + // priority + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_PRIO_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_PRIO_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_PRIO_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_PRIO_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_Priority); + + // below LOD + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + } + ScanSectorPoly(poly, 3, ScanSectorList); + + ScanBigBuildingList(CWorld::GetBigBuildingList(CCollision::ms_collisionInMemory)); + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_NONE)); + } + } +} + +void +CRenderer::RequestObjectsInFrustum(void) +{ + float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera); + RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera); + CVector vectors[9]; + RwMatrix *cammatrix; + RwV2d poly[3]; + + memset(vectors, 0, sizeof(vectors)); + vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f; + vectors[CORNER_FAR_TOPLEFT].y = vw.y * f; + vectors[CORNER_FAR_TOPLEFT].z = f; + vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f; + vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f; + vectors[CORNER_FAR_TOPRIGHT].z = f; + vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f; + vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f; + vectors[CORNER_FAR_BOTRIGHT].z = f; + vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f; + vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f; + vectors[CORNER_FAR_BOTLEFT].z = f; + + cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + + CWorld::AdvanceCurrentScanCode(); + + if(cammatrix->at.z > 0.0f){ + // looking up, bottom corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f; + }else{ + // looking down, top corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f; + } + vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f; + vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f; + vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z; + vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f; + vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f; + vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z; + RwV3dTransformPoints((RwV3d*)vectors, (RwV3d*)vectors, 9, cammatrix); + + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 || + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED){ + CRect rect; + int x1, x2, y1, y2; + LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]); + LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]); + x1 = CWorld::GetSectorIndexX(rect.left); + if(x1 < 0) x1 = 0; + x2 = CWorld::GetSectorIndexX(rect.right); + if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1; + y1 = CWorld::GetSectorIndexY(rect.bottom); + if(y1 < 0) y1 = 0; + y2 = CWorld::GetSectorIndexY(rect.top); + if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1; + for(; x1 <= x2; x1++) + for(int y = y1; y <= y2; y++) + ScanSectorList_RequestModels(CWorld::GetSector(x1, y)->m_lists); + }else{ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_RequestModels); + } +} + +float +CalcNewDelta(RwV2d *a, RwV2d *b) +{ + return (b->x - a->x) / (b->y - a->y); +} + +void +CRenderer::ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *)) +{ + float miny, maxy; + int y, yend; + int x, xstart, xend; + int i; + int a1, a2, b1, b2; + float deltaA, deltaB; + float xA, xB; + + miny = poly[0].y; + maxy = poly[0].y; + a2 = 0; + xstart = 9999; + xend = -9999; + + for(i = 1; i < numVertices; i++){ + if(poly[i].y > maxy) + maxy = poly[i].y; + if(poly[i].y < miny){ + miny = poly[i].y; + a2 = i; + } + } + y = miny; + yend = maxy; + + // Go left in poly to find first edge b + b2 = a2; + for(i = 0; i < numVertices; i++){ + b1 = b2--; + if(b2 < 0) b2 = numVertices-1; + if(poly[b1].x < xstart) + xstart = poly[b1].x; + if((int)poly[b1].y != (int)poly[b2].y) + break; + } + // Go right to find first edge a + for(i = 0; i < numVertices; i++){ + a1 = a2++; + if(a2 == numVertices) a2 = 0; + if(poly[a1].x > xend) + xend = poly[a1].x; + if((int)poly[a1].y != (int)poly[a2].y) + break; + } + + // prestep x1 and x2 to next integer y + deltaA = CalcNewDelta(&poly[a1], &poly[a2]); + xA = deltaA * (ceilf(poly[a1].y) - poly[a1].y) + poly[a1].x; + deltaB = CalcNewDelta(&poly[b1], &poly[b2]); + xB = deltaB * (ceilf(poly[b1].y) - poly[b1].y) + poly[b1].x; + + if(y != yend){ + if(deltaB < 0.0f && (int)xB < xstart) + xstart = xB; + if(deltaA >= 0.0f && (int)xA > xend) + xend = xA; + } + + while(y <= yend && y < NUMSECTORS_Y){ + // scan one x-line + if(y >= 0 && xstart < NUMSECTORS_X) + for(x = xstart; x <= xend; x++) + if(x >= 0 && x != NUMSECTORS_X) + scanfunc(CWorld::GetSector(x, y)->m_lists); + + // advance one scan line + y++; + xA += deltaA; + xB += deltaB; + + // update left side + if(y == (int)poly[b2].y){ + // reached end of edge + if(y == yend){ + if(deltaB < 0.0f){ + do{ + xstart = poly[b2--].x; + if(b2 < 0) b2 = numVertices-1; + }while(xstart > (int)poly[b2].x); + }else + xstart = xB - deltaB; + }else{ + // switch edges + if(deltaB < 0.0f) + xstart = poly[b2].x; + else + xstart = xB - deltaB; + do{ + b1 = b2--; + if(b2 < 0) b2 = numVertices-1; + if((int)poly[b1].x < xstart) + xstart = poly[b1].x; + }while(y == (int)poly[b2].y); + deltaB = CalcNewDelta(&poly[b1], &poly[b2]); + xB = deltaB * (ceilf(poly[b1].y) - poly[b1].y) + poly[b1].x; + if(deltaB < 0.0f && (int)xB < xstart) + xstart = xB; + } + }else{ + if(deltaB < 0.0f) + xstart = xB; + else + xstart = xB - deltaB; + } + + // update right side + if(y == (int)poly[a2].y){ + // reached end of edge + if(y == yend){ + if(deltaA < 0.0f) + xend = xA - deltaA; + else{ + do{ + xend = poly[a2++].x; + if(a2 == numVertices) a2 = 0; + }while(xend < (int)poly[a2].x); + } + }else{ + // switch edges + if(deltaA < 0.0f) + xend = xA - deltaA; + else + xend = poly[a2].x; + do{ + a1 = a2++; + if(a2 == numVertices) a2 = 0; + if((int)poly[a1].x > xend) + xend = poly[a1].x; + }while(y == (int)poly[a2].y); + deltaA = CalcNewDelta(&poly[a1], &poly[a2]); + xA = deltaA * (ceilf(poly[a1].y) - poly[a1].y) + poly[a1].x; + if(deltaA >= 0.0f && (int)xA > xend) + xend = xA; + } + }else{ + if(deltaA < 0.0f) + xend = xA - deltaA; + else + xend = xA; + } + } +} + +void +CRenderer::ScanBigBuildingList(CPtrList &list) +{ + CPtrNode *node; + CEntity *ent; + + for(node = list.first; node; node = node->next){ + ent = (CEntity*)node->item; + if(!ent->m_bZoneCulled && SetupBigBuildingVisibility(ent) == 1) + ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent; + } +} + +void +CRenderer::ScanSectorList(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + + if(IsEntityCullZoneVisible(ent)) + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent; + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < 150) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + case VIS_STREAMME: + if(!CStreaming::ms_disableStreaming) + if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + break; + } + else if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){ + if(!CStreaming::ms_disableStreaming) + if(SetupEntityVisibility(ent) == VIS_STREAMME) + if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + } + } + } +} + +void +CRenderer::ScanSectorList_Priority(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + + if(IsEntityCullZoneVisible(ent)) + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent; + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < 150) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + case VIS_STREAMME: + if(!CStreaming::ms_disableStreaming){ + CStreaming::RequestModel(ent->GetModelIndex(), 0); + if(CStreaming::ms_aInfoForModel[ent->GetModelIndex()].m_loadState != STREAM_LOADED) + m_loadingPriority = true; + } + break; + } + else if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){ + if(!CStreaming::ms_disableStreaming) + if(SetupEntityVisibility(ent) == VIS_STREAMME) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + } + } + } +} + +void +CRenderer::ScanSectorList_Subway(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent; + break; + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < 150) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + } + } + } +} + +void +CRenderer::ScanSectorList_RequestModels(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + if(IsEntityCullZoneVisible(ent) && ShouldModelBeStreamed(ent)) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + } + } +} + +// Put big buildings in front +// This seems pointless because the sector lists shouldn't have big buildings in the first place +void +CRenderer::SortBIGBuildings(void) +{ + int x, y; + for(y = 0; y < NUMSECTORS_Y; y++) + for(x = 0; x < NUMSECTORS_X; x++){ + SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS]); + SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + } +} + +void +CRenderer::SortBIGBuildingsForSectorList(CPtrList *list) +{ + CPtrNode *node; + CEntity *ent; + + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->bIsBIGBuilding){ + list->RemoveNode(node); + list->InsertNode(node); + } + } +} + +bool +CRenderer::ShouldModelBeStreamed(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex); + float dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + if(mi->m_noFade) + return dist - STREAM_DISTANCE < mi->GetLargestLodDistance(); + else + return dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance(); +} + +bool +CRenderer::IsEntityCullZoneVisible(CEntity *ent) +{ + CPed *ped; + CObject *obj; + + if(ent->m_bZoneCulled) + return false; + + switch(ent->m_type){ + case ENTITY_TYPE_VEHICLE: + return IsVehicleCullZoneVisible(ent); + case ENTITY_TYPE_PED: + ped = (CPed*)ent; + if(ped->bInVehicle) + return ped->m_pMyVehicle && IsVehicleCullZoneVisible(ped->m_pMyVehicle); + return !(ped->m_pCurSurface && ped->m_pCurSurface->m_bZoneCulled2); + case ENTITY_TYPE_OBJECT: + obj = (CObject*)ent; + if(!obj->bIsStatic) + return true; + return !(obj->m_pCurSurface && obj->m_pCurSurface->m_bZoneCulled2); + } + return true; +} + +bool +CRenderer::IsVehicleCullZoneVisible(CEntity *ent) +{ + CVehicle *v = (CVehicle*)ent; + switch(v->m_status) + case STATUS_SIMPLE: + case STATUS_PHYSICS: + case STATUS_ABANDONED: + case STATUS_WRECKED: + return !(v->m_pCurSurface && v->m_pCurSurface->m_bZoneCulled2); + return true; +} + +STARTPATCHES + InjectHook(0x4A7680, CRenderer::Init, PATCH_JUMP); + + InjectHook(0x4A7B90, CRenderer::RenderOneRoad, PATCH_JUMP); + InjectHook(0x4A7BA0, CRenderer::RenderOneNonRoad, PATCH_JUMP); + InjectHook(0x4A7B20, CRenderer::RenderFirstPersonVehicle, PATCH_JUMP); + InjectHook(0x4A78B0, CRenderer::RenderRoads, PATCH_JUMP); + InjectHook(0x4A7930, CRenderer::RenderEverythingBarRoads, PATCH_JUMP); + InjectHook(0x4A7AA0, CRenderer::RenderVehiclesButNotBoats, PATCH_JUMP); + InjectHook(0x4A7AE0, CRenderer::RenderBoats, PATCH_JUMP); + InjectHook(0x4A7910, CRenderer::RenderFadingInEntities, PATCH_JUMP); + + InjectHook(0x4A9350, CRenderer::SetupEntityVisibility, PATCH_JUMP); + InjectHook(0x4A9920, CRenderer::SetupBigBuildingVisibility, PATCH_JUMP); + + InjectHook(0x4A76B0, CRenderer::ConstructRenderList, PATCH_JUMP); + InjectHook(0x4A8970, CRenderer::ScanWorld, PATCH_JUMP); + InjectHook(0x4AA240, CRenderer::RequestObjectsInFrustum, PATCH_JUMP); + InjectHook(0x4A7F30, CRenderer::ScanSectorPoly, PATCH_JUMP); + InjectHook(0x4A9300, CRenderer::ScanBigBuildingList, PATCH_JUMP); + InjectHook(0x4A9BB0, CRenderer::ScanSectorList, PATCH_JUMP); + InjectHook(0x4A9E30, CRenderer::ScanSectorList_Priority, PATCH_JUMP); + InjectHook(0x4AA0A0, CRenderer::ScanSectorList_Subway, PATCH_JUMP); + InjectHook(0x4AA1D0, CRenderer::ScanSectorList_RequestModels, PATCH_JUMP); + + InjectHook(0x4AA940, CRenderer::SortBIGBuildings, PATCH_JUMP); + InjectHook(0x4AA990, CRenderer::SortBIGBuildingsForSectorList, PATCH_JUMP); + + InjectHook(0x4A9840, CRenderer::ShouldModelBeStreamed, PATCH_JUMP); + InjectHook(0x4AAA00, CRenderer::IsEntityCullZoneVisible, PATCH_JUMP); + InjectHook(0x4AAAA0, CRenderer::IsVehicleCullZoneVisible, PATCH_JUMP); +ENDPATCHES diff --git a/src/render/Renderer.h b/src/render/Renderer.h new file mode 100644 index 00000000..970d6ba5 --- /dev/null +++ b/src/render/Renderer.h @@ -0,0 +1,59 @@ +#pragma once + +class CEntity; + +extern bool gbShowPedRoadGroups; +extern bool gbShowCarRoadGroups; +extern bool gbShowCollisionPolys; + +extern bool gbDontRenderBuildings; +extern bool gbDontRenderBigBuildings; +extern bool gbDontRenderPeds; +extern bool gbDontRenderObjects; + +class CVehicle; +class CPtrList; + +class CRenderer +{ + static int32 &ms_nNoOfVisibleEntities; + static CEntity **ms_aVisibleEntityPtrs; // [2000]; + static int32 &ms_nNoOfInVisibleEntities; + static CEntity **ms_aInVisibleEntityPtrs; // [150]; + + static CVector &ms_vecCameraPosition; + static CVehicle *&m_pFirstPersonVehicle; + static bool &m_loadingPriority; +public: + static void Init(void); + // TODO: PreRender, needs CHeli and CShadows + + static void RenderRoads(void); + static void RenderFadingInEntities(void); + static void RenderEverythingBarRoads(void); + static void RenderVehiclesButNotBoats(void); + static void RenderBoats(void); + static void RenderOneRoad(CEntity *); + static void RenderOneNonRoad(CEntity *); + static void RenderFirstPersonVehicle(void); + + static int32 SetupEntityVisibility(CEntity *ent); + static int32 SetupBigBuildingVisibility(CEntity *ent); + + static void ConstructRenderList(void); + static void ScanWorld(void); + static void RequestObjectsInFrustum(void); + static void ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *)); + static void ScanBigBuildingList(CPtrList &list); + static void ScanSectorList(CPtrList *lists); + static void ScanSectorList_Priority(CPtrList *lists); + static void ScanSectorList_Subway(CPtrList *lists); + static void ScanSectorList_RequestModels(CPtrList *lists); + + static void SortBIGBuildings(void); + static void SortBIGBuildingsForSectorList(CPtrList *list); + + static bool ShouldModelBeStreamed(CEntity *ent); + static bool IsEntityCullZoneVisible(CEntity *ent); + static bool IsVehicleCullZoneVisible(CEntity *ent); +}; diff --git a/src/render/Sprite.cpp b/src/render/Sprite.cpp new file mode 100644 index 00000000..74eefccf --- /dev/null +++ b/src/render/Sprite.cpp @@ -0,0 +1,553 @@ +#include "common.h" +#include "patcher.h" +#include "Draw.h" +#include "Camera.h" +#include "Sprite.h" + +// Get rid of bullshit windows definitions, we're not running on an 8086 +#ifdef far +#undef far +#undef near +#endif + +RwIm2DVertex *CSprite2d::maVertices = (RwIm2DVertex*)0x6E9168; +float &CSprite2d::RecipNearClip = *(float*)0x880DB4; + +// Arguments: +// 2---3 +// | | +// 0---1 +void +CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far) +{ + float screenz, z, recipz; + + if(far){ + screenz = RwIm2DGetFarScreenZ(); + z = RwCameraGetFarClipPlane(Scene.camera); + }else{ + screenz = RwIm2DGetNearScreenZ(); + z = 1.0f/RecipNearClip; + } + recipz = 1.0f/z; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], 0.0f, recipz); + RwIm2DVertexSetV(&maVertices[0], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], 1.0f, recipz); + RwIm2DVertexSetV(&maVertices[1], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.top); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], 1.0f, recipz); + RwIm2DVertexSetV(&maVertices[2], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.top); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], 0.0f, recipz); + RwIm2DVertexSetV(&maVertices[3], 1.0f, recipz); +} + +void +CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + float screenz, z, recipz; + + screenz = RwIm2DGetNearScreenZ(); + z = 1.0f/RecipNearClip; + recipz = 1.0f/z; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], u0, recipz); + RwIm2DVertexSetV(&maVertices[0], v0, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], u1, recipz); + RwIm2DVertexSetV(&maVertices[1], v1, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.top); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], u2, recipz); + RwIm2DVertexSetV(&maVertices[2], v2, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.top); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], u3, recipz); + RwIm2DVertexSetV(&maVertices[3], v3, recipz); +} + +void +CSprite2d::SetRenderState(void) +{ + if(m_pTexture) + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(m_pTexture)); + else + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); +} + +void +CSprite2d::DrawRect(const CRect &r, const CRGBA &col) +{ + SetVertices(r, col, col, col, col, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(col.a != 255)); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); +} + +void +CSprite2d::DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(r, c0, c1, c2, c3, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); +} + +void +CSprite2d::DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(r, c0, c1, c2, c3, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); +} + + + +float &CSprite::m_f2DNearScreenZ = *(float*)0x8F1ABC; +float &CSprite::m_f2DFarScreenZ = *(float*)0x8F2C94; +int32 &CSprite::m_bFlushSpriteBufferSwitchZTest = *(int32*)0x8F5FB0; + +float +CSprite::CalcHorizonCoors(void) +{ + CVector p = TheCamera.GetPosition() + CVector(TheCamera.CamFrontXNorm, TheCamera.CamFrontYNorm, 0.0f)*3000.0f; + p.z = 0.0f; + p = TheCamera.m_viewMatrix * p; + return p.y * RsGlobal.maximumHeight / p.z; +} + +bool +CSprite::CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip) +{ + CVector viewvec = TheCamera.m_viewMatrix * *(CVector*)∈ + *out = *(RwV3d*)&viewvec; + if(out->z <= CDraw::GetNearClipZ() + 1.0f) return false; + if(out->z >= CDraw::GetFarClipZ() && farclip) return false; + float recip = 1.0f/out->z; + out->x *= RsGlobal.maximumWidth * recip; + out->y *= RsGlobal.maximumHeight * recip; + // What is this? size? + *outw = 70.0f/CDraw::GetFOV(); + *outh = 70.0f/CDraw::GetFOV(); + *outw *= RsGlobal.maximumWidth * recip; + *outh *= RsGlobal.maximumHeight * recip; + return true; +} + +#define SPRITEBUFFERSIZE 64 +static int32 &nSpriteBufferIndex = *(int32*)0x649A80; +static RwIm2DVertex *SpriteBufferVerts = (RwIm2DVertex*)0x649A84; //[SPRITEBUFFERSIZE*6]; +static RwIm2DVertex *verts = (RwIm2DVertex*)0x64C484; //[4]; + +void +CSprite::InitSpriteBuffer(void) +{ + m_f2DNearScreenZ = RwIm2DGetNearScreenZ(); + m_f2DFarScreenZ = RwIm2DGetFarScreenZ(); +} + +void +CSprite::FlushSpriteBuffer(void) +{ + if(nSpriteBufferIndex > 0){ + if(m_bFlushSpriteBufferSwitchZTest){ + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + }else + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6); + nSpriteBufferIndex = 0; + } +} + +void +CSprite::RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a) +{ + static short indices[] = { 0, 1, 2, 3 }; + // 0---3 + // | | + // 1---2 + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x-w; us[0] = 0.0f; + xs[1] = x-w; us[1] = 0.0f; + xs[2] = x+w; us[2] = 1.0f; + xs[3] = x+w; us[3] = 1.0f; + + ys[0] = y-h; vs[0] = 0.0f; + ys[1] = y+h; vs[1] = 1.0f; + ys[2] = y+h; vs[2] = 1.0f; + ys[3] = y-h; vs[3] = 0.0f; + + // clip + for(i = 0; i < 4; i++){ + if(xs[i] < 0.0f){ + us[i] = -xs[i] / (2.0f*w); + xs[i] = 0.0f; + } + if(xs[i] > RsGlobal.maximumWidth){ + us[i] = 1.0f - (xs[i]-RsGlobal.maximumWidth) / (2.0f*w); + xs[i] = RsGlobal.maximumWidth; + } + if(ys[i] < 0.0f){ + vs[i] = -ys[i] / (2.0f*h); + ys[i] = 0.0f; + } + if(ys[i] > RsGlobal.maximumHeight){ + vs[i] = 1.0f - (ys[i]-RsGlobal.maximumHeight) / (2.0f*h); + ys[i] = RsGlobal.maximumHeight; + } + } + + // (DrawZ - DrawNear)/(DrawFar - DrawNear) = (SpriteZ-SpriteNear)/(SpriteFar-SpriteNear) + // So to calculate SpriteZ: + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + for(i = 0; i < 4; i++){ + RwIm2DVertexSetScreenX(&verts[i], xs[i]); + RwIm2DVertexSetScreenY(&verts[i], ys[i]); + RwIm2DVertexSetScreenZ(&verts[i], screenz); + RwIm2DVertexSetCameraZ(&verts[i], z); + RwIm2DVertexSetRecipCameraZ(&verts[i], recipz); + RwIm2DVertexSetIntRGBA(&verts[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&verts[i], us[i], recipz); + RwIm2DVertexSetV(&verts[i], vs[i], recipz); + } + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, verts, 4); +} + +void +CSprite::RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + + // 0---3 + // | | + // 1---2 + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x-w; us[0] = 0.0f; + xs[1] = x-w; us[1] = 0.0f; + xs[2] = x+w; us[2] = 1.0f; + xs[3] = x+w; us[3] = 1.0f; + + ys[0] = y-h; vs[0] = 0.0f; + ys[1] = y+h; vs[1] = 1.0f; + ys[2] = y+h; vs[2] = 1.0f; + ys[3] = y-h; vs[3] = 0.0f; + + // clip + for(i = 0; i < 4; i++){ + if(xs[i] < 0.0f){ + us[i] = -xs[i] / (2.0f*w); + xs[i] = 0.0f; + } + if(xs[i] > RsGlobal.maximumWidth){ + us[i] = 1.0f - (xs[i]-RsGlobal.maximumWidth) / (2.0f*w); + xs[i] = RsGlobal.maximumWidth; + } + if(ys[i] < 0.0f){ + vs[i] = -ys[i] / (2.0f*h); + ys[i] = 0.0f; + } + if(ys[i] > RsGlobal.maximumHeight){ + vs[i] = 1.0f - (ys[i]-RsGlobal.maximumHeight) / (2.0f*h); + ys[i] = RsGlobal.maximumHeight; + } + } + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + // TODO: replace with lookup + float c = cos(DEGTORAD(rotation)); + float s = sin(DEGTORAD(rotation)); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x - c*w - s*h; us[0] = 0.0f; + xs[1] = x - c*w + s*h; us[1] = 0.0f; + xs[2] = x + c*w + s*h; us[2] = 1.0f; + xs[3] = x + c*w - s*h; us[3] = 1.0f; + + ys[0] = y - c*h + s*w; vs[0] = 0.0f; + ys[1] = y + c*h + s*w; vs[1] = 1.0f; + ys[2] = y + c*h - s*w; vs[2] = 1.0f; + ys[3] = y - c*h - s*w; vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > RsGlobal.maximumWidth && xs[1] > RsGlobal.maximumWidth && + xs[2] > RsGlobal.maximumWidth && xs[3] > RsGlobal.maximumWidth) return; + if(ys[0] > RsGlobal.maximumHeight && ys[1] > RsGlobal.maximumHeight && + ys[2] > RsGlobal.maximumHeight && ys[3] > RsGlobal.maximumHeight) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + float c = cos(DEGTORAD(rotation)); + float s = sin(DEGTORAD(rotation)); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > RsGlobal.maximumWidth && xs[1] > RsGlobal.maximumWidth && + xs[2] > RsGlobal.maximumWidth && xs[3] > RsGlobal.maximumWidth) return; + if(ys[0] > RsGlobal.maximumHeight && ys[1] > RsGlobal.maximumHeight && + ys[2] > RsGlobal.maximumHeight && ys[3] > RsGlobal.maximumHeight) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + float c = cos(DEGTORAD(rotation)); + float s = sin(DEGTORAD(rotation)); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + float cf[4]; + int i; + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > RsGlobal.maximumWidth && xs[1] > RsGlobal.maximumWidth && + xs[2] > RsGlobal.maximumWidth && xs[3] > RsGlobal.maximumWidth) return; + if(ys[0] > RsGlobal.maximumHeight && ys[1] > RsGlobal.maximumHeight && + ys[2] > RsGlobal.maximumHeight && ys[3] > RsGlobal.maximumHeight) return; + + // Colour factors, cx/y is the direction in which colours change from rgb1 to rgb2 + cf[0] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f; + cf[0] = clamp(cf[0], 0.0f, 1.0f); + cf[1] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f; + cf[1] = clamp(cf[1], 0.0f, 1.0f); + cf[2] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f; + cf[2] = clamp(cf[2], 0.0f, 1.0f); + cf[3] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f; + cf[3] = clamp(cf[3], 0.0f, 1.0f); + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], + r1*cf[indices[i]] + r2*(1.0f - cf[indices[i]]), + g1*cf[indices[i]] + g2*(1.0f - cf[indices[i]]), + b1*cf[indices[i]] + b2*(1.0f - cf[indices[i]]), + a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +STARTPATCHES + InjectHook(0x51EE90, (void (*)(const CRect&, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&, uint32))CSprite2d::SetVertices, PATCH_JUMP); + InjectHook(0x51F220, (void (*)(const CRect&, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&, + float, float, float, float, float, float, float, float))CSprite2d::SetVertices, PATCH_JUMP); + InjectHook(0x51F970, (void (*)(const CRect&, const CRGBA&))CSprite2d::DrawRect, PATCH_JUMP); + InjectHook(0x51FA00, (void (*)(const CRect&, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&))CSprite2d::DrawRect, PATCH_JUMP); + InjectHook(0x51FA80, CSprite2d::DrawRectXLU, PATCH_JUMP); + + InjectHook(0x51C4A0, CSprite::CalcHorizonCoors, PATCH_JUMP); + InjectHook(0x51C3A0, CSprite::CalcScreenCoors, PATCH_JUMP); + InjectHook(0x51C590, CSprite::InitSpriteBuffer, PATCH_JUMP); + InjectHook(0x51C520, CSprite::FlushSpriteBuffer, PATCH_JUMP); + InjectHook(0x51C960, CSprite::RenderOneXLUSprite, PATCH_JUMP); + InjectHook(0x51C5D0, CSprite::RenderBufferedOneXLUSprite, PATCH_JUMP); + InjectHook(0x51D5B0, CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension, PATCH_JUMP); + InjectHook(0x51CCD0, CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect, PATCH_JUMP); + InjectHook(0x51D9E0, CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours, PATCH_JUMP); +ENDPATCHES diff --git a/src/render/Sprite.h b/src/render/Sprite.h new file mode 100644 index 00000000..53d36de3 --- /dev/null +++ b/src/render/Sprite.h @@ -0,0 +1,37 @@ +#pragma once + +class CSprite2d +{ + RwTexture *m_pTexture; + + static RwIm2DVertex *maVertices; //[4]; +public: + static float &RecipNearClip; + + void SetRenderState(void); + + static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far); + static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + static void DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void DrawRect(const CRect &r, const CRGBA &col); + static void DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); +}; + +class CSprite +{ + static float &m_f2DNearScreenZ; + static float &m_f2DFarScreenZ; + static int32 &m_bFlushSpriteBufferSwitchZTest; +public: + static float CalcHorizonCoors(void); + static bool CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip); + static void InitSpriteBuffer(void); + static void FlushSpriteBuffer(void); + static void RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a); + static void RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a); + static void RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + static void RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + // cx/y is the direction in which the colour changes + static void RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a); +}; diff --git a/src/render/VisibilityPlugins.cpp b/src/render/VisibilityPlugins.cpp new file mode 100644 index 00000000..d9e87553 --- /dev/null +++ b/src/render/VisibilityPlugins.cpp @@ -0,0 +1,849 @@ +#include "common.h" +#include "patcher.h" +#include "templates.h" +#include "Entity.h" +#include "ModelInfo.h" +#include "Lights.h" +#include "Renderer.h" +#include "VisibilityPlugins.h" + +#define FADE_DISTANCE 20.0f + +/* +CLinkList CVisibilityPlugins::m_alphaList; +CLinkList CVisibilityPlugins::m_alphaEntityList; + +int32 CVisibilityPlugins::ms_atomicPluginOffset = -1; +int32 CVisibilityPlugins::ms_framePluginOffset = -1; +int32 CVisibilityPlugins::ms_clumpPluginOffset = -1; +*/ +CLinkList &CVisibilityPlugins::m_alphaList = *(CLinkList*)0x8F42E4; +CLinkList &CVisibilityPlugins::m_alphaEntityList = *(CLinkList*)0x943084; + +int32 &CVisibilityPlugins::ms_atomicPluginOffset = *(int32*)0x600124; +int32 &CVisibilityPlugins::ms_framePluginOffset = *(int32*)0x600128; +int32 &CVisibilityPlugins::ms_clumpPluginOffset = *(int32*)0x60012C; + +RwV3d *&CVisibilityPlugins::ms_pCameraPosn = *(RwV3d**)0x8F6270; +float &CVisibilityPlugins::ms_cullCompsDist = *(float*)0x8F2BC4; +float &CVisibilityPlugins::ms_vehicleLod0Dist = *(float*)0x885B28; +float &CVisibilityPlugins::ms_vehicleLod1Dist = *(float*)0x885B30; +float &CVisibilityPlugins::ms_vehicleFadeDist = *(float*)0x8E28B4; +float &CVisibilityPlugins::ms_bigVehicleLod0Dist = *(float*)0x8E2A84; +float &CVisibilityPlugins::ms_bigVehicleLod1Dist = *(float*)0x8E2A8C; +float &CVisibilityPlugins::ms_pedLod0Dist = *(float*)0x8F2BD4; +float &CVisibilityPlugins::ms_pedLod1Dist = *(float*)0x8F2BD8; +float &CVisibilityPlugins::ms_pedFadeDist = *(float*)0x8E2C34; + +void +CVisibilityPlugins::Initialise(void) +{ + m_alphaList.Init(20); + m_alphaList.head.item.sort = 0.0f; + m_alphaList.tail.item.sort = 100000000.0f; + m_alphaEntityList.Init(350); // TODO: set back to 150 when things are fixed + m_alphaEntityList.head.item.sort = 0.0f; + m_alphaEntityList.tail.item.sort = 100000000.0f; +} + +void +CVisibilityPlugins::InitAlphaEntityList(void) +{ + m_alphaEntityList.Clear(); +} + +bool +CVisibilityPlugins::InsertEntityIntoSortedList(CEntity *e, float dist) +{ + AlphaObjectInfo item; + item.entity = e; + item.sort = dist; + bool ret = !!m_alphaEntityList.InsertSorted(item); +// if(!ret) +// printf("list full %d\n", m_alphaEntityList.Count()); + return ret; +} + +void +CVisibilityPlugins::InitAlphaAtomicList(void) +{ + m_alphaList.Clear(); +} + +bool +CVisibilityPlugins::InsertAtomicIntoSortedList(RpAtomic *a, float dist) +{ + AlphaObjectInfo item; + item.atomic = a; + item.sort = dist; + bool ret = !!m_alphaList.InsertSorted(item); +// if(!ret) +// printf("list full %d\n", m_alphaList.Count()); + return ret; +} + +RpMaterial* +SetAlphaCB(RpMaterial *material, void *data) +{ + material->color.alpha = (uint8)(uint32)data; + return material; +} + +RpMaterial* +SetTextureCB(RpMaterial *material, void *data) +{ + RpMaterialSetTexture(material, (RwTexture*)data); + return material; +} + +void +CVisibilityPlugins::RenderAlphaAtomics(void) +{ + CLink *node; + for(node = m_alphaList.tail.prev; + node != &m_alphaList.head; + node = node->prev) + AtomicDefaultRenderCallBack(node->item.atomic); +} + +void +CVisibilityPlugins::RenderFadingEntities(void) +{ + CLink *node; + CSimpleModelInfo *mi; + for(node = m_alphaEntityList.tail.prev; + node != &m_alphaEntityList.head; + node = node->prev){ + CEntity *e = node->item.entity; + if(e->m_rwObject == nil) + continue; + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(e->m_modelIndex); + if(mi->m_noZwrite) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + + if(e->bDistanceFade){ + DeActivateDirectional(); + SetAmbientColours(); + e->bImBeingRendered = true; + RenderFadingAtomic((RpAtomic*)e->m_rwObject, node->item.sort); + e->bImBeingRendered = false; + }else + CRenderer::RenderOneNonRoad(e); + + if(mi->m_noZwrite) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + } +} + +RpAtomic* +CVisibilityPlugins::RenderWheelAtomicCB(RpAtomic *atomic) +{ + RpAtomic *lodatm; + RwMatrix *m; + RwV3d view; + float len; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + m = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn); + len = RwV3dLength(&view); + lodatm = mi->GetAtomicFromDistance(len); + if(lodatm){ + if(RpAtomicGetGeometry(lodatm) != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, RpAtomicGetGeometry(lodatm), rpATOMICSAMEBOUNDINGSPHERE); + AtomicDefaultRenderCallBack(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderObjNormalAtomic(RpAtomic *atomic) +{ + RwMatrix *m; + RwV3d view; + float len; + + m = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn); + len = RwV3dLength(&view); + if(RwV3dDotProduct(&view, RwMatrixGetUp(m)) < -0.3f*len && len > 8.0f) + return atomic; + AtomicDefaultRenderCallBack(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderAlphaAtomic(RpAtomic *atomic, int alpha) +{ + RpGeometry *geo; + uint32 flags; + + geo = RpAtomicGetGeometry(atomic); + flags = RpGeometryGetFlags(geo); + RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha); + AtomicDefaultRenderCallBack(atomic); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255); + RpGeometrySetFlags(geo, flags); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderFadingAtomic(RpAtomic *atomic, float camdist) +{ + RpAtomic *lodatm; + float fadefactor; + uint8 alpha; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + lodatm = mi->GetAtomicFromDistance(camdist - FADE_DISTANCE); + if(mi->m_additive){ + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + AtomicDefaultRenderCallBack(atomic); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + }else{ + fadefactor = (mi->GetLargestLodDistance() - (camdist - FADE_DISTANCE))/FADE_DISTANCE; + if(fadefactor > 1.0f) + fadefactor = 1.0f; + alpha = mi->m_alpha * fadefactor; + if(alpha == 255) + AtomicDefaultRenderCallBack(atomic); + else{ + RpGeometry *geo = RpAtomicGetGeometry(lodatm); + uint32 flags = RpGeometryGetFlags(geo); + RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha); + if(geo != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, geo, 0); + AtomicDefaultRenderCallBack(atomic); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255); + RpGeometrySetFlags(geo, flags); + } + } + return atomic; +} + + + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + } + AtomicDefaultRenderCallBack(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + // sort before clump + if(!InsertAtomicIntoSortedList(atomic, distsq - 0.0001f)) + AtomicDefaultRenderCallBack(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + AtomicDefaultRenderCallBack(atomic); + } + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + return atomic; + } + AtomicDefaultRenderCallBack(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + AtomicDefaultRenderCallBack(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_Boat(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist) + AtomicDefaultRenderCallBack(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod0Dist && + distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + return atomic; + } + AtomicDefaultRenderCallBack(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod0Dist && + distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + AtomicDefaultRenderCallBack(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist >= ms_vehicleLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + AtomicDefaultRenderCallBack(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; + +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod1Dist) + AtomicDefaultRenderCallBack(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + } + AtomicDefaultRenderCallBack(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + // sort before clump + if(!InsertAtomicIntoSortedList(atomic, distsq - 0.0001f)) + AtomicDefaultRenderCallBack(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + AtomicDefaultRenderCallBack(atomic); + } + } + return atomic; +} + +// TODO: this is part of a struct +static RwTexture *&playerskin = *(RwTexture**)0x941428; + +RpAtomic* +CVisibilityPlugins::RenderPlayerCB(RpAtomic *atomic) +{ + if(playerskin) + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetTextureCB, playerskin); + AtomicDefaultRenderCallBack(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPedLowDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist >= ms_pedLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + AtomicDefaultRenderCallBack(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPedHiDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist < ms_pedLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + AtomicDefaultRenderCallBack(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +float +CVisibilityPlugins::GetDistanceSquaredFromCamera(RwFrame *frame) +{ + RwMatrix *m; + RwV3d dist; + m = RwFrameGetLTM(frame); + RwV3dSub(&dist, RwMatrixGetPos(m), ms_pCameraPosn); + return RwV3dDotProduct(&dist, &dist); +} + +float +CVisibilityPlugins::GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags) +{ + RwV3d dist; + float dot, dotdoor; + + // Vehicle forward is the y axis (RwMatrix.up) + // Vehicle right is the x axis (RwMatrix.right) + + RwV3dSub(&dist, RwMatrixGetPos(atomicMat), ms_pCameraPosn); + // forward/backward facing + if(flags & (ATOMIC_FLAG_FRONT | ATOMIC_FLAG_REAR)) + dot = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + // left/right facing + else if(flags & (ATOMIC_FLAG_LEFT & ATOMIC_FLAG_RIGHT)) + dot = RwV3dDotProduct(&dist, RwMatrixGetRight(clumpMat)); + else + dot = 0.0f; + if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_REAR)) + dot = -dot; + + if(flags & (ATOMIC_FLAG_REARDOOR | ATOMIC_FLAG_FRONTDOOR)){ + if(flags & ATOMIC_FLAG_REARDOOR) + dotdoor = -RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + else if(flags & ATOMIC_FLAG_FRONTDOOR) + dotdoor = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + else + dotdoor = 0.0f; + + if(dot < 0.0f && dotdoor < 0.0f) + dot += dotdoor; + if(dot > 0.0f && dotdoor > 0.0f) + dot += dotdoor; + } + + return dot; +} + +/* These are all unused */ + +bool +CVisibilityPlugins::DefaultVisibilityCB(RpClump *clump) +{ + return true; +} + +bool +CVisibilityPlugins::FrustumSphereCB(RpClump *clump) +{ + // TODO, but unused + return true; +} + +bool +CVisibilityPlugins::VehicleVisibilityCB(RpClump *clump) +{ + // TODO, but unused + return true; +} + +bool +CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump *clump) +{ + // TODO, but unused + return true; +} + + + + +// +// RW Plugins +// + +enum +{ + ID_VISIBILITYATOMIC = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x00), + ID_VISIBILITYCLUMP = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x01), + ID_VISIBILITYFRAME = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x02), +}; + +bool +CVisibilityPlugins::PluginAttach(void) +{ + ms_atomicPluginOffset = RpAtomicRegisterPlugin(sizeof(AtomicExt), + ID_VISIBILITYATOMIC, + AtomicConstructor, AtomicDestructor, AtomicCopyConstructor); + + ms_framePluginOffset = RwFrameRegisterPlugin(sizeof(FrameExt), + ID_VISIBILITYFRAME, + FrameConstructor, FrameDestructor, FrameCopyConstructor); + + ms_clumpPluginOffset = RpClumpRegisterPlugin(sizeof(ClumpExt), + ID_VISIBILITYCLUMP, + ClumpConstructor, ClumpDestructor, ClumpCopyConstructor); + return ms_atomicPluginOffset != -1 && ms_clumpPluginOffset != -1; +} + +#define ATOMICEXT(o) (RWPLUGINOFFSET(AtomicExt, o, ms_atomicPluginOffset)) +#define FRAMEEXT(o) (RWPLUGINOFFSET(FrameExt, o, ms_framePluginOffset)) +#define CLUMPEXT(o) (RWPLUGINOFFSET(ClumpExt, o, ms_clumpPluginOffset)) + +// +// Atomic +// + +void* +CVisibilityPlugins::AtomicConstructor(void *object, int32, int32) +{ + ATOMICEXT(object)->modelInfo = nil; + return object; +} + +void* +CVisibilityPlugins::AtomicDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, int32, int32) +{ + *ATOMICEXT(dst) = *ATOMICEXT(src); + return dst; +} + +void +CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic, + CSimpleModelInfo *modelInfo) +{ + AtomicExt *ext = ATOMICEXT(atomic); + ext->modelInfo = modelInfo; + switch(modelInfo->m_type) + case MITYPE_SIMPLE: + case MITYPE_TIME: + if(modelInfo->m_normalCull) + SetAtomicRenderCallback(atomic, RenderObjNormalAtomic); +} + +CSimpleModelInfo* +CVisibilityPlugins::GetAtomicModelInfo(RpAtomic *atomic) +{ + return ATOMICEXT(atomic)->modelInfo; +} + +void +CVisibilityPlugins::SetAtomicFlag(RpAtomic *atomic, int f) +{ + ATOMICEXT(atomic)->flags |= f; +} + +void +CVisibilityPlugins::ClearAtomicFlag(RpAtomic *atomic, int f) +{ + ATOMICEXT(atomic)->flags &= ~f; +} + +int +CVisibilityPlugins::GetAtomicId(RpAtomic *atomic) +{ + return ATOMICEXT(atomic)->flags; +} + +// This is rather useless, but whatever +void +CVisibilityPlugins::SetAtomicRenderCallback(RpAtomic *atomic, RpAtomicCallBackRender cb) +{ + if(cb == nil) + cb = AtomicDefaultRenderCallBack; // not necessary + RpAtomicSetRenderCallBack(atomic, cb); +} + +// +// Frame +// + +void* +CVisibilityPlugins::FrameConstructor(void *object, int32, int32) +{ + FRAMEEXT(object)->id = 0; + return object; +} + +void* +CVisibilityPlugins::FrameDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, int32, int32) +{ + *FRAMEEXT(dst) = *FRAMEEXT(src); + return dst; +} + +void +CVisibilityPlugins::SetFrameHierarchyId(RwFrame *frame, int32 id) +{ + FRAMEEXT(frame)->id = id; +} + +int32 +CVisibilityPlugins::GetFrameHierarchyId(RwFrame *frame) +{ + return FRAMEEXT(frame)->id; +} + + +// +// Clump +// + +void* +CVisibilityPlugins::ClumpConstructor(void *object, int32, int32) +{ + ClumpExt *ext = CLUMPEXT(object); + ext->visibilityCB = DefaultVisibilityCB; + ext->alpha = 0xFF; + return object; +} + +void* +CVisibilityPlugins::ClumpDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, int32, int32) +{ + CLUMPEXT(dst)->visibilityCB = CLUMPEXT(src)->visibilityCB; + return dst; +} + +void +CVisibilityPlugins::SetClumpModelInfo(RpClump *clump, CClumpModelInfo *modelInfo) +{ + CVehicleModelInfo *vmi; + SetFrameHierarchyId(RpClumpGetFrame(clump), (int32)modelInfo); + + // Unused + switch(modelInfo->m_type){ + // ignore MLO + case MITYPE_VEHICLE: + vmi = (CVehicleModelInfo*)modelInfo; + if(vmi->m_vehicleType == VEHICLE_TYPE_TRAIN || + vmi->m_vehicleType == VEHICLE_TYPE_HELI || + vmi->m_vehicleType == VEHICLE_TYPE_PLANE) + CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB_BigVehicle; + else + CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB; + break; + } +} + +void +CVisibilityPlugins::SetClumpAlpha(RpClump *clump, int alpha) +{ + CLUMPEXT(clump)->alpha = alpha; +} + +int +CVisibilityPlugins::GetClumpAlpha(RpClump *clump) +{ + return CLUMPEXT(clump)->alpha; +} + + +STARTPATCHES + InjectHook(0x527E50, CVisibilityPlugins::Initialise, PATCH_JUMP); + InjectHook(0x528F90, CVisibilityPlugins::InitAlphaEntityList, PATCH_JUMP); + InjectHook(0x528FF0, CVisibilityPlugins::InsertEntityIntoSortedList, PATCH_JUMP); + InjectHook(0x528F80, CVisibilityPlugins::InitAlphaAtomicList, PATCH_JUMP); + InjectHook(0x528FA0, CVisibilityPlugins::InsertAtomicIntoSortedList, PATCH_JUMP); + InjectHook(0x527F60, SetAlphaCB, PATCH_JUMP); + InjectHook(0x529040, CVisibilityPlugins::RenderAlphaAtomics, PATCH_JUMP); + InjectHook(0x529070, CVisibilityPlugins::RenderFadingEntities, PATCH_JUMP); + + InjectHook(0x527F70, CVisibilityPlugins::RenderWheelAtomicCB, PATCH_JUMP); + InjectHook(0x528000, CVisibilityPlugins::RenderObjNormalAtomic, PATCH_JUMP); + InjectHook(0x5280B0, CVisibilityPlugins::RenderAlphaAtomic, PATCH_JUMP); + InjectHook(0x528100, CVisibilityPlugins::RenderFadingAtomic, PATCH_JUMP); + + InjectHook(0x5283E0, CVisibilityPlugins::RenderVehicleHiDetailCB, PATCH_JUMP); + InjectHook(0x5284B0, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB, PATCH_JUMP); + InjectHook(0x5288A0, CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle, PATCH_JUMP); + InjectHook(0x528A10, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle, PATCH_JUMP); + InjectHook(0x528AD0, CVisibilityPlugins::RenderVehicleHiDetailCB_Boat, PATCH_JUMP); + InjectHook(0x5287F0, CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle, PATCH_JUMP); + InjectHook(0x528940, CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle, PATCH_JUMP); + InjectHook(0x528240, CVisibilityPlugins::RenderVehicleReallyLowDetailCB, PATCH_JUMP); + InjectHook(0x5287B0, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle, PATCH_JUMP); + InjectHook(0x5285D0, CVisibilityPlugins::RenderTrainHiDetailCB, PATCH_JUMP); + InjectHook(0x5286A0, CVisibilityPlugins::RenderTrainHiDetailAlphaCB, PATCH_JUMP); + + InjectHook(0x528BC0, CVisibilityPlugins::RenderPedHiDetailCB, PATCH_JUMP); + InjectHook(0x528B60, CVisibilityPlugins::RenderPedLowDetailCB, PATCH_JUMP); + + + InjectHook(0x527DC0, CVisibilityPlugins::PluginAttach, PATCH_JUMP); + + InjectHook(0x527EC0, CVisibilityPlugins::SetAtomicModelInfo, PATCH_JUMP); + InjectHook(0x527F00, CVisibilityPlugins::GetAtomicModelInfo, PATCH_JUMP); + InjectHook(0x527F10, CVisibilityPlugins::SetAtomicFlag, PATCH_JUMP); + InjectHook(0x527F30, CVisibilityPlugins::ClearAtomicFlag, PATCH_JUMP); + InjectHook(0x527F50, CVisibilityPlugins::GetAtomicId, PATCH_JUMP); + InjectHook(0x528C20, CVisibilityPlugins::SetAtomicRenderCallback, PATCH_JUMP); + + InjectHook(0x528D60, CVisibilityPlugins::SetFrameHierarchyId, PATCH_JUMP); + InjectHook(0x528D80, CVisibilityPlugins::GetFrameHierarchyId, PATCH_JUMP); + + InjectHook(0x528ED0, CVisibilityPlugins::SetClumpModelInfo, PATCH_JUMP); + InjectHook(0x528F50, CVisibilityPlugins::SetClumpAlpha, PATCH_JUMP); + InjectHook(0x528F70, CVisibilityPlugins::GetClumpAlpha, PATCH_JUMP); + + + InjectHook(0x529120, CVisibilityPlugins::GetDistanceSquaredFromCamera, PATCH_JUMP); + InjectHook(0x5282A0, CVisibilityPlugins::GetDotProductWithCameraVector, PATCH_JUMP); +ENDPATCHES diff --git a/src/render/VisibilityPlugins.h b/src/render/VisibilityPlugins.h new file mode 100644 index 00000000..f041b24e --- /dev/null +++ b/src/render/VisibilityPlugins.h @@ -0,0 +1,129 @@ +#pragma once + +#include "templates.h" + +class CEntity; +class CSimpleModelInfo; +class CClumpModelInfo; + +typedef bool (*ClumpVisibilityCB)(RpClump*); + +class CVisibilityPlugins +{ +public: + struct AlphaObjectInfo + { + union { + CEntity *entity; + RpAtomic *atomic; + }; + float sort; + }; + + static CLinkList &m_alphaList; + static CLinkList &m_alphaEntityList; + static RwV3d *&ms_pCameraPosn; + static float &ms_cullCompsDist; + static float &ms_vehicleLod0Dist; + static float &ms_vehicleLod1Dist; + static float &ms_vehicleFadeDist; + static float &ms_bigVehicleLod0Dist; + static float &ms_bigVehicleLod1Dist; + static float &ms_pedLod0Dist; + static float &ms_pedLod1Dist; + static float &ms_pedFadeDist; + + static void Initialise(void); + static void InitAlphaEntityList(void); + static bool InsertEntityIntoSortedList(CEntity *e, float dist); + static void InitAlphaAtomicList(void); + static bool InsertAtomicIntoSortedList(RpAtomic *a, float dist); + + static RpAtomic *RenderWheelAtomicCB(RpAtomic *atomic); + static RpAtomic *RenderObjNormalAtomic(RpAtomic *atomic); + static RpAtomic *RenderAlphaAtomic(RpAtomic *atomic, int alpha); + static RpAtomic *RenderFadingAtomic(RpAtomic *atm, float dist); + + static RpAtomic *RenderVehicleHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailAlphaCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailCB_Boat(RpAtomic *atomic); + static RpAtomic *RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleReallyLowDetailCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderTrainHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderTrainHiDetailAlphaCB(RpAtomic *atomic); + + static RpAtomic *RenderPlayerCB(RpAtomic *atomic); + static RpAtomic *RenderPedLowDetailCB(RpAtomic *atomic); + static RpAtomic *RenderPedHiDetailCB(RpAtomic *atomic); + + static void RenderAlphaAtomics(void); + static void RenderFadingEntities(void); + + // All actually unused + static bool DefaultVisibilityCB(RpClump *clump); + static bool FrustumSphereCB(RpClump *clump); +// static bool MloVisibilityCB(RpClump *clump); + static bool VehicleVisibilityCB(RpClump *clump); + static bool VehicleVisibilityCB_BigVehicle(RpClump *clump); + + static float GetDistanceSquaredFromCamera(RwFrame *frame); + static float GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags); + + // + // RW Plugins + // + + union AtomicExt + { + CSimpleModelInfo *modelInfo; // used by SimpleModelInfo + int flags; // used by ClumpModelInfo + }; + static void SetAtomicModelInfo(RpAtomic*, CSimpleModelInfo*); + static CSimpleModelInfo *GetAtomicModelInfo(RpAtomic *atomic); + static void SetAtomicFlag(RpAtomic*, int); + static void ClearAtomicFlag(RpAtomic*, int); + static int GetAtomicId(RpAtomic *atomic); + static void SetAtomicRenderCallback(RpAtomic*, RpAtomicCallBackRender); + + static void *AtomicConstructor(void *object, int32 offset, int32 len); + static void *AtomicDestructor(void *object, int32 offset, int32 len); + static void *AtomicCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 &ms_atomicPluginOffset; + + struct FrameExt + { + // BUG: this is abused to hold a pointer by SetClumpModelInfo + int32 id; + }; + static void SetFrameHierarchyId(RwFrame *frame, int32 id); + static int32 GetFrameHierarchyId(RwFrame *frame); + + static void *FrameConstructor(void *object, int32 offset, int32 len); + static void *FrameDestructor(void *object, int32 offset, int32 len); + static void *FrameCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 &ms_framePluginOffset; + + // Not actually used + struct ClumpExt + { + ClumpVisibilityCB visibilityCB; + int alpha; + }; + static void SetClumpModelInfo(RpClump*, CClumpModelInfo*); + static void SetClumpAlpha(RpClump*, int); + static int GetClumpAlpha(RpClump*); + + static void *ClumpConstructor(void *object, int32 offset, int32 len); + static void *ClumpDestructor(void *object, int32 offset, int32 len); + static void *ClumpCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 &ms_clumpPluginOffset; + + static bool PluginAttach(void); +}; diff --git a/src/rw.cpp b/src/rw.cpp new file mode 100644 index 00000000..13383783 --- /dev/null +++ b/src/rw.cpp @@ -0,0 +1,285 @@ +#include "common.h" +#include "patcher.h" +#include +#include +#include + +// + // ADDRESS +// + +int gtaversion = -1; + +WRAPPER RwTexDictionary *RwTexDictionaryCreate(void) { EAXJMP(0x5A7160); } +WRAPPER RwBool RwTexDictionaryDestroy(RwTexDictionary*) { EAXJMP(0x5A7200); } +static uint32_t RwTexDictionaryFindNamedTexture_A = AddressByVersion(0x5A74D0, 0, 0, 0x64E060, 0, 0); +WRAPPER RwTexture *RwTexDictionaryFindNamedTexture(RwTexDictionary*, const RwChar*) { VARJMP(RwTexDictionaryFindNamedTexture_A); } +static uint32_t RwTextureRead_A = AddressByVersion(0x5A7580, 0x5A7840, 0x5A8E00, 0x64E110, 0, 0); +WRAPPER RwTexture *RwTextureRead(const RwChar*, const RwChar*) { VARJMP(RwTextureRead_A); } +static uint32_t RwTexDictionaryGetCurrent_A = AddressByVersion(0x5A7570, 0x5A7830, 0x5A8DA0, 0x64E100, 0, 0); +WRAPPER RwTexDictionary *RwTexDictionaryGetCurrent(void) { VARJMP(RwTexDictionaryGetCurrent_A); } +static uint32_t RwTexDictionarySetCurrent_A = AddressByVersion(0x5A7550, 0x5A7810, 0x5A8D80, 0x64E0E0, 0, 0); +WRAPPER RwTexDictionary *RwTexDictionarySetCurrent(RwTexDictionary * dict) { VARJMP(RwTexDictionarySetCurrent_A); } +static uint32_t RwTexDictionaryForAllTextures_A = AddressByVersion(0, 0, 0, 0x64DE20, 0, 0); +WRAPPER const RwTexDictionary *RwTexDictionaryForAllTextures(const RwTexDictionary*, RwTextureCallBack, void*) { VARJMP(RwTexDictionaryForAllTextures_A); } + +static uint32_t RwV3dLength_A = AddressByVersion(0x5A36A0, 0, 0, 0x647030, 0, 0); +WRAPPER RwReal RwV3dLength(const RwV3d*) { VARJMP(RwV3dLength_A); } +WRAPPER RwV3d *RwV3dTransformPoints(RwV3d*, const RwV3d*, RwInt32, const RwMatrix*) { EAXJMP(0x5A37D0); } + +static uint32_t D3D8AtomicDefaultInstanceCallback_A = AddressByVersion(0x5DB450, 0x5DB710, 0x5EC520, 0x67BAE0, 0, 0); +WRAPPER RwBool D3D8AtomicDefaultInstanceCallback(void*, RxD3D8InstanceData*, RwBool) { VARJMP(D3D8AtomicDefaultInstanceCallback_A); } +static uint32_t D3D8AtomicDefaultReinstanceCallback_A = AddressByVersion(0x5DBFB0, 0, 0, 0x67C640, 0, 0); +WRAPPER RwBool D3D8AtomicDefaultReinstanceCallback(void*, RwResEntry*, const RpMeshHeader*, RxD3D8AllInOneInstanceCallBack) { VARJMP(D3D8AtomicDefaultReinstanceCallback_A); } + +static uint32_t rwD3D8RWGetRasterStage_A = AddressByVersion(0x5B5390, 0x5B5650, 0x5BA2C0, 0x659840, 0x659890, 0x6587F0); +WRAPPER int rwD3D8RWGetRasterStage(int) { VARJMP(rwD3D8RWGetRasterStage_A); } + +static uint32_t RpWorldAddCamera_A = AddressByVersion(0x5AFB80, 0, 0, 0x654460, 0, 0); +WRAPPER RpWorld *RpWorldAddCamera(RpWorld*, RwCamera*) { VARJMP(RpWorldAddCamera_A); } + +static uint32_t RpMaterialRegisterPlugin_A = AddressByVersion(0x5ADD40, 0, 0, 0x6558C0, 0, 0); +WRAPPER RwInt32 RpMaterialRegisterPlugin(RwInt32, RwUInt32, RwPluginObjectConstructor, RwPluginObjectDestructor, RwPluginObjectCopy) { VARJMP(RpMaterialRegisterPlugin_A); } +WRAPPER RpMaterial *RpMaterialSetTexture(RpMaterial*, RwTexture*) { EAXJMP(0x5ADD10); } + +// + // +// + +static uint32_t RwMatrixCreate_A = AddressByVersion(0x5A3330, 0x5A35F0, 0x5A3FA0, 0x644620, 0x644670, 0x6435D0); +WRAPPER RwMatrix *RwMatrixCreate(void) { VARJMP(RwMatrixCreate_A); } +static uint32_t RwMatrixDestroy_A = AddressByVersion(0x5A3300, 0x5A35C0, 0x5A3F70, 0x6445F0, 0x644640, 0x6435A0); +WRAPPER RwBool RwMatrixDestroy(RwMatrix*) { VARJMP(RwMatrixDestroy_A); } +static uint32_t RwMatrixMultiply_A = AddressByVersion(0x5A28F0, 0x5A2BB0, 0x5A2E10, 0x6437C0, 0x643810, 0x642770); +WRAPPER RwMatrix *RwMatrixMultiply(RwMatrix*, const RwMatrix*, const RwMatrix*) { VARJMP(RwMatrixMultiply_A); } +static uint32_t RwMatrixInvert_A = AddressByVersion(0x5A2C90, 0x5A2F50, 0x5A35A0, 0x643F40, 0x643F90, 0x642EF0); +WRAPPER RwMatrix *RwMatrixInvert(RwMatrix*, const RwMatrix*) { VARJMP(RwMatrixInvert_A); } +static uint32_t RwMatrixUpdate_A = AddressByVersion(0x5A28E0, 0x5A2BA0, 0x5A2DF0, 0x6437B0, 0x643800, 0x642760); +WRAPPER RwMatrix *RwMatrixUpdate(RwMatrix*) { VARJMP(RwMatrixUpdate_A); } +static uint32_t RwMatrixRotate_A = AddressByVersion(0x5A2BF0, 0x5A2EB0, 0x5A3510, 0x643EA0, 0x643EF0, 0x642E50); +WRAPPER RwMatrix *RwMatrixRotate(RwMatrix*, const RwV3d*, RwReal, RwOpCombineType) { VARJMP(RwMatrixRotate_A); } +static uint32_t RwMatrixOptimize_A = AddressByVersion(0x5A2820, 0, 0, 0x6436F0, 0, 0); +WRAPPER RwMatrix *RwMatrixOptimize(RwMatrix *matrix, const RwMatrixTolerance *tolerance) { VARJMP(RwMatrixOptimize_A); } +WRAPPER RwMatrix *RwMatrixTransform(RwMatrix*, const RwMatrix*, RwOpCombineType) { EAXJMP(0x5A31C0); } +WRAPPER RwFrame *RwFrameForAllObjects(RwFrame*, RwObjectCallBack, void*) { EAXJMP(0x5A2340); } + +static uint32_t RwFrameCreate_A = AddressByVersion(0x5A1A00, 0x5A1CC0, 0x5A2270, 0x644AA0, 0x644AF0, 0x643A50); +WRAPPER RwFrame *RwFrameCreate(void) { VARJMP(RwFrameCreate_A); } +static uint32_t RwFrameUpdateObjects_A = AddressByVersion(0x5A1C60, 0x5A1F20, 0x5A23B4, 0x644D00, 0x644D50, 0x643CB0); +WRAPPER RwFrame *RwFrameUpdateObjects(RwFrame* a1) +{ + if (gtaversion != III_STEAM) + VARJMP(RwFrameUpdateObjects_A); + + __asm + { + mov eax, a1 + jmp RwFrameUpdateObjects_A + } +} +static uint32_t RwFrameGetLTM_A = AddressByVersion(0x5A1CE0, 0x5A1FA0, 0x5A2430, 0x644D80, 0x644DD0, 0x643D30); +WRAPPER RwMatrix *RwFrameGetLTM(RwFrame*) { VARJMP(RwFrameGetLTM_A); } +static uint32_t RwFrameTransform_A = AddressByVersion(0x5A2140, 0x5A2400, 0x5A26E0, 0x6451E0, 0x645230, 0x644190); +WRAPPER RwFrame *RwFrameTransform(RwFrame*, const RwMatrix*, RwOpCombineType) { VARJMP(RwFrameTransform_A); } +WRAPPER RwBool RwFrameDestroy(RwFrame*) { EAXJMP(0x5A1A30); } +WRAPPER RwInt32 RwFrameRegisterPlugin(RwInt32, RwUInt32,RwPluginObjectConstructor,RwPluginObjectDestructor, RwPluginObjectCopy) { EAXJMP(0x5A2380); } +WRAPPER RwFrame *RwFrameForAllChildren(RwFrame*, RwFrameCallBack, void*) { EAXJMP(0x5A1FC0); } +WRAPPER RwFrame *RwFrameAddChild(RwFrame*, RwFrame*) { EAXJMP(0x5A1D00); } +WRAPPER RwFrame *RwFrameRemoveChild(RwFrame*) { EAXJMP(0x5A1ED0); } +WRAPPER RwFrame *RwFrameScale(RwFrame*, const RwV3d*, RwOpCombineType) { EAXJMP(0x5A20A0); } + +static uint32_t RwCameraCreate_A = AddressByVersion(0x5A5360, 0x5A5620, 0x5A74E0, 0x64AB50, 0x64ABA0, 0x649B00); +WRAPPER RwCamera *RwCameraCreate(void) { VARJMP(RwCameraCreate_A); } +static uint32_t RwCameraBeginUpdate_A = AddressByVersion(0x5A5030, 0x5A52F0, 0x5A7220, 0x64A820, 0x64A870, 0x6497D0); +WRAPPER RwCamera *RwCameraBeginUpdate(RwCamera*) { VARJMP(RwCameraBeginUpdate_A); } +static uint32_t RwCameraEndUpdate_A = AddressByVersion(0x5A5020, 0x5A52E0, 0x5A7200, 0x64A810, 0x64A860, 0x6497C0); +WRAPPER RwCamera *RwCameraEndUpdate(RwCamera*) { VARJMP(RwCameraEndUpdate_A); } +static uint32_t RwCameraSetNearClipPlane_A = AddressByVersion(0x5A5070, 0x5A5330, 0x5A7270, 0x64A860, 0x64A8B0, 0x649810); +WRAPPER RwCamera *RwCameraSetNearClipPlane(RwCamera*, RwReal) { VARJMP(RwCameraSetNearClipPlane_A); } +static uint32_t RwCameraSetFarClipPlane_A = AddressByVersion(0x5A5140, 0x5A5400, 0x5A72A0, 0x64A930, 0x64A980, 0x6498E0); +WRAPPER RwCamera *RwCameraSetFarClipPlane(RwCamera*, RwReal) { VARJMP(RwCameraSetFarClipPlane_A); } +static uint32_t RwCameraSetViewWindow_A = AddressByVersion(0x5A52B0, 0x5A5570, 0x5A7440, 0x64AAA0, 0x64AAF0, 0x649A50); +WRAPPER RwCamera *RwCameraSetViewWindow(RwCamera*, const RwV2d*) { VARJMP(RwCameraSetViewWindow_A); } +static uint32_t RwCameraClear_A = AddressByVersion(0x5A51E0, 0x5A54A0, 0x5A7350, 0x64A9D0, 0x64AA20, 0x649980); +WRAPPER RwCamera *RwCameraClear(RwCamera*, RwRGBA*, RwInt32) { VARJMP(RwCameraClear_A); } + +static uint32_t RwRasterCreate_A = AddressByVersion(0x5AD930, 0x5ADBF0, 0x5B0580, 0x655490, 0x6554E0, 0x654440); +WRAPPER RwRaster *RwRasterCreate(RwInt32, RwInt32, RwInt32, RwInt32) { VARJMP(RwRasterCreate_A); } +// ADDRESS +static uint32_t RwRasterDestroy_A = AddressByVersion(0x5AD780, 0, 0, 0x6552E0, 0, 0); +WRAPPER RwBool RwRasterDestroy(RwRaster*) { VARJMP(RwRasterDestroy_A); } +static uint32_t RwRasterSetFromImage_A = AddressByVersion(0x5BBF50, 0x5BC210, 0x5C0BF0, 0x6602B0, 0x660300, 0x65F260); +WRAPPER RwRaster *RwRasterSetFromImage(RwRaster*, RwImage*) { VARJMP(RwRasterSetFromImage_A); } +static uint32_t RwRasterPushContext_A = AddressByVersion(0x5AD7C0, 0x5ADA80, 0x5B03C0, 0x655320, 0x655370, 0x6542D0); +WRAPPER RwRaster *RwRasterPushContext(RwRaster*) { VARJMP(RwRasterPushContext_A); } +static uint32_t RwRasterPopContext_A = AddressByVersion(0x5AD870, 0x5ADB30, 0x5B0460, 0x6553D0, 0x655420, 0x654380); +WRAPPER RwRaster *RwRasterPopContext(void) { VARJMP(RwRasterPopContext_A); } +static uint32_t RwRasterRenderFast_A = AddressByVersion(0x5AD710, 0x5AD9D0, 0x5B0800, 0x655270, 0x6552C0, 0x654220); +WRAPPER RwRaster *RwRasterRenderFast(RwRaster*, RwInt32, RwInt32) { VARJMP(RwRasterRenderFast_A); } +//RwRaster *RwRasterRenderScaled(RwRaster *raster, RwRect *rect) +//{ +// ((RwGlobals*)RwEngineInst)->stdFunc[rwSTANDARDRASTERRENDERSCALED](raster, rect, 0); +// return raster; +//} + +static uint32_t RwTextureCreate_A = AddressByVersion(0x5A72D0, 0x5A7590, 0x5A8AC0, 0x64DE60, 0x64DEB0, 0x64CE10); +WRAPPER RwTexture *RwTextureCreate(RwRaster*) { VARJMP(RwTextureCreate_A); } +WRAPPER RwBool RwTextureDestroy(RwTexture*) { EAXJMP(0x5A7330); } + +static uint32_t RwRenderStateGet_A = AddressByVersion(0x5A4410, 0x5A46D0, 0x5A53B0, 0x649BF0, 0x649C40, 0x648BA0); +WRAPPER RwBool RwRenderStateGet(RwRenderState, void*) { VARJMP(RwRenderStateGet_A); } +static uint32_t RwRenderStateSet_A = AddressByVersion(0x5A43C0, 0x5A4680, 0x5A5360, 0x649BA0, 0x649BF0, 0x648B50); +WRAPPER RwBool RwRenderStateSet(RwRenderState, void*) { VARJMP(RwRenderStateSet_A); } + +static uint32_t RwD3D8SetTexture_A = AddressByVersion(0x5B53A0, 0x5B5660, 0x5BA2D0, 0x659850, 0x6598A0, 0x658800); +WRAPPER RwBool RwD3D8SetTexture(RwTexture*, RwUInt32) { VARJMP(RwD3D8SetTexture_A); } +static uint32_t RwD3D8SetRenderState_A = AddressByVersion(0x5B3CF0, 0x5B3FB0, 0x5B84B0, 0x6582A0, 0x6582F0, 0x657250); +WRAPPER RwBool RwD3D8SetRenderState(RwUInt32, RwUInt32) { VARJMP(RwD3D8SetRenderState_A); } +static uint32_t RwD3D8GetRenderState_A = AddressByVersion(0x5B3D40, 0x5B4000, 0x5B8510, 0x6582F0, 0x658340, 0x6572A0); +WRAPPER void RwD3D8GetRenderState(RwUInt32, void*) { VARJMP(RwD3D8GetRenderState_A); } +static uint32_t RwD3D8SetTextureStageState_A = AddressByVersion(0x5B3D60, 0x5B4020, 0x5B8530, 0x658310, 0x658360, 0x6572C0); +WRAPPER RwBool RwD3D8SetTextureStageState(RwUInt32, RwUInt32, RwUInt32) { VARJMP(RwD3D8SetTextureStageState_A); } +static uint32_t RwD3D8SetVertexShader_A = AddressByVersion(0x5BAF90, 0x5BB250, 0x5BF440, 0x65F2F0, 0x65F340, 0x65E2A0); +WRAPPER RwBool RwD3D8SetVertexShader(RwUInt32) { VARJMP(RwD3D8SetVertexShader_A); } +static uint32_t RwD3D8SetPixelShader_A = AddressByVersion(0x5BAFD0, 0x5BB290, 0x5BF4A0, 0x65F330, 0x65F380, 0x65E2E0); +WRAPPER RwBool RwD3D8SetPixelShader(RwUInt32 handle) { VARJMP(RwD3D8SetPixelShader_A); } +static uint32_t RwD3D8SetStreamSource_A = AddressByVersion(0x5BB010, 0x5BB2D0, 0x5BF500, 0x65F370, 0x65F3C0, 0x65E320); +WRAPPER RwBool RwD3D8SetStreamSource(RwUInt32, void*, RwUInt32) { VARJMP(RwD3D8SetStreamSource_A); } +static uint32_t RwD3D8SetIndices_A = AddressByVersion(0x5BB060, 0x5BB320, 0x5BF590, 0x65F3C0, 0x65F410, 0x65E370); +WRAPPER RwBool RwD3D8SetIndices(void*, RwUInt32) { VARJMP(RwD3D8SetIndices_A); } +static uint32_t RwD3D8DrawIndexedPrimitive_A = AddressByVersion(0x5BB0B0, 0x5BB370, 0x5BF610, 0x65F410, 0x65F460, 0x65E3C0); +WRAPPER RwBool RwD3D8DrawIndexedPrimitive(RwUInt32, RwUInt32, RwUInt32, RwUInt32, RwUInt32) { VARJMP(RwD3D8DrawIndexedPrimitive_A); } +static uint32_t RwD3D8DrawPrimitive_A = AddressByVersion(0x5BB140, 0x5BB400, 0x5BF6C0, 0x65F4A0, 0x65F4F0, 0x65E450); +WRAPPER RwBool RwD3D8DrawPrimitive(RwUInt32, RwUInt32, RwUInt32) { VARJMP(RwD3D8DrawPrimitive_A); } +static uint32_t RwD3D8SetSurfaceProperties_A = AddressByVersion(0x5BB490, 0x5BB750, 0x5BFB30, 0x65F7F0, 0x65F840, 0x65E7A0); +WRAPPER RwBool RwD3D8SetSurfaceProperties(const RwRGBA*, const RwSurfaceProperties*, RwBool) { VARJMP(RwD3D8SetSurfaceProperties_A); } +static uint32_t RwD3D8SetTransform_A = AddressByVersion(0x5BB1D0, 0x5BB490, 0x5BF768, 0x65F530, 0x65F580, 0x65E4E0); +WRAPPER RwBool RwD3D8SetTransform(RwUInt32 a1, const void* a2) +{ + if (gtaversion != III_STEAM) + VARJMP(RwD3D8SetTransform_A); + + __asm + { + mov edx, [esp+8] + mov eax, [esp+4] + jmp RwD3D8SetTransform_A + } +} +static uint32_t RwD3D8GetTransform_A = AddressByVersion(0x5BB310, 0x5BB5D0, 0x5BF930, 0x65F670, 0x65F6C0, 0x65E620); +WRAPPER void RwD3D8GetTransform(RwUInt32, void*) { VARJMP(RwD3D8GetTransform_A); } + +//WRAPPER RwBool RwD3D8SetLight(RwInt32, const void*) { EAXJMP(0x65FB20); } +//WRAPPER RwBool RwD3D8EnableLight(RwInt32, RwBool) { EAXJMP(0x65FC10); } + +static uint32_t rwD3D8RenderStateIsVertexAlphaEnable_A = AddressByVersion(0x5B5A50, 0x5B5D10, 0x5BA970, 0x659F60, 0x659FB0, 0x658F10); +WRAPPER RwBool rwD3D8RenderStateIsVertexAlphaEnable(void) { VARJMP(rwD3D8RenderStateIsVertexAlphaEnable_A); }; +static uint32_t rwD3D8RenderStateVertexAlphaEnable_A = AddressByVersion(0x5B57E0, 0x5B5AA0, 0x5BA840, 0x659CF0, 0x659D40, 0x658CA0); +WRAPPER void rwD3D8RenderStateVertexAlphaEnable(RwBool x) { VARJMP(rwD3D8RenderStateVertexAlphaEnable_A); }; +static uint32_t RpAtomicGetWorldBoundingSphere_A = AddressByVersion(0x59E800, 0x59EAC0, 0x59EB20, 0x640710, 0x640760, 0x63F6C0); +WRAPPER const RwSphere *RpAtomicGetWorldBoundingSphere(RpAtomic*) { VARJMP(RpAtomicGetWorldBoundingSphere_A); }; +static uint32_t RwD3D8CameraIsSphereFullyInsideFrustum_A = AddressByVersion(0x5BBC40, 0x5BBF00, 0x5C0450, 0x65FFB0, 0x660000, 0x65EF60); +WRAPPER RwBool RwD3D8CameraIsSphereFullyInsideFrustum(const void*, const void*) { VARJMP(RwD3D8CameraIsSphereFullyInsideFrustum_A); }; +static uint32_t RwD3D8CameraIsBBoxFullyInsideFrustum_A = AddressByVersion(0x5BBCA0, 0x5BBF60, 0x5C04B0, 0x660010, 0x660060, 0x65EFC0); +WRAPPER RwBool RwD3D8CameraIsBBoxFullyInsideFrustum(const void*, const void*) { VARJMP(RwD3D8CameraIsBBoxFullyInsideFrustum_A); }; + +static uint32_t RtBMPImageRead_A = AddressByVersion(0x5AFE70, 0x5B0130, 0x5B3390, 0x657870, 0x6578C0, 0x656820); +WRAPPER RwImage *RtBMPImageRead(const RwChar*) { VARJMP(RtBMPImageRead_A); } +static uint32_t RwImageFindRasterFormat_A = AddressByVersion(0x5BBF80, 0x5BC240, 0x5C0C40, 0x6602E0, 0x660330, 0x65F290); +WRAPPER RwImage *RwImageFindRasterFormat(RwImage*, RwInt32, RwInt32*, RwInt32*, RwInt32*, RwInt32*) { VARJMP(RwImageFindRasterFormat_A); } +static uint32_t RwImageDestroy_A = AddressByVersion(0x5A9180, 0x5A9440, 0x5AB6A0, 0x6512B0, 0x651300, 0x650260); +WRAPPER RwBool RwImageDestroy(RwImage*) { VARJMP(RwImageDestroy_A); } + +// ADDRESS +static uint32_t RwImageCreate_A = AddressByVersion(0x5A9120, 0, 0, 0x651250, 0, 0); +WRAPPER RwImage *RwImageCreate(RwInt32, RwInt32, RwInt32) { VARJMP(RwImageCreate_A); } +static uint32_t RwImageAllocatePixels_A = AddressByVersion(0x5A91E0, 0, 0, 0x651310, 0, 0); +WRAPPER RwImage *RwImageAllocatePixels(RwImage *) { VARJMP(RwImageAllocatePixels_A); } +static uint32_t RwStreamOpen_A = AddressByVersion(0x5A3FE0, 0, 0, 0x6459C0, 0, 0); +WRAPPER RwStream *RwStreamOpen(RwStreamType, RwStreamAccessType, const void*) { VARJMP(RwStreamOpen_A); } +static uint32_t RwStreamClose_A = AddressByVersion(0x5A3F10, 0, 0, 0x6458F0, 0, 0); +WRAPPER RwBool RwStreamClose(RwStream*, void*) { VARJMP(RwStreamClose_A); } +static uint32_t RwStreamRead_A = AddressByVersion(0x5A3AD0, 0, 0, 0x6454B0, 0, 0); +WRAPPER RwUInt32 RwStreamRead(RwStream*, void*, RwUInt32) { VARJMP(RwStreamRead_A); } +static uint32_t RwStreamSkip_A = AddressByVersion(0x5A3DF0, 0, 0, 0x6457D0, 0, 0); +WRAPPER RwStream *RwStreamSkip(RwStream*, RwUInt32) { VARJMP(RwStreamSkip_A); } +static uint32_t RwStreamFindChunk_A = AddressByVersion(0x5AA540, 0, 0, 0x64FAC0, 0, 0); +WRAPPER RwBool RwStreamFindChunk(RwStream*, RwUInt32, RwUInt32*, RwUInt32*) { VARJMP(RwStreamFindChunk_A); } +static uint32_t RwTexDictionaryStreamRead_A = AddressByVersion(0x5924A0, 0, 0, 0x61E710, 0, 0); +WRAPPER RwTexDictionary *RwTexDictionaryStreamRead(RwStream*) { VARJMP(RwTexDictionaryStreamRead_A); } + +static uint32_t RpGeometryLock_A = AddressByVersion(0, 0, 0, 0x64CCD0, 0, 0); +static uint32_t RpGeometryUnlock_A = AddressByVersion(0, 0, 0, 0x64CD00, 0, 0); +WRAPPER RpGeometry *RpGeometryLock(RpGeometry*, RwInt32) { VARJMP(RpGeometryLock_A); } +WRAPPER RpGeometry *RpGeometryUnlock(RpGeometry*) { VARJMP(RpGeometryUnlock_A); } +WRAPPER RpGeometry *RpGeometryForAllMaterials(RpGeometry*, RpMaterialCallBack, void*) { EAXJMP(0x5ACBF0); } + +static uint32_t RwIm2DGetNearScreenZ_A = AddressByVersion(0x5A43A0, 0x5A4660, 0x5A5340, 0x649B80, 0x649BD0, 0x648B30); +WRAPPER RwReal RwIm2DGetNearScreenZ(void) { VARJMP(RwIm2DGetNearScreenZ_A); } +// ADDRESS +static uint32_t RwIm2DGetFarScreenZ_A = AddressByVersion(0x5A43B0, 0, 0, 0x649B90, 0, 0); +WRAPPER RwReal RwIm2DGetFarScreenZ(void) { VARJMP(RwIm2DGetFarScreenZ_A); } +WRAPPER RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices) { EAXJMP(0x5A4430); } +static uint32_t RwIm2DRenderIndexedPrimitive_A = AddressByVersion(0x5A4440, 0x5A4700, 0x5A5440, 0x649C20, 0x649C70, 0x648BD0); +WRAPPER RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType, RwIm2DVertex*, RwInt32, RwImVertexIndex*, RwInt32) { VARJMP(RwIm2DRenderIndexedPrimitive_A); } + +WRAPPER RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex *indices, RwInt32 numIndices) { EAXJMP(0x5B6820); } +WRAPPER void *RwIm3DTransform(RwIm3DVertex *pVerts, RwUInt32 numVerts, RwMatrix *ltm, RwUInt32 flags) { EAXJMP(0x5B6720); } +WRAPPER RwBool RwIm3DEnd(void) { EAXJMP(0x5B67F0); } + +static uint32_t _rwObjectHasFrameSetFrame_A = AddressByVersion(0x5BC950, 0x5BCC10, 0x5C1820, 0x660CC0, 0x660D10, 0x65FC70); +WRAPPER void _rwObjectHasFrameSetFrame(void*, RwFrame*) { VARJMP(_rwObjectHasFrameSetFrame_A); } + +static uint32_t RxPipelineCreate_A = AddressByVersion(0x5C2E00, 0x5C30C0, 0x5C8FC0, 0x668FC0, 0x669010, 0x667F70); +WRAPPER RxPipeline *RxPipelineCreate(void) { VARJMP(RxPipelineCreate_A); } +static uint32_t RxPipelineLock_A = AddressByVersion(0x5D29F0, 0x5D2CB0, 0x5DDBF0, 0x67AC50, 0x67ACA0, 0x679C00); +WRAPPER RxLockedPipe *RxPipelineLock(RxPipeline*) { VARJMP(RxPipelineLock_A); } +static uint32_t RxLockedPipeUnlock_A = AddressByVersion(0x5D1FA0, 0x5D2260, 0x5DD0C0, 0x67A200, 0x67A250, 0x6791B0); +WRAPPER RxPipeline *RxLockedPipeUnlock(RxLockedPipe*) { VARJMP(RxLockedPipeUnlock_A); } +static uint32_t RxNodeDefinitionGetD3D8AtomicAllInOne_A = AddressByVersion(0x5DC500, 0x5DC7C0, 0x5EC470, 0x67CB90, 0x67CBE0, 0x67BB40); +WRAPPER RxNodeDefinition *RxNodeDefinitionGetD3D8AtomicAllInOne(void) { VARJMP(RxNodeDefinitionGetD3D8AtomicAllInOne_A); } +static uint32_t RxLockedPipeAddFragment_A = AddressByVersion(0x5D2BA0, 0x5D2E60, 0x5DE000, 0x67AE00, 0x67AE50, 0x679DB0); +WRAPPER RxLockedPipe *RxLockedPipeAddFragment(RxLockedPipe*, RwUInt32*, RxNodeDefinition*, ...) { VARJMP(RxLockedPipeAddFragment_A); } +static uint32_t _rxPipelineDestroy_A = AddressByVersion(0x5C2E70, 0x5C3130, 0x5C9030, 0x669030, 0x669080, 0x667FE0); +WRAPPER void _rxPipelineDestroy(RxPipeline*) { VARJMP(_rxPipelineDestroy_A); } +static uint32_t RxPipelineFindNodeByName_A = AddressByVersion(0x5D2B10, 0x5D2DD0, 0x5DDF40, 0x67AD70, 0x67ADC0, 0x679D20); +WRAPPER RxPipelineNode *RxPipelineFindNodeByName(RxPipeline*, const RwChar*, RxPipelineNode*, RwInt32*) { VARJMP(RxPipelineFindNodeByName_A); } +static uint32_t RxD3D8AllInOneSetRenderCallBack_A = AddressByVersion(0x5DFC60, 0x5DFF20, 0x5EE330, 0x678E30, 0x678E80, 0x677DE0); +WRAPPER void RxD3D8AllInOneSetRenderCallBack(RxPipelineNode*, RxD3D8AllInOneRenderCallBack) { VARJMP(RxD3D8AllInOneSetRenderCallBack_A); } +RxD3D8AllInOneRenderCallBack RxD3D8AllInOneGetRenderCallBack(RxPipelineNode *node) +{ + return *(RxD3D8AllInOneRenderCallBack*)((uint8*)node->privateData + 12); +} +void RxD3D8AllInOneSetInstanceCallBack(RxPipelineNode *node, RxD3D8AllInOneInstanceCallBack callback) +{ + *(RxD3D8AllInOneInstanceCallBack*)node->privateData = callback; +} + +static uint32_t rxD3D8DefaultRenderCallback_A = AddressByVersion(0x5DF960, 0x5DFC20, 0x5EE350, 0x678B30, 0x678B80, 0x677AE0); +WRAPPER void rxD3D8DefaultRenderCallback(RwResEntry*, void*, RwUInt8, RwUInt32) { VARJMP(rxD3D8DefaultRenderCallback_A); } +static uint32_t rwD3D8AtomicMatFXRenderCallback_A = AddressByVersion(0x5D0B80, 0x5D0E40, 0x5D8B20, 0x676460, 0x6764B0, 0x675410); + +WRAPPER RpMaterial *RpMatFXMaterialSetEffects(RpMaterial*, RpMatFXMaterialFlags) { EAXJMP(0x5B3780); } +WRAPPER RpMaterial *RpMatFXMaterialSetupEnvMap(RpMaterial*, RwTexture*, RwFrame*, RwBool, RwReal) { EAXJMP(0x5B38D0); } +WRAPPER void rwD3D8AtomicMatFXRenderCallback(RwResEntry*, void*, RwUInt8, RwUInt32) { VARJMP(rwD3D8AtomicMatFXRenderCallback_A); } +WRAPPER RpAtomic *RpMatFXAtomicEnableEffects(RpAtomic*) { EAXJMP(0x5B3750); } + +WRAPPER RpAtomic *RpAtomicSetFrame(RpAtomic*, RwFrame*) { EAXJMP(0x5A0600); } +WRAPPER RwBool RpAtomicDestroy(RpAtomic*) { EAXJMP(0x59F020); } +WRAPPER RpAtomic *RpAtomicClone(RpAtomic*) { EAXJMP(0x59F0A0); } +WRAPPER RpAtomic *RpAtomicSetGeometry(RpAtomic*, RpGeometry*, RwUInt32) { EAXJMP(0x59EFA0); } +WRAPPER RwInt32 RpAtomicRegisterPlugin(RwInt32, RwUInt32, RwPluginObjectConstructor, RwPluginObjectDestructor, RwPluginObjectCopy) { EAXJMP(0x5A0510); } +WRAPPER void _rpAtomicResyncInterpolatedSphere(RpAtomic*) { EAXJMP(0x59E6C0); } + +WRAPPER RpAtomic *AtomicDefaultRenderCallBack(RpAtomic*) { EAXJMP(0x59E690); } + +static uint32_t RpClumpForAllAtomics_A = AddressByVersion(0x59EDD0, 0x59F090, 0x59EFC0, 0x640D00, 0x640D50, 0x63FCB0); +WRAPPER RpClump *RpClumpForAllAtomics(RpClump*, RpAtomicCallBack, void*) { VARJMP(RpClumpForAllAtomics_A); } +WRAPPER RpClump *RpClumpRender(RpClump *) { EAXJMP(0x59ED80); } +WRAPPER RpClump *RpClumpClone(RpClump *) { EAXJMP(0x59F1B0); } +WRAPPER RwBool RpClumpDestroy(RpClump * clump) { EAXJMP(0x59F500); } +WRAPPER RwInt32 RpClumpRegisterPlugin(RwInt32, RwUInt32, RwPluginObjectConstructor, RwPluginObjectDestructor, RwPluginObjectCopy) { EAXJMP(0x5A0540); } +WRAPPER RpClump *RpClumpRemoveAtomic(RpClump*, RpAtomic*) { EAXJMP(0x59F6B0); } +WRAPPER RpClump *RpClumpAddAtomic(RpClump*, RpAtomic*) { EAXJMP(0x59F680); } + +WRAPPER RpLight *RpLightSetColor(RpLight *light, const RwRGBAReal *color) { EAXJMP(0x5BC320); } \ No newline at end of file diff --git a/src/templates.h b/src/templates.h new file mode 100644 index 00000000..75bfd26b --- /dev/null +++ b/src/templates.h @@ -0,0 +1,179 @@ +#pragma once + +template +class CStore +{ +public: + int allocPtr; + T store[n]; + + T *alloc(void){ + if(this->allocPtr >= n) + printf("Size of this thing:%d needs increasing\n", n); + return &this->store[this->allocPtr++]; + } + void clear(void){ + this->allocPtr = 0; + } +}; + +template +class CPool +{ + U *m_entries; + union Flags { + struct { + uint8 id : 7; + uint8 free : 1; + }; + uint8 u; + } *m_flags; + int m_size; + int m_allocPtr; + +public: + CPool(int size){ + m_entries = (U*)malloc(sizeof(U)*size); + m_flags = (Flags*)malloc(sizeof(Flags)*size); + m_size = size; + m_allocPtr = 0; + for(int i = 0; i < size; i++){ + m_flags[i].id = 0; + m_flags[i].free = 1; + } + } + int GetSize(void) { return m_size; } + T *New(void){ + bool wrapped = false; + do + if(++m_allocPtr == m_size){ + if(wrapped) + return nil; + wrapped = true; + m_allocPtr = 0; + } + while(!m_flags[m_allocPtr].free); + m_flags[m_allocPtr].free = 0; + m_flags[m_allocPtr].id++; + return (T*)&m_entries[m_allocPtr]; + } + T *New(int handle){ + T *entry = (T*)m_entries[handle>>8]; + SetNotFreeAt(handle); + return entry; + } + void SetNotFreeAt(int handle){ + int idx = handle>>8; + m_flags[idx].free = 0; + m_flags[idx].id = handle & 0x7F; + for(m_allocPtr = 0; m_allocPtr < m_size; m_allocPtr++) + if(m_flags[m_allocPtr].free) + return; + } + void Delete(T *entry){ + int i = GetJustIndex(entry); + m_flags[i].free = 1; + if(i < m_allocPtr) + m_allocPtr = i; + } + T *GetSlot(int i){ + return m_flags[i].free ? nil : (T*)&m_entries[i]; + } + T *GetAt(int handle){ + return m_flags[handle>>8].u == handle & 0xFF ? + (T*)&m_entries[handle>>8] : nil; + } + int GetIndex(T *entry){ + int i = GetJustIndex(entry); + return m_flags[i].u + (i<<8); + } + int GetJustIndex(T *entry){ + // TODO: the cast is unsafe + return (int)((U*)entry - m_entries); + } + int GetNoOfUsedSpaces(void){ + int i; + int n = 0; + for(i = 0; i < m_size; i++) + if(!m_flags[i].free) + n++; + return n; + } +}; + +template +class CLink +{ +public: + T item; + CLink *prev; + CLink *next; + + void Insert(CLink *link){ + link->next = this->next; + this->next->prev = link; + link->prev = this; + this->next = link; + } + void Remove(void){ + this->prev->next = this->next; + this->next->prev = this->prev; + } +}; + +template +class CLinkList +{ +public: + CLink head, tail; + CLink freeHead, freeTail; + CLink *links; + + void Init(int n){ + links = new CLink[n]; + head.next = &tail; + tail.prev = &head; + freeHead.next = &freeTail; + freeTail.prev = &freeHead; + while(n--) + freeHead.Insert(&links[n]); + } + // Shutdown + void Clear(void){ + while(head.next != &tail) + Remove(head.next); + } + CLink *Insert(T const &item){ + CLink *node = freeHead.next; + if(node == &freeTail) + return nil; + node->item = item; + node->Remove(); // remove from free list + head.Insert(node); + return node; + } + CLink *InsertSorted(T const &item){ + CLink *sort; + for(sort = head.next; sort != &tail; sort = sort->next) + if(sort->item.sort >= item.sort) + break; + CLink *node = freeHead.next; + if(node == &freeTail) + return nil; + node->item = item; + node->Remove(); // remove from free list + sort->prev->Insert(node); + return node; + } + void Remove(CLink *link){ + link->Remove(); // remove from list + freeHead.Insert(link); // insert into free list + } + int Count(void){ + int n = 0; + CLink *lnk; + for(lnk = head.next; lnk != &tail; lnk = lnk->next) + n++; + return n; + } +};