Repository: AxioDL/urde Branch: main Commit: 9a365f37f24b Files: 1318 Total size: 8.5 MB Directory structure: gitextract_10668kcv/ ├── .clang-format ├── .clang-tidy ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ └── build.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── NESEmulator/ │ ├── CMakeLists.txt │ ├── CNESEmulator.cpp │ ├── CNESEmulator.hpp │ ├── apu.c │ ├── malloc.h │ └── ppu.c ├── README.md ├── Runtime/ │ ├── Audio/ │ │ ├── CAudioGroupSet.cpp │ │ ├── CAudioGroupSet.hpp │ │ ├── CAudioSys.cpp │ │ ├── CAudioSys.hpp │ │ ├── CMakeLists.txt │ │ ├── CMidiManager.cpp │ │ ├── CMidiManager.hpp │ │ ├── CSfxManager.cpp │ │ ├── CSfxManager.hpp │ │ ├── CStaticAudioPlayer.cpp │ │ ├── CStaticAudioPlayer.hpp │ │ ├── CStreamAudioManager.cpp │ │ ├── CStreamAudioManager.hpp │ │ ├── SFX/ │ │ │ ├── Atomic.h │ │ │ ├── BetaBeetle.h │ │ │ ├── Bird.h │ │ │ ├── BloodFlower.h │ │ │ ├── Burrower.h │ │ │ ├── ChozoGhost.h │ │ │ ├── ChubbWeed.h │ │ │ ├── CineBoots.h │ │ │ ├── CineGeneral.h │ │ │ ├── CineGun.h │ │ │ ├── CineMorphball.h │ │ │ ├── CineSuit.h │ │ │ ├── CineVisor.h │ │ │ ├── Crater.h │ │ │ ├── Crystallite.h │ │ │ ├── Drones.h │ │ │ ├── EliteSpacePirate.h │ │ │ ├── FireFlea.h │ │ │ ├── Flaaghra.h │ │ │ ├── FlickerBat.h │ │ │ ├── FlyingPirate.h │ │ │ ├── FrontEnd.h │ │ │ ├── GagantuanBeatle.h │ │ │ ├── Gnats.h │ │ │ ├── Gryzbee.h │ │ │ ├── IceCrack.h │ │ │ ├── IceWorld.h │ │ │ ├── InjuredPirates.h │ │ │ ├── IntroBoss.h │ │ │ ├── IntroWorld.h │ │ │ ├── JellyZap.h │ │ │ ├── LavaWorld.h │ │ │ ├── Magdolite.h │ │ │ ├── Metaree.h │ │ │ ├── Metroid.h │ │ │ ├── MetroidPrime.h │ │ │ ├── MinesWorld.h │ │ │ ├── Misc.h │ │ │ ├── MiscSamus.h │ │ │ ├── OmegaPirate.h │ │ │ ├── OverWorld.h │ │ │ ├── Parasite.h │ │ │ ├── Phazon.h │ │ │ ├── PhazonGun.h │ │ │ ├── PuddleSpore.h │ │ │ ├── PuddleToad.h │ │ │ ├── Puffer.h │ │ │ ├── ReactorDoor.h │ │ │ ├── Ridley.h │ │ │ ├── Ripper.h │ │ │ ├── RuinsWorld.h │ │ │ ├── SFX.h │ │ │ ├── SamusShip.h │ │ │ ├── Scarab.h │ │ │ ├── Seedling.h │ │ │ ├── SheeGoth.h │ │ │ ├── SnakeWeed.h │ │ │ ├── Sova.h │ │ │ ├── SpacePirate.h │ │ │ ├── SpankWeed.h │ │ │ ├── Thardus.h │ │ │ ├── TheEnd.h │ │ │ ├── Torobyte.h │ │ │ ├── Triclops.h │ │ │ ├── Turret.h │ │ │ ├── UI.h │ │ │ ├── WarWasp.h │ │ │ ├── Weapons.h │ │ │ ├── ZZZ.h │ │ │ ├── Zoomer.h │ │ │ ├── lumigek.h │ │ │ └── test.h │ │ ├── g721.c │ │ └── g721.h │ ├── AutoMapper/ │ │ ├── CAutoMapper.cpp │ │ ├── CAutoMapper.hpp │ │ ├── CMakeLists.txt │ │ ├── CMapArea.cpp │ │ ├── CMapArea.hpp │ │ ├── CMapUniverse.cpp │ │ ├── CMapUniverse.hpp │ │ ├── CMapWorld.cpp │ │ ├── CMapWorld.hpp │ │ ├── CMapWorldInfo.cpp │ │ ├── CMapWorldInfo.hpp │ │ ├── CMappableObject.cpp │ │ └── CMappableObject.hpp │ ├── CArchitectureMessage.hpp │ ├── CArchitectureQueue.hpp │ ├── CBasics.hpp │ ├── CBasicsPC.cpp │ ├── CCRC32.cpp │ ├── CCRC32.hpp │ ├── CDependencyGroup.cpp │ ├── CDependencyGroup.hpp │ ├── CDvdFile.cpp │ ├── CDvdFile.hpp │ ├── CDvdRequest.hpp │ ├── CFactoryMgr.cpp │ ├── CFactoryMgr.hpp │ ├── CGameAllocator.cpp │ ├── CGameAllocator.hpp │ ├── CGameDebug.hpp │ ├── CGameHintInfo.cpp │ ├── CGameHintInfo.hpp │ ├── CGameOptions.cpp │ ├── CGameOptions.hpp │ ├── CGameState.cpp │ ├── CGameState.hpp │ ├── CIOWin.hpp │ ├── CIOWinManager.cpp │ ├── CIOWinManager.hpp │ ├── CInGameTweakManagerBase.hpp │ ├── CInfiniteLoopDetector.cpp │ ├── CInfiniteLoopDetector.hpp │ ├── CMFGameBase.hpp │ ├── CMain.cpp │ ├── CMainFlowBase.cpp │ ├── CMainFlowBase.hpp │ ├── CMakeLists.txt │ ├── CMayaSpline.cpp │ ├── CMayaSpline.hpp │ ├── CMemoryCardSys.cpp │ ├── CMemoryCardSys.hpp │ ├── CMemoryCardSysNix.cpp │ ├── CMemoryCardSysOSX.cpp │ ├── CMemoryCardSysWin.cpp │ ├── CObjectList.cpp │ ├── CObjectList.hpp │ ├── CPakFile.cpp │ ├── CPakFile.hpp │ ├── CPlayerState.cpp │ ├── CPlayerState.hpp │ ├── CRandom16.cpp │ ├── CRandom16.hpp │ ├── CResFactory.cpp │ ├── CResFactory.hpp │ ├── CResLoader.cpp │ ├── CResLoader.hpp │ ├── CResourceNameDatabase.cpp │ ├── CResourceNameDatabase.hpp │ ├── CScannableObjectInfo.cpp │ ├── CScannableObjectInfo.hpp │ ├── CScriptMailbox.cpp │ ├── CScriptMailbox.hpp │ ├── CSimplePool.cpp │ ├── CSimplePool.hpp │ ├── CSortedLists.cpp │ ├── CSortedLists.hpp │ ├── CStateManager.cpp │ ├── CStateManager.hpp │ ├── CStaticInterference.cpp │ ├── CStaticInterference.hpp │ ├── CStopwatch.cpp │ ├── CStopwatch.hpp │ ├── CStringExtras.cpp │ ├── CStringExtras.hpp │ ├── CTextureCache.cpp │ ├── CTextureCache.hpp │ ├── CTimeProvider.cpp │ ├── CTimeProvider.hpp │ ├── CToken.cpp │ ├── CToken.hpp │ ├── CWorldSaveGameInfo.cpp │ ├── CWorldSaveGameInfo.hpp │ ├── Camera/ │ │ ├── CBallCamera.cpp │ │ ├── CBallCamera.hpp │ │ ├── CCameraFilter.cpp │ │ ├── CCameraFilter.hpp │ │ ├── CCameraManager.cpp │ │ ├── CCameraManager.hpp │ │ ├── CCameraShakeData.cpp │ │ ├── CCameraShakeData.hpp │ │ ├── CCameraSpline.cpp │ │ ├── CCameraSpline.hpp │ │ ├── CCinematicCamera.cpp │ │ ├── CCinematicCamera.hpp │ │ ├── CFirstPersonCamera.cpp │ │ ├── CFirstPersonCamera.hpp │ │ ├── CGameCamera.cpp │ │ ├── CGameCamera.hpp │ │ ├── CInterpolationCamera.cpp │ │ ├── CInterpolationCamera.hpp │ │ ├── CMakeLists.txt │ │ ├── CPathCamera.cpp │ │ └── CPathCamera.hpp │ ├── Character/ │ │ ├── CActorLights.cpp │ │ ├── CActorLights.hpp │ │ ├── CAdditiveAnimPlayback.cpp │ │ ├── CAdditiveAnimPlayback.hpp │ │ ├── CAdditiveBodyState.cpp │ │ ├── CAdditiveBodyState.hpp │ │ ├── CAllFormatsAnimSource.cpp │ │ ├── CAllFormatsAnimSource.hpp │ │ ├── CAnimCharacterSet.cpp │ │ ├── CAnimCharacterSet.hpp │ │ ├── CAnimData.cpp │ │ ├── CAnimData.hpp │ │ ├── CAnimPOIData.cpp │ │ ├── CAnimPOIData.hpp │ │ ├── CAnimPerSegmentData.hpp │ │ ├── CAnimPlaybackParms.hpp │ │ ├── CAnimSource.cpp │ │ ├── CAnimSource.hpp │ │ ├── CAnimSourceReader.cpp │ │ ├── CAnimSourceReader.hpp │ │ ├── CAnimSysContext.hpp │ │ ├── CAnimTreeAnimReaderContainer.cpp │ │ ├── CAnimTreeAnimReaderContainer.hpp │ │ ├── CAnimTreeBlend.cpp │ │ ├── CAnimTreeBlend.hpp │ │ ├── CAnimTreeDoubleChild.cpp │ │ ├── CAnimTreeDoubleChild.hpp │ │ ├── CAnimTreeLoopIn.cpp │ │ ├── CAnimTreeLoopIn.hpp │ │ ├── CAnimTreeNode.cpp │ │ ├── CAnimTreeNode.hpp │ │ ├── CAnimTreeSequence.cpp │ │ ├── CAnimTreeSequence.hpp │ │ ├── CAnimTreeSingleChild.cpp │ │ ├── CAnimTreeSingleChild.hpp │ │ ├── CAnimTreeTimeScale.cpp │ │ ├── CAnimTreeTimeScale.hpp │ │ ├── CAnimTreeTransition.cpp │ │ ├── CAnimTreeTransition.hpp │ │ ├── CAnimTreeTweenBase.cpp │ │ ├── CAnimTreeTweenBase.hpp │ │ ├── CAnimation.cpp │ │ ├── CAnimation.hpp │ │ ├── CAnimationDatabase.hpp │ │ ├── CAnimationDatabaseGame.cpp │ │ ├── CAnimationDatabaseGame.hpp │ │ ├── CAnimationManager.cpp │ │ ├── CAnimationManager.hpp │ │ ├── CAnimationSet.cpp │ │ ├── CAnimationSet.hpp │ │ ├── CAssetFactory.cpp │ │ ├── CAssetFactory.hpp │ │ ├── CBodyController.cpp │ │ ├── CBodyController.hpp │ │ ├── CBodyState.cpp │ │ ├── CBodyState.hpp │ │ ├── CBodyStateCmdMgr.cpp │ │ ├── CBodyStateCmdMgr.hpp │ │ ├── CBodyStateInfo.cpp │ │ ├── CBodyStateInfo.hpp │ │ ├── CBoneTracking.cpp │ │ ├── CBoneTracking.hpp │ │ ├── CBoolPOINode.cpp │ │ ├── CBoolPOINode.hpp │ │ ├── CCharAnimTime.cpp │ │ ├── CCharAnimTime.hpp │ │ ├── CCharLayoutInfo.cpp │ │ ├── CCharLayoutInfo.hpp │ │ ├── CCharacterFactory.cpp │ │ ├── CCharacterFactory.hpp │ │ ├── CCharacterInfo.cpp │ │ ├── CCharacterInfo.hpp │ │ ├── CCharacterSet.cpp │ │ ├── CCharacterSet.hpp │ │ ├── CEffectComponent.cpp │ │ ├── CEffectComponent.hpp │ │ ├── CFBStreamedAnimReader.cpp │ │ ├── CFBStreamedAnimReader.hpp │ │ ├── CFBStreamedCompression.cpp │ │ ├── CFBStreamedCompression.hpp │ │ ├── CGroundMovement.cpp │ │ ├── CGroundMovement.hpp │ │ ├── CHalfTransition.cpp │ │ ├── CHalfTransition.hpp │ │ ├── CHierarchyPoseBuilder.cpp │ │ ├── CHierarchyPoseBuilder.hpp │ │ ├── CIkChain.cpp │ │ ├── CIkChain.hpp │ │ ├── CInt32POINode.cpp │ │ ├── CInt32POINode.hpp │ │ ├── CLayoutDescription.hpp │ │ ├── CMakeLists.txt │ │ ├── CMetaAnimBlend.cpp │ │ ├── CMetaAnimBlend.hpp │ │ ├── CMetaAnimFactory.cpp │ │ ├── CMetaAnimFactory.hpp │ │ ├── CMetaAnimPhaseBlend.cpp │ │ ├── CMetaAnimPhaseBlend.hpp │ │ ├── CMetaAnimPlay.cpp │ │ ├── CMetaAnimPlay.hpp │ │ ├── CMetaAnimRandom.cpp │ │ ├── CMetaAnimRandom.hpp │ │ ├── CMetaAnimSequence.cpp │ │ ├── CMetaAnimSequence.hpp │ │ ├── CMetaTransFactory.cpp │ │ ├── CMetaTransFactory.hpp │ │ ├── CMetaTransMetaAnim.cpp │ │ ├── CMetaTransMetaAnim.hpp │ │ ├── CMetaTransPhaseTrans.cpp │ │ ├── CMetaTransPhaseTrans.hpp │ │ ├── CMetaTransSnap.cpp │ │ ├── CMetaTransSnap.hpp │ │ ├── CMetaTransTrans.cpp │ │ ├── CMetaTransTrans.hpp │ │ ├── CModelData.cpp │ │ ├── CModelData.hpp │ │ ├── CPASAnimInfo.cpp │ │ ├── CPASAnimInfo.hpp │ │ ├── CPASAnimParm.hpp │ │ ├── CPASAnimParmData.cpp │ │ ├── CPASAnimParmData.hpp │ │ ├── CPASAnimState.cpp │ │ ├── CPASAnimState.hpp │ │ ├── CPASDatabase.cpp │ │ ├── CPASDatabase.hpp │ │ ├── CPASParmInfo.cpp │ │ ├── CPASParmInfo.hpp │ │ ├── CPOINode.cpp │ │ ├── CPOINode.hpp │ │ ├── CParticleData.cpp │ │ ├── CParticleData.hpp │ │ ├── CParticleDatabase.cpp │ │ ├── CParticleDatabase.hpp │ │ ├── CParticleGenInfo.cpp │ │ ├── CParticleGenInfo.hpp │ │ ├── CParticlePOINode.cpp │ │ ├── CParticlePOINode.hpp │ │ ├── CPoseAsTransforms.cpp │ │ ├── CPoseAsTransforms.hpp │ │ ├── CPrimitive.cpp │ │ ├── CPrimitive.hpp │ │ ├── CRagDoll.cpp │ │ ├── CRagDoll.hpp │ │ ├── CSegId.hpp │ │ ├── CSegIdList.cpp │ │ ├── CSegIdList.hpp │ │ ├── CSegStatementSet.cpp │ │ ├── CSegStatementSet.hpp │ │ ├── CSequenceHelper.cpp │ │ ├── CSequenceHelper.hpp │ │ ├── CSkinRules.cpp │ │ ├── CSkinRules.hpp │ │ ├── CSoundPOINode.cpp │ │ ├── CSoundPOINode.hpp │ │ ├── CSteeringBehaviors.cpp │ │ ├── CSteeringBehaviors.hpp │ │ ├── CTimeScaleFunctions.cpp │ │ ├── CTimeScaleFunctions.hpp │ │ ├── CTransition.cpp │ │ ├── CTransition.hpp │ │ ├── CTransitionDatabase.hpp │ │ ├── CTransitionDatabaseGame.cpp │ │ ├── CTransitionDatabaseGame.hpp │ │ ├── CTransitionManager.cpp │ │ ├── CTransitionManager.hpp │ │ ├── CTreeUtils.cpp │ │ ├── CTreeUtils.hpp │ │ ├── CharacterCommon.cpp │ │ ├── CharacterCommon.hpp │ │ ├── IAnimReader.cpp │ │ ├── IAnimReader.hpp │ │ ├── IMetaAnim.cpp │ │ ├── IMetaAnim.hpp │ │ ├── IMetaTrans.hpp │ │ ├── IVaryingAnimationTimeScale.hpp │ │ └── TSegIdMap.hpp │ ├── Collision/ │ │ ├── CAABoxFilter.cpp │ │ ├── CAABoxFilter.hpp │ │ ├── CAreaOctTree.cpp │ │ ├── CAreaOctTree.hpp │ │ ├── CBallFilter.cpp │ │ ├── CBallFilter.hpp │ │ ├── CCollidableAABox.cpp │ │ ├── CCollidableAABox.hpp │ │ ├── CCollidableCollisionSurface.cpp │ │ ├── CCollidableCollisionSurface.hpp │ │ ├── CCollidableOBBTree.cpp │ │ ├── CCollidableOBBTree.hpp │ │ ├── CCollidableOBBTreeGroup.cpp │ │ ├── CCollidableOBBTreeGroup.hpp │ │ ├── CCollidableSphere.cpp │ │ ├── CCollidableSphere.hpp │ │ ├── CCollisionActor.cpp │ │ ├── CCollisionActor.hpp │ │ ├── CCollisionActorManager.cpp │ │ ├── CCollisionActorManager.hpp │ │ ├── CCollisionEdge.cpp │ │ ├── CCollisionEdge.hpp │ │ ├── CCollisionInfo.cpp │ │ ├── CCollisionInfo.hpp │ │ ├── CCollisionInfoList.hpp │ │ ├── CCollisionPrimitive.cpp │ │ ├── CCollisionPrimitive.hpp │ │ ├── CCollisionResponseData.cpp │ │ ├── CCollisionResponseData.hpp │ │ ├── CCollisionSurface.cpp │ │ ├── CCollisionSurface.hpp │ │ ├── CGameCollision.cpp │ │ ├── CGameCollision.hpp │ │ ├── CInternalRayCastStructure.hpp │ │ ├── CJointCollisionDescription.cpp │ │ ├── CJointCollisionDescription.hpp │ │ ├── CMakeLists.txt │ │ ├── CMaterialFilter.cpp │ │ ├── CMaterialFilter.hpp │ │ ├── CMaterialList.hpp │ │ ├── CMetroidAreaCollider.cpp │ │ ├── CMetroidAreaCollider.hpp │ │ ├── COBBTree.cpp │ │ ├── COBBTree.hpp │ │ ├── CRayCastResult.cpp │ │ ├── CRayCastResult.hpp │ │ ├── CollisionUtil.cpp │ │ ├── CollisionUtil.hpp │ │ ├── ICollisionFilter.hpp │ │ ├── InternalColliders.cpp │ │ └── InternalColliders.hpp │ ├── ConsoleVariables/ │ │ ├── CVar.cpp │ │ ├── CVar.hpp │ │ ├── CVarCommons.cpp │ │ ├── CVarCommons.hpp │ │ ├── CVarManager.cpp │ │ ├── CVarManager.hpp │ │ ├── FileStoreManager.cpp │ │ └── FileStoreManager.hpp │ ├── Flags.hpp │ ├── Formatting.hpp │ ├── GCNTypes.hpp │ ├── GameGlobalObjects.cpp │ ├── GameGlobalObjects.hpp │ ├── GameObjectLists.cpp │ ├── GameObjectLists.hpp │ ├── Graphics/ │ │ ├── CCubeMaterial.cpp │ │ ├── CCubeMaterial.hpp │ │ ├── CCubeModel.cpp │ │ ├── CCubeModel.hpp │ │ ├── CCubeRenderer.cpp │ │ ├── CCubeRenderer.hpp │ │ ├── CCubeSurface.cpp │ │ ├── CCubeSurface.hpp │ │ ├── CDrawable.hpp │ │ ├── CDrawablePlaneObject.hpp │ │ ├── CFont.cpp │ │ ├── CFont.hpp │ │ ├── CGX.cpp │ │ ├── CGX.hpp │ │ ├── CGraphics.cpp │ │ ├── CGraphics.hpp │ │ ├── CGraphicsPalette.cpp │ │ ├── CGraphicsPalette.hpp │ │ ├── CLight.cpp │ │ ├── CLight.hpp │ │ ├── CMakeLists.txt │ │ ├── CMetroidModelInstance.cpp │ │ ├── CMetroidModelInstance.hpp │ │ ├── CModel.cpp │ │ ├── CModel.hpp │ │ ├── CMoviePlayer.cpp │ │ ├── CMoviePlayer.hpp │ │ ├── CPVSAreaSet.cpp │ │ ├── CPVSAreaSet.hpp │ │ ├── CPVSVisOctree.cpp │ │ ├── CPVSVisOctree.hpp │ │ ├── CPVSVisSet.cpp │ │ ├── CPVSVisSet.hpp │ │ ├── CRainSplashGenerator.cpp │ │ ├── CRainSplashGenerator.hpp │ │ ├── CSimpleShadow.cpp │ │ ├── CSimpleShadow.hpp │ │ ├── CSkinnedModel.cpp │ │ ├── CSkinnedModel.hpp │ │ ├── CTevCombiners.cpp │ │ ├── CTevCombiners.hpp │ │ ├── CTexture.cpp │ │ ├── CTexture.hpp │ │ ├── CVertexMorphEffect.cpp │ │ ├── CVertexMorphEffect.hpp │ │ ├── GX.hpp │ │ ├── IRenderer.hpp │ │ ├── IWeaponRenderer.cpp │ │ └── IWeaponRenderer.hpp │ ├── GuiSys/ │ │ ├── CAuiEnergyBarT01.cpp │ │ ├── CAuiEnergyBarT01.hpp │ │ ├── CAuiImagePane.cpp │ │ ├── CAuiImagePane.hpp │ │ ├── CAuiMeter.cpp │ │ ├── CAuiMeter.hpp │ │ ├── CCompoundTargetReticle.cpp │ │ ├── CCompoundTargetReticle.hpp │ │ ├── CConsoleOutputWindow.cpp │ │ ├── CConsoleOutputWindow.hpp │ │ ├── CDrawStringOptions.hpp │ │ ├── CErrorOutputWindow.cpp │ │ ├── CErrorOutputWindow.hpp │ │ ├── CFontImageDef.cpp │ │ ├── CFontImageDef.hpp │ │ ├── CFontRenderState.cpp │ │ ├── CFontRenderState.hpp │ │ ├── CGuiCamera.cpp │ │ ├── CGuiCamera.hpp │ │ ├── CGuiCompoundWidget.cpp │ │ ├── CGuiCompoundWidget.hpp │ │ ├── CGuiFrame.cpp │ │ ├── CGuiFrame.hpp │ │ ├── CGuiGroup.cpp │ │ ├── CGuiGroup.hpp │ │ ├── CGuiHeadWidget.cpp │ │ ├── CGuiHeadWidget.hpp │ │ ├── CGuiLight.cpp │ │ ├── CGuiLight.hpp │ │ ├── CGuiModel.cpp │ │ ├── CGuiModel.hpp │ │ ├── CGuiObject.cpp │ │ ├── CGuiObject.hpp │ │ ├── CGuiPane.cpp │ │ ├── CGuiPane.hpp │ │ ├── CGuiSliderGroup.cpp │ │ ├── CGuiSliderGroup.hpp │ │ ├── CGuiSys.cpp │ │ ├── CGuiSys.hpp │ │ ├── CGuiTableGroup.cpp │ │ ├── CGuiTableGroup.hpp │ │ ├── CGuiTextPane.cpp │ │ ├── CGuiTextPane.hpp │ │ ├── CGuiTextSupport.cpp │ │ ├── CGuiTextSupport.hpp │ │ ├── CGuiWidget.cpp │ │ ├── CGuiWidget.hpp │ │ ├── CGuiWidgetDrawParms.hpp │ │ ├── CGuiWidgetIdDB.cpp │ │ ├── CGuiWidgetIdDB.hpp │ │ ├── CHudBallInterface.cpp │ │ ├── CHudBallInterface.hpp │ │ ├── CHudBossEnergyInterface.cpp │ │ ├── CHudBossEnergyInterface.hpp │ │ ├── CHudDecoInterface.cpp │ │ ├── CHudDecoInterface.hpp │ │ ├── CHudEnergyInterface.cpp │ │ ├── CHudEnergyInterface.hpp │ │ ├── CHudFreeLookInterface.cpp │ │ ├── CHudFreeLookInterface.hpp │ │ ├── CHudHelmetInterface.cpp │ │ ├── CHudHelmetInterface.hpp │ │ ├── CHudInterface.hpp │ │ ├── CHudMissileInterface.cpp │ │ ├── CHudMissileInterface.hpp │ │ ├── CHudRadarInterface.cpp │ │ ├── CHudRadarInterface.hpp │ │ ├── CHudThreatInterface.cpp │ │ ├── CHudThreatInterface.hpp │ │ ├── CHudVisorBeamMenu.cpp │ │ ├── CHudVisorBeamMenu.hpp │ │ ├── CInstruction.cpp │ │ ├── CInstruction.hpp │ │ ├── CMakeLists.txt │ │ ├── COrbitPointMarker.cpp │ │ ├── COrbitPointMarker.hpp │ │ ├── CRasterFont.cpp │ │ ├── CRasterFont.hpp │ │ ├── CSaveableState.cpp │ │ ├── CSaveableState.hpp │ │ ├── CScanDisplay.cpp │ │ ├── CScanDisplay.hpp │ │ ├── CSplashScreen.cpp │ │ ├── CSplashScreen.hpp │ │ ├── CStringTable.cpp │ │ ├── CStringTable.hpp │ │ ├── CTargetingManager.cpp │ │ ├── CTargetingManager.hpp │ │ ├── CTextExecuteBuffer.cpp │ │ ├── CTextExecuteBuffer.hpp │ │ ├── CTextParser.cpp │ │ ├── CTextParser.hpp │ │ ├── CTextRenderBuffer.cpp │ │ ├── CTextRenderBuffer.hpp │ │ ├── CWordBreakTables.cpp │ │ └── CWordBreakTables.hpp │ ├── IFactory.hpp │ ├── IMain.hpp │ ├── IObj.hpp │ ├── IObjFactory.hpp │ ├── IObjectStore.hpp │ ├── IRuntimeMain.hpp │ ├── IVParamObj.hpp │ ├── ImGuiConsole.cpp │ ├── ImGuiConsole.hpp │ ├── ImGuiControllerConfig.cpp │ ├── ImGuiControllerConfig.hpp │ ├── ImGuiEntitySupport.cpp │ ├── ImGuiEntitySupport.hpp │ ├── ImGuiPlayerLoadouts.cpp │ ├── ImGuiPlayerLoadouts.hpp │ ├── Input/ │ │ ├── CControllerAxis.hpp │ │ ├── CControllerButton.hpp │ │ ├── CControllerGamepadData.cpp │ │ ├── CControllerGamepadData.hpp │ │ ├── CDolphinController.cpp │ │ ├── CDolphinController.hpp │ │ ├── CFinalInput.cpp │ │ ├── CFinalInput.hpp │ │ ├── CInputGenerator.cpp │ │ ├── CInputGenerator.cpp.old │ │ ├── CInputGenerator.hpp │ │ ├── CInputGenerator.hpp.old │ │ ├── CKeyboardMouseController.hpp │ │ ├── CMakeLists.txt │ │ ├── CRumbleGenerator.cpp │ │ ├── CRumbleGenerator.hpp │ │ ├── CRumbleManager.cpp │ │ ├── CRumbleManager.hpp │ │ ├── CRumbleVoice.cpp │ │ ├── CRumbleVoice.hpp │ │ ├── ControlMapper.cpp │ │ ├── ControlMapper.hpp │ │ ├── DolphinIController.cpp │ │ ├── IController.hpp │ │ ├── InputTypes.hpp │ │ ├── RumbleFxTable.cpp │ │ └── RumbleFxTable.hpp │ ├── Logging.hpp │ ├── MP1/ │ │ ├── CArtifactDoll.cpp │ │ ├── CArtifactDoll.hpp │ │ ├── CAudioStateWin.cpp │ │ ├── CAudioStateWin.hpp │ │ ├── CAutoSave.cpp │ │ ├── CAutoSave.hpp │ │ ├── CCredits.cpp │ │ ├── CCredits.hpp │ │ ├── CFaceplateDecoration.cpp │ │ ├── CFaceplateDecoration.hpp │ │ ├── CFrontEndUI.cpp │ │ ├── CFrontEndUI.hpp │ │ ├── CGBASupport.cpp │ │ ├── CGBASupport.hpp │ │ ├── CGameCubeDoll.cpp │ │ ├── CGameCubeDoll.hpp │ │ ├── CInGameGuiManager.cpp │ │ ├── CInGameGuiManager.hpp │ │ ├── CInGameGuiManagerCommon.hpp │ │ ├── CInGameTweakManager.hpp │ │ ├── CInventoryScreen.cpp │ │ ├── CInventoryScreen.hpp │ │ ├── CLogBookScreen.cpp │ │ ├── CLogBookScreen.hpp │ │ ├── CMFGame.cpp │ │ ├── CMFGame.hpp │ │ ├── CMainFlow.cpp │ │ ├── CMainFlow.hpp │ │ ├── CMakeLists.txt │ │ ├── CMemoryCardDriver.cpp │ │ ├── CMemoryCardDriver.hpp │ │ ├── CMessageScreen.cpp │ │ ├── CMessageScreen.hpp │ │ ├── COptionsScreen.cpp │ │ ├── COptionsScreen.hpp │ │ ├── CPauseScreen.cpp │ │ ├── CPauseScreen.hpp │ │ ├── CPauseScreenBase.cpp │ │ ├── CPauseScreenBase.hpp │ │ ├── CPauseScreenBlur.cpp │ │ ├── CPauseScreenBlur.hpp │ │ ├── CPlayMovie.cpp │ │ ├── CPlayMovie.hpp │ │ ├── CPlayerVisor.cpp │ │ ├── CPlayerVisor.hpp │ │ ├── CPreFrontEnd.cpp │ │ ├── CPreFrontEnd.hpp │ │ ├── CQuitGameScreen.cpp │ │ ├── CQuitGameScreen.hpp │ │ ├── CSamusDoll.cpp │ │ ├── CSamusDoll.hpp │ │ ├── CSamusFaceReflection.cpp │ │ ├── CSamusFaceReflection.hpp │ │ ├── CSamusHud.cpp │ │ ├── CSamusHud.hpp │ │ ├── CSaveGameScreen.cpp │ │ ├── CSaveGameScreen.hpp │ │ ├── CSlideShow.cpp │ │ ├── CSlideShow.hpp │ │ ├── CSplashScreen.cpp │ │ ├── CSplashScreen.hpp │ │ ├── CStateSetterFlow.cpp │ │ ├── CStateSetterFlow.hpp │ │ ├── CTweaks.cpp │ │ ├── CTweaks.hpp │ │ ├── MP1.cpp │ │ ├── MP1.hpp │ │ ├── Tweaks/ │ │ │ ├── CTweakAutoMapper.cpp │ │ │ ├── CTweakAutoMapper.hpp │ │ │ ├── CTweakBall.cpp │ │ │ ├── CTweakBall.hpp │ │ │ ├── CTweakGame.cpp │ │ │ ├── CTweakGame.hpp │ │ │ ├── CTweakGui.cpp │ │ │ ├── CTweakGui.hpp │ │ │ ├── CTweakGuiColors.cpp │ │ │ ├── CTweakGuiColors.hpp │ │ │ ├── CTweakGunRes.cpp │ │ │ ├── CTweakGunRes.hpp │ │ │ ├── CTweakParticle.cpp │ │ │ ├── CTweakParticle.hpp │ │ │ ├── CTweakPlayer.cpp │ │ │ ├── CTweakPlayer.hpp │ │ │ ├── CTweakPlayerControl.cpp │ │ │ ├── CTweakPlayerControl.hpp │ │ │ ├── CTweakPlayerGun.cpp │ │ │ ├── CTweakPlayerGun.hpp │ │ │ ├── CTweakPlayerRes.cpp │ │ │ ├── CTweakPlayerRes.hpp │ │ │ ├── CTweakSlideShow.cpp │ │ │ ├── CTweakSlideShow.hpp │ │ │ ├── CTweakTargeting.cpp │ │ │ └── CTweakTargeting.hpp │ │ └── World/ │ │ ├── CAtomicAlpha.cpp │ │ ├── CAtomicAlpha.hpp │ │ ├── CAtomicBeta.cpp │ │ ├── CAtomicBeta.hpp │ │ ├── CBabygoth.cpp │ │ ├── CBabygoth.hpp │ │ ├── CBeetle.cpp │ │ ├── CBeetle.hpp │ │ ├── CBloodFlower.cpp │ │ ├── CBloodFlower.hpp │ │ ├── CBouncyGrenade.cpp │ │ ├── CBouncyGrenade.hpp │ │ ├── CBurrower.cpp │ │ ├── CBurrower.hpp │ │ ├── CChozoGhost.cpp │ │ ├── CChozoGhost.hpp │ │ ├── CDrone.cpp │ │ ├── CDrone.hpp │ │ ├── CDroneLaser.cpp │ │ ├── CDroneLaser.hpp │ │ ├── CElitePirate.cpp │ │ ├── CElitePirate.hpp │ │ ├── CEnergyBall.cpp │ │ ├── CEnergyBall.hpp │ │ ├── CEyeball.cpp │ │ ├── CEyeball.hpp │ │ ├── CFireFlea.cpp │ │ ├── CFireFlea.hpp │ │ ├── CFlaahgra.cpp │ │ ├── CFlaahgra.hpp │ │ ├── CFlaahgraProjectile.cpp │ │ ├── CFlaahgraProjectile.hpp │ │ ├── CFlaahgraTentacle.cpp │ │ ├── CFlaahgraTentacle.hpp │ │ ├── CFlickerBat.cpp │ │ ├── CFlickerBat.hpp │ │ ├── CFlyingPirate.cpp │ │ ├── CFlyingPirate.hpp │ │ ├── CGrenadeLauncher.cpp │ │ ├── CGrenadeLauncher.hpp │ │ ├── CIceAttackProjectile.cpp │ │ ├── CIceAttackProjectile.hpp │ │ ├── CIceSheegoth.cpp │ │ ├── CIceSheegoth.hpp │ │ ├── CJellyZap.cpp │ │ ├── CJellyZap.hpp │ │ ├── CMagdolite.cpp │ │ ├── CMagdolite.hpp │ │ ├── CMakeLists.txt │ │ ├── CMetaree.cpp │ │ ├── CMetaree.hpp │ │ ├── CMetroid.cpp │ │ ├── CMetroid.hpp │ │ ├── CMetroidBeta.cpp │ │ ├── CMetroidBeta.hpp │ │ ├── CMetroidPrime.cpp │ │ ├── CMetroidPrime.hpp │ │ ├── CMetroidPrimeProjectile.cpp │ │ ├── CMetroidPrimeProjectile.hpp │ │ ├── CMetroidPrimeRelay.cpp │ │ ├── CMetroidPrimeRelay.hpp │ │ ├── CMetroidPrimeStage2.cpp │ │ ├── CMetroidPrimeStage2.hpp │ │ ├── CNewIntroBoss.cpp │ │ ├── CNewIntroBoss.hpp │ │ ├── COmegaPirate.cpp │ │ ├── COmegaPirate.hpp │ │ ├── CParasite.cpp │ │ ├── CParasite.hpp │ │ ├── CPhazonHealingNodule.cpp │ │ ├── CPhazonHealingNodule.hpp │ │ ├── CPhazonPool.cpp │ │ ├── CPhazonPool.hpp │ │ ├── CPuddleSpore.cpp │ │ ├── CPuddleSpore.hpp │ │ ├── CPuddleToadGamma.cpp │ │ ├── CPuddleToadGamma.hpp │ │ ├── CPuffer.cpp │ │ ├── CPuffer.hpp │ │ ├── CRidley.cpp │ │ ├── CRidley.hpp │ │ ├── CRipper.cpp │ │ ├── CRipper.hpp │ │ ├── CScriptContraption.cpp │ │ ├── CScriptContraption.hpp │ │ ├── CSeedling.cpp │ │ ├── CSeedling.hpp │ │ ├── CShockWave.cpp │ │ ├── CShockWave.hpp │ │ ├── CSpacePirate.cpp │ │ ├── CSpacePirate.hpp │ │ ├── CSpankWeed.cpp │ │ ├── CSpankWeed.hpp │ │ ├── CThardus.cpp │ │ ├── CThardus.hpp │ │ ├── CThardusRockProjectile.cpp │ │ ├── CThardusRockProjectile.hpp │ │ ├── CTryclops.cpp │ │ ├── CTryclops.hpp │ │ ├── CWarWasp.cpp │ │ └── CWarWasp.hpp │ ├── MP2/ │ │ └── CMakeLists.txt │ ├── MP3/ │ │ └── CMakeLists.txt │ ├── Memory/ │ │ ├── CCircularBuffer.cpp │ │ └── CCircularBuffer.hpp │ ├── MkCastTo.py │ ├── Particle/ │ │ ├── CColorElement.cpp │ │ ├── CColorElement.hpp │ │ ├── CDecal.cpp │ │ ├── CDecal.hpp │ │ ├── CDecalDataFactory.cpp │ │ ├── CDecalDataFactory.hpp │ │ ├── CDecalDescription.hpp │ │ ├── CDecalManager.cpp │ │ ├── CDecalManager.hpp │ │ ├── CElectricDescription.hpp │ │ ├── CElementGen.cpp │ │ ├── CElementGen.hpp │ │ ├── CEmitterElement.cpp │ │ ├── CEmitterElement.hpp │ │ ├── CFlameWarp.cpp │ │ ├── CFlameWarp.hpp │ │ ├── CGenDescription.hpp │ │ ├── CIntElement.cpp │ │ ├── CIntElement.hpp │ │ ├── CMakeLists.txt │ │ ├── CModVectorElement.cpp │ │ ├── CModVectorElement.hpp │ │ ├── CParticleDataFactory.cpp │ │ ├── CParticleDataFactory.hpp │ │ ├── CParticleElectric.cpp │ │ ├── CParticleElectric.hpp │ │ ├── CParticleElectricDataFactory.cpp │ │ ├── CParticleElectricDataFactory.hpp │ │ ├── CParticleGen.cpp │ │ ├── CParticleGen.hpp │ │ ├── CParticleGlobals.cpp │ │ ├── CParticleGlobals.hpp │ │ ├── CParticleSwoosh.cpp │ │ ├── CParticleSwoosh.hpp │ │ ├── CParticleSwooshDataFactory.cpp │ │ ├── CParticleSwooshDataFactory.hpp │ │ ├── CProjectileWeaponDataFactory.cpp │ │ ├── CProjectileWeaponDataFactory.hpp │ │ ├── CRealElement.cpp │ │ ├── CRealElement.hpp │ │ ├── CSpawnSystemKeyframeData.cpp │ │ ├── CSpawnSystemKeyframeData.hpp │ │ ├── CSwooshDescription.hpp │ │ ├── CUVElement.cpp │ │ ├── CUVElement.hpp │ │ ├── CVectorElement.cpp │ │ ├── CVectorElement.hpp │ │ ├── CWarp.hpp │ │ ├── CWeaponDescription.hpp │ │ └── IElement.hpp │ ├── RetroTypes.cpp │ ├── RetroTypes.hpp │ ├── Streams/ │ │ ├── CFileOutStream.cpp │ │ ├── CFileOutStream.hpp │ │ ├── CInputStream.cpp │ │ ├── CInputStream.hpp │ │ ├── CMemoryInStream.hpp │ │ ├── CMemoryStreamOut.cpp │ │ ├── CMemoryStreamOut.hpp │ │ ├── COutputStream.cpp │ │ ├── COutputStream.hpp │ │ ├── CTextInStream.cpp │ │ ├── CTextInStream.hpp │ │ ├── CTextOutStream.cpp │ │ ├── CTextOutStream.hpp │ │ ├── CZipInputStream.cpp │ │ ├── CZipInputStream.hpp │ │ ├── ContainerReaders.hpp │ │ ├── ContainerWriters.hpp │ │ ├── IOStreams.cpp │ │ └── IOStreams.hpp │ ├── Tweaks/ │ │ ├── ITweak.hpp │ │ ├── ITweakAutoMapper.hpp │ │ ├── ITweakBall.hpp │ │ ├── ITweakGame.hpp │ │ ├── ITweakGui.hpp │ │ ├── ITweakGuiColors.hpp │ │ ├── ITweakGunRes.hpp │ │ ├── ITweakParticle.hpp │ │ ├── ITweakPlayer.hpp │ │ ├── ITweakPlayerControl.hpp │ │ ├── ITweakPlayerGun.cpp │ │ ├── ITweakPlayerGun.hpp │ │ ├── ITweakPlayerRes.hpp │ │ ├── ITweakSlideShow.hpp │ │ └── ITweakTargeting.hpp │ ├── Weapon/ │ │ ├── CAuxWeapon.cpp │ │ ├── CAuxWeapon.hpp │ │ ├── CBeamInfo.hpp │ │ ├── CBeamProjectile.cpp │ │ ├── CBeamProjectile.hpp │ │ ├── CBomb.cpp │ │ ├── CBomb.hpp │ │ ├── CBurstFire.cpp │ │ ├── CBurstFire.hpp │ │ ├── CElectricBeamProjectile.cpp │ │ ├── CElectricBeamProjectile.hpp │ │ ├── CEnergyProjectile.cpp │ │ ├── CEnergyProjectile.hpp │ │ ├── CFidget.cpp │ │ ├── CFidget.hpp │ │ ├── CFlameInfo.cpp │ │ ├── CFlameInfo.hpp │ │ ├── CFlameThrower.cpp │ │ ├── CFlameThrower.hpp │ │ ├── CGSComboFire.cpp │ │ ├── CGSComboFire.hpp │ │ ├── CGSFidget.cpp │ │ ├── CGSFidget.hpp │ │ ├── CGSFreeLook.cpp │ │ ├── CGSFreeLook.hpp │ │ ├── CGameProjectile.cpp │ │ ├── CGameProjectile.hpp │ │ ├── CGrappleArm.cpp │ │ ├── CGrappleArm.hpp │ │ ├── CGunController.cpp │ │ ├── CGunController.hpp │ │ ├── CGunMotion.cpp │ │ ├── CGunMotion.hpp │ │ ├── CGunWeapon.cpp │ │ ├── CGunWeapon.hpp │ │ ├── CIceBeam.cpp │ │ ├── CIceBeam.hpp │ │ ├── CMakeLists.txt │ │ ├── CNewFlameThrower.cpp │ │ ├── CNewFlameThrower.hpp │ │ ├── CPhazonBeam.cpp │ │ ├── CPhazonBeam.hpp │ │ ├── CPlasmaBeam.cpp │ │ ├── CPlasmaBeam.hpp │ │ ├── CPlasmaProjectile.cpp │ │ ├── CPlasmaProjectile.hpp │ │ ├── CPlayerGun.cpp │ │ ├── CPlayerGun.hpp │ │ ├── CPowerBeam.cpp │ │ ├── CPowerBeam.hpp │ │ ├── CPowerBomb.cpp │ │ ├── CPowerBomb.hpp │ │ ├── CProjectileInfo.cpp │ │ ├── CProjectileInfo.hpp │ │ ├── CProjectileWeapon.cpp │ │ ├── CProjectileWeapon.hpp │ │ ├── CTargetableProjectile.cpp │ │ ├── CTargetableProjectile.hpp │ │ ├── CWaveBeam.cpp │ │ ├── CWaveBeam.hpp │ │ ├── CWaveBuster.cpp │ │ ├── CWaveBuster.hpp │ │ ├── CWeapon.cpp │ │ ├── CWeapon.hpp │ │ ├── CWeaponMgr.cpp │ │ ├── CWeaponMgr.hpp │ │ ├── CWeaponMode.hpp │ │ ├── WeaponCommon.cpp │ │ └── WeaponCommon.hpp │ ├── World/ │ │ ├── CActor.cpp │ │ ├── CActor.hpp │ │ ├── CActorModelParticles.cpp │ │ ├── CActorModelParticles.hpp │ │ ├── CActorParameters.hpp │ │ ├── CAi.cpp │ │ ├── CAi.hpp │ │ ├── CAiFuncMap.cpp │ │ ├── CAiFuncMap.hpp │ │ ├── CAmbientAI.cpp │ │ ├── CAmbientAI.hpp │ │ ├── CAnimationParameters.hpp │ │ ├── CDamageInfo.cpp │ │ ├── CDamageInfo.hpp │ │ ├── CDamageVulnerability.cpp │ │ ├── CDamageVulnerability.hpp │ │ ├── CDestroyableRock.cpp │ │ ├── CDestroyableRock.hpp │ │ ├── CEffect.cpp │ │ ├── CEffect.hpp │ │ ├── CEnergyDrainSource.cpp │ │ ├── CEnergyDrainSource.hpp │ │ ├── CEntity.cpp │ │ ├── CEntity.hpp │ │ ├── CEntityInfo.hpp │ │ ├── CEnvFxManager.cpp │ │ ├── CEnvFxManager.hpp │ │ ├── CExplosion.cpp │ │ ├── CExplosion.hpp │ │ ├── CFire.cpp │ │ ├── CFire.hpp │ │ ├── CFishCloud.cpp │ │ ├── CFishCloud.hpp │ │ ├── CFishCloudModifier.cpp │ │ ├── CFishCloudModifier.hpp │ │ ├── CFluidPlane.cpp │ │ ├── CFluidPlane.hpp │ │ ├── CFluidPlaneCPU.cpp │ │ ├── CFluidPlaneCPU.hpp │ │ ├── CFluidPlaneDoor.cpp │ │ ├── CFluidPlaneDoor.hpp │ │ ├── CFluidPlaneGPU.cpp │ │ ├── CFluidPlaneGPU.hpp │ │ ├── CFluidPlaneManager.cpp │ │ ├── CFluidPlaneManager.hpp │ │ ├── CFluidUVMotion.cpp │ │ ├── CFluidUVMotion.hpp │ │ ├── CGameArea.cpp │ │ ├── CGameArea.hpp │ │ ├── CGameLight.cpp │ │ ├── CGameLight.hpp │ │ ├── CGrappleParameters.hpp │ │ ├── CHUDBillboardEffect.cpp │ │ ├── CHUDBillboardEffect.hpp │ │ ├── CHUDMemoParms.hpp │ │ ├── CHealthInfo.cpp │ │ ├── CHealthInfo.hpp │ │ ├── CIceImpact.cpp │ │ ├── CIceImpact.hpp │ │ ├── CKnockBackController.cpp │ │ ├── CKnockBackController.hpp │ │ ├── CLightParameters.hpp │ │ ├── CMakeLists.txt │ │ ├── CMarkerGrid.cpp │ │ ├── CMarkerGrid.hpp │ │ ├── CMorphBall.cpp │ │ ├── CMorphBall.hpp │ │ ├── CMorphBallShadow.cpp │ │ ├── CMorphBallShadow.hpp │ │ ├── CPathFindArea.cpp │ │ ├── CPathFindArea.hpp │ │ ├── CPathFindRegion.cpp │ │ ├── CPathFindRegion.hpp │ │ ├── CPathFindSearch.cpp │ │ ├── CPathFindSearch.hpp │ │ ├── CPathFindSpline.cpp │ │ ├── CPatterned.cpp │ │ ├── CPatterned.hpp │ │ ├── CPatternedInfo.cpp │ │ ├── CPatternedInfo.hpp │ │ ├── CPhysicsActor.cpp │ │ ├── CPhysicsActor.hpp │ │ ├── CPlayer.cpp │ │ ├── CPlayer.hpp │ │ ├── CPlayerCameraBob.cpp │ │ ├── CPlayerCameraBob.hpp │ │ ├── CPlayerEnergyDrain.cpp │ │ ├── CPlayerEnergyDrain.hpp │ │ ├── CProjectedShadow.cpp │ │ ├── CProjectedShadow.hpp │ │ ├── CRepulsor.cpp │ │ ├── CRepulsor.hpp │ │ ├── CRipple.cpp │ │ ├── CRipple.hpp │ │ ├── CRippleManager.cpp │ │ ├── CRippleManager.hpp │ │ ├── CScannableParameters.hpp │ │ ├── CScriptActor.cpp │ │ ├── CScriptActor.hpp │ │ ├── CScriptActorKeyframe.cpp │ │ ├── CScriptActorKeyframe.hpp │ │ ├── CScriptActorRotate.cpp │ │ ├── CScriptActorRotate.hpp │ │ ├── CScriptAiJumpPoint.cpp │ │ ├── CScriptAiJumpPoint.hpp │ │ ├── CScriptAreaAttributes.cpp │ │ ├── CScriptAreaAttributes.hpp │ │ ├── CScriptBallTrigger.cpp │ │ ├── CScriptBallTrigger.hpp │ │ ├── CScriptBeam.cpp │ │ ├── CScriptBeam.hpp │ │ ├── CScriptCameraBlurKeyframe.cpp │ │ ├── CScriptCameraBlurKeyframe.hpp │ │ ├── CScriptCameraFilterKeyframe.cpp │ │ ├── CScriptCameraFilterKeyframe.hpp │ │ ├── CScriptCameraHint.cpp │ │ ├── CScriptCameraHint.hpp │ │ ├── CScriptCameraHintTrigger.cpp │ │ ├── CScriptCameraHintTrigger.hpp │ │ ├── CScriptCameraPitchVolume.cpp │ │ ├── CScriptCameraPitchVolume.hpp │ │ ├── CScriptCameraShaker.cpp │ │ ├── CScriptCameraShaker.hpp │ │ ├── CScriptCameraWaypoint.cpp │ │ ├── CScriptCameraWaypoint.hpp │ │ ├── CScriptColorModulate.cpp │ │ ├── CScriptColorModulate.hpp │ │ ├── CScriptControllerAction.cpp │ │ ├── CScriptControllerAction.hpp │ │ ├── CScriptCounter.cpp │ │ ├── CScriptCounter.hpp │ │ ├── CScriptCoverPoint.cpp │ │ ├── CScriptCoverPoint.hpp │ │ ├── CScriptDamageableTrigger.cpp │ │ ├── CScriptDamageableTrigger.hpp │ │ ├── CScriptDebris.cpp │ │ ├── CScriptDebris.hpp │ │ ├── CScriptDebugCameraWaypoint.cpp │ │ ├── CScriptDebugCameraWaypoint.hpp │ │ ├── CScriptDistanceFog.cpp │ │ ├── CScriptDistanceFog.hpp │ │ ├── CScriptDock.cpp │ │ ├── CScriptDock.hpp │ │ ├── CScriptDockAreaChange.cpp │ │ ├── CScriptDockAreaChange.hpp │ │ ├── CScriptDoor.cpp │ │ ├── CScriptDoor.hpp │ │ ├── CScriptEMPulse.cpp │ │ ├── CScriptEMPulse.hpp │ │ ├── CScriptEffect.cpp │ │ ├── CScriptEffect.hpp │ │ ├── CScriptGenerator.cpp │ │ ├── CScriptGenerator.hpp │ │ ├── CScriptGrapplePoint.cpp │ │ ├── CScriptGrapplePoint.hpp │ │ ├── CScriptGunTurret.cpp │ │ ├── CScriptGunTurret.hpp │ │ ├── CScriptHUDMemo.cpp │ │ ├── CScriptHUDMemo.hpp │ │ ├── CScriptMazeNode.cpp │ │ ├── CScriptMazeNode.hpp │ │ ├── CScriptMemoryRelay.cpp │ │ ├── CScriptMemoryRelay.hpp │ │ ├── CScriptMidi.cpp │ │ ├── CScriptMidi.hpp │ │ ├── CScriptPickup.cpp │ │ ├── CScriptPickup.hpp │ │ ├── CScriptPickupGenerator.cpp │ │ ├── CScriptPickupGenerator.hpp │ │ ├── CScriptPlatform.cpp │ │ ├── CScriptPlatform.hpp │ │ ├── CScriptPlayerActor.cpp │ │ ├── CScriptPlayerActor.hpp │ │ ├── CScriptPlayerHint.cpp │ │ ├── CScriptPlayerHint.hpp │ │ ├── CScriptPlayerStateChange.cpp │ │ ├── CScriptPlayerStateChange.hpp │ │ ├── CScriptPointOfInterest.cpp │ │ ├── CScriptPointOfInterest.hpp │ │ ├── CScriptRandomRelay.cpp │ │ ├── CScriptRandomRelay.hpp │ │ ├── CScriptRelay.cpp │ │ ├── CScriptRelay.hpp │ │ ├── CScriptRipple.cpp │ │ ├── CScriptRipple.hpp │ │ ├── CScriptRoomAcoustics.cpp │ │ ├── CScriptRoomAcoustics.hpp │ │ ├── CScriptShadowProjector.cpp │ │ ├── CScriptShadowProjector.hpp │ │ ├── CScriptSound.cpp │ │ ├── CScriptSound.hpp │ │ ├── CScriptSpawnPoint.cpp │ │ ├── CScriptSpawnPoint.hpp │ │ ├── CScriptSpecialFunction.cpp │ │ ├── CScriptSpecialFunction.hpp │ │ ├── CScriptSpiderBallAttractionSurface.cpp │ │ ├── CScriptSpiderBallAttractionSurface.hpp │ │ ├── CScriptSpiderBallWaypoint.cpp │ │ ├── CScriptSpiderBallWaypoint.hpp │ │ ├── CScriptSpindleCamera.cpp │ │ ├── CScriptSpindleCamera.hpp │ │ ├── CScriptSteam.cpp │ │ ├── CScriptSteam.hpp │ │ ├── CScriptStreamedMusic.cpp │ │ ├── CScriptStreamedMusic.hpp │ │ ├── CScriptSwitch.cpp │ │ ├── CScriptSwitch.hpp │ │ ├── CScriptTargetingPoint.cpp │ │ ├── CScriptTargetingPoint.hpp │ │ ├── CScriptTimer.cpp │ │ ├── CScriptTimer.hpp │ │ ├── CScriptTrigger.cpp │ │ ├── CScriptTrigger.hpp │ │ ├── CScriptVisorFlare.cpp │ │ ├── CScriptVisorFlare.hpp │ │ ├── CScriptVisorGoo.cpp │ │ ├── CScriptVisorGoo.hpp │ │ ├── CScriptWater.cpp │ │ ├── CScriptWater.hpp │ │ ├── CScriptWaypoint.cpp │ │ ├── CScriptWaypoint.hpp │ │ ├── CScriptWorldTeleporter.cpp │ │ ├── CScriptWorldTeleporter.hpp │ │ ├── CSnakeWeedSwarm.cpp │ │ ├── CSnakeWeedSwarm.hpp │ │ ├── CStateMachine.cpp │ │ ├── CStateMachine.hpp │ │ ├── CTeamAiMgr.cpp │ │ ├── CTeamAiMgr.hpp │ │ ├── CTeamAiTypes.hpp │ │ ├── CVisorFlare.cpp │ │ ├── CVisorFlare.hpp │ │ ├── CVisorParameters.hpp │ │ ├── CWallCrawlerSwarm.cpp │ │ ├── CWallCrawlerSwarm.hpp │ │ ├── CWallWalker.cpp │ │ ├── CWallWalker.hpp │ │ ├── CWorld.cpp │ │ ├── CWorld.hpp │ │ ├── CWorldLight.cpp │ │ ├── CWorldLight.hpp │ │ ├── CWorldShadow.cpp │ │ ├── CWorldShadow.hpp │ │ ├── CWorldTransManager.cpp │ │ ├── CWorldTransManager.hpp │ │ ├── IGameArea.cpp │ │ ├── IGameArea.hpp │ │ ├── ScriptLoader.cpp │ │ ├── ScriptLoader.hpp │ │ ├── ScriptObjectSupport.cpp │ │ └── ScriptObjectSupport.hpp │ ├── platforms/ │ │ ├── freedesktop/ │ │ │ ├── metaforce.desktop │ │ │ └── mkwmicon.c │ │ ├── ios/ │ │ │ ├── Assets.car │ │ │ ├── Base.lproj/ │ │ │ │ └── LaunchScreen.storyboardc/ │ │ │ │ ├── 01J-lp-oVM-view-Ze5-6b-2t3.nib │ │ │ │ ├── Info.plist │ │ │ │ └── UIViewController-01J-lp-oVM.nib │ │ │ └── Info.plist.in │ │ ├── macos/ │ │ │ ├── Info.plist.in │ │ │ └── mainicon.icns │ │ ├── tvos/ │ │ │ ├── Assets.car │ │ │ ├── Base.lproj/ │ │ │ │ └── LaunchScreen.storyboardc/ │ │ │ │ ├── BYZ-38-t0r-view-8bC-Xf-vdC.nib │ │ │ │ ├── Info.plist │ │ │ │ └── UIViewController-BYZ-38-t0r.nib │ │ │ └── Info.plist.in │ │ └── win/ │ │ ├── Package.appxmanifest │ │ ├── metaforce.aps │ │ ├── metaforce.manifest │ │ └── metaforce.rc.in │ └── rstl.hpp ├── android/ │ ├── .gitignore │ ├── README.md │ ├── app/ │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ └── axiodl/ │ │ │ │ └── metaforce/ │ │ │ │ └── MetaforceActivity.java │ │ │ └── org/ │ │ │ └── libsdl/ │ │ │ └── app/ │ │ │ ├── HIDDevice.java │ │ │ ├── HIDDeviceBLESteamController.java │ │ │ ├── HIDDeviceManager.java │ │ │ ├── HIDDeviceUSB.java │ │ │ ├── SDL.java │ │ │ ├── SDLActivity.java │ │ │ ├── SDLAudioManager.java │ │ │ ├── SDLControllerManager.java │ │ │ ├── SDLDummyEdit.java │ │ │ ├── SDLInputConnection.java │ │ │ └── SDLSurface.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── scripts/ │ │ ├── stage-jni-libs.sh │ │ └── sync-sdl-java.sh │ └── settings.gradle ├── bintoc/ │ ├── CMakeLists.txt │ ├── bintoc.c │ └── bintocHelpers.cmake ├── ci/ │ ├── build-appimage.sh │ ├── build-dmg.sh │ ├── build-ipa.sh │ ├── upload-debug-linux.sh │ └── upload-debug-macos.sh ├── extern/ │ └── CMakeLists.txt ├── gbalink/ │ ├── CMakeLists.txt │ └── main.cpp ├── imgui/ │ ├── CMakeLists.txt │ ├── ImGuiEngine.cpp │ ├── ImGuiEngine.hpp │ ├── magic_enum.hpp │ └── stb_image.h ├── ios.toolchain.cmake ├── lldb-extras/ │ ├── .lldbinit │ ├── README.txt │ └── metaforce_lldb_tools.py ├── normalize_submodules.sh └── version.h.in ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- BasedOnStyle: LLVM ColumnLimit: 120 UseTab: Never TabWidth: 2 --- Language: Cpp DerivePointerAlignment: false PointerAlignment: Left AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false IndentCaseLabels: false AllowShortBlocksOnASingleLine: Always AlignOperands: true AlignTrailingComments: true AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BreakConstructorInitializersBeforeComma: true AlwaysBreakAfterReturnType: None AlwaysBreakAfterDefinitionReturnType: None AllowShortFunctionsOnASingleLine: All Cpp11BracedListStyle: true NamespaceIndentation: None BinPackArguments: true BinPackParameters: true SortIncludes: false AccessModifierOffset: -2 ConstructorInitializerIndentWidth: 0 ConstructorInitializerAllOnOneLineOrOnePerLine: true ================================================ FILE: .clang-tidy ================================================ Checks: > *, -altera-*, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-owning-memory, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-static-cast-downcast, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -fuchsia-*, -google-runtime-references, -hicpp-*, -llvm-header-guard, -llvmlibc-*, -misc-unused-parameters, -modernize-use-trailing-return-type, -readability-convert-member-functions-to-static, -readability-function-cognitive-complexity, -readability-magic-numbers, -readability-named-parameter, -readability-uppercase-literal-suffix, CheckOptions: - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic value: '1' ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: paths-ignore: - '*.md' - '*LICENSE' pull_request: env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" jobs: build-linux: name: Build Linux (${{matrix.name}} x86_64) runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - name: GCC preset: gcc - name: Clang preset: clang steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - name: Install dependencies run: | sudo apt-get update sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \ zlib1g-dev libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev \ libpulse-dev libudev-dev libpng-dev libncurses5-dev libx11-xcb-dev libfreetype-dev \ libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev libssl-dev \ libxss-dev libfuse2 - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 - name: Configure CMake run: cmake --preset x-linux-ci-${{matrix.preset}} - name: Build run: cmake --build --preset x-linux-ci-${{matrix.preset}} - name: Generate AppImage run: ci/build-appimage.sh - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: metaforce-${{env.METAFORCE_VERSION}}-linux-${{matrix.preset}}-x86_64 path: | build/install/Metaforce-*.AppImage build/install/debug.tar.* build-macos: name: Build macOS (AppleClang universal) runs-on: macos-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - name: Install dependencies run: | brew update brew upgrade --formula brew install cmake ninja graphicsmagick imagemagick pip3 install --break-system-packages markupsafe - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 - name: Configure CMake run: cmake --preset x-macos-ci - name: Build run: cmake --build --preset x-macos-ci #- name: Import signing certificate # if: 'false' # temporarily disabled #uses: devbotsxyz/xcode-import-certificate@master #with: # certificate-data: ${{secrets.MACOS_CERTIFICATE_DATA}} # certificate-passphrase: ${{secrets.MACOS_CERTIFICATE_PASSWORD}} # keychain-password: ${{secrets.MACOS_KEYCHAIN_PASSWORD}} - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: metaforce-${{env.METAFORCE_VERSION}}-macos-appleclang-universal path: | build/install/Metaforce.app build/install/debug.tar.* build-ios: name: Build iOS (AppleClang arm64) runs-on: macos-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - name: Install dependencies run: | brew update brew upgrade --formula brew install cmake ninja pip3 install --break-system-packages markupsafe rustup target add aarch64-apple-ios - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 - name: Configure CMake run: cmake --preset x-ios-ci - name: Build run: cmake --build --preset x-ios-ci --target install - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: metaforce-${{env.METAFORCE_VERSION}}-ios-appleclang-arm64 path: | build/install/Metaforce.app build/install/debug.tar.* build-tvos: name: Build tvOS (AppleClang arm64) runs-on: macos-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - name: Install dependencies run: | brew update brew upgrade --formula brew install cmake ninja pip3 install --break-system-packages markupsafe rustup toolchain install nightly rustup target add --toolchain nightly aarch64-apple-tvos - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 - name: Configure CMake run: cmake --preset x-tvos-ci - name: Build run: cmake --build --preset x-tvos-ci --target install - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: metaforce-${{env.METAFORCE_VERSION}}-tvos-appleclang-arm64 path: | build/install/Metaforce.app build/install/debug.tar.* build-windows: name: Build Windows (${{matrix.name}} x86_64) runs-on: windows-latest env: BUILD_DIR: C:\build strategy: fail-fast: false matrix: include: - name: MSVC preset: msvc - name: Clang preset: clang steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - name: Enable Visual Studio environment uses: ilammy/msvc-dev-cmd@v1 # msvc-dev-cmd sets VCPKG_ROOT, set it back - name: Override VCPKG_ROOT run: echo "VCPKG_ROOT=C:\vcpkg" >> $env:GITHUB_ENV - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 - name: Install dependencies run: | choco install ninja vcpkg install zlib:x64-windows-static bzip2:x64-windows-static zstd:x64-windows-static ` liblzma:x64-windows-static freetype:x64-windows-static - name: Configure CMake run: cmake --preset x-windows-ci-${{matrix.preset}} - name: Build run: cmake --build --preset x-windows-ci-${{matrix.preset}} - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: metaforce-${{env.METAFORCE_VERSION}}-win32-${{matrix.preset}}-x86_64 path: | ${{env.BUILD_DIR}}/install/*.exe ${{env.BUILD_DIR}}/install/debug.7z ================================================ FILE: .gitignore ================================================ *.autosave *.user .buildcache/ .directory .DS_Store .idea/ .vs/ build/ cmake-build-*/ CMakeUserPresets.json docs/* out/ Runtime/platforms/win/metaforce.rc version.h ================================================ FILE: .gitmodules ================================================ [submodule "extern/nod"] path = extern/nod url = https://github.com/encounter/nod.git branch = main [submodule "extern/kabufuda"] path = extern/kabufuda url = ../kabufuda.git branch = master [submodule "extern/jbus"] path = extern/jbus url = ../jbus.git branch = master [submodule "extern/fixNES"] path = extern/fixNES url = https://github.com/FIX94/fixNES.git branch = master [submodule "extern/libjpeg-turbo"] path = extern/libjpeg-turbo url = ../libjpeg-turbo.git branch = thp [submodule "extern/zeus"] path = extern/zeus url = ../zeus.git branch = master [submodule "extern/nativefiledialog-extended"] path = extern/nativefiledialog-extended url = https://github.com/btzy/nativefiledialog-extended [submodule "extern/aurora"] path = extern/aurora url = https://github.com/encounter/aurora.git branch = main [submodule "extern/spdlog"] path = extern/spdlog url = https://github.com/gabime/spdlog.git branch = v1.x [submodule "extern/musyx"] path = extern/musyx url = https://github.com/AxioDL/musyx.git ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.25...4.1) if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE) endif () # obtain revision info from git find_package(Git) if (GIT_FOUND) # make sure version information gets re-run when the current Git HEAD changes execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD OUTPUT_VARIABLE metaforce_git_head_filename OUTPUT_STRIP_TRAILING_WHITESPACE) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${metaforce_git_head_filename}") execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD OUTPUT_VARIABLE metaforce_git_head_symbolic OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${metaforce_git_head_symbolic} OUTPUT_VARIABLE metaforce_git_head_symbolic_filename OUTPUT_STRIP_TRAILING_WHITESPACE) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${metaforce_git_head_symbolic_filename}") # defines METAFORCE_WC_REVISION execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD OUTPUT_VARIABLE METAFORCE_WC_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE) # defines METAFORCE_WC_DESCRIBE execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tag --long --dirty OUTPUT_VARIABLE METAFORCE_WC_DESCRIBE OUTPUT_STRIP_TRAILING_WHITESPACE) # remove hash (and trailing "-0" if needed) from description string(REGEX REPLACE "(-0)?-[^-]+((-dirty)?)$" "\\2" METAFORCE_WC_DESCRIBE "${METAFORCE_WC_DESCRIBE}") # defines METAFORCE_WC_BRANCH execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD OUTPUT_VARIABLE METAFORCE_WC_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE) # defines METAFORCE_WC_DATE execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad OUTPUT_VARIABLE METAFORCE_WC_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) else () message(STATUS "Unable to find git, commit information will not be available") endif () if (METAFORCE_WC_DESCRIBE) string(REGEX REPLACE "v([0-9]+)\.([0-9]+)\.([0-9]+)\-([0-9]+).*" "\\1.\\2.\\3.\\4" METAFORCE_VERSION_STRING "${METAFORCE_WC_DESCRIBE}") string(REGEX REPLACE "v([0-9]+)\.([0-9]+)\.([0-9]+).*" "\\1.\\2.\\3" METAFORCE_SHORT_VERSION_STRING "${METAFORCE_WC_DESCRIBE}") else () set(METAFORCE_WC_DESCRIBE "UNKNOWN-VERSION") set(METAFORCE_VERSION_STRING "0.0.0") endif () string(TIMESTAMP CURRENT_YEAR "%Y") # Add version information to CI environment variables if(DEFINED ENV{GITHUB_ENV}) file(APPEND "$ENV{GITHUB_ENV}" "METAFORCE_VERSION=${METAFORCE_WC_DESCRIBE}\n") endif() message(STATUS "Metaforce version set to ${METAFORCE_WC_DESCRIBE}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") project(metaforce LANGUAGES C CXX VERSION ${METAFORCE_VERSION_STRING}) if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS) # ios.toolchain.cmake hack for SDL set(TVOS ON) set(IOS OFF) endif () if (EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX .html) endif () set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Binaries) if(APPLE AND NOT CMAKE_OSX_SYSROOT) # If the Xcode SDK is lagging behind system version, CMake needs this done first execute_process(COMMAND xcrun --sdk macosx --show-sdk-path OUTPUT_VARIABLE CMAKE_OSX_SYSROOT OUTPUT_STRIP_TRAILING_WHITESPACE) endif() set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(BUILD_SHARED_LIBS OFF CACHE BOOL "Force shared libs off" FORCE) set(BUILD_STATIC_LIBS ON CACHE BOOL "Force static libs on" FORCE) if (CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL AMD64) set(METAFORCE_VECTOR_ISA "sse41" CACHE STRING "Vector ISA to build for (sse2, sse3, sse41, avx, avx2)") endif () if(MSVC) if(${METAFORCE_VECTOR_ISA} STREQUAL "avx2") add_compile_options(/arch:AVX2) add_compile_definitions(__SSE4_1__=1) message(STATUS "Building with AVX2 Vector ISA") elseif(${METAFORCE_VECTOR_ISA} STREQUAL "avx") add_compile_options(/arch:AVX) add_compile_definitions(__SSE4_1__=1) message(STATUS "Building with AVX Vector ISA") elseif(${METAFORCE_VECTOR_ISA} STREQUAL "sse41") add_compile_definitions(__SSE4_1__=1) # clang-cl 10 requires -msse4.1, may be fixed in newer versions? if("${CMAKE_CXX_COMPILER_ID}" STREQUAL Clang) add_compile_options($<$,$>:-msse4.1>) endif() message(STATUS "Building with SSE4.1 Vector ISA") else() message(STATUS "Building with SSE2 Vector ISA") endif() if(${CMAKE_GENERATOR} MATCHES "Visual Studio*") set(VS_OPTIONS "/MP") set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT metaforce) endif() # Shaddup MSVC add_compile_definitions(UNICODE=1 _UNICODE=1 __SSE__=1 _CRT_SECURE_NO_WARNINGS=1 D_SCL_SECURE_NO_WARNINGS=1 _SCL_SECURE_NO_DEPRECATE=1 _CRT_NONSTDC_NO_WARNINGS=1 _ENABLE_EXTENDED_ALIGNED_STORAGE=1 NOMINMAX=1 _HAS_EXCEPTIONS=0) add_compile_options(/IGNORE:4221 $<$,$>:/wd4018> $<$,$>:/wd4800> $<$,$>:/wd4005> $<$,$>:/wd4311> $<$,$>:/wd4068> $<$,$>:/wd4267> $<$,$>:/wd4244> $<$,$>:/wd4200> $<$,$>:/wd4305> $<$,$>:/wd4067> $<$,$>:/wd4146> $<$,$>:/wd4309> $<$,$>:/wd4805> ${VS_OPTIONS}) string(REPLACE "/GR " "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") string(REPLACE " /EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") add_compile_options( # Disable exceptions $<$:/EHsc-> # Disable RTTI $<$:/GR-> # Enforce various standards compliant behavior. $<$:/permissive-> # Enable standard volatile semantics. $<$:/volatile:iso> # Reports the proper value for the __cplusplus preprocessor macro. $<$:/Zc:__cplusplus> ) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # Flags for MSVC (not clang-cl) add_compile_options( # Enable standards conforming preprocessor. $<$:/Zc:preprocessor> # Allow constexpr variables to have explicit external linkage. $<$:/Zc:externConstexpr> # Assume that new throws exceptions, allowing better code generation. $<$:/Zc:throwingNew> # Link-time Code Generation for Release builds $<$:/GL> ) # Link-time Code Generation for Release builds set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "/LTCG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO") set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/DEBUG /RELEASE /OPT:REF /OPT:ICF /INCREMENTAL:NO /DEBUGTYPE:cv,fixup") endif() else() if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL x86_64) if(${METAFORCE_VECTOR_ISA} STREQUAL "native") add_compile_options(-march=native) message(STATUS "Building with native ISA") elseif(${METAFORCE_VECTOR_ISA} STREQUAL "avx2") add_compile_options(-mavx2) message(STATUS "Building with AVX2 Vector ISA") elseif(${METAFORCE_VECTOR_ISA} STREQUAL "avx") add_compile_options(-mavx) message(STATUS "Building with AVX Vector ISA") elseif(${METAFORCE_VECTOR_ISA} STREQUAL "sse41") add_compile_options(-msse4.1) message(STATUS "Building with SSE4.1 Vector ISA") elseif(${METAFORCE_VECTOR_ISA} STREQUAL "sse3") add_compile_options(-msse3) message(STATUS "Building with SSE3 Vector ISA") elseif(${METAFORCE_VECTOR_ISA} STREQUAL "sse2") add_compile_options(-msse2) message(STATUS "Building with SSE2 Vector ISA") else() message(STATUS "Building with x87 Vector ISA") endif() endif() include(CheckCXXCompilerFlag) check_cxx_compiler_flag(-fno-plt HAS_NO_PLT) if (HAS_NO_PLT) add_compile_options(-fno-plt) endif() check_cxx_compiler_flag(-fno-asynchronous-unwind-tables HAS_NO_ASYNC_UNWIND_TABLES) if (HAS_NO_ASYNC_UNWIND_TABLES AND ${CMAKE_BUILD_TYPE} STREQUAL Release) # Binary size reduction add_compile_options(-fno-asynchronous-unwind-tables) endif() if (METAFORCE_ASAN) add_compile_options($<$:-stdlib=libc++> -fsanitize=address -fsanitize-address-use-after-scope) add_link_options($<$:-stdlib=libc++> -fsanitize=address -fsanitize-address-use-after-scope) elseif(METAFORCE_MSAN) add_compile_options($<$:-stdlib=libc++> -fsanitize=memory -fsanitize-memory-track-origins -fsanitize-recover=all) endif() add_compile_options($<$:-fno-rtti> $<$:-fno-exceptions> -Wall -Wno-multichar -Wno-unused-variable -Wno-unused-result -Wno-unused-but-set-variable -Wno-unused-function -Wno-sign-compare -Wno-unknown-pragmas) # doesn't work with generator expression in add_compile_options? if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") add_compile_options(-Wno-unknown-warning-option -Wno-unused-private-field) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") add_compile_options(-Wno-lto-type-mismatch -Wno-maybe-uninitialized) endif() if(APPLE) add_compile_options(-Wno-error=deprecated-declarations $<$:-flto=thin>) endif() endif() if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") include_directories(/usr/local/include) link_directories(/usr/local/lib) endif() if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") if(${CMAKE_BUILD_TYPE} STREQUAL Debug OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo) # This is required to summarize std::string add_compile_options(-fno-limit-debug-info -fno-omit-frame-pointer) endif() option(USE_LD_LLD "Link with LLD" ON) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") option(USE_LD_GOLD "Link with GNU Gold" ON) endif() include(CheckIPOSupported) check_ipo_supported(RESULT LTO_SUPPORTED) if(LTO_SUPPORTED AND ("${CMAKE_BUILD_TYPE}" STREQUAL "Release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")) option(USE_LTO "Enable LTO" ON) else() option(USE_LTO "Enable LTO" OFF) endif() # FIXME GCC 11.1 -flto is completely broken if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 11.1.0) message(NOTICE "Working around GCC 11.1 bug; disabling LTO") set(USE_LTO OFF) endif() else() option(USE_LD_LLD "Link with LLD" OFF) option(USE_LD_GOLD "Link with GNU Gold" OFF) option(USE_LTO "Enable LTO" OFF) endif() if(USE_LD_LLD) execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=lld -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) if("${LD_VERSION}" MATCHES "LLD") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -Wl,--build-id=uuid") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld") if(USE_LTO) add_compile_options(-flto=thin) add_link_options(-flto=thin) message(STATUS "LLD linker enabled with LTO.") else() message(STATUS "LLD linker enabled.") endif() set(USE_LD_GOLD OFF) else() message(WARNING "LLD linker isn't available, using the default system linker.") set(USE_LD_LLD OFF) endif() endif() if(USE_LD_GOLD) execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) if("${LD_VERSION}" MATCHES "GNU gold") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold -Wl,--disable-new-dtags") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=gold -Wl,--disable-new-dtags") if (USE_SPLIT_DWARF) add_compile_options(-gsplit-dwarf -Wl,--gdb-index) add_link_options(-gsplit-dwarf -Wl,--gdb-index) message(STATUS "GNU gold linker enabled with split DWARF.") elseif (USE_LTO) add_compile_options(-flto) add_link_options(-flto) message(STATUS "GNU gold linker enabled with LTO.") else() message(STATUS "GNU gold linker enabled.") endif() set(USE_LD_LLD OFF) else() message(WARNING "GNU gold linker isn't available, using the default system linker.") set(USE_LD_GOLD OFF) endif() endif() find_package(ZLIB REQUIRED) include(ExternalProject) set(BINTOC_CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=) if (CMAKE_TOOLCHAIN_FILE AND NOT CMAKE_CROSSCOMPILING) if (IS_ABSOLUTE "${CMAKE_TOOLCHAIN_FILE}") set(BINTOC_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}") else () set(BINTOC_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/${CMAKE_TOOLCHAIN_FILE}") endif () list(APPEND BINTOC_CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE:PATH=${BINTOC_TOOLCHAIN_FILE}) endif () if (CMAKE_MAKE_PROGRAM) list(APPEND BINTOC_CMAKE_ARGS -DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}) endif () if (DEFINED VCPKG_TARGET_TRIPLET AND NOT "${VCPKG_TARGET_TRIPLET}" STREQUAL "" AND NOT CMAKE_CROSSCOMPILING) list(APPEND BINTOC_CMAKE_ARGS -DVCPKG_TARGET_TRIPLET:STRING=${VCPKG_TARGET_TRIPLET}) endif () ExternalProject_Add(bintoc SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/bintoc" CMAKE_ARGS ${BINTOC_CMAKE_ARGS} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --config Release --target install) include(${CMAKE_CURRENT_LIST_DIR}/bintoc/bintocHelpers.cmake) add_subdirectory(extern) add_subdirectory(imgui) add_subdirectory(NESEmulator EXCLUDE_FROM_ALL) add_subdirectory(Runtime) add_subdirectory(gbalink EXCLUDE_FROM_ALL) configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h) # Packaging logic function(get_target_output_name target result_var) get_target_property(output_name ${target} OUTPUT_NAME) if (output_name STREQUAL "output_name-NOTFOUND") set(${result_var} "${target}" PARENT_SCOPE) else () set(${result_var} "${output_name}" PARENT_SCOPE) endif () endfunction() function(get_target_prefix target result_var) set(${result_var} "" PARENT_SCOPE) if (APPLE) # Have to recreate some bundle logic here, since CMake can't tell us get_target_property(is_bundle ${target} MACOSX_BUNDLE) if (is_bundle) get_target_output_name(${target} output_name) if (CMAKE_SYSTEM_NAME STREQUAL Darwin) set(${result_var} "${output_name}.app/Contents/MacOS/" PARENT_SCOPE) else () set(${result_var} "${output_name}.app/" PARENT_SCOPE) endif () endif () endif () endfunction() list(APPEND BINARY_TARGETS metaforce) set(EXTRA_TARGETS "") if (TARGET crashpad_handler) list(APPEND EXTRA_TARGETS crashpad_handler) endif () set(BIN_PREFIX "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${BIN_PREFIX}) if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) set(DEBUG_FILES_LIST "") foreach (target IN LISTS BINARY_TARGETS EXTRA_TARGETS) get_target_output_name(${target} output_name) if (WIN32) install(FILES $ DESTINATION ${BIN_PREFIX} OPTIONAL) elseif (APPLE) get_target_prefix(${target} target_prefix) install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND rm -fr \"$.dSYM\")") install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND dsymutil \"${target_prefix}$\")") install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND strip -S \"${target_prefix}$\")") if (NOT target_prefix STREQUAL "") install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND mv \"${target_prefix}$.dSYM\" .)") endif () elseif (UNIX) get_target_prefix(${target} target_prefix) install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND objcopy --only-keep-debug \"${target_prefix}$\" \"${target_prefix}$.dbg\")") install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND objcopy --strip-debug --add-gnu-debuglink=$.dbg \"${target_prefix}$\")") endif () list(APPEND DEBUG_FILES_LIST "${output_name}") endforeach () if (WIN32) list(TRANSFORM DEBUG_FILES_LIST APPEND ".pdb") list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND 7z a -t7z \"${CMAKE_INSTALL_PREFIX}/debug.7z\" ${DEBUG_FILES})") elseif (APPLE) list(TRANSFORM DEBUG_FILES_LIST APPEND ".dSYM") list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND tar acfv \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") elseif (UNIX) list(TRANSFORM DEBUG_FILES_LIST APPEND ".dbg") list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND tar -I \"xz -9 -T0\" -cvf \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") endif () endif () foreach (target IN LISTS BINARY_TARGETS) get_target_prefix(${target} target_prefix) foreach (extra_target IN LISTS EXTRA_TARGETS) get_target_prefix(${extra_target} extra_prefix) if (NOT "${target_prefix}" STREQUAL "${extra_prefix}") # Copy extra target to target prefix install(CODE "execute_process(WORKING_DIRECTORY \"${BIN_PREFIX}\" COMMAND cp \"${extra_prefix}$\" \"${target_prefix}$\")") endif () endforeach () endforeach () ================================================ FILE: CMakePresets.json ================================================ { "version": 2, "cmakeMinimumRequired": { "major": 3, "minor": 20, "patch": 0 }, "configurePresets": [ { "name": "debug", "hidden": true, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDebugDLL" } }, { "name": "relwithdebinfo", "hidden": true, "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded", "SENTRY_DSN": "$env{SENTRY_DSN}" } }, { "name": "linux-default", "displayName": "Linux (default)", "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", "USE_LTO": { "type": "BOOL", "value": false } }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Linux" ] }, "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" } } }, { "name": "linux-default-debug", "displayName": "Linux (default) Debug", "inherits": [ "debug", "linux-default" ] }, { "name": "linux-default-relwithdebinfo", "displayName": "Linux (default) RelWithDebInfo", "inherits": [ "relwithdebinfo", "linux-default" ] }, { "name": "linux-clang", "displayName": "Linux (Clang)", "inherits": [ "linux-default" ], "cacheVariables": { "CMAKE_C_COMPILER": "clang", "CMAKE_CXX_COMPILER": "clang++" } }, { "name": "linux-clang-debug", "displayName": "Linux (Clang) Debug", "inherits": [ "debug", "linux-clang" ] }, { "name": "linux-clang-relwithdebinfo", "displayName": "Linux (Clang) RelWithDebInfo", "inherits": [ "relwithdebinfo", "linux-clang" ], "cacheVariables": { "USE_LTO": { "type": "BOOL", "value": true } } }, { "name": "linux-clang-debug-asan", "displayName": "Linux (Clang) Debug w/ ASAN", "inherits": [ "linux-clang-debug" ], "cacheVariables": { "METAFORCE_ASAN": { "type": "BOOL", "value": true } } }, { "name": "linux-clang-relwithdebinfo-asan", "displayName": "Linux (Clang) RelWithDebInfo w/ ASAN", "inherits": [ "linux-clang-relwithdebinfo" ], "cacheVariables": { "METAFORCE_ASAN": { "type": "BOOL", "value": true } } }, { "name": "windows-msvc", "displayName": "Windows (MSVC)", "generator": "Ninja", "binaryDir": "${sourceDir}/out/build/${presetName}", "architecture": { "value": "x64", "strategy": "external" }, "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install", "CMAKE_TOOLCHAIN_FILE": { "type": "FILEPATH", "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" }, "VCPKG_TARGET_TRIPLET": "x64-windows-static", "VCPKG_SETUP_CMAKE_PROGRAM_PATH": { "type": "BOOL", "value": false } }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } }, { "name": "windows-msvc-debug", "displayName": "Windows (MSVC) Debug", "inherits": [ "debug", "windows-msvc" ] }, { "name": "windows-msvc-relwithdebinfo", "displayName": "Windows (MSVC) RelWithDebInfo", "inherits": [ "relwithdebinfo", "windows-msvc" ] }, { "name": "windows-clang", "displayName": "Windows (Clang)", "inherits": [ "windows-msvc" ], "cacheVariables": { "CMAKE_C_COMPILER": "clang-cl", "CMAKE_CXX_COMPILER": "clang-cl", "CMAKE_LINKER": "lld-link" }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "intelliSenseMode": "windows-clang-x64" } } }, { "name": "windows-clang-debug", "displayName": "Windows (Clang) Debug", "inherits": [ "debug", "windows-clang" ] }, { "name": "windows-clang-relwithdebinfo", "displayName": "Windows (Clang) RelWithDebInfo", "inherits": [ "relwithdebinfo", "windows-clang" ] }, { "name": "macos-default", "displayName": "macOS (default)", "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install" }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "macOS" ] } } }, { "name": "macos-default-debug", "displayName": "macOS (default) Debug", "inherits": [ "debug", "macos-default" ] }, { "name": "macos-default-relwithdebinfo", "displayName": "macOS (default) RelWithDebInfo", "inherits": [ "relwithdebinfo", "macos-default" ] }, { "name": "ios-default", "displayName": "iOS", "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "inherits": [ "relwithdebinfo" ], "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": "ios.toolchain.cmake", "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", "PLATFORM": "OS64", "DEPLOYMENT_TARGET": "14.0", "ENABLE_BITCODE": { "type": "BOOL", "value": false }, "Rust_CARGO_TARGET": "aarch64-apple-ios", "BUILD_SHARED_LIBS": { "type": "BOOL", "value": false }, "IMGUI_USE_FREETYPE": { "type": "BOOL", "value": false }, "CMAKE_DISABLE_FIND_PACKAGE_BZip2": { "type": "BOOL", "value": true }, "CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": { "type": "BOOL", "value": true }, "CMAKE_DISABLE_FIND_PACKAGE_zstd": { "type": "BOOL", "value": true } }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "macOS" ] } } }, { "name": "tvos-default", "displayName": "tvOS", "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "inherits": [ "relwithdebinfo" ], "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": "ios.toolchain.cmake", "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", "PLATFORM": "TVOS", "DEPLOYMENT_TARGET": "14.5", "ENABLE_BITCODE": { "type": "BOOL", "value": false }, "Rust_CARGO_TARGET": "aarch64-apple-tvos", "Rust_TOOLCHAIN": "nightly", "BUILD_SHARED_LIBS": { "type": "BOOL", "value": false }, "IMGUI_USE_FREETYPE": { "type": "BOOL", "value": false }, "CMAKE_DISABLE_FIND_PACKAGE_BZip2": { "type": "BOOL", "value": true }, "CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": { "type": "BOOL", "value": true }, "CMAKE_DISABLE_FIND_PACKAGE_zstd": { "type": "BOOL", "value": true } }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "macOS" ] } } }, { "name": "android-base", "hidden": true, "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "inherits": [ "relwithdebinfo" ], "cacheVariables": { "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", "CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake", "ANDROID_PLATFORM": "android-24" } }, { "name": "android-arm64", "displayName": "Android (arm64-v8a)", "inherits": [ "android-base" ], "cacheVariables": { "ANDROID_ABI": "arm64-v8a" } }, { "name": "android-x86_64", "displayName": "Android (x86_64)", "inherits": [ "android-base" ], "cacheVariables": { "ANDROID_ABI": "x86_64" } }, { "name": "x-linux-ci", "hidden": true, "inherits": [ "relwithdebinfo" ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" } }, { "name": "x-linux-ci-gcc", "inherits": [ "x-linux-ci", "linux-default" ] }, { "name": "x-linux-ci-clang", "inherits": [ "x-linux-ci", "linux-clang" ] }, { "name": "x-macos-ci", "inherits": [ "macos-default-relwithdebinfo" ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" } }, { "name": "x-ios-ci", "inherits": [ "ios-default" ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" } }, { "name": "x-tvos-ci", "inherits": [ "tvos-default" ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" } }, { "name": "x-windows-ci", "hidden": true, "inherits": [ "relwithdebinfo" ], "binaryDir": "$env{BUILD_DIR}", "cacheVariables": { "CMAKE_INSTALL_PREFIX": "$env{BUILD_DIR}/install", "CMAKE_C_COMPILER_LAUNCHER": "sccache", "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded" } }, { "name": "x-windows-ci-msvc", "inherits": [ "x-windows-ci", "windows-msvc" ] }, { "name": "x-windows-ci-clang", "inherits": [ "x-windows-ci", "windows-clang" ] } ], "buildPresets": [ { "name": "linux-default-debug", "configurePreset": "linux-default-debug", "description": "Linux (default) debug build", "displayName": "Linux (default) Debug" }, { "name": "linux-default-relwithdebinfo", "configurePreset": "linux-default-relwithdebinfo", "description": "Linux (default) release build with debug info", "displayName": "Linux (default) RelWithDebInfo" }, { "name": "linux-clang-debug", "configurePreset": "linux-clang-debug", "description": "Linux (Clang) debug build", "displayName": "Linux (Clang) Debug" }, { "name": "linux-clang-relwithdebinfo", "configurePreset": "linux-clang-relwithdebinfo", "description": "Linux (Clang) release build with debug info", "displayName": "Linux (Clang) RelWithDebInfo" }, { "name": "linux-clang-debug-asan", "configurePreset": "linux-clang-debug-asan", "description": "Linux (Clang) debug build w/ ASAN", "displayName": "Linux (Clang) Debug w/ ASAN" }, { "name": "linux-clang-relwithdebinfo-asan", "configurePreset": "linux-clang-relwithdebinfo-asan", "description": "Linux (Clang) release build with debug info w/ ASAN", "displayName": "Linux (Clang) RelWithDebInfo w/ ASAN" }, { "name": "macos-default-debug", "configurePreset": "macos-default-debug", "description": "macOS debug build", "displayName": "macOS Debug" }, { "name": "macos-default-relwithdebinfo", "configurePreset": "macos-default-relwithdebinfo", "description": "macOS release build with debug info", "displayName": "macOS RelWithDebInfo" }, { "name": "ios-default", "configurePreset": "ios-default", "description": "iOS release build with debug info", "displayName": "iOS RelWithDebInfo", "targets": [ "metaforce" ] }, { "name": "tvos-default", "configurePreset": "tvos-default", "description": "tvOS release build with debug info", "displayName": "tvOS RelWithDebInfo", "targets": [ "metaforce" ] }, { "name": "android-arm64", "configurePreset": "android-arm64", "description": "Android arm64-v8a release build with debug info", "displayName": "Android arm64-v8a RelWithDebInfo", "targets": [ "metaforce" ] }, { "name": "android-x86_64", "configurePreset": "android-x86_64", "description": "Android x86_64 release build with debug info", "displayName": "Android x86_64 RelWithDebInfo", "targets": [ "metaforce" ] }, { "name": "windows-msvc-debug", "configurePreset": "windows-msvc-debug", "description": "Windows (MSVC) debug build", "displayName": "Windows (MSVC) Debug" }, { "name": "windows-msvc-relwithdebinfo", "configurePreset": "windows-msvc-relwithdebinfo", "description": "Windows (MSVC) release build with debug info", "displayName": "Windows (MSVC) RelWithDebInfo" }, { "name": "windows-clang-debug", "configurePreset": "windows-clang-debug", "description": "Windows (Clang) debug build", "displayName": "Windows (Clang) Debug" }, { "name": "windows-clang-relwithdebinfo", "configurePreset": "windows-clang-relwithdebinfo", "description": "Windows (Clang) release build with debug info", "displayName": "Windows (Clang) RelWithDebInfo" }, { "name": "x-linux-ci-gcc", "configurePreset": "x-linux-ci-gcc", "description": "(Internal) Linux CI GCC", "displayName": "(Internal) Linux CI GCC", "targets": [ "install" ] }, { "name": "x-linux-ci-clang", "configurePreset": "x-linux-ci-clang", "description": "(Internal) Linux CI Clang", "displayName": "(Internal) Linux CI Clang", "targets": [ "install" ] }, { "name": "x-macos-ci", "configurePreset": "x-macos-ci", "description": "(Internal) macOS CI", "displayName": "(Internal) macOS CI", "targets": [ "install" ] }, { "name": "x-ios-ci", "configurePreset": "x-ios-ci", "description": "(Internal) iOS CI", "displayName": "(Internal) iOS CI", "targets": [ "install" ] }, { "name": "x-tvos-ci", "configurePreset": "x-tvos-ci", "description": "(Internal) tvOS CI", "displayName": "(Internal) tvOS CI", "targets": [ "install" ] }, { "name": "x-windows-ci-msvc", "configurePreset": "x-windows-ci-msvc", "description": "(Internal) Windows CI MSVC", "displayName": "(Internal) Windows CI MSVC", "targets": [ "install" ] }, { "name": "x-windows-ci-clang", "configurePreset": "x-windows-ci-clang", "description": "(Internal) Windows CI Clang", "displayName": "(Internal) Windows CI Clang", "targets": [ "install" ] } ] } ================================================ FILE: LICENSE ================================================ The MIT License Copyright (c) 2015-2021 Metaforce Contributors Original Authors: Jack Andersen and Phillip "Antidote" Stephens Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: NESEmulator/CMakeLists.txt ================================================ file(GLOB MAPPER_SRCS ../extern/fixNES/mapper/*.c) add_library(NESEmulator CNESEmulator.hpp CNESEmulator.cpp malloc.h apu.c ../extern/fixNES/audio_fds.c ../extern/fixNES/audio_mmc5.c ../extern/fixNES/audio_vrc6.c ../extern/fixNES/audio_vrc7.c ../extern/fixNES/audio_n163.c ../extern/fixNES/audio_s5b.c ../extern/fixNES/cpu.c ppu.c ../extern/fixNES/mem.c ../extern/fixNES/input.c ../extern/fixNES/mapper.c ../extern/fixNES/mapperList.c ../extern/fixNES/fm2play.c ../extern/fixNES/vrc_irq.c ${MAPPER_SRCS}) target_include_directories(NESEmulator PRIVATE ${CMAKE_SOURCE_DIR}/DataSpec ${CMAKE_SOURCE_DIR}/Runtime ${CMAKE_SOURCE_DIR}/extern PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(NESEmulator PRIVATE COL_32BIT=1 COL_TEX_BSWAP=1) target_link_libraries(NESEmulator RuntimeCommon) if (NOT MSVC) target_compile_options(NESEmulator PRIVATE -Wno-implicit-fallthrough -Wno-format -Wno-pointer-compare -Wno-memset-elt-size) endif () ================================================ FILE: NESEmulator/CNESEmulator.cpp ================================================ #include "CNESEmulator.hpp" #include "CGameState.hpp" #include "Input/CFinalInput.hpp" #include "Runtime/Logging.hpp" #include #include #include #include #include "malloc.h" #include #include #include #include static metaforce::MP1::CNESEmulator* EmulatorInst = nullptr; extern "C" { #include "fixNES/mapper.h" #include "fixNES/cpu.h" #include "fixNES/ppu.h" #include "fixNES/mem.h" #include "fixNES/input.h" #include "fixNES/fm2play.h" #include "fixNES/apu.h" #include "fixNES/audio_fds.h" #include "fixNES/audio_vrc7.h" #include "fixNES/mapper_h/nsf.h" /* * Portions Copyright (C) 2017 - 2019 FIX94 * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ #define DEBUG_HZ 0 #define DEBUG_MAIN_CALLS 0 #define DEBUG_KEY 0 #define DEBUG_LOAD_INFO 1 #if 0 #ifndef _WIN32 std::chrono::steady_clock::time_point s_tp = std::chrono::steady_clock::now(); static std::chrono::milliseconds::rep GetTickCount() { return std::chrono::duration_cast(std::chrono::steady_clock::now() - s_tp).count(); } #endif #endif const char *VERSION_STRING = "fixNES Alpha v1.2.7"; static char window_title[256]; static char window_title_pause[256]; enum { FTYPE_UNK = 0, FTYPE_NES, FTYPE_NSF, FTYPE_FDS, FTYPE_QD, #if ZIPSUPPORT FTYPE_ZIP, #endif }; static int emuFileType = FTYPE_UNK; static char emuFileName[1024]; uint8_t *emuNesROM = NULL; uint32_t emuNesROMsize = 0; #ifndef __LIBRETRO__ static char emuSaveName[1024]; #endif uint8_t *emuPrgRAM = NULL; uint32_t emuPrgRAMsize = 0; //used externally #ifdef COL_32BIT uint32_t textureImage[0xF000]; #define TEXIMAGE_LEN VISIBLE_DOTS*VISIBLE_LINES*4 #ifdef COL_GL_BSWAP #define GL_TEX_FMT GL_UNSIGNED_INT_8_8_8_8_REV #else //no REVerse #define GL_TEX_FMT GL_UNSIGNED_INT_8_8_8_8 #endif #else //COL_16BIT uint16_t textureImage[0xF000]; #define TEXIMAGE_LEN VISIBLE_DOTS*VISIBLE_LINES*2 #ifdef COL_GL_BSWAP #define GL_TEX_FMT GL_UNSIGNED_SHORT_5_6_5_REV #else //no REVerse #define GL_TEX_FMT GL_UNSIGNED_SHORT_5_6_5 #endif #endif bool nesPause = false; bool ppuDebugPauseFrame = false; bool doOverscan = false; bool nesPAL = false; bool nesEmuNSFPlayback = false; uint8_t emuInitialNT = NT_UNKNOWN; // static bool inPause = false; // static bool inOverscanToggle = false; // static bool inResize = false; // static bool inDiskSwitch = false; // static bool inReset = false; #if DEBUG_HZ static int emuFrameStart = 0; static int emuTimesCalled = 0; static int emuTotalElapsed = 0; #endif #if DEBUG_MAIN_CALLS static int emuMainFrameStart = 0; static int emuMainTimesCalled = 0; static int emuMainTimesSkipped = 0; static int emuMainTotalElapsed = 0; #endif #define DOTS 341 #define VISIBLE_DOTS 256 #define VISIBLE_LINES 240 static uint32_t linesToDraw = VISIBLE_LINES; static const uint32_t visibleImg = VISIBLE_DOTS * VISIBLE_LINES * 4; // static uint8_t scaleFactor = 2; static bool emuSaveEnabled = false; static bool emuFdsHasSideB = false; // static uint16_t ppuCycleTimer; //static uint16_t ppuCycleTimer; uint32_t cpuCycleTimer; uint32_t vrc7CycleTimer; //from input.c extern uint8_t inValReads[8]; //from m30.c extern bool m30_flashable; extern bool m30_singlescreen; //from m32.c extern bool m32_singlescreen; //from p16c8.c extern bool m78_m78a; //from ppu.c extern bool ppuMapper5; static volatile bool emuRenderFrame = false; extern uint8_t audioExpansion; // used externally bool emuSkipVsync = false; bool emuSkipFrame = false; // static uint32_t mCycles = 0; extern bool fdsSwitch; uint32_t apuGetMaxBufSize(); void apuResetPos(); uint8_t* ppuGetVRAM(); int audioUpdate() { if (!EmulatorInst) return 0; return EmulatorInst->audioUpdate(); } } namespace metaforce::MP1 { bool CNESEmulator::EmulatorConstructed = false; #define NESEMUP_ROM_OFFSET 0xa3f8 #define METROID_PAL false #define METROID_MAPPER 1 #define METROID_SAVE_ENABLED false #define METROID_TRAINER false #define METROID_PRG_SIZE (8 * 0x4000) #define METROID_CHR_SIZE (0 * 0x2000) #define METROID_PRG_RAM_SIZE 0x2000 CNESEmulator::CNESEmulator() { if (EmulatorConstructed) spdlog::fatal("Attempted constructing more than 1 CNESEmulator"); EmulatorConstructed = true; CDvdFile NESEmuFile("NESemuP.rel"); if (NESEmuFile) { m_nesEmuPBuf.reset(new u8[0x20000]); m_dvdReq = NESEmuFile.AsyncSeekRead(m_nesEmuPBuf.get(), 0x20000, ESeekOrigin::Begin, NESEMUP_ROM_OFFSET); } else { spdlog::fatal("Unable to open NESemuP.rel"); } } void CNESEmulator::InitializeEmulator() { nesPause = false; ppuDebugPauseFrame = false; puts(VERSION_STRING); strcpy(window_title, VERSION_STRING); memset(textureImage, 0, visibleImg); emuFileType = FTYPE_UNK; memset(emuFileName, 0, 1024); memset(emuSaveName, 0, 1024); nesPAL = METROID_PAL; uint8_t mapper = METROID_MAPPER; emuSaveEnabled = METROID_SAVE_ENABLED; bool trainer = METROID_TRAINER; uint32_t prgROMsize = METROID_PRG_SIZE; uint32_t chrROMsize = METROID_CHR_SIZE; emuPrgRAMsize = METROID_PRG_RAM_SIZE; emuPrgRAM = (uint8_t*)malloc(emuPrgRAMsize); uint8_t* prgROM = emuNesROM; if (trainer) { memcpy(emuPrgRAM + 0x1000, prgROM, 0x200); prgROM += 512; } uint8_t* chrROM = NULL; if (chrROMsize) { chrROM = emuNesROM + prgROMsize; if (trainer) chrROM += 512; } apuInitBufs(); cpuInit(); ppuInit(); memInit(); apuInit(); inputInit(); ppuSetNameTblVertical(); #if DEBUG_LOAD_INFO printf("Used Mapper: %i\n", mapper); printf("PRG: 0x%x bytes PRG RAM: 0x%x bytes CHR: 0x%x bytes\n", prgROMsize, emuPrgRAMsize, chrROMsize); #endif if (!mapperInit(mapper, prgROM, prgROMsize, emuPrgRAM, emuPrgRAMsize, chrROM, chrROMsize)) { printf("Mapper init failed!\n"); return; } #if DEBUG_LOAD_INFO printf("Trainer: %i Saving: %i VRAM Mode: Vertical\n", trainer, emuSaveEnabled); #endif sprintf(window_title, "%s NES - %s\n", nesPAL ? "PAL" : "NTSC", VERSION_STRING); sprintf(window_title_pause, "%s (Pause)", window_title); sprintf(window_title_pause, "%s (Pause)", window_title); #if DEBUG_HZ emuFrameStart = GetTickCount(); #endif #if DEBUG_MAIN_CALLS emuMainFrameStart = GetTickCount(); #endif cpuCycleTimer = nesPAL ? 16 : 12; vrc7CycleTimer = 432 / cpuCycleTimer; // do one scanline per idle loop // ppuCycleTimer = nesPAL ? 5 : 4; // mainLoopRuns = nesPAL ? DOTS*ppuCycleTimer : DOTS*ppuCycleTimer; // mainLoopPos = mainLoopRuns; // CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) { // // Nearest-neighbor FTW! // m_texture = ctx.newDynamicTexture(VISIBLE_DOTS, linesToDraw, boo::TextureFormat::RGBA8, // boo::TextureClampMode::ClampToEdgeNearest); // if (ctx.platform() == boo::IGraphicsDataFactory::Platform::OpenGL) { // Vert verts[4] = { // {{-1.f, -1.f, 0.f}, {0.f, 1.f}}, // {{-1.f, 1.f, 0.f}, {0.f, 0.f}}, // {{1.f, -1.f, 0.f}, {1.f, 1.f}}, // {{1.f, 1.f, 0.f}, {1.f, 0.f}}, // }; // m_vbo = ctx.newStaticBuffer(boo::BufferUse::Vertex, verts, sizeof(Vert), 4); // } else { // Vert verts[4] = { // {{-1.f, 1.f, 0.f}, {0.f, 1.f}}, // {{-1.f, -1.f, 0.f}, {0.f, 0.f}}, // {{1.f, 1.f, 0.f}, {1.f, 1.f}}, // {{1.f, -1.f, 0.f}, {1.f, 0.f}}, // }; // m_vbo = ctx.newStaticBuffer(boo::BufferUse::Vertex, verts, sizeof(Vert), 4); // } // m_uniBuf = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(Uniform), 1); // m_shadBind = CNESShader::BuildShaderDataBinding(ctx, m_vbo, m_uniBuf, m_texture); // return true; // } BooTrace); // double useFreq = 223740; double useFreq = apuGetFrequency(); //m_booVoice = CAudioSys::GetVoiceEngine()->allocateNewStereoVoice(useFreq, this); //m_booVoice->start(); uint32_t apuBufSz = apuGetMaxBufSize(); m_audioBufBlock.reset(new u8[apuBufSz * NUM_AUDIO_BUFFERS]); memset(m_audioBufBlock.get(), 0, apuBufSz * NUM_AUDIO_BUFFERS); for (int i = 0; i < NUM_AUDIO_BUFFERS; ++i) m_audioBufs[i] = m_audioBufBlock.get() + apuBufSz * i; EmulatorInst = this; } void CNESEmulator::DeinitializeEmulator() { // printf("\n"); emuRenderFrame = false; //m_booVoice->stop(); //m_booVoice.reset(); apuDeinitBufs(); if (emuNesROM != NULL) { if (!nesEmuNSFPlayback && (audioExpansion & EXP_FDS)) { FILE* save = fopen(emuSaveName, "wb"); if (save) { if (emuFdsHasSideB) fwrite(emuNesROM, 1, 0x20000, save); else fwrite(emuNesROM, 1, 0x10000, save); fclose(save); } } } if (emuPrgRAM != NULL) { if (emuSaveEnabled) { FILE* save = fopen(emuSaveName, "wb"); if (save) { fwrite(emuPrgRAM, 1, emuPrgRAMsize, save); fclose(save); } } free(emuPrgRAM); } emuPrgRAM = NULL; // printf("Bye!\n"); EmulatorInst = nullptr; } CNESEmulator::~CNESEmulator() { if (m_dvdReq) m_dvdReq->PostCancelRequest(); if (EmulatorInst) DeinitializeEmulator(); if (emuNesROM) { free(emuNesROM); emuNesROM = nullptr; } EmulatorConstructed = false; } int CNESEmulator::audioUpdate() { int origProcBufs = m_procBufs; // uint8_t* data = apuGetBuf(); // if (data != NULL && m_procBufs) { // uint32_t apuBufSz = apuGetMaxBufSize(); // uint32_t remBytes = apuGetBufSize(); // while (remBytes != 0) { // size_t thisBytes = std::min(remBytes, apuBufSz - m_posInHeadBuf); // memmove(m_audioBufs[m_headBuf] + m_posInHeadBuf, data, thisBytes); // data += thisBytes; // m_posInHeadBuf += thisBytes; // if (m_posInHeadBuf == apuBufSz) { // m_posInHeadBuf = 0; // --m_procBufs; // ++m_headBuf; // if (m_headBuf == NUM_AUDIO_BUFFERS) // m_headBuf = 0; // // printf("PUSH\n"); // } // remBytes -= thisBytes; // } // } // // // if (!origProcBufs) // // printf("OVERRUN\n"); // return origProcBufs; } static constexpr uint32_t AudioFrameSz = 2 * sizeof(int16_t); //size_t CNESEmulator::supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data) { // uint32_t remFrames = uint32_t(frames); // while (remFrames) { // if (m_posInTailBuf == apuGetMaxBufSize()) { // ++m_tailBuf; // if (m_tailBuf == NUM_AUDIO_BUFFERS) // m_tailBuf = 0; // m_posInTailBuf = 0; // ++m_procBufs; // // printf("POP\n"); // } // // if (m_procBufs == NUM_AUDIO_BUFFERS) { // memset(data, 0, remFrames * AudioFrameSz); // // printf("UNDERRUN\n"); // return frames; // } // // size_t copySz = std::min(apuGetMaxBufSize() - m_posInTailBuf, remFrames * AudioFrameSz); // memmove(data, m_audioBufs[m_tailBuf] + m_posInTailBuf, copySz); // data += copySz / sizeof(int16_t); // m_posInTailBuf += copySz; // remFrames -= copySz / AudioFrameSz; // } // return frames; //} void CNESEmulator::NesEmuMainLoop(bool forceDraw) { // int start = GetTickCount(); int loopCount = 0; do { if (emuRenderFrame || nesPause) { #if DEBUG_MAIN_CALLS emuMainTimesSkipped++; #endif // printf("LC RENDER: %d\n", loopCount); // TODO TODO // m_texture->load(textureImage, visibleImg); emuRenderFrame = false; break; } ++loopCount; // main CPU clock if (!cpuCycle()) exit(EXIT_SUCCESS); // run graphics ppuCycle(); // run audio apuCycle(); // mapper related irqs mapperCycle(); // mCycles++; if (ppuDrawDone()) { // printf("%i\n",mCycles); // mCycles = 0; #ifndef __LIBRETRO__ emuRenderFrame = true; #if 0 if(fm2playRunning()) fm2playUpdate(); #endif #if DEBUG_HZ emuTimesCalled++; auto end = GetTickCount(); emuTotalElapsed += end - emuFrameStart; if (emuTotalElapsed >= 1000) { printf("\r%iHz ", emuTimesCalled); emuTimesCalled = 0; emuTotalElapsed = 0; } emuFrameStart = end; #endif // update audio before drawing if (!apuUpdate()) { apuResetPos(); break; } // glutPostRedisplay(); #if 0 if(ppuDebugPauseFrame) { ppuDebugPauseFrame = false; nesPause = true; } #endif #endif if (nesEmuNSFPlayback) nsfVsync(); // keep processing frames if audio buffers are underrunning if (emuSkipFrame) emuRenderFrame = false; continue; } } while (true); #if 0 int end = GetTickCount(); printf("%dms %d %d\n", end - start, loopCount, m_procBufs); #endif #if DEBUG_MAIN_CALLS emuMainTimesCalled++; int end = GetTickCount(); // printf("%dms\n", end - start); emuMainTotalElapsed += end - emuMainFrameStart; if (emuMainTotalElapsed >= 1000) { printf("\r%i calls, %i skips ", emuMainTimesCalled, emuMainTimesSkipped); fflush(stdout); emuMainTimesCalled = 0; emuMainTimesSkipped = 0; emuMainTotalElapsed = 0; } emuMainFrameStart = end; #endif } #if 0 static void nesEmuFdsSetup(uint8_t *src, uint8_t *dst) { memcpy(dst, src, 0x38); memcpy(dst+0x3A, src+0x38, 2); uint16_t cDiskPos = 0x3E; uint16_t cROMPos = 0x3A; do { if(src[cROMPos] != 0x03) break; memcpy(dst+cDiskPos, src+cROMPos, 0x10); uint16_t copySize = (*(uint16_t*)(src+cROMPos+0xD))+1; cDiskPos+=0x12; cROMPos+=0x10; memcpy(dst+cDiskPos, src+cROMPos, copySize); cDiskPos+=copySize+2; cROMPos+=copySize; } while(cROMPos < 0xFFDC && cDiskPos < 0xFFFF); printf("%04x -> %04x\n", cROMPos, cDiskPos); } #endif struct BitstreamState { u8* rPos; int position = 0; int tmpBuf = 0; int decBit = 0; BitstreamState(u8* pos) : rPos(pos) {} void resetDecBit() { decBit = 0; } void runDecBit() { if (position == 0) { position = 8; tmpBuf = *rPos++; } decBit <<= 1; if (tmpBuf & 0x80) decBit |= 1; tmpBuf <<= 1; position--; } }; // Based on https://gist.github.com/FIX94/7593640c5cee6c37e3b23e7fcf8fe5b7 void CNESEmulator::DecryptMetroid(u8* dataIn, u8* dataOut, u32 decLen, u8 decByte, u32 xorLen, u32 xorVal) { u32 i, j; // simple add obfuscation for (i = 0; i < 0x100; i++) { dataIn[i] += decByte; decByte = dataIn[i]; } // flip the first 0x100 bytes around for (i = 0; i < 128; ++i) std::swap(dataIn[255 - i], dataIn[i]); // set up buffer pointers BitstreamState bState(dataIn + 0x100); // unscramble buffer for (i = 0; i < decLen; i++) { bState.resetDecBit(); bState.runDecBit(); if (bState.decBit) { bState.resetDecBit(); for (j = 0; j < 8; j++) bState.runDecBit(); dataOut[i] = dataIn[bState.decBit + 0x49]; } else { bState.resetDecBit(); bState.runDecBit(); if (bState.decBit) { bState.resetDecBit(); for (j = 0; j < 6; j++) bState.runDecBit(); dataOut[i] = dataIn[bState.decBit + 9]; } else { bState.resetDecBit(); bState.runDecBit(); if (bState.decBit) { bState.resetDecBit(); for (j = 0; j < 3; j++) bState.runDecBit(); dataOut[i] = dataIn[bState.decBit + 1]; } else dataOut[i] = dataIn[bState.decBit]; } } } // do checksum fixups unsigned int xorTmpVal = 0; for (i = 0; i < xorLen; i++) { xorTmpVal ^= dataOut[i]; for (j = 0; j < 8; j++) { if (xorTmpVal & 1) { xorTmpVal >>= 1; xorTmpVal ^= xorVal; } else xorTmpVal >>= 1; } } // write in calculated checksum dataOut[xorLen - 1] = u8((xorTmpVal >> 8) & 0xFF); dataOut[xorLen - 2] = u8(xorTmpVal & 0xFF); } void CNESEmulator::ProcessUserInput(const CFinalInput& input, int) { if (input.ControllerIdx() != 0) return; if (GetPasswordEntryState() != EPasswordEntryState::NotPasswordScreen) { // Don't swap A/B inValReads[BUTTON_A] = input.DA() || input.DSpecialKey(ESpecialKey::Enter) || input.DMouseButton(EMouseButton::Primary); inValReads[BUTTON_B] = input.DB() || input.DSpecialKey(ESpecialKey::Esc); } else { // Prime controls (B jumps, A shoots) inValReads[BUTTON_B] = input.DA() || input.DY() || input.DMouseButton(EMouseButton::Primary); inValReads[BUTTON_A] = input.DB() || input.DX() || input.DKey(' '); } inValReads[BUTTON_UP] = input.DDPUp() || input.DLAUp(); inValReads[BUTTON_DOWN] = input.DDPDown() || input.DLADown(); inValReads[BUTTON_LEFT] = input.DDPLeft() || input.DLALeft(); inValReads[BUTTON_RIGHT] = input.DDPRight() || input.DLARight(); inValReads[BUTTON_SELECT] = input.DZ() || input.DSpecialKey(ESpecialKey::Tab); inValReads[BUTTON_START] = input.DStart() || input.DSpecialKey(ESpecialKey::Esc); } bool CNESEmulator::CheckForGameOver(const u8* vram, u8* passwordOut) { // "PASS WORD" if (memcmp(vram + 0x14B, "\x19\xa\x1c\x1c\xff\x20\x18\x1b\xd", 9)) return false; int chOff = 0; int encOff = 0; u8 pwOut[18]; for (int i = 0; i < 24; ++i) { u8 chName = vram[0x1A9 + chOff]; ++chOff; if (chOff == 0x6 || chOff == 0x46) ++chOff; // mid-line space else if (chOff == 0xd) chOff = 64; // 2nd line if (chName > 0x3f) return false; switch (i & 0x3) { case 0: pwOut[encOff] = chName; break; case 1: pwOut[encOff] |= chName << 6; ++encOff; pwOut[encOff] = chName >> 2; break; case 2: pwOut[encOff] |= chName << 4; ++encOff; pwOut[encOff] = chName >> 4; break; case 3: pwOut[encOff] |= chName << 2; ++encOff; break; default: break; } } if (passwordOut) memmove(passwordOut, pwOut, 18); return true; } CNESEmulator::EPasswordEntryState CNESEmulator::CheckForPasswordEntryScreen(const u8* vram) { // "PASS WORD PLEASE" if (memcmp(vram + 0x88, "\x19\xa\x1c\x1c\xff\x20\x18\x1b\xd\xff\x19\x15\xe\xa\x1c\xe", 16)) return EPasswordEntryState::NotPasswordScreen; for (int i = 0; i < 13; ++i) if (vram[0x109 + i] < 0x40 || vram[0x149 + i] < 0x40) return EPasswordEntryState::Entered; return EPasswordEntryState::NotEntered; } bool CNESEmulator::SetPasswordIntoEntryScreen(u8* vram, u8* wram, const u8* password) { if (CheckForPasswordEntryScreen(vram) != EPasswordEntryState::NotEntered) return false; int i; for (i = 0; i < 18; ++i) if (password[i]) break; if (i == 18) return false; int encOff = 0; int chOff = 0; u32 lastWord = 0; for (i = 0; i < 24; ++i) { switch (i & 0x3) { case 0: lastWord = password[encOff]; ++encOff; break; case 1: lastWord = (lastWord >> 6) | (u32(password[encOff]) << 2); ++encOff; break; case 2: lastWord = (lastWord >> 6) | (u32(password[encOff]) << 4); ++encOff; break; case 3: lastWord = (lastWord >> 6); break; default: break; } u8 chName = u8(lastWord & 0x3f); wram[0x99a + i] = chName; vram[0x109 + chOff] = chName; ++chOff; if (chOff == 0x6 || chOff == 0x46) ++chOff; // mid-line space else if (chOff == 0xd) chOff = 64; // 2nd line } return true; } void CNESEmulator::Update() { if (!EmulatorInst) { if (m_dvdReq && m_dvdReq->IsComplete()) { m_dvdReq.reset(); emuNesROMsize = 0x20000; emuNesROM = (uint8_t*)malloc(emuNesROMsize); DecryptMetroid(m_nesEmuPBuf.get(), emuNesROM); m_nesEmuPBuf.reset(); InitializeEmulator(); } } else { if (nesPause) { DeinitializeEmulator(); InitializeEmulator(); return; } bool gameOver = CheckForGameOver(ppuGetVRAM(), x21_passwordFromNES); x34_passwordEntryState = CheckForPasswordEntryScreen(ppuGetVRAM()); if (x34_passwordEntryState == EPasswordEntryState::NotEntered && x38_passwordPending) { SetPasswordIntoEntryScreen(ppuGetVRAM(), emuPrgRAM, x39_passwordToNES); x38_passwordPending = false; } if (gameOver && !x20_gameOver) for (int i = 0; i < 3; ++i) // Three draw loops to ensure password display NesEmuMainLoop(true); else NesEmuMainLoop(); x20_gameOver = gameOver; } } static const float NESAspect = VISIBLE_DOTS / float(VISIBLE_LINES); void CNESEmulator::Draw(const zeus::CColor& mulColor, bool filtering) { if (!EmulatorInst) return; float widthFac = NESAspect / CGraphics::GetViewportAspect(); Uniform uniform = {zeus::CMatrix4f{}, mulColor}; uniform.m_matrix[0][0] = widthFac; // m_uniBuf->load(&uniform, sizeof(Uniform)); // // CGraphics::SetShaderDataBinding(m_shadBind); // CGraphics::DrawArray(0, 4); } void CNESEmulator::LoadPassword(const u8* state) { memmove(x39_passwordToNES, state, 18); x38_passwordPending = true; } } // namespace metaforce::MP1 ================================================ FILE: NESEmulator/CNESEmulator.hpp ================================================ #pragma once #include "RetroTypes.hpp" #include "zeus/CColor.hpp" //#include "boo/graphicsdev/IGraphicsDataFactory.hpp" #include "zeus/CMatrix4f.hpp" #include "Runtime/Graphics/CGraphics.hpp" namespace metaforce { struct CFinalInput; class IDvdRequest; namespace MP1 { #define NUM_AUDIO_BUFFERS 4 class CNESEmulator final { public: enum class EPasswordEntryState { NotPasswordScreen, NotEntered, Entered }; private: static bool EmulatorConstructed; std::unique_ptr m_nesEmuPBuf; std::shared_ptr m_dvdReq; struct Vert { zeus::CVector3f m_pos; zeus::CVector2f m_uv; }; struct Uniform { zeus::CMatrix4f m_matrix; zeus::CColor m_color; }; TGXTexObj m_texture; // boo::ObjToken m_uniBuf; // boo::ObjToken m_vbo; // boo::ObjToken m_shadBind; std::unique_ptr m_audioBufBlock; u8* m_audioBufs[NUM_AUDIO_BUFFERS]; uint32_t m_headBuf = 0; uint32_t m_tailBuf = 0; uint32_t m_procBufs = NUM_AUDIO_BUFFERS; uint32_t m_posInHeadBuf = 0; uint32_t m_posInTailBuf = 0; //boo::ObjToken m_booVoice; // void* x4_loadBuf; // void* x8_rom; // void* xc_state; // OSModuleInfo* x10_module = x4_loadBuf; // void* x14_bss; // void* x18_prgram; // void* x1c_wram; bool x20_gameOver = false; u8 x21_passwordFromNES[18]; EPasswordEntryState x34_passwordEntryState = EPasswordEntryState::NotPasswordScreen; bool x38_passwordPending = false; u8 x39_passwordToNES[18]; static void DecryptMetroid(u8* dataIn, u8* dataOut, u32 decLen = 0x20000, u8 decByte = 0xe9, u32 xorLen = 0x1FFFC, u32 xorVal = 0xA663); void InitializeEmulator(); void DeinitializeEmulator(); void NesEmuMainLoop(bool forceDraw = false); static bool CheckForGameOver(const u8* vram, u8* passwordOut = nullptr); static EPasswordEntryState CheckForPasswordEntryScreen(const uint8_t* vram); static bool SetPasswordIntoEntryScreen(u8* vram, u8* wram, const u8* password); public: CNESEmulator(); ~CNESEmulator(); void ProcessUserInput(const CFinalInput& input, int); void Update(); void Draw(const zeus::CColor& mulColor, bool filtering); void LoadPassword(const u8* state); const u8* GetPassword() const { return x21_passwordFromNES; } bool IsGameOver() const { return x20_gameOver; } EPasswordEntryState GetPasswordEntryState() const { return x34_passwordEntryState; } int audioUpdate(); //void preSupplyAudio(boo::IAudioVoice& voice, double dt) {} //size_t supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data); }; } // namespace MP1 } // namespace metaforce ================================================ FILE: NESEmulator/apu.c ================================================ #include "fixNES/apu.c" uint32_t apuGetMaxBufSize() { return apu.BufSizeBytes; } void apuResetPos() { apu.curBufPos = 0; } ================================================ FILE: NESEmulator/malloc.h ================================================ #ifndef URDE_NESEMULATOR_MALLOC_H #define URDE_NESEMULATOR_MALLOC_H #ifdef __APPLE__ #include #elif _WIN32 #include <../ucrt/malloc.h> #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Winclude-next-absolute-path" #include_next #pragma GCC diagnostic pop #endif #endif // URDE_NESEMULATOR_MALLOC_H ================================================ FILE: NESEmulator/ppu.c ================================================ #include "fixNES/ppu.c" uint8_t* ppuGetVRAM() { return ppu.VRAM; } ================================================ FILE: README.md ================================================ ## Metaforce [![Build Status]][actions] [![Discord Badge]][discord] [Build Status]: https://github.com/AxioDL/metaforce/actions/workflows/build.yml/badge.svg [actions]: https://github.com/AxioDL/metaforce/actions [Discord Badge]: https://dcbadge.vercel.app/api/server/AMBVFuf?style=flat [discord]: https://discord.gg/AMBVFuf A reverse-engineered, native reimplementation of Metroid Prime. This project is currently in **alpha** state. Builds are currently unavailable while the project undergoes large changes. Separately, a [matching decompilation](https://github.com/PrimeDecomp/prime) of Metroid Prime is currently underway. Contributions are welcome. Progress on the decompilation benefits Metaforce with bug fixes and new implementations. ![Metaforce screenshot](assets/metaforce-screen1.png) ### Platform Support * Windows 10+ (64-bit, D3D12 / Vulkan / OpenGL) * macOS 10.15+ (Metal) * Linux (Vulkan / OpenGL) * Follow [this guide](https://github.com/lutris/docs/blob/master/InstallingDrivers.md) to set up Vulkan & appropriate drivers for your distro. ### Usage Windows: - Open `metaforce.exe` macOS: - Open `Metaforce.app` Linux: - Ensure AppImage is marked as executable: `chmod +x Metaforce-*.AppImage` - Open `Metaforce-*.AppImage` #### CLI options (non-exhaustive) * `-l`: Enable console logging * `--warp [worldid] [areaid]`: Warp to a specific world/area. Example: `--warp 2 2` * `+developer=1`: Enable developer UI ### Build Prerequisites: * [CMake 3.25+](https://cmake.org) * Windows: Install `CMake Tools` in Visual Studio * macOS: `brew install cmake` * [Python 3+](https://python.org) * Windows: [Microsoft Store](https://go.microsoft.com/fwlink?linkID=2082640) * Verify it's added to `%PATH%` by typing `python` in `cmd`. * macOS: `brew install python@3` * **[Windows]** [Visual Studio 2019 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) * Select `C++ Development` and verify the following packages are included: * `Windows 10 SDK` * `CMake Tools` * `C++ Clang Compiler` * `C++ Clang-cl` * **[macOS]** [Xcode 11.5+](https://developer.apple.com/xcode/download/) * **[Linux]** Actively tested on Ubuntu 20.04, Arch Linux & derivatives. * Ubuntu 20.04+ packages ``` build-essential curl git ninja-build clang lld zlib1g-dev libcurl4-openssl-dev \ libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev libpulse-dev \ libudev-dev libpng-dev libncurses5-dev cmake libx11-xcb-dev python3 python-is-python3 \ libclang-dev libfreetype-dev libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev ``` * Arch Linux packages ``` base-devel cmake ninja llvm vulkan-headers python python-markupsafe clang lld alsa-lib libpulse libxrandr freetype2 ``` * Fedora packages ``` cmake vulkan-headers ninja-build clang-devel llvm-devel libpng-devel ``` * It's also important that you install the developer tools and libraries ``` sudo dnf groupinstall "Development Tools" "Development Libraries" ``` ### Prep Directions ```sh git clone --recursive https://github.com/AxioDL/metaforce.git cd metaforce ``` ### Update Directions ```sh cd metaforce git pull git submodule update --recursive ``` ### Build Directions For Windows, it's recommended to use Visual Studio. See below. #### ninja (Windows/macOS/Linux) Builds using `RelWithDebInfo` by default. ```sh cmake -B out -G Ninja # add extra options here cmake --build out --target metaforce hecl visigen ``` #### CMake configure options - Build in debug mode (slower runtime speed, better backtraces): `-DCMAKE_BUILD_TYPE=Debug` - Use clang+lld (faster linking): `-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` - Optimize for current CPU (resulting binaries are not portable): `-DMETAFORCE_VECTOR_ISA=native` #### CLion (Windows/macOS/Linux) *(main development / debugging IDE)* Open the repository's `CMakeLists.txt`. Optionally configure CMake options via `File` > `Settings` > `Build, Execution, Deployment` > `CMake`. #### Qt Creator (Windows/macOS/Linux) Open the repository's `CMakeLists.txt` via File > Open File or Project. Configure the desired CMake targets to build in the *Projects* area of the IDE. #### Visual Studio (Windows) Verify all required VS packages are installed from the above **Build Prerequisites** section. Open the `metaforce` directory in Visual Studio (imports CMake configuration). MSVC and clang-cl configurations should import automatically. #### Xcode (macOS) ```sh cmake -G Xcode ../metaforce ``` Then open `metaforce.xcodeproj` ================================================ FILE: Runtime/Audio/CAudioGroupSet.cpp ================================================ #include "Runtime/Audio/CAudioGroupSet.hpp" #include namespace metaforce { /* amuse::AudioGroupData CAudioGroupSet::LoadData() { const auto readU32 = [](const u8* ptr) { uint32_t value; std::memcpy(&value, ptr, sizeof(value)); return SBig(value); }; CMemoryInStream r(m_buffer.get(), INT32_MAX, CMemoryInStream::EOwnerShip::NotOwned); x10_baseName = r.Get(); x20_name = r.Get(); u8* buf = m_buffer.get() + r.GetReadPosition(); const uint32_t poolLen = readU32(buf); unsigned char* pool = buf + 4; buf += poolLen + 4; const uint32_t projLen = readU32(buf); unsigned char* proj = buf + 4; buf += projLen + 4; const uint32_t sampLen = readU32(buf); unsigned char* samp = buf + 4; buf += sampLen + 4; const uint32_t sdirLen = readU32(buf); unsigned char* sdir = buf + 4; return {proj, projLen, pool, poolLen, sdir, sdirLen, samp, sampLen, amuse::GCNDataTag{}}; } */ CAudioGroupSet::CAudioGroupSet(std::unique_ptr&& in) : m_buffer(std::move(in)) {} CFactoryFnReturn FAudioGroupSetDataFactory(const metaforce::SObjectTag& tag, std::unique_ptr&& in, u32 len, const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(std::move(in))); } } // namespace metaforce ================================================ FILE: Runtime/Audio/CAudioGroupSet.hpp ================================================ #pragma once #include #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/CToken.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/IObj.hpp" #include "Runtime/RetroTypes.hpp" //#include namespace metaforce { class CAudioGroupSet { std::unique_ptr m_buffer; std::string x10_baseName; std::string x20_name; // amuse::AudioGroupData m_data; // amuse::AudioGroupData LoadData(); public: explicit CAudioGroupSet(std::unique_ptr&& in); //const amuse::AudioGroupData& GetAudioGroupData() const { return m_data; } std::string_view GetName() const { return x20_name; } }; CFactoryFnReturn FAudioGroupSetDataFactory(const metaforce::SObjectTag& tag, std::unique_ptr&& in, u32 len, const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Audio/CAudioSys.cpp ================================================ #include "Runtime/Audio/CAudioSys.hpp" #include #include #include "Runtime/CSimplePool.hpp" #include "Runtime/Audio/CAudioGroupSet.hpp" namespace metaforce { namespace { std::unordered_map> mpGroupSetDB; std::unordered_map mpGroupSetResNameDB; constexpr std::string_view mpDefaultInvalidString = "NULL"; float s_MasterVol = 1.f; float s_SfxVol = 1.f; s16 s_VolumeScale = 0x7f; s16 s_DefaultVolumeScale = 0x7f; } // Anonymous namespace CAudioSys* CAudioSys::g_SharedSys = nullptr; TLockedToken CAudioSys::FindGroupSet(std::string_view name) { // TODO: Heterogeneous lookup when C++20 available auto search = mpGroupSetDB.find(name.data()); if (search == mpGroupSetDB.cend()) return {}; return search->second; } std::string_view CAudioSys::SysGetGroupSetName(CAssetId id) { auto search = mpGroupSetResNameDB.find(id); if (search == mpGroupSetResNameDB.cend()) return mpDefaultInvalidString; return search->second; } bool CAudioSys::SysLoadGroupSet(CSimplePool* pool, CAssetId id) { if (!FindGroupSet(SysGetGroupSetName(id))) { TLockedToken set = pool->GetObj(SObjectTag{FOURCC('AGSC'), id}); mpGroupSetDB.emplace(set->GetName(), set); mpGroupSetResNameDB.emplace(id, set->GetName()); return false; } else { return true; } } bool CAudioSys::SysLoadGroupSet(const TLockedToken& set, std::string_view name, CAssetId id) { if (!FindGroupSet(name)) { mpGroupSetDB.emplace(set->GetName(), set); mpGroupSetResNameDB.emplace(id, set->GetName()); return false; } else { return true; } } void CAudioSys::SysUnloadAudioGroupSet(std::string_view name) { auto set = FindGroupSet(name); if (!set) return; mpGroupSetDB.erase(name.data()); mpGroupSetResNameDB.erase(set.GetObjectTag()->id); } bool CAudioSys::SysIsGroupSetLoaded(std::string_view name) { return FindGroupSet(name).operator bool(); } void CAudioSys::SysAddGroupIntoAmuse(std::string_view name) { } void CAudioSys::SysRemoveGroupFromAmuse(std::string_view name) { } void CAudioSys::_UpdateVolume() { } void CAudioSys::SysSetVolume(u8 volume) { s_MasterVol = volume / 127.f; _UpdateVolume(); } void CAudioSys::SysSetSfxVolume(u8 volume, u16 time, bool music, bool fx) { s_SfxVol = volume / 127.f; _UpdateVolume(); } s16 CAudioSys::GetDefaultVolumeScale() { return s_DefaultVolumeScale; } void CAudioSys::SetDefaultVolumeScale(s16 scale) { s_DefaultVolumeScale = scale; } void CAudioSys::SetVolumeScale(s16 scale) { s_VolumeScale = scale; } } // namespace metaforce ================================================ FILE: Runtime/Audio/CAudioSys.hpp ================================================ #pragma once #include "Runtime/CToken.hpp" #include "Runtime/GCNTypes.hpp" #include "Runtime/RetroTypes.hpp" //#include //#include #include namespace metaforce { class CAudioGroupSet; class CSimplePool; CFactoryFnReturn FAudioTranslationTableFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef); class CAudioSys { public: enum class ESurroundModes { Mono, Stereo, Surround }; private: static CAudioSys* g_SharedSys; static void _UpdateVolume(); public: struct C3DEmitterParmData { zeus::CVector3f x0_pos; zeus::CVector3f xc_dir; float x18_maxDist; float x1c_distComp; u32 x20_flags; u16 x24_sfxId; float x26_maxVol; float x27_minVol; bool x28_important; // Can't be allocated over, regardless of priority u8 x29_prio; }; CAudioSys(u8, u8, u8, u8, u32) { g_SharedSys = this; } ~CAudioSys() { g_SharedSys = nullptr; } static void SetSurroundMode(ESurroundModes mode) {} static TLockedToken FindGroupSet(std::string_view name); static std::string_view SysGetGroupSetName(CAssetId id); static bool SysLoadGroupSet(CSimplePool* pool, CAssetId id); static bool SysLoadGroupSet(const TLockedToken& set, std::string_view name, CAssetId id); static void SysUnloadAudioGroupSet(std::string_view name); static bool SysIsGroupSetLoaded(std::string_view name); static void SysAddGroupIntoAmuse(std::string_view name); static void SysRemoveGroupFromAmuse(std::string_view name); static void SysSetVolume(u8 volume); static void SysSetSfxVolume(u8 volume, u16 time, bool music, bool fx); static s16 GetDefaultVolumeScale(); static void SetDefaultVolumeScale(s16 scale); static void SetVolumeScale(s16 scale); }; } // namespace metaforce ================================================ FILE: Runtime/Audio/CMakeLists.txt ================================================ set(AUDIO_SOURCES CAudioSys.hpp CAudioSys.cpp CAudioGroupSet.hpp CAudioGroupSet.cpp CSfxManager.hpp CSfxManager.cpp CMidiManager.hpp CMidiManager.cpp CStaticAudioPlayer.hpp CStaticAudioPlayer.cpp CStreamAudioManager.hpp CStreamAudioManager.cpp g721.c g721.h) runtime_add_list(Audio AUDIO_SOURCES) ================================================ FILE: Runtime/Audio/CMidiManager.cpp ================================================ #include "Runtime/Audio/CMidiManager.hpp" #include "Runtime/Streams/CInputStream.hpp" namespace metaforce { std::unordered_set CMidiManager::m_MidiWrappers = {}; void CMidiManager::StopAll() { for (auto it = m_MidiWrappers.begin(); it != m_MidiWrappers.end();) it = Stop(it, 0.f); } void CMidiManager::Stop(const CMidiHandle& handle, float fadeTime) { // handle->GetAudioSysHandle()->stopSong(fadeTime); // m_MidiWrappers.erase(handle); } std::unordered_set::iterator CMidiManager::Stop(std::unordered_set::iterator handle, float fadeTime) { // const CMidiHandle& h = *handle; // h->GetAudioSysHandle()->stopSong(fadeTime); return m_MidiWrappers.erase(handle); } CMidiHandle CMidiManager::Play(const CMidiData& data, float fadeTime, bool stopExisting, float volume) { if (stopExisting) for (auto it = m_MidiWrappers.begin(); it != m_MidiWrappers.end();) it = Stop(it, fadeTime); CMidiHandle handle = *m_MidiWrappers.insert(std::make_shared()).first; // handle->SetAudioSysHandle( // CAudioSys::GetAmuseEngine().seqPlay(data.GetGroupId(), data.GetSetupId(), data.GetArrData())); // handle->GetAudioSysHandle()->setVolume(volume, fadeTime); // handle->SetSongId(data.GetSetupId()); return handle; } CMidiManager::CMidiData::CMidiData(CInputStream& in) { in.ReadLong(); x0_setupId = in.ReadLong(); x2_groupId = in.ReadLong(); x4_agscId = in.Get(); u32 length = in.ReadLong(); x8_arrData.reset(new u8[length]); in.ReadBytes(reinterpret_cast(x8_arrData.get()), length); } CFactoryFnReturn FMidiDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/Audio/CMidiManager.hpp ================================================ #pragma once #include #include "Runtime/Audio/CSfxManager.hpp" namespace metaforce { class CMidiManager { public: class CMidiData { u16 x0_setupId; u16 x2_groupId; CAssetId x4_agscId; std::unique_ptr x8_arrData; public: u16 GetSetupId() const { return x0_setupId; } u16 GetGroupId() const { return x2_groupId; } CAssetId GetAGSCAssetId() const { return x4_agscId; } const u8* GetArrData() const { return x8_arrData.get(); } explicit CMidiData(CInputStream& in); }; class CMidiWrapper { //amuse::ObjToken x0_sequencer; // CSfxHandle x4_handle; u16 x8_songId; bool xa_available = true; public: //amuse::ObjToken GetAudioSysHandle() const { return x0_sequencer; } //void SetAudioSysHandle(amuse::ObjToken sequencer) { x0_sequencer = std::move(sequencer); } // const CSfxHandle& GetManagerHandle() const { return x4_handle; } // void SetMidiHandle(const CSfxHandle& handle) { x4_handle = handle; } bool IsAvailable() const { return xa_available; } void SetAvailable(bool available) { xa_available = available; } u16 GetSongId() const { return x8_songId; } void SetSongId(u16 songId) { x8_songId = songId; } }; using CMidiHandle = std::shared_ptr; static void StopAll(); static void Stop(const CMidiHandle& handle, float fadeTime); static std::unordered_set::iterator Stop(std::unordered_set::iterator handle, float fadeTime); static CMidiHandle Play(const CMidiData& data, float fadeTime, bool stopExisting, float volume); private: static std::unordered_set m_MidiWrappers; }; CFactoryFnReturn FMidiDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms, CObjectReference* selfRef); using CMidiHandle = CMidiManager::CMidiHandle; } // namespace metaforce ================================================ FILE: Runtime/Audio/CSfxManager.cpp ================================================ #include "Runtime/Audio/CSfxManager.hpp" #include "Runtime/Streams/CInputStream.hpp" #include "Runtime/CSimplePool.hpp" namespace metaforce { static TLockedToken> mpSfxTranslationTableTok; std::vector* CSfxManager::mpSfxTranslationTable = nullptr; //static amuse::EffectReverbHiInfo s_ReverbHiQueued; //static amuse::EffectChorusInfo s_ChorusQueued; //static amuse::EffectReverbStdInfo s_ReverbStdQueued; //static amuse::EffectDelayInfo s_DelayQueued; // //static amuse::EffectReverbHi* s_ReverbHiState = nullptr; //static amuse::EffectChorus* s_ChorusState = nullptr; //static amuse::EffectReverbStd* s_ReverbStdState = nullptr; //static amuse::EffectDelay* s_DelayState = nullptr; CFactoryFnReturn FAudioTranslationTableFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef) { std::unique_ptr> obj = std::make_unique>(); u32 count = in.ReadLong(); obj->reserve(count); for (u32 i = 0; i < count; ++i) obj->push_back(in.ReadShort()); return TToken>::GetIObjObjectFor(std::move(obj)); } std::array CSfxManager::m_channels; CSfxManager::ESfxChannels CSfxManager::m_currentChannel = CSfxManager::ESfxChannels::Default; bool CSfxManager::m_doUpdate; void* CSfxManager::m_usedSounds; bool CSfxManager::m_muted; bool CSfxManager::m_auxProcessingEnabled = false; float CSfxManager::m_reverbAmount = 1.f; CSfxManager::EAuxEffect CSfxManager::m_activeEffect = CSfxManager::EAuxEffect::None; CSfxManager::EAuxEffect CSfxManager::m_nextEffect = CSfxManager::EAuxEffect::None; //amuse::ObjToken CSfxManager::m_listener; u16 CSfxManager::kMaxPriority; u16 CSfxManager::kMedPriority; u16 CSfxManager::kInternalInvalidSfxId; u32 CSfxManager::kAllAreas; bool CSfxManager::LoadTranslationTable(CSimplePool* pool, const SObjectTag* tag) { if (!tag) return false; mpSfxTranslationTable = nullptr; mpSfxTranslationTableTok = pool->GetObj(*tag); if (!mpSfxTranslationTableTok) return false; return true; } bool CSfxManager::CSfxWrapper::IsPlaying() const { // if (CBaseSfxWrapper::IsPlaying() && x1c_voiceHandle) // return x1c_voiceHandle->state() == amuse::VoiceState::Playing; return false; } void CSfxManager::CSfxWrapper::Play() { // x1c_voiceHandle = CAudioSys::GetAmuseEngine().fxStart(x18_sfxId, x20_vol, x22_pan); // if (x1c_voiceHandle) { // if (CSfxManager::IsAuxProcessingEnabled() && UseAcoustics()) // x1c_voiceHandle->setReverbVol(m_reverbAmount); // SetPlaying(true); // } x24_ready = false; } void CSfxManager::CSfxWrapper::Stop() { // if (x1c_voiceHandle) { // x1c_voiceHandle->keyOff(); // SetPlaying(false); // x1c_voiceHandle.reset(); // } } bool CSfxManager::CSfxWrapper::Ready() { if (IsLooped()) return true; return x24_ready; } u16 CSfxManager::CSfxWrapper::GetSfxId() const { return x18_sfxId; } void CSfxManager::CSfxWrapper::UpdateEmitterSilent() { // if (x1c_voiceHandle) // x1c_voiceHandle->setVolume(1.f / 127.f); } void CSfxManager::CSfxWrapper::UpdateEmitter() { // if (x1c_voiceHandle) // x1c_voiceHandle->setVolume(x20_vol); } void CSfxManager::CSfxWrapper::SetReverb(float rev) { // if (x1c_voiceHandle && IsAuxProcessingEnabled() && UseAcoustics()) // x1c_voiceHandle->setReverbVol(rev); } bool CSfxManager::CSfxEmitterWrapper::IsPlaying() const { if (IsLooped()) return CBaseSfxWrapper::IsPlaying(); // if (CBaseSfxWrapper::IsPlaying() && x50_emitterHandle) // return x50_emitterHandle->getVoice()->state() == amuse::VoiceState::Playing; return false; } void CSfxManager::CSfxEmitterWrapper::Play() { if (CSfxManager::IsAuxProcessingEnabled() && UseAcoustics()) x1a_reverb = m_reverbAmount; else x1a_reverb = 0.f; // zeus::simd_floats pos(x24_parmData.x0_pos.mSimd); // zeus::simd_floats dir(x24_parmData.xc_dir.mSimd); // x50_emitterHandle = CAudioSys::GetAmuseEngine().addEmitter( // pos.data(), dir.data(), x24_parmData.x18_maxDist, x24_parmData.x1c_distComp, x24_parmData.x24_sfxId, // x24_parmData.x27_minVol, x24_parmData.x26_maxVol, (x24_parmData.x20_flags & 0x8) != 0); // // if (x50_emitterHandle) // SetPlaying(true); x54_ready = false; } void CSfxManager::CSfxEmitterWrapper::Stop() { // if (x50_emitterHandle) { // x50_emitterHandle->getVoice()->keyOff(); // SetPlaying(false); // x50_emitterHandle.reset(); // } } bool CSfxManager::CSfxEmitterWrapper::Ready() { if (IsLooped()) return true; return x54_ready; } CSfxManager::ESfxAudibility CSfxManager::CSfxEmitterWrapper::GetAudible(const zeus::CVector3f& vec) { float magSq = (x24_parmData.x0_pos - vec).magSquared(); float maxDist = x24_parmData.x18_maxDist * x24_parmData.x18_maxDist; if (magSq < maxDist * 0.25f) return ESfxAudibility::Aud3; else if (magSq < maxDist * 0.5f) return ESfxAudibility::Aud2; else if (magSq < maxDist) return ESfxAudibility::Aud1; return ESfxAudibility::Aud0; } u16 CSfxManager::CSfxEmitterWrapper::GetSfxId() const { return x24_parmData.x24_sfxId; } void CSfxManager::CSfxEmitterWrapper::UpdateEmitterSilent() { // if (x50_emitterHandle) { // zeus::simd_floats pos(x24_parmData.x0_pos.mSimd); // zeus::simd_floats dir(x24_parmData.xc_dir.mSimd); // x50_emitterHandle->setVectors(pos.data(), dir.data()); // x50_emitterHandle->setMaxVol(1.f / 127.f); // } x55_cachedMaxVol = x24_parmData.x26_maxVol; } void CSfxManager::CSfxEmitterWrapper::UpdateEmitter() { // if (x50_emitterHandle) { // zeus::simd_floats pos(x24_parmData.x0_pos.mSimd); // zeus::simd_floats dir(x24_parmData.xc_dir.mSimd); // x50_emitterHandle->setVectors(pos.data(), dir.data()); // x50_emitterHandle->setMaxVol(x55_cachedMaxVol); // } } void CSfxManager::CSfxEmitterWrapper::SetReverb(float rev) { if (IsAuxProcessingEnabled() && UseAcoustics()) x1a_reverb = rev; } void CSfxManager::SetChannel(ESfxChannels chan) { if (m_currentChannel == chan) return; if (m_currentChannel != ESfxChannels::Invalid) TurnOffChannel(m_currentChannel); TurnOnChannel(chan); m_currentChannel = chan; } void CSfxManager::KillAll(ESfxChannels chan) { CSfxChannel& chanObj = m_channels[size_t(chan)]; for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) { const CSfxHandle& handle = *it; handle->Stop(); handle->Release(); handle->Close(); it = chanObj.x48_handles.erase(it); } } void CSfxManager::TurnOnChannel(ESfxChannels chan) { CSfxChannel& chanObj = m_channels[size_t(chan)]; m_currentChannel = chan; m_doUpdate = true; if (chanObj.x44_listenerActive) { for (const CSfxHandle& handle : chanObj.x48_handles) { handle->UpdateEmitter(); } } } void CSfxManager::TurnOffChannel(ESfxChannels chan) { CSfxChannel& chanObj = m_channels[size_t(chan)]; for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) { const CSfxHandle& handle = *it; if (handle->IsLooped()) { handle->UpdateEmitterSilent(); } else { handle->Stop(); handle->Close(); it = chanObj.x48_handles.erase(it); continue; } ++it; } for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) { const CSfxHandle& handle = *it; if (!handle->IsLooped()) { handle->Release(); handle->Close(); it = chanObj.x48_handles.erase(it); continue; } ++it; } } void CSfxManager::AddListener(ESfxChannels channel, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CVector3f& heading, const zeus::CVector3f& up, float frontRadius, float surroundRadius, float soundSpeed, u32 flags /* 0x1 for doppler */, float vol) { // if (m_listener) // CAudioSys::GetAmuseEngine().removeListener(m_listener.get()); // zeus::simd_floats p(pos.mSimd); // zeus::simd_floats d(dir.mSimd); // zeus::simd_floats h(heading.mSimd); // zeus::simd_floats u(up.mSimd); // m_listener = CAudioSys::GetAmuseEngine().addListener(p.data(), d.data(), h.data(), u.data(), frontRadius, // surroundRadius, soundSpeed, vol); } void CSfxManager::UpdateListener(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CVector3f& heading, const zeus::CVector3f& up, float vol) { // if (m_listener) { // zeus::simd_floats p(pos.mSimd); // zeus::simd_floats d(dir.mSimd); // zeus::simd_floats h(heading.mSimd); // zeus::simd_floats u(up.mSimd); // m_listener->setVectors(p.data(), d.data(), h.data(), u.data()); // m_listener->setVolume(vol); // } } s16 CSfxManager::GetRank(CBaseSfxWrapper* sfx) { const CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)]; if (!sfx->IsInArea()) { return 0; } s16 rank = sfx->GetPriority() / 4; if (sfx->IsPlaying()) { ++rank; } if (sfx->IsLooped()) { rank -= 2; } if (sfx->Ready() && !sfx->IsPlaying()) { rank += 3; } if (chanObj.x44_listenerActive) { const ESfxAudibility aud = sfx->GetAudible(chanObj.x0_pos); if (aud == ESfxAudibility::Aud0) { return 0; } rank += int(aud) / 2; } return rank; } void CSfxManager::ApplyReverb() { const CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)]; for (const CSfxHandle& handle : chanObj.x48_handles) { handle->SetReverb(m_reverbAmount); } } float CSfxManager::GetReverbAmount() { return m_reverbAmount; } void CSfxManager::PitchBend(const CSfxHandle& handle, float pitch) { if (!handle) return; if (!handle->IsPlaying()) CSfxManager::Update(0.f); // if (handle->IsPlaying()) { // m_doUpdate = true; // handle->GetVoice()->setPitchWheel(pitch); // } } void CSfxManager::SfxVolume(const CSfxHandle& handle, float vol) { if (!handle) return; if (handle->IsEmitter()) { CSfxWrapper& wrapper = static_cast(*handle); wrapper.SetVolume(vol); } // if (handle->IsPlaying()) // handle->GetVoice()->setVolume(vol); } void CSfxManager::SfxSpan(const CSfxHandle& handle, float span) { if (!handle) return; // if (handle->IsPlaying()) // handle->GetVoice()->setSurroundPan(span); } u16 CSfxManager::TranslateSFXID(u16 id) { if (mpSfxTranslationTable == nullptr) return 0; u16 index = id; if (index >= mpSfxTranslationTable->size()) return 0; u16 ret = (*mpSfxTranslationTable)[index]; if (ret == 0xffff) return 0; return ret; } bool CSfxManager::PlaySound(const CSfxManager::CSfxHandle& handle) { return false; } void CSfxManager::StopSound(const CSfxHandle& handle) { if (!handle) return; m_doUpdate = true; handle->Stop(); handle->Release(); CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)]; handle->Close(); chanObj.x48_handles.erase(handle); } void CSfxManager::SfxStop(const CSfxHandle& handle) { StopSound(handle); } CSfxHandle CSfxManager::SfxStart(u16 id, float vol, float pan, bool useAcoustics, s16 prio, bool looped, s32 areaId) { if (m_muted || id == 0xffff) return {}; m_doUpdate = true; CSfxHandle wrapper = std::make_shared(looped, prio, id, vol, pan, useAcoustics, areaId); CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)]; chanObj.x48_handles.insert(wrapper); return wrapper; } bool CSfxManager::IsPlaying(const CSfxHandle& handle) { if (!handle) return false; return handle->IsPlaying(); } void CSfxManager::RemoveEmitter(const CSfxHandle& handle) { StopSound(handle); } void CSfxManager::UpdateEmitter(const CSfxHandle& handle, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float maxVol) { if (!handle || !handle->IsEmitter() || !handle->IsPlaying()) return; // m_doUpdate = true; // CSfxEmitterWrapper& emitter = static_cast(*handle); // emitter.GetEmitterData().x0_pos = pos; // emitter.GetEmitterData().xc_dir = dir; // emitter.GetEmitterData().x26_maxVol = maxVol; // amuse::Emitter& h = *emitter.GetHandle(); // zeus::simd_floats p(pos.mSimd); // zeus::simd_floats d(dir.mSimd); // h.setVectors(p.data(), d.data()); // h.setMaxVol(maxVol); } CSfxHandle CSfxManager::AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, bool useAcoustics, bool looped, s16 prio, s32 areaId) { const CAudioSys::C3DEmitterParmData parmData{ .x0_pos = pos, .xc_dir = dir, .x18_maxDist = 150.f, .x1c_distComp = 0.1f, .x20_flags = 1, // Continuous parameter update .x24_sfxId = id, .x26_maxVol = 1.f, .x27_minVol = 0.165f, .x28_important = false, .x29_prio = 0x7f, }; return AddEmitter(parmData, useAcoustics, prio, looped, areaId); } CSfxHandle CSfxManager::AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float vol, bool useAcoustics, bool looped, s16 prio, s32 areaId) { const CAudioSys::C3DEmitterParmData parmData{ .x0_pos = pos, .xc_dir = dir, .x18_maxDist = 150.f, .x1c_distComp = 0.1f, .x20_flags = 1, // Continuous parameter update .x24_sfxId = id, .x26_maxVol = std::max(vol, 0.165f), .x27_minVol = 0.165f, .x28_important = false, .x29_prio = 0x7f, }; return AddEmitter(parmData, useAcoustics, prio, looped, areaId); } CSfxHandle CSfxManager::AddEmitter(const CAudioSys::C3DEmitterParmData& parmData, bool useAcoustics, s16 prio, bool looped, s32 areaId) { if (m_muted || parmData.x24_sfxId == 0xffff) return {}; CAudioSys::C3DEmitterParmData data = parmData; if (looped) data.x20_flags |= 0x6; // Pausable/restartable when inaudible m_doUpdate = true; CSfxHandle wrapper = std::make_shared(looped, prio, data, useAcoustics, areaId); CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)]; chanObj.x48_handles.insert(wrapper); return wrapper; } void CSfxManager::StopAndRemoveAllEmitters() { for (auto& chanObj : m_channels) { for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) { const CSfxHandle& handle = *it; handle->Stop(); handle->Release(); handle->Close(); it = chanObj.x48_handles.erase(it); } } } void CSfxManager::EnableAuxCallback() { m_reverbAmount = 0.f; ApplyReverb(); if (m_activeEffect != EAuxEffect::None) DisableAuxCallback(); // auto studio = CAudioSys::GetAmuseEngine().getDefaultStudio(); // amuse::Submix& smix = studio->getAuxA(); // // m_activeEffect = m_nextEffect; // switch (m_activeEffect) { // case EAuxEffect::ReverbHi: // s_ReverbHiState = &smix.makeReverbHi(s_ReverbHiQueued); // break; // case EAuxEffect::Chorus: // s_ChorusState = &smix.makeChorus(s_ChorusQueued); // break; // case EAuxEffect::ReverbStd: // s_ReverbStdState = &smix.makeReverbStd(s_ReverbStdQueued); // break; // case EAuxEffect::Delay: // s_DelayState = &smix.makeDelay(s_DelayQueued); // break; // default: // break; // } m_auxProcessingEnabled = true; } //void CSfxManager::PrepareDelayCallback(const amuse::EffectDelayInfo& info) { // DisableAuxProcessing(); // s_DelayQueued = info; // m_nextEffect = EAuxEffect::Delay; // if (m_reverbAmount == 0.f) // EnableAuxCallback(); //} // //void CSfxManager::PrepareReverbStdCallback(const amuse::EffectReverbStdInfo& info) { // DisableAuxProcessing(); // s_ReverbStdQueued = info; // m_nextEffect = EAuxEffect::ReverbStd; // if (m_reverbAmount == 0.f) // EnableAuxCallback(); //} // //void CSfxManager::PrepareChorusCallback(const amuse::EffectChorusInfo& info) { // DisableAuxProcessing(); // s_ChorusQueued = info; // m_nextEffect = EAuxEffect::Chorus; // if (m_reverbAmount == 0.f) // EnableAuxCallback(); //} // //void CSfxManager::PrepareReverbHiCallback(const amuse::EffectReverbHiInfo& info) { // DisableAuxProcessing(); // s_ReverbHiQueued = info; // m_nextEffect = EAuxEffect::ReverbHi; // if (m_reverbAmount == 0.f) // EnableAuxCallback(); //} void CSfxManager::DisableAuxCallback() { // auto studio = CAudioSys::GetAmuseEngine().getDefaultStudio(); // studio->getAuxA().clearEffects(); // // switch (m_activeEffect) { // case EAuxEffect::ReverbHi: // s_ReverbHiState = nullptr; // break; // case EAuxEffect::Chorus: // s_ChorusState = nullptr; // break; // case EAuxEffect::ReverbStd: // s_ReverbStdState = nullptr; // break; // case EAuxEffect::Delay: // s_DelayState = nullptr; // break; // default: // break; // } // // m_activeEffect = EAuxEffect::None; } void CSfxManager::DisableAuxProcessing() { m_nextEffect = EAuxEffect::None; m_auxProcessingEnabled = false; } void CSfxManager::SetActiveAreas(const rstl::reserved_vector& areas) { const CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)]; for (const CSfxHandle& hnd : chanObj.x48_handles) { const TAreaId sndArea = hnd->GetArea(); if (sndArea == kInvalidAreaId) { hnd->SetInArea(true); } else { bool inArea = false; for (const TAreaId id : areas) { if (sndArea == id) { inArea = true; break; } } m_doUpdate = true; hnd->SetInArea(inArea); } } } void CSfxManager::Update(float dt) { CSfxChannel& chanObj = m_channels[size_t(m_currentChannel)]; for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) { const CSfxHandle& handle = *it; if (!handle->IsLooped()) { float timeRem = handle->GetTimeRemaining(); handle->SetTimeRemaining(timeRem - dt); if (timeRem < 0.f) { handle->Stop(); m_doUpdate = true; handle->Close(); it = chanObj.x48_handles.erase(it); continue; } } ++it; } if (m_doUpdate) { std::vector rankedSfx; rankedSfx.reserve(chanObj.x48_handles.size()); for (const CSfxHandle& handle : chanObj.x48_handles) { rankedSfx.push_back(handle); handle->SetRank(GetRank(handle.get())); } std::sort(rankedSfx.begin(), rankedSfx.end(), [](const CSfxHandle& a, const CSfxHandle& b) -> bool { return a->GetRank() < b->GetRank(); }); for (size_t i = 48; i < rankedSfx.size(); ++i) { const CSfxHandle& handle = rankedSfx[i]; if (handle->IsPlaying()) { handle->Stop(); handle->Close(); chanObj.x48_handles.erase(handle); } } for (const CSfxHandle& handle : rankedSfx) { if (handle->IsPlaying() && !handle->IsInArea()) { handle->Stop(); handle->Close(); chanObj.x48_handles.erase(handle); } } #ifndef URDE_MSAN for (const CSfxHandle& handle : chanObj.x48_handles) { if (handle->IsPlaying()) continue; if (handle->Ready() && handle->IsInArea()) handle->Play(); } #endif m_doUpdate = false; } for (auto it = chanObj.x48_handles.begin(); it != chanObj.x48_handles.end();) { const CSfxHandle& handle = *it; if (!handle->IsPlaying() && !handle->IsLooped()) { handle->Stop(); handle->Release(); m_doUpdate = true; handle->Close(); it = chanObj.x48_handles.erase(it); continue; } ++it; } if (m_auxProcessingEnabled && m_reverbAmount < 1.f) { m_reverbAmount = std::min(1.f, dt / 0.1f + m_reverbAmount); ApplyReverb(); } else if (!m_auxProcessingEnabled && m_reverbAmount > 0.f) { m_reverbAmount = std::max(0.f, m_reverbAmount - dt / (2.f * 0.1f)); ApplyReverb(); if (m_reverbAmount == 0.f) { DisableAuxCallback(); EnableAuxCallback(); } } if (mpSfxTranslationTableTok.IsLoaded() && !mpSfxTranslationTable) mpSfxTranslationTable = mpSfxTranslationTableTok.GetObj(); } void CSfxManager::Shutdown() { mpSfxTranslationTable = nullptr; mpSfxTranslationTableTok = TLockedToken>{}; StopAndRemoveAllEmitters(); DisableAuxCallback(); } } // namespace metaforce ================================================ FILE: Runtime/Audio/CSfxManager.hpp ================================================ #pragma once #include #include #include #include #include "SFX/SFX.h" #include "Runtime/RetroTypes.hpp" #include "Runtime/Audio/CAudioSys.hpp" #include namespace metaforce { class CSfxManager { static std::vector* mpSfxTranslationTable; public: enum class ESfxChannels { Invalid = -1, Default = 0, Game, PauseScreen }; enum class ESfxAudibility { Aud0, Aud1, Aud2, Aud3 }; enum class EAuxEffect { None = -1, ReverbHi = 0, Chorus, ReverbStd, Delay }; class CBaseSfxWrapper; using CSfxHandle = std::shared_ptr; /* Original imp, kept for reference class CSfxHandle { static u32 mRefCount; u32 x0_idx; public: CSfxHandle(u32 id) : x0_idx(++mRefCount << 14 | (id & 0xFFFF)) {} }; */ class CSfxChannel { friend class CSfxManager; zeus::CVector3f x0_pos; zeus::CVector3f xc_; zeus::CVector3f x18_; zeus::CVector3f x24_; /* float x30_ = 0.f; float x34_ = 0.f; float x38_ = 0.f; u32 x3c_ = 0; bool x40_ = false; */ bool x44_listenerActive = false; std::unordered_set x48_handles; }; class CBaseSfxWrapper : public std::enable_shared_from_this { float x4_timeRemaining = 15.f; s16 x8_rank = 0; s16 xa_prio; // CSfxHandle xc_handle; TAreaId x10_area; bool x14_24_isActive : 1 = true; bool x14_25_isPlaying : 1 = false; bool x14_26_looped : 1; bool x14_27_inArea : 1 = true; bool x14_28_isReleased : 1 = false; bool x14_29_useAcoustics : 1; protected: bool m_isEmitter : 1 = false; bool m_isClosed : 1 = false; public: virtual ~CBaseSfxWrapper() = default; virtual void SetActive(bool v) { x14_24_isActive = v; } virtual void SetPlaying(bool v) { x14_25_isPlaying = v; } virtual void SetRank(short v) { x8_rank = v; } virtual void SetInArea(bool v) { x14_27_inArea = v; } virtual bool IsInArea() const { return x14_27_inArea; } virtual bool IsPlaying() const { return x14_25_isPlaying; } virtual bool UseAcoustics() const { return x14_29_useAcoustics; } virtual bool IsLooped() const { return x14_26_looped; } virtual bool IsActive() const { return x14_24_isActive; } virtual s16 GetRank() const { return x8_rank; } virtual s16 GetPriority() const { return xa_prio; } virtual TAreaId GetArea() const { return x10_area; } virtual CSfxHandle GetSfxHandle() { return shared_from_this(); } virtual void Play() = 0; virtual void Stop() = 0; virtual bool Ready() = 0; virtual ESfxAudibility GetAudible(const zeus::CVector3f&) = 0; //virtual amuse::ObjToken GetVoice() const = 0; virtual u16 GetSfxId() const = 0; virtual void UpdateEmitterSilent() = 0; virtual void UpdateEmitter() = 0; virtual void SetReverb(float rev) = 0; bool IsEmitter() const { return m_isEmitter; } void Release() { x14_28_isReleased = true; x4_timeRemaining = 15.f; } bool IsReleased() const { return x14_28_isReleased; } void Close() { m_isClosed = true; } bool IsClosed() const { return m_isClosed; } float GetTimeRemaining() const { return x4_timeRemaining; } void SetTimeRemaining(float t) { x4_timeRemaining = t; } CBaseSfxWrapper(bool looped, s16 prio, /*const CSfxHandle& handle,*/ bool useAcoustics, TAreaId area) : xa_prio(prio), /*xc_handle(handle),*/ x10_area(area), x14_26_looped(looped), x14_29_useAcoustics(useAcoustics) {} }; class CSfxEmitterWrapper : public CBaseSfxWrapper { float x1a_reverb = 0.0f; CAudioSys::C3DEmitterParmData x24_parmData; //amuse::ObjToken x50_emitterHandle; bool x54_ready = true; float x55_cachedMaxVol = 0.0f; public: bool IsPlaying() const override; void Play() override; void Stop() override; bool Ready() override; ESfxAudibility GetAudible(const zeus::CVector3f&) override; //amuse::ObjToken GetVoice() const override { return x50_emitterHandle->getVoice(); } u16 GetSfxId() const override; void UpdateEmitterSilent() override; void UpdateEmitter() override; void SetReverb(float rev) override; CAudioSys::C3DEmitterParmData& GetEmitterData() { return x24_parmData; } //amuse::ObjToken GetHandle() const { return x50_emitterHandle; } CSfxEmitterWrapper(bool looped, s16 prio, const CAudioSys::C3DEmitterParmData& data, /*const CSfxHandle& handle,*/ bool useAcoustics, TAreaId area) : CBaseSfxWrapper(looped, prio, /*handle,*/ useAcoustics, area), x24_parmData(data) { m_isEmitter = true; } }; class CSfxWrapper : public CBaseSfxWrapper { u16 x18_sfxId; //amuse::ObjToken x1c_voiceHandle; float x20_vol; float x22_pan; bool x24_ready = true; public: bool IsPlaying() const override; void Play() override; void Stop() override; bool Ready() override; ESfxAudibility GetAudible(const zeus::CVector3f&) override { return ESfxAudibility::Aud3; } // amuse::ObjToken GetVoice() const override { return x1c_voiceHandle; } u16 GetSfxId() const override; void UpdateEmitterSilent() override; void UpdateEmitter() override; void SetReverb(float rev) override; void SetVolume(float vol) { x20_vol = vol; } CSfxWrapper(bool looped, s16 prio, u16 sfxId, float vol, float pan, /*const CSfxHandle& handle,*/ bool useAcoustics, TAreaId area) : CBaseSfxWrapper(looped, prio, /*handle,*/ useAcoustics, area), x18_sfxId(sfxId), x20_vol(vol), x22_pan(pan) { m_isEmitter = false; } }; static std::array m_channels; static ESfxChannels m_currentChannel; static bool m_doUpdate; static void* m_usedSounds; static bool m_muted; static bool m_auxProcessingEnabled; static float m_reverbAmount; static EAuxEffect m_activeEffect; static EAuxEffect m_nextEffect; //static amuse::ObjToken m_listener; static u16 kMaxPriority; static u16 kMedPriority; static u16 kInternalInvalidSfxId; static u32 kAllAreas; static bool LoadTranslationTable(CSimplePool* pool, const SObjectTag* tag); static bool IsAuxProcessingEnabled() { return m_auxProcessingEnabled; } static void SetChannel(ESfxChannels); static void KillAll(ESfxChannels); static void TurnOnChannel(ESfxChannels); static void TurnOffChannel(ESfxChannels); static ESfxChannels GetCurrentChannel() { return m_currentChannel; } static void AddListener(ESfxChannels channel, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CVector3f& heading, const zeus::CVector3f& up, float frontRadius, float surroundRadius, float soundSpeed, u32 flags /* 0x1 for doppler */, float vol); static void UpdateListener(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CVector3f& heading, const zeus::CVector3f& up, float vol); static bool PlaySound(const CSfxHandle& handle); static void StopSound(const CSfxHandle& handle); static s16 GetRank(CBaseSfxWrapper* sfx); static void ApplyReverb(); static float GetReverbAmount(); static void PitchBend(const CSfxHandle& handle, float pitch); static void SfxVolume(const CSfxHandle& handle, float vol); static void SfxSpan(const CSfxHandle& handle, float span); static u16 TranslateSFXID(u16); static void SfxStop(const CSfxHandle& handle); static CSfxHandle SfxStart(u16 id, float vol, float pan, bool useAcoustics, s16 prio, bool looped, s32 areaId); static bool IsPlaying(const CSfxHandle& handle); static void RemoveEmitter(const CSfxHandle& handle); static void UpdateEmitter(const CSfxHandle& handle, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float maxVol); static CSfxHandle AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, bool useAcoustics, bool looped, s16 prio, s32 areaId); static CSfxHandle AddEmitter(u16 id, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float vol, bool useAcoustics, bool looped, s16 prio, s32 areaId); static CSfxHandle AddEmitter(const CAudioSys::C3DEmitterParmData& parmData, bool useAcoustics, s16 prio, bool looped, s32 areaId); static void StopAndRemoveAllEmitters(); static void DisableAuxCallback(); static void EnableAuxCallback(); // static void PrepareDelayCallback(const amuse::EffectDelayInfo& info); // static void PrepareReverbStdCallback(const amuse::EffectReverbStdInfo& info); // static void PrepareChorusCallback(const amuse::EffectChorusInfo& info); // static void PrepareReverbHiCallback(const amuse::EffectReverbHiInfo& info); static void DisableAuxProcessing(); static void SetActiveAreas(const rstl::reserved_vector& areas); static void Update(float dt); static void Shutdown(); }; using CSfxHandle = CSfxManager::CSfxHandle; } // namespace metaforce ================================================ FILE: Runtime/Audio/CStaticAudioPlayer.cpp ================================================ #include "Runtime/Audio/CStaticAudioPlayer.hpp" #include "Runtime/CDvdFile.hpp" #include "Runtime/CDvdRequest.hpp" namespace metaforce { #define RSF_BUFFER_SIZE 0x20000 //CStaticAudioPlayer::CStaticAudioPlayer(boo::IAudioVoiceEngine& engine, std::string_view path, int loopStart, // int loopEnd) //: x0_path(path) //, x1c_loopStartSamp(loopStart & 0xfffffffe) //, x20_loopEndSamp(loopEnd & 0xfffffffe) //, m_voiceCallback(*this) //, m_voice(engine.allocateNewStereoVoice(32000, &m_voiceCallback)) { // // These are mixed directly into boo voice engine instead // // x28_dmaLeft.reset(new u8[640]); // // x30_dmaRight.reset(new u8[640]); // // CDvdFile file(path); // x10_rsfRem = file.Length(); // x14_rsfLength = x10_rsfRem; // // u32 bufCount = (x10_rsfRem + RSF_BUFFER_SIZE - 1) / RSF_BUFFER_SIZE; // x48_buffers.reserve(bufCount); // x38_dvdRequests.reserve(bufCount); // // for (int remBytes = x10_rsfRem; remBytes > 0; remBytes -= RSF_BUFFER_SIZE) { // u32 thisSz = RSF_BUFFER_SIZE; // if (remBytes < RSF_BUFFER_SIZE) // thisSz = ROUND_UP_32(remBytes); // // x48_buffers.emplace_back(new u8[thisSz]); // x38_dvdRequests.push_back(file.AsyncRead(x48_buffers.back().get(), thisSz)); // } // // g72x_init_state(&x58_leftState); // g72x_init_state(&x8c_rightState); //} bool CStaticAudioPlayer::IsReady() { if (x38_dvdRequests.size()) return x38_dvdRequests.back()->IsComplete(); return true; } void CStaticAudioPlayer::DecodeMonoAndMix(s16* bufOut, u32 numSamples, u32 cur, u32 loopEndCur, u32 loopStartCur, int vol, g72x_state& state, std::optional& loopState) const { for (u32 remBytes = numSamples / 2; remBytes;) { u32 curBuf = cur / RSF_BUFFER_SIZE; u32 thisBytes = (curBuf + 1) * RSF_BUFFER_SIZE - cur; thisBytes = std::min(thisBytes, remBytes); u32 remTillLoop = loopEndCur - cur; remTillLoop = std::min(remTillLoop, thisBytes); const std::unique_ptr& buf = x48_buffers[curBuf]; const u8* byte = &buf[cur - curBuf * RSF_BUFFER_SIZE]; for (u32 i = 0; i < remTillLoop; ++i, ++byte) { if (!loopState && cur + i == loopStartCur) loopState.emplace(state); *bufOut = SampClamp(((g721_decoder(*byte & 0xf, &state) * vol) >> 15)); bufOut += 2; *bufOut = SampClamp(((g721_decoder(*byte >> 4 & 0xf, &state) * vol) >> 15)); bufOut += 2; } cur += remTillLoop; remBytes -= remTillLoop; if (cur == loopEndCur) { cur = loopStartCur; if (loopState) state = *loopState; } } } void CStaticAudioPlayer::Decode(s16* bufOut, u32 numSamples) { DecodeMonoAndMix(bufOut, numSamples, x18_curSamp / 2, x20_loopEndSamp / 2, x1c_loopStartSamp / 2, xc0_volume, x58_leftState, m_leftStateLoop); u32 halfway = x14_rsfLength / 2; DecodeMonoAndMix(bufOut + 1, numSamples, x18_curSamp / 2 + halfway, x20_loopEndSamp / 2 + halfway, x1c_loopStartSamp / 2 + halfway, xc0_volume, x8c_rightState, m_rightStateLoop); for (u32 remSamples = numSamples; remSamples;) { u32 remTillLoop = x20_loopEndSamp - x18_curSamp; remTillLoop = std::min(remTillLoop, remSamples); x18_curSamp += remTillLoop; remSamples -= remTillLoop; if (x18_curSamp == x20_loopEndSamp) x18_curSamp = x1c_loopStartSamp; } } } // namespace metaforce ================================================ FILE: Runtime/Audio/CStaticAudioPlayer.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Audio/CAudioSys.hpp" #include "g721.h" //#include //#include namespace metaforce { class IDvdRequest; class CStaticAudioPlayer { std::string x0_path; u32 x10_rsfRem = -1; u32 x14_rsfLength; u32 x18_curSamp = 0; u32 x1c_loopStartSamp; u32 x20_loopEndSamp; // u32 x24_ = 0; // std::unique_ptr x28_dmaLeft; // std::unique_ptr x30_dmaRight; std::vector> x38_dvdRequests; std::vector> x48_buffers; g72x_state x58_leftState; g72x_state x8c_rightState; std::optional m_leftStateLoop; std::optional m_rightStateLoop; u32 xc0_volume = 32768; // Out of 32768 static int16_t SampClamp(int32_t val) { if (val < -32768) val = -32768; else if (val > 32767) val = 32767; return val; } /* struct AudioVoiceCallback { CStaticAudioPlayer& m_parent; void preSupplyAudio(boo::IAudioVoice&, double) override {} size_t supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data) override { if (m_parent.IsReady()) { m_parent.x38_dvdRequests.clear(); m_parent.Decode(data, frames); } else memset(data, 0, 4 * frames); return frames; } explicit AudioVoiceCallback(CStaticAudioPlayer& p) : m_parent(p) {} } m_voiceCallback; boo::ObjToken m_voice; */ public: // CStaticAudioPlayer(boo::IAudioVoiceEngine& engine, std::string_view path, int loopStart, int loopEnd); // CStaticAudioPlayer(std::string_view path, int loopStart, int loopEnd) // : CStaticAudioPlayer(*CAudioSys::GetVoiceEngine(), path, loopStart, loopEnd) {} bool IsReady(); void DecodeMonoAndMix(s16* bufOut, u32 numSamples, u32 cur, u32 loopEndCur, u32 loopStartCur, int vol, g72x_state& state, std::optional& loopState) const; void Decode(s16* bufOut, u32 numSamples); void SetVolume(float vol) { xc0_volume = zeus::clamp(0.f, vol, 1.f) * 32768.f; } // // void StartMixing() { m_voice->start(); } // void StopMixing() { m_voice->stop(); } }; } // namespace metaforce ================================================ FILE: Runtime/Audio/CStreamAudioManager.cpp ================================================ #include "Runtime/Audio/CStreamAudioManager.hpp" #include "Runtime/Audio/CAudioSys.hpp" #include "Runtime/CDvdFile.hpp" #include "Runtime/CDvdRequest.hpp" #include "Runtime/CStringExtras.hpp" #include #include #include #include #include //#include namespace metaforce { class CDSPStreamManager; static u32 s_HandleCounter = 0; static u32 s_HandleCounter2 = 0; /* Standard DSPADPCM header */ struct dspadpcm_header { u32 x0_num_samples; u32 x4_num_nibbles; u32 x8_sample_rate; u16 xc_loop_flag; u16 xe_format; /* 0 for ADPCM */ u32 x10_loop_start_nibble; u32 x14_loop_end_nibble; u32 x18_ca; s16 x1c_coef[8][2]; s16 x3c_gain; s16 x3e_ps; s16 x40_hist1; s16 x42_hist2; s16 x44_loop_ps; s16 x46_loop_hist1; s16 x48_loop_hist2; std::array x4a_pad; }; struct SDSPStreamInfo { const char* x0_fileName; u32 x4_sampleRate; u32 x8_headerSize = sizeof(dspadpcm_header); u32 xc_adpcmBytes; bool x10_loopFlag; u32 x14_loopStartByte; u32 x18_loopEndByte; s16 x1c_coef[8][2]; SDSPStreamInfo() = default; explicit SDSPStreamInfo(const CDSPStreamManager& stream); }; struct SDSPStream { bool x0_active; bool x1_oneshot; s32 x4_ownerId; SDSPStream* x8_stereoLeft; SDSPStream* xc_companionRight; SDSPStreamInfo x10_info; float x4c_vol; float m_leftgain, m_rightgain; // DVDFileInfo x50_dvdHandle1; // DVDFileInfo x8c_dvdHandle2; // u32 xc8_streamId = -1; // MusyX stream handle u32 xcc_fileCur = 0; std::unique_ptr xd4_ringBuffer; u32 xd8_ringBytes = 0x11DC0; // 73152 4sec in ADPCM bytes u32 xdc_ringSamples = 0x1f410; // 128016 4sec in samples s8 xe0_curBuffer = -1; bool xe8_silent = true; u8 xec_readState = 0; // 0: NoRead 1: Read 2: ReadWrap std::optional m_file; std::array, 2> m_readReqs; void ReadBuffer(int buf) { u32 halfSize = xd8_ringBytes / 2; u8* data = xd4_ringBuffer.get() + (buf ? halfSize : 0); if (x10_info.x10_loopFlag) { u32 remFileBytes = x10_info.x18_loopEndByte - xcc_fileCur; if (remFileBytes < halfSize) { // printf("Buffering %d from %d into %d\n", remFileBytes, xcc_fileCur + x10_info.x8_headerSize, buf); m_file->AsyncSeekRead(data, remFileBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur = x10_info.x14_loopStartByte; u32 remBytes = halfSize - remFileBytes; // printf("Loop Buffering %d from %d into %d\n", remBytes, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data + remFileBytes, remBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur += remBytes; } else { // printf("Buffering %d from %d into %d\n", halfSize, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data, halfSize, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur += halfSize; } } else { if (xcc_fileCur == x10_info.xc_adpcmBytes) { memset(data, 0, halfSize); return; } u32 remFileBytes = x10_info.xc_adpcmBytes - xcc_fileCur; if (remFileBytes < halfSize) { // printf("Buffering %d from %d into %d\n", remFileBytes, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data, remFileBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); memset(data + remFileBytes, 0, halfSize - remFileBytes); xcc_fileCur = x10_info.xc_adpcmBytes; } else { // printf("Buffering %d from %d into %d\n", halfSize, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data, halfSize, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur += halfSize; } } } bool BufferStream() { if (xec_readState == 0) { ReadBuffer(0); ReadBuffer(1); xec_readState = 1; return false; } else if (xec_readState == 1) { if (m_readReqs[0]->IsComplete()) { xe0_curBuffer = 0; xec_readState = 2; return true; } else { return false; } } else if (xec_readState == 2) { if (xe0_curBuffer == 1 && m_readReqs[0]->IsComplete()) { xe0_curBuffer = 0; ReadBuffer(1); return true; } else if (xe0_curBuffer == 0 && m_readReqs[1]->IsComplete()) { xe0_curBuffer = 1; ReadBuffer(0); return true; } } return false; } unsigned m_curSample = 0; unsigned m_totalSamples = 0; s16 m_prev1 = 0; s16 m_prev2 = 0; // void preSupplyAudio(boo::IAudioVoice&, double) override {} // unsigned decompressChunk(unsigned readToSample, int16_t*& data) { // unsigned startSamp = m_curSample; // // auto sampDiv = std::div(int(m_curSample), int(14)); // if (sampDiv.rem) { // unsigned samps = DSPDecompressFrameRanged(data, xd4_ringBuffer.get() + sampDiv.quot * 8, x10_info.x1c_coef, // &m_prev1, &m_prev2, unsigned(sampDiv.rem), readToSample - m_curSample); // m_curSample += samps; // data += samps; // ++sampDiv.quot; // } // // while (m_curSample < readToSample) { // unsigned samps = DSPDecompressFrame(data, xd4_ringBuffer.get() + sampDiv.quot * 8, x10_info.x1c_coef, &m_prev1, // &m_prev2, readToSample - m_curSample); // m_curSample += samps; // data += samps; // ++sampDiv.quot; // } // // return m_curSample - startSamp; // } // size_t supplyAudio(boo::IAudioVoice&, size_t frames, int16_t* data) override { // if (!x0_active) { // memset(data, 0, frames * 2); // return frames; // } // // if (xe8_silent) { // StopStream(); // memset(data, 0, frames * 2); // return frames; // } // // unsigned halfRingSamples = xdc_ringSamples / 2; // // size_t remFrames = frames; // while (remFrames) { // if (xec_readState != 2 || (xe0_curBuffer == 0 && m_curSample >= halfRingSamples)) { // if (!BufferStream()) { // memset(data, 0, remFrames * 2); // return frames; // } // } // // unsigned readToSample = // std::min(m_curSample + unsigned(remFrames), (m_curSample / halfRingSamples + 1) * halfRingSamples); // // if (!x10_info.x10_loopFlag) { // m_totalSamples += remFrames; // size_t fileSamples = x10_info.xc_adpcmBytes * 14 / 8; // if (m_totalSamples >= fileSamples) { // size_t leftover = m_totalSamples - fileSamples; // readToSample -= leftover; // remFrames -= leftover; // memset(data + remFrames, 0, leftover * 2); // StopStream(); // } // } // // unsigned leftoverSamples = 0; // if (readToSample > xdc_ringSamples) { // leftoverSamples = readToSample - xdc_ringSamples; // readToSample = xdc_ringSamples; // } // // remFrames -= decompressChunk(readToSample, data); // // if (leftoverSamples) { // BufferStream(); // m_curSample = 0; // remFrames -= decompressChunk(leftoverSamples, data); // } // } // // return frames; // } // boo::ObjToken m_booVoice; void DoAllocateStream() { xd4_ringBuffer.reset(new u8[0x11DC0]); //m_booVoice = CAudioSys::GetVoiceEngine()->allocateNewMonoVoice(32000.0, this); } static void Initialize() { for (size_t i = 0; i < g_Streams.size(); ++i) { SDSPStream& stream = g_Streams[i]; stream.x0_active = false; stream.xd4_ringBuffer.reset(); stream.xd8_ringBytes = 0x11DC0; stream.xdc_ringSamples = 0x1f410; if (i < 2) { stream.x1_oneshot = false; stream.DoAllocateStream(); } else { stream.x1_oneshot = true; } } } static void FreeAllStreams() { // for (auto& stream : g_Streams) { // stream.m_booVoice.reset(); // stream.x0_active = false; // for (auto& request : stream.m_readReqs) { // if (request) { // request->PostCancelRequest(); // request.reset(); // } // } // stream.xd4_ringBuffer.reset(); // stream.m_file = std::nullopt; // } } static s32 PickFreeStream(SDSPStream*& streamOut, bool oneshot) { for (auto& stream : g_Streams) { if (stream.x0_active || stream.x1_oneshot != oneshot) { continue; } stream.x0_active = true; stream.x4_ownerId = ++s_HandleCounter2; if (stream.x4_ownerId == -1) { stream.x4_ownerId = ++s_HandleCounter2; } stream.x8_stereoLeft = nullptr; stream.xc_companionRight = nullptr; streamOut = &stream; return stream.x4_ownerId; } return -1; } static s32 FindStreamIdx(s32 id) { for (size_t i = 0; i < g_Streams.size(); ++i) { const SDSPStream& stream = g_Streams[i]; if (stream.x4_ownerId == id) { return s32(i); } } return -1; } void UpdateStreamVolume(float vol) { // x4c_vol = vol; // if (!x0_active || xe8_silent) { // return; // } // std::array coefs{}; // coefs[size_t(boo::AudioChannel::FrontLeft)] = m_leftgain * vol; // coefs[size_t(boo::AudioChannel::FrontRight)] = m_rightgain * vol; // m_booVoice->setMonoChannelLevels(nullptr, coefs.data(), true); } static void UpdateVolume(s32 id, float vol) { // s32 idx = FindStreamIdx(id); // if (idx == -1) // return; // // SDSPStream& stream = g_Streams[idx]; // stream.UpdateStreamVolume(vol); // if (SDSPStream* left = stream.x8_stereoLeft) // left->UpdateStreamVolume(vol); // if (SDSPStream* right = stream.xc_companionRight) // right->UpdateStreamVolume(vol); } void SilenceStream() { if (!x0_active || xe8_silent) { return; } constexpr std::array coefs{}; //m_booVoice->setMonoChannelLevels(nullptr, coefs.data(), true); xe8_silent = true; x0_active = false; } static void Silence(s32 id) { s32 idx = FindStreamIdx(id); if (idx == -1) return; SDSPStream& stream = g_Streams[idx]; stream.SilenceStream(); if (SDSPStream* left = stream.x8_stereoLeft) left->SilenceStream(); if (SDSPStream* right = stream.xc_companionRight) right->SilenceStream(); } void StopStream() { x0_active = false; //m_booVoice->stop(); m_file = std::nullopt; } static bool IsStreamActive(s32 id) { s32 idx = FindStreamIdx(id); if (idx == -1) return false; SDSPStream& stream = g_Streams[idx]; return stream.x0_active; } static bool IsStreamAvailable(s32 id) { s32 idx = FindStreamIdx(id); if (idx == -1) return false; SDSPStream& stream = g_Streams[idx]; return !stream.x0_active; } static s32 AllocateMono(const SDSPStreamInfo& info, float vol, bool oneshot) { SDSPStream* stream; s32 id = PickFreeStream(stream, oneshot); if (id == -1) return -1; /* -3dB pan law for mono */ stream->AllocateStream(info, vol, 0.707f, 0.707f); return id; } static s32 AllocateStereo(const SDSPStreamInfo& linfo, const SDSPStreamInfo& rinfo, float vol, bool oneshot) { SDSPStream* lstream; s32 lid = PickFreeStream(lstream, oneshot); if (lid == -1) return -1; SDSPStream* rstream; if (PickFreeStream(rstream, oneshot) == -1) return -1; rstream->x8_stereoLeft = lstream; lstream->xc_companionRight = rstream; lstream->AllocateStream(linfo, vol, 1.f, 0.f); rstream->AllocateStream(rinfo, vol, 0.f, 1.f); return lid; } void AllocateStream(const SDSPStreamInfo& info, float vol, float left, float right) { x10_info = info; m_file.emplace(x10_info.x0_fileName); if (!xd4_ringBuffer) { DoAllocateStream(); } for (auto& request : m_readReqs) { if (request) { request->PostCancelRequest(); request.reset(); } } x4c_vol = vol; m_leftgain = left; m_rightgain = right; xe8_silent = false; xec_readState = 0; xe0_curBuffer = -1; xd8_ringBytes = 0x11DC0; xdc_ringSamples = 0x1f410; xcc_fileCur = 0; m_curSample = 0; m_totalSamples = 0; m_prev1 = 0; m_prev2 = 0; memset(xd4_ringBuffer.get(), 0, 0x11DC0); //m_booVoice->resetSampleRate(info.x4_sampleRate); //m_booVoice->start(); UpdateStreamVolume(vol); } static std::array g_Streams; }; std::array SDSPStream::g_Streams{}; class CDSPStreamManager { friend struct SDSPStreamInfo; public: enum class EState { Looping, Oneshot, Preparing }; private: dspadpcm_header x0_header; std::string x60_fileName; // arg1 bool x70_24_unclaimed : 1 = true; bool x70_25_headerReadCancelled : 1 = false; u8 x70_26_headerReadState : 2 = 0; // 0: not read 1: reading 2: read s8 x71_companionRight = -1; s8 x72_companionLeft = -1; float x73_volume = 0.f; bool x74_oneshot = false; s32 x78_handleId = -1; // arg2 s32 x7c_streamId = -1; std::shared_ptr m_dvdReq; // DVDFileInfo x80_dvdHandle; static std::array g_Streams; public: CDSPStreamManager() = default; CDSPStreamManager(std::string_view fileName, s32 handle, float volume, bool oneshot) : x60_fileName(fileName) , x70_24_unclaimed(!CDvdFile::FileExists(fileName)) , x73_volume(volume) , x74_oneshot(oneshot) , x78_handleId(handle) {} static s32 FindUnclaimedStreamIdx() { for (size_t i = 0; i < g_Streams.size(); ++i) { const CDSPStreamManager& stream = g_Streams[i]; if (stream.x70_24_unclaimed) { return s32(i); } } return -1; } static bool FindUnclaimedStereoPair(s32& left, s32& right) { const s32 idx = FindUnclaimedStreamIdx(); for (size_t i = 0; i < g_Streams.size(); ++i) { CDSPStreamManager& stream = g_Streams[i]; if (stream.x70_24_unclaimed && idx != s32(i)) { left = idx; right = s32(i); return true; } } return false; } static s32 FindClaimedStreamIdx(s32 handle) { for (size_t i = 0; i < g_Streams.size(); ++i) { const CDSPStreamManager& stream = g_Streams[i]; if (!stream.x70_24_unclaimed && stream.x78_handleId == handle) { return i; } } return -1; } static s32 GetFreeHandleId() { s32 handle; bool good; do { good = true; handle = ++s_HandleCounter; if (handle == -1) { good = false; continue; } for (auto& stream : g_Streams) { if (!stream.x70_24_unclaimed && stream.x78_handleId == handle) { good = false; break; } } } while (!good); return handle; } static EState GetStreamState(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return EState::Oneshot; CDSPStreamManager& stream = g_Streams[idx]; switch (stream.x70_26_headerReadState) { case 0: return EState::Oneshot; case 2: return EState(!stream.x0_header.xc_loop_flag); default: return EState::Preparing; } } static bool CanStop(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return true; CDSPStreamManager& stream = g_Streams[idx]; if (stream.x70_26_headerReadState == 1) return false; if (stream.x7c_streamId == -1) return true; return !SDSPStream::IsStreamActive(stream.x7c_streamId); } static bool IsStreamAvailable(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return false; CDSPStreamManager& stream = g_Streams[idx]; if (stream.x70_26_headerReadState == 1) return false; if (stream.x7c_streamId == -1) return false; return SDSPStream::IsStreamAvailable(stream.x7c_streamId); } static void AllocateStream(s32 idx) { CDSPStreamManager& stream = g_Streams[idx]; SDSPStreamInfo info(stream); if (stream.x71_companionRight == -1) { /* Mono */ if (!stream.x70_25_headerReadCancelled) stream.x7c_streamId = SDSPStream::AllocateMono(info, stream.x73_volume, stream.x74_oneshot); if (stream.x7c_streamId == -1) stream = CDSPStreamManager(); } else { /* Stereo */ CDSPStreamManager& rstream = g_Streams[stream.x71_companionRight]; SDSPStreamInfo rinfo(rstream); if (!stream.x70_25_headerReadCancelled) stream.x7c_streamId = SDSPStream::AllocateStereo(info, rinfo, stream.x73_volume, stream.x74_oneshot); if (stream.x7c_streamId == -1) { stream = CDSPStreamManager(); rstream = CDSPStreamManager(); } } } void HeaderReadComplete() { s32 selfIdx = -1; for (size_t i = 0; i < g_Streams.size(); ++i) { if (this == &g_Streams[i]) { selfIdx = s32(i); break; } } if (x70_24_unclaimed || selfIdx == -1) { *this = CDSPStreamManager(); return; } x70_26_headerReadState = 2; s32 companion = -1; if (x72_companionLeft != -1) companion = x72_companionLeft; else if (x71_companionRight != -1) companion = x71_companionRight; if (companion != -1) { /* Stereo */ CDSPStreamManager& companionStream = g_Streams[companion]; if (companionStream.x70_24_unclaimed || companionStream.x70_26_headerReadState == 0 || (companionStream.x71_companionRight != selfIdx && companionStream.x72_companionLeft != selfIdx)) { /* No consistent companion available */ *this = CDSPStreamManager(); return; } /* Companion is pending; its completion will continue */ if (companionStream.x70_26_headerReadState == 1) return; /* Use whichever stream is the left channel */ if (companionStream.x71_companionRight != -1) AllocateStream(companion); else AllocateStream(selfIdx); } else { /* Mono */ AllocateStream(selfIdx); } } static void PollHeaderReadCompletions() { for (auto& stream : g_Streams) { if (stream.m_dvdReq && stream.m_dvdReq->IsComplete()) { stream.m_dvdReq.reset(); stream.HeaderReadComplete(); } } } static bool StartMonoHeaderRead(CDSPStreamManager& stream) { if (stream.x70_26_headerReadState != 0 || stream.x70_24_unclaimed) return false; CDvdFile file(stream.x60_fileName); if (!file) return false; stream.x70_26_headerReadState = 1; stream.m_dvdReq = file.AsyncRead(&stream.x0_header, sizeof(dspadpcm_header)); return true; } static bool StartStereoHeaderRead(CDSPStreamManager& lstream, CDSPStreamManager& rstream) { if (lstream.x70_26_headerReadState != 0 || lstream.x70_24_unclaimed || rstream.x70_26_headerReadState != 0 || rstream.x70_24_unclaimed) return false; CDvdFile lfile(lstream.x60_fileName); if (!lfile) return false; CDvdFile rfile(rstream.x60_fileName); if (!rfile) return false; lstream.x70_26_headerReadState = 1; rstream.x70_26_headerReadState = 1; lstream.m_dvdReq = lfile.AsyncRead(&lstream.x0_header, sizeof(dspadpcm_header)); rstream.m_dvdReq = rfile.AsyncRead(&rstream.x0_header, sizeof(dspadpcm_header)); return true; } void WaitForReadCompletion() { if (std::shared_ptr req = m_dvdReq) req->WaitUntilComplete(); m_dvdReq.reset(); } static s32 StartStreaming(std::string_view fileName, float volume, bool oneshot) { auto pipePos = fileName.find('|'); if (pipePos == std::string::npos) { /* Mono stream */ s32 idx = FindUnclaimedStreamIdx(); if (idx == -1) return -1; s32 handle = GetFreeHandleId(); CDSPStreamManager tmpStream(fileName, handle, volume, oneshot); if (tmpStream.x70_24_unclaimed) return -1; CDSPStreamManager& stream = g_Streams[idx]; stream = tmpStream; if (!StartMonoHeaderRead(stream)) { stream.x70_25_headerReadCancelled = true; stream.WaitForReadCompletion(); stream = CDSPStreamManager(); return -1; } return handle; } else { /* Stereo stream */ s32 leftIdx = 0; s32 rightIdx = 0; if (!FindUnclaimedStereoPair(leftIdx, rightIdx)) return -1; std::string leftFile(fileName.begin(), fileName.begin() + pipePos); std::string rightFile(fileName.begin() + pipePos + 1, fileName.end()); s32 leftHandle = GetFreeHandleId(); s32 rightHandle = GetFreeHandleId(); CDSPStreamManager tmpLeftStream(leftFile, leftHandle, volume, oneshot); CDSPStreamManager tmpRightStream(rightFile, rightHandle, volume, oneshot); if (tmpLeftStream.x70_24_unclaimed || tmpRightStream.x70_24_unclaimed) return -1; tmpLeftStream.x71_companionRight = s8(rightIdx); tmpRightStream.x72_companionLeft = s8(leftIdx); CDSPStreamManager& leftStream = g_Streams[leftIdx]; CDSPStreamManager& rightStream = g_Streams[rightIdx]; leftStream = tmpLeftStream; rightStream = tmpRightStream; if (!StartStereoHeaderRead(leftStream, rightStream)) { leftStream.x70_25_headerReadCancelled = true; rightStream.x70_25_headerReadCancelled = true; leftStream.WaitForReadCompletion(); leftStream.WaitForReadCompletion(); leftStream = CDSPStreamManager(); rightStream = CDSPStreamManager(); return -1; } return leftHandle; } } static void StopStreaming(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return; CDSPStreamManager& stream = g_Streams[idx]; if (stream.x70_24_unclaimed) return; if (stream.x70_26_headerReadState == 1) { stream.x70_25_headerReadCancelled = true; return; } if (stream.x71_companionRight != -1) g_Streams[stream.x71_companionRight] = CDSPStreamManager(); SDSPStream::Silence(stream.x7c_streamId); stream = CDSPStreamManager(); } static void UpdateVolume(s32 handle, float volume) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return; CDSPStreamManager& stream = g_Streams[idx]; stream.x73_volume = volume; if (stream.x7c_streamId == -1) return; SDSPStream::UpdateVolume(stream.x7c_streamId, volume); } static void Initialize() { SDSPStream::Initialize(); for (auto& stream : g_Streams) { stream = CDSPStreamManager(); } } static void Shutdown() { SDSPStream::FreeAllStreams(); for (auto& stream : g_Streams) { stream = CDSPStreamManager(); } } }; std::array CDSPStreamManager::g_Streams{}; SDSPStreamInfo::SDSPStreamInfo(const CDSPStreamManager& stream) { x0_fileName = stream.x60_fileName.c_str(); x4_sampleRate = SBig(stream.x0_header.x8_sample_rate); xc_adpcmBytes = (SBig(stream.x0_header.x4_num_nibbles) / 2) & 0x7FFFFFE0; if (stream.x0_header.xc_loop_flag) { u32 loopStartNibble = SBig(stream.x0_header.x10_loop_start_nibble); u32 loopEndNibble = SBig(stream.x0_header.x14_loop_end_nibble); x10_loopFlag = true; x14_loopStartByte = (loopStartNibble / 2) & 0x7FFFFFE0; x18_loopEndByte = std::min((loopEndNibble / 2) & 0x7FFFFFE0, xc_adpcmBytes); } else { x10_loopFlag = false; x14_loopStartByte = 0; x18_loopEndByte = 0; } for (int i = 0; i < 8; ++i) { x1c_coef[i][0] = SBig(stream.x0_header.x1c_coef[i][0]); x1c_coef[i][1] = SBig(stream.x0_header.x1c_coef[i][1]); } } enum class EPlayerState { Stopped, FadeIn, Playing, FadeOut, FadeOutNoStop }; struct SDSPPlayer { std::string x0_fileName; EPlayerState x10_playState = EPlayerState::Stopped; float x14_volume = 0.f; float x18_fadeIn = 0.f; float x1c_fadeOut = 0.f; s32 x20_internalHandle = -1; float x24_fadeFactor = 0.f; bool x28_music = true; SDSPPlayer() = default; SDSPPlayer(EPlayerState playing, std::string_view fileName, float volume, float fadeIn, float fadeOut, s32 handle, bool music) : x0_fileName(fileName) , x10_playState(playing) , x14_volume(volume) , x18_fadeIn(fadeIn) , x1c_fadeOut(fadeOut) , x20_internalHandle(handle) , x28_music(music) {} }; using PlayerArray = std::array; static PlayerArray s_Players; // looping, oneshot static PlayerArray s_QueuedPlayers; // looping, oneshot float CStreamAudioManager::GetTargetDSPVolume(float fileVol, bool music) { if (music) return g_MusicUnmute ? (g_MusicVolume * fileVol / 127.f) : 0.f; else return g_SfxUnmute ? (g_SfxVolume * fileVol / 127.f) : 0.f; } void CStreamAudioManager::Start(bool oneshot, std::string_view fileName, float volume, bool music, float fadeIn, float fadeOut) { SDSPPlayer& p = s_Players[oneshot]; SDSPPlayer& qp = s_QueuedPlayers[oneshot]; if (p.x10_playState != EPlayerState::Stopped && !CStringExtras::CompareCaseInsensitive(fileName, p.x0_fileName)) { /* Enque new stream */ qp = SDSPPlayer(EPlayerState::FadeIn, fileName, volume, fadeIn, fadeOut, -1, music); Stop(oneshot, p.x0_fileName); } else if (p.x10_playState != EPlayerState::Stopped) { /* Fade existing stream back in */ p.x18_fadeIn = fadeIn; p.x1c_fadeOut = fadeOut; p.x14_volume = volume; if (p.x18_fadeIn <= FLT_EPSILON) { CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume, p.x28_music)); p.x24_fadeFactor = 1.f; p.x10_playState = EPlayerState::Playing; } else { p.x10_playState = EPlayerState::FadeIn; } } else { /* Start new stream */ EPlayerState state; float vol; if (fadeIn > 0.f) { state = EPlayerState::FadeIn; vol = 0.f; } else { state = EPlayerState::Playing; vol = volume; } s32 handle = CDSPStreamManager::StartStreaming(fileName, GetTargetDSPVolume(vol, music), oneshot); if (handle != -1) p = SDSPPlayer(state, fileName, volume, fadeIn, fadeOut, handle, music); } } void CStreamAudioManager::Stop(bool oneshot, std::string_view fileName) { SDSPPlayer& p = s_Players[oneshot]; SDSPPlayer& qp = s_QueuedPlayers[oneshot]; if (CStringExtras::CompareCaseInsensitive(fileName, qp.x0_fileName)) { /* Cancel enqueued file */ qp = SDSPPlayer(); } else if (CStringExtras::CompareCaseInsensitive(fileName, p.x0_fileName) && p.x20_internalHandle != -1 && p.x10_playState != EPlayerState::Stopped) { /* Fade out or stop */ if (p.x1c_fadeOut <= FLT_EPSILON) StopStreaming(oneshot); else p.x10_playState = EPlayerState::FadeOut; } } void CStreamAudioManager::FadeBackIn(bool oneshot, float fadeTime) { SDSPPlayer& p = s_Players[oneshot]; if (p.x10_playState == EPlayerState::Stopped || p.x10_playState == EPlayerState::Playing) return; p.x18_fadeIn = fadeTime; p.x10_playState = EPlayerState::FadeIn; } void CStreamAudioManager::TemporaryFadeOut(bool oneshot, float fadeTime) { SDSPPlayer& p = s_Players[oneshot]; if (p.x10_playState == EPlayerState::FadeOut || p.x10_playState == EPlayerState::Stopped) return; p.x1c_fadeOut = fadeTime; p.x10_playState = EPlayerState::FadeOutNoStop; } void CStreamAudioManager::StopStreaming(bool oneshot) { SDSPPlayer& p = s_Players[oneshot]; p.x10_playState = EPlayerState::Stopped; CDSPStreamManager::StopStreaming(p.x20_internalHandle); p.x24_fadeFactor = 0.f; p.x20_internalHandle = -1; } void CStreamAudioManager::UpdateDSP(bool oneshot, float dt) { SDSPPlayer& p = s_Players[oneshot]; if (p.x10_playState == EPlayerState::Stopped) { SDSPPlayer& qp = s_QueuedPlayers[oneshot]; if (qp.x10_playState != EPlayerState::Stopped) { Start(oneshot, qp.x0_fileName, qp.x14_volume, qp.x28_music, qp.x18_fadeIn, qp.x1c_fadeOut); qp = SDSPPlayer(); } } else { if (p.x10_playState != EPlayerState::Stopped && CDSPStreamManager::GetStreamState(p.x20_internalHandle) == CDSPStreamManager::EState::Oneshot && CDSPStreamManager::CanStop(p.x20_internalHandle)) { StopStreaming(oneshot); return; } if ((p.x10_playState != EPlayerState::FadeIn && p.x10_playState != EPlayerState::FadeOut && p.x10_playState != EPlayerState::FadeOutNoStop)) { if (p.x10_playState == EPlayerState::Playing) CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume, p.x28_music)); return; } if (p.x10_playState == EPlayerState::FadeIn) { float newFadeFactor = p.x24_fadeFactor + dt / p.x18_fadeIn; if (newFadeFactor >= 1.f) { p.x24_fadeFactor = 1.f; p.x10_playState = EPlayerState::Playing; } else { p.x24_fadeFactor = newFadeFactor; } } else if (p.x10_playState == EPlayerState::FadeOut || p.x10_playState == EPlayerState::FadeOutNoStop) { float newFadeFactor = p.x24_fadeFactor - dt / p.x1c_fadeOut; if (newFadeFactor <= 0.f) { if (p.x10_playState == EPlayerState::FadeOutNoStop) { p.x24_fadeFactor = 0.f; } else { StopStreaming(oneshot); return; } } else { p.x24_fadeFactor = newFadeFactor; } } CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume * p.x24_fadeFactor, p.x28_music)); } } void CStreamAudioManager::UpdateDSPStreamers(float dt) { UpdateDSP(false, dt); UpdateDSP(true, dt); } void CStreamAudioManager::StopAllStreams() { for (size_t i = 0; i < s_Players.size(); ++i) { StopStreaming(bool(i)); SDSPPlayer& p = s_Players[i]; SDSPPlayer& qp = s_QueuedPlayers[i]; p = SDSPPlayer(); qp = SDSPPlayer(); } } void CStreamAudioManager::Update(float dt) { CDSPStreamManager::PollHeaderReadCompletions(); UpdateDSPStreamers(dt); } void CStreamAudioManager::StopAll() { StopAllStreams(); } void CStreamAudioManager::SetMusicUnmute(bool unmute) { g_MusicUnmute = unmute; } void CStreamAudioManager::SetSfxVolume(u8 volume) { g_SfxVolume = std::min(volume, u8(127)); } void CStreamAudioManager::SetMusicVolume(u8 volume) { g_MusicVolume = std::min(volume, u8(127)); } void CStreamAudioManager::Initialize() { CDSPStreamManager::Initialize(); } void CStreamAudioManager::StopOneShot() { CStreamAudioManager::StopStreaming(true); SDSPPlayer& p = s_Players[1]; p = SDSPPlayer(); SDSPPlayer& qp = s_QueuedPlayers[1]; qp = SDSPPlayer(); } void CStreamAudioManager::Shutdown() { CDSPStreamManager::Shutdown(); } u8 CStreamAudioManager::g_MusicVolume = 0x7f; u8 CStreamAudioManager::g_SfxVolume = 0x7f; bool CStreamAudioManager::g_MusicUnmute = true; bool CStreamAudioManager::g_SfxUnmute = true; } // namespace metaforce ================================================ FILE: Runtime/Audio/CStreamAudioManager.hpp ================================================ #pragma once #include #include "Runtime/GCNTypes.hpp" namespace metaforce { class CStreamAudioManager { static u8 g_MusicVolume; static u8 g_SfxVolume; static bool g_MusicUnmute; static bool g_SfxUnmute; static float GetTargetDSPVolume(float fileVol, bool music); static void StopStreaming(bool oneshot); static void UpdateDSP(bool oneshot, float dt); static void UpdateDSPStreamers(float dt); static void StopAllStreams(); public: static void Start(bool oneshot, std::string_view fileName, float volume, bool music, float fadeIn, float fadeOut); static void Stop(bool oneshot, std::string_view fileName); static void FadeBackIn(bool oneshot, float fadeTime); static void TemporaryFadeOut(bool oneshot, float fadeTime); static void Update(float dt); static void StopAll(); static void SetMusicUnmute(bool unmute); static void SetSfxVolume(u8 volume); static void SetMusicVolume(u8 volume); static void Initialize(); static void StopOneShot(); static void Shutdown(); }; } // namespace metaforce ================================================ FILE: Runtime/Audio/SFX/Atomic.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Atomic * Date: Sat Sep 1 12:32:04 2018 */ #define GRPAtomic 1 #define SFXelu_a_elec_lp_00 42 #define SFXat2_b_fly_lp_00 43 #define SFXatm_b_fly_lp_00 44 #define SFXatm_b_fly_lp_01 45 #define SFXatm_a_bombdrp_00 46 #define SFXsfx002F 47 #define SFXsfx0030 48 #define SFXsfx0031 49 #define SFXsfx0032 50 #define SFXsfx0033 51 #define SFXsfx0034 52 #define SFXsfx0035 53 #define SFXsfx0036 54 #define SFXsfx0037 55 #define SFXsfx0038 56 #define SFXsfx0039 57 #define SFXsfx003A 58 #define SFXsfx003B 59 ================================================ FILE: Runtime/Audio/SFX/BetaBeetle.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: BetaBeetle * Date: Sat Sep 1 12:32:04 2018 */ #define GRPBetaBeetle 2 #define SFXsfx003C 60 #define SFXsfx003D 61 #define SFXsfx003E 62 #define SFXsfx003F 63 #define SFXsfx0040 64 #define SFXsfx0041 65 #define SFXsfx0042 66 #define SFXsfx0043 67 #define SFXsfx0044 68 #define SFXsfx0045 69 #define SFXsfx0046 70 #define SFXsfx0047 71 #define SFXsfx0048 72 #define SFXsfx0049 73 #define SFXsfx004A 74 #define SFXsfx004B 75 #define SFXsfx004C 76 #define SFXsfx004D 77 #define SFXsfx004E 78 #define SFXsfx004F 79 #define SFXsfx0050 80 #define SFXsfx0051 81 #define SFXsfx0052 82 #define SFXsfx0053 83 #define SFXsfx0054 84 #define SFXsfx0055 85 #define SFXsfx0056 86 #define SFXsfx0057 87 #define SFXsfx0058 88 #define SFXsfx0059 89 #define SFXsfx005A 90 #define SFXsfx005B 91 ================================================ FILE: Runtime/Audio/SFX/Bird.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Bird * Date: Sat Sep 1 12:32:04 2018 */ #define GRPBird 3 #define SFXsfx005C 92 #define SFXsfx005D 93 #define SFXsfx005E 94 #define SFXsfx005F 95 #define SFXsfx0060 96 #define SFXsfx0061 97 #define SFXsfx0062 98 ================================================ FILE: Runtime/Audio/SFX/BloodFlower.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: BloodFlower * Date: Sat Sep 1 12:32:04 2018 */ #define GRPBloodFlower 4 #define SFXblf_a_btmspit_00 99 #define SFXblf_a_bulb_00 100 #define SFXsfx0065 101 #define SFXsfx0066 102 #define SFXsfx0067 103 #define SFXblf_b_active_00 104 #define SFXblf_b_active_01 105 #define SFXsfx006A 106 #define SFXblf_b_breathe_00 107 #define SFXsfx006C 108 #define SFXsfx006D 109 #define SFXsfx006E 110 #define SFXsfx006F 111 #define SFXblf_r_death_00 112 #define SFXblf_r_death_01 113 #define SFXblf_r_impact_00 114 #define SFXfir_x_crispfire6voice_lp_00 115 #define SFXsfx0074 116 #define SFXsfx0075 117 #define SFXsfx0076 118 #define SFXsfx0077 119 ================================================ FILE: Runtime/Audio/SFX/Burrower.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Burrower * Date: Sat Sep 1 12:32:04 2018 */ #define GRPBurrower 5 #define SFXbur_a_attack_00 120 #define SFXbur_b_burrow_lp_00 121 #define SFXbur_b_idle_00 122 #define SFXbur_b_idle_01 123 #define SFXbur_b_walk_00 124 #define SFXbur_b_walk_01 125 #define SFXbur_b_walk_02 126 #define SFXbur_r_death_00 127 #define SFXsfx0080 128 #define SFXsfx0081 129 #define SFXsfx0082 130 #define SFXsfx0083 131 #define SFXsfx0084 132 #define SFXsfx0085 133 #define SFXsfx0086 134 #define SFXsfx0087 135 #define SFXsfx0088 136 #define SFXsfx0089 137 #define SFXsfx008A 138 #define SFXsfx008B 139 ================================================ FILE: Runtime/Audio/SFX/ChozoGhost.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: ChozoGhost * Date: Sat Sep 1 12:32:04 2018 */ #define GRPChozoGhost 6 #define SFXchg_a_dball_00 140 #define SFXchg_a_dcharge_00 141 #define SFXchg_a_dfire_00 142 #define SFXsfx008F 143 #define SFXsfx0090 144 #define SFXsfx0091 145 #define SFXsfx0092 146 #define SFXsfx0093 147 #define SFXsfx0094 148 #define SFXchg_a_pball_00 149 #define SFXchg_a_pfire_00 150 #define SFXsfx0097 151 #define SFXsfx0098 152 #define SFXsfx0099 153 #define SFXsfx009A 154 #define SFXchg_b_fadein_00 155 #define SFXsfx009C 156 #define SFXchg_b_float_00 157 #define SFXchg_b_growl_00 158 #define SFXchg_b_jump_00 159 #define SFXsfx00A0 160 #define SFXsfx00A1 161 #define SFXsfx00A2 162 #define SFXsfx00A3 163 #define SFXsfx00A4 164 #define SFXchg_r_death_00 165 #define SFXchg_r_hit_00 166 #define SFXchg_a_pcharge_00 167 #define SFXsfx00A8 168 #define SFXsfx00A9 169 #define SFXchg_b_growl_01 170 #define SFXchg_b_scrape_00 171 #define SFXchg_r_death_01 172 #define SFXsfx00AD 173 #define SFXchg_b_growl_03 174 #define SFXchg_b_growl_04 175 #define SFXsfx00B0 176 #define SFXchg_b_voxalert_00 177 #define SFXsfx00B2 178 #define SFXchg_b_warpin_00 179 #define SFXsfx00B4 180 #define SFXsfx00B5 181 #define SFXsfx00B6 182 #define SFXsfx00B7 183 #define SFXsfx00B8 184 #define SFXsfx00B9 185 #define SFXsfx00BA 186 #define SFXsfx00BB 187 #define SFXsfx00BC 188 #define SFXsfx00BD 189 #define SFXsfx00BE 190 #define SFXsfx00BF 191 ================================================ FILE: Runtime/Audio/SFX/ChubbWeed.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: ChubbWeed * Date: Sat Sep 1 12:32:04 2018 */ #define GRPChubbWeed 7 #define SFXchb_r_scream_00 192 #define SFXchb_r_alert_00_lp 193 #define SFXsfx00C2 194 #define SFXsfx00C3 195 #define SFXsfx00C4 196 #define SFXsfx00C5 197 #define SFXsfx00C6 198 ================================================ FILE: Runtime/Audio/SFX/CineBoots.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: CineBoots * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCineBoots 59 #define SFXci7_x_jump_00 2972 #define SFXsja_c_electric_lp_00 2973 #define SFXsfx0B9E 2974 #define SFXsfx0B9F 2975 #define SFXsfx0BA0 2976 #define SFXsfx0BA1 2977 #define SFXsfx0BA2 2978 #define SFXsfx0BA3 2979 #define SFXsfx0BA4 2980 #define SFXsfx0BA5 2981 #define SFXsfx0BA6 2982 #define SFXsfx0BA7 2983 #define SFXsfx0BA8 2984 #define SFXsfx0BA9 2985 #define SFXsfx0BAA 2986 #define SFXsfx0BAB 2987 #define SFXsfx0BAC 2988 #define SFXsfx0BAD 2989 #define SFXsfx0BAE 2990 #define SFXsfx0BAF 2991 ================================================ FILE: Runtime/Audio/SFX/CineGeneral.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: CineGeneral * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCineGeneral 60 #define SFXsfx0BB0 2992 #define SFXsfx0BB1 2993 #define SFXci5_x_mapdown_00 2994 #define SFXci5_x_mapload_lp_00 2995 #define SFXci5_x_mapspin_lp_00 2996 #define SFXci5_x_mapup_00 2997 #define SFXepr_b_grenup_00 2998 #define SFXpi2_x_missile_00 2999 #define SFXpi2_x_healthsm_00 3000 #define SFXpi2_x_smissile_00 3001 #define SFXsfx0BBA 3002 #define SFXsfx0BBB 3003 #define SFXsfx0BBC 3004 #define SFXsfx0BBD 3005 #define SFXci9_x_nrg_lp_00 3006 #define SFXsfx0BBF 3007 #define SFXci9_x_insert_00 3008 #define SFXsfx0BC1 3009 #define SFXsfx0BC2 3010 #define SFXsfx0BC3 3011 #define SFXsfx0BC4 3012 #define SFXsfx0BC5 3013 #define SFXsfx0BC6 3014 #define SFXsfx0BC7 3015 #define SFXsfx0BC8 3016 #define SFXsfx0BC9 3017 #define SFXsfx0BCA 3018 #define SFXsfx0BCB 3019 #define SFXsfx0BCC 3020 #define SFXsfx0BCD 3021 #define SFXsfx0BCE 3022 #define SFXsfx0BCF 3023 #define SFXsfx0BD0 3024 #define SFXsfx0BD1 3025 #define SFXsfx0BD2 3026 #define SFXsfx0BD3 3027 #define SFXsfx0BD4 3028 #define SFXsfx0BD5 3029 #define SFXsfx0BD6 3030 #define SFXsfx0BD7 3031 #define SFXsfx0BD8 3032 ================================================ FILE: Runtime/Audio/SFX/CineGun.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: CineGun * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCineGun 62 #define SFXsfx0BE5 3045 #define SFXci3_x_clank_00 3046 #define SFXci4_x_clank_00 3047 #define SFXsfx0BE8 3048 #define SFXci3_c_ridiclaw_00 3049 #define SFXsfx0BEA 3050 #define SFXsfx0BEB 3051 #define SFXsfx0BEC 3052 #define SFXsfx0BED 3053 #define SFXsfx0BEE 3054 #define SFXsfx0BEF 3055 #define SFXsfx0BF0 3056 #define SFXsfx0BF1 3057 #define SFXsfx0BF2 3058 ================================================ FILE: Runtime/Audio/SFX/CineMorphball.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: CineMorphball * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCineMorphball 63 #define SFXsfx0BF3 3059 #define SFXsfx0BF4 3060 #define SFXsfx0BF5 3061 #define SFXsfx0BF6 3062 #define SFXsfx0BF7 3063 #define SFXsfx0BF8 3064 #define SFXsfx0BF9 3065 #define SFXsfx0BFA 3066 #define SFXsfx0BFB 3067 #define SFXsfx0BFC 3068 ================================================ FILE: Runtime/Audio/SFX/CineSuit.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: CineSuit * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCineSuit 64 #define SFXci2_x_energy_lp_00 3069 #define SFXci2_x_energy_lp_01 3070 #define SFXsfx0BFF 3071 #define SFXsfx0C00 3072 #define SFXci2_x_jump_00 3073 #define SFXsfx0C02 3074 #define SFXci2_x_lights_lp_00 3075 #define SFXci2_x_pad_lp_00 3076 #define SFXsfx0C05 3077 #define SFXsfx0C06 3078 #define SFXci3_x_energy_02 3079 #define SFXsfx0C08 3080 #define SFXsfx0C09 3081 #define SFXci3_x_whoosh_00 3082 #define SFXsfx0C0B 3083 #define SFXsfx0C0C 3084 #define SFXsfx0C0D 3085 #define SFXsfx0C0E 3086 #define SFXsfx0C0F 3087 #define SFXsfx0C10 3088 #define SFXsfx0C11 3089 #define SFXsfx0C12 3090 #define SFXsfx0C13 3091 #define SFXsfx0C14 3092 #define SFXsfx0C15 3093 ================================================ FILE: Runtime/Audio/SFX/CineVisor.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: CineVisor * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCineVisor 61 #define SFXcin_x_visor_00 3033 #define SFXsfx0BDA 3034 #define SFXsfx0BDB 3035 #define SFXsfx0BDC 3036 #define SFXsfx0BDD 3037 #define SFXsfx0BDE 3038 #define SFXsfx0BDF 3039 #define SFXsfx0BE0 3040 #define SFXsfx0BE1 3041 #define SFXsfx0BE2 3042 #define SFXsfx0BE3 3043 #define SFXsfx0BE4 3044 ================================================ FILE: Runtime/Audio/SFX/Crater.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Crater * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCrater 44 #define SFXsfx0764 1892 #define SFXsfx0765 1893 #define SFXsfx0766 1894 #define SFXsfx0767 1895 #define SFXsfx0768 1896 #define SFXsfx0769 1897 #define SFXsfx076A 1898 #define SFXsfx076B 1899 #define SFXsfx076C 1900 #define SFXsfx076D 1901 #define SFXsfx076E 1902 #define SFXsfx076F 1903 #define SFXsfx0770 1904 #define SFXsfx0771 1905 #define SFXsfx0772 1906 #define SFXsfx0773 1907 #define SFXsfx0774 1908 #define SFXsfx0775 1909 #define SFXsfx0776 1910 #define SFXsfx0777 1911 #define SFXsfx0778 1912 #define SFXsfx0779 1913 #define SFXsfx077A 1914 #define SFXsfx077B 1915 #define SFXsfx077C 1916 #define SFXsfx077D 1917 #define SFXsfx077E 1918 #define SFXsfx077F 1919 #define SFXsfx0780 1920 #define SFXsfx0781 1921 ================================================ FILE: Runtime/Audio/SFX/Crystallite.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Crystallite * Date: Sat Sep 1 12:32:04 2018 */ #define GRPCrystallite 8 #define SFXcry_b_idle_00 199 #define SFXsfx00C8 200 #define SFXsfx00C9 201 #define SFXsfx00CA 202 #define SFXsfx00CB 203 #define SFXsfx00CC 204 #define SFXsfx00CD 205 #define SFXsfx00CE 206 #define SFXsfx00CF 207 #define SFXsfx00D0 208 ================================================ FILE: Runtime/Audio/SFX/Drones.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Drones * Date: Sat Sep 1 12:32:04 2018 */ #define GRPDrones 9 #define SFXepr_a_shockhit_00 209 #define SFXsfx00D2 210 #define SFXdrn_b_burst_00 211 #define SFXsfx00D4 212 #define SFXsfx00D5 213 #define SFXsfx00D6 214 #define SFXsfx00D7 215 #define SFXdrn_b_patrol_lp_00 216 #define SFXdrn_b_henshin_00 217 #define SFXdrn_b_rocket_lp_00 218 #define SFXdrn_b_rocket_lp_01 219 #define SFXdrn_b_rocket_lp_02 220 #define SFXsfx00DD 221 #define SFXdrn_r_death_00 222 #define SFXsfx00DF 223 #define SFXsfx00E0 224 #define SFXdrn_r_impact_00 225 #define SFXsfx00E2 226 #define SFXdrn_a_blast_00 227 #define SFXsfx00E4 228 #define SFXdrn_r_death_lp_00 229 #define SFXsfx00E6 230 #define SFXdrn_b_alert_00 231 #define SFXsfx00E8 232 #define SFXdrn_a_laser_00 233 #define SFXsfx00EA 234 #define SFXsfx00EB 235 #define SFXsfx00EC 236 #define SFXsfx00ED 237 #define SFXdrn_r_impact_01 238 #define SFXsfx00EF 239 #define SFXdrn_a_blast_01 240 #define SFXsfx00F1 241 #define SFXsfx00F2 242 #define SFXdrn_b_henshin_01 243 #define SFXdrn_b_talk_00 244 #define SFXsfx00F5 245 #define SFXdrn_r_death_lp_01 246 #define SFXdrn_a_charge_00 247 #define SFXdrn_b_beep_03 248 #define SFXsfx00F9 249 #define SFXdrn_r_empblast_01 250 #define SFXsfx00FB 251 #define SFXsfx00FC 252 #define SFXdrn_b_patrolun_lp_00 253 #define SFXdrn_a_laserun_00 254 #define SFXopr_a_shockhit_00 255 #define SFXsfx0100 256 #define SFXsfx0101 257 #define SFXsfx0102 258 #define SFXsfx0103 259 #define SFXsfx0104 260 #define SFXsfx0105 261 #define SFXsfx0106 262 #define SFXsfx0107 263 #define SFXsfx0108 264 #define SFXsfx0109 265 #define SFXsfx010A 266 #define SFXsfx010B 267 #define SFXsfx010C 268 ================================================ FILE: Runtime/Audio/SFX/EliteSpacePirate.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: EliteSpacePirate * Date: Sat Sep 1 12:32:04 2018 */ #define GRPEliteSpacePirate 10 #define SFXepr_a_grenade_00 269 #define SFXsfx010E 270 #define SFXsfx010F 271 #define SFXepr_b_hitglass_00 272 #define SFXepr_a_swoosh_00 273 #define SFXepr_b_run_00 274 #define SFXepr_b_run_01 275 #define SFXepr_a_shokwave_00 276 #define SFXsfx0115 277 #define SFXepr_a_attack_00 278 #define SFXepr_a_attack_01 279 #define SFXepr_b_land_00 280 #define SFXepr_b_alert_00 281 #define SFXepr_b_walk_00 282 #define SFXepr_b_walk_01 283 #define SFXepr_b_alert_01 284 #define SFXepr_b_absorb_lp_00 285 #define SFXepr_b_idle_00 286 #define SFXepr_b_idle_01 287 #define SFXepr_a_hitgrnd_00 288 #define SFXepr_b_walklite_00 289 #define SFXepr_b_walklite_01 290 #define SFXepr_r_pissed_00 291 #define SFXsfx0124 292 #define SFXsfx0125 293 #define SFXepr_a_swoosh_01 294 #define SFXepr_b_taunt_00 295 #define SFXopr_a_swoosh_00 296 #define SFXopr_a_swoosh_01 297 #define SFXepr_b_blokvox_00 298 #define SFXsfx012B 299 #define SFXopr_a_shokwave_00 300 #define SFXsfx012D 301 #define SFXopr_b_absorb_lp_00 302 #define SFXsfx012F 303 #define SFXsfx0130 304 #define SFXsfx0131 305 #define SFXsfx0132 306 #define SFXsfx0133 307 #define SFXsfx0134 308 #define SFXsfx0135 309 #define SFXsfx0136 310 #define SFXsfx0137 311 #define SFXsfx0138 312 #define SFXsfx0139 313 #define SFXsfx013A 314 #define SFXsfx013B 315 #define SFXsfx013C 316 #define SFXsfx013D 317 #define SFXsfx013E 318 #define SFXsfx013F 319 #define SFXsfx0140 320 #define SFXsfx0141 321 #define SFXsfx0142 322 ================================================ FILE: Runtime/Audio/SFX/FireFlea.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: FireFlea * Date: Sat Sep 1 12:32:04 2018 */ #define GRPFireFlea 11 #define SFXfif_b_idle_lp_00 323 #define SFXsfx0144 324 #define SFXfif_b_light_00 325 #define SFXfif_r_death_00 326 #define SFXfif_r_death_01 327 #define SFXfif_r_explode_00 328 #define SFXfif_r_impact_00 329 #define SFXsfx014A 330 #define SFXsfx014B 331 #define SFXsfx014C 332 #define SFXsfx014D 333 #define SFXsfx014E 334 #define SFXsfx014F 335 ================================================ FILE: Runtime/Audio/SFX/Flaaghra.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Flaaghra * Date: Sat Sep 1 12:32:04 2018 */ #define GRPFlaaghra 52 #define SFXfla_a_hitgrnd_00 2600 #define SFXfla_a_swoosh_00 2601 #define SFXfla_a_swoosh_01 2602 #define SFXfla_a_voxattak_00 2603 #define SFXfla_a_voxattak_01 2604 #define SFXfla_b_grow_00 2605 #define SFXfla_b_idle_00 2606 #define SFXsfx0A2F 2607 #define SFXfla_b_voxangry_00 2608 #define SFXfla_b_voxangry_01 2609 #define SFXfla_r_death_00 2610 #define SFXfla_r_death_01 2611 #define SFXfla_r_death_02 2612 #define SFXsfx0A35 2613 #define SFXfla_r_faint_01 2614 #define SFXfla_r_pain_00 2615 #define SFXsfx0A38 2616 #define SFXfla_a_shoot_00 2617 #define SFXfla_a_spit_00 2618 #define SFXfla_a_spit_01 2619 #define SFXsfx0A3C 2620 #define SFXsfx0A3D 2621 #define SFXfla_a_charge_00 2622 #define SFXsfx0A3F 2623 #define SFXsfx0A40 2624 #define SFXfla_b_idlesm_00 2625 #define SFXfla_a_chargevox_00 2626 #define SFXsfx0A43 2627 #define SFXsfx0A44 2628 #define SFXsfx0A45 2629 #define SFXfla_a_shootvox_00 2630 #define SFXsfx0A47 2631 #define SFXsfx0A48 2632 #define SFXsfx0A49 2633 #define SFXsfx0A4A 2634 #define SFXsfx0A4B 2635 #define SFXfla_a_spitvox_00 2636 #define SFXfla_a_spitvox_01 2637 #define SFXsfx0A4E 2638 #define SFXsfx0A4F 2639 #define SFXsfx0A50 2640 #define SFXsfx0A51 2641 #define SFXsfx0A52 2642 #define SFXfla_a_sporevox_01 2643 #define SFXfla_a_hitgrnd_01 2644 #define SFXsfx0A55 2645 #define SFXfla_r_landgrnd_00 2646 #define SFXsfx0A57 2647 #define SFXsfx0A58 2648 #define SFXfla_b_grow_01 2649 #define SFXfla_b_rise_lp_00 2650 #define SFXsfx0A5B 2651 #define SFXfla_b_dizzy_00 2652 #define SFXsfx0A5D 2653 #define SFXsfx0A5E 2654 #define SFXfla_r_painsh_00 2655 #define SFXsfx0A60 2656 #define SFXfla_b_humor_00 2657 #define SFXfla_r_painbig_00 2658 #define SFXfla_b_dizzyout_00 2659 #define SFXfla_b_faintout_00 2660 #define SFXsfx0A65 2661 #define SFXfla_b_dizzy_lp_01 2662 #define SFXsfx0A67 2663 #define SFXsfx0A68 2664 #define SFXsfx0A69 2665 #define SFXsfx0A6A 2666 #define SFXfla_b_voxshrnk_00 2667 #define SFXsfx0A6C 2668 #define SFXsfx0A6D 2669 #define SFXfla_b_voxshrnk_03 2670 #define SFXsfx0A6F 2671 #define SFXsfx0A70 2672 #define SFXsfx0A71 2673 #define SFXsfx0A72 2674 #define SFXsfx0A73 2675 #define SFXsfx0A74 2676 #define SFXsfx0A75 2677 #define SFXsfx0A76 2678 #define SFXsfx0A77 2679 #define SFXsfx0A78 2680 ================================================ FILE: Runtime/Audio/SFX/FlickerBat.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: FlickerBat * Date: Sat Sep 1 12:32:04 2018 */ #define GRPFlickerBat 12 #define SFXflk_b_flicker_00 336 #define SFXflk_b_talk_00 337 #define SFXflk_b_talk_01 338 #define SFXsfx0153 339 #define SFXsfx0154 340 #define SFXflk_r_impact_00 341 #define SFXsfx0156 342 #define SFXsfx0157 343 #define SFXsfx0158 344 #define SFXsfx0159 345 #define SFXsfx015A 346 #define SFXsfx015B 347 ================================================ FILE: Runtime/Audio/SFX/FlyingPirate.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: FlyingPirate * Date: Sat Sep 1 12:32:04 2018 */ #define GRPFlyingPirate 13 #define SFXsfx015C 348 #define SFXfpr_a_chaff_00 349 #define SFXsfx015E 350 #define SFXupr_a_mislfire_00 351 #define SFXsfx0160 352 #define SFXsfx0161 353 #define SFXfpr_a_mislfire_00 354 #define SFXfpr_b_thrust_01 355 #define SFXsfx0164 356 #define SFXfpr_b_engine_lp_00 357 #define SFXfpr_b_engine_lp_01 358 #define SFXfpr_b_engine_lp_02 359 #define SFXfpr_b_voxangry_02 360 #define SFXfpr_b_thrust_00 361 #define SFXsfx016A 362 #define SFXfpr_r_die_00 363 #define SFXfpr_b_intruder_00 364 #define SFXfpr_b_voxalert_00 365 #define SFXfpr_a_mislload_00 366 #define SFXfpr_b_voxangry_00 367 #define SFXsfx0170 368 #define SFXfpr_r_impact_00 369 #define SFXsfx0172 370 #define SFXsfx0173 371 #define SFXsfx0174 372 #define SFXfpr_b_engidle_lp_00 373 #define SFXsfx0176 374 #define SFXfpr_b_blastoff_lp_00 375 #define SFXfpr_b_blastoff_01 376 #define SFXupr_a_mislload_00 377 #define SFXupr_b_engidle_lp_00 378 #define SFXupr_b_engine_lp_00 379 #define SFXupr_b_engine_lp_01 380 #define SFXupr_b_engine_lp_02 381 #define SFXsfx017E 382 #define SFXsfx017F 383 #define SFXupr_b_voxalert_00 384 #define SFXsfx0181 385 #define SFXupr_b_voxangry_00 386 #define SFXsfx0183 387 #define SFXupr_b_voxangry_02 388 #define SFXupr_r_die_00 389 #define SFXupr_r_impact_00 390 #define SFXsfx0187 391 #define SFXsfx0188 392 #define SFXsfx0189 393 #define SFXsfx018A 394 #define SFXsfx018B 395 #define SFXsfx018C 396 #define SFXsfx018D 397 #define SFXsfx018E 398 #define SFXsfx018F 399 #define SFXsfx0190 400 #define SFXsfx0191 401 #define SFXsfx0192 402 #define SFXsfx0193 403 #define SFXsfx0194 404 #define SFXsfx0195 405 #define SFXsfx0196 406 ================================================ FILE: Runtime/Audio/SFX/FrontEnd.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: FrontEnd * Date: Sat Sep 1 12:32:04 2018 */ #define GRPFrontEnd 38 #define SFXfnt_transfore_00L 1090 #define SFXfnt_advance_R 1091 #define SFXsfx0444 1092 #define SFXfnt_selection_change 1093 #define SFXfnt_back 1094 #define SFXfnt_enum_change 1095 #define SFXfnt_advance_L 1096 #define SFXfnt_transfore_00R 1097 #define SFXfnt_transfore_01L 1098 #define SFXfnt_transfore_01R 1099 #define SFXfnt_transfore_02L 1100 #define SFXfnt_transfore_02R 1101 #define SFXfnt_transback_00L 1102 #define SFXfnt_transback_00R 1103 #define SFXfnt_transback_01L 1104 #define SFXfnt_transback_01R 1105 #define SFXfnt_transback_02L 1106 #define SFXfnt_transback_02R 1107 #define SFXfnt_tofusion_L 1108 #define SFXfnt_tofusion_R 1109 #define SFXfnt_fromfusion_L 1110 #define SFXfnt_fromfusion_R 1111 #define SFXsfx0458 1112 #define SFXsfx0459 1113 #define SFXsfx045A 1114 #define SFXsfx045B 1115 #define SFXsfx045C 1116 #define SFXsfx045D 1117 #define SFXsfx045E 1118 #define SFXsfx045F 1119 #define SFXsfx0460 1120 #define SFXsfx0461 1121 #define SFXsfx0462 1122 #define SFXsfx0463 1123 #define SFXsfx0464 1124 #define SFXsfx0465 1125 #define SFXsfx0466 1126 #define SFXsfx0467 1127 ================================================ FILE: Runtime/Audio/SFX/GagantuanBeatle.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: GagantuanBeatle * Date: Sat Sep 1 12:32:04 2018 */ #define GRPGagantuanBeatle 14 #define SFXgab_r_hitlight_01 407 #define SFXga2_b_digexplod_00 408 #define SFXga2_b_digscream_00 409 #define SFXga2_b_idle_01 410 #define SFXga2_b_scrapedirt_00 411 #define SFXgab_b_walkdirt_02 412 #define SFXgab_b_rundirt_00 413 #define SFXgab_b_rundirt_01 414 #define SFXsfx019F 415 #define SFXgab_a_attack_00 416 #define SFXgab_a_attack_01 417 #define SFXgab_b_idle_03 418 #define SFXgab_b_digexplod_00 419 #define SFXfla_b_scrapedirt_00 420 #define SFXgab_b_idle_02 421 #define SFXgab_b_idle_00 422 #define SFXgab_b_idle_01 423 #define SFXgab_b_walkdirt_00 424 #define SFXgab_b_walkdirt_01 425 #define SFXgab_r_collide_00 426 #define SFXsfx01AB 427 #define SFXgab_r_death_01 428 #define SFXgab_r_detect_00 429 #define SFXgab_r_hitlight_00 430 #define SFXgab_b_digscream_00 431 #define SFXgab_b_scrapedirt_00 432 #define SFXga2_b_dig_lp_00 433 #define SFXga2_b_rundirt_00 434 #define SFXgab_b_dig_lp_00 435 #define SFXga2_b_rundirt_01 436 #define SFXga2_b_rundirt_02 437 #define SFXga2_b_walkdirt_00 438 #define SFXga2_b_walkdirt_01 439 #define SFXga2_b_walkdirt_02 440 #define SFXga2_r_collide_00 441 #define SFXga2_a_attack_00 442 #define SFXsfx01BB 443 #define SFXsfx01BC 444 #define SFXsfx01BD 445 #define SFXsfx01BE 446 #define SFXsfx01BF 447 #define SFXsfx01C0 448 #define SFXsfx01C1 449 #define SFXsfx01C2 450 #define SFXsfx01C3 451 #define SFXsfx01C4 452 #define SFXsfx01C5 453 #define SFXsfx01C6 454 #define SFXsfx01C7 455 #define SFXsfx01C8 456 #define SFXsfx01C9 457 #define SFXsfx01CA 458 ================================================ FILE: Runtime/Audio/SFX/Gnats.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Gnats * Date: Sat Sep 1 12:32:04 2018 */ #define GRPGnats 15 #define SFXsfx01CB 459 #define SFXsfx01CC 460 #define SFXsfx01CD 461 #define SFXsfx01CE 462 #define SFXsfx01CF 463 #define SFXsfx01D0 464 #define SFXsfx01D1 465 ================================================ FILE: Runtime/Audio/SFX/Gryzbee.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Gryzbee * Date: Sat Sep 1 12:32:04 2018 */ #define GRPGryzbee 16 #define SFXgrz_b_idle_00 466 #define SFXsfx01D3 467 #define SFXsfx01D4 468 #define SFXsfx01D5 469 #define SFXsfx01D6 470 #define SFXsfx01D7 471 #define SFXsfx01D8 472 #define SFXsfx01D9 473 #define SFXsfx01DA 474 #define SFXsfx01DB 475 #define SFXsfx01DC 476 #define SFXsfx01DD 477 ================================================ FILE: Runtime/Audio/SFX/IceCrack.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: IceCrack * Date: Sat Sep 1 12:32:04 2018 */ #define GRPIceCrack 67 #define SFXsfx0C31 3121 #define SFXsfx0C32 3122 #define SFXsfx0C33 3123 #define SFXsfx0C34 3124 #define SFXsfx0C35 3125 #define SFXsfx0C36 3126 #define SFXcrk_break_subsequent 3127 #define SFXcrk_break_initial 3128 #define SFXcrk_break_final 3129 #define SFXsfx0C3A 3130 #define SFXsfx0C3B 3131 #define SFXsfx0C3C 3132 #define SFXsfx0C3D 3133 #define SFXsfx0C3E 3134 #define SFXsfx0C3F 3135 #define SFXsfx0C40 3136 #define SFXsfx0C41 3137 #define SFXsfx0C42 3138 #define SFXsfx0C43 3139 #define SFXsfx0C44 3140 ================================================ FILE: Runtime/Audio/SFX/IceWorld.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: IceWorld * Date: Sat Sep 1 12:32:04 2018 */ #define GRPIceWorld 45 #define SFXice_x_gateopen_lp_00 1922 #define SFXice_x_gatestop_00 1923 #define SFXsfx0784 1924 #define SFXice_x_towercrk_00 1925 #define SFXice_ballroll_ice 1926 #define SFXice_ballroll_snow 1927 #define SFXsfx0788 1928 #define SFXice_x_towerlnd_00 1929 #define SFXsfx078A 1930 #define SFXice_x_towerlnd_01 1931 #define SFXice_x_towercrk_01 1932 #define SFXice_x_towercrk_02 1933 #define SFXsfx078E 1934 #define SFXsfx078F 1935 #define SFXsfx0790 1936 #define SFXsfx0791 1937 #define SFXsfx0792 1938 #define SFXsfx0793 1939 #define SFXsfx0794 1940 #define SFXsfx0795 1941 #define SFXsfx0796 1942 #define SFXsfx0797 1943 #define SFXsfx0798 1944 #define SFXsfx0799 1945 #define SFXsfx079A 1946 #define SFXsfx079B 1947 #define SFXsfx079C 1948 #define SFXsfx079D 1949 #define SFXsfx079E 1950 #define SFXsfx079F 1951 #define SFXsfx07A0 1952 #define SFXsfx07A1 1953 #define SFXsfx07A2 1954 #define SFXsfx07A3 1955 #define SFXsfx07A4 1956 #define SFXsfx07A5 1957 #define SFXsfx07A6 1958 #define SFXsfx07A7 1959 #define SFXsfx07A8 1960 #define SFXsfx07A9 1961 #define SFXsfx07AA 1962 #define SFXsfx07AB 1963 #define SFXsfx07AC 1964 #define SFXsfx07AD 1965 #define SFXsfx07AE 1966 #define SFXsfx07AF 1967 #define SFXtha_b_rockup_lp_00 1968 #define SFXsfx07B1 1969 #define SFXsfx07B2 1970 #define SFXice_x_ridflap_00 1971 #define SFXsfx07B4 1972 #define SFXsfx07B5 1973 #define SFXsfx07B6 1974 #define SFXice_x_pump_00 1975 #define SFXsfx07B8 1976 #define SFXsfx07B9 1977 #define SFXsfx07BA 1978 #define SFXsfx07BB 1979 #define SFXsfx07BC 1980 #define SFXsfx07BD 1981 #define SFXice_x_piston_00 1982 #define SFXice_x_piston_lp_00 1983 #define SFXsfx07C0 1984 #define SFXsfx07C1 1985 #define SFXsfx07C2 1986 #define SFXsfx07C3 1987 #define SFXsfx07C4 1988 #define SFXsfx07C5 1989 #define SFXsfx07C6 1990 #define SFXsfx07C7 1991 #define SFXsfx07C8 1992 #define SFXsfx07C9 1993 #define SFXtha_b_debris_00 1994 #define SFXtha_b_debris_01 1995 #define SFXsfx07CC 1996 #define SFXsfx07CD 1997 #define SFXsfx07CE 1998 #define SFXsfx07CF 1999 #define SFXsfx07D0 2000 #define SFXsfx07D1 2001 #define SFXsfx07D2 2002 #define SFXsfx07D3 2003 #define SFXsfx07D4 2004 #define SFXsfx07D5 2005 #define SFXsfx07D6 2006 #define SFXsfx07D7 2007 #define SFXsfx07D8 2008 #define SFXsfx07D9 2009 #define SFXsfx07DA 2010 #define SFXsfx07DB 2011 #define SFXsfx07DC 2012 #define SFXsfx07DD 2013 #define SFXsfx07DE 2014 #define SFXsfx07DF 2015 #define SFXsfx07E0 2016 #define SFXsfx07E1 2017 #define SFXsfx07E2 2018 #define SFXsfx07E3 2019 #define SFXsfx07E4 2020 #define SFXsfx07E5 2021 #define SFXsfx07E6 2022 #define SFXsfx07E7 2023 #define SFXsfx07E8 2024 #define SFXsfx07E9 2025 #define SFXsfx07EA 2026 #define SFXsfx07EB 2027 #define SFXsfx07EC 2028 #define SFXsfx07ED 2029 #define SFXsfx07EE 2030 #define SFXsfx07EF 2031 #define SFXsfx07F0 2032 #define SFXsfx07F1 2033 #define SFXsfx07F2 2034 #define SFXsfx07F3 2035 #define SFXsfx07F4 2036 #define SFXsfx07F5 2037 #define SFXsfx07F6 2038 #define SFXsfx07F7 2039 #define SFXsfx07F8 2040 #define SFXsfx07F9 2041 ================================================ FILE: Runtime/Audio/SFX/InjuredPirates.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: InjuredPirates * Date: Sat Sep 1 12:32:04 2018 */ #define GRPInjuredPirates 17 #define SFXsfx01DE 478 #define SFXsfx01DF 479 #define SFXspr_b_exhale_00 480 #define SFXsfx01E1 481 #define SFXspr_b_moan_00 482 #define SFXsfx01E3 483 #define SFXsfx01E4 484 #define SFXsfx01E5 485 #define SFXsfx01E6 486 #define SFXsfx01E7 487 #define SFXsfx01E8 488 #define SFXsfx01E9 489 #define SFXsfx01EA 490 #define SFXsfx01EB 491 #define SFXsfx01EC 492 #define SFXsfx01ED 493 #define SFXsfx01EE 494 #define SFXsfx01EF 495 #define SFXsfx01F0 496 #define SFXsfx01F1 497 #define SFXsfx01F2 498 #define SFXsfx01F3 499 #define SFXsfx01F4 500 #define SFXsfx01F5 501 #define SFXsfx01F6 502 #define SFXsfx01F7 503 #define SFXsfx01F8 504 #define SFXsfx01F9 505 #define SFXsfx01FA 506 #define SFXsfx01FB 507 ================================================ FILE: Runtime/Audio/SFX/IntroBoss.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: IntroBoss * Date: Sat Sep 1 12:32:04 2018 */ #define GRPIntroBoss 0 #define SFXsfx0000 0 #define SFXsfx0001 1 #define SFXsfx0002 2 #define SFXpaq_a_spit_lp_00 3 #define SFXpaq_b_squawk_00 4 #define SFXpaq_b_creak_00 5 #define SFXpaq_b_creak_01 6 #define SFXpaq_b_growl_00 7 #define SFXpaq_b_growl_01 8 #define SFXpaq_b_land_00 9 #define SFXpaq_b_roar_00 10 #define SFXpaq_b_roar_01 11 #define SFXsfx000C 12 #define SFXpaq_b_walk_00 13 #define SFXpaq_b_walk_01 14 #define SFXpaq_r_impact_00 15 #define SFXpaq_r_impact_01 16 #define SFXpaq_r_ldeath_00 17 #define SFXpaq_r_sdeath_01 18 #define SFXpaq_b_swish_00 19 #define SFXsfx0014 20 #define SFXsfx0015 21 #define SFXsfx0016 22 #define SFXpaq_b_land_01 23 #define SFXpaq_b_run_00 24 #define SFXpaq_b_run_01 25 #define SFXsfx001A 26 #define SFXsfx001B 27 #define SFXsfx001C 28 #define SFXsfx001D 29 #define SFXsfx001E 30 #define SFXsfx001F 31 #define SFXsfx0020 32 #define SFXsfx0021 33 #define SFXsfx0022 34 #define SFXsfx0023 35 #define SFXsfx0024 36 #define SFXsfx0025 37 #define SFXsfx0026 38 #define SFXsfx0027 39 #define SFXsfx0028 40 #define SFXsfx0029 41 ================================================ FILE: Runtime/Audio/SFX/IntroWorld.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: IntroWorld * Date: Sat Sep 1 12:32:04 2018 */ #define GRPIntroWorld 46 #define SFXsfx07FA 2042 #define SFXsfx07FB 2043 #define SFXsfx07FC 2044 #define SFXsfx07FD 2045 #define SFXsfx07FE 2046 #define SFXint_c_suitsprk_lp_01 2047 #define SFXsfx0800 2048 #define SFXsfx0801 2049 #define SFXsfx0802 2050 #define SFXsfx0803 2051 #define SFXsfx0804 2052 #define SFXsfx0805 2053 #define SFXsfx0806 2054 #define SFXsfx0807 2055 #define SFXsfx0808 2056 #define SFXsfx0809 2057 #define SFXsfx080A 2058 #define SFXsfx080B 2059 #define SFXsfx080C 2060 #define SFXsfx080D 2061 #define SFXsfx080E 2062 #define SFXsfx080F 2063 #define SFXsfx0810 2064 #define SFXsfx0811 2065 #define SFXsfx0812 2066 #define SFXsfx0813 2067 #define SFXsfx0814 2068 #define SFXsfx0815 2069 #define SFXsfx0816 2070 #define SFXsfx0817 2071 #define SFXsfx0818 2072 #define SFXsfx0819 2073 #define SFXsfx081A 2074 #define SFXsfx081B 2075 #define SFXsfx081C 2076 #define SFXsfx081D 2077 #define SFXsfx081E 2078 #define SFXsfx081F 2079 #define SFXsfx0820 2080 #define SFXsfx0821 2081 #define SFXsfx0822 2082 #define SFXsfx0823 2083 #define SFXsfx0824 2084 #define SFXsfx0825 2085 #define SFXsfx0826 2086 #define SFXsfx0827 2087 #define SFXsfx0828 2088 #define SFXsfx0829 2089 #define SFXsfx082A 2090 #define SFXsfx082B 2091 #define SFXsfx082C 2092 #define SFXsfx082D 2093 #define SFXsfx082E 2094 #define SFXsfx082F 2095 #define SFXsfx0830 2096 #define SFXsfx0831 2097 #define SFXsfx0832 2098 #define SFXsfx0833 2099 #define SFXsfx0834 2100 #define SFXsfx0835 2101 #define SFXsfx0836 2102 #define SFXsfx0837 2103 #define SFXsfx0838 2104 #define SFXsfx0839 2105 #define SFXsfx083A 2106 #define SFXsfx083B 2107 #define SFXsfx083C 2108 #define SFXint_x_frtdoor_00 2109 #define SFXint_x_frtdoor_01 2110 #define SFXsfx083F 2111 #define SFXsfx0840 2112 #define SFXsfx0841 2113 #define SFXsfx0842 2114 #define SFXsfx0843 2115 #define SFXsfx0844 2116 #define SFXsfx0845 2117 #define SFXsfx0846 2118 #define SFXsfx0847 2119 #define SFXsfx0848 2120 #define SFXsfx0849 2121 #define SFXsfx084A 2122 #define SFXsfx084B 2123 #define SFXsfx084C 2124 #define SFXsfx084D 2125 #define SFXint_c_suitbrst_01 2126 #define SFXsfx084F 2127 #define SFXsfx0850 2128 #define SFXsfx0851 2129 #define SFXint_c_shipthst_00 2130 #define SFXsfx0853 2131 #define SFXsfx0854 2132 #define SFXsfx0855 2133 #define SFXsfx0856 2134 #define SFXsfx0857 2135 #define SFXsfx0858 2136 #define SFXsfx0859 2137 #define SFXsfx085A 2138 #define SFXsfx085B 2139 #define SFXsfx085C 2140 #define SFXsfx085D 2141 #define SFXsfx085E 2142 #define SFXsfx085F 2143 #define SFXsfx0860 2144 #define SFXsfx0861 2145 #define SFXsfx0862 2146 #define SFXsfx0863 2147 #define SFXsfx0864 2148 #define SFXsfx0865 2149 #define SFXsfx0866 2150 #define SFXsfx0867 2151 #define SFXsfx0868 2152 #define SFXsfx0869 2153 #define SFXsfx086A 2154 #define SFXsfx086B 2155 #define SFXsfx086C 2156 #define SFXsfx086D 2157 #define SFXsfx086E 2158 #define SFXsfx086F 2159 #define SFXsfx0870 2160 #define SFXint_x_clampstp_00 2161 #define SFXint_x_clamp_00 2162 #define SFXint_x_clamp_01 2163 #define SFXsfx0874 2164 #define SFXsfx0875 2165 #define SFXsfx0876 2166 #define SFXsfx0877 2167 #define SFXsfx0878 2168 #define SFXsfx0879 2169 #define SFXsfx087A 2170 #define SFXsfx087B 2171 #define SFXsfx087C 2172 #define SFXsfx087D 2173 #define SFXsfx087E 2174 #define SFXsfx087F 2175 #define SFXsfx0880 2176 #define SFXsfx0881 2177 #define SFXsfx0882 2178 #define SFXsfx0883 2179 #define SFXsfx0884 2180 ================================================ FILE: Runtime/Audio/SFX/JellyZap.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: JellyZap * Date: Sat Sep 1 12:32:04 2018 */ #define GRPJellyZap 18 #define SFXjzp_a_shock_00 508 #define SFXjzp_a_suck_lp_00 509 #define SFXjzp_b_bubbles_00 510 #define SFXjzp_b_growl_00 511 #define SFXsfx0200 512 #define SFXsfx0201 513 #define SFXsfx0202 514 #define SFXsfx0203 515 #define SFXsfx0204 516 #define SFXsfx0205 517 ================================================ FILE: Runtime/Audio/SFX/LavaWorld.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: LavaWorld * Date: Sat Sep 1 12:32:04 2018 */ #define GRPLavaWorld 47 #define SFXsfx0885 2181 #define SFXsfx0886 2182 #define SFXlav_wlklava_00 2183 #define SFXlav_wlklava_01 2184 #define SFXsfx0889 2185 #define SFXlav_ballroll_lava 2186 #define SFXsfx088B 2187 #define SFXsfx088C 2188 #define SFXsfx088D 2189 #define SFXlav_landlava_00 2190 #define SFXsfx088F 2191 #define SFXsfx0890 2192 #define SFXsfx0891 2193 #define SFXsfx0892 2194 #define SFXsfx0893 2195 #define SFXsfx0894 2196 #define SFXsfx0895 2197 #define SFXsfx0896 2198 #define SFXsfx0897 2199 #define SFXsfx0898 2200 #define SFXsfx0899 2201 #define SFXsfx089A 2202 #define SFXsfx089B 2203 #define SFXsfx089C 2204 #define SFXsfx089D 2205 #define SFXsfx089E 2206 #define SFXsfx089F 2207 #define SFXsfx08A0 2208 #define SFXsfx08A1 2209 #define SFXsfx08A2 2210 #define SFXsfx08A3 2211 #define SFXsfx08A4 2212 #define SFXsfx08A5 2213 #define SFXsfx08A6 2214 #define SFXsfx08A7 2215 #define SFXlav_x_piston_lp_00 2216 #define SFXsfx08A9 2217 #define SFXlav_x_piststop_00 2218 #define SFXlav_x_piststop_01 2219 #define SFXsfx08AC 2220 #define SFXsfx08AD 2221 #define SFXswp_x_03bridgestop_00 2222 #define SFXsfx08AF 2223 #define SFXsfx08B0 2224 #define SFXsfx08B1 2225 #define SFXsfx08B2 2226 #define SFXsfx08B3 2227 #define SFXsfx08B4 2228 #define SFXsfx08B5 2229 #define SFXsfx08B6 2230 #define SFXsfx08B7 2231 #define SFXsfx08B8 2232 #define SFXsfx08B9 2233 #define SFXsfx08BA 2234 #define SFXsfx08BB 2235 #define SFXsfx08BC 2236 #define SFXsfx08BD 2237 #define SFXsfx08BE 2238 #define SFXmag_b_rise_00 2239 #define SFXsfx08C0 2240 #define SFXsfx08C1 2241 #define SFXsfx08C2 2242 #define SFXsfx08C3 2243 #define SFXsfx08C4 2244 #define SFXsfx08C5 2245 #define SFXsfx08C6 2246 #define SFXsfx08C7 2247 #define SFXsfx08C8 2248 #define SFXsfx08C9 2249 #define SFXsfx08CA 2250 #define SFXsfx08CB 2251 #define SFXsfx08CC 2252 #define SFXlav_x_gateup_lp_00 2253 #define SFXsfx08CE 2254 #define SFXlav_x_refrig_00 2255 #define SFXlav_x_gatestop_00 2256 #define SFXsfx08D1 2257 #define SFXsfx08D2 2258 #define SFXsfx08D3 2259 #define SFXsfx08D4 2260 #define SFXsfx08D5 2261 #define SFXsfx08D6 2262 #define SFXlav_landlava_02 2263 #define SFXsfx08D8 2264 #define SFXsfx08D9 2265 #define SFXsfx08DA 2266 #define SFXsfx08DB 2267 #define SFXsfx08DC 2268 #define SFXsfx08DD 2269 #define SFXsfx08DE 2270 #define SFXsfx08DF 2271 #define SFXsfx08E0 2272 #define SFXsfx08E1 2273 #define SFXsfx08E2 2274 #define SFXsfx08E3 2275 #define SFXsfx08E4 2276 #define SFXsfx08E5 2277 #define SFXsfx08E6 2278 #define SFXsfx08E7 2279 #define SFXsfx08E8 2280 #define SFXsfx08E9 2281 #define SFXsfx08EA 2282 #define SFXsfx08EB 2283 ================================================ FILE: Runtime/Audio/SFX/Magdolite.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Magdolite * Date: Sat Sep 1 12:32:04 2018 */ #define GRPMagdolite 19 #define SFXmag_b_alert_00 518 #define SFXmag_b_idle_00 519 #define SFXsfx0208 520 #define SFXmag_r_pain_00 521 #define SFXmag_a_bite_00 522 #define SFXmag_r_death_00 523 #define SFXmag_a_breath_00 524 #define SFXmag_a_flame_lp_00 525 #define SFXmag_r_yelp_00 526 #define SFXsfx020F 527 #define SFXsfx0210 528 #define SFXsfx0211 529 #define SFXsfx0212 530 #define SFXsfx0213 531 #define SFXsfx0214 532 #define SFXsfx0215 533 #define SFXsfx0216 534 #define SFXsfx0217 535 #define SFXsfx0218 536 #define SFXsfx0219 537 #define SFXsfx021A 538 #define SFXsfx021B 539 #define SFXsfx021C 540 #define SFXsfx021D 541 #define SFXsfx021E 542 #define SFXsfx021F 543 #define SFXsfx0220 544 #define SFXsfx0221 545 #define SFXsfx0222 546 #define SFXsfx0223 547 ================================================ FILE: Runtime/Audio/SFX/Metaree.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Metaree * Date: Sat Sep 1 12:32:04 2018 */ #define GRPMetaree 20 #define SFXmtr_a_scream_00 548 #define SFXsfx0225 549 #define SFXmtr_b_spin_lp_06 550 #define SFXmtr_b_spin_lp_07 551 #define SFXsfx0228 552 #define SFXsfx0229 553 #define SFXsfx022A 554 #define SFXsfx022B 555 #define SFXsfx022C 556 #define SFXsfx022D 557 #define SFXsfx022E 558 ================================================ FILE: Runtime/Audio/SFX/Metroid.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Metroid * Date: Sat Sep 1 12:32:04 2018 */ #define GRPMetroid 21 #define SFXsfx022F 559 #define SFXsfx0230 560 #define SFXmtd_a_facehug_02 561 #define SFXmtd_a_swoosh_00 562 #define SFXmtd_a_swoosh_01 563 #define SFXmtd_a_thunk_00 564 #define SFXmtd_b_fadein_00 565 #define SFXmtd_b_fadeout_00 566 #define SFXmtd_b_float_lp_00 567 #define SFXmtd_b_float_lp_01 568 #define SFXmtd_b_idle_00 569 #define SFXmtd_b_idle_01 570 #define SFXsfx023B 571 #define SFXsfx023C 572 #define SFXsfx023D 573 #define SFXsfx023E 574 #define SFXsfx023F 575 #define SFXmtd_b_squish_00 576 #define SFXmtd_b_squish_01 577 #define SFXsfx0242 578 #define SFXmtd_b_voxangry_00 579 #define SFXsfx0244 580 #define SFXsfx0245 581 #define SFXmtd_r_impact_00 582 #define SFXsfx0247 583 #define SFXsfx0248 584 #define SFXmt2_a_facehug_02 585 #define SFXsfx024A 586 #define SFXsfx024B 587 #define SFXsfx024C 588 #define SFXsfx024D 589 #define SFXmt2_b_float_lp_00 590 #define SFXmt2_b_float_lp_01 591 #define SFXsfx0250 592 #define SFXsfx0251 593 #define SFXsfx0252 594 #define SFXmt2_b_idle_02 595 #define SFXsfx0254 596 #define SFXsfx0255 597 #define SFXmt2_b_leech_lp_00 598 #define SFXsfx0257 599 #define SFXsfx0258 600 #define SFXmt2_b_voxangry_00 601 #define SFXmt2_b_voxangry_01 602 #define SFXsfx025B 603 #define SFXmt2_r_impact_00 604 #define SFXmtd_b_grow_00 605 #define SFXmtd_b_suck_lp_00 606 #define SFXmtd_r_death_00 607 #define SFXmt2_b_float_lp_02 608 #define SFXsfx0261 609 #define SFXmtd_b_voxcalm_00 610 #define SFXsfx0263 611 #define SFXsfx0264 612 #define SFXsfx0265 613 #define SFXsfx0266 614 #define SFXsfx0267 615 #define SFXsfx0268 616 #define SFXsfx0269 617 #define SFXsfx026A 618 #define SFXsfx026B 619 #define SFXsfx026C 620 #define SFXsfx026D 621 #define SFXsfx026E 622 #define SFXsfx026F 623 ================================================ FILE: Runtime/Audio/SFX/MetroidPrime.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: MetroidPrime * Date: Sat Sep 1 12:32:04 2018 */ #define GRPMetroidPrime 58 #define SFXmtb_b_voxtaunt_00 2891 #define SFXsfx0B4C 2892 #define SFXmtb_a_claw_00 2893 #define SFXmtb_a_swoosh 2894 #define SFXmtb_b_voxattak_00 2895 #define SFXmtb_b_walk_00 2896 #define SFXmtb_b_walk_01 2897 #define SFXmtb_b_walk_02 2898 #define SFXmtb_b_voxidle_00 2899 #define SFXsfx0B54 2900 #define SFXmtb_r_painbig_00 2901 #define SFXmtb_r_painsm_00 2902 #define SFXmtb_b_voxattak_01 2903 #define SFXmtb_a_icewave_lp_00 2904 #define SFXsfx0B59 2905 #define SFXsfx0B5A 2906 #define SFXmtb_b_voxangry_00 2907 #define SFXmtb_b_voxangry_01 2908 #define SFXmtb_a_flame_lp_00 2909 #define SFXmtb_a_mirv_00 2910 #define SFXsfx0B5F 2911 #define SFXsfx0B60 2912 #define SFXmth_b_dash_00 2913 #define SFXmth_c_painbig_00 2914 #define SFXmth_b_voxcall_00 2915 #define SFXmth_b_voxidle_00 2916 #define SFXmth_b_voxidle_01 2917 #define SFXmth_b_voxtaunt_00 2918 #define SFXsfx0B67 2919 #define SFXmtb_a_hitwall_00 2920 #define SFXmth_c_painsm_00 2921 #define SFXsfx0B6A 2922 #define SFXsfx0B6B 2923 #define SFXmtb_a_flameup_lp_00 2924 #define SFXsfx0B6D 2925 #define SFXsfx0B6E 2926 #define SFXsfx0B6F 2927 #define SFXsfx0B70 2928 #define SFXsfx0B71 2929 #define SFXmth_b_emerge_00 2930 #define SFXsfx0B73 2931 #define SFXmth_b_voxattak_01 2932 #define SFXsfx0B75 2933 #define SFXmth_b_float_lp_00 2934 #define SFXsfx0B77 2935 #define SFXmth_a_blast_lp_00 2936 #define SFXsfx0B79 2937 #define SFXsfx0B7A 2938 #define SFXsfx0B7B 2939 #define SFXmth_a_blasthit_00 2940 #define SFXsfx0B7D 2941 #define SFXsfx0B7E 2942 #define SFXmtb_c_cinemove_00 2943 #define SFXsfx0B80 2944 #define SFXmtb_c_land_00 2945 #define SFXmtb_c_wakeup_00 2946 #define SFXsfx0B83 2947 #define SFXsfx0B84 2948 #define SFXsfx0B85 2949 #define SFXsfx0B86 2950 #define SFXsfx0B87 2951 #define SFXmtb_a_tractor_lp_00 2952 #define SFXmth_a_swing_00 2953 #define SFXsfx0B8A 2954 #define SFXsfx0B8B 2955 #define SFXsfx0B8C 2956 #define SFXsfx0B8D 2957 #define SFXsfx0B8E 2958 #define SFXmtb_b_land_00 2959 #define SFXmth_c_blur_00 2960 #define SFXsfx0B91 2961 #define SFXsfx0B92 2962 #define SFXsfx0B93 2963 #define SFXsfx0B94 2964 #define SFXsfx0B95 2965 #define SFXsfx0B96 2966 #define SFXmtb_a_nrgchg_00 2967 #define SFXsfx0B98 2968 #define SFXmtb_a_nrgfire_lp_00 2969 #define SFXsfx0B9A 2970 #define SFXsfx0B9B 2971 ================================================ FILE: Runtime/Audio/SFX/MinesWorld.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: MinesWorld * Date: Sat Sep 1 12:32:04 2018 */ #define GRPMinesWorld 48 #define SFXsfx08EC 2284 #define SFXsfx08ED 2285 #define SFXsfx08EE 2286 #define SFXsfx08EF 2287 #define SFXsfx08F0 2288 #define SFXsfx08F1 2289 #define SFXmin_x_cranestop_00 2290 #define SFXsfx08F3 2291 #define SFXmin_x_piston_00 2292 #define SFXsfx08F5 2293 #define SFXsfx08F6 2294 #define SFXsfx08F7 2295 #define SFXsfx08F8 2296 #define SFXmin_x_crane_lp_00 2297 #define SFXsfx08FA 2298 #define SFXsfx08FB 2299 #define SFXsfx08FC 2300 #define SFXsfx08FD 2301 #define SFXsfx08FE 2302 #define SFXsfx08FF 2303 #define SFXsfx0900 2304 #define SFXsfx0901 2305 #define SFXsfx0902 2306 #define SFXopr_c_land_00 2307 #define SFXsfx0904 2308 #define SFXsfx0905 2309 #define SFXsfx0906 2310 #define SFXsfx0907 2311 #define SFXsfx0908 2312 #define SFXmin_x_gears_lp_01 2313 #define SFXsfx090A 2314 #define SFXsfx090B 2315 #define SFXsfx090C 2316 #define SFXsfx090D 2317 #define SFXsfx090E 2318 #define SFXsfx090F 2319 #define SFXsfx0910 2320 #define SFXsfx0911 2321 #define SFXsfx0912 2322 #define SFXsfx0913 2323 #define SFXsfx0914 2324 #define SFXsfx0915 2325 #define SFXsfx0916 2326 #define SFXmin_x_turbine_lp_00 2327 #define SFXsfx0918 2328 #define SFXsfx0919 2329 #define SFXsfx091A 2330 #define SFXsfx091B 2331 #define SFXsfx091C 2332 #define SFXsfx091D 2333 #define SFXsfx091E 2334 #define SFXsfx091F 2335 #define SFXsfx0920 2336 #define SFXsfx0921 2337 #define SFXsfx0922 2338 #define SFXsfx0923 2339 #define SFXsfx0924 2340 #define SFXsfx0925 2341 #define SFXsfx0926 2342 #define SFXsfx0927 2343 #define SFXsfx0928 2344 #define SFXsfx0929 2345 #define SFXsfx092A 2346 #define SFXsfx092B 2347 #define SFXsfx092C 2348 #define SFXsfx092D 2349 #define SFXsfx092E 2350 #define SFXsfx092F 2351 #define SFXsfx0930 2352 #define SFXsfx0931 2353 #define SFXsfx0932 2354 #define SFXsfx0933 2355 #define SFXsfx0934 2356 #define SFXsfx0935 2357 #define SFXsfx0936 2358 #define SFXsfx0937 2359 #define SFXsfx0938 2360 #define SFXsfx0939 2361 #define SFXsfx093A 2362 #define SFXsfx093B 2363 #define SFXsfx093C 2364 ================================================ FILE: Runtime/Audio/SFX/Misc.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Misc * Date: Sat Sep 1 12:32:04 2018 */ #define GRPMisc 39 #define SFXdor_x_close_00 1128 #define SFXdor_x_open_00 1129 #define SFXsfx046A 1130 #define SFXpik_x_idle_00 1131 #define SFXsfx046C 1132 #define SFXamb_x_rumble_lp_00 1133 #define SFXsfx046E 1134 #define SFXpik_x_morphamb_lp_00 1135 #define SFXpik_x_powerup_00 1136 #define SFXsfx0471 1137 #define SFXsfx0472 1138 #define SFXsfx0473 1139 #define SFXsfx0474 1140 #define SFXamb_x_splash_02 1141 #define SFXsfx0476 1142 #define SFXsfx0477 1143 #define SFXsfx0478 1144 #define SFXsfx0479 1145 #define SFXsfx047A 1146 #define SFXpik_x_elevamb_lp_00 1147 #define SFXsfx047C 1148 #define SFXeff_x_largeburndeath_lp_00 1149 #define SFXeff_x_smallburndeath_lp_00 1150 #define SFXeff_x_fire_lp_00 1151 #define SFXsfx0480 1152 #define SFXsfx0481 1153 #define SFXci2_x_eletric_00 1154 #define SFXci3_x_electric_lp_00 1155 #define SFXmac_x_fire_lp_00 1156 #define SFXsfx0485 1157 #define SFXci4_x_electric_lp_00 1158 #define SFXsfx0487 1159 #define SFXsfx0488 1160 #define SFXsfx0489 1161 #define SFXsfx048A 1162 #define SFXsfx048B 1163 #define SFXdrn_b_smoke_lp_00 1164 #define SFXga2_r_explode_00 1165 #define SFXsfx048E 1166 #define SFXsfx048F 1167 #define SFXsfx0490 1168 #define SFXsfx0491 1169 #define SFXmag_r_explode_00 1170 #define SFXsfx0493 1171 #define SFXsfx0494 1172 #define SFXsfx0495 1173 #define SFXsfx0496 1174 #define SFXsfx0497 1175 #define SFXeff_x_icebrk_00 1176 #define SFXeff_x_icebrk_01 1177 #define SFXsfx049A 1178 #define SFXsfx049B 1179 #define SFXsfx049C 1180 #define SFXsfx049D 1181 #define SFXsfx049E 1182 #define SFXsfx049F 1183 #define SFXsfx04A0 1184 #define SFXsfx04A1 1185 #define SFXmac_x_fireup_00 1186 #define SFXsfx04A3 1187 #define SFXsfx04A4 1188 #define SFXsfx04A5 1189 #define SFXsfx04A6 1190 #define SFXsfx04A7 1191 #define SFXsfx04A8 1192 #define SFXsfx04A9 1193 #define SFXsfx04AA 1194 #define SFXsfx04AB 1195 #define SFXsfx04AC 1196 #define SFXeff_x_electro_lp_00 1197 #define SFXeff_x_electro_lp_01 1198 #define SFXsfx04AF 1199 #define SFXsfx04B0 1200 #define SFXsfx04B1 1201 #define SFXsfx04B2 1202 #define SFXsfx04B3 1203 #define SFXsfx04B4 1204 #define SFXsfx04B5 1205 #define SFXsfx04B6 1206 #define SFXsfx04B7 1207 #define SFXsfx04B8 1208 #define SFXsfx04B9 1209 #define SFXsfx04BA 1210 #define SFXsfx04BB 1211 #define SFXsfx04BC 1212 #define SFXsfx04BD 1213 #define SFXsfx04BE 1214 #define SFXsfx04BF 1215 #define SFXsfx04C0 1216 #define SFXsfx04C1 1217 #define SFXsfx04C2 1218 #define SFXocu_b_gas_lp_00 1219 #define SFXsfx04C4 1220 #define SFXsfx04C5 1221 #define SFXsfx04C6 1222 #define SFXtha_a_electric_00 1223 #define SFXsfx04C8 1224 #define SFXdrn_r_empelec_00 1225 #define SFXeff_x_frozen_00 1226 #define SFXeff_x_frozen_01 1227 #define SFXsfx04CC 1228 #define SFXsfx04CD 1229 #define SFXsfx04CE 1230 #define SFXsfx04CF 1231 #define SFXsfx04D0 1232 #define SFXsfx04D1 1233 #define SFXepr_b_elec_lp_00 1234 #define SFXsfx04D3 1235 #define SFXsfx04D4 1236 #define SFXsfx04D5 1237 #define SFXsfx04D6 1238 #define SFXsfx04D7 1239 #define SFXamb_x_gatestop_00 1240 #define SFXsfx04D9 1241 #define SFXsfx04DA 1242 #define SFXsfx04DB 1243 #define SFXsfx04DC 1244 #define SFXsfx04DD 1245 #define SFXsfx04DE 1246 #define SFXsfx04DF 1247 #define SFXsfx04E0 1248 #define SFXsfx04E1 1249 #define SFXsfx04E2 1250 #define SFXsfx04E3 1251 #define SFXsfx04E4 1252 #define SFXsfx04E5 1253 #define SFXsfx04E6 1254 #define SFXsfx04E7 1255 #define SFXsfx04E8 1256 #define SFXsfx04E9 1257 #define SFXsfx04EA 1258 #define SFXsfx04EB 1259 #define SFXsfx04EC 1260 #define SFXsfx04ED 1261 #define SFXsfx04EE 1262 #define SFXsfx04EF 1263 #define SFXsfx04F0 1264 #define SFXsfx04F1 1265 #define SFXsfx04F2 1266 #define SFXsfx04F3 1267 #define SFXsfx04F4 1268 #define SFXamb_x_gateup_00 1269 #define SFXsfx04F6 1270 #define SFXsfx04F7 1271 #define SFXsfx04F8 1272 #define SFXsfx04F9 1273 #define SFXsfx04FA 1274 #define SFXrid_r_explode_00 1275 #define SFXsfx04FC 1276 #define SFXsfx04FD 1277 #define SFXsfx04FE 1278 #define SFXsfx04FF 1279 #define SFXsfx0500 1280 #define SFXsfx0501 1281 #define SFXsfx0502 1282 #define SFXsfx0503 1283 #define SFXsfx0504 1284 #define SFXsfx0505 1285 #define SFXamb_x_steamsml_lp_00 1286 #define SFXsfx0507 1287 #define SFXsfx0508 1288 #define SFXsfx0509 1289 #define SFXamb_c_suitlose_lp_00 1290 #define SFXsfx050B 1291 #define SFXsfx050C 1292 #define SFXsfx050D 1293 #define SFXsfx050E 1294 #define SFXsfx050F 1295 #define SFXsfx0510 1296 #define SFXsfx0511 1297 #define SFXsfx0512 1298 #define SFXsfx0513 1299 #define SFXsfx0514 1300 #define SFXsfx0515 1301 #define SFXsfx0516 1302 #define SFXsfx0517 1303 #define SFXsfx0518 1304 #define SFXsfx0519 1305 #define SFXsfx051A 1306 #define SFXsfx051B 1307 #define SFXsfx051C 1308 #define SFXsfx051D 1309 #define SFXsfx051E 1310 #define SFXsfx051F 1311 #define SFXsfx0520 1312 #define SFXsfx0521 1313 #define SFXsfx0522 1314 #define SFXsfx0523 1315 #define SFXsfx0524 1316 #define SFXsfx0525 1317 #define SFXsfx0526 1318 #define SFXsfx0527 1319 #define SFXsfx0528 1320 #define SFXsfx0529 1321 #define SFXsfx052A 1322 #define SFXsfx052B 1323 #define SFXrid_c_elec_lp_00 1324 #define SFXsfx052D 1325 #define SFXsfx052E 1326 #define SFXsfx052F 1327 #define SFXsfx0530 1328 #define SFXsfx0531 1329 #define SFXsfx0532 1330 #define SFXsfx0533 1331 #define SFXsfx0534 1332 #define SFXsfx0535 1333 #define SFXsfx0536 1334 #define SFXsfx0537 1335 #define SFXsfx0538 1336 #define SFXsfx0539 1337 #define SFXsfx053A 1338 #define SFXsfx053B 1339 #define SFXsfx053C 1340 #define SFXsfx053D 1341 #define SFXsfx053E 1342 #define SFXsfx053F 1343 #define SFXsfx0540 1344 #define SFXsfx0541 1345 #define SFXsfx0542 1346 #define SFXsfx0543 1347 #define SFXsfx0544 1348 #define SFXsfx0545 1349 #define SFXsfx0546 1350 #define SFXsfx0547 1351 #define SFXsfx0548 1352 #define SFXsfx0549 1353 #define SFXsfx054A 1354 #define SFXsfx054B 1355 #define SFXsfx054C 1356 #define SFXsfx054D 1357 #define SFXsfx054E 1358 #define SFXsfx054F 1359 #define SFXsfx0550 1360 #define SFXsfx0551 1361 #define SFXsfx0552 1362 #define SFXsfx0553 1363 #define SFXsfx0554 1364 #define SFXsfx0555 1365 #define SFXsfx0556 1366 #define SFXsfx0557 1367 #define SFXsfx0558 1368 #define SFXsfx0559 1369 #define SFXsfx055A 1370 #define SFXsfx055B 1371 #define SFXsfx055C 1372 #define SFXsfx055D 1373 #define SFXsfx055E 1374 ================================================ FILE: Runtime/Audio/SFX/MiscSamus.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: MiscSamus * Date: Sat Sep 1 12:32:04 2018 */ #define GRPMiscSamus 41 #define SFXsam_wlkstone_00 1465 #define SFXsam_wlkstone_01 1466 #define SFXsam_suit_damage 1467 #define SFXsam_ball_jump 1468 #define SFXsam_b_highland_00 1469 #define SFXsam_b_jump_00 1470 #define SFXsam_firstjump 1471 #define SFXsam_landdirt_00 1472 #define SFXsfx05C1 1473 #define SFXsfx05C2 1474 #define SFXsam_ballland_stone 1475 #define SFXsam_ball_boost 1476 #define SFXsam_ball_charge_lp 1477 #define SFXsam_b_morphin_00 1478 #define SFXsam_b_morphout_00 1479 #define SFXsam_ballroll_dirt 1480 #define SFXsfx05C9 1481 #define SFXsfx05CA 1482 #define SFXsfx05CB 1483 #define SFXsam_wlkwater_00 1484 #define SFXsam_wlkwater_01 1485 #define SFXsam_damage_poison_lp 1486 #define SFXsfx05CF 1487 #define SFXsfx05D0 1488 #define SFXsam_vox_damage 1489 #define SFXsam_landmetl_00 1490 #define SFXsam_ball_damage 1491 #define SFXsam_b_movearm_00 1492 #define SFXsam_wlkgrate_00 1493 #define SFXsam_wlkgrate_01 1494 #define SFXsam_wlkmetal_00 1495 #define SFXsam_wlkmetal_01 1496 #define SFXsam_wlkdirt_00 1497 #define SFXsam_ballland_grate 1498 #define SFXsam_wlkdirt_01 1499 #define SFXsam_ballroll_grate 1500 #define SFXsam_ballroll_metal 1501 #define SFXsam_ballroll_stone 1502 #define SFXsfx05DF 1503 #define SFXsam_ballland_metal 1504 #define SFXsam_b_movearm_01 1505 #define SFXsfx05E2 1506 #define SFXsam_landgrate_00 1507 #define SFXsam_landstone_00 1508 #define SFXsfx05E5 1509 #define SFXsfx05E6 1510 #define SFXsam_vox_damage15 1511 #define SFXsam_vox_damage30 1512 #define SFXsam_ball_damage15 1513 #define SFXsam_ball_damage30 1514 #define SFXsfx05EB 1515 #define SFXsam_death 1516 #define SFXsfx05ED 1517 #define SFXsfx05EE 1518 #define SFXsam_b_landmetl_01 1519 #define SFXsfx05F0 1520 #define SFXsfx05F1 1521 #define SFXsfx05F2 1522 #define SFXsam_spider_lp 1523 #define SFXsfx05F4 1524 #define SFXsam_ball_wallhit 1525 #define SFXsam_grapple_fire 1526 #define SFXsam_grapple_lp 1527 #define SFXsam_grapple_swoosh 1528 #define SFXsam_wlkwood_00 1529 #define SFXsam_wlkwood_01 1530 #define SFXsam_landwood_00 1531 #define SFXsam_b_movefla_00 1532 #define SFXsam_ballland_wood 1533 #define SFXsam_ballroll_wood 1534 #define SFXsfx05FF 1535 #define SFXsfx0600 1536 #define SFXsfx0601 1537 #define SFXsfx0602 1538 #define SFXsfx0603 1539 #define SFXsfx0604 1540 #define SFXsam_b_jumpcine_00 1541 #define SFXsam_landphazon_00 1542 #define SFXsfx0607 1543 #define SFXsfx0608 1544 #define SFXsam_ballland_phazon 1545 #define SFXsfx060A 1546 #define SFXsam_ballroll_phazon 1547 #define SFXsam_b_voxland_00 1548 #define SFXsfx060D 1549 #define SFXsam_voxland_02 1550 #define SFXsfx060F 1551 #define SFXsfx0610 1552 #define SFXsam_wlkphazon_00 1553 #define SFXsam_wlkphazon_01 1554 #define SFXpds_b_water_03 1555 #define SFXsam_b_butpress_00 1556 #define SFXsam_b_butpress_01 1557 #define SFXsam_b_panlclos_00 1558 #define SFXsam_b_panlopen_00 1559 #define SFXsam_dash 1560 #define SFXsam_b_move_00 1561 #define SFXsam_b_move_01 1562 #define SFXsam_b_voxjump_00 1563 #define SFXsfx061C 1564 #define SFXsfx061D 1565 #define SFXsfx061E 1566 #define SFXsam_b_wlkmetal_02 1567 #define SFXsam_b_wlkmetal_03 1568 #define SFXsam_landgrass_00 1569 #define SFXsam_b_wlkgrass_00 1570 #define SFXsam_b_wlkgrass_01 1571 #define SFXsam_b_spin_lp_00 1572 #define SFXsam_b_landorg_00 1573 #define SFXsfx0626 1574 #define SFXsam_ballland_org 1575 #define SFXsam_ballroll_org 1576 #define SFXsam_b_wlkorg_00 1577 #define SFXsam_b_wlkorg_01 1578 #define SFXsam_landmud_00 1579 #define SFXsam_ballland_grass 1580 #define SFXsam_ballland_mud 1581 #define SFXsfx062E 1582 #define SFXsam_ballroll_grass 1583 #define SFXsam_ballroll_mud 1584 #define SFXsam_wlkmud_00 1585 #define SFXsam_wlkmud_01 1586 #define SFXsam_b_landcine_01 1587 #define SFXsfx0634 1588 #define SFXsfx0635 1589 #define SFXsfx0636 1590 #define SFXsam_vox_exhausted 1591 #define SFXsam_landsnow_00 1592 #define SFXsam_b_landsnow_01 1593 #define SFXsam_wlksnow_00 1594 #define SFXsam_wlksnow_01 1595 #define SFXsam_b_wlksnow_02 1596 #define SFXsam_b_wlksnow_03 1597 #define SFXsfx063E 1598 #define SFXsfx063F 1599 #define SFXsam_b_landcine_00 1600 #define SFXgab_b_wlksnow_00 1601 #define SFXgab_b_wlksnow_01 1602 #define SFXsfx0643 1603 #define SFXsam_r_hithelm_00 1604 #define SFXsfx0645 1605 #define SFXsfx0646 1606 #define SFXsam_landgrass_02 1607 #define SFXsam_landgrate_02 1608 #define SFXsfx0649 1609 #define SFXsfx064A 1610 #define SFXsam_b_landmetl_02 1611 #define SFXsam_landmud_02 1612 #define SFXsam_landorg_02 1613 #define SFXsam_landphazon_02 1614 #define SFXsam_landdirt_02 1615 #define SFXsam_landsnow_02 1616 #define SFXsam_landstone_02 1617 #define SFXsam_landwood_02 1618 #define SFXsam_wlkice_00 1619 #define SFXsam_wlkice_01 1620 #define SFXsfx0655 1621 #define SFXsfx0656 1622 #define SFXsam_b_landgras_01 1623 #define SFXsam_landice_00 1624 #define SFXsfx0659 1625 #define SFXsam_landice_02 1626 #define SFXsam_ballland_ice 1627 #define SFXsam_ballland_snow 1628 #define SFXpar_b_wlksnow_00 1629 #define SFXpar_b_wlksnow_01 1630 #define SFXsfx065F 1631 #define SFXsfx0660 1632 #define SFXsam_vox_damage_poison 1633 #define SFXsfx0662 1634 #define SFXsfx0663 1635 #define SFXsam_c_suithit_00 1636 #define SFXsam_c_suithit_01 1637 #define SFXsam_c_suithitv_00 1638 #define SFXsam_c_suitmov1_00 1639 #define SFXsam_r_phazhit_lp_00 1640 #define SFXsam_c_suitfall_00 1641 #define SFXsam_c_suitfall_01 1642 #define SFXsam_c_suitmov2_00 1643 #define SFXsam_c_suitmov2_01 1644 #define SFXsfx066D 1645 #define SFXsfx066E 1646 #define SFXfpr_b_land_00 1647 #define SFXfpr_b_land_01 1648 #define SFXsfx0671 1649 #define SFXspr_b_land_00 1650 #define SFXspr_b_land_01 1651 #define SFXsfx0674 1652 #define SFXsam_vox_damage_phazon 1653 #define SFXsfx0676 1654 #define SFXsfx0677 1655 #define SFXsam_vox_damage_heat 1656 #define SFXsfx0679 1657 #define SFXsfx067A 1658 #define SFXsfx067B 1659 #define SFXsam_b_wlkstone_02 1660 #define SFXsam_b_wlkstone_03 1661 #define SFXsam_b_wlkdirt_02 1662 #define SFXsam_b_wlkdirt_03 1663 #define SFXsam_b_move_02 1664 #define SFXsam_b_move_03 1665 #define SFXsfx0682 1666 #define SFXsam_c_mpwlkorg_00 1667 #define SFXsam_c_mpwlkorg_01 1668 #define SFXci7_x_spin_lp_00 1669 #define SFXsfx0686 1670 #define SFXsam_c_butpress_00 1671 #define SFXsam_c_butpress_01 1672 #define SFXsam_c_intrmove_02 1673 #define SFXsam_c_intrmove_03 1674 #define SFXsam_c_intrspin_lp_00 1675 #define SFXsam_c_iwlkmetal_02 1676 #define SFXsam_c_iwlkmetal_03 1677 #define SFXsam_c_movearm_00 1678 #define SFXsam_c_movearm_01 1679 #define SFXsam_c_moveend_00 1680 #define SFXsam_c_moveend_01 1681 #define SFXsam_c_spinend_lp_00 1682 #define SFXsfx0693 1683 #define SFXsam_landlavastone_00 1684 #define SFXsfx0695 1685 #define SFXsam_landlavastone_02 1686 #define SFXsam_ballland_lava 1687 #define SFXsam_ballroll_lavastone 1688 #define SFXsam_wlklavastone_00 1689 #define SFXsam_wlklavastone_01 1690 #define SFXsfx069B 1691 #define SFXsfx069C 1692 #define SFXsfx069D 1693 #define SFXsfx069E 1694 #define SFXsfx069F 1695 #define SFXsfx06A0 1696 #define SFXsfx06A1 1697 #define SFXsfx06A2 1698 #define SFXsfx06A3 1699 #define SFXsfx06A4 1700 #define SFXsfx06A5 1701 #define SFXsfx06A6 1702 #define SFXsfx06A7 1703 #define SFXsfx06A8 1704 #define SFXsfx06A9 1705 #define SFXsfx06AA 1706 #define SFXsfx06AB 1707 #define SFXsfx06AC 1708 #define SFXsfx06AD 1709 #define SFXsfx06AE 1710 #define SFXsfx06AF 1711 #define SFXsfx06B0 1712 #define SFXsfx06B1 1713 #define SFXsfx06B2 1714 #define SFXsfx06B3 1715 #define SFXsfx06B4 1716 #define SFXsfx06B5 1717 #define SFXsfx06B6 1718 #define SFXsfx06B7 1719 #define SFXsfx06B8 1720 #define SFXsfx06B9 1721 #define SFXsfx06BA 1722 #define SFXsfx06BB 1723 ================================================ FILE: Runtime/Audio/SFX/OmegaPirate.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: OmegaPirate * Date: Sat Sep 1 12:32:04 2018 */ #define GRPOmegaPirate 57 #define SFXsfx0B0F 2831 #define SFXsfx0B10 2832 #define SFXopr_b_voxcall_00 2833 #define SFXopr_b_voxcall_01 2834 #define SFXopr_b_voxlaugh_00 2835 #define SFXopr_r_moan_00 2836 #define SFXsfx0B15 2837 #define SFXsfx0B16 2838 #define SFXopr_b_run_01 2839 #define SFXsfx0B18 2840 #define SFXopr_b_voxalert_00 2841 #define SFXopr_b_voxalert_01 2842 #define SFXopr_b_voxattak_00 2843 #define SFXopr_b_voxattak_01 2844 #define SFXopr_b_voxblok_00 2845 #define SFXopr_b_voxidle_00 2846 #define SFXopr_b_voxidle_01 2847 #define SFXopr_b_voxpiss_00 2848 #define SFXopr_b_voxtaunt_00 2849 #define SFXopr_b_walklite_00 2850 #define SFXopr_b_walklite_01 2851 #define SFXopr_b_walk_00 2852 #define SFXopr_b_walk_01 2853 #define SFXopr_b_healnrg_lp_00 2854 #define SFXsfx0B27 2855 #define SFXsfx0B28 2856 #define SFXsfx0B29 2857 #define SFXopr_r_pain_00 2858 #define SFXopr_r_pain_01 2859 #define SFXsfx0B2C 2860 #define SFXopr_b_invis_00 2861 #define SFXopr_b_voxready_00 2862 #define SFXsfx0B2F 2863 #define SFXsfx0B30 2864 #define SFXopr_r_pain_02 2865 #define SFXsfx0B32 2866 #define SFXopr_a_grenchrg_00 2867 #define SFXsfx0B34 2868 #define SFXopr_a_grenade_00 2869 #define SFXsfx0B36 2870 #define SFXsfx0B37 2871 #define SFXsfx0B38 2872 #define SFXsfx0B39 2873 #define SFXsfx0B3A 2874 #define SFXsfx0B3B 2875 #define SFXsfx0B3C 2876 #define SFXsfx0B3D 2877 #define SFXopr_r_death_01 2878 #define SFXsfx0B3F 2879 #define SFXopr_c_samswoosh_00 2880 #define SFXsfx0B41 2881 #define SFXsfx0B42 2882 #define SFXsfx0B43 2883 #define SFXsfx0B44 2884 #define SFXsfx0B45 2885 #define SFXsfx0B46 2886 #define SFXsfx0B47 2887 #define SFXsfx0B48 2888 #define SFXsfx0B49 2889 #define SFXsfx0B4A 2890 ================================================ FILE: Runtime/Audio/SFX/OverWorld.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: OverWorld * Date: Sat Sep 1 12:32:04 2018 */ #define GRPOverWorld 51 #define SFXsfx09E0 2528 #define SFXsfx09E1 2529 #define SFXsfx09E2 2530 #define SFXsfx09E3 2531 #define SFXsfx09E4 2532 #define SFXsfx09E5 2533 #define SFXsfx09E6 2534 #define SFXsfx09E7 2535 #define SFXsfx09E8 2536 #define SFXsfx09E9 2537 #define SFXsfx09EA 2538 #define SFXsfx09EB 2539 #define SFXsfx09EC 2540 #define SFXsfx09ED 2541 #define SFXsfx09EE 2542 #define SFXsfx09EF 2543 #define SFXsfx09F0 2544 #define SFXsfx09F1 2545 #define SFXcrb_b_hiss_00 2546 #define SFXcrb_b_idle_00 2547 #define SFXsfx09F4 2548 #define SFXsfx09F5 2549 #define SFXsfx09F6 2550 #define SFXsfx09F7 2551 #define SFXsfx09F8 2552 #define SFXove_x_spinbars_lp 2553 #define SFXsfx09FA 2554 #define SFXsfx09FB 2555 #define SFXsfx09FC 2556 #define SFXsfx09FD 2557 #define SFXsfx09FE 2558 #define SFXsfx09FF 2559 #define SFXsfx0A00 2560 #define SFXsfx0A01 2561 #define SFXsfx0A02 2562 #define SFXsfx0A03 2563 #define SFXsfx0A04 2564 #define SFXsfx0A05 2565 #define SFXsfx0A06 2566 #define SFXsfx0A07 2567 #define SFXsfx0A08 2568 #define SFXsfx0A09 2569 #define SFXsfx0A0A 2570 #define SFXsfx0A0B 2571 #define SFXsfx0A0C 2572 #define SFXsfx0A0D 2573 #define SFXsfx0A0E 2574 #define SFXsfx0A0F 2575 #define SFXsfx0A10 2576 #define SFXsfx0A11 2577 #define SFXsfx0A12 2578 #define SFXsfx0A13 2579 #define SFXsfx0A14 2580 #define SFXlbm_c_beam_lp_01 2581 #define SFXsfx0A16 2582 #define SFXsfx0A17 2583 #define SFXsfx0A18 2584 #define SFXsfx0A19 2585 #define SFXsfx0A1A 2586 #define SFXsfx0A1B 2587 #define SFXsfx0A1C 2588 #define SFXsfx0A1D 2589 #define SFXsfx0A1E 2590 #define SFXsfx0A1F 2591 #define SFXsfx0A20 2592 #define SFXsfx0A21 2593 #define SFXsfx0A22 2594 #define SFXsfx0A23 2595 #define SFXsfx0A24 2596 #define SFXsfx0A25 2597 #define SFXsfx0A26 2598 #define SFXsfx0A27 2599 ================================================ FILE: Runtime/Audio/SFX/Parasite.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Parasite * Date: Sat Sep 1 12:32:04 2018 */ #define GRPParasite 22 #define SFXpar_a_voxangry_00 624 #define SFXsfx0271 625 #define SFXsfx0272 626 #define SFXsfx0273 627 #define SFXsfx0274 628 #define SFXpar_b_idle_02 629 #define SFXpar_b_munch_00 630 #define SFXsfx0277 631 #define SFXpar_b_run_00 632 #define SFXpar_b_run_01 633 #define SFXsfx027A 634 #define SFXpar_b_walk_00 635 #define SFXpar_b_walk_01 636 #define SFXsfx027D 637 #define SFXpar_b_idlelone_02 638 #define SFXpar_r_impact_00 639 #define SFXsfx0280 640 #define SFXsfx0281 641 #define SFXsfx0282 642 #define SFXsfx0283 643 #define SFXsfx0284 644 #define SFXsfx0285 645 #define SFXsfx0286 646 #define SFXsfx0287 647 ================================================ FILE: Runtime/Audio/SFX/Phazon.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Phazon * Date: Sat Sep 1 12:32:04 2018 */ #define GRPPhazon 66 #define SFXphz_damage_lp 3114 #define SFXsfx0C2B 3115 #define SFXsfx0C2C 3116 #define SFXsfx0C2D 3117 #define SFXsfx0C2E 3118 #define SFXsfx0C2F 3119 #define SFXsfx0C30 3120 ================================================ FILE: Runtime/Audio/SFX/PhazonGun.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: PhazonGun * Date: Sat Sep 1 12:32:04 2018 */ #define GRPPhazonGun 68 #define SFXphg_charge_lp 3141 #define SFXsfx0C46 3142 ================================================ FILE: Runtime/Audio/SFX/PuddleSpore.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: PuddleSpore * Date: Sat Sep 1 12:32:04 2018 */ #define GRPPuddleSpore 23 #define SFXsfx0288 648 #define SFXpds_a_voxactive_00 649 #define SFXpds_b_bubbles_00 650 #define SFXpds_b_open_00 651 #define SFXpds_b_slam_00 652 #define SFXpds_b_voxopen_lp_00 653 #define SFXpds_b_voxslam_00 654 #define SFXpds_b_water_00 655 #define SFXpds_b_water_01 656 #define SFXpds_lava_damage_lp 657 #define SFXsfx0292 658 #define SFXpds_r_voxpain_02 659 #define SFXsfx0294 660 #define SFXsfx0295 661 #define SFXsfx0296 662 #define SFXsfx0297 663 #define SFXsfx0298 664 #define SFXsfx0299 665 #define SFXsfx029A 666 #define SFXsfx029B 667 #define SFXsfx029C 668 #define SFXsfx029D 669 #define SFXsfx029E 670 #define SFXsfx029F 671 #define SFXsfx02A0 672 #define SFXsfx02A1 673 #define SFXsfx02A2 674 ================================================ FILE: Runtime/Audio/SFX/PuddleToad.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: PuddleToad * Date: Sat Sep 1 12:32:04 2018 */ #define GRPPuddleToad 24 #define SFXpud_a_suckin_00 675 #define SFXpud_a_spitout_00 676 #define SFXsfx02A5 677 #define SFXpud_b_close_00 678 #define SFXpud_b_splat_00 679 #define SFXsfx02A8 680 #define SFXsfx02A9 681 #define SFXsfx02AA 682 #define SFXpud_b_voxclose_00 683 #define SFXpud_a_suckin_lp_01 684 #define SFXsfx02AD 685 #define SFXsfx02AE 686 #define SFXpud_b_growl_00 687 #define SFXpud_b_squish_lp_00 688 #define SFXsfx02B1 689 #define SFXsfx02B2 690 #define SFXsfx02B3 691 #define SFXsfx02B4 692 #define SFXsfx02B5 693 #define SFXsfx02B6 694 #define SFXsfx02B7 695 #define SFXsfx02B8 696 #define SFXsfx02B9 697 #define SFXsfx02BA 698 #define SFXsfx02BB 699 #define SFXsfx02BC 700 #define SFXsfx02BD 701 #define SFXsfx02BE 702 #define SFXsfx02BF 703 ================================================ FILE: Runtime/Audio/SFX/Puffer.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Puffer * Date: Sat Sep 1 12:32:04 2018 */ #define GRPPuffer 25 #define SFXpuf_b_fly_lp_00 704 #define SFXsfx02C1 705 #define SFXsfx02C2 706 #define SFXsfx02C3 707 #define SFXsfx02C4 708 #define SFXsfx02C5 709 #define SFXsfx02C6 710 ================================================ FILE: Runtime/Audio/SFX/ReactorDoor.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: ReactorDoor * Date: Sat Sep 1 12:32:04 2018 */ #define GRPReactorDoor 49 #define SFXdor_x_close_01 2365 #define SFXdor_x_open_01 2366 #define SFXsfx093F 2367 #define SFXint_x_reacdoor_01 2368 #define SFXint_x_reacdoor_02 2369 #define SFXint_x_reacdoor_03 2370 #define SFXint_x_reacdoor_04 2371 #define SFXint_x_reacdoor_lp_00 2372 #define SFXsfx0945 2373 #define SFXsfx0946 2374 #define SFXsfx0947 2375 #define SFXsfx0948 2376 #define SFXsfx0949 2377 ================================================ FILE: Runtime/Audio/SFX/Ridley.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Ridley * Date: Sat Sep 1 12:32:04 2018 */ #define GRPRidley 56 #define SFXrid_a_flamerake_00 2771 #define SFXrid_a_flame_lp_00 2772 #define SFXsfx0AD5 2773 #define SFXrid_b_flap_00 2774 #define SFXrid_b_land_00 2775 #define SFXrid_b_passby_00 2776 #define SFXrid_b_popup_00 2777 #define SFXrid_b_voxangry_00 2778 #define SFXrid_b_voxangry_01 2779 #define SFXrid_b_voxattack_00 2780 #define SFXrid_b_voxattack_01 2781 #define SFXrid_b_voxidle_00 2782 #define SFXsfx0ADF 2783 #define SFXrid_b_voxtaunt_00 2784 #define SFXrid_b_voxtaunt_01 2785 #define SFXrid_b_walk_00 2786 #define SFXrid_b_walk_01 2787 #define SFXsfx0AE4 2788 #define SFXrid_b_walksm_00 2789 #define SFXrid_b_walksm_01 2790 #define SFXrid_r_chestexp_00 2791 #define SFXrid_r_death_00 2792 #define SFXrid_r_painbig_00 2793 #define SFXrid_r_pain_00 2794 #define SFXsfx0AEB 2795 #define SFXrid_a_chestglo_00 2796 #define SFXrid_a_claw_00 2797 #define SFXsfx0AEE 2798 #define SFXrid_a_mirv_00 2799 #define SFXsfx0AF0 2800 #define SFXrid_a_tail_00 2801 #define SFXsfx0AF2 2802 #define SFXsfx0AF3 2803 #define SFXsfx0AF4 2804 #define SFXrid_r_pain_lp_00 2805 #define SFXsfx0AF6 2806 #define SFXsfx0AF7 2807 #define SFXsfx0AF8 2808 #define SFXrid_c_smallexp_00 2809 #define SFXrid_c_painbig_00 2810 #define SFXsfx0AFB 2811 #define SFXsfx0AFC 2812 #define SFXsfx0AFD 2813 #define SFXsfx0AFE 2814 #define SFXsfx0AFF 2815 #define SFXsfx0B00 2816 #define SFXsfx0B01 2817 #define SFXsfx0B02 2818 #define SFXsfx0B03 2819 #define SFXsfx0B04 2820 #define SFXsfx0B05 2821 #define SFXsfx0B06 2822 #define SFXsfx0B07 2823 #define SFXsfx0B08 2824 #define SFXsfx0B09 2825 #define SFXsfx0B0A 2826 #define SFXsfx0B0B 2827 #define SFXsfx0B0C 2828 #define SFXsfx0B0D 2829 #define SFXsfx0B0E 2830 ================================================ FILE: Runtime/Audio/SFX/Ripper.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Ripper * Date: Sat Sep 1 12:32:04 2018 */ #define GRPRipper 26 #define SFXrip_b_float_lp_00 711 #define SFXrip_b_scream_00 712 #define SFXsfx02C9 713 #define SFXsfx02CA 714 #define SFXrip_r_impact_00 715 #define SFXsfx02CC 716 #define SFXsfx02CD 717 #define SFXsfx02CE 718 #define SFXsfx02CF 719 #define SFXsfx02D0 720 ================================================ FILE: Runtime/Audio/SFX/RuinsWorld.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: RuinsWorld * Date: Sat Sep 1 12:32:04 2018 */ #define GRPRuinsWorld 50 #define SFXsfx094A 2378 #define SFXsfx094B 2379 #define SFXsfx094C 2380 #define SFXsfx094D 2381 #define SFXeye_b_blink_00 2382 #define SFXeye_b_impact_00 2383 #define SFXsfx0950 2384 #define SFXsfx0951 2385 #define SFXsfx0952 2386 #define SFXsfx0953 2387 #define SFXsfx0954 2388 #define SFXsfx0955 2389 #define SFXsfx0956 2390 #define SFXsfx0957 2391 #define SFXsfx0958 2392 #define SFXsfx0959 2393 #define SFXsfx095A 2394 #define SFXrui_x_leaves_00 2395 #define SFXsfx095C 2396 #define SFXsfx095D 2397 #define SFXsfx095E 2398 #define SFXsfx095F 2399 #define SFXsfx0960 2400 #define SFXmac_x_stop_00 2401 #define SFXsfx0962 2402 #define SFXsfx0963 2403 #define SFXsfx0964 2404 #define SFXsfx0965 2405 #define SFXsfx0966 2406 #define SFXsfx0967 2407 #define SFXsfx0968 2408 #define SFXsfx0969 2409 #define SFXsfx096A 2410 #define SFXsfx096B 2411 #define SFXsfx096C 2412 #define SFXsfx096D 2413 #define SFXsfx096E 2414 #define SFXsfx096F 2415 #define SFXenc_x_genmove_lp_00 2416 #define SFXsfx0971 2417 #define SFXsfx0972 2418 #define SFXsfx0973 2419 #define SFXsfx0974 2420 #define SFXhiv_x_fall_lp_00 2421 #define SFXsfx0976 2422 #define SFXsfx0977 2423 #define SFXhiv_x_open_00 2424 #define SFXsfx0979 2425 #define SFXhiv_x_rotate_00 2426 #define SFXhiv_x_stop_00 2427 #define SFXsfx097C 2428 #define SFXsfx097D 2429 #define SFXrui_x_mapmove_lp_01 2430 #define SFXsfx097F 2431 #define SFXsfx0980 2432 #define SFXsfx0981 2433 #define SFXsfx0982 2434 #define SFXsfx0983 2435 #define SFXhiv_x_closered_lp_00 2436 #define SFXhiv_x_openred_lp_00 2437 #define SFXsfx0986 2438 #define SFXsfx0987 2439 #define SFXchz_b_balldrop_00 2440 #define SFXchz_b_balldrop_01 2441 #define SFXchz_b_release_00 2442 #define SFXchz_x_down_lp_00 2443 #define SFXsfx098C 2444 #define SFXsfx098D 2445 #define SFXsfx098E 2446 #define SFXsfx098F 2447 #define SFXsfx0990 2448 #define SFXsfx0991 2449 #define SFXsfx0992 2450 #define SFXsfx0993 2451 #define SFXsfx0994 2452 #define SFXsfx0995 2453 #define SFXsfx0996 2454 #define SFXsfx0997 2455 #define SFXsfx0998 2456 #define SFXrui_x_mirstop_00 2457 #define SFXsfx099A 2458 #define SFXrui_x_slotstop_00 2459 #define SFXfla_b_bulbopen_00 2460 #define SFXsfx099D 2461 #define SFXsfx099E 2462 #define SFXsfx099F 2463 #define SFXsfx09A0 2464 #define SFXsfx09A1 2465 #define SFXsfx09A2 2466 #define SFXsfx09A3 2467 #define SFXsfx09A4 2468 #define SFXrui_x_flamarmr_00 2469 #define SFXrui_x_flamarmr_01 2470 #define SFXrui_x_flamarm_00 2471 #define SFXrui_x_flamarm_01 2472 #define SFXrui_x_flamarm_02 2473 #define SFXrui_x_flamrise_00 2474 #define SFXrui_x_flamrise_lp_00 2475 #define SFXrui_x_flamhead_00 2476 #define SFXrui_x_flamhead_lp_00 2477 #define SFXrui_x_flamhead_lp_01 2478 #define SFXsfx09AF 2479 #define SFXsfx09B0 2480 #define SFXsfx09B1 2481 #define SFXsfx09B2 2482 #define SFXsfx09B3 2483 #define SFXsfx09B4 2484 #define SFXsfx09B5 2485 #define SFXsfx09B6 2486 #define SFXsfx09B7 2487 #define SFXsfx09B8 2488 #define SFXrui_x_mapmove_lp_00 2489 #define SFXsfx09BA 2490 #define SFXrui_x_maparm_00 2491 #define SFXrui_x_mapcover_00 2492 #define SFXsfx09BD 2493 #define SFXsfx09BE 2494 #define SFXsfx09BF 2495 #define SFXsfx09C0 2496 #define SFXsfx09C1 2497 #define SFXsfx09C2 2498 #define SFXsfx09C3 2499 #define SFXsfx09C4 2500 #define SFXmac_x_changed_00 2501 #define SFXsfx09C6 2502 #define SFXrui_x_mapstop_00 2503 #define SFXsfx09C8 2504 #define SFXrui_x_gatedown_lp_00 2505 #define SFXsfx09CA 2506 #define SFXrui_x_gatestop_00 2507 #define SFXrui_x_gateturn_lp_00 2508 #define SFXsfx09CD 2509 #define SFXrui_x_halftrk_00 2510 #define SFXrui_x_halftrk_lp_00 2511 #define SFXsfx09D0 2512 #define SFXsfx09D1 2513 #define SFXsfx09D2 2514 #define SFXsfx09D3 2515 #define SFXdob_x_moveup_lp_00 2516 #define SFXsfx09D5 2517 #define SFXsfx09D6 2518 #define SFXsfx09D7 2519 #define SFXsfx09D8 2520 #define SFXsfx09D9 2521 #define SFXsfx09DA 2522 #define SFXsfx09DB 2523 #define SFXsfx09DC 2524 #define SFXsfx09DD 2525 #define SFXsfx09DE 2526 #define SFXsfx09DF 2527 ================================================ FILE: Runtime/Audio/SFX/SFX.h ================================================ #ifndef DNAMP1_SFX_H #define DNAMP1_SFX_H #include "Atomic.h" #include "BetaBeetle.h" #include "Bird.h" #include "BloodFlower.h" #include "Burrower.h" #include "ChozoGhost.h" #include "ChubbWeed.h" #include "CineBoots.h" #include "CineGeneral.h" #include "CineGun.h" #include "CineMorphball.h" #include "CineSuit.h" #include "CineVisor.h" #include "Crater.h" #include "Crystallite.h" #include "Drones.h" #include "EliteSpacePirate.h" #include "FireFlea.h" #include "Flaaghra.h" #include "FlickerBat.h" #include "FlyingPirate.h" #include "FrontEnd.h" #include "GagantuanBeatle.h" #include "Gnats.h" #include "Gryzbee.h" #include "IceCrack.h" #include "IceWorld.h" #include "InjuredPirates.h" #include "IntroBoss.h" #include "IntroWorld.h" #include "JellyZap.h" #include "LavaWorld.h" #include "Magdolite.h" #include "Metaree.h" #include "MetroidPrime.h" #include "Metroid.h" #include "MinesWorld.h" #include "MiscSamus.h" #include "Misc.h" #include "OmegaPirate.h" #include "OverWorld.h" #include "Parasite.h" #include "PhazonGun.h" #include "Phazon.h" #include "PuddleSpore.h" #include "PuddleToad.h" #include "Puffer.h" #include "ReactorDoor.h" #include "Ridley.h" #include "Ripper.h" #include "RuinsWorld.h" #include "SFX.h" #include "SamusShip.h" #include "Scarab.h" #include "Seedling.h" #include "SheeGoth.h" #include "SnakeWeed.h" #include "Sova.h" #include "SpacePirate.h" #include "SpankWeed.h" #include "Thardus.h" #include "TheEnd.h" #include "Torobyte.h" #include "Triclops.h" #include "Turret.h" #include "UI.h" #include "WarWasp.h" #include "Weapons.h" #include "ZZZ.h" #include "Zoomer.h" #include "lumigek.h" #include "test.h" #endif // DNAMP1_SFX_H ================================================ FILE: Runtime/Audio/SFX/SamusShip.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: SamusShip * Date: Sat Sep 1 12:32:04 2018 */ #define GRPSamusShip 42 #define SFXsas_x_dooropen_00 1724 #define SFXsas_x_dooropen_01 1725 #define SFXsas_x_dooropen_02 1726 #define SFXsas_x_dooropen_03 1727 #define SFXsas_x_dooropen_04 1728 #define SFXsas_x_dooropen_05 1729 #define SFXsfx06C2 1730 #define SFXsas_x_platrise_lp_01 1731 #define SFXsas_x_thrusmov_00 1732 #define SFXsfx06C5 1733 #define SFXsas_x_thrusmov_02 1734 #define SFXsas_x_thrusmov_03 1735 #define SFXsas_x_hover_lp_00 1736 #define SFXsas_x_thrusfir_lp_01 1737 #define SFXsas_x_hover_lp_01 1738 #define SFXsfx06CB 1739 #define SFXsas_x_thrusfir_lp_04 1740 #define SFXsfx06CD 1741 #define SFXsfx06CE 1742 #define SFXsfx06CF 1743 #define SFXsfx06D0 1744 #define SFXsfx06D1 1745 #define SFXsfx06D2 1746 #define SFXsfx06D3 1747 #define SFXsfx06D4 1748 #define SFXsfx06D5 1749 #define SFXsfx06D6 1750 #define SFXsfx06D7 1751 #define SFXsfx06D8 1752 #define SFXsfx06D9 1753 #define SFXsfx06DA 1754 #define SFXsfx06DB 1755 ================================================ FILE: Runtime/Audio/SFX/Scarab.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Scarab * Date: Sat Sep 1 12:32:04 2018 */ #define GRPScarab 27 #define SFXsfx02D1 721 #define SFXsfx02D2 722 #define SFXsfx02D3 723 #define SFXsfx02D4 724 #define SFXsfx02D5 725 #define SFXsfx02D6 726 #define SFXsfx02D7 727 #define SFXsfx02D8 728 ================================================ FILE: Runtime/Audio/SFX/Seedling.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Seedling * Date: Sat Sep 1 12:32:04 2018 */ #define GRPSeedling 28 #define SFXsed_a_spine_00 729 #define SFXsed_b_idle_lp_00 730 #define SFXsfx02DB 731 #define SFXsfx02DC 732 #define SFXsed_b_alert_00 733 #define SFXsfx02DE 734 #define SFXsfx02DF 735 #define SFXsfx02E0 736 #define SFXsfx02E1 737 #define SFXsfx02E2 738 #define SFXsfx02E3 739 #define SFXsfx02E4 740 #define SFXsfx02E5 741 #define SFXsfx02E6 742 #define SFXsfx02E7 743 #define SFXsfx02E8 744 #define SFXsfx02E9 745 #define SFXsfx02EA 746 #define SFXsfx02EB 747 #define SFXsfx02EC 748 ================================================ FILE: Runtime/Audio/SFX/SheeGoth.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: SheeGoth * Date: Sat Sep 1 12:32:04 2018 */ #define GRPSheeGoth 29 #define SFXshe_a_fireball_00 749 #define SFXshe_b_shake_lp_00 750 #define SFXsfx02EF 751 #define SFXshe_a_flame_lp_00 752 #define SFXshe_a_snap_00 753 #define SFXshe_a_snap_01 754 #define SFXshe_a_stomp_00 755 #define SFXshe_a_stomp_01 756 #define SFXshe_a_voxangry_00 757 #define SFXshe_a_voxangry_01 758 #define SFXshe_a_voxangry_03 759 #define SFXshe_a_voxangry_04 760 #define SFXsh2_a_voxangry_00 761 #define SFXsfx02FA 762 #define SFXsfx02FB 763 #define SFXshe_b_idle_02 764 #define SFXsh2_a_voxangry_01 765 #define SFXshe_b_land_00 766 #define SFXsh2_a_flame_lp_00 767 #define SFXshe_b_roar_00 768 #define SFXsh2_a_snap_00 769 #define SFXsh2_a_voxangry_03 770 #define SFXsh2_a_snap_01 771 #define SFXshe_b_walk_00 772 #define SFXshe_b_walk_01 773 #define SFXshe_r_death_00 774 #define SFXshe_r_death_01 775 #define SFXshe_r_pain_00 776 #define SFXsfx0309 777 #define SFXsh2_a_voxangry_04 778 #define SFXsfx030B 779 #define SFXsfx030C 780 #define SFXsh2_b_idle_02 781 #define SFXsh2_b_land_00 782 #define SFXsh2_b_roar_00 783 #define SFXsh2_b_shake_lp_00 784 #define SFXsh2_b_walk_00 785 #define SFXsh2_b_walk_01 786 #define SFXsh2_r_death_00 787 #define SFXsh2_r_death_01 788 #define SFXsh2_r_pain_00 789 #define SFXsfx0316 790 #define SFXsfx0317 791 #define SFXsh2_b_run_00 792 #define SFXsh2_b_run_01 793 #define SFXsfx031A 794 #define SFXsfx031B 795 #define SFXsfx031C 796 #define SFXsfx031D 797 #define SFXsfx031E 798 #define SFXsfx031F 799 #define SFXsfx0320 800 #define SFXsfx0321 801 #define SFXsfx0322 802 #define SFXsfx0323 803 #define SFXsfx0324 804 #define SFXsfx0325 805 ================================================ FILE: Runtime/Audio/SFX/SnakeWeed.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: SnakeWeed * Date: Sat Sep 1 12:32:04 2018 */ #define GRPSnakeWeed 30 #define SFXsfx0326 806 #define SFXsnk_b_in_00 807 #define SFXsnk_b_out_00 808 #define SFXsfx0329 809 #define SFXsfx032A 810 #define SFXsfx032B 811 ================================================ FILE: Runtime/Audio/SFX/Sova.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Sova * Date: Sat Sep 1 12:32:04 2018 */ #define GRPSova 31 #define SFXfpr_b_walk_00 812 #define SFXfpr_b_walk_01 813 #define SFXspr_a_gun_00 814 #define SFXsfx032F 815 #define SFXsfx0330 816 #define SFXspr_b_walk_00 817 #define SFXspr_b_walk_01 818 #define SFXspr_b_walk_02 819 #define SFXspr_b_walk_03 820 #define SFXsfx0335 821 #define SFXsfx0336 822 #define SFXsfx0337 823 #define SFXsfx0338 824 #define SFXsfx0339 825 #define SFXsfx033A 826 #define SFXsfx033B 827 #define SFXsfx033C 828 #define SFXsfx033D 829 #define SFXsfx033E 830 ================================================ FILE: Runtime/Audio/SFX/SpacePirate.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: SpacePirate * Date: Sat Sep 1 12:32:04 2018 */ #define GRPSpacePirate 32 #define SFXsfx033F 831 #define SFXepr_b_swordin_00 832 #define SFXepr_b_swordout_00 833 #define SFXopr_c_movement_00 834 #define SFXsfx0343 835 #define SFXsfx0344 836 #define SFXsfx0345 837 #define SFXsfx0346 838 #define SFXsfx0347 839 #define SFXspr_b_movement_00 840 #define SFXspr_b_movement_01 841 #define SFXsfx034A 842 #define SFXsfx034B 843 #define SFXsfx034C 844 #define SFXsfx034D 845 #define SFXspr_b_voxalert_01 846 #define SFXsfx034F 847 #define SFXsfx0350 848 #define SFXsfx0351 849 #define SFXsfx0352 850 #define SFXsfx0353 851 #define SFXsfx0354 852 #define SFXsfx0355 853 #define SFXsfx0356 854 #define SFXspr_r_impact_02 855 #define SFXopr_b_swordin_00 856 #define SFXsfx0359 857 #define SFXsfx035A 858 #define SFXsfx035B 859 #define SFXspr_b_idle_02 860 #define SFXspr_b_intruder_00 861 #define SFXsfx035E 862 #define SFXsfx035F 863 #define SFXsfx0360 864 #define SFXsfx0361 865 #define SFXopr_b_swordout_00 866 #define SFXspr_r_himpact_00 867 #define SFXsfx0364 868 #define SFXspr_b_jump_00 869 #define SFXsfx0366 870 #define SFXsfx0367 871 #define SFXsfx0368 872 #define SFXspr_b_voxangry_02 873 #define SFXsfx036A 874 #define SFXsfx036B 875 #define SFXsfx036C 876 #define SFXsfx036D 877 #define SFXsfx036E 878 #define SFXsfx036F 879 #define SFXsfx0370 880 #define SFXsfx0371 881 #define SFXsfx0372 882 #define SFXsfx0373 883 #define SFXepr_b_movement_00 884 #define SFXepr_b_movement_01 885 #define SFXsfx0376 886 #define SFXsfx0377 887 #define SFXsfx0378 888 #define SFXsfx0379 889 #define SFXsfx037A 890 #define SFXsfx037B 891 #define SFXepr_r_die_00 892 #define SFXsfx037D 893 #define SFXepr_r_pain_00 894 #define SFXsfx037F 895 #define SFXsfx0380 896 #define SFXsfx0381 897 #define SFXsfx0382 898 #define SFXsfx0383 899 #define SFXsfx0384 900 #define SFXsfx0385 901 #define SFXsfx0386 902 #define SFXsfx0387 903 #define SFXsfx0388 904 #define SFXsfx0389 905 #define SFXsfx038A 906 #define SFXsfx038B 907 #define SFXsfx038C 908 #define SFXsfx038D 909 #define SFXsfx038E 910 #define SFXsfx038F 911 #define SFXsfx0390 912 #define SFXsfx0391 913 #define SFXsfx0392 914 ================================================ FILE: Runtime/Audio/SFX/SpankWeed.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: SpankWeed * Date: Sat Sep 1 12:32:04 2018 */ #define GRPSpankWeed 33 #define SFXspw_a_spank_00 915 #define SFXsfx0394 916 #define SFXspw_b_out_00 917 #define SFXspw_b_swish_02 918 #define SFXspw_b_swoosh_00 919 #define SFXsfx0398 920 #define SFXspw_r_impact_00 921 #define SFXsfx039A 922 #define SFXfla_a_tentatak_00 923 #define SFXsfx039C 924 #define SFXsfx039D 925 #define SFXsfx039E 926 #define SFXsfx039F 927 #define SFXsfx03A0 928 #define SFXsfx03A1 929 #define SFXsfx03A2 930 #define SFXfla_b_tentmove_01 931 #define SFXfla_b_tentslid_00 932 #define SFXfla_b_tentslid_01 933 #define SFXsfx03A6 934 #define SFXsfx03A7 935 #define SFXsfx03A8 936 #define SFXsfx03A9 937 #define SFXsfx03AA 938 #define SFXsfx03AB 939 #define SFXsfx03AC 940 #define SFXsfx03AD 941 #define SFXsfx03AE 942 #define SFXsfx03AF 943 #define SFXsfx03B0 944 #define SFXsfx03B1 945 #define SFXsfx03B2 946 #define SFXsfx03B3 947 #define SFXsfx03B4 948 #define SFXsfx03B5 949 #define SFXsfx03B6 950 #define SFXsfx03B7 951 #define SFXsfx03B8 952 #define SFXsfx03B9 953 #define SFXsfx03BA 954 #define SFXsfx03BB 955 #define SFXsfx03BC 956 #define SFXsfx03BD 957 #define SFXsfx03BE 958 #define SFXsfx03BF 959 ================================================ FILE: Runtime/Audio/SFX/Thardus.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Thardus * Date: Sat Sep 1 12:32:04 2018 */ #define GRPThardus 55 #define SFXtha_b_voxangry_02 2703 #define SFXtha_b_move_00 2704 #define SFXsfx0A91 2705 #define SFXtha_b_rocks_00 2706 #define SFXtha_a_stoneup_lp_00 2707 #define SFXtha_a_swoosh_00 2708 #define SFXsfx0A95 2709 #define SFXtha_a_voxattak_00 2710 #define SFXtha_a_voxattak_01 2711 #define SFXtha_b_henshin_lp_00 2712 #define SFXtha_b_hitgrnd_00 2713 #define SFXtha_b_hitgrnd_01 2714 #define SFXtha_b_hitgrnd_02 2715 #define SFXtha_b_charge_00 2716 #define SFXtha_a_thunder_00 2717 #define SFXtha_b_rocks_lp_00 2718 #define SFXsfx0A9F 2719 #define SFXtha_b_roll_lp_00 2720 #define SFXsfx0AA1 2721 #define SFXtha_b_voxangry_00 2722 #define SFXtha_b_voxangry_01 2723 #define SFXtha_b_walk_00 2724 #define SFXtha_b_walk_01 2725 #define SFXsfx0AA6 2726 #define SFXtha_r_pain_00 2727 #define SFXtha_b_boulder_00 2728 #define SFXtha_b_boulder_01 2729 #define SFXsfx0AAA 2730 #define SFXtha_b_henshin_00 2731 #define SFXtha_b_henshin_01 2732 #define SFXsfx0AAD 2733 #define SFXtha_a_icewave_lp_00 2734 #define SFXtha_b_chant_00 2735 #define SFXtha_b_enraged_00 2736 #define SFXtha_b_charge_01 2737 #define SFXtha_b_charge_02 2738 #define SFXtha_b_walk_02 2739 #define SFXtha_b_walk_03 2740 #define SFXtha_a_thunder_01 2741 #define SFXsfx0AB6 2742 #define SFXsfx0AB7 2743 #define SFXsfx0AB8 2744 #define SFXtha_a_icestorm_lp_02 2745 #define SFXsfx0ABA 2746 #define SFXsfx0ABB 2747 #define SFXsfx0ABC 2748 #define SFXtha_b_idle_00 2749 #define SFXsfx0ABE 2750 #define SFXsfx0ABF 2751 #define SFXsfx0AC0 2752 #define SFXtha_b_charge_03 2753 #define SFXtha_r_smpain_00 2754 #define SFXsfx0AC3 2755 #define SFXtha_r_pissed_00 2756 #define SFXsfx0AC5 2757 #define SFXsfx0AC6 2758 #define SFXsfx0AC7 2759 #define SFXsfx0AC8 2760 #define SFXsfx0AC9 2761 #define SFXsfx0ACA 2762 #define SFXsfx0ACB 2763 #define SFXsfx0ACC 2764 #define SFXsfx0ACD 2765 #define SFXsfx0ACE 2766 #define SFXsfx0ACF 2767 #define SFXsfx0AD0 2768 #define SFXsfx0AD1 2769 #define SFXsfx0AD2 2770 ================================================ FILE: Runtime/Audio/SFX/TheEnd.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: TheEnd * Date: Sat Sep 1 12:32:04 2018 */ #define GRPTheEnd 70 #define SFXsfx0C4D 3149 #define SFXsh2_a_fireball_lp_00 3150 #define SFXshe_a_fireball_lp_00 3151 #define SFXend_c_shipthst_00 3152 #define SFXsfx0C51 3153 #define SFXsfx0C52 3154 #define SFXsfx0C53 3155 #define SFXsfx0C54 3156 #define SFXsfx0C55 3157 #define SFXsfx0C56 3158 #define SFXsfx0C57 3159 #define SFXsfx0C58 3160 #define SFXsfx0C59 3161 #define SFXsfx0C5A 3162 #define SFXsfx0C5B 3163 #define SFXsfx0C5C 3164 #define SFXsfx0C5D 3165 #define SFXsfx0C5E 3166 ================================================ FILE: Runtime/Audio/SFX/Torobyte.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Torobyte * Date: Sat Sep 1 12:32:04 2018 */ #define GRPToroByte 35 #define SFXocu_b_idle_00 981 #define SFXsfx03D6 982 #define SFXocu_b_blink_00 983 #define SFXsfx03D8 984 #define SFXbat_r_voxdeath_00 985 #define SFXsfx03DA 986 #define SFXsfx03DB 987 #define SFXsfx03DC 988 #define SFXsfx03DD 989 #define SFXsfx03DE 990 #define SFXsfx03DF 991 #define SFXsfx03E0 992 #define SFXsfx03E1 993 #define SFXsfx03E2 994 #define SFXsfx03E3 995 #define SFXsfx03E4 996 #define SFXsfx03E5 997 #define SFXsfx03E6 998 #define SFXsfx03E7 999 ================================================ FILE: Runtime/Audio/SFX/Triclops.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Triclops * Date: Sat Sep 1 12:32:04 2018 */ #define GRPTriclops 34 #define SFXtri_a_attack_00 960 #define SFXtri_a_attract_00 961 #define SFXtri_b_idle_00 962 #define SFXsfx03C3 963 #define SFXtri_b_walk_00 964 #define SFXsfx03C5 965 #define SFXsfx03C6 966 #define SFXtri_r_impact_00 967 #define SFXtri_r_impact_01 968 #define SFXtri_b_run_00 969 #define SFXsfx03CA 970 #define SFXsfx03CB 971 #define SFXsfx03CC 972 #define SFXsfx03CD 973 #define SFXsfx03CE 974 #define SFXsfx03CF 975 #define SFXsfx03D0 976 #define SFXsfx03D1 977 #define SFXsfx03D2 978 #define SFXsfx03D3 979 #define SFXsfx03D4 980 ================================================ FILE: Runtime/Audio/SFX/Turret.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Turret * Date: Sat Sep 1 12:32:04 2018 */ #define GRPTurret 36 #define SFXsfx03E8 1000 #define SFXtur_a_laser_00 1001 #define SFXsfx03EA 1002 #define SFXsfx03EB 1003 #define SFXsfx03EC 1004 #define SFXsfx03ED 1005 #define SFXsfx03EE 1006 #define SFXsfx03EF 1007 #define SFXtur_b_lower_00 1008 #define SFXsfx03F1 1009 #define SFXsfx03F2 1010 #define SFXtur_b_raise_lp_00 1011 #define SFXtur_b_stop_00 1012 #define SFXsfx03F5 1013 #define SFXtur_b_sweep_lp_00 1014 #define SFXsfx03F7 1015 #define SFXsfx03F8 1016 #define SFXsfx03F9 1017 #define SFXsfx03FA 1018 #define SFXtur_r_powrdown_lp_00 1019 #define SFXsfx03FC 1020 #define SFXsfx03FD 1021 #define SFXsfx03FE 1022 #define SFXsfx03FF 1023 #define SFXsfx0400 1024 #define SFXsfx0401 1025 #define SFXsfx0402 1026 ================================================ FILE: Runtime/Audio/SFX/UI.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: UI * Date: Sat Sep 1 12:32:04 2018 */ #define GRPUI 40 #define SFXui_map_rotate 1375 #define SFXui_map_zoom 1376 #define SFXui_lockon_poi 1377 #define SFXui_into_map_screen 1378 #define SFXsfx0563 1379 #define SFXui_outof_map_screen 1380 #define SFXsfx0565 1381 #define SFXui_outof_visor 1382 #define SFXui_into_visor 1383 #define SFXui_visor_xray_lp 1384 #define SFXui_damage_lp 1385 #define SFXui_show_local_beacon 1386 #define SFXui_show_remote_beacon 1387 #define SFXui_visor_thermal_lp 1388 #define SFXsfx056D 1389 #define SFXui_outof_freelook 1390 #define SFXsfx056F 1391 #define SFXui_into_freelook 1392 #define SFXui_lockon_grapple 1393 #define SFXui_freelook_move_lp 1394 #define SFXui_select_visor 1395 #define SFXui_threat_warning 1396 #define SFXui_missile_warning 1397 #define SFXui_select_beam 1398 #define SFXui_threat_damage 1399 #define SFXui_hud_shutdown 1400 #define SFXui_hud_reboot 1401 #define SFXui_static_hi 1402 #define SFXui_static_lo 1403 #define SFXui_visor_scan_lp 1404 #define SFXui_energy_low 1405 #define SFXui_map_pan 1406 #define SFXui_scanning_lp 1407 #define SFXsfx0580 1408 #define SFXui_outof_scan_window 1409 #define SFXsfx0582 1410 #define SFXui_into_scan_window 1411 #define SFXsfx0584 1412 #define SFXsfx0585 1413 #define SFXui_scan_pane_reveal 1414 #define SFXui_into_hud_message 1415 #define SFXui_outof_hud_message 1416 #define SFXui_scan_complete 1417 #define SFXui_hud_memo_type 1418 #define SFXsfx058B 1419 #define SFXsfx058C 1420 #define SFXui_message_screen_key 1421 #define SFXui_options_quit_accept 1422 #define SFXui_options_quit_reject 1423 #define SFXui_quit_change 1424 #define SFXui_new_scan_complete 1425 #define SFXui_map_to_universe 1426 #define SFXui_map_from_universe 1427 #define SFXsfx0594 1428 #define SFXsfx0595 1429 #define SFXsfx0596 1430 #define SFXui_table_change_mode 1431 #define SFXui_advance 1432 #define SFXui_pause_screen_change 1433 #define SFXui_pause_screen_exit 1434 #define SFXui_pause_screen_enter 1435 #define SFXui_table_selection_change 1436 #define SFXui_option_enum_change 1437 #define SFXsfx059E 1438 #define SFXui_scan_next_page 1439 #define SFXui_samus_doll_enter 1440 #define SFXui_samus_doll_exit 1441 #define SFXui_hud_memo_a_pulse 1442 #define SFXui_show_hint_memo 1443 #define SFXui_pause_screen_next_page 1444 #define SFXsfx05A5 1445 #define SFXui_map_screen_key2 1446 #define SFXsfx05A7 1447 #define SFXsfx05A8 1448 #define SFXui_hide_hint_memo 1449 #define SFXsfx05AA 1450 #define SFXui_options_slider_change_lp 1451 #define SFXui_map_screen_key1 1452 #define SFXui_map_screen_key0 1453 #define SFXsfx05AE 1454 #define SFXsfx05AF 1455 #define SFXsfx05B0 1456 #define SFXsfx05B1 1457 #define SFXui_frontend_options_slider_change_lp 1458 #define SFXui_frontend_save_back 1459 #define SFXui_frontend_save_confirm 1460 #define SFXui_frontend_save_move 1461 #define SFXsfx05B6 1462 #define SFXsfx05B7 1463 #define SFXsfx05B8 1464 ================================================ FILE: Runtime/Audio/SFX/WarWasp.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: WarWasp * Date: Sat Sep 1 12:32:04 2018 */ #define GRPWarWasp 37 #define SFXwar_b_idle_lp_00 1027 #define SFXwa2_a_stinger_00 1028 #define SFXwa2_b_idle_lp_00 1029 #define SFXwa2_r_diescream_00 1030 #define SFXwa2_r_hitlight_00 1031 #define SFXwa2_r_wingbuzz_00 1032 #define SFXwar_r_diescream_00 1033 #define SFXwar_r_diescream_01 1034 #define SFXwar_r_diescream_02 1035 #define SFXwar_r_diescream_03 1036 #define SFXwar_r_hitdirt_00 1037 #define SFXwar_r_hitdirt_01 1038 #define SFXwar_r_hitlight_00 1039 #define SFXwar_r_hitlight_01 1040 #define SFXwar_r_wingbuzz_00 1041 #define SFXwar_r_wingbuzz_01 1042 #define SFXwar_r_wingbuzz_02 1043 #define SFXwar_r_wingbuzz_03 1044 #define SFXsfx0415 1045 #define SFXwa2_r_wingbuzz_01 1046 #define SFXwar_a_stab_00 1047 #define SFXwar_b_noise_00 1048 #define SFXwar_b_noise_01 1049 #define SFXwa3_a_stab_00 1050 #define SFXwa3_a_stab_01 1051 #define SFXwa2_b_noise_00 1052 #define SFXwa2_b_noise_01 1053 #define SFXwar_a_stab_01 1054 #define SFXwar_a_stinger_00 1055 #define SFXwar_r_wingbuzz_04 1056 #define SFXsfx0421 1057 #define SFXsfx0422 1058 #define SFXwa2_b_agitated_lp_00 1059 #define SFXsfx0424 1060 #define SFXwa2_b_noise_02 1061 #define SFXwar_b_agitated_lp_00 1062 #define SFXwa3_a_voxattak_00 1063 #define SFXwar_b_noise_02 1064 #define SFXwa3_b_agitated_lp_00 1065 #define SFXwa3_b_idle_lp_00 1066 #define SFXsfx042B 1067 #define SFXwa3_b_noise_00 1068 #define SFXsfx042D 1069 #define SFXwa3_b_noise_02 1070 #define SFXwa3_r_hitlight_00 1071 #define SFXwa3_r_wingbuzz_00 1072 #define SFXglo_b_fly_lp_00 1073 #define SFXwa3_r_wingbuzz_02 1074 #define SFXsfx0433 1075 #define SFXsfx0434 1076 #define SFXsfx0435 1077 #define SFXsfx0436 1078 #define SFXsfx0437 1079 #define SFXsfx0438 1080 #define SFXsfx0439 1081 #define SFXsfx043A 1082 #define SFXsfx043B 1083 #define SFXsfx043C 1084 #define SFXsfx043D 1085 #define SFXsfx043E 1086 #define SFXsfx043F 1087 #define SFXsfx0440 1088 #define SFXsfx0441 1089 ================================================ FILE: Runtime/Audio/SFX/Weapons.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Weapons * Date: Sat Sep 1 12:32:04 2018 */ #define GRPWeapons 43 #define SFXwpn_bomb_drop 1756 #define SFXsfx06DD 1757 #define SFXwpn_bomb_explo 1758 #define SFXwpn_chargeup_ice 1759 #define SFXsfx06E0 1760 #define SFXsfx06E1 1761 #define SFXwpn_combo_xfer 1762 #define SFXwpn_empty_action 1763 #define SFXsfx06E4 1764 #define SFXsfx06E5 1765 #define SFXwpn_chargeup_power 1766 #define SFXwpn_fire_power_charged 1767 #define SFXwpn_fire_missile 1768 #define SFXwpn_reload_missile 1769 #define SFXwpn_fire_power_normal 1770 #define SFXsfx06EB 1771 #define SFXsfx06EC 1772 #define SFXsfx06ED 1773 #define SFXwpn_morph_out_wipe 1774 #define SFXwpn_morph_in_wipe_done 1775 #define SFXwpn_fire_ice_charged 1776 #define SFXsfx06F1 1777 #define SFXsfx06F2 1778 #define SFXsfx06F3 1779 #define SFXsfx06F4 1780 #define SFXwpn_invalid_action 1781 #define SFXsfx06F6 1782 #define SFXsfx06F7 1783 #define SFXsfx06F8 1784 #define SFXsfx06F9 1785 #define SFXsfx06FA 1786 #define SFXsfx06FB 1787 #define SFXsfx06FC 1788 #define SFXsfx06FD 1789 #define SFXsfx06FE 1790 #define SFXsfx06FF 1791 #define SFXsfx0700 1792 #define SFXsfx0701 1793 #define SFXsfx0702 1794 #define SFXsfx0703 1795 #define SFXsfx0704 1796 #define SFXwpn_fire_ice_normal 1797 #define SFXsfx0706 1798 #define SFXsfx0707 1799 #define SFXsfx0708 1800 #define SFXwpn_fire_wave_normal 1801 #define SFXsfx070A 1802 #define SFXwpn_fire_plasma_normal 1803 #define SFXsfx070C 1804 #define SFXwpn_fire_phazon_normal 1805 #define SFXsfx070E 1806 #define SFXsfx070F 1807 #define SFXsfx0710 1808 #define SFXsfx0711 1809 #define SFXsfx0712 1810 #define SFXsfx0713 1811 #define SFXsfx0714 1812 #define SFXsfx0715 1813 #define SFXsfx0716 1814 #define SFXsfx0717 1815 #define SFXsfx0718 1816 #define SFXsfx0719 1817 #define SFXsfx071A 1818 #define SFXsfx071B 1819 #define SFXsfx071C 1820 #define SFXwpn_into_beam_ice 1821 #define SFXwpn_from_beam_ice 1822 #define SFXwpn_to_missile_power 1823 #define SFXwpn_from_missile_power 1824 #define SFXwpn_into_beam_plasma 1825 #define SFXwpn_from_beam_plasma 1826 #define SFXwpn_into_beam_wave 1827 #define SFXwpn_from_beam_wave 1828 #define SFXwpn_to_missile_ice 1829 #define SFXsfx0726 1830 #define SFXsfx0727 1831 #define SFXsfx0728 1832 #define SFXsfx0729 1833 #define SFXsfx072A 1834 #define SFXsfx072B 1835 #define SFXsfx072C 1836 #define SFXsfx072D 1837 #define SFXsfx072E 1838 #define SFXwpn_chargeup_plasma 1839 #define SFXwpn_fire_plasma_charged 1840 #define SFXsfx0731 1841 #define SFXwpn_combo_flamethrower 1842 #define SFXsfx0733 1843 #define SFXwpn_chargeup_wave 1844 #define SFXwpn_fire_wave_charged 1845 #define SFXsfx0736 1846 #define SFXwpn_combo_wavebuster 1847 #define SFXsfx0738 1848 #define SFXwpn_from_missile_ice 1849 #define SFXwpn_to_missile_wave 1850 #define SFXwpn_from_missile_wave 1851 #define SFXwpn_to_missile_plasma 1852 #define SFXwpn_from_missile_plasma 1853 #define SFXsfx073E 1854 #define SFXsfx073F 1855 #define SFXsfx0740 1856 #define SFXsfx0741 1857 #define SFXsfx0742 1858 #define SFXsfx0743 1859 #define SFXsfx0744 1860 #define SFXsfx0745 1861 #define SFXsfx0746 1862 #define SFXsfx0747 1863 #define SFXsfx0748 1864 #define SFXsfx0749 1865 #define SFXsfx074A 1866 #define SFXsfx074B 1867 #define SFXsfx074C 1868 #define SFXsfx074D 1869 #define SFXsfx074E 1870 #define SFXsfx074F 1871 #define SFXsfx0750 1872 #define SFXsfx0751 1873 #define SFXsfx0752 1874 #define SFXsfx0753 1875 #define SFXsfx0754 1876 #define SFXsfx0755 1877 #define SFXsfx0756 1878 #define SFXsfx0757 1879 #define SFXsfx0758 1880 #define SFXsfx0759 1881 #define SFXsfx075A 1882 #define SFXsfx075B 1883 #define SFXsfx075C 1884 #define SFXsfx075D 1885 #define SFXsfx075E 1886 #define SFXsfx075F 1887 #define SFXsfx0760 1888 #define SFXsfx0761 1889 #define SFXsfx0762 1890 #define SFXsfx0763 1891 ================================================ FILE: Runtime/Audio/SFX/ZZZ.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: ZZZ * Date: Sat Sep 1 12:32:04 2018 */ #define GRPZZZ 65 #define SFXsfx0C16 3094 #define SFXsfx0C17 3095 #define SFXsfx0C18 3096 #define SFXsfx0C19 3097 #define SFXsfx0C1A 3098 #define SFXsfx0C1B 3099 #define SFXsfx0C1C 3100 #define SFXsfx0C1D 3101 #define SFXsfx0C1E 3102 #define SFXsfx0C1F 3103 #define SFXsfx0C20 3104 #define SFXsfx0C21 3105 #define SFXsfx0C22 3106 #define SFXsfx0C23 3107 #define SFXsfx0C24 3108 #define SFXsfx0C25 3109 #define SFXsfx0C26 3110 #define SFXsfx0C27 3111 #define SFXsfx0C28 3112 #define SFXsfx0C29 3113 ================================================ FILE: Runtime/Audio/SFX/Zoomer.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: Zoomer * Date: Sat Sep 1 12:32:04 2018 */ #define GRPZoomer 54 #define SFXzom_b_idle_00 2681 #define SFXsfx0A7A 2682 #define SFXsfx0A7B 2683 #define SFXsfx0A7C 2684 #define SFXsfx0A7D 2685 #define SFXgem_b_idle_00 2686 #define SFXsfx0A7F 2687 #define SFXsfx0A80 2688 #define SFXsfx0A81 2689 #define SFXsfx0A82 2690 #define SFXsfx0A83 2691 #define SFXsfx0A84 2692 #define SFXsfx0A85 2693 #define SFXsfx0A86 2694 #define SFXsfx0A87 2695 #define SFXsfx0A88 2696 #define SFXsfx0A89 2697 #define SFXsfx0A8A 2698 #define SFXsfx0A8B 2699 #define SFXsfx0A8C 2700 #define SFXsfx0A8D 2701 #define SFXsfx0A8E 2702 ================================================ FILE: Runtime/Audio/SFX/lumigek.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: lumigek * Date: Sat Sep 1 12:32:04 2018 */ #define GRPLumigek 69 #define SFXlum_b_idle_00 3143 #define SFXsfx0C48 3144 #define SFXsfx0C49 3145 #define SFXsfx0C4A 3146 #define SFXsfx0C4B 3147 #define SFXsfx0C4C 3148 ================================================ FILE: Runtime/Audio/SFX/test.h ================================================ /* Auto-generated Amuse Defines * * Project: Audio * Subproject: test * Date: Sat Sep 1 12:32:04 2018 */ #define GRPtest 53 #define SNGIntro_Cinema 0 #define SNGMain_Plaza 1 #define SNGIntro_Exit 2 #define SNGEndGame 3 ================================================ FILE: Runtime/Audio/g721.c ================================================ /* G.721 decoder, from Sun's public domain CCITT-ADPCM sources, * retrieved from ftp://ftp.cwi.nl/pub/audio/ccitt-adpcm.tar.gz * * For reference, here's the original license: * * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 * */ #include #include "g721.h" static short power2[15] = {1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000}; /* * quan() * * quantizes the input val against the table of size short integers. * It returns i if table[i - 1] <= val < table[i]. * * Using linear search for simple coding. */ static int quan( int val, short *table, int size) { int i; for (i = 0; i < size; i++) if (val < *table++) break; return (i); } /* * fmult() * * returns the integer product of the 14-bit integer "an" and * "floating point" representation (4-bit exponent, 6-bit mantessa) "srn". */ static int fmult( int an, int srn) { short anmag, anexp, anmant; short wanexp, wanmant; short retval; anmag = (an > 0) ? an : ((-an) & 0x1FFF); anexp = quan(anmag, power2, 15) - 6; anmant = (anmag == 0) ? 32 : (anexp >= 0) ? anmag >> anexp : anmag << -anexp; wanexp = anexp + ((srn >> 6) & 0xF) - 13; wanmant = (anmant * (srn & 077) + 0x30) >> 4; retval = (wanexp >= 0) ? ((wanmant << wanexp) & 0x7FFF) : (wanmant >> -wanexp); return (((an ^ srn) < 0) ? -retval : retval); } /* * g72x_init_state() * * This routine initializes and/or resets the g72x_state structure * pointed to by 'state_ptr'. * All the initial state values are specified in the CCITT G.721 document. */ void g72x_init_state(struct g72x_state *state_ptr) { int cnta; state_ptr->yl = 34816; state_ptr->yu = 544; state_ptr->dms = 0; state_ptr->dml = 0; state_ptr->ap = 0; for (cnta = 0; cnta < 2; cnta++) { state_ptr->a[cnta] = 0; state_ptr->pk[cnta] = 0; state_ptr->sr[cnta] = 32; } for (cnta = 0; cnta < 6; cnta++) { state_ptr->b[cnta] = 0; state_ptr->dq[cnta] = 32; } state_ptr->td = 0; } /* * predictor_zero() * * computes the estimated signal from 6-zero predictor. * */ static int predictor_zero( struct g72x_state *state_ptr) { int i; int sezi; sezi = fmult(state_ptr->b[0] >> 2, state_ptr->dq[0]); for (i = 1; i < 6; i++) /* ACCUM */ sezi += fmult(state_ptr->b[i] >> 2, state_ptr->dq[i]); return (sezi); } /* * predictor_pole() * * computes the estimated signal from 2-pole predictor. * */ static int predictor_pole( struct g72x_state *state_ptr) { return (fmult(state_ptr->a[1] >> 2, state_ptr->sr[1]) + fmult(state_ptr->a[0] >> 2, state_ptr->sr[0])); } /* * step_size() * * computes the quantization step size of the adaptive quantizer. * */ static long step_size( struct g72x_state *state_ptr) { long y; long dif; long al; if (state_ptr->ap >= 256) return (state_ptr->yu); else { y = state_ptr->yl >> 6; dif = state_ptr->yu - y; al = state_ptr->ap >> 2; if (dif > 0) y += (dif * al) >> 6; else if (dif < 0) y += (dif * al + 0x3F) >> 6; return (y); } } /* * reconstruct() * * Returns reconstructed difference signal 'dq' obtained from * codeword 'i' and quantization step size scale factor 'y'. * Multiplication is performed in log base 2 domain as addition. */ static int reconstruct( int sign, /* 0 for non-negative value */ int dqln, /* G.72x codeword */ int y) /* Step size multiplier */ { short dql; /* Log of 'dq' magnitude */ short dex; /* Integer part of log */ short dqt; short dq; /* Reconstructed difference signal sample */ dql = dqln + (y >> 2); /* ADDA */ if (dql < 0) { return ((sign) ? -0x8000 : 0); } else { /* ANTILOG */ dex = (dql >> 7) & 15; dqt = 128 + (dql & 127); dq = (dqt << 7) >> (14 - dex); return ((sign) ? (dq - 0x8000) : dq); } } /* * update() * * updates the state variables for each output code */ static void update( /*int code_size,*/ /* distinguish 723_40 with others */ int y, /* quantizer step size */ int wi, /* scale factor multiplier */ int fi, /* for long/short term energies */ int dq, /* quantized prediction difference */ int sr, /* reconstructed signal */ int dqsez, /* difference from 2-pole predictor */ struct g72x_state *state_ptr) /* coder state pointer */ { int cnt; short mag, exp; /* Adaptive predictor, FLOAT A */ short a2p; /* LIMC */ short a1ul; /* UPA1 */ short pks1; /* UPA2 */ short fa1; char tr; /* tone/transition detector */ short ylint, thr2, dqthr; short ylfrac, thr1; short pk0; pk0 = (dqsez < 0) ? 1 : 0; /* needed in updating predictor poles */ mag = dq & 0x7FFF; /* prediction difference magnitude */ /* TRANS */ ylint = state_ptr->yl >> 15; /* exponent part of yl */ ylfrac = (state_ptr->yl >> 10) & 0x1F; /* fractional part of yl */ thr1 = (32 + ylfrac) << ylint; /* threshold */ thr2 = (ylint > 9) ? 31 << 10 : thr1; /* limit thr2 to 31 << 10 */ dqthr = (thr2 + (thr2 >> 1)) >> 1; /* dqthr = 0.75 * thr2 */ if (state_ptr->td == 0) /* signal supposed voice */ tr = 0; else if (mag <= dqthr) /* supposed data, but small mag */ tr = 0; /* treated as voice */ else /* signal is data (modem) */ tr = 1; /* * Quantizer scale factor adaptation. */ /* FUNCTW & FILTD & DELAY */ /* update non-steady state step size multiplier */ state_ptr->yu = y + ((wi - y) >> 5); /* LIMB */ if (state_ptr->yu < 544) /* 544 <= yu <= 5120 */ state_ptr->yu = 544; else if (state_ptr->yu > 5120) state_ptr->yu = 5120; /* FILTE & DELAY */ /* update steady state step size multiplier */ state_ptr->yl += state_ptr->yu + ((-state_ptr->yl) >> 6); /* * Adaptive predictor coefficients. */ if (tr == 1) { /* reset a's and b's for modem signal */ state_ptr->a[0] = 0; state_ptr->a[1] = 0; state_ptr->b[0] = 0; state_ptr->b[1] = 0; state_ptr->b[2] = 0; state_ptr->b[3] = 0; state_ptr->b[4] = 0; state_ptr->b[5] = 0; a2p=0; /* won't be used, clear warning */ } else { /* update a's and b's */ pks1 = pk0 ^ state_ptr->pk[0]; /* UPA2 */ /* update predictor pole a[1] */ a2p = state_ptr->a[1] - (state_ptr->a[1] >> 7); if (dqsez != 0) { fa1 = (pks1) ? state_ptr->a[0] : -state_ptr->a[0]; if (fa1 < -8191) /* a2p = function of fa1 */ a2p -= 0x100; else if (fa1 > 8191) a2p += 0xFF; else a2p += fa1 >> 5; if (pk0 ^ state_ptr->pk[1]) /* LIMC */ if (a2p <= -12160) a2p = -12288; else if (a2p >= 12416) a2p = 12288; else a2p -= 0x80; else if (a2p <= -12416) a2p = -12288; else if (a2p >= 12160) a2p = 12288; else a2p += 0x80; } /* TRIGB & DELAY */ state_ptr->a[1] = a2p; /* UPA1 */ /* update predictor pole a[0] */ state_ptr->a[0] -= state_ptr->a[0] >> 8; if (dqsez != 0) { if (pks1 == 0) state_ptr->a[0] += 192; else state_ptr->a[0] -= 192; } /* LIMD */ a1ul = 15360 - a2p; if (state_ptr->a[0] < -a1ul) state_ptr->a[0] = -a1ul; else if (state_ptr->a[0] > a1ul) state_ptr->a[0] = a1ul; /* UPB : update predictor zeros b[6] */ for (cnt = 0; cnt < 6; cnt++) { /*if (code_size == 5)*/ /* for 40Kbps G.723 */ /* state_ptr->b[cnt] -= state_ptr->b[cnt] >> 9;*/ /*else*/ /* for G.721 and 24Kbps G.723 */ state_ptr->b[cnt] -= state_ptr->b[cnt] >> 8; if (dq & 0x7FFF) { /* XOR */ if ((dq ^ state_ptr->dq[cnt]) >= 0) state_ptr->b[cnt] += 128; else state_ptr->b[cnt] -= 128; } } } for (cnt = 5; cnt > 0; cnt--) state_ptr->dq[cnt] = state_ptr->dq[cnt-1]; /* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */ if (mag == 0) { state_ptr->dq[0] = (dq >= 0) ? 0x20 : 0xFC20; } else { exp = quan(mag, power2, 15); state_ptr->dq[0] = (dq >= 0) ? (exp << 6) + ((mag << 6) >> exp) : (exp << 6) + ((mag << 6) >> exp) - 0x400; } state_ptr->sr[1] = state_ptr->sr[0]; /* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */ if (sr == 0) { state_ptr->sr[0] = 0x20; } else if (sr > 0) { exp = quan(sr, power2, 15); state_ptr->sr[0] = (exp << 6) + ((sr << 6) >> exp); } else if (sr > -32768) { mag = -sr; exp = quan(mag, power2, 15); state_ptr->sr[0] = (exp << 6) + ((mag << 6) >> exp) - 0x400; } else state_ptr->sr[0] = 0xFC20; /* DELAY A */ state_ptr->pk[1] = state_ptr->pk[0]; state_ptr->pk[0] = pk0; /* TONE */ if (tr == 1) /* this sample has been treated as data */ state_ptr->td = 0; /* next one will be treated as voice */ else if (a2p < -11776) /* small sample-to-sample correlation */ state_ptr->td = 1; /* signal may be data */ else /* signal is voice */ state_ptr->td = 0; /* * Adaptation speed control. */ state_ptr->dms += (fi - state_ptr->dms) >> 5; /* FILTA */ state_ptr->dml += (((fi << 2) - state_ptr->dml) >> 7); /* FILTB */ if (tr == 1) state_ptr->ap = 256; else if (y < 1536) /* SUBTC */ state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else if (state_ptr->td == 1) state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else if (abs((state_ptr->dms << 2) - state_ptr->dml) >= (state_ptr->dml >> 3)) state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else state_ptr->ap += (-state_ptr->ap) >> 4; } /* * Maps G.721 code word to reconstructed scale factor normalized log * magnitude values. */ static short _dqlntab[16] = {-2048, 4, 135, 213, 273, 323, 373, 425, 425, 373, 323, 273, 213, 135, 4, -2048}; /* Maps G.721 code word to log of scale factor multiplier. */ static short _witab[16] = {-12, 18, 41, 64, 112, 198, 355, 1122, 1122, 355, 198, 112, 64, 41, 18, -12}; /* * Maps G.721 code words to a set of values whose long and short * term averages are computed and then compared to give an indication * how stationary (steady state) the signal is. */ static short _fitab[16] = {0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00, 0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0}; /* * g721_decoder() * * Description: * * Decodes a 4-bit code of G.721 encoded data of i and * returns the resulting linear PCM, A-law or u-law value. * return -1 for unknown out_coding value. */ int g721_decoder(int i, struct g72x_state *state_ptr) { short sezi, sei, sez, se; /* ACCUM */ short y; /* MIX */ short sr; /* ADDB */ short dq; short dqsez; i &= 0x0f; /* mask to get proper bits */ sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ y = step_size(state_ptr); /* dynamic quantizer step size */ dq = reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */ sr = (dq < 0) ? (se - (dq & 0x3FFF)) : se + dq; /* reconst. signal */ dqsez = sr - se + sez; /* pole prediction diff. */ update(y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr); return (sr << 2); /* sr was 14-bit dynamic range */ } ================================================ FILE: Runtime/Audio/g721.h ================================================ #ifndef _g721_h #define _g721_h #ifdef __cplusplus extern "C" { #endif struct g72x_state { long yl; /* Locked or steady state step size multiplier. */ short yu; /* Unlocked or non-steady state step size multiplier. */ short dms; /* Short term energy estimate. */ short dml; /* Long term energy estimate. */ short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ short a[2]; /* Coefficients of pole portion of prediction filter. */ short b[6]; /* Coefficients of zero portion of prediction filter. */ short pk[2]; /* * Signs of previous two samples of a partially * reconstructed signal. */ short dq[6]; /* * Previous 6 samples of the quantized difference * signal represented in an internal floating point * format. */ short sr[2]; /* * Previous 2 samples of the quantized difference * signal represented in an internal floating point * format. */ char td; /* delayed tone detect, new in 1988 version */ }; void g72x_init_state(struct g72x_state *state_ptr); int g721_decoder(int i, struct g72x_state *state_ptr); #ifdef __cplusplus } #endif #endif ================================================ FILE: Runtime/AutoMapper/CAutoMapper.cpp ================================================ #include "Runtime/AutoMapper/CAutoMapper.hpp" #include "Runtime/CInGameTweakManagerBase.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/AutoMapper/CMapArea.hpp" #include "Runtime/AutoMapper/CMapUniverse.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" #include "Runtime/Input/ControlMapper.hpp" #include "Runtime/MP1/MP1.hpp" #include "Runtime/Particle/CGenDescription.hpp" #include "Runtime/World/CPlayer.hpp" #include #include namespace metaforce { void CAutoMapper::SAutoMapperRenderState::InterpolateWithClamp(const SAutoMapperRenderState& a, SAutoMapperRenderState& out, const SAutoMapperRenderState& b, float t) { t = zeus::clamp(0.f, t, 1.f); const float easeIn = zeus::clamp(0.f, t * t * t, 1.f); const float omt = 1.f - t; const float easeOut = zeus::clamp(0.f, 1.f - omt * omt * omt, 1.f); float easeInOut; if (t >= 0.5f) { easeInOut = zeus::clamp(0.f, 0.5f * std::sqrt(2.f * t - 1.f) + 0.5f, 1.f); } else { easeInOut = zeus::clamp(0.f, 1.f - (0.5f * std::sqrt(2.f * omt - 1.f) + 0.5f), 1.f); } const std::array eases{ 0.0f, t, easeOut, easeIn, easeInOut, }; if (b.x44_viewportEase != Ease::None) { const float easeB = eases[size_t(b.x44_viewportEase)]; const float easeA = 1.f - easeB; const zeus::CVector2i vpA = a.GetViewportSize(); const zeus::CVector2i vpB = b.GetViewportSize(); out.x0_viewportSize = zeus::CVector2i(vpB.x * easeB + vpA.x * easeA, vpB.y * easeB + vpA.y * easeA); } if (t == 1.f) out.m_getViewportSize = b.m_getViewportSize; else out.m_getViewportSize = nullptr; if (b.x48_camEase != Ease::None) { const float easeB = eases[size_t(b.x48_camEase)]; const float easeA = 1.f - easeB; out.x8_camOrientation = zeus::CQuaternion::slerp(a.x8_camOrientation, b.x8_camOrientation, easeB); out.x18_camDist = b.x18_camDist * easeB + a.x18_camDist * easeA; out.x1c_camAngle = b.x1c_camAngle * easeB + a.x1c_camAngle * easeA; } if (b.x4c_pointEase != Ease::None) { const float easeB = eases[size_t(b.x4c_pointEase)]; const float easeA = 1.f - easeB; out.x20_areaPoint = b.x20_areaPoint * easeB + a.x20_areaPoint * easeA; } if (b.x50_depth1Ease != Ease::None) { const float easeB = eases[size_t(b.x50_depth1Ease)]; const float easeA = 1.f - easeB; out.x2c_drawDepth1 = b.x2c_drawDepth1 * easeB + a.x2c_drawDepth1 * easeA; } if (b.x54_depth2Ease != Ease::None) { const float easeB = eases[size_t(b.x54_depth2Ease)]; const float easeA = 1.f - easeB; out.x30_drawDepth2 = b.x30_drawDepth2 * easeB + a.x30_drawDepth2 * easeA; } if (b.x58_alphaEase != Ease::None) { const float easeB = eases[size_t(b.x58_alphaEase)]; const float easeA = 1.f - easeB; out.x34_alphaSurfaceVisited = b.x34_alphaSurfaceVisited * easeB + a.x34_alphaSurfaceVisited * easeA; out.x38_alphaOutlineVisited = b.x38_alphaOutlineVisited * easeB + a.x38_alphaOutlineVisited * easeA; out.x3c_alphaSurfaceUnvisited = b.x3c_alphaSurfaceUnvisited * easeB + a.x3c_alphaSurfaceUnvisited * easeA; out.x40_alphaOutlineUnvisited = b.x40_alphaOutlineUnvisited * easeB + a.x40_alphaOutlineUnvisited * easeA; } } CAutoMapper::CAutoMapper(CStateManager& stateMgr) : x24_world(stateMgr.GetWorld()) { x8_mapu = g_SimplePool->GetObj("MAPU_MapUniverse"); x30_miniMapSamus = g_SimplePool->GetObj("CMDL_MiniMapSamus"); x3c_hintBeacon = g_SimplePool->GetObj("TXTR_HintBeacon"); xa0_curAreaId = xa4_otherAreaId = stateMgr.GetWorld()->IGetCurrentAreaId(); zeus::CMatrix3f camRot = stateMgr.GetCameraManager()->GetCurrentCamera(stateMgr)->GetTransform().buildMatrix3f(); xa8_renderStates[0] = xa8_renderStates[1] = xa8_renderStates[2] = BuildMiniMapWorldRenderState(stateMgr, camRot, xa0_curAreaId); x48_mapIcons.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x4_saveStationIcon})); x48_mapIcons.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x8_missileStationIcon})); x48_mapIcons.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->xc_elevatorIcon})); x48_mapIcons.emplace_back( g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x10_minesBreakFirstTopIcon})); x48_mapIcons.emplace_back( g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x14_minesBreakFirstBottomIcon})); for (u32 i = 0; i < 9; ++i) { x210_lstick.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x24_lStick[i]})); x25c_cstick.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x4c_cStick[i]})); } for (u32 i = 0; i < 2; ++i) { x2a8_ltrigger.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x74_lTrigger[i]})); x2bc_rtrigger.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x80_rTrigger[i]})); x2d0_abutton.emplace_back(g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), g_tweakPlayerRes->x98_aButton[i]})); } } bool CAutoMapper::CheckLoadComplete() { switch (x4_loadPhase) { case ELoadPhase::LoadResources: for (TLockedToken& tex : x48_mapIcons) if (!tex.IsLoaded()) return false; if (!x30_miniMapSamus.IsLoaded()) return false; if (!x3c_hintBeacon.IsLoaded()) return false; x4_loadPhase = ELoadPhase::LoadUniverse; [[fallthrough]]; case ELoadPhase::LoadUniverse: if (!x8_mapu.IsLoaded()) return false; x14_dummyWorlds.resize(x8_mapu->GetNumMapWorldDatas()); SetCurWorldAssetId(x24_world->IGetWorldAssetId()); x4_loadPhase = ELoadPhase::Done; [[fallthrough]]; case ELoadPhase::Done: return true; default: break; } return false; } bool CAutoMapper::NotHintNavigating() const { return x1e0_hintSteps.empty(); } bool CAutoMapper::CanLeaveMapScreenInternal(const CStateManager& mgr) const { if (!NotHintNavigating()) return false; if (IsRenderStateInterpolating()) return false; if (IsInMapperState(EAutoMapperState::MapScreenUniverse)) return true; if (x24_world != mgr.GetWorld()) return false; if (IsInMapperState(EAutoMapperState::MapScreen)) return true; return false; } void CAutoMapper::LeaveMapScreen(CStateManager& mgr) { if (x1c0_nextState == EAutoMapperState::MapScreenUniverse) { xa8_renderStates[1].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth(); xa8_renderStates[1].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth(); xa8_renderStates[0].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth(); xa8_renderStates[0].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth(); SetupMiniMapWorld(mgr); } else { x328_ = 2; xa8_renderStates[1] = xa8_renderStates[0]; xa8_renderStates[2] = xa8_renderStates[1]; xa0_curAreaId = x24_world->IGetCurrentAreaId(); xa8_renderStates[1].x20_areaPoint = GetAreaPointOfInterest(mgr, xa0_curAreaId); xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::Linear; xa8_renderStates[1].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth(); xa8_renderStates[1].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth(); xa8_renderStates[1].x50_depth1Ease = SAutoMapperRenderState::Ease::Linear; xa8_renderStates[1].x54_depth2Ease = SAutoMapperRenderState::Ease::Linear; ResetInterpolationTimer(0.25f); } } void CAutoMapper::SetupMiniMapWorld(CStateManager& mgr) { CWorld& wld = *mgr.GetWorld(); wld.GetMapWorld()->SetWhichMapAreasLoaded(wld, wld.GetCurrentAreaId(), 3); x328_ = 3; } bool CAutoMapper::HasCurrentMapUniverseWorld() const { CAssetId mlvlId = x24_world->IGetWorldAssetId(); for (const CMapUniverse::CMapWorldData& wld : *x8_mapu) if (wld.GetWorldAssetId() == mlvlId) return true; return false; } bool CAutoMapper::CheckDummyWorldLoad(CStateManager& mgr) { const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx); auto& dummyWorld = x14_dummyWorlds[x9c_worldIdx]; if (!dummyWorld) { x32c_loadingDummyWorld = false; return false; } if (!dummyWorld->ICheckWorldComplete()) return true; CWorldState& worldState = g_GameState->StateForWorld(dummyWorld->IGetWorldAssetId()); CMapWorldInfo& mwInfo = *worldState.MapWorldInfo(); zeus::CVector3f localPoint = mapuWld.GetWorldTransform().inverse() * xa8_renderStates[0].x20_areaPoint; zeus::CMatrix3f camRot = xa8_renderStates[0].x8_camOrientation.toTransform().buildMatrix3f(); TAreaId aid = FindClosestVisibleArea(localPoint, zeus::CUnitVector3f(camRot[1]), mgr, *dummyWorld, mwInfo); if (aid == -1) { x32c_loadingDummyWorld = false; return false; } xa0_curAreaId = aid; dummyWorld->IGetMapWorld()->RecalculateWorldSphere(mwInfo, *dummyWorld); x24_world = dummyWorld.get(); BeginMapperStateTransition(EAutoMapperState::MapScreen, mgr); x32c_loadingDummyWorld = false; return true; } void CAutoMapper::UpdateHintNavigation(float dt, CStateManager& mgr) { SAutoMapperHintStep& nextStep = x1e0_hintSteps.front(); bool oldProcessing = nextStep.x8_processing; nextStep.x8_processing = true; switch (nextStep.x0_type) { case SAutoMapperHintStep::Type::PanToArea: { if (x24_world->IGetMapWorld()->GetMapArea(nextStep.x4_areaId)) { xa8_renderStates[2] = xa8_renderStates[0]; xa8_renderStates[1].x20_areaPoint = GetAreaPointOfInterest(mgr, nextStep.x4_areaId); xa8_renderStates[1].ResetInterpolation(); xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::Linear; ResetInterpolationTimer(2.f * g_tweakAutoMapper->GetHintPanTime()); x1e0_hintSteps.pop_front(); } break; } case SAutoMapperHintStep::Type::PanToWorld: { const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldDataByWorldId(nextStep.x4_worldId); xa8_renderStates[2] = xa8_renderStates[0]; xa8_renderStates[1].x20_areaPoint = mwData.GetWorldCenterPoint(); xa8_renderStates[1].ResetInterpolation(); xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::Linear; ResetInterpolationTimer(2.f * g_tweakAutoMapper->GetHintPanTime()); x1e0_hintSteps.pop_front(); break; } case SAutoMapperHintStep::Type::SwitchToUniverse: { if (HasCurrentMapUniverseWorld()) { BeginMapperStateTransition(EAutoMapperState::MapScreenUniverse, mgr); x1e0_hintSteps.pop_front(); } else { x1e0_hintSteps.clear(); } break; } case SAutoMapperHintStep::Type::SwitchToWorld: { x1e0_hintSteps.pop_front(); x32c_loadingDummyWorld = true; if (CheckDummyWorldLoad(mgr)) break; x1e0_hintSteps.clear(); break; } case SAutoMapperHintStep::Type::ShowBeacon: { if (!oldProcessing) { if (xa0_curAreaId == mgr.GetNextAreaId() && x24_world == mgr.GetWorld()) CSfxManager::SfxStart(SFXui_show_local_beacon, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); else CSfxManager::SfxStart(SFXui_show_remote_beacon, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } nextStep.x4_float = std::max(0.f, nextStep.x4_float - dt); for (SAutoMapperHintLocation& loc : x1f8_hintLocations) { if (x24_world->IGetWorldAssetId() == loc.x8_worldId && xa0_curAreaId == loc.xc_areaId) { loc.x0_showBeacon = 1; loc.x4_beaconAlpha = 1.f - std::min(nextStep.x4_float / 0.5f, 1.f); break; } } if (nextStep.x4_float != 0.f) break; x1e0_hintSteps.pop_front(); break; } case SAutoMapperHintStep::Type::ZoomOut: { xa8_renderStates[2] = xa8_renderStates[0]; xa8_renderStates[1].x18_camDist = g_tweakAutoMapper->GetMaxCamDist(); xa8_renderStates[1].ResetInterpolation(); xa8_renderStates[1].x48_camEase = SAutoMapperRenderState::Ease::Linear; ResetInterpolationTimer(0.5f); x1e0_hintSteps.pop_front(); break; } case SAutoMapperHintStep::Type::ZoomIn: { xa8_renderStates[2] = xa8_renderStates[0]; xa8_renderStates[1].x18_camDist = g_tweakAutoMapper->GetCamDist(); xa8_renderStates[1].ResetInterpolation(); xa8_renderStates[1].x48_camEase = SAutoMapperRenderState::Ease::Linear; ResetInterpolationTimer(0.5f); x1e0_hintSteps.pop_front(); break; } default: break; } } bool CAutoMapper::CanLeaveMapScreen(const CStateManager& mgr) const { return x328_ == 3 && CanLeaveMapScreenInternal(mgr); } void CAutoMapper::SetCurWorldAssetId(CAssetId mlvlId) { u32 numWorlds = x8_mapu->GetNumMapWorldDatas(); for (u32 i = 0; i < numWorlds; ++i) if (x8_mapu->GetMapWorldData(i).GetWorldAssetId() == mlvlId) { x9c_worldIdx = i; break; } } void CAutoMapper::BeginMapperStateTransition(EAutoMapperState state, CStateManager& mgr) { if (state == x1c0_nextState) return; if ((state == EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) || (state != EAutoMapperState::MiniMap && x1c0_nextState == EAutoMapperState::MiniMap)) CSfxManager::KillAll(CSfxManager::ESfxChannels::PauseScreen); x1bc_state = x1c0_nextState; x1c0_nextState = state; xa8_renderStates[2] = xa8_renderStates[0]; xa8_renderStates[1] = xa8_renderStates[0]; if (x1bc_state == EAutoMapperState::MiniMap && state == EAutoMapperState::MapScreen) { xa8_renderStates[1] = BuildMapScreenWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId, false); ResetInterpolationTimer(g_tweakAutoMapper->GetOpenMapScreenTime()); } else if (x1bc_state == EAutoMapperState::MapScreen && state == EAutoMapperState::MiniMap) { xa0_curAreaId = x24_world->IGetCurrentAreaId(); xa8_renderStates[1] = BuildMiniMapWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId); ResetInterpolationTimer(g_tweakAutoMapper->GetCloseMapScreenTime()); x1f8_hintLocations.clear(); } else if (x1bc_state == EAutoMapperState::MapScreen && state == EAutoMapperState::MapScreenUniverse) { CSfxManager::SfxStart(SFXui_map_to_universe, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); xa8_renderStates[1] = BuildMapScreenUniverseRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId); TransformRenderStatesWorldToUniverse(); ResetInterpolationTimer(g_tweakAutoMapper->GetSwitchToFromUniverseTime()); } else if (x1bc_state == EAutoMapperState::MapScreenUniverse && state == EAutoMapperState::MapScreen) { CSfxManager::SfxStart(SFXui_map_from_universe, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); xa8_renderStates[1] = BuildMapScreenWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId, x1e0_hintSteps.size()); TransformRenderStateWorldToUniverse(xa8_renderStates[1]); ResetInterpolationTimer(g_tweakAutoMapper->GetSwitchToFromUniverseTime()); for (auto& wld : x14_dummyWorlds) { if (wld.get() != x24_world || x24_world == mgr.GetWorld()) wld.reset(); } } else if (x1bc_state == EAutoMapperState::MapScreenUniverse && state == EAutoMapperState::MiniMap) { x24_world = mgr.GetWorld(); xa0_curAreaId = x24_world->IGetCurrentAreaId(); xa8_renderStates[1] = BuildMiniMapWorldRenderState(mgr, xa8_renderStates[0].x8_camOrientation, xa0_curAreaId); SetCurWorldAssetId(x24_world->IGetWorldAssetId()); TransformRenderStateWorldToUniverse(xa8_renderStates[1]); ResetInterpolationTimer(g_tweakAutoMapper->GetCloseMapScreenTime()); x1f8_hintLocations.clear(); for (auto& wld : x14_dummyWorlds) { if (wld.get() != x24_world || x24_world == mgr.GetWorld()) wld.reset(); } } } void CAutoMapper::CompleteMapperStateTransition(CStateManager& mgr) { if (x1bc_state == EAutoMapperState::MapScreenUniverse) TransformRenderStatesUniverseToWorld(); if (x1c0_nextState == EAutoMapperState::MapScreen) { const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); x24_world->IGetMapWorld()->RecalculateWorldSphere(mwInfo, *x24_world); x1d8_flashTimer = 0.f; x1dc_playerFlashPulse = 0.f; } if (x1c0_nextState == EAutoMapperState::MiniMap) { x28_frmeMapScreen = TLockedToken(); m_frmeInitialized = false; x2fc_textpane_hint = nullptr; x300_textpane_instructions = nullptr; x304_textpane_instructions1 = nullptr; x308_textpane_instructions2 = nullptr; x2f8_textpane_areaname = nullptr; x30c_basewidget_leftPane = nullptr; x310_basewidget_yButtonPane = nullptr; x314_basewidget_bottomPane = nullptr; SetResLockState(x210_lstick, false); SetResLockState(x25c_cstick, false); SetResLockState(x2a8_ltrigger, false); SetResLockState(x2bc_rtrigger, false); SetResLockState(x2d0_abutton, false); } if (x1c0_nextState == EAutoMapperState::MapScreenUniverse && x328_ == 1) LeaveMapScreen(mgr); x1bc_state = x1c0_nextState; } void CAutoMapper::ResetInterpolationTimer(float duration) { x1c4_interpDur = duration; x1c8_interpTime = 0.f; } CAutoMapper::SAutoMapperRenderState CAutoMapper::BuildMiniMapWorldRenderState(const CStateManager& stateMgr, const zeus::CQuaternion& rot, TAreaId area) const { zeus::CQuaternion camOrient = GetMiniMapCameraOrientation(stateMgr); zeus::CQuaternion useOrient = (camOrient.dot(rot) >= 0.f) ? camOrient : camOrient.buildEquivalent(); SAutoMapperRenderState ret( GetMiniMapViewportSize, useOrient, g_tweakAutoMapper->GetMiniCamDist(), g_tweakAutoMapper->GetMiniCamAngle(), GetAreaPointOfInterest(stateMgr, area), GetMapAreaMiniMapDrawDepth(), GetMapAreaMiniMapDrawDepth(), GetMapAreaMiniMapDrawAlphaSurfaceVisited(stateMgr), GetMapAreaMiniMapDrawAlphaOutlineVisited(stateMgr), GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(stateMgr), GetMapAreaMiniMapDrawAlphaOutlineUnvisited(stateMgr)); ret.x44_viewportEase = SAutoMapperRenderState::Ease::Out; ret.x48_camEase = SAutoMapperRenderState::Ease::Out; ret.x4c_pointEase = SAutoMapperRenderState::Ease::Out; ret.x50_depth1Ease = SAutoMapperRenderState::Ease::Linear; ret.x54_depth2Ease = SAutoMapperRenderState::Ease::In; ret.x58_alphaEase = SAutoMapperRenderState::Ease::Linear; return ret; } CAutoMapper::SAutoMapperRenderState CAutoMapper::BuildMapScreenWorldRenderState(const CStateManager& mgr, const zeus::CQuaternion& rot, TAreaId area, bool doingHint) const { float camDist = doingHint ? g_tweakAutoMapper->GetMaxCamDist() : g_tweakAutoMapper->GetCamDist(); SAutoMapperRenderState ret(GetMapScreenViewportSize, rot, camDist, g_tweakAutoMapper->GetCamAngle(), GetAreaPointOfInterest(mgr, area), GetMapAreaMaxDrawDepth(mgr, area), GetMapAreaMaxDrawDepth(mgr, area), g_tweakAutoMapper->GetAlphaSurfaceVisited(), g_tweakAutoMapper->GetAlphaOutlineVisited(), g_tweakAutoMapper->GetAlphaSurfaceUnvisited(), g_tweakAutoMapper->GetAlphaOutlineUnvisited()); ret.x44_viewportEase = SAutoMapperRenderState::Ease::Out; ret.x48_camEase = SAutoMapperRenderState::Ease::Linear; ret.x4c_pointEase = SAutoMapperRenderState::Ease::Out; ret.x50_depth1Ease = SAutoMapperRenderState::Ease::Linear; ret.x54_depth2Ease = SAutoMapperRenderState::Ease::Out; ret.x58_alphaEase = SAutoMapperRenderState::Ease::Linear; return ret; } CAutoMapper::SAutoMapperRenderState CAutoMapper::BuildMapScreenUniverseRenderState(const CStateManager& mgr, const zeus::CQuaternion& rot, TAreaId area) const { SAutoMapperRenderState ret(GetMapScreenViewportSize, rot, g_tweakAutoMapper->GetUniverseCamDist(), g_tweakAutoMapper->GetCamAngle(), GetAreaPointOfInterest(mgr, area), GetMapAreaMaxDrawDepth(mgr, area), GetMapAreaMaxDrawDepth(mgr, area), 0.f, 0.f, 0.f, 0.f); ret.x44_viewportEase = SAutoMapperRenderState::Ease::Out; ret.x48_camEase = SAutoMapperRenderState::Ease::Linear; ret.x4c_pointEase = SAutoMapperRenderState::Ease::Out; ret.x50_depth1Ease = SAutoMapperRenderState::Ease::Linear; ret.x54_depth2Ease = SAutoMapperRenderState::Ease::Out; ret.x58_alphaEase = SAutoMapperRenderState::Ease::Linear; return ret; } void CAutoMapper::LeaveMapScreenState() { SetShouldPanningSoundBePlaying(false); SetShouldZoomingSoundBePlaying(false); SetShouldRotatingSoundBePlaying(false); } float CAutoMapper::GetBaseMapScreenCameraMoveSpeed() { return g_tweakAutoMapper->GetBaseMapScreenCameraMoveSpeed(); } float CAutoMapper::GetFinalMapScreenCameraMoveSpeed() const { float ret = GetBaseMapScreenCameraMoveSpeed(); if (g_tweakAutoMapper->GetScaleMoveSpeedWithCamDist()) ret = ret * xa8_renderStates[0].x18_camDist / g_tweakAutoMapper->GetCamDist(); return ret; } void CAutoMapper::ProcessMapRotateInput(const CFinalInput& input, const CStateManager& mgr) { const float up = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleUp, input); const float down = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleDown, input); const float left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleLeft, input); const float right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapCircleRight, input); std::array dirs{}; bool mouseHeld = false; if (const auto& kbm = input.GetKBM()) { if (kbm->m_mouseButtons[size_t(EMouseButton::Primary)]) { mouseHeld = true; if (float(m_mouseDelta.x()) < 0.f) dirs[3] = -m_mouseDelta.x(); else if (float(m_mouseDelta.x()) > 0.f) dirs[2] = m_mouseDelta.x(); if (float(m_mouseDelta.y()) < 0.f) dirs[0] = -m_mouseDelta.y(); else if (float(m_mouseDelta.y()) > 0.f) dirs[1] = m_mouseDelta.y(); } } float maxMag = up; size_t dirSlot = 0; if (down > up) { maxMag = down; dirSlot = 1; } if (left > maxMag) { maxMag = left; dirSlot = 2; } if (right > maxMag) { maxMag = right; dirSlot = 3; } dirs[dirSlot] += maxMag; if (dirs[0] > 0.f || dirs[1] > 0.f || dirs[2] > 0.f || dirs[3] > 0.f || mouseHeld) { int flags = 0x0; if (up > 0.f) flags |= 0x2; if (down > 0.f) flags |= 0x1; if (left > 0.f) flags |= 0x4; if (right > 0.f) flags |= 0x8; switch (flags) { case 1: // Down x2e4_lStickPos = 1; break; case 2: // Up x2e4_lStickPos = 5; break; case 4: // Left x2e4_lStickPos = 3; break; case 5: // Down-Left x2e4_lStickPos = 2; break; case 6: // Up-Left x2e4_lStickPos = 4; break; case 8: // Right x2e4_lStickPos = 7; break; case 9: // Down-Right x2e4_lStickPos = 8; break; case 10: // Up-Right x2e4_lStickPos = 6; break; default: break; } float deltaFrames = input.DeltaTime() * 60.f; SetShouldRotatingSoundBePlaying(dirs[0] > 0.f || dirs[1] > 0.f || dirs[2] > 0.f || dirs[3] > 0.f); zeus::CEulerAngles eulers(xa8_renderStates[0].x8_camOrientation); zeus::CRelAngle angX(eulers.x()); angX.makeRel(); zeus::CRelAngle angZ(eulers.z()); angZ.makeRel(); float dt = deltaFrames * g_tweakAutoMapper->GetCamRotateDegreesPerFrame(); angZ -= zeus::degToRad(dt * dirs[2]); angZ.makeRel(); angZ += zeus::degToRad(dt * dirs[3]); angZ.makeRel(); angX -= zeus::degToRad(dt * dirs[0]); angX.makeRel(); angX += zeus::degToRad(dt * dirs[1]); angX.makeRel(); float angXDeg = angX.asDegrees(); if (angXDeg > 180.f) angXDeg -= 360.f; angX = zeus::degToRad( zeus::clamp(g_tweakAutoMapper->GetMinCamRotateX(), angXDeg, g_tweakAutoMapper->GetMaxCamRotateX())); angX.makeRel(); zeus::CQuaternion quat; quat.rotateZ(angZ); quat.rotateX(angX); quat.rotateY(0.f); xa8_renderStates[0].x8_camOrientation = quat; } else { x2e4_lStickPos = 0; SetShouldRotatingSoundBePlaying(false); } } void CAutoMapper::ProcessMapZoomInput(const CFinalInput& input, const CStateManager& mgr) { bool in = ControlMapper::GetDigitalInput(ControlMapper::ECommands::MapZoomIn, input); bool out = ControlMapper::GetDigitalInput(ControlMapper::ECommands::MapZoomOut, input); float zoomSpeed = 1.f; if (const auto& kbm = input.GetKBM()) { m_mapScroll += kbm->m_accumScroll - m_lastAccumScroll; m_lastAccumScroll = kbm->m_accumScroll; if (m_mapScroll.delta[1] > 0.0) { in = true; zoomSpeed = std::max(1.f, float(m_mapScroll.delta[1])); m_mapScroll.delta[1] = std::max(0.0, m_mapScroll.delta[1] - (15.0 / 60.0)); } else if (m_mapScroll.delta[1] < 0.0) { out = true; zoomSpeed = std::max(1.f, float(-m_mapScroll.delta[1])); m_mapScroll.delta[1] = std::min(0.0, m_mapScroll.delta[1] + (15.0 / 60.0)); } } const EZoomState nextZoomState = [this, in, out] { switch (x324_zoomState) { case EZoomState::None: case EZoomState::In: case EZoomState::Out: if (in) { return EZoomState::In; } if (out) { return EZoomState::Out; } return EZoomState::None; default: return EZoomState::None; } }(); x324_zoomState = nextZoomState; float delta = input.DeltaTime() * 60.f * (x1bc_state == EAutoMapperState::MapScreen ? 1.f : 4.f) * g_tweakAutoMapper->GetCamZoomUnitsPerFrame() * zoomSpeed; float oldDist = xa8_renderStates[0].x18_camDist; if (x324_zoomState == EZoomState::In) { xa8_renderStates[0].x18_camDist = GetClampedMapScreenCameraDistance(xa8_renderStates[0].x18_camDist - delta); x2f0_rTriggerPos = 1; x324_zoomState = EZoomState::In; } else if (x324_zoomState == EZoomState::Out) { xa8_renderStates[0].x18_camDist = GetClampedMapScreenCameraDistance(xa8_renderStates[0].x18_camDist + delta); x2ec_lTriggerPos = 1; x324_zoomState = EZoomState::Out; } if (oldDist == xa8_renderStates[0].x18_camDist) m_mapScroll.delta[1] = 0.0; SetShouldZoomingSoundBePlaying(oldDist != xa8_renderStates[0].x18_camDist); } void CAutoMapper::ProcessMapPanInput(const CFinalInput& input, const CStateManager& mgr) { float forward = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveForward, input); float back = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveBack, input); float left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveLeft, input); float right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::MapMoveRight, input); bool mouseHeld = false; if (const auto& kbm = input.GetKBM()) { if (kbm->m_mouseButtons[size_t(EMouseButton::Middle)] || kbm->m_mouseButtons[size_t(EMouseButton::Secondary)]) { mouseHeld = true; if (float(m_mouseDelta.x()) < 0.f) right += -m_mouseDelta.x(); else if (float(m_mouseDelta.x()) > 0.f) left += m_mouseDelta.x(); if (float(m_mouseDelta.y()) < 0.f) forward += -m_mouseDelta.y(); else if (float(m_mouseDelta.y()) > 0.f) back += m_mouseDelta.y(); } } zeus::CTransform camRot = xa8_renderStates[0].x8_camOrientation.toTransform(); if (forward > 0.f || back > 0.f || left > 0.f || right > 0.f || mouseHeld) { float deltaFrames = 60.f * input.DeltaTime(); float speed = GetFinalMapScreenCameraMoveSpeed(); int flags = 0x0; if (forward > 0.f) flags |= 0x1; if (back > 0.f) flags |= 0x2; if (left > 0.f) flags |= 0x4; if (right > 0.f) flags |= 0x8; switch (flags) { case 1: // Forward x2e8_rStickPos = 1; break; case 2: // Back x2e8_rStickPos = 5; break; case 4: // Left x2e8_rStickPos = 3; break; case 5: // Forward-Left x2e8_rStickPos = 2; break; case 6: // Back-Left x2e8_rStickPos = 4; break; case 8: // Right x2e8_rStickPos = 7; break; case 9: // Forward-Right x2e8_rStickPos = 8; break; case 10: // Back-Right x2e8_rStickPos = 6; break; default: break; } zeus::CVector3f dirVec(right - left, 0.f, forward - back); zeus::CVector3f deltaVec = camRot * (dirVec * deltaFrames * speed); zeus::CVector3f newPoint = xa8_renderStates[0].x20_areaPoint + deltaVec; SetShouldPanningSoundBePlaying(deltaVec.magnitude() > input.DeltaTime()); if (x1bc_state == EAutoMapperState::MapScreen) { xa8_renderStates[0].x20_areaPoint = x24_world->IGetMapWorld()->ConstrainToWorldVolume(newPoint, camRot.basis[1]); } else { zeus::CVector3f localPoint = newPoint - x8_mapu->GetMapUniverseCenterPoint(); if (localPoint.magnitude() > x8_mapu->GetMapUniverseRadius()) newPoint = x8_mapu->GetMapUniverseCenterPoint() + localPoint.normalized() * x8_mapu->GetMapUniverseRadius(); xa8_renderStates[0].x20_areaPoint = newPoint; } } else { x2e8_rStickPos = 0; SetShouldPanningSoundBePlaying(false); float speed = g_tweakAutoMapper->GetCamPanUnitsPerFrame() * GetBaseMapScreenCameraMoveSpeed(); if (x1bc_state == EAutoMapperState::MapScreen) { const CMapArea* area = x24_world->IGetMapWorld()->GetMapArea(xa0_curAreaId); zeus::CVector3f worldPoint = area->GetAreaPostTransform(*x24_world, xa0_curAreaId) * area->GetAreaCenterPoint(); zeus::CVector3f viewPoint = worldPoint - xa8_renderStates[0].x20_areaPoint; if (viewPoint.magnitude() < speed) xa8_renderStates[0].x20_areaPoint = worldPoint; else xa8_renderStates[0].x20_areaPoint += viewPoint.normalized() * speed; } else { std::pair areas = FindClosestVisibleWorld(xa8_renderStates[0].x20_areaPoint, camRot.basis[1], mgr); const zeus::CTransform& hex = x8_mapu->GetMapWorldData(areas.first).GetMapAreaData(areas.second); zeus::CVector3f areaToHex = hex.origin - xa8_renderStates[0].x20_areaPoint; if (areaToHex.magnitude() < speed) xa8_renderStates[0].x20_areaPoint = hex.origin; else xa8_renderStates[0].x20_areaPoint += areaToHex.normalized() * speed; } } } void CAutoMapper::SetShouldPanningSoundBePlaying(bool shouldBePlaying) { if (shouldBePlaying) { if (!x1cc_panningSfx) x1cc_panningSfx = CSfxManager::SfxStart(SFXui_map_pan, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId); } else { CSfxManager::SfxStop(x1cc_panningSfx); x1cc_panningSfx.reset(); } } void CAutoMapper::SetShouldZoomingSoundBePlaying(bool shouldBePlaying) { if (shouldBePlaying) { if (!x1d4_zoomingSfx) x1d4_zoomingSfx = CSfxManager::SfxStart(SFXui_map_zoom, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId); } else { CSfxManager::SfxStop(x1d4_zoomingSfx); x1d4_zoomingSfx.reset(); } } void CAutoMapper::SetShouldRotatingSoundBePlaying(bool shouldBePlaying) { if (shouldBePlaying) { if (!x1d0_rotatingSfx) x1d0_rotatingSfx = CSfxManager::SfxStart(SFXui_map_rotate, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId); } else { CSfxManager::SfxStop(x1d0_rotatingSfx); x1d0_rotatingSfx.reset(); } } void CAutoMapper::ProcessMapScreenInput(const CFinalInput& input, CStateManager& mgr) { zeus::CMatrix3f camRot = xa8_renderStates[0].x8_camOrientation.toTransform().buildMatrix3f(); if (x1bc_state == EAutoMapperState::MapScreen) { if ((input.PA() || input.PSpecialKey(ESpecialKey::Enter)) && x328_ == 0 && HasCurrentMapUniverseWorld()) BeginMapperStateTransition(EAutoMapperState::MapScreenUniverse, mgr); } else if (x1bc_state == EAutoMapperState::MapScreenUniverse && (input.PA() || input.PSpecialKey(ESpecialKey::Enter))) { const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx); zeus::CVector3f pointLocal = mapuWld.GetWorldTransform().inverse() * xa8_renderStates[0].x20_areaPoint; if (mapuWld.GetWorldAssetId() != g_GameState->CurrentWorldAssetId()) { x32c_loadingDummyWorld = true; CheckDummyWorldLoad(mgr); } else { x24_world = mgr.GetWorld(); CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); xa0_curAreaId = FindClosestVisibleArea(pointLocal, zeus::CUnitVector3f(camRot[1]), mgr, *x24_world, mwInfo); BeginMapperStateTransition(EAutoMapperState::MapScreen, mgr); } } x2f4_aButtonPos = 0; if (input.PA() || input.PSpecialKey(ESpecialKey::Enter)) x2f4_aButtonPos = 1; if (IsInPlayerControlState()) { x2ec_lTriggerPos = 0; x2f0_rTriggerPos = 0; if (const auto& kbm = input.GetKBM()) { zeus::CVector2f mouseCoord = zeus::CVector2f(kbm->m_mouseCoord.norm[0], kbm->m_mouseCoord.norm[1]); if (!m_lastMouseCoord) { m_lastMouseCoord.emplace(mouseCoord); } else { m_mouseDelta = mouseCoord - *m_lastMouseCoord; m_lastMouseCoord.emplace(mouseCoord); m_mouseDelta.x() *= CGraphics::GetViewportAspect(); m_mouseDelta *= 100.f; } } ProcessMapRotateInput(input, mgr); ProcessMapZoomInput(input, mgr); ProcessMapPanInput(input, mgr); } } zeus::CQuaternion CAutoMapper::GetMiniMapCameraOrientation(const CStateManager& stateMgr) const { const CGameCamera* cam = stateMgr.GetCameraManager()->GetCurrentCamera(stateMgr); zeus::CEulerAngles camAngles(zeus::CQuaternion(cam->GetTransform().buildMatrix3f())); zeus::CRelAngle angle(camAngles.z()); angle.makeRel(); zeus::CQuaternion ret; ret.rotateZ(angle); ret.rotateX(zeus::degToRad(g_tweakAutoMapper->GetMiniCamXAngle())); return ret; } zeus::CVector3f CAutoMapper::GetAreaPointOfInterest(const CStateManager&, TAreaId aid) const { const CMapArea* mapa = x24_world->IGetMapWorld()->GetMapArea(aid); return mapa->GetAreaPostTransform(*x24_world, aid) * mapa->GetAreaCenterPoint(); } TAreaId CAutoMapper::FindClosestVisibleArea(const zeus::CVector3f& point, const zeus::CUnitVector3f& camDir, const CStateManager& mgr, const IWorld& wld, const CMapWorldInfo& mwInfo) const { float minDist = 9999.f; TAreaId closestArea = xa0_curAreaId; const CMapWorld* mw = wld.IGetMapWorld(); std::vector areas = mw->GetVisibleAreas(wld, mwInfo); for (TAreaId areaId : areas) { const CMapArea* mapa = mw->GetMapArea(areaId); zeus::CVector3f xfPoint = mapa->GetAreaPostTransform(wld, areaId) * mapa->GetAreaCenterPoint(); zeus::CVector3f pointToArea = xfPoint - point; pointToArea = pointToArea.canBeNormalized() ? point + (pointToArea.normalized().dot(camDir) * pointToArea.magnitude()) * camDir : point; pointToArea -= xfPoint; float dist = pointToArea.magnitude(); if (dist < minDist) { minDist = dist; closestArea = areaId; } } return closestArea; } std::pair CAutoMapper::FindClosestVisibleWorld(const zeus::CVector3f& point, const zeus::CUnitVector3f& camDir, const CStateManager& mgr) const { float minDist = 29999.f; std::pair closestWorld = {x9c_worldIdx, xa0_curAreaId}; for (u32 w = 0; w < x8_mapu->GetNumMapWorldDatas(); ++w) { const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldData(w); const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(mwData.GetWorldAssetId()).MapWorldInfo(); if (!mwInfo.IsAnythingSet()) continue; for (u32 i = 0; i < mwData.GetNumMapAreaDatas(); ++i) { const zeus::CVector3f& mwOrigin = mwData.GetMapAreaData(i).origin; zeus::CVector3f pointToArea = mwOrigin - point; pointToArea = pointToArea.canBeNormalized() ? point + (pointToArea.normalized().dot(camDir) * pointToArea.magnitude()) * camDir : point; pointToArea -= mwOrigin; float dist = pointToArea.magnitude(); if (dist < minDist) { minDist = dist; closestWorld.first = w; closestWorld.second = i; } } } return closestWorld; } zeus::CVector2i CAutoMapper::GetMiniMapViewportSize() { float scaleX = CGraphics::GetViewportWidth() / 640.f; float scaleY = CGraphics::GetViewportHeight() / 480.f; return {int(scaleX * g_tweakAutoMapper->GetMiniMapViewportWidth()), int(scaleY * g_tweakAutoMapper->GetMiniMapViewportHeight())}; } zeus::CVector2i CAutoMapper::GetMapScreenViewportSize() { return {int(CGraphics::GetViewportWidth()), int(CGraphics::GetViewportHeight())}; } float CAutoMapper::GetMapAreaMaxDrawDepth(const CStateManager&, TAreaId aid) const { return x24_world->IGetMapWorld()->GetCurrentMapAreaDepth(*x24_world, aid); } float CAutoMapper::GetMapAreaMiniMapDrawAlphaSurfaceVisited(const CStateManager& stateMgr) { float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant(); return g_tweakAutoMapper->GetMiniAlphaSurfaceVisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() + mapAlphaInterp; } float CAutoMapper::GetMapAreaMiniMapDrawAlphaOutlineVisited(const CStateManager& stateMgr) { float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant(); return g_tweakAutoMapper->GetMiniAlphaOutlineVisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() + mapAlphaInterp; } float CAutoMapper::GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(const CStateManager& stateMgr) { float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant(); return g_tweakAutoMapper->GetMiniAlphaSurfaceUnvisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() + mapAlphaInterp; } float CAutoMapper::GetMapAreaMiniMapDrawAlphaOutlineUnvisited(const CStateManager& stateMgr) { float mapAlphaInterp = g_tweakGui->GetMapAlphaInterpolant(); return g_tweakAutoMapper->GetMiniAlphaOutlineUnvisited() * (1.f - mapAlphaInterp) * stateMgr.Player()->GetGunAlpha() + mapAlphaInterp; } float CAutoMapper::GetDesiredMiniMapCameraDistance(const CStateManager& mgr) const { const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); const CMapWorld* mw = x24_world->IGetMapWorld(); zeus::CAABox aabb; const IGameArea* area = x24_world->IGetAreaAlways(xa0_curAreaId); const CMapArea* mapa = mw->GetMapArea(xa0_curAreaId); bool oneMiniMapArea = g_tweakAutoMapper->GetShowOneMiniMapArea(); for (int i = -1; i < (oneMiniMapArea ? 0 : int(area->IGetNumAttachedAreas())); ++i) { TAreaId aid = i == -1 ? xa0_curAreaId : area->IGetAttachedAreaId(i); const CMapArea* attMapa = mw->GetMapArea(aid); if (attMapa->GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(aid), mwInfo.IsAreaVisible(aid))) { zeus::CAABox areaAABB = attMapa->GetBoundingBox().getTransformedAABox(attMapa->GetAreaPostTransform(*x24_world, aid)); aabb.accumulateBounds(areaAABB.min); aabb.accumulateBounds(areaAABB.max); } } zeus::CVector3f xfPoint = mapa->GetAreaPostTransform(*x24_world, xa0_curAreaId) * mapa->GetAreaCenterPoint(); zeus::CVector3f maxMargin; maxMargin.x() = std::max(xfPoint.x() - aabb.min.x(), aabb.max.x() - xfPoint.x()); maxMargin.y() = std::max(xfPoint.y() - aabb.min.y(), aabb.max.y() - xfPoint.y()); maxMargin.z() = std::max(xfPoint.z() - aabb.min.z(), aabb.max.z() - xfPoint.z()); zeus::CVector3f extent = mapa->GetBoundingBox().max - mapa->GetBoundingBox().min; return (0.5f * (0.5f * extent.magnitude()) + 0.5f * maxMargin.magnitude()) * g_tweakAutoMapper->GetMiniMapCamDistScale() * std::tan(M_PIF / 2.f - 0.5f * 2.f * M_PIF * (xa8_renderStates[0].x1c_camAngle / 360.f)); } float CAutoMapper::GetClampedMapScreenCameraDistance(float value) const { if (x1bc_state == EAutoMapperState::MapScreenUniverse) { return zeus::clamp(g_tweakAutoMapper->GetMinUniverseCamDist(), value, g_tweakAutoMapper->GetMaxUniverseCamDist()); } return zeus::clamp(g_tweakAutoMapper->GetMinCamDist(), value, g_tweakAutoMapper->GetMaxCamDist()); } void CAutoMapper::MuteAllLoopedSounds() { CSfxManager::SfxVolume(x1cc_panningSfx, 0.f); CSfxManager::SfxVolume(x1d0_rotatingSfx, 0.f); CSfxManager::SfxVolume(x1d4_zoomingSfx, 0.f); } void CAutoMapper::UnmuteAllLoopedSounds() { CSfxManager::SfxVolume(x1cc_panningSfx, 1.f); CSfxManager::SfxVolume(x1d0_rotatingSfx, 1.f); CSfxManager::SfxVolume(x1d4_zoomingSfx, 1.f); } void CAutoMapper::ProcessControllerInput(const CFinalInput& input, CStateManager& mgr) { if (!IsRenderStateInterpolating()) { if (IsInPlayerControlState()) { if (x32c_loadingDummyWorld) CheckDummyWorldLoad(mgr); else if (x1e0_hintSteps.size()) UpdateHintNavigation(input.DeltaTime(), mgr); else if (x328_ == 0) ProcessMapScreenInput(input, mgr); } } zeus::CMatrix3f camRot = xa8_renderStates[0].x8_camOrientation.toTransform().buildMatrix3f(); if (IsInMapperState(EAutoMapperState::MapScreen)) { CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); TAreaId aid = FindClosestVisibleArea(xa8_renderStates[0].x20_areaPoint, camRot[1], mgr, *x24_world, mwInfo); if (aid != xa0_curAreaId) { xa0_curAreaId = aid; xa8_renderStates[0].x2c_drawDepth1 = GetMapAreaMaxDrawDepth(mgr, xa0_curAreaId); xa8_renderStates[0].x30_drawDepth2 = GetMapAreaMaxDrawDepth(mgr, xa0_curAreaId); } } else if (IsInMapperState(EAutoMapperState::MapScreenUniverse)) { u32 oldWldIdx = x9c_worldIdx; if (x1e0_hintSteps.size()) { SAutoMapperHintStep& nextStep = x1e0_hintSteps.front(); if (nextStep.x0_type == SAutoMapperHintStep::Type::PanToWorld || nextStep.x0_type == SAutoMapperHintStep::Type::SwitchToWorld) { SetCurWorldAssetId(nextStep.x4_worldId); } else { std::pair wld = FindClosestVisibleWorld(xa8_renderStates[0].x20_areaPoint, camRot[1], mgr); x9c_worldIdx = wld.first; } } else { std::pair wld = FindClosestVisibleWorld(xa8_renderStates[0].x20_areaPoint, camRot[1], mgr); x9c_worldIdx = wld.first; } if (x9c_worldIdx != oldWldIdx) { CAssetId curMlvl = g_GameState->CurrentWorldAssetId(); for (u32 i = 0; i < x14_dummyWorlds.size(); ++i) { auto& wld = x14_dummyWorlds[i]; const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldData(i); if (i == x9c_worldIdx && curMlvl != mwData.GetWorldAssetId()) { if (g_ResFactory->CanBuild(SObjectTag{FOURCC('MLVL'), mwData.GetWorldAssetId()})) wld = std::make_unique(mwData.GetWorldAssetId(), true); } else { wld.reset(); } } x24_world = (curMlvl == x8_mapu->GetMapWorldData(x9c_worldIdx).GetWorldAssetId()) ? mgr.GetWorld() : nullptr; } } if (x300_textpane_instructions) { if (x78_areaHintDesc.IsLoaded()) { x2fc_textpane_hint->TextSupport().SetText(x78_areaHintDesc->GetString(0)); x304_textpane_instructions1->TextSupport().SetText(u""); x300_textpane_instructions->TextSupport().SetText(u""); x308_textpane_instructions2->TextSupport().SetText(u""); } else { x2fc_textpane_hint->TextSupport().SetText(u""); std::u16string str = fmt::format(u"&image=SI,0.6,1.0,{};", g_tweakPlayerRes->x24_lStick[x2e4_lStickPos]); str += g_MainStringTable->GetString(46 + (!g_Main->IsUSA() || g_Main->IsTrilogy())); // Rotate x300_textpane_instructions->TextSupport().SetText(str); str = fmt::format(u"&image=SI,0.6,1.0,{};", g_tweakPlayerRes->x4c_cStick[x2e8_rStickPos]); str += g_MainStringTable->GetString(47 + (!g_Main->IsUSA() || g_Main->IsTrilogy())); // Move x304_textpane_instructions1->TextSupport().SetText(str); str = fmt::format(u"&image={};", g_tweakPlayerRes->x74_lTrigger[x2ec_lTriggerPos]); str += g_MainStringTable->GetString(48 + (!g_Main->IsUSA() || g_Main->IsTrilogy())); // Zoom str += fmt::format(u"&image={};", g_tweakPlayerRes->x80_rTrigger[x2f0_rTriggerPos]); x308_textpane_instructions2->TextSupport().SetText(str); } } if (input.PY() || input.PKey(' ')) { CPersistentOptions& sysOpts = g_GameState->SystemOptions(); switch (sysOpts.GetAutoMapperKeyState()) { case 0: sysOpts.SetAutoMapperKeyState(1); CSfxManager::SfxStart(SFXui_map_screen_key1, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); break; case 1: sysOpts.SetAutoMapperKeyState(2); CSfxManager::SfxStart(SFXui_map_screen_key2, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); break; case 2: sysOpts.SetAutoMapperKeyState(0); CSfxManager::SfxStart(SFXui_map_screen_key0, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); break; default: break; } } if (input.PZ() || input.PSpecialKey(ESpecialKey::Tab) || input.PB() || input.PSpecialKey(ESpecialKey::Esc)) { if (x328_ == 0) { if (CanLeaveMapScreenInternal(mgr)) { LeaveMapScreen(mgr); } else if (NotHintNavigating()) { BeginMapperStateTransition(EAutoMapperState::MapScreenUniverse, mgr); x328_ = 1; } } } } void CAutoMapper::Update(float dt, CStateManager& mgr) { if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) { x1d8_flashTimer = std::fmod(x1d8_flashTimer + dt, 0.75f); x1dc_playerFlashPulse = x1d8_flashTimer < 0.375f ? x1d8_flashTimer / 0.375f : (0.75f - x1d8_flashTimer) / 0.375f; } if (!m_frmeInitialized && x28_frmeMapScreen.IsLoaded()) { x28_frmeMapScreen->SetMaxAspect(1.78f); m_frmeInitialized = true; static_cast(x28_frmeMapScreen->FindWidget("textpane_left")) ->TextSupport() .SetText(g_MainStringTable->GetString(42 + (!g_Main->IsUSA() || g_Main->IsTrilogy()))); static_cast(x28_frmeMapScreen->FindWidget("textpane_yicon")) ->TextSupport() .SetText(g_MainStringTable->GetString(43 + (!g_Main->IsUSA() || g_Main->IsTrilogy()))); x2fc_textpane_hint = static_cast(x28_frmeMapScreen->FindWidget("textpane_hint")); x300_textpane_instructions = static_cast(x28_frmeMapScreen->FindWidget("textpane_instructions")); x304_textpane_instructions1 = static_cast(x28_frmeMapScreen->FindWidget("textpane_instructions1")); x308_textpane_instructions2 = static_cast(x28_frmeMapScreen->FindWidget("textpane_instructions2")); CGuiTextPane* mapLegend = static_cast(x28_frmeMapScreen->FindWidget("textpane_mapLegend")); mapLegend->TextSupport().ClearRenderBuffer(); mapLegend->TextSupport().SetImageBaseline(true); mapLegend->TextSupport().SetText(g_MainStringTable->GetString(49 + (!g_Main->IsUSA() || g_Main->IsTrilogy()))); x30c_basewidget_leftPane = x28_frmeMapScreen->FindWidget("basewidget_leftPane"); x310_basewidget_yButtonPane = x28_frmeMapScreen->FindWidget("basewidget_yButtonPane"); x314_basewidget_bottomPane = x28_frmeMapScreen->FindWidget("basewidget_bottomPane"); x2f8_textpane_areaname = static_cast(x28_frmeMapScreen->FindWidget("textpane_areaname")); x2f8_textpane_areaname->SetDepthTest(false); } if (m_frmeInitialized) { x28_frmeMapScreen->Update(dt); CGuiTextPane* right1 = static_cast(x28_frmeMapScreen->FindWidget("textpane_right1")); std::u16string string; if (x1bc_state == EAutoMapperState::MapScreenUniverse || (x1bc_state == EAutoMapperState::MapScreen && HasCurrentMapUniverseWorld())) string = fmt::format(u"&image={};", g_tweakPlayerRes->x98_aButton[x2f4_aButtonPos]); right1->TextSupport().SetText(string); CGuiTextPane* right = static_cast(x28_frmeMapScreen->FindWidget("textpane_right")); if (x1bc_state == EAutoMapperState::MapScreenUniverse) string = g_MainStringTable->GetString(45); else if (x1bc_state == EAutoMapperState::MapScreen && HasCurrentMapUniverseWorld()) string = g_MainStringTable->GetString(44); else string = std::u16string(); right->TextSupport().SetText(string); } float dt2 = 2.f * dt; switch (g_GameState->SystemOptions().GetAutoMapperKeyState()) { case 0: // All shown x318_leftPanePos -= dt2; x31c_yButtonPanePos -= dt2; x320_bottomPanePos -= dt2; break; case 1: // Left shown x318_leftPanePos += dt2; x31c_yButtonPanePos -= dt2; x320_bottomPanePos -= dt2; break; case 2: // All hidden x318_leftPanePos += dt2; x31c_yButtonPanePos += dt2; x320_bottomPanePos += dt2; break; default: break; } x318_leftPanePos = std::max(0.f, std::min(x318_leftPanePos, 1.f)); x31c_yButtonPanePos = std::max(0.f, std::min(x31c_yButtonPanePos, 1.f)); x320_bottomPanePos = std::max(0.f, std::min(x320_bottomPanePos, 1.f)); if (x30c_basewidget_leftPane) { float vpAspectRatio = std::max(1.78f, CGraphics::GetViewportAspect()); x30c_basewidget_leftPane->SetLocalTransform( zeus::CTransform::Translate(x318_leftPanePos * vpAspectRatio * -9.f, 0.f, 0.f) * x30c_basewidget_leftPane->GetTransform()); } if (x310_basewidget_yButtonPane) { x310_basewidget_yButtonPane->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, x31c_yButtonPanePos * -3.5f) * x310_basewidget_yButtonPane->GetTransform()); } if (x314_basewidget_bottomPane) { x314_basewidget_bottomPane->SetLocalTransform(zeus::CTransform::Translate(0.f, 0.f, x320_bottomPanePos * -7.f) * x314_basewidget_bottomPane->GetTransform()); } if (IsInMapperState(EAutoMapperState::MiniMap)) { xa8_renderStates[0].x8_camOrientation = GetMiniMapCameraOrientation(mgr); float desiredDist = GetDesiredMiniMapCameraDistance(mgr); if (std::fabs(xa8_renderStates[0].x18_camDist - desiredDist) < 3.f) xa8_renderStates[0].x18_camDist = desiredDist; else if (xa8_renderStates[0].x18_camDist < desiredDist) xa8_renderStates[0].x18_camDist += 3.f; else xa8_renderStates[0].x18_camDist -= 3.f; TAreaId curAid = x24_world->IGetCurrentAreaId(); if (curAid != xa0_curAreaId) { xa8_renderStates[2] = xa8_renderStates[0]; xa8_renderStates[1] = xa8_renderStates[0]; xa4_otherAreaId = xa0_curAreaId; xa0_curAreaId = curAid; xa8_renderStates[1].x20_areaPoint = GetAreaPointOfInterest(mgr, xa0_curAreaId); xa8_renderStates[1].x44_viewportEase = SAutoMapperRenderState::Ease::None; xa8_renderStates[1].x48_camEase = SAutoMapperRenderState::Ease::None; xa8_renderStates[1].x4c_pointEase = SAutoMapperRenderState::Ease::InOut; xa8_renderStates[1].x50_depth1Ease = SAutoMapperRenderState::Ease::Linear; xa8_renderStates[1].x54_depth2Ease = SAutoMapperRenderState::Ease::Linear; xa8_renderStates[1].x58_alphaEase = SAutoMapperRenderState::Ease::None; xa8_renderStates[1].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth(); xa8_renderStates[1].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth(); xa8_renderStates[2].x2c_drawDepth1 = GetMapAreaMiniMapDrawDepth() - 1.f; xa8_renderStates[2].x30_drawDepth2 = GetMapAreaMiniMapDrawDepth() - 1.f; ResetInterpolationTimer(g_tweakAutoMapper->GetHintPanTime()); } xa8_renderStates[1].x34_alphaSurfaceVisited = GetMapAreaMiniMapDrawAlphaSurfaceVisited(mgr); xa8_renderStates[1].x38_alphaOutlineVisited = GetMapAreaMiniMapDrawAlphaOutlineVisited(mgr); xa8_renderStates[1].x3c_alphaSurfaceUnvisited = GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(mgr); xa8_renderStates[1].x40_alphaOutlineUnvisited = GetMapAreaMiniMapDrawAlphaOutlineUnvisited(mgr); } else { if (x1c0_nextState == EAutoMapperState::MiniMap) { float desiredDist = GetDesiredMiniMapCameraDistance(mgr); if (std::fabs(xa8_renderStates[1].x18_camDist - desiredDist) < 3.f) xa8_renderStates[0].x18_camDist = desiredDist; else if (xa8_renderStates[1].x18_camDist < desiredDist) xa8_renderStates[1].x18_camDist += 3.f; else xa8_renderStates[1].x18_camDist -= 3.f; } else if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap && x24_world) { x24_world->IGetMapWorld()->RecalculateWorldSphere( *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(), *x24_world); } } if (IsRenderStateInterpolating()) { x1c8_interpTime = std::min(x1c8_interpTime + dt, x1c4_interpDur); SAutoMapperRenderState::InterpolateWithClamp(xa8_renderStates[2], xa8_renderStates[0], xa8_renderStates[1], x1c8_interpTime / x1c4_interpDur); if (x1c8_interpTime == x1c4_interpDur && x328_ == 2) SetupMiniMapWorld(mgr); } else if (IsInMapperStateTransition()) { CompleteMapperStateTransition(mgr); } CAssetId stringId = x88_mapAreaStringId; if (IsInMapperState(EAutoMapperState::MapScreenUniverse)) { IWorld* wld = x14_dummyWorlds[x9c_worldIdx].get(); if (wld && wld->ICheckWorldComplete()) stringId = wld->IGetStringTableAssetId(); else if (x24_world) stringId = x24_world->IGetStringTableAssetId(); } else if (x24_world) { const IGameArea* area = x24_world->IGetAreaAlways(xa0_curAreaId); const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); if (mwInfo.IsMapped(xa0_curAreaId) || mwInfo.IsAreaVisited(xa0_curAreaId)) stringId = area->IGetStringTableAssetId(); else stringId = {}; } if (x88_mapAreaStringId != stringId) { x88_mapAreaStringId = stringId; if (x88_mapAreaStringId.IsValid()) x8c_mapAreaString = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), x88_mapAreaStringId}); else x8c_mapAreaString = TLockedToken(); } if (x2f8_textpane_areaname) { if (x8c_mapAreaString) { if (x8c_mapAreaString.IsLoaded()) x2f8_textpane_areaname->TextSupport().SetText(x8c_mapAreaString->GetString(0)); } else { x2f8_textpane_areaname->TextSupport().SetText(u""); } } if (IsInMapperState(EAutoMapperState::MapScreen)) { CAssetId hintDesc = GetAreaHintDescriptionString(x24_world->IGetAreaAlways(xa0_curAreaId)->IGetAreaAssetId()); if (hintDesc != x74_areaHintDescId) { x74_areaHintDescId = hintDesc; if (x74_areaHintDescId.IsValid()) x78_areaHintDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), x74_areaHintDescId}); else x78_areaHintDesc = TLockedToken(); } } for (auto& wld : x14_dummyWorlds) if (wld) wld->ICheckWorldComplete(); } void CAutoMapper::Draw(const CStateManager& mgr, const zeus::CTransform& xf, float alpha) { SCOPED_GRAPHICS_DEBUG_GROUP("CAutoMapper::Draw", zeus::skPurple); alpha *= g_GameState->GameOptions().GetHUDAlpha() / 255.f; g_Renderer->SetBlendMode_AlphaBlended(); CGraphics::SetCullMode(ERglCullMode::Front); float alphaInterp; if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) { alphaInterp = 1.f; } else if (IsInMapperState(EAutoMapperState::MiniMap)) { alphaInterp = alpha; } else if (x1c0_nextState == EAutoMapperState::MiniMap) { float t = GetInterp(); alphaInterp = alpha * t + (1.f - t); } else if (x1bc_state == EAutoMapperState::MiniMap) { float t = GetInterp(); alphaInterp = alpha * (1.f - t) + t; } else { alphaInterp = 1.f; } zeus::CVector2i vp = xa8_renderStates[0].GetViewportSize(); float aspect = vp.x / float(vp.y); if (aspect > 1.78f) aspect = 1.78f; float yScale = xa8_renderStates[0].x18_camDist / std::tan(M_PIF / 2.f - 0.5f * 2.f * M_PIF * (xa8_renderStates[0].x1c_camAngle / 360.f)); float xScale = yScale * aspect; zeus::CTransform camXf(xa8_renderStates[0].x8_camOrientation, xa8_renderStates[0].x20_areaPoint); zeus::CTransform distScale = zeus::CTransform::Scale(1.f / xScale, 0.001f, 1.f / yScale); zeus::CTransform tweakScale = zeus::CTransform::Scale(g_tweakAutoMapper->GetMapPlaneScaleX(), 0.f, g_tweakAutoMapper->GetMapPlaneScaleZ()); zeus::CTransform planeXf = xf * tweakScale * distScale * camXf.inverse(); float universeInterp = 0.f; if (x1c0_nextState == EAutoMapperState::MapScreenUniverse) { if (x1bc_state == EAutoMapperState::MapScreenUniverse) universeInterp = 1.f; else universeInterp = GetInterp(); } else if (x1bc_state == EAutoMapperState::MapScreenUniverse) { universeInterp = 1.f - GetInterp(); } zeus::CTransform preXf; if (x1bc_state == EAutoMapperState::MapScreenUniverse || x1c0_nextState == EAutoMapperState::MapScreenUniverse) preXf = x8_mapu->GetMapWorldData(x9c_worldIdx).GetWorldTransform(); float objectScale = xa8_renderStates[0].x18_camDist / g_tweakAutoMapper->GetMinCamDist(); float mapAlpha = alphaInterp * (1.f - universeInterp); if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) { if (universeInterp < 1.f && x24_world) { const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); CMapWorld* mw = x24_world->IGetMapWorld(); float hintFlash = 0.f; if (x1e0_hintSteps.size() && x1e0_hintSteps.front().x0_type == SAutoMapperHintStep::Type::ShowBeacon) { if (xa0_curAreaId == mgr.GetNextAreaId() && x24_world == mgr.GetWorld()) { float pulseTime = std::fmod(x1e0_hintSteps.front().x4_float * 8.f, 1.f); hintFlash = 2.f * (pulseTime < 0.5f ? pulseTime : 1.f - pulseTime); } else { for (const SAutoMapperHintLocation& loc : x1f8_hintLocations) { if (x24_world->IGetWorldAssetId() != loc.x8_worldId) continue; if (xa0_curAreaId != loc.xc_areaId) continue; float pulseTime = std::fmod((1.f - std::max(0.f, (x1e0_hintSteps.front().x4_float - 0.5f) / 0.5f)) * 4.f, 1.f); hintFlash = 2.f * (pulseTime < 0.5f ? pulseTime : 1.f - pulseTime); break; } } } const zeus::CTransform modelXf = planeXf * preXf; const CMapWorld::CMapWorldDrawParms parms(xa8_renderStates[0].x34_alphaSurfaceVisited * alphaInterp, xa8_renderStates[0].x38_alphaOutlineVisited * alphaInterp, xa8_renderStates[0].x3c_alphaSurfaceUnvisited * alphaInterp, xa8_renderStates[0].x40_alphaOutlineUnvisited * alphaInterp, mapAlpha, 2.f, mgr, modelXf, camXf, *x24_world, mwInfo, x1dc_playerFlashPulse, hintFlash, objectScale, true); mw->Draw(parms, xa0_curAreaId, xa0_curAreaId, xa8_renderStates[0].x2c_drawDepth1, xa8_renderStates[0].x30_drawDepth2, true); } } else if (IsInMapperState(EAutoMapperState::MiniMap)) { CMapWorld* mw = x24_world->IGetMapWorld(); const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); const CMapWorld::CMapWorldDrawParms parms(xa8_renderStates[0].x34_alphaSurfaceVisited * alphaInterp, xa8_renderStates[0].x38_alphaOutlineVisited * alphaInterp, xa8_renderStates[0].x3c_alphaSurfaceUnvisited * alphaInterp, xa8_renderStates[0].x40_alphaOutlineUnvisited * alphaInterp, mapAlpha, 1.f, mgr, planeXf, camXf, *x24_world, mwInfo, 0.f, 0.f, objectScale, false); mw->Draw(parms, xa0_curAreaId, xa4_otherAreaId, xa8_renderStates[0].x2c_drawDepth1, xa8_renderStates[0].x30_drawDepth2, false); } else { CMapWorld* mw = x24_world->IGetMapWorld(); const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(x24_world->IGetWorldAssetId()).MapWorldInfo(); zeus::CTransform modelXf = planeXf * preXf; const CMapWorld::CMapWorldDrawParms parms(xa8_renderStates[0].x34_alphaSurfaceVisited * alphaInterp, xa8_renderStates[0].x38_alphaOutlineVisited * alphaInterp, xa8_renderStates[0].x3c_alphaSurfaceUnvisited * alphaInterp, xa8_renderStates[0].x40_alphaOutlineUnvisited * alphaInterp, mapAlpha, 2.f, mgr, modelXf, camXf, *x24_world, mwInfo, 0.f, 0.f, objectScale, true); mw->Draw(parms, xa0_curAreaId, xa0_curAreaId, xa8_renderStates[0].x2c_drawDepth1, xa8_renderStates[0].x30_drawDepth2, false); } if (universeInterp > 0.f) { zeus::CTransform areaXf = mgr.GetWorld() ->GetMapWorld() ->GetMapArea(mgr.GetNextAreaId()) ->GetAreaPostTransform(*mgr.GetWorld(), mgr.GetNextAreaId()); const CMapUniverse::CMapWorldData& mwData = x8_mapu->GetMapWorldDataByWorldId(g_GameState->CurrentWorldAssetId()); zeus::CTransform universeAreaXf = mwData.GetWorldTransform() * areaXf; float minMag = FLT_MAX; int hexIdx = -1; for (u32 i = 0; i < mwData.GetNumMapAreaDatas(); ++i) { float mag = (universeAreaXf.origin - mwData.GetMapAreaData(i).origin).magnitude(); if (mag < minMag) { hexIdx = i; minMag = mag; } } const CMapUniverse::CMapUniverseDrawParms parms(universeInterp, x9c_worldIdx, g_GameState->CurrentWorldAssetId(), hexIdx, x1dc_playerFlashPulse, mgr, planeXf, camXf); x8_mapu->Draw(parms, zeus::skZero3f, 0.f, 0.f); } if (!IsInMapperState(EAutoMapperState::MapScreenUniverse)) { zeus::CTransform mapXf = planeXf * preXf; if (x24_world == mgr.GetWorld()) { float func = zeus::clamp(0.f, 0.5f * (1.f + std::sin(5.f * CGraphics::GetSecondsMod900() - (M_PIF / 2.f))), 1.f); float scale = std::min(0.6f * g_tweakAutoMapper->GetMaxCamDist() / g_tweakAutoMapper->GetMinCamDist(), objectScale); zeus::CEulerAngles eulers(mgr.GetCameraManager()->GetCurrentCameraTransform(mgr)); zeus::CRelAngle angle(eulers.z()); angle.makeRel(); zeus::CTransform playerXf(zeus::CMatrix3f::RotateZ(angle), CMapArea::GetAreaPostTranslate(*x24_world, mgr.GetNextAreaId()) + mgr.GetPlayer().GetTranslation()); CGraphics::SetModelMatrix(mapXf * playerXf * zeus::CTransform::Scale(scale * (0.25f * func + 0.75f))); float colorAlpha; if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) { colorAlpha = 1.f; } else { colorAlpha = xa8_renderStates[0].x34_alphaSurfaceVisited; } colorAlpha *= mapAlpha; zeus::CColor modColor = g_tweakAutoMapper->GetMiniMapSamusModColor(); modColor.a() *= colorAlpha; CModelFlags flags(5, 0, 8 | 1, modColor); /* Depth GEqual */ // flags.m_extendedShader = EExtendedShader::DepthGEqualNoZWrite; x30_miniMapSamus->Draw(flags); } if (IsInMapperState(EAutoMapperState::MapScreen)) { CAssetId wldMlvl = x24_world->IGetWorldAssetId(); const CMapWorld* mw = x24_world->IGetMapWorld(); auto locIt = x1f8_hintLocations.cbegin(); for (; locIt != x1f8_hintLocations.cend(); ++locIt) { const SAutoMapperHintLocation& loc = *locIt; if (loc.x8_worldId != wldMlvl) continue; const CMapArea* mapa = mw->GetMapArea(loc.xc_areaId); if (!mapa) continue; zeus::CTransform camRot(camXf.buildMatrix3f(), zeus::skZero3f); CGraphics::SetModelMatrix( mapXf * zeus::CTransform::Translate(mapa->GetAreaPostTransform(*x24_world, loc.xc_areaId).origin) * zeus::CTransform::Translate(mapa->GetAreaCenterPoint()) * zeus::CTransform::Scale(objectScale) * camRot); float beaconAlpha = 0.f; if (loc.x0_showBeacon == 1) { beaconAlpha = loc.x4_beaconAlpha; } if (beaconAlpha > 0.f) { CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); x3c_hintBeacon->Load(GX_TEXMAP0, EClampMode::Repeat); g_Renderer->SetBlendMode_AdditiveAlpha(); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); zeus::CColor color = zeus::skWhite; color.a() = beaconAlpha * ((x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) ? 1.f : xa8_renderStates[0].x34_alphaSurfaceVisited) * mapAlpha; CGraphics::StreamColor(color); CGraphics::StreamTexcoord(0.f, 1.f); CGraphics::StreamVertex(zeus::CVector3f(-4.f, -8.f, 8.f)); CGraphics::StreamTexcoord(0.f, 0.f); CGraphics::StreamVertex(zeus::CVector3f(-4.f, -8.f, 0.f)); CGraphics::StreamTexcoord(1.f, 1.f); CGraphics::StreamVertex(zeus::CVector3f(4.f, -8.f, 8.f)); CGraphics::StreamTexcoord(1.f, 0.f); CGraphics::StreamVertex(zeus::CVector3f(4.f, -8.f, 0.f)); CGraphics::StreamEnd(); } } } } g_Renderer->SetDepthReadWrite(false, false); g_Renderer->SetAmbientColor(zeus::skWhite); CGraphics::DisableAllLights(); if (m_frmeInitialized) { float frmeAlpha = 0.f; if (x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap) { frmeAlpha = 1.f; } else { if (x1c0_nextState != EAutoMapperState::MiniMap) { if (x1c4_interpDur > 0.f) frmeAlpha = x1c8_interpTime / x1c4_interpDur; } else { if (x1c4_interpDur > 0.f) frmeAlpha = x1c8_interpTime / x1c4_interpDur; frmeAlpha = 1.f - frmeAlpha; } } CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_NEAR); CGuiWidgetDrawParms parms(frmeAlpha, zeus::skZero3f); x28_frmeMapScreen->Draw(parms); CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_HUD); } } void CAutoMapper::TransformRenderStatesWorldToUniverse() { const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx); zeus::CQuaternion rot = zeus::CQuaternion(mapuWld.GetWorldTransform().buildMatrix3f()); xa8_renderStates[2].x8_camOrientation *= rot; xa8_renderStates[2].x20_areaPoint = mapuWld.GetWorldTransform() * xa8_renderStates[2].x20_areaPoint; xa8_renderStates[0].x8_camOrientation *= rot; xa8_renderStates[0].x20_areaPoint = mapuWld.GetWorldTransform() * xa8_renderStates[0].x20_areaPoint; xa8_renderStates[1].x8_camOrientation *= rot; xa8_renderStates[1].x20_areaPoint = mapuWld.GetWorldTransform() * xa8_renderStates[1].x20_areaPoint; } void CAutoMapper::TransformRenderStatesUniverseToWorld() { const CMapUniverse::CMapWorldData& mapuWld = x8_mapu->GetMapWorldData(x9c_worldIdx); zeus::CTransform inv = mapuWld.GetWorldTransform().inverse(); zeus::CQuaternion invRot = zeus::CQuaternion(inv.buildMatrix3f()); xa8_renderStates[2].x8_camOrientation *= invRot; xa8_renderStates[2].x20_areaPoint = inv * xa8_renderStates[2].x20_areaPoint; xa8_renderStates[0].x8_camOrientation *= invRot; xa8_renderStates[0].x20_areaPoint = inv * xa8_renderStates[0].x20_areaPoint; xa8_renderStates[1].x8_camOrientation *= invRot; xa8_renderStates[1].x20_areaPoint = inv * xa8_renderStates[1].x20_areaPoint; } void CAutoMapper::TransformRenderStateWorldToUniverse(SAutoMapperRenderState& state) { state.x20_areaPoint = x8_mapu->GetMapWorldData(x9c_worldIdx).GetWorldTransform() * xa8_renderStates[1].x20_areaPoint; } void CAutoMapper::SetupHintNavigation() { if (!g_GameState->GameOptions().GetIsHintSystemEnabled()) return; x1e0_hintSteps.clear(); x1f8_hintLocations.clear(); CHintOptions& hintOpts = g_GameState->HintOptions(); const CHintOptions::SHintState* curHint = hintOpts.GetCurrentDisplayedHint(); bool navigating = false; if (curHint && curHint->CanContinue()) { navigating = true; x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ShowBeacon{}, 0.75f); const CGameHintInfo::CGameHint& nextHint = g_MemoryCardSys->GetHints()[hintOpts.GetNextHintIdx()]; CAssetId curMlvl = x24_world->IGetWorldAssetId(); for (const CGameHintInfo::SHintLocation& loc : nextHint.GetLocations()) { if (loc.x0_mlvlId != curMlvl) { x1e0_hintSteps.emplace_back(SAutoMapperHintStep::SwitchToUniverse{}); x1e0_hintSteps.emplace_back(SAutoMapperHintStep::PanToWorld{}, loc.x0_mlvlId); x1e0_hintSteps.emplace_back(SAutoMapperHintStep::SwitchToWorld{}, loc.x0_mlvlId); } else { x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ZoomOut{}); } x1e0_hintSteps.emplace_back(SAutoMapperHintStep::PanToArea{}, loc.x8_areaId); x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ZoomIn{}); x1e0_hintSteps.emplace_back(SAutoMapperHintStep::ShowBeacon{}, 1.f); x1f8_hintLocations.push_back({0, 0.f, loc.x0_mlvlId, loc.x8_areaId}); } } for (size_t i = 0; i < hintOpts.GetHintStates().size(); ++i) { const CHintOptions::SHintState& state = hintOpts.GetHintStates()[i]; if (navigating && hintOpts.GetNextHintIdx() == i) continue; if (state.x0_state != CHintOptions::EHintState::Displaying) continue; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[i]; for (const CGameHintInfo::SHintLocation& loc : hint.GetLocations()) x1f8_hintLocations.push_back({1, 1.f, loc.x0_mlvlId, loc.x8_areaId}); } } CAssetId CAutoMapper::GetAreaHintDescriptionString(CAssetId mreaId) { const CHintOptions& hintOpts = g_GameState->HintOptions(); for (size_t i = 0; i < hintOpts.GetHintStates().size(); ++i) { const CHintOptions::SHintState& state = hintOpts.GetHintStates()[i]; if (state.x0_state != CHintOptions::EHintState::Displaying) continue; const CGameHintInfo::CGameHint& memHint = g_MemoryCardSys->GetHints()[i]; for (const CGameHintInfo::SHintLocation& loc : memHint.GetLocations()) { if (loc.x4_mreaId != mreaId) continue; for (const SAutoMapperHintLocation& hintLoc : x1f8_hintLocations) { if (hintLoc.xc_areaId != loc.x8_areaId) continue; if (hintLoc.x4_beaconAlpha > 0.f) return loc.xc_stringId; } } } return {}; } void CAutoMapper::OnNewInGameGuiState(EInGameGuiState state, CStateManager& mgr) { if (state == EInGameGuiState::MapScreen) { MP1::CMain::EnsureWorldPaksReady(); CWorld& wld = *mgr.GetWorld(); wld.GetMapWorld()->SetWhichMapAreasLoaded(wld, 0, 9999); SetupHintNavigation(); BeginMapperStateTransition(EAutoMapperState::MapScreen, mgr); x28_frmeMapScreen = g_SimplePool->GetObj("FRME_MapScreen"); SetResLockState(x210_lstick, true); SetResLockState(x25c_cstick, true); SetResLockState(x2a8_ltrigger, true); SetResLockState(x2bc_rtrigger, true); SetResLockState(x2d0_abutton, true); } else { MP1::CMain::EnsureWorldPakReady(g_GameState->CurrentWorldAssetId()); if (x1bc_state == EAutoMapperState::MapScreenUniverse || x24_world == mgr.GetWorld()) { BeginMapperStateTransition(EAutoMapperState::MiniMap, mgr); x328_ = 0; } LeaveMapScreenState(); } } } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CAutoMapper.hpp ================================================ #pragma once #include #include #include #include #include #include "Runtime/rstl.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/AutoMapper/CMapUniverse.hpp" #include "Runtime/MP1/CInGameGuiManager.hpp" #include #include #include #include #include namespace metaforce { class CMapWorldInfo; class CStateManager; class IWorld; struct CFinalInput; class CAutoMapper { public: using EInGameGuiState = MP1::EInGameGuiState; enum class ELoadPhase { LoadResources, LoadUniverse, Done }; enum class EAutoMapperState { MiniMap, MapScreen, MapScreenUniverse }; struct SAutoMapperRenderState { enum class Ease { None, Linear, Out, In, InOut }; using FGetViewportSize = zeus::CVector2i (*)(); FGetViewportSize m_getViewportSize = nullptr; zeus::CVector2i x0_viewportSize; zeus::CQuaternion x8_camOrientation; float x18_camDist = 0.0f; float x1c_camAngle = 0.0f; zeus::CVector3f x20_areaPoint; float x2c_drawDepth1 = 0.0f; float x30_drawDepth2 = 0.0f; float x34_alphaSurfaceVisited = 0.0f; float x38_alphaOutlineVisited = 0.0f; float x3c_alphaSurfaceUnvisited = 0.0f; float x40_alphaOutlineUnvisited = 0.0f; Ease x44_viewportEase = Ease::None; Ease x48_camEase = Ease::None; Ease x4c_pointEase = Ease::None; Ease x50_depth1Ease = Ease::None; Ease x54_depth2Ease = Ease::None; Ease x58_alphaEase = Ease::None; SAutoMapperRenderState() = default; SAutoMapperRenderState(FGetViewportSize getViewportSize, const zeus::CQuaternion& camOrientation, float camDist, float camAngle, const zeus::CVector3f& areaPoint, float drawDepth1, float drawDepth2, float alphaSurfaceVisited, float alphaOutlineVisited, float alphaSurfaceUnvisited, float alphaOutlineUnvisited) : m_getViewportSize(getViewportSize) , x0_viewportSize(getViewportSize()) , x8_camOrientation(camOrientation) , x18_camDist(camDist) , x1c_camAngle(camAngle) , x20_areaPoint(areaPoint) , x2c_drawDepth1(drawDepth1) , x30_drawDepth2(drawDepth2) , x34_alphaSurfaceVisited(alphaSurfaceVisited) , x38_alphaOutlineVisited(alphaOutlineVisited) , x3c_alphaSurfaceUnvisited(alphaSurfaceUnvisited) , x40_alphaOutlineUnvisited(alphaOutlineUnvisited) {} static void InterpolateWithClamp(const SAutoMapperRenderState& a, SAutoMapperRenderState& out, const SAutoMapperRenderState& b, float t); void ResetInterpolation() { x44_viewportEase = Ease::None; x48_camEase = Ease::None; x4c_pointEase = Ease::None; x50_depth1Ease = Ease::None; x54_depth2Ease = Ease::None; x58_alphaEase = Ease::None; } zeus::CVector2i GetViewportSize() const { if (m_getViewportSize) return m_getViewportSize(); else return x0_viewportSize; } }; struct SAutoMapperHintStep { enum class Type { PanToArea, PanToWorld, SwitchToUniverse, SwitchToWorld, ShowBeacon, ZoomIn, ZoomOut }; struct PanToArea {}; struct PanToWorld {}; struct SwitchToUniverse {}; struct SwitchToWorld {}; struct ShowBeacon {}; struct ZoomIn {}; struct ZoomOut {}; Type x0_type; union { CAssetId x4_worldId; TAreaId x4_areaId; float x4_float; }; bool x8_processing = false; SAutoMapperHintStep(PanToArea, TAreaId areaId) : x0_type(Type::PanToArea), x4_areaId(areaId) {} SAutoMapperHintStep(PanToWorld, CAssetId worldId) : x0_type(Type::PanToWorld), x4_worldId(worldId) {} SAutoMapperHintStep(SwitchToUniverse) : x0_type(Type::SwitchToUniverse), x4_worldId(CAssetId()) {} SAutoMapperHintStep(SwitchToWorld, CAssetId worldId) : x0_type(Type::SwitchToWorld), x4_worldId(worldId) {} SAutoMapperHintStep(ShowBeacon, float val) : x0_type(Type::ShowBeacon), x4_float(val) {} SAutoMapperHintStep(ZoomIn) : x0_type(Type::ZoomIn), x4_worldId(CAssetId()) {} SAutoMapperHintStep(ZoomOut) : x0_type(Type::ZoomOut), x4_worldId(CAssetId()) {} }; struct SAutoMapperHintLocation { u32 x0_showBeacon; float x4_beaconAlpha; CAssetId x8_worldId; TAreaId xc_areaId; }; private: enum class EZoomState { None, In, Out }; ELoadPhase x4_loadPhase = ELoadPhase::LoadResources; TLockedToken x8_mapu; std::vector> x14_dummyWorlds; IWorld* x24_world; TLockedToken x28_frmeMapScreen; // Used to be ptr bool m_frmeInitialized = false; TLockedToken x30_miniMapSamus; TLockedToken x3c_hintBeacon; rstl::reserved_vector, 5> x48_mapIcons; CAssetId x74_areaHintDescId; TLockedToken x78_areaHintDesc; CAssetId x88_mapAreaStringId; TLockedToken x8c_mapAreaString; // Used to be optional u32 x9c_worldIdx = 0; TAreaId xa0_curAreaId; TAreaId xa4_otherAreaId; std::array xa8_renderStates; // xa8, x104, x160; current, next, prev EAutoMapperState x1bc_state = EAutoMapperState::MiniMap; EAutoMapperState x1c0_nextState = EAutoMapperState::MiniMap; float x1c4_interpDur = 0.f; float x1c8_interpTime = 0.f; CSfxHandle x1cc_panningSfx; CSfxHandle x1d0_rotatingSfx; CSfxHandle x1d4_zoomingSfx; float x1d8_flashTimer = 0.f; float x1dc_playerFlashPulse = 0.f; std::list x1e0_hintSteps; std::list x1f8_hintLocations; rstl::reserved_vector, 9> x210_lstick; rstl::reserved_vector, 9> x25c_cstick; rstl::reserved_vector, 2> x2a8_ltrigger; rstl::reserved_vector, 2> x2bc_rtrigger; rstl::reserved_vector, 2> x2d0_abutton; u32 x2e4_lStickPos = 0; u32 x2e8_rStickPos = 0; u32 x2ec_lTriggerPos = 0; u32 x2f0_rTriggerPos = 0; u32 x2f4_aButtonPos = 0; CGuiTextPane* x2f8_textpane_areaname = nullptr; CGuiTextPane* x2fc_textpane_hint = nullptr; CGuiTextPane* x300_textpane_instructions = nullptr; CGuiTextPane* x304_textpane_instructions1 = nullptr; CGuiTextPane* x308_textpane_instructions2 = nullptr; CGuiWidget* x30c_basewidget_leftPane = nullptr; CGuiWidget* x310_basewidget_yButtonPane = nullptr; CGuiWidget* x314_basewidget_bottomPane = nullptr; float x318_leftPanePos = 0.f; float x31c_yButtonPanePos = 0.f; float x320_bottomPanePos = 0.f; EZoomState x324_zoomState = EZoomState::None; u32 x328_ = 0; bool x32c_loadingDummyWorld = false; std::optional m_lastMouseCoord; zeus::CVector2f m_mouseDelta; SScrollDelta m_lastAccumScroll; SScrollDelta m_mapScroll; template static void SetResLockState(T& list, bool lock) { for (auto& res : list) if (lock) res.Lock(); else res.Unlock(); } bool NotHintNavigating() const; bool CanLeaveMapScreenInternal(const CStateManager& mgr) const; void LeaveMapScreen(CStateManager& mgr); void SetupMiniMapWorld(CStateManager& mgr); bool HasCurrentMapUniverseWorld() const; bool CheckDummyWorldLoad(CStateManager& mgr); void UpdateHintNavigation(float dt, CStateManager& mgr); static zeus::CVector2i GetMiniMapViewportSize(); static zeus::CVector2i GetMapScreenViewportSize(); static float GetMapAreaMiniMapDrawDepth() { return 2.f; } float GetMapAreaMaxDrawDepth(const CStateManager& mgr, TAreaId aid) const; static float GetMapAreaMiniMapDrawAlphaSurfaceVisited(const CStateManager& mgr); static float GetMapAreaMiniMapDrawAlphaOutlineVisited(const CStateManager& mgr); static float GetMapAreaMiniMapDrawAlphaSurfaceUnvisited(const CStateManager& mgr); static float GetMapAreaMiniMapDrawAlphaOutlineUnvisited(const CStateManager& mgr); float GetDesiredMiniMapCameraDistance(const CStateManager& mgr) const; static float GetBaseMapScreenCameraMoveSpeed(); float GetClampedMapScreenCameraDistance(float value) const; float GetFinalMapScreenCameraMoveSpeed() const; void ProcessMapRotateInput(const CFinalInput& input, const CStateManager& mgr); void ProcessMapZoomInput(const CFinalInput& input, const CStateManager& mgr); void ProcessMapPanInput(const CFinalInput& input, const CStateManager& mgr); void SetShouldPanningSoundBePlaying(bool shouldBePlaying); void SetShouldZoomingSoundBePlaying(bool shouldBePlaying); void SetShouldRotatingSoundBePlaying(bool shouldBePlaying); void TransformRenderStatesWorldToUniverse(); void TransformRenderStatesUniverseToWorld(); void TransformRenderStateWorldToUniverse(SAutoMapperRenderState&); void SetupHintNavigation(); CAssetId GetAreaHintDescriptionString(CAssetId mreaId); public: explicit CAutoMapper(CStateManager& stateMgr); bool CheckLoadComplete(); bool CanLeaveMapScreen(const CStateManager& mgr) const; float GetMapRotationX() const { return xa8_renderStates[0].x1c_camAngle; } float GetMapRotationZ() const { return xa8_renderStates[0].x8_camOrientation.yaw(); } TAreaId GetFocusAreaIndex() const { return xa0_curAreaId; } CAssetId GetCurrWorldAssetId() const { return x24_world->IGetWorldAssetId(); } void SetCurWorldAssetId(CAssetId mlvlId); void MuteAllLoopedSounds(); void UnmuteAllLoopedSounds(); void ProcessControllerInput(const CFinalInput& input, CStateManager& mgr); bool IsInPlayerControlState() const { return IsInMapperState(EAutoMapperState::MapScreen) || IsInMapperState(EAutoMapperState::MapScreenUniverse); } void Update(float dt, CStateManager& mgr); void Draw(const CStateManager& mgr, const zeus::CTransform& xf, float alpha); float GetTimeIntoInterpolation() const { return x1c8_interpTime; } void BeginMapperStateTransition(EAutoMapperState state, CStateManager& mgr); void CompleteMapperStateTransition(CStateManager& mgr); void ResetInterpolationTimer(float duration); SAutoMapperRenderState BuildMiniMapWorldRenderState(const CStateManager& stateMgr, const zeus::CQuaternion& rot, TAreaId area) const; SAutoMapperRenderState BuildMapScreenWorldRenderState(const CStateManager& mgr, const zeus::CQuaternion& rot, TAreaId area, bool doingHint) const; SAutoMapperRenderState BuildMapScreenUniverseRenderState(const CStateManager& mgr, const zeus::CQuaternion& rot, TAreaId area) const; void LeaveMapScreenState(); void ProcessMapScreenInput(const CFinalInput& input, CStateManager& mgr); zeus::CQuaternion GetMiniMapCameraOrientation(const CStateManager& stateMgr) const; zeus::CVector3f GetAreaPointOfInterest(const CStateManager& mgr, TAreaId aid) const; TAreaId FindClosestVisibleArea(const zeus::CVector3f& point, const zeus::CUnitVector3f& camDir, const CStateManager& mgr, const IWorld& wld, const CMapWorldInfo& mwInfo) const; std::pair FindClosestVisibleWorld(const zeus::CVector3f& point, const zeus::CUnitVector3f& camDir, const CStateManager& mgr) const; EAutoMapperState GetNextState() const { return x1c0_nextState; } bool IsInMapperState(EAutoMapperState state) const { return state == x1bc_state && state == x1c0_nextState; } bool IsInMapperStateTransition() const { return x1c0_nextState != x1bc_state; } bool IsRenderStateInterpolating() const { return x1c8_interpTime < x1c4_interpDur; } bool IsStateTransitioning() const { return x1bc_state != x1c0_nextState; } bool IsFullyInMiniMapState() const { return IsInMapperState(EAutoMapperState::MiniMap); } bool IsFullyOutOfMiniMapState() const { return x1bc_state != EAutoMapperState::MiniMap && x1c0_nextState != EAutoMapperState::MiniMap; } void OnNewInGameGuiState(EInGameGuiState state, CStateManager& mgr); float GetInterp() const { if (x1c4_interpDur > 0.f) return x1c8_interpTime / x1c4_interpDur; return 0.f; } }; } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMakeLists.txt ================================================ set(AUTOMAPPER_SOURCES CMapUniverse.hpp CMapUniverse.cpp CMapWorldInfo.hpp CMapWorldInfo.cpp CMapWorld.hpp CMapWorld.cpp CMapArea.hpp CMapArea.cpp CMappableObject.hpp CMappableObject.cpp CAutoMapper.hpp CAutoMapper.cpp) runtime_add_list(AutoMapper AUTOMAPPER_SOURCES) ================================================ FILE: Runtime/AutoMapper/CMapArea.cpp ================================================ #include "Runtime/AutoMapper/CMapArea.hpp" #include #include #include "Runtime/AutoMapper/CMappableObject.hpp" #include "Runtime/CToken.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/World/CWorld.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/Graphics/CGX.hpp" namespace metaforce { constexpr std::array MinesPostTransforms{{ {0.f, 0.f, 200.f}, {0.f, 0.f, 0.f}, {0.f, 0.f, -200.f}, }}; constexpr std::array MinesPostTransformIndices{ 0, // 00 Transport to Tallon Overworld South 0, // 01 Quarry Access 0, // 02 Main Quarry 0, // 03 Waste Disposal 0, // 04 Save Station Mines A 0, // 05 Security Access A 0, // 06 Ore Processing 0, // 07 Mine Security Station 0, // 08 Research Access 0, // 09 Storage Depot B 0, // 10 Elevator Access A 0, // 11 Security Access B 0, // 12 Storage Depot A 0, // 13 Elite Research 0, // 14 Elevator A 1, // 15 Elite Control Access 1, // 16 Elite Control 1, // 17 Maintenance Tunnel 1, // 18 Ventilation Shaft 2, // 19 Phazon Processing Center 1, // 20 Omega Research 2, // 21 Transport Access 2, // 22 Processing Center Access 1, // 23 Map Station Mines 1, // 24 Dynamo Access 2, // 25 Transport to Magmoor Caverns South 2, // 26 Elite Quarters 1, // 27 Central Dynamo 2, // 28 Elite Quarters Access 1, // 29 Quarantine Access A 1, // 30 Save Station Mines B 2, // 31 Metroid Quarantine B 1, // 32 Metroid Quarantine A 2, // 33 Quarantine Access B 2, // 34 Save Station Mines C 1, // 35 Elevator Access B 2, // 36 Fungal Hall B 1, // 37 Elevator B 2, // 38 Missile Station Mines 2, // 39 Phazon Mining Tunnel 2, // 40 Fungal Hall Access 2, // 41 Fungal Hall A }; CMapArea::CMapArea(CInputStream& in, u32 size) : x0_magic(in.ReadLong()) , x4_version(in.ReadLong()) , x8_(in.ReadLong()) , xc_visibilityMode(EVisMode(in.ReadLong())) , x10_box(in.Get()) , x28_mappableObjCount(in.ReadLong()) , x2c_vertexCount(in.ReadLong()) , x30_surfaceCount(in.ReadLong()) , x34_size(size - 52) { x44_buf.reset(new u8[x34_size]); in.ReadBytes(x44_buf.get(), x34_size); PostConstruct(); } void CMapArea::PostConstruct() { // OPTICK_EVENT(); x38_moStart = x44_buf.get(); x3c_vertexStart = x38_moStart + (x28_mappableObjCount * 0x50); x40_surfaceStart = x3c_vertexStart + (x2c_vertexCount * 12); m_mappableObjects.reserve(x28_mappableObjCount); for (u32 i = 0, j = 0; i < x28_mappableObjCount; ++i, j += 0x50) { m_mappableObjects.emplace_back(x38_moStart + j).PostConstruct(x44_buf.get()); } u8* tmp = x3c_vertexStart; m_verts.reserve(x2c_vertexCount); for (u32 i = 0; i < x2c_vertexCount; ++i) { float x; std::memcpy(&x, tmp, sizeof(float)); float y; std::memcpy(&y, tmp + 4, sizeof(float)); float z; std::memcpy(&z, tmp + 8, sizeof(float)); m_verts.emplace_back(CBasics::SwapBytes(x), CBasics::SwapBytes(y), CBasics::SwapBytes(z)); tmp += 12; } m_surfaces.reserve(x30_surfaceCount); for (u32 i = 0, j = 0; i < x30_surfaceCount; ++i, j += 32) { m_surfaces.emplace_back(x40_surfaceStart + j).PostConstruct(x44_buf.get()); } } bool CMapArea::GetIsVisibleToAutoMapper(bool worldVis, bool areaVis) const { switch (xc_visibilityMode) { case EVisMode::Always: return true; case EVisMode::MapStationOrVisit: return worldVis || areaVis; case EVisMode::Visit: return areaVis; case EVisMode::Never: return false; default: return true; } } zeus::CTransform CMapArea::GetAreaPostTransform(const IWorld& world, TAreaId aid) const { if (world.IGetWorldAssetId() == 0xB1AC4D65) // Phazon Mines { const zeus::CTransform& areaXf = world.IGetAreaAlways(aid)->IGetTM(); const zeus::CVector3f& postVec = MinesPostTransforms[MinesPostTransformIndices[aid]]; return zeus::CTransform::Translate(postVec) * areaXf; } else { return world.IGetAreaAlways(aid)->IGetTM(); } } const zeus::CVector3f& CMapArea::GetAreaPostTranslate(const IWorld& world, TAreaId aid) { if (world.IGetWorldAssetId() == 0xB1AC4D65) // Phazon Mines return MinesPostTransforms[MinesPostTransformIndices[aid]]; else return zeus::skZero3f; } CMapArea::CMapAreaSurface::CMapAreaSurface(const void* surfBuf) { CMemoryInStream r(surfBuf, 32, CMemoryInStream::EOwnerShip::NotOwned); x0_normal = r.Get(); xc_centroid = r.Get(); x18_surfOffset = reinterpret_cast(static_cast(r.ReadLong())); x1c_outlineOffset = reinterpret_cast(static_cast(r.ReadLong())); } void CMapArea::CMapAreaSurface::PostConstruct(const void* buf) { x18_surfOffset = reinterpret_cast(static_cast(buf) + reinterpret_cast(x18_surfOffset)); x1c_outlineOffset = reinterpret_cast(static_cast(buf) + reinterpret_cast(x1c_outlineOffset)); } void CMapArea::CMapAreaSurface::Draw(TConstVectorRef verts, const CColor& surfColor, const CColor& lineColor, float lineWidth) const { bool hasSurfAlpha = surfColor.a() > 0.0f; bool hasLineAlpha = lineColor.a() > 0.0f; u32 numSurfaces = CBasics::SwapBytes(*x18_surfOffset); u32 numOutlines = CBasics::SwapBytes(*x1c_outlineOffset); if (!verts.empty()) { CGX::SetArray(GX_VA_POS, verts); } if (hasSurfAlpha) { CGX::SetTevKColor(GX_KCOLOR0, surfColor); const u32* surface = &x18_surfOffset[1]; for (u32 i = 0; i < numSurfaces; ++i) { GXPrimitive primType = static_cast(CBasics::SwapBytes(*surface++)); u32 numVertices = CBasics::SwapBytes(*surface++); const u8* data = reinterpret_cast(surface); surface += ((numVertices + 3) & ~3) / 4; CGX::Begin(primType, GX_VTXFMT0, numVertices); for (u32 v = 0; v < numVertices; ++v) { GXPosition1x8(data[v]); } CGX::End(); } } if (hasLineAlpha) { bool thickLine = lineWidth > 1.f; for (u32 j = 0; j < (thickLine ? 1 : 0) + 1; ++j) { const u32* outline = &x1c_outlineOffset[1]; if (thickLine) { CGraphics::SetLineWidth(lineWidth - j, ERglTexOffset::One); } CColor clr = lineColor; if (thickLine) { clr.a() *= 0.5f; } CGX::SetTevKColor(GX_KCOLOR0, clr); for (u32 i = 0; i < numOutlines; ++i) { u32 numVertices = CBasics::SwapBytes(*outline++); const u8* data = reinterpret_cast(outline); outline += ((numVertices + 3) & ~3) / 4; CGX::Begin(GX_LINESTRIP, GX_VTXFMT0, numVertices); for (u32 v = 0; v < numVertices; ++v) { GXPosition1x8(data[v]); } CGX::End(); } } } } void CMapArea::CMapAreaSurface::SetupGXMaterial() { const GXVtxDescList list[2] = { {GX_VA_POS, GX_INDEX8}, {GX_VA_NULL, GX_NONE}, }; CGX::SetVtxDescv(list); CGX::SetNumChans(1); CGX::SetNumTexGens(0); CGX::SetNumTevStages(1); CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); } CFactoryFnReturn FMapAreaFactory(const SObjectTag& objTag, CInputStream& in, const CVParamTransfer&, CObjectReference*) { u32 size = g_ResFactory->ResourceSize(objTag); return TToken::GetIObjObjectFor(std::make_unique(in, size)); } } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMapArea.hpp ================================================ #pragma once #include #include #include "Runtime/Graphics/CCubeModel.hpp" #include "Runtime/AutoMapper/CMappableObject.hpp" #include "Runtime/CResFactory.hpp" #include "Runtime/RetroTypes.hpp" #include #include namespace metaforce { using CColor = zeus::CColor; using CVector3f = zeus::CVector3f; class IWorld; class CMapArea { public: class CMapAreaSurface { friend class CMapArea; CVector3f x0_normal; CVector3f xc_centroid; const u32* x18_surfOffset; const u32* x1c_outlineOffset; public: explicit CMapAreaSurface(const void* surfBuf); void PostConstruct(const void* buf); void Draw(TConstVectorRef verts, const CColor& surfColor, const CColor& lineColor, float lineWidth) const; static void SetupGXMaterial(); const CVector3f& GetNormal() const { return x0_normal; } const CVector3f& GetCenterPosition() const { return xc_centroid; } }; enum class EVisMode { Always, MapStationOrVisit, Visit, Never }; private: u32 x0_magic; u32 x4_version; u32 x8_; EVisMode xc_visibilityMode; zeus::CAABox x10_box; u32 x28_mappableObjCount; u32 x2c_vertexCount; u32 x30_surfaceCount; u32 x34_size; u8* x38_moStart; std::vector m_mappableObjects; u8* x3c_vertexStart; std::vector> m_verts; u8* x40_surfaceStart; std::vector m_surfaces; std::unique_ptr x44_buf; public: explicit CMapArea(CInputStream& in, u32 size); void PostConstruct(); bool GetIsVisibleToAutoMapper(bool worldVis, bool areaVis) const; zeus::CVector3f GetAreaCenterPoint() const { return x10_box.center(); } const zeus::CAABox& GetBoundingBox() const { return x10_box; } CMappableObject& GetMappableObject(int idx) { return m_mappableObjects[idx]; } const CMappableObject& GetMappableObject(int idx) const { return m_mappableObjects[idx]; } CMapAreaSurface& GetSurface(int idx) { return m_surfaces[idx]; } const CMapAreaSurface& GetSurface(int idx) const { return m_surfaces[idx]; } u32 GetNumMappableObjects() const { return m_mappableObjects.size(); } u32 GetNumSurfaces() const { return m_surfaces.size(); } zeus::CTransform GetAreaPostTransform(const IWorld& world, TAreaId aid) const; static const zeus::CVector3f& GetAreaPostTranslate(const IWorld& world, TAreaId aid); TConstVectorRef GetVertices() const { return m_verts; } }; CFactoryFnReturn FMapAreaFactory(const SObjectTag& objTag, CInputStream& in, const CVParamTransfer&, CObjectReference*); } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMapUniverse.cpp ================================================ #include "Runtime/AutoMapper/CMapUniverse.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/GameGlobalObjects.hpp" namespace metaforce { CMapUniverse::CMapUniverse(CInputStream& in, u32 version) : x0_hexagonId(in.Get()) { x4_hexagonToken = g_SimplePool->GetObj({FOURCC('MAPA'), x0_hexagonId}); u32 count = in.ReadLong(); x10_worldDatas.reserve(count); for (u32 i = 0; i < count; ++i) x10_worldDatas.emplace_back(in, version); } CMapUniverse::CMapWorldData::CMapWorldData(CInputStream& in, u32 version) : x0_label(in.Get()), x10_worldAssetId(in) { x14_transform = in.Get(); const u32 worldCount = in.ReadLong(); x44_hexagonXfs.reserve(worldCount); for (u32 i = 0; i < worldCount; ++i) { x44_hexagonXfs.emplace_back() = in.Get(); } if (version != 0) x54_surfColorSelected = in.Get(); else x54_surfColorSelected.fromRGBA32(255 | (u32(x10_worldAssetId.Value()) & 0xFFFFFF00)); x58_outlineColorSelected = zeus::CColor::lerp(zeus::skWhite, x54_surfColorSelected, 0.5f); x5c_surfColorUnselected = zeus::CColor::lerp(zeus::skBlack, x54_surfColorSelected, 0.5f); x60_outlineColorUnselected = zeus::CColor::lerp(zeus::skWhite, x5c_surfColorUnselected, 0.5f); for (const zeus::CTransform& xf : x44_hexagonXfs) x64_centerPoint += xf.origin; x64_centerPoint *= zeus::CVector3f(1.0f / float(x44_hexagonXfs.size())); } void CMapUniverse::Draw(const CMapUniverseDrawParms& parms, const zeus::CVector3f&, float, float) { if (!x4_hexagonToken.IsLoaded()) { return; } SCOPED_GRAPHICS_DEBUG_GROUP("CMapUniverse::Draw", zeus::skBlue); u32 totalSurfaceCount = 0; for (const CMapWorldData& data : x10_worldDatas) totalSurfaceCount += data.GetNumMapAreaDatas() * x4_hexagonToken->GetNumSurfaces(); std::vector sortInfos; sortInfos.reserve(totalSurfaceCount); for (size_t w = 0; w < x10_worldDatas.size(); ++w) { const CMapWorldData& data = x10_worldDatas[w]; const CMapWorldInfo& mwInfo = *g_GameState->StateForWorld(data.GetWorldAssetId()).MapWorldInfo(); if (!mwInfo.IsAnythingSet()) continue; zeus::CColor surfColor, outlineColor; if (s32(w) == parms.GetFocusWorldIndex()) { surfColor = data.GetSurfaceColorSelected(); surfColor.a() *= parms.GetAlpha(); outlineColor = data.GetOutlineColorSelected(); outlineColor.a() *= parms.GetAlpha(); } else { surfColor = data.GetSurfaceColorUnselected(); surfColor.a() *= parms.GetAlpha(); outlineColor = data.GetSurfaceColorUnselected(); outlineColor.a() *= parms.GetAlpha(); } for (u32 h = 0; h < data.GetNumMapAreaDatas(); ++h) { zeus::CTransform hexXf = parms.GetCameraTransform().inverse() * data.GetMapAreaData(h); for (u32 s = 0; s < x4_hexagonToken->GetNumSurfaces(); ++s) { const CMapArea::CMapAreaSurface& surf = x4_hexagonToken->GetSurface(s); zeus::CVector3f centerPos = hexXf * surf.GetCenterPosition(); sortInfos.emplace_back(centerPos.y(), w, h, s, surfColor, outlineColor); } } } if (!sortInfos.empty()) { std::sort(sortInfos.begin(), sortInfos.end(), [](const CMapObjectSortInfo& a, const CMapObjectSortInfo& b) { return a.GetZDistance() > b.GetZDistance(); }); CMapArea::CMapAreaSurface::SetupGXMaterial(); int lastWldIdx = -1; int lastHexIdx = -1; for (const CMapObjectSortInfo& info : sortInfos) { const CMapWorldData& mwData = x10_worldDatas[info.GetWorldIndex()]; zeus::CColor surfColor = info.GetSurfaceColor(); zeus::CColor outlineColor = info.GetOutlineColor(); if (parms.GetWorldAssetId() == mwData.GetWorldAssetId() && parms.GetClosestArea() == info.GetAreaIndex()) { surfColor = zeus::CColor::lerp(g_tweakAutoMapper->GetSurfaceSelectVisitedColor(), g_tweakAutoMapper->GetAreaFlashPulseColor(), parms.GetFlashPulse()); surfColor.a() = info.GetSurfaceColor().a(); outlineColor = zeus::CColor::lerp(g_tweakAutoMapper->GetOutlineSelectVisitedColor(), g_tweakAutoMapper->GetAreaFlashPulseColor(), parms.GetFlashPulse()); outlineColor.a() = info.GetOutlineColor().a(); } zeus::CTransform hexXf = mwData.GetMapAreaData(info.GetAreaIndex()); hexXf.orthonormalize(); CMapArea::CMapAreaSurface& surf = x4_hexagonToken->GetSurface(info.GetObjectIndex()); zeus::CColor color(std::max(0.f, (-parms.GetCameraTransform().basis[1]).dot(hexXf.rotate(surf.GetNormal()))) * g_tweakAutoMapper->GetMapSurfaceNormColorLinear() + g_tweakAutoMapper->GetMapSurfaceNormColorConstant()); surfColor *= color; if (info.GetAreaIndex() != lastHexIdx || info.GetWorldIndex() != lastWldIdx) CGraphics::SetModelMatrix(parms.GetPaneProjectionTransform() * mwData.GetMapAreaData(info.GetAreaIndex())); surf.Draw(x4_hexagonToken->GetVertices(), surfColor, outlineColor, 2.f); } } } CFactoryFnReturn FMapUniverseFactory(const SObjectTag&, CInputStream& in, const CVParamTransfer&, CObjectReference*) { in.ReadLong(); u32 version = in.ReadLong(); return TToken::GetIObjObjectFor(std::make_unique(in, version)); } } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMapUniverse.hpp ================================================ #pragma once #include #include #include "Runtime/CToken.hpp" #include "Runtime/IFactory.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/AutoMapper/CMapArea.hpp" #include #include #include namespace metaforce { class CStateManager; class CMapUniverse { public: class CMapUniverseDrawParms { float x0_alpha; int x4_wldIdx; CAssetId x8_wldRes; int xc_closestHex; float x10_flashPulse; // const CStateManager& x14_mgr; const zeus::CTransform& x18_model; const zeus::CTransform& x1c_view; public: CMapUniverseDrawParms(float alpha, int wldIdx, CAssetId wldRes, int closestHex, float flashPulse, const CStateManager& mgr, const zeus::CTransform& model, const zeus::CTransform& view) : x0_alpha(alpha) , x4_wldIdx(wldIdx) , x8_wldRes(wldRes) , xc_closestHex(closestHex) , x10_flashPulse(flashPulse) , // x14_mgr(mgr), x18_model(model) , x1c_view(view) {} int GetFocusWorldIndex() const { return x4_wldIdx; } const zeus::CTransform& GetCameraTransform() const { return x1c_view; } const zeus::CTransform& GetPaneProjectionTransform() const { return x18_model; } float GetAlpha() const { return x0_alpha; } CAssetId GetWorldAssetId() const { return x8_wldRes; } int GetClosestArea() const { return xc_closestHex; } float GetFlashPulse() const { return x10_flashPulse; } }; class CMapObjectSortInfo { float x0_zDist; int x4_wldIdx; int x8_hexIdx; int xc_surfIdx; zeus::CColor x10_surfColor; zeus::CColor x14_outlineColor; public: CMapObjectSortInfo(float zDist, int wldIdx, int hexIdx, int surfIdx, const zeus::CColor& surf, const zeus::CColor& outline) : x0_zDist(zDist) , x4_wldIdx(wldIdx) , x8_hexIdx(hexIdx) , xc_surfIdx(surfIdx) , x10_surfColor(surf) , x14_outlineColor(outline) {} const zeus::CColor& GetOutlineColor() const { return x14_outlineColor; } const zeus::CColor& GetSurfaceColor() const { return x10_surfColor; } int GetObjectIndex() const { return xc_surfIdx; } int GetAreaIndex() const { return x8_hexIdx; } int GetWorldIndex() const { return x4_wldIdx; } float GetZDistance() const { return x0_zDist; } }; class CMapWorldData { std::string x0_label; CAssetId x10_worldAssetId; zeus::CTransform x14_transform; std::vector x44_hexagonXfs; zeus::CColor x54_surfColorSelected; zeus::CColor x58_outlineColorSelected = zeus::CColor(1.0f, 0.0f, 1.0f); zeus::CColor x5c_surfColorUnselected = zeus::CColor(1.0f, 0.0f, 1.0f); zeus::CColor x60_outlineColorUnselected = zeus::CColor(1.0f, 0.0f, 1.0f); zeus::CVector3f x64_centerPoint = zeus::skZero3f; public: explicit CMapWorldData(CInputStream& in, u32 version); CAssetId GetWorldAssetId() const { return x10_worldAssetId; } const zeus::CVector3f& GetWorldCenterPoint() const { return x64_centerPoint; } std::string_view GetWorldLabel() const { return x0_label; } const zeus::CTransform& GetWorldTransform() const { return x14_transform; } const zeus::CTransform& GetMapAreaData(s32 idx) const { return x44_hexagonXfs[idx]; } u32 GetNumMapAreaDatas() const { return x44_hexagonXfs.size(); } const zeus::CColor& GetOutlineColorUnselected() const { return x60_outlineColorUnselected; } const zeus::CColor& GetOutlineColorSelected() const { return x58_outlineColorSelected; } const zeus::CColor& GetSurfaceColorUnselected() const { return x5c_surfColorUnselected; } const zeus::CColor& GetSurfaceColorSelected() const { return x54_surfColorSelected; } }; private: CAssetId x0_hexagonId; TLockedToken x4_hexagonToken; std::vector x10_worldDatas; zeus::CVector3f x20_universeCenter = zeus::skZero3f; float x2c_universeRadius = 1600.f; public: explicit CMapUniverse(CInputStream&, u32); const CMapWorldData& GetMapWorldData(s32 idx) const { return x10_worldDatas[idx]; } const CMapWorldData& GetMapWorldDataByWorldId(CAssetId id) const { for (const CMapWorldData& data : x10_worldDatas) if (data.GetWorldAssetId() == id) return data; return x10_worldDatas.front(); } u32 GetNumMapWorldDatas() const { return x10_worldDatas.size(); } float GetMapUniverseRadius() const { return x2c_universeRadius; } const zeus::CVector3f& GetMapUniverseCenterPoint() const { return x20_universeCenter; } void Draw(const CMapUniverseDrawParms&, const zeus::CVector3f&, float, float); std::vector::const_iterator begin() const { return x10_worldDatas.cbegin(); } std::vector::const_iterator end() const { return x10_worldDatas.cend(); } }; CFactoryFnReturn FMapUniverseFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference*); } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMapWorld.cpp ================================================ #include "Runtime/AutoMapper/CMapWorld.hpp" #include #include #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/AutoMapper/CMapWorldInfo.hpp" #include "Runtime/World/CWorld.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" namespace metaforce { namespace { struct Support { int x0_; std::array x4_; }; struct Circle2 { zeus::CVector2f x0_point; float x8_radiusSq; }; struct Circle { zeus::CVector2f x0_point; float x8_radius; Circle(const Circle2& circ2) : x0_point(circ2.x0_point), x8_radius(std::sqrt(circ2.x8_radiusSq)) {} }; Circle2 ExactCircle1(const zeus::CVector2f* a) { return { .x0_point = *a, .x8_radiusSq = 0.f, }; } Circle2 ExactCircle2(const zeus::CVector2f* a, const zeus::CVector2f* b) { return { .x0_point = 0.5f * (*a + *b), .x8_radiusSq = (*b - *a).magSquared() * 0.25f, }; } Circle2 ExactCircle3(const zeus::CVector2f* a, const zeus::CVector2f* b, const zeus::CVector2f* c) { const zeus::CVector2f d1 = *b - *a; const zeus::CVector2f d2 = *c - *a; const float cross = d1.cross(d2); const zeus::CVector2f magVec(d1.magSquared() * 0.5f, d2.magSquared() * 0.5f); if (std::fabs(cross) > 0.01f) { const zeus::CVector2f tmp((d2.y() * magVec.x() - d1.y() * magVec.y()) / cross, (d1.x() * magVec.y() - d2.x() * magVec.x()) / cross); return { .x0_point = *a + tmp, .x8_radiusSq = tmp.magSquared(), }; } else { return { .x0_point = zeus::skZero2f, .x8_radiusSq = FLT_MAX, }; } } bool PointInsideCircle(const zeus::CVector2f& point, const Circle2& circ, float& intersect) { intersect = (point - circ.x0_point).magSquared() - circ.x8_radiusSq; return intersect <= 0.f; } Circle2 UpdateSupport1(int idx, const zeus::CVector2f** list, Support& support) { const Circle2 ret = ExactCircle2(list[support.x4_[0]], list[idx]); support.x0_ = 2; support.x4_[1] = idx; return ret; } Circle2 UpdateSupport2(int idx, const zeus::CVector2f** list, Support& support) { std::array circs{}; float intersect; int circIdx = -1; float minRad = FLT_MAX; circs[0] = ExactCircle2(list[support.x4_[0]], list[idx]); if (PointInsideCircle(*list[support.x4_[1]], circs[0], intersect)) { minRad = circs[0].x8_radiusSq; circIdx = 0; } circs[1] = ExactCircle2(list[support.x4_[1]], list[idx]); if (circs[1].x8_radiusSq < minRad && PointInsideCircle(*list[support.x4_[0]], circs[1], intersect)) { circIdx = 1; } Circle2 ret; if (circIdx != -1) { ret = circs[circIdx]; support.x4_[1 - circIdx] = idx; } else { ret = ExactCircle3(list[support.x4_[0]], list[support.x4_[1]], list[idx]); support.x0_ = 3; support.x4_[2] = idx; } return ret; } Circle2 UpdateSupport3(int idx, const zeus::CVector2f** list, Support& support) { std::array circs{}; float intersect; int circIdxA = -1; int circIdxB = -1; float minRadA = FLT_MAX; float minRadB = FLT_MAX; circs[0] = ExactCircle2(list[support.x4_[0]], list[idx]); if (PointInsideCircle(*list[support.x4_[1]], circs[0], intersect)) { if (PointInsideCircle(*list[support.x4_[2]], circs[0], intersect)) { minRadA = circs[0].x8_radiusSq; circIdxA = 0; } else { minRadB = intersect; circIdxB = 0; } } else { minRadB = intersect; circIdxB = 0; } circs[1] = ExactCircle2(list[support.x4_[1]], list[idx]); if (circs[1].x8_radiusSq < minRadA) { if (PointInsideCircle(*list[support.x4_[0]], circs[1], intersect)) { if (PointInsideCircle(*list[support.x4_[2]], circs[1], intersect)) { minRadA = circs[1].x8_radiusSq; circIdxA = 1; } else if (intersect < minRadB) { minRadB = intersect; circIdxB = 1; } } else if (intersect < minRadB) { minRadB = intersect; circIdxB = 1; } } circs[2] = ExactCircle2(list[support.x4_[2]], list[idx]); if (circs[2].x8_radiusSq < minRadA) { if (PointInsideCircle(*list[support.x4_[0]], circs[2], intersect)) { if (PointInsideCircle(*list[support.x4_[1]], circs[2], intersect)) { minRadA = circs[2].x8_radiusSq; circIdxA = 2; } else if (intersect < minRadB) { minRadB = intersect; circIdxB = 2; } } else if (intersect < minRadB) { minRadB = intersect; circIdxB = 2; } } circs[3] = ExactCircle3(list[support.x4_[0]], list[support.x4_[1]], list[idx]); if (circs[3].x8_radiusSq < minRadA) { if (PointInsideCircle(*list[support.x4_[2]], circs[3], intersect)) { minRadA = circs[3].x8_radiusSq; circIdxA = 3; } else if (intersect < minRadB) { minRadB = intersect; circIdxB = 3; } } circs[4] = ExactCircle3(list[support.x4_[0]], list[support.x4_[2]], list[idx]); if (circs[4].x8_radiusSq < minRadA) { if (PointInsideCircle(*list[support.x4_[1]], circs[4], intersect)) { minRadA = circs[4].x8_radiusSq; circIdxA = 4; } else if (intersect < minRadB) { minRadB = intersect; circIdxB = 4; } } circs[5] = ExactCircle3(list[support.x4_[1]], list[support.x4_[2]], list[idx]); if (circs[5].x8_radiusSq < minRadA) { if (PointInsideCircle(*list[support.x4_[0]], circs[5], intersect)) { circIdxA = 5; } else if (intersect < minRadB) { circIdxB = 5; } } if (circIdxA == -1) circIdxA = circIdxB; switch (circIdxA) { case 0: support.x0_ = 2; support.x4_[1] = idx; break; case 1: support.x0_ = 2; support.x4_[0] = idx; break; case 2: support.x0_ = 2; support.x4_[0] = support.x4_[2]; support.x4_[1] = idx; break; case 3: support.x4_[2] = idx; break; case 4: support.x4_[1] = idx; break; case 5: support.x4_[0] = idx; break; default: break; } return circs[circIdxA]; } using FSupport = Circle2 (*)(int idx, const zeus::CVector2f** list, Support& support); constexpr std::array SupportFuncs{ nullptr, UpdateSupport1, UpdateSupport2, UpdateSupport3, }; Circle MinCircle(const std::vector& coords) { Circle2 ret = {}; if (coords.size() >= 1) { std::unique_ptr randArr(new const zeus::CVector2f*[coords.size()]); for (size_t i = 0; i < coords.size(); ++i) randArr[i] = &coords[i]; for (int i = coords.size() - 1; i >= 0; --i) { int shuf = rand() % (i + 1); if (shuf != i) std::swap(randArr[i], randArr[shuf]); } ret = ExactCircle1(randArr[0]); Support support = {}; support.x0_ = 1; for (size_t i = 1; i < coords.size();) { bool broke = false; for (int j = 0; j < support.x0_; ++j) { if ((*randArr[i] - *randArr[support.x4_[j]]).magSquared() < 0.01f) { broke = true; break; } } float intersect; if (!broke && !PointInsideCircle(*randArr[i], ret, intersect)) { Circle2 circ = SupportFuncs[support.x0_](i, randArr.get(), support); if (circ.x8_radiusSq > ret.x8_radiusSq) { i = 0; ret = circ; continue; } } ++i; } } return ret; } } // Anonymous namespace CMapWorld::CMapAreaData::CMapAreaData(CAssetId areaRes, EMapAreaList list, CMapAreaData* next) : x0_area(g_SimplePool->GetObj(SObjectTag{FOURCC('MAPA'), areaRes})), x10_list(list), x14_next(next) {} CMapWorld::CMapWorld(CInputStream& in) { x10_listHeads.resize(3); in.ReadLong(); in.ReadLong(); u32 areaCount = in.ReadLong(); x0_areas.reserve(areaCount); x20_traversed.resize(areaCount); for (u32 i = 0; i < areaCount; ++i) { CAssetId mapaId = in.Get(); x0_areas.emplace_back(mapaId, EMapAreaList::Unloaded, x0_areas.empty() ? nullptr : &x0_areas.back()); } x10_listHeads[2] = &x0_areas.back(); } bool CMapWorld::IsMapAreaInBFSInfoVector(const CMapWorld::CMapAreaData* area, const std::vector& vec) const { for (const CMapWorld::CMapAreaBFSInfo& bfs : vec) { if (&x0_areas[bfs.GetAreaIndex()] == area) return true; } return false; } void CMapWorld::SetWhichMapAreasLoaded(const IWorld& wld, int start, int count) { ClearTraversedFlags(); std::vector bfsInfos; bfsInfos.reserve(x0_areas.size()); DoBFS(wld, start, count, 9999.f, 9999.f, false, bfsInfos); for (int i = 0; i < 2; ++i) { for (CMapAreaData* data = x10_listHeads[i]; data;) { CMapAreaData* nextData = data->GetNextMapAreaData(); if (!IsMapAreaInBFSInfoVector(data, bfsInfos)) { data->Unlock(); MoveMapAreaToList(data, EMapAreaList::Unloaded); } data = nextData; } } for (CMapAreaBFSInfo& bfs : bfsInfos) { CMapAreaData& data = x0_areas[bfs.GetAreaIndex()]; data.Lock(); if (data.GetContainingList() == EMapAreaList::Unloaded) MoveMapAreaToList(&data, EMapAreaList::Loading); } } bool CMapWorld::IsMapAreasStreaming() { bool ret = false; CMapAreaData* data = x10_listHeads[1]; while (data != nullptr) { if (data->IsLoaded()) { CMapAreaData* next = data->GetNextMapAreaData(); MoveMapAreaToList(data, EMapAreaList::Loaded); data = next; } else { data = data->GetNextMapAreaData(); ret = true; } } return ret; } void CMapWorld::MoveMapAreaToList(CMapWorld::CMapAreaData* data, CMapWorld::EMapAreaList list) { CMapAreaData* last = nullptr; for (CMapAreaData* head = x10_listHeads[int(data->GetContainingList())];; last = head, head = head->GetNextMapAreaData()) { if (head != data) continue; if (!last) x10_listHeads[int(data->GetContainingList())] = head->GetNextMapAreaData(); else last->SetNextMapArea(head->GetNextMapAreaData()); break; } data->SetNextMapArea(x10_listHeads[int(list)]); data->SetContainingList(list); x10_listHeads[int(list)] = data; } s32 CMapWorld::GetCurrentMapAreaDepth(const IWorld& wld, TAreaId aid) { ClearTraversedFlags(); std::vector info; info.reserve(x0_areas.size()); DoBFS(wld, aid, 9999, 9999.f, 9999.f, false, info); if (info.empty()) return 0; return info.back().GetDepth(); } std::vector CMapWorld::GetVisibleAreas(const IWorld& wld, const CMapWorldInfo& mwInfo) const { std::vector ret; ret.reserve(x0_areas.size()); for (size_t i = 0; i < x0_areas.size(); ++i) { if (!IsMapAreaValid(wld, i, true)) continue; const CMapArea* area = GetMapArea(i); bool areaVis = mwInfo.IsAreaVisible(i); bool worldVis = mwInfo.IsWorldVisible(i); if (area->GetIsVisibleToAutoMapper(worldVis, areaVis)) ret.push_back(i); } return ret; } void CMapWorld::Draw(const CMapWorldDrawParms& parms, int curArea, int otherArea, float depth1, float depth2, bool inMapScreen) { if (depth1 == 0.f && depth2 == 0.f) return; SCOPED_GRAPHICS_DEBUG_GROUP("CMapWorld::Draw", zeus::skBlue); ClearTraversedFlags(); int areaDepth = std::ceil(std::max(depth1, depth2)); std::vector bfsInfos; bfsInfos.reserve(x0_areas.size()); if (curArea != otherArea) { x20_traversed[otherArea] = true; DoBFS(parms.GetWorld(), curArea, areaDepth, depth1, depth2, true, bfsInfos); float lowD1 = std::ceil(depth1 - 1.f); float tmp; if (depth1 == std::floor(depth1)) tmp = 0.f; else tmp = 1.f - std::fmod(depth1, 1.f); float newD1 = lowD1 + tmp; float lowD2 = std::ceil(depth2 - 1.f); if (depth2 == std::floor(depth2)) tmp = 0.f; else tmp = 1.f - std::fmod(depth2, 1.f); float newD2 = lowD2 + tmp; int otherDepth = std::ceil(std::max(newD1, newD2)); if (parms.GetWorld().IGetAreaAlways(otherArea)->IIsActive()) { x20_traversed[otherArea] = false; DoBFS(parms.GetWorld(), otherArea, otherDepth, newD1, newD2, true, bfsInfos); } } else { DoBFS(parms.GetWorld(), curArea, areaDepth, depth1, depth2, true, bfsInfos); } DrawAreas(parms, curArea, bfsInfos, inMapScreen); } void CMapWorld::DoBFS(const IWorld& wld, int startArea, int areaCount, float surfDepth, float outlineDepth, bool checkLoad, std::vector& bfsInfos) { if (areaCount <= 0 || !IsMapAreaValid(wld, startArea, checkLoad)) return; size_t size = bfsInfos.size(); bfsInfos.emplace_back(startArea, 1, surfDepth, outlineDepth); x20_traversed[startArea] = true; for (; size != bfsInfos.size(); ++size) { CMapAreaBFSInfo& testInfo = bfsInfos[size]; if (testInfo.GetDepth() == areaCount) continue; surfDepth = testInfo.GetSurfaceDrawDepth() - 1.f; outlineDepth = testInfo.GetOutlineDrawDepth() - 1.f; const IGameArea* area = wld.IGetAreaAlways(testInfo.GetAreaIndex()); for (u32 i = 0; i < area->IGetNumAttachedAreas(); ++i) { TAreaId attId = area->IGetAttachedAreaId(i); if (IsMapAreaValid(wld, attId, checkLoad) && !x20_traversed[attId]) { bfsInfos.emplace_back(attId, testInfo.GetDepth() + 1, surfDepth, outlineDepth); x20_traversed[attId] = true; } } } } bool CMapWorld::IsMapAreaValid(const IWorld& wld, int areaIdx, bool checkLoad) const { if (!wld.IGetAreaAlways(areaIdx)->IIsActive()) return false; const CMapArea* mapa = GetMapArea(areaIdx); if (checkLoad) return mapa != nullptr; return true; } void CMapWorld::DrawAreas(const CMapWorldDrawParms& parms, int selArea, const std::vector& bfsInfos, bool inMapScreen) { g_Renderer->SetBlendMode_AlphaBlended(); CGraphics::SetLineWidth(1.f, ERglTexOffset::One); int surfCount = 0; int objCount = 0; for (const CMapAreaBFSInfo& bfsInfo : bfsInfos) { const CMapArea* mapa = GetMapArea(bfsInfo.GetAreaIndex()); surfCount += mapa->GetNumSurfaces(); objCount += mapa->GetNumMappableObjects(); } std::vector sortInfos; sortInfos.reserve(surfCount + objCount + (parms.GetIsSortDoorSurfaces() ? objCount * 6 : 0)); int playerArea = parms.GetStateManager().GetNextAreaId(); const CMapWorldInfo& mwInfo = parms.GetMapWorldInfo(); for (const CMapAreaBFSInfo& bfsInfo : bfsInfos) { int thisArea = bfsInfo.GetAreaIndex(); const CMapArea* mapa = GetMapArea(thisArea); if (!mapa->GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(thisArea), mwInfo.IsAreaVisible(thisArea))) continue; float surfDepth = bfsInfo.GetSurfaceDrawDepth(); float outlineDepth = bfsInfo.GetOutlineDrawDepth(); if (surfDepth >= 1.f) surfDepth = 1.f; else if (surfDepth < 0.f) surfDepth = 0.f; else surfDepth -= std::floor(surfDepth); if (outlineDepth >= 1.f) outlineDepth = 1.f; else if (outlineDepth < 0.f) outlineDepth = 0.f; else outlineDepth -= std::floor(outlineDepth); float alphaSurf; float alphaOutline; const zeus::CColor* surfaceColor; const zeus::CColor* outlineColor; const zeus::CColor* surfacePlayerColor; const zeus::CColor* outlinePlayerColor; if (mwInfo.IsAreaVisited(thisArea)) { alphaSurf = parms.GetAlphaSurfaceVisited(); alphaOutline = parms.GetAlphaOutlineVisited(); surfaceColor = &g_tweakAutoMapper->GetSurfaceVisitedColor(); outlineColor = &g_tweakAutoMapper->GetOutlineVisitedColor(); surfacePlayerColor = &g_tweakAutoMapper->GetSurfaceSelectVisitedColor(); outlinePlayerColor = &g_tweakAutoMapper->GetOutlineSelectVisitedColor(); } else { alphaSurf = parms.GetAlphaSurfaceUnvisited(); alphaOutline = parms.GetAlphaOutlineUnvisited(); surfaceColor = &g_tweakAutoMapper->GetSurfaceUnvisitedColor(); outlineColor = &g_tweakAutoMapper->GetOutlineUnvisitedColor(); surfacePlayerColor = &g_tweakAutoMapper->GetSurfaceSelectUnvisitedColor(); outlinePlayerColor = &g_tweakAutoMapper->GetOutlineSelectUnvisitedColor(); } zeus::CColor hintFlashColor = zeus::CColor::lerp(zeus::skClear, zeus::CColor{1.f, 1.f, 1.f, 0.f}, parms.GetHintAreaFlashIntensity()); zeus::CColor finalSurfColor, finalOutlineColor; if (thisArea == selArea && inMapScreen) { finalSurfColor = *surfacePlayerColor + hintFlashColor; finalOutlineColor = *outlinePlayerColor + hintFlashColor; } else { finalSurfColor = *surfaceColor; finalSurfColor.a() = surfDepth * alphaSurf; finalOutlineColor = *outlineColor; finalOutlineColor.a() = outlineDepth * alphaOutline; } if ((selArea != playerArea || parms.GetHintAreaFlashIntensity() == 0.f) && playerArea == thisArea && this == parms.GetStateManager().GetWorld()->GetMapWorld()) { float pulse = parms.GetPlayerAreaFlashIntensity(); const zeus::CColor& flashCol = g_tweakAutoMapper->GetAreaFlashPulseColor(); finalSurfColor = zeus::CColor::lerp(finalSurfColor, flashCol, pulse); finalOutlineColor = zeus::CColor::lerp(finalOutlineColor, flashCol, pulse); } zeus::CTransform modelView = parms.GetCameraTransform().inverse() * mapa->GetAreaPostTransform(parms.GetWorld(), thisArea); for (u32 i = 0; i < mapa->GetNumSurfaces(); ++i) { const CMapArea::CMapAreaSurface& surf = mapa->GetSurface(i); zeus::CVector3f pos = modelView * surf.GetCenterPosition(); sortInfos.emplace_back(pos.y(), thisArea, CMapObjectSortInfo::EObjectCode::Surface, i, finalSurfColor, finalOutlineColor); } u32 i = 0; u32 si = 0; for (; i < mapa->GetNumMappableObjects(); ++i, si += 6) { const CMappableObject& obj = mapa->GetMappableObject(i); if (!obj.GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(thisArea), mwInfo)) continue; bool doorType = CMappableObject::IsDoorType(obj.GetType()); if (doorType) { if (!mwInfo.IsAreaVisible(thisArea)) continue; if (parms.GetIsSortDoorSurfaces()) { for (u32 s = 0; s < 6; ++s) { zeus::CVector3f center = obj.BuildSurfaceCenterPoint(s); zeus::CVector3f pos = modelView * (CMapArea::GetAreaPostTranslate(parms.GetWorld(), thisArea) + center); sortInfos.emplace_back(pos.y(), thisArea, CMapObjectSortInfo::EObjectCode::DoorSurface, si + s, zeus::CColor{1.f, 0.f, 1.f, 1.f}, zeus::CColor{1.f, 0.f, 1.f, 1.f}); } continue; } } zeus::CVector3f pos = modelView * (obj.GetTransform().origin + CMapArea::GetAreaPostTranslate(parms.GetWorld(), thisArea)); sortInfos.emplace_back(pos.y(), thisArea, doorType ? CMapObjectSortInfo::EObjectCode::Door : CMapObjectSortInfo::EObjectCode::Object, i, zeus::CColor{1.f, 0.f, 1.f, 1.f}, zeus::CColor{1.f, 0.f, 1.f, 1.f}); } } if (!sortInfos.empty()) { std::sort(sortInfos.begin(), sortInfos.end(), [](const CMapObjectSortInfo& a, const CMapObjectSortInfo& b) { return a.GetZDistance() > b.GetZDistance(); }); CMapArea::CMapAreaSurface::SetupGXMaterial(); u32 lastAreaIdx = UINT32_MAX; CMapObjectSortInfo::EObjectCode lastType = CMapObjectSortInfo::EObjectCode::Invalid; for (const CMapObjectSortInfo& info : sortInfos) { CMapArea* mapa = GetMapArea(info.GetAreaIndex()); zeus::CTransform areaPostXf = mapa->GetAreaPostTransform(parms.GetWorld(), info.GetAreaIndex()); if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Surface) { CMapArea::CMapAreaSurface& surf = mapa->GetSurface(info.GetLocalObjectIndex()); zeus::CColor color( std::max(0.f, (-parms.GetCameraTransform().basis[1]).dot(areaPostXf.rotate(surf.GetNormal()))) * g_tweakAutoMapper->GetMapSurfaceNormColorLinear() + g_tweakAutoMapper->GetMapSurfaceNormColorConstant()); color *= info.GetSurfaceColor(); if (lastAreaIdx != info.GetAreaIndex() || lastType != CMapObjectSortInfo::EObjectCode::Surface) { CGraphics::SetModelMatrix(parms.GetPlaneProjectionTransform() * areaPostXf); } surf.Draw(mapa->GetVertices(), color, info.GetOutlineColor(), parms.GetOutlineWidthScale()); lastAreaIdx = info.GetAreaIndex(); lastType = info.GetObjectCode(); } } for (const CMapObjectSortInfo& info : sortInfos) { CMapArea* mapa = GetMapArea(info.GetAreaIndex()); if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Door || info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Object) { CMappableObject& mapObj = mapa->GetMappableObject(info.GetLocalObjectIndex()); const zeus::CTransform objXf = zeus::CTransform::Translate(CMapArea::GetAreaPostTranslate(parms.GetWorld(), info.GetAreaIndex())) * mapObj.GetTransform(); if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::Door) { CGraphics::SetModelMatrix(parms.GetPlaneProjectionTransform() * objXf); } else { CGraphics::SetModelMatrix( parms.GetPlaneProjectionTransform() * objXf * zeus::CTransform(parms.GetCameraTransform().buildMatrix3f() * zeus::CMatrix3f(parms.GetObjectScale()))); } mapObj.Draw(selArea, mwInfo, parms.GetAlpha(), lastType != info.GetObjectCode()); lastType = info.GetObjectCode(); } else if (info.GetObjectCode() == CMapObjectSortInfo::EObjectCode::DoorSurface) { CMappableObject& mapObj = mapa->GetMappableObject(info.GetLocalObjectIndex() / 6); const zeus::CTransform objXf = parms.GetPlaneProjectionTransform() * zeus::CTransform::Translate(CMapArea::GetAreaPostTranslate(parms.GetWorld(), info.GetAreaIndex())) * mapObj.GetTransform(); CGraphics::SetModelMatrix(objXf); mapObj.DrawDoorSurface(selArea, mwInfo, parms.GetAlpha(), info.GetLocalObjectIndex() % 6, lastType != info.GetObjectCode()); lastType = info.GetObjectCode(); } } } } void CMapWorld::RecalculateWorldSphere(const CMapWorldInfo& mwInfo, const IWorld& wld) { std::vector coords; coords.reserve(x0_areas.size() * 8); float zMin = FLT_MAX; float zMax = -FLT_MAX; for (size_t i = 0; i < x0_areas.size(); ++i) { if (IsMapAreaValid(wld, i, true)) { const CMapArea* mapa = GetMapArea(i); if (mapa->GetIsVisibleToAutoMapper(mwInfo.IsWorldVisible(i), mwInfo.IsAreaVisible(i))) { zeus::CAABox aabb = mapa->GetBoundingBox().getTransformedAABox(mapa->GetAreaPostTransform(wld, i)); for (int j = 0; j < 8; ++j) { const zeus::CVector3f point = aabb.getPoint(j); coords.push_back(point.toVec2f()); zMin = std::min(point.z(), zMin); zMax = std::max(point.z(), zMax); } } } } const Circle circle = MinCircle(coords); x3c_worldSphereRadius = circle.x8_radius; x30_worldSpherePoint = zeus::CVector3f(circle.x0_point.x(), circle.x0_point.y(), (zMin + zMax) * 0.5f); x40_worldSphereHalfDepth = (zMax - zMin) * 0.5f; } zeus::CVector3f CMapWorld::ConstrainToWorldVolume(const zeus::CVector3f& point, const zeus::CVector3f& lookVec) const { zeus::CVector3f ret = point; if (std::fabs(lookVec.z()) > FLT_EPSILON) { float f2 = point.z() - (x40_worldSphereHalfDepth + x30_worldSpherePoint.z()); float f1 = point.z() - (x30_worldSpherePoint.z() - x40_worldSphereHalfDepth); if (f2 > 0.f) ret = point + lookVec * (-f2 / lookVec.z()); else if (f1 < 0.f) ret = point + lookVec * (-f1 / lookVec.z()); } else { ret.z() = zeus::clamp(x30_worldSpherePoint.z() - x40_worldSphereHalfDepth, float(ret.z()), x40_worldSphereHalfDepth + x30_worldSpherePoint.z()); } zeus::CVector2f tmp = x30_worldSpherePoint.toVec2f(); zeus::CVector2f vec2 = point.toVec2f() - tmp; if (vec2.magnitude() > x3c_worldSphereRadius) { tmp += vec2.normalized() * x3c_worldSphereRadius; ret.x() = float(tmp.x()); ret.y() = float(tmp.y()); } return ret; } void CMapWorld::ClearTraversedFlags() { std::fill(x20_traversed.begin(), x20_traversed.end(), false); } CFactoryFnReturn FMapWorldFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMapWorld.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/AutoMapper/CMapArea.hpp" #include #include #include namespace metaforce { class CMapWorldInfo; class CStateManager; class IWorld; class CMapWorld { public: /* skDrawProfileItemNames; */ enum class EMapAreaList { Loaded, Loading, Unloaded }; class CMapAreaBFSInfo { int x0_areaIdx; int x4_depth; float x8_surfDrawDepth; float xc_outlineDrawDepth; public: CMapAreaBFSInfo(int areaIdx, int depth, float a, float b) : x0_areaIdx(areaIdx), x4_depth(depth), x8_surfDrawDepth(a), xc_outlineDrawDepth(b) {} int GetAreaIndex() const { return x0_areaIdx; } int GetDepth() const { return x4_depth; } float GetOutlineDrawDepth() const { return x8_surfDrawDepth; } float GetSurfaceDrawDepth() const { return xc_outlineDrawDepth; } }; class CMapObjectSortInfo { float x0_zDist; int x4_areaIdx; int x8_typeAndIdx; zeus::CColor xc_surfColor; zeus::CColor x10_outlineColor; public: enum class EObjectCode { Invalid = -1, Object = 1 << 16, DoorSurface = 2 << 16, Door = 3 << 16, Surface = 4 << 16 }; CMapObjectSortInfo(float zDist, int areaIdx, EObjectCode type, int idx, const zeus::CColor& surfColor, const zeus::CColor& outlineColor) : x0_zDist(zDist) , x4_areaIdx(areaIdx) , x8_typeAndIdx(int(type) | idx) , xc_surfColor(surfColor) , x10_outlineColor(outlineColor) {} const zeus::CColor& GetOutlineColor() const { return x10_outlineColor; } const zeus::CColor& GetSurfaceColor() const { return xc_surfColor; } u32 GetLocalObjectIndex() const { return x8_typeAndIdx & 0xffff; } EObjectCode GetObjectCode() const { return EObjectCode(x8_typeAndIdx & 0xffff0000); } u32 GetAreaIndex() const { return x4_areaIdx; } float GetZDistance() const { return x0_zDist; } }; class CMapAreaData { TCachedToken x0_area; EMapAreaList x10_list; CMapAreaData* x14_next = nullptr; public: CMapAreaData(CAssetId areaRes, EMapAreaList list, CMapAreaData* next); void Lock() { x0_area.Lock(); } void Unlock() { x0_area.Unlock(); } bool IsLoaded() const { return x0_area.IsLoaded(); } CMapArea* GetMapArea() { return x0_area.GetObj(); } const CMapArea* GetMapArea() const { return x0_area.GetObj(); } CMapAreaData* GetNextMapAreaData() { return x14_next; } const CMapAreaData* GetNextMapAreaData() const { return x14_next; } EMapAreaList GetContainingList() const { return x10_list; } void SetContainingList(EMapAreaList list) { x10_list = list; } void SetNextMapArea(CMapAreaData* next) { x14_next = next; } }; class CMapWorldDrawParms { float x0_alphaSurfVisited; float x4_alphaOlVisited; float x8_alphaSurfUnvisited; float xc_alphaOlUnvisited; float x10_alpha; float x14_outlineWidthScale; const CStateManager& x18_mgr; const zeus::CTransform& x1c_modelXf; const zeus::CTransform& x20_viewXf; const IWorld& x24_wld; const CMapWorldInfo& x28_mwInfo; float x2c_playerFlashIntensity; float x30_hintFlashIntensity; float x34_objectScale; bool x38_sortDoorSurfs; public: CMapWorldDrawParms(float alphaSurfVisited, float alphaOlVisited, float alphaSurfUnvisited, float alphaOlUnvisited, float alpha, float outlineWidthScale, const CStateManager& mgr, const zeus::CTransform& modelXf, const zeus::CTransform& viewXf, const IWorld& wld, const CMapWorldInfo& mwInfo, float playerFlash, float hintFlash, float objectScale, bool sortDoorSurfs) : x0_alphaSurfVisited(alphaSurfVisited) , x4_alphaOlVisited(alphaOlVisited) , x8_alphaSurfUnvisited(alphaSurfUnvisited) , xc_alphaOlUnvisited(alphaOlUnvisited) , x10_alpha(alpha) , x14_outlineWidthScale(outlineWidthScale) , x18_mgr(mgr) , x1c_modelXf(modelXf) , x20_viewXf(viewXf) , x24_wld(wld) , x28_mwInfo(mwInfo) , x2c_playerFlashIntensity(playerFlash) , x30_hintFlashIntensity(hintFlash) , x34_objectScale(objectScale) , x38_sortDoorSurfs(sortDoorSurfs) {} const IWorld& GetWorld() const { return x24_wld; } float GetOutlineWidthScale() const { return x14_outlineWidthScale; } const zeus::CTransform& GetPlaneProjectionTransform() const { return x1c_modelXf; } float GetHintAreaFlashIntensity() const { return x30_hintFlashIntensity; } float GetPlayerAreaFlashIntensity() const { return x2c_playerFlashIntensity; } const zeus::CTransform& GetCameraTransform() const { return x20_viewXf; } float GetAlphaOutlineUnvisited() const { return xc_alphaOlUnvisited; } float GetAlphaSurfaceUnvisited() const { return x8_alphaSurfUnvisited; } float GetAlphaOutlineVisited() const { return x4_alphaOlVisited; } float GetAlphaSurfaceVisited() const { return x0_alphaSurfVisited; } float GetAlpha() const { return x10_alpha; } const CMapWorldInfo& GetMapWorldInfo() const { return x28_mwInfo; } const CStateManager& GetStateManager() const { return x18_mgr; } bool GetIsSortDoorSurfaces() const { return x38_sortDoorSurfs; } float GetObjectScale() const { return x34_objectScale; } }; private: std::vector x0_areas; rstl::reserved_vector x10_listHeads; std::vector x20_traversed; zeus::CVector3f x30_worldSpherePoint; float x3c_worldSphereRadius = 0.f; float x40_worldSphereHalfDepth = 0.f; public: explicit CMapWorld(CInputStream& in); u32 GetNumAreas() const { return x0_areas.size(); } CMapArea* GetMapArea(int aid) { return x0_areas[aid].GetMapArea(); } const CMapArea* GetMapArea(int aid) const { return x0_areas[aid].GetMapArea(); } bool IsMapAreaInBFSInfoVector(const CMapAreaData* area, const std::vector& vec) const; void SetWhichMapAreasLoaded(const IWorld& wld, int start, int count); bool IsMapAreasStreaming(); void MoveMapAreaToList(CMapAreaData* data, EMapAreaList list); s32 GetCurrentMapAreaDepth(const IWorld& wld, TAreaId aid); std::vector GetVisibleAreas(const IWorld& wld, const CMapWorldInfo& mwInfo) const; void Draw(const CMapWorldDrawParms& parms, int curArea, int otherArea, float depth1, float depth2, bool inMapScreen); void DoBFS(const IWorld& wld, int startArea, int areaCount, float surfDepth, float outlineDepth, bool checkLoad, std::vector& bfsInfos); bool IsMapAreaValid(const IWorld& wld, int areaIdx, bool checkLoad) const; void DrawAreas(const CMapWorldDrawParms& parms, int selArea, const std::vector& bfsInfos, bool inMapScreen); void RecalculateWorldSphere(const CMapWorldInfo& mwInfo, const IWorld& wld); zeus::CVector3f ConstrainToWorldVolume(const zeus::CVector3f& point, const zeus::CVector3f& lookVec) const; void ClearTraversedFlags(); }; CFactoryFnReturn FMapWorldFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMapWorldInfo.cpp ================================================ #include "Runtime/AutoMapper/CMapWorldInfo.hpp" #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/GameGlobalObjects.hpp" namespace metaforce { CMapWorldInfo::CMapWorldInfo(CInputStream& reader, const CWorldSaveGameInfo& savw, CAssetId mlvlId) { const CSaveWorldMemory& worldMem = g_MemoryCardSys->GetSaveWorldMemory(mlvlId); x4_visitedAreas.reserve((worldMem.GetAreaCount() + 31) / 32); for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) { const bool visited = reader.ReadBits(1) != 0; SetAreaVisited(i, visited); } x18_mappedAreas.reserve((worldMem.GetAreaCount() + 31) / 32); for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) { const bool mapped = reader.ReadBits(1) != 0; SetIsMapped(i, mapped); } for (const auto& doorId : savw.GetDoors()) { SetDoorVisited(doorId, reader.ReadBits(1) != 0); } x38_mapStationUsed = reader.ReadBits(1) != 0; } void CMapWorldInfo::PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw, CAssetId mlvlId) const { const CSaveWorldMemory& worldMem = g_MemoryCardSys->GetSaveWorldMemory(mlvlId); for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) { if (i < x0_visitedAreasAllocated) { writer.WriteBits(u32(IsAreaVisited(i)), 1); } else { writer.WriteBits(0, 1); } } for (u32 i = 0; i < worldMem.GetAreaCount(); ++i) { if (i < x14_mappedAreasAllocated) { writer.WriteBits(u32(IsMapped(i)), 1); } else { writer.WriteBits(0, 1); } } for (const auto& doorId : savw.GetDoors()) { writer.WriteBits(u32(IsDoorVisited(doorId)), 1); } writer.WriteBits(u32(x38_mapStationUsed), 1); } void CMapWorldInfo::SetDoorVisited(TEditorId eid, bool visited) { x28_visitedDoors[eid] = visited; } bool CMapWorldInfo::IsDoorVisited(TEditorId eid) const { return x28_visitedDoors.find(eid) != x28_visitedDoors.end(); } bool CMapWorldInfo::IsAreaVisited(TAreaId aid) const { if (u32(aid) + 1 > x0_visitedAreasAllocated) { x4_visitedAreas.resize((u32(aid) + 32) / 32); x0_visitedAreasAllocated = u32(aid) + 1; } return ((x4_visitedAreas[aid / 32] >> (aid % 32)) & 1) != 0; } void CMapWorldInfo::SetAreaVisited(TAreaId aid, bool visited) { if (u32(aid) + 1 > x0_visitedAreasAllocated) { x4_visitedAreas.resize((u32(aid) + 32) / 32); x0_visitedAreasAllocated = u32(aid) + 1; } if (visited) { x4_visitedAreas[aid / 32] |= 1U << (aid % 32); } else { x4_visitedAreas[aid / 32] &= ~(1U << (aid % 32)); } } bool CMapWorldInfo::IsMapped(TAreaId aid) const { if (u32(aid) + 1 > x14_mappedAreasAllocated) { x18_mappedAreas.resize((u32(aid) + 32) / 32); x14_mappedAreasAllocated = u32(aid) + 1; } return ((x18_mappedAreas[aid / 32] >> (aid % 32)) & 1) != 0; } void CMapWorldInfo::SetIsMapped(TAreaId aid, bool mapped) { if (u32(aid) + 1 > x14_mappedAreasAllocated) { x18_mappedAreas.resize((u32(aid) + 32) / 32); x14_mappedAreasAllocated = u32(aid) + 1; } if (mapped) { x18_mappedAreas[aid / 32] |= 1U << (aid % 32); } else { x18_mappedAreas[aid / 32] &= ~(1U << (aid % 32)); } } bool CMapWorldInfo::IsWorldVisible(TAreaId aid) const { return x38_mapStationUsed || IsMapped(aid); } bool CMapWorldInfo::IsAreaVisible(TAreaId aid) const { return IsAreaVisited(aid) || IsMapped(aid); } bool CMapWorldInfo::IsAnythingSet() const { for (u32 i = 0; i < x0_visitedAreasAllocated; ++i) { if ((x4_visitedAreas[i / 32] & (1U << (i % 32))) != 0) { return true; } } for (u32 i = 0; i < x14_mappedAreasAllocated; ++i) { if ((x18_mappedAreas[i / 32] & (1U << (i % 32))) != 0) { return true; } } return x38_mapStationUsed; } } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMapWorldInfo.hpp ================================================ #pragma once #include #include #include "RetroTypes.hpp" #include "Runtime/Streams/IOStreams.hpp" namespace metaforce { class CWorldSaveGameInfo; class CMapWorldInfo { mutable u32 x0_visitedAreasAllocated = 0; mutable std::vector x4_visitedAreas; mutable u32 x14_mappedAreasAllocated = 0; mutable std::vector x18_mappedAreas; std::map x28_visitedDoors; bool x38_mapStationUsed = false; public: CMapWorldInfo() = default; explicit CMapWorldInfo(CInputStream& reader, const CWorldSaveGameInfo& saveWorld, CAssetId mlvlId); void PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw, CAssetId mlvlId) const; bool IsMapped(TAreaId aid) const; void SetIsMapped(TAreaId aid, bool mapped); void SetDoorVisited(TEditorId eid, bool val); bool IsDoorVisited(TEditorId eid) const; bool IsAreaVisited(TAreaId aid) const; void SetAreaVisited(TAreaId aid, bool visited); bool IsWorldVisible(TAreaId aid) const; bool IsAreaVisible(TAreaId aid) const; bool IsAnythingSet() const; bool GetMapStationUsed() const { return x38_mapStationUsed; } void SetMapStationUsed(bool isUsed) { x38_mapStationUsed = isUsed; } }; } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMappableObject.cpp ================================================ #include "Runtime/AutoMapper/CMappableObject.hpp" #include "Runtime/AutoMapper/CMapWorldInfo.hpp" #include "Runtime/AutoMapper/CMapArea.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CToken.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/Graphics/CTexture.hpp" namespace metaforce { using zeus::CColor; using zeus::CVector3f; using uchar = unsigned char; struct SDrawData { float x0_x; float x4_y; float x8_z; uchar xc_idxA; uchar xd_idxB; uchar xe_idxC; uchar xf_idxD; }; static const SDrawData sDrawData[6] = { // clang-format off { 0.f, 0.f, -1.f, 6, 4, 2, 0}, { 0.f, 0.f, 1.f, 3, 1, 7, 5}, { 0.f, -1.f, 1.f, 1, 0, 5, 4}, { 0.f, 1.f, 1.f, 7, 6, 3, 2}, {-1.f, 0.f, 0.f, 3, 2, 1, 0}, { 1.f, 0.f, 0.f, 5, 4, 7, 6}, // clang-format on }; static std::array, 8> skDoorVerts{}; CMappableObject::CMappableObject(const void* buf) { CMemoryInStream r(buf, 64); x0_type = EMappableObjectType(r.ReadLong()); x4_visibilityMode = EVisMode(r.ReadLong()); x8_objId = r.ReadLong(); xc_ = r.ReadLong(); x10_transform = r.Get(); } zeus::CTransform CMappableObject::AdjustTransformForType() const { const float doorCenterX = g_tweakAutoMapper->xa4_doorCenterA; const float doorCenterZ = g_tweakAutoMapper->xac_doorCenterC; if (x0_type == EMappableObjectType::BigDoor1) { zeus::CTransform orientation; orientation.origin = {0.0f, 0.0f, -1.4f * doorCenterX}; orientation.rotateLocalZ(zeus::degToRad(90.0f)); return (x10_transform * orientation) * zeus::CTransform::Scale(zeus::CVector3f{1.5f}); } else if (x0_type == EMappableObjectType::BigDoor2) { zeus::CTransform orientation; orientation.origin = {0.f, -2.0f * doorCenterZ, -1.4f * doorCenterX}; orientation.rotateLocalZ(zeus::degToRad(-90.f)); return (x10_transform * orientation) * zeus::CTransform::Scale(zeus::CVector3f{1.5f}); } else if (x0_type == EMappableObjectType::IceDoorCeiling || x0_type == EMappableObjectType::WaveDoorCeiling || x0_type == EMappableObjectType::PlasmaDoorCeiling) { zeus::CTransform orientation; orientation.origin = {-1.65f * doorCenterX, 0.f, -1.5f * doorCenterZ}; orientation.rotateLocalY(zeus::degToRad(90.f)); return x10_transform * orientation; } else if (x0_type == EMappableObjectType::IceDoorFloor || x0_type == EMappableObjectType::WaveDoorFloor || x0_type == EMappableObjectType::PlasmaDoorFloor) { zeus::CTransform orientation; orientation.origin = {-1.65f * doorCenterX, 0.f, -1.f * doorCenterZ}; orientation.rotateLocalY(zeus::degToRad(90.f)); return x10_transform * orientation; } else if ((u32(x0_type) - u32(EMappableObjectType::IceDoorFloor2)) <= u32(EMappableObjectType::ShieldDoor) || x0_type == EMappableObjectType::PlasmaDoorFloor2) { zeus::CTransform orientation; orientation.origin = {-0.49f * doorCenterX, 0.f, -1.f * doorCenterZ}; orientation.rotateLocalY(zeus::degToRad(90.f)); return x10_transform * orientation; } else if (IsDoorType(x0_type)) { return x10_transform; } return zeus::CTransform::Translate(x10_transform.origin); } std::pair CMappableObject::GetDoorColors(int curAreaId, const CMapWorldInfo& mwInfo, float alpha) const { CColor color; if (x8_objId.AreaNum() == curAreaId) { if (mwInfo.IsDoorVisited(x8_objId) && x0_type == EMappableObjectType::ShieldDoor) { color = g_tweakAutoMapper->GetDoorColor(0); } else { int colorIdx = 0; switch (x0_type) { case EMappableObjectType::ShieldDoor: colorIdx = 1; break; case EMappableObjectType::IceDoor: case EMappableObjectType::IceDoorCeiling: case EMappableObjectType::IceDoorFloor: case EMappableObjectType::IceDoorFloor2: colorIdx = 2; break; case EMappableObjectType::WaveDoor: case EMappableObjectType::WaveDoorCeiling: case EMappableObjectType::WaveDoorFloor: case EMappableObjectType::WaveDoorFloor2: colorIdx = 3; break; case EMappableObjectType::PlasmaDoor: case EMappableObjectType::PlasmaDoorCeiling: case EMappableObjectType::PlasmaDoorFloor: case EMappableObjectType::PlasmaDoorFloor2: colorIdx = 4; break; default: break; } color = g_tweakAutoMapper->GetDoorColor(colorIdx); } } else if (mwInfo.IsDoorVisited(x8_objId)) { color = g_tweakAutoMapper->GetOpenDoorColor(); } else { color = zeus::skClear; } color.a() *= alpha; return {color, CColor(std::min(1.4f * color.r(), 1.f), std::min(1.4f * color.g(), 1.f), std::min(1.4f * color.b(), 1.f), std::min(1.4f * color.a(), 1.f))}; } void CMappableObject::PostConstruct(const void*) { x10_transform = AdjustTransformForType(); } void CMappableObject::Draw(int curArea, const CMapWorldInfo& mwInfo, float alpha, bool needsVtxLoad) { SCOPED_GRAPHICS_DEBUG_GROUP("CMappableObject::Draw", zeus::skCyan); if (IsDoorType(x0_type) == true) { std::pair colors = GetDoorColors(curArea, mwInfo, alpha); for (int i = 0; i < 6; ++i) { if (needsVtxLoad) { CGX::SetArray(GX_VA_POS, skDoorVerts); } CGX::SetTevKColor(GX_KCOLOR0, colors.first); CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4); GXPosition1x8(sDrawData[i].xc_idxA); GXPosition1x8(sDrawData[i].xd_idxB); GXPosition1x8(sDrawData[i].xe_idxC); GXPosition1x8(sDrawData[i].xf_idxD); CGX::End(); CGX::SetTevKColor(GX_KCOLOR0, colors.second); CGX::Begin(GX_LINESTRIP, GX_VTXFMT0, 5); GXPosition1x8(sDrawData[i].xc_idxA); GXPosition1x8(sDrawData[i].xd_idxB); GXPosition1x8(sDrawData[i].xf_idxD); GXPosition1x8(sDrawData[i].xe_idxC); GXPosition1x8(sDrawData[i].xc_idxA); CGX::End(); } return; } CAssetId iconRes; CColor iconColor = CColor(0xffffffffu); switch (x0_type) { case EMappableObjectType::DownArrowYellow: iconColor = CColor(0xffff96ffu); iconRes = g_tweakPlayerRes->x10_minesBreakFirstTopIcon; break; case EMappableObjectType::UpArrowYellow: iconColor = CColor(0xffff96ffu); iconRes = g_tweakPlayerRes->x14_minesBreakFirstBottomIcon; break; case EMappableObjectType::DownArrowGreen: iconColor = CColor(0x64ff96ffu); iconRes = g_tweakPlayerRes->x10_minesBreakFirstTopIcon; break; case EMappableObjectType::UpArrowGreen: iconColor = CColor(0x64ff96ffu); iconRes = g_tweakPlayerRes->x14_minesBreakFirstBottomIcon; break; case EMappableObjectType::DownArrowRed: iconColor = CColor(0xff6496ffu); iconRes = g_tweakPlayerRes->x10_minesBreakFirstTopIcon; break; case EMappableObjectType::UpArrowRed: iconColor = CColor(0xff6496ffu); iconRes = g_tweakPlayerRes->x14_minesBreakFirstBottomIcon; break; case EMappableObjectType::SaveStation: iconRes = g_tweakPlayerRes->x4_saveStationIcon; break; case EMappableObjectType::MissileStation: iconRes = g_tweakPlayerRes->x8_missileStationIcon; break; default: iconRes = g_tweakPlayerRes->xc_elevatorIcon; break; } TLockedToken tex = g_SimplePool->GetObj(SObjectTag('TXTR', iconRes)); tex->Load(GX_TEXMAP0, EClampMode::Repeat); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); iconColor.a() = alpha; CGraphics::StreamColor(iconColor); CGraphics::StreamTexcoord(0.0f, 1.0f); CGraphics::StreamVertex(-2.6f, 0.0f, 2.6f); CGraphics::StreamTexcoord(0.0f, 0.0f); CGraphics::StreamVertex(-2.6f, 0.0f, -2.6f); CGraphics::StreamTexcoord(1.0f, 1.0f); CGraphics::StreamVertex(2.6f, 0.0f, 2.6f); CGraphics::StreamTexcoord(1.0f, 0.0f); CGraphics::StreamVertex(2.6f, 0.0f, -2.6f); CGraphics::StreamEnd(); // Metaforce addition: restore GX state CMapArea::CMapAreaSurface::SetupGXMaterial(); } void CMappableObject::DrawDoorSurface(int curArea, const CMapWorldInfo& mwInfo, float alpha, int surfIdx, bool needsVtxLoad) { std::pair colors = GetDoorColors(curArea, mwInfo, alpha); const SDrawData& drawData = sDrawData[surfIdx]; if (needsVtxLoad) { CGX::SetArray(GX_VA_POS, skDoorVerts); } CGX::SetTevKColor(GX_KCOLOR0, colors.first); CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4); GXPosition1x8(drawData.xc_idxA); GXPosition1x8(drawData.xd_idxB); GXPosition1x8(drawData.xe_idxC); GXPosition1x8(drawData.xf_idxD); CGX::End(); CGX::SetTevKColor(GX_KCOLOR0, colors.second); CGX::Begin(GX_LINESTRIP, GX_VTXFMT0, 5); GXPosition1x8(drawData.xc_idxA); GXPosition1x8(drawData.xd_idxB); GXPosition1x8(drawData.xf_idxD); GXPosition1x8(drawData.xe_idxC); GXPosition1x8(drawData.xc_idxA); CGX::End(); } CVector3f CMappableObject::BuildSurfaceCenterPoint(int surfaceIdx) const { const float x = g_tweakAutoMapper->xac_doorCenterC; const float y = g_tweakAutoMapper->xa8_doorCenterB; const float z = g_tweakAutoMapper->xa4_doorCenterA; switch (surfaceIdx) { case 0: return x10_transform * CVector3f{}; case 1: return x10_transform * CVector3f(0.f, 0.f, 2.f * z); case 2: return x10_transform * CVector3f(0.f, -y, 0.f); case 3: return x10_transform * CVector3f(0.f, y, 0.f); case 4: return x10_transform * CVector3f(-x, 0.f, 0.f); case 5: return x10_transform * CVector3f(x, 0.f, 0.f); default: return CVector3f{}; } } bool CMappableObject::GetIsVisibleToAutoMapper(bool worldVis, const CMapWorldInfo& mwInfo) const { bool areaVis = mwInfo.IsAreaVisible(x8_objId.AreaNum()); switch (x4_visibilityMode) { case EVisMode::Always: default: return true; case EVisMode::MapStationOrVisit: case EVisMode::MapStationOrVisit2: return worldVis || areaVis; case EVisMode::Visit: if (IsDoorType(x0_type)) { return mwInfo.IsDoorVisited(x8_objId); } return areaVis; case EVisMode::Never: return false; } } void CMappableObject::ReadAutoMapperTweaks(const ITweakAutoMapper& tweaks) { const float x = tweaks.xac_doorCenterC; const float y = tweaks.xa8_doorCenterB; const float z = tweaks.xa4_doorCenterA; skDoorVerts[0] = aurora::Vec3(-x, -y, 0.f); skDoorVerts[1] = aurora::Vec3(-x, -y, z * 2.f); skDoorVerts[2] = aurora::Vec3(-x, y, 0.f); skDoorVerts[3] = aurora::Vec3(-x, y, z * 2.f); skDoorVerts[4] = aurora::Vec3(-x * .2f, -y, 0.f); skDoorVerts[5] = aurora::Vec3(-x * .2f, -y, z * 2.f); skDoorVerts[6] = aurora::Vec3(-x * .2f, y, 0.f); skDoorVerts[7] = aurora::Vec3(-x * .2f, y, z * 2.f); } } // namespace metaforce ================================================ FILE: Runtime/AutoMapper/CMappableObject.hpp ================================================ #pragma once #include #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/RetroTypes.hpp" #include namespace metaforce { class CMapWorldInfo; class CStateManager; class CMappableObject { public: enum class EMappableObjectType { BlueDoor = 0, ShieldDoor = 1, IceDoor = 2, WaveDoor = 3, PlasmaDoor = 4, BigDoor1 = 5, BigDoor2 = 6, IceDoorCeiling = 7, IceDoorFloor = 8, WaveDoorCeiling = 9, WaveDoorFloor = 10, PlasmaDoorCeiling = 11, PlasmaDoorFloor = 12, IceDoorFloor2 = 13, WaveDoorFloor2 = 14, PlasmaDoorFloor2 = 15, DownArrowYellow = 27, /* Maintenance Tunnel */ UpArrowYellow = 28, /* Phazon Processing Center */ DownArrowGreen = 29, /* Elevator A */ UpArrowGreen = 30, /* Elite Control Access */ DownArrowRed = 31, /* Elevator B */ UpArrowRed = 32, /* Fungal Hall Access */ TransportLift = 33, SaveStation = 34, MissileStation = 37 }; enum class EVisMode { Always, MapStationOrVisit, Visit, Never, MapStationOrVisit2 }; private: EMappableObjectType x0_type; EVisMode x4_visibilityMode; TEditorId x8_objId; u32 xc_; zeus::CTransform x10_transform; zeus::CTransform AdjustTransformForType() const; std::pair GetDoorColors(int idx, const CMapWorldInfo& mwInfo, float alpha) const; public: explicit CMappableObject(const void* buf); CMappableObject(CMappableObject&&) = default; void PostConstruct(const void*); const zeus::CTransform& GetTransform() const { return x10_transform; } EMappableObjectType GetType() const { return x0_type; } void Draw(int, const CMapWorldInfo&, float, bool); void DrawDoorSurface(int curArea, const CMapWorldInfo& mwInfo, float alpha, int surfIdx, bool needsVtxLoad); zeus::CVector3f BuildSurfaceCenterPoint(int surfIdx) const; bool IsDoorConnectedToArea(int idx, const CStateManager&) const; bool IsDoorConnectedToVisitedArea(const CStateManager&) const; bool GetIsVisibleToAutoMapper(bool worldVis, const CMapWorldInfo& mwInfo) const; bool GetIsSeen() const; static void ReadAutoMapperTweaks(const ITweakAutoMapper&); static bool GetTweakIsMapVisibilityCheat(); static bool IsDoorType(EMappableObjectType type) { return type >= EMappableObjectType::BlueDoor && type <= EMappableObjectType::PlasmaDoorFloor2; } }; } // namespace metaforce ================================================ FILE: Runtime/CArchitectureMessage.hpp ================================================ #pragma once #include #include "Runtime/GCNTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Input/CFinalInput.hpp" namespace metaforce { class CIOWin; enum class EArchMsgTarget { IOWinManager = 0, Game = 1, }; enum class EArchMsgType { RemoveIOWin = 0, CreateIOWin = 1, ChangeIOWinPriority = 2, RemoveAllIOWins = 3, TimerTick = 4, UserInput = 5, SetGameState = 6, ControllerStatus = 7, QuitGameplay = 8, FrameBegin = 10, FrameEnd = 11, }; struct IArchMsgParm { virtual ~IArchMsgParm() = default; }; struct CArchMsgParmInt32 : IArchMsgParm { u32 x4_parm; CArchMsgParmInt32(u32 parm) : x4_parm(parm) {} }; struct CArchMsgParmVoidPtr : IArchMsgParm { void* x4_parm1; CArchMsgParmVoidPtr(void* parm1) : x4_parm1(parm1) {} }; struct CArchMsgParmInt32Int32VoidPtr : IArchMsgParm { u32 x4_parm1; u32 x8_parm2; void* xc_parm3; CArchMsgParmInt32Int32VoidPtr(u32 parm1, u32 parm2, void* parm3) : x4_parm1(parm1), x8_parm2(parm2), xc_parm3(parm3) {} }; struct CArchMsgParmInt32Int32IOWin : IArchMsgParm { u32 x4_parm1; u32 x8_parm2; std::shared_ptr xc_parm3; CArchMsgParmInt32Int32IOWin(u32 parm1, u32 parm2, std::shared_ptr&& parm3) : x4_parm1(parm1), x8_parm2(parm2), xc_parm3(std::move(parm3)) {} }; struct CArchMsgParmNull : IArchMsgParm {}; struct CArchMsgParmReal32 : IArchMsgParm { float x4_parm; CArchMsgParmReal32(float parm) : x4_parm(parm) {} }; struct CArchMsgParmUserInput : IArchMsgParm { CFinalInput x4_parm; CArchMsgParmUserInput(const CFinalInput& parm) : x4_parm(parm) {} }; struct CArchMsgParmControllerStatus : IArchMsgParm { u16 x4_parm1; bool x6_parm2; CArchMsgParmControllerStatus(u16 a, bool b) : x4_parm1(a), x6_parm2(b) {} }; class CArchitectureMessage { EArchMsgTarget x0_target; EArchMsgType x4_type; std::shared_ptr x8_parm; public: CArchitectureMessage(EArchMsgTarget target, EArchMsgType type, std::shared_ptr&& parm) : x0_target(target), x4_type(type), x8_parm(std::move(parm)) {} EArchMsgTarget GetTarget() const { return x0_target; } EArchMsgType GetType() const { return x4_type; } template const T* GetParm() const { return static_cast(x8_parm.get()); } }; class MakeMsg { public: static CArchitectureMessage CreateQuitGameplay(EArchMsgTarget target) { return CArchitectureMessage(target, EArchMsgType::QuitGameplay, std::make_shared()); } static CArchitectureMessage CreateControllerStatus(EArchMsgTarget target, u16 a, bool b) { return CArchitectureMessage(target, EArchMsgType::ControllerStatus, std::make_shared(a, b)); } static const CArchMsgParmInt32& GetParmNewGameflowState(const CArchitectureMessage& msg) { return *msg.GetParm(); } static const CArchMsgParmUserInput& GetParmUserInput(const CArchitectureMessage& msg) { return *msg.GetParm(); } static CArchitectureMessage CreateUserInput(EArchMsgTarget target, const CFinalInput& input) { return CArchitectureMessage(target, EArchMsgType::UserInput, std::make_shared(input)); } static const CArchMsgParmReal32& GetParmTimerTick(const CArchitectureMessage& msg) { return *msg.GetParm(); } static CArchitectureMessage CreateTimerTick(EArchMsgTarget target, float val) { return CArchitectureMessage(target, EArchMsgType::TimerTick, std::make_shared(val)); } static const CArchMsgParmInt32Int32VoidPtr& GetParmChangeIOWinPriority(const CArchitectureMessage& msg) { return *msg.GetParm(); } static const CArchMsgParmInt32Int32IOWin& GetParmCreateIOWin(const CArchitectureMessage& msg) { return *msg.GetParm(); } static CArchitectureMessage CreateCreateIOWin(EArchMsgTarget target, int pmin, int pmax, std::shared_ptr&& iowin) { return CArchitectureMessage(target, EArchMsgType::CreateIOWin, std::make_shared(pmin, pmax, std::move(iowin))); } static const CArchMsgParmVoidPtr& GetParmDeleteIOWin(const CArchitectureMessage& msg) { return *msg.GetParm(); } static CArchitectureMessage CreateFrameBegin(EArchMsgTarget target, s32 a) { return CArchitectureMessage(target, EArchMsgType::FrameBegin, std::make_shared(a)); } static CArchitectureMessage CreateFrameEnd(EArchMsgTarget target, s32 a) { return CArchitectureMessage(target, EArchMsgType::FrameEnd, std::make_shared(a)); } /* URDE Messages */ static CArchitectureMessage CreateRemoveAllIOWins(EArchMsgTarget target) { return CArchitectureMessage(target, EArchMsgType::RemoveAllIOWins, std::make_shared()); } }; } // namespace metaforce ================================================ FILE: Runtime/CArchitectureQueue.hpp ================================================ #pragma once #include #include "Runtime/CArchitectureMessage.hpp" namespace metaforce { class CArchitectureQueue { std::list m_list; public: void Push(CArchitectureMessage&& msg) { m_list.push_back(std::move(msg)); } CArchitectureMessage Pop() { CArchitectureMessage msg = std::move(m_list.front()); m_list.pop_front(); return msg; } void Clear() { m_list.clear(); } explicit operator bool() const { return !m_list.empty(); } }; } // namespace metaforce ================================================ FILE: Runtime/CBasics.hpp ================================================ #pragma once #include #include #include #include #include #ifndef _WIN32 #include #else struct _stat64; #endif #include "Runtime/GCNTypes.hpp" namespace metaforce { using OSTime = s64; struct OSCalendarTime { int x0_sec; // seconds after the minute [0, 61] int x4_min; // minutes after the hour [0, 59] int x8_hour; // hours since midnight [0, 23] int xc_mday; // day of the month [1, 31] int x10_mon; // month since January [0, 11] int x14_year; // years in AD [1, ...] int x18_wday; // days since Sunday [0, 6] int x1c_yday; // days since January 1 [0, 365] int x20_msec; // milliseconds after the second [0,999] int x24_usec; // microseconds after the millisecond [0,999] }; class CBasics { public: #if _WIN32 using Sstat = struct ::_stat64; #else using Sstat = struct stat; #endif static void Initialize(); static const u64 SECONDS_TO_2000; static const u64 TICKS_PER_SECOND; static OSTime ToWiiTime(std::chrono::system_clock::time_point time); static std::chrono::system_clock::time_point FromWiiTime(OSTime wiiTime); static OSTime GetTime() { return ToWiiTime(std::chrono::system_clock::now()); } static u64 GetGCTicks(); static constexpr u64 GetGCTicksPerSec() { return 486000000ull; } static OSCalendarTime ToCalendarTime(OSTime time) { return ToCalendarTime(FromWiiTime(time)); } static OSCalendarTime ToCalendarTime(std::chrono::system_clock::time_point time); static u16 SwapBytes(u16 v); static u32 SwapBytes(u32 v); static u64 SwapBytes(u64 v); static s16 SwapBytes(s16 v); static s32 SwapBytes(s32 v); static s64 SwapBytes(s64 v); static float SwapBytes(float v); static double SwapBytes(double s); static void Swap2Bytes(u8* v); static void Swap4Bytes(u8* v); static void Swap8Bytes(u8* v); static int RecursiveMakeDir(const char* dir); static void MakeDir(const char* dir); static bool IsDir(const char* path); static bool IsFile(const char* path); static int Stat(const char* path, Sstat* statOut); }; } // namespace metaforce ================================================ FILE: Runtime/CBasicsPC.cpp ================================================ #ifndef _WIN32 #include #include #if __APPLE__ #include #endif #endif #include #include #include #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #ifndef _WIN32_IE #define _WIN32_IE 0x0400 #endif #include #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) #define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) #endif #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) #define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) #endif #endif #include "Runtime/CBasics.hpp" #include "Runtime/CStopwatch.hpp" #include "Runtime/Logging.hpp" #if __APPLE__ static u64 MachToDolphinNum; static u64 MachToDolphinDenom; #elif _WIN32 static LARGE_INTEGER PerfFrequency; #endif namespace metaforce { void CBasics::Initialize() { CStopwatch::InitGlobalTimer(); #if __APPLE__ mach_timebase_info_data_t timebase; mach_timebase_info(&timebase); MachToDolphinNum = GetGCTicksPerSec() * timebase.numer; MachToDolphinDenom = 1000000000ull * timebase.denom; #elif _WIN32 QueryPerformanceFrequency(&PerfFrequency); #endif } u64 CBasics::GetGCTicks() { #if __APPLE__ return mach_absolute_time() * MachToDolphinNum / MachToDolphinDenom; #elif __linux__ || __FreeBSD__ struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); return u64((tp.tv_sec * 1000000000ull) + tp.tv_nsec) * GetGCTicksPerSec() / 1000000000ull; #elif _WIN32 LARGE_INTEGER perf; QueryPerformanceCounter(&perf); perf.QuadPart *= GetGCTicksPerSec(); perf.QuadPart /= PerfFrequency.QuadPart; return perf.QuadPart; #else return 0; #endif } const u64 CBasics::SECONDS_TO_2000 = 946684800LL; const u64 CBasics::TICKS_PER_SECOND = 60750000LL; static struct tm* localtime_r(const time_t& time, struct tm& timeSt, long& gmtOff) { #ifndef _WIN32 auto ret = ::localtime_r(&time, &timeSt); if (!ret) return nullptr; gmtOff = ret->tm_gmtoff; return ret; #else struct tm _gmSt; auto reta = localtime_s(&timeSt, &time); auto retb = gmtime_s(&_gmSt, &time); if (reta || retb) return nullptr; gmtOff = mktime(&timeSt) - mktime(&_gmSt); return &timeSt; #endif } OSTime CBasics::ToWiiTime(std::chrono::system_clock::time_point time) { auto sec = std::chrono::time_point_cast(time); auto us = std::chrono::duration_cast((time - sec)).count(); time_t sysTime = std::chrono::system_clock::to_time_t(sec); struct tm _timeSt; long gmtOff; struct tm* timeSt = localtime_r(sysTime, _timeSt, gmtOff); if (!timeSt) return 0; /* Returning local */ return OSTime(TICKS_PER_SECOND * ((sysTime + gmtOff) - SECONDS_TO_2000) + us * TICKS_PER_SECOND / 1000000); } std::chrono::system_clock::time_point CBasics::FromWiiTime(OSTime wiiTime) { auto div = std::lldiv(SECONDS_TO_2000 + wiiTime, TICKS_PER_SECOND); time_t time = time_t(div.quot); time_t sysTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); struct tm _timeSt; long gmtOff; struct tm* timeSt = localtime_r(sysTime, _timeSt, gmtOff); if (!timeSt) return std::chrono::system_clock::from_time_t(0); /* Returning GMT */ return std::chrono::system_clock::from_time_t(time - gmtOff) + std::chrono::microseconds(div.rem * 1000000 / TICKS_PER_SECOND); } OSCalendarTime CBasics::ToCalendarTime(std::chrono::system_clock::time_point time) { OSCalendarTime ret; auto sec = std::chrono::time_point_cast(time); auto us = std::chrono::duration_cast((time - sec)).count(); time_t sysTime = std::chrono::system_clock::to_time_t(sec); struct tm _timeSt; long gmtOff; struct tm* timeSt = localtime_r(sysTime, _timeSt, gmtOff); if (!timeSt) return {}; ret.x0_sec = timeSt->tm_sec; ret.x4_min = timeSt->tm_min; ret.x8_hour = timeSt->tm_hour; ret.xc_mday = timeSt->tm_mday; ret.x10_mon = timeSt->tm_mon; ret.x14_year = timeSt->tm_year + 1900; ret.x18_wday = timeSt->tm_wday; ret.x1c_yday = timeSt->tm_yday; auto div = std::ldiv(us, 1000); ret.x20_msec = div.quot; ret.x24_usec = div.rem; return ret; } u16 CBasics::SwapBytes(u16 v) { Swap2Bytes(reinterpret_cast(&v)); return v; } u32 CBasics::SwapBytes(u32 v) { Swap4Bytes(reinterpret_cast(&v)); return v; } u64 CBasics::SwapBytes(u64 v) { Swap8Bytes(reinterpret_cast(&v)); return v; } s16 CBasics::SwapBytes(s16 v) { Swap2Bytes(reinterpret_cast(&v)); return v; } s32 CBasics::SwapBytes(s32 v) { Swap4Bytes(reinterpret_cast(&v)); return v; } s64 CBasics::SwapBytes(s64 v) { Swap8Bytes(reinterpret_cast(&v)); return v; } float CBasics::SwapBytes(float v) { Swap4Bytes(reinterpret_cast(&v)); return v; } double CBasics::SwapBytes(double v) { Swap8Bytes(reinterpret_cast(&v)); return v; } void CBasics::Swap2Bytes(u8* v) { u16* val = reinterpret_cast(v); #if __GNUC__ *val = __builtin_bswap16(*val); #elif _WIN32 *val = _byteswap_ushort(*val); #else *val = (*val << 8) | ((*val >> 8) & 0xFF); #endif } void CBasics::Swap4Bytes(u8* v) { u32* val = reinterpret_cast(v); #if __GNUC__ *val = __builtin_bswap32(*val); #elif _WIN32 *val = _byteswap_ulong(*val); #else *val = ((*val & 0x0000FFFF) << 16) | ((*val & 0xFFFF0000) >> 16) | ((*val & 0x00FF00FF) << 8) | ((*val & 0xFF00FF00) >> 8); #endif } void CBasics::Swap8Bytes(u8* v) { u64* val = reinterpret_cast(v); #if __GNUC__ *val = __builtin_bswap64(*val); #elif _WIN32 *val = _byteswap_uint64(*val); #else *val = ((val & 0xFF00000000000000ULL) >> 56) | ((val & 0x00FF000000000000ULL) >> 40) | ((val & 0x0000FF0000000000ULL) >> 24) | ((val & 0x000000FF00000000ULL) >> 8) | ((val & 0x00000000FF000000ULL) << 8) | ((val & 0x0000000000FF0000ULL) << 24) | ((val & 0x000000000000FF00ULL) << 40) | ((val & 0x00000000000000FFULL) << 56); #endif } int CBasics::Stat(const char* path, Sstat* statOut) { #if _WIN32 size_t pos; const nowide::wstackstring wpath(path); const wchar_t* wpathP = wpath.get(); for (pos = 0; pos < 3 && wpathP[pos] != L'\0'; ++pos) {} if (pos == 2 && wpathP[1] == L':') { wchar_t fixPath[4] = {wpathP[0], L':', L'/', L'\0'}; return _wstat64(fixPath, statOut); } return _wstat64(wpath.get(), statOut); #else return stat(path, statOut); #endif } /* recursive mkdir */ int CBasics::RecursiveMakeDir(const char* dir) { #if _WIN32 char tmp[1024]; /* copy path */ std::strncpy(tmp, dir, std::size(tmp)); const size_t len = std::strlen(tmp); if (len >= std::size(tmp)) { return -1; } /* remove trailing slash */ if (tmp[len - 1] == '/' || tmp[len - 1] == '\\') { tmp[len - 1] = 0; } /* recursive mkdir */ char* p = nullptr; Sstat sb; for (p = tmp + 1; *p; p++) { if (*p == '/' || *p == '\\') { *p = 0; /* test path */ if (Stat(tmp, &sb) != 0) { /* path does not exist - create directory */ const nowide::wstackstring wtmp(tmp); if (!CreateDirectoryW(wtmp.get(), nullptr)) { return -1; } } else if (!S_ISDIR(sb.st_mode)) { /* not a directory */ return -1; } *p = '/'; } } /* test path */ if (Stat(tmp, &sb) != 0) { /* path does not exist - create directory */ const nowide::wstackstring wtmp(tmp); if (!CreateDirectoryW(wtmp.get(), nullptr)) { return -1; } } else if (!S_ISDIR(sb.st_mode)) { /* not a directory */ return -1; } return 0; #else char tmp[1024]; /* copy path */ std::memset(tmp, 0, std::size(tmp)); std::strncpy(tmp, dir, std::size(tmp) - 1); const size_t len = std::strlen(tmp); if (len >= std::size(tmp)) { return -1; } /* remove trailing slash */ if (tmp[len - 1] == '/') { tmp[len - 1] = 0; } /* recursive mkdir */ char* p = nullptr; Sstat sb; for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = 0; /* test path */ if (Stat(tmp, &sb) != 0) { /* path does not exist - create directory */ if (mkdir(tmp, 0755) < 0) { return -1; } } else if (!S_ISDIR(sb.st_mode)) { /* not a directory */ return -1; } *p = '/'; } } /* test path */ if (Stat(tmp, &sb) != 0) { /* path does not exist - create directory */ if (mkdir(tmp, 0755) < 0) { return -1; } } else if (!S_ISDIR(sb.st_mode)) { /* not a directory */ return -1; } return 0; #endif } void CBasics::MakeDir(const char* dir) { #if _WIN32 HRESULT err; const nowide::wstackstring wdir(dir); if (!CreateDirectoryW(wdir.get(), NULL)) if ((err = GetLastError()) != ERROR_ALREADY_EXISTS) spdlog::fatal("MakeDir({})", dir); #else if (mkdir(dir, 0755)) if (errno != EEXIST) spdlog::fatal("MakeDir({}): {}", dir, strerror(errno)); #endif } bool CBasics::IsDir(const char* path) { Sstat theStat; Stat(path, &theStat); return S_ISDIR(theStat.st_mode); } bool CBasics::IsFile(const char* path) { Sstat theStat; Stat(path, &theStat); return S_ISREG(theStat.st_mode); } } // namespace metaforce ================================================ FILE: Runtime/CCRC32.cpp ================================================ #include "Runtime/CCRC32.hpp" #include namespace metaforce { namespace { constexpr std::array crc32Table{ 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, }; constexpr uint32_t permute(uint32_t checksum, uint8_t b) { return (checksum >> 8) ^ crc32Table[(checksum & 0xFF) ^ b]; } } // Anonymous namespace uint32_t CCRC32::Calculate(const void* data, uint32_t length) { if (data == nullptr || length == 0) { return 0; } uint32_t checksum = 0xFFFFFFFF; const uint8_t* buf = static_cast(data); uint32_t words = length / 4; while ((words--) > 0) { checksum = permute(checksum, *buf++); checksum = permute(checksum, *buf++); checksum = permute(checksum, *buf++); checksum = permute(checksum, *buf++); } uint32_t rem = length % 4; while ((rem--) > 0) { checksum = permute(checksum, *buf++); } return checksum; } } // namespace metaforce ================================================ FILE: Runtime/CCRC32.hpp ================================================ #pragma once #include namespace metaforce { class CCRC32 { public: static uint32_t Calculate(const void* data, uint32_t length); }; } // namespace metaforce ================================================ FILE: Runtime/CDependencyGroup.cpp ================================================ #include "Runtime/CDependencyGroup.hpp" #include "Runtime/CToken.hpp" namespace metaforce { CDependencyGroup::CDependencyGroup(CInputStream& in) { ReadFromStream(in); } void CDependencyGroup::ReadFromStream(CInputStream& in) { u32 depCount = in.ReadLong(); x0_objectTags.reserve(depCount); for (u32 i = 0; i < depCount; i++) x0_objectTags.emplace_back(in); } CFactoryFnReturn FDependencyGroupFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in, [[maybe_unused]] const CVParamTransfer& param, [[maybe_unused]] CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/CDependencyGroup.hpp ================================================ #pragma once #include #include "Runtime/CFactoryMgr.hpp" namespace metaforce { class CDependencyGroup { std::vector x0_objectTags; public: explicit CDependencyGroup(CInputStream& in); void ReadFromStream(CInputStream& in); const std::vector& GetObjectTagVector() const { return x0_objectTags; } }; CFactoryFnReturn FDependencyGroupFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/CDvdFile.cpp ================================================ #include "Runtime/CDvdFile.hpp" // #include #include #include #include #include #include #include "Runtime/CDvdRequest.hpp" #include "Runtime/Logging.hpp" #include "Runtime/CStopwatch.hpp" namespace metaforce { namespace { struct SDLDiscStreamCtx { SDL_IOStream* io = nullptr; }; int64_t sdlStreamReadAt(void* userData, uint64_t offset, void* out, size_t len) { auto* ctx = static_cast(userData); if (ctx == nullptr || ctx->io == nullptr || offset > uint64_t(std::numeric_limits::max())) { return -1; } if (SDL_SeekIO(ctx->io, static_cast(offset), SDL_IO_SEEK_SET) < 0) { return -1; } size_t total = 0; auto* dst = static_cast(out); while (total < len) { const size_t read = SDL_ReadIO(ctx->io, dst + total, len - total); if (read == 0) { break; } total += read; } return static_cast(total); } int64_t sdlStreamLen(void* userData) { auto* ctx = static_cast(userData); if (ctx == nullptr || ctx->io == nullptr) { return -1; } const Sint64 size = SDL_GetIOSize(ctx->io); return size < 0 ? -1 : static_cast(size); } void sdlStreamClose(void* userData) { auto* ctx = static_cast(userData); if (ctx == nullptr) { return; } if (ctx->io != nullptr) { SDL_CloseIO(ctx->io); } delete ctx; } u32 nodReadLoop(NodHandle* reader, void* buf, u32 len) { if (reader == nullptr || buf == nullptr || len == 0) { return 0; } auto* out = static_cast(buf); u32 totalRead = 0; while (totalRead < len) { const u32 remaining = len - totalRead; const int64_t read = nod_read(reader, out + totalRead, remaining); if (read <= 0) { break; } if (read > int64_t(remaining)) { totalRead = len; break; } totalRead += u32(read); } return totalRead; } } // namespace CDvdFile::NodHandleUnique CDvdFile::m_DvdRoot{nullptr, nod_free}; CDvdFile::NodHandleUnique CDvdFile::m_DataPartition{nullptr, nod_free}; std::unordered_map CDvdFile::m_FileEntries; class CFileDvdRequest : public IDvdRequest { std::shared_ptr m_reader; uint64_t m_begin; uint64_t m_size; void* m_buf; u32 m_len; ESeekOrigin m_whence; int m_offset; bool m_cancel = false; bool m_complete = false; std::function m_callback; public: ~CFileDvdRequest() override { CFileDvdRequest::PostCancelRequest(); } void WaitUntilComplete() override { if (!m_complete && !m_cancel) { CDvdFile::DoWork(); } } bool IsComplete() override { if (!m_complete) { CDvdFile::DoWork(); } return m_complete; } void PostCancelRequest() override { m_cancel = true; } [[nodiscard]] EMediaType GetMediaType() const override { return EMediaType::File; } CFileDvdRequest(CDvdFile& file, void* buf, u32 len, ESeekOrigin whence, int off, std::function&& cb) : m_reader(file.m_reader) , m_begin(file.m_begin) , m_size(file.m_size) , m_buf(buf) , m_len(len) , m_whence(whence) , m_offset(off) , m_callback(std::move(cb)) {} void DoRequest() { if (m_cancel) { return; } if (!m_reader) { m_complete = true; if (m_callback) { m_callback(0); } return; } u32 readLen = 0; if (m_whence == ESeekOrigin::Cur && m_offset == 0) { readLen = nodReadLoop(m_reader.get(), m_buf, m_len); } else { int seek = 0; int64_t offset = m_offset; switch (m_whence) { case ESeekOrigin::Begin: { seek = 0; offset += int64_t(m_begin); break; } case ESeekOrigin::End: { seek = 0; offset += int64_t(m_begin) + int64_t(m_size); break; } case ESeekOrigin::Cur: { seek = 1; break; } }; if (nod_seek(m_reader.get(), offset, seek) >= 0) { readLen = nodReadLoop(m_reader.get(), m_buf, m_len); } } if (m_callback) { m_callback(readLen); } m_complete = true; } }; std::vector> CDvdFile::m_RequestQueue; std::string CDvdFile::m_rootDirectory; std::string CDvdFile::m_lastError; std::unique_ptr CDvdFile::m_dolBuf; size_t CDvdFile::m_dolBufLen = 0; CDvdFile::CDvdFile(std::string_view path) : x18_path(path) { const SFileEntry* entry = ResolvePath(path); if (entry == nullptr) { return; } NodHandle* fileRaw = nullptr; if (nod_partition_open_file(m_DataPartition.get(), entry->fstIndex, &fileRaw) == NOD_RESULT_OK && fileRaw != nullptr) { m_reader = std::shared_ptr(fileRaw, nod_free); m_size = entry->size; } } // single-threaded hack void CDvdFile::DoWork() { for (std::shared_ptr& req : m_RequestQueue) { auto& concreteReq = static_cast(*req); concreteReq.DoRequest(); } m_RequestQueue.clear(); } std::shared_ptr CDvdFile::AsyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int off, std::function&& cb) { std::shared_ptr ret = std::make_shared(*this, buf, len, whence, off, std::move(cb)); m_RequestQueue.emplace_back(ret); return ret; } u32 CDvdFile::SyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int offset) { if (!m_reader) { return 0; } int seek = 0; int64_t seekOffset = offset; switch (whence) { case ESeekOrigin::Begin: { seek = 0; seekOffset += int64_t(m_begin); break; } case ESeekOrigin::End: { seek = 0; seekOffset += int64_t(m_begin) + int64_t(m_size); break; } case ESeekOrigin::Cur: { seek = 1; break; } }; if (nod_seek(m_reader.get(), seekOffset, seek) < 0) { return 0; } return nodReadLoop(m_reader.get(), buf, len); } u32 CDvdFile::SyncRead(void* buf, u32 len) { if (!m_reader) { return 0; } return nodReadLoop(m_reader.get(), buf, len); } std::string CDvdFile::NormalizePath(std::string_view path) { std::string out; out.reserve(path.size()); bool prevSlash = false; for (char c : path) { if (c == '/' || c == '\\') { if (!out.empty() && !prevSlash) { out.push_back('/'); } prevSlash = true; continue; } out.push_back(static_cast(std::tolower(static_cast(c)))); prevSlash = false; } if (!out.empty() && out.back() == '/') { out.pop_back(); } return out; } const CDvdFile::SFileEntry* CDvdFile::ResolvePath(std::string_view path) { if (!m_DataPartition) { return nullptr; } std::string normalizedPath = NormalizePath(path); if (normalizedPath.empty()) { return nullptr; } if (!m_rootDirectory.empty()) { normalizedPath = m_rootDirectory + "/" + normalizedPath; } const auto search = m_FileEntries.find(normalizedPath); return search != m_FileEntries.end() ? &search->second : nullptr; } bool CDvdFile::BuildFileEntries() { if (!m_DataPartition) { return false; } m_FileEntries.clear(); struct SDirFrame { u32 endIndex = 0; std::string path; }; struct SFstBuildContext { std::unordered_map* fileEntries = nullptr; std::vector dirStack; } ctx{&m_FileEntries, {}}; nod_partition_iterate_fst( m_DataPartition.get(), [](u32 index, NodNodeKind kind, const char* name, u32 size, void* userData) -> u32 { auto* ctx = static_cast(userData); while (!ctx->dirStack.empty() && index >= ctx->dirStack.back().endIndex) { ctx->dirStack.pop_back(); } const std::string nodeName = CDvdFile::NormalizePath(name != nullptr ? std::string_view{name} : std::string_view{}); if (nodeName.empty()) { return index + 1; } std::string fullPath; if (!ctx->dirStack.empty()) { fullPath = ctx->dirStack.back().path; fullPath += '/'; fullPath += nodeName; } else { fullPath = nodeName; } if (kind == NOD_NODE_KIND_FILE) { ctx->fileEntries->insert_or_assign(fullPath, CDvdFile::SFileEntry{index, size}); } else { ctx->dirStack.push_back({size, std::move(fullPath)}); } return index + 1; }, &ctx); return !m_FileEntries.empty(); } bool CDvdFile::LoadDolBuf() { if (!m_DataPartition) { return false; } NodPartitionMeta meta{}; if (nod_partition_meta(m_DataPartition.get(), &meta) != NOD_RESULT_OK || meta.raw_dol.data == nullptr || meta.raw_dol.size == 0) { return false; } auto dolBuf = std::make_unique(meta.raw_dol.size); std::memcpy(dolBuf.get(), meta.raw_dol.data, meta.raw_dol.size); m_dolBuf = std::move(dolBuf); m_dolBufLen = meta.raw_dol.size; return true; } bool CDvdFile::Initialize(const std::string_view& path) { Shutdown(); m_lastError.clear(); std::string pathStr(path); SDL_IOStream* io = SDL_IOFromFile(pathStr.c_str(), "rb"); if (io == nullptr) { m_lastError = std::string{"SDL_IOFromFile failed: "} + SDL_GetError(); spdlog::error("{} (path: '{}')", m_lastError, pathStr); return false; } auto* streamCtx = new (std::nothrow) SDLDiscStreamCtx{io}; if (streamCtx == nullptr) { SDL_CloseIO(io); m_lastError = "Failed to allocate SDL disc stream context"; spdlog::error("{} (path: '{}')", m_lastError, pathStr); return false; } NodHandle* discRaw = nullptr; const NodDiscOptions discOpts{ .preloader_threads = 1, }; const NodDiscStream stream{ .user_data = streamCtx, .read_at = sdlStreamReadAt, .stream_len = sdlStreamLen, .close = sdlStreamClose, }; const NodResult discResult = nod_disc_open_stream(&stream, &discOpts, &discRaw); if (discResult != NOD_RESULT_OK || discRaw == nullptr) { const char* nodError = nod_error_message(); m_lastError = fmt::format("nod_disc_open_stream failed ({}){}", int(discResult), nodError != nullptr && nodError[0] != '\0' ? fmt::format(": {}", nodError) : ""); spdlog::error("{} (path: '{}')", m_lastError, pathStr); // Ownership of streamCtx is transferred to nod_disc_open_stream. // FfiDiscStream drops and invokes close() on failure paths. return false; } m_DvdRoot = NodHandleUnique(discRaw, nod_free); NodHandle* partitionRaw = nullptr; const NodResult partitionResult = nod_disc_open_partition_kind(m_DvdRoot.get(), NOD_PARTITION_KIND_DATA, nullptr, &partitionRaw); if (partitionResult != NOD_RESULT_OK || partitionRaw == nullptr) { const char* nodError = nod_error_message(); m_lastError = fmt::format("nod_disc_open_partition_kind(data) failed ({}){}", int(partitionResult), nodError != nullptr && nodError[0] != '\0' ? fmt::format(": {}", nodError) : ""); spdlog::error("{} (path: '{}')", m_lastError, pathStr); Shutdown(); return false; } m_DataPartition = NodHandleUnique(partitionRaw, nod_free); if (!BuildFileEntries()) { m_lastError = "Failed to read disc file-system table"; spdlog::error("{} (path: '{}')", m_lastError, pathStr); Shutdown(); return false; } if (!LoadDolBuf()) { m_lastError = "Failed to load raw DOL data from disc"; spdlog::error("{} (path: '{}')", m_lastError, pathStr); Shutdown(); return false; } return true; } void CDvdFile::Shutdown() { m_RequestQueue.clear(); m_FileEntries.clear(); m_dolBuf.reset(); m_dolBufLen = 0; m_DataPartition.reset(); m_DvdRoot.reset(); } SDiscInfo CDvdFile::DiscInfo() { SDiscInfo out{}; if (!m_DvdRoot) { return out; } NodDiscHeader header{}; if (nod_disc_header(m_DvdRoot.get(), &header) != NOD_RESULT_OK) { return out; } std::memcpy(out.gameId.data(), header.game_id, sizeof(header.game_id)); out.version = header.disc_version; const char* titleBegin = header.game_title; const char* titleEnd = std::find(titleBegin, titleBegin + sizeof(header.game_title), '\0'); out.gameTitle.assign(titleBegin, titleEnd); return out; } void CDvdFile::SetRootDirectory(const std::string_view& rootDir) { m_rootDirectory = NormalizePath(rootDir); } } // namespace metaforce ================================================ FILE: Runtime/CDvdFile.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/RetroTypes.hpp" #include namespace metaforce { enum class ESeekOrigin { Begin = 0, Cur = 1, End = 2 }; struct DVDFileInfo; class IDvdRequest; struct SDiscInfo { std::array gameId; uint8_t version; std::string gameTitle; }; class CDvdFile { friend class CResLoader; friend class CFileDvdRequest; using NodHandleUnique = std::unique_ptr; struct SFileEntry { u32 fstIndex = NOD_FST_STOP; u32 size = 0; }; static NodHandleUnique m_DvdRoot; static NodHandleUnique m_DataPartition; static std::unordered_map m_FileEntries; static std::vector> m_RequestQueue; static std::string m_rootDirectory; static std::string m_lastError; static std::unique_ptr m_dolBuf; static size_t m_dolBufLen; std::string x18_path; std::shared_ptr m_reader; uint64_t m_begin = 0; uint64_t m_size = 0; static std::string NormalizePath(std::string_view path); static const SFileEntry* ResolvePath(std::string_view path); static bool BuildFileEntries(); static bool LoadDolBuf(); public: static bool Initialize(const std::string_view& path); static std::string_view GetLastError() { return m_lastError; } static SDiscInfo DiscInfo(); static void SetRootDirectory(const std::string_view& rootDir); static void Shutdown(); static u8* GetDolBuf() { return m_dolBuf.get(); } static size_t GetDolBufLen() { return m_dolBufLen; } static void DoWork(); CDvdFile(std::string_view path); operator bool() const { return m_reader.operator bool(); } void UpdateFilePos(int pos) { if (m_reader) { nod_seek(m_reader.get(), pos, 0); } } static bool FileExists(std::string_view path) { return ResolvePath(path) != nullptr; } void CloseFile() { m_reader.reset(); } std::shared_ptr AsyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int off, std::function&& cb = {}); u32 SyncSeekRead(void* buf, u32 len, ESeekOrigin whence, int offset); std::shared_ptr AsyncRead(void* buf, u32 len, std::function&& cb = {}) { return AsyncSeekRead(buf, len, ESeekOrigin::Cur, 0, std::move(cb)); } u32 SyncRead(void* buf, u32 len); u64 Length() const { return m_size; } std::string_view GetPath() const { return x18_path; } }; } // namespace metaforce ================================================ FILE: Runtime/CDvdRequest.hpp ================================================ #pragma once namespace metaforce { class IDvdRequest { public: virtual ~IDvdRequest() = default; virtual void WaitUntilComplete() = 0; virtual bool IsComplete() = 0; virtual void PostCancelRequest() = 0; enum class EMediaType { ARAM = 0, Real = 1, File = 2, NOD = 3 }; virtual EMediaType GetMediaType() const = 0; }; } // namespace metaforce ================================================ FILE: Runtime/CFactoryMgr.cpp ================================================ #include "Runtime/CFactoryMgr.hpp" #include #include #include #include //#include "optick.h" #include "Runtime/CStopwatch.hpp" #include "Runtime/IObj.hpp" namespace metaforce { constexpr std::array TypeTable{ FOURCC('CLSN'), FOURCC('CMDL'), FOURCC('CSKR'), FOURCC('ANIM'), FOURCC('CINF'), FOURCC('TXTR'), FOURCC('PLTT'), FOURCC('FONT'), FOURCC('ANCS'), FOURCC('EVNT'), FOURCC('MADF'), FOURCC('MLVL'), FOURCC('MREA'), FOURCC('MAPW'), FOURCC('MAPA'), FOURCC('SAVW'), FOURCC('SAVA'), FOURCC('PART'), FOURCC('WPSC'), FOURCC('SWHC'), FOURCC('DPSC'), FOURCC('ELSC'), FOURCC('CRSC'), FOURCC('AFSM'), FOURCC('DCLN'), FOURCC('AGSC'), FOURCC('ATBL'), FOURCC('CSNG'), FOURCC('STRG'), FOURCC('SCAN'), FOURCC('PATH'), FOURCC('DGRP'), FOURCC('HMAP'), FOURCC('CTWK'), FOURCC('FRME'), FOURCC('HINT'), FOURCC('MAPU'), FOURCC('DUMB'), FOURCC('OIDS'), }; CFactoryFnReturn CFactoryMgr::MakeObject(const SObjectTag& tag, metaforce::CInputStream& in, const CVParamTransfer& paramXfer, CObjectReference* selfRef) { auto search = x10_factories.find(tag.type); if (search == x10_factories.end()) return {}; return search->second(tag, in, paramXfer, selfRef); } bool CFactoryMgr::CanMakeMemory(const metaforce::SObjectTag& tag) const { auto search = x24_memFactories.find(tag.type); return search != x24_memFactories.cend(); } CFactoryFnReturn CFactoryMgr::MakeObjectFromMemory(const SObjectTag& tag, std::unique_ptr&& buf, int size, bool compressed, const CVParamTransfer& paramXfer, CObjectReference* selfRef) { //OPTICK_EVENT(); std::unique_ptr localBuf = std::move(buf); const auto memFactoryIter = x24_memFactories.find(tag.type); if (memFactoryIter != x24_memFactories.cend()) { if (compressed) { std::unique_ptr compRead = std::make_unique(localBuf.get(), size, CMemoryInStream::EOwnerShip::NotOwned); const u32 decompLen = compRead->ReadLong(); CZipInputStream r(std::move(compRead)); std::unique_ptr decompBuf(new u8[decompLen]); r.Get(decompBuf.get(), decompLen); return memFactoryIter->second(tag, std::move(decompBuf), decompLen, paramXfer, selfRef); } else { return memFactoryIter->second(tag, std::move(localBuf), size, paramXfer, selfRef); } } else { const auto factoryIter = x10_factories.find(tag.type); if (factoryIter == x10_factories.end()) { return {}; } if (compressed) { std::unique_ptr compRead = std::make_unique(localBuf.get(), size, CMemoryInStream::EOwnerShip::NotOwned); compRead->ReadLong(); CZipInputStream r(std::move(compRead)); return factoryIter->second(tag, r, paramXfer, selfRef); } else { CMemoryInStream r(localBuf.get(), size, CMemoryInStream::EOwnerShip::NotOwned); return factoryIter->second(tag, r, paramXfer, selfRef); } } } CFactoryMgr::ETypeTable CFactoryMgr::FourCCToTypeIdx(FourCC fcc) { for (size_t i = 0; i < 4; ++i) { fcc.getChars()[i] = char(std::toupper(fcc.getChars()[i])); } const auto search = std::find_if(TypeTable.cbegin(), TypeTable.cend(), [fcc](const FourCC& test) { return test == fcc; }); if (search == TypeTable.cend()) { return ETypeTable::Invalid; } return ETypeTable(std::distance(TypeTable.cbegin(), search)); } FourCC CFactoryMgr::TypeIdxToFourCC(ETypeTable fcc) { return TypeTable[size_t(fcc)]; } } // namespace metaforce ================================================ FILE: Runtime/CFactoryMgr.hpp ================================================ #pragma once #include #include "Runtime/IFactory.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { struct SObjectTag; class CVParamTransfer; class IObj; class CFactoryMgr { std::unordered_map x10_factories; std::unordered_map x24_memFactories; public: CFactoryFnReturn MakeObject(const SObjectTag& tag, metaforce::CInputStream& in, const CVParamTransfer& paramXfer, CObjectReference* selfRef); bool CanMakeMemory(const metaforce::SObjectTag& tag) const; CFactoryFnReturn MakeObjectFromMemory(const SObjectTag& tag, std::unique_ptr&& buf, int size, bool compressed, const CVParamTransfer& paramXfer, CObjectReference* selfRef); void AddFactory(FourCC key, FFactoryFunc func) { x10_factories.insert_or_assign(key, std::move(func)); } void AddFactory(FourCC key, FMemFactoryFunc func) { x24_memFactories.insert_or_assign(key, std::move(func)); } enum class ETypeTable : u8 { CLSN, CMDL, CSKR, ANIM, CINF, TXTR, PLTT, FONT, ANCS, EVNT, MADF, MLVL, MREA, MAPW, MAPA, SAVW, SAVA, PART, WPSC, SWHC, DPSC, ELSC, CRSC, AFSM, DCLN, AGSC, ATBL, CSNG, STRG, SCAN, PATH, DGRP, HMAP, CTWK, FRME, HINT, MAPU, DUMB, OIDS, Invalid = 127 }; static ETypeTable FourCCToTypeIdx(FourCC fcc); static FourCC TypeIdxToFourCC(ETypeTable fcc); }; } // namespace metaforce ================================================ FILE: Runtime/CGameAllocator.cpp ================================================ #include "Runtime/CGameAllocator.hpp" #include "Runtime/Logging.hpp" namespace metaforce { #pragma GCC diagnostic ignored "-Wclass-memaccess" std::vector CGameAllocator::m_allocations; u8* CGameAllocator::Alloc(size_t len) { size_t roundedLen = ROUND_UP_64(len + sizeof(SChunkDescription)); for (SAllocationDescription& alloc : m_allocations) { /* We need to supply enough room for allocation information */ if (alloc.freeOffset + roundedLen < alloc.allocSize) { u8* ptr = alloc.memptr.get() + alloc.freeOffset; SChunkDescription* chunkInfo = reinterpret_cast(ptr); *chunkInfo = SChunkDescription(); chunkInfo->parent = &alloc; chunkInfo->len = len; alloc.freeOffset += roundedLen; return ptr + sizeof(SChunkDescription); } } /* 1MiB minimum allocation to prevent constantly allocating small amounts of memory */ size_t allocSz = len; if (allocSz < (1 * 1024 * 1024 * 1024)) allocSz = 1 * 1024 * 1024 * 1024; /* Pad size to allow for allocation information */ allocSz = ROUND_UP_64(allocSz + sizeof(SChunkDescription)); auto& alloc = m_allocations.emplace_back(); alloc.memptr.reset(new u8[allocSz]); u8* ptr = alloc.memptr.get(); alloc.allocSize = allocSz; alloc.freeOffset += roundedLen; SChunkDescription* chunkInfo = reinterpret_cast(ptr); *chunkInfo = SChunkDescription(); chunkInfo->parent = &alloc; chunkInfo->len = len; return ptr + sizeof(SChunkDescription); } void CGameAllocator::Free(u8* ptr) { SChunkDescription* info = reinterpret_cast(ptr - sizeof(SChunkDescription)); if (info->magic != 0xE8E8E8E8 || info->sentinal != 0xEFEFEFEF) { spdlog::fatal("Invalid chunk description, memory corruption!"); } SAllocationDescription& alloc = *info->parent; size_t roundedLen = ROUND_UP_32(info->len + sizeof(SChunkDescription)); alloc.freeOffset -= roundedLen; /* Invalidate chunk allocation descriptor */ memset(info, 0, ROUND_UP_64(info->len + sizeof(SChunkDescription))); } } // namespace metaforce ================================================ FILE: Runtime/CGameAllocator.hpp ================================================ #pragma once #include #include #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class CGameAllocator { struct SAllocationDescription { std::unique_ptr memptr; size_t allocSize = 0; ptrdiff_t freeOffset = 0; }; struct SChunkDescription { u32 magic = 0xE8E8E8E8; SAllocationDescription* parent; size_t len = 0; u32 sentinal = 0xEFEFEFEF; }; static std::vector m_allocations; public: static u8* Alloc(size_t len); static void Free(u8* ptr); }; } // namespace metaforce ================================================ FILE: Runtime/CGameDebug.hpp ================================================ #pragma once #include namespace metaforce { struct CFinalInput; const char* StringForControlOption(int); enum class EDebugMenu {}; enum class EDebugOptions {}; enum class EDebugMainMenu {}; class CDebugOption { public: CDebugOption(EDebugMenu, EDebugOptions, const std::string&, bool); CDebugOption(EDebugMenu, EDebugOptions, const std::string&, float, float, float, float); }; class CGameDebug { public: enum class EReturnValue {}; void DeactivateMenu(); void AddDebugOption(EDebugMenu, EDebugOptions, const char*, bool); void AddDebugOption(EDebugMenu, EDebugOptions, const char*, float, float, float, float); void SetCaptureMovieTimeLeft(float); const std::string& GetCaptureMovieName(); void SetCaptureMovieName(const std::string&); void AddDebugOptions(); void CopyDebugToTweaks(); void CopyTweaksToDebug(); void ProcessControllerInput(const CFinalInput&); void Update(float); void Draw(void) const; void ActivateMenu(EDebugMainMenu, int); void AddDebugOption(const CDebugOption&); }; } // namespace metaforce ================================================ FILE: Runtime/CGameHintInfo.cpp ================================================ #include "Runtime/CGameHintInfo.hpp" #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/CToken.hpp" #include "Runtime/GameGlobalObjects.hpp" namespace metaforce { CGameHintInfo::CGameHintInfo(CInputStream& in, s32 version) { u32 hintCount = in.ReadLong(); x0_hints.reserve(hintCount); for (u32 i = 0; i < hintCount; ++i) x0_hints.emplace_back(in, version); } CGameHintInfo::CGameHint::CGameHint(CInputStream& in, s32 version) : x0_name(in.Get()) , x10_immediateTime(in.ReadFloat()) , x14_normalTime(in.ReadFloat()) , x18_stringId(in.Get()) , x1c_textTime(3.f * float(version <= 0 ? 1 : in.ReadLong())) { u32 locationCount = in.ReadLong(); x20_locations.reserve(locationCount); for (u32 i = 0; i < locationCount; ++i) x20_locations.emplace_back(in, version); } CGameHintInfo::SHintLocation::SHintLocation(CInputStream& in, s32) : x0_mlvlId(in.Get()) , x4_mreaId(in.Get()) , x8_areaId(in.ReadLong()) , xc_stringId(in.Get()) {} int CGameHintInfo::FindHintIndex(std::string_view str) { const std::vector& gameHints = g_MemoryCardSys->GetHints(); const auto it = std::find_if(gameHints.cbegin(), gameHints.cend(), [&str](const CGameHint& gh) { return gh.GetName() == str; }); return it != gameHints.cend() ? it - gameHints.cbegin() : -1; } CFactoryFnReturn FHintFactory(const SObjectTag&, CInputStream& in, const CVParamTransfer&, CObjectReference*) { in.ReadLong(); s32 version = in.ReadInt32(); return TToken::GetIObjObjectFor(std::make_unique(in, version)); } } // namespace metaforce ================================================ FILE: Runtime/CGameHintInfo.hpp ================================================ #pragma once #include #include #include "Runtime/IFactory.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CGameHintInfo { public: struct SHintLocation { CAssetId x0_mlvlId; CAssetId x4_mreaId; TAreaId x8_areaId = kInvalidAreaId; CAssetId xc_stringId; SHintLocation(CInputStream&, s32); }; class CGameHint { std::string x0_name; float x10_immediateTime; float x14_normalTime; CAssetId x18_stringId; float x1c_textTime; std::vector x20_locations; public: CGameHint(CInputStream&, s32); float GetNormalTime() const { return x14_normalTime; } float GetImmediateTime() const { return x10_immediateTime; } float GetTextTime() const { return x1c_textTime; } std::string_view GetName() const { return x0_name; } CAssetId GetStringID() const { return x18_stringId; } const std::vector& GetLocations() const { return x20_locations; } }; private: std::vector x0_hints; public: CGameHintInfo(CInputStream&, s32); const std::vector& GetHints() const { return x0_hints; } static int FindHintIndex(std::string_view str); }; CFactoryFnReturn FHintFactory(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference*); } // namespace metaforce ================================================ FILE: Runtime/CGameOptions.cpp ================================================ #include "Runtime/CGameOptions.hpp" #include #include "Runtime/CGameHintInfo.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/CWorldSaveGameInfo.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Audio/CSfxManager.hpp" #include "Runtime/Audio/CStreamAudioManager.hpp" #include "Runtime/Graphics/CMoviePlayer.hpp" #include "Runtime/Input/CFinalInput.hpp" #include "ConsoleVariables/CVarManager.hpp" namespace metaforce { constexpr std::array VisorOpts{{ {EGameOption::VisorOpacity, 21, 0.f, 255.f, 1.f, EOptionType::Float}, {EGameOption::HelmetOpacity, 22, 0.f, 255.f, 1.f, EOptionType::Float}, {EGameOption::HUDLag, 23, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::HintSystem, 24, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array DisplayOpts{{ //{EGameOption::ScreenBrightness, 25, 0.f, 8.f, 1.f, EOptionType::Float}, {EGameOption::ScreenBrightness, 25, -100.f, 100.f, 1.f, EOptionType::Float}, {EGameOption::ScreenOffsetX, 26, -30.f, 30.f, 1.f, EOptionType::Float}, {EGameOption::ScreenOffsetY, 27, -30.f, 30.f, 1.f, EOptionType::Float}, {EGameOption::ScreenStretch, 28, -10.f, 10.f, 1.f, EOptionType::Float}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array SoundOpts{{ {EGameOption::SFXVolume, 29, 0.f, 127.f, 1.f, EOptionType::Float}, {EGameOption::MusicVolume, 30, 0.f, 127.f, 1.f, EOptionType::Float}, {EGameOption::SoundMode, 31, 0.f, 1.f, 1.f, EOptionType::TripleEnum}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array ControllerOpts{{ {EGameOption::ReverseYAxis, 32, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::Rumble, 33, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::SwapBeamControls, 34, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array VisorOptsNew{{ {EGameOption::VisorOpacity, 23, 0.f, 255.f, 1.f, EOptionType::Float}, {EGameOption::HelmetOpacity, 24, 0.f, 255.f, 1.f, EOptionType::Float}, {EGameOption::HUDLag, 25, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::HintSystem, 26, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array DisplayOptsNew{{ //{EGameOption::ScreenBrightness, 25, 0.f, 8.f, 1.f, EOptionType::Float}, {EGameOption::ScreenBrightness, 28, -100.f, 100.f, 1.f, EOptionType::Float}, {EGameOption::ScreenOffsetX, 29, -30.f, 30.f, 1.f, EOptionType::Float}, {EGameOption::ScreenOffsetY, 30, -30.f, 30.f, 1.f, EOptionType::Float}, {EGameOption::ScreenStretch, 31, -10.f, 10.f, 1.f, EOptionType::Float}, {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array SoundOptsNew{{ {EGameOption::SFXVolume, 32, 0.f, 127.f, 1.f, EOptionType::Float}, {EGameOption::MusicVolume, 33, 0.f, 127.f, 1.f, EOptionType::Float}, {EGameOption::SoundMode, 34, 0.f, 1.f, 1.f, EOptionType::TripleEnum}, {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array ControllerOptsNew{{ {EGameOption::ReverseYAxis, 35, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::Rumble, 37, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::SwapBeamControls, 37, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::RestoreDefaults, 38, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults}, }}; constexpr std::array, 5> GameOptionsRegistry{{ {VisorOpts.size(), VisorOpts.data()}, {DisplayOpts.size(), DisplayOpts.data()}, {SoundOpts.size(), SoundOpts.data()}, {ControllerOpts.size(), ControllerOpts.data()}, {0, nullptr}, }}; constexpr std::array, 5> GameOptionsRegistryNew{{ {VisorOptsNew.size(), VisorOptsNew.data()}, {DisplayOptsNew.size(), DisplayOptsNew.data()}, {SoundOptsNew.size(), SoundOptsNew.data()}, {ControllerOptsNew.size(), ControllerOptsNew.data()}, {0, nullptr}, }}; CPersistentOptions::CPersistentOptions(CInputStream& stream) { for (u8& entry : x0_nesState) { entry = stream.ReadBits(8); } for (bool& entry : x68_) { entry = stream.ReadBits(8) != 0; } xc0_frozenFpsCount = stream.ReadBits(2); xc4_frozenBallCount = stream.ReadBits(2); xc8_powerBombAmmoCount = stream.ReadBits(1); xcc_logScanPercent = stream.ReadBits(7); xd0_24_fusionLinked = stream.ReadBits(1) != 0; xd0_25_normalModeBeat = stream.ReadBits(1) != 0; xd0_26_hardModeBeat = stream.ReadBits(1) != 0; xd0_27_fusionBeat = stream.ReadBits(1) != 0; xd0_28_fusionSuitActive = false; xd0_29_allItemsCollected = stream.ReadBits(1) != 0; xbc_autoMapperKeyState = stream.ReadBits(2); const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds(); size_t cinematicCount = 0; for (const auto& world : memWorlds) { TLockedToken saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()}); cinematicCount += saveWorld->GetCinematicCount(); } std::vector cinematicStates; cinematicStates.reserve(cinematicCount); for (size_t i = 0; i < cinematicCount; ++i) { cinematicStates.push_back(stream.ReadBits(1) != 0); } for (const auto& world : memWorlds) { TLockedToken saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()}); auto stateIt = cinematicStates.cbegin(); for (TEditorId cineId : saveWorld->GetCinematics()) if (*stateIt++) SetCinematicState(world.first, cineId, true); } } void CPersistentOptions::PutTo(COutputStream& w) const { for (const u8 entry : x0_nesState) { w.WriteBits(entry, 8); } for (const bool entry : x68_) { w.WriteBits(u32(entry), 8); } w.WriteBits(xc0_frozenFpsCount, 2); w.WriteBits(xc4_frozenBallCount, 2); w.WriteBits(xc8_powerBombAmmoCount, 1); w.WriteBits(xcc_logScanPercent, 7); w.WriteBits(xd0_24_fusionLinked, 1); w.WriteBits(xd0_25_normalModeBeat, 1); w.WriteBits(xd0_26_hardModeBeat, 1); w.WriteBits(xd0_27_fusionBeat, 1); w.WriteBits(xd0_29_allItemsCollected, 1); w.WriteBits(xbc_autoMapperKeyState, 2); const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds(); for (const auto& world : memWorlds) { const TLockedToken saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()}); for (const auto& cineId : saveWorld->GetCinematics()) { w.WriteBits(u32(GetCinematicState(world.first, cineId)), 1); } } } bool CPersistentOptions::GetCinematicState(CAssetId mlvlId, TEditorId cineId) const { auto existing = std::find_if(xac_cinematicStates.cbegin(), xac_cinematicStates.cend(), [&](const std::pair& pair) -> bool { return pair.first == mlvlId && pair.second == cineId; }); return existing != xac_cinematicStates.cend(); } void CPersistentOptions::SetCinematicState(CAssetId mlvlId, TEditorId cineId, bool state) { auto existing = std::find_if(xac_cinematicStates.cbegin(), xac_cinematicStates.cend(), [&](const std::pair& pair) -> bool { return pair.first == mlvlId && pair.second == cineId; }); if (state && existing == xac_cinematicStates.cend()) xac_cinematicStates.emplace_back(mlvlId, cineId); else if (!state && existing != xac_cinematicStates.cend()) xac_cinematicStates.erase(existing); } CGameOptions::CGameOptions(CInputStream& stream) { for (u8& entry : x0_) entry = stream.ReadBits(8); x44_soundMode = CAudioSys::ESurroundModes(stream.ReadBits(2)); x48_screenBrightness = stream.ReadBits(4); x4c_screenXOffset = stream.ReadBits(6) - 30; x50_screenYOffset = stream.ReadBits(6) - 30; x54_screenStretch = stream.ReadBits(5) - 10; x58_sfxVol = stream.ReadBits(7); x5c_musicVol = stream.ReadBits(7); x60_hudAlpha = stream.ReadBits(8); x64_helmetAlpha = stream.ReadBits(8); x68_24_hudLag = stream.ReadBits(1) != 0; x68_28_hintSystem = stream.ReadBits(1) != 0; x68_25_invertY = stream.ReadBits(1) != 0; x68_26_rumble = stream.ReadBits(1) != 0; x68_27_swapBeamsControls = stream.ReadBits(1) != 0; } void CGameOptions::ResetToDefaults() { x48_screenBrightness = 4; x4c_screenXOffset = 0; x50_screenYOffset = 0; x54_screenStretch = 0; x58_sfxVol = 0x7f; x5c_musicVol = 0x7f; x44_soundMode = CAudioSys::ESurroundModes::Stereo; x60_hudAlpha = 0xFF; x64_helmetAlpha = 0xFF; x68_24_hudLag = true; x68_25_invertY = false; x68_26_rumble = true; x68_27_swapBeamsControls = false; x68_28_hintSystem = true; InitSoundMode(); EnsureSettings(); } void CGameOptions::PutTo(COutputStream& writer) const { for (const u8 entry : x0_) writer.WriteBits(entry, 8); writer.WriteBits(u32(x44_soundMode), 2); writer.WriteBits(x48_screenBrightness, 4); writer.WriteBits(x4c_screenXOffset + 30, 6); writer.WriteBits(x50_screenYOffset + 30, 6); writer.WriteBits(x54_screenStretch + 10, 5); writer.WriteBits(x58_sfxVol, 7); writer.WriteBits(x5c_musicVol, 7); writer.WriteBits(x60_hudAlpha, 8); writer.WriteBits(x64_helmetAlpha, 8); writer.WriteBits(x68_24_hudLag, 1); writer.WriteBits(x68_28_hintSystem, 1); writer.WriteBits(x68_25_invertY, 1); writer.WriteBits(x68_26_rumble, 1); writer.WriteBits(x68_27_swapBeamsControls, 1); } CGameOptions::CGameOptions() : x68_24_hudLag(true) , x68_25_invertY(false) , x68_26_rumble(true) , x68_27_swapBeamsControls(false) , x68_28_hintSystem(true) { InitSoundMode(); } float CGameOptions::TuneScreenBrightness() const { return (0.375f * 1.f) + (float(x48_screenBrightness) * 0.25f); } void CGameOptions::InitSoundMode() { /* If system is mono, force x44 to mono, otherwise honor user preference */ } static float BrightnessCopyFilter = 0.f; void CGameOptions::SetScreenBrightness(s32 value, bool apply) { x48_screenBrightness = zeus::clamp(0, value, 8); if (!apply) { return; } BrightnessCopyFilter = TuneScreenBrightness(); } void CGameOptions::ApplyGamma() { float gammaT = -m_gamma / 100.f + 1.f; if (gammaT < 1.f) gammaT = gammaT * 0.5f + 0.5f; if (zeus::close_enough(gammaT, 1.f, 0.05f)) gammaT = 1.f; // CGraphics::g_BooFactory->setDisplayGamma(gammaT); } void CGameOptions::SetGamma(s32 value, bool apply) { m_gamma = zeus::clamp(-100, value, 100); if (!apply) { return; } ApplyGamma(); } void CGameOptions::SetScreenPositionX(s32 position, bool apply) { x4c_screenXOffset = zeus::clamp(-30, position, 30); if (apply) { /* TOOD: CGraphics related funcs */ } } void CGameOptions::SetScreenPositionY(s32 position, bool apply) { x50_screenYOffset = zeus::clamp(-30, position, 30); if (apply) { /* TOOD: CGraphics related funcs */ } } void CGameOptions::SetScreenStretch(s32 stretch, bool apply) { x54_screenStretch = zeus::clamp(-10, stretch, 10); if (apply) { /* TOOD: CGraphics related funcs */ } } void CGameOptions::SetSfxVolume(s32 volume, bool apply) { x58_sfxVol = zeus::clamp(0, volume, 0x7f); if (!apply) { return; } CAudioSys::SysSetSfxVolume(x58_sfxVol, 1, true, true); CStreamAudioManager::SetSfxVolume(x58_sfxVol); CMoviePlayer::SetSfxVolume(x58_sfxVol); } void CGameOptions::SetMusicVolume(s32 volume, bool apply) { x5c_musicVol = zeus::clamp(0, volume, 0x7f); if (!apply) { return; } CStreamAudioManager::SetMusicVolume(x5c_musicVol); } void CGameOptions::SetHUDAlpha(u32 alpha) { x60_hudAlpha = alpha; } void CGameOptions::SetHelmetAlpha(u32 alpha) { x64_helmetAlpha = alpha; } void CGameOptions::SetHUDLag(bool lag) { x68_24_hudLag = lag; } void CGameOptions::SetSurroundMode(int mode, bool apply) { x44_soundMode = CAudioSys::ESurroundModes(zeus::clamp(0, mode, 2)); if (apply) CAudioSys::SetSurroundMode(x44_soundMode); } CAudioSys::ESurroundModes CGameOptions::GetSurroundMode() const { return x44_soundMode; } void CGameOptions::SetInvertYAxis(bool invert) { x68_25_invertY = invert; } void CGameOptions::SetIsRumbleEnabled(bool rumble) { x68_26_rumble = rumble; } void CGameOptions::SetSwapBeamControls(bool swap) { x68_27_swapBeamsControls = swap; if (!swap) SetControls(0); else SetControls(1); } void CGameOptions::SetIsHintSystemEnabled(bool hints) { x68_28_hintSystem = hints; } void CGameOptions::SetControls(int controls) { if (controls == 0) g_currentPlayerControl = g_tweakPlayerControl; else g_currentPlayerControl = g_tweakPlayerControlAlt; ResetControllerAssets(controls); } constexpr std::array, 5> CStickToDPadRemap{{ {0x2A13C23Eu, 0xF13452F8u}, {0xA91A7703u, 0xC042EC91u}, {0x12A12131u, 0x5F556002u}, {0xA9798329u, 0xB306E26Fu}, {0xCD7B1ACAu, 0x8ADA8184u}, }}; constexpr std::array, 5> CStickOutlineToDPadRemap{{ {0x1A29C0E6u, 0xF13452F8u}, {0x5D9F9796u, 0xC042EC91u}, {0x951546A8u, 0x5F556002u}, {0x7946C4C5u, 0xB306E26Fu}, {0x409AA72Eu, 0x8ADA8184u}, }}; void CGameOptions::ResetControllerAssets(int controls) { if (controls != 1) { x6c_controlTxtrMap.clear(); } else if (x6c_controlTxtrMap.empty()) { x6c_controlTxtrMap.reserve(15); for (const auto& entry : CStickToDPadRemap) { const auto& emplaced = x6c_controlTxtrMap.emplace_back(entry); x6c_controlTxtrMap.emplace_back(emplaced.second, emplaced.first); } for (const auto& entry : CStickOutlineToDPadRemap) x6c_controlTxtrMap.emplace_back(entry); std::sort(x6c_controlTxtrMap.begin(), x6c_controlTxtrMap.end(), [](const std::pair& a, const std::pair& b) { return a.first < b.first; }); } } void CGameOptions::EnsureSettings() { SetScreenBrightness(x48_screenBrightness, true); SetGamma(m_gamma, true); SetScreenPositionX(x4c_screenXOffset, true); SetScreenPositionY(x50_screenYOffset, true); SetScreenStretch(x54_screenStretch, true); SetSfxVolume(x58_sfxVol, true); SetMusicVolume(x5c_musicVol, true); SetSurroundMode(int(x44_soundMode), true); SetHelmetAlpha(x64_helmetAlpha); SetHUDLag(x68_24_hudLag); SetInvertYAxis(x68_25_invertY); SetIsRumbleEnabled(x68_26_rumble); SetIsHintSystemEnabled(x68_28_hintSystem); SetSwapBeamControls(x68_27_swapBeamsControls); } void CGameOptions::TryRestoreDefaults(const CFinalInput& input, int category, int option, bool frontend, bool forceRestore) { const auto& options = GameOptionsRegistry[category]; if (options.first == 0) return; if (options.second[option].option != EGameOption::RestoreDefaults) return; if (!forceRestore && !input.PA() && !input.PSpecialKey(ESpecialKey::Enter)) return; if (frontend) { CSfxManager::SfxStart(SFXfnt_advance_L, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(SFXfnt_advance_R, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else { CSfxManager::SfxStart(SFXui_advance, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } CGameOptions& gameOptions = g_GameState->GameOptions(); switch (category) { case 0: gameOptions.SetHelmetAlpha(0xff); gameOptions.SetHUDLag(true); gameOptions.SetIsHintSystemEnabled(true); break; case 1: gameOptions.SetScreenBrightness(4, true); gameOptions.SetGamma(0, true); gameOptions.SetScreenPositionX(0, true); gameOptions.SetScreenPositionY(0, true); gameOptions.SetScreenStretch(0, true); break; case 2: gameOptions.SetSfxVolume(0x7f, true); gameOptions.SetMusicVolume(0x7f, true); gameOptions.SetSurroundMode(1, true); break; case 3: gameOptions.SetInvertYAxis(false); gameOptions.SetIsRumbleEnabled(true); gameOptions.SetSwapBeamControls(false); break; default: break; } } void CGameOptions::SetOption(EGameOption option, int value) { CGameOptions& options = g_GameState->GameOptions(); switch (option) { case EGameOption::VisorOpacity: options.SetHUDAlpha(value); break; case EGameOption::HelmetOpacity: options.SetHelmetAlpha(value); break; case EGameOption::HUDLag: options.SetHUDLag(value != 0); break; case EGameOption::HintSystem: options.SetIsHintSystemEnabled(value != 0); break; case EGameOption::ScreenBrightness: options.SetGamma(value, true); break; case EGameOption::ScreenOffsetX: options.SetScreenPositionX(value, true); break; case EGameOption::ScreenOffsetY: options.SetScreenPositionY(value, true); break; case EGameOption::ScreenStretch: options.SetScreenStretch(value, true); break; case EGameOption::SFXVolume: options.SetSfxVolume(value, true); break; case EGameOption::MusicVolume: options.SetMusicVolume(value, true); break; case EGameOption::SoundMode: options.SetSurroundMode(value, true); break; case EGameOption::ReverseYAxis: options.SetInvertYAxis(value != 0); break; case EGameOption::Rumble: options.SetIsRumbleEnabled(value != 0); break; case EGameOption::SwapBeamControls: options.SetSwapBeamControls(value != 0); break; default: break; } } int CGameOptions::GetOption(EGameOption option) { const CGameOptions& options = g_GameState->GameOptions(); switch (option) { case EGameOption::VisorOpacity: return options.GetHUDAlpha(); case EGameOption::HelmetOpacity: return options.GetHelmetAlpha(); case EGameOption::HUDLag: return int(options.GetHUDLag()); case EGameOption::HintSystem: return int(options.GetIsHintSystemEnabled()); case EGameOption::ScreenBrightness: return options.GetGamma(); case EGameOption::ScreenOffsetX: return options.GetScreenPositionX(); case EGameOption::ScreenOffsetY: return options.GetScreenPositionY(); case EGameOption::ScreenStretch: return options.GetScreenStretch(); case EGameOption::SFXVolume: return options.GetSfxVolume(); case EGameOption::MusicVolume: return options.GetMusicVolume(); case EGameOption::SoundMode: return int(options.GetSurroundMode()); case EGameOption::ReverseYAxis: return int(options.GetInvertYAxis()); case EGameOption::Rumble: return int(options.GetIsRumbleEnabled()); case EGameOption::SwapBeamControls: return int(options.GetSwapBeamControls()); default: break; } return 0; } CHintOptions::CHintOptions(CInputStream& stream) { const auto& hints = g_MemoryCardSys->GetHints(); x0_hintStates.reserve(hints.size()); u32 hintIdx = 0; for ([[maybe_unused]] const auto& hint : hints) { const auto state = EHintState(stream.ReadBits(2)); const s32 timeBits = stream.ReadBits(32); float time; std::memcpy(&time, &timeBits, sizeof(s32)); if (state == EHintState::Zero) { time = 0.f; } x0_hintStates.emplace_back(state, time, false); if (x10_nextHintIdx == -1 && state == EHintState::Displaying) { x10_nextHintIdx = hintIdx; } ++hintIdx; } } void CHintOptions::PutTo(COutputStream& writer) const { for (const SHintState& hint : x0_hintStates) { writer.WriteBits(u32(hint.x0_state), 2); u32 timeBits; std::memcpy(&timeBits, &hint.x4_time, sizeof(timeBits)); writer.WriteBits(timeBits, 32); } } void CHintOptions::SetNextHintTime() { if (x10_nextHintIdx == -1) return; x0_hintStates[x10_nextHintIdx].x4_time = g_MemoryCardSys->GetHints()[x10_nextHintIdx].GetTextTime() + 5.f; } void CHintOptions::InitializeMemoryState() { const auto& hints = g_MemoryCardSys->GetHints(); x0_hintStates.resize(hints.size()); } const CHintOptions::SHintState* CHintOptions::GetCurrentDisplayedHint() const { if (!g_GameState->GameOptions().GetIsHintSystemEnabled()) return nullptr; if (x10_nextHintIdx == -1) return nullptr; const SHintState& hintState = x0_hintStates[x10_nextHintIdx]; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[x10_nextHintIdx]; if (hintState.x4_time >= hint.GetTextTime()) return nullptr; if (hintState.x4_time < 3.f) return &hintState; if (!hintState.x8_dismissed) return &hintState; return nullptr; } void CHintOptions::DelayHint(std::string_view name) { const int idx = CGameHintInfo::FindHintIndex(name); if (idx == -1) { return; } if (x10_nextHintIdx == idx) { for (SHintState& state : x0_hintStates) { state.x4_time += 60.f; } } x0_hintStates[idx].x0_state = EHintState::Delayed; } void CHintOptions::ActivateImmediateHintTimer(std::string_view name) { const int idx = CGameHintInfo::FindHintIndex(name); if (idx == -1) { return; } SHintState& hintState = x0_hintStates[idx]; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[idx]; if (hintState.x0_state != EHintState::Zero) { return; } hintState.x0_state = EHintState::Waiting; hintState.x4_time = hint.GetImmediateTime(); } void CHintOptions::ActivateContinueDelayHintTimer(std::string_view name) { int idx = x10_nextHintIdx; if (idx != 0) { idx = CGameHintInfo::FindHintIndex(name); } if (idx == -1) { return; } SHintState& hintState = x0_hintStates[idx]; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[idx]; if (hintState.x0_state != EHintState::Displaying) { return; } hintState.x4_time = hint.GetTextTime(); } void CHintOptions::DismissDisplayedHint() { if (x10_nextHintIdx == -1) return; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[x10_nextHintIdx]; SHintState& hintState = x0_hintStates[x10_nextHintIdx]; if (hintState.x4_time >= hint.GetTextTime()) return; hintState.x4_time = hint.GetNormalTime(); hintState.x8_dismissed = true; } u32 CHintOptions::GetNextHintIdx() const { if (g_GameState->GameOptions().GetIsHintSystemEnabled()) return x10_nextHintIdx; return -1; } void CHintOptions::Update(float dt, const CStateManager& stateMgr) { x10_nextHintIdx = -1; int idx = 0; auto memIt = g_MemoryCardSys->GetHints().begin(); for (SHintState& state : x0_hintStates) { switch (state.x0_state) { case EHintState::Waiting: state.x4_time -= dt; if (state.x4_time <= 0.f) { state.x0_state = EHintState::Displaying; state.x4_time = memIt->GetTextTime(); } break; case EHintState::Displaying: if (x10_nextHintIdx == -1) x10_nextHintIdx = idx; break; default: break; } ++memIt; ++idx; } if (x10_nextHintIdx == -1) return; SHintState& state = x0_hintStates[x10_nextHintIdx]; const CGameHintInfo::CGameHint& data = g_MemoryCardSys->GetHints()[x10_nextHintIdx]; state.x4_time = std::max(0.f, state.x4_time - dt); if (state.x4_time < data.GetTextTime()) { for (const CGameHintInfo::SHintLocation& loc : data.GetLocations()) { if (loc.x0_mlvlId == stateMgr.GetWorld()->IGetWorldAssetId() && loc.x8_areaId == stateMgr.GetNextAreaId()) { state.x4_time = data.GetNormalTime(); state.x8_dismissed = true; } } } } } // namespace metaforce ================================================ FILE: Runtime/CGameOptions.hpp ================================================ #pragma once #include #include #include #include "Runtime/CWorldSaveGameInfo.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Audio/CAudioSys.hpp" namespace metaforce { struct CFinalInput; class CStateManager; /** Options presented in UI */ enum class EGameOption { VisorOpacity, HelmetOpacity, HUDLag, HintSystem, ScreenBrightness, ScreenOffsetX, ScreenOffsetY, ScreenStretch, SFXVolume, MusicVolume, SoundMode, ReverseYAxis, Rumble, SwapBeamControls, RestoreDefaults }; /** Option UI type */ enum class EOptionType { Float, DoubleEnum, TripleEnum, RestoreDefaults }; /** Option UI presentation information */ struct SGameOption { EGameOption option; u32 stringId; float minVal, maxVal, increment; EOptionType type; }; /** Static registry of Option UI presentation information */ extern const std::array, 5> GameOptionsRegistry; extern const std::array, 5> GameOptionsRegistryNew; /** Options tracked persistently between game sessions */ class CPersistentOptions { friend class CGameState; std::array x0_nesState{}; std::array x68_{}; std::vector> xac_cinematicStates; /* (MLVL, Cinematic) */ u32 xbc_autoMapperKeyState = 0; u32 xc0_frozenFpsCount = 0; u32 xc4_frozenBallCount = 0; u32 xc8_powerBombAmmoCount = 0; u32 xcc_logScanPercent = 0; bool xd0_24_fusionLinked : 1 = false; bool xd0_25_normalModeBeat : 1 = false; bool xd0_26_hardModeBeat : 1 = false; bool xd0_27_fusionBeat : 1 = false; bool xd0_28_fusionSuitActive : 1 = false; bool xd0_29_allItemsCollected : 1 = false; public: CPersistentOptions() = default; explicit CPersistentOptions(CInputStream& stream); bool GetCinematicState(CAssetId mlvlId, TEditorId cineId) const; void SetCinematicState(CAssetId mlvlId, TEditorId cineId, bool state); u32 GetAutoMapperKeyState() const { return xbc_autoMapperKeyState; } void SetAutoMapperKeyState(u32 state) { xbc_autoMapperKeyState = state; } bool GetPlayerLinkedFusion() const { return xd0_24_fusionLinked; } void SetPlayerLinkedFusion(bool fusionLinked) { xd0_24_fusionLinked = fusionLinked; } bool GetPlayerBeatNormalMode() const { return xd0_25_normalModeBeat; } void SetPlayerBeatNormalMode(bool normalModeBeat) { xd0_25_normalModeBeat = normalModeBeat; } bool GetPlayerBeatHardMode() const { return xd0_26_hardModeBeat; } void SetPlayerBeatHardMode(bool hardModeBeat) { xd0_26_hardModeBeat = hardModeBeat; } bool GetPlayerBeatFusion() const { return xd0_27_fusionBeat; } void SetPlayerBeatFusion(bool fusionBeat) { xd0_27_fusionBeat = fusionBeat; } bool GetPlayerFusionSuitActive() const { return xd0_28_fusionSuitActive; } void SetPlayerFusionSuitActive(bool fusionSuitActive) { xd0_28_fusionSuitActive = fusionSuitActive; } bool GetAllItemsCollected() const { return xd0_29_allItemsCollected; } void SetAllItemsCollected(bool allItemsCollected) { xd0_29_allItemsCollected = allItemsCollected; } u32 GetLogScanPercent() const { return xcc_logScanPercent; } void SetLogScanPercent(u32 percent) { xcc_logScanPercent = percent; } void IncrementFrozenFpsCount() { xc0_frozenFpsCount = std::min(int(xc0_frozenFpsCount + 1), 3); } bool GetShowFrozenFpsMessage() const { return xc0_frozenFpsCount != 3; } void IncrementFrozenBallCount() { xc4_frozenBallCount = std::min(int(xc4_frozenBallCount + 1), 3); } bool GetShowFrozenBallMessage() const { return xc4_frozenBallCount != 3; } bool GetShowPowerBombAmmoMessage() const { return xc8_powerBombAmmoCount != 1; } void IncrementPowerBombAmmoCount() { xc8_powerBombAmmoCount = std::min(1, xc8_powerBombAmmoCount + 1); } void PutTo(COutputStream& w) const; u8* GetNESState() { return x0_nesState.data(); } const u8* GetNESState() const { return x0_nesState.data(); } }; /** Options tracked per game session */ class CGameOptions { std::array x0_{}; CAudioSys::ESurroundModes x44_soundMode = CAudioSys::ESurroundModes::Stereo; u32 x48_screenBrightness = 4; s32 x4c_screenXOffset = 0; s32 x50_screenYOffset = 0; s32 x54_screenStretch = 0; u32 x58_sfxVol = 0x7f; u32 x5c_musicVol = 0x7f; u32 x60_hudAlpha = 0xff; u32 x64_helmetAlpha = 0xff; bool x68_24_hudLag : 1; bool x68_25_invertY : 1; bool x68_26_rumble : 1; bool x68_27_swapBeamsControls : 1; bool x68_28_hintSystem : 1; std::vector> x6c_controlTxtrMap; s32 m_gamma = 0; public: CGameOptions(); explicit CGameOptions(CInputStream& stream); void ResetToDefaults(); void InitSoundMode(); void EnsureSettings(); void PutTo(COutputStream& writer) const; float TuneScreenBrightness() const; void SetScreenBrightness(s32 value, bool apply); s32 GetScreenBrightness() const { return x48_screenBrightness; } void ApplyGamma(); void SetGamma(s32 value, bool apply); s32 GetGamma() const { return m_gamma; } void SetScreenPositionX(s32 position, bool apply); s32 GetScreenPositionX() const { return x4c_screenXOffset; } void SetScreenPositionY(s32 position, bool apply); s32 GetScreenPositionY() const { return x50_screenYOffset; } void SetScreenStretch(s32 stretch, bool apply); s32 GetScreenStretch() const { return x54_screenStretch; } void SetSfxVolume(s32 volume, bool apply); s32 GetSfxVolume() const { return x58_sfxVol; } void SetMusicVolume(s32 volume, bool apply); s32 GetMusicVolume() const { return x5c_musicVol; } void SetHUDAlpha(u32 alpha); u32 GetHUDAlpha() const { return x60_hudAlpha; } void SetHelmetAlpha(u32 alpha); u32 GetHelmetAlpha() const { return x64_helmetAlpha; } void SetHUDLag(bool lag); bool GetHUDLag() const { return x68_24_hudLag; } void SetSurroundMode(int mode, bool apply); CAudioSys::ESurroundModes GetSurroundMode() const; void SetInvertYAxis(bool invert); bool GetInvertYAxis() const { return x68_25_invertY; } void SetIsRumbleEnabled(bool rumble); bool GetIsRumbleEnabled() const { return x68_26_rumble; } void SetSwapBeamControls(bool swap); bool GetSwapBeamControls() const { return x68_27_swapBeamsControls; } void SetIsHintSystemEnabled(bool hints); bool GetIsHintSystemEnabled() const { return x68_28_hintSystem; } void SetControls(int controls); void ResetControllerAssets(int controls); const std::vector>& GetControlTXTRMap() const { return x6c_controlTxtrMap; } static void TryRestoreDefaults(const CFinalInput& input, int category, int option, bool frontend, bool forceRestore); static void SetOption(EGameOption option, int value); static int GetOption(EGameOption option); }; class CHintOptions { public: enum class EHintState { Zero, Waiting, Displaying, Delayed }; struct SHintState { EHintState x0_state = EHintState::Zero; float x4_time = 0.f; bool x8_dismissed = false; SHintState() = default; SHintState(EHintState state, float time, bool flag) : x0_state(state), x4_time(time), x8_dismissed(flag) {} bool CanContinue() const { return x4_time / 3.f <= 1.f; } }; private: std::vector x0_hintStates; u32 x10_nextHintIdx = -1; public: CHintOptions() = default; explicit CHintOptions(CInputStream& stream); void PutTo(COutputStream& writer) const; void SetNextHintTime(); void InitializeMemoryState(); const SHintState* GetCurrentDisplayedHint() const; void DelayHint(std::string_view name); void ActivateImmediateHintTimer(std::string_view name); void ActivateContinueDelayHintTimer(std::string_view name); void DismissDisplayedHint(); u32 GetNextHintIdx() const; const std::vector& GetHintStates() const { return x0_hintStates; } void Update(float dt, const CStateManager& stateMgr); }; } // namespace metaforce ================================================ FILE: Runtime/CGameState.cpp ================================================ #include "Runtime/CGameState.hpp" #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/CWorldSaveGameInfo.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/MP1/MP1.hpp" #include namespace metaforce { union BitsToDouble { struct { #if BYTE_ORDER == __LITTLE_ENDIAN u32 high; u32 low; #else u32 low; u32 high; #endif }; double doub; }; CScriptLayerManager::CScriptLayerManager(CInputStream& reader, const CWorldSaveGameInfo& saveWorld) { const u32 bitCount = reader.ReadBits(10); x10_saveLayers.Reserve(bitCount); for (u32 i = 0; i < bitCount; ++i) { const bool bit = reader.ReadBits(1) != 0; if (bit) { x10_saveLayers.SetBit(i); } else { x10_saveLayers.UnsetBit(i); } } } void CScriptLayerManager::PutTo(COutputStream& writer) const { u32 totalLayerCount = 0; for (size_t i = 0; i < x0_areaLayers.size(); ++i) { totalLayerCount += GetAreaLayerCount(s32(i)) - 1; } writer.WriteBits(totalLayerCount, 10); for (size_t i = 0; i < x0_areaLayers.size(); ++i) { const u32 count = GetAreaLayerCount(s32(i)); for (u32 l = 1; l < count; ++l) { writer.WriteBits(static_cast(IsLayerActive(s32(i), s32(l))), 1); } } } void CScriptLayerManager::InitializeWorldLayers(const std::vector& layers) { if (!x0_areaLayers.empty()) { return; } x0_areaLayers = layers; if (x10_saveLayers.GetBitCount() == 0) { return; } u32 a = 0; u32 b = 0; for (const CWorldLayers::Area& area : x0_areaLayers) { for (u32 l = 1; l < area.m_layerCount; ++l) { SetLayerActive(a, l, x10_saveLayers.GetBit(b++)); } ++a; } x10_saveLayers.Clear(); } CWorldState::CWorldState(CAssetId id) : x0_mlvlId(id), x4_areaId(0) { x8_mailbox = std::make_shared(); xc_mapWorldInfo = std::make_shared(); x10_desiredAreaAssetId = {}; x14_layerState = std::make_shared(); } CWorldState::CWorldState(CInputStream& reader, CAssetId mlvlId, const CWorldSaveGameInfo& saveWorld) : x0_mlvlId(mlvlId), x4_areaId(TAreaId(reader.ReadBits(32))), x10_desiredAreaAssetId(reader.ReadBits(32)) { x8_mailbox = std::make_shared(reader, saveWorld); xc_mapWorldInfo = std::make_shared(reader, saveWorld, mlvlId); x14_layerState = std::make_shared(reader, saveWorld); } void CWorldState::PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw) const { writer.WriteBits(x4_areaId, 32); writer.WriteBits(u32(x10_desiredAreaAssetId.Value()), 32); x8_mailbox->PutTo(writer, savw); xc_mapWorldInfo->PutTo(writer, savw, x0_mlvlId); x14_layerState->PutTo(writer); } CGameState::GameFileStateInfo CGameState::LoadGameFileState(const u8* data) { CMemoryInStream stream(data, 4096, CMemoryInStream::EOwnerShip::NotOwned); GameFileStateInfo ret; for (u32 i = 0; i < 128; i++) { stream.ReadBits(8); } ret.x14_timestamp = stream.ReadBits(32); ret.x20_hardMode = stream.ReadBits(1) != 0; stream.ReadBits(1); const CAssetId origMLVL = u32(stream.ReadBits(32)); ret.x8_mlvlId = origMLVL; BitsToDouble conv; conv.low = stream.ReadBits(32); conv.high = stream.ReadBits(32); ret.x0_playTime = conv.doub; CPlayerState playerState(stream); ret.x10_energyTanks = playerState.GetItemCapacity(CPlayerState::EItemType::EnergyTanks); ret.xc_health = playerState.GetHealthInfo().GetHP(); u32 itemPercent; if (origMLVL == 0x158EFE17u) itemPercent = 0; else itemPercent = playerState.CalculateItemCollectionRate() * 100 / playerState.GetPickupTotal(); ret.x18_itemPercent = itemPercent; float scanPercent; if (playerState.GetTotalLogScans() == 0) scanPercent = 0.f; else scanPercent = 100.f * playerState.GetLogScans() / float(playerState.GetTotalLogScans()); ret.x1c_scanPercent = scanPercent; return ret; } CGameState::CGameState() { x98_playerState = std::make_shared(); x9c_transManager = std::make_shared(); if (g_MemoryCardSys != nullptr) { InitializeMemoryStates(); } } CGameState::CGameState(CInputStream& stream, u32 saveIdx) : x20c_saveFileIdx(saveIdx) { x9c_transManager = std::make_shared(); x228_24_hardMode = false; x228_25_initPowerupsAtFirstSpawn = true; for (bool& value : x0_) { value = stream.ReadBits(8) != 0; } stream.ReadBits(32); x228_24_hardMode = stream.ReadBits(1) != 0; x228_25_initPowerupsAtFirstSpawn = stream.ReadBits(1) != 0; x84_mlvlId = u32(stream.ReadBits(32)); MP1::CMain::EnsureWorldPakReady(x84_mlvlId); BitsToDouble conv; conv.low = stream.ReadBits(32); conv.high = stream.ReadBits(32); xa0_playTime = conv.doub; x98_playerState = std::make_shared(stream); x17c_gameOptions = CGameOptions(stream); x1f8_hintOptions = CHintOptions(stream); const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds(); x88_worldStates.reserve(memWorlds.size()); for (const auto& memWorld : memWorlds) { TLockedToken saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), memWorld.second.GetSaveWorldAssetId()}); x88_worldStates.emplace_back(stream, memWorld.first, *saveWorld); } InitializeMemoryWorlds(); WriteBackupBuf(); } void CGameState::ReadPersistentOptions(CInputStream& r) { xa8_systemOptions = r.Get(); } void CGameState::ImportPersistentOptions(const CPersistentOptions& opts) { if (opts.xd0_24_fusionLinked) xa8_systemOptions.xd0_24_fusionLinked = true; if (opts.xd0_27_fusionBeat) xa8_systemOptions.xd0_27_fusionBeat = true; if (&opts != &xa8_systemOptions) xa8_systemOptions.x0_nesState = opts.x0_nesState; xa8_systemOptions.SetLogScanPercent(opts.GetLogScanPercent()); xa8_systemOptions.SetAllItemsCollected(opts.GetAllItemsCollected()); xa8_systemOptions.SetPlayerBeatNormalMode(opts.GetPlayerBeatNormalMode()); xa8_systemOptions.SetPlayerBeatHardMode(opts.GetPlayerBeatHardMode()); } void CGameState::ExportPersistentOptions(CPersistentOptions& opts) const { if (xa8_systemOptions.xd0_24_fusionLinked) opts.xd0_24_fusionLinked = true; if (xa8_systemOptions.xd0_27_fusionBeat) opts.xd0_27_fusionBeat = true; if (&opts != &xa8_systemOptions) opts.x0_nesState = xa8_systemOptions.x0_nesState; opts.SetPlayerFusionSuitActive(xa8_systemOptions.GetPlayerFusionSuitActive()); } void CGameState::WriteBackupBuf() { x218_backupBuf.resize(940); CMemoryStreamOut w(x218_backupBuf.data(), 940); PutTo(w); } void CGameState::PutTo(COutputStream& writer) { for (const bool value : x0_) { writer.WriteBits(u32(value), 8); } writer.WriteBits(CBasics::GetTime() / CBasics::TICKS_PER_SECOND, 32); writer.WriteBits(x228_24_hardMode, 1); writer.WriteBits(x228_25_initPowerupsAtFirstSpawn, 1); writer.WriteBits(u32(x84_mlvlId.Value()), 32); BitsToDouble conv; conv.doub = xa0_playTime; writer.WriteBits(conv.low, 32); writer.WriteBits(conv.high, 32); x98_playerState->PutTo(writer); x17c_gameOptions.PutTo(writer); x1f8_hintOptions.PutTo(writer); const auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds(); for (const auto& memWorld : memWorlds) { TLockedToken saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), memWorld.second.GetSaveWorldAssetId()}); const CWorldState& wld = StateForWorld(memWorld.first); wld.PutTo(writer, *saveWorld); } } void CGameState::SetCurrentWorldId(CAssetId id) { StateForWorld(id); x84_mlvlId = id; MP1::CMain::EnsureWorldPakReady(x84_mlvlId); } void CGameState::SetTotalPlayTime(double time) { xa0_playTime = zeus::clamp(0.0, time, 359999.0); } CWorldState& CGameState::StateForWorld(CAssetId mlvlId) { auto it = x88_worldStates.begin(); for (; it != x88_worldStates.end(); ++it) { if (it->GetWorldAssetId() == mlvlId) break; } if (it == x88_worldStates.end()) { x88_worldStates.emplace_back(mlvlId); return x88_worldStates.back(); } return *it; } float CGameState::GetHardModeDamageMultiplier() const { return g_tweakGame->GetHardModeDamageMultiplier(); } float CGameState::GetHardModeWeaponMultiplier() const { return g_tweakGame->GetHardModeWeaponMultiplier(); } void CGameState::InitializeMemoryWorlds() { const auto& memoryWorlds = g_MemoryCardSys->GetMemoryWorlds(); for (const auto& wld : memoryWorlds) { const auto& layerState = StateForWorld(wld.first).GetLayerState(); layerState->InitializeWorldLayers(wld.second.GetDefaultLayerStates()); } } void CGameState::InitializeMemoryStates() { x98_playerState->InitializeScanTimes(); x1f8_hintOptions.InitializeMemoryState(); InitializeMemoryWorlds(); WriteBackupBuf(); } } // namespace metaforce ================================================ FILE: Runtime/CGameState.hpp ================================================ #pragma once #include #include #include #include "Runtime/AutoMapper/CMapWorldInfo.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/CGameOptions.hpp" #include "Runtime/CPlayerState.hpp" #include "Runtime/CScriptMailbox.hpp" #include "Runtime/World/CWorld.hpp" #include "Runtime/World/CWorldTransManager.hpp" namespace metaforce { class CSaveWorldMemory; class WordBitmap { std::vector x0_words; size_t x10_bitCount = 0; public: void Reserve(size_t bitCount) { x0_words.reserve((bitCount + 31) / 32); } [[nodiscard]] size_t GetBitCount() const { return x10_bitCount; } [[nodiscard]] bool GetBit(size_t idx) const { size_t wordIdx = idx / 32; if (wordIdx >= x0_words.size()) { return false; } size_t wordCur = idx % 32; return ((x0_words[wordIdx] >> wordCur) & 0x1) != 0u; } void SetBit(size_t idx) { size_t wordIdx = idx / 32; while (wordIdx >= x0_words.size()) { x0_words.push_back(0); } size_t wordCur = idx % 32; x0_words[wordIdx] |= (1 << wordCur); x10_bitCount = std::max(x10_bitCount, idx + 1); } void UnsetBit(size_t idx) { size_t wordIdx = idx / 32; while (wordIdx >= x0_words.size()) { x0_words.push_back(0); } size_t wordCur = idx % 32; x0_words[wordIdx] &= ~(1 << wordCur); x10_bitCount = std::max(x10_bitCount, idx + 1); } void Clear() { x0_words.clear(); x10_bitCount = 0; } class Iterator { friend class WordBitmap; const WordBitmap& m_bmp; size_t m_idx = 0; Iterator(const WordBitmap& bmp, size_t idx) : m_bmp(bmp), m_idx(idx) {} public: using iterator_category = std::forward_iterator_tag; using value_type = bool; using difference_type = std::ptrdiff_t; using pointer = bool*; using reference = bool&; Iterator& operator++() { ++m_idx; return *this; } bool operator*() const { return m_bmp.GetBit(m_idx); } bool operator!=(const Iterator& other) const { return m_idx != other.m_idx; } }; [[nodiscard]] Iterator begin() const { return Iterator(*this, 0); } [[nodiscard]] Iterator end() const { return Iterator(*this, x10_bitCount); } }; class CScriptLayerManager { friend class CSaveWorldIntermediate; std::vector x0_areaLayers; WordBitmap x10_saveLayers; public: CScriptLayerManager() = default; CScriptLayerManager(CInputStream& reader, const CWorldSaveGameInfo& saveWorld); [[nodiscard]] bool IsLayerActive(int areaIdx, int layerIdx) const { return ((x0_areaLayers[areaIdx].m_layerBits >> layerIdx) & 1) != 0u; } void SetLayerActive(int areaIdx, int layerIdx, bool active) { if (active) { x0_areaLayers[areaIdx].m_layerBits |= uint64_t(1) << layerIdx; } else { x0_areaLayers[areaIdx].m_layerBits &= ~(uint64_t(1) << layerIdx); } } void InitializeWorldLayers(const std::vector& layers); [[nodiscard]] u32 GetAreaLayerCount(int areaIdx) const { return x0_areaLayers[areaIdx].m_layerCount; } [[nodiscard]] u32 GetAreaCount() const { return x0_areaLayers.size(); } void PutTo(COutputStream& writer) const; }; class CWorldState { CAssetId x0_mlvlId; TAreaId x4_areaId = kInvalidAreaId; std::shared_ptr x8_mailbox; std::shared_ptr xc_mapWorldInfo; CAssetId x10_desiredAreaAssetId; std::shared_ptr x14_layerState; public: explicit CWorldState(CAssetId id); CWorldState(CInputStream& reader, CAssetId mlvlId, const CWorldSaveGameInfo& saveWorld); CAssetId GetWorldAssetId() const { return x0_mlvlId; } void SetAreaId(TAreaId aid) { x4_areaId = aid; } TAreaId GetCurrentAreaId() const { return x4_areaId; } CAssetId GetDesiredAreaAssetId() const { return x10_desiredAreaAssetId; } void SetDesiredAreaAssetId(CAssetId id) { x10_desiredAreaAssetId = id; } const std::shared_ptr& Mailbox() const { return x8_mailbox; } const std::shared_ptr& MapWorldInfo() const { return xc_mapWorldInfo; } const std::shared_ptr& GetLayerState() const { return x14_layerState; } void PutTo(COutputStream& writer, const CWorldSaveGameInfo& savw) const; }; class CGameState { friend class CStateManager; std::array x0_{}; u32 x80_ = 0; CAssetId x84_mlvlId; std::vector x88_worldStates; std::shared_ptr x98_playerState; std::shared_ptr x9c_transManager; double xa0_playTime = 0.0; CPersistentOptions xa8_systemOptions; CGameOptions x17c_gameOptions; CHintOptions x1f8_hintOptions; u32 x20c_saveFileIdx = 0; u64 x210_cardSerial = 0; std::vector x218_backupBuf; bool x228_24_hardMode : 1 = false; bool x228_25_initPowerupsAtFirstSpawn : 1 = true; public: CGameState(); CGameState(CInputStream& stream, u32 saveIdx); void SetCurrentWorldId(CAssetId id); std::shared_ptr GetPlayerState() const { return x98_playerState; } std::shared_ptr GetWorldTransitionManager() const { return x9c_transManager; } void SetTotalPlayTime(double time); double GetTotalPlayTime() const { return xa0_playTime; } CPersistentOptions& SystemOptions() { return xa8_systemOptions; } CGameOptions& GameOptions() { return x17c_gameOptions; } CHintOptions& HintOptions() { return x1f8_hintOptions; } CWorldState& StateForWorld(CAssetId mlvlId); CWorldState& CurrentWorldState() { return StateForWorld(x84_mlvlId); } CAssetId CurrentWorldAssetId() const { return x84_mlvlId; } void SetHardMode(bool v) { x228_24_hardMode = v; } bool GetHardMode() const { return x228_24_hardMode; } void ReadPersistentOptions(CInputStream& r); void SetPersistentOptions(const CPersistentOptions& opts) { xa8_systemOptions = opts; } void ImportPersistentOptions(const CPersistentOptions& opts); void ExportPersistentOptions(CPersistentOptions& opts) const; void SetGameOptions(const CGameOptions& opts) { x17c_gameOptions = opts; } void WriteBackupBuf(); std::vector& BackupBuf() { return x218_backupBuf; } u32 GetFileIdx() const { return x20c_saveFileIdx; } void SetFileIdx(u32 idx) { x20c_saveFileIdx = idx; } void SetCardSerial(u64 serial) { x210_cardSerial = serial; } u64 GetCardSerial() const { return x210_cardSerial; } void PutTo(COutputStream& writer); float GetHardModeDamageMultiplier() const; float GetHardModeWeaponMultiplier() const; void InitializeMemoryWorlds(); void InitializeMemoryStates(); struct GameFileStateInfo { double x0_playTime; CAssetId x8_mlvlId; float xc_health; u32 x10_energyTanks; u32 x14_timestamp; u32 x18_itemPercent; float x1c_scanPercent; bool x20_hardMode; }; static GameFileStateInfo LoadGameFileState(const u8* data); }; } // namespace metaforce ================================================ FILE: Runtime/CIOWin.hpp ================================================ #pragma once #include #include #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class CArchitectureMessage; class CArchitectureQueue; class CIOWin { std::string x4_name; size_t m_nameHash; public: enum class EMessageReturn { Normal = 0, Exit = 1, RemoveIOWinAndExit = 2, RemoveIOWin = 3 }; explicit CIOWin(std::string_view name) : x4_name(name) { m_nameHash = std::hash()(name); } virtual ~CIOWin() = default; virtual EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) = 0; virtual bool GetIsContinueDraw() const { return true; } virtual void Draw() {} virtual void PreDraw() {} std::string_view GetName() const { return x4_name; } size_t GetNameHash() const { return m_nameHash; } }; } // namespace metaforce ================================================ FILE: Runtime/CIOWinManager.cpp ================================================ #include "Runtime/CIOWinManager.hpp" #include "Runtime/CArchitectureMessage.hpp" #include "Runtime/CIOWin.hpp" namespace metaforce { bool CIOWinManager::OnIOWinMessage(const CArchitectureMessage& msg) { switch (msg.GetType()) { case EArchMsgType::RemoveIOWin: { const CArchMsgParmVoidPtr& parm = MakeMsg::GetParmDeleteIOWin(msg); CIOWin* iow = FindIOWin(*static_cast(parm.x4_parm1)); if (iow) RemoveIOWin(iow); return false; } case EArchMsgType::CreateIOWin: { const CArchMsgParmInt32Int32IOWin& parm = MakeMsg::GetParmCreateIOWin(msg); AddIOWin(parm.xc_parm3, parm.x4_parm1, parm.x8_parm2); return false; } case EArchMsgType::ChangeIOWinPriority: { const CArchMsgParmInt32Int32VoidPtr& parm = MakeMsg::GetParmChangeIOWinPriority(msg); CIOWin* iow = FindIOWin(*static_cast(parm.xc_parm3)); if (iow) ChangeIOWinPriority(iow, parm.x4_parm1, parm.x8_parm2); return false; } case EArchMsgType::RemoveAllIOWins: { RemoveAllIOWins(); return true; } default: break; } return false; } void CIOWinManager::Draw() const { for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); iow->PreDraw(); if (!iow->GetIsContinueDraw()) break; } for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); iow->Draw(); if (!iow->GetIsContinueDraw()) break; } } bool CIOWinManager::DistributeOneMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) { CArchitectureMessage tmpMsg = msg; for (IOWinPQNode* node = x4_pumpRoot; node;) { IOWinPQNode* next = node->x8_next; CIOWin* iow = node->GetIOWin(); CIOWin::EMessageReturn mret = iow->OnMessage(tmpMsg, x8_localGatherQueue); while (x8_localGatherQueue) { tmpMsg = x8_localGatherQueue.Pop(); if (tmpMsg.GetTarget() == EArchMsgTarget::IOWinManager) { if (OnIOWinMessage(tmpMsg)) { x8_localGatherQueue.Clear(); queue.Clear(); return true; } } else queue.Push(std::move(tmpMsg)); } switch (mret) { case CIOWin::EMessageReturn::RemoveIOWinAndExit: case CIOWin::EMessageReturn::RemoveIOWin: RemoveIOWin(iow); break; default: break; } switch (mret) { case CIOWin::EMessageReturn::Exit: case CIOWin::EMessageReturn::RemoveIOWinAndExit: return false; default: break; } node = next; } return false; } void CIOWinManager::PumpMessages(CArchitectureQueue& queue) { while (queue) { CArchitectureMessage msg = queue.Pop(); if (DistributeOneMessage(msg, queue)) break; } } CIOWin* CIOWinManager::FindIOWin(std::string_view name) { size_t findHash = std::hash()(name); for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); if (iow->GetNameHash() == findHash) return iow; } for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); if (iow->GetNameHash() == findHash) return iow; } return nullptr; } std::shared_ptr CIOWinManager::FindAndShareIOWin(std::string_view name) { size_t findHash = std::hash()(name); for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) { std::shared_ptr iow = node->ShareIOWin(); if (iow->GetNameHash() == findHash) return iow; } for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) { std::shared_ptr iow = node->ShareIOWin(); if (iow->GetNameHash() == findHash) return iow; } return std::shared_ptr(); } void CIOWinManager::ChangeIOWinPriority(CIOWin* toChange, int pumpPrio, int drawPrio) { IOWinPQNode* prevNode = nullptr; for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); if (iow == toChange) { if (prevNode) prevNode->x8_next = node->x8_next; node->x4_prio = pumpPrio; IOWinPQNode* testNode = x4_pumpRoot; IOWinPQNode* testPrevNode = nullptr; while (testNode->x4_prio > pumpPrio) { testPrevNode = testNode; testNode = testNode->x8_next; } node->x8_next = testNode; if (testPrevNode) testPrevNode->x8_next = node; else x4_pumpRoot = node; break; } prevNode = node; } prevNode = nullptr; for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); if (iow == toChange) { if (prevNode) prevNode->x8_next = node->x8_next; node->x4_prio = drawPrio; IOWinPQNode* testNode = x0_drawRoot; IOWinPQNode* testPrevNode = nullptr; while (testNode->x4_prio > drawPrio) { testPrevNode = testNode; testNode = testNode->x8_next; } node->x8_next = testNode; if (testPrevNode) testPrevNode->x8_next = node; else x0_drawRoot = node; break; } prevNode = node; } } void CIOWinManager::RemoveAllIOWins() { for (IOWinPQNode* node = x0_drawRoot; node;) { IOWinPQNode* n = node; node = n->x8_next; delete n; } x0_drawRoot = nullptr; for (IOWinPQNode* node = x4_pumpRoot; node;) { IOWinPQNode* n = node; node = n->x8_next; delete n; } x4_pumpRoot = nullptr; } void CIOWinManager::RemoveIOWin(CIOWin* chIow) { IOWinPQNode* prevNode = nullptr; for (IOWinPQNode* node = x4_pumpRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); if (iow == chIow) { if (prevNode) prevNode->x8_next = node->x8_next; else x4_pumpRoot = node->x8_next; delete node; break; } prevNode = node; } prevNode = nullptr; for (IOWinPQNode* node = x0_drawRoot; node; node = node->x8_next) { CIOWin* iow = node->GetIOWin(); if (iow == chIow) { if (prevNode) prevNode->x8_next = node->x8_next; else x0_drawRoot = node->x8_next; delete node; break; } prevNode = node; } } void CIOWinManager::AddIOWin(std::weak_ptr chIow, int pumpPrio, int drawPrio) { IOWinPQNode* node; IOWinPQNode* prevNode = nullptr; for (node = x4_pumpRoot; node && pumpPrio < node->x4_prio; node = node->x8_next) prevNode = node; IOWinPQNode* newNode = new IOWinPQNode(chIow, pumpPrio, node); if (prevNode) prevNode->x8_next = newNode; else x4_pumpRoot = newNode; prevNode = nullptr; for (node = x0_drawRoot; node && drawPrio < node->x4_prio; node = node->x8_next) prevNode = node; newNode = new IOWinPQNode(chIow, drawPrio, node); if (prevNode) prevNode->x8_next = newNode; else x0_drawRoot = newNode; } } // namespace metaforce ================================================ FILE: Runtime/CIOWinManager.hpp ================================================ #pragma once #include #include #include "Runtime/CArchitectureQueue.hpp" #include "Runtime/CIOWin.hpp" #include "Runtime/rstl.hpp" namespace metaforce { class CIOWinManager { struct IOWinPQNode { std::shared_ptr x0_iowin; int x4_prio; CIOWinManager::IOWinPQNode* x8_next = nullptr; IOWinPQNode(std::weak_ptr iowin, int prio, CIOWinManager::IOWinPQNode* next) : x0_iowin(iowin), x4_prio(prio), x8_next(next) {} std::shared_ptr ShareIOWin() const { return std::shared_ptr(x0_iowin); } CIOWin* GetIOWin() const { return x0_iowin.get(); } }; IOWinPQNode* x0_drawRoot = nullptr; IOWinPQNode* x4_pumpRoot = nullptr; CArchitectureQueue x8_localGatherQueue; public: bool OnIOWinMessage(const CArchitectureMessage& msg); void Draw() const; bool DistributeOneMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue); void PumpMessages(CArchitectureQueue& queue); CIOWin* FindIOWin(std::string_view name); std::shared_ptr FindAndShareIOWin(std::string_view name); void ChangeIOWinPriority(CIOWin* toChange, int pumpPrio, int drawPrio); void RemoveAllIOWins(); void RemoveIOWin(CIOWin* toRemove); void AddIOWin(std::weak_ptr toAdd, int pumpPrio, int drawPrio); bool IsEmpty() const { return x0_drawRoot == nullptr && x4_pumpRoot == nullptr; } }; } // namespace metaforce ================================================ FILE: Runtime/CInGameTweakManagerBase.hpp ================================================ #pragma once #include #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { class CTweakValue { public: struct Audio { float x0_fadeIn, x4_fadeOut, x8_volume; std::string xc_fileName; CAssetId x1c_res; Audio() = default; Audio(float fadeIn, float fadeOut, float vol, std::string_view fileName, u32 handle) : x0_fadeIn(fadeIn), x4_fadeOut(fadeOut), x8_volume(vol), xc_fileName(fileName), x1c_res(handle) {} float GetFadeIn() const { return x0_fadeIn; } float GetFadeOut() const { return x4_fadeOut; } float GetVolume() const { return x8_volume; } std::string_view GetFileName() const { return xc_fileName; } CAssetId GetResId() const { return x1c_res; } static Audio None() { return Audio{0.f, 0.f, 0.f, "", 0}; } }; enum class EType {}; private: EType x0_type; std::string x4_key; std::string x14_str; Audio x24_audio; union { u32 x44_int; float x44_flt; }; public: CTweakValue() = default; // CTweakValue(CTextInputStream&); // void PutTo(CTextOutStream&); std::string_view GetName() const { return x4_key; } std::string_view GetValueAsString() const; void SetValueFromString(std::string_view); const Audio& GetAudio() const { return x24_audio; } EType GetType() const { return x0_type; } }; class CInGameTweakManagerBase { protected: std::vector x0_values; public: bool HasTweakValue(std::string_view name) const { return std::any_of(x0_values.cbegin(), x0_values.cend(), [name](const auto& value) { return value.GetName() == name; }); } const CTweakValue* GetTweakValue(std::string_view name) const { const auto iter = std::find_if(x0_values.cbegin(), x0_values.cend(), [name](const auto& value) { return value.GetName() == name; }); if (iter == x0_values.cend()) { return nullptr; } return &*iter; } bool ReadFromMemoryCard(std::string_view name) { return true; } static std::string GetIdentifierForMidiEvent(CAssetId world, CAssetId area, std::string_view midiObj) { return fmt::format("World {} Area {} MidiObject: {}", world, area, midiObj); } }; } // namespace metaforce ================================================ FILE: Runtime/CInfiniteLoopDetector.cpp ================================================ #include "Runtime/CInfiniteLoopDetector.hpp" #include "Runtime/Logging.hpp" namespace metaforce { namespace { std::chrono::system_clock::time_point g_WatchDog = std::chrono::system_clock::now(); std::mutex g_mutex; } // namespace CInfiniteLoopDetector::CInfiniteLoopDetector(int duration) : m_duration(duration), m_futureObj(m_stopRequested.get_future()) {} bool CInfiniteLoopDetector::stopRequested() const { return m_futureObj.wait_for(std::chrono::milliseconds(0)) != std::future_status::timeout; } void CInfiniteLoopDetector::run() { std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); while (!stopRequested()) { if (std::chrono::duration_cast(std::chrono::system_clock::now() - start) > std::chrono::milliseconds(m_duration)) { std::lock_guard guard(g_mutex); if (std::chrono::duration_cast(std::chrono::system_clock::now() - g_WatchDog) > std::chrono::milliseconds(m_duration)) { spdlog::fatal("INFINITE LOOP DETECTED!"); } } } } void CInfiniteLoopDetector::UpdateWatchDog(std::chrono::system_clock::time_point time) { std::lock_guard guard(g_mutex); g_WatchDog = time; } void CInfiniteLoopDetector::stop() { m_stopRequested.set_value(); } } // namespace metaforce ================================================ FILE: Runtime/CInfiniteLoopDetector.hpp ================================================ #pragma once #include #include #include namespace metaforce { class CInfiniteLoopDetector { int m_duration = 0; std::mutex m_mutex; std::promise m_stopRequested; std::future m_futureObj; bool stopRequested() const; public: explicit CInfiniteLoopDetector(int duration=1000); void run(); void stop(); static void UpdateWatchDog(std::chrono::system_clock::time_point WatchDog); }; } ================================================ FILE: Runtime/CMFGameBase.hpp ================================================ #pragma once #include "Runtime/CIOWin.hpp" namespace metaforce { class CMFGameBase : public CIOWin { public: explicit CMFGameBase(const char* name) : CIOWin(name) {} }; class CMFGameLoaderBase : public CIOWin { public: explicit CMFGameLoaderBase(const char* name) : CIOWin(name) {} }; } // namespace metaforce ================================================ FILE: Runtime/CMain.cpp ================================================ #include "CResourceNameDatabase.hpp" #include #include #include #include #include #include "ImGuiEngine.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/MP1/MP1.hpp" #include "Runtime/ConsoleVariables/FileStoreManager.hpp" #include "Runtime/ConsoleVariables/CVarManager.hpp" #include "Runtime/CInfiniteLoopDetector.hpp" #include "Runtime/Logging.hpp" // #include "amuse/BooBackend.hpp" #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #endif #include "../version.h" // #include // #pragma STDC FENV_ACCESS ON #include #include #include #include #include #if defined(ANDROID) #include #endif #include "Runtime/Graphics/CTexture.hpp" using namespace std::literals; class Limiter { using delta_clock = std::chrono::high_resolution_clock; using duration_t = std::chrono::nanoseconds; public: void Reset() { m_oldTime = delta_clock::now(); } void Sleep(duration_t targetFrameTime) { if (targetFrameTime.count() == 0) { return; } auto start = delta_clock::now(); duration_t adjustedSleepTime = SleepTime(targetFrameTime); if (adjustedSleepTime.count() > 0) { NanoSleep(adjustedSleepTime); duration_t overslept = TimeSince(start) - adjustedSleepTime; if (overslept < duration_t{targetFrameTime}) { m_overheadTimes[m_overheadTimeIdx] = overslept; m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size(); } } Reset(); } duration_t SleepTime(duration_t targetFrameTime) { const auto sleepTime = duration_t{targetFrameTime} - TimeSince(m_oldTime); m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) / m_overheadTimes.size(); if (sleepTime > m_overhead) { return sleepTime - m_overhead; } return duration_t{0}; } private: delta_clock::time_point m_oldTime; std::array m_overheadTimes{}; size_t m_overheadTimeIdx = 0; duration_t m_overhead = duration_t{0}; duration_t TimeSince(delta_clock::time_point start) { return std::chrono::duration_cast(delta_clock::now() - start); } #if _WIN32 void NanoSleep(const duration_t duration) { static bool initialized = false; static double countPerNs; static size_t numSleeps = 0; // QueryPerformanceFrequency's result is constant, but calling it occasionally // appears to stabilize QueryPerformanceCounter. Without it, the game drifts // from 60hz to 144hz. (Cursed, but I suspect it's NVIDIA/G-SYNC related) if (!initialized || numSleeps++ % 1000 == 0) { LARGE_INTEGER freq; if (QueryPerformanceFrequency(&freq) == 0) { spdlog::warn("QueryPerformanceFrequency failed: {}", GetLastError()); return; } countPerNs = static_cast(freq.QuadPart) / 1e9; initialized = true; numSleeps = 0; } LARGE_INTEGER start, current; QueryPerformanceCounter(&start); LONGLONG ticksToWait = static_cast(duration.count() * countPerNs); if (DWORD ms = std::chrono::duration_cast(duration).count(); ms > 1) { ::Sleep(ms - 1); } do { QueryPerformanceCounter(¤t); _mm_pause(); // Yield CPU } while (current.QuadPart - start.QuadPart < ticksToWait); } #else void NanoSleep(const duration_t duration) { std::this_thread::sleep_for(duration); } #endif }; extern std::string ExeDir; namespace metaforce { std::optional g_mainMP1; SDL_Window* g_window; static std::string CPUFeatureString(const zeus::CPUInfo& cpuInf) { std::string features; #if defined(__x86_64__) || defined(_M_X64) auto AddFeature = [&features](const char* str) { if (!features.empty()) features += ", "; features += str; }; if (cpuInf.AESNI) AddFeature("AES-NI"); if (cpuInf.SSE1) AddFeature("SSE"); if (cpuInf.SSE2) AddFeature("SSE2"); if (cpuInf.SSE3) AddFeature("SSE3"); if (cpuInf.SSSE3) AddFeature("SSSE3"); if (cpuInf.SSE4a) AddFeature("SSE4a"); if (cpuInf.SSE41) AddFeature("SSE4.1"); if (cpuInf.SSE42) AddFeature("SSE4.2"); if (cpuInf.AVX) AddFeature("AVX"); if (cpuInf.AVX2) AddFeature("AVX2"); #endif return features; } struct Application { private: int m_argc; char** m_argv; FileStoreManager& m_fileMgr; CVarManager& m_cvarManager; CVarCommons& m_cvarCommons; CResourceNameDatabase& m_nameDatabase; ImGuiConsole m_imGuiConsole; std::string m_deferredProject; bool m_projectInitialized = false; // std::optional m_amuseAllocWrapper; // std::unique_ptr m_voiceEngine; Limiter m_limiter{}; bool m_firstFrame = true; bool m_fullscreenToggleRequested = false; bool m_quitRequested = false; bool m_lAltHeld = false; using delta_clock = std::chrono::high_resolution_clock; delta_clock::time_point m_prevFrameTime; std::vector m_deferredControllers; // used to capture controllers added before CInputGenerator // is built, i.e during initialization public: Application(int argc, char** argv, FileStoreManager& fileMgr, CVarManager& cvarMgr, CVarCommons& cvarCmns, CResourceNameDatabase& nameDatabase) : m_argc(argc) , m_argv(argv) , m_fileMgr(fileMgr) , m_cvarManager(cvarMgr) , m_cvarCommons(cvarCmns) , m_nameDatabase(nameDatabase) , m_imGuiConsole(cvarMgr, cvarCmns) {} void onAppLaunched(const AuroraInfo& info) noexcept { initialize(); VISetWindowTitle(fmt::format("Metaforce {} [{}]", METAFORCE_WC_DESCRIBE, backend_name(info.backend)).c_str()); // m_voiceEngine = boo::NewAudioVoiceEngine("metaforce", "Metaforce"); // m_voiceEngine->setVolume(0.7f); // m_amuseAllocWrapper.emplace(*m_voiceEngine); #if TARGET_OS_IOS || TARGET_OS_TV m_deferredProject = std::string{m_fileMgr.getStoreRoot()} + "game.iso"; #else for (int i = 1; i < m_argc; ++i) { std::string arg = m_argv[i]; if (m_deferredProject.empty() && !arg.starts_with('-') && !arg.starts_with('+') && (CBasics::IsDir(arg.c_str()) || CBasics::IsFile(arg.c_str()))) m_deferredProject = arg; else if (arg == "--no-sound") { // m_voiceEngine->setVolume(0.f); } } #endif // m_voiceEngine->startPump(); } void initialize() { zeus::detectCPU(); const zeus::CPUInfo& cpuInf = zeus::cpuFeatures(); spdlog::info("CPU Name: {}", cpuInf.cpuBrand); spdlog::info("CPU Vendor: {}", cpuInf.cpuVendor); spdlog::info("CPU Features: {}", CPUFeatureString(cpuInf)); } void onSdlEvent(const SDL_Event& event) noexcept { switch (event.type) { case SDL_EVENT_KEY_DOWN: m_lAltHeld = event.key.key == SDLK_LALT; // Toggle fullscreen on ALT+ENTER if (event.key.key == SDLK_RETURN && (event.key.mod & SDL_KMOD_ALT) != 0u && event.key.repeat == 0u) { m_cvarCommons.m_fullscreen->fromBoolean(!m_cvarCommons.m_fullscreen->toBoolean()); } break; case SDL_EVENT_KEY_UP: if (m_lAltHeld && event.key.key == SDLK_LALT) { m_imGuiConsole.ToggleVisible(); m_lAltHeld = false; } break; case SDL_EVENT_DROP_FILE: { m_imGuiConsole.m_gameDiscSelected = event.drop.data; break; } default: break; } } bool onAppIdle(float realDt) noexcept { #ifdef NDEBUG /* Ping the watchdog to let it know we're still alive */ CInfiniteLoopDetector::UpdateWatchDog(std::chrono::system_clock::now()); #endif if (!m_projectInitialized && !m_deferredProject.empty()) { spdlog::info("Loading game from '{}'", m_deferredProject); if (CDvdFile::Initialize(m_deferredProject)) { m_projectInitialized = true; m_cvarCommons.m_lastDiscPath->fromLiteral(m_deferredProject); } else { const std::string_view dvdErr = CDvdFile::GetLastError(); if (dvdErr.empty()) { spdlog::error("Failed to open disc image '{}'", m_deferredProject); m_imGuiConsole.m_errorString = fmt::format("Failed to open disc image '{}'", m_deferredProject); } else { spdlog::error("Failed to open disc image '{}': {}", m_deferredProject, dvdErr); m_imGuiConsole.m_errorString = fmt::format("Failed to open disc image '{}': {}", m_deferredProject, dvdErr); } if (m_deferredProject.starts_with("content://")) { m_cvarCommons.m_lastDiscPath->fromLiteral(""sv); } } m_deferredProject.clear(); } const auto targetFrameTime = getTargetFrameTime(); bool skipRetrace = false; if (g_ResFactory != nullptr) { // OPTICK_EVENT("Async Load Resources"); const auto idleTime = m_limiter.SleepTime(targetFrameTime); skipRetrace = g_ResFactory->AsyncIdle(idleTime); } if (skipRetrace) { // We stopped loading resources to catch the next frame m_limiter.Reset(); } else { // No more to load, and we're under frame time { // OPTICK_EVENT("Sleep"); m_limiter.Sleep(targetFrameTime); } } // OPTICK_FRAME("MainThread"); // Check if fullscreen has been toggled, if so set the fullscreen cvar accordingly if (m_fullscreenToggleRequested) { m_cvarCommons.m_fullscreen->fromBoolean(!m_cvarCommons.getFullscreen()); m_fullscreenToggleRequested = false; } // Check if the user has modified the fullscreen CVar, if so set fullscreen state accordingly if (m_cvarCommons.m_fullscreen->isModified()) { VISetWindowFullscreen(m_cvarCommons.getFullscreen()); } // Let CVarManager inform all CVar listeners of the CVar's state and clear all mdoified flags if necessary m_cvarManager.proc(); if (!g_mainMP1 && m_projectInitialized) { g_mainMP1.emplace(nullptr, nullptr); auto result = g_mainMP1->Init(m_argc, m_argv, m_fileMgr, &m_cvarManager); if (!result.empty()) { spdlog::error("{}", result); m_imGuiConsole.m_errorString = result; g_mainMP1.reset(); CDvdFile::Shutdown(); m_projectInitialized = false; m_cvarCommons.m_lastDiscPath->fromLiteral(""sv); } } float dt = 1 / 60.f; if (m_cvarCommons.m_variableDt->toBoolean()) { dt = std::min(realDt, 1 / 30.f); } m_imGuiConsole.PreUpdate(); if (g_mainMP1) { // if (m_voiceEngine) { // m_voiceEngine->lockPump(); // } if (g_mainMP1->Proc(dt)) { return false; } // if (m_voiceEngine) { // m_voiceEngine->unlockPump(); // } } m_imGuiConsole.PostUpdate(); if (!g_mainMP1 && m_imGuiConsole.m_gameDiscSelected) { std::optional result; m_imGuiConsole.m_gameDiscSelected.swap(result); m_deferredProject = std::move(*result); } if (m_quitRequested || m_imGuiConsole.m_quitRequested || m_cvarManager.restartRequired()) { if (g_mainMP1) { g_mainMP1->Quit(); } else { return false; } } return true; } void onAppDraw() noexcept { // OPTICK_EVENT("Draw"); if (g_Renderer != nullptr) { g_Renderer->BeginScene(); if (g_mainMP1) { g_mainMP1->Draw(); } g_Renderer->EndScene(); } m_imGuiConsole.PostDraw(); } void onAppPostDraw() noexcept { // OPTICK_EVENT("PostDraw"); // if (m_voiceEngine) { // m_voiceEngine->pumpAndMixVoices(); // } #ifdef EMSCRIPTEN CDvdFile::DoWork(); #endif CGraphics::TickRenderTimings(); } void onAppWindowResized(const AuroraWindowSize& size) noexcept { if (size.width != m_cvarCommons.getWindowSize().x || size.height != m_cvarCommons.getWindowSize().y) { m_cvarCommons.m_windowSize->fromVec2i(zeus::CVector2i(size.width, size.height)); } CGraphics::SetViewportResolution({static_cast(size.fb_width), static_cast(size.fb_height)}); } void onAppWindowMoved(const AuroraWindowPos& pos) { if (pos.x > 0 && pos.y > 0 && (pos.x != m_cvarCommons.getWindowPos().x || pos.y != m_cvarCommons.getWindowPos().y)) { m_cvarCommons.m_windowPos->fromVec2i(zeus::CVector2i(pos.x, pos.y)); } } void onAppDisplayScaleChanged(float scale) noexcept { ImGuiEngine_Initialize(scale); } void onControllerAdded(uint32_t which) noexcept { m_imGuiConsole.ControllerAdded(which); } void onControllerRemoved(uint32_t which) noexcept { m_imGuiConsole.ControllerRemoved(which); } void onAppExiting() noexcept { m_imGuiConsole.Shutdown(); // if (m_voiceEngine) { // m_voiceEngine->unlockPump(); // m_voiceEngine->stopPump(); // } if (g_mainMP1) { g_mainMP1->Shutdown(); } g_mainMP1.reset(); m_cvarManager.serialize(); // m_amuseAllocWrapper.reset(); // m_voiceEngine.reset(); CDvdFile::Shutdown(); } void onImGuiInit(float scale) noexcept { ImGuiEngine_Initialize(scale); } void onImGuiAddTextures() noexcept { ImGuiEngine_AddTextures(); } [[nodiscard]] std::chrono::nanoseconds getTargetFrameTime() const { if (m_cvarCommons.getVariableFrameTime()) { return std::chrono::nanoseconds{0}; } return std::chrono::duration_cast(std::chrono::seconds{1}) / 60; } }; } // namespace metaforce static void SetupBasics() { auto result = zeus::validateCPU(); if (!result.first) { #if _WIN32 && !WINDOWS_STORE std::string msg = fmt::format("ERROR: This build of Metaforce requires the following CPU features:\n{}\n", metaforce::CPUFeatureString(result.second)); MessageBoxW(nullptr, nowide::widen(msg).c_str(), L"CPU error", MB_OK | MB_ICONERROR); #else fmt::print(stderr, "ERROR: This build of Metaforce requires the following CPU features:\n{}\n", metaforce::CPUFeatureString(result.second)); #endif exit(1); } #if defined(ANDROID) { std::vector sinks; if (auto defaultLogger = spdlog::default_logger(); defaultLogger != nullptr) { sinks = defaultLogger->sinks(); } sinks.emplace_back(std::make_shared("Metaforce")); auto logger = std::make_shared("metaforce-android", sinks.begin(), sinks.end()); logger->set_level(spdlog::level::trace); logger->flush_on(spdlog::level::warn); spdlog::set_default_logger(std::move(logger)); } #endif #if SENTRY_ENABLED std::string cacheDir{metaforce::FileStoreManager::instance()->getStoreRoot()}; logvisor::RegisterSentry("metaforce", METAFORCE_WC_DESCRIBE, cacheDir.c_str()); #endif } static bool IsClientLoggingEnabled(int argc, char** argv) { #ifdef EMSCRIPTEN return true; #else for (int i = 1; i < argc; ++i) { if (!strncmp(argv[i], "-l", 2)) { return true; } } return false; #endif } static std::unique_ptr g_app; static bool g_paused; static void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len) { spdlog::level::level_enum severity = spdlog::level::critical; switch (level) { case LOG_DEBUG: severity = spdlog::level::debug; break; case LOG_INFO: severity = spdlog::level::info; break; case LOG_WARNING: severity = spdlog::level::warn; break; case LOG_ERROR: severity = spdlog::level::err; break; default: break; } const std::string_view view(message, len); spdlog::log(severity, "[{}] {}", module, view); if (level == LOG_FATAL) { spdlog::default_logger()->flush(); auto msg = fmt::format("Metaforce encountered an internal error:\n\n{}", view); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Metaforce", msg.c_str(), metaforce::g_window); std::abort(); } } static void aurora_imgui_init_callback(const AuroraWindowSize* size) { g_app->onImGuiInit(size->scale); } #if !WINDOWS_STORE int main(int argc, char** argv) { // TODO: This seems to fix a lot of weird issues with rounding // but breaks animations, need to research why this is the case // for now it's disabled // fesetround(FE_TOWARDZERO); if (argc > 1 && !strcmp(argv[1], "--dlpackage")) { fmt::print("{}\n", METAFORCE_DLPACKAGE); return 100; } metaforce::FileStoreManager fileMgr{"AxioDL", "metaforce"}; SetupBasics(); std::vector args; for (int i = 1; i < argc; ++i) { args.emplace_back(argv[i]); } auto icon = metaforce::GetIcon(); // FIXME: logvisor needs to copy this std::string logFilePath; bool restart = false; do { metaforce::CVarManager cvarMgr{fileMgr}; metaforce::CVarCommons cvarCmns{cvarMgr}; metaforce::CResourceNameDatabase nameDatabase{fileMgr}; cvarMgr.parseCommandLine(args); if (!restart) { // TODO add clear loggers func to logvisor so we can recreate loggers on restart bool logging = IsClientLoggingEnabled(argc, argv); // #if _WIN32 // if (logging && GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_UNKNOWN) { // logvisor::CreateWin32Console(); // } // #endif // logvisor::RegisterStandardExceptions(); // if (logging) { // logvisor::RegisterConsoleLogger(); // } std::string logFile = cvarCmns.getLogFile(); if (!logFile.empty()) { std::time_t time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); char buf[100]; std::strftime(buf, 100, "%Y-%m-%d_%H-%M-%S", std::localtime(&time)); logFilePath = fmt::format("{}/{}-{}", fileMgr.getStoreRoot(), buf, logFile); // logvisor::RegisterFileLogger(logFilePath.c_str()); } } g_app = std::make_unique(argc, argv, fileMgr, cvarMgr, cvarCmns, nameDatabase); std::string configPath{fileMgr.getStoreRoot()}; const AuroraConfig config{ .appName = "Metaforce", .configPath = configPath.c_str(), .desiredBackend = metaforce::backend_from_string(cvarCmns.getGraphicsApi()), .msaa = cvarCmns.getSamples(), .maxTextureAnisotropy = static_cast(cvarCmns.getAnisotropy()), .startFullscreen = cvarCmns.getFullscreen(), .allowJoystickBackgroundEvents = cvarCmns.getAllowJoystickInBackground(), .windowPosX = cvarCmns.getWindowPos().x, .windowPosY = cvarCmns.getWindowPos().y, .windowWidth = static_cast(cvarCmns.getWindowSize().x < 0 ? 0 : cvarCmns.getWindowSize().x), .windowHeight = static_cast(cvarCmns.getWindowSize().y < 0 ? 0 : cvarCmns.getWindowSize().y), .iconRGBA8 = icon.data.get(), .iconWidth = icon.width, .iconHeight = icon.height, .logCallback = aurora_log_callback, .imGuiInitCallback = aurora_imgui_init_callback, }; const auto info = aurora_initialize(argc, argv, &config); metaforce::g_window = info.window; g_app->onImGuiAddTextures(); g_app->onAppLaunched(info); g_app->onAppWindowResized(info.windowSize); metaforce::CTexture::SetMangleMips(cvarCmns.getMangleMipmaps()); while (!cvarMgr.restartRequired()) { const auto* event = aurora_update(); bool exiting = false; while (event != nullptr && event->type != AURORA_NONE) { switch (event->type) { case AURORA_EXIT: exiting = true; break; case AURORA_SDL_EVENT: g_app->onSdlEvent(event->sdl); break; case AURORA_WINDOW_RESIZED: g_app->onAppWindowResized(event->windowSize); break; case AURORA_WINDOW_MOVED: g_app->onAppWindowMoved(event->windowPos); break; case AURORA_CONTROLLER_ADDED: g_app->onControllerAdded(event->controller); break; case AURORA_CONTROLLER_REMOVED: g_app->onControllerRemoved(event->controller); break; case AURORA_PAUSED: g_paused = true; break; case AURORA_UNPAUSED: g_paused = false; break; case AURORA_DISPLAY_SCALE_CHANGED: g_app->onAppDisplayScaleChanged(event->windowSize.scale); break; default: break; } if (exiting) { break; } ++event; } if (exiting) { break; } if (g_paused) { continue; } if (!aurora_begin_frame()) { continue; } if (!g_app->onAppIdle(1.f / 60.f /* TODO */)) { break; } g_app->onAppDraw(); aurora_end_frame(); g_app->onAppPostDraw(); } g_app->onAppExiting(); aurora_shutdown(); g_app.reset(); restart = cvarMgr.restartRequired(); } while (restart); return 0; } #endif ================================================ FILE: Runtime/CMainFlowBase.cpp ================================================ #include "Runtime/CMainFlowBase.hpp" #include "Runtime/CArchitectureMessage.hpp" namespace metaforce { CIOWin::EMessageReturn CMainFlowBase::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) { switch (msg.GetType()) { case EArchMsgType::TimerTick: AdvanceGameState(queue); break; case EArchMsgType::SetGameState: { const CArchMsgParmInt32& state = MakeMsg::GetParmNewGameflowState(msg); x14_gameState = EClientFlowStates(state.x4_parm); SetGameState(x14_gameState, queue); return EMessageReturn::Exit; } default: break; } return EMessageReturn::Normal; } } // namespace metaforce ================================================ FILE: Runtime/CMainFlowBase.hpp ================================================ #pragma once #include "Runtime/CIOWin.hpp" namespace metaforce { enum class EClientFlowStates { Unspecified = -1, None = 0, WinBad = 1, WinGood = 2, WinBest = 3, LoseGame = 4, Default = 5, StateSetter = 6, PreFrontEnd = 7, FrontEnd = 8, Game = 14, GameExit = 15 }; class CMainFlowBase : public CIOWin { protected: EClientFlowStates x14_gameState = EClientFlowStates::Unspecified; public: explicit CMainFlowBase(const char* name) : CIOWin(name) {} EMessageReturn OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) override; virtual void AdvanceGameState(CArchitectureQueue& queue) = 0; virtual void SetGameState(EClientFlowStates state, CArchitectureQueue& queue) = 0; }; } // namespace metaforce ================================================ FILE: Runtime/CMakeLists.txt ================================================ macro(runtime_add_list rel_path a_list) unset(tmp_list) foreach (path IN LISTS ${a_list}) if (IS_ABSOLUTE ${path}) list(APPEND tmp_list "${path}") else () list(APPEND tmp_list "${rel_path}/${path}") endif () endforeach (path) set(${a_list} "${tmp_list}" PARENT_SCOPE) endmacro(runtime_add_list) add_subdirectory(Audio) add_subdirectory(Character) add_subdirectory(Graphics) add_subdirectory(Collision) add_subdirectory(Camera) add_subdirectory(World) add_subdirectory(Weapon) add_subdirectory(AutoMapper) add_subdirectory(GuiSys) add_subdirectory(Input) add_subdirectory(Particle) if (WIN32) list(APPEND PLAT_SRCS CMemoryCardSysWin.cpp) else () list(APPEND PLAT_SRCS CMemoryCardSysNix.cpp) endif () find_package(Python3 COMPONENTS Interpreter REQUIRED) add_custom_command(OUTPUT TCastTo.hpp TCastTo.cpp DEPENDS MkCastTo.py COMMAND ${Python3_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/MkCastTo.py WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating cast functions") add_subdirectory(MP1) add_subdirectory(MP2) add_subdirectory(MP3) set(CAST_TO_SOURCES MkCastTo.py TCastTo.hpp TCastTo.cpp) set(RUNTIME_SOURCES_A RetroTypes.hpp RetroTypes.cpp ${CAST_TO_SOURCES} ${MP1_SOURCES} ${AUDIO_SOURCES} ${AUTOMAPPER_SOURCES} ${CAMERA_SOURCES} ${CHARACTER_SOURCES} ${COLLISION_SOURCES} ${GRAPHICS_SOURCES}) set(RUNTIME_SOURCES_B ${CAST_TO_SOURCES} ${GUISYS_SOURCES} ${INPUT_SOURCES} ${PARTICLE_SOURCES} ${WORLD_SOURCES} ${WEAPON_SOURCES} CInfiniteLoopDetector.hpp CInfiniteLoopDetector.cpp ConsoleVariables/FileStoreManager.hpp ConsoleVariables/FileStoreManager.cpp ConsoleVariables/CVar.hpp ConsoleVariables/CVar.cpp ConsoleVariables/CVarManager.hpp ConsoleVariables/CVarManager.cpp ConsoleVariables/CVarCommons.hpp ConsoleVariables/CVarCommons.cpp Tweaks/ITweak.hpp Tweaks/ITweakAutoMapper.hpp Tweaks/ITweakBall.hpp Tweaks/ITweakGame.hpp Tweaks/ITweakGui.hpp Tweaks/ITweakGuiColors.hpp Tweaks/ITweakGunRes.hpp Tweaks/ITweakParticle.hpp Tweaks/ITweakPlayer.hpp Tweaks/ITweakPlayerControl.hpp Tweaks/ITweakPlayerGun.hpp Tweaks/ITweakPlayerGun.cpp Tweaks/ITweakPlayerRes.hpp Tweaks/ITweakSlideShow.hpp Tweaks/ITweakTargeting.hpp IMain.hpp CStopwatch.hpp CStopwatch.cpp Streams/IOStreams.hpp Streams/IOStreams.cpp Streams/CMemoryStreamOut.hpp Streams/CMemoryStreamOut.cpp Streams/CInputStream.hpp Streams/CInputStream.cpp Streams/COutputStream.hpp Streams/COutputStream.cpp Streams/CMemoryInStream.hpp Streams/CZipInputStream.hpp Streams/CZipInputStream.cpp Streams/ContainerReaders.hpp Streams/CTextInStream.hpp Streams/CTextInStream.cpp Streams/CTextOutStream.hpp Streams/CTextOutStream.cpp Streams/CFileOutStream.hpp Streams/CFileOutStream.cpp CGameAllocator.hpp CGameAllocator.cpp CMemoryCardSys.hpp CMemoryCardSys.cpp CScannableObjectInfo.hpp CScannableObjectInfo.cpp CWorldSaveGameInfo.hpp CWorldSaveGameInfo.cpp CDependencyGroup.hpp CDependencyGroup.cpp CBasics.hpp CBasicsPC.cpp CIOWin.hpp CIOWinManager.hpp CIOWinManager.cpp CStateManager.hpp CStateManager.cpp CGameState.hpp CGameState.cpp CScriptMailbox.hpp CScriptMailbox.cpp CPlayerState.hpp CPlayerState.cpp CRandom16.hpp CRandom16.cpp CResFactory.hpp CResFactory.cpp CResLoader.hpp CResLoader.cpp CDvdRequest.hpp CDvdFile.hpp CDvdFile.cpp IObjectStore.hpp CSimplePool.hpp CSimplePool.cpp CGameOptions.hpp CGameOptions.cpp CStaticInterference.hpp CStaticInterference.cpp CCRC32.hpp CCRC32.cpp IFactory.hpp IObjFactory.hpp CObjectList.hpp CObjectList.cpp GameObjectLists.hpp GameObjectLists.cpp CSortedLists.hpp CSortedLists.cpp CArchitectureMessage.hpp CArchitectureQueue.hpp IObj.hpp IVParamObj.hpp CTimeProvider.hpp CTimeProvider.cpp CToken.hpp CToken.cpp CFactoryMgr.hpp CFactoryMgr.cpp CPakFile.hpp CPakFile.cpp CStringExtras.hpp CStringExtras.cpp CMainFlowBase.hpp CMainFlowBase.cpp CMFGameBase.hpp CInGameTweakManagerBase.hpp CGameDebug.hpp CGameHintInfo.hpp CGameHintInfo.cpp rstl.hpp GameGlobalObjects.hpp GameGlobalObjects.cpp GCNTypes.hpp CTextureCache.hpp CTextureCache.cpp CMayaSpline.hpp CMayaSpline.cpp ImGuiPlayerLoadouts.hpp ImGuiPlayerLoadouts.cpp CResourceNameDatabase.hpp CResourceNameDatabase.cpp ${PLAT_SRCS}) function(add_runtime_common_library name) add_library(${name} ${ARGN}) target_compile_definitions(${name} PUBLIC "-DMETAFORCE_TARGET_BYTE_ORDER=__BYTE_ORDER__") if (WINDOWS_STORE) set_property(TARGET ${name} PROPERTY VS_WINRT_COMPONENT TRUE) endif () endfunction() set(RUNTIME_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}) set(RUNTIME_LIBRARIES zeus nod::nod NESEmulator libjpeg-turbo jbus kabufuda #OptickCore imgui_support aurora::core aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx spdlog::spdlog $<$:nowide::nowide> ZLIB::ZLIB ) add_runtime_common_library(RuntimeCommon ${RUNTIME_SOURCES_A}) target_include_directories(RuntimeCommon PUBLIC ${RUNTIME_INCLUDES}) target_link_libraries(RuntimeCommon PUBLIC ${RUNTIME_LIBRARIES}) add_runtime_common_library(RuntimeCommonB ${RUNTIME_SOURCES_B}) target_include_directories(RuntimeCommonB PUBLIC ${RUNTIME_INCLUDES}) target_link_libraries(RuntimeCommonB PUBLIC ${RUNTIME_LIBRARIES}) if (WIN32) configure_file(platforms/win/metaforce.rc.in "${CMAKE_CURRENT_SOURCE_DIR}/platforms/win/metaforce.rc" @ONLY) set(PLAT_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/platforms/win/metaforce.rc" platforms/win/metaforce.manifest) if (WINDOWS_STORE) set(UWP_ASSETS platforms/win/Assets/LargeTile.scale-100.png platforms/win/Assets/LargeTile.scale-125.png platforms/win/Assets/LargeTile.scale-150.png platforms/win/Assets/LargeTile.scale-200.png platforms/win/Assets/LargeTile.scale-400.png platforms/win/Assets/SmallTile.scale-100.png platforms/win/Assets/SmallTile.scale-125.png platforms/win/Assets/SmallTile.scale-150.png platforms/win/Assets/SmallTile.scale-200.png platforms/win/Assets/SmallTile.scale-400.png platforms/win/Assets/SplashScreen.scale-100.png platforms/win/Assets/SplashScreen.scale-125.png platforms/win/Assets/SplashScreen.scale-150.png platforms/win/Assets/SplashScreen.scale-200.png platforms/win/Assets/SplashScreen.scale-400.png platforms/win/Assets/Square44x44Logo.scale-100.png platforms/win/Assets/Square44x44Logo.scale-125.png platforms/win/Assets/Square44x44Logo.scale-150.png platforms/win/Assets/Square44x44Logo.scale-200.png platforms/win/Assets/Square44x44Logo.scale-400.png platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-16.png platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-24.png platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-32.png platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-48.png platforms/win/Assets/Square44x44Logo.altform-unplated_targetsize-256.png platforms/win/Assets/Square150x150Logo.scale-100.png platforms/win/Assets/Square150x150Logo.scale-125.png platforms/win/Assets/Square150x150Logo.scale-150.png platforms/win/Assets/Square150x150Logo.scale-200.png platforms/win/Assets/Square150x150Logo.scale-400.png platforms/win/Assets/metaforce.scale-100.png platforms/win/Assets/metaforce.scale-125.png platforms/win/Assets/metaforce.scale-150.png platforms/win/Assets/metaforce.scale-200.png platforms/win/Assets/metaforce.scale-400.png platforms/win/Assets/WideTile.scale-100.png platforms/win/Assets/WideTile.scale-125.png platforms/win/Assets/WideTile.scale-150.png platforms/win/Assets/WideTile.scale-200.png platforms/win/Assets/WideTile.scale-400.png) set_property(SOURCE platforms/win/Package.appxmanifest PROPERTY VS_DEPLOYMENT_CONTENT 1) set_property(SOURCE ${UWP_ASSETS} PROPERTY VS_DEPLOYMENT_CONTENT 1) set_property(SOURCE ${UWP_ASSETS} PROPERTY VS_DEPLOYMENT_LOCATION "Assets") list(APPEND PLAT_SRCS ${UWP_ASSETS} platforms/win/Package.appxmanifest) endif () elseif (APPLE) # nothing elseif (UNIX AND NOT ANDROID) set(PLAT_LIBS rt) endif () if (ANDROID) add_library(metaforce SHARED CMain.cpp ${PLAT_SRCS} ImGuiConsole.hpp ImGuiConsole.cpp ImGuiControllerConfig.hpp ImGuiControllerConfig.cpp ImGuiEntitySupport.hpp ImGuiEntitySupport.cpp) set_target_properties(metaforce PROPERTIES OUTPUT_NAME main LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Binaries) else () add_executable(metaforce WIN32 CMain.cpp ${PLAT_SRCS} ImGuiConsole.hpp ImGuiConsole.cpp ImGuiControllerConfig.hpp ImGuiControllerConfig.cpp ImGuiEntitySupport.hpp ImGuiEntitySupport.cpp) endif () # RUNTIME_LIBRARIES repeated here for link ordering target_link_libraries(metaforce PUBLIC RuntimeCommon RuntimeCommonB ${RUNTIME_LIBRARIES} ${PLAT_LIBS} aurora::main) target_compile_definitions(metaforce PUBLIC "-DMETAFORCE_TARGET_BYTE_ORDER=__BYTE_ORDER__") if (ANDROID) # SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static # archive, force an undefined reference so the linker keeps the SDL_main object. target_link_options(metaforce PRIVATE "-Wl,-u,SDL_main") endif () if (WIN32) target_link_options(metaforce PRIVATE /ENTRY:wWinMainCRTStartup) endif () if (APPLE) if (TVOS) set(RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/tvos) set(INFO_PLIST ${RESOURCE_DIR}/Info.plist.in) file(GLOB_RECURSE RESOURCE_FILES "${RESOURCE_DIR}/Base.lproj/*") list(APPEND RESOURCE_FILES ${RESOURCE_DIR}/Assets.car) elseif (IOS) set(RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios) set(INFO_PLIST ${RESOURCE_DIR}/Info.plist.in) file(GLOB_RECURSE RESOURCE_FILES "${RESOURCE_DIR}/Base.lproj/*") list(APPEND RESOURCE_FILES ${RESOURCE_DIR}/Assets.car ${RESOURCE_DIR}/AppIcon60x60@2x.png ${RESOURCE_DIR}/AppIcon76x76@2x~ipad.png) else () set(RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos) set(INFO_PLIST ${RESOURCE_DIR}/Info.plist.in) set(RESOURCE_FILES ${RESOURCE_DIR}/mainicon.icns) endif () target_sources(metaforce PRIVATE ${RESOURCE_FILES}) # Add to resources, preserving directory structure foreach (FILE ${RESOURCE_FILES}) file(RELATIVE_PATH NEW_FILE "${RESOURCE_DIR}" ${FILE}) get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") source_group("Resources/${NEW_FILE_PATH}" FILES "${FILE}") endforeach () set_target_properties( metaforce PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_INFO_PLIST ${INFO_PLIST} MACOSX_BUNDLE_BUNDLE_NAME Metaforce MACOSX_BUNDLE_GUI_IDENTIFIER com.axiodl.Metaforce MACOSX_BUNDLE_BUNDLE_VERSION "${METAFORCE_VERSION_STRING}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${METAFORCE_SHORT_VERSION_STRING}" OUTPUT_NAME Metaforce ) endif () if (WINDOWS_STORE) set_property(TARGET metaforce PROPERTY VS_WINRT_COMPONENT TRUE) # This should match the Package.appxmanifest set_property(TARGET metaforce PROPERTY VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION "10.0.14393.0") endif () if (EMSCRIPTEN) target_link_options(metaforce PRIVATE -sTOTAL_MEMORY=268435456 -sALLOW_MEMORY_GROWTH --preload-file "${CMAKE_SOURCE_DIR}/files@/") endif () ================================================ FILE: Runtime/CMayaSpline.cpp ================================================ #include "Runtime/CMayaSpline.hpp" #include "Runtime/Streams/CInputStream.hpp" namespace metaforce { void ValidateTangent(zeus::CVector2f& tangent) { if (tangent.x() < 0.f) { tangent.x() = 0.f; } const float mag = tangent.magnitude(); if (mag != 0.f) { tangent /= mag; } if (tangent.x() == 0.f && tangent.y() != 0.f) { const float mul = tangent.y() >= 0.f ? 1.f : -1.f; tangent.x() = 0.0001f; tangent.y() = 5729578.0f * tangent.x() * mul; } } CMayaSplineKnot::CMayaSplineKnot(CInputStream& in) { x0_time = in.ReadFloat(); x4_amplitude = in.ReadFloat(); x8_ = in.ReadInt8(); x9_ = in.ReadInt8(); if (x8_ == 5) { float x = in.ReadFloat(); float y = in.ReadFloat(); xc_cachedTangentA = {x, y}; } if (x9_ == 5) { float x = in.ReadFloat(); float y = in.ReadFloat(); x14_cachedTangentB = {x, y}; } } void CMayaSplineKnot::GetTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next, zeus::CVector2f& tangentA, zeus::CVector2f& tangentB) { if (xa_24_dirty) { CalculateTangents(prev, next); } tangentA = xc_cachedTangentA; tangentB = x14_cachedTangentB; } void CMayaSplineKnot::CalculateTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next) { xa_24_dirty = false; bool calculateTangents = false; if (x8_ == 4 && prev != nullptr) { float fVar2 = std::abs(prev->GetAmplitude() - GetAmplitude()); float fVar3 = fVar2; if (next != nullptr) { fVar3 = std::abs(next->GetAmplitude() - GetAmplitude()); } if (fVar3 <= 0.05f || fVar2 <= 0.05f) { x8_ = 1; } } if (x8_ == 0) { if (prev == nullptr) { xc_cachedTangentA = {1.f, 0.f}; } else { xc_cachedTangentA = {GetTime() - prev->GetTime(), GetAmplitude() - prev->GetAmplitude()}; } } else if (x8_ == 1) { float fVar1 = 0.f; if (prev != nullptr) { fVar1 = GetTime() - prev->GetTime(); } else if (next != nullptr) { fVar1 = next->GetTime() - GetTime(); } xc_cachedTangentA = {fVar1, 0.f}; } else if (x8_ == 2) { calculateTangents = true; } else if (x8_ == 3) { xc_cachedTangentA = zeus::skOne2f; } else if (x8_ == 4) { x8_ = 2; calculateTangents = true; } if (x9_ == 0) { if (next == nullptr) { x14_cachedTangentB = {1.f, 0.f}; } else { x14_cachedTangentB = {next->GetTime() - GetTime(), next->GetAmplitude() - GetAmplitude()}; } } else if (x9_ == 1) { float fVar1 = 0.f; if (next != nullptr) { fVar1 = next->GetTime() - GetTime(); } else if (prev != nullptr) { fVar1 = GetTime() - prev->GetTime(); } x14_cachedTangentB = {fVar1, 0.f}; } else if (x9_ == 2) { calculateTangents = true; } else if (x9_ == 3) { x14_cachedTangentB = {1.f, 0.f}; } else if (x9_ == 4 && next != nullptr) { float fVar1 = next->GetAmplitude() - GetAmplitude(); float fVar2 = fVar1; if (prev != nullptr) { fVar2 = prev->GetAmplitude() - GetAmplitude(); } if (fVar1 <= 0.05f || fVar2 <= 0.05f) { x9_ = 1; } calculateTangents = true; } if (calculateTangents) { zeus::CVector2f tangentA; zeus::CVector2f tangentB; if (prev == nullptr && next != nullptr) { tangentA = tangentB = {next->GetTime() - GetTime(), next->GetAmplitude() - GetAmplitude()}; } else if (prev != nullptr && next == nullptr) { tangentA = tangentB = {GetTime() - prev->GetTime(), GetAmplitude() - prev->GetAmplitude()}; } else if (prev != nullptr && next != nullptr) { float timeDiff = next->GetTime() - prev->GetTime(); float ampDiff = next->GetAmplitude() - prev->GetAmplitude(); float amp = timeDiff >= 0.0001f ? ampDiff / timeDiff : (ampDiff <= 0.f ? -5729578.0f : 5729578.0f); float nextTimeDiff = next->GetTime() - GetTime(); float prevTimeDiff = GetTime() - prev->GetTime(); float ampA = 0.f; float ampB = 0.; float timeA = 0.; float timeB = 0.; if (nextTimeDiff >= 0.f) { ampA = nextTimeDiff * amp; } else { timeA = 0.f; ampA = amp; } if (prevTimeDiff >= 0.f) { ampB = prevTimeDiff * amp; } else { timeB = 0.f; } tangentB = {timeB, ampB}; tangentA = {timeA, ampA}; } else { tangentA.zeroOut(); tangentB.zeroOut(); } if (x8_ == 2) { xc_cachedTangentA = tangentA; } if (x9_ == 2) { x14_cachedTangentB = tangentB; } } ValidateTangent(xc_cachedTangentA); ValidateTangent(x14_cachedTangentB); } CMayaSpline::CMayaSpline(CInputStream& in, s32 count) : x0_preInfinity(in.ReadInt8()), x4_postInfinity(in.ReadInt8()) { u32 knotCount = in.ReadLong(); x8_knots.reserve(knotCount); for (size_t i = 0; i < knotCount; ++i) { x8_knots.emplace_back(in); } x18_clampMode = in.ReadInt8(); x1c_minAmplitudeTime = in.ReadFloat(); x20_maxAmplitudeTime = in.ReadFloat(); } float CMayaSpline::GetMinTime() const { return x8_knots.empty() ? 0.f : x8_knots[0].GetTime(); } float CMayaSpline::GetMaxTime() const { return x8_knots.empty() ? 0.f : x8_knots[GetKnotCount() - 1].GetTime(); } float CMayaSpline::GetDuration() const { return x8_knots.empty() ? 0.f : GetMaxTime() - GetMinTime(); } float CMayaSpline::EvaluateAt(float time) { float amplitude = EvaluateAtUnclamped(time); if (x18_clampMode == 1) { if (x1c_minAmplitudeTime > amplitude) { return x1c_minAmplitudeTime; } if (x20_maxAmplitudeTime < amplitude) { return x20_maxAmplitudeTime; } return amplitude; } else if (x18_clampMode == 2) { float center = x20_maxAmplitudeTime - x1c_minAmplitudeTime; if (center > 0.f) { if (amplitude <= FLT_EPSILON + x20_maxAmplitudeTime) { return amplitude - (center * static_cast(s32((amplitude - x20_maxAmplitudeTime) / center) + 1)); } if (amplitude < x1c_minAmplitudeTime - FLT_EPSILON) { return amplitude + (center * static_cast(std::abs(s32((amplitude - x1c_minAmplitudeTime) / center)))); } } } return amplitude; } float CMayaSpline::EvaluateAtUnclamped(float time) { if (x8_knots.empty()) { return 0.f; } u32 lastIdx = x8_knots.size() - 1; bool bVar2 = false; float retVal; if (time < x8_knots[0].GetTime()) { if (x0_preInfinity == 0) { return x8_knots[0].GetAmplitude(); } return EvaluateInfinities(time, true); } else if (x8_knots[lastIdx].GetTime() >= time) { bVar2 = false; s32 local_68 = -1; s32 iVar1 = x24_chachedKnotIndex; if (iVar1 != -1) { if (lastIdx <= iVar1 || x8_knots[lastIdx].GetTime() >= time) { if (iVar1 > 0 && x8_knots[iVar1].GetTime() > time) { s32 iVar3 = iVar1 - 1; bVar2 = x8_knots[iVar3].GetTime() < time; if (bVar2) { local_68 = iVar1; } if (x8_knots[iVar3].GetTime() == time) { x24_chachedKnotIndex = iVar3; return x8_knots[x24_chachedKnotIndex].GetAmplitude(); } } } else { retVal = x8_knots[iVar1 + 1].GetTime(); if (retVal == time) { x24_chachedKnotIndex = lastIdx; return x8_knots[x24_chachedKnotIndex].GetAmplitude(); } if (retVal > time) { bVar2 = true; local_68 = iVar1 + 1; } } } if (!bVar2 && (FindKnot(time, local_68))) { if (local_68 == 0) { x24_chachedKnotIndex = 0; return x8_knots[0].GetAmplitude(); } if (local_68 == x8_knots.size()) { x24_chachedKnotIndex = 0; return x8_knots[lastIdx].GetAmplitude(); } } lastIdx = local_68 - 1; if (x28_ != lastIdx) { x24_chachedKnotIndex = lastIdx; x28_ = lastIdx; if (x8_knots[x24_chachedKnotIndex].GetX9() == 3) { x2c_24_dirty = true; } else { x2c_24_dirty = false; rstl::reserved_vector points; FindControlPoints(x24_chachedKnotIndex, points); CalculateHermiteCoefficients(points, x34_cachedHermitCoefs); x30_cachedMinTime = points[0].x(); } } if (x2c_24_dirty) { return x8_knots[x24_chachedKnotIndex].GetTime(); } else { return EvaluateHermite(time); } } if (x4_postInfinity == 0) { return x8_knots[lastIdx].GetAmplitude(); } return EvaluateInfinities(time, false); } float CMayaSpline::EvaluateInfinities(float time, bool pre) { if (x8_knots.empty()) { return 0.f; } s32 lastIdx = x8_knots.size() - 1; CMayaSplineKnot* curKnot = &x8_knots[0]; const float startTime = x8_knots[0].GetTime(); const float endTime = x8_knots[lastIdx].GetAmplitude(); float center = endTime - startTime; if (zeus::close_enough(center, 0)) { return curKnot->GetAmplitude(); } double tmp = 0.f; float divTime = (time <= endTime) ? std::modf((time - startTime) / center, &tmp) : std::modf((time - endTime) / center, &tmp); center = center * std::abs(divTime); tmp = 1.f + std::abs(tmp); if (!pre) { if (x4_postInfinity == 4) { divTime = std::fmod(tmp, 2.f); if (zeus::close_enough(divTime, 0.f)) { center = startTime + center; } else { center = endTime - center; } } else if (x4_postInfinity == 2 || x4_postInfinity == 3) { center = startTime + center; } else if (x4_postInfinity == 1) { center = time - endTime; zeus::CVector2f tangentA; zeus::CVector2f tangentB; x8_knots[0].GetTangents((lastIdx < 1) ? nullptr : &x8_knots[lastIdx - 2], nullptr, tangentA, tangentB); if (!zeus::close_enough(tangentB.x(), 0.f)) { return x8_knots[lastIdx].GetAmplitude() + (center * tangentB.y() / tangentB.x()); } return x8_knots[lastIdx].GetAmplitude(); } } else if (x0_preInfinity == 4) { divTime = std::fmod(tmp, 2.f); if (zeus::close_enough(divTime, 0.f)) { center = endTime - center; } else { center = startTime + center; } } else if (x0_preInfinity == 2 || x0_preInfinity == 3) { center = endTime - center; } else if (x0_preInfinity == 1) { center = (startTime - time); zeus::CVector2f tangentA; zeus::CVector2f tangentB; x8_knots[0].GetTangents(nullptr, &x8_knots[1], tangentA, tangentB); if (!zeus::close_enough(tangentA.x(), 0)) { return (x8_knots[0].GetAmplitude() - (center * tangentA.y() / tangentA.x())); } return x8_knots[0].GetAmplitude(); } float eval = EvaluateAt(center); if (pre && x0_preInfinity == 3) { return eval - (tmp * x8_knots[lastIdx].GetAmplitude() - x8_knots[0].GetAmplitude()); } if (!pre && x4_postInfinity == 3) { return eval + (tmp * x8_knots[lastIdx].GetAmplitude() - x8_knots[0].GetAmplitude()); } return eval; } float CMayaSpline::EvaluateHermite(float time) { const float timeDiff = time - x30_cachedMinTime; return x34_cachedHermitCoefs[0] + (timeDiff * x34_cachedHermitCoefs[1]) + (timeDiff * x34_cachedHermitCoefs[2]) + (timeDiff * x34_cachedHermitCoefs[3]); } bool CMayaSpline::FindKnot(float time, s32& knotIndex) { if (x8_knots.empty()) { return false; } u32 lower = 0; u32 upper = x8_knots.size(); while (lower < upper) { u32 index = (lower + upper) / 2; const auto& knot = x8_knots[index]; if (knot.GetTime() > time) { upper = index - 1; } else if (time > knot.GetTime()) { lower = index + 1; } else { knotIndex = index; return true; } } knotIndex = lower; return false; } void CMayaSpline::FindControlPoints(s32 knotIndex, rstl::reserved_vector& controlPoints) { CMayaSplineKnot* knot = &x8_knots[knotIndex]; controlPoints.emplace_back(knot->GetTime(), knot->GetAmplitude()); zeus::CVector2f tangentA; zeus::CVector2f tangentB; CMayaSplineKnot* next = (knotIndex + 1 < x8_knots.size()) ? &x8_knots[knotIndex + 1] : nullptr; CMayaSplineKnot* prev = (knotIndex - 1 >= 0) ? &x8_knots[knotIndex - 1] : nullptr; knot->GetTangents(prev, next, tangentA, tangentB); knot = &x8_knots[knotIndex + 1]; controlPoints.emplace_back(controlPoints[0] + (tangentB * zeus::CVector2f{1.f / 3.f})); next = (knotIndex + 2 < x8_knots.size()) ? &x8_knots[knotIndex + 2] : nullptr; prev = (knotIndex - 2 >= 0) ? &x8_knots[knotIndex - 2] : nullptr; knot->GetTangents(prev, next, tangentA, tangentB); zeus::CVector2f knotV = {knot->GetTime(), knot->GetAmplitude()}; controlPoints.emplace_back(knotV - (tangentA * zeus::CVector2f{1.f / 3.f})); controlPoints.emplace_back(knotV); } void CMayaSpline::CalculateHermiteCoefficients(const rstl::reserved_vector& controlPoints, float* coefs) { const zeus::CVector2f point1 = controlPoints[3] - controlPoints[0]; const zeus::CVector2f point2 = controlPoints[1] - controlPoints[0]; const zeus::CVector2f point3 = controlPoints[3] - controlPoints[2]; float dVar7 = point2.x() != 0.f ? point2.y() / point2.x() : 5729578.0f; float dVar6 = point3.x() != 0.f ? point3.y() / point3.x() : 5729578.0f; const float point1XSq = point1.x() * point1.x(); coefs[0] = ((1.f / (point1XSq)) * (((dVar7 * point1.x()) + (dVar6 * point1.x()) - point1.y()) - point1.y())) / point1.x(); coefs[1] = ((1.f / (point1XSq)) * ((((point1.y() + (point1.y() + point1.y())) - (dVar7 * point1.x())) - (dVar7 * point1.x())) - (dVar6 * point1.x()))); coefs[2] = dVar7; coefs[3] = controlPoints[0].y(); } } // namespace metaforce ================================================ FILE: Runtime/CMayaSpline.hpp ================================================ #pragma once #include #include "RetroTypes.hpp" #include #include #include #include namespace metaforce { class CMayaSplineKnot { float x0_time; float x4_amplitude; u8 x8_; u8 x9_; bool xa_24_dirty : 1 = true; u8 xb_; zeus::CVector2f xc_cachedTangentA; zeus::CVector2f x14_cachedTangentB; public: CMayaSplineKnot(CInputStream& in); float GetTime() const { return x0_time; } float GetAmplitude() const { return x4_amplitude; } u8 GetX8() const { return x8_; } u8 GetX9() const { return x9_; } void GetTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next, zeus::CVector2f& tangentA, zeus::CVector2f& tangentB); void CalculateTangents(CMayaSplineKnot* prev, CMayaSplineKnot* next); }; class CMayaSpline { u32 x0_preInfinity; u32 x4_postInfinity; std::vector x8_knots; u32 x18_clampMode; float x1c_minAmplitudeTime; float x20_maxAmplitudeTime; s32 x24_chachedKnotIndex = -1; s32 x28_ = -1; bool x2c_24_dirty = false; float x30_cachedMinTime; float x34_cachedHermitCoefs[4]; public: CMayaSpline(CInputStream& in, s32 count); u32 GetKnotCount() const { return x8_knots.size(); } const std::vector& GetKnots() const { return x8_knots; } float GetMinTime() const; float GetMaxTime() const; float GetDuration() const; float EvaluateAt(float time); float EvaluateAtUnclamped(float time); float EvaluateInfinities(float time, bool Pre); float EvaluateHermite(float time); bool FindKnot(float time, int& knotIndex); void FindControlPoints(s32 knotIndex, rstl::reserved_vector& controlPoints); void CalculateHermiteCoefficients(const rstl::reserved_vector& controlPoits, float* coefs); }; } // namespace metaforce ================================================ FILE: Runtime/CMemoryCardSys.cpp ================================================ #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/CCRC32.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "ConsoleVariables/CVar.hpp" #include "ConsoleVariables/CVarManager.hpp" namespace metaforce { namespace { using ECardResult = kabufuda::ECardResult; static std::string g_CardImagePaths[2] = {}; static kabufuda::Card g_CardStates[2] = {kabufuda::Card{"GM8E", "01"}, kabufuda::Card{"GM8E", "01"}}; // static kabufuda::ECardResult g_OpResults[2] = {}; CVar* mc_dolphinAPath = nullptr; CVar* mc_dolphinBPath = nullptr; } // namespace CSaveWorldIntermediate::CSaveWorldIntermediate(CAssetId mlvl, CAssetId savw) : x0_mlvlId(mlvl), x8_savwId(savw) { if (!savw.IsValid()) x2c_dummyWorld = std::make_unique(mlvl, false); else x34_saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), savw}); } bool CSaveWorldIntermediate::InitializePump() { if (x2c_dummyWorld) { CDummyWorld& wld = *x2c_dummyWorld; if (!wld.ICheckWorldComplete()) return false; x4_strgId = wld.IGetStringTableAssetId(); x8_savwId = wld.IGetSaveWorldAssetId(); const u32 areaCount = wld.IGetAreaCount(); xc_areaIds.reserve(areaCount); for (u32 i = 0; i < areaCount; ++i) { const IGameArea* area = wld.IGetAreaAlways(i); xc_areaIds.emplace_back(area->IGetAreaSaveId()); } CAssetId mlvlId = wld.IGetWorldAssetId(); CWorldState& mlvlState = g_GameState->StateForWorld(mlvlId); x1c_defaultLayerStates = mlvlState.GetLayerState()->x0_areaLayers; x34_saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), x8_savwId}); x2c_dummyWorld.reset(); } else { if (!x34_saveWorld) return true; if (x34_saveWorld.IsLoaded() && x34_saveWorld.GetObj()) return true; } return false; } bool CMemoryCardSys::HasSaveWorldMemory(CAssetId wldId) const { auto existingSearch = std::find_if(xc_memoryWorlds.cbegin(), xc_memoryWorlds.cend(), [&](const auto& wld) { return wld.first == wldId; }); return existingSearch != xc_memoryWorlds.cend(); } const CSaveWorldMemory& CMemoryCardSys::GetSaveWorldMemory(CAssetId wldId) const { auto existingSearch = std::find_if(xc_memoryWorlds.cbegin(), xc_memoryWorlds.cend(), [&](const auto& wld) { return wld.first == wldId; }); return existingSearch->second; } CMemoryCardSys::CMemoryCardSys() { mc_dolphinAPath = CVarManager::instance()->findOrMakeCVar( "memcard.PathA"sv, "Path to the memory card image for SlotA"sv, ""sv, (CVar::EFlags::Archive | CVar::EFlags::System | CVar::EFlags::ModifyRestart)); mc_dolphinBPath = CVarManager::instance()->findOrMakeCVar( "memcard.PathB"sv, "Path to the memory card image for SlotB"sv, ""sv, (CVar::EFlags::Archive | CVar::EFlags::System | CVar::EFlags::ModifyRestart)); x0_hints = g_SimplePool->GetObj("HINT_Hints"); xc_memoryWorlds.reserve(16); x1c_worldInter.emplace(); x1c_worldInter->reserve(16); std::vector orderedMLVLs; orderedMLVLs.reserve(16); g_ResFactory->EnumerateNamedResources([&](std::string_view name, const SObjectTag& tag) -> bool { if (tag.type == FOURCC('MLVL')) orderedMLVLs.emplace_back(tag.id); return true; }); std::sort(orderedMLVLs.begin(), orderedMLVLs.end()); for (const auto& mlvl : orderedMLVLs) { if (!HasSaveWorldMemory(mlvl)) { xc_memoryWorlds.emplace_back(mlvl, CSaveWorldMemory{}); x1c_worldInter->emplace_back(mlvl, CAssetId{}); } } x30_scanCategoryCounts.resize(6); } bool CMemoryCardSys::InitializePump() { if (!x1c_worldInter) { for (const auto& world : xc_memoryWorlds) { const CSaveWorldMemory& wld = world.second; if (!wld.GetWorldName()) continue; if (!wld.GetWorldName().IsLoaded() || !wld.GetWorldName().GetObj()) return false; } return !(!x0_hints.IsLoaded() || !x0_hints.GetObj()); } bool done = true; for (CSaveWorldIntermediate& world : *x1c_worldInter) { if (world.InitializePump()) { if (!world.x34_saveWorld) continue; auto existingSearch = std::find_if(xc_memoryWorlds.begin(), xc_memoryWorlds.end(), [&](const auto& test) { return test.first == world.x0_mlvlId; }); CSaveWorldMemory& wldMemOut = existingSearch->second; wldMemOut.x4_savwId = world.x8_savwId; wldMemOut.x0_strgId = world.x4_strgId; wldMemOut.xc_areaIds = world.xc_areaIds; wldMemOut.x1c_defaultLayerStates = world.x1c_defaultLayerStates; CWorldSaveGameInfo& savw = *world.x34_saveWorld; wldMemOut.x8_areaCount = savw.GetAreaCount(); x20_scanStates.reserve(x20_scanStates.size() + savw.GetScans().size()); for (const CWorldSaveGameInfo::SScanState& scan : savw.GetScans()) { const auto scanStateIter = std::find_if(x20_scanStates.cbegin(), x20_scanStates.cend(), [&](const auto& test) { return test.first == scan.x0_id && test.second == scan.x4_category; }); if (scanStateIter == x20_scanStates.cend()) { x20_scanStates.emplace_back(scan.x0_id, scan.x4_category); ++x30_scanCategoryCounts[int(scan.x4_category)]; } } wldMemOut.x3c_saveWorld = std::move(world.x34_saveWorld); wldMemOut.x2c_worldName = g_SimplePool->GetObj(SObjectTag{FOURCC('STRG'), wldMemOut.x0_strgId}); } else done = false; } if (done) { std::sort(x20_scanStates.begin(), x20_scanStates.end(), [&](const auto& a, const auto& b) { return a.first < b.first; }); x1c_worldInter = std::nullopt; } return false; } std::pair CMemoryCardSys::GetAreaAndWorldIdForSaveId(s32 saveId) const { for (const auto& [mlvl, saveWorld] : xc_memoryWorlds) { for (TAreaId areaId = 0; areaId < saveWorld.xc_areaIds.size(); ++areaId) { if (saveWorld.xc_areaIds[areaId] == saveId) { return {mlvl, areaId}; } } } return {{}, kInvalidAreaId}; } void CMemoryCardSys::CCardFileInfo::LockBannerToken(CAssetId bannerTxtr, CSimplePool& sp) { x3c_bannerTex = bannerTxtr; x40_bannerTok.emplace(sp.GetObj({FOURCC('TXTR'), bannerTxtr})); } CMemoryCardSys::CCardFileInfo::Icon::Icon(CAssetId id, kabufuda::EAnimationSpeed speed, CSimplePool& sp) : x0_id(id), x4_speed(speed), x8_tex(sp.GetObj({FOURCC('TXTR'), id})) {} void CMemoryCardSys::CCardFileInfo::LockIconToken(CAssetId iconTxtr, kabufuda::EAnimationSpeed speed, CSimplePool& sp) { x50_iconToks.emplace_back(iconTxtr, speed, sp); } u32 CMemoryCardSys::CCardFileInfo::CalculateBannerDataSize() const { u32 ret = 68; if (x3c_bannerTex.IsValid()) { if ((*x40_bannerTok)->GetTexelFormat() == ETexelFormat::RGB5A3) { ret = 6212; } else { ret = 3652; } } bool paletteTex = false; for (const Icon& icon : x50_iconToks) { if (icon.x8_tex->GetTexelFormat() == ETexelFormat::RGB5A3) { ret += 2048; } else { ret += 1024; paletteTex = true; } } if (paletteTex) { ret += 512; } return ret; } u32 CMemoryCardSys::CCardFileInfo::CalculateTotalDataSize() const { return (CalculateBannerDataSize() + xf4_saveBuffer.size() + 8191) & ~8191; } void CMemoryCardSys::CCardFileInfo::BuildCardBuffer() { u32 bannerSz = CalculateBannerDataSize(); x104_cardBuffer.resize((bannerSz + xf4_saveBuffer.size() + 8191) & ~8191); { CMemoryStreamOut w(x104_cardBuffer.data(), x104_cardBuffer.size(), CMemoryStreamOut::EOwnerShip::NotOwned); w.WriteLong(0); char comment[64]; std::memset(comment, 0, std::size(comment)); std::strncpy(comment, x28_comment.data(), std::size(comment) - 1); w.Put(reinterpret_cast(comment), 64); WriteBannerData(w); WriteIconData(w); } memmove(x104_cardBuffer.data() + bannerSz, xf4_saveBuffer.data(), xf4_saveBuffer.size()); reinterpret_cast(*x104_cardBuffer.data()) = CBasics::SwapBytes(CCRC32::Calculate(x104_cardBuffer.data() + 4, x104_cardBuffer.size() - 4)); xf4_saveBuffer.clear(); } void CMemoryCardSys::CCardFileInfo::WriteBannerData(COutputStream& out) const { if (x3c_bannerTex.IsValid()) { const TLockedToken& tex = *x40_bannerTok; const auto format = tex->GetTexelFormat(); const auto* texels = tex->GetConstBitMapData(0); if (format == ETexelFormat::RGB5A3) { out.Put(texels, 6144); } else { out.Put(texels, 3072); } if (format == ETexelFormat::C8) { out.Put(reinterpret_cast(tex->GetPalette()->GetPaletteData()), 512); } } } void CMemoryCardSys::CCardFileInfo::WriteIconData(COutputStream& out) const { const u8* palette = nullptr; for (const Icon& icon : x50_iconToks) { const auto format = icon.x8_tex->GetTexelFormat(); const auto* texels = icon.x8_tex->GetConstBitMapData(0); if (format == ETexelFormat::RGB5A3) { out.Put(texels, 2048); } else { out.Put(texels, 1024); } if (format == ETexelFormat::C8) { palette = reinterpret_cast(icon.x8_tex->GetPalette()->GetPaletteData()); } } if (palette != nullptr) { out.Put(palette, 512); } } ECardResult CMemoryCardSys::CCardFileInfo::PumpCardTransfer() { if (x0_status == EStatus::Standby) return ECardResult::READY; else if (x0_status == EStatus::Transferring) { ECardResult result = CMemoryCardSys::GetResultCode(GetCardPort()); if (result != ECardResult::BUSY) x104_cardBuffer.clear(); if (result != ECardResult::READY) return result; x0_status = EStatus::Done; kabufuda::CardStat stat = {}; result = GetStatus(stat); if (result != ECardResult::READY) return result; result = CMemoryCardSys::SetStatus(m_handle.slot, m_handle.getFileNo(), stat); if (result != ECardResult::READY) return result; return ECardResult::BUSY; } else { ECardResult result = CMemoryCardSys::GetResultCode(GetCardPort()); if (result == ECardResult::READY) x0_status = EStatus::Standby; return result; } } ECardResult CMemoryCardSys::CCardFileInfo::GetStatus(kabufuda::CardStat& stat) const { ECardResult result = CMemoryCardSys::GetStatus(m_handle.slot, m_handle.getFileNo(), stat); if (result != ECardResult::READY) { return result; } stat.SetCommentAddr(4); stat.SetIconAddr(68); kabufuda::EImageFormat bannerFmt; if (x3c_bannerTex.IsValid()) { if ((*x40_bannerTok)->GetTexelFormat() == ETexelFormat::RGB5A3) { bannerFmt = kabufuda::EImageFormat::RGB5A3; } else { bannerFmt = kabufuda::EImageFormat::C8; } } else { bannerFmt = kabufuda::EImageFormat::None; } stat.SetBannerFormat(bannerFmt); int idx = 0; for (const Icon& icon : x50_iconToks) { stat.SetIconFormat(icon.x8_tex->GetTexelFormat() == ETexelFormat::RGB5A3 ? kabufuda::EImageFormat::RGB5A3 : kabufuda::EImageFormat::C8, idx); stat.SetIconSpeed(icon.x4_speed, idx); ++idx; } if (idx < 8) { stat.SetIconFormat(kabufuda::EImageFormat::None, idx); stat.SetIconSpeed(kabufuda::EAnimationSpeed::End, idx); } return ECardResult::READY; } ECardResult CMemoryCardSys::CCardFileInfo::CreateFile() { return CMemoryCardSys::CreateFile(m_handle.slot, x18_fileName.c_str(), CalculateTotalDataSize(), m_handle); } ECardResult CMemoryCardSys::CCardFileInfo::WriteFile() { BuildCardBuffer(); // DCStoreRange(x104_cardBuffer.data(), x104_cardBuffer.size()); x0_status = EStatus::Transferring; return CMemoryCardSys::WriteFile(m_handle, x104_cardBuffer.data(), x104_cardBuffer.size(), 0); } ECardResult CMemoryCardSys::CCardFileInfo::CloseFile() { return CMemoryCardSys::CloseFile(m_handle); } std::string CMemoryCardSys::_GetDolphinCardPath(kabufuda::ECardSlot slot) { return g_CardImagePaths[static_cast(slot)]; } void CMemoryCardSys::_ResolveDolphinCardPath(const CVar* cv, kabufuda::ECardSlot slot) { if (cv != nullptr && cv->toLiteral().empty()) { g_CardImagePaths[int(slot)] = ResolveDolphinCardPath(slot); } else if (cv != nullptr) { g_CardImagePaths[int(slot)] = cv->toLiteral(); } } kabufuda::ProbeResults CMemoryCardSys::CardProbe(kabufuda::ECardSlot port) { _ResolveDolphinCardPath(mc_dolphinAPath, kabufuda::ECardSlot::SlotA); _ResolveDolphinCardPath(mc_dolphinBPath, kabufuda::ECardSlot::SlotB); kabufuda::ProbeResults res = kabufuda::Card::probeCardFile(g_CardImagePaths[int(port)]); // g_OpResults[int(port)] = res.x0_error; return res; } ECardResult CMemoryCardSys::MountCard(kabufuda::ECardSlot port) { kabufuda::Card& card = g_CardStates[int(port)]; if (!card.open(g_CardImagePaths[int(port)])) return ECardResult::NOCARD; ECardResult result = card.getError(); // g_OpResults[int(port)] = result; if (result == ECardResult::READY) return ECardResult::READY; return result; } ECardResult CMemoryCardSys::UnmountCard(kabufuda::ECardSlot port) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } card.commit(); // g_OpResults[int(port)] = ECardResult::READY; return ECardResult::READY; } ECardResult CMemoryCardSys::CheckCard(kabufuda::ECardSlot port) { kabufuda::Card& card = g_CardStates[int(port)]; ECardResult result = card.getError(); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::CreateFile(kabufuda::ECardSlot port, const char* name, u32 size, CardFileHandle& info) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } info.slot = port; ECardResult result = card.createFile(name, size, info.handle); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::OpenFile(kabufuda::ECardSlot port, const char* name, CardFileHandle& info) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } info.slot = port; ECardResult result = card.openFile(name, info.handle); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::FastOpenFile(kabufuda::ECardSlot port, int fileNo, CardFileHandle& info) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } info.slot = port; ECardResult result = card.openFile(fileNo, info.handle); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::CloseFile(CardFileHandle& info) { kabufuda::Card& card = g_CardStates[int(info.slot)]; if (CardResult err = card.getError()) { // g_OpResults[int(info.slot)] = err; return err; } card.closeFile(info.handle); return ECardResult::READY; } ECardResult CMemoryCardSys::ReadFile(CardFileHandle& info, void* buf, s32 length, s32 offset) { kabufuda::Card& card = g_CardStates[int(info.slot)]; if (CardResult err = card.getError()) { // g_OpResults[int(info.slot)] = err; return err; } card.seek(info.handle, offset, kabufuda::SeekOrigin::Begin); card.asyncRead(info.handle, buf, length); // g_OpResults[int(info.slot)] = ECardResult::READY; return ECardResult::READY; } ECardResult CMemoryCardSys::WriteFile(CardFileHandle& info, const void* buf, s32 length, s32 offset) { kabufuda::Card& card = g_CardStates[int(info.slot)]; if (CardResult err = card.getError()) { // g_OpResults[int(info.slot)] = err; return err; } card.seek(info.handle, offset, kabufuda::SeekOrigin::Begin); card.asyncWrite(info.handle, buf, length); // g_OpResults[int(info.slot)] = ECardResult::READY; return ECardResult::READY; } ECardResult CMemoryCardSys::GetNumFreeBytes(kabufuda::ECardSlot port, s32& freeBytes, s32& freeFiles) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } card.getFreeBlocks(freeBytes, freeFiles); // g_OpResults[int(port)] = ECardResult::READY; return ECardResult::READY; } ECardResult CMemoryCardSys::GetSerialNo(kabufuda::ECardSlot port, u64& serialOut) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } card.getSerial(serialOut); // g_OpResults[int(port)] = ECardResult::READY; return ECardResult::READY; } ECardResult CMemoryCardSys::GetResultCode(kabufuda::ECardSlot port) { kabufuda::Card& card = g_CardStates[int(port)]; return card.getError(); } ECardResult CMemoryCardSys::GetStatus(kabufuda::ECardSlot port, int fileNo, CardStat& statOut) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } ECardResult result = card.getStatus(fileNo, statOut); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::SetStatus(kabufuda::ECardSlot port, int fileNo, const CardStat& stat) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } ECardResult result = card.setStatus(fileNo, stat); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::DeleteFile(kabufuda::ECardSlot port, const char* name) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } ECardResult result = card.deleteFile(name); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::FastDeleteFile(kabufuda::ECardSlot port, int fileNo) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } ECardResult result = card.deleteFile(fileNo); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::Rename(kabufuda::ECardSlot port, const char* oldName, const char* newName) { kabufuda::Card& card = g_CardStates[int(port)]; if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } ECardResult result = card.renameFile(oldName, newName); // g_OpResults[int(port)] = result; return result; } ECardResult CMemoryCardSys::FormatCard(kabufuda::ECardSlot port) { kabufuda::Card& card = g_CardStates[int(port)]; card.format(port); if (CardResult err = card.getError()) { // g_OpResults[int(port)] = err; return err; } // g_OpResults[int(port)] = ECardResult::READY; return ECardResult::READY; } void CMemoryCardSys::CommitToDisk(kabufuda::ECardSlot port) { kabufuda::Card& card = g_CardStates[int(port)]; card.commit(); } bool CMemoryCardSys::CreateDolphinCard(kabufuda::ECardSlot slot) { std::string path = _CreateDolphinCard(slot, slot == kabufuda::ECardSlot::SlotA ? mc_dolphinAPath->hasDefaultValue() : mc_dolphinBPath->hasDefaultValue()); if (CardProbe(slot).x0_error != ECardResult::READY) { return false; } MountCard(slot); FormatCard(slot); kabufuda::Card& card = g_CardStates[int(slot)]; card.waitForCompletion(); return true; } void CMemoryCardSys::_ResetCVar(kabufuda::ECardSlot slot) { switch (slot) { case kabufuda::ECardSlot::SlotA: mc_dolphinAPath->fromLiteral(""); break; case kabufuda::ECardSlot::SlotB: mc_dolphinBPath->fromLiteral(""); break; } } void CMemoryCardSys::Shutdown() { UnmountCard(kabufuda::ECardSlot::SlotA); UnmountCard(kabufuda::ECardSlot::SlotB); } } // namespace metaforce ================================================ FILE: Runtime/CMemoryCardSys.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/CGameHintInfo.hpp" #include "Runtime/Streams/CMemoryStreamOut.hpp" #include "Runtime/CToken.hpp" #include "Runtime/CWorldSaveGameInfo.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/World/CWorld.hpp" #include "Runtime/rstl.hpp" #include namespace metaforce { class CDummyWorld; class CSimplePool; class CStringTable; class CSaveWorldMemory { friend class CMemoryCardSys; CAssetId x0_strgId; CAssetId x4_savwId; u32 x8_areaCount; std::vector xc_areaIds; std::vector x1c_defaultLayerStates; TLockedToken x2c_worldName; /* used to be optional */ TLockedToken x3c_saveWorld; /* used to be optional */ public: CAssetId GetWorldNameId() const { return x0_strgId; } CAssetId GetSaveWorldAssetId() const { return x4_savwId; } u32 GetAreaCount() const { return x8_areaCount; } const std::vector& GetDefaultLayerStates() const { return x1c_defaultLayerStates; } const TLockedToken& GetWorldName() const { return x2c_worldName; } const TLockedToken& GetSaveWorld() const { return x3c_saveWorld; } const char16_t* GetFrontEndName() const { if (!x2c_worldName) return u""; return x2c_worldName->GetString(0); } }; class CSaveWorldIntermediate { friend class CMemoryCardSys; CAssetId x0_mlvlId; CAssetId x4_strgId; CAssetId x8_savwId; std::vector xc_areaIds; std::vector x1c_defaultLayerStates; std::unique_ptr x2c_dummyWorld; TLockedToken x34_saveWorld; /* Used to be auto_ptr */ public: CSaveWorldIntermediate(CAssetId mlvl, CAssetId savw); bool InitializePump(); }; class CMemoryCardSys { TLockedToken x0_hints; std::vector> xc_memoryWorlds; /* MLVL as key */ std::optional> x1c_worldInter; /* used to be auto_ptr of vector */ std::vector> x20_scanStates; rstl::reserved_vector x30_scanCategoryCounts; public: static void _ResetCVar(kabufuda::ECardSlot slot); static void _ResolveDolphinCardPath(const CVar* cv, kabufuda::ECardSlot slot); static std::string ResolveDolphinCardPath(kabufuda::ECardSlot slot); static bool CreateDolphinCard(kabufuda::ECardSlot slot); static std::string _GetDolphinCardPath(kabufuda::ECardSlot slot); static std::string _CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin); using ECardResult = kabufuda::ECardResult; struct CardResult { ECardResult result; CardResult(ECardResult res) : result(res) {} operator ECardResult() const { return result; } explicit operator bool() const { return result != ECardResult::READY; } }; struct CardFileHandle { kabufuda::ECardSlot slot; kabufuda::FileHandle handle; CardFileHandle(kabufuda::ECardSlot slot) : slot(slot) {} int getFileNo() const { return handle.getFileNo(); } }; using CardStat = kabufuda::CardStat; const std::vector& GetHints() const { return x0_hints->GetHints(); } const std::vector>& GetMemoryWorlds() const { return xc_memoryWorlds; } const std::vector>& GetScanStates() const { return x20_scanStates; } u32 GetScanCategoryCount(CWorldSaveGameInfo::EScanCategory cat) const { return x30_scanCategoryCounts[int(cat)]; } std::vector>::const_iterator LookupScanState(CAssetId id) const { return rstl::binary_find(x20_scanStates.cbegin(), x20_scanStates.cend(), id, [](const std::pair& p) { return p.first; }); } bool HasSaveWorldMemory(CAssetId wldId) const; const CSaveWorldMemory& GetSaveWorldMemory(CAssetId wldId) const; CMemoryCardSys(); bool InitializePump(); struct CCardFileInfo { struct Icon { CAssetId x0_id; kabufuda::EAnimationSpeed x4_speed; TLockedToken x8_tex; Icon(CAssetId id, kabufuda::EAnimationSpeed speed, CSimplePool& sp); }; enum class EStatus { Standby, Transferring, Done }; EStatus x0_status = EStatus::Standby; // CARDFileInfo x4_info; CardFileHandle m_handle; std::string x18_fileName; std::string x28_comment; CAssetId x3c_bannerTex; std::optional> x40_bannerTok; rstl::reserved_vector x50_iconToks; std::vector xf4_saveBuffer; std::vector x104_cardBuffer; CCardFileInfo(kabufuda::ECardSlot port, std::string_view name) : m_handle(port), x18_fileName(name) {} void LockBannerToken(CAssetId bannerTxtr, CSimplePool& sp); void LockIconToken(CAssetId iconTxtr, kabufuda::EAnimationSpeed speed, CSimplePool& sp); [[nodiscard]] kabufuda::ECardSlot GetCardPort() const { return m_handle.slot; } [[nodiscard]] int GetFileNo() const { return m_handle.getFileNo(); } [[nodiscard]] u32 CalculateBannerDataSize() const; [[nodiscard]] u32 CalculateTotalDataSize() const; void BuildCardBuffer(); void WriteBannerData(COutputStream& out) const; void WriteIconData(COutputStream& out) const; void SetComment(const std::string& c) { x28_comment = c; } ECardResult PumpCardTransfer(); ECardResult GetStatus(CardStat& stat) const; ECardResult CreateFile(); ECardResult WriteFile(); ECardResult CloseFile(); CMemoryStreamOut BeginMemoryOut(u32 sz) { xf4_saveBuffer.resize(sz); return CMemoryStreamOut(xf4_saveBuffer.data(), sz, CMemoryStreamOut::EOwnerShip::NotOwned, sz); } }; std::pair GetAreaAndWorldIdForSaveId(s32 saveId) const; static kabufuda::ProbeResults CardProbe(kabufuda::ECardSlot port); static ECardResult MountCard(kabufuda::ECardSlot port); static ECardResult UnmountCard(kabufuda::ECardSlot port); static ECardResult CheckCard(kabufuda::ECardSlot port); static ECardResult CreateFile(kabufuda::ECardSlot port, const char* name, u32 size, CardFileHandle& info); static ECardResult OpenFile(kabufuda::ECardSlot port, const char* name, CardFileHandle& info); static ECardResult FastOpenFile(kabufuda::ECardSlot port, int fileNo, CardFileHandle& info); static ECardResult CloseFile(CardFileHandle& info); static ECardResult ReadFile(CardFileHandle& info, void* buf, s32 length, s32 offset); static ECardResult WriteFile(CardFileHandle& info, const void* buf, s32 length, s32 offset); static ECardResult GetNumFreeBytes(kabufuda::ECardSlot port, s32& freeBytes, s32& freeFiles); static ECardResult GetSerialNo(kabufuda::ECardSlot port, u64& serialOut); static ECardResult GetResultCode(kabufuda::ECardSlot port); static ECardResult GetStatus(kabufuda::ECardSlot port, int fileNo, CardStat& statOut); static ECardResult SetStatus(kabufuda::ECardSlot port, int fileNo, const CardStat& stat); static ECardResult DeleteFile(kabufuda::ECardSlot port, const char* name); static ECardResult FastDeleteFile(kabufuda::ECardSlot port, int fileNo); static ECardResult Rename(kabufuda::ECardSlot port, const char* oldName, const char* newName); static ECardResult FormatCard(kabufuda::ECardSlot port); static void CommitToDisk(kabufuda::ECardSlot port); static void Shutdown(); }; } // namespace metaforce ================================================ FILE: Runtime/CMemoryCardSysNix.cpp ================================================ #include "CMemoryCardSys.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/IMain.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/Logging.hpp" #include "Runtime/Formatting.hpp" #include namespace metaforce { static std::optional GetPrefPath(const char* app) { char* path = SDL_GetPrefPath(nullptr, app); if (path == nullptr) { return {}; } std::string str{path}; SDL_free(path); return str; } std::string CMemoryCardSys::ResolveDolphinCardPath(kabufuda::ECardSlot slot) { if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { const auto dolphinPath = GetPrefPath("dolphin-emu"); if (!dolphinPath) { return {}; } auto path = *dolphinPath; path += fmt::format("GC/MemoryCard{:c}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); CBasics::Sstat theStat{}; if (CBasics::Stat(path.c_str(), &theStat) != 0 || !S_ISREG(theStat.st_mode)) { /* legacy case for older dolphin versions */ const char* home = getenv("HOME"); if (home == nullptr || home[0] != '/') { return {}; } path = home; #ifndef __APPLE__ path += fmt::format("/.dolphin-emu/GC/MemoryCard{:c}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); #else path += fmt::format("/Library/Application Support/Dolphin/GC/MemoryCard{:c}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); #endif if (CBasics::Stat(path.c_str(), &theStat) != 0 || !S_ISREG(theStat.st_mode)) { return {}; } } return path; } return {}; } std::string CMemoryCardSys::_CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin) { if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { if (dolphin) { const auto dolphinPath = GetPrefPath("dolphin-emu"); if (!dolphinPath) { return {}; } auto path = *dolphinPath + "GC"; int ret = mkdir(path.c_str(), 0755); if (ret != 0 && errno != EEXIST) { return {}; } path += fmt::format("/MemoryCard{:c}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); auto* file = fopen(path.c_str(), "wbe"); if (file != nullptr) { fclose(file); return path; } } else { std::string path = _GetDolphinCardPath(slot); if (path.find('/') == std::string::npos) { auto basePath = GetPrefPath("metaforce"); if (!basePath) { return {}; } path = *basePath + _GetDolphinCardPath(slot); } std::string tmpPath = path.substr(0, path.find_last_of('/')); int ret = mkdir(tmpPath.c_str(), 0755); if (ret != 0 && ret != EEXIST) { return {}; } auto* file = fopen(path.c_str(), "wbe"); if (file != nullptr) { fclose(file); return path; } } } return {}; } } // namespace metaforce ================================================ FILE: Runtime/CMemoryCardSysOSX.cpp ================================================ #include "CMemoryCardSys.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/IMain.hpp" namespace metaforce { std::string CMemoryCardSys::ResolveDolphinCardPath(kabufuda::ECardSlot slot) { if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { const char* home = getenv("HOME"); if (!home) return {}; std::string path = home; path += fmt::format("/Library/Application Support/Dolphin/GC/MemoryCard{:c}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); hecl::Sstat theStat; if (hecl::Stat(path.c_str(), &theStat) || !S_ISREG(theStat.st_mode)) return {}; return path; } return {}; } std::string CMemoryCardSys::_CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin) { if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { if (dolphin) { const char* home = getenv("HOME"); if (!home) return {}; std::string path = home; path += "/Library/Application Support/Dolphin/GC"; if (hecl::RecursiveMakeDir(path.c_str()) < 0) return {}; path += fmt::format("/MemoryCard{:c}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); const auto fp = hecl::FopenUnique(path.c_str(), "wb"); if (fp == nullptr) { return {}; } return path; } else { std::string path = _GetDolphinCardPath(slot); hecl::SanitizePath(path); if (path.find('/') == std::string::npos) { path = hecl::GetcwdStr() + "/" + _GetDolphinCardPath(slot); } std::string tmpPath = path.substr(0, path.find_last_of("/")); hecl::RecursiveMakeDir(tmpPath.c_str()); const auto fp = hecl::FopenUnique(path.c_str(), "wb"); if (fp) { return path; } } } return {}; } } // namespace metaforce ================================================ FILE: Runtime/CMemoryCardSysWin.cpp ================================================ #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/IMain.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/Formatting.hpp" #include #include #include #include namespace metaforce { static std::optional GetPrefPath(const char* app) { char* path = SDL_GetPrefPath(nullptr, app); if (path == nullptr) { return {}; } std::string str{path}; SDL_free(path); return str; } #if WINDOWS_STORE using namespace Windows::Storage; #endif /* Partial path-selection logic from * https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/UICommon/UICommon.cpp * Modified to not use dolphin-binary-relative paths. */ std::string CMemoryCardSys::ResolveDolphinCardPath(kabufuda::ECardSlot slot) { if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { #if !WINDOWS_STORE /* Detect where the User directory is. There are two different cases * 1. HKCU\Software\Dolphin Emulator\UserConfigPath exists * -> Use this as the user directory path * 2. My Documents exists * -> Use My Documents\Dolphin Emulator as the User directory path */ /* Check our registry keys */ HKEY hkey; wchar_t configPath[MAX_PATH] = {0}; if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Dolphin Emulator", 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) { DWORD size = MAX_PATH; if (RegQueryValueEx(hkey, L"UserConfigPath", nullptr, nullptr, (LPBYTE)configPath, &size) != ERROR_SUCCESS) configPath[0] = 0; RegCloseKey(hkey); } /* Get My Documents path in case we need it. */ wchar_t my_documents[MAX_PATH]; bool my_documents_found = SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, my_documents)); std::string path; if (configPath[0]) /* Case 1 */ path = nowide::narrow(configPath); else if (my_documents_found) /* Case 2 */ path = nowide::narrow(my_documents) + "/Dolphin Emulator"; else /* Unable to find */ return {}; #else StorageFolder ^ localFolder = ApplicationData::Current->LocalFolder; std::string path(localFolder->Path->Data()); #endif path += fmt::format("/GC/MemoryCard{}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); struct _stat64 theStat{}; if (_stat64(path.c_str(), &theStat) || !S_ISREG(theStat.st_mode)) return {}; return path; } return {}; } std::string CMemoryCardSys::_CreateDolphinCard(kabufuda::ECardSlot slot, bool dolphin) { if (g_Main->IsUSA() && !g_Main->IsTrilogy()) { if (dolphin) { #if !WINDOWS_STORE /* Detect where the User directory is. There are two different cases * 1. HKCU\Software\Dolphin Emulator\UserConfigPath exists * -> Use this as the user directory path * 2. My Documents exists * -> Use My Documents\Dolphin Emulator as the User directory path */ /* Check our registry keys */ HKEY hkey; wchar_t configPath[MAX_PATH] = {0}; if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Dolphin Emulator", 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) { DWORD size = MAX_PATH; if (RegQueryValueEx(hkey, L"UserConfigPath", nullptr, nullptr, (LPBYTE)configPath, &size) != ERROR_SUCCESS) configPath[0] = 0; RegCloseKey(hkey); } /* Get My Documents path in case we need it. */ wchar_t my_documents[MAX_PATH]; bool my_documents_found = SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, my_documents)); std::string path; if (configPath[0]) /* Case 1 */ path = nowide::narrow(configPath); else if (my_documents_found) /* Case 2 */ path = nowide::narrow(my_documents) + "/Dolphin Emulator"; else /* Unable to find */ return {}; #else StorageFolder ^ localFolder = ApplicationData::Current->LocalFolder; std::string path(localFolder->Path->Data()); #endif path += "/GC"; if (CBasics::RecursiveMakeDir(path.c_str()) < 0) return {}; path += fmt::format("/MemoryCard{}.USA.raw", slot == kabufuda::ECardSlot::SlotA ? 'A' : 'B'); const auto wpath = nowide::widen(path); FILE* fp = _wfopen(wpath.c_str(), L"wb"); if (fp == nullptr) { return {}; } fclose(fp); return path; } else { std::string path = _GetDolphinCardPath(slot); if (path.find('/') == std::string::npos) { auto prefPath = GetPrefPath("Metaforce"); if (!prefPath) { return {}; } path = *prefPath + _GetDolphinCardPath(slot); } std::string tmpPath = path.substr(0, path.find_last_of('/')); CBasics::RecursiveMakeDir(tmpPath.c_str()); const auto wpath = nowide::widen(path); FILE* fp = _wfopen(wpath.c_str(), L"wb"); if (fp) { fclose(fp); return path; } } } return {}; } } // namespace metaforce ================================================ FILE: Runtime/CObjectList.cpp ================================================ #include "Runtime/CObjectList.hpp" #ifndef NDEBUG #include "Runtime/Logging.hpp" #include "Runtime/Formatting.hpp" #endif namespace metaforce { CObjectList::CObjectList(EGameObjectList listEnum) : x2004_listEnum(listEnum) {} void CObjectList::AddObject(CEntity& entity) { if (IsQualified(entity)) { #ifndef NDEBUG if (x0_list[entity.GetUniqueId().Value()].entity != nullptr && x0_list[entity.GetUniqueId().Value()].entity != &entity) spdlog::fatal("INVALID USAGE DETECTED: Attempting to assign entity '{} ({})' to existing node '{}'!!!", entity.GetName(), entity.GetEditorId(), entity.GetUniqueId().Value()); #endif s16 prevFirst = -1; if (x2008_firstId != -1) { x0_list[x2008_firstId].prev = entity.GetUniqueId().Value(); prevFirst = x2008_firstId; } x2008_firstId = entity.GetUniqueId().Value(); SObjectListEntry& newEnt = x0_list[x2008_firstId]; newEnt.entity = &entity; newEnt.next = prevFirst; newEnt.prev = -1; ++x200a_count; } } void CObjectList::RemoveObject(TUniqueId uid) { SObjectListEntry& ent = x0_list[uid.Value()]; if (!ent.entity || ent.entity->GetUniqueId() != uid) return; if (uid.Value() == x2008_firstId) { x2008_firstId = ent.next; if (ent.next != -1) x0_list[ent.next].prev = -1; } else { x0_list[ent.prev].next = ent.next; if (ent.next != -1) x0_list[ent.next].prev = ent.prev; } ent.entity = nullptr; ent.next = -1; ent.prev = -1; --x200a_count; } const CEntity* CObjectList::operator[](size_t i) const { const SObjectListEntry& ent = x0_list[i]; if (!ent.entity || ent.entity->x30_26_scriptingBlocked) return nullptr; return ent.entity; } CEntity* CObjectList::operator[](size_t i) { SObjectListEntry& ent = x0_list[i]; if (!ent.entity || ent.entity->x30_26_scriptingBlocked) return nullptr; return ent.entity; } const CEntity* CObjectList::GetObjectById(TUniqueId uid) const { if (uid == kInvalidUniqueId) return nullptr; const SObjectListEntry& ent = x0_list[uid.Value()]; if (!ent.entity || ent.entity->x30_26_scriptingBlocked) return nullptr; return ent.entity; } CEntity* CObjectList::GetObjectById(TUniqueId uid) { if (uid == kInvalidUniqueId) return nullptr; SObjectListEntry& ent = x0_list[uid.Value()]; if (!ent.entity || ent.entity->x30_26_scriptingBlocked) return nullptr; return ent.entity; } const CEntity* CObjectList::GetValidObjectById(TUniqueId uid) const { if (uid == kInvalidUniqueId) return nullptr; const SObjectListEntry& ent = x0_list[uid.Value()]; if (!ent.entity) return nullptr; if (ent.entity->GetUniqueId() != uid) return nullptr; return ent.entity; } CEntity* CObjectList::GetValidObjectById(TUniqueId uid) { if (uid == kInvalidUniqueId) return nullptr; SObjectListEntry& ent = x0_list[uid.Value()]; if (!ent.entity) return nullptr; if (ent.entity->GetUniqueId() != uid) return nullptr; return ent.entity; } bool CObjectList::IsQualified(const CEntity&) const { return true; } } // namespace metaforce ================================================ FILE: Runtime/CObjectList.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/World/CEntity.hpp" namespace metaforce { enum class EGameObjectList { Invalid = -1, All, Actor, PhysicsActor, GameCamera, GameLight, ListeningAi, AiWaypoint, PlatformAndDoor, }; class CObjectList { friend class CGameArea; struct SObjectListEntry { CEntity* entity = nullptr; s16 next = -1; s16 prev = -1; }; std::array x0_list; // was an rstl::reserved_vector EGameObjectList x2004_listEnum; s16 x2008_firstId = -1; u16 x200a_count = 0; public: class iterator { friend class CObjectList; CObjectList& m_list; s16 m_id; iterator(CObjectList& list, s16 id) : m_list(list), m_id(id) {} public: iterator& operator++() { m_id = m_list.GetNextObjectIndex(m_id); return *this; } bool operator==(const iterator& other) const { return m_id == other.m_id; } bool operator!=(const iterator& other) const { return !operator==(other); } CEntity* operator*() const { return m_list.GetObjectByIndex(m_id); } }; class const_iterator { friend class CObjectList; const CObjectList& m_list; s16 m_id; const_iterator(const CObjectList& list, s16 id) : m_list(list), m_id(id) {} public: const_iterator& operator++() { m_id = m_list.GetNextObjectIndex(m_id); return *this; } bool operator==(const iterator& other) const { return m_id == other.m_id; } bool operator!=(const iterator& other) const { return !operator==(other); } const CEntity* operator*() const { return m_list.GetObjectByIndex(m_id); } }; [[nodiscard]] iterator begin() { return iterator(*this, x2008_firstId); } [[nodiscard]] iterator end() { return iterator(*this, -1); } [[nodiscard]] const_iterator begin() const { return const_iterator(*this, x2008_firstId); } [[nodiscard]] const_iterator end() const { return const_iterator(*this, -1); } [[nodiscard]] const_iterator cbegin() const { return begin(); } [[nodiscard]] const_iterator cend() const { return end(); } explicit CObjectList(EGameObjectList listEnum); virtual ~CObjectList() = default; void AddObject(CEntity& entity); void RemoveObject(TUniqueId uid); const CEntity* operator[](size_t i) const; CEntity* operator[](size_t i); const CEntity* GetObjectById(TUniqueId uid) const; const CEntity* GetObjectByIndex(s16 index) const { return x0_list[index].entity; } CEntity* GetObjectByIndex(s16 index) { return x0_list[index].entity; } CEntity* GetObjectById(TUniqueId uid); const CEntity* GetValidObjectById(TUniqueId uid) const; CEntity* GetValidObjectById(TUniqueId uid); s16 GetFirstObjectIndex() const { return x2008_firstId; } s16 GetNextObjectIndex(s16 prev) const { return x0_list[prev].next; } virtual bool IsQualified(const CEntity&) const; u16 size() const { return x200a_count; } }; } // namespace metaforce ================================================ FILE: Runtime/CPakFile.cpp ================================================ #include "Runtime/CPakFile.hpp" #include "Runtime/Logging.hpp" namespace metaforce { CPakFile::CPakFile(std::string_view filename, bool buildDepList, bool worldPak, bool override) : CDvdFile(filename) { if (!CDvdFile::operator bool()) spdlog::fatal("{}: Unable to open", GetPath()); x28_24_buildDepList = buildDepList; // x28_24_buildDepList = true; // Always do this so metaforce can rapidly pre-warm shaders x28_26_worldPak = worldPak; m_override = override; } CPakFile::~CPakFile() { if (x30_dvdReq) x30_dvdReq->PostCancelRequest(); } const SObjectTag* CPakFile::GetResIdByName(std::string_view name) const { for (const std::pair& p : x54_nameList) { if (CStringExtras::CompareCaseInsensitive(p.first, name)) { return &p.second; } } return nullptr; } void CPakFile::LoadResourceTable(CInputStream& r) { x74_resList.reserve( std::max(size_t(64), size_t(ROUND_UP_32(x4c_resTableCount * sizeof(SResInfo)) + sizeof(SResInfo) - 1)) / sizeof(SResInfo)); if (x28_24_buildDepList) x64_depList.reserve(x4c_resTableCount); for (u32 i = 0; i < x4c_resTableCount; ++i) { u32 flags = r.ReadLong(); FourCC fcc; r.ReadBytes(reinterpret_cast(&fcc), 4); CAssetId id = r.Get(); u32 size = r.ReadLong(); u32 offset = r.ReadLong(); if (fcc == FOURCC('MLVL')) m_mlvlId = id; x74_resList.emplace_back(id, fcc, offset, size, flags); if (x28_24_buildDepList) x64_depList.push_back(id); } std::sort(x74_resList.begin(), x74_resList.end(), [](const auto& a, const auto& b) { return a.x0_id < b.x0_id; }); } void CPakFile::DataLoad() { x30_dvdReq.reset(); CMemoryInStream r(x38_headerData.data() + x48_resTableOffset, x38_headerData.size() - x48_resTableOffset, CMemoryInStream::EOwnerShip::NotOwned); LoadResourceTable(r); x2c_asyncLoadPhase = EAsyncPhase::Loaded; if (x28_26_worldPak) { // Allocate ARAM space DMA x74_resList to ARAM } x38_headerData.clear(); } void CPakFile::InitialHeaderLoad() { CMemoryInStream r(x38_headerData.data(), x38_headerData.size(), CMemoryInStream::EOwnerShip::NotOwned); x30_dvdReq.reset(); u32 version = r.ReadLong(); if (version != 0x00030005) { spdlog::fatal("{}: Incompatible pak file version -- Current version is {:08X}, you're using {:08X}", GetPath(), 0x00030005, version); } r.ReadLong(); u32 nameCount = r.ReadLong(); x54_nameList.reserve(nameCount); for (u32 i = 0; i < nameCount; ++i) { SObjectTag tag(r); auto name = CStringExtras::ReadString(r); x54_nameList.emplace_back(name, tag); } x4c_resTableCount = r.ReadLong(); x48_resTableOffset = u32(r.GetReadPosition()); x2c_asyncLoadPhase = EAsyncPhase::DataLoad; u32 newSize = ROUND_UP_32(x4c_resTableCount * 20 + x48_resTableOffset); u32 origSize = u32(x38_headerData.size()); if (newSize > origSize) { x38_headerData.resize(newSize); x30_dvdReq = AsyncSeekRead(x38_headerData.data() + origSize, u32(x38_headerData.size() - origSize), ESeekOrigin::Begin, origSize); } else { DataLoad(); } }; void CPakFile::Warmup() { u32 length = std::min(u32(Length()), u32(8192)); x38_headerData.resize(length); x30_dvdReq = AsyncSeekRead(x38_headerData.data(), length, ESeekOrigin::Cur, 0); x2c_asyncLoadPhase = EAsyncPhase::InitialHeader; } const CPakFile::SResInfo* CPakFile::GetResInfoForLoadPreferForward(CAssetId id) const { if (x28_27_stashedInARAM) return nullptr; auto search = rstl::binary_find(x74_resList.begin(), x74_resList.end(), id, [](const SResInfo& test) { return test.x0_id; }); if (search == x74_resList.end()) return nullptr; const SResInfo* bestInfo = &*search; s32 bestDelta = x84_currentSeek - bestInfo->GetOffset(); while (++search != x74_resList.end()) { const SResInfo* thisInfo = &*search; if (thisInfo->x0_id != id) break; s32 thisDelta = x84_currentSeek - bestInfo->GetOffset(); if ((bestDelta < 0 && (thisDelta > 0 || thisDelta > bestDelta)) || (bestDelta >= 0 && thisDelta > 0 && thisDelta < bestDelta)) { bestDelta = thisDelta; bestInfo = thisInfo; } } x84_currentSeek = bestInfo->GetOffset() + bestInfo->GetSize(); return bestInfo; } const CPakFile::SResInfo* CPakFile::GetResInfoForLoadDirectionless(CAssetId id) const { if (x28_27_stashedInARAM) return nullptr; auto search = rstl::binary_find(x74_resList.begin(), x74_resList.end(), id, [](const SResInfo& test) { return test.x0_id; }); if (search == x74_resList.end()) return nullptr; const SResInfo* bestInfo = &*search; s32 bestDelta = std::abs(s32(x84_currentSeek - bestInfo->GetOffset())); while (++search != x74_resList.end()) { const SResInfo* thisInfo = &*search; if (thisInfo->x0_id != id) break; s32 thisDelta = std::abs(s32(x84_currentSeek - bestInfo->GetOffset())); if (thisDelta < bestDelta) { bestDelta = thisDelta; bestInfo = thisInfo; } } x84_currentSeek = bestInfo->GetOffset() + bestInfo->GetSize(); return bestInfo; } const CPakFile::SResInfo* CPakFile::GetResInfo(CAssetId id) const { if (x2c_asyncLoadPhase != EAsyncPhase::Loaded) return nullptr; if (x28_27_stashedInARAM) return nullptr; auto search = rstl::binary_find(x74_resList.begin(), x74_resList.end(), id, [](const SResInfo& i) { return i.x0_id; }); if (search == x74_resList.end()) return nullptr; return &*search; } void CPakFile::AsyncIdle() { if (x2c_asyncLoadPhase == EAsyncPhase::Loaded) return; if (x30_dvdReq && !x30_dvdReq->IsComplete()) return; switch (x2c_asyncLoadPhase) { case EAsyncPhase::Warmup: Warmup(); break; case EAsyncPhase::InitialHeader: InitialHeaderLoad(); break; case EAsyncPhase::DataLoad: DataLoad(); break; default: break; } } } // namespace metaforce ================================================ FILE: Runtime/CPakFile.hpp ================================================ #pragma once #include #include #include #include "Runtime/CDvdFile.hpp" #include "Runtime/CDvdRequest.hpp" #include "Runtime/CFactoryMgr.hpp" #include "Runtime/CStringExtras.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CPakFile : public CDvdFile { friend class CResLoader; public: struct SResInfo { CAssetId x0_id; bool x4_compressed : 1; CFactoryMgr::ETypeTable x4_typeIdx; u32 x5_offsetDiv32 : 27; u32 x7_sizeDiv32 : 27; SResInfo(CAssetId id, FourCC fcc, u32 offset, u32 size, u32 flags) : x0_id(id) { x4_compressed = flags != 0; x4_typeIdx = CFactoryMgr::FourCCToTypeIdx(fcc); x5_offsetDiv32 = offset / 32; x7_sizeDiv32 = size / 32; } u32 GetOffset() const { return x5_offsetDiv32 * 32; } u32 GetSize() const { return x7_sizeDiv32 * 32; } FourCC GetType() const { return CFactoryMgr::TypeIdxToFourCC(x4_typeIdx); } bool IsCompressed() const { return x4_compressed; } CAssetId GetId() const { return x0_id; } }; private: bool x28_24_buildDepList : 1; bool x28_25_aramFile : 1 = false; bool x28_26_worldPak : 1; bool x28_27_stashedInARAM : 1 = false; bool m_override : 1; enum class EAsyncPhase { Warmup = 0, InitialHeader = 1, DataLoad = 2, Loaded = 3 } x2c_asyncLoadPhase = EAsyncPhase::Warmup; std::shared_ptr x30_dvdReq; // Used to be auto_ptr std::vector x38_headerData; u32 x48_resTableOffset = 0; u32 x4c_resTableCount = 0; int x50_aramBase = -1; std::vector> x54_nameList; std::vector x64_depList; std::vector x74_resList; mutable s32 x84_currentSeek = -1; CAssetId m_mlvlId; void LoadResourceTable(CInputStream& r); void DataLoad(); void InitialHeaderLoad(); void Warmup(); public: CPakFile(std::string_view filename, bool buildDepList, bool worldPak, bool override = false); ~CPakFile(); const std::vector>& GetNameList() const { return x54_nameList; } const std::vector& GetDepList() const { return x64_depList; } const SObjectTag* GetResIdByName(std::string_view name) const; const SResInfo* GetResInfoForLoadPreferForward(CAssetId id) const; const SResInfo* GetResInfoForLoadDirectionless(CAssetId id) const; const SResInfo* GetResInfo(CAssetId id) const; bool IsWorldPak() const { return x28_26_worldPak; } bool IsOverridePak() const { return m_override; } u32 GetFakeStaticSize() const { return 0; } void AsyncIdle(); CAssetId GetMLVLId() const { return m_mlvlId; } }; } // namespace metaforce ================================================ FILE: Runtime/CPlayerState.cpp ================================================ #include "Runtime/CPlayerState.hpp" #include #include #include #include "Runtime/CStringExtras.hpp" #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Camera/CCameraManager.hpp" #include "Runtime/Camera/CFirstPersonCamera.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path #include namespace metaforce { namespace { constexpr std::array PowerUpMaxValues{ 1, 1, 1, 1, 250, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; [[maybe_unused]] constexpr std::array PowerUpNames{ "Power Beam", "Ice Beam", "Wave Beam", "Plasma Beam", "Missiles", "Scan Visor", "Morph Ball Bombs", "Power Bombs", "Flamethrower", "Thermal Visor", "Charge Beam", "Super Missile", "GrappleBeam", "X-Ray Visor", "Ice Spreader", "Space Jump Boots", "Morph Ball", "Combat Visor", "Boost Ball", "Spider Ball", "Power Suit", "Gravity Suit", "Varia Suit", "Phazon Suit", "Energy Tanks", "Unknown Item 1", "Health Refill", "Unknown Item 2", "Wavebuster", "Artifact of Truth", "Artifact of Strength", "Artifact of Elder", "Artifact of Wild", "Artifact of Lifegiver", "Artifact of Warrior", "Artifact of Chozo", "Artifact of Nature", "Artifact of Sun", "Artifact of World", "Artifact of Spirit", "Artifact of Newborn", }; constexpr std::array costs{ 5, 10, 10, 10, 1, }; constexpr std::array ComboAmmoPeriods{ 0.2f, 0.1f, 0.2f, 0.2f, 1.f, }; } // Anonymous namespace CPlayerState::CPlayerState() { x24_powerups.resize(41); } CPlayerState::CPlayerState(CInputStream& stream) { x4_enabledItems = u32(stream.ReadBits(32)); const u32 integralHP = u32(stream.ReadBits(32)); float realHP; std::memcpy(&realHP, &integralHP, sizeof(float)); xc_health.SetHP(realHP); x8_currentBeam = EBeamId(stream.ReadBits(CInputStream::GetBitCount(5))); x20_currentSuit = EPlayerSuit(stream.ReadBits(CInputStream::GetBitCount(4))); x24_powerups.resize(41); for (size_t i = 0; i < x24_powerups.size(); ++i) { if (PowerUpMaxValues[i] == 0) { continue; } const u32 a = u32(stream.ReadBits(CInputStream::GetBitCount(PowerUpMaxValues[i]))); const u32 b = u32(stream.ReadBits(CInputStream::GetBitCount(PowerUpMaxValues[i]))); x24_powerups[i] = CPowerUp(a, b); } const auto& scanStates = g_MemoryCardSys->GetScanStates(); x170_scanTimes.reserve(scanStates.size()); for (const auto& state : scanStates) { float time = stream.ReadBits(1) ? 1.f : 0.f; x170_scanTimes.emplace_back(state.first, time); } x180_scanCompletionRate.first = u32(stream.ReadBits(CInputStream::GetBitCount(0x100u))); x180_scanCompletionRate.second = u32(stream.ReadBits(CInputStream::GetBitCount(0x100u))); } void CPlayerState::PutTo(COutputStream& stream) { stream.WriteBits(x4_enabledItems, 32); const float realHP = xc_health.GetHP(); u32 integralHP; std::memcpy(&integralHP, &realHP, sizeof(u32)); stream.WriteBits(integralHP, 32); stream.WriteBits(u32(x8_currentBeam), COutputStream::GetBitCount(5)); stream.WriteBits(u32(x20_currentSuit), COutputStream::GetBitCount(4)); for (size_t i = 0; i < x24_powerups.size(); ++i) { const CPowerUp& pup = x24_powerups[i]; stream.WriteBits(pup.x0_amount, COutputStream::GetBitCount(PowerUpMaxValues[i])); stream.WriteBits(pup.x4_capacity, COutputStream::GetBitCount(PowerUpMaxValues[i])); } for (const auto& scanTime : x170_scanTimes) { if (scanTime.second >= 1.f) stream.WriteBits(true, 1); else stream.WriteBits(false, 1); } stream.WriteBits(x180_scanCompletionRate.first, COutputStream::GetBitCount(0x100)); stream.WriteBits(x180_scanCompletionRate.second, COutputStream::GetBitCount(0x100)); } u32 CPlayerState::GetMissileCostForAltAttack() const { return costs[size_t(x8_currentBeam)]; } float CPlayerState::GetComboFireAmmoPeriod() const { return ComboAmmoPeriods[size_t(x8_currentBeam)]; } u32 CPlayerState::CalculateItemCollectionRate() const { u32 total = GetItemCapacity(EItemType::PowerBombs); if (total >= 4) total -= 3; total += GetItemCapacity(EItemType::WaveBeam); total += GetItemCapacity(EItemType::IceBeam); total += GetItemCapacity(EItemType::PlasmaBeam); total += GetItemCapacity(EItemType::Missiles) / 5; total += GetItemCapacity(EItemType::MorphBallBombs); total += GetItemCapacity(EItemType::Flamethrower); total += GetItemCapacity(EItemType::ThermalVisor); total += GetItemCapacity(EItemType::ChargeBeam); total += GetItemCapacity(EItemType::SuperMissile); total += GetItemCapacity(EItemType::GrappleBeam); total += GetItemCapacity(EItemType::XRayVisor); total += GetItemCapacity(EItemType::IceSpreader); total += GetItemCapacity(EItemType::SpaceJumpBoots); total += GetItemCapacity(EItemType::MorphBall); total += GetItemCapacity(EItemType::BoostBall); total += GetItemCapacity(EItemType::SpiderBall); total += GetItemCapacity(EItemType::GravitySuit); total += GetItemCapacity(EItemType::VariaSuit); total += GetItemCapacity(EItemType::EnergyTanks); total += GetItemCapacity(EItemType::Truth); total += GetItemCapacity(EItemType::Strength); total += GetItemCapacity(EItemType::Elder); total += GetItemCapacity(EItemType::Wild); total += GetItemCapacity(EItemType::Lifegiver); total += GetItemCapacity(EItemType::Warrior); total += GetItemCapacity(EItemType::Chozo); total += GetItemCapacity(EItemType::Nature); total += GetItemCapacity(EItemType::Sun); total += GetItemCapacity(EItemType::World); total += GetItemCapacity(EItemType::Spirit); total += GetItemCapacity(EItemType::Newborn); return total + GetItemCapacity(EItemType::Wavebuster); } CHealthInfo& CPlayerState::GetHealthInfo() { return xc_health; } const CHealthInfo& CPlayerState::GetHealthInfo() const { return xc_health; } CPlayerState::EPlayerSuit CPlayerState::GetCurrentSuit() const { if (IsFusionEnabled()) return EPlayerSuit::FusionPower; return x20_currentSuit; } bool CPlayerState::CanVisorSeeFog(const CStateManager& stateMgr) const { EPlayerVisor activeVisor = GetActiveVisor(stateMgr); if (activeVisor == EPlayerVisor::Combat || activeVisor == EPlayerVisor::Scan) return true; return true; } CPlayerState::EPlayerVisor CPlayerState::GetActiveVisor(const CStateManager& stateMgr) const { const CFirstPersonCamera* cam = TCastToConstPtr(stateMgr.GetCameraManager()->GetCurrentCamera(stateMgr)).GetPtr(); return (cam ? x14_currentVisor : EPlayerVisor::Combat); } void CPlayerState::UpdateStaticInterference(CStateManager& stateMgr, float dt) { x188_staticIntf.Update(stateMgr, dt); } void CPlayerState::SetScanTime(CAssetId res, float time) { auto it = std::find_if(x170_scanTimes.begin(), x170_scanTimes.end(), [&](const auto& test) -> bool { return test.first == res; }); if (it != x170_scanTimes.end()) it->second = time; } float CPlayerState::GetScanTime(CAssetId res) const { const auto it = std::find_if(x170_scanTimes.cbegin(), x170_scanTimes.cend(), [&](const auto& test) -> bool { return test.first == res; }); if (it == x170_scanTimes.end()) return 0.f; return it->second; } bool CPlayerState::GetIsVisorTransitioning() const { return x14_currentVisor != x18_transitioningVisor || x1c_visorTransitionFactor < 0.2f; } float CPlayerState::GetVisorTransitionFactor() const { return x1c_visorTransitionFactor / 0.2f; } void CPlayerState::UpdateVisorTransition(float dt) { if (!GetIsVisorTransitioning()) return; if (x14_currentVisor == x18_transitioningVisor) { x1c_visorTransitionFactor += dt; if (x1c_visorTransitionFactor > 0.2f) x1c_visorTransitionFactor = 0.2f; } else { x1c_visorTransitionFactor -= dt; if (x1c_visorTransitionFactor < 0.f) { x14_currentVisor = x18_transitioningVisor; x1c_visorTransitionFactor = std::fabs(x1c_visorTransitionFactor); if (x1c_visorTransitionFactor > 0.19999f) x1c_visorTransitionFactor = 0.19999f; } } } void CPlayerState::StartTransitionToVisor(CPlayerState::EPlayerVisor visor) { if (x18_transitioningVisor == visor) return; x18_transitioningVisor = visor; } void CPlayerState::ResetVisor() { x18_transitioningVisor = x14_currentVisor = EPlayerVisor::Combat; x1c_visorTransitionFactor = 0.0f; } bool CPlayerState::ItemEnabled(CPlayerState::EItemType type) const { if (HasPowerUp(type)) return (x4_enabledItems & (1 << u32(type))); return false; } void CPlayerState::EnableItem(CPlayerState::EItemType type) { if (HasPowerUp(type)) x4_enabledItems |= (1 << u32(type)); } void CPlayerState::DisableItem(CPlayerState::EItemType type) { if (HasPowerUp(type)) x4_enabledItems &= ~(1 << u32(type)); } bool CPlayerState::HasPowerUp(CPlayerState::EItemType type) const { if (type < EItemType::Max) return x24_powerups[u32(type)].x4_capacity != 0; return false; } u32 CPlayerState::GetItemCapacity(CPlayerState::EItemType type) const { if (type < EItemType::Max) return x24_powerups[u32(type)].x4_capacity; return 0; } u32 CPlayerState::GetItemAmount(CPlayerState::EItemType type) const { if (type == EItemType::SpaceJumpBoots || type == EItemType::PowerBombs || type == EItemType::Flamethrower || type == EItemType::EnergyTanks || type == EItemType::Missiles || (type >= EItemType::Truth && type <= EItemType::Newborn)) { return x24_powerups[u32(type)].x0_amount; } return 0; } void CPlayerState::DecrPickup(CPlayerState::EItemType type, u32 amount) { if (type >= EItemType::Max) return; if ((type == EItemType::Missiles || type >= EItemType::PowerBombs) && type < EItemType::ThermalVisor) x24_powerups[u32(type)].x0_amount -= amount; } void CPlayerState::IncrPickup(EItemType type, u32 amount) { if (type >= EItemType::Max) return; switch (type) { case EItemType::Missiles: case EItemType::PowerBombs: case EItemType::ChargeBeam: case EItemType::SpaceJumpBoots: case EItemType::EnergyTanks: case EItemType::Truth: case EItemType::Strength: case EItemType::Elder: case EItemType::Wild: case EItemType::Lifegiver: case EItemType::Warrior: case EItemType::Chozo: case EItemType::Nature: case EItemType::Sun: case EItemType::World: case EItemType::Spirit: case EItemType::Newborn: { CPowerUp& pup = x24_powerups[u32(type)]; pup.x0_amount = std::min(pup.x0_amount + u32(amount), pup.x4_capacity); if (type == EItemType::EnergyTanks) IncrPickup(EItemType::HealthRefill, 9999); break; } case EItemType::HealthRefill: xc_health.SetHP(std::min(amount + xc_health.GetHP(), CalculateHealth())); break; default: break; } } void CPlayerState::ResetAndIncrPickUp(CPlayerState::EItemType type, u32 amount) { x24_powerups[u32(type)].x0_amount = 0; IncrPickup(type, amount); } float CPlayerState::CalculateHealth() { return (GetEnergyTankCapacity() * x24_powerups[u32(EItemType::EnergyTanks)].x0_amount) + GetBaseHealthCapacity(); } void CPlayerState::AddPowerUp(CPlayerState::EItemType type, u32 capacity) { if (type >= EItemType::Max) return; CPowerUp& pup = x24_powerups[u32(type)]; pup.x4_capacity = zeus::clamp(0u, pup.x4_capacity + capacity, PowerUpMaxValues[u32(type)]); pup.x0_amount = std::min(pup.x0_amount, pup.x4_capacity); if (type >= EItemType::PowerSuit && type <= EItemType::PhazonSuit) { if (HasPowerUp(EItemType::PhazonSuit)) x20_currentSuit = EPlayerSuit::Phazon; else if (HasPowerUp(EItemType::GravitySuit)) x20_currentSuit = EPlayerSuit::Gravity; else if (HasPowerUp(EItemType::VariaSuit)) x20_currentSuit = EPlayerSuit::Varia; else x20_currentSuit = EPlayerSuit::Power; } } void CPlayerState::ReInitializePowerUp(CPlayerState::EItemType type, u32 capacity) { x24_powerups[u32(type)].x4_capacity = 0; AddPowerUp(type, capacity); } void CPlayerState::InitializeScanTimes() { if (x170_scanTimes.size()) return; const auto& scanStates = g_MemoryCardSys->GetScanStates(); x170_scanTimes.reserve(scanStates.size()); for (const auto& state : scanStates) x170_scanTimes.emplace_back(state.first, 0.f); } u32 CPlayerState::GetPowerUpMaxValue(EItemType type) { return PowerUpMaxValues[size_t(type)]; } CPlayerState::EItemType CPlayerState::ItemNameToType(std::string_view name) { static constexpr std::array, 46> typeNameMap{{ {"powerbeam"sv, EItemType::PowerBeam}, {"icebeam"sv, EItemType::IceBeam}, {"wavebeam"sv, EItemType::WaveBeam}, {"plasmabeam"sv, EItemType::PlasmaBeam}, {"missiles"sv, EItemType::Missiles}, {"scanvisor"sv, EItemType::ScanVisor}, {"bombs"sv, EItemType::MorphBallBombs}, {"ballbombs"sv, EItemType::MorphBallBombs}, {"morphballbombs"sv, EItemType::MorphBallBombs}, {"powerbombs"sv, EItemType::PowerBombs}, {"flamethrower"sv, EItemType::Flamethrower}, {"thermalvisor"sv, EItemType::ThermalVisor}, {"chargebeam"sv, EItemType::ChargeBeam}, {"supermissile"sv, EItemType::SuperMissile}, {"grapple"sv, EItemType::GrappleBeam}, {"grapplebeam"sv, EItemType::GrappleBeam}, {"xrayvisor"sv, EItemType::XRayVisor}, {"icespreader"sv, EItemType::IceSpreader}, {"spacejump"sv, EItemType::SpaceJumpBoots}, {"spacejumpboots"sv, EItemType::SpaceJumpBoots}, {"morphball"sv, EItemType::MorphBall}, {"combatvisor"sv, EItemType::CombatVisor}, {"boostball"sv, EItemType::BoostBall}, {"spiderball"sv, EItemType::SpiderBall}, {"powersuit"sv, EItemType::PowerSuit}, {"gravitysuit"sv, EItemType::GravitySuit}, {"variasuit"sv, EItemType::VariaSuit}, {"phazonsuit"sv, EItemType::PhazonSuit}, {"energytanks"sv, EItemType::EnergyTanks}, {"unknownitem1"sv, EItemType::UnknownItem1}, {"healthrefill"sv, EItemType::HealthRefill}, {"health"sv, EItemType::HealthRefill}, {"unknownitem2"sv, EItemType::UnknownItem2}, {"wavebuster"sv, EItemType::Wavebuster}, {"truth"sv, EItemType::Truth}, {"strength"sv, EItemType::Strength}, {"elder"sv, EItemType::Elder}, {"wild"sv, EItemType::Wild}, {"lifegiver"sv, EItemType::Lifegiver}, {"warrior"sv, EItemType::Warrior}, {"chozo"sv, EItemType::Chozo}, {"nature"sv, EItemType::Nature}, {"sun"sv, EItemType::Sun}, {"world"sv, EItemType::World}, {"spirit"sv, EItemType::Spirit}, {"newborn"sv, EItemType::Newborn}, }}; std::string lowName{name}; CStringExtras::ToLower(lowName); const auto iter = std::find_if(typeNameMap.cbegin(), typeNameMap.cend(), [&lowName](const auto& entry) { return entry.first == lowName; }); if (iter == typeNameMap.cend()) { return EItemType::Invalid; } return iter->second; } std::string_view CPlayerState::ItemTypeToName(CPlayerState::EItemType type) { switch (type) { case EItemType::PowerBeam: return "Power Beam"sv; case EItemType::IceBeam: return "Ice Beam"sv; case EItemType::WaveBeam: return "Wave Beam"sv; case EItemType::PlasmaBeam: return "Plasma Beam"sv; case EItemType::Missiles: return "Missiles"sv; case EItemType::ScanVisor: return "Scan Visor"sv; case EItemType::MorphBallBombs: return "Morph Ball Bombs"sv; case EItemType::PowerBombs: return "Power Bombs"sv; case EItemType::Flamethrower: return "Flamethrower"sv; case EItemType::ThermalVisor: return "Thermal Visor"sv; case EItemType::ChargeBeam: return "Charge Beam"sv; case EItemType::SuperMissile: return "Super Missile"sv; case EItemType::GrappleBeam: return "Grapple Beam"sv; case EItemType::XRayVisor: return "X-Ray Visor"sv; case EItemType::IceSpreader: return "Ice Spreader"sv; case EItemType::SpaceJumpBoots: return "Space Jump Boots"sv; case EItemType::MorphBall: return "Morph Ball"sv; case EItemType::CombatVisor: return "Combat Visor"sv; case EItemType::BoostBall: return "Boost Ball"sv; case EItemType::SpiderBall: return "Spider Ball"sv; case EItemType::PowerSuit: return "Power Suit"sv; case EItemType::GravitySuit: return "Gravity Suit"sv; case EItemType::VariaSuit: return "Varia Suit"sv; case EItemType::PhazonSuit: return "Phazon Suit"sv; case EItemType::EnergyTanks: return "Energy Tanks"sv; case EItemType::HealthRefill: return "Health Refill"sv; case EItemType::Wavebuster: return "Wavebuster"sv; case EItemType::Truth: return "Artifact of Truth"sv; case EItemType::Strength: return "Artifact of Strength"sv; case EItemType::Elder: return "Artifact of Elder"sv; case EItemType::Wild: return "Artifact of Wild"sv; case EItemType::Lifegiver: return "Artifact of Lifegiver"sv; case EItemType::Warrior: return "Artifact of Warrior"sv; case EItemType::Chozo: return "Artifact of Chozo"sv; case EItemType::Nature: return "Artifact of Nature"sv; case EItemType::Sun: return "Artifact of Sun"sv; case EItemType::World: return "Artifact of World"sv; case EItemType::Spirit: return "Artifact of Spirit"sv; case EItemType::Newborn: return "Artifact of Newborn"sv; default: return "[unknown]"sv; } } } // namespace metaforce ================================================ FILE: Runtime/CPlayerState.hpp ================================================ #pragma once #include #include #include "Runtime/CStaticInterference.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/World/CHealthInfo.hpp" namespace metaforce { class CPlayerState { friend class CWorldTransManager; public: enum class EItemType : s32 { Invalid = -1, PowerBeam = 0, IceBeam = 1, WaveBeam = 2, PlasmaBeam = 3, Missiles = 4, ScanVisor = 5, MorphBallBombs = 6, PowerBombs = 7, Flamethrower = 8, ThermalVisor = 9, ChargeBeam = 10, SuperMissile = 11, GrappleBeam = 12, XRayVisor = 13, IceSpreader = 14, SpaceJumpBoots = 15, MorphBall = 16, CombatVisor = 17, BoostBall = 18, SpiderBall = 19, PowerSuit = 20, GravitySuit = 21, VariaSuit = 22, PhazonSuit = 23, EnergyTanks = 24, UnknownItem1 = 25, HealthRefill = 26, UnknownItem2 = 27, Wavebuster = 28, Truth = 29, Strength = 30, Elder = 31, Wild = 32, Lifegiver = 33, Warrior = 34, Chozo = 35, Nature = 36, Sun = 37, World = 38, Spirit = 39, Newborn = 40, /* This must remain at the end of the list */ Max }; enum class EPlayerVisor : u32 { Combat, XRay, Scan, Thermal, /* This must remain at the end of the list */ Max }; enum class EPlayerSuit : s32 { Invalid = -1, Power, Gravity, Varia, Phazon, FusionPower, FusionGravity, FusionVaria, FusionPhazon }; enum class EBeamId : s32 { Invalid = -1, Power, Ice, Wave, Plasma, Phazon, Phazon2 = 27 }; private: struct CPowerUp { u32 x0_amount = 0; u32 x4_capacity = 0; constexpr CPowerUp() = default; constexpr CPowerUp(u32 amount, u32 capacity) : x0_amount(amount), x4_capacity(capacity) {} }; bool x0_24_alive : 1 = true; bool x0_25_firingComboBeam : 1 = false; bool x0_26_fusion : 1 = false; u32 x4_enabledItems = 0; EBeamId x8_currentBeam = EBeamId::Power; CHealthInfo xc_health = {99.f, 50.f}; EPlayerVisor x14_currentVisor = EPlayerVisor::Combat; EPlayerVisor x18_transitioningVisor = x14_currentVisor; float x1c_visorTransitionFactor = 0.2f; EPlayerSuit x20_currentSuit = EPlayerSuit::Power; rstl::reserved_vector x24_powerups; std::vector> x170_scanTimes; std::pair x180_scanCompletionRate = {}; CStaticInterference x188_staticIntf{5}; bool m_canTakeDamage = true; public: u32 GetMissileCostForAltAttack() const; float GetComboFireAmmoPeriod() const; static constexpr float GetMissileComboChargeFactor() { return 1.8f; } u32 CalculateItemCollectionRate() const; CHealthInfo& GetHealthInfo(); const CHealthInfo& GetHealthInfo() const; u32 GetPickupTotal() const { return 99; } void SetIsFusionEnabled(bool val) { x0_26_fusion = val; } bool IsFusionEnabled() const { return x0_26_fusion; } EPlayerSuit GetCurrentSuit() const; EPlayerSuit GetCurrentSuitRaw() const { return x20_currentSuit; } EBeamId GetCurrentBeam() const { return x8_currentBeam; } void SetCurrentBeam(EBeamId beam) { x8_currentBeam = beam; } bool CanVisorSeeFog(const CStateManager& stateMgr) const; EPlayerVisor GetCurrentVisor() const { return x14_currentVisor; } EPlayerVisor GetTransitioningVisor() const { return x18_transitioningVisor; } EPlayerVisor GetActiveVisor(const CStateManager& stateMgr) const; void UpdateStaticInterference(CStateManager& stateMgr, float dt); void IncreaseScanTime(u32 time, float val); void SetScanTime(CAssetId res, float time); float GetScanTime(CAssetId time) const; bool GetIsVisorTransitioning() const; float GetVisorTransitionFactor() const; void UpdateVisorTransition(float dt); void StartTransitionToVisor(EPlayerVisor visor); void ResetVisor(); bool ItemEnabled(EItemType type) const; void DisableItem(EItemType type); void EnableItem(EItemType type); bool HasPowerUp(EItemType type) const; u32 GetItemCapacity(EItemType type) const; u32 GetItemAmount(EItemType type) const; void DecrPickup(EItemType type, u32 amount); void IncrPickup(EItemType type, u32 amount); void ResetAndIncrPickUp(EItemType type, u32 amount); static float GetEnergyTankCapacity() { return 100.f; } static float GetBaseHealthCapacity() { return 99.f; } float CalculateHealth(); void ReInitializePowerUp(EItemType type, u32 capacity); void AddPowerUp(EItemType type, u32 capacity); u32 GetLogScans() const { return x180_scanCompletionRate.first; } u32 GetTotalLogScans() const { return x180_scanCompletionRate.second; } void SetScanCompletionRate(const std::pair& p) { x180_scanCompletionRate = p; } bool IsPlayerAlive() const { return x0_24_alive; } void SetPlayerAlive(bool alive) { x0_24_alive = alive; } bool IsFiringComboBeam() const { return x0_25_firingComboBeam; } void SetFiringComboBeam(bool f) { x0_25_firingComboBeam = f; } void InitializeScanTimes(); CStaticInterference& GetStaticInterference() { return x188_staticIntf; } const std::vector>& GetScanTimes() const { return x170_scanTimes; } CPlayerState(); explicit CPlayerState(CInputStream& stream); void PutTo(COutputStream& stream); static u32 GetPowerUpMaxValue(EItemType type); static EItemType ItemNameToType(std::string_view name); static std::string_view ItemTypeToName(EItemType type); bool CanTakeDamage() const { return m_canTakeDamage; } void SetCanTakeDamage(bool c) { m_canTakeDamage = c; } }; } // namespace metaforce ================================================ FILE: Runtime/CRandom16.cpp ================================================ #include "Runtime/CRandom16.hpp" namespace metaforce { CRandom16* CRandom16::g_randomNumber = nullptr; // &DefaultRandom; CGlobalRandom* CGlobalRandom::g_currentGlobalRandom = nullptr; //&DefaultGlobalRandom; namespace { u32 g_numNextCalls = 0; u32 g_lastSeed = 0; }; void CRandom16::IncrementNumNextCalls() { ++g_numNextCalls; } u32 CRandom16::GetNumNextCalls() { return g_numNextCalls; } void CRandom16::ResetNumNextCalls() { g_numNextCalls = 0; } u32 CRandom16::GetLastSeed() { return g_lastSeed; } void CRandom16::SetLastSeed(u32 seed) { g_lastSeed = seed; } } // namespace metaforce ================================================ FILE: Runtime/CRandom16.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" namespace metaforce { class CRandom16 { s32 m_seed; static CRandom16* g_randomNumber; public: explicit CRandom16(s32 seed = 99) : m_seed(seed) {} s32 Next() { IncrementNumNextCalls(); m_seed = (m_seed * 0x41c64e6d) + 0x00003039; SetLastSeed(m_seed); return (m_seed >> 16) & 0xffff; } s32 GetSeed() const { return m_seed; } void SetSeed(s32 seed) { m_seed = seed; } float Float() { return Next() * 0.000015259022f; } float Range(float min, float max) { return min + Float() * (max - min); } s32 Range(s32 min, s32 max) { return min + (Next() % ((max - min) + 1)); } static CRandom16* GetRandomNumber() { return g_randomNumber; } static void SetRandomNumber(CRandom16* rnd) { g_randomNumber = rnd; } static void IncrementNumNextCalls(); static u32 GetNumNextCalls(); static void ResetNumNextCalls(); static u32 GetLastSeed(); static void SetLastSeed(u32 seed); }; class CGlobalRandom { CRandom16& m_random; CGlobalRandom* m_prev; static CGlobalRandom* g_currentGlobalRandom; public: CGlobalRandom(CRandom16& rand) : m_random(rand), m_prev(g_currentGlobalRandom) { g_currentGlobalRandom = this; CRandom16::SetRandomNumber(&m_random); } ~CGlobalRandom() { g_currentGlobalRandom = m_prev; if (m_prev) CRandom16::SetRandomNumber(&m_prev->m_random); else CRandom16::SetRandomNumber(nullptr); } }; } // namespace metaforce ================================================ FILE: Runtime/CResFactory.cpp ================================================ #include "Runtime/CResFactory.hpp" #include "CResourceNameDatabase.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStopwatch.hpp" #include "Runtime/Logging.hpp" #include "Runtime/Formatting.hpp" // #include "optick.h" namespace metaforce { void CResFactory::AddToLoadList(SLoadingData&& data) { const SObjectTag tag = data.x0_tag; m_loadMap.insert_or_assign(tag, m_loadList.insert(m_loadList.end(), std::move(data))); } CFactoryFnReturn CResFactory::BuildSync(const SObjectTag& tag, const CVParamTransfer& xfer, CObjectReference* selfRef) { CFactoryFnReturn ret; if (x5c_factoryMgr.CanMakeMemory(tag)) { std::unique_ptr data; int size = 0; x4_loader.LoadMemResourceSync(tag, data, &size); if (size) ret = x5c_factoryMgr.MakeObjectFromMemory(tag, std::move(data), size, x4_loader.GetResourceCompression(tag), xfer, selfRef); else ret = std::make_unique(nullptr); } else { if (auto rp = x4_loader.LoadNewResourceSync(tag, nullptr)) ret = x5c_factoryMgr.MakeObject(tag, *rp, xfer, selfRef); else ret = std::make_unique(nullptr); } spdlog::warn("sync-built {}", tag); return ret; } bool CResFactory::PumpResource(SLoadingData& data) { // OPTICK_EVENT(); if (data.x8_dvdReq && data.x8_dvdReq->IsComplete()) { data.x8_dvdReq.reset(); *data.xc_targetPtr = x5c_factoryMgr.MakeObjectFromMemory(data.x0_tag, std::move(data.x10_loadBuffer), data.x14_resSize, data.m_compressed, data.x18_cvXfer, data.m_selfRef); spdlog::info("async-built {}", data.x0_tag); if (CResourceNameDatabase::instance()) { if (const auto* name = CResourceNameDatabase::instance()->assetName(data.x0_tag.id)) { spdlog::info("rep filepath: {}", *name); } } return true; } return false; } std::unique_ptr CResFactory::Build(const SObjectTag& tag, const CVParamTransfer& xfer, CObjectReference* selfRef) { auto search = m_loadMap.find(tag); if (search != m_loadMap.end()) { while (!PumpResource(*search->second) || !search->second->xc_targetPtr) {} std::unique_ptr ret = std::move(*search->second->xc_targetPtr); m_loadList.erase(search->second); m_loadMap.erase(search); return ret; } return BuildSync(tag, xfer, selfRef); } void CResFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& xfer, std::unique_ptr* target, CObjectReference* selfRef) { auto search = m_loadMap.find(tag); if (search == m_loadMap.end()) { SLoadingData data(tag, target, xfer, x4_loader.GetResourceCompression(tag), selfRef); data.x14_resSize = x4_loader.ResourceSize(tag); if (data.x14_resSize != 0) { data.x10_loadBuffer = std::unique_ptr(new u8[data.x14_resSize]); data.x8_dvdReq = x4_loader.LoadResourceAsync(tag, data.x10_loadBuffer.get()); AddToLoadList(std::move(data)); } else { *target = std::make_unique(nullptr); } } } bool CResFactory::AsyncIdle(std::chrono::nanoseconds target) { // OPTICK_EVENT(); if (m_loadList.empty()) { return false; } auto startTime = std::chrono::high_resolution_clock::now(); do { auto& task = m_loadList.front(); if (PumpResource(task)) { m_loadMap.erase(task.x0_tag); m_loadList.pop_front(); if (m_loadList.empty()) { return false; } } } while (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime) < target); return true; } void CResFactory::CancelBuild(const SObjectTag& tag) { auto search = m_loadMap.find(tag); if (search != m_loadMap.end()) { if (search->second->x8_dvdReq) search->second->x8_dvdReq->PostCancelRequest(); m_loadList.erase(search->second); m_loadMap.erase(search); } } void CResFactory::LoadPersistentResources(CSimplePool& sp) { const auto& paks = x4_loader.GetPaks(); for (const auto& pak : paks) { if (!pak->IsWorldPak()) { for (const CAssetId& id : pak->GetDepList()) { SObjectTag tag(GetResourceTypeById(id), id); m_nonWorldTokens.push_back(sp.GetObj(tag)); m_nonWorldTokens.back().Lock(); } } } } } // namespace metaforce ================================================ FILE: Runtime/CResFactory.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/CResLoader.hpp" #include "Runtime/CToken.hpp" #include "Runtime/IFactory.hpp" #include "Runtime/IVParamObj.hpp" namespace metaforce { class IDvdRequest; class CSimplePool; class CResFactory : public IFactory { CResLoader x4_loader; CFactoryMgr x5c_factoryMgr; public: struct SLoadingData { SObjectTag x0_tag; std::shared_ptr x8_dvdReq; std::unique_ptr* xc_targetPtr = nullptr; std::unique_ptr x10_loadBuffer; u32 x14_resSize = 0; CVParamTransfer x18_cvXfer; bool m_compressed = false; CObjectReference* m_selfRef = nullptr; SLoadingData() = default; SLoadingData(const SObjectTag& tag, std::unique_ptr* ptr, const CVParamTransfer& xfer, bool compressed, CObjectReference* selfRef) : x0_tag(tag), xc_targetPtr(ptr), x18_cvXfer(xfer), m_compressed(compressed), m_selfRef(selfRef) {} }; private: std::list m_loadList; std::unordered_map::iterator> m_loadMap; std::vector m_nonWorldTokens; /* URDE: always keep non-world resources resident */ void AddToLoadList(SLoadingData&& data); CFactoryFnReturn BuildSync(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef); bool PumpResource(SLoadingData& data); public: CResLoader& GetLoader() { return x4_loader; } std::unique_ptr Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override; void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr*, CObjectReference* selfRef) override; bool AsyncIdle(std::chrono::nanoseconds target) override; void CancelBuild(const SObjectTag&) override; bool CanBuild(const SObjectTag& tag) override { return x4_loader.ResourceExists(tag); } u32 ResourceSize(const metaforce::SObjectTag& tag) override { return x4_loader.ResourceSize(tag); } std::unique_ptr LoadResourceSync(const metaforce::SObjectTag& tag) override { return x4_loader.LoadResourceSync(tag); } std::unique_ptr LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) override { return x4_loader.LoadNewResourcePartSync(tag, off, size); } void GetTagListForFile(const char* pakName, std::vector& out) const override { return x4_loader.GetTagListForFile(pakName, out); } std::shared_ptr LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) override { return x4_loader.LoadResourceAsync(tag, target); } std::shared_ptr LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size, void* target) override { return x4_loader.LoadResourcePartAsync(tag, off, size, target); } const SObjectTag* GetResourceIdByName(std::string_view name) const override { return x4_loader.GetResourceIdByName(name); } FourCC GetResourceTypeById(CAssetId id) const override { return x4_loader.GetResourceTypeById(id); } std::vector> GetResourceIdToNameList() const { return x4_loader.GetResourceIdToNameList(); } void EnumerateResources(const std::function& lambda) const override { return x4_loader.EnumerateResources(lambda); } void EnumerateNamedResources(const std::function& lambda) const override { return x4_loader.EnumerateNamedResources(lambda); } void LoadPersistentResources(CSimplePool& sp); void UnloadPersistentResources() { m_nonWorldTokens.clear(); } CResLoader* GetResLoader() override { return &x4_loader; } CFactoryMgr* GetFactoryMgr() override { return &x5c_factoryMgr; } }; } // namespace metaforce ================================================ FILE: Runtime/CResLoader.cpp ================================================ #include "Runtime/CResLoader.hpp" #include "Runtime/CPakFile.hpp" #include "Runtime/Logging.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { CResLoader::CResLoader() { x48_curPak = x18_pakLoadedList.end(); } const std::vector* CResLoader::GetTagListForFile(std::string_view name) const { const std::string namePak = std::string(name).append(".pak"); for (const std::unique_ptr& pak : x18_pakLoadedList) { if (CStringExtras::CompareCaseInsensitive(namePak, pak->x18_path)) { return &pak->GetDepList(); } } return nullptr; } void CResLoader::AddPakFileAsync(std::string_view name, bool buildDepList, bool worldPak, bool override) { const std::string namePak = std::string(name).append(".pak"); if (CDvdFile::FileExists(namePak)) { x30_pakLoadingList.emplace_back(std::make_unique(namePak, buildDepList, worldPak, override)); ++x44_pakLoadingCount; } } void CResLoader::AddPakFile(std::string_view name, bool samusPak, bool worldPak, bool override) { AddPakFileAsync(name, samusPak, worldPak, override); WaitForPakFileLoadingComplete(); } void CResLoader::WaitForPakFileLoadingComplete() { while (x44_pakLoadingCount) AsyncIdlePakLoading(); } std::unique_ptr CResLoader::LoadNewResourcePartSync(const SObjectTag& tag, u32 length, u32 offset, void* extBuf) { void* buf = extBuf; if (buf == nullptr) { buf = new u8[length]; } CPakFile* const file = FindResourceForLoad(tag); file->SyncSeekRead(buf, length, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset() + offset); return std::make_unique( buf, length, extBuf == nullptr ? CMemoryInStream::EOwnerShip::Owned : CMemoryInStream::EOwnerShip::NotOwned); } void CResLoader::LoadMemResourceSync(const SObjectTag& tag, std::unique_ptr& bufOut, int* sizeOut) { if (CPakFile* file = FindResourceForLoad(tag)) { bufOut = std::unique_ptr(new u8[x50_cachedResInfo->GetSize()]); file->SyncSeekRead(bufOut.get(), x50_cachedResInfo->GetSize(), ESeekOrigin::Begin, x50_cachedResInfo->GetOffset()); *sizeOut = x50_cachedResInfo->GetSize(); } } std::unique_ptr CResLoader::LoadResourceFromMemorySync(const SObjectTag& tag, const void* buf) { FindResourceForLoad(tag); std::unique_ptr newStrm = std::make_unique(buf, x50_cachedResInfo->GetSize(), CMemoryInStream::EOwnerShip::NotOwned); if (x50_cachedResInfo->IsCompressed()) { newStrm->ReadLong(); newStrm = std::make_unique(std::move(newStrm)); } return newStrm; } std::unique_ptr CResLoader::LoadNewResourceSync(const SObjectTag& tag, void* extBuf) { if (CPakFile* const file = FindResourceForLoad(tag)) { const size_t resSz = ROUND_UP_32(x50_cachedResInfo->GetSize()); void* buf = extBuf; if (buf == nullptr) { buf = new u8[resSz]; } file->SyncSeekRead(buf, resSz, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset()); const bool takeOwnership = extBuf == nullptr; std::unique_ptr newStrm = std::make_unique( buf, resSz, takeOwnership ? CMemoryInStream::EOwnerShip::Owned : CMemoryInStream::EOwnerShip::NotOwned); if (x50_cachedResInfo->IsCompressed()) { newStrm->ReadLong(); newStrm = std::make_unique(std::move(newStrm)); } return newStrm; } return nullptr; } std::shared_ptr CResLoader::LoadResourcePartAsync(const SObjectTag& tag, u32 off, u32 size, void* buf) { CPakFile* file = FindResourceForLoad(tag.id); return file->AsyncSeekRead(buf, size, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset() + off); } std::shared_ptr CResLoader::LoadResourceAsync(const SObjectTag& tag, void* buf) { CPakFile* file = FindResourceForLoad(tag.id); return file->AsyncSeekRead(buf, ROUND_UP_32(x50_cachedResInfo->GetSize()), ESeekOrigin::Begin, x50_cachedResInfo->GetOffset()); } std::unique_ptr CResLoader::LoadResourceSync(const metaforce::SObjectTag& tag) { CPakFile* file = FindResourceForLoad(tag.id); u32 size = ROUND_UP_32(x50_cachedResInfo->GetSize()); std::unique_ptr ret(new u8[size]); file->SyncSeekRead(ret.get(), size, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset()); return ret; } std::unique_ptr CResLoader::LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) { CPakFile* file = FindResourceForLoad(tag.id); std::unique_ptr ret(new u8[size]); file->SyncSeekRead(ret.get(), size, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset() + off); return ret; } void CResLoader::GetTagListForFile(const char* pakName, std::vector& out) const { std::string path = std::string(pakName) + ".pak"; for (const std::unique_ptr& file : m_overridePakList) { if (_GetTagListForFile(out, path, file)) return; } for (const std::unique_ptr& file : x18_pakLoadedList) { if (_GetTagListForFile(out, path, file)) return; } } bool CResLoader::_GetTagListForFile(std::vector& out, const std::string& path, const std::unique_ptr& file) const { if (CStringExtras::CompareCaseInsensitive(file->GetPath(), path)) { const auto& depList = file->GetDepList(); out.reserve(depList.size()); for (const auto& dep : depList) { const auto* const resInfo = file->GetResInfo(dep); out.emplace_back(resInfo->GetType(), dep); } return true; } return false; } bool CResLoader::GetResourceCompression(const SObjectTag& tag) const { if (FindResource(tag.id)) return x50_cachedResInfo->IsCompressed(); return false; } u32 CResLoader::ResourceSize(const SObjectTag& tag) const { if (FindResource(tag.id)) return x50_cachedResInfo->GetSize(); return 0; } bool CResLoader::ResourceExists(const SObjectTag& tag) const { return FindResource(tag.id); } FourCC CResLoader::GetResourceTypeById(CAssetId id) const { if (id.IsValid() && FindResource(id)) return x50_cachedResInfo->GetType(); return {}; } const SObjectTag* CResLoader::GetResourceIdByName(std::string_view name) const { for (const std::unique_ptr& file : m_overridePakList) if (const SObjectTag* id = file->GetResIdByName(name)) return id; for (const std::unique_ptr& file : x18_pakLoadedList) if (const SObjectTag* id = file->GetResIdByName(name)) return id; return nullptr; } bool CResLoader::AreAllPaksLoaded() const { return x44_pakLoadingCount == 0; } void CResLoader::AsyncIdlePakLoading() { for (auto it = x30_pakLoadingList.begin(); it != x30_pakLoadingList.end();) { (*it)->AsyncIdle(); if ((*it)->x2c_asyncLoadPhase == CPakFile::EAsyncPhase::Loaded) { MoveToCorrectLoadedList(std::move(*it)); it = x30_pakLoadingList.erase(it); --x44_pakLoadingCount; continue; } ++it; } } bool CResLoader::FindResource(CAssetId id) const { if (x4c_cachedResId == id) return true; for (auto it = m_overridePakList.begin(); it != m_overridePakList.end(); ++it) { if (CacheFromPak(**it, id)) { return true; } } if (x48_curPak != x18_pakLoadedList.end()) if (CacheFromPak(**x48_curPak, id)) return true; for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) { if (it == x48_curPak) continue; if (CacheFromPak(**it, id)) return true; } spdlog::warn("Unable to find asset {}", id); return false; } CPakFile* CResLoader::FindResourceForLoad(CAssetId id) { for (auto it = m_overridePakList.begin(); it != m_overridePakList.end(); ++it) { if (CacheFromPakForLoad(**it, id)) { return &**it; } } if (x48_curPak != x18_pakLoadedList.end()) if (CacheFromPakForLoad(**x48_curPak, id)) return &**x48_curPak; for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) { if (it == x48_curPak) continue; if (CacheFromPakForLoad(**it, id)) { x48_curPak = it; return &**it; } } spdlog::error("Unable to find asset {}", id); return nullptr; } CPakFile* CResLoader::FindResourceForLoad(const SObjectTag& tag) { return FindResourceForLoad(tag.id); } bool CResLoader::CacheFromPakForLoad(CPakFile& file, CAssetId id) { const CPakFile::SResInfo* info; if (x54_forwardSeek) { info = file.GetResInfoForLoadPreferForward(id); x54_forwardSeek = false; } else { info = file.GetResInfoForLoadDirectionless(id); } if (info) { x4c_cachedResId = id; x50_cachedResInfo = info; return true; } return false; } bool CResLoader::CacheFromPak(const CPakFile& file, CAssetId id) const { const CPakFile::SResInfo* info = file.GetResInfo(id); if (info) { x4c_cachedResId = id; x50_cachedResInfo = info; return true; } return false; } void CResLoader::MoveToCorrectLoadedList(std::unique_ptr&& file) { if (file->IsOverridePak()) m_overridePakList.push_back(std::move(file)); else x18_pakLoadedList.push_back(std::move(file)); } std::vector> CResLoader::GetResourceIdToNameList() const { std::vector> ret; for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) for (const auto& name : (*it)->GetNameList()) ret.push_back(name); return ret; } void CResLoader::EnumerateResources(const std::function& lambda) const { for (auto it = m_overridePakList.begin(); it != m_overridePakList.end(); ++it) { for (const CAssetId& id : (*it)->GetDepList()) { SObjectTag fcc(GetResourceTypeById(id), id); if (!lambda(fcc)) return; } } for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) { for (const CAssetId& id : (*it)->GetDepList()) { SObjectTag fcc(GetResourceTypeById(id), id); if (!lambda(fcc)) return; } } } void CResLoader::EnumerateNamedResources(const std::function& lambda) const { for (auto it = x18_pakLoadedList.begin(); it != x18_pakLoadedList.end(); ++it) for (const auto& name : (*it)->GetNameList()) if (!lambda(name.first, name.second)) return; } } // namespace metaforce ================================================ FILE: Runtime/CResLoader.hpp ================================================ #pragma once #include #include #include #include #include #include "Runtime/CPakFile.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class IDvdRequest; struct SObjectTag; class CResLoader { std::string m_loaderPath; // std::list> x0_aramList; std::list> x18_pakLoadedList; std::list> x30_pakLoadingList; std::list> m_overridePakList; // URDE Addition, Trilogy has a similar mechanism, need to verify behavior against it u32 x44_pakLoadingCount = 0; std::list>::iterator x48_curPak; mutable CAssetId x4c_cachedResId; mutable const CPakFile::SResInfo* x50_cachedResInfo = nullptr; bool x54_forwardSeek = false; bool _GetTagListForFile(std::vector& out, const std::string& path, const std::unique_ptr& file) const; public: CResLoader(); const std::vector* GetTagListForFile(std::string_view name) const; void AddPakFileAsync(std::string_view name, bool buildDepList, bool worldPak, bool override = false); void AddPakFile(std::string_view name, bool samusPak, bool worldPak, bool override = false); void WaitForPakFileLoadingComplete(); std::unique_ptr LoadNewResourcePartSync(const SObjectTag& tag, u32 length, u32 offset, void* extBuf); void LoadMemResourceSync(const SObjectTag& tag, std::unique_ptr& bufOut, int* sizeOut); std::unique_ptr LoadResourceFromMemorySync(const SObjectTag& tag, const void* buf); std::unique_ptr LoadNewResourceSync(const SObjectTag& tag, void* extBuf = nullptr); std::shared_ptr LoadResourcePartAsync(const SObjectTag& tag, u32 off, u32 size, void* buf); std::shared_ptr LoadResourceAsync(const SObjectTag& tag, void* buf); std::unique_ptr LoadResourceSync(const metaforce::SObjectTag& tag); std::unique_ptr LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size); void GetTagListForFile(const char* pakName, std::vector& out) const; bool GetResourceCompression(const SObjectTag& tag) const; u32 ResourceSize(const SObjectTag& tag) const; bool ResourceExists(const SObjectTag& tag) const; FourCC GetResourceTypeById(CAssetId id) const; const SObjectTag* GetResourceIdByName(std::string_view name) const; bool AreAllPaksLoaded() const; void AsyncIdlePakLoading(); bool FindResource(CAssetId id) const; CPakFile* FindResourceForLoad(CAssetId id); CPakFile* FindResourceForLoad(const SObjectTag& tag); bool CacheFromPakForLoad(CPakFile& file, CAssetId id); bool CacheFromPak(const CPakFile& file, CAssetId id) const; void MoveToCorrectLoadedList(std::unique_ptr&& file); std::vector> GetResourceIdToNameList() const; void EnumerateResources(const std::function& lambda) const; void EnumerateNamedResources(const std::function& lambda) const; const std::list>& GetPaks() const { return x18_pakLoadedList; } }; } // namespace metaforce ================================================ FILE: Runtime/CResourceNameDatabase.cpp ================================================ #include "Runtime/CResourceNameDatabase.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/ConsoleVariables/FileStoreManager.hpp" #include "Runtime/Streams/CMemoryInStream.hpp" namespace metaforce { constexpr std::string_view kDatabaseName = "mp_resource_names_confirmed.bin"; CResourceNameDatabase* CResourceNameDatabase::m_instance = nullptr; CResourceNameDatabase::CResourceNameDatabase(FileStoreManager& store) { m_instance = this; const std::string filename = std::string(store.getStoreRoot()) + "/" + std::string(kDatabaseName); if (!CBasics::IsFile(filename.c_str())) { return; } CBasics::Sstat st; if (CBasics::Stat(filename.c_str(), &st) != 0) { return; } std::unique_ptr const inBuf(new u8[st.st_size]); auto* file = fopen(filename.c_str(), "rb"); (void)fread(inBuf.get(), 1, st.st_size, file); (void)fclose(file); CMemoryInStream mem(inBuf.get(), st.st_size, CMemoryInStream::EOwnerShip::NotOwned); u32 count = mem.Get(); while ((count--) != 0u) { const CAssetId assetId = mem.Get(); const auto name = mem.Get(); m_assetNames[assetId] = name; } } } // namespace metaforce ================================================ FILE: Runtime/CResourceNameDatabase.hpp ================================================ #pragma once #include "RetroTypes.hpp" #include namespace metaforce { class FileStoreManager; class CResourceNameDatabase { public: explicit CResourceNameDatabase(FileStoreManager& store); bool hasAssetName(const CAssetId asset) const { return m_assetNames.contains(asset); } const std::string* assetName(const CAssetId asset) const { if (!hasAssetName(asset)) { return nullptr; } return &m_assetNames.at(asset); } static CResourceNameDatabase* instance() { return m_instance; } private: std::unordered_map m_assetNames; static CResourceNameDatabase* m_instance; }; } // namespace metaforce ================================================ FILE: Runtime/CScannableObjectInfo.cpp ================================================ #include "Runtime/CScannableObjectInfo.hpp" #include "Runtime/GameGlobalObjects.hpp" namespace metaforce { CScannableObjectInfo::CScannableObjectInfo(CInputStream& in, CAssetId resId) : x0_scannableObjectId(resId) { const u32 version = in.ReadLong(); Load(in, version); for (auto& bucket : x14_buckets) { bucket.x4_appearanceRange *= x8_totalDownloadTime; } const float appearanceOffset = g_tweakGui->GetScanAppearanceDuration(); for (size_t i = 0; i < x14_buckets.size(); ++i) { if (x14_buckets[i].x8_imagePos == UINT32_MAX) { continue; } x8_totalDownloadTime += appearanceOffset; for (size_t j = i; j < x14_buckets.size(); j++) { x14_buckets[j].x4_appearanceRange += appearanceOffset; } } for (size_t i = 0; i < x14_buckets.size() - 1; ++i) { for (size_t j = i + 1; j < x14_buckets.size(); ++j) { if (x14_buckets[i].x8_imagePos == x14_buckets[j].x8_imagePos && x14_buckets[i].x8_imagePos != UINT32_MAX) { x14_buckets[j].x8_imagePos = UINT32_MAX; } } } } void CScannableObjectInfo::Load(CInputStream& in, u32 version) { in.ReadLong(); in.ReadLong(); x4_stringId = in.Get(); if (version < 4) { x8_totalDownloadTime = in.ReadFloat(); } else { const u32 scanSpeed = in.ReadLong(); x8_totalDownloadTime = g_tweakGui->GetScanSpeed(scanSpeed); } xc_category = in.ReadLong(); if (version > 4) { x10_important = in.ReadBool(); } for (size_t i = 0; i < x14_buckets.capacity(); i++) { x14_buckets.emplace_back(in, version); } } CScannableObjectInfo::SBucket::SBucket(CInputStream& in, u32 version) { x0_texture = in.Get(); x4_appearanceRange = in.ReadFloat(); x8_imagePos = in.ReadLong(); if (version > 1) { xc_size.x = in.ReadLong(); xc_size.y = in.ReadLong(); x14_interval = in.ReadFloat(); if (version >= 3) x18_fadeDuration = in.ReadFloat(); } } CFactoryFnReturn FScannableObjectInfoFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer&, CObjectReference*) { return TToken::GetIObjObjectFor(std::make_unique(in, tag.id)); } } // namespace metaforce ================================================ FILE: Runtime/CScannableObjectInfo.hpp ================================================ #pragma once #include "Runtime/CToken.hpp" #include "Runtime/IFactory.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include namespace metaforce { class CScannableObjectInfo { public: enum class EPanelType {}; static constexpr size_t NumBuckets = 4; struct SBucket { CAssetId x0_texture; float x4_appearanceRange = 0.f; u32 x8_imagePos = 0; zeus::CVector2i xc_size; float x14_interval = 0.f; float x18_fadeDuration = 0.f; SBucket(CInputStream&, u32 version); }; private: void Load(CInputStream&, u32); CAssetId x0_scannableObjectId; CAssetId x4_stringId; float x8_totalDownloadTime = 0.f; u32 xc_category = 0; bool x10_important = false; rstl::reserved_vector x14_buckets; public: CScannableObjectInfo(CInputStream&, CAssetId); CAssetId GetScannableObjectId() const { return x0_scannableObjectId; } CAssetId GetStringTableId() const { return x4_stringId; } float GetTotalDownloadTime() const { return x8_totalDownloadTime; } const SBucket& GetBucket(size_t idx) const { return x14_buckets[idx]; } u32 GetCategory() const { return xc_category; } bool IsImportant() const { return x10_important; } }; CFactoryFnReturn FScannableObjectInfoFactory(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/CScriptMailbox.cpp ================================================ #include "Runtime/CScriptMailbox.hpp" #include "Runtime/CWorldSaveGameInfo.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/World/CWorld.hpp" #include namespace metaforce { CScriptMailbox::CScriptMailbox(CInputStream& in, const CWorldSaveGameInfo& saveWorld) { const u32 relayCount = saveWorld.GetRelayCount(); if (saveWorld.GetRelayCount()) { std::vector relayStates(saveWorld.GetRelayCount()); for (u32 i = 0; i < relayCount; ++i) { relayStates[i] = in.ReadBits(1); } for (u32 i = 0; i < relayCount; ++i) { if (!relayStates[i]) { continue; } x0_relays.push_back(saveWorld.GetRelayEditorId(i)); } } } bool CScriptMailbox::HasMsg(TEditorId id) const { return std::find(x0_relays.cbegin(), x0_relays.cend(), id) != x0_relays.cend(); } void CScriptMailbox::AddMsg(TEditorId id) { if (HasMsg(id)) { return; } x0_relays.push_back(id); } void CScriptMailbox::RemoveMsg(TEditorId id) { if (!HasMsg(id)) { return; } std::erase(x0_relays, id); } void CScriptMailbox::SendMsgs(TAreaId areaId, CStateManager& stateMgr) { const CWorld* world = stateMgr.GetWorld(); u32 relayCount = world->GetRelayCount(); bool hasActiveRelays = false; for (u32 i = 0; i < relayCount; ++i) { const CWorld::CRelay& relay = world->GetRelay(i); if (relay.GetTargetId().AreaNum() != areaId) continue; if (!HasMsg(relay.GetRelayId())) continue; stateMgr.SendScriptMsg(kInvalidUniqueId, relay.GetTargetId(), EScriptObjectMessage(relay.GetMessage()), EScriptObjectState::Any); if (relay.GetActive()) hasActiveRelays = true; } if (!hasActiveRelays) return; for (u32 i = 0; i < relayCount; ++i) { const CWorld::CRelay& relay = world->GetRelay(i); if (relay.GetTargetId().AreaNum() != areaId) continue; if (!HasMsg(relay.GetRelayId()) || !relay.GetActive()) continue; RemoveMsg(relay.GetRelayId()); } } void CScriptMailbox::PutTo(COutputStream& out, const CWorldSaveGameInfo& saveWorld) { const u32 relayCount = saveWorld.GetRelayCount(); std::vector relays(relayCount); for (const TEditorId& id : x0_relays) { const s32 idx = saveWorld.GetRelayIndex(id); if (idx >= 0) { relays[idx] = true; } } for (u32 i = 0; i < relayCount; ++i) { out.WriteBits(u32(relays[i]), 1); } } } // namespace metaforce ================================================ FILE: Runtime/CScriptMailbox.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/World/ScriptObjectSupport.hpp" namespace metaforce { class CWorldSaveGameInfo; class CStateManager; #if 0 struct CMailMessage { TEditorId x0_id; EScriptObjectMessage x4_msg; bool x8_; CMailMessage(TEditorId id, EScriptObjectMessage msg, bool flag) : x0_id(id), x4_msg(msg), x8_(flag) {} CMailMessage(const CMailMessage& other) : x0_id(other.x0_id), x4_msg(other.x4_msg), x8_(other.x8_) {} bool operator==(const CMailMessage& other) const { return (x0_id == other.x0_id && x4_msg == other.x4_msg); } }; #endif class CScriptMailbox { std::vector x0_relays; public: CScriptMailbox() = default; CScriptMailbox(CInputStream& in, const CWorldSaveGameInfo& saveWorld); bool HasMsg(TEditorId id) const; void AddMsg(TEditorId id); void RemoveMsg(TEditorId id); void SendMsgs(TAreaId areaId, CStateManager& stateMgr); void PutTo(COutputStream& out, const CWorldSaveGameInfo& saveWorld); }; } // namespace metaforce ================================================ FILE: Runtime/CSimplePool.cpp ================================================ #include "Runtime/CSimplePool.hpp" #include "Runtime/CToken.hpp" #include "Runtime/IVParamObj.hpp" #include namespace metaforce { CSimplePool::CSimplePool(IFactory& factory) : x18_factory(factory), x1c_paramXfer(new TObjOwnerParam(this)) {} CSimplePool::~CSimplePool() { assert(x8_resources.empty() && "Dangling CSimplePool resources detected"); } CToken CSimplePool::GetObj(const SObjectTag& tag, const CVParamTransfer& paramXfer) { if (!tag) { return {}; } const auto iter = x8_resources.find(tag); if (iter != x8_resources.end()) { return CToken(iter->second); } auto* const ret = new CObjectReference(*this, nullptr, tag, paramXfer); x8_resources.emplace(tag, ret); return CToken(ret); } CToken CSimplePool::GetObj(const SObjectTag& tag) { return GetObj(tag, x1c_paramXfer); } CToken CSimplePool::GetObj(std::string_view resourceName) { return GetObj(resourceName, x1c_paramXfer); } CToken CSimplePool::GetObj(std::string_view resourceName, const CVParamTransfer& paramXfer) { const SObjectTag* tag = x18_factory.GetResourceIdByName(resourceName); if (!tag) return {}; return GetObj(*tag, paramXfer); } bool CSimplePool::HasObject(const SObjectTag& tag) const { auto iter = x8_resources.find(tag); if (iter != x8_resources.cend()) return true; return x18_factory.CanBuild(tag); } bool CSimplePool::ObjectIsLive(const SObjectTag& tag) const { auto iter = x8_resources.find(tag); if (iter == x8_resources.cend()) return false; return iter->second->IsLoaded(); } void CSimplePool::Flush() {} void CSimplePool::ObjectUnreferenced(const SObjectTag& tag) { auto iter = x8_resources.find(tag); if (iter != x8_resources.end()) x8_resources.erase(iter); } std::vector CSimplePool::GetReferencedTags() const { std::vector ret; ret.reserve(x8_resources.size()); for (const auto& obj : x8_resources) ret.push_back(obj.first); return ret; } } // namespace metaforce ================================================ FILE: Runtime/CSimplePool.hpp ================================================ #pragma once #include #include #include "Runtime/IObjectStore.hpp" #include "Runtime/IVParamObj.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CObjectReference; class IFactory; class CSimplePool : public IObjectStore { protected: u8 x4_; u8 x5_; std::unordered_map x8_resources; IFactory& x18_factory; CVParamTransfer x1c_paramXfer; public: CSimplePool(IFactory& factory); ~CSimplePool() override; CToken GetObj(const SObjectTag&, const CVParamTransfer&) override; CToken GetObj(const SObjectTag&) override; CToken GetObj(std::string_view) override; CToken GetObj(std::string_view, const CVParamTransfer&) override; bool HasObject(const SObjectTag&) const override; bool ObjectIsLive(const SObjectTag&) const override; IFactory& GetFactory() const override { return x18_factory; } void Flush() override; void ObjectUnreferenced(const SObjectTag&) override; std::vector GetReferencedTags() const; size_t GetLiveObjects() const { return x8_resources.size(); } }; } // namespace metaforce ================================================ FILE: Runtime/CSortedLists.cpp ================================================ #include "Runtime/CSortedLists.hpp" #include "Runtime/World/CActor.hpp" #include #include namespace metaforce { namespace { template auto AccessElement(T& arr, S idx) -> typename T::reference { assert(std::size(arr) > static_cast(idx) && idx >= 0); return arr[idx]; } template auto AccessElement(const T& arr, S idx) -> typename T::const_reference { assert(std::size(arr) > static_cast(idx) && idx >= 0); return arr[idx]; } } // Anonymous namespace CSortedListManager::CSortedListManager() { Reset(); } void CSortedListManager::Reset() { x0_nodes.fill(SNode{}); for (auto& list : xb000_sortedLists) { list.Reset(); } } void CSortedListManager::AddToLinkedList(s16 nodeId, s16& headId, s16& tailId) { if (headId == -1) { AccessElement(x0_nodes, nodeId).x28_next = headId; headId = nodeId; tailId = nodeId; } else { if (AccessElement(x0_nodes, nodeId).x28_next != -1) { return; } if (tailId == nodeId) { return; } AccessElement(x0_nodes, nodeId).x28_next = headId; headId = nodeId; } } void CSortedListManager::RemoveFromList(ESortedList list, s16 idx) { const auto listIndex = static_cast(list); SSortedList& sl = xb000_sortedLists[listIndex]; while (idx < sl.x800_size - 1) { AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + 1)).x1c_selfIdxs[listIndex] = idx; AccessElement(sl.x0_ids, idx) = AccessElement(sl.x0_ids, idx + 1); ++idx; } --sl.x800_size; } void CSortedListManager::MoveInList(ESortedList list, s16 idx) { const auto listIndex = static_cast(list); SSortedList& sl = xb000_sortedLists[listIndex]; while (true) { if (idx > 0 && AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx - 1)).x4_box[listIndex] > AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x4_box[listIndex]) { AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx - 1)).x1c_selfIdxs[listIndex] = idx; AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x1c_selfIdxs[listIndex] = idx - 1; std::swap(AccessElement(sl.x0_ids, idx), AccessElement(sl.x0_ids, idx - 1)); --idx; } else { if (idx >= sl.x800_size - 1) { return; } if (AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + 1)).x4_box[listIndex] >= AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x4_box[listIndex]) { return; } AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + 1)).x1c_selfIdxs[listIndex] = idx; AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx)).x1c_selfIdxs[listIndex] = idx + 1; std::swap(AccessElement(sl.x0_ids, idx), AccessElement(sl.x0_ids, idx + 1)); ++idx; } } } void CSortedListManager::InsertInList(ESortedList list, SNode& node) { const auto listIndex = static_cast(list); SSortedList& sl = xb000_sortedLists[listIndex]; int insIdx = 0; for (int i = sl.x800_size; i > 0;) { /* Binary search cycle to find insert index */ if (AccessElement(x0_nodes, AccessElement(sl.x0_ids, insIdx + i / 2)).x4_box[listIndex] < node.x4_box[listIndex]) { /* Upper */ insIdx = insIdx + i / 2 + 1; i = i - i / 2 - 1; } else { /* Lower */ i /= 2; } } /* Shift ids for insert */ for (int i = sl.x800_size; i > insIdx; --i) { AccessElement(x0_nodes, AccessElement(sl.x0_ids, i - 1)).x1c_selfIdxs[listIndex] = i; AccessElement(sl.x0_ids, i) = AccessElement(sl.x0_ids, i - 1); } /* Do insert */ AccessElement(sl.x0_ids, insIdx) = node.x0_actor->GetUniqueId().Value(); node.x1c_selfIdxs[listIndex] = s16(insIdx); ++sl.x800_size; } s16 CSortedListManager::FindInListUpper(ESortedList list, float value) const { const auto listIndex = static_cast(list); const SSortedList& sl = xb000_sortedLists[listIndex]; int idx = 0; for (int i = sl.x800_size; i > 0;) { // Binary search cycle to find index if (!(value < AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + i / 2)).x4_box[listIndex])) { // Upper idx = idx + i / 2 + 1; i = i - i / 2 - 1; } else { // Lower i /= 2; } } return idx; } s16 CSortedListManager::FindInListLower(ESortedList list, float value) const { const auto listIndex = static_cast(list); const SSortedList& sl = xb000_sortedLists[listIndex]; int idx = 0; for (int i = sl.x800_size; i > 0;) { // Binary search cycle to find index if (AccessElement(x0_nodes, AccessElement(sl.x0_ids, idx + i / 2)).x4_box[listIndex] < value) { // Upper idx = idx + i / 2 + 1; i = i - i / 2 - 1; } else { // Lower i /= 2; } } return idx; } s16 CSortedListManager::ConstructIntersectionArray(const zeus::CAABox& aabb) { const int minXa = FindInListLower(ESortedList::MinX, aabb.min.x()); const int maxXa = FindInListUpper(ESortedList::MinX, aabb.max.x()); const int minXb = FindInListLower(ESortedList::MaxX, aabb.min.x()); const int maxXb = FindInListUpper(ESortedList::MaxX, aabb.max.x()); const int xEnd = std::min(int(xb000_sortedLists[3].x800_size) - maxXb, minXa) + (maxXb + (maxXa - minXa) - minXb) / 2; const int minYa = FindInListLower(ESortedList::MinY, aabb.min.y()); const int maxYa = FindInListUpper(ESortedList::MinY, aabb.max.y()); const int minYb = FindInListLower(ESortedList::MaxY, aabb.min.y()); const int maxYb = FindInListUpper(ESortedList::MaxY, aabb.max.y()); const int yEnd = std::min(int(xb000_sortedLists[4].x800_size) - maxYb, minYa) + (maxYb + (maxYa - minYa) - minYb) / 2; const int minZa = FindInListLower(ESortedList::MinZ, aabb.min.z()); const int maxZa = FindInListUpper(ESortedList::MinZ, aabb.max.z()); const int minZb = FindInListLower(ESortedList::MaxZ, aabb.min.z()); const int maxZb = FindInListUpper(ESortedList::MaxZ, aabb.max.z()); const int zEnd = std::min(int(xb000_sortedLists[5].x800_size) - maxZb, minZa) + (maxZb + (maxZa - minZa) - minZb) / 2; if (xEnd < yEnd && xEnd < zEnd) { return CalculateIntersections(ESortedList::MinX, ESortedList::MaxX, minXa, maxXa, minXb, maxXb, ESortedList::MinY, ESortedList::MaxY, ESortedList::MinZ, ESortedList::MaxZ, aabb); } else if (yEnd < zEnd) { return CalculateIntersections(ESortedList::MinY, ESortedList::MaxY, minYa, maxYa, minYb, maxYb, ESortedList::MinX, ESortedList::MaxX, ESortedList::MinZ, ESortedList::MaxZ, aabb); } else { return CalculateIntersections(ESortedList::MinZ, ESortedList::MaxZ, minZa, maxZa, minZb, maxZb, ESortedList::MinX, ESortedList::MaxX, ESortedList::MinY, ESortedList::MaxY, aabb); } } s16 CSortedListManager::CalculateIntersections(ESortedList la, ESortedList lb, s16 a, s16 b, s16 c, s16 d, ESortedList slA, ESortedList slB, ESortedList slC, ESortedList slD, const zeus::CAABox& aabb) { const auto listAIndex = static_cast(la); const auto listBIndex = static_cast(lb); s16 headId = -1; s16 tailId = -1; for (int i = a; i < b; ++i) { AddToLinkedList(AccessElement(xb000_sortedLists[listAIndex].x0_ids, i), headId, tailId); } for (int i = c; i < d; ++i) { AddToLinkedList(AccessElement(xb000_sortedLists[listBIndex].x0_ids, i), headId, tailId); } if (a < xb000_sortedLists[listBIndex].x800_size - d) { for (int i = 0; i < a; ++i) { const s16 id = AccessElement(xb000_sortedLists[listAIndex].x0_ids, i); if (AccessElement(x0_nodes, id).x4_box[listBIndex] > aabb[listBIndex]) { AddToLinkedList(id, headId, tailId); } } } else { for (int i = d; i < xb000_sortedLists[listBIndex].x800_size; ++i) { const s16 id = AccessElement(xb000_sortedLists[listBIndex].x0_ids, i); if (AccessElement(x0_nodes, id).x4_box[listAIndex] < aabb[listAIndex]) { AddToLinkedList(id, headId, tailId); } } } for (s16* id = &headId; *id != -1;) { SNode& node = AccessElement(x0_nodes, *id); if (node.x4_box[size_t(slA)] > aabb[size_t(slB)] || node.x4_box[size_t(slB)] < aabb[size_t(slA)] || node.x4_box[size_t(slC)] > aabb[size_t(slD)] || node.x4_box[size_t(slD)] < aabb[size_t(slC)]) { /* Not intersecting; remove from chain */ *id = node.x28_next; node.x28_next = -1; continue; } id = &node.x28_next; } return headId; } void CSortedListManager::BuildNearList(EntityList& out, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter, const CActor* actor) { if (mag == 0.f) { mag = 8000.f; } const zeus::CVector3f ray = dir * mag; const zeus::CVector3f sum = ray + pos; const zeus::CVector3f maxs(std::max(pos.x(), sum.x()), std::max(pos.y(), sum.y()), std::max(pos.z(), sum.z())); const zeus::CVector3f mins(std::min(sum.x(), pos.x()), std::min(sum.y(), pos.y()), std::min(sum.z(), pos.z())); BuildNearList(out, zeus::CAABox(mins, maxs), filter, actor); } void CSortedListManager::BuildNearList(EntityList& out, const CActor& actor, const zeus::CAABox& aabb) { const CMaterialFilter& filter = actor.GetMaterialFilter(); s16 id = ConstructIntersectionArray(aabb); while (id != -1) { SNode& node = AccessElement(x0_nodes, id); if (&actor != node.x0_actor && filter.Passes(node.x0_actor->GetMaterialList()) && node.x0_actor->GetMaterialFilter().Passes(actor.GetMaterialList())) { out.push_back(node.x0_actor->GetUniqueId()); } id = node.x28_next; node.x28_next = -1; } } void CSortedListManager::BuildNearList(EntityList& out, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CActor* actor) { s16 id = ConstructIntersectionArray(aabb); while (id != -1) { SNode& node = AccessElement(x0_nodes, id); if (actor != node.x0_actor && filter.Passes(node.x0_actor->GetMaterialList())) { out.push_back(node.x0_actor->GetUniqueId()); } id = node.x28_next; node.x28_next = -1; } } void CSortedListManager::Remove(const CActor* actor) { SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value()); if (!node.x2a_populated) { return; } RemoveFromList(ESortedList::MinX, node.x1c_selfIdxs[0]); RemoveFromList(ESortedList::MaxX, node.x1c_selfIdxs[3]); RemoveFromList(ESortedList::MinY, node.x1c_selfIdxs[1]); RemoveFromList(ESortedList::MaxY, node.x1c_selfIdxs[4]); RemoveFromList(ESortedList::MinZ, node.x1c_selfIdxs[2]); RemoveFromList(ESortedList::MaxZ, node.x1c_selfIdxs[5]); node.x2a_populated = false; } void CSortedListManager::Move(const CActor* actor, const zeus::CAABox& aabb) { SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value()); node.x4_box = aabb; MoveInList(ESortedList::MinX, node.x1c_selfIdxs[0]); MoveInList(ESortedList::MaxX, node.x1c_selfIdxs[3]); MoveInList(ESortedList::MinY, node.x1c_selfIdxs[1]); MoveInList(ESortedList::MaxY, node.x1c_selfIdxs[4]); MoveInList(ESortedList::MinZ, node.x1c_selfIdxs[2]); MoveInList(ESortedList::MaxZ, node.x1c_selfIdxs[5]); } void CSortedListManager::Insert(const CActor* actor, const zeus::CAABox& aabb) { SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value()); if (node.x2a_populated) { Move(actor, aabb); return; } SNode newNode(actor, aabb); InsertInList(ESortedList::MinX, newNode); InsertInList(ESortedList::MaxX, newNode); InsertInList(ESortedList::MinY, newNode); InsertInList(ESortedList::MaxY, newNode); InsertInList(ESortedList::MinZ, newNode); InsertInList(ESortedList::MaxZ, newNode); node = newNode; } bool CSortedListManager::ActorInLists(const CActor* actor) const { if (!actor) { return false; } const SNode& node = AccessElement(x0_nodes, actor->GetUniqueId().Value()); return node.x2a_populated; } } // namespace metaforce ================================================ FILE: Runtime/CSortedLists.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include namespace metaforce { enum class ESortedList { MinX, MinY, MinZ, MaxX, MaxY, MaxZ }; struct SSortedList { std::array x0_ids; u32 x800_size = 0; void Reset() { x0_ids.fill(-1); } SSortedList() { Reset(); } }; class CActor; class CSortedListManager { struct SNode { const CActor* x0_actor = nullptr; zeus::CAABox x4_box = zeus::skNullBox; std::array x1c_selfIdxs{-1, -1, -1, -1, -1, -1}; s16 x28_next = -1; bool x2a_populated = false; SNode() = default; SNode(const CActor* act, const zeus::CAABox& aabb) : x0_actor(act), x4_box(aabb), x2a_populated(true) {} }; std::array x0_nodes; std::array xb000_sortedLists; void Reset(); void AddToLinkedList(s16 nodeId, s16& headId, s16& tailId); void RemoveFromList(ESortedList list, s16 idx); void MoveInList(ESortedList list, s16 idx); void InsertInList(ESortedList list, SNode& node); s16 FindInListUpper(ESortedList list, float value) const; s16 FindInListLower(ESortedList list, float value) const; s16 ConstructIntersectionArray(const zeus::CAABox& aabb); s16 CalculateIntersections(ESortedList la, ESortedList lb, s16 a, s16 b, s16 c, s16 d, ESortedList slA, ESortedList slB, ESortedList slC, ESortedList slD, const zeus::CAABox& aabb); public: CSortedListManager(); void BuildNearList(EntityList& out, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter, const CActor* actor); void BuildNearList(EntityList& out, const CActor& actor, const zeus::CAABox& aabb); void BuildNearList(EntityList& out, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CActor* actor); void Remove(const CActor* actor); void Move(const CActor* actor, const zeus::CAABox& aabb); void Insert(const CActor* actor, const zeus::CAABox& aabb); bool ActorInLists(const CActor* actor) const; }; } // namespace metaforce ================================================ FILE: Runtime/CStateManager.cpp ================================================ #include "Runtime/CStateManager.hpp" #include #include "Runtime/AutoMapper/CMapWorldInfo.hpp" #include "Runtime/Camera/CBallCamera.hpp" #include "Runtime/Camera/CCameraShakeData.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/CMemoryCardSys.hpp" #include "Runtime/Collision/CCollisionActor.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" #include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include "Runtime/Collision/CollisionUtil.hpp" #include "Runtime/CPlayerState.hpp" #include "Runtime/CSortedLists.hpp" #include "Runtime/CTimeProvider.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Graphics/CLight.hpp" #include "Runtime/Input/ControlMapper.hpp" #include "Runtime/Input/CRumbleManager.hpp" #include "Runtime/MP1/CSamusHud.hpp" #include "Runtime/MP1/MP1.hpp" #include "Runtime/Particle/CDecalManager.hpp" #include "Runtime/Particle/CParticleElectric.hpp" #include "Runtime/Weapon/CProjectileWeapon.hpp" #include "Runtime/Weapon/CWeapon.hpp" #include "Runtime/Weapon/CWeaponMgr.hpp" #include "Runtime/World/CDestroyableRock.hpp" #include "Runtime/World/CGameLight.hpp" #include "Runtime/World/CPathFindSearch.hpp" #include "Runtime/World/CPatterned.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CProjectedShadow.hpp" #include "Runtime/World/CScriptDebris.hpp" #include "Runtime/World/CScriptDock.hpp" #include "Runtime/World/CScriptDoor.hpp" #include "Runtime/World/CScriptEffect.hpp" #include "Runtime/World/CScriptPlatform.hpp" #include "Runtime/World/CScriptPlayerActor.hpp" #include "Runtime/World/CScriptRoomAcoustics.hpp" #include "Runtime/World/CScriptSpawnPoint.hpp" #include "Runtime/World/CScriptSpecialFunction.hpp" #include "Runtime/World/CScriptWater.hpp" #include "Runtime/World/CSnakeWeedSwarm.hpp" #include "Runtime/World/CWallCrawlerSwarm.hpp" #include "Runtime/World/CWorld.hpp" #include "Runtime/ConsoleVariables/CVarManager.hpp" #include "Runtime/Logging.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path #include namespace metaforce { namespace { CVar* debugToolDrawAiPath = nullptr; CVar* debugToolDrawLighting = nullptr; CVar* debugToolDrawCollisionActors = nullptr; CVar* debugToolDrawMazePath = nullptr; CVar* debugToolDrawPlatformCollision = nullptr; CVar* sm_logScripting = nullptr; } // namespace CStateManager::CStateManager(const std::weak_ptr& mailbox, const std::weak_ptr& mwInfo, const std::weak_ptr& playerState, const std::weak_ptr& wtMgr, const std::weak_ptr& layerState) : x8b8_playerState(playerState) , x8bc_mailbox(mailbox) , x8c0_mapWorldInfo(mwInfo) , x8c4_worldTransManager(wtMgr) , x8c8_worldLayerState(layerState) { x86c_stateManagerContainer = std::make_unique(); x870_cameraManager = &x86c_stateManagerContainer->x0_cameraManager; x874_sortedListManager = &x86c_stateManagerContainer->x3c0_sortedListManager; x878_weaponManager = &x86c_stateManagerContainer->xe3d8_weaponManager; x87c_fluidPlaneManager = &x86c_stateManagerContainer->xe3ec_fluidPlaneManager; x880_envFxManager = &x86c_stateManagerContainer->xe510_envFxManager; x884_actorModelParticles = &x86c_stateManagerContainer->xf168_actorModelParticles; x88c_rumbleManager = &x86c_stateManagerContainer->xf250_rumbleManager; g_Renderer->SetDrawableCallback(&CStateManager::RendererDrawCallback, this); x90c_loaderFuncs.resize(int(EScriptObjectType::ScriptObjectTypeMAX)); x90c_loaderFuncs[size_t(EScriptObjectType::Actor)] = ScriptLoader::LoadActor; x90c_loaderFuncs[size_t(EScriptObjectType::Waypoint)] = ScriptLoader::LoadWaypoint; x90c_loaderFuncs[size_t(EScriptObjectType::Door)] = ScriptLoader::LoadDoor; x90c_loaderFuncs[size_t(EScriptObjectType::Trigger)] = ScriptLoader::LoadTrigger; x90c_loaderFuncs[size_t(EScriptObjectType::Timer)] = ScriptLoader::LoadTimer; x90c_loaderFuncs[size_t(EScriptObjectType::Counter)] = ScriptLoader::LoadCounter; x90c_loaderFuncs[size_t(EScriptObjectType::Effect)] = ScriptLoader::LoadEffect; x90c_loaderFuncs[size_t(EScriptObjectType::Platform)] = ScriptLoader::LoadPlatform; x90c_loaderFuncs[size_t(EScriptObjectType::Sound)] = ScriptLoader::LoadSound; x90c_loaderFuncs[size_t(EScriptObjectType::Generator)] = ScriptLoader::LoadGenerator; x90c_loaderFuncs[size_t(EScriptObjectType::Dock)] = ScriptLoader::LoadDock; x90c_loaderFuncs[size_t(EScriptObjectType::Camera)] = ScriptLoader::LoadCamera; x90c_loaderFuncs[size_t(EScriptObjectType::CameraWaypoint)] = ScriptLoader::LoadCameraWaypoint; x90c_loaderFuncs[size_t(EScriptObjectType::NewIntroBoss)] = ScriptLoader::LoadNewIntroBoss; x90c_loaderFuncs[size_t(EScriptObjectType::SpawnPoint)] = ScriptLoader::LoadSpawnPoint; x90c_loaderFuncs[size_t(EScriptObjectType::CameraHint)] = ScriptLoader::LoadCameraHint; x90c_loaderFuncs[size_t(EScriptObjectType::Pickup)] = ScriptLoader::LoadPickup; x90c_loaderFuncs[size_t(EScriptObjectType::MemoryRelay)] = ScriptLoader::LoadMemoryRelay; x90c_loaderFuncs[size_t(EScriptObjectType::RandomRelay)] = ScriptLoader::LoadRandomRelay; x90c_loaderFuncs[size_t(EScriptObjectType::Relay)] = ScriptLoader::LoadRelay; x90c_loaderFuncs[size_t(EScriptObjectType::Beetle)] = ScriptLoader::LoadBeetle; x90c_loaderFuncs[size_t(EScriptObjectType::HUDMemo)] = ScriptLoader::LoadHUDMemo; x90c_loaderFuncs[size_t(EScriptObjectType::CameraFilterKeyframe)] = ScriptLoader::LoadCameraFilterKeyframe; x90c_loaderFuncs[size_t(EScriptObjectType::CameraBlurKeyframe)] = ScriptLoader::LoadCameraBlurKeyframe; x90c_loaderFuncs[size_t(EScriptObjectType::DamageableTrigger)] = ScriptLoader::LoadDamageableTrigger; x90c_loaderFuncs[size_t(EScriptObjectType::Debris)] = ScriptLoader::LoadDebris; x90c_loaderFuncs[size_t(EScriptObjectType::CameraShaker)] = ScriptLoader::LoadCameraShaker; x90c_loaderFuncs[size_t(EScriptObjectType::ActorKeyframe)] = ScriptLoader::LoadActorKeyframe; x90c_loaderFuncs[size_t(EScriptObjectType::Water)] = ScriptLoader::LoadWater; x90c_loaderFuncs[size_t(EScriptObjectType::Warwasp)] = ScriptLoader::LoadWarWasp; x90c_loaderFuncs[size_t(EScriptObjectType::SpacePirate)] = ScriptLoader::LoadSpacePirate; x90c_loaderFuncs[size_t(EScriptObjectType::FlyingPirate)] = ScriptLoader::LoadFlyingPirate; x90c_loaderFuncs[size_t(EScriptObjectType::ElitePirate)] = ScriptLoader::LoadElitePirate; x90c_loaderFuncs[size_t(EScriptObjectType::MetroidBeta)] = ScriptLoader::LoadMetroidBeta; x90c_loaderFuncs[size_t(EScriptObjectType::ChozoGhost)] = ScriptLoader::LoadChozoGhost; x90c_loaderFuncs[size_t(EScriptObjectType::CoverPoint)] = ScriptLoader::LoadCoverPoint; x90c_loaderFuncs[size_t(EScriptObjectType::SpiderBallWaypoint)] = ScriptLoader::LoadSpiderBallWaypoint; x90c_loaderFuncs[size_t(EScriptObjectType::BloodFlower)] = ScriptLoader::LoadBloodFlower; x90c_loaderFuncs[size_t(EScriptObjectType::FlickerBat)] = ScriptLoader::LoadFlickerBat; x90c_loaderFuncs[size_t(EScriptObjectType::PathCamera)] = ScriptLoader::LoadPathCamera; x90c_loaderFuncs[size_t(EScriptObjectType::GrapplePoint)] = ScriptLoader::LoadGrapplePoint; x90c_loaderFuncs[size_t(EScriptObjectType::PuddleSpore)] = ScriptLoader::LoadPuddleSpore; x90c_loaderFuncs[size_t(EScriptObjectType::DebugCameraWaypoint)] = ScriptLoader::LoadDebugCameraWaypoint; x90c_loaderFuncs[size_t(EScriptObjectType::SpiderBallAttractionSurface)] = ScriptLoader::LoadSpiderBallAttractionSurface; x90c_loaderFuncs[size_t(EScriptObjectType::PuddleToadGamma)] = ScriptLoader::LoadPuddleToadGamma; x90c_loaderFuncs[size_t(EScriptObjectType::DistanceFog)] = ScriptLoader::LoadDistanceFog; x90c_loaderFuncs[size_t(EScriptObjectType::FireFlea)] = ScriptLoader::LoadFireFlea; x90c_loaderFuncs[size_t(EScriptObjectType::Metaree)] = ScriptLoader::LoadMetaree; x90c_loaderFuncs[size_t(EScriptObjectType::DockAreaChange)] = ScriptLoader::LoadDockAreaChange; x90c_loaderFuncs[size_t(EScriptObjectType::ActorRotate)] = ScriptLoader::LoadActorRotate; x90c_loaderFuncs[size_t(EScriptObjectType::SpecialFunction)] = ScriptLoader::LoadSpecialFunction; x90c_loaderFuncs[size_t(EScriptObjectType::SpankWeed)] = ScriptLoader::LoadSpankWeed; x90c_loaderFuncs[size_t(EScriptObjectType::Parasite)] = ScriptLoader::LoadParasite; x90c_loaderFuncs[size_t(EScriptObjectType::PlayerHint)] = ScriptLoader::LoadPlayerHint; x90c_loaderFuncs[size_t(EScriptObjectType::Ripper)] = ScriptLoader::LoadRipper; x90c_loaderFuncs[size_t(EScriptObjectType::PickupGenerator)] = ScriptLoader::LoadPickupGenerator; x90c_loaderFuncs[size_t(EScriptObjectType::AIKeyframe)] = ScriptLoader::LoadAIKeyframe; x90c_loaderFuncs[size_t(EScriptObjectType::PointOfInterest)] = ScriptLoader::LoadPointOfInterest; x90c_loaderFuncs[size_t(EScriptObjectType::Drone)] = ScriptLoader::LoadDrone; x90c_loaderFuncs[size_t(EScriptObjectType::Metroid)] = ScriptLoader::LoadMetroid; x90c_loaderFuncs[size_t(EScriptObjectType::DebrisExtended)] = ScriptLoader::LoadDebrisExtended; x90c_loaderFuncs[size_t(EScriptObjectType::Steam)] = ScriptLoader::LoadSteam; x90c_loaderFuncs[size_t(EScriptObjectType::Ripple)] = ScriptLoader::LoadRipple; x90c_loaderFuncs[size_t(EScriptObjectType::BallTrigger)] = ScriptLoader::LoadBallTrigger; x90c_loaderFuncs[size_t(EScriptObjectType::TargetingPoint)] = ScriptLoader::LoadTargetingPoint; x90c_loaderFuncs[size_t(EScriptObjectType::EMPulse)] = ScriptLoader::LoadEMPulse; x90c_loaderFuncs[size_t(EScriptObjectType::IceSheegoth)] = ScriptLoader::LoadIceSheegoth; x90c_loaderFuncs[size_t(EScriptObjectType::PlayerActor)] = ScriptLoader::LoadPlayerActor; x90c_loaderFuncs[size_t(EScriptObjectType::Flaahgra)] = ScriptLoader::LoadFlaahgra; x90c_loaderFuncs[size_t(EScriptObjectType::AreaAttributes)] = ScriptLoader::LoadAreaAttributes; x90c_loaderFuncs[size_t(EScriptObjectType::FishCloud)] = ScriptLoader::LoadFishCloud; x90c_loaderFuncs[size_t(EScriptObjectType::FishCloudModifier)] = ScriptLoader::LoadFishCloudModifier; x90c_loaderFuncs[size_t(EScriptObjectType::VisorFlare)] = ScriptLoader::LoadVisorFlare; x90c_loaderFuncs[size_t(EScriptObjectType::WorldTeleporter)] = ScriptLoader::LoadWorldTeleporter; x90c_loaderFuncs[size_t(EScriptObjectType::VisorGoo)] = ScriptLoader::LoadVisorGoo; x90c_loaderFuncs[size_t(EScriptObjectType::JellyZap)] = ScriptLoader::LoadJellyZap; x90c_loaderFuncs[size_t(EScriptObjectType::ControllerAction)] = ScriptLoader::LoadControllerAction; x90c_loaderFuncs[size_t(EScriptObjectType::Switch)] = ScriptLoader::LoadSwitch; x90c_loaderFuncs[size_t(EScriptObjectType::PlayerStateChange)] = ScriptLoader::LoadPlayerStateChange; x90c_loaderFuncs[size_t(EScriptObjectType::Thardus)] = ScriptLoader::LoadThardus; x90c_loaderFuncs[size_t(EScriptObjectType::WallCrawlerSwarm)] = ScriptLoader::LoadWallCrawlerSwarm; x90c_loaderFuncs[size_t(EScriptObjectType::AIJumpPoint)] = ScriptLoader::LoadAiJumpPoint; x90c_loaderFuncs[size_t(EScriptObjectType::FlaahgraTentacle)] = ScriptLoader::LoadFlaahgraTentacle; x90c_loaderFuncs[size_t(EScriptObjectType::RoomAcoustics)] = ScriptLoader::LoadRoomAcoustics; x90c_loaderFuncs[size_t(EScriptObjectType::ColorModulate)] = ScriptLoader::LoadColorModulate; x90c_loaderFuncs[size_t(EScriptObjectType::ThardusRockProjectile)] = ScriptLoader::LoadThardusRockProjectile; x90c_loaderFuncs[size_t(EScriptObjectType::Midi)] = ScriptLoader::LoadMidi; x90c_loaderFuncs[size_t(EScriptObjectType::StreamedAudio)] = ScriptLoader::LoadStreamedAudio; x90c_loaderFuncs[size_t(EScriptObjectType::WorldTeleporterToo)] = ScriptLoader::LoadWorldTeleporter; x90c_loaderFuncs[size_t(EScriptObjectType::Repulsor)] = ScriptLoader::LoadRepulsor; x90c_loaderFuncs[size_t(EScriptObjectType::GunTurret)] = ScriptLoader::LoadGunTurret; x90c_loaderFuncs[size_t(EScriptObjectType::FogVolume)] = ScriptLoader::LoadFogVolume; x90c_loaderFuncs[size_t(EScriptObjectType::Babygoth)] = ScriptLoader::LoadBabygoth; x90c_loaderFuncs[size_t(EScriptObjectType::Eyeball)] = ScriptLoader::LoadEyeball; x90c_loaderFuncs[size_t(EScriptObjectType::RadialDamage)] = ScriptLoader::LoadRadialDamage; x90c_loaderFuncs[size_t(EScriptObjectType::CameraPitchVolume)] = ScriptLoader::LoadCameraPitchVolume; x90c_loaderFuncs[size_t(EScriptObjectType::EnvFxDensityController)] = ScriptLoader::LoadEnvFxDensityController; x90c_loaderFuncs[size_t(EScriptObjectType::Magdolite)] = ScriptLoader::LoadMagdolite; x90c_loaderFuncs[size_t(EScriptObjectType::TeamAIMgr)] = ScriptLoader::LoadTeamAIMgr; x90c_loaderFuncs[size_t(EScriptObjectType::SnakeWeedSwarm)] = ScriptLoader::LoadSnakeWeedSwarm; x90c_loaderFuncs[size_t(EScriptObjectType::ActorContraption)] = ScriptLoader::LoadActorContraption; x90c_loaderFuncs[size_t(EScriptObjectType::Oculus)] = ScriptLoader::LoadOculus; x90c_loaderFuncs[size_t(EScriptObjectType::Geemer)] = ScriptLoader::LoadGeemer; x90c_loaderFuncs[size_t(EScriptObjectType::SpindleCamera)] = ScriptLoader::LoadSpindleCamera; x90c_loaderFuncs[size_t(EScriptObjectType::AtomicAlpha)] = ScriptLoader::LoadAtomicAlpha; x90c_loaderFuncs[size_t(EScriptObjectType::CameraHintTrigger)] = ScriptLoader::LoadCameraHintTrigger; x90c_loaderFuncs[size_t(EScriptObjectType::RumbleEffect)] = ScriptLoader::LoadRumbleEffect; x90c_loaderFuncs[size_t(EScriptObjectType::AmbientAI)] = ScriptLoader::LoadAmbientAI; x90c_loaderFuncs[size_t(EScriptObjectType::AtomicBeta)] = ScriptLoader::LoadAtomicBeta; x90c_loaderFuncs[size_t(EScriptObjectType::IceZoomer)] = ScriptLoader::LoadIceZoomer; x90c_loaderFuncs[size_t(EScriptObjectType::Puffer)] = ScriptLoader::LoadPuffer; x90c_loaderFuncs[size_t(EScriptObjectType::Tryclops)] = ScriptLoader::LoadTryclops; x90c_loaderFuncs[size_t(EScriptObjectType::Ridley)] = ScriptLoader::LoadRidley; x90c_loaderFuncs[size_t(EScriptObjectType::Seedling)] = ScriptLoader::LoadSeedling; x90c_loaderFuncs[size_t(EScriptObjectType::ThermalHeatFader)] = ScriptLoader::LoadThermalHeatFader; x90c_loaderFuncs[size_t(EScriptObjectType::Burrower)] = ScriptLoader::LoadBurrower; x90c_loaderFuncs[size_t(EScriptObjectType::ScriptBeam)] = ScriptLoader::LoadBeam; x90c_loaderFuncs[size_t(EScriptObjectType::WorldLightFader)] = ScriptLoader::LoadWorldLightFader; x90c_loaderFuncs[size_t(EScriptObjectType::MetroidPrimeStage2)] = ScriptLoader::LoadMetroidPrimeEssence; x90c_loaderFuncs[size_t(EScriptObjectType::MetroidPrimeStage1)] = ScriptLoader::LoadMetroidPrimeStage1; x90c_loaderFuncs[size_t(EScriptObjectType::MazeNode)] = ScriptLoader::LoadMazeNode; x90c_loaderFuncs[size_t(EScriptObjectType::OmegaPirate)] = ScriptLoader::LoadOmegaPirate; x90c_loaderFuncs[size_t(EScriptObjectType::PhazonPool)] = ScriptLoader::LoadPhazonPool; x90c_loaderFuncs[size_t(EScriptObjectType::PhazonHealingNodule)] = ScriptLoader::LoadPhazonHealingNodule; x90c_loaderFuncs[size_t(EScriptObjectType::NewCameraShaker)] = ScriptLoader::LoadNewCameraShaker; x90c_loaderFuncs[size_t(EScriptObjectType::ShadowProjector)] = ScriptLoader::LoadShadowProjector; x90c_loaderFuncs[size_t(EScriptObjectType::EnergyBall)] = ScriptLoader::LoadEnergyBall; CGameCollision::InitCollision(); ControlMapper::ResetCommandFilters(); x8f0_shadowTex = g_SimplePool->GetObj("DefaultShadow"); g_StateManager = this; if (sm_logScripting == nullptr) { sm_logScripting = CVarManager::instance()->findOrMakeCVar( "stateManager.logScripting"sv, "Prints object communication to the console", false, CVar::EFlags::ReadOnly | CVar::EFlags::Archive | CVar::EFlags::Game); } m_logScriptingReference.emplace(&m_logScripting, sm_logScripting); } CStateManager::~CStateManager() { x88c_rumbleManager->HardStopAll(); x880_envFxManager->Cleanup(); x900_activeRandom = &x8fc_random; ClearGraveyard(); for (auto it = x808_objLists[0]->begin(); it != x808_objLists[0]->end();) { CEntity* ent = *it; ++it; if (ent == x84c_player.get()) { continue; } ent->AcceptScriptMsg(EScriptObjectMessage::Deleted, kInvalidUniqueId, *this); RemoveObject(ent->GetUniqueId()); std::default_delete()(ent); } ClearGraveyard(); x84c_player->AcceptScriptMsg(EScriptObjectMessage::Deleted, kInvalidUniqueId, *this); RemoveObject(x84c_player->GetUniqueId()); x84c_player.reset(); CCollisionPrimitive::Uninitialize(); g_StateManager = nullptr; } void CStateManager::UpdateThermalVisor() { xf28_thermColdScale2 = 0.f; xf24_thermColdScale1 = 0.f; const auto visor = x8b8_playerState->GetActiveVisor(*this); if (visor != CPlayerState::EPlayerVisor::Thermal || x8cc_nextAreaId == kInvalidAreaId) { return; } CGameArea* area = x850_world->GetArea(x8cc_nextAreaId); const zeus::CTransform& playerXf = x84c_player->GetTransform(); const zeus::CVector3f playerXYPos(playerXf.origin.x(), playerXf.origin.y(), 0.f); CGameArea* lastArea = nullptr; float closestDist = FLT_MAX; for (const CGameArea::Dock& dock : area->GetDocks()) { zeus::CVector3f dockCenter = (dock.GetPlaneVertices()[0] + dock.GetPlaneVertices()[1] + dock.GetPlaneVertices()[2] + dock.GetPlaneVertices()[3]) * 0.25f; dockCenter.z() = 0.f; const float dist = (playerXYPos - dockCenter).magSquared(); if (dist < closestDist) { const TAreaId connAreaId = dock.GetConnectedAreaId(0); if (connAreaId != kInvalidAreaId) { CGameArea* connArea = x850_world->GetArea(x8cc_nextAreaId); if (connArea->IsPostConstructed()) { const auto occState = connArea->GetPostConstructed()->x10dc_occlusionState; if (occState == CGameArea::EOcclusionState::Visible) { closestDist = dist; lastArea = connArea; } } } } } if (lastArea != nullptr) { if (closestDist != 0.f) { closestDist /= std::sqrt(closestDist); } closestDist -= 2.f; if (closestDist < 8.f) { if (closestDist > 0.f) { closestDist = (closestDist / 8.f) * 0.5f + 0.5f; } else { closestDist = 0.5f; } xf24_thermColdScale1 = (1.f - closestDist) * lastArea->GetPostConstructed()->x111c_thermalCurrent + closestDist * area->GetPostConstructed()->x111c_thermalCurrent; return; } } xf24_thermColdScale1 = area->GetPostConstructed()->x111c_thermalCurrent; } void CStateManager::RendererDrawCallback(void* drawable, void* ctx, int type) { CStateManager& mgr = *static_cast(ctx); switch (type) { case 0: { CActor& actor = *static_cast(drawable); if (actor.xc8_drawnToken == mgr.x8dc_objectDrawToken) { break; } if (actor.xc6_nextDrawNode != kInvalidUniqueId) { mgr.RecursiveDrawTree(actor.xc6_nextDrawNode); } actor.Render(mgr); actor.xc8_drawnToken = mgr.x8dc_objectDrawToken; break; } case 1: static_cast(drawable)->Render(mgr.x8f0_shadowTex); break; case 2: static_cast(drawable)->Render(); break; default: break; } } bool CStateManager::RenderLast(TUniqueId uid) { if (x86c_stateManagerContainer->xf39c_renderLast.size() == 20) { return false; } x86c_stateManagerContainer->xf39c_renderLast.push_back(uid); return true; } void CStateManager::AddDrawableActorPlane(CActor& actor, const zeus::CPlane& plane, const zeus::CAABox& aabb) const { actor.SetAddedToken(x8dc_objectDrawToken + 1); g_Renderer->AddPlaneObject(&actor, aabb, plane, 0); } void CStateManager::AddDrawableActor(CActor& actor, const zeus::CVector3f& vec, const zeus::CAABox& aabb) const { actor.SetAddedToken(x8dc_objectDrawToken + 1); g_Renderer->AddDrawable(&actor, vec, aabb, 0, IRenderer::EDrawableSorting::SortedCallback); } bool CStateManager::SpecialSkipCinematic() { if (xf38_skipCineSpecialFunc == kInvalidUniqueId) { return false; } auto* ent = static_cast(ObjectById(xf38_skipCineSpecialFunc)); if (ent == nullptr || !ent->ShouldSkipCinematic(*this)) { return false; } const bool hadRandom = x900_activeRandom != nullptr; SetActiveRandomToDefault(); x870_cameraManager->SkipCinematic(*this); ent->SkipCinematic(*this); x900_activeRandom = hadRandom ? &x8fc_random : nullptr; return true; } TAreaId CStateManager::GetVisAreaId() const { const CGameCamera* cam = static_cast(x870_cameraManager->GetCurrentCamera(*this)); const CBallCamera* ballCam = x870_cameraManager->GetBallCamera(); const TAreaId curArea = x850_world->x68_curAreaId; if (cam != ballCam) { return curArea; } const zeus::CVector3f& camTranslation = ballCam->GetTranslation(); zeus::CAABox camAABB(camTranslation, camTranslation); camAABB.accumulateBounds(x84c_player->GetTranslation()); EntityList nearList; BuildNearList(nearList, camAABB, CMaterialFilter(EMaterialTypes::AIBlock, CMaterialList(), CMaterialFilter::EFilterType::Include), nullptr); for (const auto& id : nearList) { if (const TCastToConstPtr dock = GetObjectById(id)) { if (dock->GetAreaId() == curArea && dock->HasPointCrossedDock(*this, camTranslation)) { return dock->GetCurrentConnectedAreaId(*this); } } } return curArea; } s32 CStateManager::GetWeaponIdCount(TUniqueId uid, EWeaponType type) const { return x878_weaponManager->GetNumActive(uid, type); } void CStateManager::RemoveWeaponId(TUniqueId uid, EWeaponType type) { x878_weaponManager->DecrCount(uid, type); } void CStateManager::AddWeaponId(TUniqueId uid, EWeaponType type) { x878_weaponManager->IncrCount(uid, type); } void CStateManager::UpdateEscapeSequenceTimer(float dt) { if (xf0c_escapeTimer <= 0.f) { return; } xf0c_escapeTimer = std::max(FLT_EPSILON, xf0c_escapeTimer - dt); if (xf0c_escapeTimer <= FLT_EPSILON) { x8b8_playerState->SetPlayerAlive(false); } if (!g_EscapeShakeCountdownInit) { g_EscapeShakeCountdown = 0.f; g_EscapeShakeCountdownInit = true; } g_EscapeShakeCountdown -= dt; if (g_EscapeShakeCountdown >= 0.f) { return; } const float factor = 1.f - xf0c_escapeTimer / xf10_escapeTotalTime; const float factor2 = factor * factor; const CCameraShakeData shakeData(1.f, factor2 * 0.2f * x900_activeRandom->Range(0.5f, 1.f)); x870_cameraManager->AddCameraShaker(shakeData, true); x88c_rumbleManager->Rumble(*this, ERumbleFxId::EscapeSequenceShake, 0.75f, ERumblePriority::One); g_EscapeShakeCountdown = -12.f * factor2 + 15.f; } void CStateManager::ResetEscapeSequenceTimer(float time) { xf0c_escapeTimer = time; xf10_escapeTotalTime = time; } void CStateManager::SetupParticleHook(const CActor& actor) const { x884_actorModelParticles->SetupHook(actor.GetUniqueId()); } void CStateManager::MurderScriptInstanceNames() { xb40_uniqueInstanceNames.clear(); } std::string CStateManager::HashInstanceName(CInputStream& in) { return in.Get(); } void CStateManager::SetActorAreaId(CActor& actor, TAreaId aid) { const TAreaId actorAid = actor.GetAreaIdAlways(); if (actorAid == aid) { return; } if (actorAid != kInvalidAreaId) { CGameArea* area = x850_world->GetArea(actorAid); if (area->IsPostConstructed()) { area->GetAreaObjects()->RemoveObject(actor.GetUniqueId()); } } actor.x4_areaId = aid; if (aid == kInvalidAreaId) { return; } CGameArea* area = x850_world->GetArea(aid); if (!area->IsPostConstructed() || area->GetAreaObjects()->GetValidObjectById(actor.GetUniqueId())) { return; } area->GetAreaObjects()->AddObject(actor); } void CStateManager::TouchSky() const { x850_world->TouchSky(); } void CStateManager::TouchPlayerActor() { if (xf6c_playerActorHead == kInvalidUniqueId) { return; } if (CEntity* ent = ObjectById(xf6c_playerActorHead)) { static_cast(ent)->TouchModels(*this); } } void CStateManager::DrawSpaceWarp(const zeus::CVector3f& v, float strength) const { const CPlayerState::EPlayerVisor visor = x8b8_playerState->GetActiveVisor(*this); if (visor != CPlayerState::EPlayerVisor::Scan && visor != CPlayerState::EPlayerVisor::Combat) { return; } const zeus::CVector3f screenV = TCastToConstPtr(x870_cameraManager->GetCurrentCamera(*this))->ConvertToScreenSpace(v); g_Renderer->DrawSpaceWarp(screenV, strength); } void CStateManager::DrawReflection(const zeus::CVector3f& reflectPoint) { const zeus::CAABox aabb = x84c_player->GetBoundingBox(); const zeus::CVector3f playerPos = aabb.center(); zeus::CVector3f surfToPlayer = playerPos - reflectPoint; surfToPlayer.z() = 0.f; const zeus::CVector3f viewPos = playerPos - surfToPlayer.normalized() * 3.5f; const zeus::CTransform look = zeus::lookAt(viewPos, playerPos, {0.f, 0.f, -1.f}); const zeus::CTransform backupView = CGraphics::mViewMatrix; CGraphics::SetViewPointMatrix(look); const CGraphics::CProjectionState backupProj = CGraphics::GetProjectionState(); const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this); g_Renderer->SetPerspective(cam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), cam->GetNearClipDistance(), cam->GetFarClipDistance()); x84c_player->RenderReflectedPlayer(*this); CGraphics::SetViewPointMatrix(backupView); CGraphics::SetProjectionState(backupProj); } void CStateManager::ReflectionDrawer(void* ctx, const zeus::CVector3f& vec) { static_cast(ctx)->DrawReflection(vec); } void CStateManager::CacheReflection() { g_Renderer->CacheReflection(ReflectionDrawer, this, true); } bool CStateManager::CanCreateProjectile(TUniqueId uid, EWeaponType type, int maxAllowed) const { return x878_weaponManager->GetNumActive(uid, type) < maxAllowed; } void CStateManager::BuildDynamicLightListForWorld() { if (x8b8_playerState->GetActiveVisor(*this) == CPlayerState::EPlayerVisor::Thermal) { x8e0_dynamicLights.clear(); return; } if (GetLightObjectList().size() == 0) { return; } x8e0_dynamicLights.clear(); x8e0_dynamicLights.reserve(GetLightObjectList().size()); for (const CEntity* ent : GetLightObjectList()) { const auto& light = static_cast(*ent); if (light.GetActive()) { const CLight l = light.GetLight(); if (l.GetIntensity() > FLT_EPSILON && l.GetRadius() > FLT_EPSILON) { x8e0_dynamicLights.push_back(l); } } } std::sort(x8e0_dynamicLights.begin(), x8e0_dynamicLights.end(), [](const CLight& a, const CLight& b) { if (b.GetPriority() > a.GetPriority()) { return true; } else if (b.GetPriority() == a.GetPriority()) { return a.GetIntensity() > b.GetIntensity(); } else { return false; } }); } void CStateManager::DrawDebugStuff() const { if (com_developer != nullptr && !com_developer->toBoolean()) { return; } // FIXME: Add proper globals for CVars if (debugToolDrawAiPath == nullptr || debugToolDrawCollisionActors == nullptr || debugToolDrawLighting == nullptr || debugToolDrawMazePath == nullptr || debugToolDrawPlatformCollision == nullptr) { debugToolDrawAiPath = CVarManager::instance()->findCVar("debugTool.drawAiPath"); debugToolDrawMazePath = CVarManager::instance()->findCVar("debugTool.drawMazePath"); debugToolDrawCollisionActors = CVarManager::instance()->findCVar("debugTool.drawCollisionActors"); debugToolDrawLighting = CVarManager::instance()->findCVar("debugTool.drawLighting"); debugToolDrawPlatformCollision = CVarManager::instance()->findCVar("debugTool.drawPlatformCollision"); return; } CGraphics::SetModelMatrix(zeus::CTransform()); for (CEntity* ent : GetActorObjectList()) { if (const TCastToPtr ai = ent) { if (CPathFindSearch* path = ai->GetSearchPath()) { if (debugToolDrawAiPath->toBoolean()) { path->DebugDraw(); } } } else if (const TCastToPtr light = ent) { if (debugToolDrawLighting->toBoolean()) { light->DebugDraw(); } } else if (const TCastToPtr colAct = ent) { if (colAct->GetUniqueId() == x870_cameraManager->GetBallCamera()->GetCollisionActorId()) { continue; } if (debugToolDrawCollisionActors->toBoolean()) { colAct->DebugDraw(); } } else if (const TCastToPtr plat = ent) { if (debugToolDrawPlatformCollision->toBoolean() && plat->GetActive()) { plat->DebugDraw(); } } else if (const TCastToPtr tr = ent) { tr->DebugDraw(); } } auto* gameArea = x850_world->GetArea(x850_world->GetCurrentAreaId()); if (gameArea != nullptr && debugToolDrawLighting->toBoolean()) { gameArea->DebugDraw(); } if (xf70_currentMaze && debugToolDrawMazePath->toBoolean()) { xf70_currentMaze->DebugRender(); } } void CStateManager::RenderCamerasAndAreaLights() { x870_cameraManager->RenderCameras(*this); for (auto& filter : xb84_camFilterPasses) { filter.Draw(); } } void CStateManager::DrawE3DeathEffect() { const CPlayer& player = *x84c_player; if (player.x9f4_deathTime <= 0.f) { return; } if (player.x2f8_morphBallState != CPlayer::EPlayerMorphBallState::Unmorphed) { const float blurAmt = zeus::clamp(0.f, (player.x9f4_deathTime - 1.f) / (6.f - 1.f), 1.f); if (blurAmt > 0.f) { CCameraBlurPass blur; blur.SetBlur(EBlurType::HiBlur, 7.f * blurAmt, 0.f, false); blur.Draw(); } } const float whiteAmt = zeus::clamp(0.f, 1.f - player.x9f4_deathTime / (0.05f * 6.f), 1.f); zeus::CColor color = zeus::skWhite; color.a() = whiteAmt; CCameraFilterPass::DrawFilter(EFilterType::Add, EFilterShape::Fullscreen, color, nullptr, 1.f); } void CStateManager::DrawAdditionalFilters() { if (xf0c_escapeTimer >= 1.f || xf0c_escapeTimer <= 0.f || x870_cameraManager->IsInCinematicCamera()) { return; } zeus::CColor color = zeus::skWhite; color.a() = 1.f - xf0c_escapeTimer; CCameraFilterPass::DrawFilter(EFilterType::Add, EFilterShape::Fullscreen, color, nullptr, 1.f); } zeus::CFrustum CStateManager::SetupDrawFrustum(const CViewport& vp) const { zeus::CFrustum ret; const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this); const zeus::CTransform camXf = x870_cameraManager->GetCurrentCameraTransform(*this); const int vpWidth = static_cast(xf2c_viewportScale.x() * vp.mWidth); const int vpHeight = static_cast(xf2c_viewportScale.y() * vp.mHeight); const int vpLeft = (vp.mWidth - vpWidth) / 2 + vp.mLeft; const int vpTop = (vp.mHeight - vpHeight) / 2 + vp.mTop; g_Renderer->SetViewport(vpLeft, vpTop, vpWidth, vpHeight); const float fov = std::atan(std::tan(zeus::degToRad(cam->GetFov()) * 0.5f) * xf2c_viewportScale.y()) * 2.f; const float width = xf2c_viewportScale.x() * vp.mWidth; const float height = xf2c_viewportScale.y() * vp.mHeight; zeus::CProjection proj; proj.setPersp(zeus::SProjPersp{fov, width / height, cam->GetNearClipDistance(), cam->GetFarClipDistance()}); ret.updatePlanes(camXf, proj); return ret; } zeus::CFrustum CStateManager::SetupViewForDraw(const CViewport& vp) const { const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this); const zeus::CTransform camXf = x870_cameraManager->GetCurrentCameraTransform(*this); g_Renderer->SetWorldViewpoint(camXf); CCubeModel::SetNewPlayerPositionAndTime(x84c_player->GetTranslation(), CStopwatch::GetGlobalTimerObj()); const int vpWidth = static_cast(xf2c_viewportScale.x() * vp.mWidth); const int vpHeight = static_cast(xf2c_viewportScale.y() * vp.mHeight); const int vpLeft = (vp.mWidth - vpWidth) / 2 + vp.mLeft; const int vpTop = (vp.mHeight - vpHeight) / 2 + vp.mTop; g_Renderer->SetViewport(vpLeft, vpTop, vpWidth, vpHeight); CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR); const float fov = std::atan(std::tan(zeus::degToRad(cam->GetFov()) * 0.5f) * xf2c_viewportScale.y()) * 2.f; const float width = xf2c_viewportScale.x() * vp.mWidth; const float height = xf2c_viewportScale.y() * vp.mHeight; g_Renderer->SetPerspective(zeus::radToDeg(fov), width, height, cam->GetNearClipDistance(), cam->GetFarClipDistance()); zeus::CFrustum frustum; zeus::CProjection proj; proj.setPersp(zeus::SProjPersp{fov, width / height, cam->GetNearClipDistance(), cam->GetFarClipDistance()}); frustum.updatePlanes(camXf, proj); g_Renderer->SetClippingPlanes(frustum); g_Renderer->PrimColor(zeus::skWhite); CGraphics::SetModelMatrix(zeus::CTransform()); x87c_fluidPlaneManager->StartFrame(false); g_Renderer->SetDebugOption(IRenderer::EDebugOption::PVSState, static_cast(EPVSVisSetState::NodeFound)); return frustum; } void CStateManager::ResetViewAfterDraw(const CViewport& backupViewport, const zeus::CTransform& backupViewMatrix) const { g_Renderer->SetViewport(backupViewport.mLeft, backupViewport.mTop, backupViewport.mWidth, backupViewport.mHeight); const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this); zeus::CFrustum frustum; frustum.updatePlanes(backupViewMatrix, zeus::SProjPersp(zeus::degToRad(cam->GetFov()), CGraphics::GetViewportAspect(), cam->GetNearClipDistance(), cam->GetFarClipDistance())); g_Renderer->SetClippingPlanes(frustum); g_Renderer->SetPerspective(cam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), cam->GetNearClipDistance(), cam->GetFarClipDistance()); } void CStateManager::DrawWorld() { SCOPED_GRAPHICS_DEBUG_GROUP("CStateManager::DrawWorld", zeus::skBlue); const CTimeProvider timeProvider(xf14_curTimeMod900); const CViewport backupViewport = CGraphics::mViewport; /* Area camera is in (not necessarily player) */ const TAreaId visAreaId = GetVisAreaId(); x850_world->TouchSky(); const zeus::CFrustum frustum = SetupViewForDraw(CGraphics::mViewport); const zeus::CTransform backupViewMatrix = CGraphics::mViewMatrix; int areaCount = 0; std::array areaArr; for (const CGameArea& area : *x850_world) { if (areaCount == 10) { break; } CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded; if (area.IsPostConstructed()) { occState = area.GetOcclusionState(); } if (occState == CGameArea::EOcclusionState::Visible) { areaArr[areaCount++] = &area; } } std::sort(areaArr.begin(), areaArr.begin() + areaCount, [visAreaId](const CGameArea* a, const CGameArea* b) { if (a->x4_selfIdx == b->x4_selfIdx) { return false; } if (visAreaId == a->x4_selfIdx) { return false; } if (visAreaId == b->x4_selfIdx) { return true; } return CGraphics::mViewPoint.dot(a->GetAABB().center()) > CGraphics::mViewPoint.dot(b->GetAABB().center()); }); int pvsCount = 0; std::array pvsArr; for (auto area = areaArr.cbegin(); area != areaArr.cbegin() + areaCount; ++area) { const CGameArea* areaPtr = *area; CPVSVisSet& pvsSet = pvsArr[pvsCount++]; pvsSet.Reset(EPVSVisSetState::OutOfBounds); GetVisSetForArea(areaPtr->x4_selfIdx, visAreaId, pvsSet); } int mask; int targetMask; const auto visor = x8b8_playerState->GetActiveVisor(*this); const bool thermal = visor == CPlayerState::EPlayerVisor::Thermal; if (thermal) { xf34_thermalFlag = EThermalDrawFlag::Cold; mask = 0x34; targetMask = 0; } else { xf34_thermalFlag = EThermalDrawFlag::Bypass; mask = 1 << (visor == CPlayerState::EPlayerVisor::XRay ? 3 : 1); targetMask = 0; } g_Renderer->SetThermal(thermal, g_tweakGui->GetThermalVisorLevel(), g_tweakGui->GetThermalVisorColor()); g_Renderer->SetThermalColdScale(xf28_thermColdScale2 + xf24_thermColdScale1); for (int i = areaCount - 1; i >= 0; --i) { //OPTICK_EVENT("CStateManager::DrawWorld DrawArea"); const CGameArea& area = *areaArr[i]; SetupFogForArea(area); g_Renderer->EnablePVS(pvsArr[i], area.x4_selfIdx); g_Renderer->SetWorldLightFadeLevel(area.GetPostConstructed()->x1128_worldLightingLevel); g_Renderer->DrawUnsortedGeometry(area.x4_selfIdx, mask, targetMask); } if (!SetupFogForDraw()) { g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack); } x850_world->DrawSky(zeus::CTransform::Translate(CGraphics::mViewPoint)); if (areaCount != 0) { SetupFogForArea(*areaArr[areaCount - 1]); } for (const auto& id : x86c_stateManagerContainer->xf370_) { if (auto* ent = static_cast(ObjectById(id))) { if (!thermal || (ent->xe6_27_thermalVisorFlags & 1) != 0) { ent->Render(*this); } } } bool morphingPlayerVisible = false; int thermalActorCount = 0; std::array thermalActorArr; for (int i = 0; i < areaCount; ++i) { const CGameArea& area = *areaArr[i]; CPVSVisSet& pvs = pvsArr[i]; const bool isVisArea = area.x4_selfIdx == visAreaId; SetupFogForArea(area); g_Renderer->SetWorldLightFadeLevel(area.GetPostConstructed()->x1128_worldLightingLevel); for (CEntity* ent : *area.GetAreaObjects()) { if (const TCastToPtr actor = ent) { if (!actor->IsDrawEnabled()) { continue; } const TUniqueId actorId = actor->GetUniqueId(); if (!thermal && area.LookupPVSUniqueID(actorId) == actorId) { if (pvs.GetVisible(area.LookupPVSID(actorId)) == EPVSVisSetState::EndOfTree) { continue; } } if (x84c_player.get() == actor.GetPtr()) { if (thermal) { continue; } switch (x84c_player->GetMorphballTransitionState()) { case CPlayer::EPlayerMorphBallState::Unmorphed: case CPlayer::EPlayerMorphBallState::Morphed: x84c_player->AddToRenderer(frustum, *this); continue; default: morphingPlayerVisible = true; continue; } } if (!thermal || (actor->xe6_27_thermalVisorFlags & 1) != 0) { actor->AddToRenderer(frustum, *this); } if (thermal && (actor->xe6_27_thermalVisorFlags & 2) != 0) { thermalActorArr[thermalActorCount++] = actor.GetPtr(); } } } if (isVisArea && !thermal) { CDecalManager::AddToRenderer(frustum, *this); x884_actorModelParticles->AddStragglersToRenderer(*this); } ++x8dc_objectDrawToken; x84c_player->GetMorphBall()->DrawBallShadow(*this); if (xf7c_projectedShadow != nullptr) { xf7c_projectedShadow->Render(*this); } g_Renderer->EnablePVS(pvs, area.x4_selfIdx); g_Renderer->DrawSortedGeometry(area.x4_selfIdx, mask, targetMask); } x880_envFxManager->Render(*this); if (morphingPlayerVisible) { x84c_player->Render(*this); } g_Renderer->PostRenderFogs(); if (thermal) { if (x86c_stateManagerContainer->xf39c_renderLast.size()) { CGraphics::SetDepthRange(DEPTH_SCREEN_ACTORS, DEPTH_GUN); for (const auto& id : x86c_stateManagerContainer->xf39c_renderLast) { if (auto* actor = static_cast(ObjectById(id))) { if ((actor->xe6_27_thermalVisorFlags & 1) != 0) { actor->Render(*this); } } } CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR); } g_Renderer->DoThermalBlendCold(); xf34_thermalFlag = EThermalDrawFlag::Hot; for (const auto& id : x86c_stateManagerContainer->xf370_) { if (auto* actor = static_cast(ObjectById(id))) { if ((actor->xe6_27_thermalVisorFlags & 2) != 0) { actor->Render(*this); } } } for (int i = areaCount - 1; i >= 0; --i) { const CGameArea& area = *areaArr[i]; CPVSVisSet& pvs = pvsArr[i]; g_Renderer->EnablePVS(pvs, area.x4_selfIdx); g_Renderer->DrawUnsortedGeometry(area.x4_selfIdx, mask, 0x20); g_Renderer->DrawAreaGeometry(area.x4_selfIdx, mask, 0x10); } ++x8dc_objectDrawToken; for (int i = 0; i < areaCount; ++i) { const CGameArea& area = *areaArr[i]; CPVSVisSet& pvs = pvsArr[i]; for (int j = 0; j < thermalActorCount; ++j) { CActor* actor = thermalActorArr[j]; if (actor->GetAreaIdAlways() != area.x4_selfIdx) { if (actor->GetAreaIdAlways() != kInvalidAreaId || area.x4_selfIdx != visAreaId) { continue; } } actor->AddToRenderer(frustum, *this); } if (areaCount - 1 == i) { x884_actorModelParticles->AddStragglersToRenderer(*this); CDecalManager::AddToRenderer(frustum, *this); if (x84c_player) { x84c_player->AddToRenderer(frustum, *this); } } ++x8dc_objectDrawToken; g_Renderer->EnablePVS(pvs, area.x4_selfIdx); g_Renderer->DrawSortedGeometry(area.x4_selfIdx, mask, 0x10); } g_Renderer->PostRenderFogs(); } x87c_fluidPlaneManager->EndFrame(); g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack); #if 0 if (false) { CacheReflection(); } #endif if (x84c_player) { x84c_player->RenderGun(*this, x870_cameraManager->GetGlobalCameraTranslation(*this)); } if (x86c_stateManagerContainer->xf39c_renderLast.size()) { CGraphics::SetDepthRange(DEPTH_SCREEN_ACTORS, DEPTH_GUN); for (const auto& id : x86c_stateManagerContainer->xf39c_renderLast) { if (auto* actor = static_cast(ObjectById(id))) { if (!thermal || actor->xe6_27_thermalVisorFlags & 0x2) { actor->Render(*this); } } } CGraphics::SetDepthRange(DEPTH_WORLD, DEPTH_FAR); } if (thermal) { g_Renderer->DoThermalBlendHot(); g_Renderer->SetThermal(false, 0.f, zeus::skBlack); xf34_thermalFlag = EThermalDrawFlag::Bypass; } DrawDebugStuff(); RenderCamerasAndAreaLights(); ResetViewAfterDraw(backupViewport, backupViewMatrix); DrawE3DeathEffect(); DrawAdditionalFilters(); } void CStateManager::SetupFogForArea3XRange(TAreaId area) const { if (area == kInvalidAreaId) { area = x8cc_nextAreaId; } const CGameArea* areaObj = x850_world->GetAreaAlways(area); if (areaObj->IsPostConstructed()) { SetupFogForArea3XRange(*areaObj); } } void CStateManager::SetupFogForArea(TAreaId area) const { if (area == kInvalidAreaId) { area = x8cc_nextAreaId; } const CGameArea* areaObj = x850_world->GetAreaAlways(area); if (areaObj->IsPostConstructed()) { SetupFogForArea(*areaObj); } } void CStateManager::SetupFogForAreaNonCurrent(TAreaId area) const { if (area == kInvalidAreaId) { area = x8cc_nextAreaId; } const CGameArea* areaObj = x850_world->GetAreaAlways(area); if (areaObj->IsPostConstructed()) { SetupFogForAreaNonCurrent(*areaObj); } } void CStateManager::SetupFogForArea3XRange(const CGameArea& area) const { if (x8b8_playerState->GetActiveVisor(*this) != CPlayerState::EPlayerVisor::XRay) { return; } const float fogDist = area.GetXRayFogDistance(); const float farz = (g_tweakGui->GetXRayFogNearZ() * (1.f - fogDist) + g_tweakGui->GetXRayFogFarZ() * fogDist) * 3.f; g_Renderer->SetWorldFog(ERglFogMode(g_tweakGui->GetXRayFogMode()), g_tweakGui->GetXRayFogNearZ(), farz, g_tweakGui->GetXRayFogColor()); } void CStateManager::SetupFogForArea(const CGameArea& area) const { if (SetupFogForDraw()) { return; } if (x8b8_playerState->GetActiveVisor(*this) == CPlayerState::EPlayerVisor::XRay) { const float fogDist = area.GetXRayFogDistance(); const float farz = g_tweakGui->GetXRayFogNearZ() * (1.f - fogDist) + g_tweakGui->GetXRayFogFarZ() * fogDist; g_Renderer->SetWorldFog(ERglFogMode(g_tweakGui->GetXRayFogMode()), g_tweakGui->GetXRayFogNearZ(), farz, g_tweakGui->GetXRayFogColor()); } else { area.GetAreaFog()->SetCurrent(); } } void CStateManager::SetupFogForAreaNonCurrent(const CGameArea& area) const { if (SetupFogForDraw()) { return; } if (x8b8_playerState->GetActiveVisor(*this) != CPlayerState::EPlayerVisor::XRay) { return; } const float fogDist = area.GetXRayFogDistance(); const float farz = g_tweakGui->GetXRayFogNearZ() * (1.f - fogDist) + g_tweakGui->GetXRayFogFarZ() * fogDist; g_Renderer->SetWorldFog(ERglFogMode(g_tweakGui->GetXRayFogMode()), g_tweakGui->GetXRayFogNearZ(), farz, g_tweakGui->GetXRayFogColor()); } bool CStateManager::SetupFogForDraw() const { switch (x8b8_playerState->GetActiveVisor(*this)) { case CPlayerState::EPlayerVisor::Thermal: g_Renderer->SetWorldFog(ERglFogMode::None, 0.f, 1.f, zeus::skBlack); return true; case CPlayerState::EPlayerVisor::XRay: default: return false; case CPlayerState::EPlayerVisor::Combat: case CPlayerState::EPlayerVisor::Scan: auto& fog = x870_cameraManager->Fog(); if (fog.IsFogDisabled()) { return false; } fog.SetCurrent(); return true; } } void CStateManager::PreRender() { if (!xf94_24_readyToRender) { return; } SCOPED_GRAPHICS_DEBUG_GROUP("CStateManager::PreRender", zeus::skBlue); const zeus::CFrustum frustum = SetupDrawFrustum(CGraphics::mViewport); x86c_stateManagerContainer->xf370_.clear(); x86c_stateManagerContainer->xf39c_renderLast.clear(); xf7c_projectedShadow = nullptr; x850_world->PreRender(); BuildDynamicLightListForWorld(); for (const CGameArea& area : *x850_world) { auto occState = CGameArea::EOcclusionState::Occluded; if (area.IsPostConstructed()) { occState = area.GetOcclusionState(); } if (occState == CGameArea::EOcclusionState::Visible) { for (CEntity* ent : *area.GetPostConstructed()->x10c0_areaObjs) { if (const TCastToPtr act = ent) { if (act->IsDrawEnabled()) { act->CalculateRenderBounds(); act->PreRender(*this, frustum); } } } } } CacheReflection(); g_Renderer->PrepareDynamicLights(x8e0_dynamicLights); } void CStateManager::GetCharacterRenderMaskAndTarget(bool thawed, int& mask, int& target) const { switch (x8b8_playerState->GetActiveVisor(*this)) { case CPlayerState::EPlayerVisor::Combat: case CPlayerState::EPlayerVisor::Scan: mask = 0x1000; target = 0x0; break; case CPlayerState::EPlayerVisor::XRay: mask = 0x800; target = 0x0; break; case CPlayerState::EPlayerVisor::Thermal: if (thawed) { if (xf34_thermalFlag == EThermalDrawFlag::Hot) { mask = 0x600; target = 0x0; } else { mask = 0x600; target = 0x200; } } else { if (xf34_thermalFlag == EThermalDrawFlag::Cold) { mask = 0x500; target = 0x0; } else { mask = 0x500; target = 0x100; } } break; default: mask = 0x0; target = 0x0; break; } } bool CStateManager::GetVisSetForArea(TAreaId a, TAreaId b, CPVSVisSet& setOut) const { if (b == kInvalidAreaId) { return false; } const zeus::CVector3f viewPoint = CGraphics::mViewMatrix.origin; zeus::CVector3f closestDockPoint = viewPoint; bool hasClosestDock = false; if (a != b) { const CGameArea& area = *x850_world->GetGameAreas()[b]; if (area.IsPostConstructed()) { for (const CGameArea::Dock& dock : area.GetDocks()) { for (int i = 0; i < dock.GetDockRefs().size(); ++i) { const TAreaId connArea = dock.GetConnectedAreaId(i); if (connArea == a) { const auto& verts = dock.GetPlaneVertices(); const zeus::CVector3f dockCenter = (verts[0] + verts[1] + verts[2] + verts[3]) * 0.25f; if (hasClosestDock) { if ((dockCenter - viewPoint).magSquared() >= (closestDockPoint - viewPoint).magSquared()) { continue; } } closestDockPoint = dockCenter; hasClosestDock = true; } } } } } else { hasClosestDock = true; } if (hasClosestDock) { if (CPVSAreaSet* pvs = x850_world->GetGameAreas()[a]->GetPostConstructed()->xa0_pvs.get()) { const CPVSVisOctree& octree = pvs->GetVisOctree(); const zeus::CVector3f closestDockLocal = x850_world->GetGameAreas()[a]->GetInverseTransform() * closestDockPoint; CPVSVisSet set; set.SetTestPoint(octree, closestDockLocal); if (set.GetState() == EPVSVisSetState::NodeFound) { setOut = set; return true; } } } return false; } void CStateManager::RecursiveDrawTree(TUniqueId node) { if (const TCastToPtr actor = ObjectById(node)) { if (x8dc_objectDrawToken != actor->xc8_drawnToken) { if (actor->xc6_nextDrawNode != kInvalidUniqueId) { RecursiveDrawTree(actor->xc6_nextDrawNode); } if (x8dc_objectDrawToken == actor->xcc_addedToken) { actor->Render(*this); } actor->xc8_drawnToken = x8dc_objectDrawToken; } } } void CStateManager::SendScriptMsg(CEntity* dest, TUniqueId src, EScriptObjectMessage msg) { if (dest == nullptr || dest->x30_26_scriptingBlocked) { return; } if (m_logScripting) { auto srcObj = GetObjectById(src); if (srcObj != nullptr) { spdlog::info("{} is sending '{}' to '{}' id= {} -> {}", srcObj->GetName(), ScriptObjectMessageToStr(msg), dest->GetName(), dest->GetUniqueId(), src, dest->GetUniqueId()); } else { spdlog::info("Sending '{}' to '{}' id= {}", ScriptObjectMessageToStr(msg), dest->GetName(), dest->GetUniqueId()); } } dest->AcceptScriptMsg(msg, src, *this); } void CStateManager::SendScriptMsg(TUniqueId dest, TUniqueId src, EScriptObjectMessage msg) { CEntity* ent = ObjectById(dest); SendScriptMsg(ent, src, msg); } void CStateManager::SendScriptMsgAlways(TUniqueId dest, TUniqueId src, EScriptObjectMessage msg) { CEntity* dst = ObjectById(dest); if (dst == nullptr) { return; } if (m_logScripting) { auto srcObj = GetObjectById(src); if (srcObj != nullptr) { spdlog::info("{} is sending '{}' to '{}' id= {} -> {}", srcObj->GetName(), ScriptObjectMessageToStr(msg), dst->GetName(), dst->GetUniqueId(), src, dst->GetUniqueId()); } else { spdlog::info("Sending '{}' to '{}' id= {}", ScriptObjectMessageToStr(msg), dst->GetName(), dst->GetUniqueId()); } } dst->AcceptScriptMsg(msg, src, *this); } void CStateManager::SendScriptMsg(TUniqueId src, TEditorId dest, EScriptObjectMessage msg, EScriptObjectState state) { // CEntity* ent = GetObjectById(src); const auto search = GetIdListForScript(dest); if (search.first == x890_scriptIdMap.cend()) { return; } for (auto it = search.first; it != search.second; ++it) { const TUniqueId id = it->second; CEntity* dobj = GetAllObjectList().GetObjectById(id); SendScriptMsg(dobj, src, msg); } } void CStateManager::FreeScriptObjects(TAreaId aid) { for (const auto& p : x890_scriptIdMap) { if (p.first.AreaNum() == aid) { FreeScriptObject(p.second); } } std::set freedObjects; for (auto it = x8a4_loadedScriptObjects.begin(); it != x8a4_loadedScriptObjects.end();) { if (it->first.AreaNum() == aid) { freedObjects.emplace(it->first); it = x8a4_loadedScriptObjects.erase(it); continue; } ++it; } const CGameArea* area = x850_world->GetGameAreas()[aid].get(); if (area->IsPostConstructed()) { const CGameArea::CPostConstructed* pc = area->GetPostConstructed(); for (CEntity* ent : *pc->x10c0_areaObjs) { if (ent != nullptr && !ent->IsInUse()) { FreeScriptObject(ent->GetUniqueId()); } } } for (const auto& id : freedObjects) { m_incomingConnections.erase(id); } } void CStateManager::FreeScriptObject(TUniqueId id) { CEntity* ent = ObjectById(id); if (ent == nullptr || ent->IsInGraveyard()) { return; } ent->SetIsInGraveyard(true); x854_objectGraveyard.push_back(id); ent->AcceptScriptMsg(EScriptObjectMessage::Deleted, kInvalidUniqueId, *this); ent->SetIsScriptingBlocked(true); if (const TCastToPtr act = ent) { x874_sortedListManager->Remove(act.GetPtr()); act->SetUseInSortedLists(false); } if (m_logScripting) { spdlog::info("Removed '{}'", ent->GetName()); } } std::pair CStateManager::GetBuildForScript(TEditorId id) const { const auto search = x8a4_loadedScriptObjects.find(id); if (search == x8a4_loadedScriptObjects.cend()) { return {nullptr, kInvalidEditorId}; } return {&search->second, search->first}; } TEditorId CStateManager::GetEditorIdForUniqueId(TUniqueId id) const { const CEntity* ent = GetObjectById(id); if (ent != nullptr) { return ent->GetEditorId(); } return kInvalidEditorId; } TUniqueId CStateManager::GetIdForScript(TEditorId id) const { const auto search = x890_scriptIdMap.find(id); if (search == x890_scriptIdMap.cend()) { return kInvalidUniqueId; } return search->second; } std::pair::const_iterator, std::multimap::const_iterator> CStateManager::GetIdListForScript(TEditorId id) const { auto ret = x890_scriptIdMap.equal_range(id); if (ret.first != x890_scriptIdMap.cend() && ret.first->first != id) { ret.first = x890_scriptIdMap.cend(); ret.second = x890_scriptIdMap.cend(); } return ret; } void CStateManager::LoadScriptObjects(TAreaId aid, CInputStream& in, std::vector& idsOut) { in.ReadUint8(); const u32 objCount = in.ReadLong(); idsOut.reserve(idsOut.size() + objCount); for (u32 i = 0; i < objCount; ++i) { const auto objType = static_cast(in.ReadUint8()); const u32 objSize = in.ReadLong(); const u32 pos = static_cast(in.GetReadPosition()); const auto id = LoadScriptObject(aid, objType, objSize, in); if (id.first == kInvalidEditorId) { continue; } const auto build = GetBuildForScript(id.first); if (build.first) { continue; } x8a4_loadedScriptObjects[id.first] = SScriptObjectStream{objType, pos, objSize}; idsOut.push_back(id.first); } for (const auto& pair : m_incomingConnections) { if (auto* ent = ObjectById(GetIdForScript(pair.first))) { ent->SetIncomingConnectionList(&pair.second); } } } std::pair CStateManager::LoadScriptObject(TAreaId aid, EScriptObjectType type, u32 length, CInputStream& in) { //OPTICK_EVENT(); const TEditorId id = in.ReadLong(); const u32 connCount = in.ReadLong(); length -= 8; std::vector conns; conns.reserve(connCount); for (u32 i = 0; i < connCount; ++i) { const auto state = EScriptObjectState(in.ReadLong()); const auto msg = EScriptObjectMessage(in.ReadLong()); const TEditorId target = in.ReadLong(); // Metaforce Addition if (m_incomingConnections.find(target) == m_incomingConnections.cend()) { m_incomingConnections.emplace(target, std::set()); } SConnection inConn{state, msg, id}; m_incomingConnections[target].emplace(inConn); // End Metaforce Addition length -= 12; conns.push_back(SConnection{state, msg, target}); } const u32 propCount = in.ReadLong(); length -= 4; const auto startPos = in.GetReadPosition(); bool error = false; FScriptLoader loader = {}; if (type < EScriptObjectType::ScriptObjectTypeMAX && type >= EScriptObjectType::Actor) { loader = x90c_loaderFuncs[size_t(type)]; } CEntity* ent = nullptr; if (loader != nullptr) { const CEntityInfo info(aid, std::move(conns), id); ent = loader(*this, in, propCount, info); } else { error = true; } if (ent != nullptr) { AddObject(ent); } else { error = true; } const u32 readAmt = in.GetReadPosition() - startPos; if (readAmt > length) { spdlog::fatal("Script object overread while reading {}", ScriptObjectTypeToStr(type)); } const u32 leftover = length - readAmt; for (u32 i = 0; i < leftover; ++i) { in.ReadChar(); } if (error || ent == nullptr) { spdlog::error("Script load error while loading {} (Editor ID: {}, Area: {})", ScriptObjectTypeToStr(type), id, aid); return {kInvalidEditorId, kInvalidUniqueId}; } else { #ifndef NDEBUG spdlog::info("Loaded {} in area {}", ent->GetName(), ent->GetAreaIdAlways()); #endif return {id, ent->GetUniqueId()}; } } std::pair CStateManager::GenerateObject(TEditorId eid) { const std::pair build = GetBuildForScript(eid); if (build.first) { const CGameArea* area = x850_world->GetArea(build.second.AreaNum()); if (area->IsPostConstructed()) { const std::pair buf = area->GetLayerScriptBuffer(build.second.LayerNum()); CMemoryInStream stream(buf.first + build.first->x4_position, build.first->x8_length); auto ret = LoadScriptObject(build.second.AreaNum(), build.first->x0_type, build.first->x8_length, stream); // Metaforce Addition if (m_incomingConnections.find(eid) != m_incomingConnections.end()) { if (auto ent = ObjectById(ret.second)) { ent->SetIncomingConnectionList(&m_incomingConnections[eid]); } } // End Metaforce Addition return ret; } } return {kInvalidEditorId, kInvalidUniqueId}; } void CStateManager::InitScriptObjects(const std::vector& ids) { for (const auto& id : ids) { if (id == kInvalidEditorId) { continue; } const TUniqueId uid = GetIdForScript(id); SendScriptMsg(uid, kInvalidUniqueId, EScriptObjectMessage::InitializedInArea); } MurderScriptInstanceNames(); } void CStateManager::InformListeners(const zeus::CVector3f& pos, EListenNoiseType type) { for (CEntity* ent : GetListeningAiObjectList()) { if (const TCastToPtr ai = ent) { if (!ai->GetActive()) { continue; } CGameArea* area = x850_world->GetArea(ai->GetAreaIdAlways()); CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded; if (area->IsPostConstructed()) { occState = area->GetPostConstructed()->x10dc_occlusionState; } if (occState != CGameArea::EOcclusionState::Occluded) { ai->Listen(pos, type); } } } } void CStateManager::ApplyKnockBack(CActor& actor, const CDamageInfo& info, const CDamageVulnerability& vuln, const zeus::CVector3f& pos, float dampen) { if (vuln.GetVulnerability(info.GetWeaponMode(), false) == EVulnerability::Deflect) { return; } const CHealthInfo* hInfo = actor.HealthInfo(*this); if (hInfo == nullptr) { return; } const float dampedPower = (1.f - dampen) * info.GetKnockBackPower(); if (const TCastToPtr player = actor) { KnockBackPlayer(*player, pos, dampedPower, hInfo->GetKnockbackResistance()); return; } const TCastToPtr ai = actor; if (!ai && hInfo->GetHP() <= 0.f) { if (dampedPower > hInfo->GetKnockbackResistance()) { if (const TCastToPtr physActor = actor) { const zeus::CVector3f kbVec = pos * (dampedPower - hInfo->GetKnockbackResistance()) * physActor->GetMass() * 1.5f; if (physActor->GetMaterialList().HasMaterial(EMaterialTypes::Immovable) || !physActor->GetMaterialList().HasMaterial(EMaterialTypes::Solid)) { return; } physActor->ApplyImpulseWR(kbVec, zeus::CAxisAngle()); return; } } } if (ai) { ai->KnockBack(pos, *this, info, dampen == 0.f ? EKnockBackType::Direct : EKnockBackType::Radius, false, dampedPower); } } void CStateManager::KnockBackPlayer(CPlayer& player, const zeus::CVector3f& pos, float power, float resistance) { if (player.GetMaterialList().HasMaterial(EMaterialTypes::Immovable)) { return; } float usePower; if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) { usePower = power * 1000.f; const auto surface = player.GetSurfaceRestraint(); if (surface != CPlayer::ESurfaceRestraints::Normal && player.GetOrbitState() == CPlayer::EPlayerOrbitState::NoOrbit) { usePower /= 7.f; } } else { usePower = power * 500.f; } const float minVel = player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed ? 35.f : 70.f; const float playerVel = player.x138_velocity.magnitude(); const float maxVel = std::max(playerVel, minVel); const zeus::CVector3f negVel = -player.x138_velocity; usePower *= (1.f - (0.5f * zeus::CVector3f::getAngleDiff(pos, negVel)) / M_PIF); player.ApplyImpulseWR(pos * usePower, zeus::CAxisAngle()); player.UseCollisionImpulses(); player.x2d4_accelerationChangeTimer = 0.25f; const float newVel = player.x138_velocity.magnitude(); if (newVel > maxVel) { const zeus::CVector3f vel = (1.f / newVel) * player.x138_velocity * maxVel; player.SetVelocityWR(vel); } } void CStateManager::ApplyDamageToWorld(TUniqueId damager, const CActor& actor, const zeus::CVector3f& pos, const CDamageInfo& info, const CMaterialFilter& filter) { const zeus::CAABox aabb(pos - info.GetRadius(), pos + info.GetRadius()); bool bomb = false; const TCastToConstPtr weapon = actor; if (weapon) { bomb = True(weapon->GetAttribField() & (EProjectileAttrib::Bombs | EProjectileAttrib::PowerBombs)); } EntityList nearList; BuildNearList(nearList, aabb, filter, &actor); for (const auto& id : nearList) { CEntity* ent = ObjectById(id); if (ent == nullptr) { continue; } const TCastToPtr player = ent; if (bomb && player) { if (player->GetFrozenState()) { g_GameState->SystemOptions().IncrementFrozenBallCount(); MP1::CSamusHud::DisplayHudMemo(u"", CHUDMemoParms{0.f, true, true, true}); player->UnFreeze(*this); } else { if ((weapon->GetAttribField() & EProjectileAttrib::Bombs) == EProjectileAttrib::Bombs) { player->BombJump(pos, *this); } } } else if (ent->GetUniqueId() != damager) { TestBombHittingWater(actor, pos, static_cast(*ent)); if (TestRayDamage(pos, static_cast(*ent), nearList)) { ApplyRadiusDamage(actor, pos, static_cast(*ent), info); } } if (const TCastToPtr swarm = ent) { swarm->ApplyRadiusDamage(pos, info, *this); } if (const TCastToPtr swarm = ent) { swarm->ApplyRadiusDamage(pos, info, *this); } } } void CStateManager::ProcessRadiusDamage(const CActor& damager, CActor& damagee, TUniqueId senderId, const CDamageInfo& info, const CMaterialFilter& filter) { const zeus::CAABox aabb(damager.GetTranslation() - info.GetRadius(), damager.GetTranslation() + info.GetRadius()); EntityList nearList; BuildNearList(nearList, aabb, filter, nullptr); for (const auto& id : nearList) { CEntity* ent = ObjectById(id); if (ent == nullptr || ent->GetUniqueId() == damager.GetUniqueId() || ent->GetUniqueId() == senderId || ent->GetUniqueId() == damagee.GetUniqueId()) { continue; } TestBombHittingWater(damager, damager.GetTranslation(), static_cast(*ent)); if (TestRayDamage(damager.GetTranslation(), static_cast(*ent), nearList)) { ApplyRadiusDamage(damager, damager.GetTranslation(), static_cast(*ent), info); } } } void CStateManager::ApplyRadiusDamage(const CActor& a1, const zeus::CVector3f& pos, CActor& a2, const CDamageInfo& info) { zeus::CVector3f delta = a2.GetTranslation() - pos; std::optional bounds; if (delta.magSquared() < info.GetRadius() * info.GetRadius() || ((bounds = a2.GetTouchBounds()) && CCollidableSphere::Sphere_AABox_Bool(zeus::CSphere{pos, info.GetRadius()}, *bounds))) { float rad = info.GetRadius(); if (rad > FLT_EPSILON) { rad = delta.magnitude() / rad; } else { rad = 0.f; } if (rad > 0.f) { delta.normalize(); } bool alive = false; if (const CHealthInfo* hInfo = a2.HealthInfo(*this)) { if (hInfo->GetHP() > 0.f) { alive = true; } } const CDamageVulnerability* vuln = rad > 0.f ? a2.GetDamageVulnerability(pos, delta, info) : a2.GetDamageVulnerability(); if (vuln->WeaponHurts(info.GetWeaponMode(), true)) { const float dam = info.GetRadiusDamage(*vuln); if (dam > 0.f) { ApplyLocalDamage(pos, delta, a2, dam, info.GetWeaponMode()); } a2.SendScriptMsgs(EScriptObjectState::Damage, *this, EScriptObjectMessage::None); SendScriptMsg(&a2, a1.GetUniqueId(), EScriptObjectMessage::Damage); } else { a2.SendScriptMsgs(EScriptObjectState::InvulnDamage, *this, EScriptObjectMessage::None); SendScriptMsg(&a2, a1.GetUniqueId(), EScriptObjectMessage::InvulnDamage); } if (alive && info.GetKnockBackPower() > 0.f) { ApplyKnockBack(a2, info, *vuln, (a2.GetTranslation() - a1.GetTranslation()).normalized(), rad); } } } bool CStateManager::TestRayDamage(const zeus::CVector3f& pos, const CActor& damagee, const EntityList& nearList) const { const CHealthInfo* hInfo = const_cast(damagee).HealthInfo(const_cast(*this)); if (hInfo == nullptr) { return false; } static constexpr CMaterialList incList(EMaterialTypes::Solid); static constexpr CMaterialList exList(EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Occluder, EMaterialTypes::Character); static constexpr CMaterialFilter filter(incList, exList, CMaterialFilter::EFilterType::IncludeExclude); const std::optional bounds = damagee.GetTouchBounds(); if (!bounds) { return false; } const zeus::CVector3f center = bounds->center(); zeus::CVector3f dir = center - pos; if (!dir.canBeNormalized()) { return true; } const float origMag = dir.magnitude(); dir = dir * (1.f / origMag); if (RayCollideWorld(pos, center, nearList, filter, &damagee)) { return true; } const zeus::CMRay ray(pos, dir, origMag); if (!MultiRayCollideWorld(ray, filter)) { return false; } float depth; zeus::CVector3f norm; const u32 count = CollisionUtil::RayAABoxIntersection(ray, *bounds, norm, depth); if (count == 0 || count == 1) { return true; } return CGameCollision::RayDynamicIntersectionBool(*this, pos, dir, filter, nearList, &damagee, depth * origMag); } bool CStateManager::RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end, const CMaterialFilter& filter, const CActor* damagee) const { zeus::CVector3f delta = end - start; const float mag = delta.magnitude(); delta = delta / mag; EntityList nearList; BuildNearList(nearList, start, delta, mag, filter, damagee); return RayCollideWorldInternal(start, end, filter, nearList, damagee); } bool CStateManager::RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end, const EntityList& nearList, const CMaterialFilter& filter, const CActor* damagee) const { return RayCollideWorldInternal(start, end, filter, nearList, damagee); } bool CStateManager::RayCollideWorldInternal(const zeus::CVector3f& start, const zeus::CVector3f& end, const CMaterialFilter& filter, const EntityList& nearList, const CActor* damagee) const { const zeus::CVector3f delta = end - start; if (!delta.canBeNormalized()) { return true; } const float mag = delta.magnitude(); const zeus::CVector3f dir = delta * (1.f / mag); if (!CGameCollision::RayStaticIntersectionBool(*this, start, dir, mag, filter)) { return false; } return CGameCollision::RayDynamicIntersectionBool(*this, start, dir, filter, nearList, damagee, mag); } bool CStateManager::MultiRayCollideWorld(const zeus::CMRay& ray, const CMaterialFilter& filter) const { zeus::CVector3f crossed = {-ray.dir.z() * ray.dir.z() - ray.dir.y() * ray.dir.x(), ray.dir.x() * ray.dir.x() - ray.dir.z() * ray.dir.y(), ray.dir.y() * ray.dir.y() - ray.dir.x() * -ray.dir.z()}; crossed.normalize(); const zeus::CVector3f crossed2 = ray.dir.cross(crossed) * 0.35355338f; const zeus::CVector3f negCrossed2 = -crossed2; const zeus::CVector3f rms = crossed * 0.35355338f; const zeus::CVector3f negRms = -rms; for (int i = 0; i < 4; ++i) { const zeus::CVector3f& useCrossed = (i & 2) != 0 ? negCrossed2 : crossed2; const zeus::CVector3f& useRms = (i & 1) != 0 ? rms : negRms; if (CGameCollision::RayStaticIntersectionBool(*this, ray.start + useCrossed + useRms, ray.dir, ray.length, filter)) { return true; } } return false; } void CStateManager::TestBombHittingWater(const CActor& damager, const zeus::CVector3f& pos, CActor& damagee) { const TCastToConstPtr wpn = damager; if (!wpn) { return; } if (False(wpn->GetAttribField() & (EProjectileAttrib::Bombs | EProjectileAttrib::PowerBombs))) { return; } const bool powerBomb = (wpn->GetAttribField() & EProjectileAttrib::PowerBombs) == EProjectileAttrib::PowerBombs; const TCastToPtr water = damagee; if (!water) { return; } const zeus::CAABox bounds = water->GetTriggerBoundsWR(); const zeus::CVector3f hitPos(pos.x(), pos.y(), bounds.max.z()); const float bombRad = powerBomb ? 4.f : 2.f; const float delta = bounds.max.z() - pos.dot(zeus::skUp); if (delta <= bombRad && delta > 0.f) { // Below surface const float rippleFactor = 1.f - delta / bombRad; if (x87c_fluidPlaneManager->GetLastRippleDeltaTime(damager.GetUniqueId()) >= 0.15f) { const float bombMag = powerBomb ? 1.f : 0.75f; const float mag = 0.6f * bombMag + 0.4f * bombMag * std::sin(2.f * M_PIF * rippleFactor * 0.25f); water->GetFluidPlane().AddRipple(mag, damager.GetUniqueId(), hitPos, *water, *this); } if (!powerBomb) { x87c_fluidPlaneManager->CreateSplash(damager.GetUniqueId(), *this, *water, hitPos, rippleFactor, true); } } else { // Above surface const float bombMag = powerBomb ? 2.f : 1.f; if (delta <= -bombMag || delta >= 0.f) { return; } const CRayCastResult res = RayStaticIntersection(pos, zeus::skDown, -delta, CMaterialFilter::skPassEverything); if (res.IsInvalid() && x87c_fluidPlaneManager->GetLastRippleDeltaTime(damager.GetUniqueId()) >= 0.15f) { // Not blocked by static geometry const float mag = 0.6f * bombMag + 0.4f * bombMag * std::sin(2.f * M_PIF * -delta / bombMag * 0.25f); water->GetFluidPlane().AddRipple(mag, damager.GetUniqueId(), hitPos, *water, *this); } } } bool CStateManager::ApplyLocalDamage(const zeus::CVector3f& pos, const zeus::CVector3f& dir, CActor& damagee, float dam, const CWeaponMode& weapMode) { CHealthInfo* hInfo = damagee.HealthInfo(*this); if (hInfo == nullptr || dam < 0.f) { return false; } if (hInfo->GetHP() <= 0.f) { return true; } float mulDam = dam; TCastToPtr player = damagee; CAi* ai = TCastToPtr(damagee).GetPtr(); if (ai == nullptr) { ai = TCastToPtr(damagee).GetPtr(); } if (player) { if (GetPlayerState()->CanTakeDamage()) { if (x870_cameraManager->IsInCinematicCamera() || (weapMode.GetType() == EWeaponType::Phazon && x8b8_playerState->HasPowerUp(CPlayerState::EItemType::PhazonSuit))) { return false; } if (g_GameState->GetHardMode()) { mulDam *= g_GameState->GetHardModeDamageMultiplier(); } float damReduction = 0.f; if (x8b8_playerState->HasPowerUp(CPlayerState::EItemType::VariaSuit)) { damReduction = g_tweakPlayer->GetVariaDamageReduction(); } if (x8b8_playerState->HasPowerUp(CPlayerState::EItemType::GravitySuit)) { damReduction = std::max(g_tweakPlayer->GetGravityDamageReduction(), damReduction); } if (x8b8_playerState->HasPowerUp(CPlayerState::EItemType::PhazonSuit)) { damReduction = std::max(g_tweakPlayer->GetPhazonDamageReduction(), damReduction); } mulDam = -(damReduction * mulDam - mulDam); } else { mulDam = 0.f; } } const float newHp = hInfo->GetHP() - mulDam; const bool significant = std::fabs(newHp - hInfo->GetHP()) >= 0.00001; hInfo->SetHP(newHp); if (player && GetPlayerState()->CanTakeDamage()) { player->TakeDamage(significant, pos, mulDam, weapMode.GetType(), *this); if (newHp <= 0.f) { x8b8_playerState->SetPlayerAlive(false); } } if (ai != nullptr) { if (significant) { ai->TakeDamage(dir, mulDam); } if (newHp <= 0.f) { ai->Death(*this, dir, EScriptObjectState::DeathRattle); } } return significant; } bool CStateManager::ApplyDamage(TUniqueId damagerId, TUniqueId damageeId, TUniqueId radiusSender, const CDamageInfo& info, const CMaterialFilter& filter, const zeus::CVector3f& knockbackVec) { CEntity* ent0 = ObjectById(damagerId); CEntity* ent1 = ObjectById(damageeId); const TCastToPtr damager = ent0; const TCastToPtr damagee = ent1; const bool isPlayer = TCastToPtr(ent1) != nullptr; if (damagee != nullptr) { if (CHealthInfo* hInfo = damagee->HealthInfo(*this)) { zeus::CVector3f position; zeus::CVector3f direction = zeus::skRight; const bool alive = hInfo->GetHP() > 0.f; if (damager) { position = damager->GetTranslation(); direction = damager->GetTransform().basis[1]; } const CDamageVulnerability* dVuln = (damager != nullptr || isPlayer) ? damagee->GetDamageVulnerability(position, direction, info) : damagee->GetDamageVulnerability(); if (info.GetWeaponMode().GetType() == EWeaponType::None || dVuln->WeaponHurts(info.GetWeaponMode(), false)) { if (info.GetDamage() > 0.f) { ApplyLocalDamage(position, direction, *damagee, info.GetDamage(), info.GetWeaponMode()); } damagee->SendScriptMsgs(EScriptObjectState::Damage, *this, EScriptObjectMessage::None); SendScriptMsg(damagee.GetPtr(), damagerId, EScriptObjectMessage::Damage); } else { damagee->SendScriptMsgs(EScriptObjectState::InvulnDamage, *this, EScriptObjectMessage::None); SendScriptMsg(damagee.GetPtr(), damagerId, EScriptObjectMessage::InvulnDamage); } if (alive && damager && info.GetKnockBackPower() > 0.f) { zeus::CVector3f delta = knockbackVec.isZero() ? (damagee->GetTranslation() - damager->GetTranslation()) : knockbackVec; delta.z() = 0.0001f; ApplyKnockBack(*damagee, info, *dVuln, delta.normalized(), 0.f); } } if (damager && info.GetRadius() > 0.f) { ProcessRadiusDamage(*damager, *damagee, radiusSender, info, filter); } if (const TCastToPtr swarm = ent1) { if (damager) { swarm->ApplyRadiusDamage(damager->GetTranslation(), info, *this); } } } return false; } void CStateManager::UpdateAreaSounds() { rstl::reserved_vector areas; for (CGameArea& area : *x850_world) { auto occState = CGameArea::EOcclusionState::Occluded; if (area.IsPostConstructed()) { occState = area.GetOcclusionState(); } if (occState == CGameArea::EOcclusionState::Visible) { areas.push_back(area.GetAreaId()); } } CSfxManager::SetActiveAreas(areas); } void CStateManager::FrameEnd() { CModel::FrameDone(); g_SimplePool->Flush(); } void CStateManager::ProcessPlayerInput() { if (x84c_player) { x84c_player->ProcessInput(xb54_finalInput, *this); } } void CStateManager::SetGameState(EGameState state) { if (x904_gameState == state) { return; } if (x904_gameState == EGameState::SoftPaused) { x850_world->SetLoadPauseState(false); } switch (state) { case EGameState::Running: if (x88c_rumbleManager->IsDisabled()) { x88c_rumbleManager->SetDisabled(false); } break; case EGameState::SoftPaused: if (!x88c_rumbleManager->IsDisabled()) { x88c_rumbleManager->SetDisabled(true); } x850_world->SetLoadPauseState(true); break; default: break; } x904_gameState = state; } static const CFinalInput s_DisabledFinalInput = {}; void CStateManager::ProcessInput(const CFinalInput& input) { if (input.ControllerIdx() == 0) { const CGameCamera* cam = x870_cameraManager->GetCurrentCamera(*this); bool disableInput = cam->x170_25_disablesInput; if (x84c_player->x9c6_29_disableInput) { disableInput = true; } if (disableInput) { xb54_finalInput = s_DisabledFinalInput; xb54_finalInput.x0_dt = input.DeltaTime(); } else { xb54_finalInput = input; } } x870_cameraManager->ProcessInput(input, *this); } void CStateManager::UpdateGraphicsTiming(float dt) { xf14_curTimeMod900 += dt; if (xf14_curTimeMod900 > 900.f) { xf14_curTimeMod900 -= 900.f; } } void CStateManager::Update(float dt) { MP1::CMain::UpdateDiscordPresence(GetWorld()->IGetStringTableAssetId()); CElementGen::SetGlobalSeed(x8d8_updateFrameIdx); CParticleElectric::SetGlobalSeed(x8d8_updateFrameIdx); CDecal::SetGlobalSeed(x8d8_updateFrameIdx); CProjectileWeapon::SetGlobalSeed(x8d8_updateFrameIdx); xf08_pauseHudMessage = {}; CScriptEffect::ResetParticleCounts(); UpdateThermalVisor(); UpdateGameState(); const bool dying = x84c_player->x9f4_deathTime > 0.f; if (x904_gameState == EGameState::Running) { if (!TCastToPtr(x870_cameraManager->GetCurrentCamera(*this))) { g_GameState->SetTotalPlayTime(g_GameState->GetTotalPlayTime() + dt); UpdateHintState(dt); } for (size_t i = 0; i < numCameraPasses; ++i) { xb84_camFilterPasses[i].Update(dt); xd14_camBlurPasses[i].Update(dt); } } if (x904_gameState != EGameState::Paused) { PreThinkObjects(dt); x87c_fluidPlaneManager->Update(dt); } if (x904_gameState == EGameState::Running) { if (!dying) { CDecalManager::Update(dt, *this); } UpdateSortedLists(); if (!dying) { MovePlatforms(dt); MoveActors(dt); } ProcessPlayerInput(); if (x904_gameState != EGameState::SoftPaused) { CGameCollision::Move(*this, *x84c_player, dt, nullptr); } UpdateSortedLists(); if (!dying) { CrossTouchActors(); } } else { ProcessPlayerInput(); } if (!dying && x904_gameState == EGameState::Running) { x884_actorModelParticles->Update(dt, *this); } if (x904_gameState == EGameState::Running || x904_gameState == EGameState::SoftPaused) { Think(dt); } if (x904_gameState != EGameState::SoftPaused) { x870_cameraManager->Update(dt, *this); } while (xf76_lastRelay != kInvalidUniqueId) { if (CEntity* ent = ObjectById(xf76_lastRelay)) { ent->Think(dt, *this); } else { xf76_lastRelay = kInvalidUniqueId; break; } } if (x904_gameState != EGameState::Paused) { PostUpdatePlayer(dt); } if (xf84_ == xf80_hudMessageFrameCount) { ShowPausedHUDMemo(xf88_, xf8c_); --xf84_; xf88_.Reset(); } if (!dying && x904_gameState == EGameState::Running && !x870_cameraManager->IsInCinematicCamera()) { UpdateEscapeSequenceTimer(dt); } x850_world->Update(dt); x88c_rumbleManager->Update(dt); if (!dying) { x880_envFxManager->Update(dt, *this); } UpdateAreaSounds(); xf94_24_readyToRender = true; if (xf94_27_inMapScreen) { if (const CHintOptions::SHintState* hint = g_GameState->HintOptions().GetCurrentDisplayedHint()) { if (hint->CanContinue()) { g_GameState->HintOptions().DismissDisplayedHint(); } } xf94_27_inMapScreen = false; } if (!m_warping) { g_GameState->CurrentWorldState().SetAreaId(x8cc_nextAreaId); x850_world->TravelToArea(x8cc_nextAreaId, *this, false); } ClearGraveyard(); ++x8d8_updateFrameIdx; } void CStateManager::UpdateGameState() { // Intentionally empty } void CStateManager::UpdateHintState(float dt) { CHintOptions& ho = g_GameState->HintOptions(); ho.Update(dt, *this); u32 nextHintIdx = -1; u32 pageIdx = -1; if (const CHintOptions::SHintState* state = ho.GetCurrentDisplayedHint()) { const CGameHintInfo::CGameHint& next = g_MemoryCardSys->GetHints()[ho.GetNextHintIdx()]; for (const CGameHintInfo::SHintLocation& loc : next.GetLocations()) { const auto& mwInfo = g_GameState->StateForWorld(loc.x0_mlvlId).MapWorldInfo(); mwInfo->SetIsMapped(loc.x8_areaId, true); } if (state->x4_time < next.GetTextTime()) { nextHintIdx = ho.GetNextHintIdx(); pageIdx = state->x4_time / 3.f; } } if (xeec_hintIdx != nextHintIdx || xef0_hintPeriods != pageIdx) { if (nextHintIdx == -1) { CHUDMemoParms memoInfo = {0.f, true, true, true}; MP1::CSamusHud::DisplayHudMemo(u"", memoInfo); } else { const CGameHintInfo::CGameHint& data = g_MemoryCardSys->GetHints()[nextHintIdx]; CHUDMemoParms memoInfo = {0.f, true, false, true}; MP1::CSamusHud::DeferHintMemo(data.GetStringID(), pageIdx, memoInfo); } xeec_hintIdx = nextHintIdx; xef0_hintPeriods = pageIdx; } } void CStateManager::PreThinkObjects(float dt) { if (x84c_player->x9f4_deathTime > 0.f) { x84c_player->DoPreThink(dt, *this); } else if (x904_gameState == EGameState::SoftPaused) { for (CEntity* ent : GetAllObjectList()) { if (const TCastToPtr effect = ent) { effect->PreThink(dt, *this); } } } else { for (CEntity* ent : GetAllObjectList()) { if (ent != nullptr && !GetCameraObjectList().GetObjectById(ent->GetUniqueId())) { ent->PreThink(dt, *this); } } } } void CStateManager::MovePlatforms(float dt) { for (CEntity* ent : GetPlatformAndDoorObjectList()) { if (ent == nullptr || !GetPlatformAndDoorObjectList().IsPlatform(*ent)) { continue; } auto& plat = static_cast(*ent); if (!plat.GetActive() || plat.GetMass() == 0.f) { continue; } CGameCollision::Move(*this, plat, dt, nullptr); } } void CStateManager::MoveActors(float dt) { for (CEntity* ent : GetPhysicsActorObjectList()) { if (ent == nullptr || !ent->GetActive()) { continue; } auto& physActor = static_cast(*ent); if (physActor.GetMass() == 0.f) { continue; } if (const TCastToPtr ai = physActor) { bool doThink = !xf94_29_cinematicPause; if (doThink && ai->GetAreaIdAlways() != kInvalidAreaId) { const CGameArea* area = x850_world->GetAreaAlways(ai->GetAreaIdAlways()); float occTime = 0.0f; if (area->IsPostConstructed()) { occTime = area->GetPostConstructed()->x10e4_occludedTime; } if (occTime > 5.f) { doThink = false; } } if (!doThink) { SendScriptMsgAlways(ai->GetUniqueId(), kInvalidUniqueId, EScriptObjectMessage::SuspendedMove); continue; } } if (x84c_player.get() != ent) { if (!GetPlatformAndDoorObjectList().IsPlatform(*ent)) { CGameCollision::Move(*this, physActor, dt, nullptr); } } } } void CStateManager::CrossTouchActors() { std::array visits{}; EntityList nearList; for (CEntity* ent : GetActorObjectList()) { if (ent == nullptr) { continue; } auto& actor = static_cast(*ent); if (!actor.GetActive() || !actor.GetCallTouch()) { continue; } const std::optional touchAABB = actor.GetTouchBounds(); if (!touchAABB) { continue; } CMaterialFilter filter = CMaterialFilter::skPassEverything; if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Trigger)) { filter = CMaterialFilter::MakeExclude(EMaterialTypes::Trigger); } nearList.clear(); BuildNearList(nearList, *touchAABB, filter, &actor); for (const auto& id : nearList) { auto* ent2 = static_cast(ObjectById(id)); if (!ent2) { continue; } const std::optional touchAABB2 = ent2->GetTouchBounds(); if (!ent2->GetActive() || !touchAABB2) { continue; } if (visits[ent2->GetUniqueId().Value()]) { continue; } if (touchAABB->intersects(*touchAABB2)) { actor.Touch(*ent2, *this); ent2->Touch(actor, *this); } visits[actor.GetUniqueId().Value()] = true; } } } void CStateManager::Think(float dt) { if (x84c_player->x9f4_deathTime > 0.f) { x84c_player->DoThink(dt, *this); return; } if (x904_gameState == EGameState::SoftPaused) { for (CEntity* ent : GetAllObjectList()) { if (const TCastToPtr effect = ent) { effect->Think(dt, *this); } } } else { for (CEntity* ent : GetAllObjectList()) { if (const TCastToPtr ai = ent) { bool doThink = !xf94_29_cinematicPause; if (doThink && ai->GetAreaIdAlways() != kInvalidAreaId) { const CGameArea* area = x850_world->GetAreaAlways(ai->GetAreaIdAlways()); float occTime = 0.0f; if (area->IsPostConstructed()) { occTime = area->GetPostConstructed()->x10e4_occludedTime; } if (occTime > 5.f) { doThink = false; } } if (!doThink) { continue; } } if (!GetCameraObjectList().GetObjectById(ent->GetUniqueId())) { ent->Think(dt, *this); } } } } void CStateManager::PostUpdatePlayer(float dt) { x84c_player->PostUpdate(dt, *this); } void CStateManager::ShowPausedHUDMemo(CAssetId strg, float time) { xf78_hudMessageTime = time; xf08_pauseHudMessage = strg; DeferStateTransition(EStateManagerTransition::MessageScreen); } void CStateManager::ClearGraveyard() { for (const auto& id : x854_objectGraveyard) { CEntity* ent = GetAllObjectList().GetValidObjectById(id); RemoveObject(id); std::default_delete()(ent); } x854_objectGraveyard.clear(); } void CStateManager::FrameBegin(s32 frameCount) { x8d4_inputFrameIdx = frameCount; CTexture::SetCurrentFrameCount(frameCount); CGraphicsPalette::SetCurrentFrameCount(frameCount); // SwapOutTexturesToARAM(2, 0x180000); } void CStateManager::InitializeState(CAssetId mlvlId, TAreaId aid, CAssetId mreaId) { const bool hadRandom = x900_activeRandom != nullptr; SetActiveRandomToDefault(); if (xb3c_initPhase == EInitPhase::LoadWorld) { CreateStandardGameObjects(); x850_world = std::make_unique(*g_SimplePool, *g_ResFactory, mlvlId); xb3c_initPhase = EInitPhase::LoadFirstArea; } if (xb3c_initPhase == EInitPhase::LoadFirstArea) { if (!x8f0_shadowTex.IsLoaded()) { return; } x8f0_shadowTex.GetObj(); if (!x850_world->CheckWorldComplete(this, aid, mreaId)) { return; } x8cc_nextAreaId = x850_world->x68_curAreaId; CGameArea* area = x850_world->x18_areas[x8cc_nextAreaId].get(); if (x850_world->ScheduleAreaToLoad(area, *this)) { area->StartStreamIn(*this); return; } xb3c_initPhase = EInitPhase::Done; } SetCurrentAreaId(x8cc_nextAreaId); g_GameState->CurrentWorldState().SetAreaId(x8cc_nextAreaId); x850_world->TravelToArea(x8cc_nextAreaId, *this, true); UpdateRoomAcoustics(x8cc_nextAreaId); for (CEntity* ent : GetAllObjectList()) { SendScriptMsg(ent, kInvalidUniqueId, EScriptObjectMessage::WorldInitialized); } for (CEntity* ent : GetAllObjectList()) { CScriptSpawnPoint* sp = TCastToPtr(ent); if (sp != nullptr && sp->x30_24_active && sp->FirstSpawn()) { const zeus::CTransform& xf = sp->GetTransform(); const zeus::CVector3f lookVec = xf.frontVector(); if (lookVec.canBeNormalized()) { const auto lookXf = zeus::lookAt(xf.origin, xf.origin + lookVec); x84c_player->Teleport(lookXf, *this, true); } if (!g_GameState->x228_25_initPowerupsAtFirstSpawn) { break; } g_GameState->x228_25_initPowerupsAtFirstSpawn = false; for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) { const auto iType = CPlayerState::EItemType(i); u32 spawnPu = sp->GetPowerup(iType); u32 statePu = x8b8_playerState->GetItemAmount(iType); if (statePu < spawnPu) { x8b8_playerState->AddPowerUp(iType, spawnPu - statePu); } spawnPu = sp->GetPowerup(iType); statePu = x8b8_playerState->GetItemAmount(iType); if (statePu < spawnPu) { x8b8_playerState->IncrPickup(iType, spawnPu - statePu); } } } } x84c_player->AsyncLoadSuit(*this); x870_cameraManager->ResetCameras(*this); if (!hadRandom) { ClearActiveRandom(); } else { SetActiveRandomToDefault(); } x880_envFxManager->AsyncLoadResources(*this); } void CStateManager::CreateStandardGameObjects() { const float height = g_tweakPlayer->GetPlayerHeight(); const float xyHe = g_tweakPlayer->GetPlayerXYHalfExtent(); const float stepUp = g_tweakPlayer->GetStepUpHeight(); const float stepDown = g_tweakPlayer->GetStepDownHeight(); const float ballRadius = g_tweakPlayer->GetPlayerBallHalfExtent(); const zeus::CAABox pBounds = {{-xyHe, -xyHe, 0.f}, {xyHe, xyHe, height}}; const auto q = zeus::CQuaternion::fromAxisAngle(zeus::CVector3f{0.f, 0.f, 1.f}, zeus::degToRad(129.6f)); x84c_player = std::make_unique( AllocateUniqueId(), zeus::CTransform(q), pBounds, g_tweakPlayerRes->xc4_ballTransitionsANCS, zeus::CVector3f{1.65f, 1.65f, 1.65f}, 200.f, stepUp, stepDown, ballRadius, CMaterialList(EMaterialTypes::Player, EMaterialTypes::Solid, EMaterialTypes::GroundCollider)); AddObject(*x84c_player); x870_cameraManager->CreateStandardCameras(*this); } CObjectList* CStateManager::ObjectListById(EGameObjectList type) { if (type == EGameObjectList::Invalid) { return nullptr; } return x808_objLists[int(type)].get(); } const CObjectList* CStateManager::GetObjectListById(EGameObjectList type) const { if (type == EGameObjectList::Invalid) { return nullptr; } return x808_objLists[int(type)].get(); } void CStateManager::RemoveObject(TUniqueId uid) { if (CEntity* ent = GetAllObjectList().GetValidObjectById(uid)) { if (ent->GetEditorId() != kInvalidEditorId) { const auto search = x890_scriptIdMap.equal_range(ent->GetEditorId()); for (auto it = search.first; it != search.second;) { if (it->second == uid) { it = x890_scriptIdMap.erase(it); continue; } ++it; } } if (ent->GetAreaIdAlways() != kInvalidAreaId) { CGameArea* area = x850_world->GetArea(ent->GetAreaIdAlways()); if (area->IsPostConstructed()) { area->GetAreaObjects()->RemoveObject(uid); } } if (const TCastToPtr act = ent) { x874_sortedListManager->Remove(act.GetPtr()); } } for (auto& list : x808_objLists) { list->RemoveObject(uid); } } void CStateManager::UpdateRoomAcoustics(TAreaId aid) { u32 updateCount = 0; std::array updates; for (CEntity* ent : GetAllObjectList()) { if (const TCastToPtr acoustics = ent) { if (acoustics->GetAreaIdAlways() != aid || !acoustics->GetActive()) { continue; } updates[updateCount++] = acoustics.GetPtr(); } if (updateCount >= updates.size()) { break; } } if (updateCount == 0) { CScriptRoomAcoustics::DisableAuxCallbacks(); return; } const auto idx = int(updateCount * x900_activeRandom->Float() * 0.99f); updates[idx]->EnableAuxCallbacks(); } void CStateManager::SetCurrentAreaId(TAreaId aid) { if (aid != x8cc_nextAreaId) { x8d0_prevAreaId = x8cc_nextAreaId; UpdateRoomAcoustics(aid); x8cc_nextAreaId = aid; } if (aid == kInvalidAreaId) { return; } if (x8c0_mapWorldInfo->IsAreaVisited(aid)) { return; } x8c0_mapWorldInfo->SetAreaVisited(aid, true); x850_world->IGetMapWorld()->RecalculateWorldSphere(*x8c0_mapWorldInfo, *x850_world); } void CStateManager::AreaUnloaded(TAreaId) { // Intentionally empty } void CStateManager::PrepareAreaUnload(TAreaId aid) { for (CEntity* ent : GetAllObjectList()) { if (const TCastToPtr door = ent) { if (door->IsConnectedToArea(*this, aid)) { door->ForceClosed(*this); } } } FreeScriptObjects(aid); } void CStateManager::AreaLoaded(TAreaId aid) { x8bc_mailbox->SendMsgs(aid, *this); x880_envFxManager->AreaLoaded(); } void CStateManager::BuildNearList(EntityList& listOut, const zeus::CVector3f& v1, const zeus::CVector3f& v2, float f1, const CMaterialFilter& filter, const CActor* actor) const { x874_sortedListManager->BuildNearList(listOut, v1, v2, f1, filter, actor); } void CStateManager::BuildColliderList(EntityList& listOut, const CActor& actor, const zeus::CAABox& aabb) const { x874_sortedListManager->BuildNearList(listOut, actor, aabb); } void CStateManager::BuildNearList(EntityList& listOut, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CActor* actor) const { x874_sortedListManager->BuildNearList(listOut, aabb, filter, actor); } void CStateManager::UpdateActorInSortedLists(CActor& act) { if (!act.GetUseInSortedLists() || !act.xe4_27_notInSortedLists) { return; } const std::optional aabb = CalculateObjectBounds(act); const bool actorInLists = x874_sortedListManager->ActorInLists(&act); if (actorInLists || aabb) { act.xe4_27_notInSortedLists = false; if (actorInLists) { if (!act.GetActive() || !aabb) { x874_sortedListManager->Remove(&act); } else { x874_sortedListManager->Move(&act, *aabb); } } else if (act.GetActive() && aabb) { x874_sortedListManager->Insert(&act, *aabb); } } } void CStateManager::UpdateSortedLists() { if (!x850_world) { return; } for (CEntity* actor : GetActorObjectList()) { UpdateActorInSortedLists(static_cast(*actor)); } } std::optional CStateManager::CalculateObjectBounds(const CActor& actor) { std::optional bounds = actor.GetTouchBounds(); if (bounds) { zeus::CAABox aabb; aabb.accumulateBounds(bounds->min); aabb.accumulateBounds(bounds->max); if (TCastToConstPtr physAct = actor) { const zeus::CAABox physAabb = physAct->GetBoundingBox(); aabb.accumulateBounds(physAabb.min); aabb.accumulateBounds(physAabb.max); } return aabb; } else { if (const TCastToConstPtr physAct = actor) { return physAct->GetBoundingBox(); } } return std::nullopt; } void CStateManager::AddObject(CEntity& ent) { if (ent.GetEditorId() != kInvalidEditorId) { x890_scriptIdMap.insert(std::make_pair(ent.GetEditorId(), ent.GetUniqueId())); } for (auto& list : x808_objLists) { list->AddObject(ent); } if (ent.GetAreaIdAlways() == kInvalidAreaId && x84c_player && ent.GetUniqueId() != x84c_player->GetUniqueId()) { ent.x4_areaId = x84c_player->GetAreaIdAlways(); } if (ent.GetAreaIdAlways() != kInvalidAreaId) { CGameArea* area = x850_world->GetArea(ent.GetAreaIdAlways()); if (area->IsPostConstructed()) { area->GetAreaObjects()->AddObject(ent); } } if (const TCastToPtr act = ent) { UpdateActorInSortedLists(*act.GetPtr()); } ent.AcceptScriptMsg(EScriptObjectMessage::Registered, kInvalidUniqueId, *this); if (ent.GetAreaIdAlways() != kInvalidAreaId && x850_world) { CGameArea* area = x850_world->GetArea(ent.GetAreaIdAlways()); if (area->IsValidated()) { SendScriptMsg(&ent, kInvalidUniqueId, EScriptObjectMessage::InitializedInArea); } } if (m_logScripting) { spdlog::info("Added '{}'", ent.GetName()); } } void CStateManager::AddObject(CEntity* ent) { if (!ent) { return; } AddObject(*ent); } CRayCastResult CStateManager::RayStaticIntersection(const zeus::CVector3f& pos, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter) const { return CGameCollision::RayStaticIntersection(*this, pos, dir, length, filter); } CRayCastResult CStateManager::RayWorldIntersection(TUniqueId& idOut, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter, const EntityList& list) const { return CGameCollision::RayWorldIntersection(*this, idOut, pos, dir, length, filter, list); } zeus::CVector3f CStateManager::Random2f(float scaleMin, float scaleMax) { zeus::CVector3f ret(x900_activeRandom->Float() - 0.5f, x900_activeRandom->Float() - 0.5f, 0.f); if (std::fabs(ret.x()) < 0.001f) { ret.x() = 0.001f; } ret.normalize(); return ret * ((scaleMax - scaleMin) * x900_activeRandom->Float() + scaleMin); } void CStateManager::UpdateObjectInLists(CEntity& ent) { for (auto& list : x808_objLists) { if (list->GetValidObjectById(ent.GetUniqueId())) { if (!list->IsQualified(ent)) { list->RemoveObject(ent.GetUniqueId()); } } if (!list->GetValidObjectById(ent.GetUniqueId())) { list->AddObject(ent); } } } TUniqueId CStateManager::AllocateUniqueId() { const s16 lastIndex = x0_nextFreeIndex; s16 ourIndex; do { ourIndex = x0_nextFreeIndex; x0_nextFreeIndex = (ourIndex + 1) & 0x3ff; if (x0_nextFreeIndex == lastIndex) { spdlog::fatal("Object list full!"); } } while (GetAllObjectList().GetObjectByIndex(ourIndex) != nullptr); x4_idxArr[ourIndex] = (x4_idxArr[ourIndex] + 1) & 0x3f; if (TUniqueId(ourIndex, x4_idxArr[ourIndex]) == kInvalidUniqueId) { x4_idxArr[ourIndex] = 0; } return TUniqueId(ourIndex, x4_idxArr[ourIndex]); } void CStateManager::DeferStateTransition(EStateManagerTransition t) { if (t == EStateManagerTransition::InGame) { if (xf90_deferredTransition != EStateManagerTransition::InGame) { x850_world->SetLoadPauseState(false); xf90_deferredTransition = EStateManagerTransition::InGame; } } else { if (xf90_deferredTransition == EStateManagerTransition::InGame) { x850_world->SetLoadPauseState(true); xf90_deferredTransition = t; } } } bool CStateManager::CanShowMapScreen() const { const CHintOptions::SHintState* curDispHint = g_GameState->HintOptions().GetCurrentDisplayedHint(); return curDispHint == nullptr || curDispHint->CanContinue(); } std::pair CStateManager::CalculateScanCompletionRate() const { u32 num = 0; u32 denom = 0; int idx = 0; for (const std::pair& scan : x8b8_playerState->GetScanTimes()) { const auto category = g_MemoryCardSys->GetScanStates()[idx++].second; if (category != CWorldSaveGameInfo::EScanCategory::None && category != CWorldSaveGameInfo::EScanCategory::Research) { ++denom; if (scan.second == 1.f) { ++num; } } } return {num, denom}; } void CStateManager::SetBossParams(TUniqueId bossId, float maxEnergy, u32 stringIdx) { xf18_bossId = bossId; xf1c_totalBossEnergy = maxEnergy; xf20_bossStringIdx = stringIdx - (g_Main->IsUSA() && !g_Main->IsTrilogy() ? 0 : 6); } float CStateManager::IntegrateVisorFog(float f) const { if (x8b8_playerState->GetActiveVisor(*this) == CPlayerState::EPlayerVisor::Scan) { return (1.f - x8b8_playerState->GetVisorTransitionFactor()) * f; } return f; } float CStateManager::g_EscapeShakeCountdown; bool CStateManager::g_EscapeShakeCountdownInit = false; void CStateManager::sub_80044098(const CCollisionResponseData& colRespData, const CRayCastResult& rayCast, TUniqueId uid, const CWeaponMode& weaponMode, u32 w1, u8 thermalFlags) { // TODO implement } const CGameArea* CStateManager::GetCurrentArea() const { if (x850_world != nullptr && x850_world->GetCurrentAreaId() != kInvalidAreaId) { return x850_world->GetAreaAlways(x850_world->GetCurrentAreaId()); } return nullptr; }; } // namespace metaforce ================================================ FILE: Runtime/CStateManager.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include "Runtime/CBasics.hpp" #include "Runtime/CRandom16.hpp" #include "Runtime/CSortedLists.hpp" #include "Runtime/CToken.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Camera/CCameraFilter.hpp" #include "Runtime/Camera/CCameraManager.hpp" #include "Runtime/Camera/CCameraShakeData.hpp" #include "Runtime/GameObjectLists.hpp" #include "Runtime/Input/CFinalInput.hpp" #include "Runtime/Input/CRumbleManager.hpp" #include "Runtime/Weapon/CWeaponMgr.hpp" #include "Runtime/World/CActorModelParticles.hpp" #include "Runtime/World/CAi.hpp" #include "Runtime/World/CEnvFxManager.hpp" #include "Runtime/World/CFluidPlaneManager.hpp" #include "Runtime/World/ScriptLoader.hpp" #include "Runtime/World/ScriptObjectSupport.hpp" #include "Runtime/World/CScriptMazeNode.hpp" #include #include #include namespace metaforce { class CActor; class CActorModelParticles; class CDamageInfo; class CEnvFxManager; class CFluidPlaneManager; class CLight; class CMapWorldInfo; class CMaterialFilter; class CObjectList; class CPlayer; class CPlayerState; class CProjectedShadow; class CScriptMailbox; class CRumbleManager; class CSortedListManager; class CTexture; class CWorld; class CScriptLayerManager; class CWorldTransManager; struct CFinalInput; namespace MP1 { class CMFGameLoader; } struct SScriptObjectStream { // CEntity* x0_obj; EScriptObjectType x0_type; u32 x4_position; u32 x8_length; }; struct SOnScreenTex { CAssetId x0_id; zeus::CVector2i x4_extent; zeus::CVector2i xc_origin; }; enum class EStateManagerTransition { InGame, MapScreen, PauseGame, LogBook, SaveGame, MessageScreen }; enum class EThermalDrawFlag { Hot, Cold, Bypass }; class CStateManager { friend class MP1::CMFGameLoader; public: enum class EGameState { Running, SoftPaused, Paused }; private: s16 x0_nextFreeIndex = 0; std::array x4_idxArr{}; /* std::unique_ptr x80c_allObjs; std::unique_ptr x814_actorObjs; std::unique_ptr x81c_physActorObjs; std::unique_ptr x824_cameraObjs; std::unique_ptr x82c_lightObjs; std::unique_ptr x834_listenAiObjs; std::unique_ptr x83c_aiWaypointObjs; std::unique_ptr x844_platformAndDoorObjs; */ std::array, 8> x808_objLists{ std::make_unique(EGameObjectList::All), std::make_unique(), std::make_unique(), std::make_unique(), std::make_unique(), std::make_unique(), std::make_unique(), std::make_unique(), }; std::unique_ptr x84c_player; std::unique_ptr x850_world; /* Used to be a list of 32-element reserved_vectors */ std::vector x854_objectGraveyard; struct CStateManagerContainer { CCameraManager x0_cameraManager; CSortedListManager x3c0_sortedListManager; CWeaponMgr xe3d8_weaponManager; CFluidPlaneManager xe3ec_fluidPlaneManager; CEnvFxManager xe510_envFxManager; CActorModelParticles xf168_actorModelParticles; CRumbleManager xf250_rumbleManager; u32 xf344_ = 0; rstl::reserved_vector xf370_; rstl::reserved_vector xf39c_renderLast; }; std::unique_ptr x86c_stateManagerContainer; CCameraManager* x870_cameraManager = nullptr; CSortedListManager* x874_sortedListManager = nullptr; CWeaponMgr* x878_weaponManager = nullptr; CFluidPlaneManager* x87c_fluidPlaneManager = nullptr; CEnvFxManager* x880_envFxManager = nullptr; CActorModelParticles* x884_actorModelParticles = nullptr; CRumbleManager* x88c_rumbleManager = nullptr; std::multimap x890_scriptIdMap; std::map x8a4_loadedScriptObjects; std::shared_ptr x8b8_playerState; std::shared_ptr x8bc_mailbox; std::shared_ptr x8c0_mapWorldInfo; std::shared_ptr x8c4_worldTransManager; std::shared_ptr x8c8_worldLayerState; TAreaId x8cc_nextAreaId = 0; TAreaId x8d0_prevAreaId = kInvalidAreaId; // u32 x8d0_extFrameIdx = 0; u32 x8d4_inputFrameIdx = 0; u32 x8d8_updateFrameIdx = 0; u32 x8dc_objectDrawToken = 0; std::vector x8e0_dynamicLights; TLockedToken x8f0_shadowTex; /* DefaultShadow in MiscData */ CRandom16 x8fc_random; CRandom16* x900_activeRandom = nullptr; EGameState x904_gameState = EGameState::Running; rstl::reserved_vector x90c_loaderFuncs; enum class EInitPhase { LoadWorld, LoadFirstArea, Done } xb3c_initPhase = EInitPhase::LoadWorld; std::set xb40_uniqueInstanceNames; CFinalInput xb54_finalInput; static constexpr size_t numCameraPasses = 9; std::array xb84_camFilterPasses; // size: 0x2c std::array xd14_camBlurPasses; // size: 0x34 s32 xeec_hintIdx = -1; u32 xef0_hintPeriods = 0; SOnScreenTex xef4_pendingScreenTex; CAssetId xf08_pauseHudMessage; float xf0c_escapeTimer = 0.f; float xf10_escapeTotalTime = 0.f; float xf14_curTimeMod900 = 0.f; TUniqueId xf18_bossId = kInvalidUniqueId; float xf1c_totalBossEnergy = 0.f; u32 xf20_bossStringIdx = 0; float xf24_thermColdScale1 = 0.f; float xf28_thermColdScale2 = 0.f; zeus::CVector2f xf2c_viewportScale = {1.f, 1.f}; EThermalDrawFlag xf34_thermalFlag = EThermalDrawFlag::Bypass; TUniqueId xf38_skipCineSpecialFunc = kInvalidUniqueId; std::list xf3c_activeFlickerBats; std::list xf54_activeParasites; TUniqueId xf6c_playerActorHead = kInvalidUniqueId; std::unique_ptr xf70_currentMaze; TUniqueId xf74_lastTrigger = kInvalidUniqueId; TUniqueId xf76_lastRelay = kInvalidUniqueId; float xf78_hudMessageTime = 0.f; CProjectedShadow* xf7c_projectedShadow = nullptr; u32 xf80_hudMessageFrameCount = 0; s32 xf84_ = -1; CAssetId xf88_; float xf8c_ = 0.f; EStateManagerTransition xf90_deferredTransition = EStateManagerTransition::InGame; bool xf94_24_readyToRender : 1 = false; bool xf94_25_quitGame : 1 = false; bool xf94_26_generatingObject : 1 = false; bool xf94_27_inMapScreen : 1 = false; bool xf94_28_inSaveUI : 1 = false; bool xf94_29_cinematicPause : 1 = false; bool xf94_30_fullThreat : 1 = false; bool m_warping = false; std::map> m_incomingConnections; bool m_logScripting = false; std::optional> m_logScriptingReference; void UpdateThermalVisor(); static void RendererDrawCallback(void*, void*, int); public: CStateManager(const std::weak_ptr&, const std::weak_ptr&, const std::weak_ptr&, const std::weak_ptr&, const std::weak_ptr&); ~CStateManager(); u32 GetInputFrameIdx() const { return x8d4_inputFrameIdx; } bool RenderLast(TUniqueId); void AddDrawableActorPlane(CActor& actor, const zeus::CPlane&, const zeus::CAABox& aabb) const; void AddDrawableActor(CActor& actor, const zeus::CVector3f& vec, const zeus::CAABox& aabb) const; bool SpecialSkipCinematic(); TAreaId GetVisAreaId() const; s32 GetWeaponIdCount(TUniqueId, EWeaponType) const; void RemoveWeaponId(TUniqueId, EWeaponType); void AddWeaponId(TUniqueId, EWeaponType); void UpdateEscapeSequenceTimer(float); float GetEscapeSequenceTimer() const { return xf0c_escapeTimer; } void ResetEscapeSequenceTimer(float); void SetupParticleHook(const CActor& actor) const; void MurderScriptInstanceNames(); std::string HashInstanceName(CInputStream& in); void SetActorAreaId(CActor& actor, TAreaId); void TouchSky() const; void TouchPlayerActor(); void DrawSpaceWarp(const zeus::CVector3f&, float) const; void DrawReflection(const zeus::CVector3f&); static void ReflectionDrawer(void*, const zeus::CVector3f&); void CacheReflection(); bool CanCreateProjectile(TUniqueId, EWeaponType, int) const; const std::vector& GetDynamicLightList() const { return x8e0_dynamicLights; } void BuildDynamicLightListForWorld(); void DrawDebugStuff() const; void RenderCamerasAndAreaLights(); void DrawE3DeathEffect(); void DrawAdditionalFilters(); zeus::CFrustum SetupDrawFrustum(const CViewport& vp) const; zeus::CFrustum SetupViewForDraw(const CViewport& vp) const; void ResetViewAfterDraw(const CViewport& backupViewport, const zeus::CTransform& backupViewMatrix) const; void DrawWorld(); void SetupFogForArea3XRange(TAreaId area) const; void SetupFogForArea(TAreaId area) const; void SetupFogForAreaNonCurrent(TAreaId area) const; void SetupFogForArea3XRange(const CGameArea& area) const; void SetupFogForArea(const CGameArea& area) const; void SetupFogForAreaNonCurrent(const CGameArea& area) const; bool SetupFogForDraw() const; void PreRender(); void GetCharacterRenderMaskAndTarget(bool thawed, int& mask, int& target) const; bool GetVisSetForArea(TAreaId, TAreaId, CPVSVisSet& setOut) const; void RecursiveDrawTree(TUniqueId); void SendScriptMsg(CEntity* dest, TUniqueId src, EScriptObjectMessage msg); void SendScriptMsg(TUniqueId dest, TUniqueId src, EScriptObjectMessage msg); void SendScriptMsg(TUniqueId src, TEditorId dest, EScriptObjectMessage msg, EScriptObjectState state); void SendScriptMsgAlways(TUniqueId dest, TUniqueId src, EScriptObjectMessage); void FreeScriptObjects(TAreaId); void FreeScriptObject(TUniqueId); std::pair GetBuildForScript(TEditorId) const; TEditorId GetEditorIdForUniqueId(TUniqueId) const; TUniqueId GetIdForScript(TEditorId) const; std::pair::const_iterator, std::multimap::const_iterator> GetIdListForScript(TEditorId) const; std::multimap::const_iterator GetIdListEnd() const { return x890_scriptIdMap.cend(); } void LoadScriptObjects(TAreaId, CInputStream& in, std::vector& idsOut); void InitializeScriptObjects(const std::vector& objIds); std::pair LoadScriptObject(TAreaId, EScriptObjectType, u32, CInputStream& in); std::pair GenerateObject(TEditorId); void InitScriptObjects(const std::vector& ids); void InformListeners(const zeus::CVector3f&, EListenNoiseType); void ApplyKnockBack(CActor& actor, const CDamageInfo& info, const CDamageVulnerability&, const zeus::CVector3f&, float); void KnockBackPlayer(CPlayer& player, const zeus::CVector3f& pos, float power, float resistance); void ApplyDamageToWorld(TUniqueId, const CActor&, const zeus::CVector3f&, const CDamageInfo& info, const CMaterialFilter&); void ProcessRadiusDamage(const CActor&, CActor&, TUniqueId senderId, const CDamageInfo& info, const CMaterialFilter&); void ApplyRadiusDamage(const CActor&, const zeus::CVector3f&, CActor&, const CDamageInfo& info); bool TestRayDamage(const zeus::CVector3f& pos, const CActor& damagee, const EntityList& nearList) const; bool RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end, const CMaterialFilter& filter, const CActor* damagee) const; bool RayCollideWorld(const zeus::CVector3f& start, const zeus::CVector3f& end, const EntityList& nearList, const CMaterialFilter& filter, const CActor* damagee) const; bool RayCollideWorldInternal(const zeus::CVector3f& start, const zeus::CVector3f& end, const CMaterialFilter& filter, const EntityList& nearList, const CActor* damagee) const; bool MultiRayCollideWorld(const zeus::CMRay& ray, const CMaterialFilter& filter) const; void TestBombHittingWater(const CActor& damager, const zeus::CVector3f& pos, CActor& damagee); bool ApplyLocalDamage(const zeus::CVector3f&, const zeus::CVector3f&, CActor&, float, const CWeaponMode&); bool ApplyDamage(TUniqueId damagerId, TUniqueId damageeId, TUniqueId radiusSender, const CDamageInfo& info, const CMaterialFilter& filter, const zeus::CVector3f& knockbackVec); void UpdateAreaSounds(); void FrameEnd(); void ProcessPlayerInput(); void SetGameState(EGameState state); EGameState GetGameState() const { return x904_gameState; } void ProcessInput(const CFinalInput& input); void UpdateGraphicsTiming(float dt); void Update(float dt); void UpdateGameState(); void UpdateHintState(float dt); void PreThinkObjects(float dt); void MovePlatforms(float dt); void MoveActors(float dt); void CrossTouchActors(); void Think(float dt); void PostUpdatePlayer(float dt); void ShowPausedHUDMemo(CAssetId strg, float time); void ClearGraveyard(); void FrameBegin(s32 frameCount); void InitializeState(CAssetId mlvlId, TAreaId aid, CAssetId mreaId); void CreateStandardGameObjects(); const std::unique_ptr& GetObjectList() const { return x808_objLists[0]; } CObjectList* ObjectListById(EGameObjectList type); const CObjectList* GetObjectListById(EGameObjectList type) const; void RemoveObject(TUniqueId); void UpdateRoomAcoustics(TAreaId); TAreaId GetNextAreaId() const { return x8cc_nextAreaId; } void SetCurrentAreaId(TAreaId); CEntity* ObjectById(TUniqueId uid) const { return GetAllObjectList().GetObjectById(uid); } const CEntity* GetObjectById(TUniqueId uid) const { return GetAllObjectList().GetObjectById(uid); } void AreaUnloaded(TAreaId); void PrepareAreaUnload(TAreaId); void AreaLoaded(TAreaId); void BuildNearList(EntityList& listOut, const zeus::CVector3f&, const zeus::CVector3f&, float, const CMaterialFilter&, const CActor*) const; void BuildColliderList(EntityList& listOut, const CActor&, const zeus::CAABox&) const; void BuildNearList(EntityList& listOut, const zeus::CAABox&, const CMaterialFilter&, const CActor*) const; void UpdateActorInSortedLists(CActor&); void UpdateSortedLists(); std::optional CalculateObjectBounds(const CActor&); void AddObject(CEntity&); void AddObject(CEntity*); CRayCastResult RayStaticIntersection(const zeus::CVector3f& pos, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter) const; CRayCastResult RayWorldIntersection(TUniqueId& idOut, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter, const EntityList& list) const; void UpdateObjectInLists(CEntity&); TUniqueId AllocateUniqueId(); void DeferStateTransition(EStateManagerTransition t); EStateManagerTransition GetDeferredStateTransition() const { return xf90_deferredTransition; } bool CanShowMapScreen() const; TUniqueId GetSkipCinematicSpecialFunction() const { return xf38_skipCineSpecialFunc; } void SetSkipCinematicSpecialFunction(TUniqueId id) { xf38_skipCineSpecialFunc = id; } float GetHUDMessageTime() const { return xf78_hudMessageTime; } u32 GetHUDMessageFrameCount() const { return xf80_hudMessageFrameCount; } CAssetId GetPauseHUDMessage() const { return xf08_pauseHudMessage; } void IncrementHUDMessageFrameCounter() { ++xf80_hudMessageFrameCount; } bool ShouldQuitGame() const { return xf94_25_quitGame; } void SetShouldQuitGame(bool should) { xf94_25_quitGame = should; } void SetInSaveUI(bool b) { xf94_28_inSaveUI = b; } bool GetInSaveUI() const { return xf94_28_inSaveUI; } void SetInMapScreen(bool b) { xf94_27_inMapScreen = b; } bool GetInMapScreen() const { return xf94_27_inMapScreen; } bool IsFullThreat() const { return xf94_30_fullThreat; } void SetIsFullThreat(bool v) { xf94_30_fullThreat = v; } const std::shared_ptr& GetPlayerState() const { return x8b8_playerState; } CRandom16* GetActiveRandom() { return x900_activeRandom; } const CRandom16* GetActiveRandom() const { return x900_activeRandom; } zeus::CVector3f Random2f(float scaleMin, float scaleMax); void SetActiveRandomToDefault() { x900_activeRandom = &x8fc_random; } void ClearActiveRandom() { x900_activeRandom = nullptr; } CRumbleManager& GetRumbleManager() { return *x88c_rumbleManager; } const CRumbleManager& GetRumbleManager() const { return *x88c_rumbleManager; } CCameraFilterPass& GetCameraFilterPass(int idx) { return xb84_camFilterPasses[idx]; } const CCameraFilterPass& GetCameraFilterPass(int idx) const { return xb84_camFilterPasses[idx]; } CCameraBlurPass& GetCameraBlurPass(int idx) { return xd14_camBlurPasses[idx]; } const CCameraBlurPass& GetCameraBlurPass(int idx) const { return xd14_camBlurPasses[idx]; } CEnvFxManager* GetEnvFxManager() { return x880_envFxManager; } const CEnvFxManager* GetEnvFxManager() const { return x880_envFxManager; } CWorld* GetWorld() { return x850_world.get(); } const CWorld* GetWorld() const { return x850_world.get(); } CScriptMailbox* GetMailbox() { return x8bc_mailbox.get(); } const CScriptMailbox* GetRelayTracker() const { return x8bc_mailbox.get(); } CCameraManager* GetCameraManager() const { return x870_cameraManager; } CFluidPlaneManager* GetFluidPlaneManager() const { return x87c_fluidPlaneManager; } CActorModelParticles* GetActorModelParticles() const { return x884_actorModelParticles; } const std::shared_ptr& MapWorldInfo() const { return x8c0_mapWorldInfo; } const std::shared_ptr& WorldTransManager() const { return x8c4_worldTransManager; } const std::shared_ptr& WorldLayerState() const { return x8c8_worldLayerState; } std::shared_ptr& WorldLayerState() { return x8c8_worldLayerState; } CPlayer& GetPlayer() const { return *x84c_player; } CPlayer* Player() const { return x84c_player.get(); } CObjectList& GetAllObjectList() const { return *x808_objLists[0]; } CActorList& GetActorObjectList() const { return static_cast(*x808_objLists[1]); } CPhysicsActorList& GetPhysicsActorObjectList() const { return static_cast(*x808_objLists[2]); } CGameCameraList& GetCameraObjectList() const { return static_cast(*x808_objLists[3]); } CGameLightList& GetLightObjectList() const { return static_cast(*x808_objLists[4]); } CListeningAiList& GetListeningAiObjectList() const { return static_cast(*x808_objLists[5]); } CAiWaypointList& GetAiWaypointObjectList() const { return static_cast(*x808_objLists[6]); } CPlatformAndDoorList& GetPlatformAndDoorObjectList() const { return static_cast(*x808_objLists[7]); } std::pair CalculateScanCompletionRate() const; void SetCurrentMaze(std::unique_ptr maze) { xf70_currentMaze = std::move(maze); } void ClearCurrentMaze() { xf70_currentMaze.reset(); } CMazeState* GetCurrentMaze() { return xf70_currentMaze.get(); } void SetLastTriggerId(TUniqueId uid) { xf74_lastTrigger = uid; } TUniqueId GetLastTriggerId() const { return xf74_lastTrigger; } void SetLastRelayId(TUniqueId uid) { xf76_lastRelay = uid; } TUniqueId* GetLastRelayIdPtr() { return &xf76_lastRelay; } TUniqueId GetLastRelayId() const { return xf76_lastRelay; } bool GetIsGeneratingObject() const { return xf94_26_generatingObject; } void SetIsGeneratingObject(bool gen) { xf94_26_generatingObject = gen; } EThermalDrawFlag GetThermalDrawFlag() const { return xf34_thermalFlag; } const CFinalInput& GetFinalInput() const { return xb54_finalInput; } void SetBossParams(TUniqueId bossId, float maxEnergy, u32 stringIdx); TUniqueId GetBossId() const { return xf18_bossId; } float GetTotalBossEnergy() const { return xf1c_totalBossEnergy; } u32 GetBossStringIdx() const { return xf20_bossStringIdx; } void SetPendingOnScreenTex(CAssetId texId, const zeus::CVector2i& origin, const zeus::CVector2i& extent) { xef4_pendingScreenTex.x0_id = texId; xef4_pendingScreenTex.x4_extent = origin; xef4_pendingScreenTex.xc_origin = extent; } const SOnScreenTex& GetPendingScreenTex() const { return xef4_pendingScreenTex; } void SetViewportScale(const zeus::CVector2f& scale) { xf2c_viewportScale = scale; } float GetThermalColdScale1() const { return xf24_thermColdScale1; } float GetThermalColdScale2() const { return xf28_thermColdScale2; } void SetThermalColdScale2(float s) { xf28_thermColdScale2 = s; } float IntegrateVisorFog(float f) const; u32 GetUpdateFrameIndex() const { return x8d8_updateFrameIdx; } void SetCinematicPause(bool p) { xf94_29_cinematicPause = p; } void QueueMessage(u32 frameCount, CAssetId msg, float f1) { xf84_ = frameCount; xf88_ = msg; xf8c_ = f1; } TUniqueId GetPlayerActorHead() const { return xf6c_playerActorHead; } void SetPlayerActorHead(TUniqueId id) { xf6c_playerActorHead = id; } std::list& GetActiveFlickerBats() { return xf3c_activeFlickerBats; } std::list& GetActiveParasites() { return xf54_activeParasites; } static float g_EscapeShakeCountdown; static bool g_EscapeShakeCountdownInit; void sub_80044098(const CCollisionResponseData& colRespData, const CRayCastResult& rayCast, TUniqueId uid, const CWeaponMode& weaponMode, u32 w1, u8 thermalFlags); const CGameArea* GetCurrentArea() const; void SetWarping(bool warp) { m_warping = warp; } }; } // namespace metaforce ================================================ FILE: Runtime/CStaticInterference.cpp ================================================ #include "Runtime/CStaticInterference.hpp" #include namespace metaforce { CStaticInterference::CStaticInterference(size_t sourceCount) { x0_sources.reserve(sourceCount); } void CStaticInterference::RemoveSource(TUniqueId id) { const auto iter = std::find_if(x0_sources.cbegin(), x0_sources.cend(), [id](const auto& src) { return src.x0_id == id; }); if (iter == x0_sources.cend()) { return; } x0_sources.erase(iter); } void CStaticInterference::Update(CStateManager&, float dt) { std::vector newSources; newSources.reserve(x0_sources.capacity()); for (CStaticInterferenceSource& src : x0_sources) { if (src.x8_timeLeft >= 0.f) { src.x8_timeLeft -= dt; newSources.push_back(src); } } x0_sources = std::move(newSources); } float CStaticInterference::GetTotalInterference() const { float validAccum = 0.f; float invalidAccum = 0.f; for (const CStaticInterferenceSource& src : x0_sources) { if (src.x0_id == kInvalidUniqueId) invalidAccum += src.x4_magnitude; else validAccum += src.x4_magnitude; } if (validAccum > 0.80000001f) validAccum = 0.80000001f; validAccum += invalidAccum; if (validAccum > 1.f) return 1.f; return validAccum; } void CStaticInterference::AddSource(TUniqueId id, float magnitude, float duration) { magnitude = zeus::clamp(0.f, magnitude, 1.f); const auto search = std::find_if(x0_sources.begin(), x0_sources.end(), [id](const CStaticInterferenceSource& source) { return source.x0_id == id; }); if (search != x0_sources.cend()) { search->x4_magnitude = magnitude; search->x8_timeLeft = duration; return; } if (x0_sources.size() < x0_sources.capacity()) { x0_sources.push_back({id, magnitude, duration}); } } } // namespace metaforce ================================================ FILE: Runtime/CStaticInterference.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class CStateManager; struct CStaticInterferenceSource { TUniqueId x0_id; float x4_magnitude; float x8_timeLeft; }; class CStaticInterference { std::vector x0_sources; public: explicit CStaticInterference(size_t sourceCount); void RemoveSource(TUniqueId id); void Update(CStateManager&, float dt); float GetTotalInterference() const; void AddSource(TUniqueId id, float magnitude, float duration); }; } // namespace metaforce ================================================ FILE: Runtime/CStopwatch.cpp ================================================ #include "Runtime/CStopwatch.hpp" namespace metaforce { CStopwatch CStopwatch::mGlobalTimer = {}; float CStopwatch::GetElapsedTime() const { return static_cast(std::chrono::duration_cast(Time::now() - m_startTime).count()) / 1000.f; } u16 CStopwatch::GetElapsedMicros() const { return std::chrono::duration_cast(Time::now() - m_startTime).count(); } u64 CStopwatch::GetCurMicros() const { return std::chrono::duration_cast(Time::now().time_since_epoch()).count(); } void CStopwatch::InitGlobalTimer() { mGlobalTimer.Reset(); } void CStopwatch::Reset() { m_startTime = std::chrono::steady_clock::now(); } void CStopwatch::Wait(float wait) { if (std::fabs(wait) < 0.001f) { wait = 0.f; } auto waitDur = FloatSeconds{wait}; while ((Time::now() - m_startTime) < waitDur) {} } } // namespace metaforce ================================================ FILE: Runtime/CStopwatch.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include #include namespace metaforce { class CStopwatch { private: static CStopwatch mGlobalTimer; using Time = std::chrono::steady_clock; using MicroSeconds = std::chrono::microseconds; using MilliSeconds = std::chrono::milliseconds; using FloatSeconds = std::chrono::duration; Time::time_point m_startTime; public: static void InitGlobalTimer(); static CStopwatch& GetGlobalTimerObj() { return mGlobalTimer; } static float GetGlobalTime() { return mGlobalTimer.GetElapsedTime(); } void Reset(); void Wait(float wait); float GetElapsedTime() const; u16 GetElapsedMicros() const; u64 GetCurMicros() const; }; } // namespace metaforce ================================================ FILE: Runtime/CStringExtras.cpp ================================================ #include "Runtime/CStringExtras.hpp" #include "Runtime/Streams/CInputStream.hpp" #include namespace { constexpr char32_t kReplacementChar = 0xFFFD; void AppendUTF8(char32_t codePoint, std::string& out) { if (codePoint <= 0x7F) { out.push_back(static_cast(codePoint)); } else if (codePoint <= 0x7FF) { out.push_back(static_cast(0xC0 | ((codePoint >> 6) & 0x1F))); out.push_back(static_cast(0x80 | (codePoint & 0x3F))); } else if (codePoint <= 0xFFFF) { out.push_back(static_cast(0xE0 | ((codePoint >> 12) & 0x0F))); out.push_back(static_cast(0x80 | ((codePoint >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (codePoint & 0x3F))); } else { out.push_back(static_cast(0xF0 | ((codePoint >> 18) & 0x07))); out.push_back(static_cast(0x80 | ((codePoint >> 12) & 0x3F))); out.push_back(static_cast(0x80 | ((codePoint >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (codePoint & 0x3F))); } } void AppendUTF16(char32_t codePoint, std::u16string& out) { if (codePoint <= 0xFFFF) { out.push_back(static_cast(codePoint)); return; } codePoint -= 0x10000; out.push_back(static_cast(0xD800 + (codePoint >> 10))); out.push_back(static_cast(0xDC00 + (codePoint & 0x3FF))); } } // namespace namespace metaforce { std::string CStringExtras::ReadString(CInputStream& in) { u32 strLen = in.ReadLong(); std::string ret; u32 readLen = 512; char tmp[512] = {}; for (; strLen > 0; strLen -= readLen) { readLen = 512; if (strLen <= 512) { readLen = strLen; } in.ReadBytes(tmp, readLen); ret.append(tmp, readLen); } return ret; } std::string CStringExtras::ConvertToANSI(std::u16string_view sv) { std::string out; out.reserve(sv.size()); for (const char16_t c : sv) { out.push_back(static_cast(c)); } return out; } std::u16string CStringExtras::ConvertToUNICODE(std::string_view sv) { std::u16string out; out.reserve(sv.size()); for (const char c : sv) { out.push_back(static_cast(c)); } return out; } std::string CStringExtras::ConvertToUTF8(std::u16string_view sv) { std::string out; out.reserve(sv.size()); for (size_t i = 0; i < sv.size(); ++i) { char32_t codePoint = sv[i]; if (codePoint >= 0xD800 && codePoint <= 0xDBFF) { if (i + 1 < sv.size()) { const char32_t low = sv[i + 1]; if (low >= 0xDC00 && low <= 0xDFFF) { codePoint = 0x10000 + ((codePoint - 0xD800) << 10) + (low - 0xDC00); ++i; } else { codePoint = kReplacementChar; } } else { codePoint = kReplacementChar; } } else if (codePoint >= 0xDC00 && codePoint <= 0xDFFF) { codePoint = kReplacementChar; } AppendUTF8(codePoint, out); } return out; } std::u16string CStringExtras::ConvertToUTF16(std::string_view sv) { std::u16string out; out.reserve(sv.size()); const auto* bytes = reinterpret_cast(sv.data()); const size_t len = sv.size(); size_t i = 0; while (i < len) { const uint8_t c0 = bytes[i++]; char32_t codePoint = kReplacementChar; if (c0 < 0x80) { codePoint = c0; } else if ((c0 & 0xE0) == 0xC0) { if (i < len) { const uint8_t c1 = bytes[i]; if ((c1 & 0xC0) == 0x80) { const char32_t cp = ((c0 & 0x1F) << 6) | (c1 & 0x3F); if (cp >= 0x80) { codePoint = cp; ++i; } } } } else if ((c0 & 0xF0) == 0xE0) { if (i + 1 < len) { const uint8_t c1 = bytes[i]; const uint8_t c2 = bytes[i + 1]; if ((c1 & 0xC0) == 0x80 && (c2 & 0xC0) == 0x80) { const char32_t cp = ((c0 & 0x0F) << 12) | ((c1 & 0x3F) << 6) | (c2 & 0x3F); if (cp >= 0x800 && !(cp >= 0xD800 && cp <= 0xDFFF)) { codePoint = cp; i += 2; } } } } else if ((c0 & 0xF8) == 0xF0) { if (i + 2 < len) { const uint8_t c1 = bytes[i]; const uint8_t c2 = bytes[i + 1]; const uint8_t c3 = bytes[i + 2]; if ((c1 & 0xC0) == 0x80 && (c2 & 0xC0) == 0x80 && (c3 & 0xC0) == 0x80) { const char32_t cp = ((c0 & 0x07) << 18) | ((c1 & 0x3F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); if (cp >= 0x10000 && cp <= 0x10FFFF) { codePoint = cp; i += 3; } } } } AppendUTF16(codePoint, out); } return out; } } // namespace metaforce ================================================ FILE: Runtime/CStringExtras.hpp ================================================ #pragma once #include #include #include #include #include namespace metaforce { class CInputStream; class CStringExtras { public: static std::string ConvertToANSI(std::u16string_view sv); static std::u16string ConvertToUNICODE(std::string_view sv); // Metaforce addition: UTF-8/16 compatible versions of the above static std::string ConvertToUTF8(std::u16string_view sv); static std::u16string ConvertToUTF16(std::string_view sv); // Checks if the provided views into string data can be considered equal or not based on // whether or not all their characters are equal to one another in a character insensitive manner. // // NOTE: This differs slightly from the actual version of this function within the game executable // in order to better accomodate string views and potentially non-null-terminated string data. // // In the game executable, the function essentially behaves like strcasecmp in that it returns // an int indicating whether or not the first argument is less than, equal to, // or greater than the second argument. Given no usages in the code depend on the less than or // greater than cases, but rather just care about whether or not the strings are equal to one // another, this is a safe change to make. // static bool CompareCaseInsensitive(std::string_view a, std::string_view b) { return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }); } static int IndexOfSubstring(std::string_view haystack, std::string_view needle) { std::string str(haystack); std::transform(str.begin(), str.end(), str.begin(), [](char c) { return std::tolower(static_cast(c)); }); const std::string::size_type s = str.find(needle); if (s == std::string::npos) { return -1; } return s; } static inline void ToLower(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), ::tolower); } static std::string ReadString(CInputStream& in); static inline bool ParseBool(std::string_view boolean, bool* valid) { std::string val(boolean); // compare must be case insensitive // This is the cleanest solution since I only need to do it once ToLower(val); // Check for true first if (val == "true" || val == "1" || val == "yes" || val == "on") { if (valid) *valid = true; return true; } // Now false if (val == "false" || val == "0" || val == "no" || val == "off") { if (valid) *valid = true; return false; } // Well that could've gone better if (valid) *valid = false; return false; } static inline std::vector& Split(std::string_view s, char delim, std::vector& elems) { std::string tmps(s); std::stringstream ss(tmps); std::string item; while (std::getline(ss, item, delim)) { elems.push_back(item); } return elems; } static inline std::vector Split(std::string_view s, char delim) { std::vector elems; Split(s, delim, elems); return elems; } static inline std::string LeftTrim(const std::string &s) { size_t start = s.find_first_not_of(" \n\r\t\f\v"); return (start == std::string::npos) ? "" : s.substr(start); } static inline std::string RightTrim(const std::string &s) { size_t end = s.find_last_not_of(" \n\r\t\f\v"); return (end == std::string::npos) ? "" : s.substr(0, end + 1); } static inline std::string Trim(const std::string &s) { return RightTrim(LeftTrim(s)); } }; } // namespace metaforce ================================================ FILE: Runtime/CTextureCache.cpp ================================================ #include "Runtime/CTextureCache.hpp" #include "Runtime/CToken.hpp" namespace metaforce { CTextureCache::CTextureCache(CInputStream& in) { u32 textureCount = in.ReadLong(); for (u32 i = 0; i < textureCount; ++i) { CAssetId uid(in); if (m_textureInfo.find(uid) == m_textureInfo.end()) m_textureInfo.emplace(uid, in.Get()); } } const CTextureInfo* CTextureCache::GetTextureInfo(CAssetId id) const { auto it = m_textureInfo.find(id); if (it == m_textureInfo.end()) return nullptr; return &it->second; } CFactoryFnReturn FTextureCacheFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in, [[maybe_unused]] const CVParamTransfer& vparms, [[maybe_unused]] CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/CTextureCache.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/Graphics/CTexture.hpp" #include namespace metaforce { class CPaletteInfo { u32 m_format; u32 m_elementCount; u64 m_dolphinHash; public: explicit CPaletteInfo(CInputStream& in) : m_format(in.ReadLong()), m_elementCount(in.ReadLong()), m_dolphinHash(in.ReadLongLong()) {} }; class CTextureInfo { ETexelFormat m_format; u32 m_mipCount; u16 m_width; u16 m_height; u64 m_dolphinHash; std::optional m_paletteInfo; public: explicit CTextureInfo(CInputStream& in) : m_format(ETexelFormat(in.ReadLong())) , m_mipCount(in.ReadLong()) , m_width(in.ReadShort()) , m_height(in.ReadShort()) , m_dolphinHash(in.ReadLongLong()) { bool hasPal = in.ReadBool(); if (hasPal) m_paletteInfo.emplace(in); } }; class CTextureCache { public: std::map m_textureInfo; public: explicit CTextureCache(CInputStream& in); const CTextureInfo* GetTextureInfo(CAssetId id) const; }; CFactoryFnReturn FTextureCacheFactory(const metaforce::SObjectTag& tag, CInputStream& in, const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/CTimeProvider.cpp ================================================ #include "Runtime/CTimeProvider.hpp" #include "Runtime/Graphics/CGraphics.hpp" namespace metaforce { static CTimeProvider* s_currentTimeProvider = nullptr; CTimeProvider::CTimeProvider(const float& time) : x0_currentTime(time), x8_lastProvider(s_currentTimeProvider) { if (x8_lastProvider != nullptr) { x8_lastProvider->x4_first = false; } s_currentTimeProvider = this; CGraphics::SetExternalTimeProvider(this); } CTimeProvider::~CTimeProvider() { s_currentTimeProvider = x8_lastProvider; if (s_currentTimeProvider != nullptr) { s_currentTimeProvider->x4_first = true; } CGraphics::SetExternalTimeProvider(s_currentTimeProvider); } } // namespace metaforce ================================================ FILE: Runtime/CTimeProvider.hpp ================================================ #pragma once namespace metaforce { class CTimeProvider { public: const float& x0_currentTime; // in seconds bool x4_first = true; CTimeProvider* x8_lastProvider = nullptr; CTimeProvider(const float& time); ~CTimeProvider(); float GetSecondsMod900() const { return x0_currentTime; } }; } // namespace metaforce ================================================ FILE: Runtime/CToken.cpp ================================================ #include "Runtime/CToken.hpp" namespace metaforce { u16 CObjectReference::RemoveReference() { --x0_refCount; if (x0_refCount == 0) { if (x10_object) Unload(); if (IsLoading()) CancelLoad(); if (xC_objectStore) xC_objectStore->ObjectUnreferenced(x4_objTag); } return x0_refCount; } CObjectReference::CObjectReference(IObjectStore& objStore, std::unique_ptr&& obj, const SObjectTag& objTag, CVParamTransfer buildParams) : x4_objTag(objTag), xC_objectStore(&objStore), x10_object(std::move(obj)), x14_params(std::move(buildParams)) {} CObjectReference::CObjectReference(std::unique_ptr&& obj) : x10_object(std::move(obj)) {} void CObjectReference::Unlock() { --x2_lockCount; if (x2_lockCount) return; if (x10_object && xC_objectStore) Unload(); else if (IsLoading()) CancelLoad(); } void CObjectReference::Lock() { ++x2_lockCount; if (!x10_object && !x3_loading) { IFactory& fac = xC_objectStore->GetFactory(); fac.BuildAsync(x4_objTag, x14_params, &x10_object, this); x3_loading = !x10_object.operator bool(); } } void CObjectReference::CancelLoad() { if ((xC_objectStore != nullptr) && IsLoading()) { xC_objectStore->GetFactory().CancelBuild(x4_objTag); x3_loading = false; } } void CObjectReference::Unload() { x10_object.reset(); x3_loading = false; } IObj* CObjectReference::GetObject() { if (!x10_object) { IFactory& factory = xC_objectStore->GetFactory(); x10_object = factory.Build(x4_objTag, x14_params, this); } x3_loading = false; return x10_object.get(); } CObjectReference::~CObjectReference() { if (x10_object) x10_object.reset(); else if (x3_loading) xC_objectStore->GetFactory().CancelBuild(x4_objTag); } void CToken::RemoveRef() { if (x0_objRef && x0_objRef->RemoveReference() == 0) { std::default_delete()(x0_objRef); x0_objRef = nullptr; } } CToken::CToken(CObjectReference* obj) { x0_objRef = obj; ++x0_objRef->x0_refCount; } void CToken::Unlock() { if (x0_objRef && x4_lockHeld) { x0_objRef->Unlock(); x4_lockHeld = false; } } void CToken::Lock() { if (x0_objRef && !x4_lockHeld) { x0_objRef->Lock(); x4_lockHeld = true; } } bool CToken::IsLoaded() const { if (!x0_objRef || !x4_lockHeld) return false; return x0_objRef->IsLoaded(); } IObj* CToken::GetObj() { if (!x0_objRef) return nullptr; Lock(); return x0_objRef->GetObject(); } CToken& CToken::operator=(const CToken& other) { if (this == &other) return *this; Unlock(); RemoveRef(); x0_objRef = other.x0_objRef; if (x0_objRef) { ++x0_objRef->x0_refCount; if (other.x4_lockHeld) Lock(); } return *this; } CToken& CToken::operator=(CToken&& other) noexcept { Unlock(); RemoveRef(); x0_objRef = other.x0_objRef; other.x0_objRef = nullptr; x4_lockHeld = other.x4_lockHeld; other.x4_lockHeld = false; return *this; } CToken::CToken(const CToken& other) : x0_objRef(other.x0_objRef) { if (x0_objRef) { ++x0_objRef->x0_refCount; if (other.x4_lockHeld) Lock(); } } CToken::CToken(CToken&& other) noexcept : x0_objRef(other.x0_objRef), x4_lockHeld(other.x4_lockHeld) { other.x0_objRef = nullptr; other.x4_lockHeld = false; } CToken::CToken(IObj* obj) { x0_objRef = new CObjectReference(std::unique_ptr(obj)); ++x0_objRef->x0_refCount; Lock(); } CToken::CToken(std::unique_ptr&& obj) { x0_objRef = new CObjectReference(std::move(obj)); ++x0_objRef->x0_refCount; Lock(); } const SObjectTag* CToken::GetObjectTag() const { if (!x0_objRef) return nullptr; return &x0_objRef->GetObjectTag(); } CToken::~CToken() { if (x0_objRef) { if (x4_lockHeld) x0_objRef->Unlock(); RemoveRef(); } } } // namespace metaforce ================================================ FILE: Runtime/CToken.hpp ================================================ #pragma once #include #include "Runtime/IFactory.hpp" #include "Runtime/IObj.hpp" #include "Runtime/IObjectStore.hpp" #include "Runtime/IVParamObj.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class IObjectStore; /** Shared data-structure for CToken references, analogous to std::shared_ptr */ class CObjectReference { friend class CSimplePool; friend class CToken; u16 x0_refCount = 0; u16 x2_lockCount = 0; bool x3_loading = false; /* Rightmost bit of lockCount */ SObjectTag x4_objTag; IObjectStore* xC_objectStore = nullptr; std::unique_ptr x10_object; CVParamTransfer x14_params; /** Mechanism by which CToken decrements 1st ref-count, indicating CToken invalidation or reset. * Reaching 0 indicates the CToken should delete the CObjectReference */ u16 RemoveReference(); CObjectReference(IObjectStore& objStore, std::unique_ptr&& obj, const SObjectTag& objTag, CVParamTransfer buildParams); CObjectReference(std::unique_ptr&& obj); /** Indicates an asynchronous load transaction has been submitted and is not yet finished */ bool IsLoading() const { return x3_loading; } /** Indicates an asynchronous load transaction has finished and object is completely loaded */ bool IsLoaded() const { return x10_object.operator bool(); } /** Decrements 2nd ref-count, performing unload or async-load-cancel if 0 reached */ void Unlock(); /** Increments 2nd ref-count, performing async-factory-load if needed */ void Lock(); void CancelLoad(); /** Pointer-synchronized object-destructor, another building Lock cycle may be performed after */ void Unload(); /** Synchronous object-fetch, guaranteed to return complete object on-demand, blocking build if not ready */ IObj* GetObject(); public: const SObjectTag& GetObjectTag() const { return x4_objTag; } ~CObjectReference(); }; /** Counted meta-object, reference-counting against a shared CObjectReference * This class is analogous to std::shared_ptr and C++11 rvalues have been implemented accordingly * (default/empty constructor, move constructor/assign) */ class CToken { friend class CModel; friend class CSimplePool; CObjectReference* x0_objRef = nullptr; bool x4_lockHeld = false; void RemoveRef(); CToken(CObjectReference* obj); public: /* Added to test for non-null state */ explicit operator bool() const { return HasReference(); } bool HasReference() const { return x0_objRef != nullptr; } void Unlock(); void Lock(); bool IsLocked() const { return x4_lockHeld; } bool IsLoaded() const; IObj* GetObj(); const IObj* GetObj() const { return const_cast(this)->GetObj(); } CToken& operator=(const CToken& other); CToken& operator=(CToken&& other) noexcept; CToken() = default; CToken(const CToken& other); CToken(CToken&& other) noexcept; CToken(IObj* obj); CToken(std::unique_ptr&& obj); const SObjectTag* GetObjectTag() const; const CObjectReference* GetObjectReference() const { return x0_objRef; } ~CToken(); }; template class TToken : public CToken { public: static std::unique_ptr> GetIObjObjectFor(std::unique_ptr&& obj) { return TObjOwnerDerivedFromIObj::GetNewDerivedObject(std::move(obj)); } TToken() = default; virtual ~TToken() = default; TToken(const CToken& other) : CToken(other) {} TToken(CToken&& other) : CToken(std::move(other)) {} TToken(std::unique_ptr&& obj) : CToken(GetIObjObjectFor(std::move(obj))) {} TToken& operator=(std::unique_ptr&& obj) { *this = CToken(GetIObjObjectFor(std::move(obj))); return *this; } virtual void Unlock() { CToken::Unlock(); } virtual void Lock() { CToken::Lock(); } virtual T* GetObj() { TObjOwnerDerivedFromIObj* owner = static_cast*>(CToken::GetObj()); if (owner) return owner->GetObj(); return nullptr; } virtual const T* GetObj() const { return const_cast*>(this)->GetObj(); } virtual TToken& operator=(const CToken& other) { CToken::operator=(other); return *this; } T* operator->() { return GetObj(); } const T* operator->() const { return GetObj(); } T& operator*() { return *GetObj(); } const T& operator*() const { return *GetObj(); } }; template class TCachedToken : public TToken { protected: T* m_obj = nullptr; public: TCachedToken() = default; TCachedToken(const CToken& other) : TToken(other) {} TCachedToken(CToken&& other) : TToken(std::move(other)) {} T* GetObj() override { if (!m_obj) m_obj = TToken::GetObj(); return m_obj; } const T* GetObj() const override { return const_cast*>(this)->GetObj(); } void Unlock() override { TToken::Unlock(); m_obj = nullptr; } TCachedToken& operator=(const TCachedToken& other) { TToken::operator=(other); m_obj = nullptr; return *this; } TCachedToken& operator=(const CToken& other) override { TToken::operator=(other); m_obj = nullptr; return *this; } bool IsNull() const { return m_obj == nullptr; } }; template class TLockedToken : public TCachedToken { public: TLockedToken() = default; TLockedToken(const TLockedToken& other) : TCachedToken(other) { CToken::Lock(); } TLockedToken& operator=(const TLockedToken& other) { CToken oldTok = std::move(*this); TCachedToken::operator=(other); CToken::Lock(); return *this; } TLockedToken(const CToken& other) : TCachedToken(other) { CToken::Lock(); } TLockedToken& operator=(const CToken& other) override { CToken oldTok = std::move(*this); TCachedToken::operator=(other); CToken::Lock(); return *this; } TLockedToken(CToken&& other) { CToken oldTok = std::move(*this); *this = TCachedToken(std::move(other)); CToken::Lock(); } }; } // namespace metaforce ================================================ FILE: Runtime/CWorldSaveGameInfo.cpp ================================================ #include "Runtime/CWorldSaveGameInfo.hpp" #include "Runtime/CToken.hpp" namespace metaforce { CWorldSaveGameInfo::CWorldSaveGameInfo(CInputStream& in) { in.ReadLong(); const u32 version = in.ReadLong(); if (version > 1) { x0_areaCount = in.ReadLong(); } if (version > 2) { const u32 cinematicCount = in.ReadLong(); x4_cinematics.reserve(cinematicCount); for (u32 i = 0; i < cinematicCount; ++i) { x4_cinematics.emplace_back(in.ReadLong()); } const u32 relayCount = in.ReadLong(); x14_relays.reserve(relayCount); for (u32 i = 0; i < relayCount; ++i) { x14_relays.emplace_back(in.ReadLong()); } } const u32 layerCount = in.ReadLong(); x24_layers.reserve(layerCount); for (u32 i = 0; i < layerCount; ++i) { SLayerState& st = x24_layers.emplace_back(); st.x0_area = in.ReadLong(); st.x4_layer = in.ReadLong(); } const u32 doorCount = in.ReadLong(); x34_doors.reserve(doorCount); for (u32 i = 0; i < doorCount; ++i) { x34_doors.emplace_back(in.ReadLong()); } if (version <= 0) { return; } const u32 scanCount = in.ReadLong(); x44_scans.reserve(scanCount); for (u32 i = 0; i < scanCount; ++i) { SScanState& st = x44_scans.emplace_back(); st.x0_id = in.Get(); st.x4_category = EScanCategory(in.ReadLong()); } } u32 CWorldSaveGameInfo::GetAreaCount() const { return x0_areaCount; } u32 CWorldSaveGameInfo::GetCinematicCount() const { return x4_cinematics.size(); } s32 CWorldSaveGameInfo::GetCinematicIndex(const TEditorId& id) const { auto it = std::find(x4_cinematics.begin(), x4_cinematics.end(), id); if (it == x4_cinematics.end()) return -1; return it - x4_cinematics.begin(); } u32 CWorldSaveGameInfo::GetRelayCount() const { return x14_relays.size(); } s32 CWorldSaveGameInfo::GetRelayIndex(const TEditorId& id) const { auto it = std::find(x14_relays.begin(), x14_relays.end(), id); if (it == x14_relays.end()) return -1; return it - x14_relays.begin(); } TEditorId CWorldSaveGameInfo::GetRelayEditorId(u32 idx) const { return x14_relays[idx]; } u32 CWorldSaveGameInfo::GetDoorCount() const { return x34_doors.size(); } s32 CWorldSaveGameInfo::GetDoorIndex(const TEditorId& id) const { auto it = std::find(x34_doors.begin(), x34_doors.end(), id); if (it == x34_doors.end()) return -1; return it - x34_doors.begin(); } CFactoryFnReturn FWorldSaveGameInfoFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in, [[maybe_unused]] const CVParamTransfer& param, [[maybe_unused]] CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/CWorldSaveGameInfo.hpp ================================================ #pragma once #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CWorldSaveGameInfo { public: enum class EScanCategory { None, Data, Lore, Creature, Research, Artifact }; struct SScanState { CAssetId x0_id; EScanCategory x4_category; }; struct SLayerState { TAreaId x0_area; u32 x4_layer; }; private: u32 x0_areaCount; std::vector x4_cinematics; std::vector x14_relays; std::vector x24_layers; std::vector x34_doors; std::vector x44_scans; public: explicit CWorldSaveGameInfo(CInputStream& in); u32 GetAreaCount() const; u32 GetCinematicCount() const; s32 GetCinematicIndex(const TEditorId& id) const; const std::vector& GetCinematics() const { return x4_cinematics; } const std::vector& GetDoors() const { return x34_doors; } const std::vector& GetScans() const { return x44_scans; } u32 GetRelayCount() const; s32 GetRelayIndex(const TEditorId& id) const; TEditorId GetRelayEditorId(u32 idx) const; u32 GetDoorCount() const; s32 GetDoorIndex(const TEditorId& id) const; }; CFactoryFnReturn FWorldSaveGameInfoFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& param, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Camera/CBallCamera.cpp ================================================ #include "Runtime/Camera/CBallCamera.hpp" #include #include #include "Runtime/CStateManager.hpp" #include "Runtime/Camera/CFirstPersonCamera.hpp" #include "Runtime/Camera/CPathCamera.hpp" #include "Runtime/Collision/CCollisionActor.hpp" #include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Input/ControlMapper.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptCameraHint.hpp" #include "Runtime/World/CScriptDock.hpp" #include "Runtime/World/CScriptDoor.hpp" #include "Runtime/World/CScriptSpindleCamera.hpp" #include "Runtime/World/CScriptWater.hpp" #include "Runtime/rstl.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { void CCameraSpring::Reset() { x4_k2Sqrt = 2.f * std::sqrt(x0_k); x10_dx = 0.f; } float CCameraSpring::ApplyDistanceSpringNoMax(float targetX, float curX, float dt) { float useX = xc_tardis * x10_dx * dt + curX; x10_dx += xc_tardis * (x0_k * (targetX - curX) - x4_k2Sqrt * x10_dx) * dt; return std::max(useX, targetX); } float CCameraSpring::ApplyDistanceSpring(float targetX, float curX, float dt) { float useX = xc_tardis * x10_dx * dt + curX; x10_dx += xc_tardis * (x0_k * (targetX - curX) - x4_k2Sqrt * x10_dx) * dt; useX = std::max(useX, targetX); if (useX - targetX > x8_max) { useX = targetX + x8_max; } return useX; } CBallCamera::CBallCamera(TUniqueId uid, TUniqueId watchedId, const zeus::CTransform& xf, float fovy, float znear, float zfar, float aspect) : CGameCamera(uid, true, "Ball Camera", CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList), xf, fovy, znear, zfar, aspect, watchedId, false, 0) , x214_ballCameraSpring(g_tweakBall->GetBallCameraSpringConstant(), g_tweakBall->GetBallCameraSpringMax(), g_tweakBall->GetBallCameraSpringTardis()) , x228_ballCameraCentroidSpring(g_tweakBall->GetBallCameraCentroidSpringConstant(), g_tweakBall->GetBallCameraCentroidSpringMax(), g_tweakBall->GetBallCameraCentroidSpringTardis() * 1.1f) , x23c_ballCameraLookAtSpring(g_tweakBall->GetBallCameraLookAtSpringConstant(), g_tweakBall->GetBallCameraLookAtSpringMax(), g_tweakBall->GetBallCameraLookAtSpringTardis()) , x250_ballCameraCentroidDistanceSpring(g_tweakBall->GetBallCameraCentroidDistanceSpringConstant(), g_tweakBall->GetBallCameraCentroidDistanceSpringMax(), g_tweakBall->GetBallCameraCentroidDistanceSpringTardis()) , x41c_ballCameraChaseSpring(g_tweakBall->GetBallCameraChaseSpringConstant(), g_tweakBall->GetBallCameraChaseSpringMax(), g_tweakBall->GetBallCameraChaseSpringTardis()) , x448_ballCameraBoostSpring(g_tweakBall->GetBallCameraBoostSpringConstant(), g_tweakBall->GetBallCameraBoostSpringMax(), g_tweakBall->GetBallCameraBoostSpringTardis()) { x190_curMinDistance = g_tweakBall->GetBallCameraMinSpeedDistance(); x194_targetMinDistance = g_tweakBall->GetBallCameraMinSpeedDistance(); x198_maxDistance = g_tweakBall->GetBallCameraMaxSpeedDistance(); x19c_backwardsDistance = g_tweakBall->GetBallCameraBackwardsDistance(); x1a0_elevation = g_tweakBall->GetBallCameraElevation(); x1a4_curAnglePerSecond = g_tweakBall->GetBallCameraAnglePerSecond(); x1a8_targetAnglePerSecond = g_tweakBall->GetBallCameraAnglePerSecond(); x1b4_lookAtOffset = g_tweakBall->GetBallCameraOffset(); x404_chaseElevation = g_tweakBall->GetBallCameraChaseElevation(); x408_chaseDistance = g_tweakBall->GetBallCameraChaseDistance(); x40c_chaseAnglePerSecond = g_tweakBall->GetBallCameraChaseAnglePerSecond(); x410_chaseLookAtOffset = g_tweakBall->GetBallCameraChaseLookAtOffset(); x430_boostElevation = g_tweakBall->GetBallCameraBoostElevation(); x434_boostDistance = g_tweakBall->GetBallCameraBoostDistance(); x438_boostAnglePerSecond = g_tweakBall->GetBallCameraBoostAnglePerSecond(); x43c_boostLookAtOffset = g_tweakBall->GetBallCameraBoostLookAtOffset(); x468_conservativeDoorCamDistance = g_tweakBall->GetConservativeDoorCameraDistance(); x47c_failsafeState = std::make_unique(); x480_ = std::make_unique(); SetupColliders(x264_smallColliders, 2.31f, 2.31f, 0.1f, 3, 2.f, 0.5f, -M_PIF / 2.f); SetupColliders(x274_mediumColliders, 4.62f, 4.62f, 0.1f, 6, 2.f, 0.5f, -M_PIF / 2.f); SetupColliders(x284_largeColliders, 7.f, 7.f, 0.1f, 12, 2.f, 0.5f, -M_PIF / 2.f); } void CBallCamera::SetupColliders(std::vector& out, float xMag, float zMag, float radius, int count, float k, float max, float startAngle) { out.reserve(count); float theta = startAngle; for (int i = 0; i < count; ++i) { float z = std::cos(theta) * zMag; if (theta > M_PIF / 2.f) { z *= 0.25f; } out.emplace_back(radius, zeus::CVector3f{xMag * std::sin(theta), 0.f, z}, CCameraSpring{k, max, 1.f}, 1.f); theta += 2.f * M_PIF / float(count); } } void CBallCamera::Accept(IVisitor& visitor) { visitor.Visit(this); } void CBallCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) { CGameCamera::AcceptScriptMsg(msg, objId, stateMgr); switch (msg) { case EScriptObjectMessage::Registered: { x46c_collisionActorId = stateMgr.AllocateUniqueId(); auto* colAct = new CCollisionActor(x46c_collisionActorId, GetAreaId(), kInvalidUniqueId, true, 0.3f, 1.f, "BallCamera"sv); colAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::Player, EMaterialTypes::CameraPassthrough})); colAct->SetMaterialList({EMaterialTypes::ProjectilePassthrough, EMaterialTypes::ScanPassthrough, EMaterialTypes::SeeThrough, EMaterialTypes::CameraPassthrough}); colAct->SetTranslation(GetTranslation()); stateMgr.AddObject(colAct); colAct->SetMovable(false); CMotionState mState(GetTranslation(), zeus::CNUQuaternion::fromAxisAngle(zeus::skForward, 0.f), zeus::skZero3f, zeus::CAxisAngle()); colAct->SetLastNonCollidingState(mState); SetMaterialFilter(CMaterialFilter::MakeIncludeExclude( {}, {EMaterialTypes::Solid, EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CameraPassthrough})); RemoveMaterial(EMaterialTypes::Solid, stateMgr); break; } case EScriptObjectMessage::Deleted: stateMgr.FreeScriptObject(x46c_collisionActorId); x46c_collisionActorId = kInvalidUniqueId; break; default: break; } } void CBallCamera::ProcessInput(const CFinalInput& input, CStateManager& mgr) { if (input.ControllerIdx() != 0) { return; } if (TCastToConstPtr player = mgr.GetObjectById(xe8_watchedObject)) { if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { switch (x400_state) { case EBallCameraState::Chase: if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::ChaseCamera, input) || player->IsInFreeLook()) { SetState(EBallCameraState::Default, mgr); } break; case EBallCameraState::Boost: if (!player->GetMorphBall()->IsInBoost()) { SetState(EBallCameraState::Default, mgr); } break; case EBallCameraState::Default: if (x18c_25_chaseAllowed && ControlMapper::GetPressInput(ControlMapper::ECommands::ChaseCamera, input)) { SetState(EBallCameraState::Chase, mgr); } break; default: break; } if (x18c_26_boostAllowed && x400_state != EBallCameraState::Boost && (player->GetMorphBall()->IsInBoost() || player->GetMorphBall()->GetBoostChargeTime() > 0.f)) { SetState(EBallCameraState::Boost, mgr); } } } } void CBallCamera::Reset(const zeus::CTransform& xf, CStateManager& mgr) { x214_ballCameraSpring.Reset(); x228_ballCameraCentroidSpring.Reset(); x23c_ballCameraLookAtSpring.Reset(); x250_ballCameraCentroidDistanceSpring.Reset(); x41c_ballCameraChaseSpring.Reset(); x448_ballCameraBoostSpring.Reset(); zeus::CVector3f desiredPos = FindDesiredPosition(x190_curMinDistance, x1a0_elevation, xf.basis[1], mgr, false); if (TCastToConstPtr player = mgr.GetObjectById(xe8_watchedObject)) { ResetPosition(mgr); x310_idealLookVec = x1b4_lookAtOffset; x31c_predictedLookPos = x1d8_lookPos; if ((x1d8_lookPos - desiredPos).canBeNormalized()) { TeleportCamera(zeus::lookAt(desiredPos, x1d8_lookPos), mgr); } else { zeus::CTransform camXf = player->CreateTransformFromMovementDirection(); camXf.origin = desiredPos; TeleportCamera(camXf, mgr); mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId()); } x2e8_ballVelFlat = 0.f; x2ec_maxBallVel = 0.f; x190_curMinDistance = x194_targetMinDistance; x2fc_ballDeltaFlat = zeus::skZero3f; x2f0_ballDelta = zeus::skZero3f; x18d_28_obtuseDirection = false; x308_speedFactor = 0.f; x2dc_prevBallPos = player->GetBallPosition(); x294_dampedPos = GetTranslation(); x2a0_smallCentroid = zeus::skZero3f; x2ac_mediumCentroid = zeus::skZero3f; x2b8_largeCentroid = zeus::skZero3f; x2c4_smallCollidersObsCount = 0; x2c8_mediumCollidersObsCount = 0; x2cc_largeCollidersObsCount = 0; x2d0_smallColliderIt = 0; x2d4_mediumColliderIt = 0; x2d8_largeColliderIt = 0; x32c_colliderMag = 1.f; x18d_25_avoidGeometryFull = true; x18d_27_forceProcessing = true; Think(0.1f, mgr); x18d_25_avoidGeometryFull = false; x18d_27_forceProcessing = false; } } void CBallCamera::Render(CStateManager& mgr) { // Empty } void CBallCamera::SetState(EBallCameraState state, CStateManager& mgr) { switch (state) { case EBallCameraState::ToBall: { zeus::CTransform xf = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform(); SetTransform(xf); TeleportCamera(xf.origin, mgr); SetFovInterpolation(mgr.GetCameraManager()->GetFirstPersonCamera()->GetFov(), CCameraManager::ThirdPersonFOV(), 1.f, 0.f); x36c_splineState = ESplineState::Invalid; [[fallthrough]]; } case EBallCameraState::Default: case EBallCameraState::Chase: case EBallCameraState::Boost: mgr.SetGameState(CStateManager::EGameState::Running); break; case EBallCameraState::FromBall: mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId()); mgr.SetGameState(CStateManager::EGameState::Running); SetFovInterpolation(GetFov(), CCameraManager::FirstPersonFOV(), 1.f, 0.f); x36c_splineState = ESplineState::Invalid; break; default: break; } x400_state = state; } constexpr CMaterialFilter BallCameraFilter = CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}); void CBallCamera::BuildSplineNav(CStateManager& mgr) { zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); TUniqueId intersectId = kInvalidUniqueId; EntityList nearList; CRayCastResult result = mgr.RayWorldIntersection(intersectId, ballPos, zeus::skDown, 20.f, BallCameraFilter, nearList); float downFactor = result.IsValid() ? zeus::clamp(0.f, result.GetT() / 20.f, 1.f) : 1.f; x36c_splineState = ESplineState::Nav; x370_24_reevalSplineEnd = true; x3d0_24_camBehindFloorOrWall = false; x37c_camSpline.Reset(4); x37c_camSpline.AddKnot(GetTranslation(), zeus::skForward); float elevation = x1a0_elevation; float distance = x190_curMinDistance; ConstrainElevationAndDistance(elevation, distance, 0.f, mgr); zeus::CVector3f pt1(x35c_splineIntermediatePos.x(), x35c_splineIntermediatePos.y(), GetTranslation().z()); x37c_camSpline.AddKnot(pt1, zeus::skForward); zeus::CVector3f pt2 = pt1 + (x35c_splineIntermediatePos - GetTranslation()) * (0.5f + downFactor); x37c_camSpline.AddKnot(pt2, zeus::skForward); zeus::CVector3f pt2Ball = ballPos - pt2; if (pt2Ball.canBeNormalized()) { pt2Ball.normalize(); } else { pt2Ball = mgr.GetPlayer().GetMoveDir(); } zeus::CVector3f desiredPosition = FindDesiredPosition(distance, elevation, pt2Ball, mgr, false); x37c_camSpline.AddKnot(desiredPosition, zeus::skForward); x37c_camSpline.UpdateSplineLength(); x3d0_24_camBehindFloorOrWall = false; CMaterialList intersectMat; x3c8_collisionExcludeList = CMaterialList(EMaterialTypes::Floor, EMaterialTypes::Ceiling); if (!SplineIntersectTest(intersectMat, mgr)) { if (intersectMat.HasMaterial(EMaterialTypes::Floor) || intersectMat.HasMaterial(EMaterialTypes::Wall)) { x3d0_24_camBehindFloorOrWall = true; x3c8_collisionExcludeList = CMaterialList(); } } x374_splineCtrl = 0.5f * downFactor + 2.f; x378_splineCtrlRange = 2.5f; } void CBallCamera::BuildSplineArc(CStateManager& mgr) { zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); x36c_splineState = ESplineState::Arc; x370_24_reevalSplineEnd = false; x37c_camSpline.Reset(4); x37c_camSpline.AddKnot(GetTranslation(), zeus::skForward); float elevation = x1a0_elevation; float distance = x190_curMinDistance; ConstrainElevationAndDistance(elevation, distance, 0.f, mgr); zeus::CVector3f halfwayPoint = (ballPos.toVec2f() - GetTranslation().toVec2f()) * 0.5f + GetTranslation().toVec2f(); halfwayPoint.z() = GetTranslation().z(); zeus::CVector3f delta = GetTranslation() - halfwayPoint; zeus::CQuaternion rot; rot.rotateZ(zeus::degToRad(45.f)); if (mgr.GetPlayer().GetMoveDir().cross(x34_transform.basis[1]).z() >= 0.f) { rot = zeus::CQuaternion(); rot.rotateZ(zeus::degToRad(-45.f)); } delta = rot.transform(delta); zeus::CVector3f pt1 = halfwayPoint + delta; TUniqueId intersectId = kInvalidUniqueId; EntityList nearList; CRayCastResult result = mgr.RayWorldIntersection(intersectId, pt1, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList); if (result.IsValid()) { pt1 = delta.normalized() * 1.5f + result.GetPoint(); } else { pt1 = halfwayPoint + delta; } x37c_camSpline.AddKnot(pt1, zeus::skForward); FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, false); delta = rot.transform(delta); zeus::CVector3f pt2 = halfwayPoint + delta; result = mgr.RayWorldIntersection(intersectId, pt2, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList); if (result.IsValid()) { pt2 = delta.normalized() * 2.f + result.GetPoint(); } else { pt2 = halfwayPoint + delta; } x37c_camSpline.AddKnot(pt2, zeus::skForward); delta = rot.transform(delta); zeus::CVector3f pt3 = delta + halfwayPoint; result = mgr.RayWorldIntersection(intersectId, pt3, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList); if (result.IsValid()) { pt3 = delta.normalized() * 2.f + result.GetPoint(); } else { pt3 = halfwayPoint + delta; } x37c_camSpline.AddKnot(pt3, zeus::skForward); CMaterialList intersectMat; if (!SplineIntersectTest(intersectMat, mgr) && intersectMat.HasMaterial(EMaterialTypes::Wall)) { delta = pt1 - halfwayPoint; result = mgr.RayWorldIntersection(intersectId, pt1, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList); if (result.IsValid() && !result.GetMaterial().HasMaterial(EMaterialTypes::Pillar)) { x37c_camSpline.SetKnotPosition(1, result.GetPoint() - delta.normalized() * 0.3f * 1.25f); } delta = pt2 - halfwayPoint; result = mgr.RayWorldIntersection(intersectId, pt2, -delta.normalized(), delta.magnitude(), BallCameraFilter, nearList); if (result.IsValid() && !result.GetMaterial().HasMaterial(EMaterialTypes::Pillar)) { x37c_camSpline.SetKnotPosition(2, result.GetPoint() - delta.normalized() * 0.3f * 1.25f); } x37c_camSpline.UpdateSplineLength(); if (!SplineIntersectTest(intersectMat, mgr)) { x36c_splineState = ESplineState::Invalid; return; } } x374_splineCtrl = 0.5f; x378_splineCtrlRange = 0.5f; x37c_camSpline.UpdateSplineLength(); x3c8_collisionExcludeList = CMaterialList(); } bool CBallCamera::ShouldResetSpline(CStateManager& mgr) const { return x400_state != EBallCameraState::ToBall && !mgr.GetCameraManager()->IsInterpolationCameraActive() && mgr.GetPlayer().GetMorphBall()->GetSpiderBallState() != CMorphBall::ESpiderBallState::Active && x36c_splineState == ESplineState::Invalid && (x188_behaviour > EBallCameraBehaviour::SpindleCamera || x188_behaviour < EBallCameraBehaviour::HintFixedPosition); } void CBallCamera::UpdatePlayerMovement(float dt, CStateManager& mgr) { x2ec_maxBallVel = std::fabs(mgr.GetPlayer().GetActualBallMaxVelocity(dt)); zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); x2f0_ballDelta = ballPos - x2dc_prevBallPos; x2fc_ballDeltaFlat = x2f0_ballDelta; x2fc_ballDeltaFlat.z() = 0.f; if (x2fc_ballDeltaFlat.canBeNormalized()) { x2e8_ballVelFlat = x2fc_ballDeltaFlat.magnitude() / dt; } else { x2e8_ballVelFlat = 0.f; } x2dc_prevBallPos = ballPos; x18d_28_obtuseDirection = false; zeus::CVector3f camToBallFlat = ballPos - GetTranslation(); camToBallFlat.z() = 0.f; if (camToBallFlat.canBeNormalized()) { camToBallFlat.normalize(); if (std::fabs(std::acos(zeus::clamp(-1.f, camToBallFlat.dot(mgr.GetPlayer().GetMoveDir()), 1.f))) > zeus::degToRad(100.f)) { x18d_28_obtuseDirection = true; } } x308_speedFactor = 0.f; float tmpVel = x2e8_ballVelFlat - 4.f; if (tmpVel > 0.f) { x308_speedFactor = zeus::clamp(-1.f, std::fabs(std::sin(zeus::degToRad(tmpVel / (x2ec_maxBallVel - 4.f) * 90.f))), 1.f); } x190_curMinDistance = x308_speedFactor * (x198_maxDistance - x194_targetMinDistance) + x194_targetMinDistance; if (x308_speedFactor > 0.5f && mgr.GetPlayer().GetPlayerMovementState() == CPlayer::EPlayerMovementState::OnGround) { x30c_speedingTime += dt * x308_speedFactor; } else { x30c_speedingTime = 0.f; } x30c_speedingTime = zeus::clamp(0.f, x30c_speedingTime, 3.f); } void CBallCamera::UpdateTransform(const zeus::CVector3f& lookDir, const zeus::CVector3f& pos, float dt, CStateManager& mgr) { zeus::CVector3f useLookDir = lookDir; if (x18d_31_overrideLookDir) { if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) { useLookDir = hint->GetTransform().basis[1]; } } zeus::CVector3f lookDirFlat = useLookDir; lookDirFlat.z() = 0.f; if (!lookDirFlat.canBeNormalized()) { SetTranslation(pos); return; } zeus::CVector3f curLookDir = x34_transform.basis[1]; if (curLookDir.canBeNormalized()) { curLookDir.normalize(); } else { SetTransform(zeus::lookAt(pos, pos + useLookDir)); return; } float lookDirDot = zeus::clamp(-1.f, curLookDir.dot(useLookDir), 1.f); if (std::fabs(lookDirDot) >= 1.f) { SetTransform(zeus::lookAt(pos, pos + useLookDir)); } else { float angleSpeedMul = zeus::clamp(0.f, std::acos(lookDirDot) / (zeus::degToRad(60.f) * dt), 1.f); float angleDelta = dt * x1a4_curAnglePerSecond * angleSpeedMul; float lookUpDot = std::fabs(zeus::clamp(-1.f, useLookDir.dot(zeus::skUp), 1.f)); float maxAngleDelta = (1.f - lookUpDot) * zeus::degToRad(720.f) * dt; if (x36c_splineState == ESplineState::Nav) { maxAngleDelta = zeus::degToRad(240.f) * dt; if (angleDelta > maxAngleDelta) { angleDelta = maxAngleDelta; } } if (angleDelta > maxAngleDelta && !mgr.GetPlayer().IsMorphBallTransitioning() && lookUpDot > 0.999f) { angleDelta = maxAngleDelta; } switch (x400_state) { case EBallCameraState::Chase: if (x18c_25_chaseAllowed) { angleDelta = dt * x40c_chaseAnglePerSecond * angleSpeedMul; } break; case EBallCameraState::Boost: angleDelta = dt * x438_boostAnglePerSecond * angleSpeedMul; break; default: break; } if (x18d_26_lookAtBall || mgr.GetCameraManager()->IsInterpolationCameraActive()) { x18d_26_lookAtBall = false; SetTransform(zeus::CQuaternion::lookAt(curLookDir, useLookDir, 2.f * M_PIF).toTransform() * x34_transform.getRotation()); } else { SetTransform(zeus::CQuaternion::lookAt(curLookDir, useLookDir, angleDelta).toTransform() * x34_transform.getRotation()); } } SetTranslation(pos); } zeus::CVector3f CBallCamera::ConstrainYawAngle(const CPlayer& player, float distance, float yawSpeed, float dt, CStateManager& mgr) const { zeus::CVector3f playerToCamFlat = GetTranslation() - player.GetTranslation(); playerToCamFlat.z() = 0.f; zeus::CVector3f lookDir = player.GetTransform().basis[1]; if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { lookDir = player.GetMoveDir(); TCastToConstPtr door = mgr.GetObjectById(x3dc_tooCloseActorId); if ((!door || !door->x2a8_26_isOpen) && (x400_state == EBallCameraState::Boost || x400_state == EBallCameraState::Chase)) { lookDir = player.GetLeaveMorphDir(); } } if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphing) { lookDir = player.GetLeaveMorphDir(); } if (lookDir.canBeNormalized()) { lookDir.normalize(); } else { lookDir = -playerToCamFlat; } if (playerToCamFlat.canBeNormalized()) { playerToCamFlat.normalize(); } else { return -lookDir; } float angleProj = zeus::clamp(-1.f, playerToCamFlat.dot(-lookDir), 1.f); if (angleProj >= 1.f) { return -lookDir; } return zeus::CQuaternion::lookAt(playerToCamFlat, -lookDir, distance * dt * zeus::clamp(0.f, std::acos(angleProj) / yawSpeed, 1.f)) .transform(playerToCamFlat); } void CBallCamera::CheckFailsafe(float dt, CStateManager& mgr) { zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); x18d_24_prevClearLOS = x18c_31_clearLOS; zeus::CVector3f camToBall = ballPos - GetTranslation(); float camToBallMag = camToBall.magnitude(); camToBall.normalize(); EntityList nearList; mgr.BuildNearList(nearList, GetTranslation(), camToBall, camToBallMag, BallCameraFilter, nullptr); CRayCastResult result = mgr.RayWorldIntersection(x368_obscuringObjectId, GetTranslation(), camToBall, camToBallMag, BallCameraFilter, nearList); if (result.IsValid()) { x350_obscuringMaterial = result.GetMaterial(); if (!mgr.RayCollideWorld(GetTranslation(), ballPos, nearList, BallCameraFilter, &mgr.GetPlayer()) && !mgr.RayCollideWorld(GetTranslation(), mgr.GetPlayer().GetTranslation(), nearList, BallCameraFilter, &mgr.GetPlayer())) { x18c_31_clearLOS = false; if (x18d_24_prevClearLOS) { x35c_splineIntermediatePos = ballPos; if (ShouldResetSpline(mgr) && !x18e_25_noSpline && x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor) && mgr.RayCollideWorld(ballPos, ballPos + zeus::CVector3f(0.f, 0.f, -2.5f), nearList, BallCameraFilter, nullptr)) { BuildSplineNav(mgr); } } } } else { x18c_31_clearLOS = true; x350_obscuringMaterial = CMaterialList(EMaterialTypes::NoStepLogic); } if (!x18c_31_clearLOS) { x34c_obscuredTime += dt; if (ShouldResetSpline(mgr) && !x18e_25_noSpline && x350_obscuringMaterial.HasMaterial(EMaterialTypes::Pillar)) { BuildSplineArc(mgr); } } else { x34c_obscuredTime = 0.f; } x358_unobscureMag = zeus::clamp(0.f, x34c_obscuredTime * 0.5f, 1.f); x3e4_pendingFailsafe = x18c_27_obscureAvoidance && (x34c_obscuredTime > 2.f || (x3dc_tooCloseActorId != kInvalidUniqueId && x34c_obscuredTime > 1.f)) && !x18c_31_clearLOS && x36c_splineState == ESplineState::Invalid; bool doFailsafe = x3e4_pendingFailsafe; if ((GetTranslation() - ballPos).magnitude() < 0.3f + g_tweakPlayer->GetPlayerBallHalfExtent()) { doFailsafe = true; } if (x18e_27_nearbyDoorClosed) { x18e_27_nearbyDoorClosed = false; if (result.IsValid()) { doFailsafe = true; } } if (x18e_28_nearbyDoorClosing) { x18e_28_nearbyDoorClosing = false; if (IsBallNearDoor(GetTranslation(), mgr)) { doFailsafe = true; } } if (doFailsafe) { ActivateFailsafe(dt, mgr); } } void CBallCamera::UpdateObjectTooCloseId(CStateManager& mgr) { x3e0_tooCloseActorDist = 1000000.f; x3dc_tooCloseActorId = kInvalidUniqueId; const zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); for (const CEntity* ent : mgr.GetPlatformAndDoorObjectList()) { if (const TCastToConstPtr door = ent) { if (mgr.GetPlayer().GetAreaIdAlways() == door->GetAreaIdAlways()) { door->GetBoundingBox(); const float minMag = std::min((door->GetTranslation() - GetTranslation()).magnitude(), (door->GetTranslation() - ballPos).magnitude()); if (minMag < 30.f && minMag < x3e0_tooCloseActorDist) { x3dc_tooCloseActorId = door->GetUniqueId(); x3e0_tooCloseActorDist = minMag; } } } } } void CBallCamera::UpdateAnglePerSecond(float dt) { float delta = x1a8_targetAnglePerSecond - x1a4_curAnglePerSecond; if (std::fabs(delta) >= M_PIF / 1800.f) { x1a4_curAnglePerSecond += zeus::clamp(-1.f, delta / M_PIF, 1.f) * (10.471975f * dt); } else { x1a4_curAnglePerSecond = x1a8_targetAnglePerSecond; } } void CBallCamera::UpdateUsingPathCameras(float dt, CStateManager& mgr) { if (const TCastToConstPtr cam = mgr.ObjectById(mgr.GetCameraManager()->GetPathCameraId())) { TeleportCamera(cam->GetTransform(), mgr); x18d_26_lookAtBall = true; } } zeus::CVector3f CBallCamera::GetFixedLookTarget(const zeus::CVector3f& hintToLookDir, CStateManager& mgr) const { const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr); if (hint == nullptr) { return hintToLookDir; } zeus::CVector3f hintDir = hint->GetTransform().basis[1]; zeus::CVector3f hintDirFlat = hintDir; hintDirFlat.z() = 0.f; if (hintDir.canBeNormalized() && hintDirFlat.canBeNormalized()) { hintDir.normalize(); hintDirFlat.normalize(); } else { hintDir = zeus::skForward; hintDirFlat = zeus::skForward; } zeus::CVector3f hintToLookDirFlat = hintToLookDir; hintToLookDirFlat.z() = 0.f; if (hintToLookDir.canBeNormalized() && hintToLookDirFlat.canBeNormalized()) { hintToLookDirFlat.normalize(); } else { hintToLookDirFlat = hintDirFlat; } float attitude = std::acos(zeus::clamp(-1.f, hintToLookDir.dot(hintToLookDirFlat), 1.f)); if (x18c_29_clampAttitude) { float refAttitude = std::acos(zeus::clamp(-1.f, hintDir.dot(hintDirFlat), 1.f)); attitude = refAttitude + zeus::clamp(-x1ac_attitudeRange, attitude - refAttitude, x1ac_attitudeRange); } if (hintToLookDir.z() >= 0.f) { attitude = -attitude; } float azimuth = std::acos(zeus::clamp(-1.f, hintToLookDirFlat.dot(hintDirFlat), 1.f)); if (x18c_30_clampAzimuth) { azimuth = zeus::clamp(-x1b0_azimuthRange, azimuth, x1b0_azimuthRange); } if (hintToLookDirFlat.x() * hintDirFlat.y() - hintDirFlat.x() * hintToLookDirFlat.y() >= 0.f) { azimuth = -azimuth; } zeus::CQuaternion quat; quat.rotateZ(azimuth); zeus::CVector3f aziLookDirFlat = quat.transform(hintDirFlat); zeus::CVector3f attitudeAxis(aziLookDirFlat.y(), -aziLookDirFlat.x(), 0.f); attitudeAxis.normalize(); return zeus::CQuaternion::fromAxisAngle(attitudeAxis, -attitude).transform(aziLookDirFlat); } void CBallCamera::UpdateUsingFixedCameras(float dt, CStateManager& mgr) { if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) { switch (x188_behaviour) { case EBallCameraBehaviour::HintFixedPosition: { zeus::CVector3f hintToLookPos = x1d8_lookPos - hint->GetTranslation(); if (hintToLookPos.canBeNormalized()) { hintToLookPos = GetFixedLookTarget(hintToLookPos.normalized(), mgr); if ((hint->GetHint().GetOverrideFlags() & 0x40) != 0) { x18d_26_lookAtBall = true; } UpdateTransform(hintToLookPos, hint->GetTranslation(), dt, mgr); } break; } case EBallCameraBehaviour::HintFixedTransform: SetTransform(hint->GetTransform()); break; default: break; } TeleportCamera(GetTranslation(), mgr); } } zeus::CVector3f CBallCamera::ComputeVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& posDelta) const { zeus::CVector3f ret = posDelta; if (x470_clampVelTimer > 0.f && ret.canBeNormalized() && !x18d_28_obtuseDirection) { float mag = ret.magnitude(); mag = zeus::clamp(-x474_clampVelRange, mag, x474_clampVelRange); ret = ret.normalized() * mag; } return ret; } zeus::CVector3f CBallCamera::TweenVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& newVel, float rate, float dt) { zeus::CVector3f velDelta = newVel - curVel; if (velDelta.canBeNormalized()) { float t = zeus::clamp(-1.f, velDelta.magnitude() / (rate * dt), 1.f); return velDelta.normalized() * rate * dt * t + curVel; } return newVel; } zeus::CVector3f CBallCamera::MoveCollisionActor(const zeus::CVector3f& pos, float dt, CStateManager& mgr) { if (const TCastToPtr act = mgr.ObjectById(x46c_collisionActorId)) { const zeus::CVector3f posDelta = pos - act->GetTranslation(); if (!posDelta.canBeNormalized() || posDelta.magnitude() < 0.01f) { act->Stop(); return act->GetTranslation(); } const zeus::CVector3f oldTranslation = act->GetTranslation(); const zeus::CVector3f oldVel = act->GetVelocity(); const zeus::CVector3f newVel = ComputeVelocity(oldVel, posDelta * (1.f / dt)); act->SetVelocityWR(newVel); act->SetMovable(true); act->AddMaterial(EMaterialTypes::Solid, mgr); CGameCollision::Move(mgr, *act, dt, nullptr); zeus::CVector3f posDelta2 = act->GetTranslation() - pos; if (posDelta2.canBeNormalized() && posDelta2.magnitude() > 0.1f) { act->SetTranslation(oldTranslation); act->SetVelocityWR(TweenVelocity(oldVel, newVel, 50.f, dt)); CGameCollision::Move(mgr, *act, dt, nullptr); posDelta2 = act->GetTranslation() - pos; if (posDelta2.magnitude() > 0.1f) { x478_shortMoveCount += 1; } else { x478_shortMoveCount = 0; } } else { act->Stop(); x478_shortMoveCount = 0; } act->SetMovable(false); act->RemoveMaterial(EMaterialTypes::Solid, mgr); return act->GetTranslation(); } return pos; } void CBallCamera::UpdateUsingFreeLook(float dt, CStateManager& mgr) { if (x400_state == EBallCameraState::ToBall || x400_state == EBallCameraState::FromBall) { x36c_splineState = ESplineState::Invalid; return; } if (x36c_splineState == ESplineState::Nav && x188_behaviour <= EBallCameraBehaviour::SpindleCamera && x188_behaviour >= EBallCameraBehaviour::HintFixedPosition) { x36c_splineState = ESplineState::Invalid; return; } float elevation = x1a0_elevation; float distance = x190_curMinDistance; ConstrainElevationAndDistance(elevation, distance, 0.f, mgr); zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); zeus::CVector3f knotToBall = ballPos - x37c_camSpline.GetKnotPosition(2); if (knotToBall.canBeNormalized()) { knotToBall.normalize(); } else { knotToBall = mgr.GetPlayer().GetMoveDir(); } zeus::CVector3f knot3 = x37c_camSpline.GetKnotPosition(3); zeus::CVector3f desiredPos = FindDesiredPosition(distance, elevation, knotToBall, mgr, false); if (x370_24_reevalSplineEnd) { x37c_camSpline.SetKnotPosition(3, desiredPos); } x374_splineCtrl -= dt; float splineT = 1.f - zeus::clamp(0.f, x374_splineCtrl / x378_splineCtrlRange, 1.f); if (x36c_splineState == ESplineState::Nav) { CMaterialList intersectMat; if (!SplineIntersectTest(intersectMat, mgr)) { x37c_camSpline.SetKnotPosition(3, knot3); if (intersectMat.HasMaterial(EMaterialTypes::Floor)) { x36c_splineState = ESplineState::Invalid; return; } } } if (x374_splineCtrl <= 0.f || (splineT > 0.75f && x18c_31_clearLOS)) { if (x36c_splineState == ESplineState::Arc && !x18c_31_clearLOS) { CMaterialList intersectMat; if (!SplineIntersectTest(intersectMat, mgr)) { x36c_splineState = ESplineState::Invalid; } else { zeus::CVector3f oldKnot2 = x37c_camSpline.GetKnotPosition(2); zeus::CVector3f oldKnot1 = x37c_camSpline.GetKnotPosition(1); BuildSplineArc(mgr); x37c_camSpline.SetKnotPosition(3, x37c_camSpline.GetKnotPosition(1)); x37c_camSpline.SetKnotPosition(2, x37c_camSpline.GetKnotPosition(0)); x37c_camSpline.SetKnotPosition(1, oldKnot2); x37c_camSpline.SetKnotPosition(0, oldKnot1); x37c_camSpline.UpdateSplineLength(); x374_splineCtrl = x378_splineCtrlRange - x378_splineCtrlRange * (x37c_camSpline.GetKnotT(2) / x37c_camSpline.x44_length); x374_splineCtrl -= dt; splineT = zeus::clamp(0.f, x374_splineCtrl / x378_splineCtrlRange, 1.f); } } else { x36c_splineState = ESplineState::Invalid; } } x37c_camSpline.UpdateSplineLength(); const zeus::CVector3f pos = x37c_camSpline.GetInterpolatedSplinePointByLength(splineT * x37c_camSpline.x44_length).origin; if (const TCastToPtr act = mgr.ObjectById(x46c_collisionActorId)) { CMaterialFilter filter = act->GetMaterialFilter(); CMaterialFilter tmpFilter = filter; tmpFilter.IncludeList().Add(EMaterialTypes::Wall); tmpFilter.ExcludeList().Add(x3c8_collisionExcludeList); act->SetMaterialFilter(tmpFilter); MoveCollisionActor(pos, dt, mgr); act->SetMaterialFilter(filter); } zeus::CVector3f lookDir = x1d8_lookPos - desiredPos; if (x18d_26_lookAtBall) { lookDir = ballPos - desiredPos; } if (lookDir.canBeNormalized()) { lookDir.normalize(); UpdateTransform(lookDir, desiredPos, dt, mgr); } TeleportCamera(desiredPos, mgr); if (x3d0_24_camBehindFloorOrWall && x374_splineCtrl / x378_splineCtrlRange < 0.5f) { x36c_splineState = ESplineState::Invalid; } } zeus::CVector3f CBallCamera::InterpolateCameraElevation(const zeus::CVector3f& camPos, float dt) { if (x1a0_elevation < 2.f) { return camPos; } zeus::CVector3f ret = camPos; if (!x18c_31_clearLOS && x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor)) { x3d4_elevInterpTimer = 1.f; ret.z() = x3d8_elevInterpStart = GetTranslation().z(); } else if (x3d4_elevInterpTimer > 0.f) { x3d4_elevInterpTimer -= dt; ret.z() = (camPos.z() - x3d8_elevInterpStart) * (1.f - zeus::clamp(0.f, x3d4_elevInterpTimer, 1.f)) + x3d8_elevInterpStart; } return ret; } zeus::CVector3f CBallCamera::CalculateCollidersCentroid(const std::vector& colliderList, int numObscured) const { if (colliderList.size() < 3) { return zeus::skForward; } int clearColliders = 0; const CCameraCollider* prevCol = &colliderList.back(); float accumCross = 0.f; float accumX = 0.f; float accumZ = 0.f; for (const CCameraCollider& col : colliderList) { if (prevCol->x4c_occlusionCount < 2 && col.x4c_occlusionCount < 2) { float z0 = prevCol->x50_scale * prevCol->x8_lastLocalPos.z(); float x1 = prevCol->x50_scale * col.x8_lastLocalPos.x(); float x0 = prevCol->x50_scale * prevCol->x8_lastLocalPos.x(); float z1 = prevCol->x50_scale * col.x8_lastLocalPos.z(); float cross = x0 * z1 - x1 * z0; accumCross += cross; accumX += cross * (x1 + x0); accumZ += cross * (z1 + z0); } else { clearColliders += 1; } prevCol = &col; } if (static_cast(clearColliders / colliderList.size()) <= x330_clearColliderThreshold) { return zeus::skForward; } if (0.f != accumCross) { float baryCross = 3.f * accumCross; return {accumX / baryCross, 0.f, accumZ / baryCross}; } return {0.f, 2.f, 0.f}; } zeus::CVector3f CBallCamera::ApplyColliders() { zeus::CVector3f smallCentroid = CalculateCollidersCentroid(x264_smallColliders, x2c4_smallCollidersObsCount); zeus::CVector3f mediumCentroid = CalculateCollidersCentroid(x274_mediumColliders, x2c8_mediumCollidersObsCount); zeus::CVector3f largeCentroid = CalculateCollidersCentroid(x284_largeColliders, x2cc_largeCollidersObsCount); if (smallCentroid.y() == 0.f) { x2a0_smallCentroid = smallCentroid; } else { x2a0_smallCentroid = zeus::skZero3f; } float centroidX = x2a0_smallCentroid.x(); float centroidZ = x2a0_smallCentroid.z(); if (mediumCentroid.y() == 0.f) { x2ac_mediumCentroid = mediumCentroid; } else { x2ac_mediumCentroid = zeus::skZero3f; } centroidX += x2ac_mediumCentroid.x(); centroidZ += x2ac_mediumCentroid.z(); if (largeCentroid.y() == 0.f) { x2b8_largeCentroid = largeCentroid; } else { x2b8_largeCentroid = zeus::skZero3f; } centroidX += x2b8_largeCentroid.x(); centroidZ += x2b8_largeCentroid.z(); if (x18c_31_clearLOS) { centroidX /= 1.5f; } centroidZ /= 3.f; if (!x18c_31_clearLOS && x368_obscuringObjectId == kInvalidUniqueId) { float xMul = 1.5f; float zMul = 1.f; if (x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor)) { zMul += 2.f * x358_unobscureMag; } if (x350_obscuringMaterial.HasMaterial(EMaterialTypes::Wall)) { xMul += 3.f * zeus::clamp(0.f, x358_unobscureMag - 0.25f, 1.f); } centroidX *= xMul; centroidZ *= zMul; } if (!x18c_28_volumeCollider) { return zeus::skZero3f; } if (std::fabs(centroidX) < 0.05f) { centroidX = 0.f; } if (std::fabs(centroidZ) < 0.05f) { centroidZ = 0.f; } if (x18c_31_clearLOS) { centroidZ *= 0.5f; } return {centroidX, 0.f, centroidZ}; } void CBallCamera::UpdateColliders(const zeus::CTransform& xf, std::vector& colliderList, int& it, int count, float tolerance, const EntityList& nearList, float dt, CStateManager& mgr) { if (it < colliderList.size()) { x310_idealLookVec = {0.f, g_tweakBall->GetBallCameraOffset().y(), g_tweakPlayer->GetPlayerBallHalfExtent()}; x310_idealLookVec.y() *= x308_speedFactor; x31c_predictedLookPos = mgr.GetPlayer().GetMoveDir() * x310_idealLookVec.y(); x31c_predictedLookPos.z() = float(x310_idealLookVec.z()); x31c_predictedLookPos += mgr.GetPlayer().GetTranslation(); zeus::CTransform predictedLookXf = zeus::lookAt(xf.origin, x31c_predictedLookPos); float toleranceRecip = 1.f / tolerance; for (int i = 0; i < count; ++i) { zeus::CVector3f localPos = colliderList[it].x14_localPos; zeus::CVector3f worldPos = predictedLookXf.rotate(localPos) + predictedLookXf.origin; if ((colliderList[it].x2c_lastWorldPos - worldPos).magnitude() < 0.1f) { localPos = colliderList[it].x8_lastLocalPos; worldPos = colliderList[it].x2c_lastWorldPos; } zeus::CVector3f centerToCollider = worldPos - predictedLookXf.origin; float mag = centerToCollider.magnitude(); if (centerToCollider.canBeNormalized()) { centerToCollider.normalize(); TUniqueId intersectId = kInvalidUniqueId; CRayCastResult result = mgr.RayWorldIntersection(intersectId, predictedLookXf.origin, centerToCollider, mag + colliderList[it].x4_radius, BallCameraFilter, nearList); if (result.IsValid()) { zeus::CVector3f centerToPoint = centerToCollider * (result.GetT() - colliderList[it].x4_radius); worldPos = centerToPoint + predictedLookXf.origin; localPos = predictedLookXf.getRotation().inverse() * centerToPoint; } } colliderList[it].x2c_lastWorldPos = worldPos; colliderList[it].x8_lastLocalPos = localPos; zeus::CVector3f scaledWorldColliderPos = centerToCollider * mag * toleranceRecip; scaledWorldColliderPos = scaledWorldColliderPos * x308_speedFactor + x31c_predictedLookPos; colliderList[it].x20_scaledWorldPos = scaledWorldColliderPos; if (mgr.RayCollideWorld(worldPos, scaledWorldColliderPos, nearList, BallCameraFilter, nullptr)) { colliderList[it].x4c_occlusionCount = 0; } else { colliderList[it].x4c_occlusionCount += 1; } it += 1; if (it == colliderList.size()) { it = 0; } } } } zeus::CVector3f CBallCamera::AvoidGeometry(const zeus::CTransform& xf, const EntityList& nearList, float dt, CStateManager& mgr) { switch (x328_avoidGeomCycle) { case 0: UpdateColliders(xf, x264_smallColliders, x2d0_smallColliderIt, 1, 4.f, nearList, dt, mgr); break; case 1: UpdateColliders(xf, x274_mediumColliders, x2d4_mediumColliderIt, 3, 4.f, nearList, dt, mgr); break; case 2: case 3: UpdateColliders(xf, x284_largeColliders, x2d8_largeColliderIt, 4, 4.f, nearList, dt, mgr); break; default: break; } x328_avoidGeomCycle += 1; if (x328_avoidGeomCycle >= 4) { x328_avoidGeomCycle = 0; } return ApplyColliders(); } zeus::CVector3f CBallCamera::AvoidGeometryFull(const zeus::CTransform& xf, const EntityList& nearList, float dt, CStateManager& mgr) { UpdateColliders(xf, x264_smallColliders, x2d0_smallColliderIt, x264_smallColliders.size(), 4.f, nearList, dt, mgr); UpdateColliders(xf, x274_mediumColliders, x2d4_mediumColliderIt, x274_mediumColliders.size(), 4.f, nearList, dt, mgr); UpdateColliders(xf, x284_largeColliders, x2d8_largeColliderIt, x284_largeColliders.size(), 4.f, nearList, dt, mgr); return ApplyColliders(); } zeus::CAABox CBallCamera::CalculateCollidersBoundingBox(const std::vector& colliderList, CStateManager& mgr) const { zeus::CAABox aabb; for (const CCameraCollider& col : colliderList) { aabb.accumulateBounds(col.x2c_lastWorldPos); } aabb.accumulateBounds(mgr.GetPlayer().GetTranslation()); return aabb; } int CBallCamera::CountObscuredColliders(const std::vector& colliderList) const { int ret = 0; for (const CCameraCollider& c : colliderList) { if (c.x4c_occlusionCount >= 2) { ++ret; } } return ret; } void CBallCamera::UpdateCollidersDistances(std::vector& colliderList, float xMag, float zMag, float angOffset) { float theta = angOffset; for (CCameraCollider& col : colliderList) { float z = std::cos(theta) * zMag; if (theta > M_PIF / 2.f) { z *= 0.25f; } col.x14_localPos = {std::sin(theta) * xMag, 0.f, z}; theta += 2.f * M_PIF / float(colliderList.size()); } } void CBallCamera::UpdateUsingColliders(float dt, CStateManager& mgr) { if (mgr.GetPlayer().GetBombJumpCount() == 1) { return; } zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); if (mgr.GetPlayer().GetBombJumpCount() == 2) { zeus::CVector3f camToLookDir = x1d8_lookPos - GetTranslation(); if (x18d_26_lookAtBall) { camToLookDir = ballPos - GetTranslation(); } if (camToLookDir.canBeNormalized()) { camToLookDir.normalize(); UpdateTransform(camToLookDir, GetTranslation(), dt, mgr); } } else if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed || x18d_25_avoidGeometryFull) { // zeus::CTransform oldXf = x34_transform; zeus::CVector3f oldPos = GetTranslation(); x2c4_smallCollidersObsCount = CountObscuredColliders(x264_smallColliders); x2c8_mediumCollidersObsCount = CountObscuredColliders(x274_mediumColliders); x2cc_largeCollidersObsCount = CountObscuredColliders(x284_largeColliders); zeus::CVector3f posAtBallLevel = {0.f, 0.f, GetTranslation().z() - ballPos.z()}; zeus::CVector3f ballToCamFlat = GetTranslation() - ballPos; ballToCamFlat.z() = 0.f; float ballToCamFlatMag = 0.f; if (ballToCamFlat.canBeNormalized()) { ballToCamFlatMag = ballToCamFlat.magnitude(); } else { ballToCamFlat = -mgr.GetPlayer().GetMoveDir(); } posAtBallLevel = GetTranslation() - posAtBallLevel; zeus::CTransform ballToUnderCamLook; if ((posAtBallLevel - ballPos).canBeNormalized()) { ballToUnderCamLook = zeus::lookAt(ballPos, posAtBallLevel); } float distance = x214_ballCameraSpring.ApplyDistanceSpring(x190_curMinDistance, ballToCamFlatMag, (3.f + x308_speedFactor) * dt); zeus::CVector3f camToBall = ballPos - GetTranslation(); camToBall.z() = 0.f; if (camToBall.canBeNormalized()) { camToBall.normalize(); if (std::fabs(std::acos(zeus::clamp(-1.f, camToBall.dot(mgr.GetPlayer().GetMoveDir()), 1.f))) > zeus::degToRad(150.f) && mgr.GetPlayer().GetVelocity().canBeNormalized()) { distance = x214_ballCameraSpring.ApplyDistanceSpring( x308_speedFactor * (x19c_backwardsDistance - x190_curMinDistance) + x190_curMinDistance, ballToCamFlatMag, 3.f * dt); } } x334_collidersAABB = CalculateCollidersBoundingBox(x284_largeColliders, mgr); EntityList nearList; mgr.BuildNearList(nearList, x334_collidersAABB, BallCameraFilter, TCastToConstPtr(mgr.GetObjectById(x46c_collisionActorId)).GetPtr()); if (!x18c_31_clearLOS && x368_obscuringObjectId == kInvalidUniqueId) { if (x34c_obscuredTime > 0.f || x350_obscuringMaterial.HasMaterial(EMaterialTypes::Floor) || x350_obscuringMaterial.HasMaterial(EMaterialTypes::Wall)) { x32c_colliderMag += 2.f * dt; if (x32c_colliderMag < 2.f) { x32c_colliderMag = 2.f; } if (x32c_colliderMag > 2.f) { x32c_colliderMag = 2.f; } UpdateCollidersDistances(x264_smallColliders, 2.31f * x32c_colliderMag, 2.31f * x32c_colliderMag * 0.5f, -M_PIF / 2.f); UpdateCollidersDistances(x274_mediumColliders, 4.62f * x32c_colliderMag, 4.62f * x32c_colliderMag * 0.5f, -M_PIF / 2.f); UpdateCollidersDistances(x284_largeColliders, 7.f * x32c_colliderMag, 7.f * x32c_colliderMag * 0.5f, -M_PIF / 2.f); } } else { float targetColliderMag = 1.f; if (x18d_24_prevClearLOS && mgr.GetPlayer().GetMoveSpeed() < 1.f) { targetColliderMag = 0.25f; } x32c_colliderMag += (targetColliderMag - x32c_colliderMag) * dt * 2.f; UpdateCollidersDistances(x264_smallColliders, x32c_colliderMag * 2.31f, x32c_colliderMag * 2.31f, -M_PIF / 2.f); UpdateCollidersDistances(x274_mediumColliders, x32c_colliderMag * 4.62f, x32c_colliderMag * 4.62f, -M_PIF / 2.f); UpdateCollidersDistances(x284_largeColliders, x32c_colliderMag * 7.f, x32c_colliderMag * 7.f, -M_PIF / 2.f); } float elevation = x1a0_elevation; bool noDoor = !ConstrainElevationAndDistance(elevation, distance, dt, mgr); zeus::CVector3f desiredBallToCam = ballToUnderCamLook.rotate({0.f, distance, elevation}); if (TCastToConstPtr door = mgr.GetObjectById(x3dc_tooCloseActorId)) { if (!door->x2a8_26_isOpen) { if (x400_state == EBallCameraState::Boost) { zeus::CVector3f ballToCam = GetTranslation() - ballPos; if (ballToCam.canBeNormalized()) { ballToCam.normalize(); } else { ballToCam = GetTransform().basis[1]; } if (std::fabs(ballToCamFlatMag - x430_boostElevation) < 1.f) { ballToCam = ConstrainYawAngle(mgr.GetPlayer(), g_tweakBall->GetBallCameraBoostDistance(), g_tweakBall->GetBallCameraBoostYawSpeed(), dt, mgr); } ballToCam.normalize(); ballToCam.z() = 0.f; ballToCam = ballToCam * distance; ballToCam.z() = 1.f; desiredBallToCam = ballToCam; noDoor = false; } if (x18c_25_chaseAllowed && (x400_state == EBallCameraState::Chase || x188_behaviour == EBallCameraBehaviour::FreezeLookPosition)) { zeus::CVector3f ballToCam = GetTranslation() - ballPos; if (ballToCam.canBeNormalized()) { ballToCam.normalize(); } else { ballToCam = GetTransform().basis[1]; } if (std::fabs(ballToCamFlatMag - x404_chaseElevation) < 3.f) { ballToCam = ConstrainYawAngle(mgr.GetPlayer(), g_tweakBall->GetBallCameraChaseDistance(), g_tweakBall->GetBallCameraChaseYawSpeed(), dt, mgr); } ballToCam.z() = 0.f; ballToCam.normalize(); ballToCam = ballToCam * distance; ballToCam.z() = g_tweakBall->GetBallCameraElevation(); desiredBallToCam = ballToCam; noDoor = false; } } } if (x188_behaviour == EBallCameraBehaviour::HintBallToCam) { desiredBallToCam = x45c_overrideBallToCam; if (x18c_27_obscureAvoidance) { zeus::CVector3f ballToCamDir = x45c_overrideBallToCam; if (ballToCamDir.canBeNormalized()) { ballToCamDir.normalize(); } else { ballToCamDir = -mgr.GetPlayer().GetMoveDir(); } TUniqueId intersectId = kInvalidUniqueId; CRayCastResult result = mgr.RayWorldIntersection(intersectId, ballPos, ballToCamDir, distance, BallCameraFilter, nearList); if (result.IsValid()) { desiredBallToCam = ballToCamDir * result.GetT() * 0.9f; } } noDoor = false; } distance = desiredBallToCam.magnitude(); zeus::CVector3f desiredCamPos = ballPos + desiredBallToCam; float d = 0.f; if (DetectCollision(ballPos, desiredCamPos, 0.3f, d, mgr)) { if (d >= 1.f) { desiredBallToCam = desiredBallToCam.normalized() * d; desiredCamPos = ballPos + desiredBallToCam; } else { desiredBallToCam = ballPos + GetTranslation(); desiredCamPos = GetTranslation(); } } zeus::CTransform lookXf = zeus::lookAt(desiredCamPos, x1d8_lookPos); zeus::CTransform oldLookXf = zeus::lookAt(GetTranslation(), x1d8_lookPos); x1e4_nextLookXf = lookXf; lookXf = oldLookXf; zeus::CVector3f colliderPointLocal; if (x18d_25_avoidGeometryFull || !x18c_31_clearLOS) { colliderPointLocal = AvoidGeometryFull(lookXf, nearList, dt, mgr); } else { colliderPointLocal = AvoidGeometry(lookXf, nearList, dt, mgr); } zeus::CVector3f ballToCam2 = GetTranslation() - ballPos; ballToCam2.z() = 0.f; if (ballToCam2.magnitude() < 2.f) { if (x18c_31_clearLOS && x478_shortMoveCount > 2) { colliderPointLocal = colliderPointLocal / float(x478_shortMoveCount); } if (d < 3.f) { colliderPointLocal = colliderPointLocal * 0.25f; if (x18c_31_clearLOS && x478_shortMoveCount > 0) { colliderPointLocal = colliderPointLocal * x308_speedFactor; } } if (d < 1.f) { colliderPointLocal = zeus::skZero3f; } } zeus::CVector3f camDelta = lookXf.rotate(colliderPointLocal) + desiredCamPos - ballPos; if (camDelta.canBeNormalized()) { camDelta.normalize(); } zeus::CVector3f desiredPos = camDelta * distance + ballPos; if (x188_behaviour == EBallCameraBehaviour::PathCameraDesiredPos) { if (TCastToConstPtr cam = mgr.GetObjectById(mgr.GetCameraManager()->GetPathCameraId())) { desiredPos = cam->GetTranslation(); } } camDelta = x294_dampedPos - desiredPos; float camDeltaMag = camDelta.magnitude(); if (camDelta.canBeNormalized()) { camDelta.normalize(); } x294_dampedPos = camDelta * x228_ballCameraCentroidSpring.ApplyDistanceSpring(0.f, camDeltaMag, dt) + desiredPos; zeus::CVector3f posDelta = oldPos - x294_dampedPos; camDeltaMag = posDelta.magnitude(); if (posDelta.canBeNormalized()) { posDelta.normalize(); } float cDistSpringMag = x250_ballCameraCentroidDistanceSpring.ApplyDistanceSpring( 0.f, camDeltaMag, (x18d_28_obtuseDirection ? 3.f : 1.f) * dt); if (x400_state == EBallCameraState::Boost) { cDistSpringMag = x448_ballCameraBoostSpring.ApplyDistanceSpring(0.f, camDeltaMag, dt); } else if (x18c_25_chaseAllowed && (x400_state == EBallCameraState::Chase || x188_behaviour == EBallCameraBehaviour::FreezeLookPosition)) { cDistSpringMag = x41c_ballCameraChaseSpring.ApplyDistanceSpring(0.f, camDeltaMag, dt); } zeus::CVector3f finalPos = posDelta * cDistSpringMag + x294_dampedPos; if (mgr.GetPlayer().GetMorphBall()->GetSpiderBallState() != CMorphBall::ESpiderBallState::Active && !x18e_24_noElevationVelClamp && mgr.GetPlayer().GetVelocity().z() > 8.f) { zeus::CVector3f delta = finalPos - oldPos; delta.z() = zeus::clamp(-0.1f * dt, float(delta.z()), 0.1f * dt); finalPos = oldPos + delta; } if (noDoor && x400_state != EBallCameraState::ToBall) { finalPos = InterpolateCameraElevation(finalPos, dt); } if (x18d_29_noElevationInterp) { finalPos.z() = elevation + ballPos.z(); } if (ballToCam2.magnitude() < 2.f) { if (finalPos.z() < 2.f + ballPos.z()) { finalPos.z() = 2.f + ballPos.z(); } x214_ballCameraSpring.Reset(); } finalPos = ClampElevationToWater(finalPos, mgr); if (ballToCam2.magnitude() < 2.f && x3dc_tooCloseActorId != kInvalidUniqueId && x3e0_tooCloseActorDist < 5.f) { if (TCastToConstPtr door = mgr.GetObjectById(x3dc_tooCloseActorId)) { if (!door->x2a8_26_isOpen) { finalPos = GetTranslation(); } } } float backupZ = finalPos.z(); finalPos = MoveCollisionActor(finalPos, dt, mgr); if (x18c_31_clearLOS && x478_shortMoveCount > 0) { finalPos.z() = backupZ; finalPos = MoveCollisionActor(finalPos, dt, mgr); } zeus::CVector3f lookDir = x1d8_lookPos - finalPos; if (x18d_26_lookAtBall) { lookDir = ballPos - finalPos; } if (lookDir.canBeNormalized()) { lookDir.normalize(); UpdateTransform(lookDir, finalPos, dt, mgr); } if (x470_clampVelTimer > 0.f) { x470_clampVelTimer -= dt; } } } void CBallCamera::UpdateUsingSpindleCameras(float dt, CStateManager& mgr) { if (const TCastToConstPtr cam = mgr.ObjectById(mgr.GetCameraManager()->GetSpindleCameraId())) { TeleportCamera(cam->GetTransform(), mgr); x18d_26_lookAtBall = true; } } zeus::CVector3f CBallCamera::ClampElevationToWater(zeus::CVector3f& pos, CStateManager& mgr) const { zeus::CVector3f ret = pos; if (const TCastToConstPtr water = mgr.GetObjectById(mgr.GetPlayer().GetFluidId())) { const float waterZ = water->GetTriggerBoundsWR().max.z(); if (pos.z() >= waterZ && pos.z() - waterZ <= 0.25f) { ret.z() = 0.25f + waterZ; } else if (pos.z() < waterZ && pos.z() - waterZ >= -0.12f) { ret.z() = waterZ - 0.12f; } } return ret; } void CBallCamera::UpdateTransitionFromBallCamera(CStateManager& mgr) { float morphFactor = mgr.GetPlayer().GetMorphFactor(); zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition(); zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - x47c_failsafeState->x84_playerPos; x47c_failsafeState->x90_splinePoints[1] += delta; x47c_failsafeState->x90_splinePoints[2] += delta; x47c_failsafeState->x90_splinePoints[3] += delta; zeus::CVector3f splinePoint = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, morphFactor); splinePoint.z() = (splinePoint.z() - eyePos.z()) * zeus::clamp(0.f, 1.f - 1.5f * morphFactor, 1.f) + eyePos.z(); zeus::CVector3f deltaFlat = eyePos - splinePoint; deltaFlat.z() = 0.f; if (deltaFlat.magnitude() > 0.001f) { SetTransform(zeus::lookAt(splinePoint, zeus::CVector3f::lerp(x1d8_lookPos, eyePos, morphFactor))); } else { SetTransform(mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform()); SetTranslation(splinePoint); } mgr.GetCameraManager()->GetFirstPersonCamera()->Reset(x34_transform, mgr); x47c_failsafeState->x84_playerPos = mgr.GetPlayer().GetTranslation(); } void CBallCamera::UpdateUsingTransitions(float dt, CStateManager& mgr) { if (x400_state == EBallCameraState::FromBall) { UpdateTransitionFromBallCamera(mgr); return; } x18d_26_lookAtBall = false; zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition(); ballPos.z() += x1b4_lookAtOffset.z(); zeus::CVector3f lookDir = x34_transform.basis[1]; zeus::CTransform xe8 = x34_transform; switch (x400_state) { case EBallCameraState::ToBall: { float elevation = x1a0_elevation; float distance = x194_targetMinDistance; ConstrainElevationAndDistance(elevation, distance, dt, mgr); distance = x194_targetMinDistance; const bool r28 = IsBallNearDoor(GetTranslation(), mgr) || x478_shortMoveCount > 2; const zeus::CVector3f toDesired = FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, r28) - eyePos; zeus::CVector3f finalPos = toDesired * mgr.GetPlayer().GetMorphFactor() + eyePos; if (const TCastToPtr act = mgr.ObjectById(x46c_collisionActorId)) { act->SetTranslation(GetTranslation()); finalPos = ClampElevationToWater(finalPos, mgr); finalPos = MoveCollisionActor(finalPos, dt, mgr); zeus::CVector3f camToLookDir = x1d8_lookPos - finalPos; if (camToLookDir.canBeNormalized()) { camToLookDir.normalize(); const float devDot = std::fabs(zeus::clamp(-1.f, lookDir.dot(camToLookDir), 1.f)); const float devAngle = zeus::clamp(-1.f, mgr.GetPlayer().GetMorphFactor() * 0.5f, 1.f) * std::acos(devDot); if (devDot < 1.f) { SetTransform(zeus::CQuaternion::lookAt(xe8.basis[1], camToLookDir, devAngle).toTransform() * xe8.getRotation()); } else { SetTransform(zeus::lookAt(zeus::skZero3f, camToLookDir)); } } } SetTransform(ValidateCameraTransform(x34_transform, xe8)); SetTranslation(finalPos); TeleportCamera(finalPos, mgr); break; } case EBallCameraState::FromBall: { if (std::fabs(mgr.GetPlayer().GetMorphFactor() - 1.f) < 0.00001f) { SetTransform(mgr.GetPlayer().GetTransform()); SetTranslation(mgr.GetPlayer().GetEyePosition()); } else { float morphT = zeus::clamp(-1.f, mgr.GetPlayer().GetMorphFactor() / 0.9f, 1.f); zeus::CVector3f finalPos = GetTranslation(); zeus::CVector3f eyeToCam = GetTranslation() - eyePos; if (eyeToCam.canBeNormalized()) { float distance = eyeToCam.magnitude(); distance = std::min(distance, (1.f - mgr.GetPlayer().GetMorphFactor()) * x190_curMinDistance); float yawSpeed = M_PIF; zeus::CVector3f playerToCamDir = GetTranslation() - mgr.GetPlayer().GetTranslation(); zeus::CVector3f moveDir = mgr.GetPlayer().GetMoveDir(); if (playerToCamDir.canBeNormalized()) { playerToCamDir.normalize(); } else { playerToCamDir = -moveDir; } if (moveDir.canBeNormalized()) { moveDir.normalize(); yawSpeed = std::fabs(std::acos(zeus::clamp(-1.f, playerToCamDir.dot(-moveDir), 1.f))) * morphT / dt; } zeus::CVector3f useLookDir = ConstrainYawAngle(mgr.GetPlayer(), yawSpeed, zeus::degToRad(10.f), dt, mgr); useLookDir.z() = 0.f; useLookDir.normalize(); zeus::CVector3f camPos = useLookDir * distance + eyePos; camPos.z() = (GetTranslation().z() - eyePos.z()) * morphT + eyePos.z(); finalPos = ClampElevationToWater(camPos, mgr); finalPos = MoveCollisionActor(finalPos, dt, mgr); zeus::CVector3f finalToBall = ballPos - finalPos; finalToBall.z() = 0.f; zeus::CVector3f lookPos = ballPos; lookPos.z() = morphT * (eyePos.z() - ballPos.z()) + ballPos.z(); if (finalToBall.canBeNormalized()) { SetTransform(zeus::lookAt(finalPos, lookPos)); } else { SetTransform(mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform()); } } else { SetTransform(mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform()); } SetTranslation(finalPos); } break; } default: break; } mgr.GetCameraManager()->GetFirstPersonCamera()->Reset(x34_transform, mgr); } zeus::CTransform CBallCamera::UpdateCameraPositions(float dt, const zeus::CTransform& oldXf, const zeus::CTransform& newXf) { zeus::CTransform useXf = newXf; if (std::fabs(oldXf.basis[1].z()) > 0.9f && std::fabs(newXf.basis[1].z()) > 0.9f && oldXf.basis[0].dot(newXf.basis[0]) <= 0.999f) { zeus::CVector3f newRight = zeus::CQuaternion::clampedRotateTo(oldXf.basis[0], newXf.basis[0], zeus::degToRad(2.f * dt)).toTransform() * oldXf.basis[0]; if (newRight.dot(newXf.basis[1]) <= 0.999f) { zeus::CVector3f newUp = newXf.basis[1].cross(newRight).normalized(); zeus::CVector3f newForward = newXf.basis[1].normalized(); useXf = {newUp.cross(newForward), newXf.basis[1], newUp, newXf.origin}; } } return useXf; } zeus::CVector3f CBallCamera::GetFailsafeSplinePoint(const std::vector& points, float t) { t *= float(points.size() - 3); int baseIdx = 0; while (t > 1.f) { t -= 1.f; baseIdx += 1; } return zeus::getBezierPoint(points[baseIdx], points[baseIdx + 1], points[baseIdx + 2], points[baseIdx + 3], t); } bool CBallCamera::CheckFailsafeFromMorphBallState(CStateManager& mgr) const { TUniqueId xbb8 = kInvalidUniqueId; float curT = 0.f; EntityList nearList; rstl::reserved_vector resultsA; rstl::reserved_vector resultsB; while (curT < 6.f) { zeus::CVector3f pointA = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, curT / 6.f); zeus::CVector3f pointB = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, (1.f + curT) / 6.f); zeus::CVector3f pointDelta = pointB - pointA; if (pointDelta.magnitude() > 0.1f) { resultsA.push_back(mgr.RayWorldIntersection(xbb8, pointA, pointDelta.normalized(), pointDelta.magnitude(), BallCameraFilter, nearList)); resultsB.push_back(mgr.RayWorldIntersection(xbb8, pointB, -pointDelta.normalized(), pointDelta.magnitude(), BallCameraFilter, nearList)); } else { resultsA.push_back({}); resultsB.push_back({}); } curT += 1.f; } for (size_t i = 0; i < resultsA.size(); ++i) { const CRayCastResult& resA = resultsA[i]; const CRayCastResult& resB = resultsB[i]; if (resA.IsValid()) { zeus::CVector3f separation = resA.GetPoint() - resB.GetPoint(); if (separation.magnitude() < 0.00001f) { separation = GetFailsafeSplinePoint(x47c_failsafeState->x90_splinePoints, (1.f + i) / 6.f) - resA.GetPoint(); } if (separation.magnitude() > 0.3f) { return false; } } } return true; } bool CBallCamera::SplineIntersectTest(CMaterialList& intersectMat, CStateManager& mgr) const { EntityList nearList; TUniqueId xe38 = kInvalidUniqueId; rstl::reserved_vector xacc; rstl::reserved_vector xd10; constexpr auto filter = CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid, EMaterialTypes::Floor, EMaterialTypes::Wall}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}); float curT = 0.f; while (curT < 12.f) { zeus::CVector3f xdb0 = x37c_camSpline.GetInterpolatedSplinePointByTime(curT, 12.f); zeus::CVector3f xdbc = x37c_camSpline.GetInterpolatedSplinePointByTime(curT, 12.f); zeus::CVector3f xdc8 = xdbc - xdb0; if (xdc8.magnitude() > 0.1f) { xacc.push_back(mgr.RayWorldIntersection(xe38, xdb0, xdc8.normalized(), xdc8.magnitude(), filter, nearList)); xd10.push_back(mgr.RayWorldIntersection(xe38, xdbc, -xdc8.normalized(), xdc8.magnitude(), filter, nearList)); } else { xacc.push_back({}); xd10.push_back({}); } curT += 1.f; } for (size_t i = 0; i < xacc.size(); ++i) { const CRayCastResult& resA = xacc[i]; const CRayCastResult& resB = xd10[i]; if (resA.IsValid()) { zeus::CVector3f xdd4 = resA.GetPoint() - resB.GetPoint(); if (xdd4.magnitude() < 0.00001f) { xdd4 = x37c_camSpline.GetInterpolatedSplinePointByTime(1.f + i, 12.f) - resA.GetPoint(); } if (xdd4.magnitude() > 0.3f) { intersectMat = resA.GetMaterial(); return false; } } } return true; } bool CBallCamera::IsBallNearDoor(const zeus::CVector3f& pos, CStateManager& mgr) { const TCastToConstPtr door = mgr.GetObjectById(mgr.GetCameraManager()->GetBallCamera()->x3dc_tooCloseActorId); if (!door || door->x2a8_26_isOpen) { return false; } const auto tb = door->GetTouchBounds(); const zeus::CAABox testAABB(pos - 0.3f, pos + 0.3f); if (!tb || !tb->intersects(testAABB)) { return false; } if (const TCastToConstPtr dock = mgr.GetObjectById(door->x282_dockId)) { if (std::fabs(dock->GetPlane(mgr).pointToPlaneDist(pos)) < 1.15f) { return true; } } return false; } void CBallCamera::ActivateFailsafe(float dt, CStateManager& mgr) { float elevation = x1a0_elevation; float distance = x194_targetMinDistance; ConstrainElevationAndDistance(elevation, distance, dt, mgr); zeus::CVector3f desiredPos = FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, true); SetTranslation(desiredPos); ResetPosition(mgr); TeleportCamera(zeus::lookAt(desiredPos, x1d8_lookPos), mgr); mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId()); x3e4_pendingFailsafe = false; x34c_obscuredTime = 0.f; } bool CBallCamera::ConstrainElevationAndDistance(float& elevation, float& distance, float dt, CStateManager& mgr) { zeus::CVector3f ballToCam = GetTranslation() - mgr.GetPlayer().GetBallPosition(); float ballToCamMag = 0.f; if (ballToCam.canBeNormalized()) { ballToCamMag = ballToCam.toVec2f().magnitude(); } else { ballToCam = -mgr.GetPlayer().GetMoveDir(); } bool doorClose = false; float stretchFac = 1.f; float newDistance = distance; float baseElevation = elevation; float springSpeed = 1.f; if (const TCastToConstPtr door = mgr.GetObjectById(x3dc_tooCloseActorId)) { if (!door->x2a8_29_ballDoor) { stretchFac = zeus::clamp(-1.f, std::fabs(x3e0_tooCloseActorDist / (3.f * distance)), 1.f); if (x3e0_tooCloseActorDist < 3.f * distance) { doorClose = true; } if (door->x2a8_26_isOpen) { newDistance = stretchFac * (distance - x468_conservativeDoorCamDistance) + x468_conservativeDoorCamDistance; } else { newDistance = stretchFac * (distance - 5.f) + 5.f; } if (x18d_28_obtuseDirection) { newDistance *= 1.f + x308_speedFactor; } baseElevation = door->x2a8_26_isOpen ? 0.75f : 1.5f; springSpeed = 4.f; } } x214_ballCameraSpring.ApplyDistanceSpring(newDistance, ballToCamMag, dt * springSpeed); distance = newDistance; elevation = (elevation - baseElevation) * stretchFac + baseElevation; return doorClose; } zeus::CVector3f CBallCamera::FindDesiredPosition(float distance, float elevation, const zeus::CVector3f& dir, CStateManager& mgr, bool fullTest) { TCastToConstPtr player = mgr.GetObjectById(xe8_watchedObject); if (!player) { return zeus::skZero3f; } zeus::CVector3f useDir = dir; if (!dir.canBeNormalized()) { useDir = zeus::skForward; } zeus::CTransform lookDirXf = zeus::lookAt(zeus::skZero3f, useDir); zeus::CVector3f ballPos = player->GetBallPosition(); float elev = elevation; float dist = distance; ConstrainElevationAndDistance(elev, dist, 0.f, mgr); zeus::CVector3f eyePos = player->GetEyePosition(); if (!mgr.RayCollideWorld(ballPos, eyePos, BallCameraFilter, nullptr)) { eyePos = ballPos; } zeus::CVector3f idealLookVec(0.f, -dist, elev - (eyePos.z() - ballPos.z())); idealLookVec = lookDirXf.getRotation() * idealLookVec; zeus::CVector3f lookVec(0.f, distance, elev - (eyePos.z() - ballPos.z())); float idealLookDist = idealLookVec.magnitude(); float resolveLOSIntervalAng = zeus::degToRad(30.f); bool foundClear = false; bool clear = !DetectCollision(eyePos, eyePos + idealLookVec, 0.3f, idealLookDist, mgr); if (!clear && idealLookDist <= 0.f) { zeus::CAABox x13ac(ballPos - distance, ballPos + distance); x13ac.min.z() = float(ballPos.z()); x13ac.max.z() = elev + ballPos.z(); EntityList nearList; mgr.BuildNearList(nearList, x13ac, BallCameraFilter, TCastToConstPtr(mgr.GetObjectById(x46c_collisionActorId)).GetPtr()); zeus::CQuaternion rotNeg; rotNeg.rotateZ(-resolveLOSIntervalAng); zeus::CTransform xfNeg = rotNeg.toTransform(); zeus::CQuaternion rotPos; rotPos.rotateZ(resolveLOSIntervalAng); zeus::CTransform xfPos = rotPos.toTransform(); while (!foundClear && idealLookDist > dist) { idealLookVec.normalize(); idealLookVec = idealLookVec * idealLookDist; zeus::CVector3f lookVecNeg = xfNeg.rotate(idealLookVec); zeus::CVector3f lookVecPos = xfPos.rotate(idealLookVec); for (int i = 0; float(i) < 180.f / zeus::radToDeg(resolveLOSIntervalAng); ++i) { if (mgr.RayCollideWorld(eyePos, eyePos + lookVecNeg, nearList, BallCameraFilter, nullptr)) { foundClear = true; lookVec = lookVecNeg; break; } if (mgr.RayCollideWorld(eyePos, eyePos + lookVecPos, nearList, BallCameraFilter, nullptr)) { foundClear = true; lookVec = lookVecPos; break; } lookVecNeg = xfNeg * lookVecNeg; lookVecPos = xfPos * lookVecPos; } idealLookDist -= 0.3f; } } else { if (idealLookDist < 2.f) { idealLookVec.normalize(); idealLookVec = idealLookVec * 2.f; } zeus::CQuaternion rotNeg; rotNeg.rotateZ(-resolveLOSIntervalAng); zeus::CTransform xfNeg = rotNeg.toTransform(); zeus::CVector3f lookVecNeg = xfNeg * idealLookVec; zeus::CQuaternion rotPos; rotPos.rotateZ(resolveLOSIntervalAng); zeus::CTransform xfPos = rotPos.toTransform(); zeus::CVector3f lookVecPos = xfPos * idealLookVec; if (clear || (!fullTest && (idealLookDist > 2.f || x2e8_ballVelFlat > 1.25f))) { idealLookVec.normalize(); lookVec = idealLookVec * idealLookDist; foundClear = true; } else { for (int i = 0; float(i) < 180.f / zeus::radToDeg(resolveLOSIntervalAng); ++i) { idealLookDist = lookVecNeg.magnitude(); if (!DetectCollision(eyePos, eyePos + lookVecNeg, 0.3f, idealLookDist, mgr) || idealLookDist > 2.f) { lookVecNeg.normalize(); lookVec = lookVecNeg * idealLookDist; foundClear = true; break; } idealLookDist = lookVecPos.magnitude(); if (!DetectCollision(eyePos, eyePos + lookVecPos, 0.3f, idealLookDist, mgr) || idealLookDist > 2.f) { lookVecPos.normalize(); lookVec = lookVecPos * idealLookDist; foundClear = true; break; } lookVecNeg = xfNeg * lookVecNeg; lookVecPos = xfPos * lookVecPos; } if (!foundClear) { zeus::CAABox findBounds(ballPos - distance, ballPos + distance); findBounds.min.z() = float(ballPos.z()); findBounds.max.z() = elev + ballPos.z(); EntityList nearList; mgr.BuildNearList(nearList, findBounds, BallCameraFilter, TCastToConstPtr(mgr.GetObjectById(x46c_collisionActorId)).GetPtr()); zeus::CQuaternion rotNeg2; rotNeg2.rotateZ(-resolveLOSIntervalAng); zeus::CTransform xfNeg2 = rotNeg2.toTransform(); zeus::CQuaternion rotPos2; rotPos2.rotateZ(resolveLOSIntervalAng); zeus::CTransform xfPos2 = rotPos2.toTransform(); while (!foundClear && idealLookDist > dist) { idealLookVec.normalize(); idealLookVec = idealLookVec * idealLookDist; zeus::CVector3f lookVecNeg2 = xfNeg2.rotate(idealLookVec); zeus::CVector3f lookVecPos2 = xfPos2.rotate(idealLookVec); for (int i = 0; float(i) < 180.f / zeus::radToDeg(resolveLOSIntervalAng); ++i) { if (mgr.RayCollideWorld(eyePos, eyePos + lookVecNeg2, nearList, BallCameraFilter, nullptr)) { foundClear = true; lookVec = lookVecNeg2; break; } if (mgr.RayCollideWorld(eyePos, eyePos + lookVecPos2, nearList, BallCameraFilter, nullptr)) { foundClear = true; lookVec = lookVecPos2; break; } lookVecNeg2 = xfNeg2 * lookVecNeg2; lookVecPos2 = xfPos2 * lookVecPos2; } idealLookDist -= 0.3f; } } } } if (!foundClear) { return GetTranslation(); } return eyePos + lookVec; } bool CBallCamera::DetectCollision(const zeus::CVector3f& from, const zeus::CVector3f& to, float radius, float& d, CStateManager& mgr) { zeus::CVector3f delta = to - from; float deltaMag = delta.magnitude(); zeus::CVector3f deltaNorm = delta * (1.f / deltaMag); bool clear = true; if (deltaMag > 0.000001f) { float margin = 2.f * radius; zeus::CAABox aabb; aabb.accumulateBounds(from); aabb.accumulateBounds(to); aabb = zeus::CAABox(aabb.min - margin, aabb.max + margin); EntityList nearList; mgr.BuildColliderList(nearList, mgr.GetPlayer(), aabb); CAreaCollisionCache cache(aabb); CGameCollision::BuildAreaCollisionCache(mgr, cache); if (cache.HasCacheOverflowed()) { clear = false; } CCollidableSphere cSphere({zeus::skZero3f, radius}, {EMaterialTypes::Solid}); if (CGameCollision::DetectCollisionBoolean_Cached( mgr, cache, cSphere, zeus::CTransform::Translate(from), CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}), nearList)) { d = -1.f; return true; } if (clear) { TUniqueId intersectId = kInvalidUniqueId; CCollisionInfo info; double dTmp = deltaMag; if (CGameCollision::DetectCollision_Cached_Moving( mgr, cache, cSphere, zeus::CTransform::Translate(from), CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}), nearList, deltaNorm, intersectId, info, dTmp)) { d = float(dTmp); clear = false; } } } return !clear; } void CBallCamera::Think(float dt, CStateManager& mgr) { mgr.SetActorAreaId(*this, mgr.GetNextAreaId()); UpdatePlayerMovement(dt, mgr); const TCastToPtr colAct = mgr.ObjectById(x46c_collisionActorId); if (colAct) { mgr.SetActorAreaId(*colAct, mgr.GetNextAreaId()); } switch (mgr.GetPlayer().GetCameraState()) { default: if (!x18d_27_forceProcessing) { if (colAct) { colAct->SetActive(false); } return; } [[fallthrough]]; case CPlayer::EPlayerCameraState::Ball: case CPlayer::EPlayerCameraState::Transitioning: case CPlayer::EPlayerCameraState::Two: { if (colAct) { colAct->SetActive(true); } zeus::CTransform oldXf = x34_transform; if (mgr.GetPlayer().GetBombJumpCount() != 1) { UpdateLookAtPosition(dt, mgr); } CheckFailsafe(dt, mgr); UpdateObjectTooCloseId(mgr); UpdateAnglePerSecond(dt); switch (x400_state) { case EBallCameraState::Default: case EBallCameraState::Chase: case EBallCameraState::Boost: switch (x188_behaviour) { case EBallCameraBehaviour::PathCamera: UpdateUsingPathCameras(dt, mgr); break; case EBallCameraBehaviour::HintFixedPosition: case EBallCameraBehaviour::HintFixedTransform: UpdateUsingFixedCameras(dt, mgr); break; case EBallCameraBehaviour::PathCameraDesiredPos: case EBallCameraBehaviour::Default: case EBallCameraBehaviour::FreezeLookPosition: case EBallCameraBehaviour::HintBallToCam: if (x36c_splineState != ESplineState::Invalid) { UpdateUsingFreeLook(dt, mgr); } else { UpdateUsingColliders(dt, mgr); } break; case EBallCameraBehaviour::SpindleCamera: UpdateUsingSpindleCameras(dt, mgr); break; default: break; } break; case EBallCameraState::ToBall: case EBallCameraState::FromBall: UpdateUsingTransitions(dt, mgr); break; default: break; } SetTransform(ValidateCameraTransform(UpdateCameraPositions(dt, oldXf, x34_transform), oldXf)); break; } } } bool CBallCamera::CheckTransitionLineOfSight(const zeus::CVector3f& eyePos, const zeus::CVector3f& behindPos, float& eyeToOccDist, float colRadius, CStateManager& mgr) { zeus::CVector3f eyeToBehind = behindPos - eyePos; float eyeToBehindMag = eyeToBehind.magnitude(); zeus::CVector3f eyeToBehindNorm = eyeToBehind * (1.f / eyeToBehindMag); bool clear = true; if (eyeToBehindMag > 0.000001f) { float margin = 2.f * colRadius; zeus::CAABox aabb; aabb.accumulateBounds(eyePos); aabb.accumulateBounds(behindPos); aabb = zeus::CAABox(aabb.min - margin, aabb.max + margin); EntityList nearList; mgr.BuildColliderList(nearList, mgr.GetPlayer(), aabb); CAreaCollisionCache cache(aabb); CGameCollision::BuildAreaCollisionCache(mgr, cache); if (cache.HasCacheOverflowed()) { clear = false; } if (clear) { CCollisionInfo cinfo; double d = eyeToBehindMag; TUniqueId intersectId = kInvalidUniqueId; CCollidableSphere cSphere({zeus::skZero3f, colRadius}, {EMaterialTypes::Solid}); if (CGameCollision::DetectCollision_Cached_Moving( mgr, cache, cSphere, zeus::CTransform::Translate(eyePos), CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}), nearList, eyeToBehindNorm, intersectId, cinfo, d)) { eyeToOccDist = float(d); clear = false; } } } return !clear; } bool CBallCamera::TransitionFromMorphBallState(CStateManager& mgr) { x47c_failsafeState->x0_playerXf = mgr.GetPlayer().GetTransform(); x47c_failsafeState->x30_camXf = GetTransform(); x47c_failsafeState->x60_lookPos = x1d8_lookPos; x47c_failsafeState->x84_playerPos = x47c_failsafeState->x0_playerXf.origin; zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition(); float lookDist = (x47c_failsafeState->x60_lookPos - x47c_failsafeState->x30_camXf.origin).magnitude(); zeus::CVector3f behindPos = x47c_failsafeState->x0_playerXf.basis[1] * (0.6f * -lookDist) + eyePos; float eyeToOccDist = 0.f; if (CheckTransitionLineOfSight(eyePos, behindPos, eyeToOccDist, 0.6f, mgr)) { x47c_failsafeState->x6c_behindPos = x47c_failsafeState->x0_playerXf.basis[1] * -eyeToOccDist + eyePos; } else { x47c_failsafeState->x6c_behindPos = behindPos; } x47c_failsafeState->x90_splinePoints.clear(); x47c_failsafeState->x90_splinePoints.reserve(4); x47c_failsafeState->x90_splinePoints.push_back(x47c_failsafeState->x30_camXf.origin); x47c_failsafeState->x90_splinePoints.push_back(x47c_failsafeState->x6c_behindPos); x47c_failsafeState->x90_splinePoints.push_back(x47c_failsafeState->x6c_behindPos); x47c_failsafeState->x90_splinePoints.push_back(eyePos); return CheckFailsafeFromMorphBallState(mgr); } void CBallCamera::TeleportColliders(std::vector& colliderList, const zeus::CVector3f& pos) { for (CCameraCollider& collider : colliderList) { collider.x2c_lastWorldPos = pos; collider.x14_localPos = pos; collider.x20_scaledWorldPos = pos; } } void CBallCamera::TeleportCamera(const zeus::CVector3f& pos, CStateManager& mgr) { x294_dampedPos = pos; TeleportColliders(x264_smallColliders, pos); TeleportColliders(x274_mediumColliders, pos); TeleportColliders(x284_largeColliders, pos); if (const TCastToPtr act = mgr.ObjectById(x46c_collisionActorId)) { act->SetTranslation(pos); } } void CBallCamera::TeleportCamera(const zeus::CTransform& xf, CStateManager& mgr) { SetTransform(xf); TeleportCamera(xf.origin, mgr); } void CBallCamera::ResetToTweaks(CStateManager& mgr) { x188_behaviour = EBallCameraBehaviour::Default; x18c_25_chaseAllowed = true; x18c_26_boostAllowed = true; x18c_27_obscureAvoidance = true; x18c_28_volumeCollider = true; x18c_29_clampAttitude = false; x18c_30_clampAzimuth = false; x194_targetMinDistance = g_tweakBall->GetBallCameraMinSpeedDistance(); x198_maxDistance = g_tweakBall->GetBallCameraMaxSpeedDistance(); x19c_backwardsDistance = g_tweakBall->GetBallCameraBackwardsDistance(); x214_ballCameraSpring = CCameraSpring(g_tweakBall->GetBallCameraSpringConstant(), g_tweakBall->GetBallCameraSpringMax(), g_tweakBall->GetBallCameraSpringTardis()); x250_ballCameraCentroidDistanceSpring = CCameraSpring(g_tweakBall->GetBallCameraCentroidDistanceSpringConstant(), g_tweakBall->GetBallCameraCentroidDistanceSpringMax(), g_tweakBall->GetBallCameraCentroidDistanceSpringTardis()); x1b4_lookAtOffset = g_tweakBall->GetBallCameraOffset(); x410_chaseLookAtOffset = g_tweakBall->GetBallCameraChaseLookAtOffset(); x1a0_elevation = g_tweakBall->GetBallCameraElevation(); x1ac_attitudeRange = M_PIF / 2.f; x1b0_azimuthRange = M_PIF / 2.f; SetFovInterpolation(x15c_currentFov, CCameraManager::ThirdPersonFOV(), 1.f, 0.f); x1a8_targetAnglePerSecond = g_tweakBall->GetBallCameraAnglePerSecond(); x18d_29_noElevationInterp = false; x18d_30_directElevation = false; x18d_31_overrideLookDir = false; x18e_24_noElevationVelClamp = false; x18e_25_noSpline = false; x18e_26_ = false; } void CBallCamera::UpdateLookAtPosition(float dt, CStateManager& mgr) { if (TCastToConstPtr player = mgr.GetObjectById(xe8_watchedObject)) { zeus::CVector3f ballPos = player->GetBallPosition(); if (player->IsMorphBallTransitioning()) { x1d8_lookPos = ballPos; x1d8_lookPos.z() += x1b4_lookAtOffset.z(); x1c0_lookPosAhead = x1d8_lookPos; x1cc_fixedLookPos = x1d8_lookPos; } else { zeus::CVector3f dirNorm = player->GetMoveDir(); dirNorm.normalize(); zeus::CVector3f lookAtOffsetAhead(x308_speedFactor * x1b4_lookAtOffset.x(), x308_speedFactor * x1b4_lookAtOffset.y(), x1b4_lookAtOffset.z()); if (x18c_25_chaseAllowed && (x400_state == EBallCameraState::Chase || x400_state == EBallCameraState::One)) { lookAtOffsetAhead = zeus::CVector3f(x308_speedFactor * x410_chaseLookAtOffset.x(), x308_speedFactor * x410_chaseLookAtOffset.y(), x410_chaseLookAtOffset.z()); } if (mgr.GetCameraManager()->IsInterpolationCameraActive()) { lookAtOffsetAhead = zeus::CVector3f(0.f, 0.f, x1b4_lookAtOffset.z()); } zeus::CTransform moveXf = player->CreateTransformFromMovementDirection().getRotation(); if (x2fc_ballDeltaFlat.canBeNormalized()) { lookAtOffsetAhead = moveXf * lookAtOffsetAhead; } zeus::CVector3f lookAtPosAhead = ballPos + lookAtOffsetAhead; x1c0_lookPosAhead = lookAtPosAhead; x1cc_fixedLookPos = ballPos + zeus::CVector3f(0.f, 0.f, lookAtOffsetAhead.z()); zeus::CVector3f aheadToCurrentLookDelta = x1d8_lookPos - lookAtPosAhead; float aheadToCurrentLookMag = aheadToCurrentLookDelta.magnitude(); if (aheadToCurrentLookDelta.canBeNormalized()) { aheadToCurrentLookDelta.normalize(); } float lookAtSpringMag = x23c_ballCameraLookAtSpring.ApplyDistanceSpringNoMax( 0.f, aheadToCurrentLookMag, (2.f * zeus::clamp(0.f, x30c_speedingTime / 3.f, 1.f) + 1.f) * dt); if (lookAtSpringMag > 0.0001f) { lookAtPosAhead += aheadToCurrentLookDelta * lookAtSpringMag; } aheadToCurrentLookDelta = lookAtPosAhead - x1d8_lookPos; if (x18d_26_lookAtBall) { x1d8_lookPos = ballPos; x1d8_lookPos.z() += x1b4_lookAtOffset.z(); } else { x1d8_lookPos = lookAtPosAhead; } switch (x188_behaviour) { case EBallCameraBehaviour::Default: case EBallCameraBehaviour::FreezeLookPosition: case EBallCameraBehaviour::HintBallToCam: case EBallCameraBehaviour::HintInitializePosition: case EBallCameraBehaviour::PathCameraDesiredPos: case EBallCameraBehaviour::PathCamera: if (mgr.GetCameraManager()->IsInterpolationCameraActive()) { x1d8_lookPos = x1c0_lookPosAhead; x1cc_fixedLookPos = x1c0_lookPosAhead; } break; case EBallCameraBehaviour::HintFixedPosition: x1d8_lookPos = x1cc_fixedLookPos; x1c0_lookPosAhead = x1d8_lookPos; break; case EBallCameraBehaviour::HintFixedTransform: case EBallCameraBehaviour::SpindleCamera: x1d8_lookPos = x1cc_fixedLookPos; x1c0_lookPosAhead = x1cc_fixedLookPos; break; } if (x18d_30_directElevation) { x1d8_lookPos.z() = ballPos.z() + x1b4_lookAtOffset.z(); x1c0_lookPosAhead.z() = float(x1d8_lookPos.z()); x1cc_fixedLookPos.z() = float(x1d8_lookPos.z()); } if (x18d_31_overrideLookDir) { if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) { x1d8_lookPos = hint->GetTransform().basis[1] * 10.f + GetTranslation(); x1c0_lookPosAhead = x1d8_lookPos; x1cc_fixedLookPos = x1d8_lookPos; } } } } } zeus::CTransform CBallCamera::UpdateLookDirection(const zeus::CVector3f& dir, CStateManager& mgr) { zeus::CVector3f useDir = dir; if (!dir.canBeNormalized()) { useDir = zeus::skForward; } float elevation = x1a0_elevation; float distance = x190_curMinDistance; ConstrainElevationAndDistance(elevation, distance, 0.f, mgr); zeus::CVector3f pos = FindDesiredPosition(distance, elevation, useDir, mgr, false); UpdateLookAtPosition(0.f, mgr); return zeus::lookAt(pos, x1d8_lookPos); } void CBallCamera::ApplyCameraHint(CStateManager& mgr) { if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) { ResetToTweaks(mgr); x188_behaviour = hint->GetHint().GetBehaviourType(); x18c_25_chaseAllowed = (hint->GetHint().GetOverrideFlags() & 0x2) != 0; x18c_26_boostAllowed = (hint->GetHint().GetOverrideFlags() & 0x4) != 0; x18c_27_obscureAvoidance = (hint->GetHint().GetOverrideFlags() & 0x8) != 0; x18c_28_volumeCollider = (hint->GetHint().GetOverrideFlags() & 0x10) != 0; if ((hint->GetHint().GetOverrideFlags() & 0x40) != 0) { x18d_26_lookAtBall = true; } x18d_29_noElevationInterp = (hint->GetHint().GetOverrideFlags() & 0x4000) != 0; x18d_30_directElevation = (hint->GetHint().GetOverrideFlags() & 0x8000) != 0; x18d_31_overrideLookDir = (hint->GetHint().GetOverrideFlags() & 0x10000) != 0; x18e_24_noElevationVelClamp = (hint->GetHint().GetOverrideFlags() & 0x20000) != 0; x18e_25_noSpline = x18e_26_ = (hint->GetHint().GetOverrideFlags() & 0x80000) != 0; if ((hint->GetHint().GetOverrideFlags() & 0x400000) != 0) { x194_targetMinDistance = hint->GetHint().GetMinDist(); } if ((hint->GetHint().GetOverrideFlags() & 0x800000) != 0) { x198_maxDistance = hint->GetHint().GetMaxDist(); } if ((hint->GetHint().GetOverrideFlags() & 0x1000000) != 0) { x19c_backwardsDistance = hint->GetHint().GetBackwardsDist(); } if ((hint->GetHint().GetOverrideFlags() & 0x80000000) != 0) { x1a0_elevation = hint->GetHint().GetElevation(); } if ((hint->GetHint().GetOverrideFlags() & 0x2000000) != 0) { x1b4_lookAtOffset = hint->GetHint().GetLookAtOffset(); } if ((hint->GetHint().GetOverrideFlags() & 0x4000000) != 0) { x410_chaseLookAtOffset = hint->GetHint().GetChaseLookAtOffset(); } if ((hint->GetHint().GetOverrideFlags() & 0x10000000) != 0) { x18c_29_clampAttitude = true; x1ac_attitudeRange = hint->GetHint().GetAttitudeRange(); } else { x18c_29_clampAttitude = false; } if ((hint->GetHint().GetOverrideFlags() & 0x20000000) != 0) { x18c_30_clampAzimuth = true; x1b0_azimuthRange = hint->GetHint().GetAzimuthRange(); } else { x18c_30_clampAzimuth = false; } if ((hint->GetHint().GetOverrideFlags() & 0x8000000) != 0) { SetFovInterpolation(x15c_currentFov, hint->GetHint().GetFov(), 1.f, 0.f); } if ((hint->GetHint().GetOverrideFlags() & 0x40000000) != 0) { x1a8_targetAnglePerSecond = hint->GetHint().GetAnglePerSecond(); } if ((hint->GetHint().GetOverrideFlags() & 0x200) != 0) { mgr.GetPlayer().SetControlDirectionInterpolation(hint->GetHint().GetControlInterpDur()); } else { mgr.GetPlayer().ResetControlDirectionInterpolation(); } switch (hint->GetHint().GetBehaviourType()) { case EBallCameraBehaviour::HintBallToCam: { x45c_overrideBallToCam = hint->GetHint().GetBallToCam(); ResetPosition(mgr); zeus::CVector3f camPos = mgr.GetPlayer().GetBallPosition() + hint->GetHint().GetBallToCam(); if ((hint->GetHint().GetOverrideFlags() & 0x1) != 0) { float distance = hint->GetHint().GetBallToCam().toVec2f().magnitude(); zeus::CVector3f camToBall = -zeus::CVector3f(hint->GetHint().GetBallToCam().toVec2f()).normalized(); camPos = FindDesiredPosition(distance, hint->GetHint().GetBallToCam().z(), camToBall, mgr, false); } TeleportCamera(zeus::lookAt(camPos, x1d8_lookPos), mgr); break; } case EBallCameraBehaviour::HintFixedTransform: { ResetPosition(mgr); TeleportCamera(hint->GetTransform(), mgr); break; } case EBallCameraBehaviour::Default: { if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) { ResetPosition(mgr); if ((hint->GetHint().GetOverrideFlags() & 0x40000) != 0) { zeus::CVector3f lookDir = mgr.GetPlayer().GetTranslation() - mgr.GetCameraManager()->GetCurrentCameraTransform(mgr).origin; lookDir.z() = 0.f; if (lookDir.canBeNormalized()) { lookDir.normalize(); } else { lookDir = mgr.GetPlayer().GetMoveDir(); } TeleportCamera(UpdateLookDirection(lookDir, mgr), mgr); } else { TeleportCamera(zeus::lookAt(hint->GetTranslation(), x1d8_lookPos), mgr); } } break; } case EBallCameraBehaviour::HintFixedPosition: { ResetPosition(mgr); TeleportCamera(zeus::lookAt(hint->GetTranslation(), x1d8_lookPos), mgr); break; } case EBallCameraBehaviour::FreezeLookPosition: case EBallCameraBehaviour::HintInitializePosition: { if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) { ResetPosition(mgr); float elevation = x1a0_elevation; float distance = x190_curMinDistance; ConstrainElevationAndDistance(elevation, distance, 0.f, mgr); TeleportCamera(zeus::lookAt(FindDesiredPosition(distance, elevation, mgr.GetPlayer().GetMoveDir(), mgr, false), x1cc_fixedLookPos), mgr); } break; } default: break; } if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) { mgr.GetCameraManager()->SetPlayerCamera(mgr, GetUniqueId()); } } } void CBallCamera::ResetPosition(CStateManager& mgr) { x1d8_lookPos = mgr.GetPlayer().GetBallPosition(); x1d8_lookPos.z() += x1b4_lookAtOffset.z(); x1c0_lookPosAhead = x1d8_lookPos; x1cc_fixedLookPos = x1d8_lookPos; } void CBallCamera::DoorClosed(TUniqueId doorId) { if (doorId != x3dc_tooCloseActorId) { return; } x18e_27_nearbyDoorClosed = true; } void CBallCamera::DoorClosing(TUniqueId doorId) { if (doorId != x3dc_tooCloseActorId) { return; } x18e_28_nearbyDoorClosing = true; } } // namespace metaforce ================================================ FILE: Runtime/Camera/CBallCamera.hpp ================================================ #pragma once #include #include #include #include "Runtime/Camera/CCameraSpline.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include #include #include namespace metaforce { class CPlayer; class CCameraSpring { float x0_k; float x4_k2Sqrt; float x8_max; float xc_tardis; float x10_dx = 0.f; public: CCameraSpring(float k, float max, float tardis) : x0_k(k), x4_k2Sqrt(2.f * std::sqrt(k)), x8_max(max), xc_tardis(tardis) {} void Reset(); float ApplyDistanceSpringNoMax(float targetX, float curX, float dt); float ApplyDistanceSpring(float targetX, float curX, float dt); }; class CCameraCollider { friend class CBallCamera; float x4_radius; zeus::CVector3f x8_lastLocalPos; zeus::CVector3f x14_localPos; zeus::CVector3f x20_scaledWorldPos; zeus::CVector3f x2c_lastWorldPos; CCameraSpring x38_spring; u32 x4c_occlusionCount = 0; float x50_scale; public: CCameraCollider(float radius, const zeus::CVector3f& vec, const CCameraSpring& spring, float scale) : x4_radius(radius) , x8_lastLocalPos(vec) , x14_localPos(vec) , x20_scaledWorldPos(vec) , x2c_lastWorldPos(vec) , x38_spring(spring) , x50_scale(scale) {} }; class CBallCamera : public CGameCamera { public: DEFINE_ENTITY enum class EBallCameraState { Default, One, Chase, Boost, ToBall, FromBall }; enum class EBallCameraBehaviour { Default, FreezeLookPosition, // Unused HintBallToCam, HintInitializePosition, HintFixedPosition, HintFixedTransform, PathCameraDesiredPos, // Unused PathCamera, SpindleCamera }; enum class ESplineState { Invalid, Nav, Arc }; private: struct SFailsafeState { zeus::CTransform x0_playerXf; zeus::CTransform x30_camXf; zeus::CVector3f x60_lookPos; zeus::CVector3f x6c_behindPos; zeus::CVector3f x78_; zeus::CVector3f x84_playerPos; std::vector x90_splinePoints; }; EBallCameraBehaviour x188_behaviour = EBallCameraBehaviour::Default; bool x18c_24_ : 1 = true; bool x18c_25_chaseAllowed : 1 = true; bool x18c_26_boostAllowed : 1 = true; bool x18c_27_obscureAvoidance : 1 = true; bool x18c_28_volumeCollider : 1 = true; bool x18c_29_clampAttitude : 1 = false; bool x18c_30_clampAzimuth : 1 = false; bool x18c_31_clearLOS : 1 = true; bool x18d_24_prevClearLOS : 1 = true; bool x18d_25_avoidGeometryFull : 1 = false; bool x18d_26_lookAtBall : 1 = false; bool x18d_27_forceProcessing : 1 = false; bool x18d_28_obtuseDirection : 1 = false; bool x18d_29_noElevationInterp : 1 = false; bool x18d_30_directElevation : 1 = false; bool x18d_31_overrideLookDir : 1 = false; bool x18e_24_noElevationVelClamp : 1 = false; bool x18e_25_noSpline : 1 = false; bool x18e_26_ : 1 = false; bool x18e_27_nearbyDoorClosed : 1 = false; bool x18e_28_nearbyDoorClosing : 1 = false; float x190_curMinDistance; float x194_targetMinDistance; float x198_maxDistance; float x19c_backwardsDistance; float x1a0_elevation; float x1a4_curAnglePerSecond; float x1a8_targetAnglePerSecond; float x1ac_attitudeRange = zeus::degToRad(89.f); float x1b0_azimuthRange = zeus::degToRad(89.f); zeus::CVector3f x1b4_lookAtOffset; zeus::CVector3f x1c0_lookPosAhead; zeus::CVector3f x1cc_fixedLookPos; zeus::CVector3f x1d8_lookPos; zeus::CTransform x1e4_nextLookXf; CCameraSpring x214_ballCameraSpring; CCameraSpring x228_ballCameraCentroidSpring; CCameraSpring x23c_ballCameraLookAtSpring; CCameraSpring x250_ballCameraCentroidDistanceSpring; std::vector x264_smallColliders; std::vector x274_mediumColliders; std::vector x284_largeColliders; zeus::CVector3f x294_dampedPos; zeus::CVector3f x2a0_smallCentroid = zeus::skUp; zeus::CVector3f x2ac_mediumCentroid = zeus::skUp; zeus::CVector3f x2b8_largeCentroid = zeus::skUp; int x2c4_smallCollidersObsCount = 0; int x2c8_mediumCollidersObsCount = 0; int x2cc_largeCollidersObsCount = 0; int x2d0_smallColliderIt = 0; int x2d4_mediumColliderIt = 0; int x2d8_largeColliderIt = 0; zeus::CVector3f x2dc_prevBallPos; float x2e8_ballVelFlat = 0.f; float x2ec_maxBallVel = 0.f; zeus::CVector3f x2f0_ballDelta; zeus::CVector3f x2fc_ballDeltaFlat; float x308_speedFactor = 0.f; float x30c_speedingTime = 0.f; zeus::CVector3f x310_idealLookVec; zeus::CVector3f x31c_predictedLookPos; u32 x328_avoidGeomCycle = 0; float x32c_colliderMag = 1.f; float x330_clearColliderThreshold = 0.2f; zeus::CAABox x334_collidersAABB = zeus::skNullBox; float x34c_obscuredTime = 0.f; CMaterialList x350_obscuringMaterial = {EMaterialTypes::NoStepLogic}; float x358_unobscureMag = 0.f; zeus::CVector3f x35c_splineIntermediatePos; TUniqueId x368_obscuringObjectId = kInvalidUniqueId; ESplineState x36c_splineState = ESplineState::Invalid; bool x370_24_reevalSplineEnd : 1 = false; float x374_splineCtrl = 0.f; float x378_splineCtrlRange; CCameraSpline x37c_camSpline{false}; CMaterialList x3c8_collisionExcludeList = {EMaterialTypes::NoStepLogic}; bool x3d0_24_camBehindFloorOrWall : 1 = false; float x3d4_elevInterpTimer = 0.f; float x3d8_elevInterpStart = 0.f; TUniqueId x3dc_tooCloseActorId = kInvalidUniqueId; float x3e0_tooCloseActorDist = 10000.f; bool x3e4_pendingFailsafe = false; float x3e8_ = 0.f; float x3ec_ = 0.f; float x3f0_ = 0.f; float x3f4_ = 2.f; float x3f8_ = 0.f; float x3fc_ = 0.f; EBallCameraState x400_state = EBallCameraState::Default; float x404_chaseElevation; float x408_chaseDistance; float x40c_chaseAnglePerSecond; zeus::CVector3f x410_chaseLookAtOffset; CCameraSpring x41c_ballCameraChaseSpring; float x430_boostElevation; float x434_boostDistance; float x438_boostAnglePerSecond; zeus::CVector3f x43c_boostLookAtOffset; CCameraSpring x448_ballCameraBoostSpring; zeus::CVector3f x45c_overrideBallToCam; float x468_conservativeDoorCamDistance; TUniqueId x46c_collisionActorId = kInvalidUniqueId; float x470_clampVelTimer = 0.f; float x474_clampVelRange = 0.f; u32 x478_shortMoveCount = 0; std::unique_ptr x47c_failsafeState; std::unique_ptr x480_; void SetupColliders(std::vector& out, float xMag, float zMag, float radius, int count, float k, float max, float startAngle); void BuildSplineNav(CStateManager& mgr); void BuildSplineArc(CStateManager& mgr); bool ShouldResetSpline(CStateManager& mgr) const; void UpdatePlayerMovement(float dt, CStateManager& mgr); void UpdateTransform(const zeus::CVector3f& lookDir, const zeus::CVector3f& pos, float dt, CStateManager& mgr); zeus::CVector3f ConstrainYawAngle(const CPlayer& player, float distance, float yawSpeed, float dt, CStateManager& mgr) const; void CheckFailsafe(float dt, CStateManager& mgr); void UpdateObjectTooCloseId(CStateManager& mgr); void UpdateAnglePerSecond(float dt); void UpdateUsingPathCameras(float dt, CStateManager& mgr); zeus::CVector3f GetFixedLookTarget(const zeus::CVector3f& hintToLookDir, CStateManager& mgr) const; void UpdateUsingFixedCameras(float dt, CStateManager& mgr); [[nodiscard]] zeus::CVector3f ComputeVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& posDelta) const; zeus::CVector3f TweenVelocity(const zeus::CVector3f& curVel, const zeus::CVector3f& newVel, float rate, float dt); zeus::CVector3f MoveCollisionActor(const zeus::CVector3f& pos, float dt, CStateManager& mgr); void UpdateUsingFreeLook(float dt, CStateManager& mgr); zeus::CVector3f InterpolateCameraElevation(const zeus::CVector3f& camPos, float dt); [[nodiscard]] zeus::CVector3f CalculateCollidersCentroid(const std::vector& colliderList, int numObscured) const; zeus::CVector3f ApplyColliders(); void UpdateColliders(const zeus::CTransform& xf, std::vector& colliderList, int& it, int count, float tolerance, const EntityList& nearList, float dt, CStateManager& mgr); zeus::CVector3f AvoidGeometry(const zeus::CTransform& xf, const EntityList& nearList, float dt, CStateManager& mgr); zeus::CVector3f AvoidGeometryFull(const zeus::CTransform& xf, const EntityList& nearList, float dt, CStateManager& mgr); zeus::CAABox CalculateCollidersBoundingBox(const std::vector& colliderList, CStateManager& mgr) const; [[nodiscard]] int CountObscuredColliders(const std::vector& colliderList) const; void UpdateCollidersDistances(std::vector& colliderList, float xMag, float zMag, float angOffset); void UpdateUsingColliders(float dt, CStateManager& mgr); void UpdateUsingSpindleCameras(float dt, CStateManager& mgr); zeus::CVector3f ClampElevationToWater(zeus::CVector3f& pos, CStateManager& mgr) const; void UpdateTransitionFromBallCamera(CStateManager& mgr); void UpdateUsingTransitions(float dt, CStateManager& mgr); zeus::CTransform UpdateCameraPositions(float dt, const zeus::CTransform& oldXf, const zeus::CTransform& newXf); static zeus::CVector3f GetFailsafeSplinePoint(const std::vector& points, float t); bool CheckFailsafeFromMorphBallState(CStateManager& mgr) const; bool SplineIntersectTest(CMaterialList& intersectMat, CStateManager& mgr) const; void ActivateFailsafe(float dt, CStateManager& mgr); bool ConstrainElevationAndDistance(float& elevation, float& distance, float dt, CStateManager& mgr); zeus::CVector3f FindDesiredPosition(float distance, float elevation, const zeus::CVector3f& dir, CStateManager& mgr, bool fullTest); static bool DetectCollision(const zeus::CVector3f& from, const zeus::CVector3f& to, float radius, float& d, CStateManager& mgr); void TeleportColliders(std::vector& colliderList, const zeus::CVector3f& pos); static bool CheckTransitionLineOfSight(const zeus::CVector3f& eyePos, const zeus::CVector3f& behindPos, float& eyeToOccDist, float colRadius, CStateManager& mgr); public: CBallCamera(TUniqueId uid, TUniqueId watchedId, const zeus::CTransform& xf, float fovy, float znear, float zfar, float aspect); void Accept(IVisitor& visitor) override; void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId objId, CStateManager& stateMgr) override; void ProcessInput(const CFinalInput& input, CStateManager& mgr) override; void Reset(const zeus::CTransform&, CStateManager& mgr) override; void Render(CStateManager& mgr) override; [[nodiscard]] EBallCameraBehaviour GetBehaviour() const { return x188_behaviour; } [[nodiscard]] EBallCameraState GetState() const { return x400_state; } void SetState(EBallCameraState state, CStateManager& mgr); void Think(float dt, CStateManager& mgr) override; bool TransitionFromMorphBallState(CStateManager& mgr); [[nodiscard]] TUniqueId GetTooCloseActorId() const { return x3dc_tooCloseActorId; } [[nodiscard]] float GetTooCloseActorDistance() const { return x3e0_tooCloseActorDist; } void TeleportCamera(const zeus::CVector3f& pos, CStateManager& mgr); void TeleportCamera(const zeus::CTransform& xf, CStateManager& mgr); [[nodiscard]] const zeus::CVector3f& GetLookPos() const { return x1d8_lookPos; } void ResetToTweaks(CStateManager& mgr); void UpdateLookAtPosition(float dt, CStateManager& mgr); zeus::CTransform UpdateLookDirection(const zeus::CVector3f& dir, CStateManager& mgr); void SetClampVelTimer(float f) { x470_clampVelTimer = f; } void SetClampVelRange(float f) { x474_clampVelRange = f; } void ApplyCameraHint(CStateManager& mgr); void ResetPosition(CStateManager& mgr); void DoorClosed(TUniqueId doorId); void DoorClosing(TUniqueId doorId); [[nodiscard]] const zeus::CVector3f& GetLookPosAhead() const { return x1c0_lookPosAhead; } [[nodiscard]] const zeus::CVector3f& GetFixedLookPos() const { return x1cc_fixedLookPos; } const TUniqueId GetCollisionActorId() const { return x46c_collisionActorId; } static bool IsBallNearDoor(const zeus::CVector3f& pos, CStateManager& mgr); }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraFilter.cpp ================================================ #include "Runtime/Camera/CCameraFilter.hpp" #include "Runtime/CDvdFile.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include #include namespace metaforce { void CCameraFilterPass::Update(float dt) { if (x10_remTime <= 0.f) return; EFilterType origType = x0_curType; x10_remTime = std::max(0.f, x10_remTime - dt); x18_curColor = zeus::CColor::lerp(x1c_nextColor, x14_prevColor, x10_remTime / xc_duration); if (x10_remTime == 0.f) { x0_curType = x4_nextType; if (x0_curType == EFilterType::Passthru) { x24_texObj = TLockedToken(); x20_nextTxtr = {}; } } } void CCameraFilterPass::SetFilter(EFilterType type, EFilterShape shape, float time, const zeus::CColor& color, CAssetId txtr) { if (time == 0.f) { xc_duration = 0.f; x10_remTime = 0.f; if (txtr.IsValid()) x24_texObj = g_SimplePool->GetObj({FOURCC('TXTR'), txtr}); x4_nextType = type; x0_curType = type; x8_shape = shape; x1c_nextColor = color; x18_curColor = color; x14_prevColor = color; x20_nextTxtr = txtr; } else { EFilterType origType = x0_curType; CAssetId origTxtr = x20_nextTxtr; x1c_nextColor = color; x14_prevColor = x18_curColor; x8_shape = shape; x20_nextTxtr = txtr; if (txtr.IsValid()) x24_texObj = g_SimplePool->GetObj({FOURCC('TXTR'), txtr}); x10_remTime = time; xc_duration = time; x0_curType = x4_nextType; x4_nextType = type; if (type == EFilterType::Passthru) { if (x0_curType == EFilterType::Multiply) x1c_nextColor = zeus::skWhite; else if (x0_curType == EFilterType::Add || x0_curType == EFilterType::Blend) x1c_nextColor.a() = 0.f; } else { if (x0_curType == EFilterType::Passthru) { if (type == EFilterType::Multiply) { x18_curColor = zeus::skWhite; } else if (type == EFilterType::Add || type == EFilterType::Blend) { x18_curColor = x1c_nextColor; x18_curColor.a() = 0.f; x14_prevColor = x18_curColor; } } x0_curType = x4_nextType; } } } void CCameraFilterPass::DisableFilter(float time) { SetFilter(EFilterType::Passthru, x8_shape, time, zeus::skWhite, {}); } void CCameraFilterPass::Draw() { DrawFilter(x0_curType, x8_shape, x18_curColor, x24_texObj.GetObj(), GetT(x4_nextType == EFilterType::Passthru)); } float CCameraFilterPass::GetT(bool invert) const { float tmp; if (xc_duration == 0.f) tmp = 1.f; else tmp = 1.f - x10_remTime / xc_duration; if (invert) return 1.f - tmp; return tmp; } void CCameraFilterPass::DrawFilter(EFilterType type, EFilterShape shape, const zeus::CColor& color, CTexture* tex, float lod) { switch (type) { case EFilterType::Multiply: g_Renderer->SetBlendMode_ColorMultiply(); break; case EFilterType::Invert: g_Renderer->SetBlendMode_InvertDst(); break; case EFilterType::Add: g_Renderer->SetBlendMode_AdditiveAlpha(); break; case EFilterType::Subtract: CGX::SetBlendMode(GX_BM_SUBTRACT, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR); break; case EFilterType::Blend: g_Renderer->SetBlendMode_AlphaBlended(); break; case EFilterType::Widescreen: return; case EFilterType::SceneAdd: g_Renderer->SetBlendMode_AdditiveDestColor(); break; case EFilterType::NoColor: g_Renderer->SetBlendMode_NoColorWrite(); break; default: return; } DrawFilterShape(shape, color, tex, lod); g_Renderer->SetBlendMode_AlphaBlended(); } void CCameraFilterPass::DrawFullScreenColoredQuad(const zeus::CColor& color) { SCOPED_GRAPHICS_DEBUG_GROUP("CCameraFilterPass::DrawFullScreenColoredQuad", zeus::skBlue); const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f); g_Renderer->SetDepthReadWrite(false, false); g_Renderer->BeginTriangleStrip(4); g_Renderer->PrimColor(color); g_Renderer->PrimVertex({lt.x() - 1.f, 0.f, 1.f + rb.y()}); g_Renderer->PrimVertex({lt.x() - 1.f, 0.f, lt.y() - 1.f}); g_Renderer->PrimVertex({1.f + rb.x(), 0.f, 1.f + rb.y()}); g_Renderer->PrimVertex({1.f + rb.x(), 0.f, lt.y() - 1.f}); g_Renderer->EndPrimitive(); } void CCameraFilterPass::DrawFilterShape(EFilterShape shape, const zeus::CColor& color, CTexture* tex, float lod) { switch (shape) { default: if (tex == nullptr) { DrawFullScreenColoredQuad(color); } else { DrawFullScreenTexturedQuad(color, tex, lod); } break; case EFilterShape::FullscreenQuarters: if (tex == nullptr) { DrawFullScreenColoredQuad(color); } else { DrawFullScreenTexturedQuadQuarters(color, tex, lod); } break; case EFilterShape::CinemaBars: DrawWideScreen(color, tex, lod); break; case EFilterShape::ScanLinesEven: DrawScanLines(color, true); break; case EFilterShape::ScanLinesOdd: DrawScanLines(color, false); break; case EFilterShape::RandomStatic: DrawRandomStatic(color, 1.f, false); break; case EFilterShape::CookieCutterDepthRandomStatic: DrawRandomStatic(color, lod, true); break; } } void CCameraFilterPass::DrawFullScreenTexturedQuadQuarters(const zeus::CColor& color, CTexture* tex, float lod) { SCOPED_GRAPHICS_DEBUG_GROUP("CCameraFilterPass::DrawFullScreenTexturedQuadQuarters", zeus::skBlue); const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); g_Renderer->SetDepthReadWrite(false, false); if (tex != nullptr) { tex->Load(GX_TEXMAP0, EClampMode::Clamp); } CGraphics::SetCullMode(ERglCullMode::None); for (int i = 0; i < 4; ++i) { g_Renderer->SetModelMatrix(zeus::CTransform::Scale((i & 1) != 0 ? 1.f : -1.f, 0.f, (i & 2) != 0 ? 1.f : -1.f)); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamColor(color); CGraphics::StreamTexcoord(lod, lod); CGraphics::StreamVertex(lt.x(), 0.f, rb.y()); CGraphics::StreamTexcoord(lod, 0.f); CGraphics::StreamVertex(lt.x(), 0.f, 0.f); CGraphics::StreamTexcoord(0.f, lod); CGraphics::StreamVertex(0.f, 0.f, rb.y()); CGraphics::StreamTexcoord(0.f, 0.f); CGraphics::StreamVertex(0.f, 0.f, 0.f); CGraphics::StreamEnd(); } CGraphics::SetCullMode(ERglCullMode::Front); } void CCameraFilterPass::DrawFullScreenTexturedQuad(const zeus::CColor& color, CTexture* tex, float lod) { SCOPED_GRAPHICS_DEBUG_GROUP("CCameraFilterPass::DrawFullScreenTexturedQuad", zeus::skBlue); const float u = 0.5f - 0.5f * lod; const float v = 0.5f + 0.5f * lod; const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f); g_Renderer->SetDepthReadWrite(false, false); if (tex != nullptr) { tex->Load(GX_TEXMAP0, EClampMode::Repeat); } CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamColor(color); CGraphics::StreamTexcoord(u, v); CGraphics::StreamVertex(lt.x() - 1.f, 0.f, 1.f + rb.y()); CGraphics::StreamTexcoord(u, u); CGraphics::StreamVertex(lt.x() - 1.f, 0.f, lt.y() - 1.f); CGraphics::StreamTexcoord(v, v); CGraphics::StreamVertex(1.f + rb.x(), 0.f, 1.f + rb.y()); CGraphics::StreamTexcoord(v, u); CGraphics::StreamVertex(1.f + rb.x(), 0.f, lt.y() - 1.f); CGraphics::StreamEnd(); } void CCameraFilterPass::DrawRandomStatic(const zeus::CColor& color, float alpha, bool cookieCutterDepth) { // TODO this shouldn't be here static CTexture m_randomStatic{ETexelFormat::IA4, 640, 448, 1, "Camera Random Static"}; SCOPED_GRAPHICS_DEBUG_GROUP("CCameraFilterPass::DrawRandomStatic", zeus::skBlue); const auto [lb, rt] = g_Renderer->SetViewportOrtho(true, 0.f, 1.f); if (cookieCutterDepth) { CGraphics::SetAlphaCompare(ERglAlphaFunc::GEqual, static_cast((1.f - alpha) * 255.f), ERglAlphaOp::And, ERglAlphaFunc::Always, 0); g_Renderer->SetDepthReadWrite(true, true); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); } else { g_Renderer->SetDepthReadWrite(false, false); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulateColor); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); } // Upload random static texture (game reads from .text) const u8* buf = CDvdFile::GetDolBuf() + 0x4f60; u8* out = m_randomStatic.Lock(); memcpy(out, buf + ROUND_UP_32(rand() & 0x7fff), m_randomStatic.GetMemoryAllocated()); m_randomStatic.UnLock(); m_randomStatic.Load(GX_TEXMAP0, EClampMode::Clamp); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamColor(color); CGraphics::StreamTexcoord(0.f, 1.f); CGraphics::StreamVertex(lb.x() - 1.f, 0.01f, rt.y() + 1.f); CGraphics::StreamTexcoord(0.f, 0.f); CGraphics::StreamVertex(lb.x() - 1.f, 0.01f, lb.y() - 1.f); CGraphics::StreamTexcoord(1.f, 1.f); CGraphics::StreamVertex(rt.x() + 1.f, 0.01f, rt.y() + 1.f); CGraphics::StreamTexcoord(1.f, 0.f); CGraphics::StreamVertex(rt.x() + 1.f, 0.01f, lb.y() - 1.f); CGraphics::StreamEnd(); if (cookieCutterDepth) { CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0); } } void CCameraFilterPass::DrawScanLines(const zeus::CColor& color, bool even) { SCOPED_GRAPHICS_DEBUG_GROUP("CCameraFilterPass::DrawScanLines", zeus::skBlue); const auto [lt, rb] = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f); g_Renderer->SetDepthReadWrite(false, false); g_Renderer->SetModelMatrix({}); // CGraphics::SetLineWidth(2.f, 5); // g_Renderer->BeginLines(...); // TODO // CGraphics::SetLineWidth(1.f, 5); } void CCameraFilterPass::DrawWideScreen(const zeus::CColor& color, CTexture* tex, float lod) { SCOPED_GRAPHICS_DEBUG_GROUP("CCameraFilterPass::DrawWideScreen", zeus::skBlue); // const auto vp = g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f); // float f = -((vp.second.x() - vp.first.x()) * 0.0625f * 9.f - (vp.second.y() - vp.first.y())) * 0.5f; // g_Renderer->SetDepthReadWrite(false, false); // g_Renderer->SetModelMatrix({}); // if (tex != nullptr) { // tex->Load(GX_TEXMAP0, EClampMode::Repeat); // } // CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::sTevPass805a5ebc); // CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::skPassThru); // CGraphics::StreamBegin(GX_TRIANGLESTRIP); // float x = rand() % 4000; } void CCameraBlurPass::Draw(bool clearDepth) { if (x10_curType == EBlurType::NoBlur) return; // TODO // if (x10_curType == EBlurType::Xray) { // if (!m_xrayShader) // m_xrayShader.emplace(x0_paletteTex); // m_xrayShader->draw(x1c_curValue); // } else { // if (!m_shader) // m_shader.emplace(); // m_shader->draw(x1c_curValue, clearDepth); // if (clearDepth) // CGraphics::SetDepthRange(DEPTH_NEAR, DEPTH_FAR); // } } void CCameraBlurPass::Update(float dt) { if (x28_remainingTime > 0.f) { x28_remainingTime = std::max(x28_remainingTime - dt, 0.f); x1c_curValue = x18_endValue + (x20_startValue - x18_endValue) * x28_remainingTime / x24_totalTime; if (x28_remainingTime != 0.f) return; x10_curType = x14_endType; } } void CCameraBlurPass::SetBlur(EBlurType type, float amount, float duration, bool usePersistentFb) { // TODO impl usePersistentFb if (duration == 0.f) { x24_totalTime = 0.f; x28_remainingTime = 0.f; x18_endValue = amount; x1c_curValue = amount; x20_startValue = amount; if (x10_curType == EBlurType::NoBlur) { if (type == EBlurType::Xray) x0_paletteTex = g_SimplePool->GetObj("TXTR_XRayPalette"); } x14_endType = type; x10_curType = type; x2c_usePersistent = usePersistentFb; } else { x2c_usePersistent = usePersistentFb; x24_totalTime = duration; x28_remainingTime = duration; x18_endValue = x1c_curValue; x20_startValue = amount; if (type != x14_endType) { if (x10_curType == EBlurType::NoBlur) { if (type == EBlurType::Xray) x0_paletteTex = g_SimplePool->GetObj("TXTR_XRayPalette"); x10_curType = type; } x14_endType = type; } } } void CCameraBlurPass::DisableBlur(float duration) { SetBlur(EBlurType::NoBlur, 0.f, duration, x2c_usePersistent); } } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraFilter.hpp ================================================ #pragma once #include "Runtime/CToken.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/RetroTypes.hpp" #include namespace metaforce { enum class EFilterType { Passthru, Multiply, Invert, Add, Subtract, Blend, Widescreen, SceneAdd, NoColor, InvDstMultiply }; enum class EFilterShape { Fullscreen, FullscreenHalvesLeftRight, FullscreenHalvesTopBottom, FullscreenQuarters, CinemaBars, ScanLinesEven, ScanLinesOdd, RandomStatic, CookieCutterDepthRandomStatic }; class CCameraFilterPass { EFilterType x0_curType = EFilterType::Passthru; EFilterType x4_nextType = EFilterType::Passthru; EFilterShape x8_shape = EFilterShape::Fullscreen; float xc_duration = 0.f; float x10_remTime = 0.f; zeus::CColor x14_prevColor; zeus::CColor x18_curColor; zeus::CColor x1c_nextColor; CAssetId x20_nextTxtr; TLockedToken x24_texObj; // Used to be auto_ptr [[nodiscard]] float GetT(bool invert) const; static void DrawFilterShape(EFilterShape shape, const zeus::CColor& color, CTexture* tex, float lod); static void DrawFullScreenColoredQuad(const zeus::CColor& color); static void DrawFullScreenTexturedQuad(const zeus::CColor& color, CTexture* tex, float lod); static void DrawFullScreenTexturedQuadQuarters(const zeus::CColor& color, CTexture* tex, float lod); static void DrawRandomStatic(const zeus::CColor& color, float alpha, bool cookieCutterDepth); static void DrawScanLines(const zeus::CColor& color, bool even); static void DrawWideScreen(const zeus::CColor& color, CTexture* tex, float lod); public: void Update(float dt); void SetFilter(EFilterType type, EFilterShape shape, float time, const zeus::CColor& color, CAssetId txtr); void DisableFilter(float time); void Draw(); static void DrawFilter(EFilterType type, EFilterShape shape, const zeus::CColor& color, CTexture* tex, float lod); }; enum class EBlurType { NoBlur, LoBlur, HiBlur, Xray }; class CCameraBlurPass { TLockedToken x0_paletteTex; EBlurType x10_curType = EBlurType::NoBlur; EBlurType x14_endType = EBlurType::NoBlur; float x18_endValue = 0.f; float x1c_curValue = 0.f; float x20_startValue = 0.f; float x24_totalTime = 0.f; float x28_remainingTime = 0.f; bool x2c_usePersistent = false; bool x2d_noPersistentCopy = false; u32 x30_persistentBuf = 0; public: void Draw(bool clearDepth = false); void Update(float dt); void SetBlur(EBlurType type, float amount, float duration, bool usePersistentFb); void DisableBlur(float duration); EBlurType GetCurrType() const { return x10_curType; } }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraManager.cpp ================================================ #include "Runtime/Camera/CCameraManager.hpp" #include #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Camera/CBallCamera.hpp" #include "Runtime/Camera/CCameraShakeData.hpp" #include "Runtime/Camera/CCinematicCamera.hpp" #include "Runtime/Camera/CFirstPersonCamera.hpp" #include "Runtime/Camera/CInterpolationCamera.hpp" #include "Runtime/Camera/CPathCamera.hpp" #include "Runtime/World/CExplosion.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptCameraHint.hpp" #include "Runtime/World/CScriptSpindleCamera.hpp" #include "Runtime/World/CScriptWater.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { float CCameraManager::sFirstPersonFOV = 55.f; CCameraManager::CCameraManager(TUniqueId curCameraId) : x0_curCameraId(curCameraId) { CSfxManager::AddListener(CSfxManager::ESfxChannels::Game, zeus::skZero3f, zeus::skZero3f, {1.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, 50.f, 50.f, 1000.f, 1, 1.f); sFirstPersonFOV = g_tweakGame->GetFirstPersonFOV(); } bool CCameraManager::IsInFirstPersonCamera() const { return x7c_fpCamera->GetUniqueId() == x0_curCameraId; } zeus::CVector3f CCameraManager::GetGlobalCameraTranslation(const CStateManager& stateMgr) const { const CGameCamera* camera = GetCurrentCamera(stateMgr); return camera->GetTransform().rotate(x30_shakeOffset); } zeus::CTransform CCameraManager::GetCurrentCameraTransform(const CStateManager& stateMgr) const { const CGameCamera* camera = GetCurrentCamera(stateMgr); return camera->GetTransform() * zeus::CTransform::Translate(x30_shakeOffset); } void CCameraManager::RemoveCameraShaker(u32 id) { const auto iter = std::find_if(x14_shakers.cbegin(), x14_shakers.cend(), [id](const auto& shaker) { return shaker.xbc_shakerId == id; }); if (iter == x14_shakers.cend()) { return; } x14_shakers.erase(iter); } int CCameraManager::AddCameraShaker(const CCameraShakeData& data, bool sfx) { x14_shakers.emplace_back(data).xbc_shakerId = ++x2c_lastShakeId; if (!xa0_24_pendingRumble) { xa0_24_pendingRumble = true; x90_rumbleCooldown = 0.5f; } if (sfx && data.x0_duration > 0.f) { float vol = zeus::clamp(100.f, std::max(data.GetMaxAMComponent(), data.GetMaxFMComponent()) * 9.f + 100.f, 127.f); CSfxHandle sfxHandle; if (data.xc0_flags & 0x1) sfxHandle = CSfxManager::AddEmitter(SFXamb_x_rumble_lp_00, data.xc4_sfxPos, zeus::skZero3f, vol / 127.f, false, false, 0x7f, kInvalidAreaId); else sfxHandle = CSfxManager::SfxStart(SFXamb_x_rumble_lp_00, vol / 127.f, 0.f, false, 0x7f, false, kInvalidAreaId); sfxHandle->SetTimeRemaining(data.x0_duration); } return x2c_lastShakeId; } void CCameraManager::EnterCinematic(CStateManager& mgr) { mgr.GetPlayer().GetPlayerGun()->CancelFiring(mgr); mgr.GetPlayer().UnFreeze(mgr); for (const CEntity* ent : mgr.GetAllObjectList()) { if (const TCastToConstPtr explo = ent) { mgr.FreeScriptObject(explo->GetUniqueId()); } else if (const TCastToConstPtr weap = ent) { if (weap->GetActive()) { if (False(weap->GetAttribField() & EProjectileAttrib::KeepInCinematic)) { if (TCastToConstPtr(mgr.GetObjectById(weap->GetOwnerId())) || TCastToConstPtr(mgr.GetObjectById(weap->GetOwnerId()))) mgr.FreeScriptObject(weap->GetUniqueId()); } } } } } void CCameraManager::AddCinemaCamera(TUniqueId id, CStateManager& stateMgr) { if (x4_cineCameras.empty()) { EnterCinematic(stateMgr); } RemoveCinemaCamera(id, stateMgr); x4_cineCameras.push_back(id); if (const TCastToPtr cam = stateMgr.ObjectById(id)) { // Into player eye if ((cam->GetFlags() & 0x4) != 0) { float time = 4.f; float delayTime = cam->GetDuration() - 4.f; if (delayTime < 0.f) { delayTime = 0.f; time = cam->GetDuration(); } cam->SetFovInterpolation(cam->GetFov(), 55.f, time, delayTime); } } } void CCameraManager::SetInsideFluid(bool isInside, TUniqueId fluidId) { if (isInside) { ++x74_fluidCounter; x78_fluidId = fluidId; } else { --x74_fluidCounter; } } void CCameraManager::Update(float dt, CStateManager& stateMgr) { UpdateCameraHints(dt, stateMgr); ThinkCameras(dt, stateMgr); UpdateListener(stateMgr); UpdateRumble(dt, stateMgr); UpdateFog(dt, stateMgr); } CGameCamera* CCameraManager::GetCurrentCamera(CStateManager& stateMgr) const { CObjectList* camList = stateMgr.ObjectListById(EGameObjectList::GameCamera); return static_cast(camList->GetObjectById(GetCurrentCameraId())); } const CGameCamera* CCameraManager::GetCurrentCamera(const CStateManager& stateMgr) const { const CObjectList* camList = stateMgr.GetObjectListById(EGameObjectList::GameCamera); return static_cast(camList->GetObjectById(GetCurrentCameraId())); } void CCameraManager::CreateStandardCameras(CStateManager& stateMgr) { TUniqueId fpId = stateMgr.AllocateUniqueId(); x7c_fpCamera = new CFirstPersonCamera(fpId, zeus::CTransform(), stateMgr.Player()->GetUniqueId(), g_tweakPlayer->GetOrbitCameraSpeed(), sFirstPersonFOV, NearPlane(), FarPlane(), Aspect()); stateMgr.AddObject(x7c_fpCamera); stateMgr.Player()->SetCameraState(CPlayer::EPlayerCameraState::FirstPerson, stateMgr); SetCurrentCameraId(fpId, stateMgr); x80_ballCamera = new CBallCamera(stateMgr.AllocateUniqueId(), stateMgr.Player()->GetUniqueId(), zeus::CTransform(), ThirdPersonFOV(), NearPlane(), FarPlane(), Aspect()); stateMgr.AddObject(x80_ballCamera); x88_interpCamera = new CInterpolationCamera(stateMgr.AllocateUniqueId(), zeus::CTransform()); stateMgr.AddObject(x88_interpCamera); } void CCameraManager::SkipCinematic(CStateManager& stateMgr) { const TUniqueId camId = GetCurrentCameraId(); auto* ent = static_cast(stateMgr.ObjectById(camId)); while (ent) { ent->SetActive(false); ent->WasDeactivated(stateMgr); ent = TCastToPtr(GetCurrentCamera(stateMgr)).GetPtr(); } stateMgr.GetPlayer().UpdateCinematicState(stateMgr); x7c_fpCamera->SkipCinematic(); } void CCameraManager::SetPathCamera(TUniqueId id, CStateManager& mgr) { xa4_pathCamId = id; if (const TCastToPtr cam = mgr.ObjectById(id)) { cam->Reset(GetCurrentCameraTransform(mgr), mgr); x80_ballCamera->TeleportCamera(cam->GetTransform(), mgr); } } void CCameraManager::SetSpindleCamera(TUniqueId id, CStateManager& mgr) { xa2_spindleCamId = id; if (const TCastToPtr cam = mgr.ObjectById(id)) { cam->Reset(GetCurrentCameraTransform(mgr), mgr); x80_ballCamera->TeleportCamera(cam->GetTransform(), mgr); } } void CCameraManager::InterpolateToBallCamera(const zeus::CTransform& xf, TUniqueId camId, const zeus::CVector3f& lookPos, float maxTime, float positionSpeed, float rotationSpeed, bool sinusoidal, CStateManager& mgr) { if (!IsInFirstPersonCamera()) { x88_interpCamera->SetInterpolation(xf, lookPos, maxTime, positionSpeed, rotationSpeed, camId, sinusoidal, mgr); if (!ShouldBypassInterpolation()) SetCurrentCameraId(x88_interpCamera->GetUniqueId(), mgr); } } void CCameraManager::RestoreHintlessCamera(CStateManager& mgr) { const TCastToConstPtr hint = mgr.ObjectById(xa6_camHintId); const zeus::CTransform ballCamXf = x80_ballCamera->GetTransform(); xa6_camHintId = kInvalidUniqueId; xa8_hintPriority = 1000; if (!hint) { return; } zeus::CVector3f camToPlayerFlat = mgr.GetPlayer().GetTranslation() - ballCamXf.origin; camToPlayerFlat.z() = 0.f; if (camToPlayerFlat.canBeNormalized()) { camToPlayerFlat.normalize(); } else { camToPlayerFlat = mgr.GetPlayer().GetMoveDir(); } x80_ballCamera->ResetToTweaks(mgr); x80_ballCamera->UpdateLookAtPosition(0.f, mgr); if (!mgr.GetPlayer().IsMorphBallTransitioning() && hint->GetHint().GetBehaviourType() != CBallCamera::EBallCameraBehaviour::Default) { if ((hint->GetHint().GetOverrideFlags() & 0x1000) != 0) { x80_ballCamera->SetClampVelRange(hint->GetHint().GetClampVelRange()); x80_ballCamera->SetClampVelTimer(hint->GetHint().GetClampVelTime()); } else { x80_ballCamera->TeleportCamera(x80_ballCamera->UpdateLookDirection(camToPlayerFlat, mgr), mgr); InterpolateToBallCamera(ballCamXf, x80_ballCamera->GetUniqueId(), x80_ballCamera->GetLookPos(), hint->GetHint().GetClampVelTime(), hint->GetHint().GetClampVelRange(), hint->GetHint().GetClampRotRange(), (hint->GetHint().GetOverrideFlags() & 0x800) != 0, mgr); } } } void CCameraManager::SkipBallCameraCinematic(CStateManager& mgr) { if (IsInCinematicCamera()) { x80_ballCamera->TeleportCamera(GetLastCineCamera(mgr)->GetTransform(), mgr); x80_ballCamera->SetFovInterpolation(GetLastCineCamera(mgr)->GetFov(), x80_ballCamera->GetFov(), 1.f, 0.f); SkipCinematic(mgr); SetCurrentCameraId(x80_ballCamera->GetUniqueId(), mgr); } } void CCameraManager::ApplyCameraHint(const CScriptCameraHint& hint, CStateManager& mgr) { if (x80_ballCamera->GetState() == CBallCamera::EBallCameraState::ToBall) { x80_ballCamera->SetState(CBallCamera::EBallCameraState::Default, mgr); mgr.GetPlayer().SetCameraState(CPlayer::EPlayerCameraState::Ball, mgr); } const TCastToConstPtr oldHint = mgr.ObjectById(xa6_camHintId); xa6_camHintId = hint.GetUniqueId(); xa8_hintPriority = hint.GetPriority(); const zeus::CTransform camXf = GetCurrentCameraTransform(mgr); x80_ballCamera->ApplyCameraHint(mgr); if ((hint.GetHint().GetOverrideFlags() & 0x20) != 0) { x80_ballCamera->ResetPosition(mgr); } switch (hint.GetHint().GetBehaviourType()) { case CBallCamera::EBallCameraBehaviour::PathCameraDesiredPos: case CBallCamera::EBallCameraBehaviour::PathCamera: SetPathCamera(hint.GetDelegatedCamera(), mgr); break; case CBallCamera::EBallCameraBehaviour::SpindleCamera: SetSpindleCamera(hint.GetDelegatedCamera(), mgr); break; default: SetPathCamera(kInvalidUniqueId, mgr); SetSpindleCamera(kInvalidUniqueId, mgr); break; } if ((hint.GetHint().GetOverrideFlags() & 0x2000) != 0) { SkipBallCameraCinematic(mgr); } x80_ballCamera->UpdateLookAtPosition(0.f, mgr); if ((hint.GetHint().GetOverrideFlags() & 0x20) == 0 && (hint.GetHint().GetBehaviourType() != CBallCamera::EBallCameraBehaviour::Default || (oldHint && oldHint->GetHint().GetBehaviourType() != CBallCamera::EBallCameraBehaviour::Default))) { InterpolateToBallCamera(camXf, x80_ballCamera->GetUniqueId(), x80_ballCamera->GetLookPos(), hint.GetHint().GetInterpolateTime(), hint.GetHint().GetClampVelRange(), hint.GetHint().GetClampRotRange(), (hint.GetHint().GetOverrideFlags() & 0x400) != 0, mgr); } } void CCameraManager::UpdateCameraHints(float, CStateManager& mgr) { bool invalidHintRemoved = false; for (auto it = xac_cameraHints.begin(); it != xac_cameraHints.end();) { if (!TCastToConstPtr(mgr.ObjectById(it->second))) { invalidHintRemoved = true; it = xac_cameraHints.erase(it); continue; } ++it; } bool inactiveHintRemoved = false; for (const auto& id : x2b0_inactiveCameraHints) { if (const TCastToConstPtr hint = mgr.GetObjectById(id)) { if (hint->GetHelperCount() == 0 || hint->GetInactive()) { for (auto it = xac_cameraHints.begin(); it != xac_cameraHints.end(); ++it) { if (it->second == id) { xac_cameraHints.erase(it); if (xa6_camHintId == id) { inactiveHintRemoved = true; SetPathCamera(kInvalidUniqueId, mgr); SetSpindleCamera(kInvalidUniqueId, mgr); } break; } } } } } x2b0_inactiveCameraHints.clear(); bool activeHintAdded = false; for (const auto& id : x334_activeCameraHints) { if (const TCastToConstPtr hint = mgr.GetObjectById(id)) { bool activeHintPresent = false; for (auto it = xac_cameraHints.begin(); it != xac_cameraHints.end(); ++it) { if (it->second == id) { activeHintPresent = true; break; } } if (!activeHintPresent) { activeHintAdded = true; xac_cameraHints.emplace_back(hint->GetPriority(), id); } } } x334_activeCameraHints.clear(); if (inactiveHintRemoved || activeHintAdded || invalidHintRemoved) { std::sort(xac_cameraHints.begin(), xac_cameraHints.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); zeus::CTransform ballCamXf = x80_ballCamera->GetTransform(); if ((inactiveHintRemoved || invalidHintRemoved) && xac_cameraHints.empty()) { RestoreHintlessCamera(mgr); return; } bool foundHint = false; const CScriptCameraHint* bestHint = nullptr; for (auto& h : xac_cameraHints) { if (const TCastToConstPtr hint = mgr.ObjectById(h.second)) { bestHint = hint.GetPtr(); foundHint = true; break; } } if (!foundHint) { RestoreHintlessCamera(mgr); } bool changeHint = false; if (bestHint && foundHint) { if ((bestHint->GetHint().GetOverrideFlags() & 0x80) != 0 && xac_cameraHints.size() > 1) { zeus::CVector3f ballPos = mgr.GetPlayer().GetBallPosition(); if ((bestHint->GetHint().GetOverrideFlags() & 0x100) != 0) { zeus::CVector3f camToBall = ballPos - ballCamXf.origin; if (camToBall.canBeNormalized()) { camToBall.normalize(); } else { camToBall = ballCamXf.basis[1]; } for (auto it = xac_cameraHints.begin() + 1; it != xac_cameraHints.end(); ++it) { if (const TCastToConstPtr hint = mgr.ObjectById(it->second)) { if ((hint->GetHint().GetOverrideFlags() & 0x80) != 0 && hint->GetPriority() == bestHint->GetPriority() && hint->GetAreaIdAlways() == bestHint->GetAreaIdAlways()) { zeus::CVector3f hintToBall = ballPos - bestHint->GetTranslation(); if (hintToBall.canBeNormalized()) { hintToBall.normalize(); } else { hintToBall = bestHint->GetTransform().basis[1]; } const float camHintDot = zeus::clamp(-1.f, camToBall.dot(hintToBall), 1.f); zeus::CVector3f thisHintToBall = ballPos - hint->GetTranslation(); if (thisHintToBall.canBeNormalized()) { thisHintToBall.normalize(); } else { thisHintToBall = hint->GetTransform().basis[1]; } const float camThisHintDot = zeus::clamp(-1.f, camToBall.dot(thisHintToBall), 1.f); if (camThisHintDot > camHintDot) { bestHint = hint.GetPtr(); } } else { break; } } else { break; } } } else { if (const TCastToConstPtr act = mgr.GetObjectById(bestHint->GetFirstHelper())) { const zeus::CVector3f f26 = act->GetTranslation() - mgr.GetPlayer().GetBallPosition(); zeus::CVector3f ballToHelper = f26; if (ballToHelper.canBeNormalized()) { ballToHelper.normalize(); } else { ballToHelper = bestHint->GetTransform().basis[1]; } for (auto it = xac_cameraHints.begin() + 1; it != xac_cameraHints.end(); ++it) { if (const TCastToConstPtr hint = mgr.ObjectById(it->second)) { if ((hint->GetHint().GetOverrideFlags() & 0x80) != 0 && hint->GetPriority() == bestHint->GetPriority() && hint->GetAreaIdAlways() == bestHint->GetAreaIdAlways()) { zeus::CVector3f hintToHelper = act->GetTranslation() - bestHint->GetTranslation(); if (hintToHelper.canBeNormalized()) { hintToHelper.normalize(); } else { hintToHelper = bestHint->GetTransform().basis[1]; } const float ballHintDot = zeus::clamp(-1.f, ballToHelper.dot(hintToHelper), 1.f); zeus::CVector3f thisBallToHelper = f26; if (thisBallToHelper.canBeNormalized()) { thisBallToHelper.normalize(); } else { thisBallToHelper = hint->GetTransform().basis[1]; } zeus::CVector3f thisHintToHelper = act->GetTranslation() - hint->GetTranslation(); if (thisHintToHelper.canBeNormalized()) { thisHintToHelper.normalize(); } else { thisHintToHelper = hint->GetTransform().basis[1]; } const float thisBallHintDot = zeus::clamp(-1.f, thisBallToHelper.dot(thisHintToHelper), 1.f); if (thisBallHintDot > ballHintDot) { bestHint = hint.GetPtr(); } } else { break; } } else { break; } } } } if (bestHint->GetUniqueId() != xa6_camHintId) { changeHint = true; } } else if (xa6_camHintId != bestHint->GetUniqueId()) { if (bestHint->GetHint().GetBehaviourType() == CBallCamera::EBallCameraBehaviour::HintInitializePosition) { if ((bestHint->GetHint().GetOverrideFlags() & 0x20) != 0) { x80_ballCamera->TeleportCamera(zeus::lookAt(bestHint->GetTranslation(), x80_ballCamera->GetLookPos()), mgr); } DeleteCameraHint(bestHint->GetUniqueId(), mgr); if ((bestHint->GetHint().GetOverrideFlags() & 0x2000) != 0) { SkipBallCameraCinematic(mgr); } changeHint = false; } else { changeHint = true; } } if (changeHint) { ApplyCameraHint(*bestHint, mgr); } } } } void CCameraManager::ThinkCameras(float dt, CStateManager& mgr) { CGameCameraList& gcList = mgr.GetCameraObjectList(); for (CEntity* ent : gcList) { if (const TCastToPtr gc = ent) { gc->Think(dt, mgr); gc->UpdatePerspective(dt); } } if (IsInCinematicCamera()) { return; } const TUniqueId camId = GetLastCameraId(); if (const CGameCamera* cam = TCastToConstPtr(mgr.GetObjectById(camId))) { x3bc_curFov = cam->GetFov(); } } void CCameraManager::UpdateFog(float dt, CStateManager& mgr) { if (x98_fogDensitySpeed != 0.f) { x94_fogDensityFactor += dt * x98_fogDensitySpeed; if ((x98_fogDensitySpeed > 0.f) ? x94_fogDensityFactor > x9c_fogDensityFactorTarget : x94_fogDensityFactor < x9c_fogDensityFactorTarget) { x94_fogDensityFactor = x9c_fogDensityFactorTarget; x98_fogDensitySpeed = 0.f; } } if (x74_fluidCounter) { if (const TCastToConstPtr water = mgr.GetObjectById(x78_fluidId)) { const zeus::CVector2f zRange(GetCurrentCamera(mgr)->GetNearClipDistance(), CalculateFogDensity(mgr, water.GetPtr())); x3c_fog.SetFogExplicit(ERglFogMode::PerspExp, water->GetInsideFogColor(), zRange); if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) { mgr.GetCameraFilterPass(4).DisableFilter(0.f); } else { mgr.GetCameraFilterPass(4).SetFilter(EFilterType::Multiply, EFilterShape::Fullscreen, 0.f, water->GetInsideFogColor(), {}); } } xa0_26_inWater = true; } else if (xa0_26_inWater) { mgr.GetCameraManager()->x3c_fog.DisableFog(); mgr.GetCameraFilterPass(4).DisableFilter(0.f); xa0_26_inWater = false; } x3c_fog.Update(dt); } void CCameraManager::UpdateRumble(float dt, CStateManager& mgr) { x30_shakeOffset = zeus::skZero3f; for (auto it = x14_shakers.begin(); it != x14_shakers.end();) { CCameraShakeData& shaker = *it; shaker.Update(dt, mgr); if (shaker.x4_curTime >= shaker.x0_duration) { it = x14_shakers.erase(it); continue; } x30_shakeOffset += shaker.GetPoint(); ++it; } if (!x14_shakers.empty() && !xa0_25_rumbling && xa0_24_pendingRumble) { mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::CameraShake, 1.f, ERumblePriority::Two); xa0_25_rumbling = true; } if (x90_rumbleCooldown > 0.f) { x90_rumbleCooldown -= dt; } else if (xa0_25_rumbling) { xa0_24_pendingRumble = false; xa0_25_rumbling = false; } if (mgr.GetPlayer().GetCameraState() != CPlayer::EPlayerCameraState::FirstPerson && !IsInCinematicCamera()) { x30_shakeOffset = zeus::skZero3f; } } void CCameraManager::UpdateListener(CStateManager& mgr) { const zeus::CTransform xf = GetCurrentCameraTransform(mgr); CSfxManager::UpdateListener(xf.origin, zeus::skZero3f, xf.frontVector(), xf.upVector(), 1.f); } float CCameraManager::CalculateFogDensity(CStateManager& mgr, const CScriptWater* water) const { const float distanceFactor = 1.f - water->GetFluidPlane().GetAlpha(); float distance = 0; if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) { distance = g_tweakGame->GetGravityWaterFogDistanceRange() * distanceFactor + g_tweakGame->GetGravityWaterFogDistanceBase(); } else { distance = g_tweakGame->GetWaterFogDistanceRange() * distanceFactor + g_tweakGame->GetWaterFogDistanceBase(); } return distance * x94_fogDensityFactor; } void CCameraManager::ResetCameras(CStateManager& mgr) { zeus::CTransform xf = mgr.GetPlayer().CreateTransformFromMovementDirection(); xf.origin = mgr.GetPlayer().GetEyePosition(); for (CEntity* ent : mgr.GetCameraObjectList()) { const TCastToPtr camObj(ent); camObj->Reset(xf, mgr); } } void CCameraManager::SetSpecialCameras(CFirstPersonCamera& fp, CBallCamera& ball) { x7c_fpCamera = &fp; x80_ballCamera = &ball; } void CCameraManager::ProcessInput(const CFinalInput& input, CStateManager& stateMgr) { for (CEntity* ent : stateMgr.GetCameraObjectList()) { if (ent == nullptr) { continue; } auto& cam = static_cast(*ent); if (input.ControllerIdx() != cam.x16c_controllerIdx) { continue; } cam.ProcessInput(input, stateMgr); } } void CCameraManager::RenderCameras(CStateManager& mgr) { for (CEntity* cam : mgr.GetCameraObjectList()) { static_cast(cam)->Render(mgr); } } void CCameraManager::SetupBallCamera(CStateManager& mgr) { if (const TCastToConstPtr hint = mgr.ObjectById(xa6_camHintId)) { if (hint->GetHint().GetBehaviourType() == CBallCamera::EBallCameraBehaviour::HintInitializePosition) { if ((hint->GetHint().GetOverrideFlags() & 0x20) != 0) { x80_ballCamera->TeleportCamera(hint->GetTransform(), mgr); } AddInactiveCameraHint(xa6_camHintId, mgr); } else { ApplyCameraHint(*hint, mgr); } } } void CCameraManager::SetPlayerCamera(CStateManager& mgr, TUniqueId newCamId) { if (x88_interpCamera->GetActive()) { x88_interpCamera->SetActive(false); x80_ballCamera->SkipFovInterpolation(); if (!ShouldBypassInterpolation()) SetCurrentCameraId(newCamId, mgr); } } float CCameraManager::GetCameraBobMagnitude() const { return 1.f - zeus::clamp(-1.f, std::fabs(zeus::clamp(-1.f, x7c_fpCamera->GetTransform().basis[1].dot(zeus::skUp), 1.f)) / std::cos(2.f * M_PIF / 12.f), 1.f); } bool CCameraManager::HasBallCameraInitialPositionHint(CStateManager& mgr) const { if (HasCameraHint(mgr)) { switch (mgr.GetCameraManager()->GetCameraHint(mgr)->GetHint().GetBehaviourType()) { case CBallCamera::EBallCameraBehaviour::HintBallToCam: case CBallCamera::EBallCameraBehaviour::HintFixedPosition: case CBallCamera::EBallCameraBehaviour::HintFixedTransform: case CBallCamera::EBallCameraBehaviour::PathCamera: case CBallCamera::EBallCameraBehaviour::SpindleCamera: return true; default: return false; } } return false; } void CCameraManager::RemoveCinemaCamera(TUniqueId uid, CStateManager& mgr) { const auto search = std::find(x4_cineCameras.cbegin(), x4_cineCameras.cend(), uid); if (search == x4_cineCameras.cend()) { return; } x4_cineCameras.erase(search); } void CCameraManager::DeleteCameraHint(TUniqueId id, CStateManager& mgr) { const TCastToPtr hint = mgr.ObjectById(id); if (!hint) { return; } const auto search = std::find_if(x2b0_inactiveCameraHints.cbegin(), x2b0_inactiveCameraHints.cend(), [id](TUniqueId tid) { return tid == id; }); if (search != x2b0_inactiveCameraHints.cend()) { return; } hint->ClearIdList(); hint->SetInactive(true); if (x2b0_inactiveCameraHints.size() != 64) { x2b0_inactiveCameraHints.push_back(id); } } void CCameraManager::AddInactiveCameraHint(TUniqueId id, CStateManager& mgr) { if (const TCastToConstPtr hint = mgr.ObjectById(id)) { const auto search = std::find_if(x2b0_inactiveCameraHints.cbegin(), x2b0_inactiveCameraHints.cend(), [id](TUniqueId tid) { return tid == id; }); if (search == x2b0_inactiveCameraHints.cend() && x2b0_inactiveCameraHints.size() != 64) { x2b0_inactiveCameraHints.push_back(id); } } } void CCameraManager::AddActiveCameraHint(TUniqueId id, CStateManager& mgr) { if (const TCastToConstPtr hint = mgr.ObjectById(id)) { const auto search = std::find_if(x334_activeCameraHints.cbegin(), x334_activeCameraHints.cend(), [id](TUniqueId tid) { return tid == id; }); if (search == x334_activeCameraHints.cend() && xac_cameraHints.size() != 64 && x334_activeCameraHints.size() != 64) { x334_activeCameraHints.push_back(id); } } } TUniqueId CCameraManager::GetLastCineCameraId() const { if (x4_cineCameras.empty()) { return kInvalidUniqueId; } return x4_cineCameras.back(); } const CCinematicCamera* CCameraManager::GetLastCineCamera(CStateManager& mgr) const { return static_cast(mgr.GetObjectById(GetLastCineCameraId())); } const CScriptCameraHint* CCameraManager::GetCameraHint(CStateManager& mgr) const { return TCastToConstPtr(mgr.GetObjectById(xa6_camHintId)).GetPtr(); } bool CCameraManager::HasCameraHint(CStateManager& mgr) const { if (xac_cameraHints.empty() || xa6_camHintId == kInvalidUniqueId) return false; return mgr.GetObjectById(xa6_camHintId) != nullptr; } bool CCameraManager::IsInterpolationCameraActive() const { return x88_interpCamera->GetActive(); } void CCameraManager::SetFogDensity(float fogDensityTarget, float fogDensitySpeed) { x9c_fogDensityFactorTarget = fogDensityTarget; x98_fogDensitySpeed = (x9c_fogDensityFactorTarget >= x94_fogDensityFactor ? fogDensitySpeed : -fogDensitySpeed); } } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraManager.hpp ================================================ #pragma once #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/World/CGameArea.hpp" #include namespace metaforce { class CBallCamera; class CCameraShakeData; class CCinematicCamera; class CFirstPersonCamera; class CGameCamera; class CInterpolationCamera; class CScriptCameraHint; class CScriptWater; class CStateManager; struct CFinalInput; class CCameraManager { static float sFirstPersonFOV; TUniqueId x0_curCameraId; std::vector x4_cineCameras; std::list x14_shakers; u32 x2c_lastShakeId = 0; zeus::CVector3f x30_shakeOffset; CGameArea::CAreaFog x3c_fog; int x74_fluidCounter = 0; TUniqueId x78_fluidId = kInvalidUniqueId; CFirstPersonCamera* x7c_fpCamera = nullptr; CBallCamera* x80_ballCamera = nullptr; s16 x84_rumbleId = -1; CInterpolationCamera* x88_interpCamera = nullptr; float x90_rumbleCooldown = 0.f; float x94_fogDensityFactor = 1.f; float x98_fogDensitySpeed = 0.f; float x9c_fogDensityFactorTarget = 1.f; bool xa0_24_pendingRumble : 1 = false; bool xa0_25_rumbling : 1 = false; bool xa0_26_inWater : 1 = false; TUniqueId xa2_spindleCamId = kInvalidUniqueId; TUniqueId xa4_pathCamId = kInvalidUniqueId; TUniqueId xa6_camHintId = kInvalidUniqueId; s32 xa8_hintPriority = 1000; rstl::reserved_vector, 64> xac_cameraHints; rstl::reserved_vector x2b0_inactiveCameraHints; rstl::reserved_vector x334_activeCameraHints; bool x3b8_24_ : 1 = false; bool x3b8_25_ : 1 = false; float x3bc_curFov = 60.f; void SetPathCamera(TUniqueId id, CStateManager& mgr); void SetSpindleCamera(TUniqueId id, CStateManager& mgr); void RestoreHintlessCamera(CStateManager& mgr); void InterpolateToBallCamera(const zeus::CTransform& xf, TUniqueId camId, const zeus::CVector3f& lookPos, float maxTime, float positionSpeed, float rotationSpeed, bool sinusoidal, CStateManager& mgr); void SkipBallCameraCinematic(CStateManager& mgr); void ApplyCameraHint(const CScriptCameraHint& hint, CStateManager& mgr); void EnterCinematic(CStateManager& mgr); public: explicit CCameraManager(TUniqueId curCameraId = kInvalidUniqueId); static float Aspect() { return 1.42f; } static float FarPlane() { return 750.0f; } static float NearPlane() { return 0.2f; } static float FirstPersonFOV() { return sFirstPersonFOV; } static float ThirdPersonFOV() { return 60.0f; } void ResetCameras(CStateManager& mgr); void SetSpecialCameras(CFirstPersonCamera& fp, CBallCamera& ball); bool IsInCinematicCamera() const { return x4_cineCameras.size() != 0; } bool IsInFirstPersonCamera() const; zeus::CVector3f GetGlobalCameraTranslation(const CStateManager& stateMgr) const; zeus::CTransform GetCurrentCameraTransform(const CStateManager& stateMgr) const; void RemoveCameraShaker(u32 id); int AddCameraShaker(const CCameraShakeData& data, bool sfx); void AddCinemaCamera(TUniqueId id, CStateManager& stateMgr); void RemoveCinemaCamera(TUniqueId uid, CStateManager& mgr); void SetInsideFluid(bool isInside, TUniqueId fluidId); void Update(float dt, CStateManager& stateMgr); CGameCamera* GetCurrentCamera(CStateManager& stateMgr) const; const CGameCamera* GetCurrentCamera(const CStateManager& stateMgr) const; void SetCurrentCameraId(TUniqueId id, CStateManager& stateMgr) { x0_curCameraId = id; } void CreateStandardCameras(CStateManager& stateMgr); TUniqueId GetCurrentCameraId() const { if (x4_cineCameras.size()) return x4_cineCameras.back(); return x0_curCameraId; } TUniqueId GetLastCameraId() const { if (x4_cineCameras.size()) return x4_cineCameras.back(); return kInvalidUniqueId; } void SkipCinematic(CStateManager& stateMgr); CFirstPersonCamera* GetFirstPersonCamera() { return x7c_fpCamera; } const CFirstPersonCamera* GetFirstPersonCamera() const { return x7c_fpCamera; } CBallCamera* GetBallCamera() { return x80_ballCamera; } const CBallCamera* GetBallCamera() const { return x80_ballCamera; } CGameArea::CAreaFog& Fog() { return x3c_fog; } const CGameArea::CAreaFog& Fog() const { return x3c_fog; } float GetCameraBobMagnitude() const; void UpdateCameraHints(float dt, CStateManager& mgr); void ThinkCameras(float dt, CStateManager& mgr); void UpdateFog(float dt, CStateManager& mgr); void UpdateRumble(float dt, CStateManager& mgr); void UpdateListener(CStateManager& mgr); float CalculateFogDensity(CStateManager& mgr, const CScriptWater* water) const; void SetFogDensity(float fogDensityTarget, float fogDensitySpeed); void ProcessInput(const CFinalInput& input, CStateManager& stateMgr); void RenderCameras(CStateManager& mgr); void SetupBallCamera(CStateManager& mgr); void SetPlayerCamera(CStateManager& mgr, TUniqueId newCamId); int GetFluidCounter() const { return x74_fluidCounter; } bool HasBallCameraInitialPositionHint(CStateManager& mgr) const; void DeleteCameraHint(TUniqueId id, CStateManager& mgr); void AddInactiveCameraHint(TUniqueId id, CStateManager& mgr); void AddActiveCameraHint(TUniqueId id, CStateManager& mgr); TUniqueId GetLastCineCameraId() const; TUniqueId GetSpindleCameraId() const { return xa2_spindleCamId; } TUniqueId GetPathCameraId() const { return xa4_pathCamId; } const CCinematicCamera* GetLastCineCamera(CStateManager& mgr) const; const CScriptCameraHint* GetCameraHint(CStateManager& mgr) const; bool HasCameraHint(CStateManager& mgr) const; bool IsInterpolationCameraActive() const; bool ShouldBypassInterpolation() { return false; } }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraShakeData.cpp ================================================ #include "Runtime/Camera/CCameraShakeData.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/ScriptLoader.hpp" #include #include #include namespace metaforce { SCameraShakePoint SCameraShakePoint::LoadCameraShakePoint(CInputStream& in) { u32 useEnvelope = ScriptLoader::LoadParameterFlags(in); float attackTime = in.ReadFloat(); float sustainTime = in.ReadFloat(); float duration = in.ReadFloat(); float magnitude = in.ReadFloat(); return {useEnvelope != 0, attackTime, sustainTime, duration, magnitude}; } CCameraShakerComponent CCameraShakerComponent::LoadNewCameraShakerComponent(CInputStream& in) { u32 useModulation = ScriptLoader::LoadParameterFlags(in); SCameraShakePoint am = SCameraShakePoint::LoadCameraShakePoint(in); SCameraShakePoint fm = SCameraShakePoint::LoadCameraShakePoint(in); return {useModulation != 0, am, fm}; } CCameraShakeData::CCameraShakeData(CInputStream& in) { in.ReadLong(); in.ReadFloat(); in.ReadFloat(); in.ReadFloat(); in.ReadFloat(); in.ReadFloat(); in.ReadFloat(); in.ReadFloat(); in.ReadBool(); BuildProjectileCameraShake(0.5f, 0.75f); } void SCameraShakePoint::Update(float curTime) { float offTimePoint = xc_attackTime + x10_sustainTime; float factor = 1.f; if (curTime < xc_attackTime && xc_attackTime > 0.f) factor = zeus::clamp(0.f, curTime / xc_attackTime, 1.f); if (curTime >= offTimePoint && x14_duration > 0.f) factor = 1.f - (curTime - offTimePoint) / x14_duration; x4_value = x8_magnitude * factor; } void CCameraShakerComponent::Update(float curTime, float duration, float distAtt) { if (std::fabs(duration) < 0.00001f || !x4_useModulation) { x38_value = 0.f; return; } x20_fm.Update(curTime); float freq = 1.f + x20_fm.GetValue(); x8_am.Update(curTime); x38_value = x8_am.GetValue() * std::sin(2.f * M_PIF * (duration - curTime) * freq); x38_value *= distAtt; } void CCameraShakeData::Update(float dt, CStateManager& mgr) { x4_curTime += dt; float distAtt = 1.f; if (xc0_flags & 0x1) distAtt = 1.f - zeus::clamp(0.f, (xc4_sfxPos - mgr.GetPlayer().GetTranslation()).magnitude() / xd0_sfxDist, 1.f); x8_shakerX.Update(x4_curTime, x0_duration, distAtt); x44_shakerY.Update(x4_curTime, x0_duration, distAtt); x80_shakerZ.Update(x4_curTime, x0_duration, distAtt); } zeus::CVector3f CCameraShakeData::GetPoint() const { return {x8_shakerX.GetValue(), x44_shakerY.GetValue(), x80_shakerZ.GetValue()}; } float CCameraShakeData::GetMaxAMComponent() const { float ret = 0.f; if (x8_shakerX.x4_useModulation) ret = x8_shakerX.x8_am.GetValue(); if (x44_shakerY.x4_useModulation) ret = std::max(ret, x44_shakerY.x8_am.GetValue()); if (x80_shakerZ.x4_useModulation) ret = std::max(ret, x80_shakerZ.x8_am.GetValue()); return ret; } float CCameraShakeData::GetMaxFMComponent() const { float ret = 0.f; if (x8_shakerX.x4_useModulation) ret = x8_shakerX.x20_fm.GetValue(); if (x44_shakerY.x4_useModulation) ret = std::max(ret, x44_shakerY.x20_fm.GetValue()); if (x80_shakerZ.x4_useModulation) ret = std::max(ret, x80_shakerZ.x20_fm.GetValue()); return ret; } CCameraShakeData CCameraShakeData::LoadCameraShakeData(CInputStream& in) { const float xMag = in.ReadFloat(); in.ReadFloat(); const float yMag = in.ReadFloat(); in.ReadFloat(); const float zMag = in.ReadFloat(); in.ReadFloat(); const float duration = in.ReadFloat(); const SCameraShakePoint xAM(false, 0.f, 0.f, duration, 2.f * xMag); const SCameraShakePoint yAM(false, 0.f, 0.f, duration, 2.f * yMag); const SCameraShakePoint zAM(false, 0.f, 0.f, duration, 2.f * zMag); const SCameraShakePoint xFM(false, 0.f, 0.f, 0.5f * duration, 3.f); const SCameraShakePoint yFM(false, 0.f, 0.f, 0.5f * duration, 0.f); const SCameraShakePoint zFM(false, 0.f, 0.f, 0.5f * duration, 3.f); const CCameraShakerComponent shakerX(true, xAM, xFM); const CCameraShakerComponent shakerY; const CCameraShakerComponent shakerZ(true, zAM, zFM); return {duration, 100.f, 0, zeus::skZero3f, shakerX, shakerY, shakerZ}; } const CCameraShakeData CCameraShakeData::skChargedShotCameraShakeData{ 0.3f, 100.f, 0, zeus::skZero3f, CCameraShakerComponent{}, CCameraShakerComponent{ true, {false, 0.f, 0.f, 0.3f, -1.f}, {true, 0.f, 0.f, 0.05f, 0.3f}, }, CCameraShakerComponent{}, }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraShakeData.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include namespace metaforce { class CRandom16; class CStateManager; struct SCameraShakePoint { friend class CCameraShakeData; bool x0_useEnvelope = false; float x4_value = 0.f; float x8_magnitude = 0.f; float xc_attackTime = 0.f; float x10_sustainTime = 0.f; float x14_duration = 0.f; constexpr SCameraShakePoint() noexcept = default; constexpr SCameraShakePoint(bool useEnvelope, float attackTime, float sustainTime, float duration, float magnitude) noexcept : x0_useEnvelope(useEnvelope) , x8_magnitude(magnitude) , xc_attackTime(attackTime) , x10_sustainTime(sustainTime) , x14_duration(duration) {} [[nodiscard]] constexpr float GetValue() const noexcept { return x0_useEnvelope ? x8_magnitude : x4_value; } static SCameraShakePoint LoadCameraShakePoint(CInputStream& in); void Update(float curTime); }; class CCameraShakerComponent { friend class CCameraShakeData; bool x4_useModulation = false; SCameraShakePoint x8_am, x20_fm; float x38_value = 0.f; public: constexpr CCameraShakerComponent() noexcept = default; constexpr CCameraShakerComponent(bool useModulation, const SCameraShakePoint& am, const SCameraShakePoint& fm) noexcept : x4_useModulation(useModulation), x8_am(am), x20_fm(fm) {} static CCameraShakerComponent LoadNewCameraShakerComponent(CInputStream& in); void Update(float curTime, float duration, float distAtt); [[nodiscard]] constexpr float GetValue() const noexcept { return x38_value; } }; class CCameraShakeData { friend class CCameraManager; float x0_duration; float x4_curTime = 0.f; CCameraShakerComponent x8_shakerX; CCameraShakerComponent x44_shakerY; CCameraShakerComponent x80_shakerZ; u32 xbc_shakerId = 0; u32 xc0_flags; // 0x1: positional sfx zeus::CVector3f xc4_sfxPos; float xd0_sfxDist; public: static const CCameraShakeData skChargedShotCameraShakeData; constexpr CCameraShakeData(float duration, float sfxDist, u32 flags, const zeus::CVector3f& sfxPos, const CCameraShakerComponent& shaker1, const CCameraShakerComponent& shaker2, const CCameraShakerComponent& shaker3) noexcept : x0_duration(duration) , x8_shakerX(shaker1) , x44_shakerY(shaker2) , x80_shakerZ(shaker3) , xc0_flags(flags) , xc4_sfxPos(sfxPos) , xd0_sfxDist(sfxDist) {} constexpr CCameraShakeData(float duration, float magnitude) noexcept : CCameraShakeData( duration, 100.f, 0, zeus::skZero3f, CCameraShakerComponent{}, CCameraShakerComponent{}, CCameraShakerComponent{true, SCameraShakePoint{false, 0.25f * duration, 0.f, 0.75f * duration, magnitude}, SCameraShakePoint{true, 0.f, 0.f, 0.5f * duration, 2.f}}) {} explicit CCameraShakeData(CInputStream&); static constexpr CCameraShakeData BuildLandingCameraShakeData(float duration, float magnitude) noexcept { return { duration, 100.f, 0, zeus::skZero3f, CCameraShakerComponent{ true, SCameraShakePoint(false, 0.15f * duration, 0.f, 0.85f * duration, magnitude), SCameraShakePoint(true, 0.f, 0.f, 0.4f * duration, 1.5f), }, CCameraShakerComponent{}, CCameraShakerComponent{ true, SCameraShakePoint(false, 0.25f * duration, 0.f, 0.75f * duration, magnitude), SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 2.f), }, }; } static constexpr CCameraShakeData BuildProjectileCameraShake(float duration, float magnitude) noexcept { return { duration, 100.f, 0, zeus::skZero3f, CCameraShakerComponent{ true, SCameraShakePoint(false, 0.f, 0.f, duration, magnitude), SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 3.f), }, CCameraShakerComponent{}, CCameraShakerComponent{}, }; } static constexpr CCameraShakeData BuildMissileCameraShake(float duration, float magnitude, float sfxDistance, const zeus::CVector3f& sfxPos) noexcept { CCameraShakeData ret(duration, magnitude); ret.SetSfxPositionAndDistance(sfxPos, sfxDistance); return ret; } static constexpr CCameraShakeData BuildPhazonCameraShakeData(float duration, float magnitude) noexcept { return { duration, 100.f, 0, zeus::skZero3f, CCameraShakerComponent{ true, SCameraShakePoint(false, 0.15f * duration, 0.f, 0.25f * duration, magnitude), SCameraShakePoint(true, 0.f, 0.f, 0.4f * duration, 0.3f), }, CCameraShakerComponent{}, CCameraShakerComponent{ true, SCameraShakePoint(false, 0.25f * duration, 0.f, 0.25f * duration, magnitude), SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 0.5f), }, }; } static constexpr CCameraShakeData BuildPatternedExplodeShakeData(float duration, float magnitude) noexcept { return { duration, 100.f, 0, zeus::skZero3f, CCameraShakerComponent{ true, SCameraShakePoint(false, 0.25f * duration, 0.f, 0.75f * duration, magnitude), SCameraShakePoint(true, 0.f, 0.f, 0.5f * duration, 2.0f), }, CCameraShakerComponent{}, CCameraShakerComponent{}, }; } static constexpr CCameraShakeData BuildPatternedExplodeShakeData(const zeus::CVector3f& pos, float duration, float magnitude, float distance) noexcept { CCameraShakeData shakeData = BuildPatternedExplodeShakeData(duration, magnitude); shakeData.SetSfxPositionAndDistance(pos, distance); return shakeData; } void Update(float dt, CStateManager& mgr); zeus::CVector3f GetPoint() const; float GetMaxAMComponent() const; float GetMaxFMComponent() const; void SetShakerId(u32 id) { xbc_shakerId = id; } u32 GetShakerId() const { return xbc_shakerId; } static CCameraShakeData LoadCameraShakeData(CInputStream& in); constexpr void SetSfxPositionAndDistance(const zeus::CVector3f& pos, float sfxDistance) noexcept { xc0_flags |= 0x1; xc4_sfxPos = pos; xd0_sfxDist = sfxDistance; } }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraSpline.cpp ================================================ #include "Runtime/Camera/CCameraSpline.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/World/CScriptCameraWaypoint.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CCameraSpline::CCameraSpline(bool closedLoop) : x48_closedLoop(closedLoop) {} void CCameraSpline::CalculateKnots(TUniqueId cameraId, const std::vector& connections, CStateManager& mgr) { const SConnection* lastConn = nullptr; for (const SConnection& conn : connections) { if (conn.x0_state == EScriptObjectState::CameraPath && conn.x4_msg == EScriptObjectMessage::Follow) { lastConn = &conn; } } if (lastConn != nullptr) { TCastToConstPtr waypoint = mgr.ObjectById(mgr.GetIdForScript(lastConn->x8_objId)); x14_wpTracker.clear(); x14_wpTracker.reserve(4); while (waypoint) { const auto search = std::find_if(x14_wpTracker.cbegin(), x14_wpTracker.cend(), [&waypoint](const auto& a) { return a == waypoint->GetUniqueId(); }); if (search == x14_wpTracker.cend()) { x14_wpTracker.push_back(waypoint->GetUniqueId()); waypoint = mgr.ObjectById(waypoint->GetRandomNextWaypointId(mgr)); } } Reset(x14_wpTracker.size()); x14_wpTracker.clear(); waypoint = mgr.ObjectById(mgr.GetIdForScript(lastConn->x8_objId)); while (waypoint) { const auto search = std::find_if(x14_wpTracker.cbegin(), x14_wpTracker.cend(), [&waypoint](const auto& a) { return a == waypoint->GetUniqueId(); }); if (search == x14_wpTracker.cend()) { x14_wpTracker.push_back(waypoint->GetUniqueId()); AddKnot(waypoint->GetTranslation(), waypoint->GetTransform().basis[1]); waypoint = mgr.ObjectById(waypoint->GetRandomNextWaypointId(mgr)); } } } } void CCameraSpline::Initialize(TUniqueId cameraId, const std::vector& connections, CStateManager& mgr) { CalculateKnots(cameraId, connections, mgr); x44_length = CalculateSplineLength(); } void CCameraSpline::Reset(size_t size) { x4_positions.clear(); x24_t.clear(); x34_directions.clear(); if (size == 0) { return; } x4_positions.reserve(size); x24_t.reserve(size); x34_directions.reserve(size); } void CCameraSpline::AddKnot(const zeus::CVector3f& pos, const zeus::CVector3f& dir) { x4_positions.push_back(pos); x34_directions.push_back(dir); } void CCameraSpline::SetKnotPosition(size_t idx, const zeus::CVector3f& pos) { if (idx >= x4_positions.size()) { return; } x4_positions[idx] = pos; } const zeus::CVector3f& CCameraSpline::GetKnotPosition(size_t idx) const { if (idx >= x4_positions.size()) { return zeus::skZero3f; } return x4_positions[idx]; } float CCameraSpline::GetKnotT(size_t idx) const { if (idx >= x4_positions.size()) { return 0.f; } return x24_t[idx]; } float CCameraSpline::CalculateSplineLength() { float ret = 0.f; x24_t.clear(); if (!x4_positions.empty()) { zeus::CVector3f prevPoint = x4_positions[0]; float tDiv = 1.f / float(x4_positions.size() - 1); for (size_t i = 0; i < x4_positions.size(); ++i) { float subT = 0.f; float baseT = i * tDiv; x24_t.push_back(ret); while (subT <= tDiv) { subT += tDiv * 0.03125f; zeus::CVector3f nextPoint = GetInterpolatedSplinePointByTime(baseT + subT, 1.f); zeus::CVector3f delta = nextPoint - prevPoint; if (delta.canBeNormalized()) { prevPoint = nextPoint; ret += delta.magnitude(); } } } x24_t.push_back(ret); if (x48_closedLoop) { zeus::CVector3f delta = x4_positions[0] - x4_positions[x4_positions.size() - 1]; if (delta.canBeNormalized()) ret += delta.magnitude(); } return ret; } return 0.f; } bool CCameraSpline::GetSurroundingPoints(size_t idx, rstl::reserved_vector& positions, rstl::reserved_vector& directions) const { if (x4_positions.size() <= 3 || idx < 0 || idx >= x4_positions.size()) { return false; } if (idx > 0) { positions.push_back(x4_positions[idx - 1]); directions.push_back(x34_directions[idx - 1]); } else if (x48_closedLoop) { positions.push_back(x4_positions[x4_positions.size() - 1]); directions.push_back(x34_directions[x4_positions.size() - 1]); } else { positions.push_back(x4_positions[0] - (x4_positions[1] - x4_positions[0])); directions.push_back(x34_directions[0]); } positions.push_back(x4_positions[idx]); directions.push_back(x34_directions[idx]); if (idx + 1 >= x4_positions.size()) { if (x48_closedLoop) { positions.push_back(x4_positions[idx - x4_positions.size()]); directions.push_back(x34_directions[idx - x4_positions.size()]); } else { positions.push_back(x4_positions[x4_positions.size() - 1] - (x4_positions[x4_positions.size() - 2] - x4_positions[x4_positions.size() - 1])); directions.push_back(x34_directions[x4_positions.size() - 1]); } } else { positions.push_back(x4_positions[idx + 1]); directions.push_back(x34_directions[idx + 1]); } if (idx + 2 >= x4_positions.size()) { if (x48_closedLoop) { positions.push_back(x4_positions[idx + 2 - x4_positions.size()]); directions.push_back(x34_directions[idx + 2 - x4_positions.size()]); } else { positions.push_back(x4_positions[x4_positions.size() - 1] - (x4_positions[x4_positions.size() - 2] - x4_positions[x4_positions.size() - 1])); directions.push_back(x34_directions[x4_positions.size() - 1]); } } else { positions.push_back(x4_positions[idx + 2]); directions.push_back(x34_directions[idx + 2]); } return true; } zeus::CTransform CCameraSpline::GetInterpolatedSplinePointByLength(float pos) const { if (x4_positions.empty()) return zeus::CTransform(); size_t baseIdx = 0; size_t i; for (i = 1; i < x4_positions.size(); ++i) { if (x24_t[i] > pos) { baseIdx = i - 1; break; } } if (i == x4_positions.size()) baseIdx = i - 1; if (pos < 0.f) baseIdx = 0; if (pos >= x44_length) { if (x48_closedLoop) { pos -= x44_length; baseIdx = 0; } else { baseIdx = x4_positions.size() - 2; pos = x44_length; } } float range; if (baseIdx == x4_positions.size() - 1) { if (x48_closedLoop) range = x44_length - x24_t[baseIdx]; else range = x44_length - x24_t[x4_positions.size() - 2]; } else { range = x24_t[baseIdx + 1] - x24_t[baseIdx]; } float t = zeus::clamp(0.f, (pos - x24_t[baseIdx]) / range, 1.f); rstl::reserved_vector positions; rstl::reserved_vector directions; if (GetSurroundingPoints(baseIdx, positions, directions)) { float f1 = zeus::clamp(-1.f, directions[1].dot(directions[2]), 1.f); if (f1 >= 1.f) { zeus::CTransform ret = zeus::lookAt(zeus::skZero3f, directions[2]); ret.origin = zeus::getCatmullRomSplinePoint(positions[0], positions[1], positions[2], positions[3], t); return ret; } else { zeus::CTransform ret = zeus::lookAt( zeus::skZero3f, zeus::CQuaternion::lookAt(directions[1], directions[2], std::acos(f1) * t).transform(directions[1])); ret.origin = zeus::getCatmullRomSplinePoint(positions[0], positions[1], positions[2], positions[3], t); return ret; } } return zeus::CTransform(); } zeus::CVector3f CCameraSpline::GetInterpolatedSplinePointByTime(float time, float range) const { if (x4_positions.empty()) return {}; rstl::reserved_vector positions; rstl::reserved_vector directions; float rangeFac = range / float(x4_positions.size() - 1); int baseIdx = std::min(int(x4_positions.size() - 1), int(time / rangeFac)); if (GetSurroundingPoints(baseIdx, positions, directions)) return zeus::getCatmullRomSplinePoint(positions[0], positions[1], positions[2], positions[3], (time - float(baseIdx) * rangeFac) / rangeFac); return {}; } float CCameraSpline::FindClosestLengthOnSpline(float time, const zeus::CVector3f& p) const { float ret = -1.f; float minLenDelta = 10000.f; float minMag = 10000.f; size_t iterations = x4_positions.size() - 1; if (x48_closedLoop) iterations += 1; for (size_t i = 0; i < iterations; ++i) { const zeus::CVector3f& thisPos = x4_positions[i]; const zeus::CVector3f* nextPos; if (!x48_closedLoop) { nextPos = &x4_positions[i + 1]; } else { if (i == x4_positions.size() - 1) nextPos = &x4_positions[0]; else nextPos = &x4_positions[i + 1]; } zeus::CVector3f delta = *nextPos - thisPos; zeus::CVector3f nextDelta; zeus::CVector3f revDelta = thisPos - *nextPos; zeus::CVector3f nextRevDelta; if (i != 0) { nextDelta = delta + thisPos - x4_positions[i - 1]; } else { zeus::CVector3f extrap = x4_positions[0] - x4_positions[1] + x4_positions[0]; if (x48_closedLoop) extrap = x4_positions.back(); nextDelta = delta + thisPos - extrap; } nextDelta.normalize(); if (i < x4_positions.size() - 2) { nextRevDelta = revDelta + *nextPos - x4_positions[i + 2]; } else { zeus::CVector3f extrap; if (x48_closedLoop) { if (i == iterations - 1) extrap = x4_positions[1]; else extrap = x4_positions[0]; } else { extrap = x4_positions[i + 1] - x4_positions[i] + x4_positions[i + 1]; } nextRevDelta = revDelta + *nextPos - extrap; } nextRevDelta.normalize(); nextDelta.normalize(); nextRevDelta.normalize(); zeus::CVector3f ptToPlayer = p - thisPos; float proj = ptToPlayer.dot(nextDelta) / nextDelta.dot(delta.normalized()); zeus::CVector3f nextPtToPlayer = p - *nextPos; float nextProj = nextPtToPlayer.dot(nextRevDelta) / nextRevDelta.dot(revDelta.normalized()); float t = proj / (proj + nextProj); if (!x48_closedLoop) { if (i == 0 && t < 0.f) t = 0.f; if (i == x4_positions.size() - 2 && t > 1.f) t = 1.f; } if (t >= 0.f && t <= 1.f) { float tLen; if (i == x4_positions.size() - 1) tLen = x44_length - x24_t[i]; else tLen = x24_t[i + 1] - x24_t[i]; float lenT = t * tLen + x24_t[i]; zeus::CVector3f pointDelta = p - GetInterpolatedSplinePointByLength(lenT).origin; float mag = 0.f; if (pointDelta.canBeNormalized()) mag = pointDelta.magnitude(); float lenDelta = std::fabs(lenT - time); if (x48_closedLoop && lenDelta > x44_length - lenDelta) lenDelta = x44_length - lenDelta; if (zeus::close_enough(std::fabs(mag - minMag), 0.f)) { if (lenDelta < minLenDelta) { ret = lenT; minLenDelta = lenDelta; } } else { if (mag < minMag) { ret = lenT; minLenDelta = lenDelta; minMag = mag; } } } } return std::max(ret, 0.f); } float CCameraSpline::ValidateLength(float t) const { if (x48_closedLoop) { while (t >= x44_length) t -= x44_length; while (t < 0.f) t += x44_length; return t; } else { return zeus::clamp(0.f, t, x44_length); } } float CCameraSpline::ClampLength(const zeus::CVector3f& pos, bool collide, const CMaterialFilter& filter, const CStateManager& mgr) const { if (x4_positions.empty()) return 0.f; if (x48_closedLoop) return 0.f; zeus::CVector3f deltaA = pos - x4_positions.front(); zeus::CVector3f deltaB = pos - x4_positions.back(); float magA = deltaA.magnitude(); float magB = deltaB.magnitude(); if (!deltaA.canBeNormalized()) return 0.f; if (!deltaB.canBeNormalized()) return x44_length; if (collide) { bool collideA = mgr.RayStaticIntersection(x4_positions.front(), deltaA.normalized(), magA, filter).IsValid(); bool collideB = mgr.RayStaticIntersection(x4_positions.back(), deltaB.normalized(), magB, filter).IsValid(); if (collideA) return x44_length; if (collideB) return 0.f; } if (magA < magB) return 0.f; else return x44_length; } } // namespace metaforce ================================================ FILE: Runtime/Camera/CCameraSpline.hpp ================================================ #pragma once #include "World/CEntityInfo.hpp" #include "zeus/CVector3f.hpp" namespace metaforce { class CStateManager; class CMaterialFilter; class CCameraSpline { friend class CBallCamera; std::vector x4_positions; std::vector x14_wpTracker; std::vector x24_t; std::vector x34_directions; float x44_length = 0.f; bool x48_closedLoop = false; bool GetSurroundingPoints(size_t idx, rstl::reserved_vector& positions, rstl::reserved_vector& directions) const; public: explicit CCameraSpline(bool closedLoop); void CalculateKnots(TUniqueId cameraId, const std::vector& connections, CStateManager& mgr); void Initialize(TUniqueId cameraId, const std::vector& connections, CStateManager& mgr); void Reset(size_t size); void AddKnot(const zeus::CVector3f& pos, const zeus::CVector3f& dir); void SetKnotPosition(size_t idx, const zeus::CVector3f& pos); const zeus::CVector3f& GetKnotPosition(size_t idx) const; float GetKnotT(size_t idx) const; float CalculateSplineLength(); void UpdateSplineLength() { x44_length = CalculateSplineLength(); } zeus::CTransform GetInterpolatedSplinePointByLength(float pos) const; zeus::CVector3f GetInterpolatedSplinePointByTime(float time, float range) const; float FindClosestLengthOnSpline(float time, const zeus::CVector3f& p) const; float ValidateLength(float t) const; float ClampLength(const zeus::CVector3f& pos, bool collide, const CMaterialFilter& filter, const CStateManager& mgr) const; s32 GetSize() const { return x4_positions.size(); } float GetLength() const { return x44_length; } bool IsClosedLoop() const { return x48_closedLoop; } }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CCinematicCamera.cpp ================================================ #include "Runtime/Camera/CCinematicCamera.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptActor.hpp" #include "Runtime/World/CScriptCameraWaypoint.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CCinematicCamera::CCinematicCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active, float shotDuration, float fovy, float znear, float zfar, float aspect, u32 flags) : CGameCamera(uid, active, name, info, xf, fovy, znear, zfar, aspect, kInvalidUniqueId, (flags & 0x20) != 0, 0) , x1e8_duration(shotDuration) , x1f0_origFovy(fovy) , x1fc_origOrientation(zeus::CQuaternion(xf.basis)) , x21c_flags(flags) { x220_24_ = false; } void CCinematicCamera::Accept(IVisitor& visitor) { visitor.Visit(this); } void CCinematicCamera::ProcessInput(const CFinalInput&, CStateManager& mgr) { // Empty } void CCinematicCamera::Reset(const zeus::CTransform&, CStateManager& mgr) { // Empty } void CCinematicCamera::WasDeactivated(CStateManager& mgr) { mgr.GetCameraManager()->RemoveCinemaCamera(GetUniqueId(), mgr); mgr.GetPlayer().GetMorphBall()->LoadMorphBallModel(mgr); if ((x21c_flags & 0x100) != 0) { mgr.SetCinematicPause(false); } x188_viewPoints.clear(); x198_viewOrientations.clear(); x1a8_viewPointArrivals.clear(); x1b8_targets.clear(); x1c8_targetArrivals.clear(); x1d8_viewHFovs.clear(); } zeus::CVector3f CCinematicCamera::GetInterpolatedSplinePoint(const std::vector& points, int& idxOut, float tin) const { if (points.empty()) { return {}; } const float cycleT = std::fmod(tin, x1e8_duration); const float durPerPoint = x1e8_duration / float(points.size() - 1); idxOut = int(cycleT / durPerPoint); const float t = (cycleT - float(idxOut) * durPerPoint) / durPerPoint; if (points.size() == 1) { return points.front(); } if (points.size() == 2) { return (points[1] - points[0]) * t + points[0]; } zeus::CVector3f ptA; if (idxOut > 0) { ptA = points[idxOut - 1]; } else { ptA = points[0] - (points[1] - points[0]); } const zeus::CVector3f ptB = points[idxOut]; zeus::CVector3f ptC; if (size_t(idxOut + 1) >= points.size()) { const zeus::CVector3f& tmpA = points[points.size() - 1]; const zeus::CVector3f& tmpB = points[points.size() - 2]; ptC = tmpA - (tmpB - tmpA); } else { ptC = points[idxOut + 1]; } zeus::CVector3f ptD; if (size_t(idxOut + 2) >= points.size()) { const zeus::CVector3f& tmpA = points[points.size() - 1]; const zeus::CVector3f& tmpB = points[points.size() - 2]; ptD = tmpA - (tmpB - tmpA); } else { ptD = points[idxOut + 2]; } return zeus::getCatmullRomSplinePoint(ptA, ptB, ptC, ptD, t); } zeus::CQuaternion CCinematicCamera::GetInterpolatedOrientation(const std::vector& rotations, float tin) const { if (rotations.empty()) { return x1fc_origOrientation; } if (rotations.size() == 1) { return rotations.front(); } const float cycleT = std::fmod(tin, x1e8_duration); const float durPerPoint = x1e8_duration / float(rotations.size() - 1); const int idx = int(cycleT / durPerPoint); const float t = (cycleT - float(idx) * durPerPoint) / durPerPoint; return zeus::CQuaternion::slerp(rotations[idx], rotations[idx + 1], t); } float CCinematicCamera::GetInterpolatedHFov(const std::vector& fovs, float tin) const { if (fovs.empty()) { return x1f0_origFovy; } if (fovs.size() == 1) { return fovs.front(); } const float cycleT = std::fmod(tin, x1e8_duration); const float durPerPoint = x1e8_duration / float(fovs.size() - 1); const int idx = int(cycleT / durPerPoint); const float t = (cycleT - float(idx) * durPerPoint) / durPerPoint; return (fovs[idx + 1] - fovs[idx]) * t + fovs[idx]; } float CCinematicCamera::GetMoveOutofIntoAlpha() const { const float startDist = 0.25f + x160_znear; const float endDist = 1.f * startDist; const float deltaMag = (GetTranslation() - x210_moveIntoEyePos).magnitude(); if (deltaMag >= startDist && deltaMag <= endDist) { return (deltaMag - startDist) / (endDist - startDist); } if (deltaMag > endDist) { return 1.f; } return 0.f; } void CCinematicCamera::DeactivateSelf(CStateManager& mgr) { SetActive(false); SendScriptMsgs(EScriptObjectState::Inactive, mgr, EScriptObjectMessage::None); WasDeactivated(mgr); } void CCinematicCamera::Think(float dt, CStateManager& mgr) { if (GetActive()) { zeus::CVector3f viewPoint = GetTranslation(); if (!x188_viewPoints.empty()) { int idx = 0; viewPoint = GetInterpolatedSplinePoint(x188_viewPoints, idx, x1ec_t); if (idx > x1f4_passedViewPoint) { x1f4_passedViewPoint = idx; SendArrivedMsg(x1a8_viewPointArrivals[x1f4_passedViewPoint], mgr); } } const zeus::CQuaternion orientation = GetInterpolatedOrientation(x198_viewOrientations, x1ec_t); if ((x21c_flags & 0x1) == 0) { if (!x1b8_targets.empty()) { int idx = 0; zeus::CVector3f target = GetInterpolatedSplinePoint(x1b8_targets, idx, x1ec_t); if (x1b8_targets.size() == 1) { if (const TCastToConstPtr act = mgr.GetObjectById(x1c8_targetArrivals.front())) { target = act->GetTranslation(); } else { x1ec_t = x1e8_duration; } } if (idx > x1f8_passedTarget) { x1f8_passedTarget = idx; SendArrivedMsg(x1c8_targetArrivals[x1f8_passedTarget], mgr); } const zeus::CVector3f upVec = orientation.transform(zeus::skUp); if ((target - viewPoint).toVec2f().magnitude() < 0.0011920929f) { SetTranslation(target); } else { SetTransform(zeus::lookAt(viewPoint, target, upVec)); } } else { SetTransform(zeus::CTransform(orientation, viewPoint)); } } else { zeus::CVector3f target = mgr.GetPlayer().GetTranslation(); if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { target.z() += mgr.GetPlayer().GetMorphBall()->GetBallRadius(); } else { target.z() += mgr.GetPlayer().GetEyeHeight(); } const zeus::CVector3f upVec = orientation.transform(zeus::skUp); if ((target - viewPoint).toVec2f().magnitude() < 0.0011920929f) { SetTranslation(target); } else { SetTransform(zeus::lookAt(viewPoint, target, upVec)); } } x15c_currentFov = GetInterpolatedHFov(x1d8_viewHFovs, x1ec_t) / x168_aspect; x170_24_perspDirty = true; if (x20c_lookAtId != kInvalidUniqueId) { if (const TCastToPtr act = mgr.ObjectById(x20c_lookAtId)) { if (act->IsPlayerActor()) { act->SetDrawFlags({5, 0, 3, zeus::CColor(1.f, GetMoveOutofIntoAlpha())}); } } } x1ec_t += dt; if (x1ec_t > x1e8_duration) { for (auto i = static_cast(x1f4_passedViewPoint) + 1; i < x1a8_viewPointArrivals.size(); ++i) { SendArrivedMsg(x1a8_viewPointArrivals[i], mgr); } for (auto i = static_cast(x1f8_passedTarget) + 1; i < x1c8_targetArrivals.size(); ++i) { SendArrivedMsg(x1c8_targetArrivals[i], mgr); } DeactivateSelf(mgr); } } } void CCinematicCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { CGameCamera::AcceptScriptMsg(msg, uid, mgr); switch (msg) { case EScriptObjectMessage::InitializedInArea: if ((x21c_flags & 0x4) != 0 || (x21c_flags & 0x2) != 0) { for (const SConnection& conn : x20_conns) { const TUniqueId id = mgr.GetIdForScript(conn.x8_objId); if (const TCastToConstPtr act = mgr.ObjectById(id)) { if (act->IsPlayerActor()) { x20c_lookAtId = id; if (conn.x4_msg != EScriptObjectMessage::Deactivate && conn.x4_msg != EScriptObjectMessage::Reset) { break; } } } } } break; case EScriptObjectMessage::Activate: CalculateWaypoints(mgr); if ((x21c_flags & 1) == 0 && x220_24_ && x1b8_targets.empty()) { break; } x1ec_t = 0.f; Think(0.f, mgr); mgr.GetCameraManager()->AddCinemaCamera(GetUniqueId(), mgr); x1f4_passedViewPoint = 0; if (!x1a8_viewPointArrivals.empty()) { SendArrivedMsg(x1a8_viewPointArrivals[x1f4_passedViewPoint], mgr); } x1f8_passedTarget = 0; if (!x1c8_targetArrivals.empty()) { SendArrivedMsg(x1c8_targetArrivals[x1f8_passedTarget], mgr); } if ((x21c_flags & 0x100) != 0) { mgr.SetCinematicPause(true); } break; case EScriptObjectMessage::Deactivate: WasDeactivated(mgr); break; default: break; } } void CCinematicCamera::CalculateMoveOutofIntoEyePosition(bool outOfEye, CStateManager& mgr) { zeus::CQuaternion q(mgr.GetPlayer().GetTransform().basis); zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition(); if (x20c_lookAtId != kInvalidUniqueId) { if (const TCastToConstPtr act = mgr.GetObjectById(x20c_lookAtId)) { if (act->IsPlayerActor()) { if (const CModelData* mData = act->GetModelData()) { if (const CAnimData* aData = mData->GetAnimationData()) { if (const CAnimTreeNode* root = aData->GetRootAnimationTree().get()) { const CSegId lEye = aData->GetLocatorSegId("L_eye"sv); const CSegId rEye = aData->GetLocatorSegId("R_eye"sv); if (lEye.IsValid() && rEye.IsValid()) { const CCharAnimTime time = outOfEye ? CCharAnimTime(0.f) : root->VGetSteadyStateAnimInfo().GetDuration(); const CCharAnimTime* pTime = outOfEye ? nullptr : &time; eyePos = ((act->GetTransform() * mData->GetScaledLocatorTransformDynamic("L_eye"sv, pTime)).origin + (act->GetTransform() * mData->GetScaledLocatorTransformDynamic("R_eye"sv, pTime)).origin) * 0.5f; q = zeus::CQuaternion(act->GetTransform().basis); } } } } } } } zeus::CVector3f behindPos = eyePos; zeus::CVector3f behindDelta = q.transform({0.f, -g_tweakPlayerRes->xf0_cinematicMoveOutofIntoPlayerDistance, 0.f}); if (!outOfEye) { behindPos += behindDelta; behindDelta = -behindDelta; } for (size_t i = 0; i < 2; ++i) { x188_viewPoints[outOfEye ? i : x188_viewPoints.size() - (2 - i)] = behindPos; x198_viewOrientations[outOfEye ? i : x198_viewOrientations.size() - (2 - i)] = q; x1b8_targets[outOfEye ? i : x1b8_targets.size() - (2 - i)] = eyePos; behindPos += behindDelta; } x210_moveIntoEyePos = eyePos; } void CCinematicCamera::GenerateMoveOutofIntoPoints(bool outOfEye, CStateManager& mgr) { const zeus::CQuaternion q(mgr.GetPlayer().GetTransform().basis); const zeus::CVector3f eyePos = mgr.GetPlayer().GetEyePosition(); zeus::CVector3f behindDelta = q.transform({0.f, -g_tweakPlayerRes->xf0_cinematicMoveOutofIntoPlayerDistance, 0.f}); zeus::CVector3f behindPos = eyePos; if (!outOfEye) { behindPos += behindDelta; behindDelta = -behindDelta; } for (int i = 0; i < 2; ++i) { x188_viewPoints.emplace_back(behindPos); x198_viewOrientations.emplace_back(q); x1a8_viewPointArrivals.emplace_back(mgr.GetPlayer().GetUniqueId()); x1b8_targets.emplace_back(eyePos); x1c8_targetArrivals.emplace_back(kInvalidUniqueId); behindPos += behindDelta; } CalculateMoveOutofIntoEyePosition(outOfEye, mgr); } bool CCinematicCamera::PickRandomActiveConnection(const std::vector& conns, SConnection& randConn, CStateManager& mgr) { int count = 0; for (const SConnection& conn : conns) { if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) { if (const TCastToConstPtr act = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) { if (act->GetActive()) { ++count; } } } } if (count == 0) { return false; } const int randIdx = mgr.GetActiveRandom()->Next() % count; int idx = 0; for (const SConnection& conn : conns) { if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Next) { if (const TCastToConstPtr act = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) { if (act->GetActive()) { if (randIdx == idx) { randConn = conn; break; } ++idx; } } } } return true; } void CCinematicCamera::CalculateWaypoints(CStateManager& mgr) { const SConnection* firstVP = nullptr; const SConnection* firstTarget = nullptr; for (const SConnection& conn : x20_conns) { if (conn.x0_state == EScriptObjectState::CameraPath && conn.x4_msg == EScriptObjectMessage::Activate) { firstVP = &conn; } else if (conn.x0_state == EScriptObjectState::CameraTarget && conn.x4_msg == EScriptObjectMessage::Activate) { firstTarget = &conn; } } x188_viewPoints.clear(); x188_viewPoints.reserve(3); x198_viewOrientations.clear(); x198_viewOrientations.reserve(3); x1a8_viewPointArrivals.clear(); x1a8_viewPointArrivals.reserve(3); x1b8_targets.clear(); x1b8_targets.reserve(3); x1c8_targetArrivals.clear(); x1c8_targetArrivals.reserve(3); x1d8_viewHFovs.clear(); x1d8_viewHFovs.reserve(3); x220_24_ = false; if ((x21c_flags & 0x2) != 0 && (x21c_flags & 0x200) == 0) { GenerateMoveOutofIntoPoints(true, mgr); } if (firstVP) { TCastToConstPtr wp = mgr.GetObjectById(mgr.GetIdForScript(firstVP->x8_objId)); while (wp) { x188_viewPoints.push_back(wp->GetTranslation()); x198_viewOrientations.emplace_back(wp->GetTransform().basis); if (const TCastToConstPtr cwp = wp.GetPtr()) { x1d8_viewHFovs.push_back(cwp->GetHFov()); } const auto search = std::find_if(x1a8_viewPointArrivals.cbegin(), x1a8_viewPointArrivals.cend(), [&wp](TUniqueId id) { return id == wp->GetUniqueId(); }); if (search == x1a8_viewPointArrivals.cend()) { x1a8_viewPointArrivals.push_back(wp->GetUniqueId()); SConnection randConn; if (PickRandomActiveConnection(wp->GetConnectionList(), randConn, mgr)) { wp = mgr.GetObjectById(mgr.GetIdForScript(randConn.x8_objId)); } else { break; } } else { break; } } } if (firstTarget) { TCastToConstPtr tgt = mgr.GetObjectById(mgr.GetIdForScript(firstTarget->x8_objId)); while (tgt) { x1b8_targets.push_back(tgt->GetTranslation()); const auto search = std::find_if(x1c8_targetArrivals.cbegin(), x1c8_targetArrivals.cend(), [&tgt](TUniqueId id) { return id == tgt->GetUniqueId(); }); if (search == x1c8_targetArrivals.cend()) { x1c8_targetArrivals.push_back(tgt->GetUniqueId()); SConnection randConn; if (PickRandomActiveConnection(tgt->GetConnectionList(), randConn, mgr)) { tgt = mgr.GetObjectById(mgr.GetIdForScript(randConn.x8_objId)); } else { break; } } else { break; } } } if ((x21c_flags & 0x4) != 0 && (x21c_flags & 0x200) == 0) { GenerateMoveOutofIntoPoints(false, mgr); } } void CCinematicCamera::SendArrivedMsg(TUniqueId reciever, CStateManager& mgr) { mgr.SendScriptMsgAlways(reciever, GetUniqueId(), EScriptObjectMessage::Arrived); } } // namespace metaforce ================================================ FILE: Runtime/Camera/CCinematicCamera.hpp ================================================ #pragma once #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include #include namespace metaforce { class CCinematicCamera : public CGameCamera { std::vector x188_viewPoints; std::vector x198_viewOrientations; std::vector x1a8_viewPointArrivals; std::vector x1b8_targets; std::vector x1c8_targetArrivals; std::vector x1d8_viewHFovs; float x1e8_duration; float x1ec_t = 0.f; float x1f0_origFovy; int x1f4_passedViewPoint = 0; int x1f8_passedTarget = 0; zeus::CQuaternion x1fc_origOrientation; TUniqueId x20c_lookAtId = kInvalidUniqueId; zeus::CVector3f x210_moveIntoEyePos; u32 x21c_flags; // 0x1: look at player, 0x2: out of player eye, 0x4: into player eye, 0x10: finish cine skip, // 0x20: disable input, 0x40: draw player, 0x80: check failsafe, 0x100: cinematic pause, // 0x200: disable out of into bool x220_24_; zeus::CVector3f GetInterpolatedSplinePoint(const std::vector& points, int& idxOut, float t) const; zeus::CQuaternion GetInterpolatedOrientation(const std::vector& rotations, float t) const; float GetInterpolatedHFov(const std::vector& fovs, float t) const; float GetMoveOutofIntoAlpha() const; void DeactivateSelf(CStateManager& mgr); void CalculateMoveOutofIntoEyePosition(bool outOfEye, CStateManager& mgr); void GenerateMoveOutofIntoPoints(bool outOfEye, CStateManager& mgr); static bool PickRandomActiveConnection(const std::vector& conns, SConnection& randConn, CStateManager& mgr); void CalculateWaypoints(CStateManager& mgr); public: DEFINE_ENTITY CCinematicCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active, float shotDuration, float fovy, float znear, float zfar, float aspect, u32 flags); void Accept(IVisitor& visitor) override; void Think(float dt, CStateManager& mgr) override; void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override; void ProcessInput(const CFinalInput&, CStateManager& mgr) override; void Reset(const zeus::CTransform&, CStateManager& mgr) override; u32 GetFlags() const { return x21c_flags; } void WasDeactivated(CStateManager& mgr); void SendArrivedMsg(TUniqueId reciever, CStateManager& mgr); float GetDuration() const { return x1e8_duration; } }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CFirstPersonCamera.cpp ================================================ #include "Runtime/Camera/CFirstPersonCamera.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptCameraPitchVolume.hpp" #include "Runtime/World/CScriptGrapplePoint.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CFirstPersonCamera::CFirstPersonCamera(TUniqueId uid, const zeus::CTransform& xf, TUniqueId watchedObj, float orbitCameraSpeed, float fov, float nearz, float farz, float aspect) : CGameCamera(uid, true, "First Person Camera", CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList), xf, fov, nearz, farz, aspect, watchedObj, false, 0) , x188_orbitCameraSpeed(orbitCameraSpeed) , x190_gunFollowXf(xf) { MP1::tw_FieldOfView->addListener([this](CVar* cv) { _fovListener(cv); }); } void CFirstPersonCamera::Accept(IVisitor& visitor) { visitor.Visit(this); } void CFirstPersonCamera::PreThink(float dt, CStateManager& mgr) { // Empty } void CFirstPersonCamera::Think(float dt, CStateManager& mgr) { if (TCastToPtr player = mgr.ObjectById(xe8_watchedObject)) { if (!x1c6_24_deferBallTransitionProcessing) { if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { if (player->GetCameraState() != CPlayer::EPlayerCameraState::Spawned) return; SetTransform(player->CreateTransformFromMovementDirection()); SetTranslation(player->GetEyePosition()); return; } if (player->GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed) { if (player->GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphing) return; if (std::fabs(player->GetMorphFactor() - 1.f) >= 0.00001f) return; } } else { x1c6_24_deferBallTransitionProcessing = false; } zeus::CTransform backupXf = x34_transform; UpdateElevation(mgr); UpdateTransform(mgr, dt); SetTransform(ValidateCameraTransform(x34_transform, backupXf)); if (x1d4_closeInTimer > 0.f) x1d4_closeInTimer -= dt; } } void CFirstPersonCamera::ProcessInput(const CFinalInput&, CStateManager& mgr) { // Empty } void CFirstPersonCamera::Reset(const zeus::CTransform& xf, CStateManager& mgr) { SetTransform(xf); SetTranslation(mgr.GetPlayer().GetEyePosition()); x190_gunFollowXf = x34_transform; } void CFirstPersonCamera::SkipCinematic() { x1c8_closeInVec = zeus::skZero3f; x1d4_closeInTimer = 0.f; } void CFirstPersonCamera::CalculateGunFollowOrientationAndTransform(zeus::CTransform& gunXf, zeus::CQuaternion& gunQ, float dt, zeus::CVector3f& rVec) const { zeus::CVector3f gunFrontVec = x190_gunFollowXf.frontVector(); gunFrontVec.z() = 0.f; if (gunFrontVec.canBeNormalized()) gunFrontVec.normalize(); zeus::CVector3f rVecNoZ = rVec; rVecNoZ.z() = 0.f; if (rVecNoZ.canBeNormalized()) rVecNoZ.normalize(); gunXf = zeus::CQuaternion::lookAt(gunFrontVec, rVecNoZ, 2.f * M_PIF).toTransform() * x190_gunFollowXf.getRotation(); zeus::CVector3f newgunFront = gunXf.frontVector(); if (newgunFront.canBeNormalized()) newgunFront.normalize(); float angle = newgunFront.dot(rVec); angle = zeus::clamp(-1.f, angle, 1.f); gunQ = zeus::CQuaternion::lookAt(newgunFront, rVec, zeus::clamp(0.f, std::acos(angle) / dt, 1.f) * dt); } void CFirstPersonCamera::UpdateTransform(CStateManager& mgr, float dt) { TCastToPtr player(mgr.ObjectById(GetWatchedObject())); if (!player) { SetTransform(zeus::CTransform()); return; } zeus::CTransform playerXf = player->GetTransform(); zeus::CVector3f rVec = playerXf.rotate( {0.f, zeus::clamp(-1.f, std::cos(x1c0_pitch), 1.0f), zeus::clamp(-1.f, std::sin(x1c0_pitch), 1.0f)}); if (player->x3dc_inFreeLook) { float angle = player->x3ec_freeLookPitchAngle; float angleClamp = g_tweakPlayer->GetVerticalFreeLookAngleVel() - std::fabs(x1c0_pitch); angle = zeus::clamp(-angleClamp, angle, angleClamp); zeus::CVector3f vec; vec.z() = std::sin(angle); vec.y() = std::cos(-player->x3e4_freeLookYawAngle) * std::cos(angle); vec.x() = std::sin(-player->x3e4_freeLookYawAngle) * std::cos(angle); if (g_tweakPlayer->GetFreeLookTurnsPlayer()) { vec.x() = 0.f; if (!zeus::close_enough(vec, zeus::skZero3f)) vec.normalize(); } rVec = zeus::CQuaternion::lookAt({0.f, 1.f, 0.f}, rVec, 2.f * M_PIF).transform(vec); } zeus::CVector3f eyePos = player->GetEyePosition(); if (x1d4_closeInTimer > 0.f) { eyePos += zeus::clamp(0.f, 0.5f * x1d4_closeInTimer, 1.f) * x1c8_closeInVec; player->GetCameraBob()->ResetCameraBobTime(); player->GetCameraBob()->SetCameraBobTransform(zeus::CTransform()); } switch (player->GetOrbitState()) { case CPlayer::EPlayerOrbitState::ForcedOrbitObject: case CPlayer::EPlayerOrbitState::OrbitObject: { const CActor* act = TCastToConstPtr(mgr.GetObjectById(player->x310_orbitTargetId)); if (act && act->GetMaterialList().HasMaterial(EMaterialTypes::Orbit)) { zeus::CVector3f v = player->x314_orbitPoint - eyePos; if (v.canBeNormalized()) v.normalize(); rVec = v; } else { rVec = player->x314_orbitPoint - eyePos; } break; } case CPlayer::EPlayerOrbitState::OrbitPoint: case CPlayer::EPlayerOrbitState::OrbitCarcass: { if (!player->x3dd_lookButtonHeld) { rVec = player->x314_orbitPoint - eyePos; } break; } case CPlayer::EPlayerOrbitState::NoOrbit: { if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed && !player->x3dc_inFreeLook && x1c4_pitchId == kInvalidUniqueId) { if (player->x294_jumpCameraTimer > 0.f) { float angle = zeus::clamp(0.f, (player->x294_jumpCameraTimer - g_tweakPlayer->GetJumpCameraPitchDownStart()) / g_tweakPlayer->GetJumpCameraPitchDownFull(), 1.f) * g_tweakPlayer->GetJumpCameraPitchDownAngle(); angle += x1c0_pitch; rVec.x() = 0.f; rVec.y() = std::cos(angle); rVec.z() = -std::sin(angle); rVec = playerXf.rotate(rVec); } else if (player->x29c_fallCameraTimer > 0.f) { float angle = zeus::clamp(0.f, (player->x29c_fallCameraTimer - g_tweakPlayer->GetFallCameraPitchDownStart()) / g_tweakPlayer->GetFallCameraPitchDownFull(), 1.f) * g_tweakPlayer->GetFallCameraPitchDownAngle(); rVec.x() = 0.f; rVec.y() = std::cos(angle); rVec.z() = -std::sin(angle); rVec = playerXf.rotate(rVec); } } break; } default: break; } if (rVec.canBeNormalized()) rVec.normalize(); zeus::CTransform gunXf = x190_gunFollowXf; zeus::CQuaternion qGun; if (!player->x3dc_inFreeLook) { switch (player->GetOrbitState()) { default: { CalculateGunFollowOrientationAndTransform(gunXf, qGun, dt * g_tweakPlayer->GetFirstPersonCameraSpeed(), rVec); break; } case CPlayer::EPlayerOrbitState::Grapple: { CalculateGunFollowOrientationAndTransform(gunXf, qGun, dt * g_tweakPlayer->GetGrappleCameraSpeed(), rVec); break; } case CPlayer::EPlayerOrbitState::OrbitPoint: case CPlayer::EPlayerOrbitState::OrbitCarcass: { CalculateGunFollowOrientationAndTransform(gunXf, qGun, dt * g_tweakPlayer->GetOrbitCameraSpeed() * 0.25f, rVec); break; } case CPlayer::EPlayerOrbitState::ForcedOrbitObject: case CPlayer::EPlayerOrbitState::OrbitObject: { zeus::CVector3f gunFrontVec = x190_gunFollowXf.frontVector(); if (gunFrontVec.canBeNormalized()) gunFrontVec.normalize(); float scaledDt = (dt * g_tweakPlayer->GetOrbitCameraSpeed()); float angle = gunFrontVec.dot(rVec); angle = zeus::clamp(-1.f, angle, 1.f); float clampedAngle = zeus::clamp(0.f, std::acos(angle) / scaledDt, 1.f); if (angle > 0.999f || x18c_lockCamera || player->x374_orbitLockEstablished) qGun = zeus::CQuaternion::lookAt(gunFrontVec, rVec, 2.f * M_PIF); else qGun = zeus::CQuaternion::lookAt(gunFrontVec, rVec, scaledDt * clampedAngle); const CScriptGrapplePoint* gPoint = TCastToConstPtr(mgr.GetObjectById(player->x310_orbitTargetId)); if (gPoint && player->x29c_fallCameraTimer > 0.f) { gunFrontVec = x190_gunFollowXf.frontVector(); if (gunFrontVec.canBeNormalized()) gunFrontVec.normalize(); zeus::CVector3f rVecCpy = rVec; rVecCpy.z() = 0.f; if (rVecCpy.canBeNormalized()) rVecCpy.normalize(); gunXf = zeus::CQuaternion::lookAt(gunFrontVec, rVecCpy, 2.f * M_PIF).toTransform() * x190_gunFollowXf.getRotation(); gunFrontVec = gunXf.frontVector(); if (gunFrontVec.canBeNormalized()) gunFrontVec.normalize(); // float angle = gunFrontVec.dot(rVec); // float sdt = dt * g_tweakPlayer->GetGrappleCameraSpeed(); // angle = zeus::clamp(-1.f, angle, 1.f); // angle = zeus::clamp(0.f, std::acos(angle) / sdt, 1.f); qGun = zeus::CQuaternion::lookAt(gunFrontVec, rVecCpy, 2.f * M_PIF); } break; } } } else { zeus::CVector3f gunFront = x190_gunFollowXf.frontVector(); gunFront.z() = 0.f; if (gunFront.canBeNormalized()) gunFront.normalize(); zeus::CVector3f rVecCpy = rVec; rVecCpy.z() = 0.f; if (rVecCpy.canBeNormalized()) rVecCpy.normalize(); gunXf = zeus::CQuaternion::lookAt(gunFront, rVecCpy, 2.f * M_PIF).toTransform() * x190_gunFollowXf.getRotation(); gunFront = gunXf.frontVector(); if (gunFront.canBeNormalized()) gunFront.normalize(); float angle = gunFront.dot(rVec); angle = zeus::clamp(-1.f, angle, 1.f); float sdt = dt * g_tweakPlayer->GetFreeLookSpeed(); qGun = zeus::CQuaternion::lookAt( gunFront, rVec, sdt * zeus::clamp(0.f, g_tweakPlayer->GetFreeLookDampenFactor() * (std::acos(angle) / sdt), 1.f)); } zeus::CTransform bobXf = player->GetCameraBob()->GetCameraBobTransformation(); if (player->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed || player->GetOrbitState() == CPlayer::EPlayerOrbitState::Grapple || player->GetGrappleState() != CPlayer::EGrappleState::None || mgr.GetGameState() == CStateManager::EGameState::SoftPaused || mgr.GetCameraManager()->IsInCinematicCamera() || x1d4_closeInTimer > 0.f) { bobXf = zeus::CTransform(); player->GetCameraBob()->SetCameraBobTransform(bobXf); } x190_gunFollowXf = qGun.toTransform() * gunXf; SetTransform(x190_gunFollowXf * bobXf.getRotation()); x190_gunFollowXf.origin = eyePos; CActor::SetTranslation(eyePos + player->GetTransform().rotate(bobXf.origin)); x190_gunFollowXf.orthonormalize(); } void CFirstPersonCamera::UpdateElevation(CStateManager& mgr) { x1c0_pitch = 0.f; if (TCastToConstPtr player = mgr.GetObjectById(xe8_watchedObject)) { if (x1c4_pitchId != kInvalidUniqueId) { if (TCastToConstPtr pvol = mgr.GetObjectById(x1c4_pitchId)) { zeus::CVector3f pitchDirFlat = pvol->GetTransform().basis[1]; pitchDirFlat.z() = 0.f; if (!pitchDirFlat.canBeNormalized()) pitchDirFlat = zeus::skForward; zeus::CVector3f playerDirFlat = player->GetTransform().basis[1]; playerDirFlat.z() = 0.f; playerDirFlat.normalize(); float pitchDot = zeus::clamp(-1.f, pitchDirFlat.dot(playerDirFlat), 1.f); if (pitchDot < 0.f) x1c0_pitch = pvol->GetDownPitch() * -pitchDot; else x1c0_pitch = pvol->GetUpPitch() * -pitchDot; zeus::CVector3f pvolToPlayerFlat = player->GetTranslation() - pvol->GetTranslation(); pvolToPlayerFlat.z() = 0.f; float pitchMul = 0.f; if (pvolToPlayerFlat.canBeNormalized()) { float pvolPlayerProj = std::fabs(zeus::clamp(-1.f, pvolToPlayerFlat.dot(pitchDirFlat), 1.f)) * pvolToPlayerFlat.magnitude(); if (pvolPlayerProj <= pvol->GetMaxInterpolationDistance()) pitchMul = 1.f; else pitchMul = 1.f - zeus::clamp(-1.f, (pvolPlayerProj - pvol->GetMaxInterpolationDistance()) / (pvol->GetScale().y() - pvol->GetMaxInterpolationDistance()), 1.f); } x1c0_pitch *= pitchMul; } } } } void CFirstPersonCamera::_fovListener(CVar* cv) { x15c_currentFov = x180_perspInterpStartFov = x184_perspInterpEndFov = cv->toReal(); x170_24_perspDirty = true; } } // namespace metaforce ================================================ FILE: Runtime/Camera/CFirstPersonCamera.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include #include namespace metaforce { class CFirstPersonCamera : public CGameCamera { float x188_orbitCameraSpeed; bool x18c_lockCamera = false; zeus::CTransform x190_gunFollowXf; float x1c0_pitch = 0.f; TUniqueId x1c4_pitchId = kInvalidUniqueId; bool x1c6_24_deferBallTransitionProcessing : 1 = false; zeus::CVector3f x1c8_closeInVec; float x1d4_closeInTimer = 0.f; void _fovListener(CVar* cv); public: DEFINE_ENTITY CFirstPersonCamera(TUniqueId, const zeus::CTransform& xf, TUniqueId, float orbitCameraSpeed, float fov, float nearplane, float farplane, float aspect); void Accept(IVisitor& visitor) override; void PreThink(float dt, CStateManager& mgr) override; void Think(float dt, CStateManager& mgr) override; void ProcessInput(const CFinalInput&, CStateManager& mgr) override; void Reset(const zeus::CTransform&, CStateManager& mgr) override; void SkipCinematic(); const zeus::CTransform& GetGunFollowTransform() const { return x190_gunFollowXf; } void UpdateTransform(CStateManager& mgr, float dt); void UpdateElevation(CStateManager& mgr); void CalculateGunFollowOrientationAndTransform(zeus::CTransform&, zeus::CQuaternion&, float, zeus::CVector3f&) const; void SetScriptPitchId(TUniqueId uid) { x1c4_pitchId = uid; } void SetLockCamera(bool v) { x18c_lockCamera = v; } void DeferBallTransitionProcessing() { x1c6_24_deferBallTransitionProcessing = true; } }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CGameCamera.cpp ================================================ #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Camera/CCameraManager.hpp" #include "Runtime/World/CActorParameters.hpp" namespace metaforce { CGameCamera::CGameCamera(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, float fovy, float znear, float zfar, float aspect, TUniqueId watchedId, bool disableInput, u32 controllerIdx) : CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::NoStepLogic), CActorParameters::None(), kInvalidUniqueId) , xe8_watchedObject(watchedId) , x12c_origXf(xf) , x15c_currentFov(fovy) , x160_znear(znear) , x164_zfar(zfar) , x168_aspect(aspect) , x16c_controllerIdx(controllerIdx) , x170_25_disablesInput(disableInput) , x180_perspInterpStartFov(fovy) , x184_perspInterpEndFov(fovy) { xe7_29_drawEnabled = false; } void CGameCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { if (msg == EScriptObjectMessage::AddSplashInhabitant) { mgr.GetCameraManager()->SetInsideFluid(true, uid); return; } else if (msg == EScriptObjectMessage::RemoveSplashInhabitant) { mgr.GetCameraManager()->SetInsideFluid(false, kInvalidUniqueId); return; } CActor::AcceptScriptMsg(msg, uid, mgr); } void CGameCamera::SetActive(bool active) { CActor::SetActive(active); xe7_29_drawEnabled = false; } zeus::CMatrix4f CGameCamera::GetPerspectiveMatrix() const { if (x170_24_perspDirty) { xec_perspectiveMatrix = CGraphics::CalculatePerspectiveMatrix(x15c_currentFov, x168_aspect, x160_znear, x164_zfar); x170_24_perspDirty = false; } return xec_perspectiveMatrix; } zeus::CVector3f CGameCamera::ConvertToScreenSpace(const zeus::CVector3f& v) const { zeus::CVector3f rVec = x34_transform.transposeRotate(v - x34_transform.origin); if (rVec.isZero()) return {-1.f, -1.f, 1.f}; return GetPerspectiveMatrix().multiplyOneOverW(rVec); } zeus::CTransform CGameCamera::ValidateCameraTransform(const zeus::CTransform& newXf, const zeus::CTransform& oldXf) const { zeus::CTransform xfCpy(newXf); if (!zeus::close_enough(newXf.rightVector().magnitude(), 1.f) || !zeus::close_enough(newXf.frontVector().magnitude(), 1.f) || !zeus::close_enough(newXf.upVector().magnitude(), 1.f)) xfCpy.orthonormalize(); float f2 = zeus::clamp(-1.f, newXf.frontVector().dot(zeus::skUp), 1.f); if (std::fabs(f2) > 0.999f) xfCpy = oldXf; if (xfCpy.upVector().z() < -0.2f) xfCpy = zeus::CQuaternion::fromAxisAngle(xfCpy.frontVector(), M_PIF).toTransform() * xfCpy; if (!zeus::close_enough(xfCpy.rightVector().z(), 0.f) && !zeus::close_enough(xfCpy.upVector().z(), 0.f)) { if (xfCpy.frontVector().canBeNormalized()) xfCpy = zeus::lookAt(zeus::skZero3f, xfCpy.frontVector()); else xfCpy = oldXf; } xfCpy.origin = newXf.origin; return xfCpy; } void CGameCamera::UpdatePerspective(float dt) { if (x174_delayTime > 0.f) { x174_delayTime -= dt; return; } if (x178_perspInterpRemTime <= 0.f) return; x178_perspInterpRemTime -= dt; if (x178_perspInterpRemTime <= 0.f) { x15c_currentFov = x184_perspInterpEndFov; x170_24_perspDirty = true; } else { x15c_currentFov = zeus::clamp(0.f, (x178_perspInterpRemTime / x17c_perspInterpDur), 1.f) * (x180_perspInterpStartFov - x184_perspInterpEndFov) + x184_perspInterpEndFov; x170_24_perspDirty = true; } } void CGameCamera::SetFovInterpolation(float start, float fov, float time, float delayTime) { if (time < 0.f) { x15c_currentFov = fov; x170_24_perspDirty = true; x184_perspInterpEndFov = fov; x178_perspInterpRemTime = x174_delayTime = 0.f; } else { x174_delayTime = std::max(0.f, delayTime); x17c_perspInterpDur = time; x178_perspInterpRemTime = time; x180_perspInterpStartFov = start; x184_perspInterpEndFov = fov; x15c_currentFov = start; x170_24_perspDirty = true; } } void CGameCamera::SkipFovInterpolation() { if (x178_perspInterpRemTime > 0) { x15c_currentFov = x184_perspInterpEndFov; x170_24_perspDirty = true; } x178_perspInterpRemTime = x174_delayTime = 0.f; } } // namespace metaforce ================================================ FILE: Runtime/Camera/CGameCamera.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/World/CActor.hpp" #include #include namespace metaforce { struct CFinalInput; class CGameCamera : public CActor { friend class CCameraManager; friend class CStateManager; protected: TUniqueId xe8_watchedObject; mutable zeus::CMatrix4f xec_perspectiveMatrix; zeus::CTransform x12c_origXf; float x15c_currentFov; float x160_znear; float x164_zfar; float x168_aspect; u32 x16c_controllerIdx; mutable bool x170_24_perspDirty : 1 = true; bool x170_25_disablesInput : 1; float x174_delayTime = 0.f; float x178_perspInterpRemTime = 0.f; float x17c_perspInterpDur = 0.f; float x180_perspInterpStartFov; float x184_perspInterpEndFov; public: DEFINE_ENTITY CGameCamera(TUniqueId, bool active, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, float fov, float nearz, float farz, float aspect, TUniqueId watchedId, bool disableInput, u32 controllerIdx); void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override; void SetActive(bool active) override; virtual void ProcessInput(const CFinalInput&, CStateManager& mgr) = 0; virtual void Reset(const zeus::CTransform&, CStateManager& mgr) = 0; zeus::CMatrix4f GetPerspectiveMatrix() const; zeus::CVector3f ConvertToScreenSpace(const zeus::CVector3f&) const; zeus::CTransform ValidateCameraTransform(const zeus::CTransform&, const zeus::CTransform&) const; float GetNearClipDistance() const { return x160_znear; } float GetFarClipDistance() const { return x164_zfar; } float GetAspectRatio() const { return x168_aspect; } TUniqueId GetWatchedObject() const { return xe8_watchedObject; } float GetFov() const { return x15c_currentFov; } void GetControllerNumber() const; bool DisablesInput() const; void UpdatePerspective(float); void SetFovInterpolation(float start, float end, float time, float delayTime); void SkipFovInterpolation(); }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CInterpolationCamera.cpp ================================================ #include "Runtime/Camera/CInterpolationCamera.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Camera/CBallCamera.hpp" #include "Runtime/Camera/CCameraManager.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptSpindleCamera.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CInterpolationCamera::CInterpolationCamera(TUniqueId uid, const zeus::CTransform& xf) : CGameCamera(uid, false, "Interpolation Camera", CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList, kInvalidEditorId), xf, CCameraManager::ThirdPersonFOV(), CCameraManager::NearPlane(), CCameraManager::FarPlane(), CCameraManager::Aspect(), kInvalidUniqueId, false, 0) {} void CInterpolationCamera::Accept(IVisitor& visitor) { visitor.Visit(this); } void CInterpolationCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) { CGameCamera::AcceptScriptMsg(msg, sender, mgr); } void CInterpolationCamera::ProcessInput(const CFinalInput& input, CStateManager& mgr) { // Empty } void CInterpolationCamera::Render(CStateManager& mgr) { // Empty } void CInterpolationCamera::Reset(const zeus::CTransform&, CStateManager& mgr) { // Empty } void CInterpolationCamera::Think(float dt, CStateManager& mgr) { if (!GetActive()) return; x15c_currentFov = mgr.GetCameraManager()->GetBallCamera()->GetFov(); x170_24_perspDirty = true; if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphing) DeactivateInterpCamera(mgr); x18c_time += dt; if (x18c_time > x190_maxTime) x18c_time = x190_maxTime; zeus::CTransform xf = GetTransform(); if (TCastToConstPtr cam = mgr.GetObjectById(x188_targetId)) { zeus::CVector3f targetOrigin = cam->GetTranslation(); zeus::CVector3f ballLookPos = mgr.GetCameraManager()->GetBallCamera()->GetLookPos(); if (mgr.GetCameraManager()->GetBallCamera()->GetBehaviour() == CBallCamera::EBallCameraBehaviour::SpindleCamera) { if (TCastToConstPtr spindle = mgr.GetObjectById(mgr.GetCameraManager()->GetSpindleCameraId())) { float mag = (mgr.GetPlayer().GetTranslation() - spindle->GetTranslation()).magnitude(); ballLookPos = spindle->GetTranslation() + (mag * spindle->GetTransform().frontVector()); } } bool deactivate = false; if (x1d8_24_sinusoidal) deactivate = InterpolateSinusoidal(xf, targetOrigin, ballLookPos, x190_maxTime, x18c_time); else deactivate = InterpolateWithDistance(xf, targetOrigin, ballLookPos, x1d0_positionSpeed, x1d4_rotationSpeed, dt, x190_maxTime, x18c_time); SetTransform(xf); if (deactivate) DeactivateInterpCamera(mgr); } else DeactivateInterpCamera(mgr); } void CInterpolationCamera::SetInterpolation(const zeus::CTransform& xf, const zeus::CVector3f& lookPos, float maxTime, float positionSpeed, float rotationSpeed, TUniqueId camId, bool sinusoidal, CStateManager& mgr) { SetActive(true); SetTransform(xf); x1c4_lookPos = lookPos; x188_targetId = camId; x1d8_24_sinusoidal = sinusoidal; x190_maxTime = maxTime; x1d0_positionSpeed = positionSpeed; x1d4_rotationSpeed = rotationSpeed; x1dc_closeInAngle = 2.f * M_PIF; x18c_time = 0.f; if (TCastToConstPtr cam = (mgr.GetObjectById(camId))) { x15c_currentFov = cam->GetFov(); x170_24_perspDirty = true; } } void CInterpolationCamera::DeactivateInterpCamera(CStateManager& mgr) { SetActive(false); if (!mgr.GetCameraManager()->ShouldBypassInterpolation()) mgr.GetCameraManager()->SetCurrentCameraId(x188_targetId, mgr); } bool CInterpolationCamera::InterpolateSinusoidal(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin, const zeus::CVector3f& lookPos, float maxTime, float curTime) { if (curTime > maxTime) curTime = maxTime; float t = zeus::clamp(-1.f, curTime / maxTime, 1.f); float sinT = std::sin(t * (M_PIF / 2.f)); t *= 2.f; zeus::CVector3f interpOrigin = (1.f - (t - sinT)) * (GetTranslation() - targetOrigin) + targetOrigin; zeus::CVector3f lookDir = lookPos - interpOrigin; if (lookDir.canBeNormalized()) lookDir.normalize(); else lookDir = x34_transform.basis[1]; zeus::CVector3f lookDirFlat = lookDir; lookDirFlat.z() = 0.f; if (lookDirFlat.canBeNormalized()) { t = zeus::clamp(-1.f, t, 1.f); float lookProj = zeus::clamp(-1.f, x34_transform.basis[1].dot(lookDir), 1.f); float ang = (1.f - t) * std::acos(lookProj); if (ang > x1dc_closeInAngle) ang = x1dc_closeInAngle; else x1dc_closeInAngle = ang; zeus::CTransform lookXf = zeus::lookAt(interpOrigin, interpOrigin + lookDir); if (std::fabs(lookProj) < 0.999999f) { zeus::CVector3f xfLookDir = zeus::CQuaternion::lookAt(lookDir, x34_transform.basis[1], ang).transform(lookDir); lookXf = zeus::lookAt(interpOrigin, interpOrigin + xfLookDir); } xf = lookXf; } else { xf = x34_transform; xf.origin = interpOrigin; } return curTime >= maxTime; } bool CInterpolationCamera::InterpolateWithDistance(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin, const zeus::CVector3f& lookPos, float positionSpeed, float rotationSpeed, float dt, float maxTime, float curTime) { zeus::CVector3f interpOrigin = xf.origin; zeus::CVector3f originDir = targetOrigin - interpOrigin; float sdt = positionSpeed * dt; bool ret = false; bool positionFail = false; if (originDir.canBeNormalized() && originDir.magnitude() > sdt) { float lookDist = originDir.magnitude(); originDir.normalize(); float scale = zeus::clamp(-1.f, lookDist / 0.5f, 1.f) * sdt; interpOrigin += originDir * scale; if (lookDist < scale) { interpOrigin = targetOrigin; positionFail = true; } } else { interpOrigin = targetOrigin; positionFail = true; } zeus::CVector3f lookPosDelta = lookPos - x1c4_lookPos; if (lookPosDelta.magnitude() > sdt) { float deltaMag = lookPosDelta.magnitude(); lookPosDelta.normalize(); float scale = zeus::clamp(-1.f, deltaMag / 0.5f, 1.f) * sdt; x1c4_lookPos += lookPosDelta * scale; } else { x1c4_lookPos = lookPos; } zeus::CVector3f lookDir = x1c4_lookPos - interpOrigin; if (lookDir.canBeNormalized()) lookDir.normalize(); else lookDir = x34_transform.basis[1]; float lookProj = zeus::clamp(-1.f, xf.basis[1].dot(lookDir), 1.f); float ang = zeus::clamp(-1.f, std::acos(lookProj) / (M_PIF / 6.f), 1.f) * rotationSpeed * dt; zeus::CVector3f lookDirFlat = lookDir; lookDirFlat.z() = 0.f; bool rotationFail = false; if (lookDirFlat.canBeNormalized()) { zeus::CTransform lookXf = zeus::lookAt(interpOrigin, interpOrigin + lookDir); if (lookProj < 0.999999f) lookXf = zeus::CQuaternion::lookAt(xf.basis[1], lookDir, ang).toTransform() * xf.getRotation(); else rotationFail = true; lookXf.origin = interpOrigin; xf = lookXf; } else { xf = x34_transform; xf.origin = interpOrigin; rotationFail = true; } if (positionFail && rotationFail) ret = true; if (curTime >= maxTime && lookProj >= 0.9999f) ret = true; return ret; } } // namespace metaforce ================================================ FILE: Runtime/Camera/CInterpolationCamera.hpp ================================================ #pragma once #include "Runtime/Camera/CGameCamera.hpp" #include #include namespace metaforce { class CInterpolationCamera : public CGameCamera { TUniqueId x188_targetId = kInvalidUniqueId; float x18c_time = 0.f; float x190_maxTime = 0.f; zeus::CTransform x194_; zeus::CVector3f x1c4_lookPos; float x1d0_positionSpeed = 0.f; float x1d4_rotationSpeed = 0.f; float x1d8_ = 0.f; bool x1d8_24_sinusoidal : 1 = false; float x1dc_closeInAngle = M_PIF * 2.f; bool InterpolateSinusoidal(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin, const zeus::CVector3f& lookPos, float maxTime, float curTime); bool InterpolateWithDistance(zeus::CTransform& xf, const zeus::CVector3f& targetOrigin, const zeus::CVector3f& lookPos, float positionSpeed, float rotationSpeed, float dt, float maxTime, float curTime); public: DEFINE_ENTITY explicit CInterpolationCamera(TUniqueId uid, const zeus::CTransform& xf); void Accept(IVisitor& visitor) override; void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override; void ProcessInput(const CFinalInput&, CStateManager& mgr) override; void Render(CStateManager&) override; void Reset(const zeus::CTransform&, CStateManager& mgr) override; void Think(float, CStateManager&) override; void SetInterpolation(const zeus::CTransform& xf, const zeus::CVector3f& lookPos, float maxTime, float positionSpeed, float rotationSpeed, TUniqueId camId, bool sinusoidal, CStateManager& mgr); void DeactivateInterpCamera(CStateManager&); }; } // namespace metaforce ================================================ FILE: Runtime/Camera/CMakeLists.txt ================================================ set(CAMERA_SOURCES CCameraManager.hpp CCameraManager.cpp CGameCamera.hpp CGameCamera.cpp CFirstPersonCamera.hpp CFirstPersonCamera.cpp CBallCamera.hpp CBallCamera.cpp CInterpolationCamera.hpp CInterpolationCamera.cpp CPathCamera.hpp CPathCamera.cpp CCinematicCamera.hpp CCinematicCamera.cpp CCameraShakeData.hpp CCameraShakeData.cpp CCameraFilter.hpp CCameraFilter.cpp CCameraSpline.hpp CCameraSpline.cpp) runtime_add_list(Camera CAMERA_SOURCES) ================================================ FILE: Runtime/Camera/CPathCamera.cpp ================================================ #include "Runtime/Camera/CPathCamera.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Camera/CBallCamera.hpp" #include "Runtime/Camera/CCameraManager.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptCameraHint.hpp" #include "Runtime/World/CScriptDoor.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CPathCamera::CPathCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active, float lengthExtent, float filterMag, float filterProportion, float minEaseDist, float maxEaseDist, u32 flags, EInitialSplinePosition initPos) : CGameCamera(uid, active, name, info, xf, CCameraManager::ThirdPersonFOV(), CCameraManager::NearPlane(), CCameraManager::FarPlane(), CCameraManager::Aspect(), kInvalidUniqueId, false, 0) , x188_spline(flags & 1) , x1dc_lengthExtent(lengthExtent) , x1e0_filterMag(filterMag) , x1e4_filterProportion(filterProportion) , x1e8_initPos(initPos) , x1ec_flags(flags) , x1f0_minEaseDist(minEaseDist) , x1f4_maxEaseDist(maxEaseDist) {} void CPathCamera::Accept(IVisitor& visitor) { visitor.Visit(this); } void CPathCamera::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { CGameCamera::AcceptScriptMsg(msg, uid, mgr); if (GetActive() && msg == EScriptObjectMessage::InitializedInArea) x188_spline.Initialize(GetUniqueId(), GetConnectionList(), mgr); } void CPathCamera::Think(float dt, CStateManager& mgr) { if (!GetActive()) return; if (mgr.GetCameraManager()->GetPathCameraId() != GetUniqueId()) return; if (x188_spline.GetSize() <= 0) return; zeus::CTransform xf = GetTransform(); zeus::CVector3f ballLook = mgr.GetCameraManager()->GetBallCamera()->GetLookPos(); if ((x1ec_flags & 0x10)) { if (const CScriptCameraHint* hint = mgr.GetCameraManager()->GetCameraHint(mgr)) ballLook.z() = hint->GetTranslation().z(); } if (!mgr.GetPlayer().GetVelocity().canBeNormalized() && (ballLook - GetTranslation()).canBeNormalized()) { if (x1ec_flags & 4) SetTransform(x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos)); else SetTransform(zeus::lookAt(GetTranslation(), ballLook)); return; } xf = MoveAlongSpline(dt, mgr); SetTranslation(xf.origin); if (x1ec_flags & 0x20) ClampToClosedDoor(mgr); zeus::CVector3f tmp = ballLook - GetTranslation(); tmp.z() = 0.f; if (tmp.canBeNormalized()) SetTransform(zeus::lookAt(GetTranslation(), ballLook)); if (x1ec_flags & 4) SetTransform(xf); } void CPathCamera::ProcessInput(const CFinalInput&, CStateManager& mgr) { // Empty } constexpr CMaterialFilter kLineOfSightFilter = CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough}); void CPathCamera::Reset(const zeus::CTransform&, CStateManager& mgr) { CPlayer& player = mgr.GetPlayer(); zeus::CVector3f playerPt = player.GetTranslation() + zeus::CVector3f(0.f, 0.f, g_tweakPlayer->GetPlayerBallHalfExtent()); float closestLength = x188_spline.FindClosestLengthOnSpline(0.f, playerPt); float negLength = std::max(0.f, closestLength - x1dc_lengthExtent); zeus::CVector3f negPoint = x188_spline.GetInterpolatedSplinePointByLength(negLength).origin; float posLength = std::min(x188_spline.GetLength(), closestLength + x1dc_lengthExtent); zeus::CVector3f posPoint = x188_spline.GetInterpolatedSplinePointByLength(posLength).origin; zeus::CTransform camXf = mgr.GetCameraManager()->GetBallCamera()->GetTransform(); if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr); bool neg = false; if (x1e8_initPos == EInitialSplinePosition::BallCamBasis) { zeus::CVector3f tmp = playerPt - negPoint; if (tmp.canBeNormalized()) { if (tmp.normalized().dot(camXf.basis[1]) > 0.f) neg = true; } } else { neg = x1e8_initPos == EInitialSplinePosition::Negative; } #if 0 zeus::CVector3f camToSpline = splinePt - camXf.origin; mgr.RayStaticIntersection(camXf.origin, camToSpline.normalized(), camToSpline.magnitude(), kLineOfSightFilter); zeus::CVector3f camToSpline2 = splinePt2 - camXf.origin; mgr.RayStaticIntersection(camXf.origin, camToSpline2.normalized(), camToSpline2.magnitude(), kLineOfSightFilter); #endif zeus::CVector3f viewPoint; if (neg) { x1d4_pos = negLength; viewPoint = negPoint; } else { x1d4_pos = posLength; viewPoint = posPoint; } if (x1e8_initPos == EInitialSplinePosition::ClampBasis) { if (x188_spline.ClampLength(playerPt, false, kLineOfSightFilter, mgr) <= negLength) { x1d4_pos = negLength; viewPoint = negPoint; } else { x1d4_pos = posLength; viewPoint = posPoint; } } SetTransform(zeus::lookAt(viewPoint, mgr.GetCameraManager()->GetBallCamera()->GetFixedLookPos())); } zeus::CTransform CPathCamera::MoveAlongSpline(float t, CStateManager& mgr) { zeus::CTransform ret = x34_transform; x1d8_time = x188_spline.FindClosestLengthOnSpline(x1d8_time, mgr.GetPlayer().GetTranslation()); float f30 = x1dc_lengthExtent; if (x1ec_flags & 0x8) { zeus::CVector3f splineToPlayer = mgr.GetPlayer().GetTranslation() - x188_spline.GetInterpolatedSplinePointByLength(x1d8_time).origin; float distToPlayer = 0.f; splineToPlayer.z() = 0.f; if (splineToPlayer.canBeNormalized()) distToPlayer = splineToPlayer.magnitude(); float easedDistT = (distToPlayer - x1f0_minEaseDist) / (x1f4_maxEaseDist - x1f0_minEaseDist); f30 *= 1.f - std::sin(zeus::degToRad(zeus::clamp(0.f, easedDistT, 1.f) * 90.f)); } float newPos; if (x188_spline.IsClosedLoop()) { float lenA = x188_spline.ValidateLength(x1d8_time + f30); newPos = x188_spline.ValidateLength(x1d8_time - f30); float disp = std::fabs(x1d4_pos - x1d8_time); float remLen = x188_spline.GetLength() - disp; if (x1d4_pos > x1d8_time) { if (disp <= remLen) newPos = lenA; } else { if (disp > remLen) newPos = lenA; } } else { if (x1d4_pos > x1d8_time) newPos = x188_spline.ValidateLength(x1d8_time + f30); else newPos = x188_spline.ValidateLength(x1d8_time - f30); } if (x1ec_flags & 0x2) { x1d4_pos = newPos; ret = x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos); } else { if (x188_spline.IsClosedLoop()) { float absDelta = std::fabs(newPos - x1d4_pos); absDelta = std::min(absDelta, x188_spline.GetLength() - absDelta); float tBias = zeus::clamp(-1.f, absDelta / x1e4_filterProportion, 1.f) * x1e0_filterMag * t; float tmpAbs = std::fabs(x1d4_pos - newPos); float absDelta2 = x188_spline.GetLength() - tmpAbs; if (x1d4_pos > newPos) { if (tmpAbs <= absDelta2) tBias *= -1.f; } else { if (tmpAbs > absDelta2) tBias *= -1.f; } x1d4_pos = x188_spline.ValidateLength(x1d4_pos + tBias); } else { x1d4_pos = x188_spline.ValidateLength( zeus::clamp(-1.f, (newPos - x1d4_pos) / x1e4_filterProportion, 1.f) * x1e0_filterMag * t + x1d4_pos); } ret = x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos); } return ret; } void CPathCamera::ClampToClosedDoor(CStateManager& mgr) { if (TCastToConstPtr door = mgr.GetObjectById(mgr.GetCameraManager()->GetBallCamera()->GetTooCloseActorId())) { if (!door->IsOpen() && CBallCamera::IsBallNearDoor(GetTranslation(), mgr)) { x1d4_pos = (x1d4_pos > x1d8_time) ? x1d8_time - x1dc_lengthExtent : x1d8_time + x1dc_lengthExtent; SetTranslation(x188_spline.GetInterpolatedSplinePointByLength(x1d4_pos).origin); } } } } // namespace metaforce ================================================ FILE: Runtime/Camera/CPathCamera.hpp ================================================ #pragma once #include "Runtime/Camera/CCameraSpline.hpp" #include "Runtime/Camera/CGameCamera.hpp" namespace metaforce { class CPathCamera : public CGameCamera { public: enum class EInitialSplinePosition { BallCamBasis, Negative, Positive, ClampBasis }; private: CCameraSpline x188_spline; float x1d4_pos = 0.f; float x1d8_time = 0.f; float x1dc_lengthExtent; float x1e0_filterMag; float x1e4_filterProportion; EInitialSplinePosition x1e8_initPos; u32 x1ec_flags; float x1f0_minEaseDist; float x1f4_maxEaseDist; public: DEFINE_ENTITY CPathCamera(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, bool active, float lengthExtent, float filterMag, float filterProportion, float minEaseDist, float maxEaseDist, u32 flags, EInitialSplinePosition initPos); void Accept(IVisitor&) override; void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&) override; void Think(float, CStateManager&) override; void Render(CStateManager&) override {} void ProcessInput(const CFinalInput&, CStateManager& mgr) override; void Reset(const zeus::CTransform&, CStateManager& mgr) override; zeus::CTransform MoveAlongSpline(float, CStateManager&); void ClampToClosedDoor(CStateManager&); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CActorLights.cpp ================================================ #include "Runtime/Character/CActorLights.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Camera/CFirstPersonCamera.hpp" #include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Graphics/CModel.hpp" #include "Runtime/World/CExplosion.hpp" #include "Runtime/World/CGameArea.hpp" #include "Runtime/World/CGameLight.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { s32 CActorLights::sFrameSchedulerCount = 0; CActorLights::CActorLights(u32 areaUpdateFramePeriod, const zeus::CVector3f& actorPosBias, int maxDynamicLights, int maxAreaLights, bool ambientChannelOverflow, bool layer2, bool disableWorldLights, float positionUpdateThreshold) : x298_28_inArea(!disableWorldLights && maxAreaLights > 0) , x298_29_ambienceGenerated(ambientChannelOverflow) , x298_30_layer2(layer2) , x298_31_disableWorldLights(disableWorldLights) , x2a8_areaUpdateFramePeriod(areaUpdateFramePeriod) , x2ac_actorPosBias(actorPosBias) , x2b8_maxAreaLights(maxAreaLights) , x2bc_maxDynamicLights(maxDynamicLights) , x2cc_actorPositionDeltaUpdateThreshold(positionUpdateThreshold * positionUpdateThreshold) { sFrameSchedulerCount++; sFrameSchedulerCount &= 7; } void CActorLights::BuildConstantAmbientLighting() { x299_26_ambientOnly = true; x298_24_dirty = true; x29c_shadowLightArrIdx = -1; x2a0_shadowLightIdx = -1; } void CActorLights::BuildConstantAmbientLighting(const zeus::CColor& color) { x299_26_ambientOnly = false; x288_ambientColor = color; x294_aid = kInvalidAreaId; x298_24_dirty = true; x298_26_hasAreaLights = true; x29c_shadowLightArrIdx = -1; x2a0_shadowLightIdx = -1; } void CActorLights::BuildFakeLightList(const std::vector& lights, const zeus::CColor& color) { BuildConstantAmbientLighting(color); x0_areaLights.clear(); x144_dynamicLights = lights; } void CActorLights::BuildFaceLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb) { zeus::CTransform fpTransform = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform(); x298_26_hasAreaLights = true; x288_ambientColor = zeus::skBlack; x144_dynamicLights.clear(); zeus::CColor accumColor = zeus::skBlack; for (CEntity* light : mgr.GetLightObjectList()) { if (!light || !light->GetActive()) continue; CGameLight* castLight = static_cast(light); if (TCastToConstPtr explosion = mgr.GetObjectById(castLight->GetParentId())) { CLight originalLight = castLight->GetLight(); CLight explosionLight = originalLight; explosionLight.SetAttenuation( explosionLight.GetAttenuationConstant() * g_tweakGui->GetExplosionLightFalloffMultConstant(), explosionLight.GetAttenuationLinear() * g_tweakGui->GetExplosionLightFalloffMultLinear(), explosionLight.GetAttenuationQuadratic() * g_tweakGui->GetExplosionLightFalloffMultQuadratic()); zeus::CVector3f camToExplo = explosion->GetTranslation() - fpTransform.origin; if (fpTransform.transposeRotate(camToExplo).dot(zeus::skForward) >= 0.f) { camToExplo.y() = -camToExplo.y() + ITweakGui::FaceReflectionDistanceDebugValueToActualValue( g_tweakGui->GetFaceReflectionDistance()); camToExplo.z() = -camToExplo.z() + ITweakGui::FaceReflectionHeightDebugValueToActualValue(g_tweakGui->GetFaceReflectionHeight()); explosionLight.SetPosition(fpTransform * camToExplo); zeus::CSphere sphere(originalLight.GetPosition(), originalLight.GetRadius()); if (aabb.intersects(sphere)) { accumColor += explosionLight.GetNormalIndependentLightingAtPoint(fpTransform.origin); if (originalLight.GetIntensity() > FLT_EPSILON && originalLight.GetRadius() > FLT_EPSILON) x144_dynamicLights.push_back(explosionLight); } } } } float greyscale = accumColor.rgbDot(zeus::CColor(0.3f, 0.6f, 0.1f)); if (greyscale < 0.012f) x144_dynamicLights.clear(); if (greyscale > 0.03f) { float attMul = 1.f / (0.03f / greyscale); for (CLight& light : x144_dynamicLights) light.SetAttenuation(light.GetAttenuationConstant() * attMul, light.GetAttenuationLinear() * attMul, light.GetAttenuationQuadratic() * attMul); } } struct SLightValue { u32 x0_areaLightIdx; zeus::CColor x4_color; float x10_colorMag; float x14_accumulatedMag = 0.f; EPVSVisSetState x18_visiblity; }; void CActorLights::MergeOverflowLight(CLight& out, zeus::CColor& color, const CLight& in, float colorMag) { color += in.GetColor() * colorMag; out.SetAngleAttenuation(in.GetAngleAttenuationConstant() * colorMag + out.GetAngleAttenuationConstant(), in.GetAngleAttenuationLinear() * colorMag + out.GetAngleAttenuationLinear(), in.GetAngleAttenuationQuadratic() * colorMag + out.GetAngleAttenuationQuadratic()); out.SetAttenuation(in.GetAttenuationConstant() * colorMag + out.GetAttenuationConstant(), in.GetAttenuationLinear() * colorMag + out.GetAttenuationLinear(), in.GetAttenuationQuadratic() * colorMag + out.GetAttenuationQuadratic()); out.SetPosition(in.GetPosition() * colorMag + out.GetPosition()); out.SetDirection(in.GetDirection() * colorMag + out.GetDirection()); } void CActorLights::AddOverflowToLights(const CLight& light, const zeus::CColor& color, float mag) { if (mag < 0.001f || x2b8_maxAreaLights < 1) return; mag = 1.f / mag; zeus::CColor useColor = color * mag; useColor.a() = 1.f; x0_areaLights.push_back( CLight::BuildCustom(light.GetPosition() * mag, light.GetDirection() * mag, useColor, light.GetAttenuationConstant() * mag, light.GetAttenuationLinear() * mag, light.GetAttenuationQuadratic() * mag, light.GetAngleAttenuationConstant() * mag, light.GetAngleAttenuationLinear() * mag, light.GetAngleAttenuationQuadratic() * mag)); } void CActorLights::MoveAmbienceToLights(const zeus::CColor& color) { if (x298_29_ambienceGenerated || x0_areaLights.empty()) { x288_ambientColor += color * 0.333333f; x288_ambientColor.a() = 1.f; return; } zeus::CColor useColor = x0_areaLights[0].GetColor() + color; float maxComponent = std::max(useColor.r(), std::max(useColor.g(), useColor.b())); if (maxComponent > FLT_EPSILON) useColor *= (1.f / maxComponent); useColor.a() = 1.f; x0_areaLights[0].SetColor(useColor); } void CActorLights::MultiplyLightingLevels(float level) { x288_ambientColor *= level; for (CLight& light : x0_areaLights) { zeus::CColor color = light.GetColor(); color *= level; color.a() = 1.f; light.SetColor(color); } } void CActorLights::UpdateBrightLight() { if (x2dc_brightLightLag > 0 && x299_24_inBrightLight) --x2dc_brightLightLag; else if (x2dc_brightLightLag < 15 && !x299_24_inBrightLight) ++x2dc_brightLightLag; x299_25_useBrightLightLag = true; } bool CActorLights::BuildAreaLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb) { const std::vector& lightList = x298_30_layer2 ? area.GetPostConstructed()->x80_lightsB : area.GetPostConstructed()->x60_lightsA; const std::vector& gfxLightList = x298_30_layer2 ? area.GetPostConstructed()->x90_gfxLightsB : area.GetPostConstructed()->x70_gfxLightsA; float worldLightingLevel = area.GetPostConstructed()->x1128_worldLightingLevel; x298_26_hasAreaLights = lightList.size() != 0; if (!x298_26_hasAreaLights || !x298_28_inArea) { /* World lights disabled */ if (x298_31_disableWorldLights) x2d4_worldLightingLevel = worldLightingLevel; x29c_shadowLightArrIdx = -1; return true; } zeus::CVector3f vec; if (!x298_24_dirty && x294_aid == area.GetAreaId()) { /* Early return if not ready for update */ if (mgr.GetInputFrameIdx() - x2a4_lastUpdateFrame < x2a8_areaUpdateFramePeriod) return false; x2a4_lastUpdateFrame = mgr.GetInputFrameIdx(); vec = aabb.center() + x2ac_actorPosBias; if (x2d4_worldLightingLevel == worldLightingLevel) if ((x2c0_lastActorPos - vec).magSquared() < x2cc_actorPositionDeltaUpdateThreshold) return false; x2c0_lastActorPos = vec; } else { if (x294_aid != area.GetAreaId()) x2d8_brightLightIdx = -1; x2a4_lastUpdateFrame = sFrameSchedulerCount + mgr.GetInputFrameIdx(); vec = aabb.center() + x2ac_actorPosBias; x2c0_lastActorPos = vec; } /* Reset lighting state */ x2d4_worldLightingLevel = worldLightingLevel; x298_24_dirty = false; x294_aid = area.GetAreaId(); x29c_shadowLightArrIdx = -1; x288_ambientColor = zeus::skClear; /* Find candidate lights via PVS */ bool use2ndLayer; if (x298_30_layer2) { if (const CPVSAreaSet* pvs = area.GetAreaVisSet()) use2ndLayer = pvs->Has2ndLayerLights(); else use2ndLayer = true; } else { use2ndLayer = false; } CPVSVisSet sets[3]; sets[0].Reset(EPVSVisSetState::OutOfBounds); sets[1].Reset(EPVSVisSetState::OutOfBounds); sets[2].Reset(EPVSVisSetState::OutOfBounds); if (const CPVSAreaSet* pvs = area.GetAreaVisSet()) { zeus::CVector3f localVec = area.GetInverseTransform() * vec; sets[0].SetTestPoint(pvs->GetVisOctree(), localVec); localVec = area.GetInverseTransform() * aabb.max; sets[1].SetTestPoint(pvs->GetVisOctree(), localVec); localVec = area.GetInverseTransform() * aabb.min; sets[2].SetTestPoint(pvs->GetVisOctree(), localVec); } std::vector valList; valList.reserve(lightList.size()); auto lightIt = lightList.begin(); int lightIdx = 0; for (const CLight& light : gfxLightList) { if (light.GetType() == ELightType::LocalAmbient) { /* Take ambient here */ x288_ambientColor = light.GetNormalIndependentLightingAtPoint(vec); } else { EPVSVisSetState visible = EPVSVisSetState::OutOfBounds; if (area.GetAreaVisSet() && lightIt->DoesCastShadows()) { u32 pvsIdx = use2ndLayer ? area.Get2ndPVSLightFeature(lightIdx) : area.Get1stPVSLightFeature(lightIdx); visible = sets[0].GetVisible(pvsIdx); if (visible != EPVSVisSetState::OutOfBounds) visible = std::max(visible, sets[1].GetVisible(pvsIdx)); if (visible != EPVSVisSetState::OutOfBounds) visible = std::max(visible, sets[2].GetVisible(pvsIdx)); } if (visible != EPVSVisSetState::EndOfTree) { zeus::CSphere sphere(light.GetPosition(), light.GetRadius() * 2.f); if (aabb.intersects(sphere)) { /* Light passes as candidate */ SLightValue& value = valList.emplace_back(); value.x0_areaLightIdx = lightIdx; value.x4_color = light.GetNormalIndependentLightingAtPoint(vec); value.x4_color.a() = 0.f; value.x10_colorMag = value.x4_color.magnitude(); value.x18_visiblity = visible; } } } ++lightIt; ++lightIdx; } /* Sort lights most intense to least intense */ std::sort(valList.begin(), valList.end(), [](const SLightValue& a, const SLightValue& b) { return a.x10_colorMag > b.x10_colorMag; }); if (x298_27_findShadowLight) { /* Accumulate magnitudes up to most intense for shadow dynamic range check */ x288_ambientColor.a() = 0.f; float mag = x288_ambientColor.magnitude(); for (auto it = valList.rbegin(); it != valList.rend(); ++it) { mag += it->x10_colorMag; it->x14_accumulatedMag = mag; } } /* Ambient color for overflow area lights */ zeus::CColor overflowAmbColor = zeus::skClear; /* Averaged light for overflow area lights */ CLight overflowLight = CLight::BuildCustom(zeus::skZero3f, zeus::skZero3f, zeus::skBlack, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); zeus::CColor overflowLightColor = zeus::skClear; float overflowMag = 0.f; /* Max significant lights */ int maxAreaLights = !x298_29_ambienceGenerated ? x2b8_maxAreaLights - 1 : x2b8_maxAreaLights; x0_areaLights.clear(); /* Filter for performing final light visibility test */ constexpr auto filter = CMaterialFilter::MakeIncludeExclude( CMaterialList(EMaterialTypes::Solid), CMaterialList(EMaterialTypes::Projectile, EMaterialTypes::ProjectilePassthrough, EMaterialTypes::SeeThrough)); u32 mostSigLightIdx = 0; /* Narrowphase test candidates starting with most intense */ for (size_t i = 0; i < valList.size(); ++i) { const SLightValue& value = valList[i]; const CLight& light = gfxLightList[value.x0_areaLightIdx]; if (x0_areaLights.size() < maxAreaLights) { /* Significant light */ bool actorToLightContact = true; bool castShadows = lightList[value.x0_areaLightIdx].DoesCastShadows() && x298_25_castShadows; bool outOfBounds = area.GetAreaVisSet() && value.x18_visiblity == EPVSVisSetState::OutOfBounds; if (castShadows) { /* Process shadow cast */ zeus::CVector3f delta = light.GetPosition() - vec; float deltaMag = delta.magnitude(); bool useShadow = false; if (x298_27_findShadowLight && x29c_shadowLightArrIdx == -1 && light.GetType() != ELightType::LocalAmbient && deltaMag > 2.f && !aabb.pointInside(light.GetPosition())) { /* Perform shadow dynamic range check */ if (!x0_areaLights.size() || (x0_areaLights.size() == 1 && value.x10_colorMag / valList[mostSigLightIdx].x10_colorMag > 0.5f)) { useShadow = value.x10_colorMag / value.x14_accumulatedMag > x2d0_shadowDynamicRangeThreshold / (1.f + x2d0_shadowDynamicRangeThreshold); } } if (useShadow) { /* Note shadow light */ x29c_shadowLightArrIdx = x0_areaLights.size(); x2a0_shadowLightIdx = value.x0_areaLightIdx; } else if (!outOfBounds) { /* Note brightest light contact */ delta = delta * 1.f / deltaMag; actorToLightContact = CGameCollision::RayStaticIntersectionArea(area, vec, delta, deltaMag, filter); if (i == 0) { x299_24_inBrightLight = actorToLightContact; if (x2d8_brightLightIdx != value.x0_areaLightIdx) { x2dc_brightLightLag = actorToLightContact ? 0 : 15; x2d8_brightLightIdx = value.x0_areaLightIdx; } x299_25_useBrightLightLag = false; actorToLightContact = true; } } } if (actorToLightContact) { /* Add to final list */ if (x0_areaLights.size() == 0) mostSigLightIdx = i; x0_areaLights.push_back(light); } } else { /* Overflow light */ if (!x298_29_ambienceGenerated && value.x10_colorMag > 0.001f) { /* Average parameters into final light */ MergeOverflowLight(overflowLight, overflowLightColor, light, value.x10_colorMag); overflowMag += value.x10_colorMag; } else { /* Average color into ambient channel */ overflowAmbColor += value.x4_color; } } } /* Finalize overflow lights */ if (!x298_29_ambienceGenerated) AddOverflowToLights(overflowLight, overflowLightColor, overflowMag); else MoveAmbienceToLights(overflowAmbColor); /* Clamp ambient color */ if (x288_ambientColor.r() > 1.f) x288_ambientColor.r() = 1.f; if (x288_ambientColor.g() > 1.f) x288_ambientColor.g() = 1.f; if (x288_ambientColor.b() > 1.f) x288_ambientColor.b() = 1.f; x288_ambientColor.a() = 1.f; /* Multiply down lighting with world fader level */ if (worldLightingLevel < 1.f) MultiplyLightingLevels(worldLightingLevel); return true; } void CActorLights::BuildDynamicLightList(const CStateManager& mgr, const zeus::CAABox& aabb) { UpdateBrightLight(); x299_26_ambientOnly = false; x144_dynamicLights.clear(); if (!x29a_findNearestDynamicLights) { for (const CLight& light : mgr.GetDynamicLightList()) { zeus::CSphere sphere(light.GetPosition(), light.GetRadius()); if (aabb.intersects(sphere)) x144_dynamicLights.push_back(light); if (x144_dynamicLights.size() >= x2bc_maxDynamicLights) break; } } else { const CLight* addedLights[8] = {}; for (int i = 0; i < x2bc_maxDynamicLights && i < 8; ++i) { float minRad = FLT_MAX; for (const CLight& light : mgr.GetDynamicLightList()) { zeus::CSphere sphere(light.GetPosition(), light.GetRadius()); float intRadius = aabb.intersectionRadius(sphere); if (intRadius >= 0.f && intRadius < minRad) { bool alreadyIn = false; for (int j = 0; j < i; ++j) { if (&light == addedLights[j]) { alreadyIn = true; break; } } if (alreadyIn) continue; addedLights[i] = &light; minRad = intRadius; } } if (addedLights[i]) x144_dynamicLights.push_back(*addedLights[i]); if (x144_dynamicLights.size() >= x2bc_maxDynamicLights) break; } } } std::vector CActorLights::BuildLightVector() const { std::vector lights; if (!x0_areaLights.empty()) { if (x2dc_brightLightLag != 0 && x299_25_useBrightLightLag) { CLight overrideLight = x0_areaLights[0]; overrideLight.SetColor(overrideLight.GetColor() * (1.f - x2dc_brightLightLag / 15.f)); lights.push_back(overrideLight); } else { lights.push_back(x0_areaLights[0]); } for (auto it = x0_areaLights.begin() + 1; it != x0_areaLights.end(); ++it) { lights.push_back(*it); } } for (const CLight& light : x144_dynamicLights) { lights.push_back(light); } return lights; } void CActorLights::ActivateLights() const { if (x298_28_inArea) { if (!x298_26_hasAreaLights || x299_26_ambientOnly) { g_Renderer->SetAmbientColor(zeus::skWhite); CGraphics::DisableAllLights(); return; } } auto ambient = x288_ambientColor; ambient.a() = 1.f; g_Renderer->SetAmbientColor(ambient); const auto lights = BuildLightVector(); if (lights.empty()) { CGraphics::DisableAllLights(); } else { for (ERglLight idx = 0; const auto& item : lights) { CGraphics::LoadLight(idx, item); idx++; } // Sets n LSB to 1 CGraphics::SetLightState(static_cast((1 << lights.size()) + 255)); } if (x298_31_disableWorldLights) { g_Renderer->SetAmbientColor(zeus::skBlack); g_Renderer->SetGXRegister1Color({x2d4_worldLightingLevel}); } } void CActorLights::DisableAreaLights() { x2b8_maxAreaLights = 0; x298_26_hasAreaLights = false; x298_28_inArea = false; } const CLight& CActorLights::GetLight(u32 idx) const { if (x298_28_inArea) { if (idx < x0_areaLights.size()) return x0_areaLights[idx]; return x144_dynamicLights[idx - x0_areaLights.size()]; } return x144_dynamicLights[idx]; } u32 CActorLights::GetActiveLightCount() const { if (x298_28_inArea) return x0_areaLights.size() + x144_dynamicLights.size(); return x144_dynamicLights.size(); } } // namespace metaforce ================================================ FILE: Runtime/Character/CActorLights.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Graphics/CLight.hpp" #include #include #include namespace metaforce { class CBooModel; class CGameArea; class CStateManager; class CActorLights { static s32 sFrameSchedulerCount; std::vector x0_areaLights; std::vector x144_dynamicLights; zeus::CColor x288_ambientColor = zeus::skBlack; TAreaId x294_aid = kInvalidAreaId; bool x298_24_dirty : 1 = true; bool x298_25_castShadows : 1 = true; bool x298_26_hasAreaLights : 1 = false; bool x298_27_findShadowLight : 1 = false; bool x298_28_inArea : 1; bool x298_29_ambienceGenerated : 1; bool x298_30_layer2 : 1; bool x298_31_disableWorldLights : 1; bool x299_24_inBrightLight : 1 = true; bool x299_25_useBrightLightLag : 1 = false; bool x299_26_ambientOnly : 1 = false; bool x29a_findNearestDynamicLights = false; s32 x29c_shadowLightArrIdx = -1; s32 x2a0_shadowLightIdx = -1; u32 x2a4_lastUpdateFrame = 0; u32 x2a8_areaUpdateFramePeriod; zeus::CVector3f x2ac_actorPosBias; int x2b8_maxAreaLights; int x2bc_maxDynamicLights; zeus::CVector3f x2c0_lastActorPos; float x2cc_actorPositionDeltaUpdateThreshold; float x2d0_shadowDynamicRangeThreshold = 0.f; float x2d4_worldLightingLevel = 1.f; s32 x2d8_brightLightIdx = -1; u32 x2dc_brightLightLag = 0; static void MergeOverflowLight(CLight& out, zeus::CColor& color, const CLight& in, float colorMag); void AddOverflowToLights(const CLight& light, const zeus::CColor& color, float mag); void MoveAmbienceToLights(const zeus::CColor& color); void MultiplyLightingLevels(float level); void UpdateBrightLight(); public: CActorLights(u32 areaUpdateFramePeriod, const zeus::CVector3f& actorPosBias, int maxDynamicLights, int maxAreaLights, bool ambientChannelOverflow, bool layer2, bool disableWorldLights, float positionUpdateThreshold); void BuildConstantAmbientLighting(); void BuildConstantAmbientLighting(const zeus::CColor& color); void BuildFakeLightList(const std::vector& lights, const zeus::CColor& color); void BuildFaceLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb); bool BuildAreaLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb); void BuildDynamicLightList(const CStateManager& mgr, const zeus::CAABox& aabb); std::vector BuildLightVector() const; void ActivateLights() const; void SetCastShadows(bool v) { x298_25_castShadows = v; } void SetHasAreaLights(bool v) { x298_26_hasAreaLights = v; } void SetFindShadowLight(bool v) { x298_27_findShadowLight = v; } void SetShadowDynamicRangeThreshold(float t) { x2d0_shadowDynamicRangeThreshold = t; } void SetAmbienceGenerated(bool v) { x298_29_ambienceGenerated = v; } void DisableAreaLights(); void SetMaxAreaLights(int l) { x2b8_maxAreaLights = l; } void SetMaxDynamicLights(int l) { x2bc_maxDynamicLights = l; } void SetFindNearestDynamicLights(bool v) { x29a_findNearestDynamicLights = v; } void SetAmbientColor(const zeus::CColor& color) { x288_ambientColor = color; } const zeus::CColor& GetAmbientColor() const { return x288_ambientColor; } const CLight& GetLight(u32 idx) const; u32 GetActiveLightCount() const; int GetMaxAreaLights() const { return x2b8_maxAreaLights; } const std::vector& GetAreaLights() const { return x0_areaLights; } const std::vector& GetDynamicLights() const { return x144_dynamicLights; } bool GetIsDirty() const { return x298_24_dirty; } void SetDirty() { x298_24_dirty = true; } bool HasShadowLight() const { return x29c_shadowLightArrIdx != -1; } s32 GetShadowLightArrIndex() const { return x29c_shadowLightArrIdx; } s32 GetShadowLightIndex() const { return x2a0_shadowLightIdx; } u32 GetAreaUpdateFramePeriod() const { return x2a8_areaUpdateFramePeriod; } void SetAreaUpdateFramePeriod(u32 p) { x2a8_areaUpdateFramePeriod = p; } zeus::CVector3f GetActorPositionBias() const { return x2ac_actorPosBias; } void SetActorPositionBias(const zeus::CVector3f& bias) { x2ac_actorPosBias = bias; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAdditiveAnimPlayback.cpp ================================================ #include "Runtime/Character/CAdditiveAnimPlayback.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CSegStatementSet.hpp" namespace metaforce { CAdditiveAnimPlayback::CAdditiveAnimPlayback(const std::weak_ptr& anim, float weight, bool active, const CAdditiveAnimationInfo& info, bool fadeOut) : x0_info(info), x8_anim(anim.lock()), xc_targetWeight(weight), x14_active(active) { if (!active && fadeOut) x20_needsFadeOut = true; } void CAdditiveAnimPlayback::AddToSegStatementSet(const CSegIdList& list, const CCharLayoutInfo& layout, CSegStatementSet& setOut) const { CSegStatementSet stackSet; x8_anim->VGetSegStatementSet(list, stackSet); for (const CSegId& id : list.GetList()) { CAnimPerSegmentData& data = stackSet[id]; data.x10_offset = layout.GetFromParentUnrotated(id); data.x1c_hasOffset = true; } setOut.Add(list, layout, stackSet, x10_curWeight); } void CAdditiveAnimPlayback::Update(float dt) { switch (x1c_phase) { case EAdditivePlaybackPhase::FadingIn: { float a = x0_info.GetFadeInDuration(); float b = x18_weightTimer + dt; x18_weightTimer = std::min(b, a); if (a > 0.f) x10_curWeight = x18_weightTimer / a * xc_targetWeight; else x10_curWeight = xc_targetWeight; if (std::fabs(x10_curWeight - xc_targetWeight) < 0.00001f) x1c_phase = EAdditivePlaybackPhase::FadedIn; break; } case EAdditivePlaybackPhase::FadingOut: { float a = x18_weightTimer - dt; x18_weightTimer = std::max(a, 0.f); if (x0_info.GetFadeOutDuration() > 0.f) x10_curWeight = x18_weightTimer / x0_info.GetFadeOutDuration() * xc_targetWeight; else x10_curWeight = 0.f; if (std::fabs(x10_curWeight) < 0.00001f) x1c_phase = EAdditivePlaybackPhase::FadedOut; break; } default: break; } } void CAdditiveAnimPlayback::FadeOut() { switch (x1c_phase) { case EAdditivePlaybackPhase::FadedOut: case EAdditivePlaybackPhase::FadedIn: x18_weightTimer = x0_info.GetFadeOutDuration(); break; case EAdditivePlaybackPhase::FadingIn: x18_weightTimer = x18_weightTimer / x0_info.GetFadeInDuration() * x0_info.GetFadeOutDuration(); break; default: break; } if (x0_info.GetFadeOutDuration() > 0.f) x1c_phase = EAdditivePlaybackPhase::FadingOut; else x1c_phase = EAdditivePlaybackPhase::FadedOut; x10_curWeight = 0.f; } void CAdditiveAnimPlayback::SetWeight(float w) { xc_targetWeight = w; switch (x1c_phase) { case EAdditivePlaybackPhase::FadingIn: { if (x0_info.GetFadeInDuration() > 0.f) x10_curWeight = x18_weightTimer / x0_info.GetFadeInDuration() * xc_targetWeight; else x10_curWeight = xc_targetWeight; break; } case EAdditivePlaybackPhase::FadingOut: { if (x0_info.GetFadeOutDuration() > 0.f) x10_curWeight = x18_weightTimer / x0_info.GetFadeOutDuration() * xc_targetWeight; else x10_curWeight = xc_targetWeight; break; } default: x10_curWeight = xc_targetWeight; break; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CAdditiveAnimPlayback.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Streams/CInputStream.hpp" namespace metaforce { class CAdditiveAnimationInfo; class CAnimTreeNode; class CCharLayoutInfo; class CSegIdList; class CSegStatementSet; class CAdditiveAnimationInfo { float x0_fadeInDur = 0.f; float x4_fadeOutDur = 0.f; public: void read(CInputStream& in) { x0_fadeInDur = in.ReadFloat(); x4_fadeOutDur = in.ReadFloat(); } CAdditiveAnimationInfo() = default; explicit CAdditiveAnimationInfo(CInputStream& in) { read(in); } float GetFadeInDuration() const { return x0_fadeInDur; } float GetFadeOutDuration() const { return x4_fadeOutDur; } }; enum class EAdditivePlaybackPhase { None, FadingIn, FadingOut, FadedIn, FadedOut }; class CAdditiveAnimPlayback { CAdditiveAnimationInfo x0_info; std::shared_ptr x8_anim; float xc_targetWeight; float x10_curWeight = 0.f; bool x14_active; float x18_weightTimer = 0.f; EAdditivePlaybackPhase x1c_phase = EAdditivePlaybackPhase::FadingIn; bool x20_needsFadeOut = false; public: CAdditiveAnimPlayback(const std::weak_ptr& anim, float weight, bool active, const CAdditiveAnimationInfo& info, bool fadeOut); void AddToSegStatementSet(const CSegIdList& list, const CCharLayoutInfo&, CSegStatementSet&) const; void Update(float dt); void FadeOut(); void SetWeight(float w); float GetTargetWeight() const { return xc_targetWeight; } bool IsActive() const { return x14_active; } void SetActive(bool active) { x14_active = active; } const std::shared_ptr& GetAnim() const { return x8_anim; } std::shared_ptr& GetAnim() { return x8_anim; } EAdditivePlaybackPhase GetPhase() const { return x1c_phase; } void SetNeedsFadeOut(bool b) { x20_needsFadeOut = b; } bool NeedsFadeOut() const { return x20_needsFadeOut; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAdditiveBodyState.cpp ================================================ #include "Runtime/Character/CAdditiveBodyState.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Character/CBodyController.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CPASDatabase.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" namespace metaforce { void CABSAim::Start(CBodyController& bc, CStateManager& mgr) { // const CBCAdditiveAimCmd* cmd = // static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveAim)); const CPASAnimState* aimState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::AdditiveAim); // Left, Right, Up, Down for (size_t i = 0; i < x8_anims.size(); ++i) { const CPASAnimParmData parms(pas::EAnimationState::AdditiveAim, CPASAnimParm::FromEnum(s32(i))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); x8_anims[i] = best.second; x18_angles[i] = zeus::degToRad(aimState->GetAnimParmData(x8_anims[i], 1).GetReal32Value()); } const CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); x28_hWeight = -animData.GetAdditiveAnimationWeight(x8_anims[0]); x28_hWeight += animData.GetAdditiveAnimationWeight(x8_anims[1]); x30_vWeight = -animData.GetAdditiveAnimationWeight(x8_anims[3]); x30_vWeight += animData.GetAdditiveAnimationWeight(x8_anims[2]); x4_needsIdle = false; if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveIdle)) x4_needsIdle = true; } pas::EAnimationState CABSAim::GetBodyStateTransition(float dt, CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction)) return pas::EAnimationState::AdditiveReaction; if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveFlinch)) return pas::EAnimationState::AdditiveFlinch; if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveIdle) || x4_needsIdle) return pas::EAnimationState::AdditiveIdle; return pas::EAnimationState::Invalid; } pas::EAnimationState CABSAim::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { const zeus::CVector3f& target = bc.GetCommandMgr().GetAdditiveTargetVector(); if (target.canBeNormalized()) { CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); float hAngle = zeus::clamp(-x18_angles[0], std::atan2(target.x(), target.y()), x18_angles[1]); hAngle *= 0.63661975f; hAngle = zeus::clamp(-3.f, (hAngle - x28_hWeight) * 0.25f / dt, 3.f); x2c_hWeightVel += dt * zeus::clamp(-10.f, (hAngle - x2c_hWeightVel) / dt, 10.f); float hypotenuse = std::sqrt(target.y() * target.y() + target.x() * target.x()); float vAngle = zeus::clamp(-x18_angles[3], std::atan2(target.z(), hypotenuse), x18_angles[2]); vAngle *= 0.63661975f; vAngle = zeus::clamp(-3.f, (vAngle - x30_vWeight) * 0.25f / dt, 3.f); x34_vWeightVel += dt * zeus::clamp(-10.f, (vAngle - x34_vWeightVel) / dt, 10.f); float newHWeight = dt * x2c_hWeightVel + x28_hWeight; if (newHWeight != x28_hWeight) { if (std::fabs(x28_hWeight) > 0.f && x28_hWeight * newHWeight <= 0.f) animData.DelAdditiveAnimation(x8_anims[x28_hWeight < 0.f ? 0 : 1]); float absWeight = std::fabs(newHWeight); if (absWeight > 0.f) animData.AddAdditiveAnimation(x8_anims[newHWeight < 0.f ? 0 : 1], absWeight, false, false); } float newVWeight = dt * x34_vWeightVel + x30_vWeight; if (newVWeight != x30_vWeight) { if (std::fabs(x30_vWeight) > 0.f && x30_vWeight * newVWeight <= 0.f) animData.DelAdditiveAnimation(x8_anims[x30_vWeight > 0.f ? 2 : 3]); float absWeight = std::fabs(newVWeight); if (absWeight > 0.f) animData.AddAdditiveAnimation(x8_anims[newVWeight > 0.f ? 2 : 3], absWeight, false, false); } x28_hWeight = newHWeight; x30_vWeight = newVWeight; } } return st; } void CABSAim::Shutdown(CBodyController& bc) { CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); if (x28_hWeight != 0.f) animData.DelAdditiveAnimation(x8_anims[x28_hWeight < 0.f ? 0 : 1]); if (x30_vWeight != 0.f) animData.DelAdditiveAnimation(x8_anims[x30_vWeight > 0.f ? 2 : 3]); } void CABSFlinch::Start(CBodyController& bc, CStateManager& mgr) { const CBCAdditiveFlinchCmd* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveFlinch)); x4_weight = cmd->GetWeight(); CPASAnimParmData parms(pas::EAnimationState::AdditiveFlinch); std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); x8_anim = best.second; CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); animData.AddAdditiveAnimation(x8_anim, x4_weight, false, true); } pas::EAnimationState CABSFlinch::GetBodyStateTransition(float dt, CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction)) return pas::EAnimationState::AdditiveReaction; return pas::EAnimationState::Invalid; } pas::EAnimationState CABSFlinch::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); CCharAnimTime rem = animData.GetAdditiveAnimationTree(x8_anim)->VGetTimeRemaining(); if (std::fabs(rem.GetSeconds()) < 0.00001f) return pas::EAnimationState::AdditiveIdle; } return st; } pas::EAnimationState CABSIdle::GetBodyStateTransition(float dt, CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction)) return pas::EAnimationState::AdditiveReaction; if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveFlinch)) return pas::EAnimationState::AdditiveFlinch; if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveAim)) return pas::EAnimationState::AdditiveAim; return pas::EAnimationState::Invalid; } pas::EAnimationState CABSIdle::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { return GetBodyStateTransition(dt, bc); } void CABSReaction::Start(CBodyController& bc, CStateManager& mgr) { const CBCAdditiveReactionCmd* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction)); x4_weight = cmd->GetWeight(); xc_type = cmd->GetType(); x10_active = cmd->GetIsActive(); CPASAnimParmData parms(pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(s32(xc_type))); std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); x8_anim = best.second; if (x8_anim != -1) { CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); animData.AddAdditiveAnimation(x8_anim, x4_weight, x10_active, false); } } pas::EAnimationState CABSReaction::GetBodyStateTransition(float dt, CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::AdditiveReaction) && xc_type == pas::EAdditiveReactionType::IceBreakout) return pas::EAnimationState::AdditiveReaction; return pas::EAnimationState::Invalid; } pas::EAnimationState CABSReaction::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { if (x8_anim == -1) return pas::EAnimationState::AdditiveIdle; CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); if (x10_active) { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::StopReaction)) { StopAnimation(bc); bc.GetOwner().RemoveEmitter(); return pas::EAnimationState::AdditiveIdle; } } else { if (animData.IsAdditiveAnimationAdded(x8_anim)) { CCharAnimTime rem = animData.GetAdditiveAnimationTree(x8_anim)->VGetTimeRemaining(); if (std::fabs(rem.GetSeconds()) < 0.00001f) { StopAnimation(bc); return pas::EAnimationState::AdditiveIdle; } } else { return pas::EAnimationState::AdditiveIdle; } } } return st; } void CABSReaction::StopAnimation(CBodyController& bc) { if (x8_anim != -1) { CAnimData& animData = *bc.GetOwner().GetModelData()->GetAnimationData(); animData.DelAdditiveAnimation(x8_anim); x8_anim = -1; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CAdditiveBodyState.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CBodyStateCmdMgr.hpp" #include "Runtime/Character/CharacterCommon.hpp" namespace metaforce { class CActor; class CBodyController; class CStateManager; class CAdditiveBodyState { public: virtual ~CAdditiveBodyState() = default; virtual bool ApplyHeadTracking() const { return true; } virtual bool CanShoot() const { return true; } virtual void Start(CBodyController& bc, CStateManager& mgr) = 0; virtual pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) = 0; virtual void Shutdown(CBodyController& bc) = 0; }; class CABSAim : public CAdditiveBodyState { bool x4_needsIdle = false; std::array x8_anims{}; std::array x18_angles{}; float x28_hWeight = 0.f; float x2c_hWeightVel = 0.f; float x30_vWeight = 0.f; float x34_vWeightVel = 0.f; pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override; }; class CABSFlinch : public CAdditiveBodyState { float x4_weight = 1.f; u32 x8_anim = 0; pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override {} }; class CABSIdle : public CAdditiveBodyState { pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override {} pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override {} }; class CABSReaction : public CAdditiveBodyState { float x4_weight = 1.f; s32 x8_anim = -1; pas::EAdditiveReactionType xc_type = pas::EAdditiveReactionType::Invalid; bool x10_active = false; pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const; void StopAnimation(CBodyController& bc); public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override { StopAnimation(bc); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAllFormatsAnimSource.cpp ================================================ #include "Runtime/Character/CAllFormatsAnimSource.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/Character/CAnimSourceReader.hpp" #include "Runtime/Character/CFBStreamedAnimReader.hpp" #include "Runtime/Logging.hpp" namespace metaforce { void CAnimFormatUnion::SubConstruct(u8* storage, EAnimFormat fmt, CInputStream& in, IObjectStore& store) { switch (fmt) { case EAnimFormat::Uncompressed: new (storage) CAnimSource(in, store); break; case EAnimFormat::BitstreamCompressed: new (storage) CFBStreamedCompression(in, store, false); break; case EAnimFormat::BitstreamCompressed24: new (storage) CFBStreamedCompression(in, store, true); break; default: spdlog::fatal("unable to read ANIM format {}", static_cast(fmt)); } } CAnimFormatUnion::CAnimFormatUnion(CInputStream& in, IObjectStore& store) { x0_format = EAnimFormat(in.ReadLong()); SubConstruct(x4_storage, x0_format, in, store); } CAnimFormatUnion::~CAnimFormatUnion() { switch (x0_format) { case EAnimFormat::Uncompressed: reinterpret_cast(x4_storage)->~CAnimSource(); break; case EAnimFormat::BitstreamCompressed: case EAnimFormat::BitstreamCompressed24: reinterpret_cast(x4_storage)->~CFBStreamedCompression(); break; default: break; } } std::shared_ptr CAllFormatsAnimSource::GetNewReader(const TLockedToken& tok, const CCharAnimTime& startTime) { switch (tok->x0_format) { case EAnimFormat::Uncompressed: return std::make_shared(tok, startTime); case EAnimFormat::BitstreamCompressed: case EAnimFormat::BitstreamCompressed24: return std::make_shared(tok, startTime); default: break; } return {}; } CAllFormatsAnimSource::CAllFormatsAnimSource(CInputStream& in, IObjectStore& store, const SObjectTag& tag) : CAnimFormatUnion(in, store), x74_tag(tag) {} CFactoryFnReturn AnimSourceFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params, CObjectReference* selfRef) { CSimplePool* sp = params.GetOwnedObj(); return TToken::GetIObjObjectFor(std::make_unique(in, *sp, tag)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAllFormatsAnimSource.hpp ================================================ #pragma once #include #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CAnimSource.hpp" #include "Runtime/Character/CFBStreamedCompression.hpp" #include namespace metaforce { class IAnimReader; class IObjectStore; enum class EAnimFormat { Uncompressed, Unknown, BitstreamCompressed, BitstreamCompressed24 }; class CAnimFormatUnion { friend class CAllFormatsAnimSource; union { EAnimFormat x0_format; u8 _align[16]; }; u8 x4_storage[std::max(sizeof(CAnimSource), sizeof(CFBStreamedCompression))]; static void SubConstruct(u8* storage, EAnimFormat fmt, CInputStream& in, IObjectStore& store); public: explicit CAnimFormatUnion(CInputStream& in, IObjectStore& store); ~CAnimFormatUnion(); EAnimFormat GetFormat() const { return x0_format; } CAnimSource& GetAsCAnimSource() { return *reinterpret_cast(x4_storage); } CFBStreamedCompression& GetAsCFBStreamedCompression() { return *reinterpret_cast(x4_storage); } }; class CAllFormatsAnimSource : public CAnimFormatUnion { zeus::CVector3f x68_; SObjectTag x74_tag; public: explicit CAllFormatsAnimSource(CInputStream& in, IObjectStore& store, const SObjectTag& tag); static std::shared_ptr GetNewReader(const TLockedToken& tok, const CCharAnimTime& startTime); }; CFactoryFnReturn AnimSourceFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimCharacterSet.cpp ================================================ #include "Runtime/Character/CAnimCharacterSet.hpp" #include "Runtime/CToken.hpp" namespace metaforce { CAnimCharacterSet::CAnimCharacterSet(CInputStream& in) : x0_version(in.ReadShort()), x4_characterSet(in), x1c_animationSet(in) {} CFactoryFnReturn FAnimCharacterSet(const SObjectTag&, CInputStream& in, const CVParamTransfer&, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimCharacterSet.hpp ================================================ #pragma once #include "Runtime/CFactoryMgr.hpp" #include "Runtime/Character/CAnimationSet.hpp" #include "Runtime/Character/CCharacterSet.hpp" namespace metaforce { class CAnimCharacterSet { u16 x0_version; CCharacterSet x4_characterSet; CAnimationSet x1c_animationSet; public: explicit CAnimCharacterSet(CInputStream& in); const CCharacterSet& GetCharacterSet() const { return x4_characterSet; } const CAnimationSet& GetAnimationSet() const { return x1c_animationSet; } }; CFactoryFnReturn FAnimCharacterSet(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimData.cpp ================================================ #include "Runtime/Character/CAnimData.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CAdditiveAnimPlayback.hpp" #include "Runtime/Character/CAllFormatsAnimSource.hpp" #include "Runtime/Character/CAnimPerSegmentData.hpp" #include "Runtime/Character/CAnimPlaybackParms.hpp" #include "Runtime/Character/CAnimTreeBlend.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CAnimationManager.hpp" #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CCharacterFactory.hpp" #include "Runtime/Character/CCharacterInfo.hpp" #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CParticleGenInfo.hpp" #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CPrimitive.hpp" #include "Runtime/Character/CSegStatementSet.hpp" #include "Runtime/Character/CSoundPOINode.hpp" #include "Runtime/Character/CTransitionManager.hpp" #include "Runtime/Character/IAnimReader.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/Logging.hpp" namespace metaforce { rstl::reserved_vector CAnimData::g_BoolPOINodes; rstl::reserved_vector CAnimData::g_Int32POINodes; rstl::reserved_vector CAnimData::g_ParticlePOINodes; rstl::reserved_vector CAnimData::g_SoundPOINodes; rstl::reserved_vector CAnimData::g_TransientInt32POINodes; void CAnimData::FreeCache() {} void CAnimData::InitializeCache() {} CAnimData::CAnimData(CAssetId id, const CCharacterInfo& character, int defaultAnim, int charIdx, bool loop, TLockedToken layout, TToken model, const std::optional>& iceModel, const std::weak_ptr& ctx, std::shared_ptr animMgr, std::shared_ptr transMgr, TLockedToken charFactory) : x0_charFactory(charFactory) , xc_charInfo(character) , xcc_layoutData(layout) , xd8_modelData(std::move(model)) , xfc_animCtx(ctx.lock()) , x100_animMgr(std::move(animMgr)) , x108_aabb() , x1d8_selfId(id) , x1fc_transMgr(std::move(transMgr)) , x204_charIdx(charIdx) , x208_defaultAnim(defaultAnim) , x224_pose(layout->GetSegIdList().GetList().size()) , x2fc_poseBuilder(CLayoutDescription{layout}) { x220_25_loop = loop; if (iceModel) xe4_iceModelData = *iceModel; g_BoolPOINodes.resize(8); g_Int32POINodes.resize(16); g_ParticlePOINodes.resize(20); g_SoundPOINodes.resize(20); g_TransientInt32POINodes.resize(16); xd8_modelData->CalculateDefault(); for (const auto& item : xd8_modelData->GetModel()->GetPositions()) { x108_aabb.accumulateBounds({item.x, item.y, item.z}); } x120_particleDB.CacheParticleDesc(xc_charInfo.GetParticleResData()); CHierarchyPoseBuilder pb(CLayoutDescription{xcc_layoutData}); pb.BuildNoScale(x224_pose); x220_30_poseBuilt = true; if (defaultAnim == -1) { defaultAnim = 0; spdlog::warn("Character {} has invalid initial animation, so defaulting to first.", character.GetCharacterName()); } auto treeNode = GetAnimationManager()->GetAnimationTree(character.GetAnimationIndex(defaultAnim), CMetaAnimTreeBuildOrders::NoSpecialOrders()); if (treeNode != x1f8_animRoot) { x1f8_animRoot = std::move(treeNode); } } void CAnimData::SetParticleEffectState(std::string_view effectName, bool active, CStateManager& mgr) { auto search = std::find_if(xc_charInfo.x98_effects.begin(), xc_charInfo.x98_effects.end(), [effectName](const auto& v) { return v.first == effectName; }); if (search != xc_charInfo.x98_effects.end()) for (const auto& p : search->second) x120_particleDB.SetParticleEffectState(p.GetComponentName(), active, mgr); } void CAnimData::InitializeEffects(CStateManager& mgr, TAreaId aId, const zeus::CVector3f& scale) { for (const auto& effects : xc_charInfo.GetEffectList()) { for (const auto& effect : effects.second) { x120_particleDB.CacheParticleDesc(effect.GetParticleTag()); const CParticleData data{effect.GetParticleTag(), effect.GetSegmentName(), effect.GetScale(), effect.GetParentedMode()}; x120_particleDB.AddParticleEffect(effect.GetComponentName(), effect.GetFlags(), data, scale, mgr, aId, true, x21c_particleLightIdx); x120_particleDB.SetParticleEffectState(effect.GetComponentName(), false, mgr); } } } CAssetId CAnimData::GetEventResourceIdForAnimResourceId(CAssetId id) const { return x0_charFactory->GetEventResourceIdForAnimResourceId(id); } void CAnimData::AddAdditiveSegData(const CSegIdList& list, CSegStatementSet& stSet) { for (auto& additive : x434_additiveAnims) if (additive.second.GetTargetWeight() > 0.00001f) additive.second.AddToSegStatementSet(list, *xcc_layoutData.GetObj(), stSet); } SAdvancementResults CAnimData::AdvanceAdditiveAnim(std::shared_ptr& anim, const CCharAnimTime& time) { SAdvancementResults ret = anim->VAdvanceView(time); auto simplified = anim->Simplified(); if (simplified) anim = CAnimTreeNode::Cast(std::move(*simplified)); return ret; } SAdvancementDeltas CAnimData::AdvanceAdditiveAnims(float dt) { CCharAnimTime time(dt); SAdvancementDeltas deltas = {}; for (auto& additive : x434_additiveAnims) { std::shared_ptr& anim = additive.second.GetAnim(); if (additive.second.IsActive()) { while (time.GreaterThanZero() && std::fabs(time.GetSeconds()) >= 0.00001f) { x210_passedIntCount += u32(anim->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 0)); x20c_passedBoolCount += u32(anim->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0)); x214_passedParticleCount += u32(anim->GetParticlePOIList(time, g_ParticlePOINodes.data(), 8, x214_passedParticleCount, 0)); x218_passedSoundCount += u32(anim->GetSoundPOIList(time, g_SoundPOINodes.data(), 8, x218_passedSoundCount, 0)); SAdvancementResults results = AdvanceAdditiveAnim(anim, time); deltas.x0_posDelta += results.x8_deltas.x0_posDelta; deltas.xc_rotDelta *= results.x8_deltas.xc_rotDelta; time = results.x0_remTime; } } else { CCharAnimTime remTime = anim->VGetTimeRemaining(); while (remTime.GreaterThanZero() && std::fabs(remTime.GetSeconds()) >= 0.00001f) { x210_passedIntCount += u32(anim->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 0)); x20c_passedBoolCount += u32(anim->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0)); x214_passedParticleCount += u32(anim->GetParticlePOIList(time, g_ParticlePOINodes.data(), 8, x214_passedParticleCount, 0)); x218_passedSoundCount += u32(anim->GetSoundPOIList(time, g_SoundPOINodes.data(), 8, x218_passedSoundCount, 0)); SAdvancementResults results = AdvanceAdditiveAnim(anim, time); deltas.x0_posDelta += results.x8_deltas.x0_posDelta; deltas.xc_rotDelta *= results.x8_deltas.xc_rotDelta; CCharAnimTime tmpTime = anim->VGetTimeRemaining(); if (tmpTime < results.x0_remTime) remTime = tmpTime; else remTime = results.x0_remTime; } } } return deltas; } SAdvancementDeltas CAnimData::UpdateAdditiveAnims(float dt) { for (auto it = x434_additiveAnims.begin(); it != x434_additiveAnims.end();) { it->second.Update(dt); CCharAnimTime timeRem = it->second.GetAnim()->VGetTimeRemaining(); if (timeRem.EpsilonZero() && it->second.NeedsFadeOut()) it->second.FadeOut(); if (it->second.GetPhase() == EAdditivePlaybackPhase::FadedOut) { it = x434_additiveAnims.erase(it); continue; } ++it; } return AdvanceAdditiveAnims(dt); } bool CAnimData::IsAdditiveAnimation(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); return x0_charFactory->HasAdditiveInfo(animIdx); } bool CAnimData::IsAdditiveAnimationAdded(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); return search != x434_additiveAnims.cend(); } const std::shared_ptr& CAnimData::GetAdditiveAnimationTree(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); return search->second.GetAnim(); } bool CAnimData::IsAdditiveAnimationActive(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search == x434_additiveAnims.cend()) return false; return search->second.IsActive(); } void CAnimData::DelAdditiveAnimation(s32 idx) { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.begin(), x434_additiveAnims.end(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search != x434_additiveAnims.cend() && search->second.GetPhase() != EAdditivePlaybackPhase::FadingOut && search->second.GetPhase() != EAdditivePlaybackPhase::FadedOut) { search->second.FadeOut(); } } void CAnimData::AddAdditiveAnimation(s32 idx, float weight, bool active, bool fadeOut) { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.begin(), x434_additiveAnims.end(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search != x434_additiveAnims.cend()) { search->second.SetActive(active); search->second.SetWeight(weight); search->second.SetNeedsFadeOut(!search->second.IsActive() && fadeOut); } else { std::shared_ptr node = GetAnimationManager()->GetAnimationTree(animIdx, CMetaAnimTreeBuildOrders::NoSpecialOrders()); const CAdditiveAnimationInfo& info = x0_charFactory->FindAdditiveInfo(animIdx); x434_additiveAnims.emplace_back( std::make_pair(animIdx, CAdditiveAnimPlayback(node, weight, active, info, fadeOut))); } } float CAnimData::GetAdditiveAnimationWeight(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search != x434_additiveAnims.cend()) return search->second.GetTargetWeight(); return 0.f; } std::shared_ptr CAnimData::GetAnimationManager() { return x100_animMgr; } void CAnimData::SetPhase(float ph) { x1f8_animRoot->VSetPhase(ph); } void CAnimData::Touch(CSkinnedModel& model, int shadIdx) const { model.GetModel()->Touch(shadIdx); } SAdvancementDeltas CAnimData::GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const { return x1f8_animRoot->VGetAdvancementResults(a, b).x8_deltas; } CCharAnimTime CAnimData::GetTimeOfUserEvent(EUserEventType type, const CCharAnimTime& time) const { const size_t count = x1f8_animRoot->GetInt32POIList(time, g_TransientInt32POINodes.data(), g_TransientInt32POINodes.size(), 0, 64); for (size_t i = 0; i < count; ++i) { CInt32POINode& poi = g_TransientInt32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent && EUserEventType(poi.GetValue()) == type) { CCharAnimTime ret = poi.GetTime(); for (; i < count; ++i) g_TransientInt32POINodes[i] = CInt32POINode(); return ret; } else { poi = CInt32POINode(); } } return CCharAnimTime::Infinity(); } void CAnimData::MultiplyPlaybackRate(float mul) { x200_speedScale *= mul; } void CAnimData::SetPlaybackRate(float set) { x200_speedScale = set; } void CAnimData::SetRandomPlaybackRate(CRandom16& r) { for (size_t i = 0; i < x210_passedIntCount; ++i) { const CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::RandRate) { float tmp = (r.Next() % poi.GetValue()) / 100.f; if ((r.Next() % 100) < 50) x200_speedScale = 1.f + tmp; else x200_speedScale = 1.f - tmp; break; } } } void CAnimData::CalcPlaybackAlignmentParms(const CAnimPlaybackParms& parms, const std::shared_ptr& node) { zeus::CQuaternion orient; x1e8_alignRot = zeus::CQuaternion(); x220_27_ = false; if (parms.GetDeltaOrient() && parms.GetObjectXform()) { ResetPOILists(); x210_passedIntCount += u32(node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 64)); for (size_t i = 0; i < x210_passedIntCount; ++i) { const CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent && EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetRot) { SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f); orient = zeus::CQuaternion::slerp(zeus::CQuaternion(), *parms.GetDeltaOrient() * zeus::CQuaternion(parms.GetObjectXform()->buildMatrix3f().inverted()) * res.x8_deltas.xc_rotDelta.inverse(), 1.f / (60.f * poi.GetTime().GetSeconds())); x1e8_alignRot = orient; x220_27_ = true; } } } if (!x220_27_) { bool didAlign = false; bool didStart = false; zeus::CVector3f posStart, posAlign; CCharAnimTime timeStart, timeAlign; if (parms.GetTargetPos() && parms.GetObjectXform()) { ResetPOILists(); x210_passedIntCount += u32(node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 64)); for (size_t i = 0; i < x210_passedIntCount; ++i) { const CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent) { if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPosStart) { didStart = true; SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f); posStart = res.x8_deltas.x0_posDelta; timeStart = poi.GetTime(); if (parms.GetIsUseLocator()) posStart += GetLocatorTransform(poi.GetLocatorName(), &poi.GetTime()).origin; if (didAlign) break; } else if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPos) { didAlign = true; SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f); posAlign = res.x8_deltas.x0_posDelta; timeAlign = poi.GetTime(); if (parms.GetIsUseLocator()) posAlign += GetLocatorTransform(poi.GetLocatorName(), &poi.GetTime()).origin; if (didStart) break; } } } if (didAlign && didStart) { zeus::CVector3f scaleStart = *parms.GetObjectScale() * posStart; zeus::CVector3f scaleAlign = *parms.GetObjectScale() * posAlign; x1dc_alignPos = (parms.GetObjectXform()->inverse() * *parms.GetTargetPos() - scaleStart - (scaleAlign - scaleStart)) / *parms.GetObjectScale() * (1.f / (timeAlign.GetSeconds() - timeStart.GetSeconds())); x220_28_ = true; x220_26_aligningPos = false; } else { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } } else { bool didStart = false; bool didAlign = false; CCharAnimTime timeStart, timeAlign; zeus::CVector3f startPos; if (parms.GetTargetPos() && parms.GetObjectXform()) { ResetPOILists(); x210_passedIntCount += u32(node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 64)); for (size_t i = 0; i < x210_passedIntCount; ++i) { CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent) { if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPosStart) { didStart = true; timeStart = poi.GetTime(); if (didAlign) break; } else if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPos) { didAlign = true; timeAlign = poi.GetTime(); if (didStart) break; } } } if (didAlign && didStart) { CCharAnimTime frameInterval(1.f / 60.f); orient = zeus::CQuaternion(); x1e8_alignRot = zeus::CQuaternion(); x220_27_ = true; CCharAnimTime time; zeus::CVector3f pos; zeus::CQuaternion quat; bool foundStartPos = false; while (time < timeAlign) { SAdvancementResults res = node->VGetAdvancementResults(frameInterval, time); pos += quat.toTransform() * res.x8_deltas.x0_posDelta; quat *= (res.x8_deltas.xc_rotDelta * orient); if (!foundStartPos && time >= timeStart) { startPos = pos; foundStartPos = true; } time += frameInterval; } zeus::CVector3f scaleStart = startPos * *parms.GetObjectScale(); zeus::CVector3f scaleAlign = pos * *parms.GetObjectScale(); x1dc_alignPos = (parms.GetObjectXform()->inverse() * *parms.GetTargetPos() - scaleStart - (scaleAlign - scaleStart)) / *parms.GetObjectScale() * (1.f / (timeAlign.GetSeconds() - timeStart.GetSeconds())); x220_28_ = true; x220_26_aligningPos = false; } else { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } else { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } } zeus::CTransform CAnimData::GetLocatorTransform(CSegId id, const CCharAnimTime* time) const { if (id.IsInvalid()) { return {}; } if (time || !x220_31_poseCached) { const_cast(this)->RecalcPoseBuilder(time); const_cast(this)->x220_31_poseCached = time == nullptr; } zeus::CTransform ret; if (!x220_30_poseBuilt) { x2fc_poseBuilder.BuildTransform(id, ret); } else { ret.setRotation(x224_pose.GetRotation(id)); ret.origin = x224_pose.GetOffset(id); } return ret; } zeus::CTransform CAnimData::GetLocatorTransform(std::string_view name, const CCharAnimTime* time) const { return GetLocatorTransform(xcc_layoutData->GetSegIdFromString(name), time); } bool CAnimData::IsAnimTimeRemaining(float rem, std::string_view name) const { if (!x1f8_animRoot) return false; return x1f8_animRoot->VGetTimeRemaining().GetSeconds() >= rem; } float CAnimData::GetAnimTimeRemaining(std::string_view name) const { float rem = x1f8_animRoot->VGetTimeRemaining().GetSeconds(); if (x200_speedScale) return rem / x200_speedScale; return rem; } float CAnimData::GetAnimationDuration(int animIn) const { std::shared_ptr anim = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(animIn)); std::set prims; anim->GetUniquePrimitives(prims); SObjectTag tag{FOURCC('ANIM'), {}}; float durAccum = 0.f; for (const CPrimitive& prim : prims) { tag.id = prim.GetAnimResId(); TLockedToken animRes = xfc_animCtx->xc_store.GetObj(tag); CCharAnimTime dur; switch (animRes->GetFormat()) { case EAnimFormat::Uncompressed: default: { const CAnimSource& src = animRes->GetAsCAnimSource(); dur = src.GetDuration(); break; } case EAnimFormat::BitstreamCompressed: case EAnimFormat::BitstreamCompressed24: { const CFBStreamedCompression& src = animRes->GetAsCFBStreamedCompression(); dur = src.GetAnimationDuration(); break; } } durAccum += dur.GetSeconds(); } if (anim->GetType() == EMetaAnimType::Random) return durAccum / float(prims.size()); return durAccum; } std::shared_ptr CAnimData::GetAnimSysContext() const { return xfc_animCtx; } std::shared_ptr CAnimData::GetAnimationManager() const { return x100_animMgr; } void CAnimData::RecalcPoseBuilder(const CCharAnimTime* time) { if (!x1f8_animRoot) return; const CSegIdList& segIdList = GetCharLayoutInfo().GetSegIdList(); CSegStatementSet segSet; if (time) x1f8_animRoot->VGetSegStatementSet(segIdList, segSet, *time); else x1f8_animRoot->VGetSegStatementSet(segIdList, segSet); AddAdditiveSegData(segIdList, segSet); for (const CSegId& id : segIdList.GetList()) { if (id == 3) continue; CAnimPerSegmentData& segData = segSet[id]; if (segData.x1c_hasOffset) x2fc_poseBuilder.Insert(id, segData.x0_rotation, segData.x10_offset); else x2fc_poseBuilder.Insert(id, segData.x0_rotation); } } void CAnimData::RenderAuxiliary(const zeus::CFrustum& frustum) const { x120_particleDB.AddToRendererClipped(frustum); } void CAnimData::Render(CSkinnedModel& model, const CModelFlags& drawFlags, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals) { SetupRender(model, morphEffect, averagedNormals); DrawSkinnedModel(model, drawFlags); } void CAnimData::SetupRender(CSkinnedModel& model, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals) { //OPTICK_EVENT(); if (!x220_30_poseBuilt) { x2fc_poseBuilder.BuildNoScale(x224_pose); x220_30_poseBuilt = true; } PoseSkinnedModel(model, x224_pose, morphEffect, averagedNormals); } void CAnimData::DrawSkinnedModel(CSkinnedModel& model, const CModelFlags& flags) { CGX::SetChanCtrl(CGX::EChannelId::Channel0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, CGraphics::mLightActive, CGraphics::mLightActive.any() ? GX_DF_CLAMP : GX_DF_NONE, CGraphics::mLightActive.any() ? GX_AF_SPOT : GX_AF_NONE); model.Draw(flags); } void CAnimData::PreRender() { if (!x220_31_poseCached) { RecalcPoseBuilder(nullptr); x220_31_poseCached = true; x220_30_poseBuilt = false; } } void CAnimData::BuildPose() { if (!x220_31_poseCached) { RecalcPoseBuilder(nullptr); x220_31_poseCached = true; x220_30_poseBuilt = false; } if (!x220_30_poseBuilt) { x2fc_poseBuilder.BuildNoScale(x224_pose); x220_30_poseBuilt = true; } } void CAnimData::PrimitiveSetToTokenVector(const std::set& primSet, std::vector& tokensOut, bool preLock) { tokensOut.reserve(primSet.size()); SObjectTag tag{FOURCC('ANIM'), {}}; for (const CPrimitive& prim : primSet) { tag.id = prim.GetAnimResId(); tokensOut.push_back(g_SimplePool->GetObj(tag)); if (preLock) tokensOut.back().Lock(); } } void CAnimData::GetAnimationPrimitives(const CAnimPlaybackParms& parms, std::set& primsOut) const { std::shared_ptr animA = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(parms.GetAnimationId())); animA->GetUniquePrimitives(primsOut); if (parms.GetSecondAnimationId() != -1) { std::shared_ptr animB = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(parms.GetSecondAnimationId())); animB->GetUniquePrimitives(primsOut); } } void CAnimData::SetAnimation(const CAnimPlaybackParms& parms, bool noTrans) { if (parms.GetAnimationId() == x40c_playbackParms.GetAnimationId() || (parms.GetSecondAnimationId() == x40c_playbackParms.GetSecondAnimationId() && parms.GetSecondAnimationId() != -1) || (parms.GetBlendFactor() == x40c_playbackParms.GetBlendFactor() && parms.GetBlendFactor() != 1.f)) { if (x220_29_animationJustStarted) return; } x40c_playbackParms.SetAnimationId(parms.GetAnimationId()); x40c_playbackParms.SetSecondAnimationId(parms.GetSecondAnimationId()); x40c_playbackParms.SetBlendFactor(parms.GetBlendFactor()); x200_speedScale = 1.f; x208_defaultAnim = parms.GetAnimationId(); s32 animIdxA = xc_charInfo.GetAnimationIndex(parms.GetAnimationId()); ResetPOILists(); std::shared_ptr blendNode; if (parms.GetSecondAnimationId() != -1) { s32 animIdxB = xc_charInfo.GetAnimationIndex(parms.GetSecondAnimationId()); std::shared_ptr treeA = x100_animMgr->GetAnimationTree(animIdxA, CMetaAnimTreeBuildOrders::NoSpecialOrders()); std::shared_ptr treeB = x100_animMgr->GetAnimationTree(animIdxB, CMetaAnimTreeBuildOrders::NoSpecialOrders()); blendNode = std::make_shared(false, treeA, treeB, parms.GetBlendFactor(), CAnimTreeBlend::CreatePrimitiveName(treeA, treeB, parms.GetBlendFactor())); } else { blendNode = x100_animMgr->GetAnimationTree(animIdxA, CMetaAnimTreeBuildOrders::NoSpecialOrders()); } if (!noTrans && x1f8_animRoot) x1f8_animRoot = x1fc_transMgr->GetTransitionTree(x1f8_animRoot, blendNode); else x1f8_animRoot = blendNode; x220_24_animating = parms.GetIsPlayAnimation(); CalcPlaybackAlignmentParms(parms, blendNode); ResetPOILists(); x220_29_animationJustStarted = true; } SAdvancementDeltas CAnimData::DoAdvance(float dt, bool& suspendParticles, CRandom16& random, bool advTree) { suspendParticles = false; zeus::CVector3f offsetPre, offsetPost; zeus::CQuaternion quatPre, quatPost; ResetPOILists(); float scaleDt = dt * x200_speedScale; if (x2fc_poseBuilder.HasRoot()) { SAdvancementDeltas deltas = UpdateAdditiveAnims(scaleDt); offsetPre = deltas.x0_posDelta; quatPre = deltas.xc_rotDelta; } if (!x220_24_animating) { suspendParticles = true; return {}; } if (x220_29_animationJustStarted) { x220_29_animationJustStarted = false; suspendParticles = true; } if (advTree && x1f8_animRoot) { SetRandomPlaybackRate(random); CCharAnimTime time(scaleDt); if (x220_25_loop) { while (time.GreaterThanZero() && !time.EpsilonZero()) { x210_passedIntCount += u32(x1f8_animRoot->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 0)); x20c_passedBoolCount += u32( x1f8_animRoot->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0)); x214_passedParticleCount += u32(x1f8_animRoot->GetParticlePOIList(time, g_ParticlePOINodes.data(), 16, x214_passedParticleCount, 0)); x218_passedSoundCount += u32(x1f8_animRoot->GetSoundPOIList(time, g_SoundPOINodes.data(), 16, x218_passedSoundCount, 0)); AdvanceAnim(time, offsetPost, quatPost); } } else { CCharAnimTime remTime = x1f8_animRoot->VGetTimeRemaining(); while (!remTime.EpsilonZero() && !time.EpsilonZero()) { x210_passedIntCount += u32(x1f8_animRoot->GetInt32POIList(time, g_Int32POINodes.data(), g_Int32POINodes.size(), x210_passedIntCount, 0)); x20c_passedBoolCount += u32( x1f8_animRoot->GetBoolPOIList(time, g_BoolPOINodes.data(), g_BoolPOINodes.size(), x20c_passedBoolCount, 0)); x214_passedParticleCount += u32(x1f8_animRoot->GetParticlePOIList(time, g_ParticlePOINodes.data(), 16, x214_passedParticleCount, 0)); x218_passedSoundCount += u32(x1f8_animRoot->GetSoundPOIList(time, g_SoundPOINodes.data(), 16, x218_passedSoundCount, 0)); AdvanceAnim(time, offsetPost, quatPost); remTime = x1f8_animRoot->VGetTimeRemaining(); time = std::max(0.f, std::min(remTime.GetSeconds(), time.GetSeconds())); if (remTime.EpsilonZero()) { x220_24_animating = false; x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } } x220_31_poseCached = false; x220_30_poseBuilt = false; } return {offsetPost + offsetPre, quatPost * quatPre}; } SAdvancementDeltas CAnimData::Advance(float dt, const zeus::CVector3f& scale, CStateManager& stateMgr, TAreaId aid, bool advTree) { bool suspendParticles; SAdvancementDeltas deltas = DoAdvance(dt, suspendParticles, *stateMgr.GetActiveRandom(), advTree); if (suspendParticles) x120_particleDB.SuspendAllActiveEffects(stateMgr); for (size_t i = 0; i < x214_passedParticleCount; ++i) { const CParticlePOINode& node = g_ParticlePOINodes[i]; if (node.GetCharacterIndex() == -1 || node.GetCharacterIndex() == x204_charIdx) { x120_particleDB.AddParticleEffect(node.GetString(), node.GetFlags(), node.GetParticleData(), scale, stateMgr, aid, false, x21c_particleLightIdx); } } return deltas; } SAdvancementDeltas CAnimData::AdvanceIgnoreParticles(float dt, CRandom16& random, bool advTree) { bool suspendParticles; return DoAdvance(dt, suspendParticles, random, advTree); } void CAnimData::AdvanceAnim(CCharAnimTime& time, zeus::CVector3f& offset, zeus::CQuaternion& quat) { SAdvancementResults results; std::optional> simplified; if (x104_animDir == EAnimDir::Forward) { results = x1f8_animRoot->VAdvanceView(time); simplified = x1f8_animRoot->Simplified(); } if (simplified) x1f8_animRoot = CAnimTreeNode::Cast(std::move(*simplified)); if ((x220_28_ || x220_27_) && x210_passedIntCount > 0) { for (size_t i = 0; i < x210_passedIntCount; ++i) { const CInt32POINode& node = g_Int32POINodes[i]; if (node.GetPoiType() == EPOIType::UserEvent) { switch (EUserEventType(node.GetValue())) { case EUserEventType::AlignTargetPosStart: { x220_26_aligningPos = true; break; } case EUserEventType::AlignTargetPos: { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; break; } case EUserEventType::AlignTargetRot: { x1e8_alignRot = zeus::CQuaternion(); x220_27_ = false; break; } default: break; } } } } offset += results.x8_deltas.x0_posDelta; if (x220_26_aligningPos) offset += x1dc_alignPos * time.GetSeconds(); zeus::CQuaternion rot = results.x8_deltas.xc_rotDelta * x1e8_alignRot; quat = quat * rot; x1dc_alignPos = rot.transform(x1dc_alignPos); time = results.x0_remTime; } void CAnimData::SetXRayModel(const TLockedToken& model, const TLockedToken& skinRules) { xf4_xrayModel = std::make_shared(model, skinRules, xd8_modelData->GetLayoutInfo()); xf4_xrayModel->CalculateDefault(); } void CAnimData::SetInfraModel(const TLockedToken& model, const TLockedToken& skinRules) { xf8_infraModel = std::make_shared(model, skinRules, xd8_modelData->GetLayoutInfo()); xf4_xrayModel->CalculateDefault(); } void CAnimData::PoseSkinnedModel(CSkinnedModel& model, const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals) { model.Calculate(pose, morphEffect, averagedNormals, nullptr); } void CAnimData::AdvanceParticles(const zeus::CTransform& xf, float dt, const zeus::CVector3f& vec, CStateManager& stateMgr) { x120_particleDB.Update(dt, x224_pose, *xcc_layoutData, xf, vec, stateMgr); } float CAnimData::GetAverageVelocity(int animIn) const { std::shared_ptr anim = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(animIn)); std::set prims; anim->GetUniquePrimitives(prims); SObjectTag tag{FOURCC('ANIM'), {}}; float velAccum = 0.f; float durAccum = 0.f; for (const CPrimitive& prim : prims) { tag.id = prim.GetAnimResId(); TLockedToken animRes = xfc_animCtx->xc_store.GetObj(tag); CCharAnimTime dur; float avgVel; switch (animRes->GetFormat()) { case EAnimFormat::Uncompressed: default: { const CAnimSource& src = animRes->GetAsCAnimSource(); dur = src.GetDuration(); avgVel = src.GetAverageVelocity(); break; } case EAnimFormat::BitstreamCompressed: case EAnimFormat::BitstreamCompressed24: { const CFBStreamedCompression& src = animRes->GetAsCFBStreamedCompression(); dur = src.GetAnimationDuration(); avgVel = src.GetAverageVelocity(); break; } } velAccum += dur.GetSeconds() * avgVel; durAccum += dur.GetSeconds(); } if (durAccum > 0.f) return velAccum / durAccum; return 0.f; } void CAnimData::ResetPOILists() { x20c_passedBoolCount = 0; x210_passedIntCount = 0; x214_passedParticleCount = 0; x218_passedSoundCount = 0; } CSegId CAnimData::GetLocatorSegId(std::string_view name) const { return xcc_layoutData->GetSegIdFromString(name); } zeus::CAABox CAnimData::GetBoundingBox(const zeus::CTransform& xf) const { return GetBoundingBox().getTransformedAABox(xf); } zeus::CAABox CAnimData::GetBoundingBox() const { auto aabbList = xc_charInfo.GetAnimBBoxList(); if (aabbList.empty()) return x108_aabb; CAnimTreeEffectiveContribution contrib = x1f8_animRoot->GetContributionOfHighestInfluence(); auto search = rstl::binary_find( aabbList.cbegin(), aabbList.cend(), contrib.x4_name, [](const std::pair& other) -> const std::string& { return other.first; }); if (search == aabbList.cend()) return x108_aabb; return search->second; } void CAnimData::SubstituteModelData(const TCachedToken& model) { xd8_modelData = model; x108_aabb = {}; for (const auto& item : xd8_modelData->GetModel()->GetPositions()) { x108_aabb.accumulateBounds({item.x, item.y, item.z}); } } void CAnimData::SetParticleCEXTValue(std::string_view name, int idx, float value) { auto search = std::find_if(xc_charInfo.x98_effects.begin(), xc_charInfo.x98_effects.end(), [&name](const auto& v) { return v.first == name; }); if (search != xc_charInfo.x98_effects.end() && search->second.size()) x120_particleDB.SetCEXTValue(search->second.front().GetComponentName(), idx, value); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimData.hpp ================================================ #pragma once #include #include #include #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CAdditiveAnimPlayback.hpp" #include "Runtime/Character/CAnimPlaybackParms.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CCharacterFactory.hpp" #include "Runtime/Character/CCharacterInfo.hpp" #include "Runtime/Character/CHierarchyPoseBuilder.hpp" #include "Runtime/Character/CParticleDatabase.hpp" #include "Runtime/Character/CPoseAsTransforms.hpp" #include "Runtime/Character/IAnimReader.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" #include #include #include enum class EUserEventType { Projectile = 0, EggLay = 1, LoopedSoundStop = 2, AlignTargetPos = 3, AlignTargetRot = 4, ChangeMaterial = 5, Delete = 6, GenerateEnd = 7, DamageOn = 8, DamageOff = 9, AlignTargetPosStart = 10, DeGenerate = 11, Landing = 12, TakeOff = 13, FadeIn = 14, FadeOut = 15, ScreenShake = 16, BeginAction = 17, EndAction = 18, BecomeRagDoll = 19, IkLock = 20, IkRelease = 21, BreakLockOn = 22, BecomeShootThrough = 23, RemoveCollision = 24, ObjectPickUp = 25, ObjectDrop = 26, EventStart = 27, EventStop = 28, Activate = 29, Deactivate = 30, SoundPlay = 31, SoundStop = 32, EffectOn = 33, EffectOff = 34 }; namespace metaforce { class CAnimTreeNode; class CAnimationManager; class CBoolPOINode; class CCharAnimTime; class CCharLayoutInfo; class CInt32POINode; class CModel; class CSkinnedModelWithAvgNormals; class CParticlePOINode; class CPrimitive; class CRandom16; class CSegIdList; class CSegStatementSet; class CSkinRules; class CSoundPOINode; class CStateManager; class CTransitionManager; class CVertexMorphEffect; class IAnimReader; class IMetaAnim; struct CAnimSysContext; struct CModelFlags; struct SAdvancementDeltas; struct SAdvancementResults; class CAnimData { friend class CModelData; friend class CActor; friend class CPlayerGun; friend class CGrappleArm; friend class CWallCrawlerSwarm; public: enum class EAnimDir { Forward, Backward }; private: TLockedToken x0_charFactory; CCharacterInfo xc_charInfo; TLockedToken xcc_layoutData; TLockedToken xd8_modelData; TLockedToken xe4_iceModelData; std::shared_ptr xf4_xrayModel; std::shared_ptr xf8_infraModel; std::shared_ptr xfc_animCtx; std::shared_ptr x100_animMgr; EAnimDir x104_animDir = EAnimDir::Forward; zeus::CAABox x108_aabb; CParticleDatabase x120_particleDB; CAssetId x1d8_selfId; zeus::CVector3f x1dc_alignPos; zeus::CQuaternion x1e8_alignRot; std::shared_ptr x1f8_animRoot; std::shared_ptr x1fc_transMgr; float x200_speedScale = 1.f; s32 x204_charIdx; s32 x208_defaultAnim; u32 x20c_passedBoolCount = 0; u32 x210_passedIntCount = 0; u32 x214_passedParticleCount = 0; u32 x218_passedSoundCount = 0; s32 x21c_particleLightIdx = 0; bool x220_24_animating : 1 = false; bool x220_25_loop : 1 = false; bool x220_26_aligningPos : 1 = false; bool x220_27_ : 1 = false; bool x220_28_ : 1 = false; bool x220_29_animationJustStarted : 1 = false; bool x220_30_poseBuilt : 1 = false; bool x220_31_poseCached : 1 = false; CPoseAsTransforms x224_pose; CHierarchyPoseBuilder x2fc_poseBuilder; CAnimPlaybackParms x40c_playbackParms; rstl::reserved_vector, 8> x434_additiveAnims; static rstl::reserved_vector g_BoolPOINodes; static rstl::reserved_vector g_Int32POINodes; static rstl::reserved_vector g_ParticlePOINodes; static rstl::reserved_vector g_SoundPOINodes; static rstl::reserved_vector g_TransientInt32POINodes; public: CAnimData(CAssetId, const CCharacterInfo& character, int defaultAnim, int charIdx, bool loop, TLockedToken layout, TToken model, const std::optional>& iceModel, const std::weak_ptr& ctx, std::shared_ptr animMgr, std::shared_ptr transMgr, TLockedToken charFactory); void SetParticleEffectState(std::string_view effectName, bool active, CStateManager& mgr); void InitializeEffects(CStateManager& mgr, TAreaId aId, const zeus::CVector3f& scale); CAssetId GetEventResourceIdForAnimResourceId(CAssetId id) const; void AddAdditiveSegData(const CSegIdList& list, CSegStatementSet& stSet); static SAdvancementResults AdvanceAdditiveAnim(std::shared_ptr& anim, const CCharAnimTime& time); SAdvancementDeltas AdvanceAdditiveAnims(float dt); SAdvancementDeltas UpdateAdditiveAnims(float dt); bool IsAdditiveAnimation(s32 idx) const; bool IsAdditiveAnimationAdded(s32 idx) const; const std::shared_ptr& GetRootAnimationTree() const { return x1f8_animRoot; } const std::shared_ptr& GetAdditiveAnimationTree(s32 idx) const; bool IsAdditiveAnimationActive(s32 idx) const; void DelAdditiveAnimation(s32 idx); void AddAdditiveAnimation(s32 idx, float weight, bool active, bool fadeOut); float GetAdditiveAnimationWeight(s32 idx) const; std::shared_ptr GetAnimationManager(); const CCharacterInfo& GetCharacterInfo() const { return xc_charInfo; } const CCharLayoutInfo& GetCharLayoutInfo() const { return *xcc_layoutData.GetObj(); } void SetPhase(float ph); void Touch(CSkinnedModel& model, int shaderIdx) const; SAdvancementDeltas GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const; CCharAnimTime GetTimeOfUserEvent(EUserEventType type, const CCharAnimTime& time) const; void MultiplyPlaybackRate(float mul); void SetPlaybackRate(float set); void SetRandomPlaybackRate(CRandom16& r); void CalcPlaybackAlignmentParms(const CAnimPlaybackParms& parms, const std::shared_ptr& node); zeus::CTransform GetLocatorTransform(CSegId id, const CCharAnimTime* time) const; zeus::CTransform GetLocatorTransform(std::string_view name, const CCharAnimTime* time) const; bool IsAnimTimeRemaining(float rem, std::string_view name) const; float GetAnimTimeRemaining(std::string_view name) const; float GetAnimationDuration(int animIn) const; bool GetIsLoop() const { return x220_25_loop; } void EnableLooping(bool val) { x220_25_loop = val; x220_24_animating = true; } void EnableAnimation(bool val) { x220_24_animating = val; } bool IsAnimating() const { return x220_24_animating; } void SetAnimDir(EAnimDir dir) { x104_animDir = dir; } std::shared_ptr GetAnimSysContext() const; std::shared_ptr GetAnimationManager() const; void RecalcPoseBuilder(const CCharAnimTime* time); void RenderAuxiliary(const zeus::CFrustum& frustum) const; void Render(CSkinnedModel& model, const CModelFlags& drawFlags, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals); void SetupRender(CSkinnedModel& model, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals); static void DrawSkinnedModel(CSkinnedModel& model, const CModelFlags& flags); void PreRender(); void BuildPose(); const CPoseAsTransforms& GetPose() const { return x224_pose; } static void PrimitiveSetToTokenVector(const std::set& primSet, std::vector& tokensOut, bool preLock); void GetAnimationPrimitives(const CAnimPlaybackParms& parms, std::set& primsOut) const; void SetAnimation(const CAnimPlaybackParms& parms, bool noTrans); SAdvancementDeltas DoAdvance(float dt, bool& suspendParticles, CRandom16& random, bool advTree); SAdvancementDeltas Advance(float dt, const zeus::CVector3f& scale, CStateManager& stateMgr, TAreaId aid, bool advTree); SAdvancementDeltas AdvanceIgnoreParticles(float dt, CRandom16& random, bool advTree); void AdvanceAnim(CCharAnimTime& time, zeus::CVector3f& offset, zeus::CQuaternion& quat); void SetXRayModel(const TLockedToken& model, const TLockedToken& skinRules); std::shared_ptr GetXRayModel() const { return xf4_xrayModel; } void SetInfraModel(const TLockedToken& model, const TLockedToken& skinRules); std::shared_ptr GetInfraModel() const { return xf8_infraModel; } TLockedToken& GetModelData() { return xd8_modelData; } const TLockedToken& GetModelData() const { return xd8_modelData; } static void PoseSkinnedModel(CSkinnedModel& model, const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals); void AdvanceParticles(const zeus::CTransform& xf, float dt, const zeus::CVector3f&, CStateManager& stateMgr); float GetAverageVelocity(int animIn) const; void ResetPOILists(); CSegId GetLocatorSegId(std::string_view name) const; zeus::CAABox GetBoundingBox(const zeus::CTransform& xf) const; zeus::CAABox GetBoundingBox() const; void SubstituteModelData(const TCachedToken& model); static void FreeCache(); static void InitializeCache(); CHierarchyPoseBuilder& PoseBuilder() { return x2fc_poseBuilder; } const CHierarchyPoseBuilder& GetPoseBuilder() const { return x2fc_poseBuilder; } const CParticleDatabase& GetParticleDB() const { return x120_particleDB; } CParticleDatabase& GetParticleDB() { return x120_particleDB; } void SetParticleCEXTValue(std::string_view name, int idx, float value); float GetSpeedScale() const { return x200_speedScale; } u32 GetPassedBoolPOICount() const { return x20c_passedBoolCount; } u32 GetPassedIntPOICount() const { return x210_passedIntCount; } u32 GetPassedParticlePOICount() const { return x214_passedParticleCount; } u32 GetPassedSoundPOICount() const { return x218_passedSoundCount; } s32 GetCharacterIndex() const { return x204_charIdx; } u16 GetDefaultAnimation() const { return x208_defaultAnim; } TLockedToken& GetIceModel() { return xe4_iceModelData; } const TLockedToken& GetIceModel() const { return xe4_iceModelData; } void SetParticleLightIdx(s32 idx) { x21c_particleLightIdx = idx; } void MarkPoseDirty() { x220_30_poseBuilt = false; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimPOIData.cpp ================================================ #include "Runtime/Character/CAnimPOIData.hpp" #include "Runtime/CToken.hpp" namespace metaforce { CAnimPOIData::CAnimPOIData(CInputStream& in) : x0_version(in.ReadLong()) { u32 boolCount = in.ReadLong(); x4_boolNodes.reserve(boolCount); for (u32 i = 0; i < boolCount; ++i) x4_boolNodes.emplace_back(in); u32 int32Count = in.ReadLong(); x14_int32Nodes.reserve(int32Count); for (u32 i = 0; i < int32Count; ++i) x14_int32Nodes.emplace_back(in); u32 particleCount = in.ReadLong(); x24_particleNodes.reserve(particleCount); for (u32 i = 0; i < particleCount; ++i) x24_particleNodes.emplace_back(in); if (x0_version >= 2) { u32 soundCount = in.ReadLong(); x34_soundNodes.reserve(soundCount); for (u32 i = 0; i < soundCount; ++i) x34_soundNodes.emplace_back(in); } } CFactoryFnReturn AnimPOIDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimPOIData.hpp ================================================ #pragma once #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CSoundPOINode.hpp" namespace metaforce { class CAnimPOIData { u32 x0_version; std::vector x4_boolNodes; std::vector x14_int32Nodes; std::vector x24_particleNodes; std::vector x34_soundNodes; public: explicit CAnimPOIData(CInputStream& in); const std::vector& GetBoolPOIStream() const { return x4_boolNodes; } const std::vector& GetInt32POIStream() const { return x14_int32Nodes; } const std::vector& GetParticlePOIStream() const { return x24_particleNodes; } const std::vector& GetSoundPOIStream() const { return x34_soundNodes; } }; CFactoryFnReturn AnimPOIDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& parms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimPerSegmentData.hpp ================================================ #pragma once #include #include namespace metaforce { struct CAnimPerSegmentData { zeus::CQuaternion x0_rotation; zeus::CVector3f x10_offset; bool x1c_hasOffset = false; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimPlaybackParms.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include #include #include namespace metaforce { class CAnimPlaybackParms { s32 x0_animA = -1; s32 x4_animB = -1; float x8_blendWeight = 1.f; bool xc_animating = true; // s32 x10_ = 0; const zeus::CVector3f* x14_targetPos = nullptr; bool x18_useLocator = false; const zeus::CQuaternion* x1c_deltaOrient = nullptr; const zeus::CTransform* x20_objectXf = nullptr; const zeus::CVector3f* x24_objectScale = nullptr; public: constexpr CAnimPlaybackParms() = default; constexpr CAnimPlaybackParms(s32 animA, s32 animB, float blendWeight, bool animating) : x0_animA(animA), x4_animB(animB), x8_blendWeight(blendWeight), xc_animating(animating) {} constexpr CAnimPlaybackParms(s32 anim, const zeus::CQuaternion* deltaOrient, const zeus::CVector3f* targetPos, const zeus::CTransform* xf, const zeus::CVector3f* scale, bool useLocator) : x0_animA(anim) , x14_targetPos(targetPos) , x18_useLocator(useLocator) , x1c_deltaOrient(deltaOrient) , x20_objectXf(xf) , x24_objectScale(scale) {} constexpr const zeus::CTransform* GetObjectXform() const { return x20_objectXf; } constexpr const zeus::CQuaternion* GetDeltaOrient() const { return x1c_deltaOrient; } constexpr const zeus::CVector3f* GetTargetPos() const { return x14_targetPos; } constexpr bool GetIsUseLocator() const { return x18_useLocator; } constexpr const zeus::CVector3f* GetObjectScale() const { return x24_objectScale; } constexpr s32 GetAnimationId() const { return x0_animA; } constexpr s32 GetSecondAnimationId() const { return x4_animB; } constexpr float GetBlendFactor() const { return x8_blendWeight; } constexpr void SetAnimationId(s32 id) { x0_animA = id; } constexpr void SetSecondAnimationId(s32 id) { x4_animB = id; } constexpr void SetBlendFactor(float f) { x8_blendWeight = f; } constexpr bool GetIsPlayAnimation() const { return xc_animating; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimSource.cpp ================================================ #include "Runtime/Character/CAnimSource.hpp" #include #include "Runtime/Character/CAnimPOIData.hpp" #include "Runtime/Character/CSegId.hpp" #include "Runtime/Character/CSegIdList.hpp" #include "Runtime/Character/CSegStatementSet.hpp" namespace metaforce { static constexpr float ClampZeroToOne(float in) { return std::clamp(in, 0.0f, 1.0f); } u32 RotationAndOffsetStorage::DataSizeInBytes(u32 rotPerFrame, u32 transPerFrame, u32 frameCount) { return (transPerFrame * 12 + rotPerFrame * 16) * frameCount; } void RotationAndOffsetStorage::CopyRotationsAndOffsets(const std::vector& rots, const std::vector& offs, u32 frameCount, float* arrOut) { std::vector::const_iterator rit = rots.cbegin(); std::vector::const_iterator oit = offs.cbegin(); u32 rotsPerFrame = rots.size() / frameCount; u32 offsPerFrame = offs.size() / frameCount; for (u32 i = 0; i < frameCount; ++i) { for (u32 j = 0; j < rotsPerFrame; ++j) { const zeus::CQuaternion& rot = *rit++; arrOut[0] = rot.w(); arrOut[1] = rot.x(); arrOut[2] = rot.y(); arrOut[3] = rot.z(); arrOut += 4; } for (u32 j = 0; j < offsPerFrame; ++j) { const zeus::CVector3f& off = *oit++; arrOut[0] = off.x(); arrOut[1] = off.y(); arrOut[2] = off.z(); arrOut += 3; } } } std::unique_ptr RotationAndOffsetStorage::GetRotationsAndOffsets(const std::vector& rots, const std::vector& offs, u32 frameCount) { u32 size = DataSizeInBytes(rots.size() / frameCount, offs.size() / frameCount, frameCount); auto ret = std::make_unique((size / 4 + 1) * 4); CopyRotationsAndOffsets(rots, offs, frameCount, ret.get()); return ret; } RotationAndOffsetStorage::CRotationAndOffsetVectors::CRotationAndOffsetVectors(CInputStream& in) { const u32 quatCount = in.ReadLong(); x0_rotations.reserve(quatCount); for (u32 i = 0; i < quatCount; ++i) { x0_rotations.emplace_back() = in.Get(); } const u32 vecCount = in.ReadLong(); x10_offsets.reserve(vecCount); for (u32 i = 0; i < vecCount; ++i) { x10_offsets.emplace_back() = in.Get(); } } u32 RotationAndOffsetStorage::GetFrameSizeInBytes() const { return (x10_transPerFrame * 12 + xc_rotPerFrame * 16); } RotationAndOffsetStorage::RotationAndOffsetStorage(const CRotationAndOffsetVectors& vectors, u32 frameCount) { x0_storage = GetRotationsAndOffsets(vectors.x0_rotations, vectors.x10_offsets, frameCount); x8_frameCount = frameCount; xc_rotPerFrame = vectors.x0_rotations.size() / frameCount; x10_transPerFrame = vectors.x10_offsets.size() / frameCount; } static std::vector ReadIndexTable(CInputStream& in) { std::vector ret; u32 count = in.ReadLong(); ret.reserve(count); for (u32 i = 0; i < count; ++i) ret.push_back(in.ReadUint8()); return ret; } void CAnimSource::CalcAverageVelocity() { u8 rootIdx = x20_rotationChannels[3]; u8 rootTransIdx = x30_translationChannels[rootIdx]; float accum = 0.f; const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4; const u32 rotFloatsPerFrame = x40_data.xc_rotPerFrame * 4; for (u32 i = 1; i < x10_frameCount; ++i) { const float* frameDataA = &x40_data.x0_storage[(i - 1) * floatsPerFrame + rotFloatsPerFrame + rootTransIdx * 3]; const float* frameDataB = &x40_data.x0_storage[i * floatsPerFrame + rotFloatsPerFrame + rootTransIdx * 3]; zeus::CVector3f vecA(frameDataA[0], frameDataA[1], frameDataA[2]); zeus::CVector3f vecB(frameDataB[0], frameDataB[1], frameDataB[2]); float frameVel = (vecB - vecA).magnitude(); if (frameVel > 0.00001f) accum += frameVel; } x60_averageVelocity = accum / x0_duration.GetSeconds(); } CAnimSource::CAnimSource(CInputStream& in, IObjectStore& store) : x0_duration(in) , x8_interval(in) , x10_frameCount(in.ReadLong()) , x1c_rootBone(in) , x20_rotationChannels(ReadIndexTable(in)) , x30_translationChannels(ReadIndexTable(in)) , x40_data(RotationAndOffsetStorage::CRotationAndOffsetVectors(in), x10_frameCount) , x54_evntId(in) { if (x54_evntId.IsValid()) { x58_evntData = store.GetObj({SBIG('EVNT'), x54_evntId}); x58_evntData.GetObj(); } CalcAverageVelocity(); } void CAnimSource::GetSegStatementSet(const CSegIdList& list, CSegStatementSet& set, const CCharAnimTime& time) const { const auto frameIdx = u32(time / x8_interval); const auto nextFrameIdx = (frameIdx + 1) % x10_frameCount; float remTime = time.GetSeconds() - frameIdx * x8_interval.GetSeconds(); if (std::fabs(remTime) < 0.00001f) { remTime = 0.f; } const float t = ClampZeroToOne(remTime / x8_interval.GetSeconds()); const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4; const u32 rotFloatsPerFrame = x40_data.xc_rotPerFrame * 4; for (const CSegId& id : list.GetList()) { const u8 rotIdx = x20_rotationChannels[id]; if (rotIdx != 0xff) { const float* frameDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotIdx * 4]; const float* frameDataB = &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotIdx * 4]; const zeus::CQuaternion quatA(frameDataA[0], frameDataA[1], frameDataA[2], frameDataA[3]); const zeus::CQuaternion quatB(frameDataB[0], frameDataB[1], frameDataB[2], frameDataB[3]); set[id].x0_rotation = zeus::CQuaternion::slerp(quatA, quatB, t); const u8 transIdx = x30_translationChannels[rotIdx]; if (transIdx != 0xff) { const float* frameVecDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3]; const float* frameVecDataB = &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3]; const zeus::CVector3f vecA(frameVecDataA[0], frameVecDataA[1], frameVecDataA[2]); const zeus::CVector3f vecB(frameVecDataB[0], frameVecDataB[1], frameVecDataB[2]); set[id].x10_offset = zeus::CVector3f::lerp(vecA, vecB, t); set[id].x1c_hasOffset = true; } } } } const std::vector& CAnimSource::GetSoundPOIStream() const { return x58_evntData->GetSoundPOIStream(); } const std::vector& CAnimSource::GetParticlePOIStream() const { return x58_evntData->GetParticlePOIStream(); } const std::vector& CAnimSource::GetInt32POIStream() const { return x58_evntData->GetInt32POIStream(); } const std::vector& CAnimSource::GetBoolPOIStream() const { return x58_evntData->GetBoolPOIStream(); } zeus::CQuaternion CAnimSource::GetRotation(const CSegId& seg, const CCharAnimTime& time) const { u8 rotIdx = x20_rotationChannels[seg]; if (rotIdx != 0xff) { const auto frameIdx = u32(time / x8_interval); const auto nextFrameIdx = (frameIdx + 1) % x10_frameCount; float remTime = time.GetSeconds() - frameIdx * x8_interval.GetSeconds(); if (std::fabs(remTime) < 0.00001f) remTime = 0.f; float t = ClampZeroToOne(remTime / x8_interval.GetSeconds()); const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4; const float* frameDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotIdx * 4]; const float* frameDataB = &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotIdx * 4]; zeus::CQuaternion quatA(frameDataA[0], frameDataA[1], frameDataA[2], frameDataA[3]); zeus::CQuaternion quatB(frameDataB[0], frameDataB[1], frameDataB[2], frameDataB[3]); return zeus::CQuaternion::slerp(quatA, quatB, t); } else { return {}; } } zeus::CVector3f CAnimSource::GetOffset(const CSegId& seg, const CCharAnimTime& time) const { u8 rotIdx = x20_rotationChannels[seg]; if (rotIdx != 0xff) { u8 transIdx = x30_translationChannels[rotIdx]; if (transIdx == 0xff) return {}; const auto frameIdx = u32(time / x8_interval); const auto nextFrameIdx = (frameIdx + 1) % x10_frameCount; float remTime = time.GetSeconds() - frameIdx * x8_interval.GetSeconds(); if (std::fabs(remTime) < 0.00001f) remTime = 0.f; float t = ClampZeroToOne(remTime / x8_interval.GetSeconds()); const u32 floatsPerFrame = x40_data.x10_transPerFrame * 3 + x40_data.xc_rotPerFrame * 4; const u32 rotFloatsPerFrame = x40_data.xc_rotPerFrame * 4; const float* frameDataA = &x40_data.x0_storage[frameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3]; const float* frameDataB = &x40_data.x0_storage[nextFrameIdx * floatsPerFrame + rotFloatsPerFrame + transIdx * 3]; zeus::CVector3f vecA(frameDataA[0], frameDataA[1], frameDataA[2]); zeus::CVector3f vecB(frameDataB[0], frameDataB[1], frameDataB[2]); return zeus::CVector3f::lerp(vecA, vecB, t); } else { return {}; } } bool CAnimSource::HasOffset(const CSegId& seg) const { u8 rotIdx = x20_rotationChannels[seg]; if (rotIdx == 0xff) return false; u8 transIdx = x30_translationChannels[rotIdx]; return transIdx != 0xff; } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimSource.hpp ================================================ #pragma once #include #include #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CCharAnimTime.hpp" #include "Runtime/Character/CSegId.hpp" #include #include namespace metaforce { class CAnimPOIData; class CBoolPOINode; class CInt32POINode; class CParticlePOINode; class CSegId; class CSegIdList; class CSegStatementSet; class CSoundPOINode; class IObjectStore; class RotationAndOffsetStorage { friend class CAnimSource; std::unique_ptr x0_storage; u32 x8_frameCount; u32 xc_rotPerFrame; u32 x10_transPerFrame; std::unique_ptr GetRotationsAndOffsets(const std::vector& rots, const std::vector& offs, u32 frameCount); static void CopyRotationsAndOffsets(const std::vector& rots, const std::vector& offs, u32 frameCount, float*); static u32 DataSizeInBytes(u32 rotPerFrame, u32 transPerFrame, u32 frameCount); public: struct CRotationAndOffsetVectors { std::vector x0_rotations; std::vector x10_offsets; explicit CRotationAndOffsetVectors(CInputStream& in); }; u32 GetFrameSizeInBytes() const; RotationAndOffsetStorage(const CRotationAndOffsetVectors& vectors, u32 frameCount); }; class CAnimSource { friend class CAnimSourceInfo; CCharAnimTime x0_duration; CCharAnimTime x8_interval; u32 x10_frameCount; CSegId x1c_rootBone; std::vector x20_rotationChannels; std::vector x30_translationChannels; RotationAndOffsetStorage x40_data; CAssetId x54_evntId; TCachedToken x58_evntData; float x60_averageVelocity; void CalcAverageVelocity(); public: explicit CAnimSource(CInputStream& in, IObjectStore& store); void GetSegStatementSet(const CSegIdList& list, CSegStatementSet& set, const CCharAnimTime& time) const; const std::vector& GetSoundPOIStream() const; const std::vector& GetParticlePOIStream() const; const std::vector& GetInt32POIStream() const; const std::vector& GetBoolPOIStream() const; const TCachedToken& GetPOIData() const { return x58_evntData; } float GetAverageVelocity() const { return x60_averageVelocity; } zeus::CQuaternion GetRotation(const CSegId& seg, const CCharAnimTime& time) const; zeus::CVector3f GetOffset(const CSegId& seg, const CCharAnimTime& time) const; bool HasOffset(const CSegId& seg) const; const CCharAnimTime& GetDuration() const { return x0_duration; } const CSegId& GetRootBoneId() const { return x1c_rootBone; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimSourceReader.cpp ================================================ #include "Runtime/Character/CAnimSourceReader.hpp" #include #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CFBStreamedAnimReader.hpp" #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CSoundPOINode.hpp" namespace metaforce { CAnimSourceInfo::CAnimSourceInfo(TSubAnimTypeToken token) : x4_token(std::move(token)) {} bool CAnimSourceInfo::HasPOIData() const { return x4_token->x58_evntData.HasReference(); } const std::vector& CAnimSourceInfo::GetBoolPOIStream() const { return x4_token->GetBoolPOIStream(); } const std::vector& CAnimSourceInfo::GetInt32POIStream() const { return x4_token->GetInt32POIStream(); } const std::vector& CAnimSourceInfo::GetParticlePOIStream() const { return x4_token->GetParticlePOIStream(); } const std::vector& CAnimSourceInfo::GetSoundPOIStream() const { return x4_token->GetSoundPOIStream(); } CCharAnimTime CAnimSourceInfo::GetAnimationDuration() const { return x4_token->GetDuration(); } std::set> CAnimSourceReaderBase::GetUniqueParticlePOIs() const { const std::vector& particleNodes = x4_sourceInfo->GetParticlePOIStream(); std::set> ret; for (const CParticlePOINode& node : particleNodes) { if (node.GetUnique()) { ret.emplace(node.GetString(), node.GetIndex()); } } return ret; } std::set> CAnimSourceReaderBase::GetUniqueInt32POIs() const { const std::vector& int32Nodes = x4_sourceInfo->GetInt32POIStream(); std::set> ret; for (const CInt32POINode& node : int32Nodes) { if (node.GetUnique()) { ret.emplace(node.GetString(), node.GetIndex()); } } return ret; } std::set> CAnimSourceReaderBase::GetUniqueBoolPOIs() const { const std::vector& boolNodes = x4_sourceInfo->GetBoolPOIStream(); std::set> ret; for (const CBoolPOINode& node : boolNodes) { if (node.GetUnique()) { ret.emplace(node.GetString(), node.GetIndex()); } } return ret; } void CAnimSourceReaderBase::PostConstruct(const CCharAnimTime& time) { x14_passedBoolCount = 0; x18_passedIntCount = 0; x1c_passedParticleCount = 0; x20_passedSoundCount = 0; if (x4_sourceInfo->HasPOIData()) { std::set> boolPOIs = GetUniqueBoolPOIs(); std::set> int32POIs = GetUniqueInt32POIs(); std::set> particlePOIs = GetUniqueParticlePOIs(); x24_boolStates.resize(boolPOIs.size()); x34_int32States.resize(int32POIs.size()); x44_particleStates.resize(particlePOIs.size()); for (const auto& poi : boolPOIs) x24_boolStates[poi.second] = std::make_pair(poi.first, false); for (const auto& poi : int32POIs) x34_int32States[poi.second] = std::make_pair(poi.first, 0); for (const auto& poi : particlePOIs) x44_particleStates[poi.second] = std::make_pair(poi.first, CParticleData::EParentedMode::Initial); } CCharAnimTime tmpTime = time; if (tmpTime.GreaterThanZero()) { while (tmpTime.GreaterThanZero()) { SAdvancementResults res = VAdvanceView(tmpTime); tmpTime = res.x0_remTime; } } else if (x4_sourceInfo->HasPOIData()) { UpdatePOIStates(); if (!time.GreaterThanZero()) { x14_passedBoolCount = 0; x18_passedIntCount = 0; x1c_passedParticleCount = 0; x20_passedSoundCount = 0; } } } void CAnimSourceReaderBase::UpdatePOIStates() { const std::vector& boolNodes = x4_sourceInfo->GetBoolPOIStream(); const std::vector& int32Nodes = x4_sourceInfo->GetInt32POIStream(); const std::vector& particleNodes = x4_sourceInfo->GetParticlePOIStream(); const std::vector& soundNodes = x4_sourceInfo->GetSoundPOIStream(); while (x14_passedBoolCount < boolNodes.size() && boolNodes[x14_passedBoolCount].GetTime() <= xc_curTime) { const auto& node = boolNodes[x14_passedBoolCount]; if (node.GetIndex() >= 0) { x24_boolStates[node.GetIndex()].second = node.GetValue(); } ++x14_passedBoolCount; } while (x18_passedIntCount < int32Nodes.size() && int32Nodes[x18_passedIntCount].GetTime() <= xc_curTime) { const auto& node = int32Nodes[x18_passedIntCount]; if (node.GetIndex() >= 0) { x34_int32States[node.GetIndex()].second = node.GetValue(); } ++x18_passedIntCount; } while (x1c_passedParticleCount < particleNodes.size() && particleNodes[x1c_passedParticleCount].GetTime() <= xc_curTime) { const auto& node = particleNodes[x1c_passedParticleCount]; if (node.GetIndex() >= 0) { x44_particleStates[node.GetIndex()].second = node.GetParticleData().GetParentedMode(); } ++x1c_passedParticleCount; } while (x20_passedSoundCount < soundNodes.size() && soundNodes[x20_passedSoundCount].GetTime() <= xc_curTime) { ++x20_passedSoundCount; } } size_t CAnimSourceReaderBase::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (x4_sourceInfo->HasPOIData()) { const std::vector& boolNodes = x4_sourceInfo->GetBoolPOIStream(); return _getPOIList(time, listOut, capacity, iterator, unk, boolNodes, xc_curTime, *x4_sourceInfo, x14_passedBoolCount); } return 0; } size_t CAnimSourceReaderBase::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (x4_sourceInfo->HasPOIData()) { const std::vector& int32Nodes = x4_sourceInfo->GetInt32POIStream(); return _getPOIList(time, listOut, capacity, iterator, unk, int32Nodes, xc_curTime, *x4_sourceInfo, x18_passedIntCount); } return 0; } size_t CAnimSourceReaderBase::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (x4_sourceInfo->HasPOIData()) { const std::vector& particleNodes = x4_sourceInfo->GetParticlePOIStream(); return _getPOIList(time, listOut, capacity, iterator, unk, particleNodes, xc_curTime, *x4_sourceInfo, x1c_passedParticleCount); } return 0; } size_t CAnimSourceReaderBase::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (x4_sourceInfo->HasPOIData()) { const std::vector& soundNodes = x4_sourceInfo->GetSoundPOIStream(); return _getPOIList(time, listOut, capacity, iterator, unk, soundNodes, xc_curTime, *x4_sourceInfo, x20_passedSoundCount); } return 0; } bool CAnimSourceReaderBase::VGetBoolPOIState(std::string_view name) const { const auto iter = std::find_if(x24_boolStates.cbegin(), x24_boolStates.cend(), [name](const auto& entry) { return entry.first == name; }); if (iter == x24_boolStates.cend()) { return false; } return iter->second; } s32 CAnimSourceReaderBase::VGetInt32POIState(std::string_view name) const { const auto iter = std::find_if(x34_int32States.cbegin(), x34_int32States.cend(), [name](const auto& entry) { return entry.first == name; }); if (iter == x34_int32States.cend()) { return 0; } return iter->second; } CParticleData::EParentedMode CAnimSourceReaderBase::VGetParticlePOIState(std::string_view name) const { const auto iter = std::find_if(x44_particleStates.cbegin(), x44_particleStates.cend(), [name](const auto& entry) { return entry.first == name; }); if (iter == x44_particleStates.cend()) { return CParticleData::EParentedMode::Initial; } return iter->second; } CAnimSourceReaderBase::CAnimSourceReaderBase(std::unique_ptr&& sourceInfo, const CCharAnimTime& time) : x4_sourceInfo(std::move(sourceInfo)), xc_curTime(time) {} CAnimSourceReaderBase::CAnimSourceReaderBase(std::unique_ptr&& sourceInfo, const CAnimSourceReaderBase& other) : x4_sourceInfo(std::move(sourceInfo)) , xc_curTime(other.xc_curTime) , x14_passedBoolCount(other.x14_passedBoolCount) , x18_passedIntCount(other.x18_passedIntCount) , x1c_passedParticleCount(other.x1c_passedParticleCount) , x20_passedSoundCount(other.x20_passedSoundCount) , x24_boolStates(other.x24_boolStates) , x34_int32States(other.x34_int32States) , x44_particleStates(other.x44_particleStates) {} SAdvancementResults CAnimSourceReader::VGetAdvancementResults(const CCharAnimTime& dt, const CCharAnimTime& startOff) const { SAdvancementResults ret; CCharAnimTime accum = xc_curTime + startOff; if (xc_curTime + startOff >= x54_source->GetDuration()) { ret.x0_remTime = dt; return ret; } else if (dt.EqualsZero()) { return ret; } else { CCharAnimTime prevTime = accum; accum += dt; CCharAnimTime remTime; if (accum > x54_source->GetDuration()) { remTime = accum - x54_source->GetDuration(); accum = x54_source->GetDuration(); } zeus::CQuaternion ra = x54_source->GetRotation(3, prevTime).inverse(); zeus::CQuaternion rb = x54_source->GetRotation(3, accum); ret.x0_remTime = remTime; ret.x8_deltas.xc_rotDelta = rb * ra; if (x54_source->HasOffset(3)) { zeus::CVector3f ta = x54_source->GetOffset(3, prevTime); zeus::CVector3f tb = x54_source->GetOffset(3, accum); ret.x8_deltas.x0_posDelta = zeus::CMatrix3f(rb.inverse()) * (tb - ta); } return ret; } } void CAnimSourceReader::VSetPhase(float phase) { xc_curTime = phase * x54_source->GetDuration().GetSeconds(); if (x54_source->GetPOIData()) { UpdatePOIStates(); if (!xc_curTime.GreaterThanZero()) { x14_passedBoolCount = 0; x18_passedIntCount = 0; x1c_passedParticleCount = 0; x20_passedSoundCount = 0; } } } SAdvancementResults CAnimSourceReader::VReverseView(const CCharAnimTime& dt) { SAdvancementResults ret; if (xc_curTime.EqualsZero()) { xc_curTime = x54_source->GetDuration(); ret.x0_remTime = dt; return ret; } else if (dt.EqualsZero()) { return ret; } else { CCharAnimTime prevTime = xc_curTime; xc_curTime -= dt; CCharAnimTime remTime; if (xc_curTime < CCharAnimTime()) { remTime = CCharAnimTime() - xc_curTime; xc_curTime = CCharAnimTime(); } if (x54_source->GetPOIData()) UpdatePOIStates(); zeus::CQuaternion ra = x54_source->GetRotation(3, prevTime).inverse(); zeus::CQuaternion rb = x54_source->GetRotation(3, xc_curTime); ret.x0_remTime = remTime; ret.x8_deltas.xc_rotDelta = rb * ra; if (x54_source->HasOffset(3)) { zeus::CVector3f ta = x54_source->GetOffset(3, prevTime); zeus::CVector3f tb = x54_source->GetOffset(3, xc_curTime); ret.x8_deltas.x0_posDelta = zeus::CMatrix3f(rb.inverse()) * (tb - ta); } return ret; } } std::unique_ptr CAnimSourceReader::VClone() const { return std::make_unique(*this); } void CAnimSourceReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const { x54_source->GetSegStatementSet(list, setOut, xc_curTime); } void CAnimSourceReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const { x54_source->GetSegStatementSet(list, setOut, time); } SAdvancementResults CAnimSourceReader::VAdvanceView(const CCharAnimTime& dt) { SAdvancementResults ret; if (xc_curTime == x54_source->GetDuration()) { xc_curTime = {}; x14_passedBoolCount = 0; x18_passedIntCount = 0; x1c_passedParticleCount = 0; x20_passedSoundCount = 0; ret.x0_remTime = dt; return ret; } else if (dt.EqualsZero()) { return ret; } else { CCharAnimTime prevTime = xc_curTime; xc_curTime += dt; CCharAnimTime remTime; if (xc_curTime > x54_source->GetDuration()) { remTime = xc_curTime - x54_source->GetDuration(); xc_curTime = x54_source->GetDuration(); } if (x54_source->GetPOIData()) UpdatePOIStates(); zeus::CQuaternion ra = x54_source->GetRotation(3, prevTime).inverse(); zeus::CQuaternion rb = x54_source->GetRotation(3, xc_curTime); ret.x0_remTime = remTime; ret.x8_deltas.xc_rotDelta = rb * ra; if (x54_source->HasOffset(3)) { zeus::CVector3f ta = x54_source->GetOffset(3, prevTime); zeus::CVector3f tb = x54_source->GetOffset(3, xc_curTime); ret.x8_deltas.x0_posDelta = zeus::CMatrix3f(rb.inverse()) * (tb - ta); } return ret; } } CCharAnimTime CAnimSourceReader::VGetTimeRemaining() const { return x54_source->GetDuration() - xc_curTime; } CSteadyStateAnimInfo CAnimSourceReader::VGetSteadyStateAnimInfo() const { return x64_steadyStateInfo; } bool CAnimSourceReader::VHasOffset(const CSegId& seg) const { return x54_source->HasOffset(seg); } zeus::CVector3f CAnimSourceReader::VGetOffset(const CSegId& seg) const { return x54_source->GetOffset(seg, xc_curTime); } zeus::CVector3f CAnimSourceReader::VGetOffset(const CSegId& seg, const CCharAnimTime& time) const { return x54_source->GetOffset(seg, time); } zeus::CQuaternion CAnimSourceReader::VGetRotation(const CSegId& seg) const { return x54_source->GetRotation(seg, xc_curTime); } CAnimSourceReader::CAnimSourceReader(const TSubAnimTypeToken& source, const CCharAnimTime& time) : CAnimSourceReaderBase(std::make_unique(source), {}) , x54_source(source) , x64_steadyStateInfo(false, source->GetDuration(), source->GetOffset(source->GetRootBoneId(), time)) { PostConstruct(time); } CAnimSourceReader::CAnimSourceReader(const CAnimSourceReader& other) : CAnimSourceReaderBase(std::make_unique(other.x54_source), other) , x54_source(other.x54_source) , x64_steadyStateInfo(other.x64_steadyStateInfo) {} } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimSourceReader.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/CToken.hpp" #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CAnimSource.hpp" #include "Runtime/Character/CParticleData.hpp" #include "Runtime/Character/IAnimReader.hpp" namespace metaforce { class IAnimSourceInfo { public: virtual ~IAnimSourceInfo() = default; virtual bool HasPOIData() const = 0; virtual const std::vector& GetBoolPOIStream() const = 0; virtual const std::vector& GetInt32POIStream() const = 0; virtual const std::vector& GetParticlePOIStream() const = 0; virtual const std::vector& GetSoundPOIStream() const = 0; virtual CCharAnimTime GetAnimationDuration() const = 0; }; class CAnimSourceInfo : public IAnimSourceInfo { TSubAnimTypeToken x4_token; public: explicit CAnimSourceInfo(TSubAnimTypeToken token); bool HasPOIData() const override; const std::vector& GetBoolPOIStream() const override; const std::vector& GetInt32POIStream() const override; const std::vector& GetParticlePOIStream() const override; const std::vector& GetSoundPOIStream() const override; CCharAnimTime GetAnimationDuration() const override; }; class CAnimSourceReaderBase : public IAnimReader { protected: std::unique_ptr x4_sourceInfo; CCharAnimTime xc_curTime; u32 x14_passedBoolCount = 0; u32 x18_passedIntCount = 0; u32 x1c_passedParticleCount = 0; u32 x20_passedSoundCount = 0; std::vector> x24_boolStates; std::vector> x34_int32States; std::vector> x44_particleStates; std::set> GetUniqueParticlePOIs() const; std::set> GetUniqueInt32POIs() const; std::set> GetUniqueBoolPOIs() const; protected: void PostConstruct(const CCharAnimTime& time); void UpdatePOIStates(); CAnimSourceReaderBase(std::unique_ptr&& sourceInfo, const CAnimSourceReaderBase& other); public: CAnimSourceReaderBase(std::unique_ptr&& sourceInfo, const CCharAnimTime& time); size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const override; bool VGetBoolPOIState(std::string_view name) const override; s32 VGetInt32POIState(std::string_view name) const override; CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override; using IAnimReader::VGetOffset; virtual zeus::CVector3f VGetOffset(const CSegId& seg, const CCharAnimTime& b) const = 0; virtual bool VSupportsReverseView() const = 0; virtual SAdvancementResults VReverseView(const CCharAnimTime& time) = 0; const CCharAnimTime& GetCurTime() const { return xc_curTime; } }; class CAnimSourceReader : public CAnimSourceReaderBase { TSubAnimTypeToken x54_source; CSteadyStateAnimInfo x64_steadyStateInfo; public: CAnimSourceReader(const TSubAnimTypeToken& source, const CCharAnimTime& time); CAnimSourceReader(const CAnimSourceReader& other); SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override; bool VSupportsReverseView() const override { return true; } void VSetPhase(float) override; SAdvancementResults VReverseView(const CCharAnimTime& time) override; std::unique_ptr VClone() const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override; SAdvancementResults VAdvanceView(const CCharAnimTime& a) override; CCharAnimTime VGetTimeRemaining() const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; bool VHasOffset(const CSegId& seg) const override; zeus::CVector3f VGetOffset(const CSegId& seg) const override; zeus::CVector3f VGetOffset(const CSegId& seg, const CCharAnimTime& time) const override; zeus::CQuaternion VGetRotation(const CSegId& seg) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimSysContext.hpp ================================================ #pragma once #include #include "Runtime/CRandom16.hpp" #include "Runtime/CToken.hpp" #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CTransitionDatabaseGame.hpp" namespace metaforce { class CSimplePool; struct CAnimSysContext { TToken x0_transDB; std::shared_ptr x8_random; CSimplePool& xc_store; CAnimSysContext(TToken transDB, u32 randomSeed, CSimplePool& store) : x0_transDB(std::move(transDB)), x8_random(std::make_shared(randomSeed)), xc_store(store) {} }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeAnimReaderContainer.cpp ================================================ #include "Runtime/Character/CAnimTreeAnimReaderContainer.hpp" #include "Runtime/Character/CFBStreamedAnimReader.hpp" namespace metaforce { CAnimTreeAnimReaderContainer::CAnimTreeAnimReaderContainer(std::string_view name, std::shared_ptr reader, u32 dbIdx) : CAnimTreeNode(name), x14_reader(std::move(reader)), x1c_animDbIdx(dbIdx) {} u32 CAnimTreeAnimReaderContainer::Depth() const { return 1; } CAnimTreeEffectiveContribution CAnimTreeAnimReaderContainer::VGetContributionOfHighestInfluence() const { return {1.f, x4_name, VGetSteadyStateAnimInfo(), VGetTimeRemaining(), x1c_animDbIdx}; } u32 CAnimTreeAnimReaderContainer::VGetNumChildren() const { return 0; } std::shared_ptr CAnimTreeAnimReaderContainer::VGetBestUnblendedChild() const { return {}; } SAdvancementResults CAnimTreeAnimReaderContainer::VAdvanceView(const CCharAnimTime& dt) { return x14_reader->VAdvanceView(dt); } CCharAnimTime CAnimTreeAnimReaderContainer::VGetTimeRemaining() const { return x14_reader->VGetTimeRemaining(); } CSteadyStateAnimInfo CAnimTreeAnimReaderContainer::VGetSteadyStateAnimInfo() const { return x14_reader->VGetSteadyStateAnimInfo(); } bool CAnimTreeAnimReaderContainer::VHasOffset(const CSegId& seg) const { return x14_reader->VHasOffset(seg); } zeus::CVector3f CAnimTreeAnimReaderContainer::VGetOffset(const CSegId& seg) const { return x14_reader->VGetOffset(seg); } zeus::CQuaternion CAnimTreeAnimReaderContainer::VGetRotation(const CSegId& seg) const { return x14_reader->VGetRotation(seg); } size_t CAnimTreeAnimReaderContainer::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_reader->GetBoolPOIList(time, listOut, capacity, iterator, unk); } size_t CAnimTreeAnimReaderContainer::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_reader->GetInt32POIList(time, listOut, capacity, iterator, unk); } size_t CAnimTreeAnimReaderContainer::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_reader->GetParticlePOIList(time, listOut, capacity, iterator, unk); } size_t CAnimTreeAnimReaderContainer::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_reader->GetSoundPOIList(time, listOut, capacity, iterator, unk); } bool CAnimTreeAnimReaderContainer::VGetBoolPOIState(std::string_view name) const { return x14_reader->VGetBoolPOIState(name); } s32 CAnimTreeAnimReaderContainer::VGetInt32POIState(std::string_view name) const { return x14_reader->VGetInt32POIState(name); } CParticleData::EParentedMode CAnimTreeAnimReaderContainer::VGetParticlePOIState(std::string_view name) const { return x14_reader->VGetParticlePOIState(name); } void CAnimTreeAnimReaderContainer::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const { return x14_reader->VGetSegStatementSet(list, setOut); } void CAnimTreeAnimReaderContainer::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const { return x14_reader->VGetSegStatementSet(list, setOut, time); } std::unique_ptr CAnimTreeAnimReaderContainer::VClone() const { return std::make_unique(x4_name, x14_reader->Clone(), x1c_animDbIdx); } std::optional> CAnimTreeAnimReaderContainer::VSimplified() { return {}; } void CAnimTreeAnimReaderContainer::VSetPhase(float ph) { x14_reader->VSetPhase(ph); } SAdvancementResults CAnimTreeAnimReaderContainer::VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const { return x14_reader->VGetAdvancementResults(a, b); } void CAnimTreeAnimReaderContainer::VGetWeightedReaders( rstl::reserved_vector>, 16>& out, float w) const { out.emplace_back(std::make_pair(w, x14_reader)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeAnimReaderContainer.hpp ================================================ #pragma once #include #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" namespace metaforce { class CAnimTreeAnimReaderContainer : public CAnimTreeNode { std::shared_ptr x14_reader; u32 x1c_animDbIdx; public: CAnimTreeAnimReaderContainer(std::string_view name, std::shared_ptr reader, u32 animDbIdx); u32 Depth() const override; CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override; u32 VGetNumChildren() const override; std::shared_ptr VGetBestUnblendedChild() const override; void VGetWeightedReaders(rstl::reserved_vector>, 16>& out, float w) const override; SAdvancementResults VAdvanceView(const CCharAnimTime& a) override; CCharAnimTime VGetTimeRemaining() const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; bool VHasOffset(const CSegId& seg) const override; zeus::CVector3f VGetOffset(const CSegId& seg) const override; zeus::CQuaternion VGetRotation(const CSegId& seg) const override; size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const override; bool VGetBoolPOIState(std::string_view name) const override; s32 VGetInt32POIState(std::string_view name) const override; CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override; std::unique_ptr VClone() const override; std::optional> VSimplified() override; void VSetPhase(float) override; SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeBlend.cpp ================================================ #include "Runtime/Character/CAnimTreeBlend.hpp" namespace metaforce { std::string CAnimTreeBlend::CreatePrimitiveName(const std::shared_ptr& a, const std::shared_ptr& b, float scale) { return ""; } CAnimTreeBlend::CAnimTreeBlend(bool b1, const std::shared_ptr& a, const std::shared_ptr& b, float blendWeight, std::string_view name) : CAnimTreeTweenBase(b1, a, b, 1 | 2, name), x24_blendWeight(blendWeight) {} SAdvancementResults CAnimTreeBlend::VAdvanceView(const CCharAnimTime& dt) { IncAdvancementDepth(); SAdvancementResults resA = x14_a->VAdvanceView(dt); SAdvancementResults resB = x18_b->VAdvanceView(dt); DecAdvancementDepth(); if (ShouldCullTree()) { if (GetBlendingWeight() < 0.5f) x20_25_cullSelector = 1; else x20_25_cullSelector = 2; } const SAdvancementResults& maxRemTime = (resA.x0_remTime < resB.x0_remTime) ? resB : resA; if (x1c_flags & 0x1) { return {maxRemTime.x0_remTime, SAdvancementDeltas::Blend(resA.x8_deltas, resB.x8_deltas, GetBlendingWeight())}; } else { return resB; } } CCharAnimTime CAnimTreeBlend::VGetTimeRemaining() const { CCharAnimTime remA = x14_a->VGetTimeRemaining(); CCharAnimTime remB = x18_b->VGetTimeRemaining(); return (remA < remB) ? remB : remA; } CSteadyStateAnimInfo CAnimTreeBlend::VGetSteadyStateAnimInfo() const { CSteadyStateAnimInfo ssA = x14_a->VGetSteadyStateAnimInfo(); CSteadyStateAnimInfo ssB = x18_b->VGetSteadyStateAnimInfo(); zeus::CVector3f resOffset; if (ssA.GetDuration() < ssB.GetDuration()) { resOffset = ssA.GetOffset() * (ssB.GetDuration() / ssA.GetDuration()) * x24_blendWeight + ssB.GetOffset() * (1.f - x24_blendWeight); } else if (ssB.GetDuration() < ssA.GetDuration()) { resOffset = ssA.GetOffset() * x24_blendWeight + ssB.GetOffset() * (ssA.GetDuration() / ssB.GetDuration()) * (1.f - x24_blendWeight); } else { resOffset = ssA.GetOffset() + ssB.GetOffset(); } return {ssA.IsLooping(), (ssA.GetDuration() < ssB.GetDuration()) ? ssB.GetDuration() : ssA.GetDuration(), resOffset}; } std::unique_ptr CAnimTreeBlend::VClone() const { return std::make_unique(x20_24_b1, CAnimTreeNode::Cast(x14_a->Clone()), CAnimTreeNode::Cast(x18_b->Clone()), x24_blendWeight, x4_name); } void CAnimTreeBlend::SetBlendingWeight(float w) { x24_blendWeight = w; } float CAnimTreeBlend::VGetBlendingWeight() const { return x24_blendWeight; } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeBlend.hpp ================================================ #pragma once #include #include #include "Runtime/Character/CAnimTreeTweenBase.hpp" namespace metaforce { class CAnimTreeBlend : public CAnimTreeTweenBase { float x24_blendWeight; public: static std::string CreatePrimitiveName(const std::shared_ptr& a, const std::shared_ptr& b, float scale); CAnimTreeBlend(bool, const std::shared_ptr& a, const std::shared_ptr& b, float blendWeight, std::string_view name); SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override; CCharAnimTime VGetTimeRemaining() const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; std::unique_ptr VClone() const override; void SetBlendingWeight(float w) override; float VGetBlendingWeight() const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeDoubleChild.cpp ================================================ #include "Runtime/Character/CAnimTreeDoubleChild.hpp" namespace metaforce { CAnimTreeDoubleChild::CAnimTreeDoubleChild(const std::weak_ptr& a, const std::weak_ptr& b, std::string_view name) : CAnimTreeNode(name), x14_a(a.lock()), x18_b(b.lock()) {} CAnimTreeDoubleChild::CDoubleChildAdvancementResult CAnimTreeDoubleChild::AdvanceViewBothChildren(const CCharAnimTime& time, bool runLeft, bool loopLeft) { CCharAnimTime lRemTime = time; CCharAnimTime totalTime; if (!runLeft) totalTime = CCharAnimTime(); else if (loopLeft) totalTime = CCharAnimTime::Infinity(); else totalTime = x14_a->VGetTimeRemaining(); SAdvancementDeltas leftDeltas, rightDeltas; CCharAnimTime rRemTime = time; if (time.GreaterThanZero()) { while (lRemTime.GreaterThanZero() && !lRemTime.EpsilonZero() && totalTime.GreaterThanZero() && (loopLeft || !totalTime.EpsilonZero())) { SAdvancementResults res = x14_a->VAdvanceView(lRemTime); auto simp = x14_a->Simplified(); if (simp) x14_a = CAnimTreeNode::Cast(std::move(*simp)); leftDeltas.x0_posDelta += res.x8_deltas.x0_posDelta; leftDeltas.xc_rotDelta = leftDeltas.xc_rotDelta * res.x8_deltas.xc_rotDelta; if (!loopLeft) totalTime = x14_a->VGetTimeRemaining(); lRemTime = res.x0_remTime; } while (rRemTime.GreaterThanZero() && !rRemTime.EpsilonZero()) { SAdvancementResults res = x18_b->VAdvanceView(rRemTime); auto simp = x18_b->Simplified(); if (simp) x18_b = CAnimTreeNode::Cast(std::move(*simp)); rightDeltas.x0_posDelta += res.x8_deltas.x0_posDelta; rightDeltas.xc_rotDelta = rightDeltas.xc_rotDelta * res.x8_deltas.xc_rotDelta; rRemTime = res.x0_remTime; } } return {time, leftDeltas, rightDeltas}; } SAdvancementResults CAnimTreeDoubleChild::VAdvanceView(const CCharAnimTime& a) { SAdvancementResults resA = x14_a->VAdvanceView(a); SAdvancementResults resB = x14_a->VAdvanceView(a); return (resA.x0_remTime > resB.x0_remTime) ? resA : resB; } size_t CAnimTreeDoubleChild::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { size_t newCapacity = x14_a->GetBoolPOIList(time, listOut, capacity, iterator, unk); newCapacity += x18_b->GetBoolPOIList(time, listOut, capacity, newCapacity + iterator, unk); if (newCapacity > capacity) { newCapacity = capacity; } std::sort(listOut, listOut + newCapacity); return newCapacity; } size_t CAnimTreeDoubleChild::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { size_t newCapacity = x14_a->GetInt32POIList(time, listOut, capacity, iterator, unk); newCapacity += x18_b->GetInt32POIList(time, listOut, capacity, newCapacity + iterator, unk); if (newCapacity > capacity) { newCapacity = capacity; } std::sort(listOut, listOut + newCapacity); return newCapacity; } size_t CAnimTreeDoubleChild::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { size_t newCapacity = x14_a->GetParticlePOIList(time, listOut, capacity, iterator, unk); newCapacity += x18_b->GetParticlePOIList(time, listOut, capacity, newCapacity + iterator, unk); if (newCapacity > capacity) { newCapacity = capacity; } std::sort(listOut, listOut + newCapacity); return newCapacity; } size_t CAnimTreeDoubleChild::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { size_t newCapacity = x14_a->GetSoundPOIList(time, listOut, capacity, iterator, unk); newCapacity += x18_b->GetSoundPOIList(time, listOut, capacity, newCapacity + iterator, unk); if (newCapacity > capacity) { newCapacity = capacity; } std::sort(listOut, listOut + newCapacity); return newCapacity; } bool CAnimTreeDoubleChild::VGetBoolPOIState(std::string_view name) const { return x18_b->VGetBoolPOIState(name); } s32 CAnimTreeDoubleChild::VGetInt32POIState(std::string_view name) const { return x18_b->VGetInt32POIState(name); } CParticleData::EParentedMode CAnimTreeDoubleChild::VGetParticlePOIState(std::string_view name) const { return x18_b->VGetParticlePOIState(name); } void CAnimTreeDoubleChild::VSetPhase(float phase) { x14_a->VSetPhase(phase); x18_b->VSetPhase(phase); } SAdvancementResults CAnimTreeDoubleChild::VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const { SAdvancementResults resA = x14_a->VGetAdvancementResults(a, b); SAdvancementResults resB = x18_b->VGetAdvancementResults(a, b); return (resA.x0_remTime > resB.x0_remTime) ? resA : resB; } u32 CAnimTreeDoubleChild::Depth() const { return std::max(x14_a->Depth(), x18_b->Depth()) + 1; } CAnimTreeEffectiveContribution CAnimTreeDoubleChild::VGetContributionOfHighestInfluence() const { CAnimTreeEffectiveContribution cA = x14_a->GetContributionOfHighestInfluence(); CAnimTreeEffectiveContribution cB = x18_b->GetContributionOfHighestInfluence(); float leftWeight = (1.f - VGetRightChildWeight()) * cA.GetContributionWeight(); float rightWeight = VGetRightChildWeight() * cB.GetContributionWeight(); if (leftWeight > rightWeight) { return {leftWeight, cA.GetPrimitiveName(), cA.GetSteadyStateAnimInfo(), cA.GetTimeRemaining(), cA.GetAnimDatabaseIndex()}; } else { return {rightWeight, cB.GetPrimitiveName(), cB.GetSteadyStateAnimInfo(), cB.GetTimeRemaining(), cB.GetAnimDatabaseIndex()}; } } u32 CAnimTreeDoubleChild::VGetNumChildren() const { return x14_a->VGetNumChildren() + x18_b->VGetNumChildren() + 2; } std::shared_ptr CAnimTreeDoubleChild::VGetBestUnblendedChild() const { std::shared_ptr bestChild = (VGetRightChildWeight() > 0.5f) ? x18_b : x14_a; if (!bestChild) return {}; return bestChild->GetBestUnblendedChild(); } void CAnimTreeDoubleChild::VGetWeightedReaders( rstl::reserved_vector>, 16>& out, float w) const { x14_a->VGetWeightedReaders(out, w); x18_b->VGetWeightedReaders(out, w); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeDoubleChild.hpp ================================================ #pragma once #include #include #include "Runtime/rstl.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" namespace metaforce { class CAnimTreeDoubleChild : public CAnimTreeNode { public: class CDoubleChildAdvancementResult { CCharAnimTime x0_trueAdvancement; SAdvancementDeltas x8_leftDeltas; SAdvancementDeltas x24_rightDeltas; public: CDoubleChildAdvancementResult(const CCharAnimTime& trueAdvancement, const SAdvancementDeltas& leftDeltas, const SAdvancementDeltas& rightDeltas) : x0_trueAdvancement(trueAdvancement), x8_leftDeltas(leftDeltas), x24_rightDeltas(rightDeltas) {} const SAdvancementDeltas& GetLeftAdvancementDeltas() const { return x8_leftDeltas; } const SAdvancementDeltas& GetRightAdvancementDeltas() const { return x24_rightDeltas; } const CCharAnimTime& GetTrueAdvancement() const { return x0_trueAdvancement; } }; protected: std::shared_ptr x14_a; std::shared_ptr x18_b; CDoubleChildAdvancementResult AdvanceViewBothChildren(const CCharAnimTime& time, bool runLeft, bool loopLeft); public: CAnimTreeDoubleChild(const std::weak_ptr& a, const std::weak_ptr& b, std::string_view name); SAdvancementResults VAdvanceView(const CCharAnimTime& a) override; size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const override; bool VGetBoolPOIState(std::string_view name) const override; s32 VGetInt32POIState(std::string_view name) const override; CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override; void VSetPhase(float) override; SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override; u32 Depth() const override; CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override; u32 VGetNumChildren() const override; std::shared_ptr VGetBestUnblendedChild() const override; void VGetWeightedReaders(rstl::reserved_vector>, 16>& out, float w) const override; virtual float VGetRightChildWeight() const = 0; float GetRightChildWeight() const { return VGetRightChildWeight(); } const std::shared_ptr& GetLeftChild() const { return x14_a; } const std::shared_ptr& GetRightChild() const { return x18_b; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeLoopIn.cpp ================================================ #include "Runtime/Character/CAnimTreeLoopIn.hpp" #include "Runtime/Character/CTreeUtils.hpp" namespace metaforce { std::string CAnimTreeLoopIn::CreatePrimitiveName(const std::weak_ptr& a, const std::weak_ptr& b, const std::weak_ptr& c) { return {}; } CAnimTreeLoopIn::CAnimTreeLoopIn(const std::weak_ptr& a, const std::weak_ptr& b, const std::weak_ptr& c, const CAnimSysContext& animCtx, std::string_view name) : CAnimTreeSingleChild(CTreeUtils::GetTransitionTree(a, c, animCtx), name) , x18_nextAnim(b.lock()) , x20_animCtx(animCtx) , x30_fundamentals(CSequenceHelper(x14_child, x18_nextAnim, animCtx).ComputeSequenceFundamentals()) {} CAnimTreeLoopIn::CAnimTreeLoopIn(const std::weak_ptr& a, const std::weak_ptr& b, bool didLoopIn, CAnimSysContext animCtx, std::string_view name, CSequenceFundamentals fundamentals, const CCharAnimTime& time) : CAnimTreeSingleChild(a, name) , x18_nextAnim(b.lock()) , x1c_didLoopIn(didLoopIn) , x20_animCtx(std::move(animCtx)) , x30_fundamentals(std::move(fundamentals)) , x88_curTime(time) {} CAnimTreeEffectiveContribution CAnimTreeLoopIn::VGetContributionOfHighestInfluence() const { return x14_child->GetContributionOfHighestInfluence(); } std::optional> CAnimTreeLoopIn::VSimplified() { CCharAnimTime remTime = x14_child->VGetTimeRemaining(); if (remTime.GreaterThanZero() && !remTime.EpsilonZero()) { auto simp = x14_child->Simplified(); if (simp) x14_child = CAnimTreeNode::Cast(std::move(*simp)); } else if (x1c_didLoopIn && x14_child->VGetTimeRemaining().EqualsZero()) { return x14_child->Clone(); } return {}; } std::shared_ptr CAnimTreeLoopIn::VGetBestUnblendedChild() const { if (std::shared_ptr bestChild = x14_child->GetBestUnblendedChild()) { return std::make_shared(CAnimTreeNode::Cast(bestChild->Clone()), x18_nextAnim, x1c_didLoopIn, x20_animCtx, x4_name, x30_fundamentals, x88_curTime); } return {}; } std::unique_ptr CAnimTreeLoopIn::VClone() const { return std::make_unique(CAnimTreeNode::Cast(x14_child->Clone()), x18_nextAnim, x1c_didLoopIn, x20_animCtx, x4_name, x30_fundamentals, x88_curTime); } size_t CAnimTreeLoopIn::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x30_fundamentals.GetBoolPointsOfInterest(), x88_curTime); } size_t CAnimTreeLoopIn::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x30_fundamentals.GetInt32PointsOfInterest(), x88_curTime); } size_t CAnimTreeLoopIn::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x30_fundamentals.GetParticlePointsOfInterest(), x88_curTime); } size_t CAnimTreeLoopIn::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x30_fundamentals.GetSoundPointsOfInterest(), x88_curTime); } CSteadyStateAnimInfo CAnimTreeLoopIn::VGetSteadyStateAnimInfo() const { return x30_fundamentals.GetSteadyStateAnimInfo(); } CCharAnimTime CAnimTreeLoopIn::VGetTimeRemaining() const { return x30_fundamentals.GetSteadyStateAnimInfo().GetDuration() - x88_curTime; } SAdvancementResults CAnimTreeLoopIn::VAdvanceView(const CCharAnimTime& dt) { std::shared_ptr origChild = x14_child; SAdvancementResults res = origChild->VAdvanceView(dt); x88_curTime += dt - res.x0_remTime; CCharAnimTime remTime = origChild->VGetTimeRemaining(); if ((remTime.EpsilonZero() || (dt - res.x0_remTime).EpsilonZero()) && !x1c_didLoopIn) { x14_child = CTreeUtils::GetTransitionTree(origChild, x18_nextAnim, x20_animCtx); x1c_didLoopIn = true; } return res; } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeLoopIn.hpp ================================================ #pragma once #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CAnimTreeSingleChild.hpp" #include "Runtime/Character/CSequenceHelper.hpp" namespace metaforce { class CAnimTreeLoopIn : public CAnimTreeSingleChild { std::shared_ptr x18_nextAnim; bool x1c_didLoopIn = false; CAnimSysContext x20_animCtx; CSequenceFundamentals x30_fundamentals; CCharAnimTime x88_curTime; public: static std::string CreatePrimitiveName(const std::weak_ptr& a, const std::weak_ptr& b, const std::weak_ptr& c); CAnimTreeLoopIn(const std::weak_ptr& a, const std::weak_ptr& b, const std::weak_ptr& c, const CAnimSysContext& animCtx, std::string_view name); CAnimTreeLoopIn(const std::weak_ptr& a, const std::weak_ptr& b, bool didLoopIn, CAnimSysContext animCtx, std::string_view name, CSequenceFundamentals fundamentals, const CCharAnimTime& time); CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override; bool VSupportsReverseView() const { return false; } std::optional> VSimplified() override; std::shared_ptr VGetBestUnblendedChild() const override; std::unique_ptr VClone() const override; size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; CCharAnimTime VGetTimeRemaining() const override; SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeNode.cpp ================================================ #include "Runtime/Character/CAnimTreeNode.hpp" namespace metaforce { CAnimTreeEffectiveContribution CAnimTreeNode::GetContributionOfHighestInfluence() const { return VGetContributionOfHighestInfluence(); } u32 CAnimTreeNode::GetNumChildren() const { return VGetNumChildren(); } std::shared_ptr CAnimTreeNode::GetBestUnblendedChild() const { return VGetBestUnblendedChild(); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeNode.hpp ================================================ #pragma once #include #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/IAnimReader.hpp" namespace metaforce { class CAnimTreeNode : public IAnimReader { protected: std::string x4_name; public: explicit CAnimTreeNode(std::string_view name) : x4_name(name) {} bool IsCAnimTreeNode() const override { return true; } static std::shared_ptr Cast(std::unique_ptr&& ptr) { if (ptr->IsCAnimTreeNode()) return std::static_pointer_cast(std::shared_ptr(std::move(ptr))); return {}; } virtual u32 Depth() const = 0; virtual CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const = 0; virtual u32 VGetNumChildren() const = 0; virtual std::shared_ptr VGetBestUnblendedChild() const = 0; virtual void VGetWeightedReaders(rstl::reserved_vector>, 16>& out, float w) const = 0; void GetWeightedReaders(rstl::reserved_vector>, 16>& out, float w) const { VGetWeightedReaders(out, w); } CAnimTreeEffectiveContribution GetContributionOfHighestInfluence() const; u32 GetNumChildren() const; std::shared_ptr GetBestUnblendedChild() const; std::string_view GetName() const { return x4_name; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeSequence.cpp ================================================ #include "Runtime/Character/CAnimTreeSequence.hpp" #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CTreeUtils.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { CAnimTreeSequence::CAnimTreeSequence(std::vector> seq, CAnimSysContext animSys, std::string_view name) : CAnimTreeSingleChild(seq[0]->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders()), name) , x18_animCtx(std::move(animSys)) , x28_sequence(std::move(seq)) , x3c_fundamentals(CSequenceHelper(x28_sequence, x18_animCtx).ComputeSequenceFundamentals()) , x94_curTime(0.f) {} CAnimTreeSequence::CAnimTreeSequence(const std::shared_ptr& curNode, std::vector> metaAnims, CAnimSysContext animSys, std::string_view name, CSequenceFundamentals fundamentals, const CCharAnimTime& time) : CAnimTreeSingleChild(curNode, name) , x18_animCtx(std::move(animSys)) , x28_sequence(std::move(metaAnims)) , x3c_fundamentals(std::move(fundamentals)) , x94_curTime(time) {} CAnimTreeEffectiveContribution CAnimTreeSequence::VGetContributionOfHighestInfluence() const { return x14_child->GetContributionOfHighestInfluence(); } std::shared_ptr CAnimTreeSequence::VGetBestUnblendedChild() const { std::shared_ptr ch = x14_child->GetBestUnblendedChild(); if (!ch) return ch; return std::make_shared( std::static_pointer_cast(std::shared_ptr(ch->Clone())), x28_sequence, x18_animCtx, x4_name, x3c_fundamentals, x94_curTime); } SAdvancementResults CAnimTreeSequence::VAdvanceView(const CCharAnimTime& dt) { CCharAnimTime totalDelta; zeus::CVector3f posDelta; zeus::CQuaternion rotDelta; std::shared_ptr curChild = x14_child; if (x38_curIdx >= x28_sequence.size() && curChild->VGetTimeRemaining().EqualsZero()) { x3c_fundamentals = CSequenceHelper(x28_sequence, x18_animCtx).ComputeSequenceFundamentals(); x38_curIdx = 0; x14_child = CTreeUtils::GetTransitionTree( curChild, x28_sequence[x38_curIdx]->GetAnimationTree(x18_animCtx, CMetaAnimTreeBuildOrders::NoSpecialOrders()), x18_animCtx); curChild = x14_child; } CCharAnimTime remTime = dt; // Note: EpsilonZero check added while (remTime.GreaterThanZero() && !remTime.EpsilonZero() && x38_curIdx < x28_sequence.size()) { CCharAnimTime chRem = curChild->VGetTimeRemaining(); if (chRem.EqualsZero()) { ++x38_curIdx; if (x38_curIdx < x28_sequence.size()) { x14_child = CTreeUtils::GetTransitionTree( curChild, x28_sequence[x38_curIdx]->GetAnimationTree(x18_animCtx, CMetaAnimTreeBuildOrders::NoSpecialOrders()), x18_animCtx); } } curChild = x14_child; if (x38_curIdx < x28_sequence.size()) { SAdvancementResults res = curChild->VAdvanceView(remTime); if (auto simp = curChild->Simplified()) { curChild = CAnimTreeNode::Cast(std::move(*simp)); x14_child = curChild; } CCharAnimTime prevRemTime = remTime; remTime = res.x0_remTime; totalDelta += prevRemTime - remTime; posDelta += res.x8_deltas.x0_posDelta; rotDelta = rotDelta * res.x8_deltas.xc_rotDelta; } } x94_curTime += totalDelta; return {dt - totalDelta, {posDelta, rotDelta}}; } CCharAnimTime CAnimTreeSequence::VGetTimeRemaining() const { if (x38_curIdx == x28_sequence.size() - 1) return x14_child->VGetTimeRemaining(); return x3c_fundamentals.GetSteadyStateAnimInfo().GetDuration() - x94_curTime; } CSteadyStateAnimInfo CAnimTreeSequence::VGetSteadyStateAnimInfo() const { return x3c_fundamentals.GetSteadyStateAnimInfo(); } size_t CAnimTreeSequence::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetBoolPointsOfInterest(), x94_curTime); } size_t CAnimTreeSequence::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetInt32PointsOfInterest(), x94_curTime); } size_t CAnimTreeSequence::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetParticlePointsOfInterest(), x94_curTime); } size_t CAnimTreeSequence::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return _getPOIList(time, listOut, capacity, iterator, unk, x3c_fundamentals.GetSoundPointsOfInterest(), x94_curTime); } std::unique_ptr CAnimTreeSequence::VClone() const { return std::make_unique( std::static_pointer_cast(std::shared_ptr(x14_child->Clone())), x28_sequence, x18_animCtx, x4_name, x3c_fundamentals, x94_curTime); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeSequence.hpp ================================================ #pragma once #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CAnimTreeSingleChild.hpp" #include "Runtime/Character/CSequenceHelper.hpp" namespace metaforce { class IMetaAnim; class CTransitionDatabaseGame; class CAnimTreeSequence : public CAnimTreeSingleChild { CAnimSysContext x18_animCtx; std::vector> x28_sequence; u32 x38_curIdx = 0; CSequenceFundamentals x3c_fundamentals; CCharAnimTime x94_curTime; public: CAnimTreeSequence(std::vector> seq, CAnimSysContext animSys, std::string_view name); CAnimTreeSequence(const std::shared_ptr& curNode, std::vector> metaAnims, CAnimSysContext animSys, std::string_view name, CSequenceFundamentals fundamentals, const CCharAnimTime& time); CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override; std::shared_ptr VGetBestUnblendedChild() const override; bool VSupportsReverseView() const { return false; } SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override; CCharAnimTime VGetTimeRemaining() const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const override; std::unique_ptr VClone() const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeSingleChild.cpp ================================================ #include "Runtime/Character/CAnimTreeSingleChild.hpp" namespace metaforce { CAnimTreeSingleChild::CAnimTreeSingleChild(const std::weak_ptr& node, std::string_view name) : CAnimTreeNode(name), x14_child(node.lock()) {} SAdvancementResults CAnimTreeSingleChild::VAdvanceView(const CCharAnimTime& dt) { return x14_child->VAdvanceView(dt); } CCharAnimTime CAnimTreeSingleChild::VGetTimeRemaining() const { return x14_child->VGetTimeRemaining(); } bool CAnimTreeSingleChild::VHasOffset(const CSegId& seg) const { return x14_child->VHasOffset(seg); } zeus::CVector3f CAnimTreeSingleChild::VGetOffset(const CSegId& seg) const { return x14_child->VGetOffset(seg); } zeus::CQuaternion CAnimTreeSingleChild::VGetRotation(const CSegId& seg) const { return x14_child->VGetRotation(seg); } size_t CAnimTreeSingleChild::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_child->GetBoolPOIList(time, listOut, capacity, iterator, unk); } size_t CAnimTreeSingleChild::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_child->GetInt32POIList(time, listOut, capacity, iterator, unk); } size_t CAnimTreeSingleChild::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_child->GetParticlePOIList(time, listOut, capacity, iterator, unk); } size_t CAnimTreeSingleChild::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { return x14_child->GetSoundPOIList(time, listOut, capacity, iterator, unk); } bool CAnimTreeSingleChild::VGetBoolPOIState(std::string_view name) const { return x14_child->VGetBoolPOIState(name); } s32 CAnimTreeSingleChild::VGetInt32POIState(std::string_view name) const { return x14_child->VGetInt32POIState(name); } CParticleData::EParentedMode CAnimTreeSingleChild::VGetParticlePOIState(std::string_view name) const { return x14_child->VGetParticlePOIState(name); } void CAnimTreeSingleChild::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const { x14_child->VGetSegStatementSet(list, setOut); } void CAnimTreeSingleChild::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const { x14_child->VGetSegStatementSet(list, setOut, time); } void CAnimTreeSingleChild::VSetPhase(float phase) { x14_child->VSetPhase(phase); } SAdvancementResults CAnimTreeSingleChild::VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const { return x14_child->VGetAdvancementResults(a, b); } u32 CAnimTreeSingleChild::Depth() const { return x14_child->Depth() + 1; } u32 CAnimTreeSingleChild::VGetNumChildren() const { return x14_child->VGetNumChildren() + 1; } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeSingleChild.hpp ================================================ #pragma once #include #include "Runtime/GCNTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" namespace metaforce { class CAnimTreeSingleChild : public CAnimTreeNode { protected: std::shared_ptr x14_child; public: CAnimTreeSingleChild(const std::weak_ptr& node, std::string_view name); SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override; CCharAnimTime VGetTimeRemaining() const override; bool VHasOffset(const CSegId& seg) const override; zeus::CVector3f VGetOffset(const CSegId& seg) const override; zeus::CQuaternion VGetRotation(const CSegId& seg) const override; size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const override; bool VGetBoolPOIState(std::string_view name) const override; s32 VGetInt32POIState(std::string_view name) const override; CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override; void VSetPhase(float) override; SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override; u32 Depth() const override; u32 VGetNumChildren() const override; void VGetWeightedReaders(rstl::reserved_vector>, 16>& out, float w) const override { x14_child->VGetWeightedReaders(out, w); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeTimeScale.cpp ================================================ #include "Runtime/Character/CAnimTreeTimeScale.hpp" namespace metaforce { CAnimTreeTimeScale::CAnimTreeTimeScale(const std::weak_ptr& node, float scale, std::string_view name) : CAnimTreeSingleChild(node, name) , x18_timeScale(new CConstantAnimationTimeScale(scale)) , x28_targetAccelTime(CCharAnimTime::Infinity()) {} CAnimTreeTimeScale::CAnimTreeTimeScale(const std::weak_ptr& node, std::unique_ptr&& timeScale, const CCharAnimTime& time, std::string_view name) : CAnimTreeSingleChild(node, name), x18_timeScale(std::move(timeScale)), x28_targetAccelTime(time) { x30_initialTime = x14_child->VGetSteadyStateAnimInfo().GetDuration() - x14_child->VGetTimeRemaining(); } std::string CAnimTreeTimeScale::CreatePrimitiveName(const std::weak_ptr&, float, const CCharAnimTime&, float) { return {}; } CCharAnimTime CAnimTreeTimeScale::GetRealLifeTime(const CCharAnimTime& time) const { CCharAnimTime timeRem = x14_child->VGetTimeRemaining(); CCharAnimTime ret = std::min(timeRem, time); if (x28_targetAccelTime > CCharAnimTime()) { if (ret < CCharAnimTime(x28_targetAccelTime - x20_curAccelTime)) return x18_timeScale->VTimeScaleIntegral(x20_curAccelTime.GetSeconds(), (x20_curAccelTime + ret).GetSeconds()); else { CCharAnimTime integral = x18_timeScale->VTimeScaleIntegral(x20_curAccelTime.GetSeconds(), x28_targetAccelTime.GetSeconds()); if (integral > ret) return x18_timeScale->VFindUpperLimit(x20_curAccelTime.GetSeconds(), ret.GetSeconds()) - x20_curAccelTime.GetSeconds(); else return integral + (ret - integral); } } return ret; } void CAnimTreeTimeScale::VSetPhase(float phase) { x14_child->VSetPhase(phase); } std::optional> CAnimTreeTimeScale::VSimplified() { if (auto simp = x14_child->Simplified()) { auto newNode = std::make_unique(CAnimTreeNode::Cast(std::move(*simp)), x18_timeScale->Clone(), x28_targetAccelTime, x4_name); newNode->x20_curAccelTime = x20_curAccelTime; newNode->x30_initialTime = x30_initialTime; return {std::move(newNode)}; } if (x20_curAccelTime == x28_targetAccelTime) { return {x14_child->Clone()}; } return std::nullopt; } size_t CAnimTreeTimeScale::VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { const CCharAnimTime useTime = time == CCharAnimTime::Infinity() ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time); const size_t ret = x14_child->GetBoolPOIList(useTime, listOut, capacity, iterator, unk); if (x28_targetAccelTime > CCharAnimTime()) { for (size_t i = 0; i < ret; ++i) { listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime())); } } return ret; } size_t CAnimTreeTimeScale::VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { const CCharAnimTime useTime = time == CCharAnimTime::Infinity() ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time); const size_t ret = x14_child->GetInt32POIList(useTime, listOut, capacity, iterator, unk); if (x28_targetAccelTime > CCharAnimTime()) { for (size_t i = 0; i < ret; ++i) { listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime())); } } return ret; } size_t CAnimTreeTimeScale::VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { const CCharAnimTime useTime = time == CCharAnimTime::Infinity() ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time); const size_t ret = x14_child->GetParticlePOIList(useTime, listOut, capacity, iterator, unk); if (x28_targetAccelTime > CCharAnimTime()) { for (size_t i = 0; i < ret; ++i) { listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime())); } } return ret; } size_t CAnimTreeTimeScale::VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { const CCharAnimTime useTime = (time == CCharAnimTime::Infinity()) ? x14_child->VGetTimeRemaining() : GetRealLifeTime(time); const size_t ret = x14_child->GetSoundPOIList(useTime, listOut, capacity, iterator, unk); if (x28_targetAccelTime > CCharAnimTime()) { for (size_t i = 0; i < ret; ++i) { listOut[iterator + i].SetTime(GetRealLifeTime(listOut[i].GetTime())); } } return ret; } bool CAnimTreeTimeScale::VGetBoolPOIState(std::string_view name) const { return x14_child->VGetBoolPOIState(name); } s32 CAnimTreeTimeScale::VGetInt32POIState(std::string_view name) const { return x14_child->VGetInt32POIState(name); } CParticleData::EParentedMode CAnimTreeTimeScale::VGetParticlePOIState(std::string_view name) const { return x14_child->VGetParticlePOIState(name); } CAnimTreeEffectiveContribution CAnimTreeTimeScale::VGetContributionOfHighestInfluence() const { CAnimTreeEffectiveContribution c = x14_child->VGetContributionOfHighestInfluence(); return {c.GetContributionWeight(), c.GetPrimitiveName(), VGetSteadyStateAnimInfo(), VGetTimeRemaining(), c.GetAnimDatabaseIndex()}; } std::shared_ptr CAnimTreeTimeScale::VGetBestUnblendedChild() const { if (std::shared_ptr bestChild = x14_child->VGetBestUnblendedChild()) { auto newNode = std::make_shared(CAnimTreeNode::Cast(bestChild->Clone()), x18_timeScale->Clone(), x28_targetAccelTime, x4_name); newNode->x20_curAccelTime = x20_curAccelTime; newNode->x30_initialTime = x30_initialTime; return {std::move(newNode)}; } return nullptr; } std::unique_ptr CAnimTreeTimeScale::VClone() const { auto newNode = std::make_unique(CAnimTreeNode::Cast(x14_child->Clone()), x18_timeScale->Clone(), x28_targetAccelTime, x4_name); newNode->x20_curAccelTime = x20_curAccelTime; newNode->x30_initialTime = x30_initialTime; return {std::move(newNode)}; } CSteadyStateAnimInfo CAnimTreeTimeScale::VGetSteadyStateAnimInfo() const { CSteadyStateAnimInfo ssInfo = x14_child->VGetSteadyStateAnimInfo(); if (x28_targetAccelTime == CCharAnimTime::Infinity()) { return {ssInfo.IsLooping(), x18_timeScale->VFindUpperLimit(0.f, ssInfo.GetDuration().GetSeconds()), ssInfo.GetOffset()}; } else { CCharAnimTime time; if (x20_curAccelTime.GreaterThanZero()) time = x18_timeScale->VTimeScaleIntegral(0.f, x20_curAccelTime.GetSeconds()); return {ssInfo.IsLooping(), x30_initialTime + time + VGetTimeRemaining(), ssInfo.GetOffset()}; } } CCharAnimTime CAnimTreeTimeScale::VGetTimeRemaining() const { CCharAnimTime timeRem = x14_child->VGetTimeRemaining(); if (x28_targetAccelTime == CCharAnimTime::Infinity()) return CCharAnimTime(x18_timeScale->VFindUpperLimit(x20_curAccelTime.GetSeconds(), timeRem.GetSeconds())) - x20_curAccelTime; else return GetRealLifeTime(timeRem); } SAdvancementResults CAnimTreeTimeScale::VAdvanceView(const CCharAnimTime& dt) { if (dt.EqualsZero() && dt > CCharAnimTime()) return x14_child->VAdvanceView(dt); CCharAnimTime origAccelTime = x20_curAccelTime; CCharAnimTime newTime = x20_curAccelTime + dt; if (newTime < x28_targetAccelTime) { SAdvancementResults res = x14_child->VAdvanceView(x18_timeScale->VTimeScaleIntegral(origAccelTime.GetSeconds(), newTime.GetSeconds())); if (res.x0_remTime.EqualsZero()) { x20_curAccelTime = newTime; res.x0_remTime = CCharAnimTime(); return res; } else { x20_curAccelTime = x18_timeScale->VFindUpperLimit(origAccelTime.GetSeconds(), (newTime - res.x0_remTime).GetSeconds()); res.x0_remTime = dt - (x20_curAccelTime - origAccelTime); return res; } } else { CCharAnimTime newDt( x18_timeScale->VTimeScaleIntegral(origAccelTime.GetSeconds(), x28_targetAccelTime.GetSeconds())); SAdvancementResults res2; if (newDt.GreaterThanZero()) res2 = x14_child->VAdvanceView(newDt); x20_curAccelTime = x28_targetAccelTime; res2.x0_remTime = res2.x0_remTime + (newTime - x28_targetAccelTime); return res2; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeTimeScale.hpp ================================================ #pragma once #include #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CAnimTreeSingleChild.hpp" #include "Runtime/Character/CTimeScaleFunctions.hpp" namespace metaforce { class CAnimTreeTimeScale : public CAnimTreeSingleChild { std::unique_ptr x18_timeScale; CCharAnimTime x20_curAccelTime; CCharAnimTime x28_targetAccelTime; CCharAnimTime x30_initialTime; public: CAnimTreeTimeScale(const std::weak_ptr& node, float timeScale, std::string_view name); CAnimTreeTimeScale(const std::weak_ptr& node, std::unique_ptr&& timeScale, const CCharAnimTime& time, std::string_view name); static std::string CreatePrimitiveName(const std::weak_ptr&, float, const CCharAnimTime&, float); CCharAnimTime GetRealLifeTime(const CCharAnimTime&) const; void VSetPhase(float) override; std::optional> VSimplified() override; size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const override; size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const override; bool VGetBoolPOIState(std::string_view name) const override; s32 VGetInt32POIState(std::string_view name) const override; CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const override; CAnimTreeEffectiveContribution VGetContributionOfHighestInfluence() const override; std::shared_ptr VGetBestUnblendedChild() const override; std::unique_ptr VClone() const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; CCharAnimTime VGetTimeRemaining() const override; SAdvancementResults VAdvanceView(const CCharAnimTime& dt) override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeTransition.cpp ================================================ #include "Runtime/Character/CAnimTreeTransition.hpp" namespace metaforce { std::string CAnimTreeTransition::CreatePrimitiveName(const std::weak_ptr&, const std::weak_ptr&, float) { return {}; } CAnimTreeTransition::CAnimTreeTransition(bool b1, const std::weak_ptr& a, const std::weak_ptr& b, const CCharAnimTime& transDur, const CCharAnimTime& timeInTrans, bool runA, bool loopA, int flags, std::string_view name, bool initialized) : CAnimTreeTweenBase(b1, a, b, flags, name) , x24_transDur(transDur) , x2c_timeInTrans(timeInTrans) , x34_runA(runA) , x35_loopA(loopA) , x36_initialized(initialized) {} CAnimTreeTransition::CAnimTreeTransition(bool b1, const std::weak_ptr& a, const std::weak_ptr& b, const CCharAnimTime& transDur, bool runA, int flags, std::string_view name) : CAnimTreeTweenBase(b1, a, b, flags, name) , x24_transDur(transDur) , x34_runA(runA) , x35_loopA(a.lock()->VGetBoolPOIState("Loop")) {} std::shared_ptr CAnimTreeTransition::VGetBestUnblendedChild() const { std::shared_ptr child = x18_b->GetBestUnblendedChild(); return (child ? child : x18_b); } CCharAnimTime CAnimTreeTransition::VGetTimeRemaining() const { CCharAnimTime transTimeRem = x24_transDur - x2c_timeInTrans; CCharAnimTime rightTimeRem = x18_b->VGetTimeRemaining(); return (rightTimeRem < transTimeRem) ? transTimeRem : rightTimeRem; } CSteadyStateAnimInfo CAnimTreeTransition::VGetSteadyStateAnimInfo() const { CSteadyStateAnimInfo bInfo = x18_b->VGetSteadyStateAnimInfo(); if (x24_transDur < bInfo.GetDuration()) return CSteadyStateAnimInfo(bInfo.IsLooping(), bInfo.GetDuration(), bInfo.GetOffset()); return CSteadyStateAnimInfo(bInfo.IsLooping(), x24_transDur, bInfo.GetOffset()); } std::unique_ptr CAnimTreeTransition::VClone() const { return std::make_unique( x20_24_b1, std::static_pointer_cast(std::shared_ptr(x14_a->Clone())), std::static_pointer_cast(std::shared_ptr(x18_b->Clone())), x24_transDur, x2c_timeInTrans, x34_runA, x35_loopA, x1c_flags, x4_name, x36_initialized); } std::optional> CAnimTreeTransition::VSimplified() { if (zeus::close_enough(GetBlendingWeight(), 1.f)) { if (auto simp = x18_b->Simplified()) return simp; return {x18_b->Clone()}; } return CAnimTreeTweenBase::VSimplified(); } std::optional> CAnimTreeTransition::VReverseSimplified() { if (zeus::close_enough(GetBlendingWeight(), 0.f)) return {x14_a->Clone()}; return CAnimTreeTweenBase::VReverseSimplified(); } SAdvancementResults CAnimTreeTransition::AdvanceViewForTransitionalPeriod(const CCharAnimTime& time) { IncAdvancementDepth(); CDoubleChildAdvancementResult res = AdvanceViewBothChildren(time, x34_runA, x35_loopA); DecAdvancementDepth(); if (res.GetTrueAdvancement().EqualsZero()) return {}; float oldWeight = GetBlendingWeight(); x2c_timeInTrans += res.GetTrueAdvancement(); float newWeight = GetBlendingWeight(); if (ShouldCullTree()) { if (newWeight < 0.5f) x20_25_cullSelector = 1; else x20_25_cullSelector = 2; } if (x1c_flags & 0x1) { return {res.GetTrueAdvancement(), SAdvancementDeltas::Interpolate(res.GetLeftAdvancementDeltas(), res.GetRightAdvancementDeltas(), oldWeight, newWeight)}; } return {res.GetTrueAdvancement(), res.GetRightAdvancementDeltas()}; } SAdvancementResults CAnimTreeTransition::VAdvanceView(const CCharAnimTime& time) { if (time.EqualsZero()) { IncAdvancementDepth(); x18_b->VAdvanceView(time); if (x34_runA) x14_a->VAdvanceView(time); DecAdvancementDepth(); if (ShouldCullTree()) x20_25_cullSelector = 1; return {}; } if (!x36_initialized) x36_initialized = true; if (x2c_timeInTrans + time < x24_transDur) { SAdvancementResults res = AdvanceViewForTransitionalPeriod(time); res.x0_remTime = time - res.x0_remTime; return res; } CCharAnimTime transTimeRem = x24_transDur - x2c_timeInTrans; SAdvancementResults res; if (transTimeRem.GreaterThanZero()) { res = AdvanceViewForTransitionalPeriod(transTimeRem); if (res.x0_remTime != transTimeRem) return res; // NOTE: URDE can hit an infinite loop if transTimeRem // becomes negative (floating point inaccuracy). // This line was moved into this branch as a workaround. res.x0_remTime = time - transTimeRem; } return res; } void CAnimTreeTransition::SetBlendingWeight(float w) { std::static_pointer_cast(x18_b)->SetBlendingWeight(w); } float CAnimTreeTransition::VGetBlendingWeight() const { if (x24_transDur.GreaterThanZero()) return x2c_timeInTrans.GetSeconds() / x24_transDur.GetSeconds(); return 0.f; } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeTransition.hpp ================================================ #pragma once #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CAnimTreeTweenBase.hpp" namespace metaforce { class CAnimTreeTransition : public CAnimTreeTweenBase { protected: CCharAnimTime x24_transDur; CCharAnimTime x2c_timeInTrans; bool x34_runA; bool x35_loopA; bool x36_initialized = false; SAdvancementResults AdvanceViewForTransitionalPeriod(const CCharAnimTime& time); public: static std::string CreatePrimitiveName(const std::weak_ptr&, const std::weak_ptr&, float); CAnimTreeTransition(bool b1, const std::weak_ptr& a, const std::weak_ptr& b, const CCharAnimTime& transDur, const CCharAnimTime& timeInTrans, bool runA, bool loopA, int flags, std::string_view name, bool initialized); CAnimTreeTransition(bool b1, const std::weak_ptr& a, const std::weak_ptr& b, const CCharAnimTime& transDur, bool runA, int flags, std::string_view name); std::shared_ptr VGetBestUnblendedChild() const override; CCharAnimTime VGetTimeRemaining() const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; std::unique_ptr VClone() const override; std::optional> VSimplified() override; std::optional> VReverseSimplified() override; SAdvancementResults VAdvanceView(const CCharAnimTime& a) override; void SetBlendingWeight(float w) override; float VGetBlendingWeight() const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeTweenBase.cpp ================================================ #include "Runtime/Character/CAnimTreeTweenBase.hpp" #include "Runtime/Character/CSegIdList.hpp" #include "Runtime/Character/CSegStatementSet.hpp" namespace metaforce { s32 CAnimTreeTweenBase::sAdvancementDepth = 0; CAnimTreeTweenBase::CAnimTreeTweenBase(bool b1, const std::weak_ptr& a, const std::weak_ptr& b, int flags, std::string_view name) : CAnimTreeDoubleChild(a, b, name), x1c_flags(flags), x20_24_b1{b1} {} void CAnimTreeTweenBase::VGetWeightedReaders( rstl::reserved_vector>, 16>& out, float w) const { float weight = GetBlendingWeight(); x14_a->VGetWeightedReaders(out, (1.f - weight) * w); x18_b->VGetWeightedReaders(out, weight * w); } void CAnimTreeTweenBase::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const { float w = GetBlendingWeight(); static int sStack = 0; ++sStack; if (w >= 1.f) { x18_b->VGetSegStatementSet(list, setOut); } else if (sStack > 3) { const auto& n = w > 0.5f ? x18_b : x14_a; auto ptr = n->GetBestUnblendedChild(); if (!ptr) { ptr = n; } ptr->VGetSegStatementSet(list, setOut); } else { CSegStatementSet setA, setB; x14_a->VGetSegStatementSet(list, setA); x18_b->VGetSegStatementSet(list, setB); for (CSegId id : list.GetList()) { if (w < 0.0001f) { setOut[id].x0_rotation = setA[id].x0_rotation; if (setA[id].x1c_hasOffset) { setOut[id].x10_offset = setA[id].x10_offset; setOut[id].x1c_hasOffset = true; } } else { setOut[id].x0_rotation = zeus::CQuaternion::slerpShort(setA[id].x0_rotation, setB[id].x0_rotation, w); if (setA[id].x1c_hasOffset && setB[id].x1c_hasOffset) { setOut[id].x10_offset = zeus::CVector3f::lerp(setA[id].x10_offset, setB[id].x10_offset, w); setOut[id].x1c_hasOffset = true; } } } } --sStack; } void CAnimTreeTweenBase::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const { float w = GetBlendingWeight(); static int sStack = 0; ++sStack; if (w >= 1.f) { x18_b->VGetSegStatementSet(list, setOut, time); } else if (sStack > 3) { const auto& n = w > 0.5f ? x18_b : x14_a; n->GetBestUnblendedChild()->VGetSegStatementSet(list, setOut, time); } else { CSegStatementSet setA, setB; x14_a->VGetSegStatementSet(list, setA, time); x18_b->VGetSegStatementSet(list, setB, time); for (CSegId id : list.GetList()) { setOut[id].x0_rotation = zeus::CQuaternion::slerpShort(setA[id].x0_rotation, setB[id].x0_rotation, w); if (setA[id].x1c_hasOffset && setB[id].x1c_hasOffset) { setOut[id].x10_offset = zeus::CVector3f::lerp(setA[id].x10_offset, setB[id].x10_offset, w); setOut[id].x1c_hasOffset = true; } } } --sStack; } bool CAnimTreeTweenBase::VHasOffset(const CSegId& seg) const { return (x14_a->VHasOffset(seg) && x18_b->VHasOffset(seg)); } zeus::CVector3f CAnimTreeTweenBase::VGetOffset(const CSegId& seg) const { const float weight = GetBlendingWeight(); if (weight >= 1.0f) return x18_b->VGetOffset(seg); const zeus::CVector3f oA = x14_a->VGetOffset(seg); const zeus::CVector3f oB = x18_b->VGetOffset(seg); return zeus::CVector3f::lerp(oA, oB, weight); } zeus::CQuaternion CAnimTreeTweenBase::VGetRotation(const CSegId& seg) const { const float weight = GetBlendingWeight(); if (weight >= 1.0f) return x18_b->VGetRotation(seg); const zeus::CQuaternion qA = x14_a->VGetRotation(seg); const zeus::CQuaternion qB = x18_b->VGetRotation(seg); return zeus::CQuaternion::slerp(qA, qB, weight); } std::optional> CAnimTreeTweenBase::VSimplified() { if (x20_25_cullSelector == 0) { auto simpA = x14_a->Simplified(); auto simpB = x18_b->Simplified(); if (!simpA && !simpB) return {}; auto clone = Clone(); if (simpA) static_cast(*clone).x14_a = CAnimTreeNode::Cast(std::move(*simpA)); if (simpB) static_cast(*clone).x18_b = CAnimTreeNode::Cast(std::move(*simpB)); return {std::move(clone)}; } else { auto tmp = (x20_25_cullSelector == 1) ? x18_b : x14_a; auto tmpUnblended = tmp->GetBestUnblendedChild(); if (!tmpUnblended) return {tmp->Clone()}; else return {tmpUnblended->Clone()}; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimTreeTweenBase.hpp ================================================ #pragma once #include #include #include "Runtime/rstl.hpp" #include "Runtime/Character/CAnimTreeDoubleChild.hpp" namespace metaforce { class CAnimTreeTweenBase : public CAnimTreeDoubleChild { static s32 sAdvancementDepth; protected: int x1c_flags; bool x20_24_b1 : 1; u8 x20_25_cullSelector : 2 = 0; public: CAnimTreeTweenBase(bool, const std::weak_ptr& a, const std::weak_ptr& b, int, std::string_view name); virtual void SetBlendingWeight(float w) = 0; virtual float VGetBlendingWeight() const = 0; float GetBlendingWeight() const { return VGetBlendingWeight(); } void VGetWeightedReaders(rstl::reserved_vector>, 16>& out, float w) const override; float VGetRightChildWeight() const override { return GetBlendingWeight(); } void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override; bool VHasOffset(const CSegId& seg) const override; zeus::CVector3f VGetOffset(const CSegId& seg) const override; zeus::CQuaternion VGetRotation(const CSegId& seg) const override; std::optional> VSimplified() override; virtual std::optional> VReverseSimplified() { return CAnimTreeTweenBase::VSimplified(); } static bool ShouldCullTree() { return 3 <= sAdvancementDepth; } static void IncAdvancementDepth() { sAdvancementDepth++; } static void DecAdvancementDepth() { sAdvancementDepth--; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimation.cpp ================================================ #include "Runtime/Character/CAnimation.hpp" #include "Runtime/Character/CMetaAnimFactory.hpp" namespace metaforce { CAnimation::CAnimation(CInputStream& in) { x0_name = in.Get(); x10_anim = CMetaAnimFactory::CreateMetaAnim(in); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimation.hpp ================================================ #pragma once #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CMetaAnimFactory.hpp" namespace metaforce { class IMetaAnim; class CAnimation { std::string x0_name; std::shared_ptr x10_anim; public: explicit CAnimation(CInputStream& in); const std::shared_ptr& GetMetaAnim() const { return x10_anim; } std::string_view GetMetaAnimName() const { return x0_name; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimationDatabase.hpp ================================================ #pragma once #include #include #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class CPrimitive; class IMetaAnim; class CAnimationDatabase { public: virtual ~CAnimationDatabase() = default; virtual const std::shared_ptr& GetMetaAnim(s32) const = 0; virtual u32 GetNumMetaAnims() const = 0; virtual const char* GetMetaAnimName(s32) const = 0; virtual void GetAllUniquePrimitives(std::vector&) const = 0; virtual void GetUniquePrimitivesFromMetaAnim(std::set&, std::string_view) const = 0; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimationDatabaseGame.cpp ================================================ #include "Runtime/Character/CAnimationDatabaseGame.hpp" #include "Runtime/Character/CAnimation.hpp" #include "Runtime/Character/CPrimitive.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { CAnimationDatabaseGame::CAnimationDatabaseGame(const std::vector& anims) { x10_anims.reserve(anims.size()); for (const CAnimation& anim : anims) x10_anims.emplace_back(anim.GetMetaAnim()); } const std::shared_ptr& CAnimationDatabaseGame::GetMetaAnim(s32 idx) const { return x10_anims[idx]; } u32 CAnimationDatabaseGame::GetNumMetaAnims() const { return x10_anims.size(); } const char* CAnimationDatabaseGame::GetMetaAnimName(s32 idx) const { return "Meta-animation name unavailable in Release mode."; } void CAnimationDatabaseGame::GetAllUniquePrimitives(std::vector& primsOut) const { std::set primitives; for (const std::shared_ptr& anim : x10_anims) anim->GetUniquePrimitives(primitives); primsOut.reserve(primitives.size()); for (const CPrimitive& prim : primitives) primsOut.push_back(prim); } void CAnimationDatabaseGame::GetUniquePrimitivesFromMetaAnim(std::set& primsOut, std::string_view name) const { // Empty } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimationDatabaseGame.hpp ================================================ #pragma once #include #include #include "Runtime/Character/CAnimationDatabase.hpp" namespace metaforce { class CAnimation; class CAnimationDatabaseGame final : public CAnimationDatabase { std::vector> x10_anims; public: explicit CAnimationDatabaseGame(const std::vector& anims); const std::shared_ptr& GetMetaAnim(s32 idx) const override; u32 GetNumMetaAnims() const override; const char* GetMetaAnimName(s32 idx) const override; void GetAllUniquePrimitives(std::vector& primsOut) const override; void GetUniquePrimitivesFromMetaAnim(std::set& primsOut, std::string_view name) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimationManager.cpp ================================================ #include "Runtime/Character/CAnimationManager.hpp" #include "Runtime/Character/CAnimationDatabaseGame.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { const CAnimationDatabaseGame* CAnimationManager::GetAnimationDatabase() const { return x0_animDB.GetObj(); } std::shared_ptr CAnimationManager::GetAnimationTree(s32 animIdx, const CMetaAnimTreeBuildOrders& orders) const { const std::shared_ptr& anim = x0_animDB->GetMetaAnim(animIdx); return anim->GetAnimationTree(x8_sysCtx, orders); } const std::shared_ptr& CAnimationManager::GetMetaAnimation(s32 idx) const { return x0_animDB->GetMetaAnim(idx); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimationManager.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/Character/CAnimationDatabaseGame.hpp" #include "Runtime/Character/CAnimSysContext.hpp" namespace metaforce { class CAnimTreeNode; class CSimplePool; class IMetaAnim; struct CMetaAnimTreeBuildOrders; class CAnimationManager { TToken x0_animDB; CAnimSysContext x8_sysCtx; public: CAnimationManager(TToken animDB, CAnimSysContext sysCtx) : x0_animDB(std::move(animDB)), x8_sysCtx(std::move(sysCtx)) {} const CAnimationDatabaseGame* GetAnimationDatabase() const; std::shared_ptr GetAnimationTree(s32, const CMetaAnimTreeBuildOrders& orders) const; const std::shared_ptr& GetMetaAnimation(s32) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimationSet.cpp ================================================ #include "Runtime/Character/CAnimationSet.hpp" #include "Runtime/Character/CMetaTransFactory.hpp" namespace metaforce { CAnimationSet::CAnimationSet(CInputStream& in) : x0_tableCount(in.ReadShort()) { u32 animationCount = in.ReadLong(); x4_animations.reserve(animationCount); for (u32 i = 0; i < animationCount; ++i) x4_animations.emplace_back(in); u32 transitionCount = in.ReadLong(); x14_transitions.reserve(transitionCount); for (u32 i = 0; i < transitionCount; ++i) x14_transitions.emplace_back(in); x24_defaultTransition = CMetaTransFactory::CreateMetaTrans(in); if (x0_tableCount > 1) { u32 additiveAnimCount = in.ReadLong(); x28_additiveInfo.reserve(additiveAnimCount); for (u32 i = 0; i < additiveAnimCount; ++i) { u32 id = in.ReadLong(); x28_additiveInfo.emplace_back(id, in); } x38_defaultAdditiveInfo.read(in); } if (x0_tableCount > 2) { u32 halfTransitionCount = in.ReadLong(); x40_halfTransitions.reserve(halfTransitionCount); for (u32 i = 0; i < halfTransitionCount; ++i) x40_halfTransitions.emplace_back(in); } if (x0_tableCount > 3) { u32 animResourcesCount = in.ReadLong(); x50_animRes.reserve(animResourcesCount); for (u32 i = 0; i < animResourcesCount; ++i) { CAssetId anim = in.Get(); CAssetId evnt = in.Get(); x50_animRes.emplace_back(anim, evnt); } } } } // namespace metaforce ================================================ FILE: Runtime/Character/CAnimationSet.hpp ================================================ #pragma once #include #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CAdditiveAnimPlayback.hpp" #include "Runtime/Character/CAnimation.hpp" #include "Runtime/Character/CHalfTransition.hpp" #include "Runtime/Character/CTransition.hpp" namespace metaforce { class CAnimationSet { u16 x0_tableCount; std::vector x4_animations; std::vector x14_transitions; std::shared_ptr x24_defaultTransition; std::vector> x28_additiveInfo; CAdditiveAnimationInfo x38_defaultAdditiveInfo; std::vector x40_halfTransitions; std::vector> x50_animRes; public: explicit CAnimationSet(CInputStream& in); const std::vector& GetAnimations() const { return x4_animations; } const std::vector& GetTransitions() const { return x14_transitions; } const std::shared_ptr& GetDefaultTransition() const { return x24_defaultTransition; } const std::vector& GetHalfTransitions() const { return x40_halfTransitions; } const std::vector>& GetAdditiveInfo() const { return x28_additiveInfo; } const CAdditiveAnimationInfo& GetDefaultAdditiveInfo() const { return x38_defaultAdditiveInfo; } const std::vector>& GetAnimResIds() const { return x50_animRes; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CAssetFactory.cpp ================================================ #include "Runtime/Character/CAssetFactory.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Character/CAnimCharacterSet.hpp" #include "Runtime/Character/CCharacterFactory.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CModelData.hpp" namespace metaforce { CFactoryFnReturn CCharacterFactoryBuilder::CDummyFactory::Build(const SObjectTag& tag, const CVParamTransfer&, CObjectReference* selfRef) { TLockedToken ancs = g_SimplePool->GetObj({SBIG('ANCS'), tag.id}); return TToken::GetIObjObjectFor( std::make_unique(*g_SimplePool, *ancs.GetObj(), tag.id)); } void CCharacterFactoryBuilder::CDummyFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& parms, std::unique_ptr* objOut, CObjectReference* selfRef) { *objOut = Build(tag, parms, selfRef); } void CCharacterFactoryBuilder::CDummyFactory::CancelBuild(const SObjectTag&) {} bool CCharacterFactoryBuilder::CDummyFactory::CanBuild(const SObjectTag&) { return true; } const SObjectTag* CCharacterFactoryBuilder::CDummyFactory::GetResourceIdByName(std::string_view) const { return nullptr; } FourCC CCharacterFactoryBuilder::CDummyFactory::GetResourceTypeById(CAssetId id) const { return {}; } void CCharacterFactoryBuilder::CDummyFactory::EnumerateResources( const std::function& lambda) const {} void CCharacterFactoryBuilder::CDummyFactory::EnumerateNamedResources( const std::function& lambda) const {} u32 CCharacterFactoryBuilder::CDummyFactory::ResourceSize(const metaforce::SObjectTag& tag) { return 0; } std::shared_ptr CCharacterFactoryBuilder::CDummyFactory::LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) { return {}; } std::shared_ptr CCharacterFactoryBuilder::CDummyFactory::LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size, void* target) { return {}; } std::unique_ptr CCharacterFactoryBuilder::CDummyFactory::LoadResourceSync(const metaforce::SObjectTag& tag) { return {}; } std::unique_ptr CCharacterFactoryBuilder::CDummyFactory::LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) { return {}; } CCharacterFactoryBuilder::CCharacterFactoryBuilder() : x4_dummyStore(x0_dummyFactory) {} TToken CCharacterFactoryBuilder::GetFactory(const CAnimRes& res) { return x4_dummyStore.GetObj({SBIG('ANCS'), res.GetId()}); } } // namespace metaforce ================================================ FILE: Runtime/Character/CAssetFactory.hpp ================================================ #pragma once #include #include #include "Runtime/CSimplePool.hpp" #include "Runtime/CToken.hpp" #include "Runtime/IFactory.hpp" #include "Runtime/IObj.hpp" namespace metaforce { class CCharacterFactory; class CAnimRes; class CCharacterFactoryBuilder { public: class CDummyFactory : public IFactory { public: CFactoryFnReturn Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override; void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr*, CObjectReference* selfRef) override; void CancelBuild(const SObjectTag&) override; bool CanBuild(const SObjectTag&) override; const SObjectTag* GetResourceIdByName(std::string_view) const override; FourCC GetResourceTypeById(CAssetId id) const override; void EnumerateResources(const std::function& lambda) const override; void EnumerateNamedResources(const std::function& lambda) const override; u32 ResourceSize(const metaforce::SObjectTag& tag) override; std::shared_ptr LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) override; std::shared_ptr LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size, void* target) override; std::unique_ptr LoadResourceSync(const metaforce::SObjectTag& tag) override; std::unique_ptr LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) override; }; private: CDummyFactory x0_dummyFactory; CSimplePool x4_dummyStore; public: CCharacterFactoryBuilder(); TToken GetFactory(const CAnimRes& res); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyController.cpp ================================================ #include "Runtime/Character/CBodyController.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Character/CPASAnimParm.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" #include "Runtime/World/CActor.hpp" #include "Runtime/World/CActorModelParticles.hpp" #include "Runtime/World/CPhysicsActor.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CBodyController::CBodyController(CActor& actor, float turnSpeed, EBodyType bodyType) : x0_actor(actor), x2a4_bodyStateInfo(actor, bodyType), x2f4_bodyType(bodyType), x2fc_turnSpeed(turnSpeed) { x2a4_bodyStateInfo.x18_bodyController = this; } void CBodyController::EnableAnimation(bool enable) { x0_actor.GetModelData()->GetAnimationData()->EnableAnimation(enable); } void CBodyController::Activate(CStateManager& mgr) { x300_25_active = true; x2a4_bodyStateInfo.SetState(pas::EAnimationState(GetPASDatabase().GetDefaultState())); x2a4_bodyStateInfo.GetCurrentState()->Start(*this, mgr); x2a4_bodyStateInfo.GetCurrentAdditiveState()->Start(*this, mgr); } void CBodyController::UpdateBody(float dt, CStateManager& mgr) { UpdateFrozenInfo(dt, mgr); if (x320_fireDur > 0.f) { if (x328_timeOnFire > x320_fireDur) { x328_timeOnFire = 0.f; x320_fireDur = 0.f; } else { x328_timeOnFire += dt; } } else if (x324_electrocutionDur > 0.f) { if (x32c_timeElectrocuting > x324_electrocutionDur) { x32c_timeElectrocuting = 0.f; x324_electrocutionDur = 0.f; } else { x32c_timeElectrocuting += dt; } } if (GetPercentageFrozen() < 1.f && x300_28_playDeathAnims) { pas::EAnimationState nextState = x2a4_bodyStateInfo.GetCurrentState()->UpdateBody(dt, *this, mgr); if (nextState != pas::EAnimationState::Invalid) { x2a4_bodyStateInfo.GetCurrentState()->Shutdown(*this); x2a4_bodyStateInfo.SetState(nextState); x2a4_bodyStateInfo.GetCurrentState()->Start(*this, mgr); } nextState = x2a4_bodyStateInfo.GetCurrentAdditiveState()->UpdateBody(dt, *this, mgr); if (nextState != pas::EAnimationState::Invalid) { x2a4_bodyStateInfo.GetCurrentAdditiveState()->Shutdown(*this); x2a4_bodyStateInfo.SetAdditiveState(nextState); x2a4_bodyStateInfo.GetCurrentAdditiveState()->Start(*this, mgr); } } } void CBodyController::SetTurnSpeed(float speed) { x2fc_turnSpeed = std::max(0.f, speed); } void CBodyController::Update(float dt, CStateManager& mgr) { SetPlaybackRate(1.f); if (x300_25_active) { x300_24_animationOver = !x0_actor.GetModelData()->GetAnimationData()->IsAnimTimeRemaining(dt, "Whole Body"sv); x4_cmdMgr.BlendSteeringCmds(); x2dc_rot = zeus::CQuaternion(); UpdateBody(dt, mgr); if (TCastToPtr act = x0_actor) act->RotateInOneFrameOR(x2dc_rot, dt); x4_cmdMgr.Reset(); } } bool CBodyController::HasBodyState(pas::EAnimationState state) const { return GetPASDatabase().HasState(state); } void CBodyController::SetCurrentAnimation(const CAnimPlaybackParms& parms, bool loop, bool noTrans) { x0_actor.GetModelData()->GetAnimationData()->SetAnimation(parms, noTrans); x0_actor.GetModelData()->EnableLooping(loop); x2f8_curAnim = parms.GetAnimationId(); } float CBodyController::GetAnimTimeRemaining() const { return x0_actor.GetModelData()->GetAnimationData()->GetAnimTimeRemaining("Whole Body"); } void CBodyController::SetPlaybackRate(float rate) { x0_actor.GetModelData()->GetAnimationData()->SetPlaybackRate(rate); } // GX uses a HW approximation of 3/8 + 5/8 instead of 1/3 + 2/3. const CPASDatabase& CBodyController::GetPASDatabase() const { return x0_actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase(); } void CBodyController::MultiplyPlaybackRate(float mul) { x0_actor.GetModelData()->GetAnimationData()->MultiplyPlaybackRate(mul); } void CBodyController::FaceDirection(const zeus::CVector3f& v0, float dt) { if (x300_26_frozen) return; zeus::CVector3f noZ = v0; noZ.z() = 0.f; if (noZ.canBeNormalized()) { if (TCastToPtr act = x0_actor) { zeus::CQuaternion rot = zeus::CQuaternion::lookAt(act->GetTransform().basis[1], noZ.normalized(), zeus::degToRad(dt * x2fc_turnSpeed)); rot.setImaginary(act->GetTransform().transposeRotate(rot.getImaginary())); act->RotateInOneFrameOR(rot, dt); } } } void CBodyController::FaceDirection3D(const zeus::CVector3f& v0, const zeus::CVector3f& v1, float dt) { if (x300_26_frozen) return; if (v0.canBeNormalized() && v1.canBeNormalized()) { if (TCastToPtr act = x0_actor) { zeus::CUnitVector3f uv0 = v0; zeus::CUnitVector3f uv1 = v1; float dot = uv0.dot(uv1); if (!zeus::close_enough(dot, 1.f)) { if (dot < -0.9999f) { zeus::CQuaternion rot = zeus::CQuaternion::fromAxisAngle(act->GetTransform().basis[2], zeus::degToRad(dt * x2fc_turnSpeed)); rot.setImaginary(act->GetTransform().transposeRotate(rot.getImaginary())); act->RotateInOneFrameOR(rot, dt); } else { zeus::CQuaternion rot = zeus::CQuaternion::clampedRotateTo(uv1, uv0, zeus::degToRad(dt * x2fc_turnSpeed)); rot.setImaginary(x0_actor.GetTransform().transposeRotate(rot.getImaginary())); act->RotateInOneFrameOR(rot, dt); } } } } } bool CBodyController::HasBodyInfo(const CActor& actor) { return actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().GetNumAnimStates() != 0; } void CBodyController::PlayBestAnimation(const CPASAnimParmData& parms, CRandom16& r) { std::pair best = GetPASDatabase().FindBestAnimation(parms, r, -1); CAnimPlaybackParms playParms(best.second, -1, 1.f, true); SetCurrentAnimation(playParms, false, false); } void CBodyController::LoopBestAnimation(const CPASAnimParmData& parms, CRandom16& r) { std::pair best = GetPASDatabase().FindBestAnimation(parms, r, -1); CAnimPlaybackParms playParms(best.second, -1, 1.f, true); SetCurrentAnimation(playParms, true, false); } void CBodyController::Freeze(float intoFreezeDur, float frozenDur, float breakoutDur) { x304_intoFreezeDur = intoFreezeDur; x308_frozenDur = frozenDur; x30c_breakoutDur = breakoutDur; x300_26_frozen = true; x300_27_hasBeenFrozen = true; if (TCastToPtr act = x0_actor) { x314_backedUpForce = act->GetConstantForce(); act->SetConstantForce(zeus::skZero3f); act->SetMomentumWR(zeus::skZero3f); } x320_fireDur = 0.f; x328_timeOnFire = 0.f; x310_timeFrozen = 0.f; } void CBodyController::UnFreeze() { SetPlaybackRate(1.f); x300_26_frozen = false; x304_intoFreezeDur = 0.f; x308_frozenDur = 0.f; x30c_breakoutDur = 0.f; x310_timeFrozen = 0.f; x0_actor.SetVolume(1.f); if (TCastToPtr act = x0_actor) { act->SetConstantForce(x314_backedUpForce); act->SetVelocityWR(x314_backedUpForce * (1.f / act->GetMass())); } } float CBodyController::GetPercentageFrozen() const { float sum = x304_intoFreezeDur + x308_frozenDur + x30c_breakoutDur; if (x310_timeFrozen == 0.f || sum == 0.f) return 0.f; if (x310_timeFrozen <= x304_intoFreezeDur && x304_intoFreezeDur > 0.f) return x310_timeFrozen / x304_intoFreezeDur; if (x310_timeFrozen < sum - x30c_breakoutDur) return 1.f; if (x30c_breakoutDur <= 0.f) return 1.f; return 1.f - (x310_timeFrozen - (x308_frozenDur + x304_intoFreezeDur)) / x30c_breakoutDur; } void CBodyController::SetOnFire(float duration) { x320_fireDur = duration; x328_timeOnFire = 0.f; if (IsFrozen()) UnFreeze(); } void CBodyController::DouseFlames() { if (x320_fireDur <= 0.f) return; x320_fireDur = 0.f; x328_timeOnFire = 0.f; } void CBodyController::SetElectrocuting(float duration) { if (!IsElectrocuting()) { CBCAdditiveReactionCmd reaction(pas::EAdditiveReactionType::Electrocution, 1.f, true); x4_cmdMgr.DeliverCmd(reaction); } x324_electrocutionDur = duration; x32c_timeElectrocuting = 0.f; if (IsFrozen()) UnFreeze(); else if (IsOnFire()) DouseFlames(); } void CBodyController::DouseElectrocuting() { x324_electrocutionDur = 0.f; x32c_timeElectrocuting = 0.f; CBodyStateCmd cmd(EBodyStateCmd::StopReaction); x4_cmdMgr.DeliverCmd(cmd); } void CBodyController::UpdateFrozenInfo(float dt, CStateManager& mgr) { if (x300_26_frozen) { float totalTime = x304_intoFreezeDur + x308_frozenDur + x30c_breakoutDur; if (x310_timeFrozen > totalTime && x2a4_bodyStateInfo.GetCurrentAdditiveStateId() != pas::EAnimationState::AdditiveReaction) { UnFreeze(); x0_actor.SendScriptMsgs(EScriptObjectState::UnFrozen, mgr, EScriptObjectMessage::None); mgr.GetActorModelParticles()->StartIce(x0_actor); return; } if (x310_timeFrozen <= totalTime) { float percUnfrozen = 1.f; if (x310_timeFrozen < totalTime - x30c_breakoutDur) percUnfrozen = 1.f - GetPercentageFrozen(); MultiplyPlaybackRate(percUnfrozen); x310_timeFrozen += dt; x0_actor.SetVolume(percUnfrozen); if (x310_timeFrozen > totalTime && HasIceBreakoutState()) { CBCAdditiveReactionCmd cmd(pas::EAdditiveReactionType::IceBreakout, 1.f, false); x4_cmdMgr.DeliverCmd(cmd); } } } } bool CBodyController::HasIceBreakoutState() const { CPASAnimParmData parms(pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(static_cast(pas::EAdditiveReactionType::IceBreakout))); std::pair best = GetPASDatabase().FindBestAnimation(parms, -1); return best.first > 0.f; } void CBodyController::StopElectrocution() { x324_electrocutionDur = 0.f; x32c_timeElectrocuting = 0.f; x4_cmdMgr.DeliverCmd(CBodyStateCmd(EBodyStateCmd::StopReaction)); } void CBodyController::FrozenBreakout() { if (x300_26_frozen) { float timeToBreakout = x304_intoFreezeDur + x308_frozenDur; if (x310_timeFrozen < timeToBreakout) x310_timeFrozen = timeToBreakout; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyController.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CBodyStateCmdMgr.hpp" #include "Runtime/Character/CBodyStateInfo.hpp" #include "Runtime/Character/CharacterCommon.hpp" #include #include namespace metaforce { class CActor; class CAnimPlaybackParms; class CPASAnimParmData; class CPASDatabase; class CRandom16; class CStateManager; struct CFinalInput; class CBodyController { CActor& x0_actor; CBodyStateCmdMgr x4_cmdMgr; CBodyStateInfo x2a4_bodyStateInfo; zeus::CQuaternion x2dc_rot; pas::ELocomotionType x2ec_locomotionType = pas::ELocomotionType::Relaxed; pas::EFallState x2f0_fallState = pas::EFallState::Zero; EBodyType x2f4_bodyType; s32 x2f8_curAnim = -1; float x2fc_turnSpeed; bool x300_24_animationOver : 1 = false; bool x300_25_active : 1 = false; bool x300_26_frozen : 1 = false; bool x300_27_hasBeenFrozen : 1 = false; bool x300_28_playDeathAnims : 1 = true; float x304_intoFreezeDur = 0.f; float x308_frozenDur = 0.f; float x30c_breakoutDur = 0.f; float x310_timeFrozen = 0.f; zeus::CVector3f x314_backedUpForce; float x320_fireDur = 0.f; float x324_electrocutionDur = 0.f; float x328_timeOnFire = 0.f; float x32c_timeElectrocuting = 0.f; float x330_restrictedFlyerMoveSpeed = 0.f; public: CBodyController(CActor& owner, float turnSpeed, EBodyType bodyType); pas::EAnimationState GetCurrentStateId() const { return x2a4_bodyStateInfo.GetCurrentStateId(); } CBodyStateCmdMgr& GetCommandMgr() { return x4_cmdMgr; } const CBodyStateCmdMgr& GetCommandMgr() const { return x4_cmdMgr; } void SetDoDeathAnims(bool d) { x300_28_playDeathAnims = d; } bool IsElectrocuting() const { return x324_electrocutionDur > 0.f; } bool IsOnFire() const { return x320_fireDur > 0.f; } bool IsFrozen() const { return x300_26_frozen; } const CBodyStateInfo& GetBodyStateInfo() const { return x2a4_bodyStateInfo; } CBodyStateInfo& BodyStateInfo() { return x2a4_bodyStateInfo; } float GetTurnSpeed() const { return x2fc_turnSpeed; } void SetLocomotionType(pas::ELocomotionType type) { x2ec_locomotionType = type; } pas::ELocomotionType GetLocomotionType() const { return x2ec_locomotionType; } CActor& GetOwner() const { return x0_actor; } bool IsAnimationOver() const { return x300_24_animationOver; } void EnableAnimation(bool enable); bool ShouldPlayDeathAnims() const { return x300_28_playDeathAnims; } s32 GetCurrentAnimId() const { return x2f8_curAnim; } void Activate(CStateManager& mgr); CAdditiveBodyState* GetCurrentAdditiveState() { return x2a4_bodyStateInfo.GetCurrentAdditiveState(); } void SetState(pas::EAnimationState state) { x2a4_bodyStateInfo.SetState(state); } void Update(float dt, CStateManager& mgr); bool ShouldBeHurled() const { return HasBodyState(pas::EAnimationState::Hurled); } bool HasBodyState(pas::EAnimationState state) const; pas::EFallState GetFallState() const { return x2f0_fallState; } void SetFallState(pas::EFallState state) { x2f0_fallState = state; } void UpdateBody(float dt, CStateManager& mgr); void SetAdditiveState(pas::EAnimationState state) { x2a4_bodyStateInfo.SetAdditiveState(state); } void SetTurnSpeed(float speed); void SetCurrentAnimation(const CAnimPlaybackParms& parms, bool loop, bool noTrans); float GetAnimTimeRemaining() const; void SetPlaybackRate(float rate); void MultiplyPlaybackRate(float mul); void SetDeltaRotation(const zeus::CQuaternion& q) { x2dc_rot *= q; } void FaceDirection(const zeus::CVector3f& v0, float dt); void FaceDirection3D(const zeus::CVector3f& v0, const zeus::CVector3f& v1, float dt); static bool HasBodyInfo(const CActor& actor); const CPASDatabase& GetPASDatabase() const; void PlayBestAnimation(const CPASAnimParmData& parms, CRandom16& r); void LoopBestAnimation(const CPASAnimParmData& parms, CRandom16& r); void Freeze(float intoFreezeDur, float frozenDur, float breakoutDur); void UnFreeze(); float GetPercentageFrozen() const; void SetOnFire(float duration); void DouseFlames(); void SetElectrocuting(float duration); void DouseElectrocuting(); void UpdateFrozenInfo(float dt, CStateManager& mgr); bool HasIceBreakoutState() const; void StopElectrocution(); void FrozenBreakout(); pas::EAnimationState GetCurrentAdditiveStateId() const { return x2a4_bodyStateInfo.GetCurrentAdditiveStateId(); } EBodyType GetBodyType() const { return x2f4_bodyType; } bool HasBeenFrozen() const { return x300_27_hasBeenFrozen; } float GetRestrictedFlyerMoveSpeed() const { return x330_restrictedFlyerMoveSpeed; } void SetRestrictedFlyerMoveSpeed(float speed) { x330_restrictedFlyerMoveSpeed = speed; } bool GetActive() const { return x300_25_active; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyState.cpp ================================================ #include "Runtime/Character/CBodyState.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Character/CBodyController.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" #include "Runtime/World/CActor.hpp" #include "Runtime/World/CPatterned.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { void CBSAttack::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)); const CPASDatabase& pasDatabase = bc.GetPASDatabase(); const CPASAnimParmData parms(pas::EAnimationState::MeleeAttack, CPASAnimParm::FromEnum(s32(cmd->GetAttackSeverity())), CPASAnimParm::FromEnum(s32(bc.GetLocomotionType()))); const std::pair best = pasDatabase.FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); if (cmd->HasAttackTargetPos()) { x20_targetPos = cmd->GetAttackTargetPos(); CCharAnimTime evTime = bc.GetOwner().GetModelData()->GetAnimationData()->GetTimeOfUserEvent( EUserEventType::AlignTargetPosStart, CCharAnimTime::Infinity()); x2c_alignTargetPosStartTime = (evTime != CCharAnimTime::Infinity()) ? evTime.GetSeconds() : 0.f; evTime = bc.GetOwner().GetModelData()->GetAnimationData()->GetTimeOfUserEvent(EUserEventType::AlignTargetPos, CCharAnimTime::Infinity()); x30_alignTargetPosTime = (evTime != CCharAnimTime::Infinity()) ? evTime.GetSeconds() : bc.GetAnimTimeRemaining(); } else { x20_targetPos = zeus::skZero3f; x2c_alignTargetPosStartTime = -1.f; x30_alignTargetPosTime = -1.f; } x4_nextState = pas::EAnimationState::Locomotion; x34_curTime = 0.f; } pas::EAnimationState CBSAttack::GetBodyStateTransition(float dt, const CBodyController& bc) { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } if (const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Slide))) { x8_slide = *cmd; x4_nextState = pas::EAnimationState::Slide; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) { return pas::EAnimationState::Generate; } if (bc.IsAnimationOver()) { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) { return pas::EAnimationState::MeleeAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) { return pas::EAnimationState::ProjectileAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) { return pas::EAnimationState::LoopAttack; } return x4_nextState; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) { return x4_nextState; } return pas::EAnimationState::Invalid; } void CBSAttack::UpdatePhysicsActor(const CBodyController& bc, float dt) { if (x20_targetPos.isZero()) { return; } if (x34_curTime < x2c_alignTargetPosStartTime || x34_curTime > x30_alignTargetPosTime) { return; } if (const TCastToPtr act = bc.GetOwner()) { zeus::CVector3f delta = x20_targetPos - act->GetTranslation(); const float dur = x30_alignTargetPosTime - x2c_alignTargetPosStartTime; if (dur > 0.f) { delta *= zeus::CVector3f(dt / dur); } const zeus::CVector3f localDelta = act->GetTransform().transposeRotate(delta); act->ApplyImpulseWR(act->GetMoveToORImpulseWR(localDelta, dt), zeus::CAxisAngle()); } } pas::EAnimationState CBSAttack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { x34_curTime += dt; const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } UpdatePhysicsActor(bc, dt); } else if (st == pas::EAnimationState::Slide) { bc.GetCommandMgr().DeliverCmd(x8_slide); } return st; } void CBSProjectileAttack::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)); zeus::CVector3f localDelta = bc.GetOwner().GetTransform().transposeRotate(cmd->GetTargetPosition() - bc.GetOwner().GetTranslation()); zeus::CRelAngle angle = std::atan2(localDelta.y(), localDelta.x()); angle.makeRel(); const float attackAngle = angle.asDegrees(); const CPASAnimParmData parms( pas::EAnimationState::ProjectileAttack, CPASAnimParm::FromEnum(s32(cmd->GetAttackSeverity())), CPASAnimParm::FromReal32(angle.asDegrees()), CPASAnimParm::FromEnum(s32(bc.GetLocomotionType()))); const std::pair best1 = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (cmd->BlendTwoClosest()) { const std::pair best2 = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), best1.second); const CPASAnimState* projAttackState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::ProjectileAttack); float angle1 = projAttackState->GetAnimParmData(best1.second, 1).GetReal32Value(); float angle2 = projAttackState->GetAnimParmData(best2.second, 1).GetReal32Value(); if (angle1 - angle2 > 180.f) { angle2 += 360.f; } else if (angle2 - angle1 > 180.f) { angle1 += 360.f; } const CAnimPlaybackParms playParms(best1.second, best2.second, (angle1 - attackAngle) / (angle1 - angle2), true); bc.SetCurrentAnimation(playParms, false, false); } else { const CAnimPlaybackParms playParms(best1.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } } pas::EAnimationState CBSProjectileAttack::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } if (bc.IsAnimationOver() || bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSProjectileAttack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } } return st; } void CBSDie::Start(CBodyController& bc, CStateManager& mgr) { bool shouldReset = true; if (bc.ShouldPlayDeathAnims()) { const CPASAnimParmData parms(pas::EAnimationState::Death, CPASAnimParm::FromEnum(s32(bc.GetFallState()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > 0.f) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); x4_remTime = bc.GetAnimTimeRemaining(); shouldReset = false; } } if (shouldReset) { bc.EnableAnimation(false); x4_remTime = bc.ShouldPlayDeathAnims() ? 3.f : 4.f; } x8_isDead = false; } pas::EAnimationState CBSDie::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { x4_remTime -= dt; if (x4_remTime <= 0.f) { bc.EnableAnimation(false); x8_isDead = true; } return pas::EAnimationState::Invalid; } void CBSFall::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)); zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection()); zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x()); angle.makeRel(); const CPASAnimParmData parms(pas::EAnimationState::Fall, CPASAnimParm::FromReal32(angle.asDegrees()), CPASAnimParm::FromEnum(s32(cmd->GetHitSeverity()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); const CPASAnimState* knockdownState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Fall); if (!knockdownState->GetAnimParmData(best.second, 2).GetBoolValue()) { const float animAngle = zeus::degToRad(knockdownState->GetAnimParmData(best.second, 0).GetReal32Value()); zeus::CRelAngle delta1 = angle - animAngle; delta1.makeRel(); zeus::CRelAngle delta2 = animAngle - angle; delta2.makeRel(); const float minAngle = std::min(float(delta1), float(delta2)); x8_remTime = 0.15f * bc.GetAnimTimeRemaining(); const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle; x4_rotateSpeed = (x8_remTime > FLT_EPSILON) ? flippedAngle / x8_remTime : flippedAngle; } else { x8_remTime = 0.f; x4_rotateSpeed = 0.f; } xc_fallState = pas::EFallState(knockdownState->GetAnimParmData(best.second, 3).GetEnumValue()); } pas::EAnimationState CBSFall::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.IsAnimationOver()) { return pas::EAnimationState::LieOnGround; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSFall::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid && x8_remTime > 0.f) { zeus::CQuaternion quat; quat.rotateZ(x4_rotateSpeed * dt); bc.SetDeltaRotation(quat); x8_remTime -= dt; } return st; } void CBSFall::Shutdown(CBodyController& bc) { bc.SetFallState(xc_fallState); } void CBSGetup::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Getup)); const CPASAnimParmData parms(pas::EAnimationState::Getup, CPASAnimParm::FromEnum(s32(bc.GetFallState())), CPASAnimParm::FromEnum(s32(cmd->GetGetupType()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > FLT_EPSILON) { if (best.second != bc.GetCurrentAnimId()) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } x4_fallState = pas::EFallState( bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Getup)->GetAnimParmData(best.second, 2).GetEnumValue()); } else { x4_fallState = pas::EFallState::Zero; } } pas::EAnimationState CBSGetup::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.IsAnimationOver()) { if (x4_fallState == pas::EFallState::Zero) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Getup; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSGetup::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { return GetBodyStateTransition(dt, bc); } void CBSGetup::Shutdown(CBodyController& bc) { bc.SetFallState(x4_fallState); } void CBSKnockBack::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)); const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection()); zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x()); angle.makeRel(); const CPASAnimParmData parms(pas::EAnimationState::KnockBack, CPASAnimParm::FromReal32(angle.asDegrees()), CPASAnimParm::FromEnum(s32(cmd->GetHitSeverity()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); const CPASAnimState* knockbackState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::KnockBack); if (!knockbackState->GetAnimParmData(best.second, 2).GetBoolValue()) { const float animAngle = zeus::degToRad(knockbackState->GetAnimParmData(best.second, 0).GetReal32Value()); zeus::CRelAngle delta1 = angle - animAngle; delta1.makeRel(); zeus::CRelAngle delta2 = animAngle - angle; delta2.makeRel(); const float minAngle = std::min(float(delta1), float(delta2)); const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle; xc_remTime = 0.15f * bc.GetAnimTimeRemaining(); x8_rotateSpeed = (xc_remTime > FLT_EPSILON) ? flippedAngle / xc_remTime : flippedAngle; } else { xc_remTime = 0.f; x8_rotateSpeed = 0.f; } x4_curTime = 0.f; } pas::EAnimationState CBSKnockBack::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack) && x4_curTime > 0.2f) { return pas::EAnimationState::KnockBack; } if (bc.IsAnimationOver()) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSKnockBack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { x4_curTime += dt; if (xc_remTime > 0.f) { zeus::CQuaternion quat; quat.rotateZ(x8_rotateSpeed * dt); bc.SetDeltaRotation(quat); xc_remTime -= dt; } } return st; } CBSLieOnGround::CBSLieOnGround(CActor& actor) { x4_24_hasGroundHit = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().HasState( pas::EAnimationState::GroundHit); } void CBSLieOnGround::Start(CBodyController& bc, CStateManager& mgr) { const CPASAnimParmData parms(pas::EAnimationState::LieOnGround, CPASAnimParm::FromEnum(s32(bc.GetFallState()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > 0.f) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); } else { bc.EnableAnimation(false); } } pas::EAnimationState CBSLieOnGround::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Die)) { return pas::EAnimationState::Death; } if (x4_24_hasGroundHit && bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::GroundHit; } if (!bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion) && bc.GetCommandMgr().GetCmd(EBodyStateCmd::Getup)) { return pas::EAnimationState::Getup; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSLieOnGround::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { return GetBodyStateTransition(dt, bc); } void CBSLieOnGround::Shutdown(CBodyController& bc) { bc.EnableAnimation(true); } void CBSStep::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)); const CPASAnimParmData parms(pas::EAnimationState::Step, CPASAnimParm::FromEnum(s32(cmd->GetStepDirection())), CPASAnimParm::FromEnum(s32(cmd->GetStepType()))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); } pas::EAnimationState CBSStep::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) { return pas::EAnimationState::Generate; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) { return pas::EAnimationState::MeleeAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) { return pas::EAnimationState::ProjectileAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) { return pas::EAnimationState::LoopAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump)) { return pas::EAnimationState::Jump; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) { return pas::EAnimationState::Scripted; } if (bc.IsAnimationOver() || bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSStep::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid && !bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } return st; } void CBSTurn::Start(CBodyController& bc, CStateManager& mgr) { const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1]; const zeus::CVector2f lookDir2d(lookDir.toVec2f()); x8_dest = zeus::CVector2f(bc.GetCommandMgr().GetFaceVector().toVec2f()); const float deltaAngle = zeus::radToDeg(zeus::CVector2f::getAngleDiff(lookDir2d, x8_dest)); x10_turnDir = pas::ETurnDirection(zeus::CVector2f(lookDir2d.y(), -lookDir2d.x()).dot(x8_dest) > 0.f); const CPASAnimParmData parms(pas::EAnimationState::Turn, CPASAnimParm::FromEnum(s32(x10_turnDir)), CPASAnimParm::FromReal32(deltaAngle), CPASAnimParm::FromEnum(s32(bc.GetLocomotionType()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); const float animAngle = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Turn)->GetAnimParmData(best.second, 1).GetReal32Value(); x4_rotateSpeed = zeus::degToRad((x10_turnDir == pas::ETurnDirection::Left) ? animAngle - deltaAngle : deltaAngle - animAngle); const float timeRem = bc.GetAnimTimeRemaining(); if (timeRem > 0.f) { x4_rotateSpeed /= timeRem; } } bool CBSTurn::FacingDest(const CBodyController& bc) const { const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1]; const zeus::CVector2f lookDir2d(lookDir.toVec2f()); const zeus::CVector2f leftDir(lookDir2d.y(), -lookDir2d.x()); if (x10_turnDir == pas::ETurnDirection::Left) { if (leftDir.dot(x8_dest) < 0.f) { return true; } } else { if (leftDir.dot(x8_dest) > 0.f) { return true; } } return false; } pas::EAnimationState CBSTurn::GetBodyStateTransition(float dt, CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) { return pas::EAnimationState::Generate; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) { return pas::EAnimationState::MeleeAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) { return pas::EAnimationState::ProjectileAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) { return pas::EAnimationState::LoopAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump)) { return pas::EAnimationState::Jump; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) { return pas::EAnimationState::Step; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) { return pas::EAnimationState::Scripted; } if (bc.IsAnimationOver() || FacingDest(bc) || !bc.GetCommandMgr().GetMoveVector().isZero()) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSTurn::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { zeus::CQuaternion quat; quat.rotateZ(x4_rotateSpeed * dt); bc.SetDeltaRotation(quat); } return st; } void CBSFlyerTurn::Start(CBodyController& bc, CStateManager& mgr) { if (bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Turn)->HasAnims()) { CBSTurn::Start(bc, mgr); } else { x8_dest = zeus::CVector2f(bc.GetCommandMgr().GetFaceVector().toVec2f()); const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1]; const zeus::CVector2f lookDir2d(lookDir.toVec2f()); x10_turnDir = pas::ETurnDirection(zeus::CVector2f(lookDir2d.y(), -lookDir2d.x()).dot(x8_dest) > 0.f); const CPASAnimParmData parms(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0), CPASAnimParm::FromEnum(s32(bc.GetLocomotionType()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.second != bc.GetCurrentAnimId()) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); } } } pas::EAnimationState CBSFlyerTurn::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { if (bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Turn)->HasAnims()) { return CBSTurn::UpdateBody(dt, bc, mgr); } const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { if (!bc.GetCommandMgr().GetFaceVector().isZero()) { x8_dest = zeus::CVector2f(bc.GetCommandMgr().GetFaceVector().toVec2f()); const zeus::CVector3f& lookDir = bc.GetOwner().GetTransform().basis[1]; const zeus::CVector2f lookDir2d(lookDir.toVec2f()); x10_turnDir = pas::ETurnDirection(zeus::CVector2f(lookDir2d.y(), -lookDir2d.x()).dot(x8_dest) > 0.f); } bc.FaceDirection(zeus::CVector3f(x8_dest.x(), x8_dest.y(), 0.f), dt); } return st; } void CBSLoopAttack::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)); x8_loopAttackType = cmd->GetAttackType(); xc_24_waitForAnimOver = cmd->WaitForAnimOver(); xc_25_advance = false; if (bc.GetLocomotionType() == pas::ELocomotionType::Crouch) { x4_state = pas::ELoopState::Loop; const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_loopAttackType))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } else { x4_state = pas::ELoopState::Begin; const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_loopAttackType))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > FLT_EPSILON) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } else { x4_state = pas::ELoopState::Loop; const CPASAnimParmData loopParms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_loopAttackType))); bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom()); } } } pas::EAnimationState CBSLoopAttack::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } if (x4_state == pas::ELoopState::End) { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) { return pas::EAnimationState::MeleeAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) { return pas::EAnimationState::ProjectileAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) { return pas::EAnimationState::LoopAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) { return pas::EAnimationState::Step; } if (!bc.GetCommandMgr().GetMoveVector().isZero()) { return pas::EAnimationState::Locomotion; } if (!bc.GetCommandMgr().GetFaceVector().isZero()) { return pas::EAnimationState::Turn; } } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSLoopAttack::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { xc_25_advance |= bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState) != nullptr; switch (x4_state) { case pas::ELoopState::Begin: if (xc_25_advance && (!xc_24_waitForAnimOver || bc.IsAnimationOver())) { x4_state = pas::ELoopState::Invalid; return pas::EAnimationState::Locomotion; } if (bc.IsAnimationOver()) { const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(1), CPASAnimParm::FromEnum(s32(x8_loopAttackType))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); x4_state = pas::ELoopState::Loop; } else if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } break; case pas::ELoopState::Loop: if (xc_25_advance && (!xc_24_waitForAnimOver || bc.IsAnimationOver())) { if (bc.GetLocomotionType() != pas::ELocomotionType::Crouch) { const CPASAnimParmData parms(pas::EAnimationState::LoopAttack, CPASAnimParm::FromEnum(2), CPASAnimParm::FromEnum(s32(x8_loopAttackType))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); x4_state = pas::ELoopState::End; } else { x4_state = pas::ELoopState::Invalid; return pas::EAnimationState::Locomotion; } } break; case pas::ELoopState::End: if (bc.IsAnimationOver()) { x4_state = pas::ELoopState::Invalid; return pas::EAnimationState::Locomotion; } break; default: break; } } return st; } void CBSLoopReaction::Start(CBodyController& bc, CStateManager& mgr) { if (const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction))) { x8_reactionType = cmd->GetReactionType(); xc_24_loopHit = false; } else { const auto* hcmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)); x8_reactionType = hcmd->GetReactionType(); xc_24_loopHit = true; } x4_state = pas::ELoopState::Begin; const CPASAnimParmData parms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(s32(x8_reactionType)), CPASAnimParm::FromEnum(s32(x4_state))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > FLT_EPSILON) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } else { x4_state = pas::ELoopState::Loop; const CPASAnimParmData loopParms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(s32(x8_reactionType)), CPASAnimParm::FromEnum(s32(x4_state))); // Intentionally using parms instead of loopParms bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } } pas::EAnimationState CBSLoopReaction::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (!xc_24_loopHit && bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } if (x4_state == pas::ELoopState::End) { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) { return pas::EAnimationState::MeleeAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) { return pas::EAnimationState::ProjectileAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) { return pas::EAnimationState::LoopAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) { return pas::EAnimationState::Step; } if (!bc.GetCommandMgr().GetMoveVector().isZero()) { return pas::EAnimationState::Locomotion; } if (!bc.GetCommandMgr().GetFaceVector().isZero()) { return pas::EAnimationState::Turn; } } return pas::EAnimationState::Invalid; } bool CBSLoopReaction::PlayExitAnimation(CBodyController& bc, CStateManager& mgr) const { const CPASAnimParmData parms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(int(x8_reactionType)), CPASAnimParm::FromEnum(2)); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > 0.f) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); return true; } return false; } pas::EAnimationState CBSLoopReaction::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { switch (x4_state) { case pas::ELoopState::Begin: if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) { if (PlayExitAnimation(bc, mgr)) { x4_state = pas::ELoopState::End; } else { x4_state = pas::ELoopState::Invalid; return pas::EAnimationState::Locomotion; } } else { if (bc.IsAnimationOver()) { const CPASAnimParmData parms(pas::EAnimationState::LoopReaction, CPASAnimParm::FromEnum(s32(x8_reactionType)), CPASAnimParm::FromEnum(1)); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); x4_state = pas::ELoopState::Loop; } else if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } } break; case pas::ELoopState::Loop: if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) { if (PlayExitAnimation(bc, mgr)) { x4_state = pas::ELoopState::End; } else { x4_state = pas::ELoopState::Invalid; return pas::EAnimationState::Locomotion; } } else if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } break; case pas::ELoopState::End: if (bc.IsAnimationOver()) { x4_state = pas::ELoopState::Invalid; return pas::EAnimationState::Locomotion; } break; default: break; } } return st; } void CBSGroundHit::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)); const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection()); zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x()); angle.makeRel(); const CPASAnimParmData parms(pas::EAnimationState::GroundHit, CPASAnimParm::FromEnum(s32(bc.GetFallState())), CPASAnimParm::FromReal32(angle.asDegrees())); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); const CPASAnimState* groundHitState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::GroundHit); if (!groundHitState->GetAnimParmData(best.second, 2).GetBoolValue()) { const float animAngle = zeus::degToRad(groundHitState->GetAnimParmData(best.second, 1).GetReal32Value()); zeus::CRelAngle delta1 = angle - animAngle; delta1.makeRel(); zeus::CRelAngle delta2 = animAngle - angle; delta2.makeRel(); const float minAngle = std::min(float(delta1), float(delta2)); const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle; x8_remTime = 0.15f * bc.GetAnimTimeRemaining(); x4_rotateSpeed = (x8_remTime > FLT_EPSILON) ? flippedAngle / x8_remTime : flippedAngle; } else { x8_remTime = 0.f; x4_rotateSpeed = 0.f; } xc_fallState = pas::EFallState(groundHitState->GetAnimParmData(best.second, 3).GetEnumValue()); } pas::EAnimationState CBSGroundHit::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (!bc.IsAnimationOver()) { return pas::EAnimationState::Invalid; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Die)) { return pas::EAnimationState::Death; } return pas::EAnimationState::LieOnGround; } pas::EAnimationState CBSGroundHit::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid && x8_remTime > 0.f) { zeus::CQuaternion quat; quat.rotateZ(x4_rotateSpeed * dt); bc.SetDeltaRotation(quat); x8_remTime -= dt; } return st; } void CBSGroundHit::Shutdown(CBodyController& bc) { bc.SetFallState(xc_fallState); } void CBSGenerate::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)); s32 anim; if (!cmd->UseSpecialAnimId()) { const CPASAnimParmData parms(pas::EAnimationState::Generate, CPASAnimParm::FromEnum(s32(cmd->GetGenerateType()))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); anim = best.second; } else { anim = cmd->GetSpecialAnimId(); } if (cmd->HasExitTargetPos()) { const CAnimPlaybackParms playParms(anim, nullptr, &cmd->GetExitTargetPos(), &bc.GetOwner().GetTransform(), &bc.GetOwner().GetModelData()->GetScale(), false); bc.SetCurrentAnimation(playParms, false, false); } else { const CAnimPlaybackParms playParms(anim, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } } pas::EAnimationState CBSGenerate::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) { return pas::EAnimationState::Generate; } if (bc.IsAnimationOver() || bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSGenerate::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } } return st; } void CBSJump::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump)); x8_jumpType = cmd->GetJumpType(); xc_waypoint1 = cmd->GetJumpTarget(); x24_waypoint2 = cmd->GetSecondJumpTarget(); x30_25_wallJump = cmd->IsWallJump(); x30_28_startInJumpLoop = cmd->StartInJumpLoop(); x30_24_bodyForceSet = false; x30_27_wallBounceComplete = false; if (x30_25_wallJump) { x30_26_wallBounceRight = (xc_waypoint1 - bc.GetOwner().GetTranslation()).cross(zeus::skUp).dot(x24_waypoint2 - xc_waypoint1) < 0.f; } if (!cmd->StartInJumpLoop()) { x4_state = pas::EJumpState::IntoJump; const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_jumpType))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); } else { PlayJumpLoop(mgr, bc); } } pas::EAnimationState CBSJump::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump) && bc.GetBodyType() == EBodyType::WallWalker) { return pas::EAnimationState::Jump; } return pas::EAnimationState::Invalid; } bool CBSJump::CheckForWallJump(CBodyController& bc, CStateManager& mgr) { if (!x30_25_wallJump || x30_27_wallBounceComplete) { return false; } if (const TCastToPtr act = bc.GetOwner()) { const float distToWall = (xc_waypoint1 - act->GetTranslation()).magnitude(); const zeus::CAABox aabb = act->GetBoundingBox(); const float xExtent = (aabb.max.x() - aabb.min.x()) * 0.5f; if (distToWall < 1.414f * xExtent || (act->MadeSolidCollision() && distToWall < 3.f * xExtent)) { x4_state = x30_26_wallBounceRight ? pas::EJumpState::WallBounceRight : pas::EJumpState::WallBounceLeft; const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_jumpType))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor); return true; } } return false; } void CBSJump::CheckForLand(CBodyController& bc, CStateManager& mgr) { if (const TCastToPtr act = bc.GetOwner()) { if (act->MadeSolidCollision() || act->IsOnGround()) { x4_state = pas::EJumpState::OutOfJump; const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_jumpType))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor); } } } void CBSJump::PlayJumpLoop(CStateManager& mgr, CBodyController& bc) { const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(1), CPASAnimParm::FromEnum(s32(x8_jumpType))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > 99.f) { x4_state = pas::EJumpState::AmbushJump; const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } else { x4_state = pas::EJumpState::Loop; const CPASAnimParmData loopParms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_jumpType))); bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom()); } if (const TCastToPtr act = bc.GetOwner()) { mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Falling); mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Jumped); x30_24_bodyForceSet = false; x18_velocity = act->GetVelocity(); } } pas::EAnimationState CBSJump::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { switch (x4_state) { case pas::EJumpState::IntoJump: if (bc.IsAnimationOver()) { PlayJumpLoop(mgr, bc); } break; case pas::EJumpState::AmbushJump: if (!x30_24_bodyForceSet) { if (const TCastToPtr act = bc.GetOwner()) { act->SetConstantForce(act->GetMass() * x18_velocity); } x30_24_bodyForceSet = true; } if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } if (bc.IsAnimationOver()) { x4_state = pas::EJumpState::Loop; const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_jumpType))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } else { if (!CheckForWallJump(bc, mgr)) { CheckForLand(bc, mgr); } } break; case pas::EJumpState::Loop: if (!x30_24_bodyForceSet) { if (const TCastToPtr act = bc.GetOwner()) { act->SetConstantForce(act->GetMass() * x18_velocity); } x30_24_bodyForceSet = true; } if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } if (!CheckForWallJump(bc, mgr)) { CheckForLand(bc, mgr); } break; case pas::EJumpState::WallBounceLeft: case pas::EJumpState::WallBounceRight: if (const TCastToPtr act = bc.GetOwner()) { act->Stop(); act->SetMomentumWR(zeus::skZero3f); } if (bc.IsAnimationOver()) { mgr.SendScriptMsg(&bc.GetOwner(), kInvalidUniqueId, EScriptObjectMessage::Falling); x4_state = pas::EJumpState::Loop; const CPASAnimParmData parms(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_jumpType))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); x30_27_wallBounceComplete = true; if (const TCastToPtr act = bc.GetOwner()) { const zeus::CVector3f toFinal = x24_waypoint2 - act->GetTranslation(); const float tmp = std::sqrt(act->GetGravityConstant() / (2.f * toFinal.z())); act->SetVelocityWR(zeus::CVector3f(tmp * toFinal.x(), tmp * toFinal.y(), 0.f)); } } break; case pas::EJumpState::OutOfJump: if (bc.IsAnimationOver()) { x4_state = pas::EJumpState::Invalid; return pas::EAnimationState::Locomotion; } break; default: break; } } return st; } bool CBSJump::ApplyAnimationDeltas() const { return !(x4_state == pas::EJumpState::AmbushJump || x4_state == pas::EJumpState::Loop); } bool CBSJump::IsInAir(const CBodyController& bc) const { return x4_state == pas::EJumpState::AmbushJump || x4_state == pas::EJumpState::Loop; } bool CBSJump::CanShoot() const { return x4_state == pas::EJumpState::AmbushJump || x4_state == pas::EJumpState::Loop; } void CBSHurled::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)); x4_state = pas::EHurledState(cmd->GetSkipLaunchState()); const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetHitDirection()); zeus::CRelAngle angle = std::atan2(localDir.y(), localDir.x()); angle.makeRel(); x8_knockAngle = angle.asDegrees(); const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(-1), CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(x4_state))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); const CPASAnimState* hurledState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Hurled); xc_animSeries = hurledState->GetAnimParmData(best.second, 0).GetInt32Value(); mgr.SendScriptMsg(&bc.GetOwner(), kInvalidUniqueId, EScriptObjectMessage::Falling); mgr.SendScriptMsg(&bc.GetOwner(), kInvalidUniqueId, EScriptObjectMessage::Jumped); if (!zeus::close_enough(cmd->GetLaunchVelocity(), zeus::skZero3f, 0.0001f)) { if (const TCastToPtr act = bc.GetOwner()) { act->SetConstantForce(act->GetMass() * cmd->GetLaunchVelocity()); } } const float animAngle = zeus::degToRad(hurledState->GetAnimParmData(best.second, 1).GetReal32Value()); zeus::CRelAngle delta1 = angle - animAngle; delta1.makeRel(); zeus::CRelAngle delta2 = animAngle - angle; delta2.makeRel(); const float minAngle = std::min(float(delta1), float(delta2)); x14_remTime = 0.15f * bc.GetAnimTimeRemaining(); const float flippedAngle = (delta1 > M_PIF) ? -minAngle : minAngle; x10_rotateSpeed = (x14_remTime > FLT_EPSILON) ? flippedAngle / x14_remTime : flippedAngle; x18_curTime = 0.f; x2c_24_needsRecover = false; } pas::EAnimationState CBSHurled::GetBodyStateTransition(float dt, CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::NextState)) { return pas::EAnimationState::LieOnGround; } if (x18_curTime > 0.25f) { if (auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled))) { cmd->SetSkipLaunchState(true); return pas::EAnimationState::Hurled; } } return pas::EAnimationState::Invalid; } void CBSHurled::Recover(CStateManager& mgr, CBodyController& bc, pas::EHurledState state) { const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries), CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(state))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > FLT_EPSILON) { const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); x4_state = state; if (const TCastToPtr act = bc.GetOwner()) { act->SetMomentumWR(zeus::skZero3f); } } x2c_24_needsRecover = false; } void CBSHurled::PlayStrikeWallAnimation(CBodyController& bc, CStateManager& mgr) { const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries), CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(3)); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first <= FLT_EPSILON) { return; } const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); x4_state = pas::EHurledState::StrikeWall; } void CBSHurled::PlayLandAnimation(CBodyController& bc, CStateManager& mgr) { const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries), CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(x4_state))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); const CPASAnimState* hurledState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Hurled); bc.SetFallState(pas::EFallState(hurledState->GetAnimParmData(best.second, 3).GetEnumValue())); if (const TCastToPtr act = bc.GetOwner()) { mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor); } } bool CBSHurled::ShouldStartStrikeWall(const CBodyController& bc) const { if (const TCastToConstPtr ai = bc.GetOwner()) { if (ai->MadeSolidCollision()) { if (!ai->IsOnGround()) { return true; } } } return false; } bool CBSHurled::ShouldStartLand(float dt, const CBodyController& bc) const { bool ret = true; if (const TCastToConstPtr ai = bc.GetOwner()) { ret = false; if (ai->IsOnGround()) { return true; } if (zeus::close_enough(ai->GetTranslation(), x1c_lastTranslation, 0.0001f) && ai->GetVelocity().z() < 0.f) { x28_landedDur += dt; if (x28_landedDur >= 0.25f) { ret = true; } } else { x28_landedDur = 0.f; } x1c_lastTranslation = ai->GetTranslation(); } return ret; } pas::EAnimationState CBSHurled::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { x18_curTime += dt; if (x14_remTime > 0.f) { zeus::CQuaternion quat; quat.rotateZ(x10_rotateSpeed * dt); bc.SetDeltaRotation(quat); x14_remTime -= dt; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) { x2c_24_needsRecover = true; } switch (x4_state) { case pas::EHurledState::KnockIntoAir: { if (bc.IsAnimationOver()) { const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries), CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(1)); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); x4_state = pas::EHurledState::KnockLoop; x28_landedDur = 0.f; } break; } case pas::EHurledState::KnockLoop: if (ShouldStartLand(dt, bc)) { x4_state = pas::EHurledState::KnockDown; PlayLandAnimation(bc, mgr); } else if (ShouldStartStrikeWall(bc)) { PlayStrikeWallAnimation(bc, mgr); if (const TCastToPtr ai = bc.GetOwner()) { ai->SetVelocityWR(zeus::skDown * (2.f * dt * ai->GetGravityConstant())); } } else if (x2c_24_needsRecover) { Recover(mgr, bc, pas::EHurledState::Six); } break; case pas::EHurledState::StrikeWall: if (bc.IsAnimationOver()) { x4_state = pas::EHurledState::StrikeWallFallLoop; const CPASAnimParmData parms(pas::EAnimationState::Hurled, CPASAnimParm::FromInt32(xc_animSeries), CPASAnimParm::FromReal32(x8_knockAngle), CPASAnimParm::FromEnum(s32(x4_state))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); x28_landedDur = 0.f; } break; case pas::EHurledState::StrikeWallFallLoop: if (ShouldStartLand(dt, bc)) { x4_state = pas::EHurledState::OutOfStrikeWall; PlayLandAnimation(bc, mgr); } else if (x2c_24_needsRecover) { Recover(mgr, bc, pas::EHurledState::Seven); } break; case pas::EHurledState::Six: case pas::EHurledState::Seven: if (const TCastToPtr act = bc.GetOwner()) { act->SetVelocityWR(act->GetVelocity() * std::pow(0.9, 60.0 * dt)); } if (bc.IsAnimationOver()) { x4_state = pas::EHurledState::Invalid; return pas::EAnimationState::Locomotion; } break; case pas::EHurledState::KnockDown: case pas::EHurledState::OutOfStrikeWall: if (bc.IsAnimationOver()) { x4_state = pas::EHurledState::Invalid; if (bc.GetFallState() == pas::EFallState::Zero) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::LieOnGround; } break; default: break; } } return st; } void CBSSlide::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Slide)); const zeus::CVector3f localDir = bc.GetOwner().GetTransform().transposeRotate(cmd->GetSlideDirection()); const float angle = std::atan2(localDir.y(), localDir.x()); const CPASAnimParmData parms(pas::EAnimationState::Slide, CPASAnimParm::FromEnum(s32(cmd->GetSlideType())), CPASAnimParm::FromReal32(zeus::radToDeg(angle))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); const float timeRem = bc.GetAnimTimeRemaining(); if (timeRem > FLT_EPSILON) { const CPASAnimState* slideState = bc.GetPASDatabase().GetAnimState(pas::EAnimationState::Slide); const float animAngle = zeus::degToRad(slideState->GetAnimParmData(best.second, 1).GetReal32Value()); const float delta1 = zeus::CRelAngle(angle - animAngle).asRel(); const float flippedAngle = (delta1 > M_PIF) ? delta1 - 2.f * M_PIF : delta1; x4_rotateSpeed = flippedAngle / timeRem; } else { x4_rotateSpeed = 0.f; } } pas::EAnimationState CBSSlide::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.IsAnimationOver()) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSSlide::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid && x4_rotateSpeed != 0.f) { zeus::CQuaternion quat; quat.rotateZ(x4_rotateSpeed * dt); bc.SetDeltaRotation(quat); } return st; } void CBSTaunt::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Taunt)); const CPASAnimParmData parms(pas::EAnimationState::Taunt, CPASAnimParm::FromEnum(s32(cmd->GetTauntType()))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); } pas::EAnimationState CBSTaunt::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } if (bc.IsAnimationOver()) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSTaunt::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid && !bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } return st; } void CBSScripted::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)); x4_24_loopAnim = cmd->IsLooped(); x4_25_timedLoop = cmd->GetUseLoopDuration(); x8_remTime = cmd->GetLoopDuration(); const CAnimPlaybackParms playParms(cmd->GetAnimId(), -1, 1.f, true); bc.SetCurrentAnimation(playParms, cmd->IsLooped(), false); } pas::EAnimationState CBSScripted::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) { return pas::EAnimationState::Scripted; } if (x4_24_loopAnim) { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) { return pas::EAnimationState::Locomotion; } } else if (bc.IsAnimationOver()) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSScripted::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } if (x4_24_loopAnim && x4_25_timedLoop) { x8_remTime -= dt; if (x8_remTime <= 0.f) { return pas::EAnimationState::Locomotion; } } } return st; } void CBSCover::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::Cover)); x8_coverDirection = cmd->GetDirection(); x4_state = pas::ECoverState::IntoCover; const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_coverDirection))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const zeus::CQuaternion orientDelta = zeus::CQuaternion::lookAt(zeus::skForward, cmd->GetAlignDirection(), 2.f * M_PIF); const CAnimPlaybackParms playParms(best.second, &orientDelta, &cmd->GetTarget(), &bc.GetOwner().GetTransform(), &bc.GetOwner().GetModelData()->GetScale(), false); bc.SetCurrentAnimation(playParms, false, false); xc_needsExit = false; if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) { xc_needsExit = true; } } pas::EAnimationState CBSCover::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } pas::EAnimationState CBSCover::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { switch (x4_state) { case pas::ECoverState::Lean: case pas::ECoverState::IntoCover: if (bc.IsAnimationOver()) { x4_state = pas::ECoverState::Cover; const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_coverDirection))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) { xc_needsExit = true; } break; case pas::ECoverState::Cover: if (!bc.GetCommandMgr().GetTargetVector().isZero()) { bc.FaceDirection(bc.GetCommandMgr().GetTargetVector(), dt); } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState) || xc_needsExit) { xc_needsExit = false; x4_state = pas::ECoverState::OutOfCover; const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_coverDirection))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); } else if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LeanFromCover)) { x4_state = pas::ECoverState::Lean; const CPASAnimParmData parms(pas::EAnimationState::Cover, CPASAnimParm::FromEnum(s32(x4_state)), CPASAnimParm::FromEnum(s32(x8_coverDirection))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); } break; case pas::ECoverState::OutOfCover: if (bc.IsAnimationOver()) { x4_state = pas::ECoverState::Invalid; return pas::EAnimationState::Locomotion; } break; default: break; } } return st; } void CBSWallHang::Start(CBodyController& bc, CStateManager& mgr) { const auto* cmd = static_cast(bc.GetCommandMgr().GetCmd(EBodyStateCmd::WallHang)); x4_state = pas::EWallHangState::IntoJump; x8_wpId = cmd->GetTarget(); x18_25_needsExit = false; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); } pas::EAnimationState CBSWallHang::GetBodyStateTransition(float dt, const CBodyController& bc) const { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } return pas::EAnimationState::Invalid; } void CBSWallHang::FixInPlace(CBodyController& bc) { if (const TCastToPtr ai = bc.GetOwner()) { ai->SetConstantForce(zeus::skZero3f); ai->SetVelocityWR(zeus::skZero3f); } } bool CBSWallHang::CheckForLand(CBodyController& bc, CStateManager& mgr) { if (const TCastToPtr ai = bc.GetOwner()) { if (ai->MadeSolidCollision() || ai->IsOnGround()) { x4_state = pas::EWallHangState::DetachOutOfJump; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); mgr.SendScriptMsg(ai.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor); return true; } } return false; } bool CBSWallHang::CheckForWall(CBodyController& bc, CStateManager& mgr) { if (const TCastToPtr ai = bc.GetOwner()) { float magSq = 10.f; const TCastToConstPtr wp = mgr.ObjectById(x8_wpId); if (wp) { magSq = (wp->GetTranslation() - ai->GetTranslation()).magSquared(); } if (magSq < 1.f || ai->MadeSolidCollision()) { x4_state = pas::EWallHangState::IntoWallHang; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); const zeus::CVector3f& target = wp ? wp->GetTranslation() : ai->GetTranslation(); const CAnimPlaybackParms playParms(best.second, nullptr, &target, &bc.GetOwner().GetTransform(), &bc.GetOwner().GetModelData()->GetScale(), false); bc.SetCurrentAnimation(playParms, false, false); ai->SetVelocityWR(zeus::skZero3f); ai->SetMomentumWR(zeus::skZero3f); mgr.SendScriptMsg(ai.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::OnFloor); return true; } } return false; } void CBSWallHang::SetLaunchVelocity(CBodyController& bc) { if (x18_24_launched) { return; } if (const TCastToPtr act = bc.GetOwner()) { act->SetVelocityWR(xc_launchVel); act->SetConstantForce(xc_launchVel * act->GetMass()); } x18_24_launched = true; } pas::EAnimationState CBSWallHang::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { switch (x4_state) { case pas::EWallHangState::IntoJump: { const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(1)); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > 0.f) { x4_state = pas::EWallHangState::JumpArc; const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } else { x4_state = pas::EWallHangState::JumpAirLoop; const CPASAnimParmData loopParms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom()); } if (const TCastToPtr act = bc.GetOwner()) { mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Jumped); if (const TCastToConstPtr wp = mgr.ObjectById(x8_wpId)) { const zeus::CVector3f toWp = wp->GetTranslation() - act->GetTranslation(); if (toWp.z() > 0.f) { float tmp = -act->GetMomentum().z() / act->GetMass(); const float tmp2 = std::sqrt(tmp * 2.f * toWp.z()); tmp = 1.f / (tmp2 / tmp); xc_launchVel = zeus::CVector3f(tmp * toWp.x(), tmp * toWp.y(), tmp2); x18_24_launched = false; } } } break; } case pas::EWallHangState::JumpArc: { SetLaunchVelocity(bc); if (bc.IsAnimationOver()) { x4_state = pas::EWallHangState::JumpAirLoop; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } else { CheckForWall(bc, mgr); } break; } case pas::EWallHangState::JumpAirLoop: { SetLaunchVelocity(bc); if (!CheckForWall(bc, mgr)) { CheckForLand(bc, mgr); } break; } case pas::EWallHangState::IntoWallHang: { if (bc.IsAnimationOver()) { x4_state = pas::EWallHangState::WallHang; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } else if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState)) { x18_25_needsExit = true; } break; } case pas::EWallHangState::WallHang: { if (!bc.GetCommandMgr().GetTargetVector().isZero()) { if (const TCastToConstPtr wp = mgr.ObjectById(x8_wpId)) { if (const TCastToConstPtr act = bc.GetOwner()) { const zeus::CVector3f lookDir = bc.GetCommandMgr().GetTargetVector().normalized(); const float actorDotWp = act->GetTransform().basis[1].dot(wp->GetTransform().basis[1]); const float lookDotWp = lookDir.dot(wp->GetTransform().basis[1]); if (actorDotWp < -0.5f || lookDotWp > 0.5f) { bc.FaceDirection(lookDir, dt); } } } } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ExitState) || x18_25_needsExit) { x4_state = pas::EWallHangState::OutOfWallHang; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.PlayBestAnimation(parms, *mgr.GetActiveRandom()); } FixInPlace(bc); break; } case pas::EWallHangState::Five: { if (bc.IsAnimationOver()) { x4_state = pas::EWallHangState::WallHang; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } FixInPlace(bc); break; } case pas::EWallHangState::OutOfWallHang: { if (bc.IsAnimationOver()) { const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(7)); const std::pair best = bc.GetPASDatabase().FindBestAnimation(parms, *mgr.GetActiveRandom(), -1); if (best.first > 0.f) { x4_state = pas::EWallHangState::OutOfWallHangTurn; const CAnimPlaybackParms playParms(best.second, -1, 1.f, true); bc.SetCurrentAnimation(playParms, false, false); } else { x4_state = pas::EWallHangState::DetachJumpLoop; const CPASAnimParmData loopParms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.LoopBestAnimation(loopParms, *mgr.GetActiveRandom()); } if (const TCastToPtr act = bc.GetOwner()) { mgr.SendScriptMsg(act.GetPtr(), kInvalidUniqueId, EScriptObjectMessage::Jumped); x18_24_launched = false; if (const TCastToConstPtr wp = mgr.ObjectById(x8_wpId)) { xc_launchVel = 15.f * wp->GetTransform().basis[1]; xc_launchVel.z() = 5.f; } else { xc_launchVel = -15.f * act->GetTransform().basis[1]; } act->SetAngularMomentum(zeus::CAxisAngle()); } } break; } case pas::EWallHangState::OutOfWallHangTurn: { SetLaunchVelocity(bc); if (bc.IsAnimationOver()) { x4_state = pas::EWallHangState::DetachJumpLoop; const CPASAnimParmData parms(pas::EAnimationState::WallHang, CPASAnimParm::FromEnum(s32(x4_state))); bc.LoopBestAnimation(parms, *mgr.GetActiveRandom()); } else { CheckForLand(bc, mgr); } break; } case pas::EWallHangState::DetachJumpLoop: { SetLaunchVelocity(bc); CheckForLand(bc, mgr); break; } case pas::EWallHangState::DetachOutOfJump: { if (bc.IsAnimationOver()) { x4_state = pas::EWallHangState::Invalid; return pas::EAnimationState::Locomotion; } break; } default: break; } } return st; } bool CBSWallHang::IsInAir(const CBodyController& bc) const { switch (x4_state) { case pas::EWallHangState::JumpArc: case pas::EWallHangState::JumpAirLoop: case pas::EWallHangState::OutOfWallHangTurn: case pas::EWallHangState::DetachJumpLoop: return true; default: return false; } } bool CBSWallHang::ApplyAnimationDeltas() const { switch (x4_state) { case pas::EWallHangState::IntoJump: case pas::EWallHangState::IntoWallHang: case pas::EWallHangState::WallHang: case pas::EWallHangState::Five: case pas::EWallHangState::OutOfWallHang: case pas::EWallHangState::DetachOutOfJump: return true; default: return false; } } bool CBSWallHang::ApplyHeadTracking() const { switch (x4_state) { case pas::EWallHangState::WallHang: case pas::EWallHangState::Five: return true; default: return false; } } bool CBSWallHang::ApplyGravity() const { switch (x4_state) { case pas::EWallHangState::WallHang: case pas::EWallHangState::IntoWallHang: case pas::EWallHangState::OutOfWallHang: return false; default: return true; } } float CBSLocomotion::GetStartVelocityMagnitude(const CBodyController& bc) const { if (const TCastToConstPtr act = bc.GetOwner()) { const float maxSpeed = bc.GetBodyStateInfo().GetMaxSpeed(); float ret = 0.f; if (maxSpeed > 0.f) { ret = act->GetVelocity().magnitude() / maxSpeed; } return std::min(1.f, ret); } return 0.f; } void CBSLocomotion::ReStartBodyState(CBodyController& bc, bool maintainVel) { UpdateLocomotionAnimation(0.f, maintainVel ? GetStartVelocityMagnitude(bc) : 0.f, bc, true); } float CBSLocomotion::ComputeWeightPercentage(const std::pair& a, const std::pair& b, float f) const { const float range = b.second - a.second; if (range > FLT_EPSILON) { return std::max(0.f, std::min(1.f, (f - a.second) / range)); } return 0.f; } void CBSLocomotion::Start(CBodyController& bc, CStateManager& mgr) { x4_locomotionType = bc.GetLocomotionType(); if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MaintainVelocity)) { ReStartBodyState(bc, true); } else { ReStartBodyState(bc, false); } } pas::EAnimationState CBSLocomotion::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { const pas::EAnimationState st = GetBodyStateTransition(dt, bc); if (st == pas::EAnimationState::Invalid) { UpdateLocomotionAnimation(dt, ApplyLocomotionPhysics(dt, bc), bc, false); } return st; } void CBSLocomotion::Shutdown(CBodyController& bc) { bc.MultiplyPlaybackRate(1.f); } float CBSLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) { if (const TCastToConstPtr act = bc.GetOwner()) { const zeus::CVector3f vec = (zeus::close_enough(bc.GetCommandMgr().GetFaceVector(), zeus::skZero3f, 0.0001f)) ? bc.GetCommandMgr().GetMoveVector() : bc.GetCommandMgr().GetFaceVector(); if (vec.canBeNormalized()) { if (IsPitchable()) { zeus::CVector3f tmp = vec; tmp.z() = 0.f; zeus::CVector3f lookVec = act->GetTransform().basis[1]; lookVec.z() = 0.f; lookVec.normalize(); bc.FaceDirection3D(tmp, lookVec, dt); zeus::CVector3f lookVec2 = act->GetTransform().basis[1]; lookVec2.z() = float(vec.z()); lookVec2.normalize(); if (!zeus::close_enough(lookVec, lookVec2, 0.0001f)) { const zeus::CRelAngle pitchAngle = std::min(bc.GetBodyStateInfo().GetMaximumPitch(), zeus::CVector3f::getAngleDiff(vec, tmp)); lookVec2 = zeus::CVector3f::slerp(lookVec, lookVec2, pitchAngle); } bc.FaceDirection3D(lookVec2, act->GetTransform().basis[1], dt); zeus::CVector3f lookVec3 = act->GetTransform().basis[1]; lookVec3.z() = 0.f; bc.FaceDirection3D(lookVec3, act->GetTransform().basis[1], dt); } else { bc.FaceDirection(vec.normalized(), dt); } } return std::min(1.f, bc.GetCommandMgr().GetMoveVector().magnitude()); } return 0.f; } pas::EAnimationState CBSLocomotion::GetBodyStateTransition(float, CBodyController& bc) { if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Hurled)) { return pas::EAnimationState::Hurled; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockDown)) { return pas::EAnimationState::Fall; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopHitReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::KnockBack)) { return pas::EAnimationState::KnockBack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Locomotion)) { bc.GetCommandMgr().ClearLocomotionCmds(); return pas::EAnimationState::Invalid; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Slide)) { return pas::EAnimationState::Slide; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Generate)) { return pas::EAnimationState::Generate; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::MeleeAttack)) { return pas::EAnimationState::MeleeAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::ProjectileAttack)) { return pas::EAnimationState::ProjectileAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopAttack)) { return pas::EAnimationState::LoopAttack; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::LoopReaction)) { return pas::EAnimationState::LoopReaction; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Jump)) { return pas::EAnimationState::Jump; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Taunt)) { return pas::EAnimationState::Taunt; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Step)) { return pas::EAnimationState::Step; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Cover)) { return pas::EAnimationState::Cover; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::WallHang)) { return pas::EAnimationState::WallHang; } if (bc.GetCommandMgr().GetCmd(EBodyStateCmd::Scripted)) { return pas::EAnimationState::Scripted; } if (bc.GetCommandMgr().GetMoveVector().isZero()) { if (!bc.GetCommandMgr().GetFaceVector().isZero()) { if (!IsMoving()) { return pas::EAnimationState::Turn; } } } if (x4_locomotionType != bc.GetLocomotionType()) { return pas::EAnimationState::Locomotion; } return pas::EAnimationState::Invalid; } CBSBiPedLocomotion::CBSBiPedLocomotion(CActor& actor) { const CPASDatabase& pasDatabase = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase(); for (int i = 0; i < 14; ++i) { rstl::reserved_vector, 8>& innerVec = x8_anims.emplace_back(); for (int j = 0; j < 8; ++j) { const CPASAnimParmData parms(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(j), CPASAnimParm::FromEnum(i)); const std::pair best = pasDatabase.FindBestAnimation(parms, -1); float avgVel = 0.f; if (best.second != -1) { avgVel = actor.GetAverageAnimVelocity(best.second); if (j == 0) { avgVel = 0.f; } } innerVec.push_back({best.second, avgVel}); } } } void CBSBiPedLocomotion::Start(CBodyController& bc, CStateManager& mgr) { x3c8_primeTime = 0.f; CBSLocomotion::Start(bc, mgr); } pas::EAnimationState CBSBiPedLocomotion::UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) { if (x3c8_primeTime < 0.2f) { x3c8_primeTime += dt; } return CBSLocomotion::UpdateBody(dt, bc, mgr); } float CBSBiPedLocomotion::GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const { return GetLocoAnimation(type, anim).second; } float CBSBiPedLocomotion::UpdateRun(float vel, CBodyController& bc, pas::ELocomotionAnim anim) { const std::pair& walk = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk); const std::pair& run = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Run); const float perc = ComputeWeightPercentage(walk, run, vel); if (perc < 0.4f) { const float rate = walk.second > 0.f ? vel / walk.second : 1.f; if (anim != pas::ELocomotionAnim::Walk && bc.GetCurrentAnimId() != walk.first) { const CAnimPlaybackParms playParms(walk.first, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); x3c8_primeTime = 0.f; } bc.MultiplyPlaybackRate(rate); x3c4_anim = pas::ELocomotionAnim::Walk; return rate; } else { const float rate = std::min(1.f, vel / run.second); if (anim != pas::ELocomotionAnim::Run && bc.GetCurrentAnimId() != run.first) { const CAnimPlaybackParms playParms(run.first, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); x3c8_primeTime = 0.f; } bc.MultiplyPlaybackRate(rate); x3c4_anim = pas::ELocomotionAnim::Run; return rate; } } float CBSBiPedLocomotion::UpdateWalk(float vel, CBodyController& bc, pas::ELocomotionAnim anim) { if (anim != pas::ELocomotionAnim::Walk) { const std::pair& walk = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk); if (bc.GetCurrentAnimId() != walk.first) { const CAnimPlaybackParms playParms(walk.first, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); x3c8_primeTime = 0.f; } x3c4_anim = pas::ELocomotionAnim::Walk; } const std::pair& idle = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Idle); const std::pair& walk = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk); const float perc = std::max(0.5f, ComputeWeightPercentage(idle, walk, vel)); bc.MultiplyPlaybackRate(perc); return perc; } constexpr std::array Strafes{ pas::ELocomotionAnim::StrafeRight, pas::ELocomotionAnim::StrafeLeft, pas::ELocomotionAnim::Walk, pas::ELocomotionAnim::BackUp, pas::ELocomotionAnim::StrafeUp, pas::ELocomotionAnim::StrafeDown, }; float CBSBiPedLocomotion::UpdateStrafe(float vel, CBodyController& bc, pas::ELocomotionAnim anim) { if (const TCastToConstPtr act = bc.GetOwner()) { const zeus::CVector3f localVec = act->GetTransform().transposeRotate(bc.GetCommandMgr().GetMoveVector()); const zeus::CVector3f localVecSq = localVec * localVec; int maxComp = 0; for (int i = 0; i < 3; ++i) { if (localVecSq[i] >= localVecSq[maxComp]) { maxComp = i; } } const int strafeKey = maxComp * 2 + (localVec[maxComp] > 0.f ? 0 : 1); const pas::ELocomotionAnim strafeType = Strafes[strafeKey]; const float rate = vel * GetLocomotionSpeed(x4_locomotionType, strafeType); if (anim != strafeType) { const std::pair& strafe = GetLocoAnimation(x4_locomotionType, strafeType); if (bc.GetCurrentAnimId() != strafe.first) { const CAnimPlaybackParms playParms(strafe.first, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); x3c8_primeTime = 0.f; } x3c4_anim = strafeType; } const std::pair& idle = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Idle); const std::pair& strafe = GetLocoAnimation(x4_locomotionType, strafeType); const float perc = std::max(0.5f, ComputeWeightPercentage(idle, strafe, rate)); bc.MultiplyPlaybackRate(perc); } return 1.f; } float CBSBiPedLocomotion::UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) { if (init || x3c8_primeTime >= 0.2f) { const auto anim = init ? pas::ELocomotionAnim::Invalid : x3c4_anim; const float maxSpeed = velMag * GetLocomotionSpeed(x4_locomotionType, pas::ELocomotionAnim::Run); if (IsStrafing(bc) && velMag >= 0.01f) { return UpdateStrafe(velMag, bc, anim); } if (maxSpeed < 0.01f) { if (anim != pas::ELocomotionAnim::Idle || init) { if (!bc.GetBodyStateInfo().GetLocoAnimChangeAtEndOfAnimOnly() || bc.GetAnimTimeRemaining() <= dt || init) { const std::pair& best = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Idle); if (bc.GetCurrentAnimId() != best.first) { const CAnimPlaybackParms playParms(best.first, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); x3c8_primeTime = 0.f; } x3c4_anim = pas::ELocomotionAnim::Idle; } } } else { const std::pair& best = GetLocoAnimation(x4_locomotionType, pas::ELocomotionAnim::Walk); if (maxSpeed < best.second) { return UpdateWalk(maxSpeed, bc, anim); } return UpdateRun(maxSpeed, bc, anim); } } return 1.0f; } bool CBSBiPedLocomotion::IsStrafing(const CBodyController& bc) const { if (!zeus::close_enough(bc.GetCommandMgr().GetMoveVector(), zeus::skZero3f, 0.0001f)) { if (!zeus::close_enough(bc.GetCommandMgr().GetFaceVector(), zeus::skZero3f, 0.0001f)) { return true; } } return false; } CBSFlyerLocomotion::CBSFlyerLocomotion(CActor& actor, bool pitchable) : CBSBiPedLocomotion(actor), x3cc_pitchable(pitchable) {} float CBSFlyerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) { const float ret = CBSLocomotion::ApplyLocomotionPhysics(dt, bc); if (const TCastToPtr act = bc.GetOwner()) { if (std::fabs(bc.GetCommandMgr().GetMoveVector().z()) > 0.01f && (!x3cc_pitchable || bc.GetBodyStateInfo().GetMaximumPitch() < 0.17453292f)) { zeus::CVector3f dir; dir.z() = dt * bc.GetBodyStateInfo().GetMaxSpeed() * bc.GetCommandMgr().GetMoveVector().z(); act->ApplyImpulseWR(act->GetMoveToORImpulseWR(dir, dt), zeus::CAxisAngle()); } } return ret; } CBSWallWalkerLocomotion::CBSWallWalkerLocomotion(CActor& actor) : CBSBiPedLocomotion(actor) {} float CBSWallWalkerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) { if (const TCastToConstPtr act = bc.GetOwner()) { const float maxSpeed = bc.GetBodyStateInfo().GetMaxSpeed(); const zeus::CVector3f scaledMove = bc.GetCommandMgr().GetMoveVector() * maxSpeed; if ((zeus::CVector3f::getAngleDiff(bc.GetCommandMgr().GetFaceVector(), scaledMove) < (M_PIF / 2.f) ? scaledMove : bc.GetCommandMgr().GetFaceVector()) .canBeNormalized()) { bc.FaceDirection3D(scaledMove.normalized(), act->GetTransform().basis[1], dt); } zeus::CVector3f impulse = act->GetMoveToORImpulseWR(act->GetTransform().transposeRotate(scaledMove * dt), dt); impulse = act->GetMass() > FLT_EPSILON ? impulse / act->GetMass() : zeus::CVector3f(0.f, act->GetVelocity().magnitude(), 0.f); if (maxSpeed > FLT_EPSILON) { return std::min(1.f, impulse.magnitude() / maxSpeed); } } return 0.f; } CBSNewFlyerLocomotion::CBSNewFlyerLocomotion(CActor& actor) : CBSBiPedLocomotion(actor) {} float CBSNewFlyerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) { if (TCastToConstPtr(bc.GetOwner())) { bc.FaceDirection(bc.GetCommandMgr().GetFaceVector(), dt); } return 0.f; } constexpr std::array RunStrafes{ pas::ELocomotionAnim::StrafeRight, pas::ELocomotionAnim::StrafeLeft, pas::ELocomotionAnim::Run, pas::ELocomotionAnim::BackUp, pas::ELocomotionAnim::StrafeUp, pas::ELocomotionAnim::StrafeDown, }; float CBSNewFlyerLocomotion::UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) { if (const TCastToConstPtr act = bc.GetOwner()) { pas::ELocomotionAnim strafeType = pas::ELocomotionAnim::Idle; if (bc.GetCommandMgr().GetMoveVector().canBeNormalized()) { const zeus::CVector3f localVec = act->GetTransform().transposeRotate(bc.GetCommandMgr().GetMoveVector()); const zeus::CVector3f localVecSq = localVec * localVec; int maxComp = 0; for (int i = 0; i < 3; ++i) { if (localVecSq[i] >= localVecSq[maxComp]) { maxComp = i; } } const int strafeKey = maxComp * 2 + (localVec[maxComp] > 0.f ? 0 : 1); strafeType = RunStrafes[strafeKey]; } if (init || strafeType != x3c4_anim) { const std::pair& strafe = GetLocoAnimation(x4_locomotionType, strafeType); if (init || bc.GetCurrentAnimId() != strafe.first) { const CAnimPlaybackParms playParms(strafe.first, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); } x3c4_anim = strafeType; } } return 1.f; } CBSRestrictedLocomotion::CBSRestrictedLocomotion(CActor& actor) { const CPASDatabase& pasDatabase = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase(); for (int i = 0; i < 14; ++i) { const CPASAnimParmData parms(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0), CPASAnimParm::FromEnum(i)); const std::pair best = pasDatabase.FindBestAnimation(parms, -1); x8_anims.push_back(best.second); } } float CBSRestrictedLocomotion::UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) { const pas::ELocomotionAnim anim = init ? pas::ELocomotionAnim::Invalid : x44_anim; if (anim != pas::ELocomotionAnim::Idle) { const s32 newAnim = x8_anims[int(x4_locomotionType)]; if (newAnim != bc.GetCurrentAnimId()) { const CAnimPlaybackParms playParms(newAnim, -1, 1.f, true); bc.SetCurrentAnimation(playParms, true, false); } x44_anim = pas::ELocomotionAnim::Idle; } return 1.f; } CBSRestrictedFlyerLocomotion::CBSRestrictedFlyerLocomotion(CActor& actor) : CBSRestrictedLocomotion(actor) {} float CBSRestrictedFlyerLocomotion::ApplyLocomotionPhysics(float dt, CBodyController& bc) { if (const TCastToPtr act = bc.GetOwner()) { bc.FaceDirection(bc.GetCommandMgr().GetFaceVector(), dt); act->ApplyImpulseWR(bc.GetCommandMgr().GetMoveVector() * bc.GetRestrictedFlyerMoveSpeed() * act->GetMass(), zeus::CAxisAngle()); } return 0.f; } } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyState.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CBodyStateCmdMgr.hpp" #include "Runtime/Character/CharacterCommon.hpp" #include namespace metaforce { class CBodyController; class CStateManager; class CActor; class CBodyState { public: virtual ~CBodyState() = default; virtual bool IsInAir(const CBodyController&) const { return false; } virtual bool IsDead() const { return false; } virtual bool IsDying() const { return false; } virtual bool IsMoving() const { return false; } virtual bool ApplyGravity() const { return true; } virtual bool ApplyHeadTracking() const { return true; } virtual bool ApplyAnimationDeltas() const { return true; } virtual bool CanShoot() const { return false; } virtual void Start(CBodyController&, CStateManager&) = 0; virtual pas::EAnimationState UpdateBody(float, CBodyController&, CStateManager&) = 0; virtual void Shutdown(CBodyController&) = 0; }; class CBSAttack : public CBodyState { pas::EAnimationState x4_nextState = pas::EAnimationState::Invalid; CBCSlideCmd x8_slide; zeus::CVector3f x20_targetPos; float x2c_alignTargetPosStartTime = -1.f; float x30_alignTargetPosTime = -1.f; float x34_curTime = 0.f; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc); void UpdatePhysicsActor(const CBodyController& bc, float dt); public: bool CanShoot() const override { return false; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSProjectileAttack : public CBodyState { pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: bool CanShoot() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSDie : public CBodyState { float x4_remTime = 0.f; bool x8_isDead = false; public: bool IsDead() const override { return x8_isDead; } bool IsDying() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSFall : public CBodyState { float x4_rotateSpeed = 0.f; float x8_remTime = 0.f; pas::EFallState xc_fallState = pas::EFallState::Invalid; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override; }; class CBSGetup : public CBodyState { pas::EFallState x4_fallState = pas::EFallState::Invalid; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override; }; class CBSKnockBack : public CBodyState { float x4_curTime = 0.f; float x8_rotateSpeed = 0.f; float xc_remTime = 0.f; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: bool IsMoving() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSLieOnGround : public CBodyState { bool x4_24_hasGroundHit : 1; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: explicit CBSLieOnGround(CActor& actor); void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override; }; class CBSStep : public CBodyState { pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: bool IsMoving() const override { return true; } bool CanShoot() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSTurn : public CBodyState { protected: float x4_rotateSpeed = 0.f; zeus::CVector2f x8_dest; pas::ETurnDirection x10_turnDir = pas::ETurnDirection::Invalid; bool FacingDest(const CBodyController& bc) const; public: bool CanShoot() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} virtual pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const; }; class CBSFlyerTurn : public CBSTurn { public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; }; class CBSLoopAttack : public CBodyState { pas::ELoopState x4_state = pas::ELoopState::Invalid; pas::ELoopAttackType x8_loopAttackType = pas::ELoopAttackType::Invalid; bool xc_24_waitForAnimOver : 1 = false; bool xc_25_advance : 1 = false; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: CBSLoopAttack() = default; bool CanShoot() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSLoopReaction : public CBodyState { pas::ELoopState x4_state = pas::ELoopState::Invalid; pas::EReactionType x8_reactionType = pas::EReactionType::Invalid; bool xc_24_loopHit : 1 = false; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; bool PlayExitAnimation(CBodyController& bc, CStateManager& mgr) const; public: CBSLoopReaction() = default; void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSGroundHit : public CBodyState { float x4_rotateSpeed = 0.f; float x8_remTime = 0.f; pas::EFallState xc_fallState = pas::EFallState::Invalid; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override; }; class CBSGenerate : public CBodyState { pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSJump : public CBodyState { pas::EJumpState x4_state = pas::EJumpState::Invalid; pas::EJumpType x8_jumpType{}; zeus::CVector3f xc_waypoint1; zeus::CVector3f x18_velocity; zeus::CVector3f x24_waypoint2; bool x30_24_bodyForceSet : 1 = false; bool x30_25_wallJump : 1 = false; bool x30_26_wallBounceRight : 1 = false; bool x30_27_wallBounceComplete : 1 = false; bool x30_28_startInJumpLoop : 1 = false; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; bool CheckForWallJump(CBodyController& bc, CStateManager& mgr); void CheckForLand(CBodyController& bc, CStateManager& mgr); void PlayJumpLoop(CStateManager& mgr, CBodyController& bc); public: CBSJump() = default; bool IsMoving() const override { return true; } bool ApplyHeadTracking() const override { return false; } bool CanShoot() const override; void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; bool ApplyAnimationDeltas() const override; bool IsInAir(const CBodyController& bc) const override; void Shutdown(CBodyController&) override {} }; class CBSHurled : public CBodyState { pas::EHurledState x4_state = pas::EHurledState::Invalid; float x8_knockAngle = 0.f; int xc_animSeries = -1; float x10_rotateSpeed = 0.f; float x14_remTime = 0.f; float x18_curTime = 0.f; mutable zeus::CVector3f x1c_lastTranslation; mutable float x28_landedDur = 0.f; bool x2c_24_needsRecover : 1 = false; pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc) const; void Recover(CStateManager& mgr, CBodyController& bc, pas::EHurledState state); void PlayStrikeWallAnimation(CBodyController& bc, CStateManager& mgr); void PlayLandAnimation(CBodyController& bc, CStateManager& mgr); bool ShouldStartStrikeWall(const CBodyController& bc) const; bool ShouldStartLand(float dt, const CBodyController& bc) const; public: CBSHurled() = default; bool IsMoving() const override { return true; } bool IsInAir(const CBodyController&) const override { return true; } bool ApplyHeadTracking() const override { return false; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSSlide : public CBodyState { float x4_rotateSpeed = 0.f; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: bool ApplyHeadTracking() const override { return false; } bool IsMoving() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSTaunt : public CBodyState { pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSScripted : public CBodyState { bool x4_24_loopAnim : 1 = false; bool x4_25_timedLoop : 1 = false; float x8_remTime = 0.f; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: CBSScripted() = default; bool ApplyHeadTracking() const override { return false; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSCover : public CBodyState { pas::ECoverState x4_state = pas::ECoverState::Invalid; pas::ECoverDirection x8_coverDirection = pas::ECoverDirection::Invalid; bool xc_needsExit = false; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; public: bool ApplyHeadTracking() const override { return false; } bool IsMoving() const override { return true; } bool CanShoot() const override { return x4_state == pas::ECoverState::Lean; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSWallHang : public CBodyState { pas::EWallHangState x4_state = pas::EWallHangState::Invalid; TUniqueId x8_wpId = kInvalidUniqueId; zeus::CVector3f xc_launchVel; bool x18_24_launched : 1 = false; bool x18_25_needsExit : 1 = false; pas::EAnimationState GetBodyStateTransition(float dt, const CBodyController& bc) const; void FixInPlace(CBodyController& bc); bool CheckForLand(CBodyController& bc, CStateManager& mgr); bool CheckForWall(CBodyController& bc, CStateManager& mgr); void SetLaunchVelocity(CBodyController& bc); public: CBSWallHang() = default; bool IsMoving() const override { return true; } bool CanShoot() const override { return x4_state == pas::EWallHangState::WallHang; } bool IsInAir(const CBodyController& bc) const override; bool ApplyGravity() const override; bool ApplyHeadTracking() const override; bool ApplyAnimationDeltas() const override; void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController&) override {} }; class CBSLocomotion : public CBodyState { protected: pas::ELocomotionType x4_locomotionType = pas::ELocomotionType::Invalid; float GetStartVelocityMagnitude(const CBodyController& bc) const; void ReStartBodyState(CBodyController& bc, bool maintainVel); float ComputeWeightPercentage(const std::pair& a, const std::pair& b, float f) const; public: bool IsMoving() const override = 0; bool CanShoot() const override { return true; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; void Shutdown(CBodyController& bc) override; virtual bool IsPitchable() const { return false; } virtual float GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const = 0; virtual float ApplyLocomotionPhysics(float dt, CBodyController& bc); virtual float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) = 0; virtual pas::EAnimationState GetBodyStateTransition(float dt, CBodyController& bc); }; class CBSBiPedLocomotion : public CBSLocomotion { protected: rstl::reserved_vector, 8>, 14> x8_anims; pas::ELocomotionAnim x3c4_anim = pas::ELocomotionAnim::Invalid; float x3c8_primeTime = 0.0f; float UpdateRun(float vel, CBodyController& bc, pas::ELocomotionAnim anim); float UpdateWalk(float vel, CBodyController& bc, pas::ELocomotionAnim anim); float UpdateStrafe(float vel, CBodyController& bc, pas::ELocomotionAnim anim); const std::pair& GetLocoAnimation(pas::ELocomotionType type, pas::ELocomotionAnim anim) const { return x8_anims[size_t(type)][size_t(anim)]; } public: explicit CBSBiPedLocomotion(CActor& actor); bool IsMoving() const override { return x3c4_anim != pas::ELocomotionAnim::Idle; } void Start(CBodyController& bc, CStateManager& mgr) override; pas::EAnimationState UpdateBody(float dt, CBodyController& bc, CStateManager& mgr) override; float GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const override; float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) override; virtual bool IsStrafing(const CBodyController& bc) const; }; class CBSFlyerLocomotion : public CBSBiPedLocomotion { bool x3cc_pitchable; public: explicit CBSFlyerLocomotion(CActor& actor, bool pitchable); bool IsPitchable() const override { return x3cc_pitchable; } float ApplyLocomotionPhysics(float dt, CBodyController& bc) override; virtual bool IsBackPedal(CBodyController& bc) const { return false; } }; class CBSWallWalkerLocomotion : public CBSBiPedLocomotion { public: explicit CBSWallWalkerLocomotion(CActor& actor); float ApplyLocomotionPhysics(float dt, CBodyController& bc) override; }; class CBSNewFlyerLocomotion : public CBSBiPedLocomotion { public: explicit CBSNewFlyerLocomotion(CActor& actor); float ApplyLocomotionPhysics(float dt, CBodyController& bc) override; float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) override; }; class CBSRestrictedLocomotion : public CBSLocomotion { rstl::reserved_vector x8_anims; pas::ELocomotionAnim x44_anim = pas::ELocomotionAnim::Invalid; public: explicit CBSRestrictedLocomotion(CActor& actor); bool IsMoving() const override { return false; } float GetLocomotionSpeed(pas::ELocomotionType type, pas::ELocomotionAnim anim) const override { return 0.f; } float UpdateLocomotionAnimation(float dt, float velMag, CBodyController& bc, bool init) override; }; class CBSRestrictedFlyerLocomotion : public CBSRestrictedLocomotion { public: explicit CBSRestrictedFlyerLocomotion(CActor& actor); float ApplyLocomotionPhysics(float dt, CBodyController& bc) override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyStateCmdMgr.cpp ================================================ #include "Runtime/Character/CBodyStateCmdMgr.hpp" #include namespace metaforce { CBodyStateCmdMgr::CBodyStateCmdMgr() { x40_commandTable.push_back(&xb8_getup); x40_commandTable.push_back(&xc4_step); x40_commandTable.push_back(&xd4_die); x40_commandTable.push_back(&xdc_knockDown); x40_commandTable.push_back(&xf4_knockBack); x40_commandTable.push_back(&x10c_meleeAttack); x40_commandTable.push_back(&x128_projectileAttack); x40_commandTable.push_back(&x144_loopAttack); x40_commandTable.push_back(&x154_loopReaction); x40_commandTable.push_back(&x160_loopHitReaction); x40_commandTable.push_back(&x16c_exitState); x40_commandTable.push_back(&x174_leanFromCover); x40_commandTable.push_back(&x17c_nextState); x40_commandTable.push_back(&x184_maintainVelocity); x40_commandTable.push_back(&x18c_generate); x40_commandTable.push_back(&x1ac_hurled); x40_commandTable.push_back(&x1d0_jump); x40_commandTable.push_back(&x1f8_slide); x40_commandTable.push_back(&x210_taunt); x40_commandTable.push_back(&x21c_scripted); x40_commandTable.push_back(&x230_cover); x40_commandTable.push_back(&x254_wallHang); x40_commandTable.push_back(&x260_locomotion); x40_commandTable.push_back(&x268_additiveIdle); x40_commandTable.push_back(&x270_additiveAim); x40_commandTable.push_back(&x278_additiveFlinch); x40_commandTable.push_back(&x284_additiveReaction); x40_commandTable.push_back(&x298_stopReaction); } void CBodyStateCmdMgr::DeliverCmd(const CBCLocomotionCmd& cmd) { if (cmd.GetWeight() <= FLT_EPSILON) return; x3c_steeringSpeed += cmd.GetWeight(); x0_move += cmd.GetMoveVector() * cmd.GetWeight(); xc_face += cmd.GetFaceVector() * cmd.GetWeight(); } void CBodyStateCmdMgr::BlendSteeringCmds() { if (x3c_steeringSpeed > FLT_EPSILON) { float stepMul = 1.f / x3c_steeringSpeed; xc_face *= zeus::CVector3f(stepMul); switch (x30_steeringMode) { case ESteeringBlendMode::Normal: x0_move *= zeus::CVector3f(stepMul); break; case ESteeringBlendMode::FullSpeed: if (!zeus::close_enough(x0_move, zeus::skZero3f, 0.0001f)) { x0_move.normalize(); x0_move *= zeus::CVector3f(x38_steeringSpeedMax); } break; case ESteeringBlendMode::Clamped: x0_move *= zeus::CVector3f(stepMul); if (!zeus::close_enough(x0_move, zeus::skZero3f, 0.0001f)) { if (x0_move.magnitude() < x34_steeringSpeedMin) x0_move = x0_move.normalized() * x34_steeringSpeedMin; else if (x0_move.magnitude() > x38_steeringSpeedMax) x0_move = x0_move.normalized() * x38_steeringSpeedMax; } break; default: break; } } } void CBodyStateCmdMgr::Reset() { x0_move = zeus::skZero3f; xc_face = zeus::skZero3f; x18_target = zeus::skZero3f; x3c_steeringSpeed = 0.f; xb4_deliveredCmdMask = 0; } void CBodyStateCmdMgr::ClearLocomotionCmds() { x0_move = zeus::skZero3f; xc_face = zeus::skZero3f; x3c_steeringSpeed = 0.f; } } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyStateCmdMgr.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CharacterCommon.hpp" #include namespace metaforce { class CBodyStateCmd { EBodyStateCmd x4_cmd; public: virtual ~CBodyStateCmd() = default; constexpr explicit CBodyStateCmd(EBodyStateCmd cmd) : x4_cmd(cmd) {} constexpr EBodyStateCmd GetCommandId() const { return x4_cmd; } }; class CBCMeleeAttackCmd : public CBodyStateCmd { pas::ESeverity x8_severity = pas::ESeverity::Invalid; zeus::CVector3f xc_targetPos; bool x18_hasTargetPos = false; public: constexpr explicit CBCMeleeAttackCmd() : CBodyStateCmd(EBodyStateCmd::MeleeAttack) {} constexpr explicit CBCMeleeAttackCmd(pas::ESeverity severity) : CBodyStateCmd(EBodyStateCmd::MeleeAttack), x8_severity(severity) {} constexpr explicit CBCMeleeAttackCmd(pas::ESeverity severity, const zeus::CVector3f& target) : CBodyStateCmd(EBodyStateCmd::MeleeAttack), x8_severity(severity), xc_targetPos(target), x18_hasTargetPos(true) {} constexpr pas::ESeverity GetAttackSeverity() const { return x8_severity; } constexpr bool HasAttackTargetPos() const { return x18_hasTargetPos; } constexpr const zeus::CVector3f& GetAttackTargetPos() const { return xc_targetPos; } }; class CBCProjectileAttackCmd : public CBodyStateCmd { pas::ESeverity x8_severity = pas::ESeverity::Invalid; zeus::CVector3f xc_target; bool x18_blendAnims = false; public: constexpr explicit CBCProjectileAttackCmd() : CBodyStateCmd(EBodyStateCmd::ProjectileAttack) {} constexpr explicit CBCProjectileAttackCmd(pas::ESeverity severity, const zeus::CVector3f& vec, bool b) : CBodyStateCmd(EBodyStateCmd::ProjectileAttack), x8_severity(severity), xc_target(vec), x18_blendAnims(b) {} constexpr pas::ESeverity GetAttackSeverity() const { return x8_severity; } constexpr const zeus::CVector3f& GetTargetPosition() const { return xc_target; } constexpr bool BlendTwoClosest() const { return x18_blendAnims; } }; class CBCStepCmd : public CBodyStateCmd { pas::EStepDirection x8_dir = pas::EStepDirection::Invalid; pas::EStepType xc_type = pas::EStepType::Normal; public: constexpr explicit CBCStepCmd() : CBodyStateCmd(EBodyStateCmd::Step) {} constexpr explicit CBCStepCmd(pas::EStepDirection dir, pas::EStepType type) : CBodyStateCmd(EBodyStateCmd::Step), x8_dir(dir), xc_type(type) {} constexpr pas::EStepDirection GetStepDirection() const { return x8_dir; } constexpr pas::EStepType GetStepType() const { return xc_type; } }; class CBCJumpCmd : public CBodyStateCmd { pas::EJumpType x8_type = pas::EJumpType::Normal; zeus::CVector3f xc_waypoint1; zeus::CVector3f x18_waypoint2; bool x24_24_wallJump : 1 = false; bool x24_25_startInJumpLoop : 1 = false; public: constexpr explicit CBCJumpCmd() : CBodyStateCmd(EBodyStateCmd::Jump) {} constexpr explicit CBCJumpCmd(const zeus::CVector3f& wp1, pas::EJumpType type, bool startInLoop = false) : CBodyStateCmd(EBodyStateCmd::Jump), x8_type(type), xc_waypoint1(wp1), x24_25_startInJumpLoop{startInLoop} {} constexpr explicit CBCJumpCmd(const zeus::CVector3f& wp1, const zeus::CVector3f& wp2, pas::EJumpType type) : CBodyStateCmd(EBodyStateCmd::Jump), x8_type(type), xc_waypoint1(wp1), x18_waypoint2(wp2), x24_24_wallJump{true} {} constexpr pas::EJumpType GetJumpType() const { return x8_type; } constexpr const zeus::CVector3f& GetJumpTarget() const { return xc_waypoint1; } constexpr const zeus::CVector3f& GetSecondJumpTarget() const { return x18_waypoint2; } constexpr bool IsWallJump() const { return x24_24_wallJump; } constexpr bool StartInJumpLoop() const { return x24_25_startInJumpLoop; } }; class CBCGenerateCmd : public CBodyStateCmd { pas::EGenerateType x8_type = pas::EGenerateType::Invalid; zeus::CVector3f xc_targetPos; s32 x18_animId = -1; bool x1c_24_targetTransform : 1 = false; bool x1c_25_overrideAnim : 1 = false; public: constexpr explicit CBCGenerateCmd() : CBodyStateCmd(EBodyStateCmd::Generate) {} constexpr explicit CBCGenerateCmd(pas::EGenerateType type) : CBodyStateCmd(EBodyStateCmd::Generate), x8_type(type) {} constexpr explicit CBCGenerateCmd(pas::EGenerateType type, s32 animId) : CBodyStateCmd(EBodyStateCmd::Generate), x8_type(type), x18_animId(animId), x1c_25_overrideAnim{animId != -1} {} constexpr explicit CBCGenerateCmd(pas::EGenerateType type, const zeus::CVector3f& vec, bool targetTransform = false, bool overrideAnim = false) : CBodyStateCmd(EBodyStateCmd::Generate) , x8_type(type) , xc_targetPos(vec) , x1c_24_targetTransform{targetTransform} , x1c_25_overrideAnim{overrideAnim} {} constexpr pas::EGenerateType GetGenerateType() const { return x8_type; } constexpr const zeus::CVector3f& GetExitTargetPos() const { return xc_targetPos; } constexpr bool HasExitTargetPos() const { return x1c_24_targetTransform; } constexpr s32 GetSpecialAnimId() const { return x18_animId; } constexpr bool UseSpecialAnimId() const { return x1c_25_overrideAnim; } }; class CBCKnockBackCmd : public CBodyStateCmd { zeus::CVector3f x8_dir; pas::ESeverity x14_severity = pas::ESeverity::Invalid; public: constexpr explicit CBCKnockBackCmd() : CBodyStateCmd(EBodyStateCmd::KnockBack) {} constexpr explicit CBCKnockBackCmd(const zeus::CVector3f& vec, pas::ESeverity severity) : CBodyStateCmd(EBodyStateCmd::KnockBack), x8_dir(vec), x14_severity(severity) {} constexpr const zeus::CVector3f& GetHitDirection() const { return x8_dir; } constexpr pas::ESeverity GetHitSeverity() const { return x14_severity; } }; class CBCHurledCmd : public CBodyStateCmd { zeus::CVector3f x8_direction; zeus::CVector3f x14_launchVel; bool x20_startInKnockLoop = false; public: constexpr explicit CBCHurledCmd() : CBodyStateCmd(EBodyStateCmd::Hurled) {} constexpr explicit CBCHurledCmd(const zeus::CVector3f& dir, const zeus::CVector3f& launchVel, bool startInLoop = false) : CBodyStateCmd(EBodyStateCmd::Hurled) , x8_direction(dir) , x14_launchVel(launchVel) , x20_startInKnockLoop(startInLoop) {} constexpr const zeus::CVector3f& GetHitDirection() const { return x8_direction; } constexpr const zeus::CVector3f& GetLaunchVelocity() const { return x14_launchVel; } constexpr bool GetSkipLaunchState() const { return x20_startInKnockLoop; } constexpr void SetSkipLaunchState(bool s) { x20_startInKnockLoop = s; } }; class CBCGetupCmd : public CBodyStateCmd { pas::EGetupType x8_type = pas::EGetupType::Invalid; public: constexpr explicit CBCGetupCmd() : CBodyStateCmd(EBodyStateCmd::Getup) {} constexpr explicit CBCGetupCmd(pas::EGetupType type) : CBodyStateCmd(EBodyStateCmd::Getup), x8_type(type) {} constexpr pas::EGetupType GetGetupType() const { return x8_type; } }; class CBCLoopReactionCmd : public CBodyStateCmd { pas::EReactionType x8_type = pas::EReactionType::Invalid; public: constexpr explicit CBCLoopReactionCmd() : CBodyStateCmd(EBodyStateCmd::LoopReaction) {} constexpr explicit CBCLoopReactionCmd(pas::EReactionType type) : CBodyStateCmd(EBodyStateCmd::LoopReaction), x8_type(type) {} constexpr pas::EReactionType GetReactionType() const { return x8_type; } }; class CBCLoopHitReactionCmd : public CBodyStateCmd { pas::EReactionType x8_type = pas::EReactionType::Invalid; public: constexpr explicit CBCLoopHitReactionCmd() : CBodyStateCmd(EBodyStateCmd::LoopHitReaction) {} constexpr explicit CBCLoopHitReactionCmd(pas::EReactionType type) : CBodyStateCmd(EBodyStateCmd::LoopHitReaction), x8_type(type) {} constexpr pas::EReactionType GetReactionType() const { return x8_type; } }; class CBCKnockDownCmd : public CBodyStateCmd { zeus::CVector3f x8_dir; pas::ESeverity x14_severity = pas::ESeverity::Invalid; public: constexpr explicit CBCKnockDownCmd() : CBodyStateCmd(EBodyStateCmd::KnockDown) {} constexpr explicit CBCKnockDownCmd(const zeus::CVector3f& vec, pas::ESeverity severity) : CBodyStateCmd(EBodyStateCmd::KnockDown), x8_dir(vec), x14_severity(severity) {} constexpr const zeus::CVector3f& GetHitDirection() const { return x8_dir; } constexpr pas::ESeverity GetHitSeverity() const { return x14_severity; } }; class CBCSlideCmd : public CBodyStateCmd { pas::ESlideType x8_type = pas::ESlideType::Invalid; zeus::CVector3f xc_dir; public: constexpr explicit CBCSlideCmd() : CBodyStateCmd(EBodyStateCmd::Slide) {} constexpr explicit CBCSlideCmd(pas::ESlideType type, const zeus::CVector3f& dir) : CBodyStateCmd(EBodyStateCmd::Slide), x8_type(type), xc_dir(dir) {} constexpr pas::ESlideType GetSlideType() const { return x8_type; } constexpr const zeus::CVector3f& GetSlideDirection() const { return xc_dir; } }; class CBCScriptedCmd : public CBodyStateCmd { s32 x8_anim = -1; bool xc_24_loopAnim : 1 = false; bool xc_25_timedLoop : 1 = false; float x10_loopDur = 0.f; public: constexpr explicit CBCScriptedCmd() : CBodyStateCmd(EBodyStateCmd::Scripted) {} constexpr explicit CBCScriptedCmd(int i, bool b1, bool b2, float f) : CBodyStateCmd(EBodyStateCmd::Scripted), x8_anim(i), xc_24_loopAnim{b1}, xc_25_timedLoop{b2}, x10_loopDur(f) {} constexpr s32 GetAnimId() const { return x8_anim; } constexpr bool IsLooped() const { return xc_24_loopAnim; } constexpr bool GetUseLoopDuration() const { return xc_25_timedLoop; } constexpr float GetLoopDuration() const { return x10_loopDur; } }; class CBCCoverCmd : public CBodyStateCmd { pas::ECoverDirection x8_dir = pas::ECoverDirection::Invalid; zeus::CVector3f xc_targetPos; zeus::CVector3f x18_alignDir; public: constexpr explicit CBCCoverCmd() : CBodyStateCmd(EBodyStateCmd::Cover) {} constexpr explicit CBCCoverCmd(pas::ECoverDirection dir, const zeus::CVector3f& v1, const zeus::CVector3f& v2) : CBodyStateCmd(EBodyStateCmd::Cover), x8_dir(dir), xc_targetPos(v1), x18_alignDir(v2) {} constexpr pas::ECoverDirection GetDirection() const { return x8_dir; } constexpr const zeus::CVector3f& GetTarget() const { return xc_targetPos; } constexpr const zeus::CVector3f& GetAlignDirection() const { return x18_alignDir; } }; class CBCWallHangCmd : public CBodyStateCmd { TUniqueId x8_wpId = kInvalidUniqueId; public: constexpr explicit CBCWallHangCmd() : CBodyStateCmd(EBodyStateCmd::WallHang) {} constexpr explicit CBCWallHangCmd(TUniqueId uid) : CBodyStateCmd(EBodyStateCmd::WallHang), x8_wpId(uid) {} constexpr TUniqueId GetTarget() const { return x8_wpId; } }; class CBCAdditiveAimCmd : public CBodyStateCmd { public: constexpr explicit CBCAdditiveAimCmd() : CBodyStateCmd(EBodyStateCmd::AdditiveAim) {} }; class CBCAdditiveFlinchCmd : public CBodyStateCmd { float x8_weight = 1.f; public: constexpr explicit CBCAdditiveFlinchCmd() : CBodyStateCmd(EBodyStateCmd::AdditiveFlinch) {} constexpr explicit CBCAdditiveFlinchCmd(float f) : CBodyStateCmd(EBodyStateCmd::AdditiveFlinch), x8_weight(f) {} constexpr float GetWeight() const { return x8_weight; } }; class CBCAdditiveReactionCmd : public CBodyStateCmd { float x8_weight = 1.f; pas::EAdditiveReactionType xc_type = pas::EAdditiveReactionType::Invalid; bool x10_active = false; public: constexpr explicit CBCAdditiveReactionCmd() : CBodyStateCmd(EBodyStateCmd::AdditiveReaction) {} constexpr explicit CBCAdditiveReactionCmd(pas::EAdditiveReactionType type, float weight, bool active) : CBodyStateCmd(EBodyStateCmd::AdditiveReaction), x8_weight(weight), xc_type(type), x10_active(active) {} constexpr pas::EAdditiveReactionType GetType() const { return xc_type; } constexpr float GetWeight() const { return x8_weight; } constexpr bool GetIsActive() const { return x10_active; } }; class CBCLoopAttackCmd : public CBodyStateCmd { pas::ELoopAttackType x8_type = pas::ELoopAttackType::Invalid; bool xc_waitForAnimOver = false; public: constexpr explicit CBCLoopAttackCmd() : CBodyStateCmd(EBodyStateCmd::LoopAttack) {} constexpr explicit CBCLoopAttackCmd(pas::ELoopAttackType type, bool waitForAnimOver = false) : CBodyStateCmd(EBodyStateCmd::LoopAttack), x8_type(type), xc_waitForAnimOver(waitForAnimOver) {} constexpr pas::ELoopAttackType GetAttackType() const { return x8_type; } constexpr bool WaitForAnimOver() const { return xc_waitForAnimOver; } }; class CBCTauntCmd : public CBodyStateCmd { pas::ETauntType x8_type = pas::ETauntType::Invalid; public: constexpr explicit CBCTauntCmd() : CBodyStateCmd(EBodyStateCmd::Taunt) {} constexpr explicit CBCTauntCmd(pas::ETauntType type) : CBodyStateCmd(EBodyStateCmd::Taunt), x8_type(type) {} constexpr pas::ETauntType GetTauntType() const { return x8_type; } }; class CBCLocomotionCmd { zeus::CVector3f x0_move; zeus::CVector3f xc_face; float x18_weight; public: constexpr explicit CBCLocomotionCmd(const zeus::CVector3f& move, const zeus::CVector3f& face, float weight) : x0_move(move), xc_face(face), x18_weight(weight) {} constexpr const zeus::CVector3f& GetMoveVector() const { return x0_move; } constexpr const zeus::CVector3f& GetFaceVector() const { return xc_face; } constexpr float GetWeight() const { return x18_weight; } }; enum class ESteeringBlendMode { Normal, FullSpeed, Clamped }; class CBodyStateCmdMgr { zeus::CVector3f x0_move; zeus::CVector3f xc_face; zeus::CVector3f x18_target; zeus::CVector3f x24_additiveTarget; ESteeringBlendMode x30_steeringMode = ESteeringBlendMode::Normal; float x34_steeringSpeedMin = 0.f; float x38_steeringSpeedMax = 1.f; float x3c_steeringSpeed = 0.f; rstl::reserved_vector x40_commandTable; u32 xb4_deliveredCmdMask = 0; CBCGetupCmd xb8_getup; CBCStepCmd xc4_step; CBodyStateCmd xd4_die{EBodyStateCmd::Die}; CBCKnockDownCmd xdc_knockDown; CBCKnockBackCmd xf4_knockBack; CBCMeleeAttackCmd x10c_meleeAttack; CBCProjectileAttackCmd x128_projectileAttack; CBCLoopAttackCmd x144_loopAttack; CBCLoopReactionCmd x154_loopReaction; CBCLoopHitReactionCmd x160_loopHitReaction; CBodyStateCmd x16c_exitState{EBodyStateCmd::ExitState}; CBodyStateCmd x174_leanFromCover{EBodyStateCmd::LeanFromCover}; CBodyStateCmd x17c_nextState{EBodyStateCmd::NextState}; CBodyStateCmd x184_maintainVelocity{EBodyStateCmd::MaintainVelocity}; CBCGenerateCmd x18c_generate; CBCHurledCmd x1ac_hurled; CBCJumpCmd x1d0_jump; CBCSlideCmd x1f8_slide; CBCTauntCmd x210_taunt; CBCScriptedCmd x21c_scripted; CBCCoverCmd x230_cover; CBCWallHangCmd x254_wallHang; CBodyStateCmd x260_locomotion{EBodyStateCmd::Locomotion}; CBodyStateCmd x268_additiveIdle{EBodyStateCmd::AdditiveIdle}; CBCAdditiveAimCmd x270_additiveAim; CBCAdditiveFlinchCmd x278_additiveFlinch; CBCAdditiveReactionCmd x284_additiveReaction; CBodyStateCmd x298_stopReaction{EBodyStateCmd::StopReaction}; void DeliverCmd(EBodyStateCmd cmd) { xb4_deliveredCmdMask |= (1 << int(cmd)); } public: CBodyStateCmdMgr(); void DeliverCmd(const CBodyStateCmd& cmd) { *x40_commandTable[int(cmd.GetCommandId())] = cmd; DeliverCmd(cmd.GetCommandId()); } void DeliverCmd(const CBCGetupCmd& cmd) { xb8_getup = cmd; DeliverCmd(EBodyStateCmd::Getup); } void DeliverCmd(const CBCStepCmd& cmd) { xc4_step = cmd; DeliverCmd(EBodyStateCmd::Step); } void DeliverCmd(const CBCKnockDownCmd& cmd) { xdc_knockDown = cmd; DeliverCmd(EBodyStateCmd::KnockDown); } void DeliverCmd(const CBCKnockBackCmd& cmd) { xf4_knockBack = cmd; DeliverCmd(EBodyStateCmd::KnockBack); } void DeliverCmd(const CBCMeleeAttackCmd& cmd) { x10c_meleeAttack = cmd; DeliverCmd(EBodyStateCmd::MeleeAttack); } void DeliverCmd(const CBCProjectileAttackCmd& cmd) { x128_projectileAttack = cmd; DeliverCmd(EBodyStateCmd::ProjectileAttack); } void DeliverCmd(const CBCLoopAttackCmd& cmd) { x144_loopAttack = cmd; DeliverCmd(EBodyStateCmd::LoopAttack); } void DeliverCmd(const CBCLoopReactionCmd& cmd) { x154_loopReaction = cmd; DeliverCmd(EBodyStateCmd::LoopReaction); } void DeliverCmd(const CBCLoopHitReactionCmd& cmd) { x160_loopHitReaction = cmd; DeliverCmd(EBodyStateCmd::LoopHitReaction); } void DeliverCmd(const CBCGenerateCmd& cmd) { x18c_generate = cmd; DeliverCmd(EBodyStateCmd::Generate); } void DeliverCmd(const CBCHurledCmd& cmd) { x1ac_hurled = cmd; DeliverCmd(EBodyStateCmd::Hurled); } void DeliverCmd(const CBCJumpCmd& cmd) { x1d0_jump = cmd; DeliverCmd(EBodyStateCmd::Jump); } void DeliverCmd(const CBCSlideCmd& cmd) { x1f8_slide = cmd; DeliverCmd(EBodyStateCmd::Slide); } void DeliverCmd(const CBCTauntCmd& cmd) { x210_taunt = cmd; DeliverCmd(EBodyStateCmd::Taunt); } void DeliverCmd(const CBCScriptedCmd& cmd) { x21c_scripted = cmd; DeliverCmd(EBodyStateCmd::Scripted); } void DeliverCmd(const CBCCoverCmd& cmd) { x230_cover = cmd; DeliverCmd(EBodyStateCmd::Cover); } void DeliverCmd(const CBCWallHangCmd& cmd) { x254_wallHang = cmd; DeliverCmd(EBodyStateCmd::WallHang); } void DeliverCmd(const CBCAdditiveAimCmd& cmd) { x270_additiveAim = cmd; DeliverCmd(EBodyStateCmd::AdditiveAim); } void DeliverCmd(const CBCAdditiveFlinchCmd& cmd) { x278_additiveFlinch = cmd; DeliverCmd(EBodyStateCmd::AdditiveFlinch); } void DeliverCmd(const CBCAdditiveReactionCmd& cmd) { x284_additiveReaction = cmd; DeliverCmd(EBodyStateCmd::AdditiveReaction); } void DeliverCmd(const CBCLocomotionCmd& cmd); void DeliverFaceVector(const zeus::CVector3f& f) { xc_face = f; } void DeliverTargetVector(const zeus::CVector3f& t) { x18_target = t; } void DeliverAdditiveTargetVector(const zeus::CVector3f& t) { x24_additiveTarget = t; } void SetSteeringBlendSpeed(float s) { x3c_steeringSpeed = s; } void SetSteeringBlendMode(ESteeringBlendMode m) { x30_steeringMode = m; } void SetSteeringSpeedRange(float rmin, float rmax) { x34_steeringSpeedMin = rmin; x38_steeringSpeedMax = rmax; } void BlendSteeringCmds(); void Reset(); void ClearLocomotionCmds(); CBodyStateCmd* GetCmd(EBodyStateCmd cmd) { if ((xb4_deliveredCmdMask & (1U << u32(cmd))) != 0) { return x40_commandTable[size_t(cmd)]; } return nullptr; } const CBodyStateCmd* GetCmd(EBodyStateCmd cmd) const { if ((xb4_deliveredCmdMask & (1U << u32(cmd))) != 0) { return x40_commandTable[size_t(cmd)]; } return nullptr; } const zeus::CVector3f& GetMoveVector() const { return x0_move; } const zeus::CVector3f& GetFaceVector() const { return xc_face; } const zeus::CVector3f& GetTargetVector() const { return x18_target; } const zeus::CVector3f& GetAdditiveTargetVector() const { return x24_additiveTarget; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyStateInfo.cpp ================================================ #include "Runtime/Character/CBodyStateInfo.hpp" #include "Runtime/Character/CBodyController.hpp" #include "Runtime/World/CActor.hpp" namespace metaforce { CBodyStateInfo::CBodyStateInfo(CActor& actor, EBodyType type) { x34_24_changeLocoAtEndOfAnimOnly = false; const CPASDatabase& pasDatabase = actor.GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase(); for (size_t i = 0; i < pasDatabase.GetNumAnimStates(); ++i) { const CPASAnimState* state = pasDatabase.GetAnimStateByIndex(i); std::unique_ptr bs; switch (type) { case EBodyType::BiPedal: bs = SetupBiPedalBodyStates(state->GetStateId(), actor); break; case EBodyType::Restricted: default: bs = SetupRestrictedBodyStates(state->GetStateId(), actor); break; case EBodyType::Flyer: bs = SetupFlyerBodyStates(state->GetStateId(), actor); break; case EBodyType::Pitchable: bs = SetupPitchableBodyStates(state->GetStateId(), actor); break; case EBodyType::WallWalker: bs = SetupWallWalkerBodyStates(state->GetStateId(), actor); break; case EBodyType::NewFlyer: bs = SetupNewFlyerBodyStates(state->GetStateId(), actor); break; case EBodyType::RestrictedFlyer: bs = SetupRestrictedFlyerBodyStates(state->GetStateId(), actor); break; } if (bs) x0_stateMap[pas::EAnimationState(state->GetStateId())] = std::move(bs); } x1c_additiveStates.reserve(4); x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveIdle, std::make_unique()); x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveAim, std::make_unique()); x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveFlinch, std::make_unique()); x1c_additiveStates.emplace_back(pas::EAnimationState::AdditiveReaction, std::make_unique()); } std::unique_ptr CBodyStateInfo::SetupRestrictedFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const { switch (stateId) { case pas::EAnimationState::Fall: return std::make_unique(); case pas::EAnimationState::Getup: return std::make_unique(); case pas::EAnimationState::LieOnGround: return std::make_unique(actor); case pas::EAnimationState::Step: return std::make_unique(); case pas::EAnimationState::Death: return std::make_unique(); case pas::EAnimationState::Locomotion: return std::make_unique(actor); case pas::EAnimationState::KnockBack: return std::make_unique(); case pas::EAnimationState::MeleeAttack: return std::make_unique(); case pas::EAnimationState::ProjectileAttack: return std::make_unique(); case pas::EAnimationState::LoopAttack: return std::make_unique(); case pas::EAnimationState::Turn: return std::make_unique(); case pas::EAnimationState::LoopReaction: return std::make_unique(); case pas::EAnimationState::GroundHit: return std::make_unique(); case pas::EAnimationState::Generate: return std::make_unique(); case pas::EAnimationState::Jump: return std::make_unique(); case pas::EAnimationState::Hurled: return std::make_unique(); case pas::EAnimationState::Slide: return std::make_unique(); case pas::EAnimationState::Taunt: return std::make_unique(); case pas::EAnimationState::Scripted: return std::make_unique(); default: return {}; } } std::unique_ptr CBodyStateInfo::SetupNewFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const { switch (stateId) { case pas::EAnimationState::Fall: return std::make_unique(); case pas::EAnimationState::Getup: return std::make_unique(); case pas::EAnimationState::LieOnGround: return std::make_unique(actor); case pas::EAnimationState::Step: return std::make_unique(); case pas::EAnimationState::Death: return std::make_unique(); case pas::EAnimationState::Locomotion: return std::make_unique(actor); case pas::EAnimationState::KnockBack: return std::make_unique(); case pas::EAnimationState::MeleeAttack: return std::make_unique(); case pas::EAnimationState::ProjectileAttack: return std::make_unique(); case pas::EAnimationState::LoopAttack: return std::make_unique(); case pas::EAnimationState::Turn: return std::make_unique(); case pas::EAnimationState::LoopReaction: return std::make_unique(); case pas::EAnimationState::GroundHit: return std::make_unique(); case pas::EAnimationState::Generate: return std::make_unique(); case pas::EAnimationState::Jump: return std::make_unique(); case pas::EAnimationState::Hurled: return std::make_unique(); case pas::EAnimationState::Slide: return std::make_unique(); case pas::EAnimationState::Taunt: return std::make_unique(); case pas::EAnimationState::Scripted: return std::make_unique(); default: return {}; } } std::unique_ptr CBodyStateInfo::SetupWallWalkerBodyStates(pas::EAnimationState stateId, CActor& actor) const { switch (stateId) { case pas::EAnimationState::Fall: return std::make_unique(); case pas::EAnimationState::Getup: return std::make_unique(); case pas::EAnimationState::LieOnGround: return std::make_unique(actor); case pas::EAnimationState::Step: return std::make_unique(); case pas::EAnimationState::Death: return std::make_unique(); case pas::EAnimationState::Locomotion: return std::make_unique(actor); case pas::EAnimationState::KnockBack: return std::make_unique(); case pas::EAnimationState::MeleeAttack: return std::make_unique(); case pas::EAnimationState::ProjectileAttack: return std::make_unique(); case pas::EAnimationState::LoopAttack: return std::make_unique(); case pas::EAnimationState::Turn: return std::make_unique(); case pas::EAnimationState::LoopReaction: return std::make_unique(); case pas::EAnimationState::GroundHit: return std::make_unique(); case pas::EAnimationState::Generate: return std::make_unique(); case pas::EAnimationState::Jump: return std::make_unique(); case pas::EAnimationState::Hurled: return std::make_unique(); case pas::EAnimationState::Slide: return std::make_unique(); case pas::EAnimationState::Taunt: return std::make_unique(); case pas::EAnimationState::Scripted: return std::make_unique(); default: return {}; } } std::unique_ptr CBodyStateInfo::SetupPitchableBodyStates(pas::EAnimationState stateId, CActor& actor) const { switch (stateId) { case pas::EAnimationState::Fall: return std::make_unique(); case pas::EAnimationState::Getup: return std::make_unique(); case pas::EAnimationState::LieOnGround: return std::make_unique(actor); case pas::EAnimationState::Step: return std::make_unique(); case pas::EAnimationState::Death: return std::make_unique(); case pas::EAnimationState::Locomotion: return std::make_unique(actor, true); case pas::EAnimationState::KnockBack: return std::make_unique(); case pas::EAnimationState::MeleeAttack: return std::make_unique(); case pas::EAnimationState::ProjectileAttack: return std::make_unique(); case pas::EAnimationState::LoopAttack: return std::make_unique(); case pas::EAnimationState::Turn: return std::make_unique(); case pas::EAnimationState::LoopReaction: return std::make_unique(); case pas::EAnimationState::GroundHit: return std::make_unique(); case pas::EAnimationState::Generate: return std::make_unique(); case pas::EAnimationState::Jump: return std::make_unique(); case pas::EAnimationState::Hurled: return std::make_unique(); case pas::EAnimationState::Slide: return std::make_unique(); case pas::EAnimationState::Taunt: return std::make_unique(); case pas::EAnimationState::Scripted: return std::make_unique(); default: return {}; } } std::unique_ptr CBodyStateInfo::SetupFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const { switch (stateId) { case pas::EAnimationState::Fall: return std::make_unique(); case pas::EAnimationState::Getup: return std::make_unique(); case pas::EAnimationState::LieOnGround: return std::make_unique(actor); case pas::EAnimationState::Step: return std::make_unique(); case pas::EAnimationState::Death: return std::make_unique(); case pas::EAnimationState::Locomotion: return std::make_unique(actor, false); case pas::EAnimationState::KnockBack: return std::make_unique(); case pas::EAnimationState::MeleeAttack: return std::make_unique(); case pas::EAnimationState::ProjectileAttack: return std::make_unique(); case pas::EAnimationState::LoopAttack: return std::make_unique(); case pas::EAnimationState::Turn: return std::make_unique(); case pas::EAnimationState::LoopReaction: return std::make_unique(); case pas::EAnimationState::GroundHit: return std::make_unique(); case pas::EAnimationState::Generate: return std::make_unique(); case pas::EAnimationState::Jump: return std::make_unique(); case pas::EAnimationState::Hurled: return std::make_unique(); case pas::EAnimationState::Slide: return std::make_unique(); case pas::EAnimationState::Taunt: return std::make_unique(); case pas::EAnimationState::Scripted: return std::make_unique(); default: return {}; } } std::unique_ptr CBodyStateInfo::SetupRestrictedBodyStates(pas::EAnimationState stateId, CActor& actor) const { switch (stateId) { case pas::EAnimationState::Fall: return std::make_unique(); case pas::EAnimationState::Getup: return std::make_unique(); case pas::EAnimationState::LieOnGround: return std::make_unique(actor); case pas::EAnimationState::Step: return std::make_unique(); case pas::EAnimationState::Death: return std::make_unique(); case pas::EAnimationState::Locomotion: return std::make_unique(actor); case pas::EAnimationState::KnockBack: return std::make_unique(); case pas::EAnimationState::MeleeAttack: return std::make_unique(); case pas::EAnimationState::ProjectileAttack: return std::make_unique(); case pas::EAnimationState::LoopAttack: return std::make_unique(); case pas::EAnimationState::Turn: return std::make_unique(); case pas::EAnimationState::LoopReaction: return std::make_unique(); case pas::EAnimationState::GroundHit: return std::make_unique(); case pas::EAnimationState::Generate: return std::make_unique(); case pas::EAnimationState::Jump: return std::make_unique(); case pas::EAnimationState::Hurled: return std::make_unique(); case pas::EAnimationState::Slide: return std::make_unique(); case pas::EAnimationState::Taunt: return std::make_unique(); case pas::EAnimationState::Scripted: return std::make_unique(); case pas::EAnimationState::Cover: return std::make_unique(); default: return {}; } } std::unique_ptr CBodyStateInfo::SetupBiPedalBodyStates(pas::EAnimationState stateId, CActor& actor) const { switch (stateId) { case pas::EAnimationState::Fall: return std::make_unique(); case pas::EAnimationState::Getup: return std::make_unique(); case pas::EAnimationState::LieOnGround: return std::make_unique(actor); case pas::EAnimationState::Step: return std::make_unique(); case pas::EAnimationState::Death: return std::make_unique(); case pas::EAnimationState::Locomotion: return std::make_unique(actor); case pas::EAnimationState::KnockBack: return std::make_unique(); case pas::EAnimationState::MeleeAttack: return std::make_unique(); case pas::EAnimationState::ProjectileAttack: return std::make_unique(); case pas::EAnimationState::LoopAttack: return std::make_unique(); case pas::EAnimationState::Turn: return std::make_unique(); case pas::EAnimationState::LoopReaction: return std::make_unique(); case pas::EAnimationState::GroundHit: return std::make_unique(); case pas::EAnimationState::Generate: return std::make_unique(); case pas::EAnimationState::Jump: return std::make_unique(); case pas::EAnimationState::Hurled: return std::make_unique(); case pas::EAnimationState::Slide: return std::make_unique(); case pas::EAnimationState::Taunt: return std::make_unique(); case pas::EAnimationState::Scripted: return std::make_unique(); case pas::EAnimationState::Cover: return std::make_unique(); case pas::EAnimationState::WallHang: return std::make_unique(); default: return {}; } } float CBodyStateInfo::GetLocomotionSpeed(pas::ELocomotionAnim anim) const { auto search = x0_stateMap.find(pas::EAnimationState::Locomotion); if (search != x0_stateMap.cend() && search->second && x18_bodyController) { const CBSLocomotion& bs = static_cast(*search->second); return bs.GetLocomotionSpeed(x18_bodyController->GetLocomotionType(), anim); } return 0.f; } float CBodyStateInfo::GetMaxSpeed() const { float ret = GetLocomotionSpeed(pas::ELocomotionAnim::Run); if (std::fabs(ret) < 0.00001f) { for (int i = 0; i < 8; ++i) { float tmp = GetLocomotionSpeed(pas::ELocomotionAnim(i)); if (tmp > ret) ret = tmp; } } return ret; } CBodyState* CBodyStateInfo::GetCurrentState() { auto search = x0_stateMap.find(x14_state); if (search == x0_stateMap.end()) return nullptr; return search->second.get(); } const CBodyState* CBodyStateInfo::GetCurrentState() const { auto search = x0_stateMap.find(x14_state); if (search == x0_stateMap.end()) return nullptr; return search->second.get(); } void CBodyStateInfo::SetState(pas::EAnimationState s) { auto search = x0_stateMap.find(s); if (search == x0_stateMap.end()) return; x14_state = s; } CAdditiveBodyState* CBodyStateInfo::GetCurrentAdditiveState() { for (auto& state : x1c_additiveStates) { if (x2c_additiveState == state.first) return state.second.get(); } return nullptr; } void CBodyStateInfo::SetAdditiveState(pas::EAnimationState s) { for (auto& state : x1c_additiveStates) { if (s == state.first) { x2c_additiveState = s; return; } } } bool CBodyStateInfo::ApplyHeadTracking() const { if (x14_state == pas::EAnimationState::Invalid) return false; return GetCurrentState()->ApplyHeadTracking(); } } // namespace metaforce ================================================ FILE: Runtime/Character/CBodyStateInfo.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CAdditiveBodyState.hpp" #include "Runtime/Character/CBodyState.hpp" #include "Runtime/Character/CharacterCommon.hpp" namespace metaforce { class CActor; class CBodyStateInfo { friend class CBodyController; std::map> x0_stateMap; pas::EAnimationState x14_state = pas::EAnimationState::Invalid; CBodyController* x18_bodyController = nullptr; std::vector>> x1c_additiveStates; pas::EAnimationState x2c_additiveState = pas::EAnimationState::AdditiveIdle; float x30_maxPitch = 0.f; bool x34_24_changeLocoAtEndOfAnimOnly; std::unique_ptr SetupRestrictedFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const; std::unique_ptr SetupNewFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const; std::unique_ptr SetupWallWalkerBodyStates(pas::EAnimationState stateId, CActor& actor) const; std::unique_ptr SetupPitchableBodyStates(pas::EAnimationState stateId, CActor& actor) const; std::unique_ptr SetupFlyerBodyStates(pas::EAnimationState stateId, CActor& actor) const; std::unique_ptr SetupRestrictedBodyStates(pas::EAnimationState stateId, CActor& actor) const; std::unique_ptr SetupBiPedalBodyStates(pas::EAnimationState stateId, CActor& actor) const; public: CBodyStateInfo(CActor& actor, EBodyType type); float GetLocomotionSpeed(pas::ELocomotionAnim anim) const; float GetMaxSpeed() const; float GetMaximumPitch() const { return x30_maxPitch; } void SetMaximumPitch(float pitch) { x30_maxPitch = pitch; } bool GetLocoAnimChangeAtEndOfAnimOnly() const { return x34_24_changeLocoAtEndOfAnimOnly; } void SetLocoAnimChangeAtEndOfAnimOnly(bool s) { x34_24_changeLocoAtEndOfAnimOnly = s; } CBodyState* GetCurrentState(); const CBodyState* GetCurrentState() const; pas::EAnimationState GetCurrentStateId() const { return x14_state; } void SetState(pas::EAnimationState s); CAdditiveBodyState* GetCurrentAdditiveState(); pas::EAnimationState GetCurrentAdditiveStateId() const { return x2c_additiveState; } void SetAdditiveState(pas::EAnimationState s); bool ApplyHeadTracking() const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CBoneTracking.cpp ================================================ #include "Runtime/Character/CBoneTracking.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Character/CAnimData.hpp" #include "Runtime/Character/CBodyController.hpp" #include "Runtime/Character/CHierarchyPoseBuilder.hpp" #include "Runtime/World/CPatterned.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CBoneTracking::CBoneTracking(const CAnimData& animData, std::string_view bone, float maxTrackingAngle, float angSpeed, EBoneTrackingFlags flags) : x14_segId(animData.GetCharLayoutInfo().GetSegIdFromString(bone)) , x1c_maxTrackingAngle(maxTrackingAngle) , x20_angSpeed(angSpeed) , x36_26_noParent(True(flags & EBoneTrackingFlags::NoParent)) , x36_27_noParentOrigin(True(flags & EBoneTrackingFlags::NoParentOrigin)) , x36_28_noHorizontalAim(True(flags & EBoneTrackingFlags::NoHorizontalAim)) , x36_29_parentIk(True(flags & EBoneTrackingFlags::ParentIk)) {} void CBoneTracking::Update(float dt) { x18_time += dt; } void CBoneTracking::PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& xf, const zeus::CVector3f& vec, const CBodyController& bodyController) { TCastToPtr patterned = bodyController.GetOwner(); PreRender(mgr, animData, xf, vec, (bodyController.GetBodyStateInfo().ApplyHeadTracking() && patterned && patterned->ApplyBoneTracking())); } void CBoneTracking::PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& worldXf, const zeus::CVector3f& localOffsetScale, bool tracking) { if (x14_segId == 0) return; CHierarchyPoseBuilder& pb = animData.PoseBuilder(); TCastToConstPtr targetAct = mgr.GetObjectById(x34_target); if (x36_24_active && tracking && (targetAct || x24_targetPosition)) { x36_25_hasTrackedRotation = true; const auto& layoutInfo = pb.CharLayoutInfo(); CSegId bone; if (x36_26_noParent) bone = x14_segId; else bone = layoutInfo->GetRootNode()->GetBoneMap()[x14_segId].x0_parentId; zeus::CTransform parentBoneXf; pb.BuildTransform(bone, parentBoneXf); zeus::CVector3f pos = parentBoneXf.origin; if (x36_27_noParentOrigin && !x36_26_noParent) { zeus::CTransform thisBoneXf; pb.BuildTransform(x14_segId, thisBoneXf); pos = thisBoneXf.origin; } parentBoneXf.origin = pos * localOffsetScale; zeus::CTransform finalXf = worldXf * parentBoneXf; zeus::CVector3f localDir = finalXf .transposeRotate((targetAct ? targetAct->GetAimPosition(mgr, 0.f) : *x24_targetPosition) - finalXf.origin) .normalized(); if (x36_28_noHorizontalAim) localDir = zeus::CVector3f(0.f, localDir.toVec2f().magnitude(), localDir.z()); if (x36_29_parentIk) { float negElev = -parentBoneXf.basis[1].z(); zeus::CVector3f ikBase(0.f, std::sqrt(1.f - negElev * negElev), negElev); float angle = zeus::CVector3f::getAngleDiff(ikBase, localDir); angle = std::min(angle, x1c_maxTrackingAngle); localDir = zeus::CVector3f::slerp(ikBase, localDir, angle); } else { float angle = zeus::CVector3f::getAngleDiff(zeus::skForward, localDir); angle = std::min(angle, x1c_maxTrackingAngle); localDir = zeus::CVector3f::slerp(zeus::skForward, localDir, angle); } float angle = zeus::CVector3f::getAngleDiff(x0_curRotation.transform(zeus::skForward), localDir); float clampedAngle = std::min(angle, x18_time * x20_angSpeed); if (clampedAngle > 1.0e-05f) { x0_curRotation = zeus::CQuaternion::slerpShort( x0_curRotation, zeus::CQuaternion::lookAt(zeus::skForward, zeus::CUnitVector3f(localDir), 2.f * M_PIF), clampedAngle / angle); } pb.GetTreeMap()[x14_segId].x4_rotation = x0_curRotation; animData.MarkPoseDirty(); } else if (x36_25_hasTrackedRotation) { zeus::CQuaternion qb = pb.GetTreeMap()[x14_segId].x4_rotation; float angle = zeus::CVector3f::getAngleDiff(x0_curRotation.transform(zeus::skForward), qb.transform(zeus::skForward)); float maxAngDelta = x18_time * x20_angSpeed; float clampedAngle = std::min(angle, maxAngDelta); if (clampedAngle > 0.5f * maxAngDelta) { x0_curRotation = zeus::CQuaternion::slerpShort(x0_curRotation, qb, clampedAngle / angle); pb.GetTreeMap()[x14_segId].x4_rotation = x0_curRotation; animData.MarkPoseDirty(); } else { x36_25_hasTrackedRotation = false; x0_curRotation = qb; } } else { x0_curRotation = pb.GetTreeMap()[x14_segId].x4_rotation; } x18_time = 0.f; } void CBoneTracking::SetActive(bool active) { x36_24_active = active; } void CBoneTracking::SetTarget(TUniqueId target) { x34_target = target; } void CBoneTracking::UnsetTarget() { x34_target = kInvalidUniqueId; } void CBoneTracking::SetTargetPosition(const zeus::CVector3f& targetPos) { x24_targetPosition = targetPos; } void CBoneTracking::SetNoHorizontalAim(bool b) { x36_28_noHorizontalAim = b; } } // namespace metaforce ================================================ FILE: Runtime/Character/CBoneTracking.hpp ================================================ #pragma once #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CSegId.hpp" #include #include #include namespace metaforce { class CAnimData; class CStateManager; class CBodyController; enum class EBoneTrackingFlags { None = 0, NoParent = 1, NoParentOrigin = 2, NoHorizontalAim = 4, ParentIk = 8 }; ENABLE_BITWISE_ENUM(EBoneTrackingFlags) class CBoneTracking { zeus::CQuaternion x0_curRotation = zeus::CQuaternion(); float x10_ = 0.f; CSegId x14_segId; float x18_time = 0.f; float x1c_maxTrackingAngle; float x20_angSpeed; std::optional x24_targetPosition; TUniqueId x34_target = kInvalidUniqueId; bool x36_24_active : 1 = false; bool x36_25_hasTrackedRotation : 1 = false; bool x36_26_noParent : 1; bool x36_27_noParentOrigin : 1; bool x36_28_noHorizontalAim : 1; bool x36_29_parentIk : 1; public: CBoneTracking(const CAnimData& animData, std::string_view bone, float maxTrackingAngle, float angSpeed, EBoneTrackingFlags flags); void Update(float dt); void PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& xf, const zeus::CVector3f& vec, const CBodyController& bodyController); void PreRender(const CStateManager& mgr, CAnimData& animData, const zeus::CTransform& worldXf, const zeus::CVector3f& localOffsetScale, bool tracking); void SetActive(bool active); void SetTarget(TUniqueId id); void UnsetTarget(); void SetTargetPosition(const zeus::CVector3f& pos); void SetNoHorizontalAim(bool b); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CBoolPOINode.cpp ================================================ #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CAnimSourceReader.hpp" namespace metaforce { CBoolPOINode::CBoolPOINode() : CPOINode("root", EPOIType::EmptyBool, CCharAnimTime(), -1, false, 1.f, -1, 0) {} CBoolPOINode::CBoolPOINode(CInputStream& in) : CPOINode(in), x38_val(in.ReadBool()) {} CBoolPOINode CBoolPOINode::CopyNodeMinusStartTime(const CBoolPOINode& node, const CCharAnimTime& startTime) { CBoolPOINode ret = node; ret.x1c_time -= startTime; return ret; } } // namespace metaforce ================================================ FILE: Runtime/Character/CBoolPOINode.hpp ================================================ #pragma once #include "Runtime/Character/CPOINode.hpp" namespace metaforce { class IAnimSourceInfo; class CBoolPOINode : public CPOINode { bool x38_val = false; public: explicit CBoolPOINode(); explicit CBoolPOINode(CInputStream& in); bool GetValue() const { return x38_val; } static CBoolPOINode CopyNodeMinusStartTime(const CBoolPOINode& node, const CCharAnimTime& startTime); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CCharAnimTime.cpp ================================================ #include "Runtime/Character/CCharAnimTime.hpp" #include #include namespace metaforce { bool CCharAnimTime::EqualsZero() const { if (x4_type == EType::ZeroIncreasing || x4_type == EType::ZeroSteady || x4_type == EType::ZeroDecreasing) return true; return x0_time == 0.f; } bool CCharAnimTime::EpsilonZero() const { return (std::fabs(x0_time) < 0.00001f); } bool CCharAnimTime::GreaterThanZero() const { if (EqualsZero()) return false; return x0_time > 0.f; } bool CCharAnimTime::operator==(const CCharAnimTime& other) const { if (x4_type == EType::NonZero) { if (other.x4_type == EType::NonZero) return x0_time == other.x0_time; return false; } if (EqualsZero()) { if (other.EqualsZero()) { int type = -1; if (x4_type != EType::ZeroDecreasing) { if (x4_type != EType::ZeroSteady) type = 1; else type = 0; } int otherType = -1; if (other.x4_type != EType::ZeroDecreasing) { if (other.x4_type != EType::ZeroSteady) otherType = 1; else otherType = 0; } return type == otherType; } return false; } if (other.x4_type == EType::Infinity) return x0_time * other.x0_time > 0.f; return false; } bool CCharAnimTime::operator!=(const CCharAnimTime& other) const { return !(*this == other); } bool CCharAnimTime::operator>=(const CCharAnimTime& other) const { if (*this == other) return true; return *this > other; } bool CCharAnimTime::operator<=(const CCharAnimTime& other) const { if (*this == other) return true; return *this < other; } bool CCharAnimTime::operator>(const CCharAnimTime& other) const { return (!(*this == other) && !(*this < other)); } bool CCharAnimTime::operator<(const CCharAnimTime& other) const { if (x4_type == EType::NonZero) { if (other.x4_type == EType::NonZero) { return x0_time < other.x0_time; } return other.EqualsZero() ? x0_time < 0.f : other.x0_time > 0; } if (!EqualsZero()) { if (other.x4_type == EType::Infinity) { return x0_time >= 0 || other.x0_time <= 0.f; } return x0_time < 0.f; } if (!other.EqualsZero()) { if (other.x4_type == EType::NonZero) { return other.x0_time > 0.f; } return other.x0_time > 0.f; } int type = x4_type == EType::ZeroDecreasing ? -1 : x4_type == EType::ZeroSteady ? 0 : 1; int otherType = other.x4_type == EType::ZeroDecreasing ? -1 : other.x4_type == EType::ZeroSteady ? 0 : 1; return type < otherType; } CCharAnimTime& CCharAnimTime::operator*=(const CCharAnimTime& other) { return *this = *this * other; } CCharAnimTime& CCharAnimTime::operator+=(const CCharAnimTime& other) { return *this = *this + other; } CCharAnimTime CCharAnimTime::operator+(const CCharAnimTime& other) const { if (x4_type == EType::Infinity && other.x4_type == EType::Infinity) { if (other.x0_time != x0_time) return {}; return *this; } else if (x4_type == EType::Infinity) return *this; else if (other.x4_type == EType::Infinity) return other; if (!EqualsZero() || !other.EqualsZero()) return {x0_time + other.x0_time}; int type = -1; if (x4_type != EType::ZeroDecreasing) { type = x4_type == EType::ZeroSteady ? 0 : 1; } int otherType = -1; if (other.x4_type != EType::ZeroDecreasing) { otherType = other.x4_type == EType::ZeroSteady ? 0 : 1; } type += otherType; otherType = std::max(-1, std::min(type, 1)); if (otherType == -1) return {EType::ZeroDecreasing, 0.f}; else if (otherType == 0) return {EType::ZeroSteady, 0.f}; else return {EType::ZeroIncreasing, 0.f}; } CCharAnimTime& CCharAnimTime::operator-=(const CCharAnimTime& other) { return *this = *this - other; } CCharAnimTime CCharAnimTime::operator-(const CCharAnimTime& other) const { if (x4_type == EType::Infinity && other.x4_type == EType::Infinity) { if (other.x0_time == x0_time) return {}; return *this; } else if (x4_type == EType::Infinity) return *this; else if (other.x4_type == EType::Infinity) { return {EType::Infinity, -other.x0_time}; } if (!EqualsZero() || !other.EqualsZero()) return {x0_time - other.x0_time}; int type = -1; if (x4_type != EType::ZeroDecreasing) { if (x4_type != EType::ZeroSteady) type = 1; else type = 0; } int otherType = -1; if (other.x4_type != EType::ZeroDecreasing) { if (other.x4_type != EType::ZeroSteady) otherType = 1; else otherType = 0; } type -= otherType; if (type == -1) return {EType::ZeroDecreasing, 0.f}; else if (type == 0) return {EType::ZeroSteady, 0.f}; else return {EType::ZeroIncreasing, 0.f}; } CCharAnimTime CCharAnimTime::operator*(const CCharAnimTime& other) const { if (x4_type == EType::Infinity && other.x4_type == EType::Infinity) { if (other.x0_time != x0_time) return {}; return *this; } else if (x4_type == EType::Infinity) return *this; else if (other.x4_type == EType::Infinity) return other; if (!EqualsZero() || !other.EqualsZero()) return {x0_time * other.x0_time}; int type = -1; if (x4_type != EType::ZeroDecreasing) { if (x4_type != EType::ZeroSteady) type = 1; else type = 0; } int otherType = -1; if (other.x4_type != EType::ZeroDecreasing) { if (other.x4_type != EType::ZeroSteady) otherType = 1; else otherType = 0; } type += otherType; otherType = std::max(-1, std::min(type, 1)); if (otherType == -1) return {EType::ZeroDecreasing, 0.f}; else if (otherType == 0) return {EType::ZeroSteady, 0.f}; else return {EType::ZeroIncreasing, 0.f}; } CCharAnimTime CCharAnimTime::operator*(const float& other) const { if (other == 0.f) return {}; if (!EqualsZero()) return {x0_time * other}; if (other > 0.f) return *this; if (x4_type == EType::ZeroDecreasing) { return {EType::ZeroIncreasing, 0.f}; } else if (x4_type == EType::ZeroSteady) { return {EType::ZeroSteady, 0.f}; } else { return {EType::ZeroDecreasing, 0.f}; } } float CCharAnimTime::operator/(const CCharAnimTime& other) const { if (other.EqualsZero()) return 0.f; return x0_time / other.x0_time; } } // namespace metaforce ================================================ FILE: Runtime/Character/CCharAnimTime.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #undef min #undef max namespace metaforce { class CCharAnimTime { public: enum class EType { NonZero, ZeroIncreasing, ZeroSteady, ZeroDecreasing, Infinity }; private: float x0_time = 0.f; EType x4_type = EType::ZeroSteady; public: constexpr CCharAnimTime() = default; constexpr CCharAnimTime(float time) : x0_time(time), x4_type(x0_time != 0.f ? EType::NonZero : EType::ZeroSteady) {} constexpr CCharAnimTime(EType type, float t) : x0_time(t), x4_type(type) {} explicit CCharAnimTime(CInputStream& in) : x0_time(in.ReadFloat()), x4_type(EType(in.ReadLong())) {} static constexpr CCharAnimTime Infinity() { return {EType::Infinity, 1.0f}; } float GetSeconds() const { return x0_time; } bool EqualsZero() const; bool EpsilonZero() const; bool GreaterThanZero() const; bool operator==(const CCharAnimTime& other) const; bool operator!=(const CCharAnimTime& other) const; bool operator>=(const CCharAnimTime& other) const; bool operator<=(const CCharAnimTime& other) const; bool operator>(const CCharAnimTime& other) const; bool operator<(const CCharAnimTime& other) const; CCharAnimTime& operator*=(const CCharAnimTime& other); CCharAnimTime& operator+=(const CCharAnimTime& other); CCharAnimTime operator+(const CCharAnimTime& other) const; CCharAnimTime& operator-=(const CCharAnimTime& other); CCharAnimTime operator-(const CCharAnimTime& other) const; CCharAnimTime operator*(const CCharAnimTime& other) const; CCharAnimTime operator*(const float& other) const; float operator/(const CCharAnimTime& other) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CCharLayoutInfo.cpp ================================================ #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/CToken.hpp" namespace metaforce { zeus::CVector3f CCharLayoutInfo::GetFromParentUnrotated(const CSegId& id) const { const CCharLayoutNode::Bone& bone = x0_node->GetBoneMap()[id]; if (x0_node->GetBoneMap().HasElement(bone.x0_parentId)) { const CCharLayoutNode::Bone& parent = x0_node->GetBoneMap()[bone.x0_parentId]; return bone.x4_origin - parent.x4_origin; } else { return bone.x4_origin; } } zeus::CVector3f CCharLayoutInfo::GetFromRootUnrotated(const CSegId& id) const { const CCharLayoutNode::Bone& bone = x0_node->GetBoneMap()[id]; return bone.x4_origin; } CSegId CCharLayoutInfo::GetSegIdFromString(std::string_view name) const { const auto it = x18_segIdMap.find(name); if (it == x18_segIdMap.cend()) { return {}; } return it->second; } void CCharLayoutNode::Bone::read(CInputStream& in) { x0_parentId = CSegId(in); x4_origin = in.Get(); const u32 chCount = in.ReadLong(); x10_children.reserve(chCount); for (u32 i = 0; i < chCount; ++i) { x10_children.emplace_back(in); } } CCharLayoutNode::CCharLayoutNode(CInputStream& in) : x0_boneMap(in.ReadLong()) { const u32 cap = x0_boneMap.GetCapacity(); for (u32 i = 0; i < cap; ++i) { const u32 thisId = in.ReadLong(); Bone& bone = x0_boneMap[thisId]; bone.read(in); } } CCharLayoutInfo::CCharLayoutInfo(CInputStream& in) : x0_node(std::make_shared(in)), x8_segIdList(in) { const u32 mapCount = in.ReadLong(); for (u32 i = 0; i < mapCount; ++i) { std::string key = in.Get(); x18_segIdMap.emplace(std::move(key), in); } } CFactoryFnReturn FCharLayoutInfo(const SObjectTag&, CInputStream& in, const CVParamTransfer&, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CCharLayoutInfo.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CSegId.hpp" #include "Runtime/Character/CSegIdList.hpp" #include "Runtime/Character/TSegIdMap.hpp" #include namespace metaforce { class CCharLayoutNode { public: struct Bone { CSegId x0_parentId; zeus::CVector3f x4_origin; std::vector x10_children; void read(CInputStream& in); }; private: TSegIdMap x0_boneMap; public: explicit CCharLayoutNode(CInputStream& in); const TSegIdMap& GetBoneMap() const { return x0_boneMap; } }; class CCharLayoutInfo { std::shared_ptr x0_node; CSegIdList x8_segIdList; std::map> x18_segIdMap; public: explicit CCharLayoutInfo(CInputStream& in); const std::shared_ptr& GetRootNode() const { return x0_node; } const CSegIdList& GetSegIdList() const { return x8_segIdList; } zeus::CVector3f GetFromParentUnrotated(const CSegId& id) const; zeus::CVector3f GetFromRootUnrotated(const CSegId& id) const; CSegId GetSegIdFromString(std::string_view name) const; }; CFactoryFnReturn FCharLayoutInfo(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Character/CCharacterFactory.cpp ================================================ #include "Runtime/Character/CCharacterFactory.hpp" #include "Runtime/CRandom16.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Character/CAdditiveAnimPlayback.hpp" #include "Runtime/Character/CAnimCharacterSet.hpp" #include "Runtime/Character/CAnimData.hpp" #include "Runtime/Character/CAnimationDatabaseGame.hpp" #include "Runtime/Character/CAnimationManager.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CParticleGenInfo.hpp" #include "Runtime/Character/CPrimitive.hpp" #include "Runtime/Character/CTransitionDatabaseGame.hpp" #include "Runtime/Character/CTransitionManager.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" namespace metaforce { CFactoryFnReturn CCharacterFactory::CDummyFactory::Build(const SObjectTag& tag, const CVParamTransfer& params, CObjectReference* selfRef) { const CCharacterInfo& charInfo = *params.GetOwnedObj(); switch (tag.type.toUint32()) { case 0: return TToken::GetIObjObjectFor(std::make_unique( *g_SimplePool, charInfo.GetModelId(), charInfo.GetSkinRulesId(), charInfo.GetCharLayoutInfoId())); case 1: return TToken::GetIObjObjectFor(std::make_unique( *g_SimplePool, charInfo.GetIceModelId(), charInfo.GetIceSkinRulesId(), charInfo.GetCharLayoutInfoId())); default: break; } return {}; } void CCharacterFactory::CDummyFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& parms, std::unique_ptr* objOut, CObjectReference* selfRef) { *objOut = Build(tag, parms, selfRef); } void CCharacterFactory::CDummyFactory::CancelBuild(const SObjectTag&) {} bool CCharacterFactory::CDummyFactory::CanBuild(const SObjectTag&) { return true; } const SObjectTag* CCharacterFactory::CDummyFactory::GetResourceIdByName(std::string_view) const { return nullptr; } FourCC CCharacterFactory::CDummyFactory::GetResourceTypeById(CAssetId id) const { return {}; } void CCharacterFactory::CDummyFactory::EnumerateResources(const std::function& lambda) const {} void CCharacterFactory::CDummyFactory::EnumerateNamedResources( const std::function& lambda) const {} u32 CCharacterFactory::CDummyFactory::ResourceSize(const metaforce::SObjectTag& tag) { return 0; } std::shared_ptr CCharacterFactory::CDummyFactory::LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) { return {}; } std::shared_ptr CCharacterFactory::CDummyFactory::LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size, void* target) { return {}; } std::unique_ptr CCharacterFactory::CDummyFactory::LoadResourceSync(const metaforce::SObjectTag& tag) { return {}; } std::unique_ptr CCharacterFactory::CDummyFactory::LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) { return {}; } std::unique_ptr CCharacterFactory::CreateCharacter(int charIdx, bool loop, const TLockedToken& factory, int defaultAnim) { const CCharacterInfo& charInfo = x4_charInfoDB[charIdx]; const CVParamTransfer charParm(new TObjOwnerParam(&charInfo)); TToken skinnedModel = x70_cacheResPool.GetObj({FourCC(0u), charInfo.GetModelId()}, charParm); std::optional> iceModel; if (charInfo.GetIceModelId().IsValid() && charInfo.GetIceSkinRulesId().IsValid()) { iceModel.emplace(x70_cacheResPool.GetObj({FourCC(1u), charInfo.GetIceModelId()}, charParm)); } return std::make_unique(x68_selfId, charInfo, defaultAnim, charIdx, loop, x14_charLayoutInfoDB[charIdx], std::move(skinnedModel), iceModel, x24_sysContext, x28_animMgr, x2c_transMgr, factory); } CAssetId CCharacterFactory::GetEventResourceIdForAnimResourceId(CAssetId id) const { auto search = std::find_if(x58_animResources.cbegin(), x58_animResources.cend(), [&](const std::pair& elem) -> bool { return id == elem.first; }); if (search == x58_animResources.cend()) return CAssetId(); return search->second; } const CAdditiveAnimationInfo& CCharacterFactory::FindAdditiveInfo(s32 idx) const { auto search = rstl::binary_find(x40_additiveInfo.cbegin(), x40_additiveInfo.cend(), idx, [](const auto& anim) { return anim.first; }); if (search == x40_additiveInfo.cend()) return x50_defaultAdditiveInfo; return search->second; } bool CCharacterFactory::HasAdditiveInfo(s32 idx) const { auto search = rstl::binary_find(x40_additiveInfo.cbegin(), x40_additiveInfo.cend(), idx, [](const auto& anim) { return anim.first; }); return search != x40_additiveInfo.cend(); } std::vector CCharacterFactory::GetCharacterInfoDB(const CAnimCharacterSet& ancs) { std::vector ret; const std::map& charInfoMap = ancs.GetCharacterSet().GetCharacterInfoMap(); ret.reserve(charInfoMap.size()); for (const auto& charInfo : charInfoMap) ret.push_back(charInfo.second); return ret; } std::vector> CCharacterFactory::GetCharLayoutInfoDB(CSimplePool& store, const std::vector& chars) { std::vector> ret; ret.reserve(chars.size()); for (const CCharacterInfo& charInfo : chars) ret.push_back(store.GetObj({SBIG('CINF'), charInfo.GetCharLayoutInfoId()})); return ret; } CCharacterFactory::CCharacterFactory(CSimplePool& store, const CAnimCharacterSet& ancs, CAssetId selfId) : x4_charInfoDB(GetCharacterInfoDB(ancs)) , x14_charLayoutInfoDB(GetCharLayoutInfoDB(store, x4_charInfoDB)) , x24_sysContext(std::make_shared( TToken(std::make_unique( ancs.GetAnimationSet().GetTransitions(), ancs.GetAnimationSet().GetHalfTransitions(), ancs.GetAnimationSet().GetDefaultTransition())), 2334, store)) , x28_animMgr(std::make_shared( TToken(std::make_unique(ancs.GetAnimationSet().GetAnimations())), *x24_sysContext)) , x2c_transMgr(std::make_shared(*x24_sysContext)) , x40_additiveInfo(ancs.GetAnimationSet().GetAdditiveInfo()) , x50_defaultAdditiveInfo(ancs.GetAnimationSet().GetDefaultAdditiveInfo()) , x58_animResources(ancs.GetAnimationSet().GetAnimResIds()) , x68_selfId(selfId) , x70_cacheResPool(x6c_dummyFactory) { std::vector primitives; x28_animMgr->GetAnimationDatabase()->GetAllUniquePrimitives(primitives); x30_animSourceDB.reserve(primitives.size()); for (const CPrimitive& prim : primitives) { x30_animSourceDB.emplace_back(store.GetObj({SBIG('ANIM'), prim.GetAnimResId()})); } } } // namespace metaforce ================================================ FILE: Runtime/Character/CCharacterFactory.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/CSimplePool.hpp" #include "Runtime/CToken.hpp" #include "Runtime/IFactory.hpp" #include "Runtime/IObjFactory.hpp" #include "Runtime/Character/CAnimationSet.hpp" #include "Runtime/Character/CCharacterInfo.hpp" namespace metaforce { class CAdditiveAnimationInfo; class CAllFormatsAnimSource; class CAnimCharacterSet; class CAnimData; class CAnimationManager; class CCharLayoutInfo; class CSimplePool; class CTransitionDatabaseGame; class CTransitionManager; class CCharacterFactory : public IObjFactory { public: class CDummyFactory : public IFactory { public: CFactoryFnReturn Build(const SObjectTag&, const CVParamTransfer&, CObjectReference* selfRef) override; void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr*, CObjectReference* selfRef) override; void CancelBuild(const SObjectTag&) override; bool CanBuild(const SObjectTag&) override; const SObjectTag* GetResourceIdByName(std::string_view) const override; FourCC GetResourceTypeById(CAssetId id) const override; void EnumerateResources(const std::function& lambda) const override; void EnumerateNamedResources(const std::function& lambda) const override; u32 ResourceSize(const metaforce::SObjectTag& tag) override; std::shared_ptr LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) override; std::shared_ptr LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size, void* target) override; std::unique_ptr LoadResourceSync(const metaforce::SObjectTag& tag) override; std::unique_ptr LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) override; }; private: std::vector x4_charInfoDB; std::vector> x14_charLayoutInfoDB; std::shared_ptr x24_sysContext; std::shared_ptr x28_animMgr; std::shared_ptr x2c_transMgr; std::vector> x30_animSourceDB; std::vector> x40_additiveInfo; CAdditiveAnimationInfo x50_defaultAdditiveInfo; std::vector> x58_animResources; CAssetId x68_selfId; CDummyFactory x6c_dummyFactory; CSimplePool x70_cacheResPool; static std::vector GetCharacterInfoDB(const CAnimCharacterSet& ancs); static std::vector> GetCharLayoutInfoDB(CSimplePool& store, const std::vector& chars); public: CCharacterFactory(CSimplePool& store, const CAnimCharacterSet& ancs, CAssetId); std::unique_ptr CreateCharacter(int charIdx, bool loop, const TLockedToken& factory, int defaultAnim); CAssetId GetEventResourceIdForAnimResourceId(CAssetId animId) const; const CCharacterInfo& GetCharInfo(int charIdx) const { return x4_charInfoDB[charIdx]; } const CAdditiveAnimationInfo& FindAdditiveInfo(s32 idx) const; bool HasAdditiveInfo(s32 idx) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CCharacterInfo.cpp ================================================ #include "Runtime/Character/CCharacterInfo.hpp" #include "Runtime/Streams/IOStreams.hpp" namespace metaforce { CCharacterInfo::CParticleResData::CParticleResData(CInputStream& in, u16 tableCount) { const u32 partCount = in.ReadLong(); x0_part.reserve(partCount); for (u32 i = 0; i < partCount; ++i) { x0_part.emplace_back(in.Get()); } const u32 swhcCount = in.ReadLong(); x10_swhc.reserve(swhcCount); for (u32 i = 0; i < swhcCount; ++i) { x10_swhc.emplace_back(in.Get()); } const u32 unkCount = in.ReadLong(); x20_elsc.reserve(unkCount); for (u32 i = 0; i < unkCount; ++i) { x20_elsc.emplace_back(in.Get()); } if (tableCount > 5) { const u32 elscCount = in.ReadLong(); x30_elsc.reserve(elscCount); for (u32 i = 0; i < elscCount; ++i) { x30_elsc.emplace_back(in.Get()); } } } static std::vector>> MakeAnimInfoVector(CInputStream& in) { std::vector>> ret; const u32 animInfoCount = in.ReadLong(); ret.reserve(animInfoCount); for (u32 i = 0; i < animInfoCount; ++i) { const s32 idx = in.ReadLong(); std::string a = in.Get(); std::string b = in.Get(); ret.emplace_back(idx, std::make_pair(std::move(a), std::move(b))); } return ret; } CCharacterInfo::CCharacterInfo(CInputStream& in) : x0_tableCount(in.ReadShort()) , x4_name(in.Get()) , x14_cmdl(in) , x18_cskr(in) , x1c_cinf(in) , x20_animInfo(MakeAnimInfoVector(in)) , x30_pasDatabase(in) , x44_partRes(in, x0_tableCount) , x84_unk(in.ReadLong()) { if (x0_tableCount > 1) { const u32 aabbCount = in.ReadLong(); x88_aabbs.reserve(aabbCount); for (u32 i = 0; i < aabbCount; ++i) { std::string name = in.Get(); x88_aabbs.emplace_back(std::move(name), zeus::CAABox()); x88_aabbs.back().second = in.Get(); } } if (x0_tableCount > 2) { const u32 effectCount = in.ReadLong(); x98_effects.reserve(effectCount); for (u32 i = 0; i < effectCount; ++i) { std::string name = in.Get(); x98_effects.emplace_back(std::move(name), std::vector()); std::vector& comps = x98_effects.back().second; const u32 compCount = in.ReadLong(); comps.reserve(compCount); for (u32 j = 0; j < compCount; ++j) { comps.emplace_back(in); } } } if (x0_tableCount > 3) { xa8_cmdlOverlay = in.Get(); xac_cskrOverlay = in.Get(); } if (x0_tableCount > 4) { const u32 aidxCount = in.ReadLong(); xb0_animIdxs.reserve(aidxCount); for (u32 i = 0; i < aidxCount; ++i) { xb0_animIdxs.push_back(in.ReadLong()); } } } s32 CCharacterInfo::GetAnimationIndex(std::string_view name) const { for (const auto& pair : x20_animInfo) { if (pair.second.second == name) return pair.first; } return -1; } } // namespace metaforce ================================================ FILE: Runtime/Character/CCharacterInfo.hpp ================================================ #pragma once #include #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CEffectComponent.hpp" #include "Runtime/Character/CPASDatabase.hpp" #include namespace metaforce { class CCharacterInfo { friend class CAnimData; public: struct CParticleResData { std::vector x0_part; std::vector x10_swhc; std::vector x20_elsc; std::vector x30_elsc; CParticleResData(CInputStream& in, u16 tableCount); CParticleResData(std::vector part, std::vector swhc, std::vector elsc1, std::vector elsc2) : x0_part(std::move(part)), x10_swhc(std::move(swhc)), x20_elsc(std::move(elsc1)), x30_elsc(std::move(elsc2)) {} }; private: u16 x0_tableCount; std::string x4_name; CAssetId x14_cmdl; CAssetId x18_cskr; CAssetId x1c_cinf; std::vector>> x20_animInfo; CPASDatabase x30_pasDatabase; CParticleResData x44_partRes; u32 x84_unk; std::vector> x88_aabbs; std::vector>> x98_effects; CAssetId xa8_cmdlOverlay; CAssetId xac_cskrOverlay; std::vector xb0_animIdxs; public: explicit CCharacterInfo(CInputStream& in); std::string_view GetCharacterName() const { return x4_name; } CAssetId GetModelId() const { return x14_cmdl; } CAssetId GetSkinRulesId() const { return x18_cskr; } CAssetId GetCharLayoutInfoId() const { return x1c_cinf; } const std::vector>& GetAnimBBoxList() const { return x88_aabbs; } const std::vector>>& GetEffectList() const { return x98_effects; } CAssetId GetIceModelId() const { return xa8_cmdlOverlay; } CAssetId GetIceSkinRulesId() const { return xac_cskrOverlay; } const CParticleResData& GetParticleResData() const { return x44_partRes; } s32 GetAnimationIndex(s32 idx) const { return xb0_animIdxs.at(idx); } const CPASDatabase& GetPASDatabase() const { return x30_pasDatabase; } s32 GetAnimationIndex(std::string_view) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CCharacterSet.cpp ================================================ #include "Runtime/Character/CCharacterSet.hpp" namespace metaforce { CCharacterSet::CCharacterSet(CInputStream& in) : x0_version(in.ReadShort()) { u32 charCount = in.ReadLong(); for (u32 i = 0; i < charCount; ++i) { u32 id = in.ReadLong(); x4_characters.emplace(id, in); } } } // namespace metaforce ================================================ FILE: Runtime/Character/CCharacterSet.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CCharacterInfo.hpp" namespace metaforce { class CCharacterSet { u16 x0_version; std::map x4_characters; public: explicit CCharacterSet(CInputStream& in); const std::map& GetCharacterInfoMap() const { return x4_characters; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CEffectComponent.cpp ================================================ #include "Runtime/Character/CEffectComponent.hpp" namespace metaforce { SObjectTag CEffectComponent::GetSObjectTagFromStream(CInputStream& in) { return in.Get(); } CEffectComponent::CEffectComponent(CInputStream& in) { x0_name = in.Get(); x10_tag = GetSObjectTagFromStream(in); x18_boneName = in.Get(); x28_scale = in.ReadFloat(); x2c_parentedMode = CParticleData::EParentedMode(in.ReadLong()); x30_flags = in.ReadLong(); } } // namespace metaforce ================================================ FILE: Runtime/Character/CEffectComponent.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CParticleData.hpp" namespace metaforce { class CEffectComponent { std::string x0_name; SObjectTag x10_tag; std::string x18_boneName; float x28_scale; CParticleData::EParentedMode x2c_parentedMode; u32 x30_flags; static SObjectTag GetSObjectTagFromStream(CInputStream& in); public: explicit CEffectComponent(CInputStream& in); std::string_view GetComponentName() const { return x0_name; } const SObjectTag& GetParticleTag() const { return x10_tag; } std::string_view GetSegmentName() const { return x18_boneName; } float GetScale() const { return x28_scale; } CParticleData::EParentedMode GetParentedMode() const { return x2c_parentedMode; } u32 GetFlags() const { return x30_flags; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CFBStreamedAnimReader.cpp ================================================ #include "Runtime/Character/CFBStreamedAnimReader.hpp" #include #include #include #include #include "Runtime/Character/CSegIdList.hpp" #include "Runtime/Character/CSegStatementSet.hpp" namespace metaforce { void CFBStreamedAnimReaderTotals::Allocate(u32 chanCount) { const u32 chan2 = chanCount * 2; const u32 chan32 = chanCount * 32; const size_t sz = chan32 + chanCount + chan2 + chan32; x0_buffer = std::make_unique(sz); x4_cumulativeInts32 = reinterpret_cast(x0_buffer.get()); x8_hasTrans1 = reinterpret_cast(x4_cumulativeInts32 + chanCount * 8); xc_segIds2 = reinterpret_cast(x8_hasTrans1 + chanCount); x10_computedFloats32 = reinterpret_cast(xc_segIds2 + chanCount); } void CFBStreamedAnimReaderTotals::Initialize(const CFBStreamedCompression& source) { x1c_curKey = 0; x20_calculated = false; const u8* chans = source.GetPerChannelHeaders(); u32 boneChanCount = *reinterpret_cast(chans); chans += 4; if (source.m_pc) { for (unsigned b = 0; b < boneChanCount; ++b) { xc_segIds2[b] = *reinterpret_cast(chans); chans += 8; s32* cumulativesOut = &x4_cumulativeInts32[8 * b]; const s32* cumulativesIn = reinterpret_cast(chans); cumulativesOut[0] = 0; cumulativesOut[1] = cumulativesIn[0] >> 8; cumulativesOut[2] = cumulativesIn[1] >> 8; cumulativesOut[3] = cumulativesIn[2] >> 8; chans += 12; u32 tCount = *reinterpret_cast(chans); chans += 4; if (tCount) { x8_hasTrans1[b] = true; const s32* cumulativesIn = reinterpret_cast(chans); cumulativesOut[4] = cumulativesIn[0] >> 8; cumulativesOut[5] = cumulativesIn[1] >> 8; cumulativesOut[6] = cumulativesIn[2] >> 8; chans += 12; } else x8_hasTrans1[b] = false; } } else { for (unsigned b = 0; b < boneChanCount; ++b) { xc_segIds2[b] = *reinterpret_cast(chans); chans += 6; s32* cumulativesOut = &x4_cumulativeInts32[8 * b]; cumulativesOut[0] = 0; cumulativesOut[1] = *reinterpret_cast(chans); cumulativesOut[2] = *reinterpret_cast(chans + 3); cumulativesOut[3] = *reinterpret_cast(chans + 6); chans += 9; u16 tCount = *reinterpret_cast(chans); chans += 2; if (tCount) { x8_hasTrans1[b] = true; cumulativesOut[4] = *reinterpret_cast(chans); cumulativesOut[5] = *reinterpret_cast(chans + 3); cumulativesOut[6] = *reinterpret_cast(chans + 6); chans += 9; } else x8_hasTrans1[b] = false; } } } CFBStreamedAnimReaderTotals::CFBStreamedAnimReaderTotals(const CFBStreamedCompression& source) { const CFBStreamedCompression::Header& header = source.MainHeader(); x14_rotDiv = header.rotDiv; x18_transMult = header.translationMult; const u8* chans = source.GetPerChannelHeaders(); x24_boneChanCount = *reinterpret_cast(chans); Allocate(x24_boneChanCount); Initialize(source); } void CFBStreamedAnimReaderTotals::IncrementInto(CBitLevelLoader& loader, const CFBStreamedCompression& source, CFBStreamedAnimReaderTotals& dest) { dest.x20_calculated = false; const u8* chans = source.GetPerChannelHeaders(); u32 boneChanCount = *reinterpret_cast(chans); chans += 4; if (source.m_pc) { for (unsigned b = 0; b < boneChanCount; ++b) { chans += 8; const s32* cumulativesIn = &x4_cumulativeInts32[8 * b]; s32* cumulativesOut = &dest.x4_cumulativeInts32[8 * b]; const s32* qsIn = reinterpret_cast(chans); cumulativesOut[0] = loader.LoadBool(); cumulativesOut[1] = cumulativesIn[1] + loader.LoadSigned(qsIn[0] & 0xff); cumulativesOut[2] = cumulativesIn[2] + loader.LoadSigned(qsIn[1] & 0xff); cumulativesOut[3] = cumulativesIn[3] + loader.LoadSigned(qsIn[2] & 0xff); chans += 12; u32 tCount = *reinterpret_cast(chans); chans += 4; if (tCount) { const s32* qsIn = reinterpret_cast(chans); cumulativesOut[4] = cumulativesIn[4] + loader.LoadSigned(qsIn[0] & 0xff); cumulativesOut[5] = cumulativesIn[5] + loader.LoadSigned(qsIn[1] & 0xff); cumulativesOut[6] = cumulativesIn[6] + loader.LoadSigned(qsIn[2] & 0xff); chans += 12; } } } else { for (unsigned b = 0; b < boneChanCount; ++b) { chans += 6; const s32* cumulativesIn = &x4_cumulativeInts32[8 * b]; s32* cumulativesOut = &dest.x4_cumulativeInts32[8 * b]; cumulativesOut[0] = loader.LoadBool(); cumulativesOut[1] = cumulativesIn[1] + loader.LoadSigned(*reinterpret_cast(chans + 2)); cumulativesOut[2] = cumulativesIn[2] + loader.LoadSigned(*reinterpret_cast(chans + 5)); cumulativesOut[3] = cumulativesIn[3] + loader.LoadSigned(*reinterpret_cast(chans + 8)); chans += 9; u16 tCount = *reinterpret_cast(chans); chans += 2; if (tCount) { cumulativesOut[4] = cumulativesIn[4] + loader.LoadSigned(*reinterpret_cast(chans + 2)); cumulativesOut[5] = cumulativesIn[5] + loader.LoadSigned(*reinterpret_cast(chans + 5)); cumulativesOut[6] = cumulativesIn[6] + loader.LoadSigned(*reinterpret_cast(chans + 8)); chans += 9; } } } dest.x1c_curKey = x1c_curKey + 1; } void CFBStreamedAnimReaderTotals::CalculateDown() { for (unsigned b = 0; b < x24_boneChanCount; ++b) { const s32* cumulativesIn = &x4_cumulativeInts32[8 * b]; float* compOut = &x10_computedFloats32[8 * b]; float q = M_PIF / 2.f / float(x14_rotDiv); compOut[1] = std::sin(cumulativesIn[1] * q); compOut[2] = std::sin(cumulativesIn[2] * q); compOut[3] = std::sin(cumulativesIn[3] * q); compOut[0] = std::sqrt(std::max(1.f - (compOut[1] * compOut[1] + compOut[2] * compOut[2] + compOut[3] * compOut[3]), 0.f)); if (cumulativesIn[0]) compOut[0] = -compOut[0]; if (x8_hasTrans1[b]) { compOut[4] = cumulativesIn[4] * x18_transMult; compOut[5] = cumulativesIn[5] * x18_transMult; compOut[6] = cumulativesIn[6] * x18_transMult; } } x20_calculated = true; } CFBStreamedPairOfTotals::CFBStreamedPairOfTotals(const TSubAnimTypeToken& source) : x0_source(source), xc_rotsAndOffs(source->xc_rotsAndOffs.get()), x14_a(*source), x3c_b(*source) {} void CFBStreamedPairOfTotals::SetTime(CBitLevelLoader& loader, const CCharAnimTime& time) { /* Implementation is a bit different than original; * T evaluated pre-emptively with key indices. * CalculateDown is also called here as needed. */ const CFBStreamedCompression::Header& header = x0_source->MainHeader(); CCharAnimTime interval(header.interval); const u32* timeBitmap = x0_source->GetTimes(); CCharAnimTime priorTime(0); CCharAnimTime curTime(0); int prior = -1; int next = -1; int cur = 0; for (unsigned b = 0; b < timeBitmap[0]; ++b) { int word = b / 32; int bit = b % 32; if ((timeBitmap[word + 1] >> bit) & 1) { if (curTime <= time) { prior = cur; priorTime = curTime; } else if (curTime > time) { next = cur; if (prior == -1) { prior = cur; priorTime = curTime; x78_t = 0.f; } else { x78_t = (time - priorTime) / (curTime - priorTime); } break; } ++cur; } curTime += interval; } if (prior != -1 && u32(prior) < Prior().x1c_curKey) { Prior().Initialize(*x0_source); Next().Initialize(*x0_source); loader.Reset(); } if (prior != -1 && next == -1) { next = prior; x78_t = 1.f; } if (next != -1) { while (u32(next) > Next().x1c_curKey) { DoIncrement(loader); } } if (!Prior().IsCalculated()) { Prior().CalculateDown(); } if (!Next().IsCalculated()) { Next().CalculateDown(); } } void CFBStreamedPairOfTotals::DoIncrement(CBitLevelLoader& loader) { x10_nextSel ^= 1; Prior().IncrementInto(loader, *x0_source, Next()); } u32 CBitLevelLoader::LoadUnsigned(u8 q) { u32 byteCur = (m_bitIdx / 32) * 4; u32 bitRem = m_bitIdx % 32; /* Fill 32 bit buffer with region containing bits */ /* Make them least significant */ u32 tempBuf = *reinterpret_cast(m_data + byteCur) >> bitRem; /* If this shift underflows the value, buffer the next 32 bits */ /* And tack onto shifted buffer */ if ((bitRem + q) > 32) { u32 tempBuf2 = *reinterpret_cast(m_data + byteCur + 4); tempBuf |= (tempBuf2 << (32 - bitRem)); } /* Mask it */ u32 mask = (1 << q) - 1; tempBuf &= mask; /* Return delta value */ m_bitIdx += q; return tempBuf; } s32 CBitLevelLoader::LoadSigned(u8 q) { u32 byteCur = (m_bitIdx / 32) * 4; u32 bitRem = m_bitIdx % 32; /* Fill 32 bit buffer with region containing bits */ /* Make them least significant */ u32 tempBuf = *reinterpret_cast(m_data + byteCur) >> bitRem; /* If this shift underflows the value, buffer the next 32 bits */ /* And tack onto shifted buffer */ if ((bitRem + q) > 32) { u32 tempBuf2 = *reinterpret_cast(m_data + byteCur + 4); tempBuf |= (tempBuf2 << (32 - bitRem)); } /* Mask it */ u32 mask = (1 << q) - 1; tempBuf &= mask; /* Sign extend */ u32 sign = (tempBuf >> (q - 1)) & 0x1; if (sign) tempBuf |= ~0u << q; /* Return delta value */ m_bitIdx += q; return s32(tempBuf); } bool CBitLevelLoader::LoadBool() { u32 byteCur = (m_bitIdx / 32) * 4; u32 bitRem = m_bitIdx % 32; /* Fill 32 bit buffer with region containing bits */ /* Make them least significant */ u32 tempBuf = *reinterpret_cast(m_data + byteCur) >> bitRem; /* That's it */ m_bitIdx += 1; return tempBuf & 0x1; } CSegIdToIndexConverter::CSegIdToIndexConverter(const CFBStreamedAnimReaderTotals& totals) { std::fill(std::begin(x0_indices), std::end(x0_indices), -1); for (u32 b = 0; b < totals.x24_boneChanCount; ++b) { u16 segId = totals.xc_segIds2[b]; if (segId >= 100) continue; x0_indices[segId] = b; } } CFBStreamedAnimReader::CFBStreamedAnimReader(const TSubAnimTypeToken& source, const CCharAnimTime& time) : CAnimSourceReaderBase(std::make_unique>(source), {}) , x54_source(source) , x64_steadyStateInfo(source->IsLooping(), source->GetAnimationDuration(), source->GetRootOffset()) , x7c_totals(source) , x104_bitstreamData(source->GetBitstreamPointer()) , x108_bitLoader(x104_bitstreamData) , x114_segIdToIndex(x7c_totals.x10_nextSel ? x7c_totals.x14_a : x7c_totals.x3c_b) { x7c_totals.SetTime(x108_bitLoader, CCharAnimTime()); PostConstruct(time); } bool CFBStreamedAnimReader::HasOffset(const CSegId& seg) const { s32 idx = x114_segIdToIndex.SegIdToIndex(seg); if (idx == -1) return false; return x7c_totals.Prior().x8_hasTrans1[idx]; } zeus::CVector3f CFBStreamedAnimReader::GetOffset(const CSegId& seg) const { s32 idx = x114_segIdToIndex.SegIdToIndex(seg); if (idx == -1) return {}; const float* af = x7c_totals.Prior().GetFloats(idx); const float* bf = x7c_totals.Next().GetFloats(idx); zeus::CVector3f a(af[4], af[5], af[6]); zeus::CVector3f b(bf[4], bf[5], bf[6]); return zeus::CVector3f::lerp(a, b, x7c_totals.GetT()); } zeus::CQuaternion CFBStreamedAnimReader::GetRotation(const CSegId& seg) const { s32 idx = x114_segIdToIndex.SegIdToIndex(seg); if (idx == -1) return {}; const float* af = x7c_totals.Prior().GetFloats(idx); const float* bf = x7c_totals.Next().GetFloats(idx); zeus::CQuaternion a(af[0], af[1], af[2], af[3]); zeus::CQuaternion b(bf[0], bf[1], bf[2], bf[3]); return zeus::CQuaternion::slerp(a, b, x7c_totals.GetT()); } SAdvancementResults CFBStreamedAnimReader::VGetAdvancementResults(const CCharAnimTime& dt, const CCharAnimTime& startOff) const { SAdvancementResults res = {}; CCharAnimTime resolveTime = xc_curTime + startOff; CCharAnimTime animDur = x54_source->GetAnimationDuration(); if (resolveTime >= animDur || dt.EqualsZero()) return res; const_cast(this)->x7c_totals.SetTime(const_cast(this)->x108_bitLoader, resolveTime); zeus::CQuaternion priorQ = GetRotation(3); zeus::CVector3f priorV = GetOffset(3); CCharAnimTime nextTime = resolveTime + dt; if (nextTime > animDur) { nextTime = animDur; res.x0_remTime = nextTime - animDur; } const_cast(this)->x7c_totals.SetTime(const_cast(this)->x108_bitLoader, nextTime); zeus::CQuaternion nextQ = GetRotation(3); zeus::CVector3f nextV = GetOffset(3); res.x8_deltas.xc_rotDelta = priorQ.inverse() * nextQ; if (HasOffset(3)) res.x8_deltas.x0_posDelta = res.x8_deltas.xc_rotDelta.transform(nextV - priorV); return res; } void CFBStreamedAnimReader::VSetPhase(float ph) { xc_curTime = x64_steadyStateInfo.GetDuration() * ph; x7c_totals.SetTime(x108_bitLoader, xc_curTime); if (x54_source->HasPOIData()) { UpdatePOIStates(); if (!xc_curTime.GreaterThanZero()) { x14_passedBoolCount = 0; x18_passedIntCount = 0; x1c_passedParticleCount = 0; x20_passedSoundCount = 0; } } } SAdvancementResults CFBStreamedAnimReader::VReverseView(const CCharAnimTime& time) { return {}; } std::unique_ptr CFBStreamedAnimReader::VClone() const { return std::make_unique(x54_source, xc_curTime); } void CFBStreamedAnimReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const { const_cast(this)->x7c_totals.SetTime(const_cast(this)->x108_bitLoader, xc_curTime); for (const CSegId& id : list.GetList()) { CAnimPerSegmentData& out = setOut[id]; out.x0_rotation = GetRotation(id); out.x1c_hasOffset = HasOffset(id); if (out.x1c_hasOffset) out.x10_offset = GetOffset(id); } } void CFBStreamedAnimReader::VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const { const_cast(this)->x7c_totals.SetTime(const_cast(this)->x108_bitLoader, time); for (const CSegId& id : list.GetList()) { CAnimPerSegmentData& out = setOut[id]; out.x0_rotation = GetRotation(id); out.x1c_hasOffset = HasOffset(id); if (out.x1c_hasOffset) out.x10_offset = GetOffset(id); } } SAdvancementResults CFBStreamedAnimReader::VAdvanceView(const CCharAnimTime& dt) { SAdvancementResults res = {}; CCharAnimTime animDur = x54_source->GetAnimationDuration(); if (xc_curTime == animDur) { xc_curTime = CCharAnimTime(); x7c_totals.SetTime(x108_bitLoader, xc_curTime); x14_passedBoolCount = 0; x18_passedIntCount = 0; x1c_passedParticleCount = 0; x20_passedSoundCount = 0; res.x0_remTime = dt; return res; } else if (dt.EqualsZero()) { return res; } zeus::CQuaternion priorQ = GetRotation(3); zeus::CVector3f priorV = GetOffset(3); xc_curTime += dt; CCharAnimTime overTime; if (xc_curTime > animDur) { overTime = xc_curTime - animDur; xc_curTime = animDur; } x7c_totals.SetTime(x108_bitLoader, xc_curTime); if (x54_source->HasPOIData()) UpdatePOIStates(); zeus::CQuaternion nextQ = GetRotation(3); zeus::CVector3f nextV = GetOffset(3); res.x0_remTime = overTime; res.x8_deltas.xc_rotDelta = nextQ * priorQ.inverse(); if (HasOffset(3)) res.x8_deltas.x0_posDelta = nextQ.inverse().transform(nextV - priorV); return res; } CCharAnimTime CFBStreamedAnimReader::VGetTimeRemaining() const { return x54_source->GetAnimationDuration() - xc_curTime; } CSteadyStateAnimInfo CFBStreamedAnimReader::VGetSteadyStateAnimInfo() const { return x64_steadyStateInfo; } bool CFBStreamedAnimReader::VHasOffset(const CSegId& seg) const { return HasOffset(seg); } zeus::CVector3f CFBStreamedAnimReader::VGetOffset(const CSegId& seg) const { const_cast(this)->x7c_totals.SetTime(const_cast(this)->x108_bitLoader, xc_curTime); return GetOffset(seg); } zeus::CVector3f CFBStreamedAnimReader::VGetOffset(const CSegId& seg, const CCharAnimTime& time) const { const_cast(this)->x7c_totals.SetTime(const_cast(this)->x108_bitLoader, time); return GetOffset(seg); } zeus::CQuaternion CFBStreamedAnimReader::VGetRotation(const CSegId& seg) const { const_cast(this)->x7c_totals.SetTime(const_cast(this)->x108_bitLoader, xc_curTime); return GetRotation(seg); } template class TAnimSourceInfo; } // namespace metaforce ================================================ FILE: Runtime/Character/CFBStreamedAnimReader.hpp ================================================ #pragma once #include #include #include "Runtime/Character/CAnimSourceReader.hpp" #include "Runtime/Character/CFBStreamedCompression.hpp" namespace metaforce { class CBitLevelLoader; template class TAnimSourceInfo : public IAnimSourceInfo { TSubAnimTypeToken x4_token; public: explicit TAnimSourceInfo(TSubAnimTypeToken token) : x4_token(std::move(token)) {} bool HasPOIData() const override { return x4_token->HasPOIData(); } const std::vector& GetBoolPOIStream() const override { return x4_token->GetBoolPOIStream(); } const std::vector& GetInt32POIStream() const override { return x4_token->GetInt32POIStream(); } const std::vector& GetParticlePOIStream() const override { return x4_token->GetParticlePOIStream(); } const std::vector& GetSoundPOIStream() const override { return x4_token->GetSoundPOIStream(); } CCharAnimTime GetAnimationDuration() const override { return x4_token->GetAnimationDuration(); } }; class CFBStreamedAnimReaderTotals { friend class CSegIdToIndexConverter; friend class CFBStreamedPairOfTotals; friend class CFBStreamedAnimReader; std::unique_ptr x0_buffer; s32* x4_cumulativeInts32; /* Used to be 16 per channel */ u8* x8_hasTrans1; u16* xc_segIds2; float* x10_computedFloats32; u32 x14_rotDiv; float x18_transMult; u32 x1c_curKey = 0; bool x20_calculated = false; u32 x24_boneChanCount; void Allocate(u32 chanCount); public: explicit CFBStreamedAnimReaderTotals(const CFBStreamedCompression& source); void Initialize(const CFBStreamedCompression& source); void IncrementInto(CBitLevelLoader& loader, const CFBStreamedCompression& source, CFBStreamedAnimReaderTotals& dest); void CalculateDown(); bool IsCalculated() const { return x20_calculated; } const float* GetFloats(int chanIdx) const { return &x10_computedFloats32[chanIdx * 8]; } }; class CFBStreamedPairOfTotals { friend class CFBStreamedAnimReader; TSubAnimTypeToken x0_source; u32* xc_rotsAndOffs; bool x10_nextSel = true; CFBStreamedAnimReaderTotals x14_a; CFBStreamedAnimReaderTotals x3c_b; float x78_t = 0.f; public: explicit CFBStreamedPairOfTotals(const TSubAnimTypeToken& source); void SetTime(CBitLevelLoader& loader, const CCharAnimTime& time); void DoIncrement(CBitLevelLoader& loader); float GetT() const { return x78_t; } CFBStreamedAnimReaderTotals& Next() { return x10_nextSel ? x3c_b : x14_a; } CFBStreamedAnimReaderTotals& Prior() { return x10_nextSel ? x14_a : x3c_b; } const CFBStreamedAnimReaderTotals& Next() const { return x10_nextSel ? x3c_b : x14_a; } const CFBStreamedAnimReaderTotals& Prior() const { return x10_nextSel ? x14_a : x3c_b; } }; class CBitLevelLoader { const u8* m_data; size_t m_bitIdx = 0; public: explicit CBitLevelLoader(const void* data) : m_data(reinterpret_cast(data)) {} void Reset() { m_bitIdx = 0; } u32 LoadUnsigned(u8 q); s32 LoadSigned(u8 q); bool LoadBool(); size_t GetCurBit() const { return m_bitIdx; } }; class CSegIdToIndexConverter { std::array x0_indices; public: explicit CSegIdToIndexConverter(const CFBStreamedAnimReaderTotals& totals); s32 SegIdToIndex(const CSegId& id) const { return x0_indices[id]; } }; class CFBStreamedAnimReader : public CAnimSourceReaderBase { TSubAnimTypeToken x54_source; CSteadyStateAnimInfo x64_steadyStateInfo; CFBStreamedPairOfTotals x7c_totals; const u8* x104_bitstreamData; CBitLevelLoader x108_bitLoader; CSegIdToIndexConverter x114_segIdToIndex; bool HasOffset(const CSegId& seg) const; zeus::CVector3f GetOffset(const CSegId& seg) const; zeus::CQuaternion GetRotation(const CSegId& seg) const; public: explicit CFBStreamedAnimReader(const TSubAnimTypeToken& source, const CCharAnimTime& time); SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const override; bool VSupportsReverseView() const override { return false; } void VSetPhase(float) override; SAdvancementResults VReverseView(const CCharAnimTime& time) override; std::unique_ptr VClone() const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const override; void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const override; SAdvancementResults VAdvanceView(const CCharAnimTime& a) override; CCharAnimTime VGetTimeRemaining() const override; CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const override; bool VHasOffset(const CSegId& seg) const override; zeus::CVector3f VGetOffset(const CSegId& seg) const override; zeus::CVector3f VGetOffset(const CSegId& seg, const CCharAnimTime& time) const override; zeus::CQuaternion VGetRotation(const CSegId& seg) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CFBStreamedCompression.cpp ================================================ #include "Runtime/Character/CFBStreamedCompression.hpp" #include #include #include "Runtime/Character/CFBStreamedAnimReader.hpp" namespace metaforce { namespace { template T ReadValue(const u8* data) { static_assert(std::is_trivially_copyable_v); T value = 0; std::memcpy(&value, data, sizeof(value)); return value; } template void WriteValue(u8* data, T value) { static_assert(std::is_trivially_copyable_v); std::memcpy(data, &value, sizeof(value)); } } // Anonymous namespace CFBStreamedCompression::CFBStreamedCompression(CInputStream& in, IObjectStore& objStore, bool pc) : m_pc(pc) { x0_scratchSize = in.ReadLong(); x4_evnt = in.Get(); xc_rotsAndOffs = GetRotationsAndOffsets(x0_scratchSize / 4 + 1, in); if (x4_evnt.IsValid()) x8_evntToken = objStore.GetObj(SObjectTag{FOURCC('EVNT'), x4_evnt}); x10_averageVelocity = CalculateAverageVelocity(GetPerChannelHeaders()); } const u32* CFBStreamedCompression::GetTimes() const { return xc_rotsAndOffs.get() + 9; } const u8* CFBStreamedCompression::GetPerChannelHeaders() const { const u32* bitmap = GetTimes(); const u32 bitmapWordCount = (bitmap[0] + 31) / 32; return reinterpret_cast(bitmap + bitmapWordCount + 1); } const u8* CFBStreamedCompression::GetBitstreamPointer() const { const u32* bitmap = GetTimes(); const u32 bitmapWordCount = (bitmap[0] + 31) / 32; const u8* chans = reinterpret_cast(bitmap + bitmapWordCount + 1); const u32 boneChanCount = ReadValue(chans); chans += 4; if (m_pc) { for (u32 b = 0; b < boneChanCount; ++b) { chans += 20; const u32 tCount = ReadValue(chans); chans += 4; if (tCount != 0) { chans += 12; } } } else { for (u32 b = 0; b < boneChanCount; ++b) { chans += 15; const u16 tCount = ReadValue(chans); chans += 2; if (tCount != 0) { chans += 9; } } } return chans; } std::unique_ptr CFBStreamedCompression::GetRotationsAndOffsets(u32 words, CInputStream& in) const { std::unique_ptr ret(new u32[words]); Header head; head.read(in); std::memcpy(ret.get(), &head, sizeof(head)); u32* bitmapOut = &ret[9]; const u32 bitmapBitCount = in.ReadLong(); bitmapOut[0] = bitmapBitCount; const u32 bitmapWordCount = (bitmapBitCount + 31) / 32; for (u32 i = 0; i < bitmapWordCount; ++i) { bitmapOut[i + 1] = in.ReadLong(); } in.ReadLong(); u8* chans = reinterpret_cast(bitmapOut + bitmapWordCount + 1); u8* bs = ReadBoneChannelDescriptors(chans, in); const u32 bsWords = ComputeBitstreamWords(chans); u32* bsPtr = reinterpret_cast(bs); for (u32 w = 0; w < bsWords; ++w) bsPtr[w] = in.ReadLong(); return ret; } u8* CFBStreamedCompression::ReadBoneChannelDescriptors(u8* out, CInputStream& in) const { const u32 boneChanCount = in.ReadLong(); WriteValue(out, boneChanCount); out += 4; if (m_pc) { for (u32 b = 0; b < boneChanCount; ++b) { WriteValue(out, in.ReadLong()); out += 4; WriteValue(out, in.ReadLong()); out += 4; for (int i = 0; i < 3; ++i) { WriteValue(out, in.ReadLong()); out += 4; } const u32 tCount = in.ReadLong(); WriteValue(out, tCount); out += 4; if (tCount != 0) { for (int i = 0; i < 3; ++i) { WriteValue(out, in.ReadLong()); out += 4; } } } } else { for (u32 b = 0; b < boneChanCount; ++b) { WriteValue(out, in.ReadLong()); out += 4; WriteValue(out, in.ReadShort()); out += 2; for (int i = 0; i < 3; ++i) { WriteValue(out, in.ReadShort()); out += 2; WriteValue(out, in.ReadUint8()); out += 1; } const u16 tCount = in.ReadShort(); WriteValue(out, tCount); out += 2; if (tCount != 0) { for (int i = 0; i < 3; ++i) { WriteValue(out, in.ReadShort()); out += 2; WriteValue(out, in.ReadUint8()); out += 1; } } } } return out; } u32 CFBStreamedCompression::ComputeBitstreamWords(const u8* chans) const { const u32 boneChanCount = ReadValue(chans); chans += 4; u32 keyCount; u32 totalBits = 0; if (m_pc) { keyCount = ReadValue(chans + 0x4); for (u32 c = 0; c < boneChanCount; ++c) { chans += 0x8; totalBits += 1; totalBits += ReadValue(chans) & 0xff; totalBits += ReadValue(chans + 0x4) & 0xff; totalBits += ReadValue(chans + 0x8) & 0xff; const u32 tKeyCount = ReadValue(chans + 0xc); chans += 0x10; if (tKeyCount != 0) { totalBits += ReadValue(chans) & 0xff; totalBits += ReadValue(chans + 0x4) & 0xff; totalBits += ReadValue(chans + 0x8) & 0xff; chans += 0xc; } } } else { keyCount = ReadValue(chans + 0x4); for (u32 c = 0; c < boneChanCount; ++c) { chans += 0x6; totalBits += 1; totalBits += ReadValue(chans + 0x2); totalBits += ReadValue(chans + 0x5); totalBits += ReadValue(chans + 0x8); const u16 tKeyCount = ReadValue(chans + 0x9); chans += 0xb; if (tKeyCount != 0) { totalBits += ReadValue(chans + 0x2); totalBits += ReadValue(chans + 0x5); totalBits += ReadValue(chans + 0x8); chans += 0x9; } } } return (totalBits * keyCount + 31) / 32; } float CFBStreamedCompression::CalculateAverageVelocity(const u8* chans) const { const u32 boneChanCount = ReadValue(chans); chans += 4; u32 keyCount; u32 rootIdx = 0; if (m_pc) { keyCount = ReadValue(chans + 0x4); for (u32 c = 0; c < boneChanCount; ++c) { const u32 boneId = ReadValue(chans); if (boneId == 3) { break; } ++rootIdx; chans += 0x8; const u32 tKeyCount = ReadValue(chans + 0xc); chans += 0x10; if (tKeyCount != 0) { chans += 0xc; } } } else { keyCount = ReadValue(chans + 0x4); for (u32 c = 0; c < boneChanCount; ++c) { const u32 boneId = ReadValue(chans); if (boneId == 3) { break; } ++rootIdx; chans += 0x6; const u16 tKeyCount = ReadValue(chans + 0x9); chans += 0xb; if (tKeyCount != 0) { chans += 0x9; } } } CBitLevelLoader loader(GetBitstreamPointer()); CFBStreamedAnimReaderTotals tempTotals(*this); tempTotals.CalculateDown(); const float* floats = tempTotals.GetFloats(rootIdx); zeus::CVector3f transCompA(floats[4], floats[5], floats[6]); float accumMag = 0.f; for (u32 i = 0; i < keyCount; ++i) { tempTotals.IncrementInto(loader, *this, tempTotals); tempTotals.CalculateDown(); zeus::CVector3f transCompB(floats[4], floats[5], floats[6]); accumMag += (transCompB - transCompA).magnitude(); transCompA = transCompB; } return accumMag / GetAnimationDuration().GetSeconds(); } } // namespace metaforce ================================================ FILE: Runtime/Character/CFBStreamedCompression.hpp ================================================ #pragma once #include #include #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CAnimPOIData.hpp" #include namespace metaforce { class IObjectStore; class CFBStreamedCompression { friend class CFBStreamedAnimReader; friend class CFBStreamedAnimReaderTotals; friend class CFBStreamedPairOfTotals; public: struct Header { u32 unk0; float duration; float interval; u32 rootBoneId; u32 looping; u32 rotDiv; float translationMult; u32 boneChannelCount; u32 unk3; void read(CInputStream& in) { /* unk0 */ unk0 = in.ReadLong(); /* duration */ duration = in.ReadFloat(); /* interval */ interval = in.ReadFloat(); /* rootBoneId */ rootBoneId = in.ReadLong(); /* looping */ looping = in.ReadLong(); /* rotDiv */ rotDiv = in.ReadLong(); /* translationMult */ translationMult = in.ReadFloat(); /* boneChannelCount */ boneChannelCount = in.ReadLong(); /* unk3 */ unk3 = in.ReadLong(); } }; private: bool m_pc; u32 x0_scratchSize; CAssetId x4_evnt; TLockedToken x8_evntToken; std::unique_ptr xc_rotsAndOffs; float x10_averageVelocity; zeus::CVector3f x14_rootOffset; u8* ReadBoneChannelDescriptors(u8* out, CInputStream& in) const; u32 ComputeBitstreamWords(const u8* chans) const; std::unique_ptr GetRotationsAndOffsets(u32 words, CInputStream& in) const; float CalculateAverageVelocity(const u8* chans) const; public: explicit CFBStreamedCompression(CInputStream& in, IObjectStore& objStore, bool pc); const Header& MainHeader() const { return *reinterpret_cast(xc_rotsAndOffs.get()); } const u32* GetTimes() const; const u8* GetPerChannelHeaders() const; const u8* GetBitstreamPointer() const; bool IsLooping() const { return MainHeader().looping; } CCharAnimTime GetAnimationDuration() const { return MainHeader().duration; } float GetAverageVelocity() const { return x10_averageVelocity; } const zeus::CVector3f& GetRootOffset() const { return x14_rootOffset; } bool HasPOIData() const { return x8_evntToken.HasReference(); } const std::vector& GetBoolPOIStream() const { return x8_evntToken->GetBoolPOIStream(); } const std::vector& GetInt32POIStream() const { return x8_evntToken->GetInt32POIStream(); } const std::vector& GetParticlePOIStream() const { return x8_evntToken->GetParticlePOIStream(); } const std::vector& GetSoundPOIStream() const { return x8_evntToken->GetSoundPOIStream(); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CGroundMovement.cpp ================================================ #include "Runtime/Character/CGroundMovement.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Collision/CAABoxFilter.hpp" #include "Runtime/Collision/CCollisionInfoList.hpp" #include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/Collision/CollisionUtil.hpp" #include "Runtime/World/CPhysicsActor.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptPlatform.hpp" #include "Runtime/World/CWorld.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path #include "math.h" namespace metaforce { void CGroundMovement::CheckFalling(CPhysicsActor& actor, CStateManager& mgr, float /*dt*/) { bool oob = true; zeus::CAABox plBox = *actor.GetTouchBounds(); for (const CGameArea& area : *mgr.GetWorld()) { if (area.GetAABB().intersects(plBox)) { oob = false; break; } } if (oob) { mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor); actor.SetAngularVelocityWR(actor.GetAngularVelocityWR() * 0.98f); zeus::CVector3f vel = actor.GetTransform().transposeRotate(actor.GetVelocity()); vel.z() = 0.f; actor.SetVelocityOR(vel); actor.SetMomentumWR(zeus::skZero3f); } else { mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::Falling); } } void CGroundMovement::MoveGroundCollider(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* nearList) { CMotionState oldState = actor.GetMotionState(); CMotionState newState = actor.PredictMotion_Internal(dt); float deltaMag = newState.x0_translation.magnitude(); TUniqueId idDetect = kInvalidUniqueId; CCollisionInfoList collisionList; zeus::CAABox motionVol = actor.GetMotionVolume(dt); EntityList useColliderList; if (nearList != nullptr) { useColliderList = *nearList; } mgr.BuildColliderList(useColliderList, actor, motionVol); CAreaCollisionCache cache(motionVol); float collideDt = dt; if (actor.GetCollisionPrimitive()->GetPrimType() != FOURCC('OBTG')) { CGameCollision::BuildAreaCollisionCache(mgr, cache); if (deltaMag > 0.5f * CGameCollision::GetMinExtentForCollisionPrimitive(*actor.GetCollisionPrimitive())) { zeus::CVector3f point = actor.GetCollisionPrimitive()->CalculateAABox(actor.GetPrimitiveTransform()).center(); TUniqueId intersectId = kInvalidUniqueId; CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid}); CRayCastResult result = mgr.RayWorldIntersection(intersectId, point, newState.x0_translation.normalized(), deltaMag, filter, useColliderList); if (result.IsValid()) { collideDt = dt * (result.GetT() / deltaMag); newState = actor.PredictMotion_Internal(collideDt); } } } actor.MoveCollisionPrimitive(newState.x0_translation); if (CGameCollision::DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), actor.GetMaterialFilter(), useColliderList, idDetect, collisionList)) { actor.AddMotionState(newState); float resolved = 0.f; if (ResolveUpDown(cache, mgr, actor, actor.GetMaterialFilter(), useColliderList, actor.GetStepUpHeight(), 0.f, resolved, collisionList)) { actor.SetMotionState(oldState); MoveGroundColliderXY(cache, mgr, actor, actor.GetMaterialFilter(), useColliderList, collideDt); } } else { actor.AddMotionState(newState); } float stepDown = actor.GetStepDownHeight(); float resolved = 0.f; collisionList.Clear(); TUniqueId stepZId = kInvalidUniqueId; if (stepDown >= 0.f && MoveGroundColliderZ(cache, mgr, actor, actor.GetMaterialFilter(), useColliderList, -stepDown, resolved, collisionList, stepZId)) { if (collisionList.GetCount() > 0) { CCollisionInfoList filteredList; CollisionUtil::FilterByClosestNormal(zeus::CVector3f{0.f, 0.f, 1.f}, collisionList, filteredList); if (filteredList.GetCount() > 0) { if (CGameCollision::IsFloor(filteredList.Front().GetMaterialLeft(), filteredList.Front().GetNormalLeft())) { if (TCastToPtr plat = mgr.ObjectById(stepZId)) { mgr.SendScriptMsg(plat.GetPtr(), actor.GetUniqueId(), EScriptObjectMessage::AddPlatformRider); } CGameCollision::SendMaterialMessage(mgr, filteredList.Front().GetMaterialLeft(), actor); mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor); } else { CheckFalling(actor, mgr, dt); } } } } else { CheckFalling(actor, mgr, dt); } actor.ClearForcesAndTorques(); actor.MoveCollisionPrimitive(zeus::skZero3f); if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Player)) { CGameCollision::CollisionFailsafe(mgr, cache, actor, *actor.GetCollisionPrimitive(), useColliderList, 0.f, 1); } } bool CGroundMovement::ResolveUpDown(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor, const CMaterialFilter& filter, EntityList& nearList, float stepUp, float stepDown, float& fOut, CCollisionInfoList& list) { float zextent = stepDown; if (list.GetCount() <= 0) { return true; } zeus::CAABox aabb = zeus::CAABox(); zeus::CVector3f normAccum = zeus::skZero3f; for (CCollisionInfo& info : list) { if (CGameCollision::IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) { aabb.accumulateBounds(info.GetPoint()); aabb.accumulateBounds(info.GetExtreme()); normAccum += info.GetNormalLeft(); } } if (normAccum.canBeNormalized()) { normAccum.normalize(); } else { return true; } zeus::CAABox actorAABB = actor.GetBoundingBox(); if (normAccum.z() >= 0.f) { zextent = aabb.max.z() - actorAABB.min.z() + 0.02f; if (zextent > stepUp) { return true; } } else { zextent = aabb.min.z() - actorAABB.max.z() - 0.02f; if (zextent < -stepDown) { return true; } } actor.MoveCollisionPrimitive({0.f, 0.f, zextent}); if (!CGameCollision::DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), filter, nearList)) { fOut = zextent; actor.SetTranslation(actor.GetTranslation() + zeus::CVector3f(0.f, 0.f, zextent)); actor.MoveCollisionPrimitive(zeus::skZero3f); bool floor = false; for (CCollisionInfo& info : list) { if (CGameCollision::IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) { floor = true; break; } } if (!floor) { mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::LandOnNotFloor); } return false; } return true; } bool CGroundMovement::MoveGroundColliderZ(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor, const CMaterialFilter& filter, EntityList& nearList, float amt, float& resolved, CCollisionInfoList& list, TUniqueId& idOut) { actor.MoveCollisionPrimitive({0.f, 0.f, amt}); zeus::CAABox aabb = zeus::CAABox(); if (CGameCollision::DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), filter, nearList, idOut, list)) { for (CCollisionInfo& info : list) { aabb.accumulateBounds(info.GetPoint()); aabb.accumulateBounds(info.GetExtreme()); } zeus::CAABox actorAABB = actor.GetBoundingBox(); float zextent = 0.f; if (amt > 0.f) { zextent = aabb.min.z() - actorAABB.max.z() - 0.02f + amt; } else { zextent = aabb.max.z() - actorAABB.min.z() + 0.02f + amt; } actor.MoveCollisionPrimitive({0.f, 0.f, zextent}); if (!CGameCollision::DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), filter, nearList)) { resolved = zextent; actor.SetTranslation(actor.GetTranslation() + zeus::CVector3f(0.f, 0.f, zextent)); actor.MoveCollisionPrimitive(zeus::skZero3f); } bool floor = false; for (CCollisionInfo& info : list) { if (CGameCollision::IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) { floor = true; break; } } if (!floor) { mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::LandOnNotFloor); } CCollisionInfoList filteredList; if (amt > 0.f) { CollisionUtil::FilterByClosestNormal({0.f, 0.f, -1.f}, list, filteredList); } else { CollisionUtil::FilterByClosestNormal({0.f, 0.f, 1.f}, list, filteredList); } if (filteredList.GetCount() > 0) { CGameCollision::MakeCollisionCallbacks(mgr, actor, idOut, filteredList); } return true; } return false; } void CGroundMovement::MoveGroundColliderXY(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor, const CMaterialFilter& filter, EntityList& nearList, float dt) { bool didCollide = false; bool isPlayer = actor.GetMaterialList().HasMaterial(EMaterialTypes::Player); float remDt = dt; float originalDt = dt; TCastToPtr otherActor; CCollisionInfoList collisionList; CMotionState newMState = actor.PredictMotion_Internal(dt); float transMag = newMState.x0_translation.magnitude(); float divMag = NAN; if (isPlayer) { divMag = std::max(transMag / 5.f, 0.005f); } else { divMag = std::max(transMag / 3.f, 0.02f); } float minExtent = 0.5f * CGameCollision::GetMinExtentForCollisionPrimitive(*actor.GetCollisionPrimitive()); if (transMag > minExtent) { dt = minExtent * (dt / transMag); originalDt = dt; newMState = actor.PredictMotion_Internal(dt); divMag = std::min(divMag, minExtent); } float nonCollideDt = dt; do { actor.MoveCollisionPrimitive(newMState.x0_translation); collisionList.Clear(); TUniqueId otherId = kInvalidUniqueId; bool collided = CGameCollision::DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), filter, nearList, otherId, collisionList); if (collided) { otherActor = mgr.ObjectById(otherId); } actor.MoveCollisionPrimitive(zeus::skZero3f); if (collided) { didCollide = true; if (newMState.x0_translation.magnitude() < divMag) { CCollisionInfoList backfaceFilteredList; CCollisionInfoList floorFilteredList; zeus::CVector3f deltaVel = actor.GetVelocity(); if (otherActor) { deltaVel -= otherActor->GetVelocity(); } CollisionUtil::FilterOutBackfaces(deltaVel, collisionList, backfaceFilteredList); CAABoxFilter::FilterBoxFloorCollisions(backfaceFilteredList, floorFilteredList); CGameCollision::MakeCollisionCallbacks(mgr, actor, otherId, floorFilteredList); if (floorFilteredList.GetCount() == 0 && isPlayer) { CMotionState lastNonCollideState = actor.GetLastNonCollidingState(); lastNonCollideState.x1c_velocity *= zeus::CVector3f(0.5f); lastNonCollideState.x28_angularMomentum *= zeus::CVector3f(0.5f); actor.SetMotionState(lastNonCollideState); } for (const CCollisionInfo& info : floorFilteredList) { CCollisionInfo infoCopy = info; float restitution = CGameCollision::GetCoefficientOfRestitution(infoCopy) + actor.GetCoefficientOfRestitutionModifier(); if (otherActor) { CGameCollision::CollideWithDynamicBodyNoRot(actor, *otherActor, infoCopy, restitution, true); } else { CGameCollision::CollideWithStaticBodyNoRot(actor, infoCopy.GetMaterialLeft(), infoCopy.GetMaterialRight(), infoCopy.GetNormalLeft(), restitution, true); } } remDt -= dt; nonCollideDt = std::min(originalDt, remDt); dt = nonCollideDt; } else { nonCollideDt *= 0.5f; dt *= 0.5f; } } else { actor.AddMotionState(newMState); remDt -= dt; dt = nonCollideDt; actor.MoveCollisionPrimitive(zeus::skZero3f); } newMState = actor.PredictMotion_Internal(dt); } while (remDt > 0.f); if (!didCollide && !actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider)) { mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::Falling); } actor.MoveCollisionPrimitive(zeus::skZero3f); } zeus::CVector3f CGroundMovement::CollisionDamping(const zeus::CVector3f& vel, const zeus::CVector3f& dir, const zeus::CVector3f& cNorm, float normCoeff, float deltaCoeff) { zeus::CVector3f dampedDir = (cNorm * -2.f * cNorm.dot(dir) + dir).normalized(); zeus::CVector3f dampedNorm = cNorm * cNorm.dot(dampedDir); return (dampedDir - dampedNorm) * vel.magnitude() * deltaCoeff + normCoeff * vel.magnitude() * dampedNorm; } void CGroundMovement::MoveGroundCollider_New(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* nearList) { zeus::CAABox motionVol = actor.GetMotionVolume(dt); EntityList useNearList; if (nearList != nullptr) { useNearList = *nearList; } else { mgr.BuildColliderList(useNearList, actor, motionVol); } CAreaCollisionCache cache(motionVol); CGameCollision::BuildAreaCollisionCache(mgr, cache); auto& player = static_cast(actor); player.x9c5_28_slidingOnWall = false; bool applyJump = player.x258_movementState == CPlayer::EPlayerMovementState::ApplyJump; bool dampUnderwater = false; if (player.x9c4_31_inWaterMovement) { if (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) { dampUnderwater = true; } } bool noJump = (player.x258_movementState != CPlayer::EPlayerMovementState::ApplyJump && player.x258_movementState != CPlayer::EPlayerMovementState::Jump); float stepDown = player.GetStepDownHeight(); float stepUp = player.GetStepUpHeight(); bool doStepDown = true; CMaterialList material(EMaterialTypes::NoStepLogic); SMoveObjectResult result; if (!applyJump) { const SMovementOptions opts{ .x0_setWaterLandingForce = false, .x4_waterLandingForceCoefficient = 0.f, .x8_minimumWaterLandingForce = 0.f, .xc_anyZThreshold = 0.37f, .x10_downwardZThreshold = 0.25f, .x14_waterLandingVelocityReduction = 0.f, .x18_dampForceAndMomentum = true, .x19_alwaysClip = false, .x1a_disableClipForFloorOnly = noJump, .x1c_maxCollisionCycles = 4, .x20_minimumTranslationDelta = 0.002f, .x24_dampedNormalCoefficient = 0.f, .x28_dampedDeltaCoefficient = 1.f, .x2c_floorElasticForce = 0.f, .x30_wallElasticConstant = 0.02f, .x34_wallElasticLinear = 0.2f, .x38_maxPositiveVerticalVelocity = player.GetMaximumPlayerPositiveVerticalVelocity(mgr), .x3c_floorPlaneNormal = player.GetLastFloorPlaneNormal(), }; if (noJump) { zeus::CVector3f vel = player.GetVelocity(); vel.z() = 0.f; actor.SetVelocityWR(vel); actor.x15c_force.z() = 0.f; actor.x150_momentum.z() = 0.f; actor.x168_impulse.z() = 0.f; } CPhysicsState physStatePre = actor.GetPhysicsState(); CMaterialList material2 = MoveObjectAnalytical(mgr, actor, dt, useNearList, cache, opts, result); CPhysicsState physStatePost = actor.GetPhysicsState(); /* NoStepLogic must be the only set material bit to bypass step logic */ if (material2.XOR({EMaterialTypes::NoStepLogic}) != 0u) { SMovementOptions optsCopy = opts; optsCopy.x19_alwaysClip = noJump; optsCopy.x30_wallElasticConstant = 0.03f; const zeus::CVector3f postToPre = physStatePre.GetTranslation() - physStatePost.GetTranslation(); const float postToPreMag = postToPre.magSquared(); const float quarterStepUp = 0.25f * stepUp; rstl::reserved_vector physStateList; rstl::reserved_vector stepDeltaList; rstl::reserved_vector collisionInfoList; rstl::reserved_vector uniqueIdList; rstl::reserved_vector materialListList; bool done = false; for (int i = 0; i < 2 && !done; ++i) { double useStepUp = (i == 0) ? quarterStepUp : stepUp; actor.SetPhysicsState(physStatePre); CCollisionInfo collisionInfo; TUniqueId id = kInvalidUniqueId; CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetTransform(), actor.GetMaterialFilter(), useNearList, {0.f, 0.f, 1.f}, id, collisionInfo, useStepUp); if (collisionInfo.IsValid()) { useStepUp = std::max(0.0, useStepUp - optsCopy.x20_minimumTranslationDelta); done = true; } if (useStepUp > 0.0005) { actor.SetTranslation(actor.GetTranslation() + zeus::CVector3f(0.f, 0.f, static_cast(useStepUp))); SMoveObjectResult result2; CMaterialList material3 = MoveObjectAnalytical(mgr, actor, dt, useNearList, cache, optsCopy, result2); CCollisionInfo collisionInfo2; double useStepDown2 = useStepUp + stepDown; TUniqueId id2 = kInvalidUniqueId; if (useStepDown2 > 0.0) { CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetTransform(), actor.GetMaterialFilter(), useNearList, {0.f, 0.f, -1.f}, id2, collisionInfo2, useStepDown2); } else { useStepDown2 = 0.0; } float minStep = std::min(useStepUp, useStepDown2); zeus::CVector3f offsetStep = actor.GetTranslation() - zeus::CVector3f(0.f, 0.f, minStep); bool floor = (collisionInfo2.IsValid() && CGameCollision::CanBlock(collisionInfo2.GetMaterialLeft(), collisionInfo2.GetNormalLeft())); zeus::CVector3f postToPre2 = physStatePre.GetTranslation() - offsetStep; float stepDelta = postToPre2.magSquared(); if (floor && postToPreMag < stepDelta) { useStepDown2 = std::max(0.0, useStepDown2 - 0.0005); actor.SetTranslation(actor.GetTranslation() - zeus::CVector3f(0.f, 0.f, static_cast(useStepDown2))); physStateList.push_back(actor.GetPhysicsState()); stepDeltaList.push_back(stepDelta); collisionInfoList.push_back(collisionInfo2); uniqueIdList.push_back(id2); materialListList.push_back(material3); } } } if (physStateList.empty()) { actor.SetPhysicsState(physStatePost); material = material2; } else { float maxFloat = -1.0e10f; int maxIdx = -1; for (size_t i = 0; i < physStateList.size(); ++i) { if (maxFloat < stepDeltaList[i]) { maxFloat = stepDeltaList[i]; maxIdx = i; } } actor.SetPhysicsState(physStateList[maxIdx]); mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor); if (CEntity* ent = mgr.ObjectById(uniqueIdList[maxIdx])) { result.x0_id.emplace(uniqueIdList[maxIdx]); result.x8_collision.emplace(collisionInfoList[maxIdx]); if (TCastToPtr(ent)) { mgr.SendScriptMsg(ent, actor.GetUniqueId(), EScriptObjectMessage::AddPlatformRider); } } CCollisionInfo& cInfo = collisionInfoList[maxIdx]; CGameCollision::SendMaterialMessage(mgr, cInfo.GetMaterialLeft(), actor); doStepDown = false; actor.SetLastFloorPlaneNormal({cInfo.GetNormalLeft()}); } } } else { const SMovementOptions opts{ .x0_setWaterLandingForce = true, .x4_waterLandingForceCoefficient = dampUnderwater ? 35.f : 1.f, .x8_minimumWaterLandingForce = dampUnderwater ? 5.f : 0.f, .xc_anyZThreshold = dampUnderwater ? 0.05f : 0.37f, .x10_downwardZThreshold = dampUnderwater ? 0.01f : 0.25f, .x14_waterLandingVelocityReduction = dampUnderwater ? 0.2f : 0.f, .x18_dampForceAndMomentum = false, .x19_alwaysClip = false, .x1a_disableClipForFloorOnly = false, .x1c_maxCollisionCycles = 4, .x20_minimumTranslationDelta = 0.002f, .x24_dampedNormalCoefficient = 0.f, .x28_dampedDeltaCoefficient = 1.f, .x2c_floorElasticForce = 0.1f, .x30_wallElasticConstant = 0.2f, .x38_maxPositiveVerticalVelocity = player.GetMaximumPlayerPositiveVerticalVelocity(mgr), .x3c_floorPlaneNormal = player.GetLastFloorPlaneNormal(), }; material = MoveObjectAnalytical(mgr, actor, dt, useNearList, cache, opts, result); } if (doStepDown) { CCollisionInfo collisionInfo; double stepDown2 = actor.GetStepDownHeight(); float zOffset = 0.f; TUniqueId id = kInvalidUniqueId; if (stepDown2 > FLT_EPSILON) { zeus::CTransform xf = actor.GetTransform(); xf.origin += zeus::CVector3f(0.f, 0.f, 0.0005f); if (!CGameCollision::DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(), xf, actor.GetMaterialFilter(), useNearList)) { actor.SetTranslation(xf.origin); zOffset = 0.0005f; stepDown2 += 0.0005; } CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetTransform(), actor.GetMaterialFilter(), useNearList, {0.f, 0.f, -1.f}, id, collisionInfo, stepDown2); } if (id != kInvalidUniqueId) { result.x0_id.emplace(id); result.x8_collision.emplace(collisionInfo); } if (!collisionInfo.IsValid() || !CGameCollision::CanBlock(collisionInfo.GetMaterialLeft(), collisionInfo.GetNormalLeft())) { if (zOffset > 0.f) { zeus::CTransform xf = actor.GetTransform(); xf.origin -= zeus::CVector3f(0.f, 0.f, zOffset); } if (collisionInfo.IsValid()) { player.x9c5_28_slidingOnWall = true; } CheckFalling(actor, mgr, dt); player.SetLastFloorPlaneNormal({}); } else { mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::OnFloor); stepDown2 = std::max(0.0, stepDown2 - 0.0005); actor.SetTranslation(actor.GetTranslation() - zeus::CVector3f(0.f, 0.f, static_cast(stepDown2))); if (TCastToPtr plat = mgr.ObjectById(id)) { mgr.SendScriptMsg(plat.GetPtr(), actor.GetUniqueId(), EScriptObjectMessage::AddPlatformRider); } CGameCollision::SendMaterialMessage(mgr, collisionInfo.GetMaterialLeft(), actor); actor.SetLastFloorPlaneNormal({collisionInfo.GetNormalLeft()}); } } actor.ClearForcesAndTorques(); if (material.HasMaterial(EMaterialTypes::Wall)) { player.SetPlayerHitWallDuringMove(); } if (result.x0_id) { CCollisionInfoList list; list.Add(*result.x8_collision, false); CGameCollision::MakeCollisionCallbacks(mgr, actor, *result.x0_id, list); } CMotionState mState = actor.GetMotionState(); mState.x0_translation = actor.GetLastNonCollidingState().x0_translation; mState.x1c_velocity = actor.GetLastNonCollidingState().x1c_velocity; actor.SetLastNonCollidingState(mState); const CCollisionPrimitive* usePrim = actor.GetCollisionPrimitive(); std::unique_ptr prim; if (usePrim->GetPrimType() == FOURCC('AABX')) { const auto& existingAABB = static_cast(*usePrim); prim = std::make_unique( zeus::CAABox(existingAABB.GetBox().min + 0.0001f, existingAABB.GetBox().max - 0.0001f), usePrim->GetMaterial()); usePrim = prim.get(); } else if (usePrim->GetPrimType() == FOURCC('SPHR')) { const auto& existingSphere = static_cast(*usePrim); prim = std::make_unique( zeus::CSphere(existingSphere.GetSphere().position, existingSphere.GetSphere().radius - 0.0001f), usePrim->GetMaterial()); usePrim = prim.get(); } CGameCollision::CollisionFailsafe(mgr, cache, actor, *usePrim, useNearList, 0.f, 1); } bool CGroundMovement::RemoveNormalComponent(const zeus::CVector3f& a, const zeus::CVector3f& b, zeus::CVector3f& c, float& d) { float dot = a.dot(c); if (std::fabs(dot) > 0.99f) { return false; } float dot2 = b.dot(c); float dot3 = b.dot((c - a * dot).normalized()); if (dot2 > 0.f && dot3 < 0.f) { return false; } if (std::fabs(dot2) > 0.01f && std::fabs(dot3 / dot2) > 4.f) { return false; } c -= dot * a; d = dot; return true; } bool CGroundMovement::RemoveNormalComponent(const zeus::CVector3f& a, zeus::CVector3f& b) { float dot = a.dot(b); if (std::fabs(dot) > 0.99f) { return false; } b -= a * dot; return true; } static bool RemovePositiveZComponentFromNormal(zeus::CVector3f& vec) { if (vec.z() > 0.f && vec.z() < 0.99f) { vec.z() = 0.f; vec.normalize(); return true; } return false; } CMaterialList CGroundMovement::MoveObjectAnalytical(CStateManager& mgr, CPhysicsActor& actor, float dt, EntityList& nearList, CAreaCollisionCache& cache, const SMovementOptions& opts, SMoveObjectResult& result) { result.x6c_processedCollisions = 0; CMaterialList ret; zeus::CVector3f floorPlaneNormal = opts.x3c_floorPlaneNormal ? *opts.x3c_floorPlaneNormal : zeus::skZero3f; bool floorCollision = opts.x3c_floorPlaneNormal.has_value(); float remDt = dt; for (size_t i = 0; remDt > 0.f; ++i) { float collideDt = remDt; CMotionState mState = actor.PredictMotion_Internal(remDt); double mag = mState.x0_translation.magnitude(); zeus::CVector3f normTrans = (1.f / ((float(mag) > FLT_EPSILON) ? float(mag) : 1.f)) * mState.x0_translation; TUniqueId id = kInvalidUniqueId; CCollisionInfo collisionInfo; if (mag > opts.x20_minimumTranslationDelta) { double oldMag = mag; CGameCollision::DetectCollision_Cached_Moving(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), actor.GetMaterialFilter(), nearList, normTrans, id, collisionInfo, mag); if (id != kInvalidUniqueId) { result.x0_id.emplace(id); result.x8_collision.emplace(collisionInfo); } collideDt = static_cast(mag / oldMag * remDt); } mag = std::max(0.f, float(mag) - opts.x20_minimumTranslationDelta); zeus::CVector3f collisionNorm = collisionInfo.GetNormalLeft(); bool floor = CGameCollision::CanBlock(collisionInfo.GetMaterialLeft(), collisionNorm); bool clipCollision = true; if (!opts.x19_alwaysClip) { if (!opts.x1a_disableClipForFloorOnly || floor) { clipCollision = false; } } float collisionFloorDot = 0.f; if (collisionInfo.IsValid()) { result.x6c_processedCollisions += 1; if (floor) { ret.Add(EMaterialTypes::Floor); floorPlaneNormal = collisionInfo.GetNormalLeft(); floorCollision = true; } else { ret.Add(EMaterialTypes::Wall); } if (clipCollision) { if (floorCollision) { if (!CGroundMovement::RemoveNormalComponent(floorPlaneNormal, normTrans, collisionNorm, collisionFloorDot)) { RemovePositiveZComponentFromNormal(collisionNorm); } else { collisionNorm.normalize(); } } else { RemovePositiveZComponentFromNormal(collisionNorm); } } mState = actor.PredictMotion_Internal(collideDt); } mState.x0_translation = normTrans * static_cast(mag); actor.AddMotionState(mState); if (collisionInfo.IsValid()) { zeus::CVector3f vel = actor.GetVelocity().canBeNormalized() ? CGroundMovement::CollisionDamping(actor.GetVelocity(), actor.GetVelocity().normalized(), collisionNorm, opts.x24_dampedNormalCoefficient, opts.x28_dampedDeltaCoefficient) : zeus::skZero3f; float elasticForce = floor ? opts.x2c_floorElasticForce : opts.x34_wallElasticLinear * collisionFloorDot + opts.x30_wallElasticConstant; float dot = collisionNorm.dot(vel); if (dot < elasticForce) { vel += (elasticForce - dot) * collisionNorm; } if (clipCollision && floorCollision) { if (!CGroundMovement::RemoveNormalComponent(floorPlaneNormal, vel)) { vel.z() = 0.f; } } if (vel.z() > opts.x38_maxPositiveVerticalVelocity) { vel *= zeus::CVector3f(opts.x38_maxPositiveVerticalVelocity / vel.z()); } if (opts.x18_dampForceAndMomentum) { if (actor.x15c_force.canBeNormalized()) { // zeus::CVector3f prevForce = actor.x15c_force; actor.x15c_force = CGroundMovement::CollisionDamping(actor.x15c_force, actor.x15c_force.normalized(), collisionNorm, 0.f, 1.f); } if (actor.x150_momentum.canBeNormalized()) { actor.x150_momentum = CGroundMovement::CollisionDamping(actor.x150_momentum, actor.x150_momentum.normalized(), collisionNorm, 0.f, 1.f); } } if (opts.x0_setWaterLandingForce && !floor) { if (collisionInfo.GetNormalLeft().z() < -0.1f && vel.z() > 0.f) { vel.z() *= 0.5f; } float zNormAbs = std::fabs(collisionInfo.GetNormalLeft().z()); if ((zNormAbs > opts.x10_downwardZThreshold && vel.z() < 0.f) || zNormAbs > opts.xc_anyZThreshold) { actor.x15c_force = zeus::CVector3f( 0.f, 0.f, -(1.f + std::max(opts.x4_waterLandingForceCoefficient * zNormAbs, opts.x8_minimumWaterLandingForce)) * actor.GetWeight()); vel *= zeus::CVector3f(1.f - opts.x14_waterLandingVelocityReduction); } } actor.SetVelocityWR(vel); } else { zeus::CVector3f vel = actor.x138_velocity; if (actor.x138_velocity.z() > opts.x38_maxPositiveVerticalVelocity) { vel *= zeus::CVector3f(opts.x38_maxPositiveVerticalVelocity / vel.z()); } actor.SetVelocityWR(vel); } actor.ClearImpulses(); remDt -= collideDt; if (i >= opts.x1c_maxCollisionCycles) { break; } } result.x70_processedDt = dt - remDt; return ret; } } // namespace metaforce ================================================ FILE: Runtime/Character/CGroundMovement.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Collision/CCollisionInfo.hpp" #include namespace metaforce { class CAreaCollisionCache; class CCollisionInfoList; class CMaterialFilter; class CPhysicsActor; class CStateManager; class CGroundMovement { public: struct SMovementOptions { bool x0_setWaterLandingForce; float x4_waterLandingForceCoefficient; float x8_minimumWaterLandingForce; float xc_anyZThreshold; float x10_downwardZThreshold; float x14_waterLandingVelocityReduction; bool x18_dampForceAndMomentum; bool x19_alwaysClip; bool x1a_disableClipForFloorOnly; u32 x1c_maxCollisionCycles; float x20_minimumTranslationDelta; float x24_dampedNormalCoefficient; float x28_dampedDeltaCoefficient; float x2c_floorElasticForce; float x30_wallElasticConstant; float x34_wallElasticLinear; float x38_maxPositiveVerticalVelocity; std::optional x3c_floorPlaneNormal; }; struct SMoveObjectResult { std::optional x0_id; std::optional x8_collision; u32 x6c_processedCollisions; float x70_processedDt; }; static void CheckFalling(CPhysicsActor& actor, CStateManager& mgr, float); static void MoveGroundCollider(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* nearList); static bool ResolveUpDown(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor, const CMaterialFilter& filter, EntityList& nearList, float, float, float&, CCollisionInfoList& list); static bool MoveGroundColliderZ(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor, const CMaterialFilter& filter, EntityList& nearList, float, float&, CCollisionInfoList& list, TUniqueId& idOut); static void MoveGroundColliderXY(CAreaCollisionCache& cache, CStateManager& mgr, CPhysicsActor& actor, const CMaterialFilter& filter, EntityList& nearList, float); static zeus::CVector3f CollisionDamping(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CVector3f& c, float d, float e); static void MoveGroundCollider_New(CStateManager& mgr, CPhysicsActor& actor, float, const EntityList* nearList); static bool RemoveNormalComponent(const zeus::CVector3f&, const zeus::CVector3f&, zeus::CVector3f&, float&); static bool RemoveNormalComponent(const zeus::CVector3f& a, zeus::CVector3f& b); static CMaterialList MoveObjectAnalytical(CStateManager& mgr, CPhysicsActor& actor, float, EntityList& nearList, CAreaCollisionCache& cache, const SMovementOptions& opts, SMoveObjectResult& result); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CHalfTransition.cpp ================================================ #include "Runtime/Character/CHalfTransition.hpp" #include "Runtime/Character/CMetaTransFactory.hpp" namespace metaforce { CHalfTransition::CHalfTransition(CInputStream& in) { x0_id = in.ReadLong(); x4_trans = CMetaTransFactory::CreateMetaTrans(in); } } // namespace metaforce ================================================ FILE: Runtime/Character/CHalfTransition.hpp ================================================ #pragma once #include #include "Runtime/GCNTypes.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaTrans.hpp" namespace metaforce { class CHalfTransition { u32 x0_id; std::shared_ptr x4_trans; public: explicit CHalfTransition(CInputStream& in); u32 GetId() const { return x0_id; } const std::shared_ptr& GetMetaTrans() const { return x4_trans; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CHierarchyPoseBuilder.cpp ================================================ #include "Runtime/Character/CHierarchyPoseBuilder.hpp" #include "Runtime/Character/CAnimData.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include namespace metaforce { void CHierarchyPoseBuilder::BuildIntoHierarchy(const CCharLayoutInfo& layout, const CSegId& boneId, const CSegId& nullId) { if (!x38_treeMap.HasElement(boneId)) { const CCharLayoutNode::Bone& bone = layout.GetRootNode()->GetBoneMap()[boneId]; if (bone.x0_parentId == nullId) { x30_rootId = boneId; x34_hasRoot = true; zeus::CVector3f origin = layout.GetFromParentUnrotated(boneId); CTreeNode& node = x38_treeMap[boneId]; node.x14_offset = origin; } else { BuildIntoHierarchy(layout, bone.x0_parentId, nullId); zeus::CVector3f origin = layout.GetFromParentUnrotated(boneId); CTreeNode& pNode = x38_treeMap[bone.x0_parentId]; CTreeNode& node = x38_treeMap[boneId]; node.x14_offset = origin; node.x1_sibling = pNode.x0_child; pNode.x0_child = boneId; } } } void CHierarchyPoseBuilder::RecursivelyBuildNoScale(const CSegId& boneId, const CTreeNode& node, CPoseAsTransforms& pose, const zeus::CQuaternion& parentRot, const zeus::CMatrix3f& parentXf, const zeus::CVector3f& parentOffset) const { zeus::CQuaternion quat = parentRot * node.x4_rotation; zeus::CMatrix3f xf = quat; zeus::CVector3f xfOffset = parentXf * node.x14_offset + parentOffset; pose.Insert(boneId, quat, xfOffset); CSegId curBone = node.x0_child; while (curBone != 0) { const CTreeNode& node = x38_treeMap[curBone]; RecursivelyBuild(curBone, node, pose, quat, xf, xfOffset); curBone = node.x1_sibling; } } void CHierarchyPoseBuilder::RecursivelyBuild(const CSegId& boneId, const CTreeNode& node, CPoseAsTransforms& pose, const zeus::CQuaternion& parentRot, const zeus::CMatrix3f& parentXf, const zeus::CVector3f& parentOffset) const { zeus::CQuaternion quat = parentRot * node.x4_rotation; float scale; if (x0_layoutDesc.GetScaledLayoutDescription()) { scale = x0_layoutDesc.GetScaledLayoutDescription()->GlobalScale(); } else { scale = 1.f; } zeus::CMatrix3f rotation; if (scale == 1.f) rotation = quat; else rotation = parentXf * (zeus::CMatrix3f{node.x4_rotation} * zeus::CMatrix3f{scale}); zeus::CVector3f offset = parentOffset + (parentXf * node.x14_offset); pose.Insert(boneId, rotation, offset); CSegId curBone = node.x0_child; while (curBone != 0) { const CTreeNode& node = x38_treeMap[curBone]; RecursivelyBuild(curBone, node, pose, quat, quat, offset); curBone = node.x1_sibling; } } void CHierarchyPoseBuilder::BuildTransform(const CSegId& boneId, zeus::CTransform& xfOut) const { TLockedToken layoutInfoTok; float scale; if (x0_layoutDesc.GetScaledLayoutDescription()) { layoutInfoTok = x0_layoutDesc.GetScaledLayoutDescription()->ScaledLayout(); scale = x0_layoutDesc.GetScaledLayoutDescription()->GlobalScale(); } else { layoutInfoTok = x0_layoutDesc.GetCharLayoutInfo(); scale = 1.f; } const CCharLayoutInfo& layoutInfo = *layoutInfoTok.GetObj(); u32 idCount = 0; CSegId buildIDs[100]; { CSegId curId = boneId; while (curId != 2) { buildIDs[idCount++] = curId; curId = layoutInfo.GetRootNode()->GetBoneMap()[curId].x0_parentId; } } zeus::CQuaternion accumRot; zeus::CMatrix3f accumXF; zeus::CVector3f accumPos; for (CSegId* id = &buildIDs[idCount]; id != buildIDs; --id) { CSegId& thisId = id[-1]; const CTreeNode& node = x38_treeMap[thisId]; accumRot *= node.x4_rotation; accumPos += accumXF * node.x14_offset; if (scale == 1.f) accumXF = accumRot; else accumXF = accumXF * zeus::CMatrix3f(node.x4_rotation) * zeus::CMatrix3f(scale); } xfOut.setRotation(accumXF); xfOut.origin = accumPos; } void CHierarchyPoseBuilder::BuildNoScale(CPoseAsTransforms& pose) { pose.Clear(); const CTreeNode& node = x38_treeMap[x30_rootId]; zeus::CQuaternion quat; zeus::CMatrix3f mtx; zeus::CVector3f vec; RecursivelyBuildNoScale(x30_rootId, node, pose, quat, mtx, vec); } void CHierarchyPoseBuilder::Insert(const CSegId& boneId, const zeus::CQuaternion& quat) { CTreeNode& node = x38_treeMap[boneId]; node.x4_rotation = quat; } void CHierarchyPoseBuilder::Insert(const CSegId& boneId, const zeus::CQuaternion& quat, const zeus::CVector3f& offset) { CTreeNode& node = x38_treeMap[boneId]; node.x4_rotation = quat; node.x14_offset = offset; } CHierarchyPoseBuilder::CHierarchyPoseBuilder(const CLayoutDescription& layout) : x0_layoutDesc(layout), x38_treeMap(layout.GetCharLayoutInfo()->GetSegIdList().GetList().size()) { TLockedToken layoutInfoTok; if (layout.GetScaledLayoutDescription()) layoutInfoTok = layout.GetScaledLayoutDescription()->ScaledLayout(); else layoutInfoTok = layout.GetCharLayoutInfo(); const CCharLayoutInfo& layoutInfo = *layoutInfoTok.GetObj(); const CSegIdList& segIDs = layoutInfo.GetSegIdList(); for (const CSegId& id : segIDs.GetList()) BuildIntoHierarchy(layoutInfo, id, 2); } } // namespace metaforce ================================================ FILE: Runtime/Character/CHierarchyPoseBuilder.hpp ================================================ #pragma once #include "Runtime/Character/CLayoutDescription.hpp" #include "Runtime/Character/CSegId.hpp" #include "Runtime/Character/TSegIdMap.hpp" #include #include namespace metaforce { class CCharLayoutInfo; class CLayoutDescription; class CPoseAsTransforms; class CHierarchyPoseBuilder { public: struct CTreeNode { CSegId x0_child = 0; CSegId x1_sibling = 0; zeus::CQuaternion x4_rotation; zeus::CVector3f x14_offset; }; private: CLayoutDescription x0_layoutDesc; CSegId x30_rootId; bool x34_hasRoot = false; TSegIdMap x38_treeMap; void BuildIntoHierarchy(const CCharLayoutInfo& layout, const CSegId& boneId, const CSegId& nullId); void RecursivelyBuildNoScale(const CSegId& boneId, const CTreeNode& node, CPoseAsTransforms& pose, const zeus::CQuaternion& rot, const zeus::CMatrix3f& scale, const zeus::CVector3f& offset) const; void RecursivelyBuild(const CSegId& boneId, const CTreeNode& node, CPoseAsTransforms& pose, const zeus::CQuaternion& rot, const zeus::CMatrix3f& scale, const zeus::CVector3f& offset) const; public: explicit CHierarchyPoseBuilder(const CLayoutDescription& layout); const TLockedToken& CharLayoutInfo() const { return x0_layoutDesc.ScaledLayout(); } bool HasRoot() const { return x34_hasRoot; } void BuildTransform(const CSegId& boneId, zeus::CTransform& xfOut) const; void BuildNoScale(CPoseAsTransforms& pose); void Insert(const CSegId& boneId, const zeus::CQuaternion& quat); void Insert(const CSegId& boneId, const zeus::CQuaternion& quat, const zeus::CVector3f& offset); TSegIdMap& GetTreeMap() { return x38_treeMap; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CIkChain.cpp ================================================ #include "Runtime/Character/CIkChain.hpp" #include "Runtime/Character/CAnimData.hpp" namespace metaforce { void CIkChain::Update(float dt) { if (x44_24_activated) x40_time = zeus::min(x40_time + dt, 1.f); else x40_time = zeus::max(0.f, x40_time - dt); } void CIkChain::Deactivate() { x44_24_activated = false; } void CIkChain::Activate(const CAnimData& animData, const CSegId& segId, const zeus::CTransform& xf) { x0_bone = segId; const auto& info = animData.GetPoseBuilder().CharLayoutInfo(); x1_p1 = info->GetRootNode()->GetBoneMap()[x0_bone].x0_parentId; if (x1_p1 != 2) { x2_p2 = info->GetRootNode()->GetBoneMap()[x1_p1].x0_parentId; x4_p2p1Dir = info->GetFromParentUnrotated(x1_p1); x1c_p2p1Length = x4_p2p1Dir.magnitude(); x4_p2p1Dir = x4_p2p1Dir / x1c_p2p1Length; x10_p1BoneDir = info->GetFromParentUnrotated(x0_bone); x20_p1BoneLength = x10_p1BoneDir.magnitude(); x10_p1BoneDir = x10_p1BoneDir / x20_p1BoneLength; x34_holdPos = xf.origin; x24_holdRot = zeus::CQuaternion(xf.basis); x44_24_activated = true; } } void CIkChain::PreRender(CAnimData& animData, const zeus::CTransform& xf, const zeus::CVector3f& scale) { if (x40_time > 0.f) { zeus::CTransform p2Xf = animData.GetLocatorTransform(x2_p2, nullptr); zeus::CVector3f localDelta = xf.transposeRotate(x34_holdPos - xf.origin); localDelta /= scale; localDelta = p2Xf.transposeRotate(localDelta - p2Xf.origin); zeus::CQuaternion p2Rot = animData.PoseBuilder().GetTreeMap()[x2_p2].x4_rotation; zeus::CQuaternion p1Rot = animData.PoseBuilder().GetTreeMap()[x1_p1].x4_rotation; zeus::CQuaternion boneRot = animData.PoseBuilder().GetTreeMap()[x0_bone].x4_rotation; zeus::CQuaternion newP2Rot = p2Rot; zeus::CQuaternion newP1Rot = p1Rot; Solve(newP2Rot, newP1Rot, localDelta); zeus::CQuaternion newBoneRot = (zeus::CQuaternion((xf * p2Xf).basis) * p2Rot.inverse() * newP2Rot * newP1Rot).inverse() * x24_holdRot; if (x40_time < 1.f) { newP2Rot = zeus::CQuaternion::slerpShort(p2Rot, newP2Rot, x40_time); newP1Rot = zeus::CQuaternion::slerpShort(p1Rot, newP1Rot, x40_time); newBoneRot = zeus::CQuaternion::slerpShort(boneRot, newBoneRot, x40_time); } animData.PoseBuilder().GetTreeMap()[x2_p2].x4_rotation = newP2Rot; animData.PoseBuilder().GetTreeMap()[x1_p1].x4_rotation = newP1Rot; animData.PoseBuilder().GetTreeMap()[x0_bone].x4_rotation = newBoneRot; animData.MarkPoseDirty(); } } void CIkChain::Solve(zeus::CQuaternion& q1, zeus::CQuaternion& q2, const zeus::CVector3f& vec) { const float mag = vec.magnitude(); const float magSq = mag * mag; const float twoMag = (2.0f * mag); float f29 = std::acos(zeus::clamp(-1.f, (((x20_p1BoneLength * magSq) + x20_p1BoneLength) - (x1c_p2p1Length * x1c_p2p1Length)) / (twoMag * x20_p1BoneLength), 1.f)); float f30 = std::acos(zeus::clamp( -1.f, ((x1c_p2p1Length * (magSq - (x20_p1BoneLength * x20_p1BoneLength))) + x1c_p2p1Length) / (twoMag * x1c_p2p1Length), 1.f)); zeus::CVector3f vecA = q2.transform(x10_p1BoneDir); zeus::CVector3f crossVecA = x4_p2p1Dir.cross(vecA); float crossAMag = crossVecA.magnitude(); crossVecA *= zeus::CVector3f(1.f / crossVecA.magnitude()); float angle = std::asin(zeus::min(crossAMag, 1.f)); if (x4_p2p1Dir.dot(vecA) < 0.f) angle = M_PIF - angle; q2 = zeus::CQuaternion::fromAxisAngle(crossVecA, (f30 + f29) - angle) * q2; zeus::CVector3f v1 = q1.transform((x1c_p2p1Length * x4_p2p1Dir) + (x20_p1BoneLength * q2.transform(x10_p1BoneDir))); zeus::CVector3f v2 = q1.transform(vec); zeus::CVector3f crossVecB = v1.normalized().cross((1.f / mag) * v2); angle = std::asin(zeus::min(crossVecB.magnitude(), 1.f)); if (v1.dot((1.f / mag) * v2) < 0.f) angle = M_PIF - angle; q1 = zeus::CQuaternion::fromAxisAngle(crossVecB * (1.f / crossVecB.magnitude()), angle) * q1; } } // namespace metaforce ================================================ FILE: Runtime/Character/CIkChain.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CSegId.hpp" #include #include #include namespace metaforce { class CAnimData; class CSegId; class CIkChain { CSegId x0_bone; CSegId x1_p1; CSegId x2_p2; zeus::CVector3f x4_p2p1Dir = zeus::skForward; zeus::CVector3f x10_p1BoneDir = zeus::skForward; float x1c_p2p1Length = 1.f; float x20_p1BoneLength = 1.f; zeus::CQuaternion x24_holdRot; zeus::CVector3f x34_holdPos; float x40_time = 0.f; bool x44_24_activated : 1 = false; public: CIkChain() = default; bool GetActive() const { return x44_24_activated; } void Update(float); void Deactivate(); void Activate(const CAnimData&, const CSegId&, const zeus::CTransform&); void PreRender(CAnimData&, const zeus::CTransform&, const zeus::CVector3f&); void Solve(zeus::CQuaternion&, zeus::CQuaternion&, const zeus::CVector3f&); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CInt32POINode.cpp ================================================ #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CAnimSourceReader.hpp" namespace metaforce { CInt32POINode::CInt32POINode() : CInt32POINode(""sv, EPOIType::EmptyInt32, CCharAnimTime(), -1, false, 1.f, -1, 0, 0, "root"sv) {} CInt32POINode::CInt32POINode(std::string_view name, EPOIType type, const CCharAnimTime& time, s32 index, bool unique, float weight, s32 charIndex, s32 flags, s32 val, std::string_view locator) : CPOINode(name, type, time, index, unique, weight, charIndex, flags), x38_val(val), x3c_locatorName(locator) {} CInt32POINode::CInt32POINode(CInputStream& in) : CPOINode(in), x38_val(in.ReadLong()), x3c_locatorName(in.Get()) {} CInt32POINode CInt32POINode::CopyNodeMinusStartTime(const CInt32POINode& node, const CCharAnimTime& startTime) { CInt32POINode ret = node; ret.x1c_time -= startTime; return ret; } } // namespace metaforce ================================================ FILE: Runtime/Character/CInt32POINode.hpp ================================================ #pragma once #include #include "Runtime/GCNTypes.hpp" #include "Runtime/Character/CPOINode.hpp" namespace metaforce { class IAnimSourceInfo; class CInt32POINode : public CPOINode { s32 x38_val; std::string x3c_locatorName; public: CInt32POINode(); CInt32POINode(std::string_view, EPOIType, const CCharAnimTime&, s32, bool, float, s32, s32, s32, std::string_view); explicit CInt32POINode(CInputStream& in); s32 GetValue() const { return x38_val; } std::string_view GetLocatorName() const { return x3c_locatorName; } static CInt32POINode CopyNodeMinusStartTime(const CInt32POINode& node, const CCharAnimTime& startTime); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CLayoutDescription.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include namespace metaforce { class CCharLayoutInfo; class CLayoutDescription { public: class CScaledLayoutDescription { TLockedToken x0_layoutToken; float xc_scale = 0.0f; std::optional x10_scaleVec; public: const TLockedToken& ScaledLayout() const { return x0_layoutToken; } float GlobalScale() const { return xc_scale; } const std::optional& GetScaleVec() const { return x10_scaleVec; } }; private: TLockedToken x0_layoutToken; std::optional xc_scaled; public: explicit CLayoutDescription(const TLockedToken& token) : x0_layoutToken(token) {} const std::optional& GetScaledLayoutDescription() const { return xc_scaled; } const TLockedToken& GetCharLayoutInfo() const { return x0_layoutToken; } bool UsesScale() const { return bool(xc_scaled); } const TLockedToken& ScaledLayout() const { if (UsesScale()) return xc_scaled->ScaledLayout(); return x0_layoutToken; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMakeLists.txt ================================================ set(CHARACTER_SOURCES CharacterCommon.hpp CharacterCommon.cpp CAssetFactory.hpp CAssetFactory.cpp CCharacterFactory.hpp CCharacterFactory.cpp CModelData.hpp CModelData.cpp CAnimData.hpp CAnimData.cpp CCharAnimTime.hpp CCharAnimTime.cpp IMetaAnim.hpp IMetaAnim.cpp IMetaTrans.hpp IVaryingAnimationTimeScale.hpp CAnimationDatabase.hpp CAnimationDatabaseGame.hpp CAnimationDatabaseGame.cpp CTransitionDatabase.hpp CTransitionDatabaseGame.hpp CTransitionDatabaseGame.cpp CHierarchyPoseBuilder.hpp CHierarchyPoseBuilder.cpp CPoseAsTransforms.hpp CPoseAsTransforms.cpp CCharLayoutInfo.hpp CCharLayoutInfo.cpp CLayoutDescription.hpp CSegIdList.hpp CSegIdList.cpp CSegId.hpp TSegIdMap.hpp CIkChain.hpp CIkChain.cpp CSkinRules.hpp CSkinRules.cpp CAnimCharacterSet.hpp CAnimCharacterSet.cpp CAnimationSet.hpp CAnimationSet.cpp CCharacterSet.hpp CCharacterSet.cpp CCharacterInfo.hpp CCharacterInfo.cpp CPASDatabase.hpp CPASDatabase.cpp CPASAnimState.hpp CPASAnimState.cpp CPASParmInfo.hpp CPASParmInfo.cpp CPASAnimInfo.hpp CPASAnimInfo.cpp CPASAnimParm.hpp CPASAnimParmData.hpp CPASAnimParmData.cpp CEffectComponent.hpp CEffectComponent.cpp CAnimation.hpp CAnimation.cpp CAnimationManager.hpp CAnimationManager.cpp CTransition.hpp CTransition.cpp CTransitionManager.hpp CTransitionManager.cpp CMetaAnimFactory.hpp CMetaAnimFactory.cpp CMetaAnimPlay.hpp CMetaAnimPlay.cpp CMetaAnimBlend.hpp CMetaAnimBlend.cpp CMetaAnimPhaseBlend.hpp CMetaAnimPhaseBlend.cpp CMetaAnimRandom.hpp CMetaAnimRandom.cpp CMetaAnimSequence.hpp CMetaAnimSequence.cpp CMetaTransFactory.hpp CMetaTransFactory.cpp CMetaTransMetaAnim.hpp CMetaTransMetaAnim.cpp CMetaTransTrans.hpp CMetaTransTrans.cpp CMetaTransPhaseTrans.hpp CMetaTransPhaseTrans.cpp CMetaTransSnap.hpp CMetaTransSnap.cpp CAnimTreeLoopIn.hpp CAnimTreeLoopIn.cpp CAnimTreeSequence.hpp CAnimTreeSequence.cpp CSequenceHelper.hpp CSequenceHelper.cpp CAnimTreeAnimReaderContainer.hpp CAnimTreeAnimReaderContainer.cpp CTreeUtils.hpp CTreeUtils.cpp CAnimTreeBlend.hpp CAnimTreeBlend.cpp CAnimTreeNode.hpp CAnimTreeNode.cpp CAnimTreeTimeScale.hpp CAnimTreeTimeScale.cpp CAnimTreeTransition.hpp CAnimTreeTransition.cpp CAnimTreeTweenBase.hpp CAnimTreeTweenBase.cpp CAnimTreeSingleChild.hpp CAnimTreeSingleChild.cpp CAnimTreeDoubleChild.hpp CAnimTreeDoubleChild.cpp CAnimPlaybackParms.hpp IAnimReader.hpp IAnimReader.cpp CPrimitive.hpp CPrimitive.cpp CHalfTransition.hpp CHalfTransition.cpp CTimeScaleFunctions.hpp CTimeScaleFunctions.cpp CParticleData.hpp CParticleData.cpp CParticleDatabase.hpp CParticleDatabase.cpp CParticleGenInfo.hpp CParticleGenInfo.cpp CAnimPOIData.hpp CAnimPOIData.cpp CPOINode.hpp CPOINode.cpp CBoolPOINode.hpp CBoolPOINode.cpp CInt32POINode.hpp CInt32POINode.cpp CSoundPOINode.hpp CSoundPOINode.cpp CParticlePOINode.hpp CParticlePOINode.cpp CAnimSourceReader.hpp CAnimSourceReader.cpp CAnimSource.hpp CAnimSource.cpp CFBStreamedAnimReader.hpp CFBStreamedAnimReader.cpp CFBStreamedCompression.hpp CFBStreamedCompression.cpp CAllFormatsAnimSource.hpp CAllFormatsAnimSource.cpp CSegStatementSet.hpp CSegStatementSet.cpp CAnimPerSegmentData.hpp CAdditiveAnimPlayback.hpp CAdditiveAnimPlayback.cpp CActorLights.hpp CActorLights.cpp CAnimSysContext.hpp CBodyState.hpp CBodyState.cpp CAdditiveBodyState.hpp CAdditiveBodyState.cpp CBodyStateCmdMgr.hpp CBodyStateCmdMgr.cpp CBodyController.hpp CBodyController.cpp CGroundMovement.hpp CGroundMovement.cpp CSteeringBehaviors.hpp CSteeringBehaviors.cpp CBodyStateInfo.hpp CBodyStateInfo.cpp CBoneTracking.hpp CBoneTracking.cpp CRagDoll.hpp CRagDoll.cpp) runtime_add_list(Character CHARACTER_SOURCES) ================================================ FILE: Runtime/Character/CMetaAnimBlend.cpp ================================================ #include "Runtime/Character/CMetaAnimBlend.hpp" #include "Runtime/Character/CAnimTreeBlend.hpp" #include "Runtime/Character/CMetaAnimFactory.hpp" namespace metaforce { CMetaAnimBlend::CMetaAnimBlend(CInputStream& in) { x4_animA = CMetaAnimFactory::CreateMetaAnim(in); x8_animB = CMetaAnimFactory::CreateMetaAnim(in); xc_blend = in.ReadFloat(); x10_ = in.ReadBool(); } void CMetaAnimBlend::GetUniquePrimitives(std::set& primsOut) const { x4_animA->GetUniquePrimitives(primsOut); x4_animA->GetUniquePrimitives(primsOut); } std::shared_ptr CMetaAnimBlend::VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const { CMetaAnimTreeBuildOrders oa = CMetaAnimTreeBuildOrders::NoSpecialOrders(); CMetaAnimTreeBuildOrders ob = orders.x0_recursiveAdvance ? CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance) : CMetaAnimTreeBuildOrders::NoSpecialOrders(); auto a = x4_animA->GetAnimationTree(animSys, oa); auto b = x8_animB->GetAnimationTree(animSys, ob); return std::make_shared(x10_, a, b, xc_blend, CAnimTreeBlend::CreatePrimitiveName(a, b, xc_blend)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimBlend.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { class CMetaAnimBlend : public IMetaAnim { std::shared_ptr x4_animA; std::shared_ptr x8_animB; float xc_blend; bool x10_; public: explicit CMetaAnimBlend(CInputStream& in); EMetaAnimType GetType() const override { return EMetaAnimType::Blend; } void GetUniquePrimitives(std::set& primsOut) const override; std::shared_ptr VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimFactory.cpp ================================================ #include "Runtime/Character/CMetaAnimFactory.hpp" #include "Runtime/Character/CMetaAnimBlend.hpp" #include "Runtime/Character/CMetaAnimPhaseBlend.hpp" #include "Runtime/Character/CMetaAnimPlay.hpp" #include "Runtime/Character/CMetaAnimRandom.hpp" #include "Runtime/Character/CMetaAnimSequence.hpp" namespace metaforce { std::shared_ptr CMetaAnimFactory::CreateMetaAnim(CInputStream& in) { EMetaAnimType type = EMetaAnimType(in.ReadLong()); switch (type) { case EMetaAnimType::Play: return std::make_shared(in); case EMetaAnimType::Blend: return std::make_shared(in); case EMetaAnimType::PhaseBlend: return std::make_shared(in); case EMetaAnimType::Random: return std::make_shared(in); case EMetaAnimType::Sequence: return std::make_shared(in); default: break; } return {}; } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimFactory.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { class CMetaAnimFactory { public: static std::shared_ptr CreateMetaAnim(CInputStream& in); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimPhaseBlend.cpp ================================================ #include "Runtime/Character/CMetaAnimPhaseBlend.hpp" #include "Runtime/Character/CAnimTreeBlend.hpp" #include "Runtime/Character/CAnimTreeTimeScale.hpp" #include "Runtime/Character/CMetaAnimFactory.hpp" namespace metaforce { CMetaAnimPhaseBlend::CMetaAnimPhaseBlend(CInputStream& in) { x4_animA = CMetaAnimFactory::CreateMetaAnim(in); x8_animB = CMetaAnimFactory::CreateMetaAnim(in); xc_blend = in.ReadFloat(); x10_ = in.ReadBool(); } void CMetaAnimPhaseBlend::GetUniquePrimitives(std::set& primsOut) const { x4_animA->GetUniquePrimitives(primsOut); x8_animB->GetUniquePrimitives(primsOut); } std::shared_ptr CMetaAnimPhaseBlend::VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const { if (orders.x0_recursiveAdvance) return GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance)); auto a = x4_animA->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders()); auto b = x8_animB->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders()); auto da = a->GetContributionOfHighestInfluence().GetSteadyStateAnimInfo().GetDuration(); auto db = b->GetContributionOfHighestInfluence().GetSteadyStateAnimInfo().GetDuration(); auto dblend = da + (db - da) * xc_blend; float fa = da / dblend; float fb = db / dblend; auto tsa = std::make_shared( a, fa, CAnimTreeTimeScale::CreatePrimitiveName(a, fa, CCharAnimTime::Infinity(), -1.f)); auto tsb = std::make_shared( b, fb, CAnimTreeTimeScale::CreatePrimitiveName(b, fb, CCharAnimTime::Infinity(), -1.f)); return std::make_shared(x10_, tsa, tsb, xc_blend, CAnimTreeBlend::CreatePrimitiveName(tsa, tsb, xc_blend)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimPhaseBlend.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { class CMetaAnimPhaseBlend : public IMetaAnim { std::shared_ptr x4_animA; std::shared_ptr x8_animB; float xc_blend; bool x10_; public: explicit CMetaAnimPhaseBlend(CInputStream& in); EMetaAnimType GetType() const override { return EMetaAnimType::PhaseBlend; } void GetUniquePrimitives(std::set& primsOut) const override; std::shared_ptr VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimPlay.cpp ================================================ #include "Runtime/Character/CMetaAnimPlay.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/Character/CAllFormatsAnimSource.hpp" #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CAnimTreeAnimReaderContainer.hpp" namespace metaforce { CMetaAnimPlay::CMetaAnimPlay(CInputStream& in) : x4_primitive(in), x1c_startTime(in) {} void CMetaAnimPlay::GetUniquePrimitives(std::set& primsOut) const { primsOut.insert(x4_primitive); } std::shared_ptr CMetaAnimPlay::VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const { if (orders.x0_recursiveAdvance) return GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance)); TLockedToken prim = animSys.xc_store.GetObj(SObjectTag{FOURCC('ANIM'), x4_primitive.GetAnimResId()}); return std::make_shared( x4_primitive.GetName(), CAllFormatsAnimSource::GetNewReader(prim, x1c_startTime), x4_primitive.GetAnimDbIdx()); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimPlay.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CPrimitive.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { class CMetaAnimPlay : public IMetaAnim { CPrimitive x4_primitive; CCharAnimTime x1c_startTime; public: explicit CMetaAnimPlay(CInputStream& in); EMetaAnimType GetType() const override { return EMetaAnimType::Play; } void GetUniquePrimitives(std::set& primsOut) const override; std::shared_ptr VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimRandom.cpp ================================================ #include "Runtime/Character/CMetaAnimRandom.hpp" #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CMetaAnimFactory.hpp" namespace metaforce { CMetaAnimRandom::RandomData CMetaAnimRandom::CreateRandomData(CInputStream& in) { CMetaAnimRandom::RandomData ret; u32 randCount = in.ReadLong(); ret.reserve(randCount); for (u32 i = 0; i < randCount; ++i) { std::shared_ptr metaAnim = CMetaAnimFactory::CreateMetaAnim(in); ret.emplace_back(std::move(metaAnim), in.ReadLong()); } return ret; } CMetaAnimRandom::CMetaAnimRandom(CInputStream& in) : x4_randomData(CreateRandomData(in)) {} void CMetaAnimRandom::GetUniquePrimitives(std::set& primsOut) const { for (const auto& pair : x4_randomData) pair.first->GetUniquePrimitives(primsOut); } std::shared_ptr CMetaAnimRandom::VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const { const u32 r = animSys.x8_random->Range(1, 100); const std::pair, u32>* useRd = nullptr; for (const auto& rd : x4_randomData) { useRd = &rd; if (r <= rd.second) { break; } } return useRd->first->GetAnimationTree(animSys, orders); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimRandom.hpp ================================================ #pragma once #include #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { class CMetaAnimRandom : public IMetaAnim { using RandomData = std::vector, u32>>; RandomData x4_randomData; static RandomData CreateRandomData(CInputStream& in); public: explicit CMetaAnimRandom(CInputStream& in); EMetaAnimType GetType() const override { return EMetaAnimType::Random; } void GetUniquePrimitives(std::set& primsOut) const override; std::shared_ptr VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimSequence.cpp ================================================ #include "Runtime/Character/CMetaAnimSequence.hpp" #include "Runtime/Character/CAnimTreeSequence.hpp" #include "Runtime/Character/CMetaAnimFactory.hpp" namespace metaforce { std::vector> CMetaAnimSequence::CreateSequence(CInputStream& in) { std::vector> ret; u32 seqCount = in.ReadLong(); ret.reserve(seqCount); for (u32 i = 0; i < seqCount; ++i) ret.push_back(CMetaAnimFactory::CreateMetaAnim(in)); return ret; } CMetaAnimSequence::CMetaAnimSequence(CInputStream& in) : x4_sequence(CreateSequence(in)) {} void CMetaAnimSequence::GetUniquePrimitives(std::set& primsOut) const { for (const std::shared_ptr& anim : x4_sequence) anim->GetUniquePrimitives(primsOut); } std::shared_ptr CMetaAnimSequence::VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const { if (orders.x0_recursiveAdvance) return GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::PreAdvanceForAll(*orders.x0_recursiveAdvance)); #if 0 /* Originally used to generate name string */ std::vector anims; anims.reserve(anims.size()); for (const std::shared_ptr& anim : x4_sequence) { std::shared_ptr chNode = anim->GetAnimationTree(animSys, orders); anims.emplace_back(chNode->GetName()); } #endif return std::make_shared(x4_sequence, animSys, ""); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaAnimSequence.hpp ================================================ #pragma once #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { class CMetaAnimSequence : public IMetaAnim { std::vector> x4_sequence; std::vector> CreateSequence(CInputStream& in); public: explicit CMetaAnimSequence(CInputStream& in); EMetaAnimType GetType() const override { return EMetaAnimType::Sequence; } void GetUniquePrimitives(std::set& primsOut) const override; std::shared_ptr VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransFactory.cpp ================================================ #include "Runtime/Character/CMetaTransFactory.hpp" #include "Runtime/Character/CMetaTransMetaAnim.hpp" #include "Runtime/Character/CMetaTransPhaseTrans.hpp" #include "Runtime/Character/CMetaTransSnap.hpp" #include "Runtime/Character/CMetaTransTrans.hpp" namespace metaforce { std::shared_ptr CMetaTransFactory::CreateMetaTrans(CInputStream& in) { EMetaTransType type = EMetaTransType(in.ReadLong()); switch (type) { case EMetaTransType::MetaAnim: return std::make_shared(in); case EMetaTransType::Trans: return std::make_shared(in); case EMetaTransType::PhaseTrans: return std::make_shared(in); case EMetaTransType::Snap: return std::make_shared(); default: break; } return {}; } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransFactory.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaTrans.hpp" namespace metaforce { class CMetaTransFactory { public: static std::shared_ptr CreateMetaTrans(CInputStream& in); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransMetaAnim.cpp ================================================ #include "Runtime/Character/CMetaTransMetaAnim.hpp" #include "Runtime/Character/CAnimTreeLoopIn.hpp" #include "Runtime/Character/CMetaAnimFactory.hpp" namespace metaforce { CMetaTransMetaAnim::CMetaTransMetaAnim(CInputStream& in) : x4_metaAnim(CMetaAnimFactory::CreateMetaAnim(in)) {} std::shared_ptr CMetaTransMetaAnim::VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const { std::shared_ptr animNode = x4_metaAnim->GetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders()); return std::make_shared(a, b, animNode, animSys, CAnimTreeLoopIn::CreatePrimitiveName(a, b, animNode)); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransMetaAnim.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaAnim.hpp" #include "Runtime/Character/IMetaTrans.hpp" namespace metaforce { class CMetaTransMetaAnim : public IMetaTrans { std::shared_ptr x4_metaAnim; public: explicit CMetaTransMetaAnim(CInputStream& in); EMetaTransType GetType() const override { return EMetaTransType::MetaAnim; } std::shared_ptr VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransPhaseTrans.cpp ================================================ #include "Runtime/Character/CMetaTransPhaseTrans.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CAnimTreeTimeScale.hpp" #include "Runtime/Character/CAnimTreeTransition.hpp" #include "Runtime/Character/CTimeScaleFunctions.hpp" namespace metaforce { CMetaTransPhaseTrans::CMetaTransPhaseTrans(CInputStream& in) { x4_transDur = CCharAnimTime(in); xc_ = in.ReadBool(); xd_runA = in.ReadBool(); x10_flags = in.ReadLong(); } std::shared_ptr CMetaTransPhaseTrans::VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const { std::shared_ptr nA = a.lock(); CAnimTreeEffectiveContribution cA = nA->GetContributionOfHighestInfluence(); std::shared_ptr nB = b.lock(); CAnimTreeEffectiveContribution cB = nB->GetContributionOfHighestInfluence(); float y2A = cA.GetSteadyStateAnimInfo().GetDuration() / cB.GetSteadyStateAnimInfo().GetDuration(); float y1B = cB.GetSteadyStateAnimInfo().GetDuration() / cA.GetSteadyStateAnimInfo().GetDuration(); nB->VSetPhase(zeus::clamp(0.f, 1.f - cA.GetTimeRemaining() / cA.GetSteadyStateAnimInfo().GetDuration(), 1.f)); auto tsA = std::make_shared( a, std::make_unique(CCharAnimTime{}, 1.f, x4_transDur, y2A), x4_transDur, CAnimTreeTimeScale::CreatePrimitiveName(a, 1.f, x4_transDur, y2A)); auto tsB = std::make_shared( b, std::make_unique(CCharAnimTime{}, y1B, x4_transDur, 1.f), x4_transDur, CAnimTreeTimeScale::CreatePrimitiveName(b, y1B, x4_transDur, 1.f)); return std::make_shared( xc_, tsA, tsB, x4_transDur, xd_runA, x10_flags, CAnimTreeTransition::CreatePrimitiveName(tsA, tsB, x4_transDur.GetSeconds())); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransPhaseTrans.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CCharAnimTime.hpp" #include "Runtime/Character/IMetaTrans.hpp" namespace metaforce { class CMetaTransPhaseTrans : public IMetaTrans { CCharAnimTime x4_transDur; bool xc_; bool xd_runA; u32 x10_flags; public: explicit CMetaTransPhaseTrans(CInputStream& in); EMetaTransType GetType() const override { return EMetaTransType::PhaseTrans; } std::shared_ptr VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransSnap.cpp ================================================ #include "Runtime/Character/CMetaTransSnap.hpp" namespace metaforce { std::shared_ptr CMetaTransSnap::VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const { return b.lock(); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransSnap.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/IMetaTrans.hpp" namespace metaforce { class CMetaTransSnap : public IMetaTrans { public: EMetaTransType GetType() const override { return EMetaTransType::Snap; } std::shared_ptr VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransTrans.cpp ================================================ #include "Runtime/Character/CMetaTransTrans.hpp" #include "Runtime/Character/CAnimTreeTransition.hpp" namespace metaforce { CMetaTransTrans::CMetaTransTrans(CInputStream& in) { x4_transDur = in.Get(); xc_ = in.ReadBool(); xd_runA = in.ReadBool(); x10_flags = in.ReadLong(); } std::shared_ptr CMetaTransTrans::VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const { return std::make_shared( xc_, a, b, x4_transDur, xd_runA, x10_flags, CAnimTreeTransition::CreatePrimitiveName(a, b, x4_transDur.GetSeconds())); } } // namespace metaforce ================================================ FILE: Runtime/Character/CMetaTransTrans.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CCharAnimTime.hpp" #include "Runtime/Character/IMetaTrans.hpp" namespace metaforce { class CMetaTransTrans : public IMetaTrans { CCharAnimTime x4_transDur; bool xc_; bool xd_runA; u32 x10_flags; public: explicit CMetaTransTrans(CInputStream& in); EMetaTransType GetType() const override { return EMetaTransType::Trans; } std::shared_ptr VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CModelData.cpp ================================================ #include "Runtime/Character/CModelData.hpp" #include "Runtime/CPlayerState.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Character/CActorLights.hpp" #include "Runtime/Character/CAdditiveAnimPlayback.hpp" #include "Runtime/Character/CAnimData.hpp" #include "Runtime/Character/CAssetFactory.hpp" #include "Runtime/Character/CCharacterFactory.hpp" #include "Runtime/Character/IAnimReader.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" #include "Runtime/Graphics/CVertexMorphEffect.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Logging.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { CModelData::~CModelData() = default; CModelData::CModelData() {} CModelData CModelData::CModelDataNull() { return CModelData(); } CModelData::CModelData(const CStaticRes& res) : x0_scale(res.GetScale()) { x1c_normalModel = g_SimplePool->GetObj({SBIG('CMDL'), res.GetId()}); if (!x1c_normalModel) spdlog::fatal("unable to find CMDL {}", res.GetId()); } CModelData::CModelData(const CAnimRes& res) : x0_scale(res.GetScale()) { TToken factory = g_CharFactoryBuilder->GetFactory(res); x10_animData = factory->CreateCharacter(res.GetCharacterNodeId(), res.CanLoop(), factory, res.GetDefaultAnim()); } SAdvancementDeltas CModelData::GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const { if (x10_animData) return x10_animData->GetAdvancementDeltas(a, b); else return {}; } void CModelData::Render(const CStateManager& stateMgr, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags) { Render(GetRenderingModel(stateMgr), xf, lights, drawFlags); } bool CModelData::IsLoaded(int shaderIdx) { if (x10_animData) { if (!x10_animData->xd8_modelData->GetModel()->IsLoaded(shaderIdx)) return false; if (auto* model = x10_animData->xf4_xrayModel.get()) if (!model->GetModel()->IsLoaded(shaderIdx)) return false; if (auto* model = x10_animData->xf8_infraModel.get()) if (!model->GetModel()->IsLoaded(shaderIdx)) return false; } if (auto* model = x1c_normalModel.GetObj()) if (!model->IsLoaded(shaderIdx)) return false; if (auto* model = x2c_xrayModel.GetObj()) if (!model->IsLoaded(shaderIdx)) return false; if (auto* model = x3c_infraModel.GetObj()) if (!model->IsLoaded(shaderIdx)) return false; return true; } u32 CModelData::GetNumMaterialSets() const { if (x10_animData) return x10_animData->GetModelData()->GetModel()->GetNumMaterialSets(); if (x1c_normalModel) return x1c_normalModel->GetNumMaterialSets(); return 1; } CModelData::EWhichModel CModelData::GetRenderingModel(const CStateManager& stateMgr) { switch (stateMgr.GetPlayerState()->GetActiveVisor(stateMgr)) { case CPlayerState::EPlayerVisor::XRay: return CModelData::EWhichModel::XRay; case CPlayerState::EPlayerVisor::Thermal: if (stateMgr.GetThermalDrawFlag() == EThermalDrawFlag::Cold) return CModelData::EWhichModel::Thermal; return CModelData::EWhichModel::ThermalHot; default: return CModelData::EWhichModel::Normal; } } CSkinnedModel& CModelData::PickAnimatedModel(EWhichModel which) const { CSkinnedModel* ret = nullptr; switch (which) { case EWhichModel::XRay: ret = x10_animData->xf4_xrayModel.get(); break; case EWhichModel::Thermal: case EWhichModel::ThermalHot: ret = x10_animData->xf8_infraModel.get(); break; default: break; } if (ret) return *ret; return *x10_animData->xd8_modelData.GetObj(); } TLockedToken& CModelData::PickStaticModel(EWhichModel which) { switch (which) { case EWhichModel::XRay: if (x2c_xrayModel) { return x2c_xrayModel; } break; case EWhichModel::Thermal: case EWhichModel::ThermalHot: if (x3c_infraModel) { return x3c_infraModel; } break; default: break; } return x1c_normalModel; } void CModelData::SetXRayModel(const std::pair& modelSkin) { if (modelSkin.first.IsValid()) { if (g_ResFactory->GetResourceTypeById(modelSkin.first) == SBIG('CMDL')) { if (x10_animData && modelSkin.second.IsValid() && g_ResFactory->GetResourceTypeById(modelSkin.second) == SBIG('CSKR')) { x10_animData->SetXRayModel(g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first}), g_SimplePool->GetObj({SBIG('CSKR'), modelSkin.second})); } else { x2c_xrayModel = g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first}); if (!x2c_xrayModel) spdlog::fatal("unable to find CMDL {}", modelSkin.first); } } } } void CModelData::SetInfraModel(const std::pair& modelSkin) { if (modelSkin.first.IsValid()) { if (g_ResFactory->GetResourceTypeById(modelSkin.first) == SBIG('CMDL')) { if (x10_animData && modelSkin.second.IsValid() && g_ResFactory->GetResourceTypeById(modelSkin.second) == SBIG('CSKR')) { x10_animData->SetInfraModel(g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first}), g_SimplePool->GetObj({SBIG('CSKR'), modelSkin.second})); } else { x3c_infraModel = g_SimplePool->GetObj({SBIG('CMDL'), modelSkin.first}); if (!x3c_infraModel) spdlog::fatal("unable to find CMDL {}", modelSkin.first); } } } } bool CModelData::IsDefinitelyOpaque(EWhichModel which) const { if (x10_animData) { CSkinnedModel& model = PickAnimatedModel(which); return model.GetModel()->IsOpaque(); } else { const auto& model = PickStaticModel(which); return model->IsOpaque(); } } bool CModelData::GetIsLoop() const { if (!x10_animData) return false; return x10_animData->GetIsLoop(); } float CModelData::GetAnimationDuration(int idx) const { if (!x10_animData) return 0.f; return x10_animData->GetAnimationDuration(idx); } void CModelData::EnableLooping(bool enable) { if (!x10_animData) return; x10_animData->EnableLooping(enable); } void CModelData::AdvanceParticles(const zeus::CTransform& xf, float dt, CStateManager& stateMgr) { if (!x10_animData) return; x10_animData->AdvanceParticles(xf, dt, x0_scale, stateMgr); } zeus::CAABox CModelData::GetBounds() const { if (x10_animData) { return x10_animData->GetBoundingBox(zeus::CTransform::Scale(x0_scale)); } else { const zeus::CAABox& aabb = x1c_normalModel->GetAABB(); return zeus::CAABox(aabb.min * x0_scale, aabb.max * x0_scale); } } zeus::CAABox CModelData::GetBounds(const zeus::CTransform& xf) const { zeus::CTransform xf2 = xf * zeus::CTransform::Scale(x0_scale); if (x10_animData) return x10_animData->GetBoundingBox(xf2); else return x1c_normalModel->GetAABB().getTransformedAABox(xf2); } zeus::CTransform CModelData::GetScaledLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const { zeus::CTransform xf = GetLocatorTransformDynamic(name, time); xf.origin *= x0_scale; return xf; } zeus::CTransform CModelData::GetScaledLocatorTransform(std::string_view name) const { zeus::CTransform xf = GetLocatorTransform(name); xf.origin *= x0_scale; return xf; } zeus::CTransform CModelData::GetLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const { if (x10_animData) return x10_animData->GetLocatorTransform(name, time); else return {}; } zeus::CTransform CModelData::GetLocatorTransform(std::string_view name) const { if (x10_animData) return x10_animData->GetLocatorTransform(name, nullptr); else return {}; } SAdvancementDeltas CModelData::AdvanceAnimationIgnoreParticles(float dt, CRandom16& rand, bool advTree) { if (x10_animData) return x10_animData->AdvanceIgnoreParticles(dt, rand, advTree); else return {}; } SAdvancementDeltas CModelData::AdvanceAnimation(float dt, CStateManager& stateMgr, TAreaId aid, bool advTree) { if (x10_animData) return x10_animData->Advance(dt, x0_scale, stateMgr, aid, advTree); else return {}; } bool CModelData::IsAnimating() const { if (!x10_animData) return false; return x10_animData->IsAnimating(); } bool CModelData::IsInFrustum(const zeus::CTransform& xf, const zeus::CFrustum& frustum) const { if (!x10_animData && !x1c_normalModel) return true; return frustum.aabbFrustumTest(GetBounds(xf)); } void CModelData::RenderParticles(const zeus::CFrustum& frustum) const { if (x10_animData) x10_animData->RenderAuxiliary(frustum); } void CModelData::Touch(EWhichModel which, int shaderIdx) { if (x10_animData) x10_animData->Touch(PickAnimatedModel(which), shaderIdx); else PickStaticModel(which)->Touch(shaderIdx); } void CModelData::Touch(const CStateManager& stateMgr, int shaderIdx) { Touch(GetRenderingModel(stateMgr), shaderIdx); } void CModelData::RenderThermal(const zeus::CTransform& xf, const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags) { CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); CGraphics::DisableAllLights(); if (x10_animData) { CSkinnedModel& model = PickAnimatedModel(EWhichModel::ThermalHot); x10_animData->SetupRender(model, nullptr, {}); ThermalDraw(model, mulColor, addColor, flags); } else { auto& model = PickStaticModel(EWhichModel::ThermalHot); g_Renderer->DrawThermalModel(*model, mulColor, addColor, {}, {}, flags); } } void CModelData::RenderUnsortedParts(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags) { if ((x14_25_sortThermal && which == EWhichModel::ThermalHot) || x10_animData || !x1c_normalModel || drawFlags.x0_blendMode > 4) { x14_24_renderSorted = false; return; } CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); if (lights != nullptr) { lights->ActivateLights(); } else { CGraphics::DisableAllLights(); g_Renderer->SetAmbientColor(x18_ambientColor); } PickStaticModel(which)->DrawUnsortedParts(drawFlags); g_Renderer->SetAmbientColor(zeus::skWhite); CGraphics::DisableAllLights(); x14_24_renderSorted = true; } void CModelData::Render(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags) { if (x14_25_sortThermal && which == EWhichModel::ThermalHot) { zeus::CColor mul(drawFlags.x4_color.a(), drawFlags.x4_color.a()); RenderThermal(xf, mul, {0.f, 0.f, 0.f, 0.25f}, drawFlags); return; } CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); if (lights != nullptr) { lights->ActivateLights(); } else { CGraphics::DisableAllLights(); g_Renderer->SetAmbientColor(x18_ambientColor); } if (x10_animData) { x10_animData->Render(PickAnimatedModel(which), drawFlags, nullptr, {}); } else { // TODO supposed to be optional_object? if (x1c_normalModel) { auto& model = PickStaticModel(which); if (x14_24_renderSorted) { model->DrawSortedParts(drawFlags); } else { model->Draw(drawFlags); } } } g_Renderer->SetAmbientColor(zeus::skWhite); CGraphics::DisableAllLights(); x14_24_renderSorted = false; } void CModelData::FlatDraw(EWhichModel which, const zeus::CTransform& xf, bool unsortedOnly, const CModelFlags& flags) { g_Renderer->SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); CGraphics::DisableAllLights(); if (!x10_animData) { g_Renderer->DrawModelFlat(*PickStaticModel(which), flags, unsortedOnly, {}, {}); } else { auto model = PickAnimatedModel(which); x10_animData->SetupRender(model, nullptr, {}); model.DoDrawCallback([=](TConstVectorRef positions, TConstVectorRef normals) { auto m = model.GetModel(); g_Renderer->DrawModelFlat(*m, flags, unsortedOnly, positions, normals); }); } } void CModelData::MultiLightingDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights, const zeus::CColor& alphaColor, const zeus::CColor& additiveColor) { CModel* model = nullptr; const auto callback = [&](TConstVectorRef positions, TConstVectorRef normals) { CGraphics::DisableAllLights(); constexpr CModelFlags flags1{5, 0, 3, zeus::CColor{1.f, 0.f}}; const CModelFlags flags2{5, 0, 1, alphaColor}; const CModelFlags flags3{7, 0, 1, additiveColor}; if (positions.empty()) { model->Draw(flags1); if (lights != nullptr) { lights->ActivateLights(); } model->Draw(flags2); model->Draw(flags3); } else { model->Draw(positions, normals, flags1); if (lights != nullptr) { lights->ActivateLights(); } model->Draw(positions, normals, flags2); model->Draw(positions, normals, flags3); } }; CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); if (x10_animData) { auto& skinnedModel = PickAnimatedModel(which); x10_animData->SetupRender(skinnedModel, nullptr, {}); model = skinnedModel.GetModel().GetObj(); skinnedModel.DoDrawCallback(callback); } else { model = PickStaticModel(which).GetObj(); callback({}, {}); } } void CModelData::MultiPassDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags* flags, u32 count) { CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); if (lights == nullptr) { CGraphics::DisableAllLights(); g_Renderer->SetAmbientColor(x18_ambientColor); } else { lights->ActivateLights(); } if (x10_animData) { auto& skinnedModel = PickAnimatedModel(which); x10_animData->SetupRender(skinnedModel, nullptr, {}); auto& model = *skinnedModel.GetModel(); skinnedModel.DoDrawCallback([&](auto positions, auto normals) { for (int i = 0; i < count; ++i) { model.Draw(positions, normals, flags[i]); } }); } else { auto& model = *PickStaticModel(which); for (int i = 0; i < count; ++i) { model.Draw(flags[i]); } } } void CModelData::DisintegrateDraw(const CStateManager& mgr, const zeus::CTransform& xf, CTexture& tex, const zeus::CColor& addColor, float t) { DisintegrateDraw(GetRenderingModel(mgr), xf, tex, addColor, t); } void CModelData::DisintegrateDraw(EWhichModel which, const zeus::CTransform& xf, CTexture& tex, const zeus::CColor& addColor, float t) { zeus::CTransform scaledXf = xf * zeus::CTransform::Scale(x0_scale); CGraphics::SetModelMatrix(scaledXf); CGraphics::DisableAllLights(); const auto aabb = GetBounds(scaledXf); if (x10_animData) { auto& model = PickAnimatedModel(which); x10_animData->SetupRender(model, nullptr, {}); model.DoDrawCallback([&](auto positions, auto normals) { g_Renderer->DrawModelDisintegrate(*model.GetModel(), tex, addColor, positions, normals, t); }); } else { g_Renderer->DrawModelDisintegrate(*PickStaticModel(which), tex, addColor, {}, {}, t); } } void CModelData::ThermalDraw(CSkinnedModel& model, const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags) { model.DoDrawCallback([&](auto positions, auto normals) { g_Renderer->DrawThermalModel(*model.GetModel(), mulColor, addColor, positions, normals, flags); }); } void CModelData::ThermalDraw(CSkinnedModel& model, TConstVectorRef positions, TConstVectorRef normals, const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags) { g_Renderer->DrawThermalModel(*model.GetModel(), mulColor, addColor, positions, normals, flags); } } // namespace metaforce ================================================ FILE: Runtime/Character/CModelData.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CAnimData.hpp" #include "Runtime/Graphics/CModel.hpp" #include #include #include namespace metaforce { class CActorLights; class CAnimData; class CCharAnimTime; class CModel; class CRandom16; class CSkinnedModel; class CStateManager; struct CModelFlags; struct SAdvancementDeltas; class CStaticRes { CAssetId x0_cmdlId; zeus::CVector3f x4_scale; public: constexpr CStaticRes() = default; CStaticRes(CAssetId id, const zeus::CVector3f& scale) : x0_cmdlId(id), x4_scale(scale) {} CAssetId GetId() const { return x0_cmdlId; } const zeus::CVector3f& GetScale() const { return x4_scale; } explicit operator bool() const { return x0_cmdlId.IsValid(); } }; class CAnimRes { CAssetId x0_ancsId; s32 x4_charIdx = -1; zeus::CVector3f x8_scale; bool x14_canLoop = false; /* NOTE: x18_bodyType - Removed in retail */ s32 x18_defaultAnim = -1; /* NOTE: used to be x1c in demo */ public: CAnimRes() = default; CAnimRes(CAssetId ancs, s32 charIdx, const zeus::CVector3f& scale, const s32 defaultAnim, bool loop) : x0_ancsId(ancs), x4_charIdx(charIdx), x8_scale(scale), x14_canLoop(loop), x18_defaultAnim(defaultAnim) {} CAssetId GetId() const { return x0_ancsId; } s32 GetCharacterNodeId() const { return x4_charIdx; } void SetCharacterNodeId(s32 id) { x4_charIdx = id; } const zeus::CVector3f& GetScale() const { return x8_scale; } bool CanLoop() const { return x14_canLoop; } void SetCanLoop(bool loop) { x14_canLoop = loop; } s32 GetDefaultAnim() const { return x18_defaultAnim; } void SetDefaultAnim(s32 anim) { x18_defaultAnim = anim; } }; class CModelData { friend class CActor; zeus::CVector3f x0_scale; std::unique_ptr x10_animData; bool x14_24_renderSorted : 1 = false; bool x14_25_sortThermal : 1 = false; zeus::CColor x18_ambientColor; // were rstl::optional_object> TLockedToken x1c_normalModel; TLockedToken x2c_xrayModel; TLockedToken x3c_infraModel; public: enum class EWhichModel { Normal, XRay, Thermal, ThermalHot }; void SetSortThermal(bool sort) { x14_25_sortThermal = sort; } bool GetSortThermal() const { return x14_25_sortThermal; } ~CModelData(); explicit CModelData(const CStaticRes& res); explicit CModelData(const CAnimRes& res); CModelData(CModelData&&) = default; CModelData& operator=(CModelData&&) = default; CModelData(); static CModelData CModelDataNull(); SAdvancementDeltas GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const; bool IsLoaded(int shaderIdx); static EWhichModel GetRenderingModel(const CStateManager& stateMgr); CSkinnedModel& PickAnimatedModel(EWhichModel which) const; TLockedToken& PickStaticModel(EWhichModel which); const TLockedToken& PickStaticModel(EWhichModel which) const { return const_cast(this)->PickStaticModel(which); } void SetXRayModel(const std::pair& modelSkin); void SetInfraModel(const std::pair& modelSkin); bool IsDefinitelyOpaque(EWhichModel) const; bool GetIsLoop() const; float GetAnimationDuration(int idx) const; void EnableLooping(bool enable); void AdvanceParticles(const zeus::CTransform& xf, float dt, CStateManager& stateMgr); zeus::CAABox GetBounds() const; zeus::CAABox GetBounds(const zeus::CTransform& xf) const; zeus::CTransform GetScaledLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const; zeus::CTransform GetScaledLocatorTransform(std::string_view name) const; zeus::CTransform GetLocatorTransformDynamic(std::string_view name, const CCharAnimTime* time) const; zeus::CTransform GetLocatorTransform(std::string_view name) const; SAdvancementDeltas AdvanceAnimationIgnoreParticles(float dt, CRandom16& rand, bool advTree); SAdvancementDeltas AdvanceAnimation(float dt, CStateManager& stateMgr, TAreaId aid, bool advTree); bool IsAnimating() const; bool IsInFrustum(const zeus::CTransform& xf, const zeus::CFrustum& frustum) const; void RenderParticles(const zeus::CFrustum& frustum) const; void Touch(EWhichModel, int shaderIdx); void Touch(const CStateManager& stateMgr, int shaderIdx); void RenderThermal(const zeus::CTransform& xf, const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags); void RenderUnsortedParts(EWhichModel, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags); void Render(const CStateManager& stateMgr, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags); void Render(EWhichModel, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags); void FlatDraw(EWhichModel which, const zeus::CTransform& xf, bool unsortedOnly, const CModelFlags& flags); void MultiLightingDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights, const zeus::CColor& alphaColor, const zeus::CColor& additiveColor); void MultiPassDraw(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags* flags, u32 count); void DisintegrateDraw(const CStateManager& mgr, const zeus::CTransform& xf, CTexture& tex, const zeus::CColor& addColor, float t); void DisintegrateDraw(EWhichModel which, const zeus::CTransform& xf, CTexture& tex, const zeus::CColor& addColor, float t); static void ThermalDraw(CSkinnedModel& model, const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags); static void ThermalDraw(CSkinnedModel& model, TConstVectorRef positions, TConstVectorRef normals, const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags); CAnimData* GetAnimationData() { return x10_animData.get(); } const CAnimData* GetAnimationData() const { return x10_animData.get(); } const TLockedToken& GetNormalModel() const { return x1c_normalModel; } const TLockedToken& GetXRayModel() const { return x2c_xrayModel; } const TLockedToken& GetThermalModel() const { return x3c_infraModel; } bool IsNull() const { return !x10_animData && !x1c_normalModel; } u32 GetNumMaterialSets() const; const zeus::CVector3f& GetScale() const { return x0_scale; } void SetScale(const zeus::CVector3f& scale) { x0_scale = scale; } bool HasAnimData() const { return x10_animData != nullptr; } bool HasNormalModel() const { return x1c_normalModel.HasReference(); } bool HasModel(EWhichModel which) const { if (x10_animData) { switch (which) { case EWhichModel::Normal: return true; case EWhichModel::XRay: return x10_animData->GetXRayModel() != nullptr; case EWhichModel::Thermal: return x10_animData->GetInfraModel() != nullptr; default: return false; } } switch (which) { case EWhichModel::Normal: return x1c_normalModel.HasReference(); case EWhichModel::XRay: return x2c_xrayModel.HasReference(); case EWhichModel::Thermal: return x3c_infraModel.HasReference(); default: return false; } } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPASAnimInfo.cpp ================================================ #include "Runtime/Character/CPASAnimInfo.hpp" namespace metaforce { CPASAnimInfo::CPASAnimInfo(u32 id, rstl::reserved_vector&& parms) : x0_id(id), x4_parms(std::move(parms)) {} CPASAnimParm::UParmValue CPASAnimInfo::GetAnimParmValue(size_t idx) const { if (idx >= x4_parms.size()) { return CPASAnimParm::UParmValue{}; } return x4_parms[idx]; } CPASAnimParm CPASAnimInfo::GetAnimParmData(size_t idx, CPASAnimParm::EParmType type) const { if (idx >= x4_parms.size()) { return CPASAnimParm::NoParameter(); } const CPASAnimParm::UParmValue& parm = x4_parms[idx]; switch (type) { case CPASAnimParm::EParmType::Int32: return CPASAnimParm::FromInt32(parm.m_int); case CPASAnimParm::EParmType::UInt32: return CPASAnimParm::FromUint32(parm.m_uint); case CPASAnimParm::EParmType::Float: return CPASAnimParm::FromReal32(parm.m_float); case CPASAnimParm::EParmType::Bool: return CPASAnimParm::FromBool(parm.m_bool); case CPASAnimParm::EParmType::Enum: return CPASAnimParm::FromEnum(parm.m_int); default: return CPASAnimParm::NoParameter(); } } } // namespace metaforce ================================================ FILE: Runtime/Character/CPASAnimInfo.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CPASAnimParm.hpp" namespace metaforce { class CPASAnimInfo { u32 x0_id; rstl::reserved_vector x4_parms; public: explicit CPASAnimInfo(u32 id) : x0_id(id) {} explicit CPASAnimInfo(u32 id, rstl::reserved_vector&& parms); u32 GetAnimId() const { return x0_id; } CPASAnimParm::UParmValue GetAnimParmValue(size_t idx) const; CPASAnimParm GetAnimParmData(size_t idx, CPASAnimParm::EParmType type) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPASAnimParm.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" namespace metaforce { class CPASAnimParm { public: enum class EParmType { None = -1, Int32 = 0, UInt32 = 1, Float = 2, Bool = 3, Enum = 4 }; union UParmValue { s32 m_int; u32 m_uint; float m_float; bool m_bool; }; private: UParmValue x0_value; EParmType x4_type; public: constexpr CPASAnimParm(UParmValue val, EParmType tp) : x0_value(val), x4_type(tp) {} [[nodiscard]] constexpr EParmType GetParameterType() const { return x4_type; } [[nodiscard]] constexpr s32 GetEnumValue() const { return x0_value.m_int; } [[nodiscard]] constexpr bool GetBoolValue() const { return x0_value.m_bool; } [[nodiscard]] constexpr float GetReal32Value() const { return x0_value.m_float; } [[nodiscard]] constexpr u32 GetUint32Value() const { return x0_value.m_uint; } [[nodiscard]] constexpr s32 GetInt32Value() const { return x0_value.m_int; } [[nodiscard]] static constexpr CPASAnimParm FromEnum(s32 val) { UParmValue valin{}; valin.m_int = val; return CPASAnimParm(valin, EParmType::Enum); } [[nodiscard]] static constexpr CPASAnimParm FromBool(bool val) { UParmValue valin{}; valin.m_bool = val; return CPASAnimParm(valin, EParmType::Bool); } [[nodiscard]] static constexpr CPASAnimParm FromReal32(float val) { UParmValue valin{}; valin.m_float = val; return CPASAnimParm(valin, EParmType::Float); } [[nodiscard]] static constexpr CPASAnimParm FromUint32(u32 val) { UParmValue valin{}; valin.m_uint = val; return CPASAnimParm(valin, EParmType::UInt32); } [[nodiscard]] static constexpr CPASAnimParm FromInt32(s32 val) { UParmValue valin{}; valin.m_int = val; return CPASAnimParm(valin, EParmType::Int32); } [[nodiscard]] static constexpr CPASAnimParm NoParameter() { UParmValue valin{}; valin.m_int = -1; return CPASAnimParm(valin, EParmType::None); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPASAnimParmData.cpp ================================================ #include "Runtime/Character/CPASAnimParmData.hpp" namespace metaforce { CPASAnimParmData::CPASAnimParmData(pas::EAnimationState stateId, const CPASAnimParm& parm1, const CPASAnimParm& parm2, const CPASAnimParm& parm3, const CPASAnimParm& parm4, const CPASAnimParm& parm5, const CPASAnimParm& parm6, const CPASAnimParm& parm7, const CPASAnimParm& parm8) : x0_stateId(stateId) { x4_parms.push_back(parm1); x4_parms.push_back(parm2); x4_parms.push_back(parm3); x4_parms.push_back(parm4); x4_parms.push_back(parm5); x4_parms.push_back(parm6); x4_parms.push_back(parm7); x4_parms.push_back(parm8); } } // namespace metaforce ================================================ FILE: Runtime/Character/CPASAnimParmData.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CharacterCommon.hpp" #include "Runtime/Character/CPASAnimParm.hpp" namespace metaforce { class CPASAnimParmData { pas::EAnimationState x0_stateId; rstl::reserved_vector x4_parms; public: CPASAnimParmData() = default; explicit CPASAnimParmData(pas::EAnimationState stateId, const CPASAnimParm& parm1 = CPASAnimParm::NoParameter(), const CPASAnimParm& parm2 = CPASAnimParm::NoParameter(), const CPASAnimParm& parm3 = CPASAnimParm::NoParameter(), const CPASAnimParm& parm4 = CPASAnimParm::NoParameter(), const CPASAnimParm& parm5 = CPASAnimParm::NoParameter(), const CPASAnimParm& parm6 = CPASAnimParm::NoParameter(), const CPASAnimParm& parm7 = CPASAnimParm::NoParameter(), const CPASAnimParm& parm8 = CPASAnimParm::NoParameter()); pas::EAnimationState GetStateId() const { return x0_stateId; } const rstl::reserved_vector& GetAnimParmData() const { return x4_parms; } static auto NoParameters(pas::EAnimationState stateId) { return CPASAnimParmData(stateId); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPASAnimState.cpp ================================================ #include "Runtime/Character/CPASAnimState.hpp" #include "Runtime/CRandom16.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CharacterCommon.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" #include #include #include #include namespace metaforce { CPASAnimState::CPASAnimState(CInputStream& in) { x0_id = static_cast(in.ReadLong()); u32 parmCount = in.ReadLong(); u32 animCount = in.ReadLong(); x4_parms.reserve(parmCount); x14_anims.reserve(animCount); x24_selectionCache.reserve(animCount); for (u32 i = 0; i < parmCount; ++i) x4_parms.emplace_back(in); for (u32 i = 0; i < animCount; ++i) { s32 id = in.ReadLong(); rstl::reserved_vector parms; for (const CPASParmInfo& parm : x4_parms) { CPASAnimParm::UParmValue val = {}; switch (parm.GetParameterType()) { case CPASAnimParm::EParmType::Int32: val.m_int = in.ReadInt32(); break; case CPASAnimParm::EParmType::UInt32: val.m_uint = in.ReadLong(); break; case CPASAnimParm::EParmType::Float: val.m_float = in.ReadFloat(); break; case CPASAnimParm::EParmType::Bool: val.m_bool = in.ReadBool(); break; case CPASAnimParm::EParmType::Enum: val.m_int = in.ReadInt32(); break; default: break; } parms.push_back(val); } auto search = std::lower_bound(x14_anims.begin(), x14_anims.end(), id, [](const CPASAnimInfo& item, const u32& testId) -> bool { return item.GetAnimId() < testId; }); x14_anims.emplace(search, id, std::move(parms)); } } CPASAnimState::CPASAnimState(pas::EAnimationState stateId) : x0_id(stateId) {} CPASAnimParm CPASAnimState::GetAnimParmData(s32 animId, size_t parmIdx) const { const auto search = rstl::binary_find(x14_anims.cbegin(), x14_anims.cend(), animId, [](const CPASAnimInfo& item) { return item.GetAnimId(); }); if (search == x14_anims.cend()) { return CPASAnimParm::NoParameter(); } const CPASParmInfo& parm = x4_parms.at(parmIdx); return search->GetAnimParmData(parmIdx, parm.GetParameterType()); } s32 CPASAnimState::PickRandomAnimation(CRandom16& rand) const { if (x24_selectionCache.size() == 1) return x24_selectionCache[0]; if (x24_selectionCache.size() > 1) { u32 idx = u32(floor(rand.Float())); if (idx == x24_selectionCache.size()) idx--; return x24_selectionCache[idx]; } return -1; } std::pair CPASAnimState::FindBestAnimation(const rstl::reserved_vector& parms, CRandom16& rand, s32 ignoreAnim) const { x24_selectionCache.clear(); float weight = -1.f; for (const CPASAnimInfo& info : x14_anims) { if (info.GetAnimId() == ignoreAnim) continue; float calcWeight = 1.f; if (x4_parms.size() > 0) calcWeight = 0.f; u32 unweightedCount = 0; for (size_t i = 0; i < x4_parms.size(); ++i) { CPASAnimParm::UParmValue val = info.GetAnimParmValue(i); const CPASParmInfo& parmInfo = x4_parms[i]; float parmWeight = parmInfo.GetParameterWeight(); float computedWeight = 0.f; switch (parmInfo.GetWeightFunction()) { case CPASParmInfo::EWeightFunction::AngularPercent: computedWeight = ComputeAngularPercentErrorWeight(i, parms[i], val); break; case CPASParmInfo::EWeightFunction::ExactMatch: computedWeight = ComputeExactMatchWeight(i, parms[i], val); break; case CPASParmInfo::EWeightFunction::PercentError: computedWeight = ComputePercentErrorWeight(i, parms[i], val); break; case CPASParmInfo::EWeightFunction::NoWeight: unweightedCount++; break; default: break; } calcWeight += parmWeight * computedWeight; } if (unweightedCount == x4_parms.size()) calcWeight = 1.0f; if (calcWeight > weight) { x24_selectionCache.clear(); x24_selectionCache.push_back(info.GetAnimId()); weight = calcWeight; } else if (weight == calcWeight) { x24_selectionCache.push_back(info.GetAnimId()); weight = calcWeight; } } return {weight, PickRandomAnimation(rand)}; } float CPASAnimState::ComputeExactMatchWeight(size_t, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const { switch (parm.GetParameterType()) { case CPASAnimParm::EParmType::Int32: return (parm.GetInt32Value() == parmVal.m_int ? 1.f : 0.f); case CPASAnimParm::EParmType::UInt32: return (parm.GetUint32Value() == parmVal.m_uint ? 1.f : 0.f); case CPASAnimParm::EParmType::Float: return ((parmVal.m_float - parm.GetReal32Value()) < FLT_EPSILON ? 1.f : 0.f); case CPASAnimParm::EParmType::Bool: return (parm.GetBoolValue() == parmVal.m_bool ? 1.f : 0.f); case CPASAnimParm::EParmType::Enum: return (parm.GetEnumValue() == parmVal.m_int ? 1.f : 0.f); default: break; } return 0.f; } float CPASAnimState::ComputePercentErrorWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const { float range = 0.f; float val = 0.f; switch (parm.GetParameterType()) { case CPASAnimParm::EParmType::Int32: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int; val = std::fabs(parm.GetInt32Value() - parmVal.m_int); break; } case CPASAnimParm::EParmType::UInt32: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_uint - info.GetWeightMinValue().m_uint; val = std::fabs(int(parmVal.m_uint) - int(parm.GetUint32Value())); break; } case CPASAnimParm::EParmType::Float: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_float - info.GetWeightMinValue().m_float; val = std::fabs(parm.GetReal32Value() - parmVal.m_float); break; } case CPASAnimParm::EParmType::Bool: { val = parm.GetBoolValue() == parmVal.m_bool ? 0.f : 1.f; break; } case CPASAnimParm::EParmType::Enum: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int; val = std::fabs(parm.GetEnumValue() - parmVal.m_int); break; } default: break; } if (range > FLT_EPSILON) return 1.f - val / range; return (val < FLT_EPSILON ? 1.f : 0.f); } float CPASAnimState::ComputeAngularPercentErrorWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const { float range = 0.f; float val = 0.f; switch (parm.GetParameterType()) { case CPASAnimParm::EParmType::Int32: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int; val = std::fabs(parmVal.m_int - parm.GetInt32Value()); break; } case CPASAnimParm::EParmType::UInt32: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_uint - info.GetWeightMinValue().m_uint; val = std::fabs(int(parmVal.m_uint) - int(parm.GetUint32Value())); break; } case CPASAnimParm::EParmType::Float: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_float - info.GetWeightMinValue().m_float; val = std::fabs(parm.GetReal32Value() - parmVal.m_float); break; } case CPASAnimParm::EParmType::Bool: { val = parm.GetBoolValue() == parmVal.m_bool ? 0.f : 1.f; break; } case CPASAnimParm::EParmType::Enum: { const CPASParmInfo& info = x4_parms[idx]; range = info.GetWeightMaxValue().m_int - info.GetWeightMinValue().m_int + 1; val = std::fabs(parm.GetEnumValue() - parmVal.m_int); break; } default: break; } if (range > FLT_EPSILON) { float mid = 0.5f * range; float offset = 1.f - ((val > mid ? range - val : val) / mid); return zeus::clamp(0.f, offset, 1.f); } return (val >= FLT_EPSILON ? 0.f : 1.f); } } // namespace metaforce ================================================ FILE: Runtime/Character/CPASAnimState.hpp ================================================ #pragma once #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CharacterCommon.hpp" #include "Runtime/Character/CPASAnimInfo.hpp" #include "Runtime/Character/CPASParmInfo.hpp" namespace metaforce { class CRandom16; class CPASAnimParmData; class CPASAnimState { pas::EAnimationState x0_id; std::vector x4_parms; std::vector x14_anims; mutable std::vector x24_selectionCache; float ComputeExactMatchWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const; float ComputePercentErrorWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const; float ComputeAngularPercentErrorWeight(size_t idx, const CPASAnimParm& parm, CPASAnimParm::UParmValue parmVal) const; s32 PickRandomAnimation(CRandom16& rand) const; public: explicit CPASAnimState(CInputStream& in); explicit CPASAnimState(pas::EAnimationState stateId); pas::EAnimationState GetStateId() const { return x0_id; } size_t GetNumAnims() const { return x14_anims.size(); } bool HasAnims() const { return !x14_anims.empty(); } CPASAnimParm GetAnimParmData(s32 animId, size_t parmIdx) const; std::pair FindBestAnimation(const rstl::reserved_vector& parms, CRandom16& rand, s32 ignoreAnim) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPASDatabase.cpp ================================================ #include "Runtime/Character/CPASDatabase.hpp" #include "Runtime/CRandom16.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" #include namespace metaforce { void CPASDatabase::AddAnimState(CPASAnimState&& state) { auto it = std::lower_bound(x0_states.begin(), x0_states.end(), state, [](const CPASAnimState& item, const CPASAnimState& test) -> bool { return item.GetStateId() < test.GetStateId(); }); x0_states.insert(it, std::move(state)); } CPASDatabase::CPASDatabase(CInputStream& in) { in.ReadLong(); u32 animStateCount = in.ReadLong(); u32 defaultState = in.ReadLong(); x0_states.reserve(animStateCount); for (u32 i = 0; i < animStateCount; ++i) { CPASAnimState state(in); AddAnimState(std::move(state)); } if (animStateCount) SetDefaultState(defaultState); } std::pair CPASDatabase::FindBestAnimation(const CPASAnimParmData& data, s32 ignoreAnim) const { CRandom16 rnd(4660); return FindBestAnimation(data, rnd, ignoreAnim); } std::pair CPASDatabase::FindBestAnimation(const CPASAnimParmData& data, CRandom16& rand, s32 ignoreAnim) const { auto it = rstl::binary_find(x0_states.cbegin(), x0_states.cend(), data.GetStateId(), [](const CPASAnimState& item) { return item.GetStateId(); }); if (it == x0_states.cend()) return {0.f, -1}; return (*it).FindBestAnimation(data.GetAnimParmData(), rand, ignoreAnim); } } // namespace metaforce ================================================ FILE: Runtime/Character/CPASDatabase.hpp ================================================ #pragma once #include #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CPASAnimState.hpp" namespace metaforce { class CRandom16; class CPASAnimParmData; class CPASDatabase { std::vector x0_states; s32 x10_defaultState; void AddAnimState(CPASAnimState&& state); void SetDefaultState(s32 state) { x10_defaultState = state; } public: explicit CPASDatabase(CInputStream& in); std::pair FindBestAnimation(const CPASAnimParmData& data, s32 ignoreAnim) const; std::pair FindBestAnimation(const CPASAnimParmData& data, CRandom16& rand, s32 ignoreAnim) const; s32 GetDefaultState() const { return x10_defaultState; } size_t GetNumAnimStates() const { return x0_states.size(); } const CPASAnimState* GetAnimState(pas::EAnimationState id) const { for (const CPASAnimState& state : x0_states) if (id == state.GetStateId()) return &state; return nullptr; } const CPASAnimState* GetAnimStateByIndex(size_t index) const { if (index >= x0_states.size()) { return nullptr; } return &x0_states[index]; } bool HasState(pas::EAnimationState id) const { const auto& st = std::find_if(x0_states.begin(), x0_states.end(), [&id](const CPASAnimState& other) -> bool { return other.GetStateId() == id; }); return st != x0_states.end(); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPASParmInfo.cpp ================================================ #include "Runtime/Character/CPASParmInfo.hpp" namespace metaforce { CPASParmInfo::CPASParmInfo(CInputStream& in) : x0_type(CPASAnimParm::EParmType(in.ReadLong())), x4_weightFunction(EWeightFunction(in.ReadLong())) { xc_min.m_int = 0; x10_max.m_int = 0; x8_weight = in.ReadFloat(); switch (x0_type) { case CPASAnimParm::EParmType::Int32: xc_min.m_int = in.ReadInt32(); x10_max.m_int = in.ReadInt32(); break; case CPASAnimParm::EParmType::UInt32: xc_min.m_uint = in.ReadLong(); x10_max.m_uint = in.ReadLong(); break; case CPASAnimParm::EParmType::Float: xc_min.m_float = in.ReadFloat(); x10_max.m_float = in.ReadFloat(); break; case CPASAnimParm::EParmType::Bool: xc_min.m_bool = in.ReadBool(); x10_max.m_bool = in.ReadBool(); break; case CPASAnimParm::EParmType::Enum: xc_min.m_int = in.ReadInt32(); x10_max.m_int = in.ReadInt32(); break; default: break; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CPASParmInfo.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CPASAnimParm.hpp" namespace metaforce { class CPASParmInfo { public: enum class EWeightFunction { ExactMatch, PercentError, AngularPercent, NoWeight }; private: CPASAnimParm::EParmType x0_type; EWeightFunction x4_weightFunction; float x8_weight; CPASAnimParm::UParmValue xc_min; CPASAnimParm::UParmValue x10_max; public: explicit CPASParmInfo(CInputStream& in); CPASAnimParm::EParmType GetParameterType() const { return x0_type; } EWeightFunction GetWeightFunction() const { return x4_weightFunction; } float GetParameterWeight() const { return x8_weight; } CPASAnimParm::UParmValue GetWeightMinValue() const { return xc_min; } CPASAnimParm::UParmValue GetWeightMaxValue() const { return x10_max; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPOINode.cpp ================================================ #include "Runtime/Character/CPOINode.hpp" #include "Runtime/Character/CAnimSourceReader.hpp" #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CSoundPOINode.hpp" namespace metaforce { CPOINode::CPOINode(std::string_view name, EPOIType type, const CCharAnimTime& time, s32 index, bool unique, float weight, s32 e, s32 f) : x4_(1) , x8_name(name) , x18_type(type) , x1c_time(time) , x24_index(index) , x28_unique(unique) , x2c_weight(weight) , x30_charIdx(e) , x34_flags(f) {} CPOINode::CPOINode(CInputStream& in) : x4_(in.ReadShort()) , x8_name(in.Get()) , x18_type(EPOIType(in.ReadShort())) , x1c_time(in) , x24_index(in.ReadInt32()) , x28_unique(in.ReadBool()) , x2c_weight(in.ReadFloat()) , x30_charIdx(in.ReadInt32()) , x34_flags(in.ReadInt32()) {} bool CPOINode::operator>(const CPOINode& other) const { return x1c_time > other.x1c_time; } bool CPOINode::operator<(const CPOINode& other) const { return x1c_time < other.x1c_time; } template size_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo, size_t passedCount) { size_t ret = 0; if (animInfo.HasPOIData() && stream.size()) { const CCharAnimTime dur = animInfo.GetAnimationDuration(); CCharAnimTime targetTime = curTime + time; if (targetTime >= dur) { targetTime = dur; } if (passedCount >= stream.size()) { return ret; } CCharAnimTime nodeTime = stream[passedCount].GetTime(); while (passedCount < stream.size() && nodeTime <= targetTime) { const size_t idx = iterator + ret; if (idx < capacity) { listOut[idx] = T::CopyNodeMinusStartTime(stream[passedCount], curTime); ++ret; } ++passedCount; if (passedCount < stream.size()) nodeTime = stream[passedCount].GetTime(); } } return ret; } template size_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime) { size_t ret = 0; const CCharAnimTime targetTime = curTime + time; for (size_t it = iterator; it < stream.size(); ++it) { const CCharAnimTime nodeTime = stream[it].GetTime(); if (nodeTime > targetTime) { return ret; } const size_t idx = iterator + ret; if (nodeTime >= curTime && idx < capacity) { listOut[idx] = T::CopyNodeMinusStartTime(stream[it], curTime); ++ret; } } return ret; } template size_t _getPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo, size_t passedCount); template size_t _getPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime); template size_t _getPOIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo, size_t passedCount); template size_t _getPOIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime); template size_t _getPOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo, size_t passedCount); template size_t _getPOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime); template size_t _getPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo, size_t passedCount); template size_t _getPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime); } // namespace metaforce ================================================ FILE: Runtime/Character/CPOINode.hpp ================================================ #pragma once #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CCharAnimTime.hpp" namespace metaforce { class IAnimSourceInfo; enum class EPOIType : u16 { Loop = 0, EmptyBool = 1, EmptyInt32 = 2, SoundInt32 = 4, Particle = 5, UserEvent = 6, RandRate = 7, Sound = 8, }; class CPOINode { protected: u16 x4_ = 1; std::string x8_name; EPOIType x18_type; CCharAnimTime x1c_time; s32 x24_index; bool x28_unique; float x2c_weight; s32 x30_charIdx = -1; s32 x34_flags; public: explicit CPOINode(std::string_view name, EPOIType type, const CCharAnimTime& time, s32 index, bool unique, float weight, s32 charIdx, s32 flags); explicit CPOINode(CInputStream& in); virtual ~CPOINode() = default; std::string_view GetString() const { return x8_name; } const CCharAnimTime& GetTime() const { return x1c_time; } void SetTime(const CCharAnimTime& time) { x1c_time = time; } EPOIType GetPoiType() const { return x18_type; } s32 GetIndex() const { return x24_index; } bool GetUnique() const { return x28_unique; } float GetWeight() const { return x2c_weight; } s32 GetCharacterIndex() const { return x30_charIdx; } s32 GetFlags() const { return x34_flags; } bool operator>(const CPOINode& other) const; bool operator<(const CPOINode& other) const; }; template size_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime, const IAnimSourceInfo& animInfo, size_t passedCount); template size_t _getPOIList(const CCharAnimTime& time, T* listOut, size_t capacity, size_t iterator, u32 unk1, const std::vector& stream, const CCharAnimTime& curTime); } // namespace metaforce ================================================ FILE: Runtime/Character/CParticleData.cpp ================================================ #include "Runtime/Character/CParticleData.hpp" namespace metaforce { CParticleData::CParticleData(CInputStream& in) : x0_duration(in.ReadLong()) , x4_particle(in) , xc_boneName(in.Get()) , x1c_scale(in.ReadFloat()) , x20_parentMode(EParentedMode(in.ReadLong())) {} } // namespace metaforce ================================================ FILE: Runtime/Character/CParticleData.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" #include namespace metaforce { class CParticleData { public: enum class EParentedMode { Initial, ContinuousEmitter, ContinuousSystem }; private: u32 x0_duration = 0; SObjectTag x4_particle; std::string xc_boneName = "root"; float x1c_scale = 1.f; EParentedMode x20_parentMode = EParentedMode::Initial; public: CParticleData() = default; CParticleData(const SObjectTag& tag, std::string_view boneName, float scale, EParentedMode mode) : x4_particle(tag), xc_boneName(boneName), x1c_scale(scale), x20_parentMode(mode) {} explicit CParticleData(CInputStream& in); u32 GetDuration() const { return x0_duration; } const SObjectTag& GetTag() const { return x4_particle; } std::string_view GetSegmentName() const { return xc_boneName; } float GetScale() const { return x1c_scale; } EParentedMode GetParentedMode() const { return x20_parentMode; } }; class CAuxiliaryParticleData { u32 x0_duration = 0; SObjectTag x4_particle; zeus::CVector3f xc_translation; float x18_scale = 1.f; public: CAuxiliaryParticleData(u32 duration, const SObjectTag& particle, const zeus::CVector3f& translation, float scale) : x0_duration(duration), x4_particle(particle), xc_translation(translation), x18_scale(scale) {} u32 GetDuration() const { return x0_duration; } const SObjectTag& GetTag() const { return x4_particle; } const zeus::CVector3f& GetTranslation() const { return xc_translation; } float GetScale() const { return x18_scale; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CParticleDatabase.cpp ================================================ #include "Runtime/Character/CParticleDatabase.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CPoseAsTransforms.hpp" #include "Runtime/Particle/CElementGen.hpp" #include "Runtime/Particle/CParticleElectric.hpp" #include "Runtime/Particle/CParticleSwoosh.hpp" namespace metaforce { CParticleDatabase::CParticleDatabase() = default; void CParticleDatabase::CacheParticleDesc(const SObjectTag& tag) {} void CParticleDatabase::CacheParticleDesc(const CCharacterInfo::CParticleResData& desc) { for (CAssetId id : desc.x0_part) { auto search = x0_particleDescs.find(id); if (search == x0_particleDescs.cend()) x0_particleDescs[id] = std::make_shared>(g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), id})); } for (CAssetId id : desc.x10_swhc) { auto search = x14_swooshDescs.find(id); if (search == x14_swooshDescs.cend()) x14_swooshDescs[id] = std::make_shared>(g_SimplePool->GetObj(SObjectTag{FOURCC('SWHC'), id})); } for (CAssetId id : desc.x20_elsc) { auto search = x28_electricDescs.find(id); if (search == x28_electricDescs.cend()) x28_electricDescs[id] = std::make_shared>(g_SimplePool->GetObj(SObjectTag{FOURCC('ELSC'), id})); } } void CParticleDatabase::SetModulationColorAllActiveEffectsForParticleDB(const zeus::CColor& color, DrawMap& map) { for (auto& e : map) { if (e.second) { e.second->SetModulationColor(color); } } } void CParticleDatabase::SetModulationColorAllActiveEffects(const zeus::CColor& color) { SetModulationColorAllActiveEffectsForParticleDB(color, x3c_rendererDrawLoop); SetModulationColorAllActiveEffectsForParticleDB(color, x50_firstDrawLoop); SetModulationColorAllActiveEffectsForParticleDB(color, x64_lastDrawLoop); SetModulationColorAllActiveEffectsForParticleDB(color, x78_rendererDraw); SetModulationColorAllActiveEffectsForParticleDB(color, x8c_firstDraw); SetModulationColorAllActiveEffectsForParticleDB(color, xa0_lastDraw); } void CParticleDatabase::SuspendAllActiveEffectsForParticleDB(CStateManager& mgr, DrawMap& map) { for (auto& e : map) { e.second->SetParticleEmission(false, mgr); } } void CParticleDatabase::SuspendAllActiveEffects(CStateManager& stateMgr) { SuspendAllActiveEffectsForParticleDB(stateMgr, x3c_rendererDrawLoop); SuspendAllActiveEffectsForParticleDB(stateMgr, x50_firstDrawLoop); SuspendAllActiveEffectsForParticleDB(stateMgr, x64_lastDrawLoop); } void CParticleDatabase::DeleteAllLightsForParticleDB(CStateManager& mgr, DrawMap& map) { for (auto& e : map) { e.second->DeleteLight(mgr); } } void CParticleDatabase::DeleteAllLights(CStateManager& mgr) { DeleteAllLightsForParticleDB(mgr, x3c_rendererDrawLoop); DeleteAllLightsForParticleDB(mgr, x50_firstDrawLoop); DeleteAllLightsForParticleDB(mgr, x64_lastDrawLoop); DeleteAllLightsForParticleDB(mgr, x78_rendererDraw); DeleteAllLightsForParticleDB(mgr, x8c_firstDraw); DeleteAllLightsForParticleDB(mgr, xa0_lastDraw); } void CParticleDatabase::UpdateParticleGenDB(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo, const zeus::CTransform& xf, const zeus::CVector3f& scale, CStateManager& stateMgr, DrawMap& map, bool deleteIfDone) { for (auto it = map.begin(); it != map.end();) { CParticleGenInfo& info = *it->second; if (info.GetIsActive()) { if (info.GetType() == EParticleGenType::Normal) { const CSegId segId = charInfo.GetSegIdFromString(info.GetLocatorName()); if (segId.IsInvalid()) { ++it; continue; } if (!pose.ContainsDataFor(segId)) { ++it; continue; } const zeus::CVector3f& off = pose.GetOffset(segId); switch (info.GetParentedMode()) { case CParticleData::EParentedMode::Initial: { if (info.GetIsGrabInitialData()) { zeus::CTransform segXf((info.GetFlags() & 0x10) ? zeus::CMatrix3f() : pose.GetRotation(segId), off * scale); zeus::CTransform compXf = xf * segXf; info.SetCurTransform(compXf.getRotation()); info.SetCurOffset(compXf.origin); info.SetCurrentTime(0.f); info.SetIsGrabInitialData(false); } info.SetOrientation(info.GetCurTransform(), stateMgr); info.SetTranslation(info.GetCurOffset(), stateMgr); if (info.GetFlags() & 0x2000) info.SetGlobalScale(info.GetCurScale() * scale); else info.SetGlobalScale(info.GetCurScale()); break; } case CParticleData::EParentedMode::ContinuousEmitter: case CParticleData::EParentedMode::ContinuousSystem: { if (info.GetIsGrabInitialData()) { info.SetCurrentTime(0.f); info.SetIsGrabInitialData(false); } zeus::CTransform segXf(pose.GetRotation(segId), off * scale); zeus::CTransform compXf = xf * segXf; if (info.GetParentedMode() == CParticleData::EParentedMode::ContinuousEmitter) { info.SetTranslation(compXf.origin, stateMgr); if (info.GetFlags() & 0x10) info.SetOrientation(xf.getRotation(), stateMgr); else info.SetOrientation(compXf.getRotation(), stateMgr); } else { info.SetGlobalTranslation(compXf.origin, stateMgr); if (info.GetFlags() & 0x10) info.SetGlobalOrientation(xf.getRotation(), stateMgr); else info.SetGlobalOrientation(compXf.getRotation(), stateMgr); } if (info.GetFlags() & 0x2000) info.SetGlobalScale(info.GetCurScale() * scale); else info.SetGlobalScale(info.GetCurScale()); break; } default: break; } } float sec = (info.GetInactiveStartTime() == 0.f) ? 10000000.f : info.GetInactiveStartTime(); if (info.GetCurrentTime() > sec) { info.SetIsActive(false); info.SetParticleEmission(false, stateMgr); info.MarkFinishTime(); if (info.GetFlags() & 1) info.DestroyParticles(); } } info.Update(dt, stateMgr); if (!info.GetIsActive()) { if (!info.HasActiveParticles() && info.GetCurrentTime() - info.GetFinishTime() > 5.f && deleteIfDone) { info.DeleteLight(stateMgr); it = map.erase(it); continue; } } else if (info.IsSystemDeletable()) { info.DeleteLight(stateMgr); it = map.erase(it); continue; } info.OffsetTime(dt); ++it; } } void CParticleDatabase::Update(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo, const zeus::CTransform& xf, const zeus::CVector3f& scale, CStateManager& stateMgr) { if (!xb4_24_updatesEnabled) return; UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x3c_rendererDrawLoop, true); UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x50_firstDrawLoop, true); UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x64_lastDrawLoop, true); UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x78_rendererDraw, false); UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, x8c_firstDraw, false); UpdateParticleGenDB(dt, pose, charInfo, xf, scale, stateMgr, xa0_lastDraw, false); xb4_25_anySystemsDrawnWithModel = (x50_firstDrawLoop.size() || x64_lastDrawLoop.size() || x8c_firstDraw.size() || xa0_lastDraw.size()); } void CParticleDatabase::RenderParticleGenMap(const DrawMap& map) { for (const auto& e : map) { e.second->Render(); } } void CParticleDatabase::RenderParticleGenMapMasked(const DrawMap& map, int mask, int target) { for (const auto& e : map) { if ((e.second->GetFlags() & mask) == target) { e.second->Render(); } } } void CParticleDatabase::AddToRendererClippedParticleGenMap(const DrawMap& map, const zeus::CFrustum& frustum) { for (const auto& e : map) { const auto bounds = e.second->GetBounds(); if (bounds && frustum.aabbFrustumTest(*bounds)) { e.second->AddToRenderer(); } } } void CParticleDatabase::AddToRendererClippedParticleGenMapMasked(const DrawMap& map, const zeus::CFrustum& frustum, int mask, int target) { for (const auto& e : map) { if ((e.second->GetFlags() & mask) == target) { const auto bounds = e.second->GetBounds(); if (bounds && frustum.aabbFrustumTest(*bounds)) { e.second->AddToRenderer(); } } } } void CParticleDatabase::RenderSystemsToBeDrawnLastMasked(int mask, int target) const { RenderParticleGenMapMasked(xa0_lastDraw, mask, target); RenderParticleGenMapMasked(x64_lastDrawLoop, mask, target); } void CParticleDatabase::RenderSystemsToBeDrawnLast() const { RenderParticleGenMap(xa0_lastDraw); RenderParticleGenMap(x64_lastDrawLoop); } void CParticleDatabase::RenderSystemsToBeDrawnFirstMasked(int mask, int target) const { RenderParticleGenMapMasked(x8c_firstDraw, mask, target); RenderParticleGenMapMasked(x50_firstDrawLoop, mask, target); } void CParticleDatabase::RenderSystemsToBeDrawnFirst() const { RenderParticleGenMap(x8c_firstDraw); RenderParticleGenMap(x50_firstDrawLoop); } void CParticleDatabase::AddToRendererClippedMasked(const zeus::CFrustum& frustum, int mask, int target) const { AddToRendererClippedParticleGenMapMasked(x78_rendererDraw, frustum, mask, target); AddToRendererClippedParticleGenMapMasked(x3c_rendererDrawLoop, frustum, mask, target); } void CParticleDatabase::AddToRendererClipped(const zeus::CFrustum& frustum) const { AddToRendererClippedParticleGenMap(x78_rendererDraw, frustum); AddToRendererClippedParticleGenMap(x3c_rendererDrawLoop, frustum); } CParticleGenInfo* CParticleDatabase::GetParticleEffect(std::string_view name) const { if (const auto search = x3c_rendererDrawLoop.find(name); search != x3c_rendererDrawLoop.cend()) { return search->second.get(); } if (const auto search = x50_firstDrawLoop.find(name); search != x50_firstDrawLoop.cend()) { return search->second.get(); } if (const auto search = x64_lastDrawLoop.find(name); search != x64_lastDrawLoop.cend()) { return search->second.get(); } if (const auto search = x78_rendererDraw.find(name); search != x78_rendererDraw.cend()) { return search->second.get(); } if (const auto search = x8c_firstDraw.find(name); search != x8c_firstDraw.cend()) { return search->second.get(); } if (const auto search = xa0_lastDraw.find(name); search != xa0_lastDraw.cend()) { return search->second.get(); } return nullptr; } void CParticleDatabase::SetParticleEffectState(std::string_view name, bool active, CStateManager& mgr) { if (CParticleGenInfo* info = GetParticleEffect(name)) { info->SetParticleEmission(active, mgr); info->SetIsActive(active); if (!active && (info->GetFlags() & 1) != 0) info->DestroyParticles(); info->SetIsGrabInitialData(true); } } void CParticleDatabase::SetCEXTValue(std::string_view name, int idx, float value) { if (CParticleGenInfo* info = GetParticleEffect(name)) { std::static_pointer_cast(static_cast(info)->GetParticleSystem()) ->SetExternalVar(idx, value); } } template static s32 _getGraphicLightId(const T& system, const U& desc) { if (system->SystemHasLight()) return s32(desc.GetObjectTag()->id.Value()); return -1; } void CParticleDatabase::AddAuxiliaryParticleEffect(std::string_view name, int flags, const CAuxiliaryParticleData& data, const zeus::CVector3f& scale, CStateManager& mgr, TAreaId aid, int lightId) { if (CParticleGenInfo* info = GetParticleEffect(name)) { if (!info->GetIsActive()) { info->SetParticleEmission(true, mgr); info->SetIsActive(true); info->SetIsGrabInitialData(true); info->SetFlags(flags); } return; } zeus::CVector3f scaleVec; if (flags & 0x2) scaleVec.splat(data.GetScale()); else scaleVec = scale * data.GetScale(); switch (data.GetTag().type.toUint32()) { case SBIG('PART'): { const auto search = x0_particleDescs.find(data.GetTag().id); if (search != x0_particleDescs.cend()) { auto sys = std::make_shared(*search->second); auto newGen = std::make_unique( data.GetTag(), sys, data.GetDuration(), "NOT_A_VALID_LOCATOR", scaleVec, CParticleData::EParentedMode::Initial, flags, mgr, aid, lightId + _getGraphicLightId(sys, *search->second), EParticleGenType::Auxiliary); newGen->SetGlobalTranslation(data.GetTranslation(), mgr); newGen->SetIsGrabInitialData(false); InsertParticleGen(false, flags, name, std::move(newGen)); } break; } default: break; } } void CParticleDatabase::AddParticleEffect(std::string_view name, int flags, const CParticleData& data, const zeus::CVector3f& scale, CStateManager& mgr, TAreaId aid, bool oneShot, int lightId) { if (CParticleGenInfo* info = GetParticleEffect(name)) { if (!info->GetIsActive()) { info->SetParticleEmission(true, mgr); info->SetIsActive(true); info->SetIsGrabInitialData(true); info->SetFlags(flags); } return; } zeus::CVector3f scaleVec; if (flags & 0x2) scaleVec.splat(data.GetScale()); else scaleVec = scale * data.GetScale(); std::unique_ptr newGen; switch (data.GetTag().type.toUint32()) { case SBIG('PART'): { const auto search = x0_particleDescs.find(data.GetTag().id); if (search != x0_particleDescs.cend()) { auto sys = std::make_shared(*search->second); newGen = std::make_unique( data.GetTag(), sys, data.GetDuration(), data.GetSegmentName(), scaleVec, data.GetParentedMode(), flags, mgr, aid, lightId + _getGraphicLightId(sys, *search->second), EParticleGenType::Normal); } break; } case SBIG('SWHC'): { const auto search = x14_swooshDescs.find(data.GetTag().id); if (search != x14_swooshDescs.cend()) { auto sys = std::make_shared(*search->second, 0); newGen = std::make_unique(data.GetTag(), sys, data.GetDuration(), data.GetSegmentName(), scaleVec, data.GetParentedMode(), flags, mgr, aid, -1, EParticleGenType::Normal); } break; } case SBIG('ELSC'): { const auto search = x28_electricDescs.find(data.GetTag().id); if (search != x28_electricDescs.cend()) { auto sys = std::make_shared(*search->second); newGen = std::make_unique( data.GetTag(), sys, data.GetDuration(), data.GetSegmentName(), scaleVec, data.GetParentedMode(), flags, mgr, aid, lightId + _getGraphicLightId(sys, *search->second), EParticleGenType::Normal); } break; } default: break; } if (newGen) { newGen->SetIsActive(true); newGen->SetParticleEmission(true, mgr); newGen->SetIsGrabInitialData(true); InsertParticleGen(oneShot, flags, name, std::move(newGen)); } } void CParticleDatabase::InsertParticleGen(bool oneShot, int flags, std::string_view name, std::unique_ptr&& gen) { DrawMap* useMap; if (oneShot) { if ((flags & 0x40) != 0) { useMap = &xa0_lastDraw; } else if ((flags & 0x20) != 0) { useMap = &x8c_firstDraw; } else { useMap = &x78_rendererDraw; } } else { if ((flags & 0x40) != 0) { useMap = &x64_lastDrawLoop; } else if ((flags & 0x20) != 0) { useMap = &x50_firstDrawLoop; } else { useMap = &x3c_rendererDrawLoop; } } useMap->emplace(name, std::move(gen)); if ((flags & 0x60) != 0) { xb4_25_anySystemsDrawnWithModel = true; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CParticleDatabase.hpp ================================================ #pragma once #include #include #include #include "Runtime/CToken.hpp" #include "Runtime/Character/CCharacterInfo.hpp" #include "Runtime/Character/CParticleGenInfo.hpp" #include "Runtime/Particle/CGenDescription.hpp" #include "Runtime/Particle/CSwooshDescription.hpp" #include "Runtime/Particle/CElectricDescription.hpp" #include #include namespace metaforce { class CCharLayoutInfo; class CPoseAsTransforms; class CParticleDatabase { using DrawMap = std::map, std::less<>>; std::map>> x0_particleDescs; std::map>> x14_swooshDescs; std::map>> x28_electricDescs; DrawMap x3c_rendererDrawLoop; DrawMap x50_firstDrawLoop; DrawMap x64_lastDrawLoop; DrawMap x78_rendererDraw; DrawMap x8c_firstDraw; DrawMap xa0_lastDraw; bool xb4_24_updatesEnabled : 1 = true; bool xb4_25_anySystemsDrawnWithModel : 1 = false; static void SetModulationColorAllActiveEffectsForParticleDB(const zeus::CColor& color, DrawMap& map); static void SuspendAllActiveEffectsForParticleDB(CStateManager& mgr, DrawMap& map); static void DeleteAllLightsForParticleDB(CStateManager& mgr, DrawMap& map); static void RenderParticleGenMap(const DrawMap& map); static void RenderParticleGenMapMasked(const DrawMap& map, int mask, int target); static void AddToRendererClippedParticleGenMap(const DrawMap& map, const zeus::CFrustum& frustum); static void AddToRendererClippedParticleGenMapMasked(const DrawMap& map, const zeus::CFrustum& frustum, int mask, int target); static void UpdateParticleGenDB(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo, const zeus::CTransform& xf, const zeus::CVector3f& vec, CStateManager& stateMgr, DrawMap& map, bool deleteIfDone); public: CParticleDatabase(); void CacheParticleDesc(const SObjectTag& tag); void CacheParticleDesc(const CCharacterInfo::CParticleResData& desc); void SetModulationColorAllActiveEffects(const zeus::CColor& color); void SuspendAllActiveEffects(CStateManager& stateMgr); void DeleteAllLights(CStateManager& stateMgr); void Update(float dt, const CPoseAsTransforms& pose, const CCharLayoutInfo& charInfo, const zeus::CTransform& xf, const zeus::CVector3f& scale, CStateManager& stateMgr); void RenderSystemsToBeDrawnLastMasked(int mask, int target) const; void RenderSystemsToBeDrawnLast() const; void RenderSystemsToBeDrawnFirstMasked(int mask, int target) const; void RenderSystemsToBeDrawnFirst() const; void AddToRendererClippedMasked(const zeus::CFrustum& frustum, int mask, int target) const; void AddToRendererClipped(const zeus::CFrustum& frustum) const; CParticleGenInfo* GetParticleEffect(std::string_view name) const; void SetParticleEffectState(std::string_view name, bool active, CStateManager& mgr); void SetCEXTValue(std::string_view name, int idx, float value); void AddAuxiliaryParticleEffect(std::string_view name, int flags, const CAuxiliaryParticleData& data, const zeus::CVector3f& scale, CStateManager& mgr, TAreaId aid, int lightId); void AddParticleEffect(std::string_view name, int flags, const CParticleData& data, const zeus::CVector3f& scale, CStateManager& mgr, TAreaId aid, bool oneShot, int lightId); void InsertParticleGen(bool oneShot, int flags, std::string_view name, std::unique_ptr&& gen); bool AreAnySystemsDrawnWithModel() const { return xb4_25_anySystemsDrawnWithModel; } void SetUpdatesEnabled(bool active) { xb4_24_updatesEnabled = active; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CParticleGenInfo.cpp ================================================ #include "Runtime/Character/CParticleGenInfo.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Graphics/IRenderer.hpp" #include "Runtime/Particle/CParticleGen.hpp" #include "Runtime/World/CGameLight.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CParticleGenInfo::CParticleGenInfo(const SObjectTag& part, int frameCount, std::string_view boneName, const zeus::CVector3f& scale, CParticleData::EParentedMode parentMode, int flags, EParticleGenType type) : x4_part(part) , xc_seconds(frameCount / 60.f) , x10_boneName(boneName) , x28_parentMode(parentMode) , x2c_flags(flags) , x30_particleScale(scale) , x80_type(type) {} static TUniqueId _initializeLight(const std::weak_ptr& system, CStateManager& stateMgr, TAreaId areaId, int lightId) { TUniqueId ret = kInvalidUniqueId; std::shared_ptr systemRef = system.lock(); if (systemRef->SystemHasLight()) { ret = stateMgr.AllocateUniqueId(); stateMgr.AddObject( new CGameLight(ret, areaId, false, "ParticleLight", zeus::CTransform(systemRef->GetOrientation().buildMatrix3f(), systemRef->GetTranslation()), kInvalidUniqueId, systemRef->GetLight(), u32(lightId), 0, 0.f)); } return ret; } CParticleGenInfoGeneric::CParticleGenInfoGeneric(const SObjectTag& part, const std::weak_ptr& system, int frameCount, std::string_view boneName, const zeus::CVector3f& scale, CParticleData::EParentedMode parentMode, int flags, CStateManager& stateMgr, TAreaId areaId, int lightId, EParticleGenType state) : CParticleGenInfo(part, frameCount, boneName, scale, parentMode, flags, state), x84_system(system) { if (lightId == -1) x88_lightId = kInvalidUniqueId; else x88_lightId = _initializeLight(system, stateMgr, areaId, lightId); } void CParticleGenInfoGeneric::AddToRenderer() { g_Renderer->AddParticleGen(*x84_system); } void CParticleGenInfoGeneric::Render() { x84_system->Render(); } void CParticleGenInfoGeneric::Update(float dt, CStateManager& stateMgr) { x84_system->Update(dt); if (x88_lightId == kInvalidUniqueId) { return; } if (const TCastToPtr gl = stateMgr.ObjectById(x88_lightId)) { gl->SetLight(x84_system->GetLight()); } } void CParticleGenInfoGeneric::SetOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) { x84_system->SetOrientation(xf); if (x88_lightId == kInvalidUniqueId) { return; } if (const TCastToPtr gl = stateMgr.ObjectById(x88_lightId)) { gl->SetRotation(zeus::CQuaternion(xf.buildMatrix3f())); } } void CParticleGenInfoGeneric::SetTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) { x84_system->SetTranslation(trans); if (x88_lightId == kInvalidUniqueId) { return; } if (const TCastToPtr gl = stateMgr.ObjectById(x88_lightId)) { gl->SetTranslation(trans); } } void CParticleGenInfoGeneric::SetGlobalOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) { x84_system->SetGlobalOrientation(xf); if (x88_lightId == kInvalidUniqueId) { return; } if (const TCastToPtr gl = stateMgr.ObjectById(x88_lightId)) { gl->SetRotation(zeus::CQuaternion(xf.buildMatrix3f())); } } void CParticleGenInfoGeneric::SetGlobalTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) { x84_system->SetGlobalTranslation(trans); if (x88_lightId == kInvalidUniqueId) { return; } if (const TCastToPtr gl = stateMgr.ObjectById(x88_lightId)) { gl->SetTranslation(trans); } } void CParticleGenInfoGeneric::SetGlobalScale(const zeus::CVector3f& scale) { x84_system->SetGlobalScale(scale); } void CParticleGenInfoGeneric::SetParticleEmission(bool isActive, CStateManager& stateMgr) { x84_system->SetParticleEmission(isActive); if (x88_lightId == kInvalidUniqueId) { return; } if (const TCastToPtr gl = stateMgr.ObjectById(x88_lightId)) { gl->SetActive(isActive); } } bool CParticleGenInfoGeneric::IsSystemDeletable() const { return x84_system->IsSystemDeletable(); } std::optional CParticleGenInfoGeneric::GetBounds() const { return x84_system->GetBounds(); } bool CParticleGenInfoGeneric::HasActiveParticles() const { return x84_system->GetParticleCount() != 0; } void CParticleGenInfoGeneric::DestroyParticles() { x84_system->DestroyParticles(); } bool CParticleGenInfoGeneric::HasLight() const { return x84_system->SystemHasLight(); } TUniqueId CParticleGenInfoGeneric::GetLightId() const { return x88_lightId; } void CParticleGenInfoGeneric::DeleteLight(CStateManager& mgr) { if (x88_lightId != kInvalidUniqueId) { mgr.FreeScriptObject(x88_lightId); x88_lightId = kInvalidUniqueId; } } void CParticleGenInfoGeneric::SetModulationColor(const zeus::CColor& color) { x84_system->SetModulationColor(color); } } // namespace metaforce ================================================ FILE: Runtime/Character/CParticleGenInfo.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CParticleData.hpp" #include #include #include namespace metaforce { class CParticleGen; class CStateManager; struct SObjectTag; enum class EParticleGenType { Normal, Auxiliary }; class CParticleGenInfo { SObjectTag x4_part; float xc_seconds; std::string x10_boneName; float x20_curTime = 0.f; bool x24_active = false; CParticleData::EParentedMode x28_parentMode; s32 x2c_flags; zeus::CVector3f x30_particleScale; float x3c_finishTime = 0.f; bool x40_grabInitialData = false; zeus::CTransform x44_transform; zeus::CVector3f x74_offset; EParticleGenType x80_type; public: CParticleGenInfo(const SObjectTag& part, int frameCount, std::string_view boneName, const zeus::CVector3f& scale, CParticleData::EParentedMode parentMode, int flags, EParticleGenType type); virtual ~CParticleGenInfo() = default; virtual void AddToRenderer() = 0; virtual void Render() = 0; virtual void Update(float dt, CStateManager& stateMgr) = 0; virtual void SetOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) = 0; virtual void SetTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) = 0; virtual void SetGlobalOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) = 0; virtual void SetGlobalTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) = 0; virtual void SetGlobalScale(const zeus::CVector3f& scale) = 0; virtual void SetParticleEmission(bool isActive, CStateManager& stateMgr) = 0; virtual bool IsSystemDeletable() const = 0; virtual std::optional GetBounds() const = 0; virtual bool HasActiveParticles() const = 0; virtual void DestroyParticles() = 0; virtual bool HasLight() const = 0; virtual TUniqueId GetLightId() const = 0; virtual void DeleteLight(CStateManager& stateMgr) = 0; virtual void SetModulationColor(const zeus::CColor& color) = 0; void SetFlags(s32 flags) { x2c_flags = flags; } s32 GetFlags() const { return x2c_flags; } void SetIsGrabInitialData(bool grabInitialData) { x40_grabInitialData = grabInitialData; } bool GetIsGrabInitialData() const { return x40_grabInitialData; } bool GetIsActive() const { return x24_active; } void SetIsActive(bool isActive) { x24_active = isActive; } void OffsetTime(float dt) { x20_curTime += dt; } const zeus::CVector3f& GetCurOffset() const { return x74_offset; } void SetCurOffset(const zeus::CVector3f& offset) { x74_offset = offset; } const zeus::CTransform& GetCurTransform() const { return x44_transform; } void SetCurTransform(const zeus::CTransform& xf) { x44_transform = xf; } const zeus::CVector3f& GetCurScale() const { return x30_particleScale; } void SetCurScale(const zeus::CVector3f& scale) { x30_particleScale = scale; } void SetInactiveStartTime(float seconds) { xc_seconds = seconds; } float GetInactiveStartTime() const { return xc_seconds; } void MarkFinishTime() { x3c_finishTime = x20_curTime; } float GetFinishTime() const { return x3c_finishTime; } float GetCurrentTime() const { return x20_curTime; } void SetCurrentTime(float time) { x20_curTime = time; } EParticleGenType GetType() const { return x80_type; } CParticleData::EParentedMode GetParentedMode() const { return x28_parentMode; } std::string_view GetLocatorName() const { return x10_boneName; } }; class CParticleGenInfoGeneric : public CParticleGenInfo { std::shared_ptr x84_system; TUniqueId x88_lightId; public: CParticleGenInfoGeneric(const SObjectTag& part, const std::weak_ptr& system, int frames, std::string_view boneName, const zeus::CVector3f& scale, CParticleData::EParentedMode parentMode, int flags, CStateManager& stateMgr, TAreaId areaId, int lightId, EParticleGenType state); void AddToRenderer() override; void Render() override; void Update(float dt, CStateManager& stateMgr) override; void SetOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) override; void SetTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) override; void SetGlobalOrientation(const zeus::CTransform& xf, CStateManager& stateMgr) override; void SetGlobalTranslation(const zeus::CVector3f& trans, CStateManager& stateMgr) override; void SetGlobalScale(const zeus::CVector3f& scale) override; void SetParticleEmission(bool isActive, CStateManager& stateMgr) override; bool IsSystemDeletable() const override; std::optional GetBounds() const override; bool HasActiveParticles() const override; void DestroyParticles() override; bool HasLight() const override; TUniqueId GetLightId() const override; void DeleteLight(CStateManager& mgr) override; void SetModulationColor(const zeus::CColor& color) override; const std::shared_ptr& GetParticleSystem() const { return x84_system; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CParticlePOINode.cpp ================================================ #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CAnimSourceReader.hpp" namespace metaforce { CParticlePOINode::CParticlePOINode() : CPOINode("root", EPOIType::Particle, CCharAnimTime(), -1, false, 1.f, -1, 0) {} CParticlePOINode::CParticlePOINode(CInputStream& in) : CPOINode(in), x38_data(in) {} CParticlePOINode CParticlePOINode::CopyNodeMinusStartTime(const CParticlePOINode& node, const CCharAnimTime& startTime) { CParticlePOINode ret = node; ret.x1c_time -= startTime; return ret; } } // namespace metaforce ================================================ FILE: Runtime/Character/CParticlePOINode.hpp ================================================ #pragma once #include "Runtime/Character/CParticleData.hpp" #include "Runtime/Character/CPOINode.hpp" namespace metaforce { class IAnimSourceInfo; class CParticlePOINode : public CPOINode { CParticleData x38_data; public: explicit CParticlePOINode(); explicit CParticlePOINode(CInputStream& in); const CParticleData& GetParticleData() const { return x38_data; } static CParticlePOINode CopyNodeMinusStartTime(const CParticlePOINode& node, const CCharAnimTime& startTime); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPoseAsTransforms.cpp ================================================ #include "Runtime/Character/CPoseAsTransforms.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" namespace metaforce { CPoseAsTransforms::CPoseAsTransforms(u8 boneCount) : x1_count(boneCount), xd0_transformArr(std::make_unique(boneCount)) {} bool CPoseAsTransforms::ContainsDataFor(const CSegId& id) const { const std::pair& link = x8_links[id]; return link.first.IsValid() || link.second.IsValid(); } void CPoseAsTransforms::Clear() { x8_links.fill({}); xd4_lastInserted = 0; x0_nextId = 0; } void CPoseAsTransforms::AccumulateScaledTransform(const CSegId& id, zeus::CMatrix3f& rotation, float scale) const { rotation.addScaledMatrix(GetRotation(id), scale); } const zeus::CTransform& CPoseAsTransforms::GetTransform(const CSegId& id) const { const std::pair& link = x8_links[id]; assert(link.second.IsValid()); return xd0_transformArr[link.second]; } const zeus::CVector3f& CPoseAsTransforms::GetOffset(const CSegId& id) const { const std::pair& link = x8_links[id]; assert(link.second.IsValid()); return xd0_transformArr[link.second].origin; } const zeus::CMatrix3f& CPoseAsTransforms::GetRotation(const CSegId& id) const { const std::pair& link = x8_links[id]; assert(link.second.IsValid()); return xd0_transformArr[link.second].basis; } void CPoseAsTransforms::Insert(const CSegId& id, const zeus::CMatrix3f& rotation, const zeus::CVector3f& offset) { xd0_transformArr[x0_nextId] = zeus::CTransform(rotation, offset); std::pair& link = x8_links[id]; link.first = xd4_lastInserted; link.second = x0_nextId; xd4_lastInserted = id; ++x0_nextId; } CSegId CPoseAsTransforms::GetParent(const CSegId& id) const { const std::pair& link = x8_links[id]; assert(link.first.IsValid()); return link.first; } } // namespace metaforce ================================================ FILE: Runtime/Character/CPoseAsTransforms.hpp ================================================ #pragma once #include #include #include #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CSegId.hpp" #include "Runtime/RetroTypes.hpp" #include #include #include namespace metaforce { class CPoseAsTransforms { friend class CAnimData; private: CSegId x0_nextId = 0; CSegId x1_count; std::array, 100> x8_links; std::unique_ptr xd0_transformArr; CSegId xd4_lastInserted = 0; public: explicit CPoseAsTransforms(u8 boneCount); void Clear(); void AccumulateScaledTransform(const CSegId& id, zeus::CMatrix3f& rotation, float scale) const; void Insert(const CSegId& id, const zeus::CMatrix3f& rotation, const zeus::CVector3f& offset); [[nodiscard]] bool ContainsDataFor(const CSegId& id) const; [[nodiscard]] const zeus::CTransform& GetTransform(const CSegId& id) const; [[nodiscard]] const zeus::CVector3f& GetOffset(const CSegId& id) const; [[nodiscard]] const zeus::CMatrix3f& GetRotation(const CSegId& id) const; [[nodiscard]] CSegId GetLastInserted() const { return xd4_lastInserted; } [[nodiscard]] CSegId GetParent(const CSegId& id) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CPrimitive.cpp ================================================ #include "Runtime/Character/CPrimitive.hpp" namespace metaforce { CPrimitive::CPrimitive(CInputStream& in) { x0_animId = in.Get(); x4_animIdx = in.ReadLong(); x8_animName = in.Get(); } } // namespace metaforce ================================================ FILE: Runtime/Character/CPrimitive.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CPrimitive { CAssetId x0_animId; u32 x4_animIdx; std::string x8_animName; public: explicit CPrimitive(CInputStream& in); CAssetId GetAnimResId() const { return x0_animId; } u32 GetAnimDbIdx() const { return x4_animIdx; } std::string_view GetName() const { return x8_animName; } bool operator<(const CPrimitive& other) const { return x8_animName < other.x8_animName; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CRagDoll.cpp ================================================ #include "Runtime/Character/CRagDoll.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" #include "Runtime/Collision/CCollisionInfo.hpp" #include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include "Runtime/Collision/CMetroidAreaCollider.hpp" namespace metaforce { void CRagDoll::CRagDollLengthConstraint::Update() { zeus::CVector3f delta = x4_p2->x4_curPos - x0_p1->x4_curPos; float magSq = delta.magSquared(); float lenSq = x8_length * x8_length; bool doSolve = true; switch (xc_ineqType) { case 1: // Min doSolve = magSq < lenSq; break; case 2: // Max doSolve = magSq > lenSq; break; default: break; } if (!doSolve) return; zeus::CVector3f solveVec = delta * (lenSq / (magSq + lenSq) - 0.5f); x0_p1->x4_curPos -= solveVec; x4_p2->x4_curPos += solveVec; } void CRagDoll::CRagDollJointConstraint::Update() { // L_hip, R_shoulder, L_shoulder, L_hip, L_knee, L_ankle zeus::CVector3f P4ToP5 = x10_p5->x4_curPos - xc_p4->x4_curPos; // L_hip->L_knee zeus::CVector3f cross = P4ToP5.cross((x8_p3->x4_curPos - x0_p1->x4_curPos).cross(x4_p2->x4_curPos - x0_p1->x4_curPos)); // L_hip->L_knee X (L_hip->L_shoulder X L_hip->R_shoulder) if (cross.canBeNormalized()) { zeus::CVector3f hipUp = cross.cross(P4ToP5).normalized(); float dot = (x14_p6->x4_curPos - x10_p5->x4_curPos).dot(hipUp); if (dot > 0.f) { zeus::CVector3f solveVec = 0.5f * dot * hipUp; x14_p6->x4_curPos -= solveVec; x10_p5->x4_curPos += solveVec; } } } void CRagDoll::CRagDollPlaneConstraint::Update() { zeus::CVector3f P1ToP2 = (x4_p2->x4_curPos - x0_p1->x4_curPos).normalized(); float dot = P1ToP2.dot(xc_p4->x4_curPos - x8_p3->x4_curPos); if (dot < 0.f) { zeus::CVector3f solveVec = 0.5f * dot * P1ToP2; xc_p4->x4_curPos -= solveVec; x10_p5->x4_curPos += solveVec; } } CRagDoll::CRagDoll(float normalGravity, float floatingGravity, float overTime, u32 flags) : x44_normalGravity(normalGravity) , x48_floatingGravity(floatingGravity) , x50_overTimer(overTime) , x68_27_continueSmallMovements(bool(flags & 0x1)) , x68_28_noOverTimer(bool(flags & 0x2)) , x68_29_noAiCollision(bool(flags & 0x4)) {} void CRagDoll::AccumulateForces(float dt, float waterTop) { float fps = 1.f / dt; x64_angTimer += dt; if (x64_angTimer > 4.f) x64_angTimer -= 4.f; float targetZ = std::sin(zeus::degToRad(90.f) * x64_angTimer) * 0.1f + (waterTop - 0.2f); zeus::CVector3f centerOfVolume; float totalVolume = 0.f; for (auto& particle : x4_particles) { float volume = particle.x10_radius * particle.x10_radius * particle.x10_radius; totalVolume += volume; centerOfVolume += particle.x4_curPos * volume; float fromTargetZ = particle.x4_curPos.z() - targetZ; float verticalAcc = x48_floatingGravity; float termVelCoefficient = 0.f; if (std::fabs(fromTargetZ) < 0.5f) { termVelCoefficient = 0.5f * fromTargetZ / 0.5f + 0.5f; verticalAcc = x48_floatingGravity * -fromTargetZ / 0.5f; } else if (fromTargetZ > 0.f) { verticalAcc = x44_normalGravity; termVelCoefficient = 1.f; } particle.x20_velocity.z() += verticalAcc; zeus::CVector3f vel = (particle.x4_curPos - particle.x14_prevPos) * fps; float velMag = vel.magnitude(); if (velMag > FLT_EPSILON) { particle.x20_velocity -= vel * (1.f / velMag) * ((velMag * velMag * 0.75f * (1.2f * termVelCoefficient + 1000.f * (1.f - termVelCoefficient))) / (8000.f * particle.x10_radius)); } } zeus::CVector3f averageTorque; centerOfVolume = centerOfVolume / totalVolume; for (const auto& particle : x4_particles) { float volume = particle.x10_radius * particle.x10_radius * particle.x10_radius; averageTorque += (particle.x4_curPos - centerOfVolume).cross(particle.x4_curPos - particle.x14_prevPos) * volume; } averageTorque = averageTorque * (fps / totalVolume); if (averageTorque.canBeNormalized()) for (auto& particle : x4_particles) particle.x20_velocity -= averageTorque.cross(particle.x4_curPos - centerOfVolume) * 25.f; } void CRagDoll::AddParticle(CSegId id, const zeus::CVector3f& prevPos, const zeus::CVector3f& curPos, float radius) { x4_particles.emplace_back(id, curPos, radius, prevPos); } void CRagDoll::AddLengthConstraint(int i1, int i2) { x14_lengthConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2], (x4_particles[i1].x4_curPos - x4_particles[i2].x4_curPos).magnitude(), 0); } void CRagDoll::AddMaxLengthConstraint(int i1, int i2, float length) { x14_lengthConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2], length, 2); } void CRagDoll::AddMinLengthConstraint(int i1, int i2, float length) { x14_lengthConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2], length, 1); } void CRagDoll::AddJointConstraint(int i1, int i2, int i3, int i4, int i5, int i6) { x24_jointConstraints.emplace_back(&x4_particles[i1], &x4_particles[i2], &x4_particles[i3], &x4_particles[i4], &x4_particles[i5], &x4_particles[i6]); } zeus::CQuaternion CRagDoll::BoneAlign(CHierarchyPoseBuilder& pb, const CCharLayoutInfo& charInfo, int i1, int i2, const zeus::CQuaternion& q) { zeus::CVector3f fromParent = charInfo.GetFromParentUnrotated(x4_particles[i2].x0_id); zeus::CVector3f delta = x4_particles[i2].x4_curPos - x4_particles[i1].x4_curPos; delta = q.inverse().transform(delta); zeus::CQuaternion ret = zeus::CQuaternion::shortestRotationArc(fromParent, delta); pb.GetTreeMap()[x4_particles[i1].x0_id].x4_rotation = ret; return ret; } zeus::CAABox CRagDoll::CalculateRenderBounds() const { zeus::CAABox aabb; for (const auto& particle : x4_particles) { aabb.accumulateBounds( zeus::CAABox(particle.x4_curPos - particle.x10_radius, particle.x4_curPos + particle.x10_radius)); } return aabb; } void CRagDoll::CheckStatic(float dt) { x4c_impactCount = 0; x54_impactVel = 0.f; float halfDt = 0.5f * dt; float halfDeltaUnitSq = halfDt * halfDt; x58_averageVel = zeus::skZero3f; bool movingSlowly = true; for (auto& particle : x4_particles) { zeus::CVector3f delta = particle.x4_curPos - particle.x14_prevPos; x58_averageVel += delta; if (delta.magSquared() > halfDeltaUnitSq) movingSlowly = false; if (particle.x3c_24_impactPending) { x4c_impactCount += 1; x54_impactVel = std::max(particle.x38_impactFrameVel, x54_impactVel); } } if (!x4_particles.empty()) x58_averageVel = x58_averageVel * (1.f / (dt * x4_particles.size())); x54_impactVel /= dt; if (!x68_28_noOverTimer) { x50_overTimer -= dt; if (x50_overTimer <= 0.f) x68_25_over = true; } if (movingSlowly && x68_24_prevMovingSlowly) x68_25_over = true; x68_24_prevMovingSlowly = movingSlowly; } void CRagDoll::ClearForces() { for (auto& particle : x4_particles) particle.x20_velocity = zeus::skZero3f; } void CRagDoll::SatisfyConstraints(CStateManager& mgr) { for (auto& length : x14_lengthConstraints) length.Update(); for (auto& joint : x24_jointConstraints) joint.Update(); for (auto& plane : x34_planeConstraints) plane.Update(); if (SatisfyWorldConstraints(mgr, 1)) SatisfyWorldConstraints(mgr, 2); } bool CRagDoll::SatisfyWorldConstraints(CStateManager& mgr, int pass) { zeus::CAABox aabb; for (const auto& particle : x4_particles) { if (pass == 1 || particle.x3c_24_impactPending) { aabb.accumulateBounds(particle.x14_prevPos - particle.x10_radius); aabb.accumulateBounds(particle.x14_prevPos + particle.x10_radius); aabb.accumulateBounds(particle.x4_curPos - particle.x10_radius); aabb.accumulateBounds(particle.x4_curPos + particle.x10_radius); } } CAreaCollisionCache ccache(aabb); CGameCollision::BuildAreaCollisionCache(mgr, ccache); bool needs2ndPass = false; TUniqueId bestId = kInvalidUniqueId; CMaterialList include; if (x68_29_noAiCollision) include = CMaterialList(EMaterialTypes::Solid); else include = CMaterialList(EMaterialTypes::Solid, EMaterialTypes::AIBlock); CMaterialList exclude; if (x68_29_noAiCollision) exclude = CMaterialList(EMaterialTypes::Character, EMaterialTypes::Player, EMaterialTypes::AIBlock, EMaterialTypes::Occluder); else exclude = CMaterialList(EMaterialTypes::Character, EMaterialTypes::Player); EntityList nearList; CMaterialFilter filter = CMaterialFilter::MakeIncludeExclude(include, exclude); mgr.BuildNearList(nearList, aabb, filter, nullptr); for (auto& particle : x4_particles) { if (pass == 1 || particle.x3c_24_impactPending) { zeus::CVector3f delta = particle.x4_curPos - particle.x14_prevPos; float deltaMag = delta.magnitude(); if (deltaMag > 0.0001f) { delta = delta * (1.f / deltaMag); double d = deltaMag; CCollidableSphere sphere(zeus::CSphere(particle.x14_prevPos, particle.x10_radius), include); CCollisionInfo info; CGameCollision::DetectCollision_Cached_Moving(mgr, ccache, sphere, {}, filter, nearList, delta, bestId, info, d); if (info.IsValid()) { needs2ndPass = true; switch (pass) { case 1: { particle.x3c_24_impactPending = true; float dot = delta.dot(info.GetNormalLeft()); particle.x2c_impactResponseDelta = -0.125f * dot * deltaMag * info.GetNormalLeft(); particle.x38_impactFrameVel = -dot * deltaMag; particle.x4_curPos += (0.0001f - (deltaMag - float(d)) * dot) * info.GetNormalLeft(); break; } case 2: particle.x4_curPos = float(d - 0.0001) * delta + particle.x14_prevPos; break; default: break; } } } else if (!x68_27_continueSmallMovements) { particle.x4_curPos = particle.x14_prevPos; } } } return needs2ndPass; } void CRagDoll::SatisfyWorldConstraintsOnConstruction(CStateManager& mgr) { for (auto& particle : x4_particles) particle.x3c_24_impactPending = true; SatisfyWorldConstraints(mgr, 2); for (auto& particle : x4_particles) particle.x14_prevPos = particle.x4_curPos; } void CRagDoll::Verlet(float dt) { for (auto& particle : x4_particles) { zeus::CVector3f oldPos = particle.x4_curPos; particle.x4_curPos += (particle.x4_curPos - particle.x14_prevPos) * (particle.x3c_24_impactPending ? 0.9f : 1.f); particle.x4_curPos += particle.x20_velocity * (dt * dt); particle.x4_curPos += particle.x2c_impactResponseDelta; particle.x14_prevPos = oldPos; zeus::CVector3f deltaPos = particle.x4_curPos - particle.x14_prevPos; if (deltaPos.magSquared() > 4.f) particle.x4_curPos = deltaPos.normalized() * 2.f + particle.x14_prevPos; particle.x3c_24_impactPending = false; particle.x2c_impactResponseDelta = zeus::skZero3f; } } void CRagDoll::PreRender(const zeus::CVector3f& v, CModelData& mData) { // Empty } void CRagDoll::Update(CStateManager& mgr, float dt, float waterTop) { if (!x68_25_over || x68_27_continueSmallMovements) { AccumulateForces(dt, waterTop); Verlet(dt); SatisfyConstraints(mgr); ClearForces(); CheckStatic(dt); } } void CRagDoll::Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData) { zeus::CVector3f scale = mData.GetScale(); CAnimData* aData = mData.GetAnimationData(); aData->BuildPose(); for (auto& particle : x4_particles) { if (particle.x0_id.IsValid()) { particle.x4_curPos = xf * (aData->GetPose().GetOffset(particle.x0_id) * scale); } } SatisfyWorldConstraints(mgr, 2); for (auto& particle : x4_particles) { particle.x3c_24_impactPending = false; } x68_26_primed = true; } } // namespace metaforce ================================================ FILE: Runtime/Character/CRagDoll.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CSegId.hpp" #include #include #include namespace metaforce { class CCharLayoutInfo; class CHierarchyPoseBuilder; class CModelData; class CStateManager; class CRagDoll { protected: class CRagDollParticle { friend class CRagDoll; CSegId x0_id; zeus::CVector3f x4_curPos; float x10_radius; zeus::CVector3f x14_prevPos; zeus::CVector3f x20_velocity; zeus::CVector3f x2c_impactResponseDelta; float x38_impactFrameVel = 0.f; bool x3c_24_impactPending : 1 = false; bool x3c_25_ : 1 = false; public: CRagDollParticle(CSegId id, const zeus::CVector3f& curPos, float radius, const zeus::CVector3f& prevPos) : x0_id(id), x4_curPos(curPos), x10_radius(radius), x14_prevPos(prevPos) {} CSegId GetBone() const { return x0_id; } const zeus::CVector3f& GetPosition() const { return x4_curPos; } zeus::CVector3f& Position() { return x4_curPos; } const zeus::CVector3f& GetVelocity() const { return x20_velocity; } zeus::CVector3f& Velocity() { return x20_velocity; } float GetRadius() const { return x10_radius; } }; class CRagDollLengthConstraint { friend class CRagDoll; CRagDollParticle* x0_p1; CRagDollParticle* x4_p2; float x8_length; int xc_ineqType; public: CRagDollLengthConstraint(CRagDollParticle* p1, CRagDollParticle* p2, float f1, int i1) : x0_p1(p1), x4_p2(p2), x8_length(f1), xc_ineqType(i1) {} void Update(); float GetLength() const { return x8_length; } }; class CRagDollJointConstraint { friend class CRagDoll; CRagDollParticle* x0_p1; // Shoulder plane 0 CRagDollParticle* x4_p2; // Shoulder plane 1 CRagDollParticle* x8_p3; // Shoulder plane 2 CRagDollParticle* xc_p4; // Shoulder CRagDollParticle* x10_p5; // Elbow CRagDollParticle* x14_p6; // Wrist public: CRagDollJointConstraint(CRagDollParticle* p1, CRagDollParticle* p2, CRagDollParticle* p3, CRagDollParticle* p4, CRagDollParticle* p5, CRagDollParticle* p6) : x0_p1(p1), x4_p2(p2), x8_p3(p3), xc_p4(p4), x10_p5(p5), x14_p6(p6) {} void Update(); }; class CRagDollPlaneConstraint { friend class CRagDoll; CRagDollParticle* x0_p1; CRagDollParticle* x4_p2; CRagDollParticle* x8_p3; CRagDollParticle* xc_p4; CRagDollParticle* x10_p5; public: CRagDollPlaneConstraint(CRagDollParticle* p1, CRagDollParticle* p2, CRagDollParticle* p3, CRagDollParticle* p4, CRagDollParticle* p5) : x0_p1(p1), x4_p2(p2), x8_p3(p3), xc_p4(p4), x10_p5(p5) {} void Update(); }; std::vector x4_particles; std::vector x14_lengthConstraints; std::vector x24_jointConstraints; std::vector x34_planeConstraints; float x44_normalGravity; float x48_floatingGravity; u32 x4c_impactCount = 0; float x50_overTimer; float x54_impactVel = 0.f; zeus::CVector3f x58_averageVel; float x64_angTimer = 0.f; bool x68_24_prevMovingSlowly : 1 = false; bool x68_25_over : 1 = false; bool x68_26_primed : 1 = false; bool x68_27_continueSmallMovements : 1; bool x68_28_noOverTimer : 1; bool x68_29_noAiCollision : 1; void AccumulateForces(float dt, float waterTop); void SetNumParticles(int num) { x4_particles.reserve(num); } void AddParticle(CSegId id, const zeus::CVector3f& prevPos, const zeus::CVector3f& curPos, float radius); void SetNumLengthConstraints(int num) { x14_lengthConstraints.reserve(num); } void AddLengthConstraint(int i1, int i2); void AddMaxLengthConstraint(int i1, int i2, float length); void AddMinLengthConstraint(int i1, int i2, float length); void SetNumJointConstraints(int num) { x24_jointConstraints.reserve(num); } void AddJointConstraint(int i1, int i2, int i3, int i4, int i5, int i6); zeus::CQuaternion BoneAlign(CHierarchyPoseBuilder& pb, const CCharLayoutInfo& charInfo, int i1, int i2, const zeus::CQuaternion& q); void CheckStatic(float dt); void ClearForces(); void SatisfyConstraints(CStateManager& mgr); bool SatisfyWorldConstraints(CStateManager& mgr, int pass); void SatisfyWorldConstraintsOnConstruction(CStateManager& mgr); void Verlet(float dt); public: virtual ~CRagDoll() = default; CRagDoll(float normalGravity, float floatingGravity, float overTime, u32 flags); virtual void PreRender(const zeus::CVector3f& v, CModelData& mData); virtual void Update(CStateManager& mgr, float dt, float waterTop); virtual void Prime(CStateManager& mgr, const zeus::CTransform& xf, CModelData& mData); zeus::CAABox CalculateRenderBounds() const; bool IsPrimed() const { return x68_26_primed; } bool WillContinueSmallMovements() const { return x68_27_continueSmallMovements; } bool IsOver() const { return x68_25_over; } void SetNoOverTimer(bool b) { x68_28_noOverTimer = b; } void SetContinueSmallMovements(bool b) { x68_27_continueSmallMovements = b; } u32 GetImpactCount() const { return x4c_impactCount; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CSegId.hpp ================================================ #pragma once #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CSegId { u8 x0_segId = 0xFF; public: constexpr CSegId() noexcept = default; constexpr CSegId(u8 id) noexcept : x0_segId(id) {} explicit CSegId(CInputStream& in) : x0_segId(in.ReadLong()) {} constexpr CSegId& operator++() noexcept { ++x0_segId; return *this; } constexpr CSegId& operator--() noexcept { --x0_segId; return *this; } constexpr operator u8() const noexcept { return x0_segId; } constexpr bool IsValid() const noexcept { return !IsInvalid(); } constexpr bool IsInvalid() const noexcept { return x0_segId == 0xFF; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CSegIdList.cpp ================================================ #include "Runtime/Character/CSegIdList.hpp" namespace metaforce { CSegIdList::CSegIdList(CInputStream& in) { u32 count = in.ReadLong(); x0_list.reserve(count); for (u32 i = 0; i < count; ++i) x0_list.emplace_back(in); } } // namespace metaforce ================================================ FILE: Runtime/Character/CSegIdList.hpp ================================================ #pragma once #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CSegId.hpp" namespace metaforce { class CSegIdList { std::vector x0_list; public: explicit CSegIdList(CInputStream& in); const std::vector& GetList() const { return x0_list; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CSegStatementSet.cpp ================================================ #include "Runtime/Character/CSegStatementSet.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CSegIdList.hpp" namespace metaforce { void CSegStatementSet::Add(const CSegIdList& list, const CCharLayoutInfo& layout, const CSegStatementSet& other, float weight) { for (const CSegId& id : list.GetList()) { x4_segData[id].x0_rotation *= zeus::CQuaternion::slerp(zeus::CQuaternion(), other.x4_segData[id].x0_rotation, weight); if (other.x4_segData[id].x1c_hasOffset && x4_segData[id].x1c_hasOffset) { zeus::CVector3f off = other.x4_segData[id].x10_offset - layout.GetFromParentUnrotated(id); x4_segData[id].x10_offset += off * weight; } } } } // namespace metaforce ================================================ FILE: Runtime/Character/CSegStatementSet.hpp ================================================ #pragma once #include #include "Runtime/Character/CAnimPerSegmentData.hpp" #include "Runtime/Character/CSegId.hpp" namespace metaforce { class CCharLayoutInfo; class CSegIdList; class CSegStatementSet { private: /* Used to be a pointer to arbitrary subclass-provided storage, * now it's a self-stored array */ std::array x4_segData; public: void Add(const CSegIdList& list, const CCharLayoutInfo& layout, const CSegStatementSet& other, float weight); CAnimPerSegmentData& operator[](const CSegId& idx) { return x4_segData[idx]; } const CAnimPerSegmentData& operator[](const CSegId& idx) const { return x4_segData[idx]; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CSequenceHelper.cpp ================================================ #include "Runtime/Character/CSequenceHelper.hpp" #include #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CSoundPOINode.hpp" #include "Runtime/Character/CTreeUtils.hpp" #include "Runtime/Character/IMetaAnim.hpp" namespace metaforce { CSequenceFundamentals::CSequenceFundamentals(const CSteadyStateAnimInfo& ssInfo, std::vector boolNodes, std::vector int32Nodes, std::vector particleNodes, std::vector soundNodes) : x0_ssInfo(ssInfo) , x18_boolNodes(std::move(boolNodes)) , x28_int32Nodes(std::move(int32Nodes)) , x38_particleNodes(std::move(particleNodes)) , x48_soundNodes(std::move(soundNodes)) {} CSequenceHelper::CSequenceHelper(const std::shared_ptr& a, const std::shared_ptr& b, CAnimSysContext animCtx) : x0_animCtx(std::move(animCtx)) { x10_treeNodes.reserve(2); x10_treeNodes.push_back(a); x10_treeNodes.push_back(b); } CSequenceHelper::CSequenceHelper(const std::vector>& nodes, CAnimSysContext animCtx) : x0_animCtx(std::move(animCtx)) { x10_treeNodes.reserve(nodes.size()); for (const std::shared_ptr& meta : nodes) x10_treeNodes.push_back(meta->GetAnimationTree(x0_animCtx, CMetaAnimTreeBuildOrders::NoSpecialOrders())); } CSequenceFundamentals CSequenceHelper::ComputeSequenceFundamentals() { CCharAnimTime duration; zeus::CVector3f offset; std::vector boolNodes; std::vector int32Nodes; std::vector particleNodes; std::vector soundNodes; if (x10_treeNodes.size() > 0) { std::shared_ptr node = CAnimTreeNode::Cast(x10_treeNodes[0]->Clone()); for (size_t i = 0; i < x10_treeNodes.size(); ++i) { std::array boolNodeArr; const size_t numBools = node->GetBoolPOIList(CCharAnimTime::Infinity(), boolNodeArr.data(), boolNodeArr.size(), 0, 0); boolNodes.reserve(boolNodes.size() + numBools); for (size_t j = 0; j < numBools; ++j) { CBoolPOINode& n = boolNodeArr[j]; n.SetTime(n.GetTime() + duration); boolNodes.push_back(n); } std::array int32NodeArr; const size_t numInt32s = node->GetInt32POIList(CCharAnimTime::Infinity(), int32NodeArr.data(), int32NodeArr.size(), 0, 0); int32Nodes.reserve(int32Nodes.size() + numInt32s); for (size_t j = 0; j < numInt32s; ++j) { CInt32POINode& n = int32NodeArr[j]; n.SetTime(n.GetTime() + duration); int32Nodes.push_back(n); } std::array particleNodeArr; const size_t numParticles = node->GetParticlePOIList(CCharAnimTime::Infinity(), particleNodeArr.data(), particleNodeArr.size(), 0, 0); particleNodes.reserve(particleNodes.size() + numParticles); for (size_t j = 0; j < numParticles; ++j) { CParticlePOINode& n = particleNodeArr[j]; n.SetTime(n.GetTime() + duration); particleNodes.push_back(n); } std::array soundNodeArr; const size_t numSounds = node->GetSoundPOIList(CCharAnimTime::Infinity(), soundNodeArr.data(), soundNodeArr.size(), 0, 0); soundNodes.reserve(soundNodes.size() + numSounds); for (size_t j = 0; j < numSounds; ++j) { CSoundPOINode& n = soundNodeArr[j]; n.SetTime(n.GetTime() + duration); soundNodes.push_back(n); } duration += node->VGetTimeRemaining(); CCharAnimTime remTime = node->VGetTimeRemaining(); while (!remTime.EqualsZero() && !remTime.EpsilonZero()) { SAdvancementResults res = node->VAdvanceView(remTime); auto simp = node->Simplified(); if (simp) node = CAnimTreeNode::Cast(std::move(*simp)); // CCharAnimTime prevRemTime = remTime; remTime = res.x0_remTime; /* This was originally accumulating uninitialized register values (stack variable misuse?) */ offset += res.x8_deltas.x0_posDelta; } if (i < x10_treeNodes.size() - 1) { node = CTreeUtils::GetTransitionTree(node, CAnimTreeNode::Cast(x10_treeNodes[i + 1]->Clone()), x0_animCtx); } } } return {{false, duration, offset}, boolNodes, int32Nodes, particleNodes, soundNodes}; } } // namespace metaforce ================================================ FILE: Runtime/Character/CSequenceHelper.hpp ================================================ #pragma once #include #include #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CSoundPOINode.hpp" #include "Runtime/Character/CTransitionDatabaseGame.hpp" namespace metaforce { class IMetaAnim; class CSequenceFundamentals { CSteadyStateAnimInfo x0_ssInfo; std::vector x18_boolNodes; std::vector x28_int32Nodes; std::vector x38_particleNodes; std::vector x48_soundNodes; public: CSequenceFundamentals(const CSteadyStateAnimInfo& ssInfo, std::vector boolNodes, std::vector int32Nodes, std::vector particleNodes, std::vector soundNodes); const CSteadyStateAnimInfo& GetSteadyStateAnimInfo() const { return x0_ssInfo; } const std::vector& GetBoolPointsOfInterest() const { return x18_boolNodes; } const std::vector& GetInt32PointsOfInterest() const { return x28_int32Nodes; } const std::vector& GetParticlePointsOfInterest() const { return x38_particleNodes; } const std::vector& GetSoundPointsOfInterest() const { return x48_soundNodes; } }; class CSequenceHelper { CAnimSysContext x0_animCtx; std::vector> x10_treeNodes; std::vector x20_; public: CSequenceHelper(const std::shared_ptr& a, const std::shared_ptr& b, CAnimSysContext animCtx); CSequenceHelper(const std::vector>& nodes, CAnimSysContext animCtx); CSequenceFundamentals ComputeSequenceFundamentals(); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CSkinRules.cpp ================================================ #include "Runtime/Character/CSkinRules.hpp" #include "Runtime/CToken.hpp" #include "Runtime/Character/CPoseAsTransforms.hpp" #include "Runtime/Graphics/CModel.hpp" namespace metaforce { static u32 ReadCount(CInputStream& in) { s32 result = in.ReadLong(); if (result == -1) { return in.ReadLong(); } u8 junk[784]; u32 iVar2 = 0; for (u32 i = 0; i < (result * 3); i += iVar2) { iVar2 = ((result * 3) - i); iVar2 = 192 < iVar2 ? 192 : iVar2; in.Get(junk, iVar2 * 4); } return result; } CSkinRules::CSkinRules(CInputStream& in) { u32 weightCount = in.ReadLong(); x0_bones.reserve(weightCount); for (int i = 0; i < weightCount; ++i) { x0_bones.emplace_back(in); } x10_vertexCount = ReadCount(in); x14_normalCount = ReadCount(in); } void CSkinRules::BuildAccumulatedTransforms(const CPoseAsTransforms& pose, const CCharLayoutInfo& info) { std::array points; CSegId segId = pose.GetLastInserted(); while (segId != 0) { zeus::CVector3f origin; if (segId != 3) { // root ID origin = info.GetFromRootUnrotated(segId); } const auto rotatedOrigin = pose.GetRotation(segId) * origin; points[segId] = pose.GetOffset(segId) - rotatedOrigin; segId = pose.GetParent(segId); } for (auto& bone : x0_bones) { bone.BuildAccumulatedTransform(pose, points.data()); } } void CSkinRules::BuildPoints(TConstVectorRef positions, TVectorRef out) { size_t offset = 0; for (auto& bone : x0_bones) { u32 vertexCount = bone.GetVertexCount(); bone.BuildPoints(positions.data() + offset, out, vertexCount); offset += vertexCount; } } void CSkinRules::BuildNormals(TConstVectorRef normals, TVectorRef out) { size_t offset = 0; for (auto& bone : x0_bones) { u32 vertexCount = bone.GetVertexCount(); bone.BuildNormals(normals.data() + offset, out, vertexCount); offset += vertexCount; } } CFactoryFnReturn FSkinRulesFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params, CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } static inline auto StreamInSkinWeighting(CInputStream& in) { rstl::reserved_vector weights; u32 weightCount = in.ReadLong(); for (int i = 0; i < std::min(3u, weightCount); ++i) { weights.emplace_back(in); } for (int i = 3; i < weightCount; ++i) { SSkinWeighting{in}; } return weights; } CVirtualBone::CVirtualBone(CInputStream& in) : x0_weights(StreamInSkinWeighting(in)), x1c_vertexCount(in.ReadLong()) {} void CVirtualBone::BuildPoints(const aurora::Vec3* in, TVectorRef out, u32 count) const { for (u32 i = 0; i < count; ++i) { const auto& vec = in[i]; zeus::CVector3f zout = x20_xf * zeus::CVector3f{vec.x, vec.y, vec.z}; out->emplace_back(zout.x(), zout.y(), zout.z()); } } void CVirtualBone::BuildNormals(const aurora::Vec3* in, TVectorRef out, u32 count) const { for (u32 i = 0; i < count; ++i) { const auto& vec = in[i]; zeus::CVector3f zout = x50_rotation * zeus::CVector3f{vec.x, vec.y, vec.z}; out->emplace_back(zout.x(), zout.y(), zout.z()); } } void CVirtualBone::BuildAccumulatedTransform(const CPoseAsTransforms& pose, const zeus::CVector3f* points) { BuildFinalPosMatrix(pose, points); x50_rotation = pose.GetRotation(x0_weights[0].x0_id); } static inline zeus::CMatrix3f WeightedMatrix(const zeus::CMatrix3f& m1, float w1, const zeus::CMatrix3f& m2, float w2) { return { m1[0] * w1 + m2[0] * w2, m1[1] * w1 + m2[1] * w2, m1[2] * w1 + m2[2] * w2, }; } static inline zeus::CVector3f WeightedVector(const zeus::CVector3f& v1, float w1, const zeus::CVector3f& v2, float w2) { return v1 * w1 + v2 * w2; } void CVirtualBone::BuildFinalPosMatrix(const CPoseAsTransforms& pose, const zeus::CVector3f* points) { if (x0_weights.size() == 1) { const auto id = x0_weights[0].x0_id; x20_xf = {pose.GetRotation(id), points[id]}; } else if (x0_weights.size() == 2) { const auto w0 = x0_weights[0]; const auto w1 = x0_weights[1]; x20_xf = { WeightedMatrix(pose.GetRotation(w0.x0_id), w0.x4_weight, pose.GetRotation(w1.x0_id), w1.x4_weight), WeightedVector(points[w0.x0_id], w0.x4_weight, points[w1.x0_id], w1.x4_weight), }; } else if (x0_weights.size() == 3) { const auto w0 = x0_weights[0]; const auto w1 = x0_weights[1]; const auto w2 = x0_weights[2]; auto rot = WeightedMatrix(pose.GetRotation(w0.x0_id), w0.x4_weight, pose.GetRotation(w1.x0_id), w1.x4_weight); auto pos = WeightedVector(points[w0.x0_id], w0.x4_weight, points[w1.x0_id], w1.x4_weight); pose.AccumulateScaledTransform(w2.x0_id, rot, w2.x4_weight); x20_xf = {rot, pos + points[w2.x0_id] * w2.x4_weight}; } else { x20_xf = {}; } } } // namespace metaforce ================================================ FILE: Runtime/Character/CSkinRules.hpp ================================================ #pragma once #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/Character/CSegId.hpp" #include "Runtime/Graphics/CCubeModel.hpp" #include "Runtime/RetroTypes.hpp" #include namespace metaforce { class CCharLayoutInfo; class CPoseAsTransforms; class CModel; struct SSkinWeighting { CSegId x0_id; float x4_weight; explicit SSkinWeighting(CInputStream& in) : x0_id(in), x4_weight(in.ReadFloat()) {} }; class CVirtualBone { friend class CSkinnedModel; rstl::reserved_vector x0_weights; u32 x1c_vertexCount; zeus::CTransform x20_xf; zeus::CMatrix3f x50_rotation; public: explicit CVirtualBone(CInputStream& in); void BuildPoints(const aurora::Vec3* in, TVectorRef out, u32 count) const; void BuildNormals(const aurora::Vec3* in, TVectorRef out, u32 count) const; void BuildAccumulatedTransform(const CPoseAsTransforms& pose, const zeus::CVector3f* points); [[nodiscard]] const auto& GetWeights() const { return x0_weights; } [[nodiscard]] u32 GetVertexCount() const { return x1c_vertexCount; } private: void BuildFinalPosMatrix(const CPoseAsTransforms& pose, const zeus::CVector3f* points); }; class CSkinRules { friend class CSkinnedModel; std::vector x0_bones; u32 x10_vertexCount = 0; u32 x14_normalCount = 0; public: explicit CSkinRules(CInputStream& in); void BuildPoints(TConstVectorRef positions, TVectorRef out); void BuildNormals(TConstVectorRef normals, TVectorRef out); void BuildAccumulatedTransforms(const CPoseAsTransforms& pose, const CCharLayoutInfo& info); [[nodiscard]] u32 GetVertexCount() const { return x10_vertexCount; } [[nodiscard]] u32 GetNormalCount() const { return x14_normalCount; } }; CFactoryFnReturn FSkinRulesFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& params, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Character/CSoundPOINode.cpp ================================================ #include "Runtime/Character/CSoundPOINode.hpp" #include "Runtime/Character/CAnimSourceReader.hpp" namespace metaforce { CSoundPOINode::CSoundPOINode() : CPOINode("root", EPOIType::Sound, CCharAnimTime(), -1, false, 1.f, -1, 0) , x38_sfxId(0) , x3c_falloff(0.f) , x40_maxDist(0.f) {} CSoundPOINode::CSoundPOINode(CInputStream& in) : CPOINode(in), x38_sfxId(in.ReadLong()), x3c_falloff(in.ReadFloat()), x40_maxDist(in.ReadFloat()) {} CSoundPOINode::CSoundPOINode(std::string_view name, EPOIType a, const CCharAnimTime& time, u32 b, bool c, float d, u32 e, u32 f, u32 sfxId, float falloff, float maxDist) : CPOINode(name, a, time, b, c, d, e, f), x38_sfxId(sfxId), x3c_falloff(falloff), x40_maxDist(maxDist) {} CSoundPOINode CSoundPOINode::CopyNodeMinusStartTime(const CSoundPOINode& node, const CCharAnimTime& startTime) { CSoundPOINode ret = node; ret.x1c_time -= startTime; return ret; } } // namespace metaforce ================================================ FILE: Runtime/Character/CSoundPOINode.hpp ================================================ #pragma once #include "Runtime/Character/CCharAnimTime.hpp" #include "Runtime/Character/CPOINode.hpp" namespace metaforce { class IAnimSourceInfo; class CSoundPOINode : public CPOINode { u32 x38_sfxId; float x3c_falloff; float x40_maxDist; public: explicit CSoundPOINode(); explicit CSoundPOINode(CInputStream& in); explicit CSoundPOINode(std::string_view name, EPOIType type, const CCharAnimTime& time, u32 b, bool c, float d, u32 e, u32 f, u32 sfxId, float falloff, float maxDist); static CSoundPOINode CopyNodeMinusStartTime(const CSoundPOINode& node, const CCharAnimTime& startTime); u32 GetSfxId() const { return x38_sfxId; } float GetFalloff() const { return x3c_falloff; } float GetMaxDist() const { return x40_maxDist; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CSteeringBehaviors.cpp ================================================ #include "Runtime/Character/CSteeringBehaviors.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/World/CPhysicsActor.hpp" namespace metaforce { zeus::CVector3f CSteeringBehaviors::Flee(const CPhysicsActor& actor, const zeus::CVector3f& v0) const { zeus::CVector3f actVec = actor.GetTranslation() - v0; if (actVec.canBeNormalized()) return actVec.normalized(); return actor.GetTransform().frontVector(); } zeus::CVector3f CSteeringBehaviors::Seek(const CPhysicsActor& actor, const zeus::CVector3f& target) const { zeus::CVector3f posDiff = target - actor.GetTranslation(); if (posDiff.canBeNormalized()) return posDiff.normalized(); return {}; } zeus::CVector3f CSteeringBehaviors::Arrival(const CPhysicsActor& actor, const zeus::CVector3f& dest, float dampingRadius) const { zeus::CVector3f posDiff = dest - actor.GetTranslation(); if (!posDiff.canBeNormalized()) return {}; if (posDiff.magSquared() < (dampingRadius * dampingRadius)) dampingRadius = posDiff.magSquared() / (dampingRadius * dampingRadius); else dampingRadius = 1.f; return dampingRadius * posDiff.normalized(); } zeus::CVector3f CSteeringBehaviors::Pursuit(const CPhysicsActor& actor, const zeus::CVector3f& v0, const zeus::CVector3f& v1) const { zeus::CVector3f target; if (!ProjectLinearIntersection(actor.GetTranslation(), actor.GetVelocity().magnitude(), v0, v1, target)) target = v1 * 1.f + v0; return CSteeringBehaviors::Seek(actor, target); } zeus::CVector3f CSteeringBehaviors::Separation(const CPhysicsActor& actor, const zeus::CVector3f& pos, float separation) const { zeus::CVector3f posDiff = actor.GetTranslation() - pos; if (posDiff.magSquared() >= separation * separation) return {}; if (!posDiff.canBeNormalized()) return actor.GetTransform().frontVector(); return (1.f - (posDiff.magSquared() / (separation * separation))) * posDiff.normalized(); } zeus::CVector3f CSteeringBehaviors::Alignment(const CPhysicsActor& actor, EntityList& list, const CStateManager& mgr) const { zeus::CVector3f align; if (!list.empty()) { for (const TUniqueId& id : list) if (const CActor* act = static_cast(mgr.GetObjectById(id))) align += act->GetTransform().frontVector(); align *= zeus::CVector3f(1.f / float(list.size())); } float diff = zeus::CVector3f::getAngleDiff(actor.GetTransform().frontVector(), align); return align * (diff / M_PIF); } zeus::CVector3f CSteeringBehaviors::Cohesion(const CPhysicsActor& actor, EntityList& list, float dampingRadius, const CStateManager& mgr) const { zeus::CVector3f dest; if (!list.empty()) { for (const TUniqueId& id : list) if (const CActor* act = static_cast(mgr.GetObjectById(id))) dest += act->GetTranslation(); dest *= zeus::CVector3f(1.f / float(list.size())); return Arrival(actor, dest, dampingRadius); } return dest; } zeus::CVector2f CSteeringBehaviors::Flee2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const { zeus::CVector2f diffVec = actor.GetTranslation().toVec2f() - v0; if (diffVec.magSquared() > FLT_EPSILON) return diffVec.normalized(); else return actor.GetTransform().basis[1].toVec2f(); } zeus::CVector2f CSteeringBehaviors::Arrival2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const { zeus::CVector2f diffVec = v0 - actor.GetTranslation().toVec2f(); if (diffVec.magSquared() > FLT_EPSILON) return diffVec.normalized(); else return {}; } bool CSteeringBehaviors::SolveQuadratic(float a, float b, float c, float& xPos, float& xNeg) { float numSq = b * b - 4.f * a * c; if (numSq < FLT_EPSILON || std::fabs(a) < FLT_EPSILON) return false; numSq = std::sqrt(numSq); float denom = 2.f * a; xPos = (-b + numSq) / denom; xNeg = (-b - numSq) / denom; return true; } bool CSteeringBehaviors::SolveCubic(const rstl::reserved_vector& in, rstl::reserved_vector& out) { if (in[3] != 0.f) { float f3 = 3.f * in[3]; float f31 = in[2] / f3; float f4 = in[1] / f3 - f31 * f31; float f0 = (f31 * f4 - in[0]) / in[3]; float f1 = 2.f * f31 * f31; f3 = f4 * f4 * f4; float f24 = -0.5f * (f31 * f1 - f0); f1 = f24 * f24 + f3; if (f1 < 0.f) { float f25 = std::acos(zeus::clamp(-1.f, f24 / std::sqrt(-f3), 1.f)); f24 = 2.f * std::pow(-f3, 0.166667f); for (float f23 = 0.f; f23 < 2.01f; f23 += 1.f) out.push_back(std::cos((2.f * f23 * M_PIF + f25) / 3.f) * f24 - f31); if (out[1] < out[0]) std::swap(out[1], out[0]); if (out[2] < out[1]) std::swap(out[2], out[1]); if (out[1] < out[0]) std::swap(out[1], out[0]); } else { float f30 = std::sqrt(f1); float f25 = std::pow(std::fabs(f24 + f30), 0.333333f); f1 = std::pow(std::fabs(f24 - f30), 0.333333f); f1 = (f24 - f30) > 0.f ? f1 : -f1; f25 = (f24 + f30) > 0.f ? f25 : -f25; out.push_back(f25 + f1 - f31); } for (float& f : out) { float f8 = (2.f * in[2] + 3.f * f * in[3]) * f + in[1]; if (f8 != 0.f) f -= (((f * in[3] + in[2]) * f + in[1]) * f + in[0]) / f8; } } else if (in[2] != 0.f) { float f23 = 0.5f * in[1] / in[2]; float f1 = f23 * f23 - (in[1] / in[2]); if (f1 >= 0.f) { f1 = std::sqrt(f1); out.push_back(-f23 - f1); out.push_back(-f23 + f1); } } else if (in[1] != 0.f) { out.push_back(-in[0] / in[1]); } return out.size() != 0; } bool CSteeringBehaviors::SolveQuartic(const rstl::reserved_vector& in, rstl::reserved_vector& out) { if (in[4] == 0.f) { rstl::reserved_vector newIn; newIn.push_back(in[0]); newIn.push_back(in[1]); newIn.push_back(in[2]); newIn.push_back(in[3]); return SolveCubic(newIn, out); } else { rstl::reserved_vector newIn; float f30 = in[3] / (4.f * in[4]); float f2 = in[1] / in[4]; float f29 = f30 * (8.f * f30 * f30 - 2.f * in[2] / in[4]) + f2; float f31 = -6.f * f30 * f30 + (in[2] / in[4]); float f28 = f30 * (f30 * (-3.f * f30 * f30 + (in[2] / in[4])) - f2) + (in[0] / in[4]); newIn.push_back(4.f * f28 * f31 - f29 * f29); newIn.push_back(-8.f * f28); newIn.push_back(-4.f * f31); newIn.push_back(8.f); rstl::reserved_vector newOut; if (SolveCubic(newIn, newOut)) { float f26 = 2.f * newOut.back() - f31; f31 = std::sqrt(f26); float f1; if (f31 == 0.f) { f1 = newOut.back() * newOut.back() - f28; if (f1 < 0.f) return false; f1 = std::sqrt(f1); } else { f1 = f29 / (2.f * f31); } float f1b = f26 - (newOut.back() + f1) * 4.f; f26 = f26 - (newOut.back() - f1) * 4.f; if (f1b >= 0.f) { f1b = std::sqrt(f1b); out.push_back((f31 - f1b) * 0.5f - f30); out.push_back((f31 + f1b) * 0.5f - f30); } if (f26 >= 0.f) { f1b = std::sqrt(f26); out.push_back((-f31 - f1b) * 0.5f - f30); out.push_back((-f31 + f1b) * 0.5f - f30); } for (float& f : out) { float f10 = ((3.f * in[3] + 4.f * f * in[4]) * f + 2.f * in[2]) * f + in[1]; if (f10 != 0.f) f -= ((((f * in[4] + in[3]) * f + in[2]) * f + in[1]) * f + in[0]) / f10; } if (out.size() > 2) { if (out[2] < out[0]) std::swap(out[2], out[0]); if (out[3] < out[1]) std::swap(out[3], out[1]); if (out[1] < out[0]) std::swap(out[1], out[0]); if (out[3] < out[2]) std::swap(out[3], out[2]); if (out[2] < out[1]) std::swap(out[2], out[1]); } } return out.size() != 0; } } bool CSteeringBehaviors::ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1, const zeus::CVector3f& v2, zeus::CVector3f& v3) { zeus::CVector3f posDiff = v1 - v0; float xPos, xNeg; if (SolveQuadratic(v2.magSquared() - f1 * f1, posDiff.dot(v2) * 2.f, posDiff.magSquared(), xPos, xNeg) && xNeg > 0.f) { v3 = v2 * xNeg + v1; return true; } return false; } bool CSteeringBehaviors::ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, zeus::CVector3f& v4) { rstl::reserved_vector newIn; rstl::reserved_vector newOut; zeus::CVector3f f7 = v1 - v0; newIn.push_back(f7.magSquared()); newIn.push_back(f7.dot(v2) * 2.f); newIn.push_back(f7.dot(v3) + v2.magSquared() - f1 * f1); newIn.push_back(v2.dot(v3)); newIn.push_back(v3.magSquared() * 0.25f); bool ret = false; if (SolveQuartic(newIn, newOut)) for (float& f : newOut) if (f > 0.f) { ret = true; v4 = v1 + v2 * f + 0.5f * f * f * v3; } return ret; } bool CSteeringBehaviors::ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2, const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, zeus::CVector3f& v4) { if (f1 > 0.f) { if (v2.canBeNormalized()) { zeus::CVector3f _12c = (v1 - v3).toVec2f(); if (_12c.canBeNormalized()) { zeus::CVector3f f25 = v1; zeus::CVector3f f22 = v2; float f17 = (f25 - v0).magnitude() / f1 - 0.f; float f18 = FLT_MAX; zeus::CVector3f _150 = _12c.normalized(); float f26 = _150.dot(f22); float f27 = _150.cross(zeus::skUp).dot(f22); for (float f19 = 0.f; f17 < f18 && f19 < 4.f;) { if (zeus::close_enough(f17, f2) || f17 < 0.f) { v4 = f25; return true; } f25 += f2 * f22; f18 = f17; _12c = (f25 - v3).toVec2f(); if (!_12c.canBeNormalized()) break; zeus::CVector3f _168 = _12c.normalized(); f22 = _168.cross(zeus::skUp) * f27 + f26 * _168; f19 += f2; f17 = (f25 - v0).magnitude() / f1 - f19; } } else { return ProjectLinearIntersection(v0, f1, v1, v2, v4); } } else { v4 = v1; return true; } } return false; } bool CSteeringBehaviors::ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2, const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, const zeus::CVector3f& v4, zeus::CVector3f& v5) { if (f1 > 0.f) { zeus::CVector3f _12c = (v1 - v4).toVec2f(); if (v2.canBeNormalized() && _12c.canBeNormalized()) { zeus::CVector3f f24 = v1; zeus::CVector3f f21 = v2; float f16 = (f24 - v0).magnitude() / f1 - 0.f; float f17 = FLT_MAX; zeus::CVector3f _150 = _12c.normalized(); float f25 = _150.dot(f21); float f26 = _150.cross(zeus::skUp).dot(f21); for (float f18 = 0.f; f16 < f17 && f18 < 4.f;) { if (zeus::close_enough(f16, f2) || f16 < 0.f) { v5 = f24; return true; } f24 += f2 * f21; f17 = f16; f18 += f2; f16 = (f24 - v0).magnitude() / f1 - f18; _12c = (f24 - v4).toVec2f(); if (!_12c.canBeNormalized()) break; zeus::CVector3f _168 = _12c.normalized(); f21 = _168.cross(zeus::skUp) * f26 + f25 * _168; } } else { return ProjectLinearIntersection(v0, f1, v1, v2, v3, v5); } } return false; } zeus::CVector3f CSteeringBehaviors::ProjectOrbitalPosition(const zeus::CVector3f& pos, const zeus::CVector3f& vel, const zeus::CVector3f& orbitPoint, float dt, float preThinkDt) { zeus::CVector3f usePos = pos; if (vel.canBeNormalized()) { zeus::CVector3f pointToPos = pos - orbitPoint; pointToPos.z() = 0.f; if (pointToPos.canBeNormalized()) { zeus::CVector3f useVel = vel; pointToPos.normalize(); float f29 = pointToPos.dot(useVel); float f30 = pointToPos.cross(zeus::skUp).dot(useVel); for (float curDt = 0.f; curDt < dt;) { usePos += preThinkDt * useVel; zeus::CVector3f usePointToPos = usePos - orbitPoint; usePointToPos.z() = 0.f; if (usePointToPos.canBeNormalized()) { usePointToPos.normalize(); useVel = usePointToPos.cross(zeus::skUp) * f30 + usePointToPos * f29; } curDt += std::min(dt - curDt, preThinkDt); } } } return usePos; } } // namespace metaforce ================================================ FILE: Runtime/Character/CSteeringBehaviors.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include #include namespace metaforce { class CPhysicsActor; class CStateManager; class CSteeringBehaviors { float x0_ = M_PIF / 2.f; public: zeus::CVector3f Flee(const CPhysicsActor& actor, const zeus::CVector3f& v0) const; zeus::CVector3f Seek(const CPhysicsActor& actor, const zeus::CVector3f& target) const; zeus::CVector3f Arrival(const CPhysicsActor& actor, const zeus::CVector3f& dest, float dampingRadius) const; zeus::CVector3f Pursuit(const CPhysicsActor& actor, const zeus::CVector3f& v0, const zeus::CVector3f& v1) const; zeus::CVector3f Separation(const CPhysicsActor& actor, const zeus::CVector3f& pos, float separation) const; zeus::CVector3f Alignment(const CPhysicsActor& actor, EntityList& list, const CStateManager& mgr) const; zeus::CVector3f Cohesion(const CPhysicsActor& actor, EntityList& list, float dampingRadius, const CStateManager& mgr) const; zeus::CVector2f Flee2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const; zeus::CVector2f Arrival2D(const CPhysicsActor& actor, const zeus::CVector2f& v0) const; static bool SolveQuadratic(float, float, float, float&, float&); static bool SolveCubic(const rstl::reserved_vector& in, rstl::reserved_vector& out); static bool SolveQuartic(const rstl::reserved_vector& in, rstl::reserved_vector& out); static bool ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1, const zeus::CVector3f& v2, zeus::CVector3f& v3); static bool ProjectLinearIntersection(const zeus::CVector3f& v0, float f1, const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, zeus::CVector3f& v4); static bool ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2, const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, zeus::CVector3f& v4); static bool ProjectOrbitalIntersection(const zeus::CVector3f& v0, float f1, float f2, const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, const zeus::CVector3f& v4, zeus::CVector3f& v5); static zeus::CVector3f ProjectOrbitalPosition(const zeus::CVector3f& pos, const zeus::CVector3f& vel, const zeus::CVector3f& orbitPoint, float dt, float preThinkDt); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CTimeScaleFunctions.cpp ================================================ #include "Runtime/Character/CTimeScaleFunctions.hpp" #include namespace metaforce { std::unique_ptr IVaryingAnimationTimeScale::Clone() const { return VClone(); } float CConstantAnimationTimeScale::VTimeScaleIntegral(float lowerLimit, float upperLimit) const { return (upperLimit - lowerLimit) * x4_scale; } float CConstantAnimationTimeScale::VFindUpperLimit(float lowerLimit, float root) const { return (root / x4_scale) + lowerLimit; } std::unique_ptr CConstantAnimationTimeScale::VClone() const { return std::make_unique(x4_scale); } std::unique_ptr CConstantAnimationTimeScale::VGetFunctionMirrored(float) const { return Clone(); } CLinearAnimationTimeScale::CLinearAnimationTimeScale(const CCharAnimTime& t1, float y1, const CCharAnimTime& t2, float y2) { float y2my1 = y2 - y1; float t2mt1 = (t2 - t1).GetSeconds(); x4_desc.x4_slope = y2my1 / t2mt1; x4_desc.x8_yIntercept = y1 - y2my1 / t2mt1 * t1.GetSeconds(); x4_desc.xc_t1 = t1.GetSeconds(); x4_desc.x10_t2 = t2.GetSeconds(); } std::unique_ptr CLinearAnimationTimeScale::CFunctionDescription::FunctionMirroredAround(float value) const { float slope = -x4_slope; float t1 = 2.f * value - x10_t2; float t2 = 2.f * value - xc_t1; float newYInt = x8_yIntercept - x4_slope * 2.f * value; float y1 = slope * t1 + newYInt; float y2 = slope * t2 + newYInt; return std::make_unique(t1, y1, t2, y2); } float CLinearAnimationTimeScale::VTimeScaleIntegral(float lowerLimit, float upperLimit) const { if (lowerLimit <= upperLimit) return TimeScaleIntegralWithSortedLimits(x4_desc, lowerLimit, upperLimit); else return -TimeScaleIntegralWithSortedLimits(x4_desc, upperLimit, lowerLimit); } float CLinearAnimationTimeScale::TimeScaleIntegralWithSortedLimits(const CFunctionDescription& desc, float lowerLimit, float upperLimit) { float lowerEval = desc.x4_slope * lowerLimit + desc.x8_yIntercept; float upperEval = desc.x4_slope * upperLimit + desc.x8_yIntercept; return (upperLimit - lowerLimit) * 0.5f * (lowerEval + upperEval); } float CLinearAnimationTimeScale::VFindUpperLimit(float lowerLimit, float root) const { return FindUpperLimitFromRoot(x4_desc, lowerLimit, root); } float CLinearAnimationTimeScale::FindUpperLimitFromRoot(const CFunctionDescription& desc, float lowerLimit, float root) { float M = 0.5f * desc.x4_slope; float upperLimit = lowerLimit; float m = 2.f * M; float lowerIntegration = M * lowerLimit * lowerLimit + desc.x8_yIntercept * lowerLimit; for (int i = 0; i < 16; ++i) { float factor = (M * upperLimit * upperLimit + desc.x8_yIntercept * upperLimit - lowerIntegration - root) / (m * upperLimit + desc.x8_yIntercept); upperLimit -= factor; if (zeus::close_enough(factor, 0.f)) return upperLimit; } return -1.f; } std::unique_ptr CLinearAnimationTimeScale::VClone() const { float y1 = x4_desc.x4_slope * x4_desc.xc_t1 + x4_desc.x8_yIntercept; float y2 = x4_desc.x4_slope * x4_desc.x10_t2 + x4_desc.x8_yIntercept; return std::make_unique(x4_desc.xc_t1, y1, x4_desc.x10_t2, y2); } std::unique_ptr CLinearAnimationTimeScale::VGetFunctionMirrored(float value) const { return x4_desc.FunctionMirroredAround(value); } } // namespace metaforce ================================================ FILE: Runtime/Character/CTimeScaleFunctions.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CCharAnimTime.hpp" namespace metaforce { enum class EVaryingAnimationTimeScaleType { Constant, Linear }; class IVaryingAnimationTimeScale { public: virtual ~IVaryingAnimationTimeScale() = default; virtual EVaryingAnimationTimeScaleType GetType() const = 0; virtual float VTimeScaleIntegral(float lowerLimit, float upperLimit) const = 0; virtual float VFindUpperLimit(float lowerLimit, float root) const = 0; virtual std::unique_ptr VClone() const = 0; virtual std::unique_ptr VGetFunctionMirrored(float value) const = 0; std::unique_ptr Clone() const; }; class CConstantAnimationTimeScale : public IVaryingAnimationTimeScale { private: float x4_scale; public: explicit CConstantAnimationTimeScale(float scale) : x4_scale(scale) {} EVaryingAnimationTimeScaleType GetType() const override { return EVaryingAnimationTimeScaleType::Constant; } float VTimeScaleIntegral(float lowerLimit, float upperLimit) const override; float VFindUpperLimit(float lowerLimit, float root) const override; std::unique_ptr VClone() const override; std::unique_ptr VGetFunctionMirrored(float value) const override; }; class CLinearAnimationTimeScale : public IVaryingAnimationTimeScale { struct CFunctionDescription { float x4_slope; float x8_yIntercept; float xc_t1; float x10_t2; std::unique_ptr FunctionMirroredAround(float value) const; } x4_desc; static float FindUpperLimitFromRoot(const CFunctionDescription& desc, float lowerLimit, float root); static float TimeScaleIntegralWithSortedLimits(const CFunctionDescription& desc, float lowerLimit, float upperLimit); public: explicit CLinearAnimationTimeScale(const CCharAnimTime& t1, float y1, const CCharAnimTime& t2, float y2); EVaryingAnimationTimeScaleType GetType() const override { return EVaryingAnimationTimeScaleType::Linear; } float VTimeScaleIntegral(float lowerLimit, float upperLimit) const override; float VFindUpperLimit(float lowerLimit, float root) const override; std::unique_ptr VClone() const override; std::unique_ptr VGetFunctionMirrored(float value) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CTransition.cpp ================================================ #include "Runtime/Character/CTransition.hpp" namespace metaforce { CTransition::CTransition(CInputStream& in) : x0_id(in.ReadLong()) , x4_animA(in.ReadLong()) , x8_animB(in.ReadLong()) , xc_trans(CMetaTransFactory::CreateMetaTrans(in)) {} } // namespace metaforce ================================================ FILE: Runtime/Character/CTransition.hpp ================================================ #pragma once #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Character/CMetaTransFactory.hpp" namespace metaforce { class CTransition { u32 x0_id; u32 x4_animA; u32 x8_animB; std::shared_ptr xc_trans; public: explicit CTransition(CInputStream& in); u32 GetAnimA() const { return x4_animA; } u32 GetAnimB() const { return x8_animB; } std::pair GetAnimPair() const { return {x4_animA, x8_animB}; } const std::shared_ptr& GetMetaTrans() const { return xc_trans; } }; } // namespace metaforce ================================================ FILE: Runtime/Character/CTransitionDatabase.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class IMetaTrans; class CTransitionDatabase { public: virtual ~CTransitionDatabase() = default; virtual const std::shared_ptr& GetMetaTrans(u32, u32) const = 0; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CTransitionDatabaseGame.cpp ================================================ #include "Runtime/Character/CTransitionDatabaseGame.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CHalfTransition.hpp" #include "Runtime/Character/CTransition.hpp" #include #include namespace metaforce { CTransitionDatabaseGame::CTransitionDatabaseGame(const std::vector& transitions, const std::vector& halfTransitions, std::shared_ptr defaultTrans) : x10_defaultTrans(std::move(defaultTrans)) { x14_transitions.reserve(transitions.size()); for (const CTransition& trans : transitions) x14_transitions.emplace_back(trans.GetAnimPair(), trans.GetMetaTrans()); std::sort(x14_transitions.begin(), x14_transitions.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); x24_halfTransitions.reserve(halfTransitions.size()); for (const CHalfTransition& trans : halfTransitions) x24_halfTransitions.emplace_back(trans.GetId(), trans.GetMetaTrans()); std::sort(x24_halfTransitions.begin(), x24_halfTransitions.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); } const std::shared_ptr& CTransitionDatabaseGame::GetMetaTrans(u32 a, u32 b) const { const auto it = rstl::binary_find(x14_transitions.cbegin(), x14_transitions.cend(), std::make_pair(a, b), [](const auto& p) { return p.first; }); if (it != x14_transitions.cend()) { return it->second; } const auto it2 = rstl::binary_find(x24_halfTransitions.cbegin(), x24_halfTransitions.cend(), b, [](const auto& p) { return p.first; }); if (it2 != x24_halfTransitions.cend()) { return it2->second; } return x10_defaultTrans; } } // namespace metaforce ================================================ FILE: Runtime/Character/CTransitionDatabaseGame.hpp ================================================ #pragma once #include #include #include #include "Runtime/Character/CTransitionDatabase.hpp" namespace metaforce { class CTransition; class CHalfTransition; class CTransitionDatabaseGame final : public CTransitionDatabase { std::shared_ptr x10_defaultTrans; std::vector, std::shared_ptr>> x14_transitions; std::vector>> x24_halfTransitions; public: CTransitionDatabaseGame(const std::vector& transitions, const std::vector& halfTransitions, std::shared_ptr defaultTrans); const std::shared_ptr& GetMetaTrans(u32, u32) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CTransitionManager.cpp ================================================ #include "Runtime/Character/CTransitionManager.hpp" #include "Runtime/Character/CTreeUtils.hpp" namespace metaforce { std::shared_ptr CTransitionManager::GetTransitionTree(const std::shared_ptr& a, const std::shared_ptr& b) const { return CTreeUtils::GetTransitionTree(a, b, x0_animCtx); } } // namespace metaforce ================================================ FILE: Runtime/Character/CTransitionManager.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CTransitionDatabaseGame.hpp" namespace metaforce { class CAnimTreeNode; class CRandom16; class CSimplePool; class CTransitionManager { CAnimSysContext x0_animCtx; public: explicit CTransitionManager(CAnimSysContext sysCtx) : x0_animCtx(std::move(sysCtx)) {} std::shared_ptr GetTransitionTree(const std::shared_ptr& a, const std::shared_ptr& b) const; }; } // namespace metaforce ================================================ FILE: Runtime/Character/CTreeUtils.cpp ================================================ #include "Runtime/Character/CTreeUtils.hpp" #include "Runtime/Character/CAnimSysContext.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CTransitionDatabaseGame.hpp" #include "Runtime/Character/IMetaTrans.hpp" namespace metaforce { std::shared_ptr CTreeUtils::GetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animCtx) { CAnimTreeEffectiveContribution contribA = a.lock()->GetContributionOfHighestInfluence(); CAnimTreeEffectiveContribution contribB = b.lock()->GetContributionOfHighestInfluence(); return animCtx.x0_transDB->GetMetaTrans(contribA.GetAnimDatabaseIndex(), contribB.GetAnimDatabaseIndex()) ->GetTransitionTree(a, b, animCtx); } } // namespace metaforce ================================================ FILE: Runtime/Character/CTreeUtils.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class CAnimTreeNode; struct CAnimSysContext; class CTreeUtils { public: static std::shared_ptr GetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animCtx); }; } // namespace metaforce ================================================ FILE: Runtime/Character/CharacterCommon.cpp ================================================ #include "CharacterCommon.hpp" using namespace std::literals; namespace metaforce::pas { std::string_view AnimationStateToStr(EAnimationState state) { switch (state) { case EAnimationState::Invalid: return "Invalid"sv; case EAnimationState::Fall: return "Fall"sv; case EAnimationState::Getup: return "Getup"sv; case EAnimationState::LieOnGround: return "LieOnGround"sv; case EAnimationState::Step: return "Step"sv; case EAnimationState::Death: return "Death"sv; case EAnimationState::Locomotion: return "Locomotion"sv; case EAnimationState::KnockBack: return "KnockBack"sv; case EAnimationState::MeleeAttack: return "MeleeAttack"sv; case EAnimationState::Turn: return "Turn"sv; case EAnimationState::LoopAttack: return "LoopAttack"sv; case EAnimationState::LoopReaction: return "LoopReaction"sv; case EAnimationState::GroundHit: return "GroundHit"sv; case EAnimationState::Generate: return "Generate"sv; case EAnimationState::Jump: return "Jump"sv; case EAnimationState::Hurled: return "Hurled"sv; case EAnimationState::Slide: return "Slide"sv; case EAnimationState::Taunt: return "Taunt"sv; case EAnimationState::Scripted: return "Scripted"sv; case EAnimationState::ProjectileAttack: return "ProjectileAttack"sv; case EAnimationState::Cover: return "Cover"sv; case EAnimationState::WallHang: return "WallHang"sv; case EAnimationState::AdditiveIdle: return "AdditiveIdle"sv; case EAnimationState::AdditiveAim: return "AdditiveAim"sv; case EAnimationState::AdditiveFlinch: return "AdditiveFlinch"sv; case EAnimationState::AdditiveReaction: return "AdditiveReaction"sv; default: return "[unknown]"; } } } // namespace metaforce::pas ================================================ FILE: Runtime/Character/CharacterCommon.hpp ================================================ #pragma once #include namespace metaforce { namespace pas { enum class ELocomotionType { Invalid = -1, Crouch = 0, Relaxed = 1, Lurk = 2, Combat = 3, Internal4 = 4, Internal5 = 5, Internal6 = 6, Internal7 = 7, Internal8 = 8, Internal9 = 9, Internal10 = 10, Internal11 = 11, Internal12 = 12, Internal13 = 13, Internal14 = 14 }; enum class ELocomotionAnim { Invalid = -1, Idle, Walk, Run, BackUp, StrafeLeft, StrafeRight, StrafeUp, StrafeDown }; enum class EAnimationState { Invalid = -1, Fall = 0, Getup = 1, LieOnGround = 2, Step = 3, Death = 4, Locomotion = 5, KnockBack = 6, MeleeAttack = 7, Turn = 8, LoopAttack = 9, LoopReaction = 10, GroundHit = 11, Generate = 12, Jump = 13, Hurled = 14, Slide = 15, Taunt = 16, Scripted = 17, ProjectileAttack = 18, Cover = 19, WallHang = 20, AdditiveIdle = 21, AdditiveAim = 22, AdditiveFlinch = 23, AdditiveReaction = 24 }; std::string_view AnimationStateToStr(EAnimationState state); enum class EHurledState { Invalid = -1, KnockIntoAir, KnockLoop, KnockDown, StrikeWall, StrikeWallFallLoop, OutOfStrikeWall, Six, Seven }; enum class EFallState { Invalid = -1, Zero, One, Two }; enum class EReactionType { Invalid = -1, Zero, One, Two, Three }; enum class EAdditiveReactionType { Invalid = -1, Electrocution, One, Two, IceBreakout, Four, Five, Six, Seven }; enum class EJumpType { Normal, One, Ambush }; enum class EJumpState { Invalid = -1, IntoJump, AmbushJump, Loop, OutOfJump, WallBounceLeft, WallBounceRight }; enum class EStepDirection { Invalid = -1, Forward = 0, Backward = 1, Left = 2, Right = 3, Up = 4, Down = 5 }; enum class EStepType { Normal = 0, Dodge = 1, BreakDodge = 2, RollDodge = 3 }; enum class ESeverity { Invalid = -1, Zero = 0, One = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6, Seven = 7, Eight = 8 }; enum class EGetupType { Invalid = -1, Zero = 0, One = 1, Two = 2 }; enum class ELoopState { Invalid = -1, Begin, Loop, End }; enum class ELoopAttackType { Invalid = -1, Zero, One, Two, Three }; enum class EGenerateType { Invalid = -1, Zero, One, Two, Three, Four, Five, Six, Seven, Eight }; enum class ESlideType { Invalid = -1, Zero = 0 }; enum class ETauntType { Invalid = -1, Zero, One, Two }; enum class ECoverState { Invalid = -1, IntoCover, Cover, Lean, OutOfCover }; enum class ECoverDirection { Invalid = -1, Left, Right }; enum class ETurnDirection { Invalid = -1, Right, Left }; enum class EWallHangState { Invalid = -1, IntoJump, JumpArc, JumpAirLoop, IntoWallHang, WallHang, Five, OutOfWallHang, OutOfWallHangTurn, DetachJumpLoop, DetachOutOfJump }; } // namespace pas enum class EBodyType { Invalid, BiPedal, Restricted, Flyer, Pitchable, RestrictedFlyer, WallWalker, NewFlyer }; enum class EBodyStateCmd { Getup, Step, Die, KnockDown, KnockBack, MeleeAttack, ProjectileAttack, LoopAttack, LoopReaction, LoopHitReaction, ExitState, LeanFromCover, NextState, MaintainVelocity, Generate, Hurled, Jump, Slide, Taunt, Scripted, Cover, WallHang, Locomotion, AdditiveIdle, AdditiveAim, AdditiveFlinch, AdditiveReaction, StopReaction }; } // namespace metaforce ================================================ FILE: Runtime/Character/IAnimReader.cpp ================================================ #include "Runtime/Character/IAnimReader.hpp" #include "Runtime/Character/CCharAnimTime.hpp" namespace metaforce { SAdvancementDeltas SAdvancementDeltas::Interpolate(const SAdvancementDeltas& a, const SAdvancementDeltas& b, float oldWeight, float newWeight) { float weightSum = oldWeight + newWeight; return {b.x0_posDelta * weightSum * 0.5f - a.x0_posDelta * (weightSum - 2.f) * 0.5f, zeus::CQuaternion::slerpShort(a.xc_rotDelta, b.xc_rotDelta, weightSum * 0.5f)}; } SAdvancementDeltas SAdvancementDeltas::Blend(const SAdvancementDeltas& a, const SAdvancementDeltas& b, float w) { return {b.x0_posDelta * w - a.x0_posDelta * (1.f - w), zeus::CQuaternion::slerpShort(a.xc_rotDelta, b.xc_rotDelta, w)}; } SAdvancementResults IAnimReader::VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const { SAdvancementResults ret; ret.x0_remTime = a; return ret; } size_t IAnimReader::GetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (time.GreaterThanZero()) { return VGetBoolPOIList(time, listOut, capacity, iterator, unk); } return 0; } size_t IAnimReader::GetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (time.GreaterThanZero()) { return VGetInt32POIList(time, listOut, capacity, iterator, unk); } return 0; } size_t IAnimReader::GetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (time.GreaterThanZero()) { return VGetParticlePOIList(time, listOut, capacity, iterator, unk); } return 0; } size_t IAnimReader::GetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32 unk) const { if (time.GreaterThanZero()) { return VGetSoundPOIList(time, listOut, capacity, iterator, unk); } return 0; } } // namespace metaforce ================================================ FILE: Runtime/Character/IAnimReader.hpp ================================================ #pragma once #include #include #include #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CAllFormatsAnimSource.hpp" #include "Runtime/Character/CCharAnimTime.hpp" #include "Runtime/Character/CParticleData.hpp" #include #include namespace metaforce { class CBoolPOINode; class CInt32POINode; class CParticlePOINode; class CSegId; class CSegIdList; class CSegStatementSet; class CSoundPOINode; struct SAdvancementDeltas { zeus::CVector3f x0_posDelta; zeus::CQuaternion xc_rotDelta; static SAdvancementDeltas Interpolate(const SAdvancementDeltas& a, const SAdvancementDeltas& b, float oldWeight, float newWeight); static SAdvancementDeltas Blend(const SAdvancementDeltas& a, const SAdvancementDeltas& b, float w); }; struct SAdvancementResults { CCharAnimTime x0_remTime; SAdvancementDeltas x8_deltas; }; class CSteadyStateAnimInfo { CCharAnimTime x0_duration; zeus::CVector3f x8_offset; bool x14_looping = false; public: CSteadyStateAnimInfo(bool looping, const CCharAnimTime& duration, const zeus::CVector3f& offset) : x0_duration(duration), x8_offset(offset), x14_looping(looping) {} const CCharAnimTime& GetDuration() const { return x0_duration; } const zeus::CVector3f& GetOffset() const { return x8_offset; } bool IsLooping() const { return x14_looping; } }; struct CAnimTreeEffectiveContribution { float x0_contributionWeight; std::string x4_name; CSteadyStateAnimInfo x14_ssInfo; CCharAnimTime x2c_remTime; u32 x34_dbIdx; public: CAnimTreeEffectiveContribution(float cweight, std::string_view name, const CSteadyStateAnimInfo& ssInfo, const CCharAnimTime& remTime, u32 dbIdx) : x0_contributionWeight(cweight), x4_name(name), x14_ssInfo(ssInfo), x2c_remTime(remTime), x34_dbIdx(dbIdx) {} float GetContributionWeight() const { return x0_contributionWeight; } std::string_view GetPrimitiveName() const { return x4_name; } const CSteadyStateAnimInfo& GetSteadyStateAnimInfo() const { return x14_ssInfo; } const CCharAnimTime& GetTimeRemaining() const { return x2c_remTime; } u32 GetAnimDatabaseIndex() const { return x34_dbIdx; } }; template class TSubAnimTypeToken : public TLockedToken {}; template <> class TSubAnimTypeToken : public TLockedToken { public: // Converting constructor TSubAnimTypeToken(const TLockedToken& token) : TLockedToken(token) {} CAnimSource* GetObj() override { CAllFormatsAnimSource* source = reinterpret_cast(TLockedToken::GetObj()); return &source->GetAsCAnimSource(); } const CAnimSource* GetObj() const override { return const_cast*>(this)->GetObj(); } }; template <> class TSubAnimTypeToken : public TLockedToken { public: // Converting constructor TSubAnimTypeToken(const TLockedToken& token) : TLockedToken(token) {} CFBStreamedCompression* GetObj() override { CAllFormatsAnimSource* source = reinterpret_cast(TLockedToken::GetObj()); return &source->GetAsCFBStreamedCompression(); } const CFBStreamedCompression* GetObj() const override { return const_cast*>(this)->GetObj(); } }; class IAnimReader { public: virtual ~IAnimReader() = default; virtual bool IsCAnimTreeNode() const { return false; } virtual SAdvancementResults VAdvanceView(const CCharAnimTime& a) = 0; virtual CCharAnimTime VGetTimeRemaining() const = 0; virtual CSteadyStateAnimInfo VGetSteadyStateAnimInfo() const = 0; virtual bool VHasOffset(const CSegId& seg) const = 0; virtual zeus::CVector3f VGetOffset(const CSegId& seg) const = 0; virtual zeus::CQuaternion VGetRotation(const CSegId& seg) const = 0; virtual size_t VGetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const = 0; virtual size_t VGetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const = 0; virtual size_t VGetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const = 0; virtual size_t VGetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const = 0; virtual bool VGetBoolPOIState(std::string_view name) const = 0; virtual s32 VGetInt32POIState(std::string_view name) const = 0; virtual CParticleData::EParentedMode VGetParticlePOIState(std::string_view name) const = 0; virtual void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut) const = 0; virtual void VGetSegStatementSet(const CSegIdList& list, CSegStatementSet& setOut, const CCharAnimTime& time) const = 0; virtual std::unique_ptr VClone() const = 0; virtual std::optional> VSimplified() { return {}; } virtual void VSetPhase(float) = 0; virtual SAdvancementResults VGetAdvancementResults(const CCharAnimTime& a, const CCharAnimTime& b) const; size_t GetBoolPOIList(const CCharAnimTime& time, CBoolPOINode* listOut, size_t capacity, size_t iterator, u32) const; size_t GetInt32POIList(const CCharAnimTime& time, CInt32POINode* listOut, size_t capacity, size_t iterator, u32) const; size_t GetParticlePOIList(const CCharAnimTime& time, CParticlePOINode* listOut, size_t capacity, size_t iterator, u32) const; size_t GetSoundPOIList(const CCharAnimTime& time, CSoundPOINode* listOut, size_t capacity, size_t iterator, u32) const; std::optional> Simplified() { return VSimplified(); } std::unique_ptr Clone() const { return VClone(); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/IMetaAnim.cpp ================================================ #include "Runtime/Character/IMetaAnim.hpp" #include #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CCharAnimTime.hpp" #include "Runtime/Character/IAnimReader.hpp" namespace metaforce { std::shared_ptr IMetaAnim::GetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const { if (orders.x44_singleAdvance) { std::shared_ptr tree = VGetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders()); if (orders.x44_singleAdvance->IsTime() || orders.x44_singleAdvance->IsString()) { CCharAnimTime time = GetTime(*orders.x44_singleAdvance, *tree); AdvanceAnim(*tree, time); } return tree; } return VGetAnimationTree(animSys, CMetaAnimTreeBuildOrders::NoSpecialOrders()); } void IMetaAnim::AdvanceAnim(IAnimReader& anim, const CCharAnimTime& dt) { CCharAnimTime remDt = dt; while (remDt > CCharAnimTime()) { SAdvancementResults res = anim.VAdvanceView(remDt); remDt = res.x0_remTime; } } CCharAnimTime IMetaAnim::GetTime(const CPreAdvanceIndicator& ind, const IAnimReader& anim) { if (ind.IsTime()) { return ind.GetTime(); } std::array nodes; const CCharAnimTime rem = anim.VGetTimeRemaining(); const size_t count = anim.VGetBoolPOIList(rem, nodes.data(), nodes.size(), 0, 0); const char* cmpStr = ind.GetString(); for (size_t i = 0; i < count; ++i) { const CBoolPOINode& node = nodes[i]; if (node.GetString() != cmpStr || !node.GetValue()) { continue; } return node.GetTime(); } return {}; } } // namespace metaforce ================================================ FILE: Runtime/Character/IMetaAnim.hpp ================================================ #pragma once #include #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Character/CCharAnimTime.hpp" namespace metaforce { class CAnimTreeNode; class CPrimitive; class IAnimReader; struct CAnimSysContext; struct CMetaAnimTreeBuildOrders; enum class EMetaAnimType { Play, Blend, PhaseBlend, Random, Sequence }; class CPreAdvanceIndicator { bool x0_isTime; CCharAnimTime x4_time; const char* xc_string; /* u32 x10_; u32 x14_; u32 x18_fireTime; u32 x1c_damageDelay; u32 x20_; u32 x24_; u32 x28_; u32 x2c_; u32 x30_; u32 x34_; u32 x38_; u16 x3c_; */ public: explicit CPreAdvanceIndicator(const CCharAnimTime& time) : x0_isTime(true), x4_time(time) {} explicit CPreAdvanceIndicator(const char* string) : x0_isTime(false), xc_string(string) {} const char* GetString() const { return xc_string; } bool IsString() const { return !x0_isTime; } const CCharAnimTime& GetTime() const { return x4_time; } bool IsTime() const { return x0_isTime; } }; struct CMetaAnimTreeBuildOrders { std::optional x0_recursiveAdvance; std::optional x44_singleAdvance; static CMetaAnimTreeBuildOrders NoSpecialOrders() { return {}; } static CMetaAnimTreeBuildOrders PreAdvanceForAll(const CPreAdvanceIndicator& ind) { CMetaAnimTreeBuildOrders ret; ret.x44_singleAdvance.emplace(ind); return ret; } }; class IMetaAnim { public: virtual ~IMetaAnim() = default; virtual std::shared_ptr GetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const; virtual void GetUniquePrimitives(std::set& primsOut) const = 0; virtual EMetaAnimType GetType() const = 0; virtual std::shared_ptr VGetAnimationTree(const CAnimSysContext& animSys, const CMetaAnimTreeBuildOrders& orders) const = 0; static void AdvanceAnim(IAnimReader& anim, const CCharAnimTime& dt); static CCharAnimTime GetTime(const CPreAdvanceIndicator& ind, const IAnimReader& anim); }; } // namespace metaforce ================================================ FILE: Runtime/Character/IMetaTrans.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class CAnimTreeNode; struct CAnimSysContext; enum class EMetaTransType { MetaAnim, Trans, PhaseTrans, Snap }; class IMetaTrans { public: virtual ~IMetaTrans() = default; virtual std::shared_ptr VGetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) const = 0; virtual EMetaTransType GetType() const = 0; std::shared_ptr GetTransitionTree(const std::weak_ptr& a, const std::weak_ptr& b, const CAnimSysContext& animSys) { return VGetTransitionTree(a, b, animSys); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/IVaryingAnimationTimeScale.hpp ================================================ #pragma once #include #include "Runtime/Character/CCharAnimTime.hpp" namespace metaforce { class IVaryingAnimationTimeScale { public: virtual ~IVaryingAnimationTimeScale() = default; virtual u32 GetType() const = 0; virtual float VTimeScaleIntegral(const float&, const float&) const = 0; virtual float VFindUpperLimit(const float&, const float&) const = 0; virtual std::unique_ptr VClone() const = 0; virtual std::unique_ptr VGetFunctionMirrored(const float&) const = 0; CCharAnimTime FindUpperLimit(const CCharAnimTime& a, const CCharAnimTime& b) const { return VFindUpperLimit(a, b); } CCharAnimTime TimeScaleIntegral(const CCharAnimTime& a, const CCharAnimTime& b) const { return VTimeScaleIntegral(a, b); } std::unique_ptr Clone() const { return VClone(); } }; } // namespace metaforce ================================================ FILE: Runtime/Character/TSegIdMap.hpp ================================================ #pragma once #include #include #include #include "Runtime/Character/CSegId.hpp" namespace metaforce { template class TSegIdMap { CSegId x0_boneCount = 0; CSegId x1_capacity = 0; u32 x4_maxCapacity = 100; std::array, 100> x8_indirectionMap; std::unique_ptr xd0_bones; CSegId xd4_curPrevBone = 0; public: explicit TSegIdMap(const CSegId& capacity) : x1_capacity(capacity), xd0_bones(new T[capacity]) {} T& operator[](const CSegId& id) { return SetElement(id); } const T& operator[](const CSegId& id) const { return xd0_bones[x8_indirectionMap[id].second]; } T& SetElement(const CSegId& id, T&& obj) { size_t idx; if (HasElement(id)) { idx = x8_indirectionMap[id].second; } else { x8_indirectionMap[id] = std::make_pair(xd4_curPrevBone, x0_boneCount); xd4_curPrevBone = id; idx = x0_boneCount; ++x0_boneCount; } xd0_bones[idx] = std::move(obj); return xd0_bones[idx]; } T& SetElement(const CSegId& id) { size_t idx; if (HasElement(id)) { idx = x8_indirectionMap[id].second; } else { x8_indirectionMap[id] = std::make_pair(xd4_curPrevBone, x0_boneCount); xd4_curPrevBone = id; idx = x0_boneCount; ++x0_boneCount; } return xd0_bones[idx]; } void DelElement(const CSegId& id) { if (!HasElement(id)) { return; } if (id == xd4_curPrevBone) { xd4_curPrevBone = x8_indirectionMap[id].first; } x8_indirectionMap[id] = {}; --x0_boneCount; } bool HasElement(const CSegId& id) const { return x8_indirectionMap[id].first.IsValid(); } u32 GetCapacity() const { return x1_capacity; } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CAABoxFilter.cpp ================================================ #include "Runtime/Collision/CAABoxFilter.hpp" #include "Runtime/Collision/CCollisionInfoList.hpp" #include "Runtime/Collision/CollisionUtil.hpp" namespace metaforce { void CAABoxFilter::Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const { FilterBoxFloorCollisions(in, out); } void CAABoxFilter::FilterBoxFloorCollisions(const CCollisionInfoList& in, CCollisionInfoList& out) { float minZ = 10000.f; for (const CCollisionInfo& info : in) { if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Wall) && info.GetPoint().z() < minZ) minZ = info.GetPoint().z(); } CCollisionInfoList temp; for (const CCollisionInfo& info : in) { if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Floor)) { if (info.GetPoint().z() < minZ) temp.Add(info, false); } else { temp.Add(info, false); } } CollisionUtil::AddAverageToFront(temp, out); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CAABoxFilter.hpp ================================================ #pragma once #include "Runtime/Collision/ICollisionFilter.hpp" namespace metaforce { class CCollisionInfoList; class CAABoxFilter : public ICollisionFilter { public: explicit CAABoxFilter(CActor& actor) : ICollisionFilter(actor) {} void Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const override; static void FilterBoxFloorCollisions(const CCollisionInfoList& in, CCollisionInfoList& out); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CAreaOctTree.cpp ================================================ #include "Runtime/Collision/CAreaOctTree.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include "Runtime/Streams/IOStreams.hpp" #include #include #include #include #include namespace metaforce { static bool _close_enough(float f1, float f2, float epsilon) { return std::fabs(f1 - f2) <= epsilon; } static bool BoxLineTest(const zeus::CAABox& aabb, const zeus::CLine& line, float& lT, float& hT) { zeus::simd_floats aabbMinF(aabb.min.mSimd); zeus::simd_floats aabbMaxF(aabb.max.mSimd); zeus::simd_floats lineOrigin(line.origin.mSimd); zeus::simd_floats lineDir(line.dir.mSimd); const float* aabbMin = aabbMinF.data(); const float* aabbMax = aabbMaxF.data(); const float* lorigin = lineOrigin.data(); const float* ldir = lineDir.data(); lT = -FLT_MAX; hT = FLT_MAX; for (int i = 0; i < 3; ++i) { if (_close_enough(*ldir, 0.f, 0.000099999997f)) if (*lorigin < *aabbMin || *lorigin > *aabbMax) return false; if (*ldir < 0.f) { if (*aabbMax - *lorigin < lT * *ldir) lT = (*aabbMax - *lorigin) * 1.f / *ldir; if (*aabbMin - *lorigin > hT * *ldir) hT = (*aabbMin - *lorigin) * 1.f / *ldir; } else { if (*aabbMin - *lorigin > lT * *ldir) lT = (*aabbMin - *lorigin) * 1.f / *ldir; if (*aabbMax - *lorigin < hT * *ldir) hT = (*aabbMax - *lorigin) * 1.f / *ldir; } ++aabbMin; ++aabbMax; ++lorigin; ++ldir; } return lT <= hT; } constexpr std::array SomeIndexA{1, 2, 4}; constexpr std::array SomeIndexB{1, 2, 0}; constexpr std::array, 8> SomeIndexC{{ {0, 1, 2, 4, 5, 6, 8, 0xA}, {0, 1, 2, 3, 5, 6, 8, 0xA}, {0, 1, 2, 4, 5, 6, 9, 0xB}, {0, 1, 2, 3, 5, 6, 9, 0xC}, {0, 1, 2, 4, 5, 7, 8, 0xD}, {0, 1, 2, 3, 5, 7, 8, 0xE}, {0, 1, 2, 4, 5, 7, 9, 0xF}, {0, 1, 2, 3, 5, 7, 9, 0xF}, }}; constexpr std::array>, 16> SubdivIndex{{ {0, {0, 0, 0}}, {1, {0, 0, 0}}, {1, {1, 0, 0}}, {2, {0, 1, 0}}, {2, {1, 0, 0}}, {1, {2, 0, 0}}, {2, {0, 2, 0}}, {2, {2, 0, 0}}, {2, {2, 1, 0}}, {2, {1, 2, 0}}, {3, {0, 2, 1}}, {3, {1, 0, 2}}, {3, {0, 1, 2}}, {3, {2, 1, 0}}, {3, {2, 0, 1}}, {3, {1, 2, 0}}, }}; bool CAreaOctTree::Node::LineTestInternal(const zeus::CLine& line, const CMaterialFilter& filter, float lT, float hT, float maxT, const zeus::CVector3f& vec) const { float lowT = (1.f - FLT_EPSILON * 100.f) * lT; float highT = (1.f + FLT_EPSILON * 100.f) * hT; if (maxT != 0.f) { if (lowT < 0.f) lowT = 0.f; if (highT > maxT) highT = maxT; if (lowT > highT) return true; } if (x20_nodeType == ETreeType::Leaf) { TriListReference triList = GetTriangleArray(); for (u16 i = 0; i < triList.GetSize(); ++i) { CCollisionSurface triangle = x1c_owner.GetMasterListTriangle(triList.GetAt(i)); // https://en.wikipedia.org/wiki/Möller–Trumbore_intersection_algorithm // Find vectors for two edges sharing V0 zeus::CVector3f e0 = triangle.GetVert(1) - triangle.GetVert(0); zeus::CVector3f e1 = triangle.GetVert(2) - triangle.GetVert(0); // Begin calculating determinant - also used to calculate u parameter zeus::CVector3f P = line.dir.cross(e1); float det = P.dot(e0); // If determinant is near zero, ray lies in plane of triangle // or ray is parallel to plane of triangle if (std::fabs(det) < (FLT_EPSILON * 10.f)) continue; float invDet = 1.f / det; // Calculate distance from V1 to ray origin zeus::CVector3f T = line.origin - triangle.GetVert(0); // Calculate u parameter and test bound float u = invDet * T.dot(P); // The intersection lies outside of the triangle if (u < 0.f || u > 1.f) continue; // Prepare to test v parameter zeus::CVector3f Q = T.cross(e0); // Calculate T parameter and test bound float t = invDet * Q.dot(e1); if (t >= highT || t < lowT) continue; // Calculate V parameter and test bound float v = invDet * Q.dot(line.dir); if (v < 0.f || u + v > 1.f) continue; // Do material filter CMaterialList matList(triangle.GetSurfaceFlags()); if (filter.Passes(matList)) return false; } } else if (x20_nodeType == ETreeType::Branch) { if (GetChildFlags() == 0xA) // 2 leaves { for (int i = 0; i < 2; ++i) { Node child = GetChild(i); float tf1 = lT; float tf2 = hT; if (BoxLineTest(child.GetBoundingBox(), line, tf1, tf2)) if (!child.LineTestInternal(line, filter, tf1, tf2, maxT, vec)) return false; } return true; } zeus::CVector3f center = x0_aabb.center(); zeus::CVector3f r6 = line.origin + lT * line.dir; zeus::CVector3f r7 = line.origin + hT * line.dir; zeus::CVector3f r9 = vec * (center - line.origin); int r28 = 0; int r25 = 0; int r26 = 0; for (size_t i = 0; i < 3; ++i) { if (r6[i] >= center[i]) r28 |= SomeIndexA[i]; if (r7[i] >= center[i]) r25 |= SomeIndexA[i]; if (r9[i] < r9[SomeIndexB[i]]) r26 |= SomeIndexA[i]; } float f21 = lT; int r26b = r28; const std::pair>& idx = SubdivIndex[SomeIndexC[r26][r28 ^ r25]]; for (int i = 0; i <= idx.first; ++i) { float f22 = (i < idx.first) ? r9[idx.second[i]] : hT; if (f22 > lowT && f21 <= f22) { Node child = GetChild(r26b); if (child.x20_nodeType != ETreeType::Invalid) if (!child.LineTestInternal(line, filter, f21, f22, maxT, vec)) return false; } if (i < idx.first) r26b ^= 1 << idx.second[i]; f21 = f22; } } return true; } void CAreaOctTree::Node::LineTestExInternal(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res, float lT, float hT, float maxT, const zeus::CVector3f& dirRecip) const { float lowT = (1.f - FLT_EPSILON * 100.f) * lT; float highT = (1.f + FLT_EPSILON * 100.f) * hT; if (maxT != 0.f) { if (lowT < 0.f) lowT = 0.f; if (highT > maxT) highT = maxT; if (lowT > highT) return; } if (x20_nodeType == ETreeType::Leaf) { TriListReference triList = GetTriangleArray(); float bestT = highT; bool foundTriangle = false; SRayResult tmpRes; for (u16 i = 0; i < triList.GetSize(); ++i) { CCollisionSurface triangle = x1c_owner.GetMasterListTriangle(triList.GetAt(i)); // https://en.wikipedia.org/wiki/Möller–Trumbore_intersection_algorithm // Find vectors for two edges sharing V0 zeus::CVector3f e0 = triangle.GetVert(1) - triangle.GetVert(0); zeus::CVector3f e1 = triangle.GetVert(2) - triangle.GetVert(0); // Begin calculating determinant - also used to calculate u parameter zeus::CVector3f P = line.dir.cross(e1); float det = P.dot(e0); // If determinant is near zero, ray lies in plane of triangle // or ray is parallel to plane of triangle if (std::fabs(det) < (FLT_EPSILON * 10.f)) continue; float invDet = 1.f / det; // Calculate distance from V1 to ray origin zeus::CVector3f T = line.origin - triangle.GetVert(0); // Calculate u parameter and test bound float u = invDet * T.dot(P); // The intersection lies outside of the triangle if (u < 0.f || u > 1.f) continue; // Prepare to test v parameter zeus::CVector3f Q = T.cross(e0); // Calculate T parameter and test bound float t = invDet * Q.dot(e1); if (t >= bestT || t < lowT) continue; // Calculate V parameter and test bound float v = invDet * Q.dot(line.dir); if (v < 0.f || u + v > 1.f) continue; // Do material filter CMaterialList matList(triangle.GetSurfaceFlags()); if (filter.Passes(matList) && t <= bestT) { bestT = t; foundTriangle = true; tmpRes.x10_surface.emplace(triangle); tmpRes.x3c_t = t; } } if (foundTriangle) { res = tmpRes; res.x0_plane = res.x10_surface->GetPlane(); } } else if (x20_nodeType == ETreeType::Branch) { if (GetChildFlags() == 0xA) // 2 leaves { std::array tmpRes; for (int i = 0; i < 2; ++i) { Node child = GetChild(i); float tf1 = lT; float tf2 = hT; if (BoxLineTest(child.GetBoundingBox(), line, tf1, tf2)) child.LineTestExInternal(line, filter, tmpRes[i], tf1, tf2, maxT, dirRecip); } if (!tmpRes[0].x10_surface && !tmpRes[1].x10_surface) { res = SRayResult(); } else if (tmpRes[0].x10_surface && tmpRes[1].x10_surface) { if (tmpRes[0].x3c_t < tmpRes[1].x3c_t) res = tmpRes[0]; else res = tmpRes[1]; } else if (tmpRes[0].x10_surface) { res = tmpRes[0]; } else { res = tmpRes[1]; } if (res.x3c_t > highT) res = SRayResult(); return; } zeus::CVector3f center = x0_aabb.center(); // r26 zeus::CVector3f lowPoint = line.origin + lT * line.dir; zeus::CVector3f highPoint = line.origin + hT * line.dir; std::array comps{-1, -1, -1, 0}; std::array compT; int numComps = 0; for (size_t i = 0; i < compT.size(); ++i) { if (lowPoint[i] >= center[i] || highPoint[i] <= center[i]) { if (highPoint[i] >= center[i] || lowPoint[i] <= center[i]) { continue; } } if (_close_enough(line.dir[i], 0.f, 0.000099999997f)) { continue; } comps[numComps++] = static_cast(i); compT[i] = dirRecip[i] * (center[i] - line.origin[i]); } // Sort componentT least to greatest switch (numComps) { default: return; case 0: case 1: break; case 2: if (compT[comps[1]] < compT[comps[0]]) std::swap(comps[1], comps[0]); break; case 3: if (compT[0] < compT[1]) { if (compT[0] >= compT[2]) { comps[0] = 2; comps[1] = 0; comps[2] = 1; } else if (compT[1] < compT[2]) { comps[0] = 0; comps[1] = 1; comps[2] = 2; } else { comps[0] = 0; comps[1] = 2; comps[2] = 1; } } else { if (compT[1] >= compT[2]) { comps[0] = 2; comps[1] = 1; comps[2] = 0; } else if (compT[0] < compT[2]) { comps[0] = 1; comps[1] = 0; comps[2] = 2; } else { comps[0] = 1; comps[1] = 2; comps[2] = 0; } } break; } zeus::CVector3f lineStart = line.origin + (lT * line.dir); int selector = 0; if (lineStart.x() >= center.x()) selector = 1; if (lineStart.y() >= center.y()) selector |= 1 << 1; if (lineStart.z() >= center.z()) selector |= 1 << 2; float tmpLoT = lT; for (int i = -1; i < numComps; ++i) { if (i >= 0) selector ^= 1 << comps[i]; float tmpHiT = (i < numComps - 1) ? compT[comps[i + 1]] : hT; if (tmpHiT > lowT && tmpLoT <= tmpHiT) { Node child = GetChild(selector); if (child.x20_nodeType != ETreeType::Invalid) child.LineTestExInternal(line, filter, res, tmpLoT, tmpHiT, maxT, dirRecip); if (res.x10_surface) { if (res.x3c_t > highT) res = SRayResult(); break; } } tmpLoT = tmpHiT; } } } bool CAreaOctTree::Node::LineTest(const zeus::CLine& line, const CMaterialFilter& filter, float length) const { if (x20_nodeType == ETreeType::Invalid) return true; float f1 = 0.f; float f2 = 0.f; if (!BoxLineTest(x0_aabb, line, f1, f2)) return true; zeus::CVector3f recip = 1.f / line.dir; return LineTestInternal(line, filter, f1 - 0.000099999997f, f2 + 0.000099999997f, length, recip); } void CAreaOctTree::Node::LineTestEx(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res, float length) const { if (x20_nodeType == ETreeType::Invalid) return; float lT = 0.f; float hT = 0.f; if (!BoxLineTest(x0_aabb, line, lT, hT)) return; zeus::CVector3f recip = 1.f / line.dir; LineTestExInternal(line, filter, res, lT - 0.000099999997f, hT + 0.000099999997f, length, recip); } CAreaOctTree::Node CAreaOctTree::Node::GetChild(int idx) const { u16 flags = *reinterpret_cast(x18_ptr); const u32* offsets = reinterpret_cast(x18_ptr + 4); ETreeType type = ETreeType((flags >> (2 * idx)) & 0x3); if (type == ETreeType::Branch) { zeus::CAABox pos, neg, res; x0_aabb.splitZ(neg, pos); if (idx & 4) { zeus::CAABox(pos).splitY(neg, pos); if (idx & 2) { zeus::CAABox(pos).splitX(neg, pos); if (idx & 1) res = pos; else res = neg; } else { zeus::CAABox(neg).splitX(neg, pos); if (idx & 1) res = pos; else res = neg; } } else { zeus::CAABox(neg).splitY(neg, pos); if (idx & 2) { zeus::CAABox(pos).splitX(neg, pos); if (idx & 1) res = pos; else res = neg; } else { zeus::CAABox(neg).splitX(neg, pos); if (idx & 1) res = pos; else res = neg; } } return Node(x18_ptr + offsets[idx] + 36, res, x1c_owner, ETreeType::Branch); } else if (type == ETreeType::Leaf) { const float* aabb = reinterpret_cast(x18_ptr + offsets[idx] + 36); zeus::CAABox aabbObj(aabb[0], aabb[1], aabb[2], aabb[3], aabb[4], aabb[5]); return Node(aabb, aabbObj, x1c_owner, ETreeType::Leaf); } else { return Node(nullptr, zeus::skNullBox, x1c_owner, ETreeType::Invalid); } } void CAreaOctTree::SwapTreeNode(u8* ptr, Node::ETreeType type) { if (type == Node::ETreeType::Branch) { u16* typeBits = reinterpret_cast(ptr); *typeBits = CBasics::SwapBytes(*typeBits); u32* offsets = reinterpret_cast(ptr + 4); for (int i = 0; i < 8; ++i) { Node::ETreeType ctype = Node::ETreeType((*typeBits >> (2 * i)) & 0x3); offsets[i] = CBasics::SwapBytes(offsets[i]); SwapTreeNode(ptr + offsets[i] + 36, ctype); } } else if (type == Node::ETreeType::Leaf) { float* aabb = reinterpret_cast(ptr); aabb[0] = CBasics::SwapBytes(aabb[0]); aabb[1] = CBasics::SwapBytes(aabb[1]); aabb[2] = CBasics::SwapBytes(aabb[2]); aabb[3] = CBasics::SwapBytes(aabb[3]); aabb[4] = CBasics::SwapBytes(aabb[4]); aabb[5] = CBasics::SwapBytes(aabb[5]); u16* countIdxs = reinterpret_cast(ptr + 24); *countIdxs = CBasics::SwapBytes(*countIdxs); for (u16 i = 0; i < *countIdxs; ++i) countIdxs[i + 1] = CBasics::SwapBytes(countIdxs[i + 1]); } } CAreaOctTree::CAreaOctTree(const zeus::CAABox& aabb, Node::ETreeType treeType, const u8* buf, const u8* treeBuf, u32 matCount, const u32* materials, const u8* vertMats, const u8* edgeMats, const u8* polyMats, u32 edgeCount, const CCollisionEdge* edges, u32 polyCount, const u16* polyEdges, u32 vertCount, const float* verts) : x0_aabb(aabb) , x18_treeType(treeType) , x1c_buf(buf) , x20_treeBuf(treeBuf) , x24_matCount(matCount) , x28_materials(materials) , x2c_vertMats(vertMats) , x30_edgeMats(edgeMats) , x34_polyMats(polyMats) , x38_edgeCount(edgeCount) , x3c_edges(edges) , x40_polyCount(polyCount) , x44_polyEdges(polyEdges) , x48_vertCount(vertCount) , x4c_verts(verts) { SwapTreeNode(const_cast(x20_treeBuf), treeType); for (u32 i = 0; i < matCount; ++i) const_cast(x28_materials)[i] = CBasics::SwapBytes(x28_materials[i]); for (u32 i = 0; i < edgeCount; ++i) const_cast(x3c_edges)[i].swapBig(); for (u32 i = 0; i < polyCount; ++i) const_cast(x44_polyEdges)[i] = CBasics::SwapBytes(x44_polyEdges[i]); for (u32 i = 0; i < vertCount * 3; ++i) const_cast(x4c_verts)[i] = CBasics::SwapBytes(x4c_verts[i]); } std::unique_ptr CAreaOctTree::MakeFromMemory(const u8* buf, unsigned int size) { CMemoryInStream r(buf + 8, size - 8, CMemoryInStream::EOwnerShip::NotOwned); r.ReadLong(); r.ReadLong(); zeus::CAABox aabb = r.Get(); Node::ETreeType nodeType = Node::ETreeType(r.ReadLong()); u32 treeSize = r.ReadLong(); const u8* cur = reinterpret_cast(buf) + 8 + r.GetReadPosition(); const u8* treeBuf = cur; cur += treeSize; u32 matCount = CBasics::SwapBytes(*reinterpret_cast(cur)); cur += 4; const u32* matBuf = reinterpret_cast(cur); cur += 4 * matCount; u32 vertMatsCount = CBasics::SwapBytes(*reinterpret_cast(cur)); cur += 4; const u8* vertMatsBuf = cur; cur += vertMatsCount; u32 edgeMatsCount = CBasics::SwapBytes(*reinterpret_cast(cur)); cur += 4; const u8* edgeMatsBuf = cur; cur += edgeMatsCount; u32 polyMatsCount = CBasics::SwapBytes(*reinterpret_cast(cur)); cur += 4; const u8* polyMatsBuf = cur; cur += polyMatsCount; u32 edgeCount = CBasics::SwapBytes(*reinterpret_cast(cur)); cur += 4; const CCollisionEdge* edgeBuf = reinterpret_cast(cur); cur += edgeCount * sizeof(edgeCount); u32 polyCount = CBasics::SwapBytes(*reinterpret_cast(cur)); cur += 4; const u16* polyBuf = reinterpret_cast(cur); cur += polyCount * 2; u32 vertCount = CBasics::SwapBytes(*reinterpret_cast(cur)); cur += 4; const float* vertBuf = reinterpret_cast(cur); return std::make_unique(aabb, nodeType, reinterpret_cast(buf + 8), treeBuf, matCount, matBuf, vertMatsBuf, edgeMatsBuf, polyMatsBuf, edgeCount, edgeBuf, polyCount, polyBuf, vertCount, vertBuf); } CCollisionSurface CAreaOctTree::GetMasterListTriangle(u16 idx) const { const CCollisionEdge& e0 = x3c_edges[x44_polyEdges[idx * 3]]; const CCollisionEdge& e1 = x3c_edges[x44_polyEdges[idx * 3 + 1]]; u16 vert2 = e1.GetVertIndex2(); if (e1.GetVertIndex1() != e0.GetVertIndex1()) if (e1.GetVertIndex1() != e0.GetVertIndex2()) vert2 = e1.GetVertIndex1(); u32 material = x28_materials[x34_polyMats[idx]]; if (material & 0x2000000) return CCollisionSurface(GetVert(e0.GetVertIndex2()), GetVert(e0.GetVertIndex1()), GetVert(vert2), material); else return CCollisionSurface(GetVert(e0.GetVertIndex1()), GetVert(e0.GetVertIndex2()), GetVert(vert2), material); } void CAreaOctTree::GetTriangleVertexIndices(u16 idx, u16 indicesOut[3]) const { const CCollisionEdge& e0 = x3c_edges[x44_polyEdges[idx * 3]]; const CCollisionEdge& e1 = x3c_edges[x44_polyEdges[idx * 3 + 1]]; indicesOut[2] = (e1.GetVertIndex1() != e0.GetVertIndex1() && e1.GetVertIndex1() != e0.GetVertIndex2()) ? e1.GetVertIndex1() : e1.GetVertIndex2(); u32 material = x28_materials[x34_polyMats[idx]]; if (material & 0x2000000) { indicesOut[0] = e0.GetVertIndex2(); indicesOut[1] = e0.GetVertIndex1(); } else { indicesOut[0] = e0.GetVertIndex1(); indicesOut[1] = e0.GetVertIndex2(); } } } // namespace metaforce ================================================ FILE: Runtime/Collision/CAreaOctTree.hpp ================================================ #pragma once #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Collision/CCollisionEdge.hpp" #include "Runtime/Collision/CCollisionSurface.hpp" #include #include #include #include namespace metaforce { class CMaterialFilter; class CAreaOctTree { friend class CBooRenderer; public: struct SRayResult { zeus::CPlane x0_plane; std::optional x10_surface; float x3c_t; }; class TriListReference { const u16* m_ptr; public: explicit TriListReference(const u16* ptr) : m_ptr(ptr) {} u16 GetAt(int idx) const { return m_ptr[idx + 1]; } u16 GetSize() const { return m_ptr[0]; } }; class Node { public: enum class ETreeType { Invalid, Branch, Leaf }; private: zeus::CAABox x0_aabb; const u8* x18_ptr; const CAreaOctTree& x1c_owner; ETreeType x20_nodeType; bool LineTestInternal(const zeus::CLine& line, const CMaterialFilter& filter, float lT, float hT, float maxT, const zeus::CVector3f& vec) const; void LineTestExInternal(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res, float lT, float hT, float maxT, const zeus::CVector3f& dirRecip) const; public: Node(const void* ptr, const zeus::CAABox& aabb, const CAreaOctTree& owner, ETreeType type) : x0_aabb(aabb), x18_ptr(reinterpret_cast(ptr)), x1c_owner(owner), x20_nodeType(type) {} bool LineTest(const zeus::CLine& line, const CMaterialFilter& filter, float length) const; void LineTestEx(const zeus::CLine& line, const CMaterialFilter& filter, SRayResult& res, float length) const; const CAreaOctTree& GetOwner() const { return x1c_owner; } const zeus::CAABox& GetBoundingBox() const { return x0_aabb; } u16 GetChildFlags() const { return *reinterpret_cast(x18_ptr); } Node GetChild(int idx) const; TriListReference GetTriangleArray() const { return TriListReference(reinterpret_cast(x18_ptr + 24)); } ETreeType GetChildType(int idx) const { u16 flags = *reinterpret_cast(x18_ptr); return ETreeType((flags >> (2 * idx)) & 0x3); } ETreeType GetTreeType() const { return x20_nodeType; } }; zeus::CAABox x0_aabb; Node::ETreeType x18_treeType; const u8* x1c_buf; const u8* x20_treeBuf; u32 x24_matCount; const u32* x28_materials; const u8* x2c_vertMats; const u8* x30_edgeMats; const u8* x34_polyMats; u32 x38_edgeCount; const CCollisionEdge* x3c_edges; u32 x40_polyCount; const u16* x44_polyEdges; u32 x48_vertCount; const float* x4c_verts; void SwapTreeNode(u8* ptr, Node::ETreeType type); public: CAreaOctTree(const zeus::CAABox& aabb, Node::ETreeType treeType, const u8* buf, const u8* treeBuf, u32 matCount, const u32* materials, const u8* vertMats, const u8* edgeMats, const u8* polyMats, u32 edgeCount, const CCollisionEdge* edges, u32 polyCount, const u16* polyEdges, u32 vertCount, const float* verts); const zeus::CAABox& GetAABB() const { return x0_aabb; } Node GetRootNode() const { return Node(x20_treeBuf, x0_aabb, *this, x18_treeType); } const u8* GetTreeMemory() const { return x20_treeBuf; } zeus::CVector3f GetVert(int idx) const { const float* vert = &x4c_verts[idx * 3]; return zeus::CVector3f(vert[0], vert[1], vert[2]); } const CCollisionEdge& GetEdge(int idx) const { return x3c_edges[idx]; } u32 GetVertMaterial(int idx) const { return x28_materials[x2c_vertMats[idx]]; } u32 GetEdgeMaterial(int idx) const { return x28_materials[x30_edgeMats[idx]]; } u32 GetTriangleMaterial(int idx) const { return x28_materials[x34_polyMats[idx]]; } u32 GetNumEdges() const { return x38_edgeCount; } u32 GetNumVerts() const { return x48_vertCount; } u32 GetNumTriangles() const { return x40_polyCount; } CCollisionSurface GetMasterListTriangle(u16 idx) const; void GetTriangleVertexIndices(u16 idx, u16 indicesOut[3]) const; const u16* GetTriangleEdgeIndices(u16 idx) const { return &x44_polyEdges[idx * 3]; } static std::unique_ptr MakeFromMemory(const u8* buf, unsigned int size); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CBallFilter.cpp ================================================ #include "Runtime/Collision/CBallFilter.hpp" #include "Runtime/Collision/CollisionUtil.hpp" namespace metaforce { void CBallFilter::Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const { CollisionUtil::AddAverageToFront(in, out); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CBallFilter.hpp ================================================ #pragma once #include "Runtime/Collision/ICollisionFilter.hpp" namespace metaforce { class CCollisionInfoList; class CPhysicsActor; class CBallFilter : public ICollisionFilter { public: explicit CBallFilter(CActor& actor) : ICollisionFilter(actor) {} void Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableAABox.cpp ================================================ #include "Runtime/Collision/CCollidableAABox.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" #include "Runtime/Collision/CCollisionInfo.hpp" #include "Runtime/Collision/CInternalRayCastStructure.hpp" #include "Runtime/Collision/CollisionUtil.hpp" namespace metaforce { constexpr CCollisionPrimitive::Type sType(CCollidableAABox::SetStaticTableIndex, "CCollidableAABox"); CCollidableAABox::CCollidableAABox() = default; CCollidableAABox::CCollidableAABox(const zeus::CAABox& aabox, const CMaterialList& list) : CCollisionPrimitive(list), x10_aabox(aabox) {} zeus::CAABox CCollidableAABox::Transform(const zeus::CTransform& xf) const { return {xf.origin + x10_aabox.min, xf.origin + x10_aabox.max}; } u32 CCollidableAABox::GetTableIndex() const { return sTableIndex; } zeus::CAABox CCollidableAABox::CalculateAABox(const zeus::CTransform& xf) const { return Transform(xf); } zeus::CAABox CCollidableAABox::CalculateLocalAABox() const { return x10_aabox; } FourCC CCollidableAABox::GetPrimType() const { return SBIG('AABX'); } CRayCastResult CCollidableAABox::CastRayInternal(const CInternalRayCastStructure& rayCast) const { if (!rayCast.GetFilter().Passes(GetMaterial())) return {}; zeus::CTransform rayCastXfInv = rayCast.GetTransform().inverse(); zeus::CVector3f localRayStart = rayCastXfInv * rayCast.GetRay().start; zeus::CVector3f localRayDir = rayCastXfInv.rotate(rayCast.GetRay().dir); float tMin, tMax; int axis; bool sign; if (!CollisionUtil::BoxLineTest(x10_aabox, localRayStart, localRayDir, tMin, tMax, axis, sign) || tMin < 0.f || (rayCast.GetMaxTime() > 0.f && tMin > rayCast.GetMaxTime())) return {}; zeus::CVector3f planeNormal; planeNormal[axis] = sign ? 1.f : -1.f; float planeD = axis ? x10_aabox.min[axis] : -x10_aabox.max[axis]; CRayCastResult result(tMin, localRayStart + tMin * localRayDir, zeus::CPlane(planeNormal, planeD), GetMaterial()); result.Transform(rayCast.GetTransform()); return result; } const CCollisionPrimitive::Type& CCollidableAABox::GetType() { return sType; } void CCollidableAABox::SetStaticTableIndex(u32 index) { sTableIndex = index; } bool CCollidableAABox::CollideMovingAABox(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) { const CCollidableAABox& p0 = static_cast(collision.GetLeft().GetPrim()); const CCollidableAABox& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CAABox b0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CAABox b1 = p1.Transform(collision.GetRight().GetTransform()); double d; zeus::CVector3f point, normal; if (CollisionUtil::AABox_AABox_Moving(b0, b1, dir, d, point, normal) && d > 0.0 && d < dOut) { dOut = d; infoOut = CCollisionInfo(point, p0.GetMaterial(), p1.GetMaterial(), normal, -normal); return true; } return false; } bool CCollidableAABox::CollideMovingSphere(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) { const CCollidableAABox& p0 = static_cast(collision.GetLeft().GetPrim()); const CCollidableSphere& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform()); zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform()); double d = dOut; zeus::CVector3f point, normal; if (CollisionUtil::MovingSphereAABox(s1, b0, -dir, d, point, normal) && d < dOut) { point = s1.position - s1.radius * normal; dOut = d; infoOut = CCollisionInfo(point, p0.GetMaterial(), p1.GetMaterial(), -normal); return true; } return false; } namespace Collide { bool AABox_AABox(const CInternalCollisionStructure& collision, CCollisionInfoList& list) { const CCollidableAABox& box0 = static_cast(collision.GetLeft().GetPrim()); const CCollidableAABox& box1 = static_cast(collision.GetRight().GetPrim()); zeus::CAABox aabb0 = box0.Transform(collision.GetLeft().GetTransform()); zeus::CAABox aabb1 = box1.Transform(collision.GetRight().GetTransform()); return CollisionUtil::AABoxAABoxIntersection(aabb0, box0.GetMaterial(), aabb1, box1.GetMaterial(), list); } bool AABox_AABox_Bool(const CInternalCollisionStructure& collision) { const CCollidableAABox& box0 = static_cast(collision.GetLeft().GetPrim()); const CCollidableAABox& box1 = static_cast(collision.GetRight().GetPrim()); zeus::CAABox aabb0 = box0.Transform(collision.GetLeft().GetTransform()); zeus::CAABox aabb1 = box1.Transform(collision.GetRight().GetTransform()); return CollisionUtil::AABoxAABoxIntersection(aabb0, aabb1); } } // namespace Collide } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableAABox.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include "Runtime/Collision/CCollisionPrimitive.hpp" #include namespace metaforce { namespace Collide { bool AABox_AABox(const CInternalCollisionStructure&, CCollisionInfoList&); bool AABox_AABox_Bool(const CInternalCollisionStructure&); } // namespace Collide class CCollidableAABox : public CCollisionPrimitive { static inline u32 sTableIndex = UINT32_MAX; zeus::CAABox x10_aabox; public: CCollidableAABox(); CCollidableAABox(const zeus::CAABox&, const CMaterialList&); zeus::CAABox Transform(const zeus::CTransform&) const; u32 GetTableIndex() const override; zeus::CAABox CalculateAABox(const zeus::CTransform&) const override; zeus::CAABox CalculateLocalAABox() const override; FourCC GetPrimType() const override; CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override; const zeus::CAABox& GetBox() const { return x10_aabox; } zeus::CAABox& Box() { return x10_aabox; } void SetBox(const zeus::CAABox& box) { x10_aabox = box; } static const CCollisionPrimitive::Type& GetType(); static void SetStaticTableIndex(u32 index); static bool CollideMovingAABox(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&); static bool CollideMovingSphere(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableCollisionSurface.cpp ================================================ #include "Runtime/Collision/CCollidableCollisionSurface.hpp" namespace metaforce { constexpr CCollisionPrimitive::Type sType(CCollidableCollisionSurface::SetStaticTableIndex, "CCollidableCollisionSurface"); const CCollisionPrimitive::Type& CCollidableCollisionSurface::GetType() { return sType; } void CCollidableCollisionSurface::SetStaticTableIndex(u32 index) { sTableIndex = index; } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableCollisionSurface.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include "Runtime/Collision/CCollisionPrimitive.hpp" namespace metaforce { class CCollidableCollisionSurface { static inline u32 sTableIndex = UINT32_MAX; public: static const CCollisionPrimitive::Type& GetType(); static void SetStaticTableIndex(u32 index); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableOBBTree.cpp ================================================ #include "Runtime/Collision/CCollidableOBBTree.hpp" #include #include "Runtime/Collision/CCollisionInfoList.hpp" #include "Runtime/Collision/CInternalRayCastStructure.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include "Runtime/Collision/CollisionUtil.hpp" namespace metaforce { CCollidableOBBTree::CCollidableOBBTree(const COBBTree* tree, const metaforce::CMaterialList& material) : CCollisionPrimitive(material), x10_tree(tree) {} bool CCollidableOBBTree::LineIntersectsLeaf(const COBBTree::CLeafData& leaf, CRayCastInfo& info) const { bool ret = false; u16 intersectIdx = 0; for (size_t i = 0; i < leaf.GetSurfaceVector().size(); ++i) { u16 surfIdx = leaf.GetSurfaceVector()[i]; CCollisionSurface surface = x10_tree->GetSurface(surfIdx); CMaterialList matList = GetMaterial(); matList.Add(surface.GetSurfaceFlags()); if (info.GetMaterialFilter().Passes(matList)) { if (CollisionUtil::RayTriangleIntersection(info.GetRay().start, info.GetRay().dir, surface.GetVerts(), info.Magnitude())) { intersectIdx = surfIdx; ret = true; } } } if (ret) { CCollisionSurface surf = x10_tree->GetSurface(intersectIdx); info.Plane() = surf.GetPlane(); info.Material() = CMaterialList(surf.GetSurfaceFlags()); } return ret; } bool CCollidableOBBTree::LineIntersectsOBBTree(const COBBTree::CNode& n0, const COBBTree::CNode& n1, CRayCastInfo& info) const { bool ret = false; float t0, t1; bool intersects0 = false; bool intersects1 = false; if (CollisionUtil::LineIntersectsOBBox(n0.GetOBB(), info.GetRay(), t0) && t0 < info.GetMagnitude()) intersects0 = true; if (CollisionUtil::LineIntersectsOBBox(n1.GetOBB(), info.GetRay(), t1) && t1 < info.GetMagnitude()) intersects1 = true; if (intersects0 && intersects1) { if (t0 < t1) { if (n0.IsLeaf() ? LineIntersectsLeaf(n0.GetLeafData(), info) : LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info)) { if (info.GetMagnitude() < t1) return true; ret = true; } if (n1.IsLeaf()) { if (LineIntersectsLeaf(n1.GetLeafData(), info)) return true; } else { if (LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info)) return true; } } else { if (n1.IsLeaf() ? LineIntersectsLeaf(n1.GetLeafData(), info) : LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info)) { if (info.GetMagnitude() < t0) return true; ret = true; } if (n0.IsLeaf()) { if (LineIntersectsLeaf(n0.GetLeafData(), info)) return true; } else { if (LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info)) return true; } } } else if (intersects0) { return n0.IsLeaf() ? LineIntersectsLeaf(n0.GetLeafData(), info) : LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info); } else if (intersects1) { return n1.IsLeaf() ? LineIntersectsLeaf(n1.GetLeafData(), info) : LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info); } return ret; } bool CCollidableOBBTree::LineIntersectsOBBTree(const COBBTree::CNode& node, CRayCastInfo& info) const { float t; bool ret = false; if (CollisionUtil::LineIntersectsOBBox(node.GetOBB(), info.GetRay(), t) && t < info.GetMagnitude()) { if (node.IsLeaf()) { if (LineIntersectsLeaf(node.GetLeafData(), info)) ret = true; } else { if (LineIntersectsOBBTree(node.GetLeft(), node.GetRight(), info)) ret = true; } const_cast(node).SetHit(true); } else { const_cast(*this).x18_misses += 1; } return ret; } CRayCastResult CCollidableOBBTree::LineIntersectsTree(const zeus::CMRay& ray, const CMaterialFilter& filter, float maxTime, const zeus::CTransform& xf) const { zeus::CMRay useRay = ray.getInvUnscaledTransformRay(xf); CRayCastInfo info(useRay, filter, maxTime); if (LineIntersectsOBBTree(x10_tree->GetRoot(), info)) { zeus::CPlane xfPlane = TransformPlane(info.GetPlane(), xf); return CRayCastResult(info.GetMagnitude(), ray.start + info.GetMagnitude() * ray.dir, xfPlane, info.GetMaterial()); } else { return {}; } } zeus::CPlane CCollidableOBBTree::TransformPlane(const zeus::CPlane& pl, const zeus::CTransform& xf) { zeus::CVector3f normal = xf.rotate(pl.normal()); return zeus::CPlane(normal, (xf * (pl.normal() * pl.d())).dot(normal)); } bool CCollidableOBBTree::SphereCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CSphere& sphere, const CMaterialList& matList, const CMaterialFilter& filter, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) const { bool ret = false; zeus::CAABox aabb(sphere.position - sphere.radius, sphere.position + sphere.radius); zeus::CAABox moveAABB = aabb; zeus::CVector3f moveVec = float(dOut) * dir; moveAABB.accumulateBounds(aabb.max + moveVec); moveAABB.accumulateBounds(aabb.min + moveVec); zeus::CVector3f center = moveAABB.center(); zeus::CVector3f extent = moveAABB.extents(); for (u16 triIdx : leaf.GetSurfaceVector()) { CCollisionSurface surf = x10_tree->GetTransformedSurface(triIdx, xf); CMaterialList triMat = GetMaterial(); triMat.Add(CMaterialList(surf.GetSurfaceFlags())); if (filter.Passes(triMat)) { if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) { const_cast(*this).x1c_hits += 1; zeus::CVector3f surfNormal = surf.GetNormal(); if ((sphere.position + moveVec - surf.GetVert(0)).dot(surfNormal) <= sphere.radius) { const float mag = (sphere.radius - (sphere.position - surf.GetVert(0)).dot(surfNormal)) / dir.dot(surfNormal); const zeus::CVector3f intersectPoint = sphere.position + mag * dir; const std::array outsideEdges{ (intersectPoint - surf.GetVert(0)).dot((surf.GetVert(1) - surf.GetVert(0)).cross(surfNormal)) < 0.f, (intersectPoint - surf.GetVert(1)).dot((surf.GetVert(2) - surf.GetVert(1)).cross(surfNormal)) < 0.f, (intersectPoint - surf.GetVert(2)).dot((surf.GetVert(0) - surf.GetVert(2)).cross(surfNormal)) < 0.f, }; if (mag >= 0.f && !outsideEdges[0] && !outsideEdges[1] && !outsideEdges[2] && mag < dOut) { infoOut = CCollisionInfo(intersectPoint - sphere.radius * surfNormal, matList, triMat, surfNormal); dOut = mag; ret = true; } const bool intersects = (sphere.position - surf.GetVert(0)).dot(surfNormal) <= sphere.radius; std::array testVert{true, true, true}; const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx); for (int k = 0; k < 3; ++k) { if (intersects || outsideEdges[k]) { u16 edgeIdx = edgeIndices[k]; if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupEdgeList[edgeIdx]) { CMetroidAreaCollider::g_DupEdgeList[edgeIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMaterialList edgeMat(x10_tree->GetEdgeMaterial(edgeIdx)); if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) { int nextIdx = (k + 1) % 3; zeus::CVector3f edgeVec = surf.GetVert(nextIdx) - surf.GetVert(k); float edgeVecMag = edgeVec.magnitude(); edgeVec *= zeus::CVector3f(1.f / edgeVecMag); float dirDotEdge = dir.dot(edgeVec); zeus::CVector3f edgeRej = dir - dirDotEdge * edgeVec; float edgeRejMagSq = edgeRej.magSquared(); zeus::CVector3f vertToSphere = sphere.position - surf.GetVert(k); float vtsDotEdge = vertToSphere.dot(edgeVec); zeus::CVector3f vtsRej = vertToSphere - vtsDotEdge * edgeVec; if (edgeRejMagSq > 0.f) { const float tmp = 2.f * vtsRej.dot(edgeRej); const float tmp2 = 4.f * edgeRejMagSq * (vtsRej.magSquared() - sphere.radius * sphere.radius) - tmp * tmp; if (tmp2 >= 0.f) { const float mag2 = 0.5f / edgeRejMagSq * (-tmp - std::sqrt(tmp2)); if (mag2 >= 0.f) { const float t = mag2 * dirDotEdge + vtsDotEdge; if (t >= 0.f && t <= edgeVecMag && mag2 < dOut) { zeus::CVector3f point = surf.GetVert(k) + t * edgeVec; infoOut = CCollisionInfo(point, matList, edgeMat, (sphere.position + mag2 * dir - point).normalized()); dOut = mag2; ret = true; testVert[k] = false; testVert[nextIdx] = false; } else if (t < -sphere.radius && dirDotEdge <= 0.f) { testVert[k] = false; } else if (t > edgeVecMag + sphere.radius && dirDotEdge >= 0.0) { testVert[nextIdx] = false; } } } else { testVert[k] = false; testVert[nextIdx] = false; } } } } } } const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx); for (int k = 0; k < 3; ++k) { const u16 vertIdx = vertIndices[k]; if (testVert[k]) { if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupVertexList[vertIdx]) { CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; double d = dOut; if (CollisionUtil::RaySphereIntersection_Double(zeus::CSphere(surf.GetVert(k), sphere.radius), sphere.position, dir, d) && d >= 0.0) { infoOut = CCollisionInfo(surf.GetVert(k), matList, x10_tree->GetVertMaterial(vertIdx), (sphere.position + dir * d - surf.GetVert(k)).normalized()); dOut = d; ret = true; } } } else { CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; } } } } else { const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx); CMetroidAreaCollider::g_DupEdgeList[edgeIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupEdgeList[edgeIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupEdgeList[edgeIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx); CMetroidAreaCollider::g_DupVertexList[vertIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupVertexList[vertIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupVertexList[vertIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; } } } return ret; } bool CCollidableOBBTree::SphereCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const { bool ret = false; const_cast(*this).x14_tries += 1; if (obb.OBBIntersectsBox(node.GetOBB())) { const_cast(node).SetHit(true); if (node.IsLeaf()) { if (SphereCollideWithLeafMoving(node.GetLeafData(), xf, sphere, material, filter, dir, dOut, info)) ret = true; } else { if (SphereCollisionMoving(node.GetLeft(), xf, sphere, obb, material, filter, dir, dOut, info)) ret = true; if (SphereCollisionMoving(node.GetRight(), xf, sphere, obb, material, filter, dir, dOut, info)) ret = true; } } else { const_cast(*this).x18_misses += 1; } return ret; } bool CCollidableOBBTree::AABoxCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CAABox& aabb, const CMaterialList& matList, const CMaterialFilter& filter, const CMovingAABoxComponents& components, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) const { bool ret = false; zeus::CAABox movedAABB = components.x6e8_aabb; zeus::CVector3f moveVec = float(dOut) * dir; movedAABB.accumulateBounds(aabb.min + moveVec); movedAABB.accumulateBounds(aabb.max + moveVec); zeus::CVector3f center = movedAABB.center(); zeus::CVector3f extent = movedAABB.extents(); zeus::CVector3f normal, point; for (u16 triIdx : leaf.GetSurfaceVector()) { CCollisionSurface surf = x10_tree->GetTransformedSurface(triIdx, xf); CMaterialList triMat = GetMaterial(); triMat.Add(CMaterialList(surf.GetSurfaceFlags())); if (filter.Passes(triMat)) { if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) { const_cast(*this).x1c_hits += 1; const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx); double d = dOut; if (CMetroidAreaCollider::MovingAABoxCollisionCheck_BoxVertexTri(surf, aabb, components.x6c4_vertIdxs, dir, d, normal, point) && d < dOut) { ret = true; infoOut = CCollisionInfo(point, matList, triMat, normal); dOut = d; } for (int k = 0; k < 3; ++k) { u16 vertIdx = vertIndices[k]; const zeus::CVector3f& vtx = surf.GetVert(k); if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupVertexList[vertIdx]) { CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; if (movedAABB.pointInside(vtx)) { d = dOut; if (CMetroidAreaCollider::MovingAABoxCollisionCheck_TriVertexBox(vtx, aabb, dir, d, normal, point) && d < dOut) { CMaterialList vertMat(x10_tree->GetVertMaterial(vertIdx)); ret = true; infoOut = CCollisionInfo(point, matList, vertMat, normal); dOut = d; } } } } const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx); for (int k = 0; k < 3; ++k) { u16 edgeIdx = edgeIndices[k]; if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupEdgeList[edgeIdx]) { CMetroidAreaCollider::g_DupEdgeList[edgeIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMaterialList edgeMat(x10_tree->GetEdgeMaterial(edgeIdx)); if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) { d = dOut; if (CMetroidAreaCollider::MovingAABoxCollisionCheck_Edge(surf.GetVert(k), surf.GetVert((k + 1) % 3), components.x0_edges, dir, d, normal, point) && d < dOut) { ret = true; infoOut = CCollisionInfo(point, matList, edgeMat, normal); dOut = d; } } } } } else { const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx); CMetroidAreaCollider::g_DupEdgeList[edgeIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupEdgeList[edgeIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupEdgeList[edgeIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx); CMetroidAreaCollider::g_DupVertexList[vertIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupVertexList[vertIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; CMetroidAreaCollider::g_DupVertexList[vertIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount; } } } return ret; } bool CCollidableOBBTree::AABoxCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, const CMovingAABoxComponents& components, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const { bool ret = false; const_cast(*this).x14_tries += 1; if (obb.OBBIntersectsBox(node.GetOBB())) { const_cast(node).SetHit(true); if (node.IsLeaf()) { if (AABoxCollideWithLeafMoving(node.GetLeafData(), xf, aabb, material, filter, components, dir, dOut, info)) ret = true; } else { if (AABoxCollisionMoving(node.GetLeft(), xf, aabb, obb, material, filter, components, dir, dOut, info)) ret = true; if (AABoxCollisionMoving(node.GetRight(), xf, aabb, obb, material, filter, components, dir, dOut, info)) ret = true; } } else { const_cast(*this).x18_misses += 1; } return ret; } bool CCollidableOBBTree::SphereCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere, const zeus::COBBox& obb, const CMaterialFilter& filter) const { const_cast(*this).x14_tries += 1; if (obb.OBBIntersectsBox(node.GetOBB())) { const_cast(node).SetHit(true); if (node.IsLeaf()) { for (u16 surfIdx : node.GetLeafData().GetSurfaceVector()) { CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf); CMaterialList triMat = GetMaterial(); triMat.Add(CMaterialList(surf.GetSurfaceFlags())); if (filter.Passes(triMat) && CollisionUtil::TriSphereOverlap(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) return true; } } else { if (SphereCollisionBoolean(node.GetLeft(), xf, sphere, obb, filter)) return true; if (SphereCollisionBoolean(node.GetRight(), xf, sphere, obb, filter)) return true; } } else { const_cast(*this).x18_misses += 1; } return false; } bool CCollidableOBBTree::AABoxCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb, const zeus::COBBox& obb, const CMaterialFilter& filter) const { zeus::CVector3f center = aabb.center(); zeus::CVector3f extent = aabb.extents(); const_cast(*this).x14_tries += 1; if (obb.OBBIntersectsBox(node.GetOBB())) { const_cast(node).SetHit(true); if (node.IsLeaf()) { for (u16 surfIdx : node.GetLeafData().GetSurfaceVector()) { CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf); CMaterialList triMat = GetMaterial(); triMat.Add(CMaterialList(surf.GetSurfaceFlags())); if (filter.Passes(triMat) && CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) return true; } } else { if (AABoxCollisionBoolean(node.GetLeft(), xf, aabb, obb, filter)) return true; if (AABoxCollisionBoolean(node.GetRight(), xf, aabb, obb, filter)) return true; } } else { const_cast(*this).x18_misses += 1; } return false; } bool CCollidableOBBTree::SphereCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CSphere& sphere, const CMaterialList& material, const CMaterialFilter& filter, CCollisionInfoList& infoList) const { bool ret = false; zeus::CVector3f point, normal; for (u16 surfIdx : leaf.GetSurfaceVector()) { CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf); CMaterialList triMat = GetMaterial(); triMat.Add(CMaterialList(surf.GetSurfaceFlags())); if (filter.Passes(triMat)) { const_cast(*this).x1c_hits += 1; if (CollisionUtil::TriSphereIntersection(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2), point, normal)) { CCollisionInfo collision(point, material, triMat, normal); infoList.Add(collision, false); ret = true; } } } return ret; } bool CCollidableOBBTree::SphereCollision(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, CCollisionInfoList& infoList) const { bool ret = false; const_cast(*this).x14_tries += 1; if (obb.OBBIntersectsBox(node.GetOBB())) { const_cast(node).SetHit(true); if (node.IsLeaf()) { if (SphereCollideWithLeaf(node.GetLeafData(), xf, sphere, material, filter, infoList)) ret = true; } else { if (SphereCollision(node.GetLeft(), xf, sphere, obb, material, filter, infoList)) ret = true; if (SphereCollision(node.GetRight(), xf, sphere, obb, material, filter, infoList)) ret = true; } } else { const_cast(*this).x18_misses += 1; } return ret; } bool CCollidableOBBTree::AABoxCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CAABox& aabb, const CMaterialList& material, const CMaterialFilter& filter, const std::array& planes, CCollisionInfoList& infoList) const { bool ret = false; zeus::CVector3f center = aabb.center(); zeus::CVector3f extent = aabb.extents(); for (u16 surfIdx : leaf.GetSurfaceVector()) { CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf); CMaterialList triMat = GetMaterial(); triMat.Add(CMaterialList(surf.GetSurfaceFlags())); if (filter.Passes(triMat) && CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) { zeus::CAABox newAABB = zeus::CAABox(); const_cast(*this).x1c_hits += 1; if (CMetroidAreaCollider::ConvexPolyCollision(planes, surf.GetVerts(), newAABB)) { zeus::CPlane plane = surf.GetPlane(); CCollisionInfo collision(newAABB, triMat, material, plane.normal(), -plane.normal()); infoList.Add(collision, false); ret = true; } } } return ret; } bool CCollidableOBBTree::AABoxCollision(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, const std::array& planes, CCollisionInfoList& infoList) const { bool ret = false; const_cast(*this).x14_tries += 1; if (obb.OBBIntersectsBox(node.GetOBB())) { const_cast(node).SetHit(true); if (node.IsLeaf()) { if (AABoxCollideWithLeaf(node.GetLeafData(), xf, aabb, material, filter, planes, infoList)) ret = true; } else { if (AABoxCollision(node.GetLeft(), xf, aabb, obb, material, filter, planes, infoList)) ret = true; if (AABoxCollision(node.GetRight(), xf, aabb, obb, material, filter, planes, infoList)) ret = true; } } else { const_cast(*this).x18_misses += 1; } return ret; } FourCC CCollidableOBBTree::GetPrimType() const { return SBIG('OBBT'); } CRayCastResult CCollidableOBBTree::CastRayInternal(const CInternalRayCastStructure& rayCast) const { return LineIntersectsTree(rayCast.GetRay(), rayCast.GetFilter(), rayCast.GetMaxTime(), rayCast.GetTransform()); } zeus::CAABox CCollidableOBBTree::CalculateAABox(const zeus::CTransform& xf) const { return zeus::COBBox::FromAABox(x10_tree->CalculateLocalAABox(), xf).calculateAABox(); } zeus::CAABox CCollidableOBBTree::CalculateLocalAABox() const { return x10_tree->CalculateLocalAABox(); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableOBBTree.hpp ================================================ #pragma once #include "Runtime/Collision/CCollisionPrimitive.hpp" #include "Runtime/Collision/CMetroidAreaCollider.hpp" #include "Runtime/Collision/COBBTree.hpp" #include #include #include namespace metaforce { class CRayCastInfo { const zeus::CMRay& x0_ray; const CMaterialFilter& x4_filter; float x8_mag; zeus::CPlane xc_plane = {zeus::skUp, 0.f}; CMaterialList x20_material; public: CRayCastInfo(const zeus::CMRay& ray, const CMaterialFilter& filter, float mag) : x0_ray(ray), x4_filter(filter), x8_mag(mag) {} const zeus::CMRay& GetRay() const { return x0_ray; } const CMaterialFilter& GetMaterialFilter() const { return x4_filter; } float GetMagnitude() const { return x8_mag; } float& Magnitude() { return x8_mag; } const zeus::CPlane& GetPlane() const { return xc_plane; } zeus::CPlane& Plane() { return xc_plane; } const CMaterialList& GetMaterial() const { return x20_material; } CMaterialList& Material() { return x20_material; } }; class CCollidableOBBTree : public CCollisionPrimitive { friend class CCollidableOBBTreeGroup; const COBBTree* x10_tree = nullptr; u32 x14_tries = 0; u32 x18_misses = 0; u32 x1c_hits = 0; static inline u32 sTableIndex = 0; bool LineIntersectsLeaf(const COBBTree::CLeafData& leaf, CRayCastInfo& info) const; bool LineIntersectsOBBTree(const COBBTree::CNode& n0, const COBBTree::CNode& n1, CRayCastInfo& info) const; bool LineIntersectsOBBTree(const COBBTree::CNode& node, CRayCastInfo& info) const; CRayCastResult LineIntersectsTree(const zeus::CMRay& ray, const CMaterialFilter& filter, float maxTime, const zeus::CTransform& xf) const; static zeus::CPlane TransformPlane(const zeus::CPlane& pl, const zeus::CTransform& xf); bool SphereCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CSphere& sphere, const CMaterialList& material, const CMaterialFilter& filter, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const; bool SphereCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const; bool AABoxCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CAABox& aabb, const CMaterialList& material, const CMaterialFilter& filter, const CMovingAABoxComponents& components, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const; bool AABoxCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, const CMovingAABoxComponents& components, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const; bool SphereCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere, const zeus::COBBox& obb, const CMaterialFilter& filter) const; bool AABoxCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb, const zeus::COBBox& obb, const CMaterialFilter& filter) const; bool SphereCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CSphere& sphere, const CMaterialList& material, const CMaterialFilter& filter, CCollisionInfoList& infoList) const; bool SphereCollision(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CSphere& sphere, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, CCollisionInfoList& infoList) const; bool AABoxCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf, const zeus::CAABox& aabb, const CMaterialList& material, const CMaterialFilter& filter, const std::array& planes, CCollisionInfoList& infoList) const; bool AABoxCollision(const COBBTree::CNode& node, const zeus::CTransform& xf, const zeus::CAABox& aabb, const zeus::COBBox& obb, const CMaterialList& material, const CMaterialFilter& filter, const std::array& planes, CCollisionInfoList& infoList) const; public: CCollidableOBBTree(const COBBTree* tree, const CMaterialList& material); ~CCollidableOBBTree() override = default; void ResetTestStats() const; void ResetTestStatsRecurse(const COBBTree::CNode&) const; u32 GetTableIndex() const override { return sTableIndex; } zeus::CAABox CalculateAABox(const zeus::CTransform&) const override; zeus::CAABox CalculateLocalAABox() const override; FourCC GetPrimType() const override; CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableOBBTreeGroup.cpp ================================================ #include "Runtime/Collision/CCollidableOBBTreeGroup.hpp" #include "Runtime/CToken.hpp" #include "Runtime/Collision/CCollidableAABox.hpp" #include "Runtime/Collision/CCollidableOBBTree.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" #include "Runtime/Collision/CInternalRayCastStructure.hpp" #include "Runtime/Collision/CollisionUtil.hpp" namespace metaforce { constexpr CCollisionPrimitive::Type sType(CCollidableOBBTreeGroup::SetStaticTableIndex, "CCollidableOBBTreeGroup"); CCollidableOBBTreeGroupContainer::CCollidableOBBTreeGroupContainer(CInputStream& in) { const u32 treeCount = in.ReadLong(); x0_trees.reserve(treeCount); for (u32 i = 0; i < treeCount; i++) { auto tree = std::make_unique(in); x0_trees.push_back(std::move(tree)); } x10_aabbs.reserve(x0_trees.size()); for (const std::unique_ptr& tree : x0_trees) { x10_aabbs.push_back(CCollidableOBBTree(tree.get(), CMaterialList()).CalculateLocalAABox()); x20_aabox.accumulateBounds(x10_aabbs.back()); } } CCollidableOBBTreeGroupContainer::CCollidableOBBTreeGroupContainer(const zeus::CVector3f& extent, const zeus::CVector3f& center) { x0_trees.push_back(COBBTree::BuildOrientedBoundingBoxTree(extent, center)); for (const std::unique_ptr& tree : x0_trees) { x10_aabbs.push_back(CCollidableOBBTree(tree.get(), CMaterialList()).CalculateLocalAABox()); x20_aabox.accumulateBounds(x10_aabbs.back()); } } CCollidableOBBTreeGroup::CCollidableOBBTreeGroup(const CCollidableOBBTreeGroupContainer* container, const CMaterialList& matList) : CCollisionPrimitive(matList), x10_container(container) {} void CCollidableOBBTreeGroup::ResetTestStats() const { /* Remove me? */ } u32 CCollidableOBBTreeGroup::GetTableIndex() const { return sTableIndex; } zeus::CAABox CCollidableOBBTreeGroup::CalculateAABox(const zeus::CTransform& xf) const { return x10_container->x20_aabox.getTransformedAABox(xf); } zeus::CAABox CCollidableOBBTreeGroup::CalculateLocalAABox() const { return x10_container->x20_aabox; } FourCC CCollidableOBBTreeGroup::GetPrimType() const { return SBIG('OBTG'); } CRayCastResult CCollidableOBBTreeGroup::CastRayInternal(const CInternalRayCastStructure& rayCast) const { CRayCastResult ret; zeus::CMRay xfRay = rayCast.GetRay().getInvUnscaledTransformRay(rayCast.GetTransform()); auto aabbIt = x10_container->x10_aabbs.cbegin(); float mag = rayCast.GetMaxTime(); for (const std::unique_ptr& tree : x10_container->x0_trees) { CCollidableOBBTree obbTree(tree.get(), GetMaterial()); float tMin = 0.f; float tMax = 0.f; if (CollisionUtil::RayAABoxIntersection(xfRay, *aabbIt++, tMin, tMax) != 0u) { CInternalRayCastStructure localCast(xfRay.start, xfRay.dir, mag, zeus::CTransform(), rayCast.GetFilter()); CRayCastResult localResult = obbTree.CastRayInternal(localCast); if (localResult.IsValid()) { if (ret.IsInvalid() || localResult.GetT() < ret.GetT()) { ret = localResult; mag = localResult.GetT(); } } } } ret.Transform(rayCast.GetTransform()); return ret; } const CCollisionPrimitive::Type& CCollidableOBBTreeGroup::GetType() { return sType; } void CCollidableOBBTreeGroup::SetStaticTableIndex(u32 index) { sTableIndex = index; } bool CCollidableOBBTreeGroup::SphereCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list) { bool ret = false; const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::COBBox obb1 = zeus::COBBox::FromAABox(p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() * collision.GetLeft().GetTransform()); for (const std::unique_ptr& tree : p1.x10_container->x0_trees) { CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial()); if (obbTree.SphereCollision(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), s0, obb1, p0.GetMaterial(), collision.GetLeft().GetFilter(), list)) { ret = true; } } return ret; } bool CCollidableOBBTreeGroup::SphereCollideBoolean(const CInternalCollisionStructure& collision) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::COBBox obb1 = zeus::COBBox::FromAABox(p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() * collision.GetLeft().GetTransform()); for (const std::unique_ptr& tree : p1.x10_container->x0_trees) { CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial()); if (obbTree.SphereCollisionBoolean(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), s0, obb1, collision.GetLeft().GetFilter())) { return true; } } return false; } bool CCollidableOBBTreeGroup::CollideMovingSphere(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& mag, CCollisionInfo& info) { bool ret = false; const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CAABox movedAABB = p0.CalculateLocalAABox(); zeus::CVector3f moveVec = float(mag) * dir; movedAABB.accumulateBounds(movedAABB.min + moveVec); movedAABB.accumulateBounds(movedAABB.max + moveVec); zeus::COBBox p0Obb = zeus::COBBox::FromAABox(movedAABB, collision.GetRight().GetTransform().inverse() * collision.GetLeft().GetTransform()); for (const std::unique_ptr& tree : p1.x10_container->x0_trees) { CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial()); CMetroidAreaCollider::ResetInternalCounters(); if (obbTree.SphereCollisionMoving(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), s0, p0Obb, p0.GetMaterial(), collision.GetLeft().GetFilter(), dir, mag, info)) { ret = true; } } return ret; } bool CCollidableOBBTreeGroup::AABoxCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list) { bool ret = false; const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); const zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform()); const zeus::COBBox p0Obb = zeus::COBBox::FromAABox( p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() * collision.GetLeft().GetTransform()); const std::array planes{{ {zeus::skRight, b0.min.dot(zeus::skRight)}, {zeus::skLeft, b0.max.dot(zeus::skLeft)}, {zeus::skForward, b0.min.dot(zeus::skForward)}, {zeus::skBack, b0.max.dot(zeus::skBack)}, {zeus::skUp, b0.min.dot(zeus::skUp)}, {zeus::skDown, b0.max.dot(zeus::skDown)}, }}; for (const std::unique_ptr& tree : p1.x10_container->x0_trees) { CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial()); if (obbTree.AABoxCollision(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), b0, p0Obb, p0.GetMaterial(), collision.GetLeft().GetFilter(), planes, list)) { ret = true; } } return ret; } bool CCollidableOBBTreeGroup::AABoxCollideBoolean(const CInternalCollisionStructure& collision) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform()); zeus::COBBox p0Obb = zeus::COBBox::FromAABox(p0.CalculateLocalAABox(), collision.GetRight().GetTransform().inverse() * collision.GetLeft().GetTransform()); for (const std::unique_ptr& tree : p1.x10_container->x0_trees) { CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial()); if (obbTree.AABoxCollisionBoolean(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), b0, p0Obb, collision.GetLeft().GetFilter())) { return true; } } return false; } bool CCollidableOBBTreeGroup::CollideMovingAABox(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& mag, CCollisionInfo& info) { bool ret = false; const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CAABox b0 = p0.CalculateAABox(collision.GetLeft().GetTransform()); CMovingAABoxComponents components(b0, dir); zeus::CAABox movedAABB = p0.CalculateLocalAABox(); zeus::CVector3f moveVec = float(mag) * dir; movedAABB.accumulateBounds(movedAABB.min + moveVec); movedAABB.accumulateBounds(movedAABB.max + moveVec); zeus::COBBox p0Obb = zeus::COBBox::FromAABox(movedAABB, collision.GetRight().GetTransform().inverse() * collision.GetLeft().GetTransform()); for (const std::unique_ptr& tree : p1.x10_container->x0_trees) { CCollidableOBBTree obbTree(tree.get(), p1.GetMaterial()); CMetroidAreaCollider::ResetInternalCounters(); if (obbTree.AABoxCollisionMoving(obbTree.x10_tree->GetRoot(), collision.GetRight().GetTransform(), b0, p0Obb, p0.GetMaterial(), collision.GetLeft().GetFilter(), components, dir, mag, info)) { ret = true; } } return ret; } CFactoryFnReturn FCollidableOBBTreeGroupFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef) { return TToken::GetIObjObjectFor( std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableOBBTreeGroup.hpp ================================================ #pragma once #include #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Collision/COBBTree.hpp" #include "Runtime/Collision/CCollisionPrimitive.hpp" #include #include namespace metaforce { class CCollidableOBBTreeGroupContainer { friend class CCollidableOBBTreeGroup; std::vector> x0_trees; std::vector x10_aabbs; zeus::CAABox x20_aabox; public: explicit CCollidableOBBTreeGroupContainer(CInputStream& in); CCollidableOBBTreeGroupContainer(const zeus::CVector3f&, const zeus::CVector3f&); u32 NumTrees() const { return x0_trees.size(); } }; class CCollidableOBBTreeGroup : public CCollisionPrimitive { static inline u32 sTableIndex = UINT32_MAX; const CCollidableOBBTreeGroupContainer* x10_container; public: CCollidableOBBTreeGroup(const CCollidableOBBTreeGroupContainer*, const CMaterialList&); ~CCollidableOBBTreeGroup() override = default; void ResetTestStats() const; u32 GetTableIndex() const override; zeus::CAABox CalculateAABox(const zeus::CTransform&) const override; zeus::CAABox CalculateLocalAABox() const override; FourCC GetPrimType() const override; CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override; COBBTree const* GetOBBTreeAABox(int index) const { return x10_container->x0_trees[index].get(); } CCollidableOBBTreeGroupContainer const* GetContainer() const { return x10_container; } static const Type& GetType(); static void SetStaticTableIndex(u32 index); /* Sphere Collide */ static bool SphereCollide(const CInternalCollisionStructure&, CCollisionInfoList&); static bool SphereCollideBoolean(const CInternalCollisionStructure&); static bool CollideMovingSphere(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&); /* AABox Collide */ static bool AABoxCollide(const CInternalCollisionStructure&, CCollisionInfoList&); static bool AABoxCollideBoolean(const CInternalCollisionStructure&); static bool CollideMovingAABox(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&); }; CFactoryFnReturn FCollidableOBBTreeGroupFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableSphere.cpp ================================================ #include "Runtime/Collision/CCollidableSphere.hpp" #include "Runtime/Collision/CCollidableAABox.hpp" #include "Runtime/Collision/CCollisionInfoList.hpp" #include "Runtime/Collision/CInternalRayCastStructure.hpp" #include "Runtime/Collision/CollisionUtil.hpp" namespace metaforce { constexpr CCollisionPrimitive::Type sType(CCollidableSphere::SetStaticTableIndex, "CCollidableSphere"); namespace Collide { bool Sphere_AABox(const CInternalCollisionStructure& collision, CCollisionInfoList& list) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CAABox b1 = p1.Transform(collision.GetRight().GetTransform()); float distSq = 0.f; int flags = 0; for (int i = 0; i < 3; ++i) { if (s0.position[i] < b1.min[i]) { if (s0.position[i] + s0.radius >= b1.min[i]) { float dist = s0.position[i] - b1.min[i]; distSq += dist * dist; flags |= 1 << (2 * i); } else { return false; } } else if (s0.position[i] > b1.max[i]) { if (s0.position[i] - s0.radius <= b1.max[i]) { float dist = s0.position[i] - b1.max[i]; distSq += dist * dist; flags |= 1 << (2 * i + 1); } else { return false; } } } if (flags == 0) { zeus::CVector3f normal = (s0.position - b1.center()).normalized(); zeus::CVector3f point = s0.position + normal * s0.radius; CCollisionInfo info(point, p0.GetMaterial(), p1.GetMaterial(), normal); list.Add(info, false); return true; } if (distSq > s0.radius * s0.radius) { return false; } zeus::CVector3f point; switch (flags) { case 0x1a: point = zeus::CVector3f(b1.max.x(), b1.max.y(), b1.min.z()); break; case 0x19: point = zeus::CVector3f(b1.min.x(), b1.max.y(), b1.min.z()); break; case 0x16: point = zeus::CVector3f(b1.max.x(), b1.min.y(), b1.min.z()); break; case 0x15: point = zeus::CVector3f(b1.min.x(), b1.min.y(), b1.min.z()); break; case 0x2a: point = zeus::CVector3f(b1.max.x(), b1.max.y(), b1.max.z()); break; case 0x29: point = zeus::CVector3f(b1.min.x(), b1.max.y(), b1.max.z()); break; case 0x26: point = zeus::CVector3f(b1.max.x(), b1.min.y(), b1.max.z()); break; case 0x25: point = zeus::CVector3f(b1.min.x(), b1.min.y(), b1.max.z()); break; case 0x11: point = zeus::CVector3f(b1.min.x(), s0.position.y(), b1.min.z()); break; case 0x12: point = zeus::CVector3f(b1.max.x(), s0.position.y(), b1.min.z()); break; case 0x14: point = zeus::CVector3f(s0.position.x(), b1.min.y(), b1.min.z()); break; case 0x18: point = zeus::CVector3f(s0.position.x(), b1.max.y(), b1.min.z()); break; case 0x5: point = zeus::CVector3f(b1.min.x(), b1.min.y(), s0.position.z()); break; case 0x6: point = zeus::CVector3f(b1.max.x(), b1.min.y(), s0.position.z()); break; case 0x9: point = zeus::CVector3f(b1.min.x(), b1.max.y(), s0.position.z()); break; case 0xa: point = zeus::CVector3f(b1.max.x(), b1.max.y(), s0.position.z()); break; case 0x21: point = zeus::CVector3f(b1.min.x(), s0.position.y(), b1.max.z()); break; case 0x22: point = zeus::CVector3f(b1.max.x(), s0.position.y(), b1.max.z()); break; case 0x24: point = zeus::CVector3f(s0.position.x(), b1.min.y(), b1.max.z()); break; case 0x28: point = zeus::CVector3f(s0.position.x(), b1.max.y(), b1.max.z()); break; case 0x1: point = zeus::CVector3f(b1.min.x(), s0.position.y(), s0.position.z()); break; case 0x2: point = zeus::CVector3f(b1.max.x(), s0.position.y(), s0.position.z()); break; case 0x4: point = zeus::CVector3f(s0.position.x(), b1.min.y(), s0.position.z()); break; case 0x8: point = zeus::CVector3f(s0.position.x(), b1.max.y(), s0.position.z()); break; case 0x10: point = zeus::CVector3f(s0.position.x(), s0.position.y(), b1.min.z()); break; case 0x20: point = zeus::CVector3f(s0.position.x(), s0.position.y(), b1.max.z()); break; default: break; } CCollisionInfo info(point, p0.GetMaterial(), p1.GetMaterial(), (s0.position - point).normalized()); list.Add(info, false); return true; } bool Sphere_AABox_Bool(const CInternalCollisionStructure& collision) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CAABox b1 = p1.Transform(collision.GetRight().GetTransform()); return CCollidableSphere::Sphere_AABox_Bool(s0, b1); } bool Sphere_Sphere(const CInternalCollisionStructure& collision, CCollisionInfoList& list) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform()); float radiusSum = s0.radius + s1.radius; zeus::CVector3f delta = s0.position - s1.position; float deltaMagSq = delta.magSquared(); if (deltaMagSq <= radiusSum * radiusSum) { zeus::CVector3f deltaNorm = delta.canBeNormalized() ? (1.f / std::sqrt(deltaMagSq)) * delta : zeus::skRight; zeus::CVector3f collisionPoint = deltaNorm * s1.radius + s1.position; CCollisionInfo info(collisionPoint, p0.GetMaterial(), p1.GetMaterial(), deltaNorm); list.Add(info, false); return true; } return false; } bool Sphere_Sphere_Bool(const CInternalCollisionStructure& collision) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform()); float radiusSum = s0.radius + s1.radius; return (s0.position - s1.position).magSquared() <= radiusSum * radiusSum; } } // namespace Collide CCollidableSphere::CCollidableSphere(const zeus::CSphere& sphere, const CMaterialList& list) : CCollisionPrimitive(list), x10_sphere(sphere) {} zeus::CSphere CCollidableSphere::Transform(const zeus::CTransform& xf) const { return zeus::CSphere(xf * x10_sphere.position, x10_sphere.radius); } u32 CCollidableSphere::GetTableIndex() const { return sTableIndex; } zeus::CAABox CCollidableSphere::CalculateAABox(const zeus::CTransform& xf) const { zeus::CVector3f xfPos = xf * x10_sphere.position; return {xfPos - x10_sphere.radius, xfPos + x10_sphere.radius}; } zeus::CAABox CCollidableSphere::CalculateLocalAABox() const { return {x10_sphere.position - x10_sphere.radius, x10_sphere.position + x10_sphere.radius}; } FourCC CCollidableSphere::GetPrimType() const { return SBIG('SPHR'); } CRayCastResult CCollidableSphere::CastRayInternal(const CInternalRayCastStructure& rayCast) const { if (!rayCast.GetFilter().Passes(GetMaterial())) { return {}; } zeus::CSphere xfSphere = Transform(rayCast.GetTransform()); float t = 0.f; zeus::CVector3f point; if (CollisionUtil::RaySphereIntersection(xfSphere, rayCast.GetRay().start, rayCast.GetRay().dir, rayCast.GetMaxTime(), t, point)) { zeus::CVector3f delta = point - xfSphere.position; float deltaMag = delta.magnitude(); zeus::CUnitVector3f planeNormal = (deltaMag > 0.01f) ? delta * (1.f / deltaMag) : rayCast.GetRay().dir; float planeD = point.dot(planeNormal); return CRayCastResult(t, point, zeus::CPlane(planeNormal, planeD), GetMaterial()); } return {}; } const CCollisionPrimitive::Type& CCollidableSphere::GetType() { return sType; } bool CCollidableSphere::CollideMovingAABox(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CAABox b1 = p1.CalculateAABox(collision.GetRight().GetTransform()); double d = dOut; zeus::CVector3f point; zeus::CVector3f normal; if (CollisionUtil::MovingSphereAABox(s0, b1, dir, d, point, normal) && d < dOut) { dOut = d; infoOut = CCollisionInfo(point, p0.GetMaterial(), p1.GetMaterial(), normal); return true; } return false; } bool CCollidableSphere::CollideMovingSphere(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) { const auto& p0 = static_cast(collision.GetLeft().GetPrim()); const auto& p1 = static_cast(collision.GetRight().GetPrim()); zeus::CSphere s0 = p0.Transform(collision.GetLeft().GetTransform()); zeus::CSphere s1 = p1.Transform(collision.GetRight().GetTransform()); double d = dOut; if (CollisionUtil::RaySphereIntersection_Double(zeus::CSphere(s1.position, s0.radius + s1.radius), s0.position, dir, d) && d >= 0.0 && d < dOut) { dOut = d; zeus::CVector3f normal = (s0.position + float(d) * dir - s1.position).normalized(); infoOut = CCollisionInfo(s1.position + s1.radius * normal, p0.GetMaterial(), p1.GetMaterial(), normal); return true; } return false; } bool CCollidableSphere::Sphere_AABox_Bool(const zeus::CSphere& sphere, const zeus::CAABox& aabb) { float mag = 0.f; for (int i = 0; i < 3; ++i) { if (sphere.position[i] < aabb.min[i]) { float tmp = sphere.position[i] - aabb.min[i]; mag += tmp * tmp; } else if (sphere.position[i] > aabb.max[i]) { float tmp = sphere.position[i] - aabb.max[i]; mag += tmp * tmp; } } return mag <= sphere.radius * sphere.radius; } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollidableSphere.hpp ================================================ #pragma once #include "Runtime/Collision/CCollisionPrimitive.hpp" #include #include namespace metaforce { namespace Collide { bool Sphere_AABox(const CInternalCollisionStructure&, CCollisionInfoList&); bool Sphere_AABox_Bool(const CInternalCollisionStructure&); bool Sphere_Sphere(const CInternalCollisionStructure&, CCollisionInfoList&); bool Sphere_Sphere_Bool(const CInternalCollisionStructure&); } // namespace Collide class CCollidableSphere : public CCollisionPrimitive { static inline u32 sTableIndex = UINT32_MAX; zeus::CSphere x10_sphere; public: CCollidableSphere(const zeus::CSphere&, const CMaterialList&); const zeus::CSphere& GetSphere() const { return x10_sphere; } void SetSphereCenter(const zeus::CVector3f& center) { x10_sphere.position = center; } void SetSphereRadius(float radius) { x10_sphere.radius = radius; } zeus::CSphere Transform(const zeus::CTransform& xf) const; u32 GetTableIndex() const override; zeus::CAABox CalculateAABox(const zeus::CTransform&) const override; zeus::CAABox CalculateLocalAABox() const override; FourCC GetPrimType() const override; CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const override; static const Type& GetType(); static void SetStaticTableIndex(u32 index) { sTableIndex = index; } static bool CollideMovingAABox(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&); static bool CollideMovingSphere(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&); static bool Sphere_AABox_Bool(const zeus::CSphere& sphere, const zeus::CAABox& aabb); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionActor.cpp ================================================ #include "Runtime/Collision/CCollisionActor.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/Collision/CCollidableOBBTreeGroup.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" #include "Runtime/World/CActorParameters.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { constexpr CMaterialList skDefaultCollisionActorMaterials = CMaterialList(EMaterialTypes::Solid, EMaterialTypes::CollisionActor, EMaterialTypes::ScanPassthrough, EMaterialTypes::CameraPassthrough); CCollisionActor::CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& extent, const zeus::CVector3f& center, bool active, float mass, std::string_view name) : CPhysicsActor(uid, active, "CollisionActor", CEntityInfo(areaId, CEntity::NullConnectionList), zeus::CTransform(), CModelData::CModelDataNull(), skDefaultCollisionActorMaterials, zeus::skNullBox, SMoverData(mass), CActorParameters::None(), 0.3f, 0.1f) , x258_primitiveType(EPrimitiveType::OBBTreeGroup) , x25c_owner(owner) , x260_boxSize(extent) , x26c_center(center) , x278_obbContainer(std::make_unique(extent, center)) , x27c_obbTreeGroupPrimitive(std::make_unique(x278_obbContainer.get(), GetMaterialList())) { x10_name += ' '; x10_name += name; SetCoefficientOfRestitutionModifier(0.5f); SetCallTouch(false); SetMaterialFilter(CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::CollisionActor, EMaterialTypes::NoStaticCollision})); } CCollisionActor::CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& boxSize, bool active, float mass, std::string_view name) : CPhysicsActor(uid, active, "CollisionActor", CEntityInfo(areaId, CEntity::NullConnectionList), zeus::CTransform(), CModelData::CModelDataNull(), skDefaultCollisionActorMaterials, zeus::skNullBox, SMoverData(mass), CActorParameters::None(), 0.3f, 0.1f) , x258_primitiveType(EPrimitiveType::AABox) , x25c_owner(owner) , x260_boxSize(boxSize) , x280_aaboxPrimitive( std::make_unique(zeus::CAABox(-0.5f * boxSize, 0.5f * boxSize), CMaterialList(EMaterialTypes::Solid, EMaterialTypes::NoStaticCollision))) { x10_name += ' '; x10_name += name; SetCoefficientOfRestitutionModifier(0.5f); SetCallTouch(false); SetMaterialFilter(CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::CollisionActor, EMaterialTypes::NoStaticCollision})); } CCollisionActor::CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, bool active, float radius, float mass, std::string_view name) : CPhysicsActor(uid, active, "CollisionActor", CEntityInfo(areaId, CEntity::NullConnectionList), zeus::CTransform(), CModelData::CModelDataNull(), skDefaultCollisionActorMaterials, zeus::skNullBox, SMoverData(mass), CActorParameters::None(), 0.3f, 0.1f) , x258_primitiveType(EPrimitiveType::Sphere) , x25c_owner(owner) , x284_spherePrimitive(std::make_unique( zeus::CSphere(zeus::skZero3f, radius), CMaterialList(EMaterialTypes::NoStaticCollision, EMaterialTypes::Solid))) , x288_sphereRadius(radius) { x10_name += ' '; x10_name += name; SetCoefficientOfRestitutionModifier(0.5f); SetCallTouch(false); SetMaterialFilter(CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::CollisionActor, EMaterialTypes::NoStaticCollision})); } void CCollisionActor::Accept(IVisitor& visitor) { visitor.Visit(this); } void CCollisionActor::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) { switch (msg) { case EScriptObjectMessage::Falling: case EScriptObjectMessage::Registered: case EScriptObjectMessage::Deleted: case EScriptObjectMessage::InitializedInArea: break; case EScriptObjectMessage::Touched: case EScriptObjectMessage::Damage: case EScriptObjectMessage::InvulnDamage: { if (CEntity* ent = mgr.ObjectById(x25c_owner)) { x2fc_lastTouched = sender; mgr.SendScriptMsg(ent, GetUniqueId(), msg); } } break; default: mgr.SendScriptMsgAlways(x25c_owner, GetUniqueId(), msg); break; } CActor::AcceptScriptMsg(msg, sender, mgr); } CHealthInfo* CCollisionActor::HealthInfo(CStateManager&) { return &x28c_healthInfo; } const CDamageVulnerability* CCollisionActor::GetDamageVulnerability() const { return &x294_damageVuln; } const CDamageVulnerability* CCollisionActor::GetDamageVulnerability(const zeus::CVector3f&, const zeus::CVector3f&, const CDamageInfo&) const { return GetDamageVulnerability(); } void CCollisionActor::SetDamageVulnerability(const CDamageVulnerability& vuln) { x294_damageVuln = vuln; } zeus::CVector3f CCollisionActor::GetScanObjectIndicatorPosition(const CStateManager& mgr) const { const auto* gameCamera = static_cast(mgr.GetCameraManager()->GetCurrentCamera(mgr)); float scanScale; if (x258_primitiveType == EPrimitiveType::Sphere) { scanScale = GetSphereRadius(); } else { const zeus::CVector3f v = GetBoxSize(); float comp = v.x() >= v.y() ? v.z() : v.y(); if (comp < v.z()) { comp = v.x(); } scanScale = 0.5f * comp; } scanScale *= 3.0f; zeus::CVector3f orbitPos = GetOrbitPosition(mgr); return orbitPos - scanScale * (orbitPos - gameCamera->GetTranslation()).normalized(); } const CCollisionPrimitive* CCollisionActor::GetCollisionPrimitive() const { if (x258_primitiveType == EPrimitiveType::OBBTreeGroup) { return x27c_obbTreeGroupPrimitive.get(); } if (x258_primitiveType == EPrimitiveType::AABox) { return x280_aaboxPrimitive.get(); } return x284_spherePrimitive.get(); } EWeaponCollisionResponseTypes CCollisionActor::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&, const CWeaponMode&, EProjectileAttrib) const { return x300_responseType; } zeus::CTransform CCollisionActor::GetPrimitiveTransform() const { zeus::CTransform xf = x34_transform; xf.origin = CPhysicsActor::GetPrimitiveTransform().origin; return xf; } std::optional CCollisionActor::GetTouchBounds() const { std::optional aabox; if (x258_primitiveType == EPrimitiveType::OBBTreeGroup) aabox = {x27c_obbTreeGroupPrimitive->CalculateAABox(x34_transform)}; else if (x258_primitiveType == EPrimitiveType::AABox) aabox = {x280_aaboxPrimitive->CalculateAABox(x34_transform)}; else if (x258_primitiveType == EPrimitiveType::Sphere) aabox = {x284_spherePrimitive->CalculateAABox(x34_transform)}; aabox->accumulateBounds(aabox->max + x304_extendedTouchBounds); aabox->accumulateBounds(aabox->min - x304_extendedTouchBounds); return aabox; } void CCollisionActor::OnScanStateChanged(EScanState state, CStateManager& mgr) { if (const TCastToPtr actor = mgr.ObjectById(x25c_owner)) { actor->OnScanStateChanged(state, mgr); } CActor::OnScanStateChanged(state, mgr); } void CCollisionActor::Touch(CActor& actor, CStateManager& mgr) { x2fc_lastTouched = actor.GetUniqueId(); mgr.SendScriptMsgAlways(x25c_owner, GetUniqueId(), EScriptObjectMessage::Touched); } zeus::CVector3f CCollisionActor::GetOrbitPosition(const CStateManager&) const { return GetTouchBounds()->center(); } void CCollisionActor::SetSphereRadius(float radius) { if (x258_primitiveType != EPrimitiveType::Sphere) { return; } x288_sphereRadius = radius; x284_spherePrimitive->SetSphereRadius(radius); } void CCollisionActor::DebugDraw() { // zeus::CAABox aabox; // if (x258_primitiveType == EPrimitiveType::OBBTreeGroup) // aabox = x27c_obbTreeGroupPrimitive->CalculateAABox(x34_transform); // else if (x258_primitiveType == EPrimitiveType::AABox) // aabox = x280_aaboxPrimitive->CalculateAABox(x34_transform); // else if (x258_primitiveType == EPrimitiveType::Sphere) // aabox = x284_spherePrimitive->CalculateAABox(x34_transform); // m_aabox.setAABB(aabox); // zeus::CColor col = !GetActive() ? zeus::skRed : zeus::skGreen; // col.a() = 0.5f; // m_aabox.draw(col); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionActor.hpp ================================================ #pragma once #include #include #include "Runtime/World/CDamageVulnerability.hpp" #include "Runtime/World/CHealthInfo.hpp" #include "Runtime/World/CPhysicsActor.hpp" #include namespace metaforce { class CCollidableSphere; class CCollidableOBBTreeGroup; class CCollidableOBBTreeGroupContainer; class CCollisionActor : public CPhysicsActor { enum class EPrimitiveType { OBBTreeGroup, AABox, Sphere }; EPrimitiveType x258_primitiveType; TUniqueId x25c_owner; zeus::CVector3f x260_boxSize; zeus::CVector3f x26c_center; std::unique_ptr x278_obbContainer; std::unique_ptr x27c_obbTreeGroupPrimitive; std::unique_ptr x280_aaboxPrimitive; std::unique_ptr x284_spherePrimitive; float x288_sphereRadius; CHealthInfo x28c_healthInfo = CHealthInfo(0.f); CDamageVulnerability x294_damageVuln = CDamageVulnerability::NormalVulnerabilty(); TUniqueId x2fc_lastTouched = kInvalidUniqueId; EWeaponCollisionResponseTypes x300_responseType = EWeaponCollisionResponseTypes::EnemyNormal; zeus::CVector3f x304_extendedTouchBounds = zeus::skZero3f; public: DEFINE_ENTITY CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& extent, const zeus::CVector3f& center, bool active, float mass, std::string_view name); CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, const zeus::CVector3f& boxSize, bool active, float mass, std::string_view name); CCollisionActor(TUniqueId uid, TAreaId areaId, TUniqueId owner, bool active, float radius, float mass, std::string_view name); void Accept(IVisitor& visitor) override; void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) override; void DebugDraw(); CHealthInfo* HealthInfo(CStateManager& mgr) override; const CDamageVulnerability* GetDamageVulnerability() const override; const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2, const CDamageInfo& dInfo) const override; void OnScanStateChanged(EScanState state, CStateManager& mgr) override; void Touch(CActor& actor, CStateManager& mgr) override; zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override; const CCollisionPrimitive* GetCollisionPrimitive() const override; EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f& vec1, const zeus::CVector3f& vec2, const CWeaponMode& mode, EProjectileAttrib attribute) const override; void SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes type) { x300_responseType = type; } zeus::CTransform GetPrimitiveTransform() const override; std::optional GetTouchBounds() const override; void SetDamageVulnerability(const CDamageVulnerability& vuln); const zeus::CVector3f& GetBoxSize() const { return x260_boxSize; } TUniqueId GetOwnerId() const { return x25c_owner; } TUniqueId GetLastTouchedObject() const { return x2fc_lastTouched; } zeus::CVector3f GetScanObjectIndicatorPosition(const CStateManager& mgr) const override; void SetExtendedTouchBounds(const zeus::CVector3f& boundExt) { x304_extendedTouchBounds = boundExt; } void SetSphereRadius(float radius); float GetSphereRadius() const { return x288_sphereRadius; } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionActorManager.cpp ================================================ #include "Runtime/Collision/CCollisionActorManager.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Collision/CCollisionActor.hpp" #include "Runtime/Collision/CMaterialList.hpp" #include "Runtime/World/CActor.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CCollisionActorManager::CCollisionActorManager(CStateManager& mgr, TUniqueId owner, TAreaId area, const std::vector& descs, bool active) : x10_ownerId(owner), x12_active(active) { if (TCastToConstPtr act = mgr.GetObjectById(x10_ownerId)) { const zeus::CTransform xf = act->GetTransform(); const CAnimData* animData = act->GetModelData()->GetAnimationData(); const zeus::CVector3f scale = act->GetModelData()->GetScale(); const zeus::CTransform scaleXf = zeus::CTransform::Scale(scale); x0_jointDescriptions.reserve(descs.size()); for (const CJointCollisionDescription& desc : descs) { CJointCollisionDescription modDesc = desc; modDesc.ScaleAllBounds(scale); const zeus::CTransform locXf = GetWRLocatorTransform(*animData, modDesc.GetPivotId(), xf, scaleXf); if (modDesc.GetNextId().IsInvalid()) { // We only have the pivot id const TUniqueId newId = mgr.AllocateUniqueId(); CCollisionActor* newAct = nullptr; if (modDesc.GetType() == CJointCollisionDescription::ECollisionType::Sphere) { newAct = new CCollisionActor(newId, area, x10_ownerId, active, modDesc.GetRadius(), modDesc.GetMass(), desc.GetName()); } else if (modDesc.GetType() == CJointCollisionDescription::ECollisionType::OBB) { newAct = new CCollisionActor(newId, area, x10_ownerId, modDesc.GetBounds(), modDesc.GetPivotPoint(), active, modDesc.GetMass(), desc.GetName()); } else { newAct = new CCollisionActor(newId, area, x10_ownerId, modDesc.GetBounds(), active, modDesc.GetMass(), desc.GetName()); } newAct->SetTransform(locXf); mgr.AddObject(newAct); x0_jointDescriptions.push_back(desc); x0_jointDescriptions.back().SetCollisionActorId(newId); } else { // We have another bone in to connect to! const zeus::CTransform locXf2 = GetWRLocatorTransform(*animData, modDesc.GetNextId(), xf, scaleXf); const float dist = (locXf2.origin - locXf.origin).magnitude(); if (modDesc.GetType() != CJointCollisionDescription::ECollisionType::OBBAutoSize) { const TUniqueId newId = mgr.AllocateUniqueId(); auto* newAct = new CCollisionActor(newId, area, x10_ownerId, active, modDesc.GetRadius(), modDesc.GetMass(), desc.GetName()); newAct->SetTransform(locXf); mgr.AddObject(newAct); x0_jointDescriptions.push_back(CJointCollisionDescription::SphereCollision( modDesc.GetPivotId(), modDesc.GetRadius(), modDesc.GetName(), 0.001f)); x0_jointDescriptions.back().SetCollisionActorId(newId); const u32 numSeps = u32(dist / modDesc.GetMaxSeparation()); if (numSeps != 0) { x0_jointDescriptions.reserve(x0_jointDescriptions.capacity() + numSeps); const float pitch = dist / float(numSeps + 1); for (u32 i = 0; i < numSeps; ++i) { const float separation = pitch * float(i + 1); x0_jointDescriptions.push_back(CJointCollisionDescription::SphereSubdivideCollision( modDesc.GetPivotId(), modDesc.GetNextId(), modDesc.GetRadius(), separation, CJointCollisionDescription::EOrientationType::One, modDesc.GetName(), 0.001f)); const TUniqueId newId2 = mgr.AllocateUniqueId(); auto* newAct2 = new CCollisionActor(newId2, area, x10_ownerId, active, modDesc.GetRadius(), modDesc.GetMass(), desc.GetName()); if (modDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) { newAct2->SetTransform(zeus::CTransform::Translate(locXf.origin + (separation * locXf.basis[1]))); } else { const zeus::CVector3f delta = (locXf2.origin - locXf.origin).normalized(); zeus::CVector3f upVector = locXf.basis[2]; if (zeus::close_enough(std::fabs(delta.dot(upVector)), 1.f)) { upVector = locXf.basis[1]; } const zeus::CTransform lookAt = zeus::lookAt(zeus::skZero3f, delta, upVector); newAct2->SetTransform(zeus::CTransform::Translate(locXf.origin + (separation * lookAt.basis[1]))); } mgr.AddObject(newAct2); x0_jointDescriptions.back().SetCollisionActorId(newId2); } } } else { if (dist <= FLT_EPSILON) { continue; } zeus::CVector3f bounds = modDesc.GetBounds(); bounds.y() += dist; auto* newAct = new CCollisionActor(mgr.AllocateUniqueId(), area, x10_ownerId, bounds, zeus::CVector3f(0.f, 0.5f * dist, 0.f), active, modDesc.GetMass(), desc.GetName()); if (modDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) { newAct->SetTransform(locXf); } else { const zeus::CVector3f delta = (locXf2.origin - locXf.origin).normalized(); zeus::CVector3f upVector = locXf.basis[2]; if (zeus::close_enough(std::fabs(delta.dot(upVector)), 1.f)) { upVector = locXf.basis[1]; } newAct->SetTransform(zeus::lookAt(locXf.origin, locXf.origin + delta, upVector)); } mgr.AddObject(newAct); x0_jointDescriptions.push_back(desc); x0_jointDescriptions.back().SetCollisionActorId(newAct->GetUniqueId()); } } } } } void CCollisionActorManager::Destroy(CStateManager& mgr) { for (const CJointCollisionDescription& desc : x0_jointDescriptions) { mgr.FreeScriptObject(desc.GetCollisionActorId()); } x13_destroyed = true; } void CCollisionActorManager::SetActive(CStateManager& mgr, bool active) { x12_active = active; for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) { if (TCastToPtr act = mgr.ObjectById(jDesc.GetCollisionActorId())) { const bool curActive = act->GetActive(); if (curActive != active) { act->SetActive(active); if (!active) { Update(0.f, mgr, EUpdateOptions::WorldSpace); } } } } } void CCollisionActorManager::AddMaterial(CStateManager& mgr, const CMaterialList& list) { for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) { if (TCastToPtr act = mgr.ObjectById(jDesc.GetCollisionActorId())) { act->AddMaterial(list); } } } void CCollisionActorManager::SetMovable(CStateManager& mgr, bool movable) { if (x14_movable == movable) { return; } x14_movable = movable; for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) { if (TCastToPtr act = mgr.ObjectById(jDesc.GetCollisionActorId())) { act->SetMovable(x14_movable); act->SetUseInSortedLists(x14_movable); } } } void CCollisionActorManager::Update(float dt, CStateManager& mgr, EUpdateOptions opts) { if (!x14_movable) { SetMovable(mgr, true); } if (!x12_active) { return; } if (const TCastToConstPtr act = mgr.ObjectById(x10_ownerId)) { const CAnimData& animData = *act->GetModelData()->GetAnimationData(); const zeus::CTransform actXf = act->GetTransform(); const zeus::CTransform scaleXf = zeus::CTransform::Scale(act->GetModelData()->GetScale()); for (const CJointCollisionDescription& jDesc : x0_jointDescriptions) { if (TCastToPtr cAct = mgr.ObjectById(jDesc.GetCollisionActorId())) { const zeus::CTransform pivotXf = GetWRLocatorTransform(animData, jDesc.GetPivotId(), actXf, scaleXf); zeus::CVector3f origin = pivotXf.origin; if (jDesc.GetType() == CJointCollisionDescription::ECollisionType::OBB || jDesc.GetType() == CJointCollisionDescription::ECollisionType::OBBAutoSize) { if (jDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) { cAct->SetTransform(zeus::CQuaternion(pivotXf.basis).toTransform(cAct->GetTranslation())); } else { const zeus::CTransform nextXf = GetWRLocatorTransform(animData, jDesc.GetNextId(), actXf, scaleXf); cAct->SetTransform(zeus::CQuaternion(zeus::lookAt(pivotXf.origin, nextXf.origin, pivotXf.basis[2]).basis) .toTransform(cAct->GetTranslation())); } } else if (jDesc.GetType() == CJointCollisionDescription::ECollisionType::SphereSubdivide) { if (jDesc.GetOrientationType() == CJointCollisionDescription::EOrientationType::Zero) { origin += jDesc.GetMaxSeparation() * pivotXf.basis[1]; } else { const zeus::CTransform nextXf = GetWRLocatorTransform(animData, jDesc.GetNextId(), actXf, scaleXf); origin += zeus::lookAt(origin, nextXf.origin, pivotXf.basis[2]).basis[1] * jDesc.GetMaxSeparation(); } } if (opts == EUpdateOptions::ObjectSpace) { cAct->MoveToOR(cAct->GetTransform().transposeRotate(origin - cAct->GetTranslation()), dt); } else { cAct->SetTranslation(origin); } } } } } zeus::CTransform CCollisionActorManager::GetWRLocatorTransform(const CAnimData& animData, CSegId id, const zeus::CTransform& worldXf, const zeus::CTransform& localXf) { zeus::CTransform locXf = animData.GetLocatorTransform(id, nullptr); const zeus::CVector3f origin = worldXf * (localXf * locXf.origin); locXf = worldXf.multiplyIgnoreTranslation(locXf); locXf.origin = origin; return locXf; } std::optional CCollisionActorManager::GetDeviation(const CStateManager& mgr, CSegId seg) { for (const CJointCollisionDescription& desc : x0_jointDescriptions) { if (desc.GetPivotId() != seg) { continue; } if (const TCastToConstPtr act = mgr.GetObjectById(x10_ownerId)) { if (const TCastToConstPtr colAct = mgr.GetObjectById(desc.GetCollisionActorId())) { const zeus::CTransform xf = GetWRLocatorTransform(*act->GetModelData()->GetAnimationData(), desc.GetPivotId(), act->GetTransform(), zeus::CTransform::Scale(act->GetModelData()->GetScale())); return {colAct->GetTranslation() - xf.origin}; } } } return std::nullopt; } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionActorManager.hpp ================================================ #pragma once #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Collision/CJointCollisionDescription.hpp" #include #include namespace metaforce { class CAnimData; class CCollisionActor; class CMaterialList; class CStateManager; class CCollisionActorManager { public: enum class EUpdateOptions { ObjectSpace, WorldSpace }; private: std::vector x0_jointDescriptions; TUniqueId x10_ownerId; bool x12_active; bool x13_destroyed = false; bool x14_movable = true; public: CCollisionActorManager(CStateManager& mgr, TUniqueId owner, TAreaId area, const std::vector& descs, bool active); void Update(float dt, CStateManager& mgr, EUpdateOptions opts); void Destroy(CStateManager& mgr); void SetActive(CStateManager& mgr, bool active); [[nodiscard]] bool GetActive() const { return x12_active; } void AddMaterial(CStateManager& mgr, const CMaterialList& list); void SetMovable(CStateManager& mgr, bool movable); [[nodiscard]] u32 GetNumCollisionActors() const { return x0_jointDescriptions.size(); } std::optional GetDeviation(const CStateManager&, CSegId); [[nodiscard]] const CJointCollisionDescription& GetCollisionDescFromIndex(u32 i) const { return x0_jointDescriptions[i]; } static zeus::CTransform GetWRLocatorTransform(const CAnimData& animData, CSegId id, const zeus::CTransform& worldXf, const zeus::CTransform& localXf); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionEdge.cpp ================================================ #include "Runtime/Collision/CCollisionEdge.hpp" #include "Runtime/Streams/CInputStream.hpp" namespace metaforce { CCollisionEdge::CCollisionEdge(CInputStream& in) { x0_index1 = in.ReadShort(); x2_index2 = in.ReadShort(); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionEdge.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" namespace metaforce { class CCollisionEdge { u16 x0_index1 = -1; u16 x2_index2 = -1; public: constexpr CCollisionEdge() noexcept = default; explicit CCollisionEdge(CInputStream&); constexpr CCollisionEdge(u16 v0, u16 v1) noexcept : x0_index1(v0), x2_index2(v1) {} [[nodiscard]] constexpr u16 GetVertIndex1() const noexcept { return x0_index1; } [[nodiscard]] constexpr u16 GetVertIndex2() const noexcept { return x2_index2; } constexpr void swapBig() noexcept { x0_index1 = SBig(x0_index1); x2_index2 = SBig(x2_index2); } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionInfo.cpp ================================================ #include "Runtime/Collision/CCollisionInfo.hpp" #include namespace metaforce { CCollisionInfo CCollisionInfo::GetSwapped() const { CCollisionInfo ret; ret.x0_point = x0_point; ret.xc_extentX = xc_extentX; ret.x30_valid = x30_valid; ret.x31_hasExtents = x31_hasExtents; ret.x38_materialLeft = x40_materialRight; ret.x40_materialRight = x38_materialLeft; ret.x48_normalLeft = x54_normalRight; ret.x54_normalRight = x48_normalLeft; return ret; } void CCollisionInfo::Swap() { x48_normalLeft = -x48_normalLeft; x54_normalRight = -x54_normalRight; std::swap(x38_materialLeft, x40_materialRight); } zeus::CVector3f CCollisionInfo::GetExtreme() const { return x0_point + xc_extentX + x18_extentY + x24_extentZ; } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionInfo.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/Collision/CMaterialList.hpp" #include #include #include namespace metaforce { class CCollisionInfo { friend class CCollisionInfoList; zeus::CVector3f x0_point; zeus::CVector3f xc_extentX; zeus::CVector3f x18_extentY; zeus::CVector3f x24_extentZ; bool x30_valid = false; bool x31_hasExtents = false; CMaterialList x38_materialLeft; CMaterialList x40_materialRight; zeus::CUnitVector3f x48_normalLeft; zeus::CUnitVector3f x54_normalRight; public: CCollisionInfo() = default; CCollisionInfo(const zeus::CVector3f& point, const CMaterialList& list1, const CMaterialList& list2, const zeus::CUnitVector3f& normalLeft, const zeus::CUnitVector3f& normalRight) : x0_point(point) , x30_valid(true) , x31_hasExtents(false) , x38_materialLeft(list2) , x40_materialRight(list1) , x48_normalLeft(normalLeft) , x54_normalRight(normalRight) {} CCollisionInfo(const zeus::CVector3f& point, const CMaterialList& list1, const CMaterialList& list2, const zeus::CUnitVector3f& normal) : x0_point(point) , x30_valid(true) , x31_hasExtents(false) , x38_materialLeft(list2) , x40_materialRight(list1) , x48_normalLeft(normal) , x54_normalRight(-normal) {} CCollisionInfo(const zeus::CAABox& aabox, const CMaterialList& list1, const CMaterialList& list2, const zeus::CUnitVector3f& normalLeft, const zeus::CUnitVector3f& normalRight) : x0_point(aabox.min) , xc_extentX(aabox.max.x() - aabox.min.x(), 0.f, 0.f) , x18_extentY(0.f, aabox.max.y() - aabox.min.y(), 0.f) , x24_extentZ(0.f, 0.f, aabox.max.z() - aabox.min.z()) , x30_valid(true) , x31_hasExtents(true) , x38_materialLeft(list2) , x40_materialRight(list1) , x48_normalLeft(normalLeft) , x54_normalRight(normalRight) {} CCollisionInfo GetSwapped() const; bool IsValid() const { return x30_valid; } const CMaterialList& GetMaterialLeft() const { return x38_materialLeft; } const CMaterialList& GetMaterialRight() const { return x40_materialRight; } zeus::CVector3f GetExtreme() const; void Swap(); const zeus::CUnitVector3f& GetNormalLeft() const { return x48_normalLeft; } const zeus::CUnitVector3f& GetNormalRight() const { return x54_normalRight; } const zeus::CVector3f& GetPoint() const { return x0_point; } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionInfoList.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Collision/CCollisionInfo.hpp" namespace metaforce { class CCollisionInfoList { rstl::reserved_vector x0_list; public: CCollisionInfoList() = default; zeus::CVector3f GetAverageLeftNormal() const { zeus::CVector3f ret; for (const auto& inf : x0_list) { ret += inf.GetNormalLeft(); } return ret / x0_list.size(); } zeus::CVector3f GetAveragePoint() const { zeus::CVector3f ret; for (const auto& inf : x0_list) { ret += inf.GetPoint(); } return ret / x0_list.size(); } CMaterialList GetUnionOfAllLeftMaterials() const { CMaterialList list; for (const auto& inf : x0_list) { list.Union(inf.GetMaterialLeft()); } return list; } size_t GetCount() const { return x0_list.size(); } void Swap(s32 idx) { if (idx >= x0_list.size()) return; x0_list[idx].Swap(); } void Add(const CCollisionInfo& info, bool swap) { if (x0_list.size() == 32) return; if (!swap) x0_list.push_back(info); else x0_list.push_back(info.GetSwapped()); } void Clear() { x0_list.clear(); } const CCollisionInfo& Front() const { return x0_list.front(); } const CCollisionInfo& GetItem(int i) const { return x0_list[i]; } auto end() noexcept { return x0_list.end(); } auto end() const noexcept { return x0_list.end(); } auto begin() noexcept { return x0_list.begin(); } auto begin() const noexcept { return x0_list.begin(); } void AccumulateNewContactsInto(CCollisionInfoList& other_list) { for (CCollisionInfo const& cur_info : x0_list) { bool dont_add_new_info = false; for (CCollisionInfo& other_info : other_list) { if (!zeus::close_enough(other_info.GetPoint(), cur_info.GetPoint(), 0.1f)) { continue; } zeus::CVector3f norm = other_info.GetNormalLeft().normalized(); if (zeus::close_enough(norm, cur_info.GetNormalLeft(), 1.2f)) { dont_add_new_info = true; other_info.x0_point = (other_info.x0_point + cur_info.x0_point) * 0.5f; other_info.x38_materialLeft.Add(cur_info.x38_materialLeft); other_info.x40_materialRight.Add(cur_info.x40_materialRight); other_info.x48_normalLeft = other_info.x48_normalLeft + cur_info.x48_normalLeft; break; } } if (!dont_add_new_info) { other_list.Add(cur_info, false); } } for (CCollisionInfo& other_info : other_list.x0_list) { other_info.x48_normalLeft.normalize(); other_info.x54_normalRight = -other_info.x48_normalLeft; } } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionPrimitive.cpp ================================================ #include "Runtime/Collision/CCollisionPrimitive.hpp" #include #include #include #include #include "Runtime/Collision/CCollisionInfoList.hpp" #include "Runtime/Collision/CInternalRayCastStructure.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include "Runtime/Collision/InternalColliders.hpp" namespace metaforce { s32 CCollisionPrimitive::sNumTypes = 0; bool CCollisionPrimitive::sInitComplete = false; bool CCollisionPrimitive::sTypesAdded = false; bool CCollisionPrimitive::sTypesAdding = false; bool CCollisionPrimitive::sCollidersAdded = false; bool CCollisionPrimitive::sCollidersAdding = false; std::unique_ptr> CCollisionPrimitive::sCollisionTypeList; std::unique_ptr> CCollisionPrimitive::sTableOfCollidables; std::unique_ptr> CCollisionPrimitive::sTableOfBooleanCollidables; std::unique_ptr> CCollisionPrimitive::sTableOfMovingCollidables; ComparisonFunc CCollisionPrimitive::sNullCollider = {}; BooleanComparisonFunc CCollisionPrimitive::sNullBooleanCollider = {}; MovingComparisonFunc CCollisionPrimitive::sNullMovingCollider = {}; CCollisionPrimitive::CCollisionPrimitive(const CMaterialList& list) : x8_material(list) {} void CCollisionPrimitive::SetMaterial(const CMaterialList& material) { x8_material = material; } const CMaterialList& CCollisionPrimitive::GetMaterial() const { return x8_material; } CRayCastResult CCollisionPrimitive::CastRay(const zeus::CVector3f& start, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter, const zeus::CTransform& xf) const { return CastRayInternal(CInternalRayCastStructure(start, dir, length, xf, filter)); } std::vector::const_iterator CCollisionPrimitive::TypeIndexFromTypeInfo(const char* name) { return std::find_if(sCollisionTypeList->cbegin(), sCollisionTypeList->cend(), [name](const auto& type) { return std::strcmp(name, type.GetInfo()) == 0; }); } bool CCollisionPrimitive::InternalCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list) { u32 idx0 = collision.GetLeft().GetPrim().GetTableIndex(); u32 idx1 = collision.GetRight().GetPrim().GetTableIndex(); ComparisonFunc func; if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) { sNullCollider = nullptr; func = sNullCollider; } else { func = (*sTableOfCollidables)[sNumTypes * idx1 + idx0]; } if (func) { if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) || !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial())) return false; return func(collision, list); } if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) { sNullCollider = nullptr; func = sNullCollider; } else { func = (*sTableOfCollidables)[sNumTypes * idx0 + idx1]; } if (func) { if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) || !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial())) return false; CInternalCollisionStructure swappedCollision(collision.GetRight(), collision.GetLeft()); u32 startListCount = list.GetCount(); if (func(swappedCollision, list)) { for (auto it = list.begin() + startListCount; it != list.end(); ++it) it->Swap(); return true; } } return false; } bool CCollisionPrimitive::Collide(const CInternalCollisionStructure::CPrimDesc& prim0, const CInternalCollisionStructure::CPrimDesc& prim1, CCollisionInfoList& list) { return InternalCollide({prim0, prim1}, list); } bool CCollisionPrimitive::InternalCollideBoolean(const CInternalCollisionStructure& collision) { u32 idx0 = collision.GetLeft().GetPrim().GetTableIndex(); u32 idx1 = collision.GetRight().GetPrim().GetTableIndex(); BooleanComparisonFunc func; if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) { sNullBooleanCollider = nullptr; func = sNullBooleanCollider; } else { func = (*sTableOfBooleanCollidables)[sNumTypes * idx1 + idx0]; } if (func) { if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) || !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial())) return false; return func(collision); } if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) { sNullBooleanCollider = nullptr; func = sNullBooleanCollider; } else { func = (*sTableOfBooleanCollidables)[sNumTypes * idx0 + idx1]; } if (func) { if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) || !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial())) return false; CInternalCollisionStructure swappedCollision(collision.GetRight(), collision.GetLeft()); return func(swappedCollision); } CCollisionInfoList list; return InternalCollide(collision, list); } bool CCollisionPrimitive::CollideBoolean(const CInternalCollisionStructure::CPrimDesc& prim0, const CInternalCollisionStructure::CPrimDesc& prim1) { return InternalCollideBoolean({prim0, prim1}); } bool CCollisionPrimitive::InternalCollideMoving(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) { u32 idx0 = collision.GetLeft().GetPrim().GetTableIndex(); u32 idx1 = collision.GetRight().GetPrim().GetTableIndex(); MovingComparisonFunc func; if (idx0 == UINT32_MAX || idx1 == UINT32_MAX) { sNullMovingCollider = nullptr; func = sNullMovingCollider; } else { func = (*sTableOfMovingCollidables)[sNumTypes * idx1 + idx0]; } if (func) { if (!collision.GetLeft().GetFilter().Passes(collision.GetRight().GetPrim().GetMaterial()) || !collision.GetRight().GetFilter().Passes(collision.GetLeft().GetPrim().GetMaterial())) return false; return func(collision, dir, dOut, infoOut); } return false; } bool CCollisionPrimitive::CollideMoving(const CInternalCollisionStructure::CPrimDesc& prim0, const CInternalCollisionStructure::CPrimDesc& prim1, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut) { return InternalCollideMoving({prim0, prim1}, dir, dOut, infoOut); } void CCollisionPrimitive::InitBeginTypes() { sCollisionTypeList = std::make_unique>(); sCollisionTypeList->reserve(3); sTypesAdding = true; InternalColliders::AddTypes(); } void CCollisionPrimitive::InitAddType(const Type& tp) { tp.GetSetter()(sCollisionTypeList->size()); sCollisionTypeList->push_back(tp); } void CCollisionPrimitive::InitEndTypes() { sCollisionTypeList->shrink_to_fit(); sNumTypes = sCollisionTypeList->size(); sTypesAdding = false; sTypesAdded = true; } void CCollisionPrimitive::InitBeginColliders() { const size_t tableSz = sCollisionTypeList->size() * sCollisionTypeList->size(); sTableOfCollidables = std::make_unique>(tableSz); sTableOfBooleanCollidables = std::make_unique>(tableSz); sTableOfMovingCollidables = std::make_unique>(tableSz); sCollidersAdding = true; InternalColliders::AddColliders(); } void CCollisionPrimitive::InitAddBooleanCollider(const BooleanComparison& cmp) { const auto iter1 = TypeIndexFromTypeInfo(cmp.GetType1()); const auto iter2 = TypeIndexFromTypeInfo(cmp.GetType2()); const auto index1 = std::distance(sCollisionTypeList->cbegin(), iter1); const auto index2 = std::distance(sCollisionTypeList->cbegin(), iter2); const bool hasReachedEnd = iter1 == sCollisionTypeList->cend() || iter2 == sCollisionTypeList->cend(); if (index1 >= sNumTypes || index2 >= sNumTypes || hasReachedEnd) { return; } BooleanComparisonFunc& funcOut = hasReachedEnd ? sNullBooleanCollider : (*sTableOfBooleanCollidables)[index2 * sNumTypes + index1]; funcOut = cmp.GetCollider(); } void CCollisionPrimitive::InitAddBooleanCollider(BooleanComparisonFunc cmp, const char* a, const char* b) { InitAddBooleanCollider({cmp, a, b}); } void CCollisionPrimitive::InitAddMovingCollider(const MovingComparison& cmp) { const auto iter1 = TypeIndexFromTypeInfo(cmp.GetType1()); const auto iter2 = TypeIndexFromTypeInfo(cmp.GetType2()); const auto index1 = std::distance(sCollisionTypeList->cbegin(), iter1); const auto index2 = std::distance(sCollisionTypeList->cbegin(), iter2); const bool hasReachedEnd = iter1 == sCollisionTypeList->cend() || iter2 == sCollisionTypeList->cend(); if (index1 >= sNumTypes || index2 >= sNumTypes || hasReachedEnd) { return; } MovingComparisonFunc& funcOut = hasReachedEnd ? sNullMovingCollider : (*sTableOfMovingCollidables)[index2 * sNumTypes + index1]; funcOut = cmp.GetCollider(); } void CCollisionPrimitive::InitAddMovingCollider(MovingComparisonFunc cmp, const char* a, const char* b) { InitAddMovingCollider({cmp, a, b}); } void CCollisionPrimitive::InitAddCollider(const Comparison& cmp) { const auto iter1 = TypeIndexFromTypeInfo(cmp.GetType1()); const auto iter2 = TypeIndexFromTypeInfo(cmp.GetType2()); const auto index1 = std::distance(sCollisionTypeList->cbegin(), iter1); const auto index2 = std::distance(sCollisionTypeList->cbegin(), iter2); const bool hasReachedEnd = iter1 == sCollisionTypeList->cend() || iter2 == sCollisionTypeList->cend(); if (index1 >= sNumTypes || index2 >= sNumTypes || hasReachedEnd) { return; } ComparisonFunc& funcOut = hasReachedEnd ? sNullCollider : (*sTableOfCollidables)[index2 * sNumTypes + index1]; funcOut = cmp.GetCollider(); } void CCollisionPrimitive::InitAddCollider(ComparisonFunc cmp, const char* a, const char* b) { InitAddCollider({cmp, a, b}); } void CCollisionPrimitive::InitEndColliders() { sCollidersAdding = false; sCollidersAdded = true; sInitComplete = true; } void CCollisionPrimitive::Uninitialize() { sInitComplete = false; sCollidersAdding = false; sCollidersAdded = false; sTypesAdding = false; sTypesAdded = false; sNumTypes = 0; sCollisionTypeList.reset(); sTableOfCollidables.reset(); sTableOfMovingCollidables.reset(); sTableOfBooleanCollidables.reset(); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionPrimitive.hpp ================================================ #pragma once #include #include #include #include "Runtime/Collision/CMaterialList.hpp" #include "Runtime/Collision/CRayCastResult.hpp" #include #include #include namespace metaforce { class CCollisionPrimitive; class CMaterialFilter; class CInternalCollisionStructure { public: class CPrimDesc { const CCollisionPrimitive& x0_prim; const CMaterialFilter& x4_filter; zeus::CTransform x8_xf; public: CPrimDesc(const CCollisionPrimitive& prim, const CMaterialFilter& filter, const zeus::CTransform& xf) : x0_prim(prim), x4_filter(filter), x8_xf(xf) {} const CCollisionPrimitive& GetPrim() const { return x0_prim; } const CMaterialFilter& GetFilter() const { return x4_filter; } const zeus::CTransform& GetTransform() const { return x8_xf; } }; private: CPrimDesc x0_p0; CPrimDesc x38_p1; public: CInternalCollisionStructure(const CPrimDesc& p0, const CPrimDesc& p1) : x0_p0(p0), x38_p1(p1) {} const CPrimDesc& GetLeft() const { return x0_p0; } const CPrimDesc& GetRight() const { return x38_p1; } }; class COBBTree; class CCollisionInfo; class CCollisionInfoList; class CInternalRayCastStructure; using BooleanComparisonFunc = bool (*)(const CInternalCollisionStructure&); using ComparisonFunc = bool (*)(const CInternalCollisionStructure&, CCollisionInfoList&); using MovingComparisonFunc = bool (*)(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&); using PrimitiveSetter = void (*)(u32); class CCollisionPrimitive { public: class Type { PrimitiveSetter x0_setter = nullptr; const char* x4_info = nullptr; public: constexpr Type() noexcept = default; constexpr Type(PrimitiveSetter setter, const char* info) noexcept : x0_setter(setter), x4_info(info) {} constexpr const char* GetInfo() const noexcept { return x4_info; } constexpr PrimitiveSetter GetSetter() const noexcept { return x0_setter; } }; class Comparison { ComparisonFunc x0_collider; const char* x4_type1; const char* x8_type2; public: constexpr Comparison(ComparisonFunc collider, const char* type1, const char* type2) noexcept : x0_collider(collider), x4_type1(type1), x8_type2(type2) {} constexpr ComparisonFunc GetCollider() const noexcept { return x0_collider; } constexpr const char* GetType1() const noexcept { return x4_type1; } constexpr const char* GetType2() const noexcept { return x8_type2; } }; class MovingComparison { MovingComparisonFunc x0_collider; const char* x4_type1; const char* x8_type2; public: constexpr MovingComparison(MovingComparisonFunc collider, const char* type1, const char* type2) noexcept : x0_collider(collider), x4_type1(type1), x8_type2(type2) {} constexpr MovingComparisonFunc GetCollider() const noexcept { return x0_collider; } constexpr const char* GetType1() const noexcept { return x4_type1; } constexpr const char* GetType2() const noexcept { return x8_type2; } }; class BooleanComparison { BooleanComparisonFunc x0_collider; const char* x4_type1; const char* x8_type2; public: constexpr BooleanComparison(BooleanComparisonFunc collider, const char* type1, const char* type2) noexcept : x0_collider(collider), x4_type1(type1), x8_type2(type2) {} constexpr BooleanComparisonFunc GetCollider() const noexcept { return x0_collider; } constexpr const char* GetType1() const noexcept { return x4_type1; } constexpr const char* GetType2() const noexcept { return x8_type2; } }; private: CMaterialList x8_material; static s32 sNumTypes; static bool sInitComplete; static bool sTypesAdded; static bool sTypesAdding; static bool sCollidersAdded; static bool sCollidersAdding; static std::unique_ptr> sCollisionTypeList; static std::unique_ptr> sTableOfCollidables; static std::unique_ptr> sTableOfBooleanCollidables; static std::unique_ptr> sTableOfMovingCollidables; static ComparisonFunc sNullCollider; static BooleanComparisonFunc sNullBooleanCollider; static MovingComparisonFunc sNullMovingCollider; // Attempts to locate an entry within the collision type list that matches the supplied name. // Returns the end iterator in the event of no matches. static std::vector::const_iterator TypeIndexFromTypeInfo(const char* name); static bool InternalCollide(const CInternalCollisionStructure& collision, CCollisionInfoList& list); static bool InternalCollideBoolean(const CInternalCollisionStructure& collision); static bool InternalCollideMoving(const CInternalCollisionStructure& collision, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut); public: CCollisionPrimitive() = default; explicit CCollisionPrimitive(const CMaterialList& list); virtual u32 GetTableIndex() const = 0; virtual void SetMaterial(const CMaterialList&); virtual const CMaterialList& GetMaterial() const; virtual zeus::CAABox CalculateAABox(const zeus::CTransform&) const = 0; virtual zeus::CAABox CalculateLocalAABox() const = 0; virtual FourCC GetPrimType() const = 0; virtual ~CCollisionPrimitive() = default; virtual CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const = 0; CRayCastResult CastRay(const zeus::CVector3f& start, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter, const zeus::CTransform& xf) const; static bool Collide(const CInternalCollisionStructure::CPrimDesc& prim0, const CInternalCollisionStructure::CPrimDesc& prim1, CCollisionInfoList& list); static bool CollideBoolean(const CInternalCollisionStructure::CPrimDesc& prim0, const CInternalCollisionStructure::CPrimDesc& prim1); static bool CollideMoving(const CInternalCollisionStructure::CPrimDesc& prim0, const CInternalCollisionStructure::CPrimDesc& prim1, const zeus::CVector3f& dir, double& dOut, CCollisionInfo& infoOut); static void InitBeginTypes(); static void InitAddType(const Type& tp); static void InitEndTypes(); static void InitBeginColliders(); static void InitAddBooleanCollider(const BooleanComparison& cmp); static void InitAddBooleanCollider(BooleanComparisonFunc, const char*, const char*); static void InitAddMovingCollider(const MovingComparison& cmp); static void InitAddMovingCollider(MovingComparisonFunc, const char*, const char*); static void InitAddCollider(const Comparison& cmp); static void InitAddCollider(ComparisonFunc, const char*, const char*); static void InitEndColliders(); static void Uninitialize(); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionResponseData.cpp ================================================ #include "Runtime/Collision/CCollisionResponseData.hpp" #include #include "Runtime/CRandom16.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/Graphics/CModel.hpp" #include "Runtime/Particle/CDecalDescription.hpp" #include "Runtime/Particle/CElectricDescription.hpp" #include "Runtime/Particle/CGenDescription.hpp" #include "Runtime/Particle/CParticleDataFactory.hpp" #include "Runtime/Particle/CSwooshDescription.hpp" namespace metaforce { namespace { constexpr std::array skWorldMaterialTable{ EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Unknown2, EWeaponCollisionResponseTypes::Metal, EWeaponCollisionResponseTypes::Grass, EWeaponCollisionResponseTypes::Ice, EWeaponCollisionResponseTypes::Goo, EWeaponCollisionResponseTypes::Metal, EWeaponCollisionResponseTypes::Wood, EWeaponCollisionResponseTypes::Grass, EWeaponCollisionResponseTypes::Lava, EWeaponCollisionResponseTypes::Lava, EWeaponCollisionResponseTypes::Ice, EWeaponCollisionResponseTypes::Mud, EWeaponCollisionResponseTypes::Metal, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Goo, EWeaponCollisionResponseTypes::Goo, EWeaponCollisionResponseTypes::Sand, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Metal, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, EWeaponCollisionResponseTypes::Default, }; constexpr s32 kInvalidSFX = -1; constexpr std::array kWCRTSFXIDs{{ SBIG('NSFX'), SBIG('DSFX'), SBIG('CSFX'), SBIG('MSFX'), SBIG('GRFX'), SBIG('ICFX'), SBIG('GOFX'), SBIG('WSFX'), SBIG('WTFX'), SBIG('2MUD'), SBIG('2LAV'), SBIG('2SAN'), SBIG('2PRJ'), SBIG('DCFX'), SBIG('DSFX'), SBIG('DSHX'), SBIG('DEFX'), SBIG('ESFX'), SBIG('SHFX'), SBIG('BEFX'), SBIG('WWFX'), SBIG('TAFX'), SBIG('GTFX'), SBIG('SPFX'), SBIG('FPFX'), SBIG('FFFX'), SBIG('PAFX'), SBIG('BMFX'), SBIG('BFFX'), SBIG('PBFX'), SBIG('IBFX'), SBIG('4SVA'), SBIG('4RPR'), SBIG('4MTR'), SBIG('4PDS'), SBIG('4FLB'), SBIG('4DRN'), SBIG('4MRE'), SBIG('CZFX'), SBIG('JZAS'), SBIG('2ISE'), SBIG('2BSE'), SBIG('2ATB'), SBIG('2ATA'), SBIG('BSFX'), SBIG('WSFX'), SBIG('TSFX'), SBIG('GSFX'), SBIG('SSFX'), SBIG('FSFX'), SBIG('SFFX'), SBIG('PSFX'), SBIG('MSFX'), SBIG('SBFX'), SBIG('PBSX'), SBIG('IBSX'), SBIG('5SVA'), SBIG('5RPR'), SBIG('5MTR'), SBIG('5PDS'), SBIG('5FLB'), SBIG('5DRN'), SBIG('5MRE'), SBIG('CSFX'), SBIG('JZPS'), SBIG('4ISE'), SBIG('4BSE'), SBIG('4ATB'), SBIG('4ATA'), SBIG('BHFX'), SBIG('WHFX'), SBIG('THFX'), SBIG('GHFX'), SBIG('SHFX'), SBIG('FHFX'), SBIG('HFFX'), SBIG('PHFX'), SBIG('MHFX'), SBIG('HBFX'), SBIG('PBHX'), SBIG('IBHX'), SBIG('6SVA'), SBIG('6RPR'), SBIG('6MTR'), SBIG('6PDS'), SBIG('6FLB'), SBIG('6DRN'), SBIG('6MRE'), SBIG('CHFX'), SBIG('JZHS'), SBIG('6ISE'), SBIG('6BSE'), SBIG('6ATB'), SBIG('6ATA'), }}; constexpr std::array kWCRTIDs{{ SBIG('NODP'), SBIG('DEFS'), SBIG('CRTS'), SBIG('MTLS'), SBIG('GRAS'), SBIG('ICEE'), SBIG('GOOO'), SBIG('WODS'), SBIG('WATR'), SBIG('1MUD'), SBIG('1LAV'), SBIG('1SAN'), SBIG('1PRJ'), SBIG('DCHR'), SBIG('DCHS'), SBIG('DCSH'), SBIG('DENM'), SBIG('DESP'), SBIG('DESH'), SBIG('BTLE'), SBIG('WASP'), SBIG('TALP'), SBIG('PTGM'), SBIG('SPIR'), SBIG('FPIR'), SBIG('FFLE'), SBIG('PARA'), SBIG('BMON'), SBIG('BFLR'), SBIG('PBOS'), SBIG('IBOS'), SBIG('1SVA'), SBIG('1RPR'), SBIG('1MTR'), SBIG('1PDS'), SBIG('1FLB'), SBIG('1DRN'), SBIG('1MRE'), SBIG('CHOZ'), SBIG('JZAP'), SBIG('1ISE'), SBIG('1BSE'), SBIG('1ATB'), SBIG('1ATA'), SBIG('BTSP'), SBIG('WWSP'), SBIG('TASP'), SBIG('TGSP'), SBIG('SPSP'), SBIG('FPSP'), SBIG('FFSP'), SBIG('PSSP'), SBIG('BMSP'), SBIG('BFSP'), SBIG('PBSP'), SBIG('IBSP'), SBIG('2SVA'), SBIG('2RPR'), SBIG('2MTR'), SBIG('2PDS'), SBIG('2FLB'), SBIG('2DRN'), SBIG('2MRE'), SBIG('CHSP'), SBIG('JZSP'), SBIG('3ISE'), SBIG('3BSE'), SBIG('3ATB'), SBIG('3ATA'), SBIG('BTSH'), SBIG('WWSH'), SBIG('TASH'), SBIG('TGSH'), SBIG('SPSH'), SBIG('FPSH'), SBIG('FFSH'), SBIG('PSSH'), SBIG('BMSH'), SBIG('BFSH'), SBIG('PBSH'), SBIG('IBSH'), SBIG('3SVA'), SBIG('3RPR'), SBIG('3MTR'), SBIG('3PDS'), SBIG('3FLB'), SBIG('3DRN'), SBIG('3MRE'), SBIG('CHSH'), SBIG('JZSH'), SBIG('5ISE'), SBIG('5BSE'), SBIG('5ATB'), SBIG('5ATA'), }}; constexpr std::array kWCRTDecalIDs{{ SBIG('NCDL'), SBIG('DDCL'), SBIG('CODL'), SBIG('MEDL'), SBIG('GRDL'), SBIG('ICDL'), SBIG('GODL'), SBIG('WODL'), SBIG('WTDL'), SBIG('3MUD'), SBIG('3LAV'), SBIG('3SAN'), SBIG('CHDL'), SBIG('ENDL'), }}; using CPF = CParticleDataFactory; } // Anonymous namespace void CCollisionResponseData::AddParticleSystemToResponse(EWeaponCollisionResponseTypes type, CInputStream& in, CSimplePool* resPool) { const auto i = size_t(type); const std::vector tracker(8); x0_generators[i] = CPF::GetChildGeneratorDesc(in, resPool, tracker).x0_res; } bool CCollisionResponseData::CheckAndAddDecalToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool) { size_t i = 0; for (const FourCC& type : kWCRTDecalIDs) { if (type == clsId) { const FourCC cls = CPF::GetClassID(in); if (cls == SBIG('NONE')) { return true; } const CAssetId id = in.Get(); if (!id.IsValid()) { return true; } x20_decals[i].emplace(resPool->GetObj({FOURCC('DPSC'), id})); return true; } i++; } return false; } bool CCollisionResponseData::CheckAndAddSoundFXToResponse(FourCC clsId, CInputStream& in) { size_t i = 0; for (const FourCC& type : kWCRTSFXIDs) { if (type == clsId) { const FourCC cls = CPF::GetClassID(in); if (cls == SBIG('NONE')) { return true; } x10_sfx[i] = CPF::GetInt(in); return true; } i++; } return false; } bool CCollisionResponseData::CheckAndAddParticleSystemToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool) { size_t i = 0; for (const FourCC& type : kWCRTIDs) { if (type == clsId) { AddParticleSystemToResponse(EWeaponCollisionResponseTypes(i), in, resPool); return true; } i++; } return false; } bool CCollisionResponseData::CheckAndAddResourceToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool) { if (CheckAndAddParticleSystemToResponse(clsId, in, resPool)) return true; if (CheckAndAddSoundFXToResponse(clsId, in)) return true; if (CheckAndAddDecalToResponse(clsId, in, resPool)) return true; return false; } CCollisionResponseData::CCollisionResponseData(CInputStream& in, CSimplePool* resPool) : x0_generators(94), x10_sfx(94, kInvalidSFX), x20_decals(94) { FourCC clsId = CPF::GetClassID(in); if (clsId == UncookedResType()) { CRandom16 rand; CGlobalRandom gr(rand); while (clsId != SBIG('_END')) { clsId = CPF::GetClassID(in); if (CheckAndAddResourceToResponse(clsId, in, resPool)) continue; if (clsId == SBIG('RNGE')) { CPF::GetClassID(in); x30_RNGE = CPF::GetReal(in); } else if (clsId == SBIG('FOFF')) { CPF::GetClassID(in); x34_FOFF = CPF::GetReal(in); } } } } const std::optional>& CCollisionResponseData::GetParticleDescription(EWeaponCollisionResponseTypes type) const { if (x0_generators[size_t(type)]) { return x0_generators[size_t(type)]; } bool foundType = false; if (ResponseTypeIsEnemyNormal(type)) { type = EWeaponCollisionResponseTypes::EnemyNormal; foundType = true; } else if (ResponseTypeIsEnemySpecial(type)) { type = EWeaponCollisionResponseTypes::EnemySpecial; foundType = true; } else if (ResponseTypeIsEnemyShielded(type)) { type = EWeaponCollisionResponseTypes::EnemyShielded; foundType = true; } if (foundType && !x0_generators[size_t(type)]) { type = EWeaponCollisionResponseTypes::EnemyNormal; } if (!x0_generators[size_t(type)] && type != EWeaponCollisionResponseTypes::None) { type = EWeaponCollisionResponseTypes::Default; } return x0_generators[size_t(type)]; } const std::optional>& CCollisionResponseData::GetDecalDescription(EWeaponCollisionResponseTypes type) const { return x20_decals[size_t(type)]; } s32 CCollisionResponseData::GetSoundEffectId(EWeaponCollisionResponseTypes type) const { if (x10_sfx[size_t(type)] == kInvalidSFX) { if (ResponseTypeIsEnemyNormal(type)) type = EWeaponCollisionResponseTypes::EnemyNormal; else if (ResponseTypeIsEnemySpecial(type)) type = EWeaponCollisionResponseTypes::EnemySpecial; else if (ResponseTypeIsEnemyShielded(type)) type = EWeaponCollisionResponseTypes::EnemyShielded; else type = EWeaponCollisionResponseTypes::Default; } return x10_sfx[size_t(type)]; } EWeaponCollisionResponseTypes CCollisionResponseData::GetWorldCollisionResponseType(s32 id) { if (id < 0 || size_t(id) >= skWorldMaterialTable.size()) { return EWeaponCollisionResponseTypes::Default; } return skWorldMaterialTable[id]; } bool CCollisionResponseData::ResponseTypeIsEnemyNormal(EWeaponCollisionResponseTypes type) { return (type >= EWeaponCollisionResponseTypes::Unknown19 && type <= EWeaponCollisionResponseTypes::AtomicAlpha); } bool CCollisionResponseData::ResponseTypeIsEnemySpecial(EWeaponCollisionResponseTypes type) { return (type >= EWeaponCollisionResponseTypes::Unknown44 && type <= EWeaponCollisionResponseTypes::Unknown68); } bool CCollisionResponseData::ResponseTypeIsEnemyShielded(EWeaponCollisionResponseTypes type) { return (type >= EWeaponCollisionResponseTypes::Unknown69 && type <= EWeaponCollisionResponseTypes::AtomicAlphaReflect); } FourCC CCollisionResponseData::UncookedResType() { return SBIG('CRSM'); } CFactoryFnReturn FCollisionResponseDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference*) { CSimplePool* sp = vparms.GetOwnedObj(); return TToken::GetIObjObjectFor(std::make_unique(in, sp)); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionResponseData.hpp ================================================ #pragma once #include #include #include "Runtime/CFactoryMgr.hpp" #include "Runtime/CToken.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/IObj.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Collision/CMaterialList.hpp" #include "Runtime/Particle/CDecalDescription.hpp" namespace metaforce { class CGenDescription; class CSimplePool; enum class EWeaponCollisionResponseTypes { None, Default, Unknown2, Metal, Grass, Ice, Goo, Wood, Water, Mud, Lava, Sand, Projectile, OtherProjectile, Unknown14, Unknown15, EnemyNormal, EnemySpecial, EnemyShielded, Unknown19, Unknown20, Unknown21, Unknown22, Unknown23, Unknown24, Unknown25, Unknown26, Unknown27, Unknown28, Unknown29, Unknown30, Unknown31, Unknown32, Unknown33, Unknown34, Unknown35, Unknown36, Unknown37, ChozoGhost, Unknown39, Unknown40, Unknown41, AtomicBeta, AtomicAlpha, Unknown44, Unknown45, Unknown46, Unknown47, Unknown48, Unknown49, Unknown50, Unknown51, Unknown52, Unknown53, Unknown54, Unknown55, Unknown56, Unknown57, Unknown58, Unknown59, Unknown60, Unknown61, Unknown62, Unknown63, Unknown64, Unknown65, Unknown66, Unknown67, Unknown68, Unknown69, Unknown70, Unknown71, Unknown72, Unknown73, Unknown74, Unknown75, Unknown76, Unknown77, Unknown78, Unknown79, Unknown80, Unknown81, Unknown82, Unknown83, Unknown84, Unknown85, Unknown86, Unknown87, Unknown88, Unknown89, Unknown90, Unknown91, AtomicBetaReflect, AtomicAlphaReflect }; class CCollisionResponseData { std::vector>> x0_generators; std::vector x10_sfx; std::vector>> x20_decals; float x30_RNGE = 50.0f; float x34_FOFF = 0.2f; void AddParticleSystemToResponse(EWeaponCollisionResponseTypes type, CInputStream& in, CSimplePool* resPool); bool CheckAndAddDecalToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool); bool CheckAndAddSoundFXToResponse(FourCC clsId, CInputStream& in); bool CheckAndAddParticleSystemToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool); bool CheckAndAddResourceToResponse(FourCC clsId, CInputStream& in, CSimplePool* resPool); public: explicit CCollisionResponseData(CInputStream& in, CSimplePool* resPool); const std::optional>& GetParticleDescription(EWeaponCollisionResponseTypes type) const; const std::optional>& GetDecalDescription(EWeaponCollisionResponseTypes type) const; s32 GetSoundEffectId(EWeaponCollisionResponseTypes type) const; static EWeaponCollisionResponseTypes GetWorldCollisionResponseType(s32 id); static bool ResponseTypeIsEnemyShielded(EWeaponCollisionResponseTypes type); static bool ResponseTypeIsEnemyNormal(EWeaponCollisionResponseTypes type); static bool ResponseTypeIsEnemySpecial(EWeaponCollisionResponseTypes type); float GetAudibleRange() const { return x30_RNGE; } float GetAudibleFallOff() const { return x34_FOFF; } static FourCC UncookedResType(); }; CFactoryFnReturn FCollisionResponseDataFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionSurface.cpp ================================================ #include "Runtime/Collision/CCollisionSurface.hpp" #include namespace metaforce { CCollisionSurface::CCollisionSurface(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CVector3f& c, u32 flags) : x0_data{a, b, c}, x24_flags(flags) {} zeus::CVector3f CCollisionSurface::GetNormal() const { const zeus::CVector3f v1 = (x0_data[1] - x0_data[0]).cross(x0_data[2] - x0_data[0]); return zeus::CUnitVector3f(v1, true); } zeus::CPlane CCollisionSurface::GetPlane() const { const zeus::CVector3f norm = GetNormal(); return {norm, norm.dot(x0_data[0])}; } } // namespace metaforce ================================================ FILE: Runtime/Collision/CCollisionSurface.hpp ================================================ #pragma once #include #include "Runtime/GCNTypes.hpp" #include #include namespace metaforce { class CCollisionSurface { public: using Vertices = std::array; private: Vertices x0_data; u32 x24_flags; public: CCollisionSurface(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CVector3f& c, u32 flags); zeus::CVector3f GetNormal() const; const zeus::CVector3f& GetVert(s32 idx) const { return x0_data[idx]; } const Vertices& GetVerts() const { return x0_data; } zeus::CPlane GetPlane() const; u32 GetSurfaceFlags() const { return x24_flags; } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CGameCollision.cpp ================================================ #include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Character/CGroundMovement.hpp" #include "Runtime/Collision/CAABoxFilter.hpp" #include "Runtime/Collision/CBallFilter.hpp" #include "Runtime/Collision/CCollidableOBBTreeGroup.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" #include "Runtime/Collision/CCollisionInfoList.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include "Runtime/Collision/CMaterialList.hpp" #include "Runtime/Collision/CMetroidAreaCollider.hpp" #include "Runtime/Collision/CollisionUtil.hpp" #include "Runtime/World/CActor.hpp" #include "Runtime/World/CScriptPlatform.hpp" #include "Runtime/World/CWorld.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { namespace { static constexpr bool skPlayerUsesNewColliderLogic = true; } static float CollisionImpulseFiniteVsInfinite(float mass, float velNormDot, float restitution) { return mass * -(1.f + restitution) * velNormDot; } static float CollisionImpulseFiniteVsFinite(float mass0, float mass1, float velNormDot, float restitution) { return (-(1.f + restitution) * velNormDot) / ((1.f / mass0) + (1.f / mass1)); } void CGameCollision::InitCollision() { /* Types */ CCollisionPrimitive::InitBeginTypes(); CCollisionPrimitive::InitAddType(CCollidableOBBTreeGroup::GetType()); CCollisionPrimitive::InitEndTypes(); /* Colliders */ CCollisionPrimitive::InitBeginColliders(); CCollisionPrimitive::InitAddCollider(CCollidableOBBTreeGroup::SphereCollide, "CCollidableSphere", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddCollider(CCollidableOBBTreeGroup::AABoxCollide, "CCollidableAABox", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddBooleanCollider(CCollidableOBBTreeGroup::SphereCollideBoolean, "CCollidableSphere", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddBooleanCollider(CCollidableOBBTreeGroup::AABoxCollideBoolean, "CCollidableAABox", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddMovingCollider(CCollidableOBBTreeGroup::CollideMovingAABox, "CCollidableAABox", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddMovingCollider(CCollidableOBBTreeGroup::CollideMovingSphere, "CCollidableSphere", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddCollider(CGameCollision::NullCollisionCollider, "CCollidableOBBTreeGroup", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddBooleanCollider(CGameCollision::NullBooleanCollider, "CCollidableOBBTreeGroup", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitAddMovingCollider(CGameCollision::NullMovingCollider, "CCollidableOBBTreeGroup", "CCollidableOBBTreeGroup"); CCollisionPrimitive::InitEndColliders(); } void CGameCollision::MovePlayer(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList) { actor.SetAngularEnabled(true); actor.AddMotionState(actor.PredictAngularMotion(dt)); if (!actor.IsUseStandardCollider()) { if (!actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider)) { MoveAndCollide(mgr, actor, dt, CBallFilter(actor), colliderList); } else if (skPlayerUsesNewColliderLogic) { CGroundMovement::MoveGroundCollider_New(mgr, actor, dt, colliderList); } else { CGroundMovement::MoveGroundCollider(mgr, actor, dt, colliderList); } } else { MoveAndCollide(mgr, actor, dt, CBallFilter(actor), colliderList); } actor.SetAngularEnabled(false); } void CGameCollision::MoveAndCollide(CStateManager& mgr, CPhysicsActor& actor, float dt, const ICollisionFilter& filter, const EntityList* colliderList) { bool isPlayer = actor.GetMaterialList().HasMaterial(EMaterialTypes::Player); bool r28 = false; bool r27 = false; int r26 = 0; float f31 = dt; float _4AC4 = dt; float _4AC8 = dt; CCollisionInfoList accumList; CMotionState mState = actor.PredictMotion_Internal(dt); float transMag = mState.x0_translation.magnitude(); float m1 = 0.0005f / actor.GetCollisionAccuracyModifier(); float m2 = transMag / (5.f * actor.GetCollisionAccuracyModifier()); float mMax = std::max(m1, m2); float m3 = 0.001f / actor.GetCollisionAccuracyModifier(); zeus::CAABox motionVol = actor.GetMotionVolume(dt); EntityList useColliderList; if (colliderList) useColliderList = *colliderList; else mgr.BuildColliderList(useColliderList, actor, zeus::CAABox(motionVol.min - 1.f, motionVol.max + 1.f)); CAreaCollisionCache cache(motionVol); if (actor.GetCollisionPrimitive()->GetPrimType() != FOURCC('OBTG') && !actor.GetMaterialFilter().GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision)) { BuildAreaCollisionCache(mgr, cache); zeus::CVector3f pos = actor.GetCollisionPrimitive()->CalculateAABox(actor.GetPrimitiveTransform()).center(); float halfExtent = 0.5f * GetMinExtentForCollisionPrimitive(*actor.GetCollisionPrimitive()); if (transMag > halfExtent) { TUniqueId id = kInvalidUniqueId; zeus::CVector3f dir = (1.f / transMag) * mState.x0_translation; CRayCastResult intersectRes = mgr.RayWorldIntersection(id, pos, dir, transMag, actor.GetMaterialFilter(), useColliderList); if (intersectRes.IsValid()) { f31 = dt * (intersectRes.GetT() / transMag); mState = actor.PredictMotion_Internal(f31); _4AC8 = halfExtent * (dt / transMag); mMax = std::min(mMax, halfExtent); } } } float f27 = f31; while (true) { actor.MoveCollisionPrimitive(mState.x0_translation); if (DetectCollisionBoolean_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), actor.GetMaterialFilter(), useColliderList)) { r28 = true; if (mState.x0_translation.magnitude() < mMax) { r27 = true; accumList.Clear(); TUniqueId id = kInvalidUniqueId; DetectCollision_Cached(mgr, cache, *actor.GetCollisionPrimitive(), actor.GetPrimitiveTransform(), actor.GetMaterialFilter(), useColliderList, id, accumList); TCastToPtr otherActor = mgr.ObjectById(id); actor.MoveCollisionPrimitive(zeus::skZero3f); zeus::CVector3f relVel = GetActorRelativeVelocities(actor, otherActor.GetPtr()); CCollisionInfoList filterList0, filterList1; CollisionUtil::FilterOutBackfaces(relVel, accumList, filterList0); if (filterList0.GetCount() > 0) { filter.Filter(filterList0, filterList1); if (!filterList1.GetCount() && actor.GetMaterialList().HasMaterial(EMaterialTypes::Player)) { CMotionState mState = actor.GetLastNonCollidingState(); mState.x1c_velocity *= zeus::CVector3f(0.5f); mState.x28_angularMomentum *= zeus::CVector3f(0.5f); actor.SetMotionState(mState); } } MakeCollisionCallbacks(mgr, actor, id, filterList1); SendScriptMessages(mgr, actor, otherActor.GetPtr(), filterList1); ResolveCollisions(actor, otherActor.GetPtr(), filterList1); _4AC4 -= f31; f27 = std::min(_4AC4, _4AC8); f31 = f27; } else { f27 *= 0.5f; f31 *= 0.5f; } } else { actor.AddMotionState(mState); _4AC4 -= f31; f31 = f27; actor.ClearImpulses(); actor.MoveCollisionPrimitive(zeus::skZero3f); } ++r26; if (_4AC4 > 0.f && ((mState.x0_translation.magnitude() > m3 && r27) || !r27) && r26 <= 1000) mState = actor.PredictMotion_Internal(f31); else break; } f27 = _4AC4 / dt; if (!r28 && !actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider)) mgr.SendScriptMsg(&actor, kInvalidUniqueId, EScriptObjectMessage::Falling); if (isPlayer) CollisionFailsafe(mgr, cache, actor, *actor.GetCollisionPrimitive(), useColliderList, f27, 2); actor.ClearForcesAndTorques(); actor.MoveCollisionPrimitive(zeus::skZero3f); } zeus::CVector3f CGameCollision::GetActorRelativeVelocities(const CPhysicsActor& act0, const CPhysicsActor* act1) { zeus::CVector3f ret = act0.GetVelocity(); if (act1) { bool rider = false; if (const TCastToConstPtr plat = act1) { rider = plat->IsRider(act0.GetUniqueId()); } if (!rider) { ret -= act1->GetVelocity(); } } return ret; } void CGameCollision::Move(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList) { if (!actor.IsMovable()) return; if (actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider) || actor.WillMove(mgr)) { if (actor.IsAngularEnabled()) actor.AddMotionState(actor.PredictAngularMotion(dt)); actor.UseCollisionImpulses(); if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Solid)) { if (actor.GetMaterialList().HasMaterial(EMaterialTypes::Player)) MovePlayer(mgr, actor, dt, colliderList); else if (actor.GetMaterialList().HasMaterial(EMaterialTypes::GroundCollider)) CGroundMovement::MoveGroundCollider(mgr, actor, dt, colliderList); else MoveAndCollide(mgr, actor, dt, CAABoxFilter(actor), colliderList); } else { actor.AddMotionState(actor.PredictMotion_Internal(dt)); actor.ClearForcesAndTorques(); } mgr.UpdateActorInSortedLists(actor); } } bool CGameCollision::CanBlock(const CMaterialList& mat, const zeus::CUnitVector3f& v) { if ((mat.HasMaterial(EMaterialTypes::Character) && !mat.HasMaterial(EMaterialTypes::SolidCharacter)) || mat.HasMaterial(EMaterialTypes::NoPlayerCollision)) { return false; } if (mat.HasMaterial(EMaterialTypes::Floor)) { return true; } return (v.z() > 0.85f); } bool CGameCollision::IsFloor(const CMaterialList& mat, const zeus::CUnitVector3f& v) { return mat.HasMaterial(EMaterialTypes::Floor) || v.z() > 0.85f; } void CGameCollision::SendMaterialMessage(CStateManager& mgr, const CMaterialList& mat, CActor& act) { EScriptObjectMessage msg; if (mat.HasMaterial(EMaterialTypes::Ice)) { msg = EScriptObjectMessage::OnIceSurface; } else if (mat.HasMaterial(EMaterialTypes::MudSlow)) { msg = EScriptObjectMessage::OnMudSlowSurface; } else { msg = EScriptObjectMessage::OnNormalSurface; } mgr.SendScriptMsg(&act, kInvalidUniqueId, msg); } CRayCastResult CGameCollision::RayStaticIntersection(const CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter) { CRayCastResult ret; float bestT = length; if (bestT <= 0.f) { bestT = 100000.f; } zeus::CLine line(pos, dir); for (const CGameArea& area : *mgr.GetWorld()) { CAreaOctTree::SRayResult rayRes; CAreaOctTree& collision = *area.GetPostConstructed()->x0_collision; collision.GetRootNode().LineTestEx(line, filter, rayRes, length); if (!rayRes.x10_surface || (length != 0.f && length < rayRes.x3c_t)) { continue; } if (rayRes.x3c_t < bestT) { ret = CRayCastResult(rayRes.x3c_t, dir * rayRes.x3c_t + pos, rayRes.x0_plane, rayRes.x10_surface->GetSurfaceFlags()); bestT = rayRes.x3c_t; } } return ret; } bool CGameCollision::RayStaticIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& start, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter) { if (length <= 0.f) { length = 100000.f; } zeus::CLine line(start, dir); for (const CGameArea& area : *mgr.GetWorld()) { const CAreaOctTree& collision = *area.GetPostConstructed()->x0_collision; CAreaOctTree::Node root = collision.GetRootNode(); if (!root.LineTest(line, filter, length)) { return false; } } return true; } CRayCastResult CGameCollision::RayDynamicIntersection(const CStateManager& mgr, TUniqueId& idOut, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter, const EntityList& nearList) { CRayCastResult ret; float bestT = length; if (bestT <= 0.f) { bestT = 100000.f; } for (TUniqueId id : nearList) { const CEntity* ent = mgr.GetObjectById(id); if (const TCastToConstPtr physActor = ent) { const zeus::CTransform xf = physActor->GetPrimitiveTransform(); const CCollisionPrimitive* prim = physActor->GetCollisionPrimitive(); const CRayCastResult res = prim->CastRay(pos, dir, bestT, filter, xf); if (!res.IsInvalid() && res.GetT() < bestT) { bestT = res.GetT(); ret = res; idOut = physActor->GetUniqueId(); } } } return ret; } bool CGameCollision::RayDynamicIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const CMaterialFilter& filter, const EntityList& nearList, const CActor* damagee, float length) { if (length <= 0.f) { length = 100000.f; } for (TUniqueId id : nearList) { const CEntity* ent = mgr.GetObjectById(id); if (const TCastToConstPtr physActor = ent) { if (damagee != nullptr && physActor->GetUniqueId() == damagee->GetUniqueId()) { continue; } const zeus::CTransform xf = physActor->GetPrimitiveTransform(); const CCollisionPrimitive* prim = physActor->GetCollisionPrimitive(); const CRayCastResult res = prim->CastRay(pos, dir, length, filter, xf); if (!res.IsInvalid()) { return false; } } } return true; } CRayCastResult CGameCollision::RayWorldIntersection(const CStateManager& mgr, TUniqueId& idOut, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter, const EntityList& nearList) { CRayCastResult staticRes = RayStaticIntersection(mgr, pos, dir, mag, filter); CRayCastResult dynamicRes = RayDynamicIntersection(mgr, idOut, pos, dir, mag, filter, nearList); if (dynamicRes.IsValid()) { if (staticRes.IsInvalid()) { return dynamicRes; } if (staticRes.GetT() >= dynamicRes.GetT()) { return dynamicRes; } } return staticRes; } bool CGameCollision::RayStaticIntersectionArea(const CGameArea& area, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter) { if (mag <= 0.f) { mag = 100000.f; } CAreaOctTree::Node node = area.GetPostConstructed()->x0_collision->GetRootNode(); zeus::CLine line(pos, dir); return node.LineTest(line, filter, mag); } void CGameCollision::BuildAreaCollisionCache(const CStateManager& mgr, CAreaCollisionCache& cache) { cache.ClearCache(); for (const CGameArea& area : *mgr.GetWorld()) { const CAreaOctTree& areaCollision = *area.GetPostConstructed()->x0_collision; CMetroidAreaCollider::COctreeLeafCache octreeCache(areaCollision); CMetroidAreaCollider::BuildOctreeLeafCache(areaCollision.GetRootNode(), cache.GetCacheBounds(), octreeCache); cache.AddOctreeLeafCache(octreeCache); } } float CGameCollision::GetMinExtentForCollisionPrimitive(const CCollisionPrimitive& prim) { if (prim.GetPrimType() == FOURCC('SPHR')) { const auto& sphere = static_cast(prim); return 2.f * sphere.GetSphere().radius; } if (prim.GetPrimType() == FOURCC('AABX')) { const auto& aabx = static_cast(prim); const zeus::CVector3f extent = aabx.GetBox().max - aabx.GetBox().min; float minExtent = std::min(extent.x(), extent.y()); minExtent = std::min(minExtent, extent.z()); return minExtent; } if (prim.GetPrimType() == FOURCC('ABSH')) { // Combination AABB / Sphere cut from game } return 1.f; } bool CGameCollision::DetectCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList) { if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision) && DetectStaticCollisionBoolean(mgr, prim, xf, filter)) { return true; } if (DetectDynamicCollisionBoolean(prim, xf, nearList, mgr)) { return true; } return false; } bool CGameCollision::DetectCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList) { if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision) && DetectStaticCollisionBoolean_Cached(mgr, cache, prim, xf, filter)) { return true; } if (DetectDynamicCollisionBoolean(prim, xf, nearList, mgr)) { return true; } return false; } bool CGameCollision::DetectStaticCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter) { if (prim.GetPrimType() == FOURCC('OBTG')) { return false; } if (prim.GetPrimType() == FOURCC('AABX')) { zeus::CAABox aabb = prim.CalculateAABox(xf); for (const CGameArea& area : *mgr.GetWorld()) { if (CMetroidAreaCollider::AABoxCollisionCheckBoolean(*area.GetPostConstructed()->x0_collision, aabb, filter)) { return true; } } } else if (prim.GetPrimType() == FOURCC('SPHR')) { const auto& sphere = static_cast(prim); zeus::CAABox aabb = prim.CalculateAABox(xf); zeus::CSphere xfSphere = sphere.Transform(xf); for (const CGameArea& area : *mgr.GetWorld()) { if (CMetroidAreaCollider::SphereCollisionCheckBoolean(*area.GetPostConstructed()->x0_collision, aabb, xfSphere, filter)) { return true; } } } else if (prim.GetPrimType() == FOURCC('ABSH')) { // Combination AABB / Sphere cut from game } return false; } bool CGameCollision::DetectStaticCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter) { if (prim.GetPrimType() == FOURCC('OBTG')) { return false; } zeus::CAABox aabb = prim.CalculateAABox(xf); if (!aabb.inside(cache.GetCacheBounds())) { zeus::CAABox newAABB(aabb.min - 0.2f, aabb.max + 0.2f); newAABB.accumulateBounds(cache.GetCacheBounds()); cache.SetCacheBounds(newAABB); BuildAreaCollisionCache(mgr, cache); } if (cache.HasCacheOverflowed()) { return DetectStaticCollisionBoolean(mgr, prim, xf, filter); } if (prim.GetPrimType() == FOURCC('AABX')) { for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { if (CMetroidAreaCollider::AABoxCollisionCheckBoolean_Cached(leafCache, aabb, filter)) { return true; } } } else if (prim.GetPrimType() == FOURCC('SPHR')) { const auto& sphere = static_cast(prim); zeus::CSphere xfSphere = sphere.Transform(xf); for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { if (CMetroidAreaCollider::SphereCollisionCheckBoolean_Cached(leafCache, aabb, xfSphere, filter)) { return true; } } } else if (prim.GetPrimType() == FOURCC('ABSH')) { // Combination AABB / Sphere cut from game } return false; } bool CGameCollision::DetectDynamicCollisionBoolean(const CCollisionPrimitive& prim, const zeus::CTransform& xf, const EntityList& nearList, const CStateManager& mgr) { for (const auto& id : nearList) { if (const TCastToConstPtr actor = mgr.GetObjectById(id)) { const CInternalCollisionStructure::CPrimDesc p0(prim, CMaterialFilter::skPassEverything, xf); const CInternalCollisionStructure::CPrimDesc p1( *actor->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, actor->GetPrimitiveTransform()); if (CCollisionPrimitive::CollideBoolean(p0, p1)) { return true; } } } return false; } bool CGameCollision::DetectCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& infoList) { idOut = kInvalidUniqueId; bool ret = false; if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision)) { if (DetectStaticCollision_Cached(mgr, cache, prim, xf, filter, infoList)) { ret = true; } } TUniqueId id = kInvalidUniqueId; if (DetectDynamicCollision(prim, xf, nearList, id, infoList, mgr)) { ret = true; idOut = id; } return ret; } bool CGameCollision::DetectCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList, const zeus::CVector3f& dir, TUniqueId& idOut, CCollisionInfo& infoOut, double& d) { bool ret = false; idOut = kInvalidUniqueId; if (!filter.GetExcludeList().HasMaterial(EMaterialTypes::NoStaticCollision)) { if (CGameCollision::DetectStaticCollision_Cached_Moving(mgr, cache, prim, xf, filter, dir, infoOut, d)) { ret = true; } } if (CGameCollision::DetectDynamicCollisionMoving(prim, xf, nearList, dir, idOut, infoOut, d, mgr)) { ret = true; } return ret; } bool CGameCollision::DetectStaticCollision(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, CCollisionInfoList& list) { if (prim.GetPrimType() == FOURCC('OBTG')) { return false; } bool ret = false; if (prim.GetPrimType() == FOURCC('AABX')) { zeus::CAABox aabb = prim.CalculateAABox(xf); for (const CGameArea& area : *mgr.GetWorld()) { if (CMetroidAreaCollider::AABoxCollisionCheck(*area.GetPostConstructed()->x0_collision, aabb, filter, prim.GetMaterial(), list)) { ret = true; } } } else if (prim.GetPrimType() == FOURCC('SPHR')) { const auto& sphere = static_cast(prim); zeus::CAABox aabb = prim.CalculateAABox(xf); zeus::CSphere xfSphere = sphere.Transform(xf); for (const CGameArea& area : *mgr.GetWorld()) { if (CMetroidAreaCollider::SphereCollisionCheck(*area.GetPostConstructed()->x0_collision, aabb, xfSphere, prim.GetMaterial(), filter, list)) { ret = true; } } } else if (prim.GetPrimType() == FOURCC('ABSH')) { // Combination AABB / Sphere cut from game } return ret; } bool CGameCollision::DetectStaticCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, CCollisionInfoList& list) { if (prim.GetPrimType() == FOURCC('OBTG')) { return false; } bool ret = false; zeus::CAABox calcAABB = prim.CalculateAABox(xf); if (!calcAABB.inside(cache.GetCacheBounds())) { zeus::CAABox newAABB(calcAABB.min - 0.2f, calcAABB.max + 0.2f); newAABB.accumulateBounds(cache.GetCacheBounds()); cache.SetCacheBounds(newAABB); BuildAreaCollisionCache(mgr, cache); } if (cache.HasCacheOverflowed()) { return DetectStaticCollision(mgr, prim, xf, filter, list); } if (prim.GetPrimType() == FOURCC('AABX')) { for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { if (CMetroidAreaCollider::AABoxCollisionCheck_Cached(leafCache, calcAABB, filter, prim.GetMaterial(), list)) { ret = true; } } } else if (prim.GetPrimType() == FOURCC('SPHR')) { const auto& sphere = static_cast(prim); zeus::CSphere xfSphere = sphere.Transform(xf); for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { if (CMetroidAreaCollider::SphereCollisionCheck_Cached(leafCache, calcAABB, xfSphere, prim.GetMaterial(), filter, list)) { ret = true; } } } else if (prim.GetPrimType() == FOURCC('ABSH')) { // Combination AABB / Sphere cut from game } return ret; } bool CGameCollision::DetectStaticCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const zeus::CVector3f& dir, CCollisionInfo& infoOut, double& dOut) { if (prim.GetPrimType() == FOURCC('OBTG')) { return false; } zeus::CVector3f offset = float(dOut) * dir; zeus::CAABox aabb = prim.CalculateAABox(xf); zeus::CAABox offsetAABB = aabb; offsetAABB.accumulateBounds(offset + offsetAABB.min); offsetAABB.accumulateBounds(offset + offsetAABB.max); if (!offsetAABB.inside(cache.GetCacheBounds())) { zeus::CAABox newAABB(offsetAABB.min - 0.2f, offsetAABB.max + 0.2f); newAABB.accumulateBounds(cache.GetCacheBounds()); cache.SetCacheBounds(newAABB); BuildAreaCollisionCache(mgr, cache); } if (prim.GetPrimType() == FOURCC('AABX')) { for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { CCollisionInfo info; double d = dOut; if (CMetroidAreaCollider::MovingAABoxCollisionCheck_Cached( leafCache, aabb, filter, CMaterialList(EMaterialTypes::Solid), dir, dOut, info, d) && d < dOut) { infoOut = info; dOut = d; } } } else if (prim.GetPrimType() == FOURCC('SPHR')) { const auto& sphere = static_cast(prim); zeus::CSphere xfSphere = sphere.Transform(xf); for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { CCollisionInfo info; double d = dOut; if (CMetroidAreaCollider::MovingSphereCollisionCheck_Cached( leafCache, aabb, xfSphere, filter, CMaterialList(EMaterialTypes::Solid), dir, dOut, info, d) && d < dOut) { infoOut = info; dOut = d; } } } return infoOut.IsValid(); } bool CGameCollision::DetectDynamicCollision(const CCollisionPrimitive& prim, const zeus::CTransform& xf, const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& list, const CStateManager& mgr) { for (const auto& id : nearList) { if (const TCastToConstPtr actor = mgr.GetObjectById(id)) { const CInternalCollisionStructure::CPrimDesc p0(prim, CMaterialFilter::skPassEverything, xf); const CInternalCollisionStructure::CPrimDesc p1( *actor->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, actor->GetPrimitiveTransform()); if (CCollisionPrimitive::Collide(p0, p1, list)) { idOut = actor->GetUniqueId(); return true; } } } return false; } bool CGameCollision::DetectDynamicCollisionMoving(const CCollisionPrimitive& prim, const zeus::CTransform& xf, const EntityList& nearList, const zeus::CVector3f& dir, TUniqueId& idOut, CCollisionInfo& infoOut, double& dOut, const CStateManager& mgr) { bool ret = false; for (const auto& id : nearList) { double d = dOut; CCollisionInfo info; if (const TCastToConstPtr actor = mgr.GetObjectById(id)) { const CInternalCollisionStructure::CPrimDesc p0(prim, CMaterialFilter::skPassEverything, xf); const CInternalCollisionStructure::CPrimDesc p1( *actor->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, actor->GetPrimitiveTransform()); if (CCollisionPrimitive::CollideMoving(p0, p1, dir, d, info) && d < dOut) { ret = true; infoOut = info; dOut = d; idOut = actor->GetUniqueId(); } } } return ret; } bool CGameCollision::DetectCollision(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& infoOut) { bool ret = false; CMaterialList exclude = filter.ExcludeList(); if (!exclude.HasMaterial(EMaterialTypes::Occluder) && DetectStaticCollision(mgr, prim, xf, filter, infoOut)) { ret = true; } TUniqueId tmpId = kInvalidUniqueId; if (DetectDynamicCollision(prim, xf, nearList, tmpId, infoOut, mgr)) { ret = true; idOut = tmpId; } return ret; } void CGameCollision::MakeCollisionCallbacks(CStateManager& mgr, CPhysicsActor& actor, TUniqueId id, const CCollisionInfoList& list) { actor.CollidedWith(id, list, mgr); if (id == kInvalidUniqueId) { return; } if (const TCastToPtr physicalActor = mgr.ObjectById(id)) { CCollisionInfoList swappedList = list; for (CCollisionInfo& info : swappedList) { info.Swap(); } physicalActor->CollidedWith(physicalActor->GetUniqueId(), list, mgr); } } void CGameCollision::SendScriptMessages(CStateManager& mgr, CActor& a0, CActor* a1, const CCollisionInfoList& list) { bool onFloor = false; bool platform = false; for (const CCollisionInfo& info : list) { if (IsFloor(info.GetMaterialLeft(), info.GetNormalLeft())) { onFloor = true; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Platform)) { platform = true; } SendMaterialMessage(mgr, info.GetMaterialLeft(), a0); } } if (onFloor) { mgr.SendScriptMsg(&a0, kInvalidUniqueId, EScriptObjectMessage::OnFloor); if (platform) { if (const TCastToPtr plat = a1) { mgr.SendScriptMsg(plat.GetPtr(), a0.GetUniqueId(), EScriptObjectMessage::AddPlatformRider); } } else if (a1 != nullptr) { if (const TCastToPtr plat = a0) { for (const CCollisionInfo& info : list) { if (IsFloor(info.GetMaterialRight(), info.GetNormalRight())) { if (info.GetMaterialRight().HasMaterial(EMaterialTypes::Platform)) { platform = true; } SendMaterialMessage(mgr, info.GetMaterialLeft(), a0); } } if (platform) { mgr.SendScriptMsg(plat.GetPtr(), a1->GetUniqueId(), EScriptObjectMessage::AddPlatformRider); } } } } } void CGameCollision::ResolveCollisions(CPhysicsActor& a0, CPhysicsActor* a1, const CCollisionInfoList& list) { for (const CCollisionInfo& info : list) { CCollisionInfo infoCopy = info; const float restitution = GetCoefficientOfRestitution(infoCopy) + a0.GetCoefficientOfRestitutionModifier(); if (a1 != nullptr) { CollideWithDynamicBodyNoRot(a0, *a1, infoCopy, restitution, false); } else { CollideWithStaticBodyNoRot(a0, infoCopy.GetMaterialLeft(), infoCopy.GetMaterialRight(), infoCopy.GetNormalLeft(), restitution, false); } } } void CGameCollision::CollideWithDynamicBodyNoRot(CPhysicsActor& a0, CPhysicsActor& a1, const CCollisionInfo& info, float restitution, bool zeroZ) { zeus::CVector3f normal = info.GetNormalLeft(); if (zeroZ) { normal.z() = 0.f; } zeus::CVector3f relVel = GetActorRelativeVelocities(a0, &a1); float velNormDot = relVel.dot(normal); float a0MaxCollisionVel = std::max(a0.GetVelocity().magnitude(), a0.GetMaximumCollisionVelocity()); float a1MaxCollisionVel = std::max(a1.GetVelocity().magnitude(), a1.GetMaximumCollisionVelocity()); bool a0Move = !a0.GetMaterialList().HasMaterial(EMaterialTypes::Immovable) && a0.GetMass() != 0.f; bool a1Move = !a1.GetMaterialList().HasMaterial(EMaterialTypes::Immovable) && a1.GetMass() != 0.f; if (velNormDot < -0.0001f) { if (a0Move) { if (a1Move) { float impulse = CollisionImpulseFiniteVsFinite(a0.GetMass(), a1.GetMass(), velNormDot, restitution); a0.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle()); a1.ApplyImpulseWR(normal * -impulse, zeus::CAxisAngle()); } else { float impulse = CollisionImpulseFiniteVsInfinite(a0.GetMass(), velNormDot, restitution); a0.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle()); } } else { if (a1Move) { float impulse = CollisionImpulseFiniteVsInfinite(a1.GetMass(), velNormDot, restitution); a1.ApplyImpulseWR(normal * -impulse, zeus::CAxisAngle()); } else { a0.SetVelocityWR(zeus::skZero3f); a1.SetVelocityWR(zeus::skZero3f); } } a0.UseCollisionImpulses(); a1.UseCollisionImpulses(); } else if (velNormDot < 0.1f) { if (a0Move) { float impulse = 0.05f * a0.GetMass(); a0.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle()); a0.UseCollisionImpulses(); } if (a1Move) { float impulse = -0.05f * a1.GetMass(); a1.ApplyImpulseWR(normal * impulse, zeus::CAxisAngle()); a1.UseCollisionImpulses(); } } if (a0.GetVelocity().magnitude() > a0MaxCollisionVel) { a0.SetVelocityWR(a0.GetVelocity().normalized() * a0MaxCollisionVel); } if (a1.GetVelocity().magnitude() > a1MaxCollisionVel) { a1.SetVelocityWR(a1.GetVelocity().normalized() * a1MaxCollisionVel); } } void CGameCollision::CollideWithStaticBodyNoRot(CPhysicsActor& a0, const CMaterialList& m0, const CMaterialList& m1, const zeus::CUnitVector3f& normal, float restitution, bool zeroZ) { zeus::CUnitVector3f useNorm = normal; if (zeroZ && m0.HasMaterial(EMaterialTypes::Player) && !m1.HasMaterial(EMaterialTypes::Floor)) { useNorm.z() = 0.f; } if (useNorm.canBeNormalized()) { useNorm.normalize(); float velNormDot = a0.GetVelocity().dot(useNorm); if (velNormDot < -0.0001f) { a0.ApplyImpulseWR(useNorm * CollisionImpulseFiniteVsInfinite(a0.GetMass(), velNormDot, restitution), zeus::CAxisAngle()); a0.UseCollisionImpulses(); } else if (velNormDot < 0.001f) { a0.ApplyImpulseWR(0.05f * a0.GetMass() * useNorm, zeus::CAxisAngle()); a0.UseCollisionImpulses(); } } } void CGameCollision::CollisionFailsafe(const CStateManager& mgr, CAreaCollisionCache& cache, CPhysicsActor& actor, const CCollisionPrimitive& prim, const EntityList& nearList, float f1, u32 failsafeTicks) { actor.MoveCollisionPrimitive(zeus::skZero3f); if (f1 > 0.5f) { actor.SetNumTicksPartialUpdate(actor.GetNumTicksPartialUpdate() + 1); } if (actor.GetNumTicksPartialUpdate() > 1 || DetectCollisionBoolean_Cached(mgr, cache, prim, actor.GetPrimitiveTransform(), actor.GetMaterialFilter(), nearList)) { actor.SetNumTicksPartialUpdate(0); actor.SetNumTicksStuck(actor.GetNumTicksStuck() + 1); if (actor.GetNumTicksStuck() < failsafeTicks) { return; } CMotionState oldMState = actor.GetMotionState(); CMotionState lastNonCollide = actor.GetLastNonCollidingState(); actor.SetMotionState(lastNonCollide); if (!DetectCollisionBoolean_Cached(mgr, cache, prim, actor.GetPrimitiveTransform(), actor.GetMaterialFilter(), nearList)) { lastNonCollide.x1c_velocity *= zeus::CVector3f(0.5f); lastNonCollide.x28_angularMomentum *= zeus::CVector3f(0.5f); actor.SetLastNonCollidingState(lastNonCollide); //++gDebugPrintCount; actor.SetNumTicksStuck(0); } else { actor.SetMotionState(oldMState); if (auto nonIntersectVec = FindNonIntersectingVector(mgr, cache, actor, prim, nearList)) { oldMState.x0_translation += *nonIntersectVec; actor.SetMotionState(oldMState); actor.SetLastNonCollidingState(actor.GetMotionState()); //++gDebugPrintCount; } else { //++gDebugPrintCount; lastNonCollide.x1c_velocity *= zeus::CVector3f(0.5f); lastNonCollide.x28_angularMomentum *= zeus::CVector3f(0.5f); actor.SetLastNonCollidingState(lastNonCollide); } } } else { actor.SetLastNonCollidingState(actor.GetMotionState()); actor.SetNumTicksStuck(0); } } std::optional CGameCollision::FindNonIntersectingVector(const CStateManager& mgr, CAreaCollisionCache& cache, CPhysicsActor& actor, const CCollisionPrimitive& prim, const EntityList& nearList) { zeus::CTransform xf = actor.GetPrimitiveTransform(); zeus::CVector3f origOrigin = xf.origin; zeus::CVector3f center = prim.CalculateAABox(xf).center(); for (int i = 2; i < 1000; i += (i / 2)) { const float pos = static_cast(i) * 0.005f; const float neg = -pos; for (int j = 0; j < 26; ++j) { zeus::CVector3f vec; switch (j) { case 0: vec = {0.f, pos, 0.f}; break; case 1: vec = {0.f, neg, 0.f}; break; case 2: vec = {pos, 0.f, 0.f}; break; case 3: vec = {neg, 0.f, 0.f}; break; case 4: vec = {0.f, 0.f, pos}; break; case 5: vec = {0.f, 0.f, neg}; break; case 6: vec = {0.f, pos, pos}; break; case 7: vec = {0.f, neg, neg}; break; case 8: vec = {0.f, neg, pos}; break; case 9: vec = {0.f, pos, neg}; break; case 10: vec = {pos, 0.f, pos}; break; case 11: vec = {neg, 0.f, neg}; break; case 12: vec = {neg, 0.f, pos}; break; case 13: vec = {pos, 0.f, neg}; break; case 14: vec = {pos, pos, 0.f}; break; case 15: vec = {neg, neg, 0.f}; break; case 16: vec = {neg, pos, 0.f}; break; case 17: vec = {pos, neg, 0.f}; break; case 18: vec = {pos, pos, pos}; break; case 19: vec = {neg, pos, pos}; break; case 20: vec = {pos, neg, pos}; break; case 21: vec = {neg, neg, pos}; break; case 22: vec = {pos, pos, neg}; break; case 23: vec = {neg, pos, neg}; break; case 24: vec = {pos, neg, neg}; break; case 25: vec = {neg, neg, neg}; break; default: break; } zeus::CVector3f worldPoint = vec + origOrigin; if (mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId())->GetAABB().pointInside(worldPoint)) { if (mgr.RayCollideWorld(center, center + vec, nearList, CMaterialFilter::skPassEverything, &actor)) { xf.origin = worldPoint; if (!DetectCollisionBoolean_Cached(mgr, cache, prim, xf, actor.GetMaterialFilter(), nearList)) { return {vec}; } } } } } return {}; } void CGameCollision::AvoidStaticCollisionWithinRadius(const CStateManager& mgr, CPhysicsActor& actor, u32 iterations, float dt, float height, float size, float mass, float radius) { const zeus::CVector3f& actorPos = actor.GetTranslation(); const zeus::CVector3f pos = actorPos + zeus::CVector3f{0.f, 0.f, height}; const float largeRadius = 1.2f * radius; const zeus::CVector3f diff{size + largeRadius, size + largeRadius, largeRadius}; const zeus::CAABox aabb{pos - diff, pos + diff}; CAreaCollisionCache cache{aabb}; BuildAreaCollisionCache(mgr, cache); const CCollidableSphere prim{{pos, radius}, {EMaterialTypes::Solid}}; if (!DetectStaticCollisionBoolean_Cached(mgr, cache, prim, {}, CMaterialFilter::MakeExclude(EMaterialTypes::Floor))) { zeus::CVector3f velocity = zeus::skZero3f; float seg = zeus::degToRad(360.f) / iterations; for (u32 i = 0; i < iterations; ++i) { const float angle = seg * i; const zeus::CVector3f vec{std::sin(angle), std::cos(angle), 0.f}; double out = size; CCollisionInfo info{}; if (cache.HasCacheOverflowed()) { cache.ClearCache(); zeus::CAABox aabb2{pos, pos}; aabb2.accumulateBounds(actorPos + (size * vec)); cache.SetCacheBounds(zeus::CAABox{aabb2.min - radius, aabb2.max + radius}); BuildAreaCollisionCache(mgr, cache); } if (DetectStaticCollision_Cached_Moving(mgr, cache, prim, {}, CMaterialFilter::MakeExclude(EMaterialTypes::Floor), vec, info, out)) { float force = static_cast((size - out) / size) / iterations; velocity -= force * vec; } } actor.SetVelocityWR(actor.GetVelocity() + (dt * (mass * velocity))); } } } // namespace metaforce ================================================ FILE: Runtime/Collision/CGameCollision.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Collision/CCollisionPrimitive.hpp" #include "Runtime/Collision/CMetroidAreaCollider.hpp" #include "Runtime/Collision/CRayCastResult.hpp" #include #include namespace metaforce { class CActor; class CCollisionInfo; class CCollisionInfoList; class CGameArea; class CMaterialFilter; class CMaterialList; class CPhysicsActor; class CStateManager; class ICollisionFilter; class CGameCollision { static void MovePlayer(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList); static void MoveAndCollide(CStateManager& mgr, CPhysicsActor& actor, float dt, const ICollisionFilter& filter, const EntityList* colliderList); static zeus::CVector3f GetActorRelativeVelocities(const CPhysicsActor& act0, const CPhysicsActor* act1); public: static float GetCoefficientOfRestitution(const CCollisionInfo&) { return 0.f; } static bool NullMovingCollider(const CInternalCollisionStructure&, const zeus::CVector3f&, double&, CCollisionInfo&) { return false; } static bool NullBooleanCollider(const CInternalCollisionStructure&) { return false; } static bool NullCollisionCollider(const CInternalCollisionStructure&, CCollisionInfoList&) { return false; } static void InitCollision(); static void Move(CStateManager& mgr, CPhysicsActor& actor, float dt, const EntityList* colliderList); static bool CanBlock(const CMaterialList&, const zeus::CUnitVector3f&); static bool IsFloor(const CMaterialList&, const zeus::CUnitVector3f&); static void SendMaterialMessage(CStateManager&, const CMaterialList&, CActor&); static CRayCastResult RayStaticIntersection(const CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter); static bool RayStaticIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& start, const zeus::CVector3f& dir, float length, const CMaterialFilter& filter); static CRayCastResult RayDynamicIntersection(const CStateManager& mgr, TUniqueId& idOut, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter, const EntityList& nearList); static bool RayDynamicIntersectionBool(const CStateManager& mgr, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const CMaterialFilter& filter, const EntityList& nearList, const CActor* damagee, float length); static CRayCastResult RayWorldIntersection(const CStateManager& mgr, TUniqueId& idOut, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter, const EntityList& nearList); static bool RayStaticIntersectionArea(const CGameArea& area, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, const CMaterialFilter& filter); static void BuildAreaCollisionCache(const CStateManager& mgr, CAreaCollisionCache& cache); static float GetMinExtentForCollisionPrimitive(const CCollisionPrimitive& prim); static bool DetectCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList); static bool DetectCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList); static bool DetectStaticCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter); static bool DetectStaticCollisionBoolean_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter); static bool DetectDynamicCollisionBoolean(const CCollisionPrimitive& prim, const zeus::CTransform& xf, const EntityList& nearList, const CStateManager& mgr); static bool DetectCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& infoList); static bool DetectCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList, const zeus::CVector3f& vec, TUniqueId& idOut, CCollisionInfo& infoOut, double&); static bool DetectStaticCollision(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, CCollisionInfoList& list); static bool DetectStaticCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, CCollisionInfoList& list); static bool DetectStaticCollision_Cached_Moving(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const zeus::CVector3f& vec, CCollisionInfo& infoOut, double& d); static bool DetectDynamicCollision(const CCollisionPrimitive& prim, const zeus::CTransform& xf, const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& list, const CStateManager& mgr); static bool DetectDynamicCollisionMoving(const CCollisionPrimitive& prim, const zeus::CTransform& xf, const EntityList& nearList, const zeus::CVector3f& vec, TUniqueId& idOut, CCollisionInfo& infoOut, double& d, const CStateManager& mgr); static bool DetectCollision(const CStateManager& mgr, const CCollisionPrimitive& prim, const zeus::CTransform& xf, const CMaterialFilter& filter, const EntityList& nearList, TUniqueId& idOut, CCollisionInfoList& infoOut); static void MakeCollisionCallbacks(CStateManager& mgr, CPhysicsActor& actor, TUniqueId id, const CCollisionInfoList& list); static void SendScriptMessages(CStateManager& mgr, CActor& a0, CActor* a1, const CCollisionInfoList& list); static void ResolveCollisions(CPhysicsActor& a0, CPhysicsActor* a1, const CCollisionInfoList& list); static void CollideWithDynamicBodyNoRot(CPhysicsActor& a0, CPhysicsActor& a1, const CCollisionInfo& info, float restitution, bool); static void CollideWithStaticBodyNoRot(CPhysicsActor& a0, const CMaterialList& m0, const CMaterialList& m1, const zeus::CUnitVector3f& normal, float restitution, bool); static void CollisionFailsafe(const CStateManager& mgr, CAreaCollisionCache& cache, CPhysicsActor& actor, const CCollisionPrimitive& prim, const EntityList& nearList, float, u32 failsafeTicks); static std::optional FindNonIntersectingVector(const CStateManager& mgr, CAreaCollisionCache& cache, CPhysicsActor& actor, const CCollisionPrimitive& prim, const EntityList& nearList); static void AvoidStaticCollisionWithinRadius(const CStateManager& mgr, CPhysicsActor& actor, u32 iterations, float dt, float height, float size, float mass, float radius); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CInternalRayCastStructure.hpp ================================================ #pragma once #include "Runtime/Collision/CMaterialFilter.hpp" #include #include #include namespace metaforce { class CInternalRayCastStructure { zeus::CMRay x0_ray; float x38_maxTime; zeus::CTransform x3c_xf; const CMaterialFilter& x6c_filter; public: CInternalRayCastStructure(const zeus::CVector3f& start, const zeus::CVector3f& dir, float length, const zeus::CTransform& xf, const CMaterialFilter& filter) : x0_ray(start, dir, length), x38_maxTime(length), x3c_xf(xf), x6c_filter(filter) {} const zeus::CMRay& GetRay() const { return x0_ray; } const zeus::CVector3f& GetStart() const { return x0_ray.start; } const zeus::CVector3f& GetNormal() const { return x0_ray.end; } float GetMaxTime() const { return x38_maxTime; } const zeus::CTransform& GetTransform() const { return x3c_xf; } const CMaterialFilter& GetFilter() const { return x6c_filter; } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CJointCollisionDescription.cpp ================================================ #include "Runtime/Collision/CJointCollisionDescription.hpp" namespace metaforce { CJointCollisionDescription::CJointCollisionDescription(ECollisionType colType, CSegId pivotId, CSegId nextId, const zeus::CVector3f& bounds, const zeus::CVector3f& pivotPoint, float radius, float maxSeparation, EOrientationType orientType, std::string_view name, float mass) : x0_colType(colType) , x4_orientType(orientType) , x8_pivotId(pivotId) , x9_nextId(nextId) , xc_bounds(bounds) , x18_pivotPoint(pivotPoint) , x24_radius(radius) , x28_maxSeparation(maxSeparation) , x2c_name(name) , x40_mass(mass) {} CJointCollisionDescription CJointCollisionDescription::SphereSubdivideCollision(CSegId pivotId, CSegId nextId, float radius, float maxSeparation, EOrientationType orientType, std::string_view name, float mass) { return CJointCollisionDescription(ECollisionType::SphereSubdivide, pivotId, nextId, zeus::skZero3f, zeus::skZero3f, radius, maxSeparation, orientType, name, mass); } CJointCollisionDescription CJointCollisionDescription::SphereCollision(CSegId pivotId, float radius, std::string_view name, float mass) { return CJointCollisionDescription(ECollisionType::Sphere, pivotId, {}, zeus::skZero3f, zeus::skZero3f, radius, 0.f, EOrientationType::Zero, name, mass); } CJointCollisionDescription CJointCollisionDescription::AABoxCollision(CSegId pivotId, const zeus::CVector3f& bounds, std::string_view name, float mass) { return CJointCollisionDescription(ECollisionType::AABox, pivotId, {}, bounds, zeus::skZero3f, 0.f, 0.f, EOrientationType::Zero, name, mass); } CJointCollisionDescription CJointCollisionDescription::OBBAutoSizeCollision(CSegId pivotId, CSegId nextId, const zeus::CVector3f& bounds, EOrientationType orientType, std::string_view name, float mass) { return CJointCollisionDescription(ECollisionType::OBBAutoSize, pivotId, nextId, bounds, zeus::skZero3f, 0.f, 0.f, orientType, name, mass); } CJointCollisionDescription CJointCollisionDescription::OBBCollision(CSegId pivotId, const zeus::CVector3f& bounds, const zeus::CVector3f& pivotPoint, std::string_view name, float mass) { return CJointCollisionDescription(ECollisionType::OBB, pivotId, {}, bounds, pivotPoint, 0.f, 0.f, EOrientationType::Zero, name, mass); } void CJointCollisionDescription::ScaleAllBounds(const zeus::CVector3f& scale) { xc_bounds *= scale; x24_radius *= scale.x(); x28_maxSeparation *= scale.x(); x18_pivotPoint *= scale; } } // namespace metaforce ================================================ FILE: Runtime/Collision/CJointCollisionDescription.hpp ================================================ #pragma once #include #include "Runtime/Character/CSegId.hpp" #include #include namespace metaforce { struct SJointInfo { const char* from; const char* to; float radius; float separation; }; struct SAABoxJointInfo { const char* name; zeus::CVector3f extents; }; struct SOBBJointInfo { const char* from; const char* to; zeus::CVector3f bounds; }; struct SOBBRadiiJointInfo { const char* from; const char* to; float radius; }; struct SSphereJointInfo { const char* name; float radius; }; class CJointCollisionDescription { public: enum class ECollisionType { Sphere, SphereSubdivide, AABox, OBBAutoSize, OBB, }; enum class EOrientationType { Zero, One }; private: ECollisionType x0_colType; EOrientationType x4_orientType; CSegId x8_pivotId; CSegId x9_nextId; zeus::CVector3f xc_bounds; zeus::CVector3f x18_pivotPoint; float x24_radius; float x28_maxSeparation; std::string x2c_name; TUniqueId x3c_actorId = kInvalidUniqueId; float x40_mass; public: CJointCollisionDescription(ECollisionType, CSegId, CSegId, const zeus::CVector3f&, const zeus::CVector3f&, float, float, EOrientationType, std::string_view, float); static CJointCollisionDescription SphereSubdivideCollision(CSegId pivotId, CSegId nextId, float radius, float maxSeparation, EOrientationType orientType, std::string_view name, float mass); static CJointCollisionDescription SphereCollision(CSegId pivotId, float radius, std::string_view name, float mass); static CJointCollisionDescription AABoxCollision(CSegId pivotId, const zeus::CVector3f& bounds, std::string_view name, float mass); static CJointCollisionDescription OBBAutoSizeCollision(CSegId pivotId, CSegId nextId, const zeus::CVector3f& bounds, EOrientationType orientType, std::string_view, float mass); static CJointCollisionDescription OBBCollision(CSegId pivotId, const zeus::CVector3f& bounds, const zeus::CVector3f& pivotPoint, std::string_view name, float mass); std::string_view GetName() const { return x2c_name; } TUniqueId GetCollisionActorId() const { return x3c_actorId; } void SetCollisionActorId(TUniqueId id) { x3c_actorId = id; } const zeus::CVector3f& GetBounds() const { return xc_bounds; } float GetRadius() const { return x24_radius; } float GetMaxSeparation() const { return x28_maxSeparation; } EOrientationType GetOrientationType() const { return x4_orientType; } float GetMass() const { return x40_mass; } const zeus::CVector3f& GetPivotPoint() const { return x18_pivotPoint; } ECollisionType GetType() const { return x0_colType; } CSegId GetNextId() const { return x9_nextId; } CSegId GetPivotId() const { return x8_pivotId; } void ScaleAllBounds(const zeus::CVector3f& scale); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CMakeLists.txt ================================================ set(COLLISION_SOURCES CAreaOctTree.hpp CAreaOctTree.cpp CollisionUtil.hpp CollisionUtil.cpp CGameCollision.hpp CGameCollision.cpp CCollisionResponseData.hpp CCollisionResponseData.cpp CCollisionInfo.hpp CCollisionInfo.cpp CCollisionInfoList.hpp CCollisionEdge.hpp CCollisionEdge.cpp CCollisionSurface.hpp CCollisionSurface.cpp InternalColliders.hpp InternalColliders.cpp CMetroidAreaCollider.hpp CMetroidAreaCollider.cpp COBBTree.hpp COBBTree.cpp CCollidableAABox.hpp CCollidableAABox.cpp CCollidableCollisionSurface.hpp CCollidableCollisionSurface.cpp CCollidableSphere.hpp CCollidableSphere.cpp CCollidableOBBTree.hpp CCollidableOBBTree.cpp CCollidableOBBTreeGroup.hpp CCollidableOBBTreeGroup.cpp CCollisionPrimitive.hpp CCollisionPrimitive.cpp CMaterialList.hpp CMaterialFilter.hpp CMaterialFilter.cpp CInternalRayCastStructure.hpp CRayCastResult.hpp CRayCastResult.cpp CCollisionActor.hpp CCollisionActor.cpp CCollisionActorManager.hpp CCollisionActorManager.cpp CJointCollisionDescription.hpp CJointCollisionDescription.cpp CAABoxFilter.hpp CAABoxFilter.cpp CBallFilter.hpp CBallFilter.cpp ICollisionFilter.hpp) runtime_add_list(Collision COLLISION_SOURCES) ================================================ FILE: Runtime/Collision/CMaterialFilter.cpp ================================================ #include "Runtime/Collision/CMaterialFilter.hpp" namespace metaforce { const CMaterialFilter CMaterialFilter::skPassEverything({0x00000000FFFFFFFF}, {0}, EFilterType::Always); } // namespace metaforce ================================================ FILE: Runtime/Collision/CMaterialFilter.hpp ================================================ #pragma once #include "Runtime/Collision/CMaterialList.hpp" namespace metaforce { class CMaterialFilter { public: enum class EFilterType { Always, Include, Exclude, IncludeExclude }; private: CMaterialList x0_include = CMaterialList(0x00000000FFFFFFFF); CMaterialList x8_exclude = CMaterialList(0); EFilterType x10_type = EFilterType::Always; public: static const CMaterialFilter skPassEverything; constexpr CMaterialFilter(const CMaterialList& include, const CMaterialList& exclude, EFilterType type) noexcept : x0_include(include), x8_exclude(exclude), x10_type(type) {} static constexpr CMaterialFilter MakeInclude(const CMaterialList& include) noexcept { return CMaterialFilter(include, {}, EFilterType::Include); } static constexpr CMaterialFilter MakeExclude(const CMaterialList& exclude) noexcept { return CMaterialFilter({u64(0x00000000FFFFFFFF)}, exclude, EFilterType::Exclude); } static constexpr CMaterialFilter MakeIncludeExclude(const CMaterialList& include, const CMaterialList& exclude) noexcept { return CMaterialFilter(include, exclude, EFilterType::IncludeExclude); } constexpr const CMaterialList& GetIncludeList() const noexcept { return x0_include; } constexpr const CMaterialList& GetExcludeList() const noexcept { return x8_exclude; } constexpr CMaterialList& IncludeList() noexcept { return x0_include; } constexpr CMaterialList& ExcludeList() noexcept { return x8_exclude; } const CMaterialList& IncludeList() const noexcept { return x0_include; } const CMaterialList& ExcludeList() const noexcept { return x8_exclude; } constexpr bool Passes(const CMaterialList& list) const noexcept { switch (x10_type) { case EFilterType::Always: return true; case EFilterType::Include: return (list.x0_list & x0_include.x0_list) != 0; case EFilterType::Exclude: return (list.x0_list & x8_exclude.x0_list) == 0; case EFilterType::IncludeExclude: if ((list.x0_list & x0_include.x0_list) == 0) return false; return (list.x0_list & x8_exclude.x0_list) == 0; default: return true; } } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CMaterialList.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" namespace metaforce { enum class EMaterialTypes { NoStepLogic = 0, Stone = 1, Metal = 2, Grass = 3, Ice = 4, Pillar = 5, MetalGrating = 6, Phazon = 7, Dirt = 8, Lava = 9, LavaStone = 10, Snow = 11, MudSlow = 12, HalfPipe = 13, Mud = 14, Glass = 15, Shield = 16, Sand = 17, ProjectilePassthrough = 18, Solid = 19, NoPlatformCollision = 20, CameraPassthrough = 21, Wood = 22, Organic = 23, NoEdgeCollision = 24, RedundantEdgeOrFlippedTri = 25, SeeThrough = 26, ScanPassthrough = 27, AIPassthrough = 28, Ceiling = 29, Wall = 30, Floor = 31, Player = 32, Character = 33, Trigger = 34, Projectile = 35, Bomb = 36, GroundCollider = 37, NoStaticCollision = 38, Scannable = 39, Target = 40, Orbit = 41, Occluder = 42, Immovable = 43, Debris = 44, PowerBomb = 45, Unknown46 = 46, CollisionActor = 47, AIBlock = 48, Platform = 49, NonSolidDamageable = 50, RadarObject = 51, PlatformSlave = 52, AIJoint = 53, Unknown54 = 54, SolidCharacter = 55, ExcludeFromLineOfSightTest = 56, ExcludeFromRadar = 57, NoPlayerCollision = 58, SixtyThree = 63 }; class CMaterialList { friend class CMaterialFilter; u64 x0_list = 0; public: constexpr CMaterialList() noexcept = default; constexpr CMaterialList(u64 flags) noexcept : x0_list(flags) {} constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4, EMaterialTypes t5, EMaterialTypes t6) noexcept : CMaterialList(t1, t2, t3, t4, t5) { x0_list |= u64{1} << u64(t6); } constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4, EMaterialTypes t5) noexcept : CMaterialList(t1, t2, t3, t4) { x0_list |= u64{1} << u64(t5); } constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3, EMaterialTypes t4) noexcept : CMaterialList(t1, t2, t3) { x0_list |= u64{1} << u64(t4); } constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2, EMaterialTypes t3) noexcept : CMaterialList(t1, t2) { x0_list |= u64{1} << u64(t3); } constexpr CMaterialList(EMaterialTypes t1, EMaterialTypes t2) noexcept : CMaterialList(t1) { x0_list |= u64{1} << u64(t2); } constexpr CMaterialList(EMaterialTypes t1) noexcept : x0_list(u64{1} << u64(t1)) {} constexpr u64 GetValue() const noexcept { return x0_list; } static constexpr s32 BitPosition(u64 flags) noexcept { for (s32 ret = 0, i = 0; i < 32; ++i) { if ((flags & 1) != 0u) { return ret; } flags >>= 1; ++ret; } return -1; } constexpr void Add(EMaterialTypes type) noexcept { x0_list |= (u64{1} << u64(type)); } constexpr void Add(const CMaterialList& l) noexcept { x0_list |= l.x0_list; } constexpr void Remove(EMaterialTypes type) noexcept { x0_list &= ~(u64{1} << u64(type)); } constexpr void Remove(const CMaterialList& other) noexcept { x0_list &= ~(other.x0_list); } constexpr bool HasMaterial(EMaterialTypes type) const noexcept { return (x0_list & (u64{1} << u64(type))) != 0; } constexpr bool SharesMaterials(const CMaterialList& other) const noexcept { return (other.x0_list & x0_list) ? true : false; } constexpr u64 Intersection(const CMaterialList& other) const noexcept { return other.x0_list & x0_list; } constexpr u64 XOR(const CMaterialList& other) const noexcept { return x0_list ^ other.x0_list; } void Union(const CMaterialList& other) noexcept { x0_list |= other.x0_list; } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CMetroidAreaCollider.cpp ================================================ #include "Runtime/Collision/CMetroidAreaCollider.hpp" #include "Runtime/Collision/CCollisionInfoList.hpp" #include "Runtime/Collision/CMaterialFilter.hpp" #include "Runtime/Collision/CollisionUtil.hpp" namespace metaforce { u32 CMetroidAreaCollider::g_CalledClip = 0; u32 CMetroidAreaCollider::g_RejectedByClip = 0; u32 CMetroidAreaCollider::g_TrianglesProcessed = 0; u32 CMetroidAreaCollider::g_DupTrianglesProcessed = 0; u16 CMetroidAreaCollider::g_DupPrimitiveCheckCount = 0; std::array CMetroidAreaCollider::g_DupVertexList{}; std::array CMetroidAreaCollider::g_DupEdgeList{}; std::array CMetroidAreaCollider::g_DupTriangleList{}; CAABoxAreaCache::CAABoxAreaCache(const zeus::CAABox& aabb, const std::array& pl, const CMaterialFilter& filter, const CMaterialList& material, CCollisionInfoList& collisionList) : x0_aabb(aabb) , x4_planes(pl) , x8_filter(filter) , xc_material(material) , x10_collisionList(collisionList) , x14_center(aabb.center()) , x20_halfExtent(aabb.extents()) {} CBooleanAABoxAreaCache::CBooleanAABoxAreaCache(const zeus::CAABox& aabb, const CMaterialFilter& filter) : x0_aabb(aabb), x4_filter(filter), x8_center(aabb.center()), x14_halfExtent(aabb.extents()) {} CSphereAreaCache::CSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter, const CMaterialList& material, CCollisionInfoList& collisionList) : x0_aabb(aabb), x4_sphere(sphere), x8_filter(filter), xc_material(material), x10_collisionList(collisionList) {} CBooleanSphereAreaCache::CBooleanSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter) : x0_aabb(aabb), x4_sphere(sphere), x8_filter(filter) {} SBoxEdge::SBoxEdge(const zeus::CAABox& aabb, int idx, const zeus::CVector3f& dir) : x0_seg(aabb.getEdge(zeus::CAABox::EBoxEdgeId(idx))) , x28_start(x0_seg.x0_start) , x40_end(x0_seg.x18_end) , x58_delta(x40_end - x28_start) , x70_coDir(x58_delta.cross(dir).asNormalized()) , x88_dirCoDirDot(x28_start.dot(x70_coDir)) {} static void FlagEdgeIndicesForFace(int face, std::array& edgeFlags) { switch (face) { case 0: edgeFlags[10] = true; edgeFlags[11] = true; edgeFlags[2] = true; edgeFlags[4] = true; return; case 1: edgeFlags[8] = true; edgeFlags[9] = true; edgeFlags[0] = true; edgeFlags[6] = true; return; case 2: edgeFlags[4] = true; edgeFlags[5] = true; edgeFlags[6] = true; edgeFlags[7] = true; return; case 3: edgeFlags[0] = true; edgeFlags[1] = true; edgeFlags[2] = true; edgeFlags[3] = true; return; case 4: edgeFlags[7] = true; edgeFlags[8] = true; edgeFlags[3] = true; edgeFlags[11] = true; return; case 5: edgeFlags[1] = true; edgeFlags[5] = true; edgeFlags[9] = true; edgeFlags[10] = true; return; default: break; } } static void FlagVertexIndicesForFace(int face, std::array& vertFlags) { switch (face) { case 0: vertFlags[1] = true; vertFlags[3] = true; vertFlags[5] = true; vertFlags[7] = true; return; case 1: vertFlags[0] = true; vertFlags[2] = true; vertFlags[4] = true; vertFlags[6] = true; return; case 2: vertFlags[2] = true; vertFlags[3] = true; vertFlags[6] = true; vertFlags[7] = true; return; case 3: vertFlags[0] = true; vertFlags[1] = true; vertFlags[4] = true; vertFlags[5] = true; return; case 4: vertFlags[4] = true; vertFlags[5] = true; vertFlags[6] = true; vertFlags[7] = true; return; case 5: vertFlags[0] = true; vertFlags[1] = true; vertFlags[2] = true; vertFlags[3] = true; return; default: break; } } CMovingAABoxComponents::CMovingAABoxComponents(const zeus::CAABox& aabb, const zeus::CVector3f& dir) : x6e8_aabb(aabb) { std::array edgeFlags{}; std::array vertFlags{}; int useFaces = 0; for (int i = 0; i < 3; ++i) { if (dir[i] != 0.f) { const int face = i * 2 + (dir[i] < 0.f); FlagEdgeIndicesForFace(face, edgeFlags); FlagVertexIndicesForFace(face, vertFlags); useFaces += 1; } } for (size_t i = 0; i < edgeFlags.size(); ++i) { if (edgeFlags[i]) { x0_edges.emplace_back(aabb, s32(i), dir); } } for (size_t i = 0; i < vertFlags.size(); ++i) { if (vertFlags[i]) { x6c4_vertIdxs.push_back(u32(i)); } } if (useFaces == 1) { x6e8_aabb = zeus::CAABox(); x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[0])); x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[1])); x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[2])); x6e8_aabb.accumulateBounds(aabb.getPoint(x6c4_vertIdxs[3])); } } CMetroidAreaCollider::COctreeLeafCache::COctreeLeafCache(const CAreaOctTree& octTree) : x0_octTree(octTree) {} void CMetroidAreaCollider::COctreeLeafCache::AddLeaf(const CAreaOctTree::Node& node) { if (x4_nodeCache.size() == x4_nodeCache.capacity()) { x908_24_overflow = true; return; } x4_nodeCache.push_back(node); } void CMetroidAreaCollider::BuildOctreeLeafCache(const CAreaOctTree::Node& node, const zeus::CAABox& aabb, CMetroidAreaCollider::COctreeLeafCache& cache) { for (int i = 0; i < 8; ++i) { CAreaOctTree::Node::ETreeType type = node.GetChildType(i); if (type != CAreaOctTree::Node::ETreeType::Invalid) { CAreaOctTree::Node ch = node.GetChild(i); if (aabb.intersects(ch.GetBoundingBox())) { if (type == CAreaOctTree::Node::ETreeType::Leaf) cache.AddLeaf(ch); else BuildOctreeLeafCache(ch, aabb, cache); } } } } static zeus::CVector3f ClipRayToPlane(const zeus::CVector3f& a, const zeus::CVector3f& b, const zeus::CPlane& plane) { return (1.f - -plane.pointToPlaneDist(a) / (b - a).dot(plane.normal())) * (a - b) + b; } bool CMetroidAreaCollider::ConvexPolyCollision(const std::array& planes, const std::array& verts, zeus::CAABox& aabb) { std::array, 2> vecs; g_CalledClip += 1; g_RejectedByClip -= 1; vecs[0].push_back(verts[0]); vecs[0].push_back(verts[1]); vecs[0].push_back(verts[2]); int vecIdx = 0; int otherVecIdx = 1; for (int i = 0; i < 6; ++i) { rstl::reserved_vector& vec = vecs[vecIdx]; rstl::reserved_vector& otherVec = vecs[otherVecIdx]; otherVec.clear(); bool inFrontOf = planes[i].pointToPlaneDist(vec.front()) >= 0.f; for (size_t j = 0; j < vec.size(); ++j) { const zeus::CVector3f& b = vec[(j + 1) % vec.size()]; if (inFrontOf) otherVec.push_back(vec[j]); bool nextInFrontOf = planes[i].pointToPlaneDist(b) >= 0.f; if (nextInFrontOf ^ inFrontOf) otherVec.push_back(ClipRayToPlane(vec[j], b, planes[i])); inFrontOf = nextInFrontOf; } if (otherVec.empty()) return false; vecIdx ^= 1; otherVecIdx ^= 1; } rstl::reserved_vector& accumVec = vecs[otherVecIdx ^ 1]; for (const zeus::CVector3f& point : accumVec) aabb.accumulateBounds(point); g_RejectedByClip -= 1; return true; } bool CMetroidAreaCollider::AABoxCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const CMaterialFilter& filter) { CBooleanAABoxAreaCache cache(aabb, filter); for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) { if (cache.x0_aabb.intersects(node.GetBoundingBox())) { CAreaOctTree::TriListReference list = node.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { ++g_TrianglesProcessed; CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(list.GetAt(j)); if (cache.x4_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) { if (CollisionUtil::TriBoxOverlap(cache.x8_center, cache.x14_halfExtent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) return true; } } } } return false; } bool CMetroidAreaCollider::AABoxCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node, const CBooleanAABoxAreaCache& cache) { for (int i = 0; i < 8; ++i) { CAreaOctTree::Node::ETreeType type = node.GetChildType(i); if (type != CAreaOctTree::Node::ETreeType::Invalid) { CAreaOctTree::Node ch = node.GetChild(i); if (cache.x0_aabb.intersects(ch.GetBoundingBox())) { if (type == CAreaOctTree::Node::ETreeType::Leaf) { CAreaOctTree::TriListReference list = ch.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { ++g_TrianglesProcessed; CCollisionSurface surf = ch.GetOwner().GetMasterListTriangle(list.GetAt(j)); if (cache.x4_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) { if (CollisionUtil::TriBoxOverlap(cache.x8_center, cache.x14_halfExtent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) return true; } } } else { if (AABoxCollisionCheckBoolean_Internal(ch, cache)) return true; } } } } return false; } bool CMetroidAreaCollider::AABoxCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const CMaterialFilter& filter) { CBooleanAABoxAreaCache cache(aabb, filter); return AABoxCollisionCheckBoolean_Internal(octTree.GetRootNode(), cache); } bool CMetroidAreaCollider::SphereCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter) { CBooleanSphereAreaCache cache(aabb, sphere, filter); for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) { if (cache.x0_aabb.intersects(node.GetBoundingBox())) { CAreaOctTree::TriListReference list = node.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { ++g_TrianglesProcessed; CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(list.GetAt(j)); if (cache.x8_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) { if (CollisionUtil::TriSphereOverlap(cache.x4_sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) return true; } } } } return false; } bool CMetroidAreaCollider::SphereCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node, const CBooleanSphereAreaCache& cache) { for (int i = 0; i < 8; ++i) { CAreaOctTree::Node::ETreeType type = node.GetChildType(i); if (type != CAreaOctTree::Node::ETreeType::Invalid) { CAreaOctTree::Node ch = node.GetChild(i); if (cache.x0_aabb.intersects(ch.GetBoundingBox())) { if (type == CAreaOctTree::Node::ETreeType::Leaf) { CAreaOctTree::TriListReference list = ch.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { ++g_TrianglesProcessed; CCollisionSurface surf = ch.GetOwner().GetMasterListTriangle(list.GetAt(j)); if (cache.x8_filter.Passes(CMaterialList(surf.GetSurfaceFlags()))) { if (CollisionUtil::TriSphereOverlap(cache.x4_sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) return true; } } } else { if (SphereCollisionCheckBoolean_Internal(ch, cache)) return true; } } } } return false; } bool CMetroidAreaCollider::SphereCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter) { CAreaOctTree::Node node = octTree.GetRootNode(); CBooleanSphereAreaCache cache(aabb, sphere, filter); return SphereCollisionCheckBoolean_Internal(node, cache); } bool CMetroidAreaCollider::AABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CMaterialList& matList, CCollisionInfoList& list) { bool ret = false; const std::array planes{{ {zeus::skRight, aabb.min.dot(zeus::skRight)}, {zeus::skLeft, aabb.max.dot(zeus::skLeft)}, {zeus::skForward, aabb.min.dot(zeus::skForward)}, {zeus::skBack, aabb.max.dot(zeus::skBack)}, {zeus::skUp, aabb.min.dot(zeus::skUp)}, {zeus::skDown, aabb.max.dot(zeus::skDown)}, }}; CAABoxAreaCache cache(aabb, planes, filter, matList, list); ResetInternalCounters(); for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) { if (aabb.intersects(node.GetBoundingBox())) { CAreaOctTree::TriListReference listRef = node.GetTriangleArray(); for (int j = 0; j < listRef.GetSize(); ++j) { ++g_TrianglesProcessed; u16 triIdx = listRef.GetAt(j); if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) { g_DupTrianglesProcessed += 1; } else { g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount; CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(triIdx); CMaterialList material(surf.GetSurfaceFlags()); if (cache.x8_filter.Passes(material)) { if (CollisionUtil::TriBoxOverlap(cache.x14_center, cache.x20_halfExtent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) { zeus::CAABox aabb2 = zeus::CAABox(); if (ConvexPolyCollision(cache.x4_planes, surf.GetVerts(), aabb2)) { zeus::CPlane plane = surf.GetPlane(); CCollisionInfo collision(aabb2, cache.xc_material, material, plane.normal(), -plane.normal()); cache.x10_collisionList.Add(collision, false); ret = true; } } } } } } } return ret; } bool CMetroidAreaCollider::AABoxCollisionCheck_Internal(const CAreaOctTree::Node& node, const CAABoxAreaCache& cache) { bool ret = false; switch (node.GetTreeType()) { case CAreaOctTree::Node::ETreeType::Invalid: return false; case CAreaOctTree::Node::ETreeType::Branch: { for (int i = 0; i < 8; ++i) { CAreaOctTree::Node ch = node.GetChild(i); if (ch.GetBoundingBox().intersects(cache.x0_aabb)) if (AABoxCollisionCheck_Internal(ch, cache)) ret = true; } break; } case CAreaOctTree::Node::ETreeType::Leaf: { CAreaOctTree::TriListReference list = node.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { ++g_TrianglesProcessed; u16 triIdx = list.GetAt(j); if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) { g_DupTrianglesProcessed += 1; } else { g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount; CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(triIdx); CMaterialList material(surf.GetSurfaceFlags()); if (cache.x8_filter.Passes(material)) { if (CollisionUtil::TriBoxOverlap(cache.x14_center, cache.x20_halfExtent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) { zeus::CAABox aabb = zeus::CAABox(); if (ConvexPolyCollision(cache.x4_planes, surf.GetVerts(), aabb)) { zeus::CPlane plane = surf.GetPlane(); CCollisionInfo collision(aabb, cache.xc_material, material, plane.normal(), -plane.normal()); cache.x10_collisionList.Add(collision, false); ret = true; } } } } } break; } default: break; } return ret; } bool CMetroidAreaCollider::AABoxCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CMaterialList& matList, CCollisionInfoList& list) { const std::array planes{{ {zeus::skRight, aabb.min.dot(zeus::skRight)}, {zeus::skLeft, aabb.max.dot(zeus::skLeft)}, {zeus::skForward, aabb.min.dot(zeus::skForward)}, {zeus::skBack, aabb.max.dot(zeus::skBack)}, {zeus::skUp, aabb.min.dot(zeus::skUp)}, {zeus::skDown, aabb.max.dot(zeus::skDown)}, }}; const CAABoxAreaCache cache(aabb, planes, filter, matList, list); ResetInternalCounters(); const CAreaOctTree::Node node = octTree.GetRootNode(); return AABoxCollisionCheck_Internal(node, cache); } bool CMetroidAreaCollider::SphereCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialList& matList, const CMaterialFilter& filter, CCollisionInfoList& clist) { ResetInternalCounters(); bool ret = false; zeus::CVector3f point, normal; for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) { if (aabb.intersects(node.GetBoundingBox())) { CAreaOctTree::TriListReference list = node.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { ++g_TrianglesProcessed; u16 triIdx = list.GetAt(j); if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) { g_DupTrianglesProcessed += 1; } else { g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount; CCollisionSurface surf = node.GetOwner().GetMasterListTriangle(triIdx); CMaterialList material(surf.GetSurfaceFlags()); if (filter.Passes(material)) { if (CollisionUtil::TriSphereIntersection(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2), point, normal)) { CCollisionInfo collision(point, matList, material, normal); clist.Add(collision, false); ret = true; } } } } } } return ret; } bool CMetroidAreaCollider::SphereCollisionCheck_Internal(const CAreaOctTree::Node& node, const CSphereAreaCache& cache) { bool ret = false; zeus::CVector3f point, normal; for (int i = 0; i < 8; ++i) { CAreaOctTree::Node::ETreeType chTp = node.GetChildType(i); if (chTp != CAreaOctTree::Node::ETreeType::Invalid) { CAreaOctTree::Node ch = node.GetChild(i); if (cache.x0_aabb.intersects(ch.GetBoundingBox())) { if (chTp == CAreaOctTree::Node::ETreeType::Leaf) { CAreaOctTree::TriListReference list = ch.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { ++g_TrianglesProcessed; u16 triIdx = list.GetAt(j); if (g_DupPrimitiveCheckCount == g_DupTriangleList[triIdx]) { g_DupTrianglesProcessed += 1; } else { g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount; CCollisionSurface surf = ch.GetOwner().GetMasterListTriangle(triIdx); CMaterialList material(surf.GetSurfaceFlags()); if (cache.x8_filter.Passes(material)) { if (CollisionUtil::TriSphereIntersection(cache.x4_sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2), point, normal)) { CCollisionInfo collision(point, cache.xc_material, material, normal); cache.x10_collisionList.Add(collision, false); ret = true; } } } } } else { if (SphereCollisionCheck_Internal(ch, cache)) ret = true; } } } } return ret; } bool CMetroidAreaCollider::SphereCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialList& matList, const CMaterialFilter& filter, CCollisionInfoList& list) { CSphereAreaCache cache(aabb, sphere, filter, matList, list); ResetInternalCounters(); CAreaOctTree::Node node = octTree.GetRootNode(); return SphereCollisionCheck_Internal(node, cache); } bool CMetroidAreaCollider::MovingAABoxCollisionCheck_BoxVertexTri( const CCollisionSurface& surf, const zeus::CAABox& aabb, const rstl::reserved_vector& vertIndices, const zeus::CVector3f& dir, double& d, zeus::CVector3f& normalOut, zeus::CVector3f& pointOut) { bool ret = false; for (u32 idx : vertIndices) { zeus::CVector3f point = aabb.getPoint(idx); if (CollisionUtil::RayTriangleIntersection_Double(point, dir, surf.GetVerts(), d)) { pointOut = float(d) * dir + point; normalOut = surf.GetNormal(); ret = true; } } return ret; } bool CMetroidAreaCollider::MovingAABoxCollisionCheck_TriVertexBox(const zeus::CVector3f& vert, const zeus::CAABox& aabb, const zeus::CVector3f& dir, double& dOut, zeus::CVector3f& normal, zeus::CVector3f& point) { zeus::CMRay ray(vert, -dir, dOut); zeus::CVector3f norm; double d; if (CollisionUtil::RayAABoxIntersection_Double(ray, aabb, norm, d) == 2) { d *= dOut; if (d < dOut) { normal = -norm; dOut = d; point = vert; return true; } } return false; } bool CMetroidAreaCollider::MovingAABoxCollisionCheck_Edge(const zeus::CVector3f& ev0, const zeus::CVector3f& ev1, const rstl::reserved_vector& edges, const zeus::CVector3f& dir, double& d, zeus::CVector3f& normal, zeus::CVector3f& point) { bool ret = false; for (const SBoxEdge& edge : edges) { zeus::CVector3d ev0d = ev0; zeus::CVector3d ev1d = ev1; if ((edge.x70_coDir.dot(ev1d) >= edge.x88_dirCoDirDot) != (edge.x70_coDir.dot(ev0d) >= edge.x88_dirCoDirDot)) { zeus::CVector3d delta = ev0d - ev1d; zeus::CVector3d cross0 = edge.x58_delta.cross(delta); if (cross0.magSquared() >= DBL_EPSILON) { zeus::CVector3d cross0Norm = cross0.asNormalized(); if (cross0Norm.dot(dir) >= 0.0) { ev1d = ev0; ev0d = ev1; delta = ev0d - ev1d; cross0Norm = edge.x58_delta.cross(delta).asNormalized(); } zeus::CVector3d clipped = ev0d + (-(ev0d.dot(edge.x70_coDir) - edge.x88_dirCoDirDot) / delta.dot(edge.x70_coDir)) * delta; int maxCompIdx = (std::fabs(edge.x70_coDir.x()) > std::fabs(edge.x70_coDir.y())) ? 0 : 1; if (std::fabs(edge.x70_coDir[maxCompIdx]) < std::fabs(edge.x70_coDir.z())) maxCompIdx = 2; int ci0, ci1; switch (maxCompIdx) { case 0: ci0 = 1; ci1 = 2; break; case 1: ci0 = 0; ci1 = 2; break; default: ci0 = 0; ci1 = 1; break; } double mag = (edge.x58_delta[ci0] * (clipped[ci1] - edge.x28_start[ci1]) - edge.x58_delta[ci1] * (clipped[ci0] - edge.x28_start[ci0])) / (edge.x58_delta[ci0] * dir[ci1] - edge.x58_delta[ci1] * dir[ci0]); if (mag >= 0.0 && mag < d) { zeus::CVector3d clippedMag = clipped - mag * zeus::CVector3d(dir); if ((edge.x28_start - clippedMag).dot(edge.x40_end - clippedMag) < 0.0 && mag < d) { normal = cross0Norm.asCVector3f(); d = mag; point = clipped.asCVector3f(); ret = true; } } } } } return ret; } bool CMetroidAreaCollider::MovingAABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CMaterialList& matList, const zeus::CVector3f& dir, float mag, CCollisionInfo& infoOut, double& dOut) { bool ret = false; ResetInternalCounters(); dOut = mag; CMovingAABoxComponents components(aabb, dir); zeus::CAABox movedAABB = components.x6e8_aabb; zeus::CVector3f moveVec = mag * dir; movedAABB.accumulateBounds(aabb.min + moveVec); movedAABB.accumulateBounds(aabb.max + moveVec); zeus::CVector3f center = movedAABB.center(); zeus::CVector3f extent = movedAABB.extents(); zeus::CVector3f normal, point; for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) { if (movedAABB.intersects(node.GetBoundingBox())) { CAreaOctTree::TriListReference list = node.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { u16 triIdx = list.GetAt(j); if (g_DupPrimitiveCheckCount != g_DupTriangleList[triIdx]) { g_TrianglesProcessed += 1; g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount; CMaterialList triMat(node.GetOwner().GetTriangleMaterial(triIdx)); if (filter.Passes(triMat)) { std::array vertIndices; node.GetOwner().GetTriangleVertexIndices(triIdx, vertIndices.data()); CCollisionSurface surf(node.GetOwner().GetVert(vertIndices[0]), node.GetOwner().GetVert(vertIndices[1]), node.GetOwner().GetVert(vertIndices[2]), triMat.GetValue()); if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) { bool triRet = false; double d = dOut; if (MovingAABoxCollisionCheck_BoxVertexTri(surf, aabb, components.x6c4_vertIdxs, dir, d, normal, point) && d < dOut) { triRet = true; ret = true; infoOut = CCollisionInfo(point, matList, triMat, normal); dOut = d; } for (const u16 vertIdx : vertIndices) { zeus::CVector3f vtx = node.GetOwner().GetVert(vertIdx); if (g_DupPrimitiveCheckCount != g_DupVertexList[vertIdx]) { g_DupVertexList[vertIdx] = g_DupPrimitiveCheckCount; if (movedAABB.pointInside(vtx)) { d = dOut; if (MovingAABoxCollisionCheck_TriVertexBox(vtx, aabb, dir, d, normal, point) && d < dOut) { CMaterialList vertMat(node.GetOwner().GetVertMaterial(vertIdx)); triRet = true; ret = true; infoOut = CCollisionInfo(point, matList, vertMat, normal); dOut = d; } } } } const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx); for (int k = 0; k < 3; ++k) { u16 edgeIdx = edgeIndices[k]; if (g_DupPrimitiveCheckCount != g_DupEdgeList[edgeIdx]) { g_DupEdgeList[edgeIdx] = g_DupPrimitiveCheckCount; CMaterialList edgeMat(node.GetOwner().GetEdgeMaterial(edgeIdx)); if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) { d = dOut; const CCollisionEdge& edge = node.GetOwner().GetEdge(edgeIdx); if (MovingAABoxCollisionCheck_Edge(node.GetOwner().GetVert(edge.GetVertIndex1()), node.GetOwner().GetVert(edge.GetVertIndex2()), components.x0_edges, dir, d, normal, point) && d < dOut) { triRet = true; ret = true; infoOut = CCollisionInfo(point, matList, edgeMat, normal); dOut = d; } } } } if (triRet) { moveVec = float(dOut) * dir; movedAABB = components.x6e8_aabb; movedAABB.accumulateBounds(aabb.min + moveVec); movedAABB.accumulateBounds(aabb.max + moveVec); center = movedAABB.center(); extent = movedAABB.extents(); } } else { const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx); g_DupEdgeList[edgeIndices[0]] = g_DupPrimitiveCheckCount; g_DupEdgeList[edgeIndices[1]] = g_DupPrimitiveCheckCount; g_DupEdgeList[edgeIndices[2]] = g_DupPrimitiveCheckCount; g_DupVertexList[vertIndices[0]] = g_DupPrimitiveCheckCount; g_DupVertexList[vertIndices[1]] = g_DupPrimitiveCheckCount; g_DupVertexList[vertIndices[2]] = g_DupPrimitiveCheckCount; } } } } } } return ret; } bool CMetroidAreaCollider::MovingSphereCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter, const CMaterialList& matList, const zeus::CVector3f& dir, float mag, CCollisionInfo& infoOut, double& dOut) { bool ret = false; ResetInternalCounters(); dOut = mag; zeus::CAABox movedAABB = aabb; zeus::CVector3f moveVec = mag * dir; movedAABB.accumulateBounds(aabb.min + moveVec); movedAABB.accumulateBounds(aabb.max + moveVec); zeus::CVector3f center = movedAABB.center(); zeus::CVector3f extent = movedAABB.extents(); for (const CAreaOctTree::Node& node : leafCache.x4_nodeCache) { if (movedAABB.intersects(node.GetBoundingBox())) { CAreaOctTree::TriListReference list = node.GetTriangleArray(); for (int j = 0; j < list.GetSize(); ++j) { u16 triIdx = list.GetAt(j); if (g_DupPrimitiveCheckCount != g_DupTriangleList[triIdx]) { g_TrianglesProcessed += 1; g_DupTriangleList[triIdx] = g_DupPrimitiveCheckCount; CMaterialList triMat(node.GetOwner().GetTriangleMaterial(triIdx)); if (filter.Passes(triMat)) { std::array vertIndices; node.GetOwner().GetTriangleVertexIndices(triIdx, vertIndices.data()); CCollisionSurface surf(node.GetOwner().GetVert(vertIndices[0]), node.GetOwner().GetVert(vertIndices[1]), node.GetOwner().GetVert(vertIndices[2]), triMat.GetValue()); if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) { zeus::CVector3f surfNormal = surf.GetNormal(); if ((sphere.position + moveVec - surf.GetVert(0)).dot(surfNormal) <= sphere.radius) { bool triRet = false; float mag = (sphere.radius - (sphere.position - surf.GetVert(0)).dot(surfNormal)) / dir.dot(surfNormal); zeus::CVector3f intersectPoint = sphere.position + mag * dir; const std::array outsideEdges{ (intersectPoint - surf.GetVert(0)).dot(surfNormal.cross(surf.GetVert(1) - surf.GetVert(0))) < 0.f, (intersectPoint - surf.GetVert(1)).dot(surfNormal.cross(surf.GetVert(2) - surf.GetVert(1))) < 0.f, (intersectPoint - surf.GetVert(2)).dot(surfNormal.cross(surf.GetVert(0) - surf.GetVert(2))) < 0.f, }; if (mag >= 0.f && !outsideEdges[0] && !outsideEdges[1] && !outsideEdges[2] && mag < dOut) { infoOut = CCollisionInfo(intersectPoint - sphere.radius * surfNormal, matList, triMat, surfNormal); dOut = mag; triRet = true; ret = true; } bool intersects = (sphere.position - surf.GetVert(0)).dot(surfNormal) <= sphere.radius; std::array testVert{true, true, true}; const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx); for (int k = 0; k < 3; ++k) { if (intersects || outsideEdges[k]) { u16 edgeIdx = edgeIndices[k]; if (g_DupPrimitiveCheckCount != g_DupEdgeList[edgeIdx]) { g_DupEdgeList[edgeIdx] = g_DupPrimitiveCheckCount; CMaterialList edgeMat(node.GetOwner().GetEdgeMaterial(edgeIdx)); if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) { int nextIdx = (k + 1) % 3; zeus::CVector3f edgeVec = surf.GetVert(nextIdx) - surf.GetVert(k); float edgeVecMag = edgeVec.magnitude(); edgeVec *= zeus::CVector3f(1.f / edgeVecMag); float dirDotEdge = dir.dot(edgeVec); zeus::CVector3f edgeRej = dir - dirDotEdge * edgeVec; float edgeRejMagSq = edgeRej.magSquared(); zeus::CVector3f vertToSphere = sphere.position - surf.GetVert(k); float vtsDotEdge = vertToSphere.dot(edgeVec); zeus::CVector3f vtsRej = vertToSphere - vtsDotEdge * edgeVec; if (edgeRejMagSq > 0.f) { float tmp = 2.f * vtsRej.dot(edgeRej); float tmp2 = 4.f * edgeRejMagSq * (vtsRej.magSquared() - sphere.radius * sphere.radius) - tmp * tmp; if (tmp2 >= 0.f) { float mag = 0.5f / edgeRejMagSq * (-tmp - std::sqrt(tmp2)); if (mag >= 0.f) { float t = mag * dirDotEdge + vtsDotEdge; if (t >= 0.f && t <= edgeVecMag && mag < dOut) { zeus::CVector3f point = surf.GetVert(k) + t * edgeVec; infoOut = CCollisionInfo(point, matList, edgeMat, (sphere.position + mag * dir - point).normalized()); dOut = mag; triRet = true; ret = true; testVert[k] = false; testVert[nextIdx] = false; } else if (t < -sphere.radius && dirDotEdge <= 0.f) { testVert[k] = false; } else if (t > edgeVecMag + sphere.radius && dirDotEdge >= 0.0) { testVert[nextIdx] = false; } } } else { testVert[k] = false; testVert[nextIdx] = false; } } } } } } for (int k = 0; k < 3; ++k) { u16 vertIdx = vertIndices[k]; if (testVert[k]) { if (g_DupPrimitiveCheckCount != g_DupVertexList[vertIdx]) { g_DupVertexList[vertIdx] = g_DupPrimitiveCheckCount; double d = dOut; if (CollisionUtil::RaySphereIntersection_Double(zeus::CSphere(surf.GetVert(k), sphere.radius), sphere.position, dir, d) && d >= 0.0) { infoOut = CCollisionInfo(surf.GetVert(k), matList, node.GetOwner().GetVertMaterial(vertIdx), (sphere.position + dir * d - surf.GetVert(k)).normalized()); dOut = d; triRet = true; ret = true; } } } else { g_DupVertexList[vertIdx] = g_DupPrimitiveCheckCount; } } if (triRet) { moveVec = float(dOut) * dir; movedAABB = aabb; movedAABB.accumulateBounds(aabb.min + moveVec); movedAABB.accumulateBounds(aabb.max + moveVec); center = movedAABB.center(); extent = movedAABB.extents(); } } } else { const u16* edgeIndices = node.GetOwner().GetTriangleEdgeIndices(triIdx); g_DupEdgeList[edgeIndices[0]] = g_DupPrimitiveCheckCount; g_DupEdgeList[edgeIndices[1]] = g_DupPrimitiveCheckCount; g_DupEdgeList[edgeIndices[2]] = g_DupPrimitiveCheckCount; g_DupVertexList[vertIndices[0]] = g_DupPrimitiveCheckCount; g_DupVertexList[vertIndices[1]] = g_DupPrimitiveCheckCount; g_DupVertexList[vertIndices[2]] = g_DupPrimitiveCheckCount; } } } } } } return ret; } void CMetroidAreaCollider::ResetInternalCounters() { g_CalledClip = 0; g_RejectedByClip = 0; g_TrianglesProcessed = 0; g_DupTrianglesProcessed = 0; if (g_DupPrimitiveCheckCount == 0xffff) { g_DupVertexList.fill(0); g_DupEdgeList.fill(0); g_DupTriangleList.fill(0); g_DupPrimitiveCheckCount += 1; } g_DupPrimitiveCheckCount += 1; } void CAreaCollisionCache::ClearCache() { x18_leafCaches.clear(); x1b40_24_leafOverflow = false; x1b40_25_cacheOverflow = false; } void CAreaCollisionCache::AddOctreeLeafCache(const CMetroidAreaCollider::COctreeLeafCache& leafCache) { if (!leafCache.GetNumLeaves()) return; if (leafCache.HasCacheOverflowed()) x1b40_24_leafOverflow = true; if (x18_leafCaches.size() < 3) { x18_leafCaches.push_back(leafCache); } else { x1b40_24_leafOverflow = true; x1b40_25_cacheOverflow = true; } } } // namespace metaforce ================================================ FILE: Runtime/Collision/CMetroidAreaCollider.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Collision/CAreaOctTree.hpp" #include #include #include #include namespace metaforce { class CCollisionInfo; class CCollisionInfoList; class CMaterialList; class CAABoxAreaCache { friend class CMetroidAreaCollider; const zeus::CAABox& x0_aabb; const std::array& x4_planes; const CMaterialFilter& x8_filter; const CMaterialList& xc_material; CCollisionInfoList& x10_collisionList; zeus::CVector3f x14_center; zeus::CVector3f x20_halfExtent; public: CAABoxAreaCache(const zeus::CAABox& aabb, const std::array& pl, const CMaterialFilter& filter, const CMaterialList& material, CCollisionInfoList& collisionList); }; class CBooleanAABoxAreaCache { friend class CMetroidAreaCollider; const zeus::CAABox& x0_aabb; const CMaterialFilter& x4_filter; zeus::CVector3f x8_center; zeus::CVector3f x14_halfExtent; public: CBooleanAABoxAreaCache(const zeus::CAABox& aabb, const CMaterialFilter& filter); }; class CSphereAreaCache { friend class CMetroidAreaCollider; const zeus::CAABox& x0_aabb; const zeus::CSphere& x4_sphere; const CMaterialFilter& x8_filter; const CMaterialList& xc_material; CCollisionInfoList& x10_collisionList; public: CSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter, const CMaterialList& material, CCollisionInfoList& collisionList); }; class CBooleanSphereAreaCache { friend class CMetroidAreaCollider; const zeus::CAABox& x0_aabb; const zeus::CSphere& x4_sphere; const CMaterialFilter& x8_filter; public: CBooleanSphereAreaCache(const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter); }; struct SBoxEdge { zeus::CLineSeg x0_seg; zeus::CVector3d x28_start; zeus::CVector3d x40_end; zeus::CVector3d x58_delta; zeus::CVector3d x70_coDir; double x88_dirCoDirDot; SBoxEdge(const zeus::CAABox& aabb, int idx, const zeus::CVector3f& dir); }; class CMovingAABoxComponents { friend class CMetroidAreaCollider; friend class CCollidableOBBTree; rstl::reserved_vector x0_edges; rstl::reserved_vector x6c4_vertIdxs; zeus::CAABox x6e8_aabb; public: CMovingAABoxComponents(const zeus::CAABox& aabb, const zeus::CVector3f& dir); }; class CMetroidAreaCollider { friend class CCollidableOBBTree; static u32 g_CalledClip; static u32 g_RejectedByClip; static u32 g_TrianglesProcessed; static u32 g_DupTrianglesProcessed; static u16 g_DupPrimitiveCheckCount; static std::array g_DupVertexList; static std::array g_DupEdgeList; static std::array g_DupTriangleList; static bool AABoxCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node, const CBooleanAABoxAreaCache& cache); static bool AABoxCollisionCheck_Internal(const CAreaOctTree::Node& node, const CAABoxAreaCache& cache); static bool SphereCollisionCheckBoolean_Internal(const CAreaOctTree::Node& node, const CBooleanSphereAreaCache& cache); static bool SphereCollisionCheck_Internal(const CAreaOctTree::Node& node, const CSphereAreaCache& cache); static bool MovingAABoxCollisionCheck_BoxVertexTri(const CCollisionSurface& surf, const zeus::CAABox& aabb, const rstl::reserved_vector& vertIndices, const zeus::CVector3f& dir, double& d, zeus::CVector3f& normal, zeus::CVector3f& point); static bool MovingAABoxCollisionCheck_TriVertexBox(const zeus::CVector3f& vert, const zeus::CAABox& aabb, const zeus::CVector3f& dir, double& d, zeus::CVector3f& normal, zeus::CVector3f& point); static bool MovingAABoxCollisionCheck_Edge(const zeus::CVector3f& ev0, const zeus::CVector3f& ev1, const rstl::reserved_vector& edges, const zeus::CVector3f& dir, double& d, zeus::CVector3f& normal, zeus::CVector3f& point); public: class COctreeLeafCache { friend class CMetroidAreaCollider; const CAreaOctTree& x0_octTree; rstl::reserved_vector x4_nodeCache; bool x908_24_overflow : 1 = false; public: explicit COctreeLeafCache(const CAreaOctTree& octTree); void AddLeaf(const CAreaOctTree::Node& node); u32 GetNumLeaves() const { return x4_nodeCache.size(); } bool HasCacheOverflowed() const { return x908_24_overflow; } const CAreaOctTree& GetOctTree() const { return x0_octTree; } rstl::reserved_vector::const_iterator begin() const { return x4_nodeCache.begin(); } rstl::reserved_vector::const_iterator end() const { return x4_nodeCache.end(); } }; static void BuildOctreeLeafCache(const CAreaOctTree::Node& root, const zeus::CAABox& aabb, CMetroidAreaCollider::COctreeLeafCache& cache); static bool ConvexPolyCollision(const std::array& planes, const std::array& verts, zeus::CAABox& aabb); static bool AABoxCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const CMaterialFilter& filter); static bool AABoxCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const CMaterialFilter& filter); static bool SphereCollisionCheckBoolean_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter); static bool SphereCollisionCheckBoolean(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter); static bool AABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CMaterialList& matList, CCollisionInfoList& list); static bool AABoxCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CMaterialList& matList, CCollisionInfoList& list); static bool SphereCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialList& matList, const CMaterialFilter& filter, CCollisionInfoList& list); static bool SphereCollisionCheck(const CAreaOctTree& octTree, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialList& matList, const CMaterialFilter& filter, CCollisionInfoList& list); static bool MovingAABoxCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const CMaterialFilter& filter, const CMaterialList& matList, const zeus::CVector3f& dir, float mag, CCollisionInfo& infoOut, double& dOut); static bool MovingSphereCollisionCheck_Cached(const COctreeLeafCache& leafCache, const zeus::CAABox& aabb, const zeus::CSphere& sphere, const CMaterialFilter& filter, const CMaterialList& matList, const zeus::CVector3f& dir, float mag, CCollisionInfo& infoOut, double& dOut); static void ResetInternalCounters(); static std::array& GetTriangleList() { return g_DupTriangleList; } static u16 GetPrimitiveCheckCount() { return g_DupPrimitiveCheckCount; } }; class CAreaCollisionCache { zeus::CAABox x0_aabb; rstl::reserved_vector x18_leafCaches; bool x1b40_24_leafOverflow : 1 = false; bool x1b40_25_cacheOverflow : 1 = false; public: explicit CAreaCollisionCache(const zeus::CAABox& aabb) : x0_aabb(aabb) {} void ClearCache(); const zeus::CAABox& GetCacheBounds() const { return x0_aabb; } void SetCacheBounds(const zeus::CAABox& aabb) { x0_aabb = aabb; } void AddOctreeLeafCache(const CMetroidAreaCollider::COctreeLeafCache& leafCache); u32 GetNumCaches() const { return x18_leafCaches.size(); } const CMetroidAreaCollider::COctreeLeafCache& GetOctreeLeafCache(int idx) { return x18_leafCaches[idx]; } bool HasCacheOverflowed() const { return x1b40_24_leafOverflow; } rstl::reserved_vector::const_iterator begin() const { return x18_leafCaches.begin(); } rstl::reserved_vector::const_iterator end() const { return x18_leafCaches.end(); } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/COBBTree.cpp ================================================ #include "Runtime/Collision/COBBTree.hpp" #include #include "Runtime/Collision/CCollidableOBBTreeGroup.hpp" namespace metaforce { namespace { constexpr std::array DefaultEdgeMaterials{ 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 2, 2, }; constexpr std::array DefaultSurfaceMaterials{ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, }; constexpr std::array DefaultEdges{{ {4, 1}, {1, 5}, {5, 4}, {4, 0}, {0, 1}, {7, 2}, {2, 6}, {6, 7}, {7, 3}, {3, 2}, {6, 0}, {4, 6}, {2, 0}, {5, 3}, {7, 5}, {1, 3}, {6, 5}, {0, 3}, }}; constexpr std::array DefaultSurfaceIndices{ 0, 1, 2, 0, 3, 4, 5, 6, 7, 5, 8, 9, 10, 3, 11, 10, 6, 12, 13, 8, 14, 13, 1, 15, 16, 14, 7, 16, 11, 2, 17, 15, 4, 17, 12, 9, }; /* This is exactly what retro did >.< */ u32 verify_deaf_babe(CInputStream& in) { return in.ReadLong(); } /* This is exactly what retro did >.< */ u32 verify_version(CInputStream& in) { return in.ReadLong(); } } // Anonymous namespace COBBTree::COBBTree(CInputStream& in) : x0_magic(verify_deaf_babe(in)) , x4_version(verify_version(in)) , x8_memsize(in.ReadLong()) , x18_indexData(in) , x88_root(std::make_unique(in)) {} std::unique_ptr COBBTree::BuildOrientedBoundingBoxTree(const zeus::CVector3f& extent, const zeus::CVector3f& center) { zeus::CAABox aabb(extent * -0.5f + center, extent * 0.5f + center); std::unique_ptr ret = std::make_unique(); COBBTree::SIndexData& idxData = ret->x18_indexData; idxData.x0_materials.reserve(3); idxData.x0_materials.push_back(0x40180000); idxData.x0_materials.push_back(0x42180000); idxData.x0_materials.push_back(0x41180000); idxData.x10_vertMaterials = std::vector(8, u8(0)); idxData.x20_edgeMaterials = std::vector(DefaultEdgeMaterials.cbegin(), DefaultEdgeMaterials.cend()); idxData.x30_surfaceMaterials = std::vector(DefaultSurfaceMaterials.cbegin(), DefaultSurfaceMaterials.cend()); idxData.x40_edges = std::vector(DefaultEdges.cbegin(), DefaultEdges.cend()); idxData.x50_surfaceIndices = std::vector(DefaultSurfaceIndices.cbegin(), DefaultSurfaceIndices.cend()); idxData.x60_vertices.reserve(8); for (int i = 0; i < 8; ++i) { idxData.x60_vertices.push_back(aabb.getPoint(i)); } std::vector surface; surface.reserve(12); for (int i = 0; i < 12; ++i) { surface.push_back(i); } ret->x88_root = std::make_unique(zeus::CTransform::Translate(center), extent * 0.5f, nullptr, nullptr, std::make_unique(std::move(surface))); return ret; } CCollisionSurface COBBTree::GetSurface(u16 idx) const { const auto surfIdx = size_t{idx} * 3; const CCollisionEdge e0 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx]]; const CCollisionEdge e1 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx + 1]]; const u16 vert1 = e0.GetVertIndex1(); const u16 vert2 = e0.GetVertIndex2(); u16 vert3 = e1.GetVertIndex1(); if (vert3 == vert1 || vert3 == vert2) { vert3 = e1.GetVertIndex2(); } const u32 mat = x18_indexData.x0_materials[x18_indexData.x30_surfaceMaterials[idx]]; if ((mat & 0x2000000) != 0) { return CCollisionSurface(x18_indexData.x60_vertices[vert2], x18_indexData.x60_vertices[vert1], x18_indexData.x60_vertices[vert3], mat); } return CCollisionSurface(x18_indexData.x60_vertices[vert1], x18_indexData.x60_vertices[vert2], x18_indexData.x60_vertices[vert3], mat); } std::array COBBTree::GetTriangleVertexIndices(u16 idx) const { const auto surfIdx = size_t{idx} * 3; const CCollisionEdge& e0 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx]]; const CCollisionEdge& e1 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx + 1]]; std::array indices{}; indices[2] = (e1.GetVertIndex1() != e0.GetVertIndex1() && e1.GetVertIndex1() != e0.GetVertIndex2()) ? e1.GetVertIndex1() : e1.GetVertIndex2(); const u32 material = x18_indexData.x0_materials[x18_indexData.x30_surfaceMaterials[idx]]; if ((material & 0x2000000) != 0) { indices[0] = e0.GetVertIndex2(); indices[1] = e0.GetVertIndex1(); } else { indices[0] = e0.GetVertIndex1(); indices[1] = e0.GetVertIndex2(); } return indices; } CCollisionSurface COBBTree::GetTransformedSurface(u16 idx, const zeus::CTransform& xf) const { const auto surfIdx = size_t{idx} * 3; const CCollisionEdge e0 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx]]; const CCollisionEdge e1 = x18_indexData.x40_edges[x18_indexData.x50_surfaceIndices[surfIdx + 1]]; const u16 vert1 = e0.GetVertIndex1(); const u16 vert2 = e0.GetVertIndex2(); u16 vert3 = e1.GetVertIndex1(); if (vert3 == vert1 || vert3 == vert2) { vert3 = e1.GetVertIndex2(); } const u32 mat = x18_indexData.x0_materials[x18_indexData.x30_surfaceMaterials[idx]]; if ((mat & 0x2000000) != 0) { return CCollisionSurface(xf * x18_indexData.x60_vertices[vert2], xf * x18_indexData.x60_vertices[vert1], xf * x18_indexData.x60_vertices[vert3], mat); } return CCollisionSurface(xf * x18_indexData.x60_vertices[vert1], xf * x18_indexData.x60_vertices[vert2], xf * x18_indexData.x60_vertices[vert3], mat); } zeus::CAABox COBBTree::CalculateLocalAABox() const { return CalculateAABox(zeus::CTransform()); } zeus::CAABox COBBTree::CalculateAABox(const zeus::CTransform& xf) const { if (x88_root) { return x88_root->GetOBB().calculateAABox(xf); } return zeus::CAABox(); } COBBTree::SIndexData::SIndexData(CInputStream& in) { u32 count = in.ReadLong(); x0_materials.reserve(count); for (u32 i = 0; i < count; i++) { x0_materials.emplace_back(in.ReadLong()); } count = in.ReadLong(); for (u32 i = 0; i < count; i++) { x10_vertMaterials.emplace_back(in.ReadUint8()); } count = in.ReadLong(); for (u32 i = 0; i < count; i++) { x20_edgeMaterials.emplace_back(in.ReadUint8()); } count = in.ReadLong(); for (u32 i = 0; i < count; i++) { x30_surfaceMaterials.emplace_back(in.ReadUint8()); } count = in.ReadLong(); for (u32 i = 0; i < count; i++) { x40_edges.emplace_back(in); } count = in.ReadLong(); for (u32 i = 0; i < count; i++) { x50_surfaceIndices.emplace_back(in.ReadShort()); } count = in.ReadLong(); for (u32 i = 0; i < count; i++) { x60_vertices.emplace_back(in.Get()); } } COBBTree::CNode::CNode(const zeus::CTransform& xf, const zeus::CVector3f& point, std::unique_ptr&& left, std::unique_ptr&& right, std::unique_ptr&& leaf) : x0_obb(xf, point) , x3c_isLeaf(leaf != nullptr) , x40_left(std::move(left)) , x44_right(std::move(right)) , x48_leaf(std::move(leaf)) {} COBBTree::CNode::CNode(CInputStream& in) { x0_obb = in.Get(); x3c_isLeaf = in.ReadBool(); if (x3c_isLeaf) x48_leaf = std::make_unique(in); else { x40_left = std::make_unique(in); x44_right = std::make_unique(in); } } size_t COBBTree::CNode::GetMemoryUsage() const { size_t ret = 0; if (x3c_isLeaf) ret = x48_leaf->GetMemoryUsage() + /*sizeof(CNode)*/ 80; else { if (x40_left) ret = x40_left->GetMemoryUsage() + /*sizeof(CNode)*/ 80; if (x44_right) ret += x44_right->GetMemoryUsage(); } return (ret + 3) & ~3; } COBBTree::CLeafData::CLeafData(std::vector&& surface) : x0_surface(std::move(surface)) {} const std::vector& COBBTree::CLeafData::GetSurfaceVector() const { return x0_surface; } size_t COBBTree::CLeafData::GetMemoryUsage() const { const size_t ret = (x0_surface.size() * 2) + /*sizeof(CLeafData)*/ 16; return (ret + 3) & ~3; } COBBTree::CLeafData::CLeafData(CInputStream& in) { const u32 edgeCount = in.ReadLong(); for (u32 i = 0; i < edgeCount; i++) { x0_surface.emplace_back(in.ReadShort()); } } } // namespace metaforce ================================================ FILE: Runtime/Collision/COBBTree.hpp ================================================ #pragma once #include #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Collision/CCollisionEdge.hpp" #include "Runtime/Collision/CCollisionSurface.hpp" #include #include namespace metaforce { class CCollidableOBBTreeGroupContainer; class COBBTree { public: struct SIndexData { std::vector x0_materials; std::vector x10_vertMaterials; std::vector x20_edgeMaterials; std::vector x30_surfaceMaterials; std::vector x40_edges; std::vector x50_surfaceIndices; std::vector x60_vertices; SIndexData() = default; explicit SIndexData(CInputStream&); }; class CLeafData { std::vector x0_surface; public: CLeafData() = default; explicit CLeafData(std::vector&& surface); explicit CLeafData(CInputStream&); const std::vector& GetSurfaceVector() const; size_t GetMemoryUsage() const; }; class CNode { zeus::COBBox x0_obb; bool x3c_isLeaf = false; std::unique_ptr x40_left; std::unique_ptr x44_right; std::unique_ptr x48_leaf; bool x4c_hit = false; public: CNode() = default; CNode(const zeus::CTransform&, const zeus::CVector3f&, std::unique_ptr&&, std::unique_ptr&&, std::unique_ptr&&); explicit CNode(CInputStream&); bool WasHit() const { return x4c_hit; } void SetHit(bool h) { x4c_hit = h; } const CNode& GetLeft() const { return *x40_left; } const CNode& GetRight() const { return *x44_right; } const CLeafData& GetLeafData() const { return *x48_leaf; } const zeus::COBBox& GetOBB() const { return x0_obb; } size_t GetMemoryUsage() const; bool IsLeaf() const { return x3c_isLeaf; } }; private: u32 x0_magic = 0; u32 x4_version = 0; u32 x8_memsize = 0; /* CSimpleAllocator xc_ We're not using this but lets keep track*/ SIndexData x18_indexData; std::unique_ptr x88_root; public: COBBTree() = default; explicit COBBTree(CInputStream&); static std::unique_ptr BuildOrientedBoundingBoxTree(const zeus::CVector3f&, const zeus::CVector3f&); CCollisionSurface GetSurface(u16 idx) const; const u16* GetTriangleEdgeIndices(u16 idx) const { return &x18_indexData.x50_surfaceIndices[idx * 3]; } // In the game binary, this used to use an out pointer for the indices after the index. std::array GetTriangleVertexIndices(u16 idx) const; const CCollisionEdge& GetEdge(int idx) const { return x18_indexData.x40_edges[idx]; } const zeus::CVector3f& GetVert(int idx) const { return x18_indexData.x60_vertices[idx]; } u32 GetVertMaterial(u16 idx) const { return x18_indexData.x0_materials[x18_indexData.x10_vertMaterials[idx]]; } u32 GetEdgeMaterial(u16 idx) const { return x18_indexData.x0_materials[x18_indexData.x20_edgeMaterials[idx]]; } CCollisionSurface GetTransformedSurface(u16 idx, const zeus::CTransform& xf) const; zeus::CAABox CalculateLocalAABox() const; zeus::CAABox CalculateAABox(const zeus::CTransform&) const; const CNode& GetRoot() const { return *x88_root; } u32 NumSurfaceMaterials() const { return x18_indexData.x30_surfaceMaterials.size(); } }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CRayCastResult.cpp ================================================ #include "Runtime/Collision/CRayCastResult.hpp" namespace metaforce { void CRayCastResult::MakeInvalid() { /* NOTE: CRayCastResult: Enable this if it's required, this is a total guess - Phil */ #if 0 x0_time = 0.f; x4_point.zeroOut(); x10_plane.vec.zeroOut();; x10_plane.d = 0.f; x28_material = CMaterialList(); #endif x20_invalid = EInvalid::Invalid; } void CRayCastResult::Transform(const zeus::CTransform& xf) { x4_point = xf * x4_point; x10_plane = zeus::CPlane(xf.rotate(x10_plane.normal()), x10_plane.normal().dot(x4_point)); } } // namespace metaforce ================================================ FILE: Runtime/Collision/CRayCastResult.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include "Runtime/Collision/CMaterialList.hpp" #include #include #include namespace metaforce { class CRayCastResult { public: enum class EInvalid : u8 { Invalid, Valid }; private: float x0_t = 0.f; zeus::CVector3f x4_point; zeus::CPlane x10_plane; EInvalid x20_invalid = EInvalid::Invalid; CMaterialList x28_material; public: CRayCastResult() = default; CRayCastResult(const CRayCastResult& other, EInvalid invalid) : x0_t(other.x0_t) , x4_point(other.x4_point) , x10_plane(other.x10_plane) , x20_invalid(invalid) , x28_material(other.x28_material) {} CRayCastResult(float t, const zeus::CVector3f& point, const zeus::CPlane& plane, const CMaterialList& matList) : x0_t(t), x4_point(point), x10_plane(plane), x20_invalid(EInvalid::Valid), x28_material(matList) {} void MakeInvalid(); bool IsInvalid() const { return x20_invalid == EInvalid::Invalid; } bool IsValid() const { return x20_invalid == EInvalid::Valid; } float GetT() const { return x0_t; } const zeus::CVector3f& GetPoint() const { return x4_point; } const zeus::CPlane& GetPlane() const { return x10_plane; } const CMaterialList& GetMaterial() const { return x28_material; } void Transform(const zeus::CTransform&); }; } // namespace metaforce ================================================ FILE: Runtime/Collision/CollisionUtil.cpp ================================================ #include "Runtime/Collision/CollisionUtil.hpp" #include #include #include #include "Runtime/Collision/CCollisionInfo.hpp" #include "Runtime/Collision/CCollisionInfoList.hpp" #include namespace metaforce::CollisionUtil { bool LineIntersectsOBBox(const zeus::COBBox& obb, const zeus::CMRay& ray, float& d) { zeus::CVector3f norm; return RayAABoxIntersection(ray.getInvUnscaledTransformRay(obb.transform), {-obb.extents, obb.extents}, norm, d); } u32 RayAABoxIntersection(const zeus::CMRay& ray, const zeus::CAABox& aabb, float& tMin, float& tMax) { tMin = -999999.f; tMax = 999999.f; for (size_t i = 0; i < 3; ++i) { if (std::fabs(ray.dir[i]) < 0.00001f) { if (ray.start[i] < aabb.min[i] || ray.start[i] > aabb.max[i]) { return 0; } } else { if (ray.dir[i] < 0.f) { const float startToMax = aabb.max[i] - ray.start[i]; const float startToMin = aabb.min[i] - ray.start[i]; const float dirRecip = 1.f / ray.dir[i]; if (startToMax < tMin * ray.dir[i]) { tMin = startToMax * dirRecip; } if (startToMin > tMax * ray.dir[i]) { tMax = startToMin * dirRecip; } } else { const float startToMin = aabb.min[i] - ray.start[i]; const float startToMax = aabb.max[i] - ray.start[i]; const float dirRecip = 1.f / ray.dir[i]; if (startToMin > tMin * ray.dir[i]) { tMin = startToMin * dirRecip; } if (startToMax < tMax * ray.dir[i]) { tMax = startToMax * dirRecip; } } } } return tMin <= tMax ? 2 : 0; } u32 RayAABoxIntersection(const zeus::CMRay& ray, const zeus::CAABox& aabb, zeus::CVector3f& norm, float& d) { std::array sign{2, 2, 2}; bool bad = true; zeus::CVector3f rayStart = ray.start; zeus::CVector3f rayDelta = ray.delta; zeus::CVector3f aabbMin = aabb.min; zeus::CVector3f aabbMax = aabb.max; zeus::CVector3f vec0 = {-1.f, -1.f, -1.f}; zeus::CVector3f vec1; if (rayDelta.x() != 0.f && rayDelta.y() != 0.f && rayDelta.z() != 0.f) { for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; vec0[i] = (aabbMin[i] - rayStart[i]) / rayDelta[i]; } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; vec0[i] = (aabbMax[i] - rayStart[i]) / rayDelta[i]; } } if (bad) { d = 0.f; return 1; } } else { zeus::CVector3f end; for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; end[i] = float(aabbMin[i]); } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; end[i] = float(aabbMax[i]); } } if (bad) { d = 0.f; return 1; } for (size_t i = 0; i < sign.size(); ++i) { if (sign[i] != 2 && rayDelta[i] != 0.f) { vec0[i] = (end[i] - rayStart[i]) / rayDelta[i]; } } } float maxComp = vec0.x(); size_t maxCompIdx = 0; if (maxComp < vec0.y()) { maxComp = vec0.y(); maxCompIdx = 1; } if (maxComp < vec0.z()) { maxComp = vec0.z(); maxCompIdx = 2; } if (maxComp < 0.f || maxComp > 1.f) return 0; for (size_t i = 0; i < 3; ++i) { if (maxCompIdx != i) { vec1[i] = maxComp * rayDelta[i] + rayStart[i]; if (vec1[i] > aabbMax[i]) { return 0; } } } d = maxComp; norm = zeus::skZero3f; norm[maxCompIdx] = (sign[maxCompIdx] == 1) ? -1.f : 1.f; return 2; } u32 RayAABoxIntersection_Double(const zeus::CMRay& ray, const zeus::CAABox& aabb, zeus::CVector3f& norm, double& d) { std::array sign{2, 2, 2}; bool bad = true; zeus::CVector3d rayStart = ray.start; zeus::CVector3d rayDelta = ray.delta; zeus::CVector3d aabbMin = aabb.min; zeus::CVector3d aabbMax = aabb.max; zeus::CVector3d vec0 = {-1.0, -1.0, -1.0}; zeus::CVector3d vec1; if (rayDelta.x() != 0.0 && rayDelta.y() != 0.0 && rayDelta.z() != 0.0) { for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; vec0[i] = (aabbMin[i] - rayStart[i]) / rayDelta[i]; } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; vec0[i] = (aabbMax[i] - rayStart[i]) / rayDelta[i]; } } if (bad) { d = 0.0; return 1; } } else { zeus::CVector3d end; for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; end[i] = double(aabbMin[i]); } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; end[i] = double(aabbMax[i]); } } if (bad) { d = 0.0; return 1; } for (size_t i = 0; i < sign.size(); ++i) { if (sign[i] != 2 && rayDelta[i] != 0.0) { vec0[i] = (end[i] - rayStart[i]) / rayDelta[i]; } } } double maxComp = vec0.x(); size_t maxCompIdx = 0; if (maxComp < vec0.y()) { maxComp = vec0.y(); maxCompIdx = 1; } if (maxComp < vec0.z()) { maxComp = vec0.z(); maxCompIdx = 2; } if (maxComp < 0.0 || maxComp > 1.0) return 0; for (size_t i = 0; i < 3; ++i) { if (maxCompIdx != i) { vec1[i] = maxComp * rayDelta[i] + rayStart[i]; if (vec1[i] > aabbMax[i]) { return 0; } } } d = maxComp; norm = zeus::skZero3f; norm[maxCompIdx] = (sign[maxCompIdx] == 1) ? -1.0 : 1.0; return 2; } bool RaySphereIntersection_Double(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir, double& T) { const zeus::CVector3d sPosD = sphere.position; const zeus::CVector3d posD = pos; const zeus::CVector3d sphereToPos = posD - sPosD; const double f30 = sphereToPos.dot(zeus::CVector3d(dir)) * 2.0; const double f1 = f30 * f30 - 4.0 * (sphereToPos.magSquared() - sphere.radius * sphere.radius); if (f1 >= 0.0) { const double intersectT = 0.5 * (-f30 - std::sqrt(f1)); if (T == 0 || intersectT < T) { T = intersectT; return true; } } return false; } bool RaySphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, float& T, zeus::CVector3f& point) { const zeus::CVector3f rayToSphere = sphere.position - pos; const float magSq = rayToSphere.magSquared(); const float dirDot = rayToSphere.dot(dir); const float radSq = sphere.radius * sphere.radius; if (dirDot < 0.f && magSq > radSq) { return false; } const float intersectSq = radSq - (magSq - dirDot * dirDot); if (intersectSq < 0.f) { return false; } T = magSq > radSq ? dirDot - std::sqrt(intersectSq) : dirDot + std::sqrt(intersectSq); if (T < mag || mag == 0.f) { point = pos + T * dir; return true; } return false; } bool RayTriangleIntersection_Double(const zeus::CVector3f& point, const zeus::CVector3f& dir, const std::array& verts, double& d) { const zeus::CVector3d v0tov1 = verts[1] - verts[0]; const zeus::CVector3d v0tov2 = verts[2] - verts[0]; const zeus::CVector3d cross0 = zeus::CVector3d(dir).cross(v0tov2); const double dot0 = v0tov1.dot(cross0); if (dot0 < DBL_EPSILON) { return false; } const zeus::CVector3d v0toPoint = point - verts[0]; const double dot1 = v0toPoint.dot(cross0); if (dot1 < 0.0 || dot1 > dot0) { return false; } const zeus::CVector3d cross1 = v0toPoint.cross(v0tov1); const double dot2 = cross1.dot(dir); if (dot2 < 0.0 || dot1 + dot2 > dot0) { return false; } const double final = 1.0 / dot0 * cross1.dot(v0tov2); if (final < 0.0 || final >= d) { return false; } d = final; return true; } bool RayTriangleIntersection(const zeus::CVector3f& point, const zeus::CVector3f& dir, const std::array& verts, float& d) { const zeus::CVector3f v0tov1 = verts[1] - verts[0]; const zeus::CVector3f v0tov2 = verts[2] - verts[0]; const zeus::CVector3f cross0 = dir.cross(v0tov2); const float dot0 = v0tov1.dot(cross0); if (dot0 < DBL_EPSILON) { return false; } const zeus::CVector3f v0toPoint = point - verts[0]; const float dot1 = v0toPoint.dot(cross0); if (dot1 < 0.f || dot1 > dot0) { return false; } const zeus::CVector3f cross1 = v0toPoint.cross(v0tov1); const float dot2 = cross1.dot(dir); if (dot2 < 0.f || dot1 + dot2 > dot0) { return false; } const float final = 1.f / dot0 * cross1.dot(v0tov2); if (final < 0.f || final >= d) { return false; } d = final; return true; } void FilterOutBackfaces(const zeus::CVector3f& vec, const CCollisionInfoList& in, CCollisionInfoList& out) { if (vec.canBeNormalized()) { const zeus::CVector3f norm = vec.normalized(); for (const CCollisionInfo& info : in) { if (info.GetNormalLeft().dot(norm) < 0.001f) { out.Add(info, false); } } } else { out = in; } } void FilterByClosestNormal(const zeus::CVector3f& norm, const CCollisionInfoList& in, CCollisionInfoList& out) { float maxDot = -1.1f; int idx = -1; int i = 0; for (const CCollisionInfo& info : in) { const float dot = info.GetNormalLeft().dot(norm); if (dot > maxDot) { maxDot = dot; idx = i; } ++i; } if (idx != -1) { out.Add(in.GetItem(idx), false); } } constexpr std::array AABBNormalTable{{ {-1.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, -1.f}, {0.f, 0.f, 1.f}, }}; bool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const CMaterialList& list0, const zeus::CAABox& aabb1, const CMaterialList& list1, CCollisionInfoList& infoList) { const zeus::CVector3f maxOfMin(std::max(aabb0.min.x(), aabb1.min.x()), std::max(aabb0.min.y(), aabb1.min.y()), std::max(aabb0.min.z(), aabb1.min.z())); const zeus::CVector3f minOfMax(std::min(aabb0.max.x(), aabb1.max.x()), std::min(aabb0.max.y(), aabb1.max.y()), std::min(aabb0.max.z(), aabb1.max.z())); if (maxOfMin.x() >= minOfMax.x() || maxOfMin.y() >= minOfMax.y() || maxOfMin.z() >= minOfMax.z()) { return false; } const zeus::CAABox boolAABB(maxOfMin, minOfMax); const std::array ineqFlags{ (aabb0.min.x() <= aabb1.min.x() ? 1 << 0 : 0) | (aabb0.min.x() <= aabb1.max.x() ? 1 << 1 : 0) | (aabb0.max.x() <= aabb1.min.x() ? 1 << 2 : 0) | (aabb0.max.x() <= aabb1.max.x() ? 1 << 3 : 0), (aabb0.min.y() <= aabb1.min.y() ? 1 << 0 : 0) | (aabb0.min.y() <= aabb1.max.y() ? 1 << 1 : 0) | (aabb0.max.y() <= aabb1.min.y() ? 1 << 2 : 0) | (aabb0.max.y() <= aabb1.max.y() ? 1 << 3 : 0), (aabb0.min.z() <= aabb1.min.z() ? 1 << 0 : 0) | (aabb0.min.z() <= aabb1.max.z() ? 1 << 1 : 0) | (aabb0.max.z() <= aabb1.min.z() ? 1 << 2 : 0) | (aabb0.max.z() <= aabb1.max.z() ? 1 << 3 : 0), }; for (size_t i = 0; i < ineqFlags.size(); ++i) { switch (ineqFlags[i]) { case 0x2: // aabb0.min <= aabb1.max { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[i * 2 + 1], -AABBNormalTable[i * 2 + 1]); infoList.Add(info, false); break; } case 0xB: // aabb0.min <= aabb1.min && aabb0.max <= aabb1.min && aabb0.max <= aabb1.max { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[i * 2], -AABBNormalTable[i * 2]); infoList.Add(info, false); break; } default: break; } } if (infoList.GetCount() != 0) { return true; } { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[4], -AABBNormalTable[4]); infoList.Add(info, false); } { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[5], -AABBNormalTable[5]); infoList.Add(info, false); } return true; } bool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1) { return aabb0.intersects(aabb1); } /* http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/tribox2.txt */ /********************************************************/ /* AABB-triangle overlap test code */ /* by Tomas Akenine-Möller */ /* Function: int triBoxOverlap(float boxcenter[3], */ /* float boxhalfsize[3],float triverts[3][3]); */ /* History: */ /* 2001-03-05: released the code in its first version */ /* 2001-06-18: changed the order of the tests, faster */ /* */ /* Acknowledgement: Many thanks to Pierre Terdiman for */ /* suggestions and discussions on how to optimize code. */ /* Thanks to David Hunt for finding a ">="-bug! */ /********************************************************/ static bool planeBoxOverlap(const zeus::CVector3f& normal, float d, const zeus::CVector3f& maxbox) { zeus::CVector3f vmin; zeus::CVector3f vmax; for (int q = 0; q <= 2; q++) { if (normal[q] > 0.0f) { vmin[q] = -maxbox[q]; vmax[q] = maxbox[q]; } else { vmin[q] = maxbox[q]; vmax[q] = -maxbox[q]; } } if (normal.dot(vmin) + d > 0.0f) { return false; } if (normal.dot(vmax) + d >= 0.0f) { return true; } return false; } /*======================== X-tests ========================*/ #define AXISTEST_X01(a, b, fa, fb) \ do { \ p0 = a * v0.y() - b * v0.z(); \ p2 = a * v2.y() - b * v2.z(); \ if (p0 < p2) { \ min = p0; \ max = p2; \ } else { \ min = p2; \ max = p0; \ } \ rad = fa * boxhalfsize.y() + fb * boxhalfsize.z(); \ if (min > rad || max < -rad) \ return false; \ } while (false) #define AXISTEST_X2(a, b, fa, fb) \ do { \ p0 = a * v0.y() - b * v0.z(); \ p1 = a * v1.y() - b * v1.z(); \ if (p0 < p1) { \ min = p0; \ max = p1; \ } else { \ min = p1; \ max = p0; \ } \ rad = fa * boxhalfsize.y() + fb * boxhalfsize.z(); \ if (min > rad || max < -rad) \ return false; \ } while (false) /*======================== Y-tests ========================*/ #define AXISTEST_Y02(a, b, fa, fb) \ do { \ p0 = -a * v0.x() + b * v0.z(); \ p2 = -a * v2.x() + b * v2.z(); \ if (p0 < p2) { \ min = p0; \ max = p2; \ } else { \ min = p2; \ max = p0; \ } \ rad = fa * boxhalfsize.x() + fb * boxhalfsize.z(); \ if (min > rad || max < -rad) \ return false; \ } while (false) #define AXISTEST_Y1(a, b, fa, fb) \ do { \ p0 = -a * v0.x() + b * v0.z(); \ p1 = -a * v1.x() + b * v1.z(); \ if (p0 < p1) { \ min = p0; \ max = p1; \ } else { \ min = p1; \ max = p0; \ } \ rad = fa * boxhalfsize.x() + fb * boxhalfsize.z(); \ if (min > rad || max < -rad) \ return false; \ } while (false) /*======================== Z-tests ========================*/ #define AXISTEST_Z12(a, b, fa, fb) \ do { \ p1 = a * v1.x() - b * v1.y(); \ p2 = a * v2.x() - b * v2.y(); \ if (p2 < p1) { \ min = p2; \ max = p1; \ } else { \ min = p1; \ max = p2; \ } \ rad = fa * boxhalfsize.x() + fb * boxhalfsize.y(); \ if (min > rad || max < -rad) \ return false; \ } while (false) #define AXISTEST_Z0(a, b, fa, fb) \ do { \ p0 = a * v0.x() - b * v0.y(); \ p1 = a * v1.x() - b * v1.y(); \ if (p0 < p1) { \ min = p0; \ max = p1; \ } else { \ min = p1; \ max = p0; \ } \ rad = fa * boxhalfsize.x() + fb * boxhalfsize.y(); \ if (min > rad || max < -rad) \ return false; \ } while (false) bool TriBoxOverlap(const zeus::CVector3f& boxcenter, const zeus::CVector3f& boxhalfsize, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2) { /* use separating axis theorem to test overlap between triangle and box */ /* need to test for overlap in these directions: */ /* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */ /* we do not even need to test these) */ /* 2) normal of the triangle */ /* 3) crossproduct(edge from tri, {x,y,z}-directin) */ /* this gives 3x3=9 more tests */ float min, max, d, p0, p1, p2, rad, fex, fey, fez; /* This is the fastest branch on Sun */ /* move everything so that the boxcenter is in (0,0,0) */ const zeus::CVector3f v0 = trivert0 - boxcenter; const zeus::CVector3f v1 = trivert1 - boxcenter; const zeus::CVector3f v2 = trivert2 - boxcenter; /* compute triangle edges */ const zeus::CVector3f e0 = v1 - v0; // Tri edge 0 const zeus::CVector3f e1 = v2 - v1; // Tri edge 1 const zeus::CVector3f e2 = v0 - v2; // Tri edge 2 /* Bullet 3: */ /* test the 9 tests first (this was faster) */ fex = std::fabs(e0.x()); fey = std::fabs(e0.y()); fez = std::fabs(e0.z()); AXISTEST_X01(e0.z(), e0.y(), fez, fey); AXISTEST_Y02(e0.z(), e0.x(), fez, fex); AXISTEST_Z12(e0.y(), e0.x(), fey, fex); fex = std::fabs(e1.x()); fey = std::fabs(e1.y()); fez = std::fabs(e1.z()); AXISTEST_X01(e1.z(), e1.y(), fez, fey); AXISTEST_Y02(e1.z(), e1.x(), fez, fex); AXISTEST_Z0(e1.y(), e1.x(), fey, fex); fex = std::fabs(e2.x()); fey = std::fabs(e2.y()); fez = std::fabs(e2.z()); AXISTEST_X2(e2.z(), e2.y(), fez, fey); AXISTEST_Y1(e2.z(), e2.x(), fez, fex); AXISTEST_Z12(e2.y(), e2.x(), fey, fex); /* Bullet 1: */ /* first test overlap in the {x,y,z}-directions */ /* find min, max of the triangle each direction, and test for overlap in */ /* that direction -- this is equivalent to testing a minimal AABB around */ /* the triangle against the AABB */ /* test in X-direction */ std::tie(min, max) = std::minmax({v0.x(), v1.x(), v2.x()}); if (min > boxhalfsize.x() || max < -boxhalfsize.x()) { return false; } /* test in Y-direction */ std::tie(min, max) = std::minmax({v0.y(), v1.y(), v2.y()}); if (min > boxhalfsize.y() || max < -boxhalfsize.y()) { return false; } /* test in Z-direction */ std::tie(min, max) = std::minmax({v0.z(), v1.z(), v2.z()}); if (min > boxhalfsize.z() || max < -boxhalfsize.z()) { return false; } /* Bullet 2: */ /* test if the box intersects the plane of the triangle */ /* compute plane equation of triangle: normal*x+d=0 */ const zeus::CVector3f normal = e0.cross(e1); d = -normal.dot(v0); /* plane eq: normal.x+d=0 */ if (!planeBoxOverlap(normal, d, boxhalfsize)) { return false; } return true; /* box and triangle overlaps */ } double TriPointSqrDist(const zeus::CVector3f& point, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, float* baryX, float* baryY) { const zeus::CVector3d A = trivert0 - point; const zeus::CVector3d B = trivert1 - trivert0; const zeus::CVector3d C = trivert2 - trivert0; const double bMag = B.magSquared(); const double cMag = C.magSquared(); const double bDotC = B.dot(C); const double aDotB = A.dot(B); const double aDotC = A.dot(C); double ret = A.magSquared(); const double rej = std::fabs(bMag * cMag - bDotC * bDotC); double retB = bDotC * aDotC - cMag * aDotB; double retA = bDotC * aDotB - bMag * aDotC; if (retB + retA <= rej) { if (retB < 0.0) { if (retA < 0.0) { if (aDotB < 0.0) { retA = 0.0; if (-aDotB >= bMag) { retB = 1.0; ret += 2.0 * aDotB + bMag; } else { retB = -aDotB / bMag; ret += aDotB * retB; } } else { retB = 0.0; if (aDotC >= 0.0) { retA = 0.0; } else if (-aDotC >= cMag) { retA = 1.0; ret += 2.0 * aDotC + cMag; } else { retA = -aDotC / cMag; ret += aDotC * retA; } } } else { retB = 0.0; if (aDotC >= 0.0) { retA = 0.0; } else if (-aDotC >= cMag) { retA = 1.0; ret += 2.0 * aDotC + cMag; } else { retA = -aDotC / cMag; ret += aDotC * retA; } } } else if (retA < 0.0) { retA = 0.0; if (aDotB >= 0.0) { retB = 0.0; } else if (-aDotB >= bMag) { retB = 1.0; ret += 2.0 * aDotB + bMag; } else { retB = -aDotB / bMag; ret += aDotB * retB; } } else { const float f3 = 1.0 / rej; retA *= f3; retB *= f3; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } else if (retB < 0.0) { retB = bDotC + aDotB; retA = cMag + aDotC; if (retA > retB) { retA -= retB; retB = bMag - 2.0 * bDotC; retB += cMag; if (retA >= retB) { retB = 1.0; retA = 0.0; ret += 2.0 * aDotB + bMag; } else { retB = retA / retB; retA = 1.0 - retB; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } else { retB = 0.0; if (retA <= 0.0) { retA = 1.0; ret += 2.0 * aDotC + cMag; } else if (aDotC >= 0.0) { retA = 0.0; } else { retA = -aDotC / cMag; ret += aDotC * retA; } } } else { if (retA < 0.0) { retB = bDotC + aDotC; retA = bMag + aDotB; if (retA > retB) { retA -= retB; retB = bMag - 2.0 * bDotC; retB += cMag; if (retA >= retB) { retA = 1.0; retB = 0.0; ret += 2.0 * aDotC + cMag; } else { retA /= retB; retB = 1.0 - retA; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } else { retA = 0.0; if (retA <= 0.0) { retB = 1.0; ret += 2.0 * aDotB + bMag; } else if (aDotB >= 0.0) { retB = 0.0; } else { retB = -aDotB / bMag; ret += aDotB * retB; } } } else { retB = cMag + aDotC; retB -= bDotC; retA = retB - aDotB; if (retA <= 0.0) { retB = 0.0; retA = 1.0; ret += 2.0 * aDotC + cMag; } else { retB = bMag - 2.0 * bDotC; retB += cMag; if (retA >= retB) { retB = 1.0; retA = 0.0; ret += 2.0 * aDotB + bMag; } else { retB = retA / retB; retA = 1.0 - retB; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } } } if (baryX != nullptr) { *baryX = float(retA); } if (baryY != nullptr) { *baryY = float(retB); } return ret; } bool TriSphereOverlap(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2) { return TriPointSqrDist(sphere.position, trivert0, trivert1, trivert2, nullptr, nullptr) <= sphere.radius * sphere.radius; } bool TriSphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, zeus::CVector3f& point, zeus::CVector3f& normal) { float baryX; float baryY; if (TriPointSqrDist(sphere.position, trivert0, trivert1, trivert2, &baryX, &baryY) > sphere.radius * sphere.radius) { return false; } const zeus::CVector3f barys(baryX, baryY, 1.f - (baryX + baryY)); point = zeus::baryToWorld(trivert2, trivert1, trivert0, barys); if (baryX == 0.f || baryX == 1.f || baryY == 0.f || baryY == 1.f || barys.z() == 0.f || barys.z() == 1.f) { normal = -sphere.getSurfaceNormal(point); } else { normal = (trivert1 - trivert0).cross(trivert2 - trivert0).normalized(); } return true; } bool BoxLineTest(const zeus::CAABox& aabb, const zeus::CVector3f& point, const zeus::CVector3f& dir, float& tMin, float& tMax, int& axis, bool& sign) { tMin = -999999.f; tMax = 999999.f; for (size_t i = 0; i < 3; ++i) { if (dir[i] == 0.f) { if (point[i] < aabb.min[i] || point[i] > aabb.max[i]) { return false; } } const float dirRecip = 1.f / dir[i]; float tmpMin; float tmpMax; if (dir[i] < 0.f) { tmpMin = (aabb.max[i] - point[i]) * dirRecip; tmpMax = (aabb.min[i] - point[i]) * dirRecip; } else { tmpMin = (aabb.min[i] - point[i]) * dirRecip; tmpMax = (aabb.max[i] - point[i]) * dirRecip; } if (tmpMin > tMin) { sign = dir[i] < 0.f; axis = i; tMin = tmpMin; } if (tmpMax < tMax) { tMax = tmpMax; } } return tMin <= tMax; } bool LineCircleIntersection2d(const zeus::CVector3f& point, const zeus::CVector3f& dir, const zeus::CSphere& sphere, int axis1, int axis2, float& d) { const zeus::CVector3f delta = sphere.position - point; const zeus::CVector2f deltaVec(delta[axis1], delta[axis2]); const zeus::CVector2f dirVec(dir[axis1], dir[axis2]); const float dirVecMag = dirVec.magnitude(); if (dirVecMag < FLT_EPSILON) { return false; } const float deltaVecDot = deltaVec.dot(dirVec / dirVecMag); const float deltaVecMagSq = deltaVec.magSquared(); const float sphereRadSq = sphere.radius * sphere.radius; if (deltaVecDot < 0.f && deltaVecMagSq > sphereRadSq) { return false; } const float tSq = sphereRadSq - (deltaVecMagSq - deltaVecDot * deltaVecDot); if (tSq < 0.f) { return false; } const float t = std::sqrt(tSq); d = (deltaVecMagSq > sphereRadSq) ? deltaVecDot - t : deltaVecDot + t; d /= dirVecMag; return true; } bool MovingSphereAABox(const zeus::CSphere& sphere, const zeus::CAABox& aabb, const zeus::CVector3f& dir, double& dOut, zeus::CVector3f& point, zeus::CVector3f& normal) { const zeus::CAABox expAABB(aabb.min - sphere.radius, aabb.max + sphere.radius); float tMin; float tMax; int axis; bool sign; if (!BoxLineTest(expAABB, sphere.position, dir, tMin, tMax, axis, sign)) { return false; } point = sphere.position + tMin * dir; const int nextAxis1 = (axis + 1) % 3; // r0 const int nextAxis2 = (axis + 2) % 3; // r5 const bool inMin1 = point[nextAxis1] >= aabb.min[nextAxis1]; // r6 const bool inMax1 = point[nextAxis1] <= aabb.max[nextAxis1]; // r8 const bool inBounds1 = inMin1 && inMax1; // r9 const bool inMin2 = point[nextAxis2] >= aabb.min[nextAxis2]; // r7 const bool inMax2 = point[nextAxis2] <= aabb.max[nextAxis2]; // r4 const bool inBounds2 = inMin2 && inMax2; // r8 if (inBounds1 && inBounds2) { if (tMin < 0.f || tMin > dOut) { return false; } normal[axis] = sign ? 1.f : -1.f; dOut = tMin; point -= normal * sphere.radius; return true; } if (!inBounds1 && !inBounds2) { const int pointFlags = (1 << axis) * sign | (1 << nextAxis1) * inMin1 | (1 << nextAxis2) * inMin2; const zeus::CVector3f aabbPoint = aabb.getPoint(pointFlags); float d; if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint, sphere.radius), sphere.position, dir, dOut, d, point)) { int useAxis = -1; for (int i = 0; i < 3; ++i) { if ((pointFlags & (1 << i)) ? aabbPoint[i] > point[i] : aabbPoint[i] < point[i]) { useAxis = i; break; } } if (useAxis == -1) { normal = (point - aabbPoint).normalized(); point -= sphere.radius * normal; return true; } const int useAxisNext1 = (useAxis + 1) % 3; const int useAxisNext2 = (useAxis + 2) % 3; float d; if (CollisionUtil::LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint, sphere.radius), useAxisNext1, useAxisNext2, d) && d > 0.f && d < dOut) { if (point[useAxis] > aabb.max[useAxis]) { const int useAxisBit = 1 << useAxis; if (pointFlags & useAxisBit) { return false; } const zeus::CVector3f aabbPoint1 = aabb.getPoint(pointFlags | useAxisBit); if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint1, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint1).normalized(); point -= normal * sphere.radius; return true; } else { return false; } } else if (point[useAxis] < aabb.min[useAxis]) { const int useAxisBit = 1 << useAxis; if (!(pointFlags & useAxisBit)) { return false; } const zeus::CVector3f aabbPoint1 = aabb.getPoint(pointFlags ^ useAxisBit); if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint1, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint1).normalized(); point -= normal * sphere.radius; return true; } else { return false; } } else { normal = point - aabbPoint; normal.normalize(); point -= normal * sphere.radius; return true; } } } else { int reverseCount = 0; float dMin = 1.0e10f; int minAxis = 0; for (int i = 0; i < 3; ++i) { if (std::fabs(dir[i]) > FLT_EPSILON) { const bool pointMax = (pointFlags & (1 << i)) != 0; if (pointMax != (dir[i] > 0.f)) { ++reverseCount; const float d = 1.f / dir[i] * ((pointMax ? aabb.max[i] : aabb.min[i]) - sphere.position[i]); if (d < 0.f) { return false; } if (d < dMin) { dMin = d; minAxis = i; } } } } if (reverseCount < 2) { return false; } const int useAxisNext1 = (minAxis + 1) % 3; const int useAxisNext2 = (minAxis + 2) % 3; float d; if (CollisionUtil::LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint, sphere.radius), useAxisNext1, useAxisNext2, d) && d > 0.f && d < dOut) { point = sphere.position + d * dir; if (point[minAxis] > aabb.max[minAxis]) { return false; } if (point[minAxis] < aabb.min[minAxis]) { return false; } dOut = d; normal = point - aabbPoint; normal.normalize(); point -= sphere.radius * normal; return true; } else { return false; } } } const bool useNextAxis1 = inBounds1 ? nextAxis2 : nextAxis1; const bool useNextAxis2 = inBounds1 ? nextAxis1 : nextAxis2; const int pointFlags = ((1 << int(useNextAxis1)) * (inBounds1 ? inMin2 : inMin1)) | ((1 << axis) * sign); const zeus::CVector3f aabbPoint2 = aabb.getPoint(pointFlags); float d; if (LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint2, sphere.radius), axis, useNextAxis1, d) && d > 0.f && d < dOut) { point = sphere.position + d * dir; if (point[useNextAxis2] > aabb.max[useNextAxis2]) { const zeus::CVector3f aabbPoint3 = aabb.getPoint(pointFlags | (1 << int(useNextAxis2))); if (point[useNextAxis2] < expAABB.max[useNextAxis2]) { if (RaySphereIntersection(zeus::CSphere(aabbPoint3, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint3).normalized(); point -= sphere.radius * normal; return true; } } return false; } if (point[useNextAxis2] < aabb.min[useNextAxis2]) { if (point[useNextAxis2] > expAABB.min[useNextAxis2]) { if (RaySphereIntersection(zeus::CSphere(aabbPoint2, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint2).normalized(); point -= sphere.radius * normal; return true; } } return false; } else { dOut = d; normal = point - aabbPoint2; normal.normalize(); point -= sphere.radius * normal; return true; } } return false; } bool AABox_AABox_Moving(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1, const zeus::CVector3f& dir, double& d, zeus::CVector3f& point, zeus::CVector3f& normal) { zeus::CVector3d vecMin(-FLT_MAX); zeus::CVector3d vecMax(FLT_MAX); for (size_t i = 0; i < 3; ++i) { if (std::fabs(dir[i]) < FLT_EPSILON) { if (aabb0.min[i] >= aabb1.min[i] && aabb0.min[i] <= aabb1.max[i]) { continue; } if (aabb0.max[i] >= aabb1.min[i] && aabb0.max[i] <= aabb1.max[i]) { continue; } if (aabb0.min[i] < aabb1.min[i] && aabb0.max[i] > aabb1.max[i]) { continue; } return false; } else { if (aabb0.max[i] < aabb1.min[i] && dir[i] > 0.f) { vecMin[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } else if (aabb1.max[i] < aabb0.min[i] && dir[i] < 0.f) { vecMin[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } else if (aabb1.max[i] > aabb0.min[i] && dir[i] < 0.f) { vecMin[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } else if (aabb0.max[i] > aabb1.min[i] && dir[i] > 0.f) { vecMin[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } if (aabb1.max[i] > aabb0.min[i] && dir[i] > 0.f) { vecMax[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } else if (aabb0.max[i] > aabb1.min[i] && dir[i] < 0.f) { vecMax[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } else if (aabb0.max[i] < aabb1.min[i] && dir[i] < 0.f) { vecMax[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } else if (aabb1.max[i] < aabb0.min[i] && dir[i] > 0.f) { vecMax[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } } } int maxAxis = 0; if (vecMin[1] > vecMin[0]) { maxAxis = 1; } if (vecMin[2] > vecMin[maxAxis]) { maxAxis = 2; } const double minMax = std::min(std::min(vecMax[2], vecMax[1]), vecMax[0]); if (vecMin[maxAxis] > minMax) { return false; } d = vecMin[maxAxis]; normal = zeus::skZero3f; normal[maxAxis] = dir[maxAxis] > 0.f ? -1.f : 1.f; for (size_t i = 0; i < 3; ++i) { point[i] = dir[i] > 0.f ? aabb0.max[i] : aabb0.min[i]; } point += float(d) * dir; return true; } void AddAverageToFront(const CCollisionInfoList& in, CCollisionInfoList& out) { if (in.GetCount() > 1) { zeus::CVector3f pointAccum; zeus::CVector3f normAccum; for (const CCollisionInfo& info : in) { pointAccum += info.GetPoint(); normAccum += info.GetNormalLeft(); } if (normAccum.canBeNormalized()) { out.Add(CCollisionInfo(pointAccum / float(in.GetCount()), in.GetItem(0).GetMaterialRight(), in.GetItem(0).GetMaterialLeft(), normAccum.normalized()), false); } } for (const CCollisionInfo& info : in) { out.Add(info, false); } } } // namespace metaforce::CollisionUtil ================================================ FILE: Runtime/Collision/CollisionUtil.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include "Runtime/Collision/CMaterialList.hpp" #include #include #include #include #include namespace metaforce { class CCollisionInfoList; namespace CollisionUtil { bool LineIntersectsOBBox(const zeus::COBBox&, const zeus::CMRay&, float&); u32 RayAABoxIntersection(const zeus::CMRay&, const zeus::CAABox&, float&, float&); u32 RayAABoxIntersection(const zeus::CMRay&, const zeus::CAABox&, zeus::CVector3f&, float&); u32 RayAABoxIntersection_Double(const zeus::CMRay&, const zeus::CAABox&, zeus::CVector3f&, double&); bool RaySphereIntersection_Double(const zeus::CSphere&, const zeus::CVector3f&, const zeus::CVector3f&, double&); bool RaySphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, float& T, zeus::CVector3f& point); bool RayTriangleIntersection_Double(const zeus::CVector3f& point, const zeus::CVector3f& dir, const std::array& verts, double& d); bool RayTriangleIntersection(const zeus::CVector3f& point, const zeus::CVector3f& dir, const std::array& verts, float& d); void FilterOutBackfaces(const zeus::CVector3f& vec, const CCollisionInfoList& in, CCollisionInfoList& out); void FilterByClosestNormal(const zeus::CVector3f& norm, const CCollisionInfoList& in, CCollisionInfoList& out); bool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const CMaterialList& list0, const zeus::CAABox& aabb1, const CMaterialList& list1, CCollisionInfoList& infoList); bool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1); bool TriBoxOverlap(const zeus::CVector3f& boxcenter, const zeus::CVector3f& boxhalfsize, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2); double TriPointSqrDist(const zeus::CVector3f& point, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, float* baryX, float* baryY); bool TriSphereOverlap(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2); bool TriSphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, zeus::CVector3f& point, zeus::CVector3f& normal); bool BoxLineTest(const zeus::CAABox& aabb, const zeus::CVector3f& point, const zeus::CVector3f& dir, float& tMin, float& tMax, int& axis, bool& sign); bool LineCircleIntersection2d(const zeus::CVector3f& point, const zeus::CVector3f& dir, const zeus::CSphere& sphere, int axis1, int axis2, float& d); bool MovingSphereAABox(const zeus::CSphere& sphere, const zeus::CAABox& aabb, const zeus::CVector3f& dir, double& d, zeus::CVector3f& point, zeus::CVector3f& normal); bool AABox_AABox_Moving(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1, const zeus::CVector3f& dir, double& d, zeus::CVector3f& point, zeus::CVector3f& normal); void AddAverageToFront(const CCollisionInfoList& in, CCollisionInfoList& out); } // namespace CollisionUtil } // namespace metaforce ================================================ FILE: Runtime/Collision/ICollisionFilter.hpp ================================================ #pragma once namespace metaforce { class CActor; class CCollisionInfoList; class ICollisionFilter { CActor& x4_actor; protected: explicit ICollisionFilter(CActor& actor) : x4_actor(actor) {} public: virtual ~ICollisionFilter() = default; virtual void Filter(const CCollisionInfoList& in, CCollisionInfoList& out) const = 0; }; } // namespace metaforce ================================================ FILE: Runtime/Collision/InternalColliders.cpp ================================================ #include "Runtime/Collision/InternalColliders.hpp" #include "Runtime/Collision/CCollidableAABox.hpp" #include "Runtime/Collision/CCollidableCollisionSurface.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" namespace metaforce::InternalColliders { void AddTypes() { CCollisionPrimitive::InitAddType(CCollidableAABox::GetType()); CCollisionPrimitive::InitAddType(CCollidableCollisionSurface::GetType()); CCollisionPrimitive::InitAddType(CCollidableSphere::GetType()); } void AddColliders() { CCollisionPrimitive::InitAddCollider(Collide::AABox_AABox, "CCollidableAABox", "CCollidableAABox"); CCollisionPrimitive::InitAddCollider(Collide::Sphere_AABox, "CCollidableSphere", "CCollidableAABox"); CCollisionPrimitive::InitAddCollider(Collide::Sphere_Sphere, "CCollidableSphere", "CCollidableSphere"); CCollisionPrimitive::InitAddBooleanCollider(Collide::AABox_AABox_Bool, "CCollidableAABox", "CCollidableAABox"); CCollisionPrimitive::InitAddBooleanCollider(Collide::Sphere_AABox_Bool, "CCollidableSphere", "CCollidableAABox"); CCollisionPrimitive::InitAddBooleanCollider(Collide::Sphere_Sphere_Bool, "CCollidableSphere", "CCollidableSphere"); CCollisionPrimitive::InitAddMovingCollider(CCollidableAABox::CollideMovingAABox, "CCollidableAABox", "CCollidableAABox"); CCollisionPrimitive::InitAddMovingCollider(CCollidableAABox::CollideMovingSphere, "CCollidableAABox", "CCollidableSphere"); CCollisionPrimitive::InitAddMovingCollider(CCollidableSphere::CollideMovingAABox, "CCollidableSphere", "CCollidableAABox"); CCollisionPrimitive::InitAddMovingCollider(CCollidableSphere::CollideMovingSphere, "CCollidableSphere", "CCollidableSphere"); } } // namespace metaforce::InternalColliders ================================================ FILE: Runtime/Collision/InternalColliders.hpp ================================================ #pragma once namespace metaforce::InternalColliders { void AddTypes(); void AddColliders(); } // namespace metaforce::InternalColliders ================================================ FILE: Runtime/ConsoleVariables/CVar.cpp ================================================ #include "Runtime/CStringExtras.hpp" #include "Runtime/ConsoleVariables/CVar.hpp" #include "Runtime/Formatting.hpp" #include "Runtime/ConsoleVariables/CVarManager.hpp" namespace metaforce { extern CVar* com_developer; extern CVar* com_enableCheats; using namespace std::literals; CVar::CVar(std::string_view name, std::string_view value, std::string_view help, CVar::EFlags flags) : CVar(name, help, EType::Literal) { fromLiteral(value); init(flags); } CVar::CVar(std::string_view name, const zeus::CVector2i& value, std::string_view help, EFlags flags) : CVar(name, help, EType::Vec2i) { fromVec2i(value); init(flags); } CVar::CVar(std::string_view name, const zeus::CVector2f& value, std::string_view help, EFlags flags) : CVar(name, help, EType::Vec2f) { fromVec2f(value); init(flags); } CVar::CVar(std::string_view name, const zeus::CVector2d& value, std::string_view help, EFlags flags) : CVar(name, help, EType::Vec2d) { fromVec2d(value); init(flags); } CVar::CVar(std::string_view name, const zeus::CVector3f& value, std::string_view help, EFlags flags) : CVar(name, help, EType::Vec3f) { fromVec3f(value); init(flags, false); } CVar::CVar(std::string_view name, const zeus::CVector3d& value, std::string_view help, EFlags flags) : CVar(name, help, EType::Vec3d) { fromVec3d(value); init(flags, false); } CVar::CVar(std::string_view name, const zeus::CVector4f& value, std::string_view help, EFlags flags) : CVar(name, help, EType::Vec4f) { fromVec4f(value); init(flags, false); } CVar::CVar(std::string_view name, const zeus::CVector4d& value, std::string_view help, EFlags flags) : CVar(name, help, EType::Vec4d) { fromVec4d(value); init(flags, false); } CVar::CVar(std::string_view name, double value, std::string_view help, EFlags flags) : CVar(name, help, EType::Real) { fromReal(value); init(flags); } CVar::CVar(std::string_view name, bool value, std::string_view help, CVar::EFlags flags) : CVar(name, help, EType::Boolean) { fromBoolean(value); init(flags); } CVar::CVar(std::string_view name, int32_t value, std::string_view help, CVar::EFlags flags) : CVar(name, help, EType::Signed) { fromInteger(value); init(flags); } CVar::CVar(std::string_view name, uint32_t value, std::string_view help, CVar::EFlags flags) : CVar(name, help, EType::Unsigned) { fromInteger(value); init(flags); } std::string CVar::help() const { return m_help + (m_defaultValue.empty() ? "" : "\ndefault: " + m_defaultValue) + (isReadOnly() ? " [ReadOnly]" : ""); } zeus::CVector2i CVar::toVec2i(bool* isValid) const { if (m_type != EType::Vec2i) { if (isValid != nullptr) *isValid = false; return {}; } if (isValid != nullptr) *isValid = true; std::array f; std::sscanf(m_value.c_str(), "%i %i", &f[0], &f[1]); return {f[0], f[1]}; } zeus::CVector2f CVar::toVec2f(bool* isValid) const { if (m_type != EType::Vec2f) { if (isValid != nullptr) *isValid = false; return {}; } if (isValid != nullptr) *isValid = true; std::array f; std::sscanf(m_value.c_str(), "%g %g", &f[0], &f[1]); return {f[0], f[1]}; } zeus::CVector2d CVar::toVec2d(bool* isValid) const { if (m_type != EType::Vec2d) { if (isValid != nullptr) *isValid = false; return {}; } if (isValid != nullptr) *isValid = true; std::array f; std::sscanf(m_value.c_str(), "%lg %lg", &f[0], &f[1]); return {f[0], f[1]}; } zeus::CVector3f CVar::toVec3f(bool* isValid) const { if (m_type != EType::Vec3f) { if (isValid != nullptr) *isValid = false; return {}; } if (isValid != nullptr) *isValid = true; std::array f; std::sscanf(m_value.c_str(), "%g %g %g", &f[0], &f[1], &f[2]); return {f[0], f[1], f[2]}; } zeus::CVector3d CVar::toVec3d(bool* isValid) const { if (m_type != EType::Vec3d) { if (isValid != nullptr) *isValid = false; return {}; } if (isValid != nullptr) *isValid = true; std::array f; std::sscanf(m_value.c_str(), "%lg %lg %lg", &f[0], &f[1], &f[2]); return {f[0], f[1], f[2]}; } zeus::CVector4f CVar::toVec4f(bool* isValid) const { if (m_type != EType::Vec4f) { if (isValid != nullptr) *isValid = false; return {}; } if (isValid != nullptr) *isValid = true; std::array f; std::sscanf(m_value.c_str(), "%g %g %g %g", &f[0], &f[1], &f[2], &f[3]); return {f[0], f[1], f[2], f[3]}; } zeus::CVector4d CVar::toVec4d(bool* isValid) const { if (m_type != EType::Vec4d) { if (isValid != nullptr) *isValid = false; return {}; } if (isValid != nullptr) *isValid = true; std::array f{}; std::sscanf(m_value.c_str(), "%lg %lg %lg %lg", &f[0], &f[1], &f[2], &f[3]); return {f[0], f[1], f[2], f[3]}; } double CVar::toReal(bool* isValid) const { if (m_type != EType::Real) { if (isValid) *isValid = false; return 0.0f; } if (isValid != nullptr) *isValid = true; return strtod(m_value.c_str(), nullptr); } bool CVar::toBoolean(bool* isValid) const { if (m_type != EType::Boolean) { if (isValid) *isValid = false; return false; } return CStringExtras::ParseBool(m_value, isValid); } int32_t CVar::toSigned(bool* isValid) const { if (m_type != EType::Signed && m_type != EType::Unsigned) { if (isValid) *isValid = false; return 0; } if (isValid != nullptr) *isValid = true; return strtol(m_value.c_str(), nullptr, 0); } uint32_t CVar::toUnsigned(bool* isValid) const { if (m_type != EType::Signed && m_type != EType::Unsigned) { if (isValid) *isValid = false; return 0; } if (isValid != nullptr) *isValid = true; return strtoul(m_value.c_str(), nullptr, 0); } std::string CVar::toLiteral(bool* isValid) const { if (m_type != EType::Literal && (com_developer && com_developer->toBoolean())) { if (isValid != nullptr) *isValid = false; } else if (isValid != nullptr) { *isValid = true; } // Even if it's not a literal, it's still safe to return return m_value; } bool CVar::fromVec2i(const zeus::CVector2i& val) { if (!safeToModify(EType::Vec2i)) return false; m_value.assign(fmt::format("{} {}", val.x, val.y)); m_flags |= EFlags::Modified; return true; } bool CVar::fromVec2f(const zeus::CVector2f& val) { if (!safeToModify(EType::Vec2f)) return false; m_value.assign(fmt::format("{} {}", val.x(), val.y())); m_flags |= EFlags::Modified; return true; } bool CVar::fromVec2d(const zeus::CVector2d& val) { if (!safeToModify(EType::Vec2d)) return false; m_value.assign(fmt::format("{} {}", val.x(), val.y())); m_flags |= EFlags::Modified; return true; } bool CVar::fromVec3f(const zeus::CVector3f& val) { if (!safeToModify(EType::Vec3f)) return false; m_value.assign(fmt::format("{} {} {}", val.x(), val.y(), val.z())); m_flags |= EFlags::Modified; return true; } bool CVar::fromVec3d(const zeus::CVector3d& val) { if (!safeToModify(EType::Vec3d)) return false; m_value.assign(fmt::format("{} {} {}", val.x(), val.y(), val.z())); m_flags |= EFlags::Modified; return true; } bool CVar::fromVec4f(const zeus::CVector4f& val) { if (!safeToModify(EType::Vec4f)) return false; m_value.assign(fmt::format("{} {} {} {}", val.x(), val.y(), val.z(), val.w())); m_flags |= EFlags::Modified; return true; } bool CVar::fromVec4d(const zeus::CVector4d& val) { if (!safeToModify(EType::Vec4d)) return false; m_value.assign(fmt::format("{} {} {} {}", val.x(), val.y(), val.z(), val.w())); m_flags |= EFlags::Modified; return true; } bool CVar::fromReal(double val) { if (!safeToModify(EType::Real)) return false; m_value.assign(fmt::format("{}", val)); setModified(); return true; } bool CVar::fromBoolean(bool val) { if (!safeToModify(EType::Boolean)) return false; if (val) m_value = "true"sv; else m_value = "false"sv; setModified(); return true; } bool CVar::fromInteger(int32_t val) { if ((com_developer && com_enableCheats) && (!com_developer->toBoolean() || !com_enableCheats->toBoolean()) && isCheat()) return false; // We'll accept both signed an unsigned input if (m_type != EType::Signed && m_type != EType::Unsigned) return false; if (isReadOnly() && (com_developer && !com_developer->toBoolean())) return false; // Properly format based on signedness m_value = fmt::format("{}", (m_type == EType::Signed ? val : static_cast(val))); setModified(); return true; } bool CVar::fromInteger(uint32_t val) { if ((com_developer && com_enableCheats) && (!com_developer->toBoolean() || !com_enableCheats->toBoolean()) && isCheat()) return false; // We'll accept both signed an unsigned input if (m_type != EType::Signed && m_type != EType::Unsigned) return false; if (isReadOnly() && (com_developer && !com_developer->toBoolean())) return false; // Properly format based on signedness m_value = fmt::format("{}", (m_type == EType::Unsigned ? val : static_cast(val))); setModified(); return true; } bool CVar::fromLiteral(std::string_view val) { if (!safeToModify(EType::Literal)) return false; m_value.assign(val); setModified(); return true; } bool CVar::fromLiteralToType(std::string_view val) { if (!safeToModify(m_type) || !isValidInput(val)) return false; m_value = val; setModified(); return true; } bool CVar::isModified() const { return True(m_flags & EFlags::Modified); } bool CVar::modificationRequiresRestart() const { return True(m_flags & EFlags::ModifyRestart); } bool CVar::isReadOnly() const { return True(m_flags & EFlags::ReadOnly); } bool CVar::isCheat() const { return True(m_flags & EFlags::Cheat); } bool CVar::isHidden() const { return True(m_flags & EFlags::Hidden); } bool CVar::isArchive() const { return True(m_flags & EFlags::Archive); } bool CVar::isInternalArchivable() const { return True(m_flags & EFlags::InternalArchivable); } bool CVar::isColor() const { return True(m_flags & EFlags::Color) && (m_type == EType::Vec3f || m_type == EType::Vec3d || m_type == EType::Vec3f || m_type == EType::Vec4f || m_type == EType::Vec4d); } bool CVar::isNoDeveloper() const { return True(m_flags & EFlags::NoDeveloper); } bool CVar::wasDeserialized() const { return m_wasDeserialized; } bool CVar::hasDefaultValue() const { return m_defaultValue == m_value; } void CVar::clearModified() { if (!modificationRequiresRestart()) m_flags &= ~EFlags::Modified; } void CVar::forceClearModified() { m_flags &= ~EFlags::Modified; } void CVar::setModified() { m_flags |= EFlags::Modified; } void CVar::unlock() { if (isReadOnly() && !m_unlocked) { m_oldFlags = m_flags; m_flags &= ~EFlags::ReadOnly; m_unlocked = true; } } void CVar::lock() { if (!isReadOnly() && m_unlocked) { // We want to keep if we've been modified so we can inform our listeners bool modified = True(m_flags & EFlags::Modified); m_flags = m_oldFlags; // If we've been modified insert that back into m_flags if (modified) { m_flags |= EFlags::Modified; } m_unlocked = false; } } void CVar::dispatch() { for (const ListenerFunc& listen : m_listeners) listen(this); for (auto* ref : m_valueReferences) { ref->updateValue(); } } bool isInt(std::string_view v) { char* p; std::strtol(v.data(), &p, 10); return p != nullptr && *p == 0; } bool isInt(const std::vector& v) { for (auto& s : v) { if (!isInt(s)) return false; } return true; } bool isReal(std::string_view v) { char* p; std::strtod(v.data(), &p); return p != nullptr && *p == 0; } bool isReal(const std::vector& v) { for (auto& s : v) { if (!isReal(s)) return false; } return true; } bool CVar::isValidInput(std::string_view input) const { std::vector parts = CStringExtras::Split(input, ' '); char* p; switch (m_type) { case EType::Boolean: { bool valid = false; CStringExtras::ParseBool(input, &valid); return valid; } case EType::Signed: std::strtol(input.data(), &p, 0); return p != nullptr && *p == 0; case EType::Unsigned: std::strtoul(input.data(), &p, 0); return p != nullptr && *p == 0; case EType::Real: { bool size = parts.size() == 1; bool ret = isReal(input); return ret && size; } case EType::Literal: return true; case EType::Vec2i: return parts.size() == 2 && isInt(parts); case EType::Vec2f: case EType::Vec2d: return parts.size() == 2 && isReal(parts); case EType::Vec3f: case EType::Vec3d: return parts.size() == 3 && isReal(parts); case EType::Vec4f: case EType::Vec4d: return parts.size() == 4 && isReal(parts); } return false; } bool CVar::safeToModify(EType type) const { // Are we NoDevelper? if (isNoDeveloper()) return false; // Are we a cheat? if (isCheat() && (com_developer && com_enableCheats) && (!com_developer->toBoolean() || !com_enableCheats->toBoolean())) return false; // Are we read only? if (isReadOnly() && (com_developer && !com_developer->toBoolean())) return false; return m_type == type; } void CVar::init(EFlags flags, bool removeColor) { m_defaultValue = m_value; m_flags = flags; if (removeColor) { // If the user specifies color, we don't want it m_flags &= ~EFlags::Color; } } } // namespace metaforce ================================================ FILE: Runtime/ConsoleVariables/CVar.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include "zeus/zeus.hpp" #include #include #include namespace metaforce { namespace StoreCVar { enum class EType : uint32_t { Boolean, Signed, Unsigned, Real, Literal, Vec2i, Vec2f, Vec2d, Vec3f, Vec3d, Vec4f, Vec4d }; enum class EFlags { None = 0, System = (1 << 0), Game = (1 << 1), Editor = (1 << 2), Gui = (1 << 3), Cheat = (1 << 4), Hidden = (1 << 5), ReadOnly = (1 << 6), Archive = (1 << 7), InternalArchivable = (1 << 8), Modified = (1 << 9), ModifyRestart = (1 << 10), //!< If this bit is set, any modification will inform the user that a restart is required Color = (1 << 11), //!< If this bit is set, Vec3f and Vec4f will be displayed in the console with a colored square NoDeveloper = (1 << 12), //!< Not even developer mode can modify this Any = -1 }; ENABLE_BITWISE_ENUM(EFlags) class CVar { public: std::string m_name; std::string m_value; }; struct CVarContainer { u32 magic = 'CVAR'; std::vector cvars; }; } // namespace StoreCVar class CVarManager; class ICVarValueReference; class CVar : protected StoreCVar::CVar { friend class CVarManager; public: typedef std::function ListenerFunc; using EType = StoreCVar::EType; using EFlags = StoreCVar::EFlags; CVar(std::string_view name, std::string_view value, std::string_view help, EFlags flags); CVar(std::string_view name, const zeus::CVector2i& value, std::string_view help, EFlags flags); CVar(std::string_view name, const zeus::CVector2f& value, std::string_view help, EFlags flags); CVar(std::string_view name, const zeus::CVector2d& value, std::string_view help, EFlags flags); CVar(std::string_view name, const zeus::CVector3f& value, std::string_view help, EFlags flags); CVar(std::string_view name, const zeus::CVector3d& value, std::string_view help, EFlags flags); CVar(std::string_view name, const zeus::CVector4f& value, std::string_view help, EFlags flags); CVar(std::string_view name, const zeus::CVector4d& value, std::string_view help, EFlags flags); CVar(std::string_view name, double value, std::string_view help, EFlags flags); CVar(std::string_view name, bool value, std::string_view help, EFlags flags); CVar(std::string_view name, int32_t value, std::string_view help, EFlags flags); CVar(std::string_view name, uint32_t value, std::string_view help, EFlags flags); std::string_view name() const { return m_name; } std::string_view rawHelp() const { return m_help; } std::string_view defaultValue() const { return m_defaultValue; } std::string help() const; std::string value() const { return m_value; } template inline bool toValue(T& value) const; zeus::CVector2i toVec2i(bool* isValid = nullptr) const; zeus::CVector2f toVec2f(bool* isValid = nullptr) const; zeus::CVector2d toVec2d(bool* isValid = nullptr) const; zeus::CVector3f toVec3f(bool* isValid = nullptr) const; zeus::CVector3d toVec3d(bool* isValid = nullptr) const; zeus::CVector4f toVec4f(bool* isValid = nullptr) const; zeus::CVector4d toVec4d(bool* isValid = nullptr) const; double toReal(bool* isValid = nullptr) const; bool toBoolean(bool* isValid = nullptr) const; int32_t toSigned(bool* isValid = nullptr) const; uint32_t toUnsigned(bool* isValid = nullptr) const; std::string toLiteral(bool* isValid = nullptr) const; template inline bool fromValue(T value) { return false; } bool fromVec2i(const zeus::CVector2i& val); bool fromVec2f(const zeus::CVector2f& val); bool fromVec2d(const zeus::CVector2d& val); bool fromVec3f(const zeus::CVector3f& val); bool fromVec3d(const zeus::CVector3d& val); bool fromVec4f(const zeus::CVector4f& val); bool fromVec4d(const zeus::CVector4d& val); bool fromReal(double val); bool fromBoolean(bool val); bool fromInteger(int32_t val); bool fromInteger(uint32_t val); bool fromLiteral(std::string_view val); bool fromLiteralToType(std::string_view val); bool isVec2i() const { return m_type == EType::Vec2i; } bool isVec2f() const { return m_type == EType::Vec2f; } bool isVec2d() const { return m_type == EType::Vec2d; } bool isVec3f() const { return m_type == EType::Vec3f; } bool isVec3d() const { return m_type == EType::Vec3d; } bool isVec4f() const { return m_type == EType::Vec4f; } bool isVec4d() const { return m_type == EType::Vec4d; } bool isFloat() const { return m_type == EType::Real; } bool isBoolean() const { return m_type == EType::Boolean; } bool isInteger() const { return m_type == EType::Signed || m_type == EType::Unsigned; } bool isLiteral() const { return m_type == EType::Literal; } bool isModified() const; bool modificationRequiresRestart() const; bool isReadOnly() const; bool isCheat() const; bool isHidden() const; bool isArchive() const; bool isInternalArchivable() const; bool isNoDeveloper() const; bool isColor() const; bool wasDeserialized() const; bool hasDefaultValue() const; EType type() const { return m_type; } EFlags flags() const { return (m_unlocked ? m_oldFlags : m_flags); } /*! * \brief Unlocks the CVar for writing if it is ReadOnly. * Handle with care!!! if you use unlock(), make sure * you lock the cvar using lock() * \see lock */ void unlock(); /*! * \brief Locks the CVar to prevent writing if it is ReadOnly. * Unlike its partner function unlock, lock is harmless * \see unlock */ void lock(); void addListener(ListenerFunc func) { m_listeners.push_back(std::move(func)); } void addVariableReference(ICVarValueReference* v) { m_valueReferences.push_back(v); } void removeVariableReference(ICVarValueReference* v) { auto it = std::find(m_valueReferences.begin(), m_valueReferences.end(), v); if (it != m_valueReferences.end()) { m_valueReferences.erase(it); } } bool isValidInput(std::string_view input) const; private: CVar(std::string_view name, std::string_view help, EType type) : m_help(help), m_type(type) { m_name = name; } void dispatch(); void clearModified(); void forceClearModified(); void setModified(); std::string m_help; EType m_type; std::string m_defaultValue; EFlags m_flags = EFlags::None; EFlags m_oldFlags = EFlags::None; bool m_unlocked = false; bool m_wasDeserialized = false; std::vector m_listeners; std::vector m_valueReferences; bool safeToModify(EType type) const; void init(EFlags flags, bool removeColor = true); }; template <> inline bool CVar::toValue(zeus::CVector2f& value) const { bool isValid = false; value = toVec2f(&isValid); return isValid; } template <> inline bool CVar::toValue(zeus::CVector2d& value) const { bool isValid = false; value = toVec2d(&isValid); return isValid; } template <> inline bool CVar::toValue(zeus::CVector3f& value) const { bool isValid = false; value = toVec3f(&isValid); return isValid; } template <> inline bool CVar::toValue(zeus::CVector3d& value) const { bool isValid = false; value = toVec3d(&isValid); return isValid; } template <> inline bool CVar::toValue(zeus::CVector4f& value) const { bool isValid = false; value = toVec4f(&isValid); return isValid; } template <> inline bool CVar::toValue(zeus::CVector4d& value) const { bool isValid = false; value = toVec4d(&isValid); return isValid; } template <> inline bool CVar::toValue(double& value) const { bool isValid = false; value = toReal(&isValid); return isValid; } template <> inline bool CVar::toValue(float& value) const { bool isValid = false; value = static_cast(toReal(&isValid)); return isValid; } template <> inline bool CVar::toValue(bool& value) const { bool isValid = false; value = toBoolean(&isValid); return isValid; } template <> inline bool CVar::toValue(int32_t& value) const { bool isValid = false; value = toSigned(&isValid); return isValid; } template <> inline bool CVar::toValue(uint32_t& value) const { bool isValid = false; value = toUnsigned(&isValid); return isValid; } template <> inline bool CVar::toValue(std::string& value) const { bool isValid = false; value = toLiteral(&isValid); return isValid; } template <> inline bool CVar::fromValue(const zeus::CVector2f& val) { return fromVec2f(val); } template <> inline bool CVar::fromValue(const zeus::CVector2d& val) { return fromVec2d(val); } template <> inline bool CVar::fromValue(const zeus::CVector3f& val) { return fromVec3f(val); } template <> inline bool CVar::fromValue(const zeus::CVector3d& val) { return fromVec3d(val); } template <> inline bool CVar::fromValue(const zeus::CVector4f& val) { return fromVec4f(val); } template <> inline bool CVar::fromValue(const zeus::CVector4d& val) { return fromVec4d(val); } template <> inline bool CVar::fromValue(float val) { return fromReal(val); } template <> inline bool CVar::fromValue(double val) { return fromReal(val); } template <> inline bool CVar::fromValue(bool val) { return fromBoolean(val); } template <> inline bool CVar::fromValue(int32_t val) { return fromInteger(val); } template <> inline bool CVar::fromValue(uint32_t val) { return fromInteger(val); } template <> inline bool CVar::fromValue(std::string_view val) { return fromLiteral(val); } class CVarUnlocker { CVar* m_cvar; public: CVarUnlocker(CVar* cvar) : m_cvar(cvar) { if (m_cvar) m_cvar->unlock(); } ~CVarUnlocker() { if (m_cvar) m_cvar->lock(); } }; class ICVarValueReference { protected: CVar* m_cvar = nullptr; public: ICVarValueReference() = default; explicit ICVarValueReference(CVar* cv) : m_cvar(cv) { if (m_cvar != nullptr) { m_cvar->addVariableReference(this); } } virtual ~ICVarValueReference() { if (m_cvar != nullptr) { m_cvar->removeVariableReference(this); } m_cvar = nullptr; } virtual void updateValue() = 0; }; template class CVarValueReference : public ICVarValueReference { T* m_valueRef = nullptr; public: CVarValueReference() = default; explicit CVarValueReference(T* t, CVar* cv) : ICVarValueReference(cv) { m_valueRef = t; if (m_valueRef && m_cvar) { m_cvar->toValue(*m_valueRef); } } void updateValue() override { if (m_valueRef != nullptr && m_cvar->isModified()) { m_cvar->toValue(*m_valueRef); } } }; } // namespace metaforce ================================================ FILE: Runtime/ConsoleVariables/CVarCommons.cpp ================================================ #include "Runtime/ConsoleVariables/CVarCommons.hpp" namespace metaforce { namespace { CVarCommons* m_instance = nullptr; } CVarCommons::CVarCommons(CVarManager& manager) : m_mgr(manager) { m_fullscreen = m_mgr.findOrMakeCVar("fullscreen"sv, "Start in fullscreen"sv, false, CVar::EFlags::System | CVar::EFlags::Archive); m_allowJoystickInBackground = m_mgr.findOrMakeCVar("allowJoystickInBackground"sv, "Enable joystick input while window does not have focus"sv, true, CVar::EFlags::System | CVar::EFlags::Archive); m_graphicsApi = m_mgr.findOrMakeCVar("graphicsApi"sv, "API to use for rendering graphics"sv, DEFAULT_GRAPHICS_API, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart); m_drawSamples = m_mgr.findOrMakeCVar("drawSamples"sv, "Number of MSAA samples to use for render targets"sv, 1, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart); m_texAnisotropy = m_mgr.findOrMakeCVar("texAnisotropy"sv, "Number of anisotropic samples to use for sampling textures"sv, 1, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart); m_deepColor = m_mgr.findOrMakeCVar("deepColor"sv, "Allow framebuffer with color depth greater-then 24-bits"sv, false, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart); m_variableDt = m_mgr.findOrMakeCVar("variableDt", "Enable variable delta time (experimental)", false, (CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart)); m_windowSize = m_mgr.findOrMakeCVar("windowSize", "Stores the last known window size", zeus::CVector2i(1280, 960), (CVar::EFlags::System | CVar::EFlags::Archive)); m_windowPos = m_mgr.findOrMakeCVar("windowPos", "Stores the last known window position", zeus::CVector2i(-1, -1), (CVar::EFlags::System | CVar::EFlags::Archive)); m_debugOverlayPlayerInfo = m_mgr.findOrMakeCVar( "debugOverlay.playerInfo"sv, "Displays information about the player, such as location and orientation"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayWorldInfo = m_mgr.findOrMakeCVar( "debugOverlay.worldInfo"sv, "Displays information about the current world, such as world asset ID, and areaId"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayAreaInfo = m_mgr.findOrMakeCVar( "debugOverlay.areaInfo"sv, "Displays information about the current area, such as asset ID, object/layer counts, and active layer bits"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayLayerInfo = m_mgr.findOrMakeCVar("debugOverlay.layerInfo"sv, "Displays information about the currently active area layers"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayShowFrameCounter = m_mgr.findOrMakeCVar("debugOverlay.showFrameCounter"sv, "Displays the current frame index"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayShowFramerate = m_mgr.findOrMakeCVar("debugOverlay.showFramerate"sv, "Displays the current framerate"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayShowInGameTime = m_mgr.findOrMakeCVar("debugOverlay.showInGameTime"sv, "Displays the current in game time"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayShowRoomTimer = m_mgr.findOrMakeCVar( "debugOverlay.showRoomTimer", "Displays the current/last room timers in seconds and frames"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayShowResourceStats = m_mgr.findOrMakeCVar( "debugOverlay.showResourceStats"sv, "Displays the current live resource object and token counts"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayShowRandomStats = m_mgr.findOrMakeCVar("debugOverlay.showRandomStats", "Displays the current number of random calls per frame"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayPipelineInfo = m_mgr.findOrMakeCVar("debugOverlay.pipelineInfo"sv, "Displays the current pipeline memory usage per frame"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayDrawCallInfo = m_mgr.findOrMakeCVar("debugOverlay.drawCallInfo"sv, "Displays the current number of draw calls per frame"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayBufferInfo = m_mgr.findOrMakeCVar("debugOverlay.bufferInfo"sv, "Displays the current buffer memory usage per frame"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayShowInput = m_mgr.findOrMakeCVar("debugOverlay.showInput"sv, "Displays controller input"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugOverlayCorner = m_mgr.findOrMakeCVar("debugOverlay.overlayCorner"sv, "ImGui debug overlay corner"sv, 2 /* bottom-left */, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::Hidden); m_debugInputOverlayCorner = m_mgr.findOrMakeCVar("debugOverlay.inputOverlayCorner"sv, "ImGui input overlay corner"sv, 3 /* bottom-right */, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::Hidden); m_debugToolDrawAiPath = m_mgr.findOrMakeCVar("debugTool.drawAiPath", "Draws the selected paths of any AI in the room"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugToolDrawLighting = m_mgr.findOrMakeCVar("debugTool.drawLighting", "Draws the lighting setup in a room"sv, false, CVar::EFlags::Game | CVar::EFlags::ReadOnly); m_debugToolDrawCollisionActors = m_mgr.findOrMakeCVar("debugTool.drawCollisionActors", "Draws the collision actors for enemies and objects"sv, false, CVar::EFlags::Game | CVar::EFlags::ReadOnly); m_debugToolDrawMazePath = m_mgr.findOrMakeCVar("debugTool.drawMazePath", "Draws the maze path in Dynamo"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugToolDrawPlatformCollision = m_mgr.findOrMakeCVar("debugTool.drawPlatformCollision", "Draws the bounding boxes of platforms"sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly); m_debugToolMangleMipmaps = m_mgr.findOrMakeCVar( "debugTool.mangleMipmaps", "Sets each mipmap of a texture to a known color based on distance."sv, false, CVar::EFlags::Game | CVar::EFlags::Archive | CVar::EFlags::ReadOnly | CVar::EFlags::ModifyRestart); m_logFile = m_mgr.findOrMakeCVar("logFile"sv, "Any log prints will be stored to this file upon exit"sv, "app.log"sv, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::ModifyRestart); m_lastDiscPath = m_mgr.findOrMakeCVar("lastDiscPath"sv, "Most recently loaded disc image path"sv, ""sv, CVar::EFlags::System | CVar::EFlags::Archive | CVar::EFlags::Hidden); m_instance = this; } CVarCommons* CVarCommons::instance() { return m_instance; } } // namespace metaforce ================================================ FILE: Runtime/ConsoleVariables/CVarCommons.hpp ================================================ #pragma once #include #include #include #include "Runtime/ConsoleVariables/CVarManager.hpp" #undef min #undef max namespace metaforce { using namespace std::literals; #if defined(__APPLE__) #define DEFAULT_GRAPHICS_API "Metal"sv #else #define DEFAULT_GRAPHICS_API "Vulkan"sv #endif struct CVarCommons { CVarManager& m_mgr; CVar* m_fullscreen = nullptr; CVar* m_allowJoystickInBackground = nullptr; CVar* m_graphicsApi = nullptr; CVar* m_drawSamples = nullptr; CVar* m_texAnisotropy = nullptr; CVar* m_deepColor = nullptr; CVar* m_variableDt = nullptr; CVar* m_windowSize = nullptr; CVar* m_windowPos = nullptr; CVar* m_debugOverlayPlayerInfo = nullptr; CVar* m_debugOverlayWorldInfo = nullptr; CVar* m_debugOverlayAreaInfo = nullptr; CVar* m_debugOverlayLayerInfo = nullptr; CVar* m_debugOverlayShowFrameCounter = nullptr; CVar* m_debugOverlayShowFramerate = nullptr; CVar* m_debugOverlayShowInGameTime = nullptr; CVar* m_debugOverlayShowResourceStats = nullptr; CVar* m_debugOverlayShowRandomStats = nullptr; CVar* m_debugOverlayShowRoomTimer = nullptr; CVar* m_debugOverlayPipelineInfo = nullptr; CVar* m_debugOverlayDrawCallInfo = nullptr; CVar* m_debugOverlayBufferInfo = nullptr; CVar* m_debugOverlayShowInput = nullptr; CVar* m_debugOverlayCorner = nullptr; CVar* m_debugInputOverlayCorner = nullptr; CVar* m_debugToolDrawAiPath = nullptr; CVar* m_debugToolDrawLighting = nullptr; CVar* m_debugToolDrawCollisionActors = nullptr; CVar* m_debugToolDrawMazePath = nullptr; CVar* m_debugToolDrawPlatformCollision = nullptr; CVar* m_debugToolMangleMipmaps = nullptr; CVar* m_logFile = nullptr; CVar* m_lastDiscPath = nullptr; CVarCommons(CVarManager& manager); bool getFullscreen() const { return m_fullscreen->toBoolean(); } bool getAllowJoystickInBackground() const { return m_allowJoystickInBackground->toBoolean(); } void setFullscreen(bool b) { m_fullscreen->fromBoolean(b); } std::string getGraphicsApi() const { return m_graphicsApi->toLiteral(); } void setGraphicsApi(std::string_view api) { m_graphicsApi->fromLiteral(api); } uint32_t getSamples() const { return std::max(1u, m_drawSamples->toUnsigned()); } void setSamples(uint32_t v) { m_drawSamples->fromInteger(std::max(uint32_t(1), v)); } uint32_t getAnisotropy() const { return std::max(1u, uint32_t(m_texAnisotropy->toUnsigned())); } zeus::CVector2i getWindowSize() const { return m_windowSize->toVec2i(); } zeus::CVector2i getWindowPos() const { return m_windowPos->toVec2i(); } void setAnisotropy(uint32_t v) { m_texAnisotropy->fromInteger(std::max(1u, v)); } bool getDeepColor() const { return m_deepColor->toBoolean(); } void setDeepColor(bool b) { m_deepColor->fromBoolean(b); } bool getVariableFrameTime() const { return m_variableDt->toBoolean(); } void setVariableFrameTime(bool b) { m_variableDt->fromBoolean(b); } std::string getLogFile() const { return m_logFile->toLiteral(); }; void setLogFile(std::string_view log) { m_logFile->fromLiteral(log); } bool getMangleMipmaps() const { return m_debugToolMangleMipmaps->toBoolean(); } void setMangleMipmaps(bool b) { m_debugToolMangleMipmaps->fromBoolean(b); } void serialize() { m_mgr.serialize(); } static CVarCommons* instance(); }; } // namespace hecl ================================================ FILE: Runtime/ConsoleVariables/CVarManager.cpp ================================================ #include "Runtime/ConsoleVariables/CVarManager.hpp" #include "Runtime/ConsoleVariables/FileStoreManager.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/Streams/CTextInStream.hpp" #include "Runtime/Streams/CTextOutStream.hpp" #include "Runtime/Streams/CMemoryInStream.hpp" #include "Runtime/Streams/CMemoryStreamOut.hpp" #include "Runtime/CStringExtras.hpp" #include "Runtime/Formatting.hpp" #include #include #include #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif namespace metaforce { CVar* com_developer = nullptr; CVar* com_configfile = nullptr; CVar* com_enableCheats = nullptr; CVar* com_cubemaps = nullptr; static const std::regex cmdLineRegexEnable(R"(^\+([\w\.]+)([=])?([\/\\\s\w\.\-]+)?)"); static const std::regex cmdLineRegexDisable(R"(^\-([\w\.]+)([=])?([\/\\\s\w\.\-]+)?)"); CVarManager* CVarManager::m_instance = nullptr; CVarManager::CVarManager(FileStoreManager& store, bool useBinary) : m_store(store), m_useBinary(useBinary) { m_instance = this; com_configfile = newCVar("config", "File to store configuration", std::string("config"), CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::NoDeveloper | CVar::EFlags::Hidden); com_developer = newCVar("developer", "Enables developer mode", false, (CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable)); com_enableCheats = newCVar( "cheats", "Enable cheats", false, (CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::Hidden | CVar::EFlags::InternalArchivable)); com_cubemaps = newCVar("cubemaps", "Enable cubemaps", false, (CVar::EFlags::Game | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable)); } CVarManager::~CVarManager() {} CVar* CVarManager::registerCVar(std::unique_ptr&& cvar) { std::string tmp(cvar->name()); CStringExtras::ToLower(tmp); if (m_cvars.find(tmp) != m_cvars.end()) { return nullptr; } CVar* ret = cvar.get(); m_cvars.insert_or_assign(std::move(tmp), std::move(cvar)); return ret; } CVar* CVarManager::findCVar(std::string_view name) { std::string lower(name); CStringExtras::ToLower(lower); auto search = m_cvars.find(lower); if (search == m_cvars.end()) return nullptr; return search->second.get(); } std::vector CVarManager::archivedCVars() const { std::vector ret; for (const auto& pair : m_cvars) if (pair.second->isArchive()) ret.push_back(pair.second.get()); return ret; } std::vector CVarManager::cvars(CVar::EFlags filter) const { std::vector ret; for (const auto& pair : m_cvars) if (filter == CVar::EFlags::Any || True(pair.second->flags() & filter)) ret.push_back(pair.second.get()); return ret; } void CVarManager::deserialize(CVar* cvar) { /* Make sure we're not trying to deserialize a CVar that is invalid*/ if (!cvar) { return; } /* First let's check for a deferred value */ std::string lowName = cvar->name().data(); CStringExtras::ToLower(lowName); if (const auto iter = m_deferedCVars.find(lowName); iter != m_deferedCVars.end()) { std::string val = std::move(iter->second); m_deferedCVars.erase(lowName); if (cvar->isBoolean() && val.empty()) { // We were deferred without a value, assume true cvar->fromBoolean(true); cvar->m_wasDeserialized = true; return; } if (!val.empty() && cvar->fromLiteralToType(val)) { cvar->m_wasDeserialized = true; return; } } /* Enforce isArchive and isInternalArchivable now that we've checked if it's been deferred */ if (!cvar->isArchive() && !cvar->isInternalArchivable()) { return; } /* We were either unable to find a deferred value or got an invalid value */ std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + ".yaml"; auto container = loadCVars(filename); auto serialized = std::find_if(container.cbegin(), container.cend(), [&cvar](const auto& c) { return c.m_name == cvar->name(); }); if (serialized != container.cend()) { if (cvar->m_value != serialized->m_value) { { CVarUnlocker lc(cvar); cvar->fromLiteralToType(serialized->m_value); cvar->m_wasDeserialized = true; } if (cvar->modificationRequiresRestart()) { cvar->dispatch(); cvar->forceClearModified(); } } } } void CVarManager::serialize() { std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + ".yaml"; /* If we have an existing config load it in, so we can update it */ auto container = loadCVars(filename); u32 minLength = 0; bool write = false; for (const auto& pair : m_cvars) { const auto& cvar = pair.second; if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) { write = true; /* Look for an existing CVar in the file... */ auto serialized = std::find_if(container.begin(), container.end(), [&cvar](const auto& c) { return c.m_name == cvar->name(); }); if (serialized != container.end()) { /* Found it! Update the value */ serialized->m_value = cvar->value(); } else { /* Store this value as a new CVar in the config */ container.emplace_back(StoreCVar::CVar{std::string(cvar->name()), cvar->value()}); } } } /* Compute length needed for all cvars */ std::for_each(container.cbegin(), container.cend(), [&minLength](const auto& cvar) { minLength += cvar.m_name.length() + cvar.m_value.length() + 2; }); /* Only write the CVars if any have been modified */ if (!write) { return; } // Allocate enough space to write all the strings with some space to spare const auto requiredLen = minLength + (4 * container.size()); std::unique_ptr workBuf(new u8[requiredLen]); CMemoryStreamOut memOut(workBuf.get(), requiredLen, CMemoryStreamOut::EOwnerShip::NotOwned, 32); CTextOutStream textOut(memOut); for (const auto& cvar : container) { auto str = fmt::format("{}: {}", cvar.m_name, cvar.m_value); textOut.WriteString(str); } auto* file = fopen(filename.c_str(), #ifdef _MSC_VER "wb" #else "wbe" #endif ); if (file != nullptr) { u32 writeLen = memOut.GetWritePosition(); u32 offset = 0; while (offset < writeLen) { offset += fwrite(workBuf.get() + offset, 1, writeLen - offset, file); } fflush(file); } fclose(file); } std::vector CVarManager::loadCVars(const std::string& filename) const { std::vector ret; CBasics::Sstat st; if (CBasics::Stat(filename.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { auto* file = fopen(filename.c_str(), #ifdef _MSC_VER "rb" #else "rbe" #endif ); if (file != nullptr) { std::unique_ptr inBuf(new u8[st.st_size]); fread(inBuf.get(), 1, st.st_size, file); fclose(file); CMemoryInStream mem(inBuf.get(), st.st_size, CMemoryInStream::EOwnerShip::NotOwned); CTextInStream textIn(mem, st.st_size); while (!textIn.IsEOF()) { auto cvString = textIn.GetNextLine(); if (cvString.empty()) { continue; } auto parts = CStringExtras::Split(cvString, ':'); if (parts.size() < 2) { continue; } const auto key = CStringExtras::Trim(parts[0]); auto value = parts[1]; for (size_t i = 2; i < parts.size(); ++i) { value += ":"; value += parts[i]; } value = CStringExtras::Trim(value); ret.emplace_back(StoreCVar::CVar{key, value}); } } } return ret; } CVarManager* CVarManager::instance() { return m_instance; } void CVarManager::setDeveloperMode(bool v, bool setDeserialized) { com_developer->unlock(); com_developer->fromBoolean(v); if (setDeserialized) com_developer->m_wasDeserialized = true; com_developer->lock(); com_developer->setModified(); } void CVarManager::setCheatsEnabled(bool v, bool setDeserialized) { com_enableCheats->unlock(); com_enableCheats->fromBoolean(v); if (setDeserialized) com_enableCheats->m_wasDeserialized = true; com_enableCheats->lock(); com_enableCheats->setModified(); } bool CVarManager::restartRequired() const { return std::any_of(m_cvars.cbegin(), m_cvars.cend(), [](const auto& entry) { return entry.second->isModified() && entry.second->modificationRequiresRestart(); }); } void CVarManager::parseCommandLine(const std::vector& args) { bool oldDeveloper = suppressDeveloper(); std::string developerName(com_developer->name()); CStringExtras::ToLower(developerName); for (const std::string& arg : args) { if (arg[0] != '+' && arg[0] != '-') { continue; } std::smatch matches; std::string cvarName; std::string cvarValue; bool set = false; if (std::regex_match(arg, matches, cmdLineRegexEnable)) { std::vector realMatches; for (auto match : matches) { if (match.matched) { realMatches.push_back(match); } } if (realMatches.size() == 2) { cvarName = matches[1].str(); } else if (realMatches.size() == 4) { cvarName = matches[1].str(); cvarValue = matches[3].str(); } set = true; } else if (std::regex_match(arg, matches, cmdLineRegexDisable)) { std::vector realMatches; for (auto match : matches) { if (match.matched) { realMatches.push_back(match); } } if (realMatches.size() == 2) { cvarName = matches[1].str(); } else if (realMatches.size() == 4) { cvarName = matches[1].str(); cvarValue = matches[3].str(); } set = false; } if (CVar* cv = findCVar(cvarName)) { if (cvarValue.empty() && cv->isBoolean()) { // We were set from the command line with an empty value, assume true cv->fromBoolean(set); } else if (!cvarValue.empty()) { cv->fromLiteralToType(cvarValue); } cv->m_wasDeserialized = true; cv->forceClearModified(); CStringExtras::ToLower(cvarName); if (developerName == cvarName) { /* Make sure we're not overriding developer mode when we restore */ oldDeveloper = com_developer->toBoolean(); } } else { /* Unable to find an existing CVar, let's defer for the time being 8 */ CStringExtras::ToLower(cvarName); if (cvarValue.empty()) { cvarValue = set ? "true" : "false"; } m_deferedCVars.insert(std::make_pair(std::move(cvarName), std::move(cvarValue))); } } restoreDeveloper(oldDeveloper); } bool CVarManager::suppressDeveloper() { bool oldDeveloper = com_developer->toBoolean(); CVarUnlocker unlock(com_developer); com_developer->fromBoolean(true); return oldDeveloper; } void CVarManager::restoreDeveloper(bool oldDeveloper) { CVarUnlocker unlock(com_developer); com_developer->fromBoolean(oldDeveloper); } void CVarManager::proc() { for (const auto& [name, cvar] : m_cvars) { if (cvar->isModified()) { cvar->dispatch(); } if (cvar->isModified() && !cvar->modificationRequiresRestart()) { // Clear the modified flag now that we've informed everyone we've changed cvar->clearModified(); } } } } // namespace metaforce ================================================ FILE: Runtime/ConsoleVariables/CVarManager.hpp ================================================ #pragma once #include #include #include #include #include #include "Runtime/ConsoleVariables/CVar.hpp" namespace metaforce { class FileStoreManager; extern CVar* com_developer; extern CVar* com_configfile; extern CVar* com_enableCheats; extern CVar* com_cubemaps; class CVarManager final { using CVarContainer = StoreCVar::CVarContainer; template CVar* _newCVar(std::string_view name, std::string_view help, const T& value, CVar::EFlags flags) { if (CVar* ret = registerCVar(std::make_unique(name, value, help, flags))) { deserialize(ret); return ret; } return nullptr; } FileStoreManager& m_store; bool m_useBinary; static CVarManager* m_instance; public: CVarManager() = delete; CVarManager(const CVarManager&) = delete; CVarManager& operator=(const CVarManager&) = delete; CVarManager& operator=(const CVarManager&&) = delete; CVarManager(FileStoreManager& store, bool useBinary = false); ~CVarManager(); CVar* newCVar(std::string_view name, std::string_view help, const zeus::CVector2i& value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, const zeus::CVector2f& value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, const zeus::CVector2d& value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, const zeus::CVector3f& value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, const zeus::CVector3d& value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, const zeus::CVector4f& value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, const zeus::CVector4d& value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, std::string_view value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, bool value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } // Float and double are internally identical, all floating point values are stored as `double` CVar* newCVar(std::string_view name, std::string_view help, float value, CVar::EFlags flags) { return _newCVar(name, help, static_cast(value), flags); } CVar* newCVar(std::string_view name, std::string_view help, double value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } // Integer CVars can be seamlessly converted between either type, the distinction is to make usage absolutely clear CVar* newCVar(std::string_view name, std::string_view help, int32_t value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* newCVar(std::string_view name, std::string_view help, uint32_t value, CVar::EFlags flags) { return _newCVar(name, help, value, flags); } CVar* registerCVar(std::unique_ptr&& cvar); CVar* findCVar(std::string_view name); template CVar* findOrMakeCVar(std::string_view name, _Args&&... args) { if (CVar* cv = findCVar(name)) return cv; return newCVar(name, std::forward<_Args>(args)...); } std::vector archivedCVars() const; std::vector cvars(CVar::EFlags filter = CVar::EFlags::Any) const; void deserialize(CVar* cvar); void serialize(); static CVarManager* instance(); void proc(); void setDeveloperMode(bool v, bool setDeserialized = false); void setCheatsEnabled(bool v, bool setDeserialized = false); bool restartRequired() const; void parseCommandLine(const std::vector& args); FileStoreManager& fileStoreManager() { return m_store; } private: bool suppressDeveloper(); void restoreDeveloper(bool oldDeveloper); std::unordered_map> m_cvars; std::map m_deferedCVars; std::vector loadCVars(const std::string& filename) const; }; } // namespace hecl ================================================ FILE: Runtime/ConsoleVariables/FileStoreManager.cpp ================================================ #include "Runtime/ConsoleVariables/FileStoreManager.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/Logging.hpp" #include #if _WIN32 #include #endif #if _WIN32 #include #endif #if WINDOWS_STORE using namespace Windows::Storage; #endif namespace metaforce { namespace { FileStoreManager* g_instance = nullptr; } FileStoreManager::FileStoreManager(std::string_view org, std::string_view domain) : m_org(org), m_domain(domain) { if (g_instance != nullptr) { spdlog::fatal("Attempting to build another FileStoreManager!!"); } auto prefPath = SDL_GetPrefPath(org.data(), domain.data()); if (prefPath == nullptr) { #if _WIN32 #if !WINDOWS_STORE WCHAR home[MAX_PATH]; if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, home))) spdlog::fatal("unable to locate profile for file store"); std::string path = nowide::narrow(home); #else StorageFolder ^ cacheFolder = ApplicationData::Current->LocalCacheFolder; std::string path(cacheFolder->Path->Data()); #endif path += "/." + m_org; CBasics::MakeDir(path.c_str()); path += '/'; path += domain.data(); CBasics::MakeDir(path.c_str()); m_storeRoot = path; #else const char* xdg_data_home = getenv("XDG_DATA_HOME"); std::string path; if (xdg_data_home) { if (xdg_data_home[0] != '/') spdlog::fatal("invalid $XDG_DATA_HOME for file store (must be absolute)"); path = xdg_data_home; } else { const char* home = getenv("HOME"); if (!home) spdlog::fatal("unable to locate $HOME for file store"); path = home; path += "/.local/share"; } path += "/" + m_org + "/" + domain.data(); if (CBasics::RecursiveMakeDir(path.c_str()) != 0) { spdlog::fatal("unable to mkdir at {}", path); } m_storeRoot = path; #endif } else { m_storeRoot = std::string(prefPath); SDL_free(prefPath); } g_instance = this; } FileStoreManager* FileStoreManager::instance() { if (g_instance == nullptr) { spdlog::fatal("Requested FileStoreManager instance before it's built!"); } return g_instance; } } // namespace metaforce ================================================ FILE: Runtime/ConsoleVariables/FileStoreManager.hpp ================================================ #pragma once #include #include namespace metaforce { /** * @brief Per-platform file store resolution */ class FileStoreManager { std::string m_org; std::string m_domain; std::string m_storeRoot; public: FileStoreManager(FileStoreManager&) = delete; FileStoreManager(FileStoreManager&&) = delete; void operator=(FileStoreManager&) = delete; void operator=(FileStoreManager&&) = delete; FileStoreManager(std::string_view org, std::string_view domain); std::string_view getOrg() const { return m_org; } std::string_view getDomain() const { return m_domain; } /** * @brief Returns the full path to the file store, including domain * @return Full path to store e.g /home/foo/.hecl/bar */ std::string_view getStoreRoot() const { return m_storeRoot; } static FileStoreManager* instance(); }; } ================================================ FILE: Runtime/Flags.hpp ================================================ #pragma once #include namespace metaforce { template class Flags { public: using MaskType = std::underlying_type_t; // constructors constexpr Flags() noexcept : m_mask(0) {} constexpr Flags(BitType bit) noexcept : m_mask(static_cast(bit)) {} constexpr Flags(Flags const& rhs) noexcept : m_mask(rhs.m_mask) {} constexpr explicit Flags(MaskType flags) noexcept : m_mask(flags) {} [[nodiscard]] constexpr bool IsSet(Flags const bit) const noexcept { return bool(*this & bit); } // relational operators bool operator==(Flags const& rhs) const noexcept { return m_mask == rhs.m_mask; } // logical operator constexpr bool operator!() const noexcept { return !m_mask; } // bitwise operators constexpr Flags operator&(Flags const& rhs) const noexcept { return Flags(m_mask & rhs.m_mask); } constexpr Flags operator|(Flags const& rhs) const noexcept { return Flags(m_mask | rhs.m_mask); } constexpr Flags operator^(Flags const& rhs) const noexcept { return Flags(m_mask ^ rhs.m_mask); } // assignment operators constexpr Flags& operator=(Flags const& rhs) noexcept { m_mask = rhs.m_mask; return *this; } constexpr Flags& operator|=(Flags const& rhs) noexcept { m_mask |= rhs.m_mask; return *this; } constexpr Flags& operator&=(Flags const& rhs) noexcept { m_mask &= rhs.m_mask; return *this; } constexpr Flags& operator^=(Flags const& rhs) noexcept { m_mask ^= rhs.m_mask; return *this; } // cast operators explicit constexpr operator bool() const noexcept { return m_mask != 0; } explicit constexpr operator MaskType() const noexcept { return m_mask; } private: MaskType m_mask; }; } // namespace metaforce ================================================ FILE: Runtime/Formatting.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include // IWYU pragma: export #include #include #include #include #include #include #define FMT_CUSTOM_FORMATTER(Type, str, ...) \ template \ struct fmt::formatter : fmt::formatter, CharT> { \ template \ auto format(const Type& obj, FormatContext& ctx) const -> decltype(ctx.out()) { \ if constexpr (std::is_same_v) { \ return fmt::format_to(ctx.out(), str __VA_OPT__(, ) __VA_ARGS__); \ } else if constexpr (std::is_same_v) { \ return fmt::format_to(ctx.out(), u8##str __VA_OPT__(, ) __VA_ARGS__); \ } else if constexpr (std::is_same_v) { \ return fmt::format_to(ctx.out(), u##str __VA_OPT__(, ) __VA_ARGS__); \ } else if constexpr (std::is_same_v) { \ return fmt::format_to(ctx.out(), U##str __VA_OPT__(, ) __VA_ARGS__); \ } else if constexpr (std::is_same_v) { \ return fmt::format_to(ctx.out(), L##str __VA_OPT__(, ) __VA_ARGS__); \ } else { \ static_assert(!sizeof(CharT), "Unsupported character type for formatter"); \ } \ } \ } FMT_CUSTOM_FORMATTER(metaforce::CAssetId, "{:08X}", obj.Value()); FMT_CUSTOM_FORMATTER(metaforce::FourCC, "{:c}{:c}{:c}{:c}", obj.getChars()[0], obj.getChars()[1], obj.getChars()[2], obj.getChars()[3]); FMT_CUSTOM_FORMATTER(metaforce::SObjectTag, "{} {}", obj.type, obj.id); FMT_CUSTOM_FORMATTER(metaforce::TEditorId, "{:08X}", obj.id); static_assert(sizeof(metaforce::kUniqueIdType) == sizeof(u16), "TUniqueId size does not match expected size! Update TUniqueId format string!"); FMT_CUSTOM_FORMATTER(metaforce::TUniqueId, "{:04X}", obj.id); FMT_CUSTOM_FORMATTER(zeus::CVector3f, "({} {} {})", obj.x(), obj.y(), obj.z()); FMT_CUSTOM_FORMATTER(zeus::CVector2f, "({} {})", obj.x(), obj.y()); FMT_CUSTOM_FORMATTER(zeus::CMatrix3f, "\n({} {} {})" "\n({} {} {})" "\n({} {} {})", obj[0][0], obj[1][0], obj[2][0], obj[0][1], obj[1][1], obj[2][1], obj[0][2], obj[1][2], obj[2][2]); FMT_CUSTOM_FORMATTER(zeus::CMatrix4f, "\n({} {} {} {})" "\n({} {} {} {})" "\n({} {} {} {})" "\n({} {} {} {})", obj[0][0], obj[1][0], obj[2][0], obj[3][0], obj[0][1], obj[1][1], obj[2][1], obj[3][1], obj[0][2], obj[1][2], obj[2][2], obj[3][2], obj[0][3], obj[1][3], obj[2][3], obj[3][3]); FMT_CUSTOM_FORMATTER(zeus::CTransform, "\n({} {} {} {})" "\n({} {} {} {})" "\n({} {} {} {})", obj.basis[0][0], obj.basis[1][0], obj.basis[2][0], obj.origin[0], obj.basis[0][1], obj.basis[1][1], obj.basis[2][1], obj.origin[1], obj.basis[0][2], obj.basis[1][2], obj.basis[2][2], obj.origin[2]); ================================================ FILE: Runtime/GCNTypes.hpp ================================================ #pragma once #include #include using s8 = int8_t; using u8 = uint8_t; using s16 = int16_t; using u16 = uint16_t; using s32 = int32_t; using u32 = uint32_t; using s64 = int64_t; using u64 = uint64_t; #ifndef ROUND_UP_256 #define ROUND_UP_256(val) (((val) + 255) & ~255) #endif #ifndef ROUND_UP_64 #define ROUND_UP_64(val) (((val) + 63) & ~63) #endif #ifndef ROUND_UP_32 #define ROUND_UP_32(val) (((val) + 31) & ~31) #endif #ifndef ROUND_UP_16 #define ROUND_UP_16(val) (((val) + 15) & ~15) #endif #ifndef ROUND_UP_8 #define ROUND_UP_8(val) (((val) + 7) & ~7) #endif #ifndef ROUND_UP_4 #define ROUND_UP_4(val) (((val) + 3) & ~3) #endif #ifndef ENABLE_BITWISE_ENUM #define ENABLE_BITWISE_ENUM(type) \ constexpr type operator|(type a, type b) noexcept { \ using T = std::underlying_type_t; \ return type(static_cast(a) | static_cast(b)); \ } \ constexpr type operator&(type a, type b) noexcept { \ using T = std::underlying_type_t; \ return type(static_cast(a) & static_cast(b)); \ } \ constexpr type& operator|=(type& a, type b) noexcept { \ using T = std::underlying_type_t; \ a = type(static_cast(a) | static_cast(b)); \ return a; \ } \ constexpr type& operator&=(type& a, type b) noexcept { \ using T = std::underlying_type_t; \ a = type(static_cast(a) & static_cast(b)); \ return a; \ } \ constexpr type operator~(type key) noexcept { \ using T = std::underlying_type_t; \ return type(~static_cast(key)); \ } \ constexpr bool True(type key) noexcept { \ using T = std::underlying_type_t; \ return static_cast(key) != 0; \ } \ constexpr bool False(type key) noexcept { \ using T = std::underlying_type_t; \ return static_cast(key) == 0; \ } #endif ================================================ FILE: Runtime/GameGlobalObjects.cpp ================================================ #include "Runtime/GameGlobalObjects.hpp" namespace metaforce { namespace MP1 { class CGameArchitectureSupport* g_archSupport = nullptr; } class IMain* g_Main = nullptr; class CMemoryCardSys* g_MemoryCardSys = nullptr; class IFactory* g_ResFactory = nullptr; class CSimplePool* g_SimplePool = nullptr; class CCharacterFactoryBuilder* g_CharFactoryBuilder = nullptr; class CAiFuncMap* g_AiFuncMap = nullptr; class CGameState* g_GameState = nullptr; class CInGameTweakManagerBase* g_TweakManager = nullptr; class CCubeRenderer* g_Renderer = nullptr; class CStringTable* g_MainStringTable = nullptr; class CTextureCache* g_TextureCache = nullptr; class CInputGenerator* g_InputGenerator = nullptr; class IController* g_Controller = nullptr; class CStateManager* g_StateManager = nullptr; ITweakGame* g_tweakGame = nullptr; ITweakPlayer* g_tweakPlayer = nullptr; ITweakPlayerControl* g_tweakPlayerControl = nullptr; ITweakPlayerControl* g_tweakPlayerControlAlt = nullptr; ITweakPlayerControl* g_currentPlayerControl = nullptr; ITweakPlayerGun* g_tweakPlayerGun = nullptr; ITweakGunRes* g_tweakGunRes = nullptr; ITweakPlayerRes* g_tweakPlayerRes = nullptr; ITweakTargeting* g_tweakTargeting = nullptr; ITweakAutoMapper* g_tweakAutoMapper = nullptr; ITweakGui* g_tweakGui = nullptr; ITweakSlideShow* g_tweakSlideShow = nullptr; ITweakParticle* g_tweakParticle = nullptr; ITweakBall* g_tweakBall = nullptr; ITweakGuiColors* g_tweakGuiColors = nullptr; } // namespace metaforce ================================================ FILE: Runtime/GameGlobalObjects.hpp ================================================ #pragma once #define USE_DOWNCAST_TWEAKS 1 #if USE_DOWNCAST_TWEAKS #include "Runtime/MP1/Tweaks/CTweakAutoMapper.hpp" #include "Runtime/MP1/Tweaks/CTweakBall.hpp" #include "Runtime/MP1/Tweaks/CTweakGame.hpp" #include "Runtime/MP1/Tweaks/CTweakGui.hpp" #include "Runtime/MP1/Tweaks/CTweakGui.hpp" #include "Runtime/MP1/Tweaks/CTweakGuiColors.hpp" #include "Runtime/MP1/Tweaks/CTweakGunRes.hpp" #include "Runtime/MP1/Tweaks/CTweakParticle.hpp" #include "Runtime/MP1/Tweaks/CTweakPlayer.hpp" #include "Runtime/MP1/Tweaks/CTweakPlayerControl.hpp" #include "Runtime/MP1/Tweaks/CTweakPlayerGun.hpp" #include "Runtime/MP1/Tweaks/CTweakPlayerRes.hpp" #include "Runtime/MP1/Tweaks/CTweakSlideShow.hpp" #include "Runtime/MP1/Tweaks/CTweakTargeting.hpp" #include "Runtime/MP1/Tweaks/CTweakGuiColors.hpp" #include "Runtime/MP1/Tweaks/CTweakTargeting.hpp" #else #include "Runtime/Tweaks/ITweakAutoMapper.hpp" #include "Runtime/Tweaks/ITweakBall.hpp" #include "Runtime/Tweaks/ITweakGame.hpp" #include "Runtime/Tweaks/ITweakGui.hpp" #include "Runtime/Tweaks/ITweakGui.hpp" #include "Runtime/Tweaks/ITweakGuiColors.hpp" #include "Runtime/Tweaks/ITweakGunRes.hpp" #include "Runtime/Tweaks/ITweakParticle.hpp" #include "Runtime/Tweaks/ITweakPlayer.hpp" #include "Runtime/Tweaks/ITweakPlayerControl.hpp" #include "Runtime/Tweaks/ITweakPlayerGun.hpp" #include "Runtime/Tweaks/ITweakPlayerRes.hpp" #include "Runtime/Tweaks/ITweakSlideShow.hpp" #include "Runtime/Tweaks/ITweakTargeting.hpp" #endif #include "Runtime/CTextureCache.hpp" namespace metaforce { extern class IMain* g_Main; namespace MP1 { extern class CGameArchitectureSupport* g_archSupport; } extern class CMemoryCardSys* g_MemoryCardSys; extern class IFactory* g_ResFactory; extern class CSimplePool* g_SimplePool; extern class CCharacterFactoryBuilder* g_CharFactoryBuilder; extern class CAiFuncMap* g_AiFuncMap; extern class CGameState* g_GameState; extern class CInGameTweakManagerBase* g_TweakManager; extern class CCubeRenderer* g_Renderer; extern class CStringTable* g_MainStringTable; extern class CTextureCache* g_TextureCache; extern class CInputGenerator* g_InputGenerator; extern class IController* g_Controller; extern class CStateManager* g_StateManager; #if USE_DOWNCAST_TWEAKS using ITweakGame = metaforce::MP1::CTweakGame; using ITweakPlayer = metaforce::MP1::CTweakPlayer; using ITweakPlayerControl = metaforce::MP1::CTweakPlayerControl; using ITweakPlayerGun = metaforce::MP1::CTweakPlayerGun; using ITweakGunRes = metaforce::MP1::CTweakGunRes; using ITweakAutoMapper = metaforce::MP1::CTweakAutoMapper; using ITweakGui = metaforce::MP1::CTweakGui; using ITweakSlideShow = metaforce::MP1::CTweakSlideShow; using ITweakParticle = metaforce::MP1::CTweakParticle; using ITweakBall = metaforce::MP1::CTweakBall; using ITweakGuiColors = metaforce::MP1::CTweakGuiColors; using ITweakPlayerRes = metaforce::MP1::CTweakPlayerRes; using ITweakTargeting = metaforce::MP1::CTweakTargeting; #else using ITweakGame = metaforce::Tweaks::ITweakGame; using ITweakPlayer = metaforce::Tweaks::ITweakPlayer; using ITweakPlayerControl = metaforce::Tweaks::ITweakPlayerControl; using ITweakPlayerGun = metaforce::Tweaks::ITweakPlayerGun; using ITweakGunRes = metaforce::Tweaks::ITweakGunRes; using ITweakAutoMapper = metaforce::Tweaks::ITweakAutoMapper; using ITweakGui = metaforce::Tweaks::ITweakGui; using ITweakSlideShow = metaforce::Tweaks::ITweakSlideShow; using ITweakParticle = metaforce::Tweaks::ITweakParticle; using ITweakBall = metaforce::Tweaks::ITweakBall; using ITweakGuiColors = metaforce::Tweaks::ITweakGuiColors; using ITweakPlayerRes = metaforce::Tweaks::ITweakPlayerRes; using ITweakTargeting = metaforce::Tweaks::ITweakTargeting; #endif extern ITweakGame* g_tweakGame; extern ITweakPlayer* g_tweakPlayer; extern ITweakPlayerControl* g_tweakPlayerControl; extern ITweakPlayerControl* g_tweakPlayerControlAlt; extern ITweakPlayerControl* g_currentPlayerControl; extern ITweakPlayerGun* g_tweakPlayerGun; extern ITweakGunRes* g_tweakGunRes; extern ITweakPlayerRes* g_tweakPlayerRes; extern ITweakTargeting* g_tweakTargeting; extern ITweakAutoMapper* g_tweakAutoMapper; extern ITweakGui* g_tweakGui; extern ITweakSlideShow* g_tweakSlideShow; extern ITweakParticle* g_tweakParticle; extern ITweakBall* g_tweakBall; extern ITweakGuiColors* g_tweakGuiColors; } // namespace metaforce ================================================ FILE: Runtime/GameObjectLists.cpp ================================================ #include "Runtime/GameObjectLists.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/World/CGameLight.hpp" #include "Runtime/World/CPatterned.hpp" #include "Runtime/World/CScriptAiJumpPoint.hpp" #include "Runtime/World/CScriptCoverPoint.hpp" #include "Runtime/World/CScriptDoor.hpp" #include "Runtime/World/CScriptPlatform.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { CActorList::CActorList() : CObjectList(EGameObjectList::Actor) {} bool CActorList::IsQualified(const CEntity& ent) const { return TCastToConstPtr(ent).IsValid(); } CPhysicsActorList::CPhysicsActorList() : CObjectList(EGameObjectList::PhysicsActor) {} bool CPhysicsActorList::IsQualified(const CEntity& ent) const { return TCastToConstPtr(ent).IsValid(); } CGameCameraList::CGameCameraList() : CObjectList(EGameObjectList::GameCamera) {} bool CGameCameraList::IsQualified(const CEntity& ent) const { return TCastToConstPtr(ent).IsValid(); } CListeningAiList::CListeningAiList() : CObjectList(EGameObjectList::ListeningAi) {} bool CListeningAiList::IsQualified(const CEntity& ent) const { const TCastToConstPtr ai(ent); return ai && ai->IsListening(); } CAiWaypointList::CAiWaypointList() : CObjectList(EGameObjectList::AiWaypoint) {} bool CAiWaypointList::IsQualified(const CEntity& ent) const { return TCastToConstPtr(ent) || TCastToConstPtr(ent); } CPlatformAndDoorList::CPlatformAndDoorList() : CObjectList(EGameObjectList::PlatformAndDoor) {} bool CPlatformAndDoorList::IsQualified(const CEntity& ent) const { return IsDoor(ent) || IsPlatform(ent); } bool CPlatformAndDoorList::IsDoor(const CEntity& ent) const { return TCastToConstPtr(ent).IsValid(); } bool CPlatformAndDoorList::IsPlatform(const CEntity& ent) const { return TCastToConstPtr(ent).IsValid(); } CGameLightList::CGameLightList() : CObjectList(EGameObjectList::GameLight) {} bool CGameLightList::IsQualified(const CEntity& lt) const { return TCastToConstPtr(lt).IsValid(); } } // namespace metaforce ================================================ FILE: Runtime/GameObjectLists.hpp ================================================ #pragma once #include "Runtime/CObjectList.hpp" namespace metaforce { class CActorList : public CObjectList { public: CActorList(); bool IsQualified(const CEntity&) const override; }; class CPhysicsActorList : public CObjectList { public: CPhysicsActorList(); bool IsQualified(const CEntity&) const override; }; class CGameCameraList : public CObjectList { public: CGameCameraList(); bool IsQualified(const CEntity&) const override; }; class CListeningAiList : public CObjectList { public: CListeningAiList(); bool IsQualified(const CEntity&) const override; }; class CAiWaypointList : public CObjectList { public: CAiWaypointList(); bool IsQualified(const CEntity&) const override; }; class CPlatformAndDoorList : public CObjectList { public: CPlatformAndDoorList(); bool IsQualified(const CEntity&) const override; bool IsDoor(const CEntity&) const; bool IsPlatform(const CEntity&) const; }; class CGameLightList : public CObjectList { public: CGameLightList(); bool IsQualified(const CEntity&) const override; }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeMaterial.cpp ================================================ #include "Graphics/CCubeMaterial.hpp" #include "GameGlobalObjects.hpp" #include "Graphics/CCubeModel.hpp" #include "Graphics/CCubeRenderer.hpp" #include "Graphics/CCubeSurface.hpp" #include "Graphics/CGX.hpp" #include "Graphics/CModel.hpp" #include "Logging.hpp" namespace metaforce { static u32 sReflectionType = 0; static u32 sLastMaterialUnique = UINT32_MAX; static const u8* sLastMaterialCached = nullptr; static const CCubeModel* sLastModelCached = nullptr; static const CCubeModel* sRenderingModel = nullptr; static float sReflectionAlpha = 0.f; void CCubeMaterial::SetCurrent(const CModelFlags& flags, const CCubeSurface& surface, CCubeModel& model) { if (sLastMaterialCached == x0_data) { if (sReflectionType == 1) { if (sLastModelCached == sRenderingModel) { return; } } else if (sReflectionType != 2) { return; } } if (CCubeModel::sRenderModelBlack) { SetCurrentBlack(); return; } sRenderingModel = &model; sLastMaterialCached = x0_data; u32 numIndStages = 0; const auto matFlags = GetFlags(); const u8* materialDataCur = x0_data; const bool reflection = bool( matFlags & (Flags(CCubeMaterialFlagBits::fSamusReflection) | CCubeMaterialFlagBits::fSamusReflectionSurfaceEye)); if (reflection) { if (!(matFlags & CCubeMaterialFlagBits::fSamusReflectionSurfaceEye)) { EnsureViewDepStateCached(nullptr); } else { EnsureViewDepStateCached(&surface); } } u32 texCount = SBig(*reinterpret_cast(materialDataCur + 4)); if (flags.x2_flags & CModelFlagBits::NoTextureLock) { materialDataCur += (2 + texCount) * 4; } else { materialDataCur += 8; for (u32 i = 0; i < texCount; ++i) { u32 texIdx = SBig(*reinterpret_cast(materialDataCur)); model.GetTexture(texIdx)->Load(static_cast(i), EClampMode::Repeat); materialDataCur += 4; } } auto groupIdx = SBig(*reinterpret_cast(materialDataCur + 4)); if (sLastMaterialUnique != UINT32_MAX && sLastMaterialUnique == groupIdx && sReflectionType == 0) { return; } sLastMaterialUnique = groupIdx; u32 vatFlags = SBig(*reinterpret_cast(materialDataCur)); CGX::SetVtxDescv_Compressed(vatFlags); materialDataCur += 8; bool packedLightMaps = matFlags.IsSet(CCubeMaterialFlagBits::fLightmapUvArray); if (packedLightMaps != CCubeModel::sUsingPackedLightmaps) { model.SetUsingPackedLightmaps(packedLightMaps); } u32 finalKColorCount = 0; if (matFlags & CCubeMaterialFlagBits::fKonstValues) { u32 konstCount = SBig(*reinterpret_cast(materialDataCur)); finalKColorCount = konstCount; materialDataCur += 4; for (u32 i = 0; i < konstCount; ++i) { u32 kColor = SBig(*reinterpret_cast(materialDataCur)); materialDataCur += 4; CGX::SetTevKColor(static_cast(i), kColor); } } u32 blendFactors = SBig(*reinterpret_cast(materialDataCur)); materialDataCur += 4; if (g_Renderer->IsInAreaDraw()) { CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR); } else { SetupBlendMode(blendFactors, flags, matFlags.IsSet(CCubeMaterialFlagBits::fAlphaTest)); } bool indTex = matFlags.IsSet(CCubeMaterialFlagBits::fSamusReflectionIndirectTexture); u32 indTexSlot = 0; if (indTex) { indTexSlot = SBig(*reinterpret_cast(materialDataCur)); materialDataCur += 4; } HandleDepth(flags.x2_flags, matFlags); u32 chanCount = SBig(*reinterpret_cast(materialDataCur)); materialDataCur += 4; u32 firstChan = SBig(*reinterpret_cast(materialDataCur)); materialDataCur += 4 * chanCount; u32 finalNumColorChans = HandleColorChannels(chanCount, firstChan); u32 firstTev = 0; if (CCubeModel::sRenderModelShadow) firstTev = 2; u32 matTevCount = SBig(*reinterpret_cast(materialDataCur)); materialDataCur += 4; u32 finalTevCount = matTevCount; const u32* texMapTexCoordFlags = reinterpret_cast(materialDataCur + matTevCount * 20); const u32* tcgs = reinterpret_cast(texMapTexCoordFlags + matTevCount); bool usesTevReg2 = false; u32 finalCCFlags = 0; u32 finalACFlags = 0; if (g_Renderer->IsThermalVisorActive()) { finalTevCount = firstTev + 1; u32 ccFlags = SBig(*reinterpret_cast(materialDataCur + 8)); finalCCFlags = ccFlags; auto outputReg = static_cast(ccFlags >> 9 & 0x3); if (outputReg == GX_TEVREG0) { materialDataCur += 20; texMapTexCoordFlags += 1; finalCCFlags = SBig(*reinterpret_cast(materialDataCur + 8)); GXSetTevColor(GX_TEVREG0, GXColor{0xc0, 0xc0, 0xc0, 0xc0}); } finalACFlags = SBig(*reinterpret_cast(materialDataCur + 12)); HandleTev(firstTev, reinterpret_cast(materialDataCur), texMapTexCoordFlags, CCubeModel::sRenderModelShadow); usesTevReg2 = false; } else { finalTevCount = firstTev + matTevCount; for (u32 i = firstTev; i < finalTevCount; ++i) { HandleTev(i, reinterpret_cast(materialDataCur), texMapTexCoordFlags, CCubeModel::sRenderModelShadow && i == firstTev); u32 ccFlags = SBig(*reinterpret_cast(materialDataCur + 8)); finalCCFlags = ccFlags; finalACFlags = SBig(*reinterpret_cast(materialDataCur + 12)); auto outputReg = static_cast(ccFlags >> 9 & 0x3); if (outputReg == GX_TEVREG2) { usesTevReg2 = true; } materialDataCur += 20; texMapTexCoordFlags += 1; } } u32 tcgCount = 0; if (g_Renderer->IsThermalVisorActive()) { u32 fullTcgCount = SBig(*tcgs); tcgCount = std::min(fullTcgCount, 2u); for (u32 i = 0; i < tcgCount; ++i) { CGX::SetTexCoordGen(GXTexCoordID(i), SBig(tcgs[i + 1])); } tcgs += fullTcgCount + 1; } else { tcgCount = SBig(*tcgs); for (u32 i = 0; i < tcgCount; ++i) { CGX::SetTexCoordGen(GXTexCoordID(i), SBig(tcgs[i + 1])); } tcgs += tcgCount + 1; } const u32* uvAnim = tcgs; u32 animCount = SBig(uvAnim[1]); uvAnim += 2; u32 texMtx = GX_TEXMTX0; u32 pttTexMtx = GX_PTTEXMTX0; for (u32 i = 0; i < animCount; ++i) { u32 size = HandleAnimatedUV(uvAnim, static_cast(texMtx), static_cast(pttTexMtx)); if (size == 0) break; uvAnim += size; texMtx += 3; pttTexMtx += 3; } if (flags.x0_blendMode != 0) { HandleTransparency(finalTevCount, finalKColorCount, flags, blendFactors, finalCCFlags, finalACFlags); } if (reflection) { if (sReflectionAlpha > 0.f) { u32 additionalTevs = 0; if (indTex) { additionalTevs = HandleReflection(usesTevReg2, indTexSlot, 0, finalTevCount, texCount, tcgCount, finalKColorCount, finalCCFlags, finalACFlags); numIndStages = 1; tcgCount += 2; } else { additionalTevs = HandleReflection(usesTevReg2, 255, 0, finalTevCount, texCount, tcgCount, finalKColorCount, finalCCFlags, finalACFlags); tcgCount += 1; } texCount += 1; finalTevCount += additionalTevs; finalKColorCount += 1; } else if (((finalCCFlags >> 9) & 0x3) != 0) { DoPassthru(finalTevCount); finalTevCount += 1; } } if (CCubeModel::sRenderModelShadow) { DoModelShadow(texCount, tcgCount); tcgCount += 1; } CGX::SetNumIndStages(numIndStages); CGX::SetNumTevStages(finalTevCount); CGX::SetNumTexGens(tcgCount); CGX::SetNumChans(finalNumColorChans); } void CCubeMaterial::SetCurrentBlack() { const auto flags = GetFlags(); const auto vatFlags = GetVatFlags(); if (flags.IsSet(CCubeMaterialFlagBits::fDepthSorting) || flags.IsSet(CCubeMaterialFlagBits::fAlphaTest)) { CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ZERO, GX_BL_ONE, GX_LO_CLEAR); } else { CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR); } CGX::SetVtxDescv_Compressed(vatFlags); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO /* ? CC_ONE */); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO /* ? CA_KONST */); CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_1); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_POS, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetNumTevStages(1); CGX::SetNumChans(0); CGX::SetNumTexGens(1); CGX::SetNumIndStages(0); } void CCubeMaterial::SetupBlendMode(u32 blendFactors, const CModelFlags& flags, bool alphaTest) { auto newSrcFactor = static_cast(blendFactors & 0xffff); auto newDstFactor = static_cast(blendFactors >> 16 & 0xffff); if (alphaTest) { // discard fragments with alpha < 0.25 CGX::SetAlphaCompare(GX_GEQUAL, 64, GX_AOP_OR, GX_NEVER, 0); newSrcFactor = GX_BL_ONE; newDstFactor = GX_BL_ZERO; } else { CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0); } if (flags.x0_blendMode > 4 && newSrcFactor == GX_BL_ONE) { newSrcFactor = GX_BL_SRCALPHA; if (newDstFactor == GX_BL_ZERO) { newDstFactor = flags.x0_blendMode > 6 ? GX_BL_ONE : GX_BL_INVSRCALPHA; } } CGX::SetBlendMode(GX_BM_BLEND, newSrcFactor, newDstFactor, GX_LO_CLEAR); } void CCubeMaterial::HandleDepth(CModelFlagsFlags modelFlags, CCubeMaterialFlags matFlags) { GXCompare func = GX_NEVER; if (!(modelFlags & CModelFlagBits::DepthTest)) { func = GX_ALWAYS; } else if (modelFlags & CModelFlagBits::DepthGreater) { func = modelFlags & CModelFlagBits::DepthNonInclusive ? GX_GREATER : GX_GEQUAL; } else { func = modelFlags & CModelFlagBits::DepthNonInclusive ? GX_LESS : GX_LEQUAL; } bool depthWrite = modelFlags & CModelFlagBits::DepthUpdate && matFlags & CCubeMaterialFlagBits::fDepthWrite; CGX::SetZMode(true, func, depthWrite); } void CCubeMaterial::ResetCachedMaterials() { KillCachedViewDepState(); sLastMaterialUnique = UINT32_MAX; sRenderingModel = nullptr; sLastMaterialCached = nullptr; } void CCubeMaterial::KillCachedViewDepState() { sLastModelCached = nullptr; } void CCubeMaterial::EnsureViewDepStateCached(const CCubeSurface* surface) { // TODO if ((surface != nullptr || sLastModelCached != sRenderingModel) && sRenderingModel != nullptr) { sLastModelCached = sRenderingModel; if (surface == nullptr) { sReflectionType = 1; } else { sReflectionType = 2; } if (g_Renderer->IsReflectionDirty()) { } else { g_Renderer->SetReflectionDirty(true); } } } u32 CCubeMaterial::HandleColorChannels(u32 chanCount, u32 firstChan) { static constexpr GXColor sGXBlack = {0, 0, 0, 0}; static constexpr GXColor sGXWhite = {0xFF, 0xFF, 0xFF, 0xFF}; if (CCubeModel::sRenderModelShadow) { if (chanCount != 0) { CGX::SetChanAmbColor(CGX::EChannelId::Channel1, sGXBlack); CGX::SetChanMatColor(CGX::EChannelId::Channel1, sGXWhite); CGX::SetChanCtrl(CGX::EChannelId::Channel1, true, GX_SRC_REG, GX_SRC_REG, CCubeModel::sChannel1EnableLightMask, GX_DF_CLAMP, GX_AF_SPOT); const auto chan0Lights = CGraphics::GetLightMask() & ~CCubeModel::sChannel0DisableLightMask; CGX::SetChanCtrl_Compressed(CGX::EChannelId::Channel0, chan0Lights, firstChan); if (chan0Lights.any()) { CGX::SetChanMatColor(CGX::EChannelId::Channel0, sGXWhite); } else { CGX::SetChanMatColor(CGX::EChannelId::Channel0, CGX::GetChanAmbColor(CGX::EChannelId::Channel0)); } } return 2; } if (chanCount == 2) { CGX::SetChanAmbColor(CGX::EChannelId::Channel1, sGXBlack); CGX::SetChanMatColor(CGX::EChannelId::Channel1, sGXWhite); } else { CGX::SetChanCtrl(CGX::EChannelId::Channel1, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); } if (chanCount >= 1) { const auto lightMask = CGraphics::GetLightMask(); CGX::SetChanCtrl_Compressed(CGX::EChannelId::Channel0, lightMask, firstChan); if (lightMask.any()) { CGX::SetChanMatColor(CGX::EChannelId::Channel0, sGXWhite); } else { CGX::SetChanMatColor(CGX::EChannelId::Channel0, CGX::GetChanAmbColor(CGX::EChannelId::Channel0)); } } else { CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); } return chanCount; } void CCubeMaterial::HandleTev(u32 tevCur, const u32* materialDataCur, const u32* texMapTexCoordFlags, bool shadowMapsEnabled) { const u32 colorArgs = shadowMapsEnabled ? 0x7a04f : SBig(materialDataCur[0]); const u32 alphaArgs = SBig(materialDataCur[1]); const u32 colorOps = SBig(materialDataCur[2]); const u32 alphaOps = SBig(materialDataCur[3]); const auto stage = static_cast(tevCur); CGX::SetStandardDirectTev_Compressed(stage, colorArgs, alphaArgs, colorOps, alphaOps); u32 tmtcFlags = SBig(*texMapTexCoordFlags); u32 matFlags = SBig(materialDataCur[4]); CGX::SetTevOrder(stage, static_cast(tmtcFlags & 0xFF), static_cast(tmtcFlags >> 8 & 0xFF), static_cast(matFlags & 0xFF)); CGX::SetTevKColorSel(stage, static_cast(matFlags >> 0x8 & 0xFF)); CGX::SetTevKAlphaSel(stage, static_cast(matFlags >> 0x10 & 0xFF)); } u32 CCubeMaterial::HandleAnimatedUV(const u32* uvAnim, GXTexMtx texMtx, GXPTTexMtx ptTexMtx) { static const Mtx postMtx = { {0.5f, 0.0f, 0.0f, 0.5f}, {0.0f, 0.0f, 0.5f, 0.5f}, {0.0f, 0.0f, 0.0f, 1.0f}, }; static Mtx translateMtx = { {1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}, }; u32 type = SBig(*uvAnim); const float* params = reinterpret_cast(uvAnim + 1); switch (type) { case 0: { auto xf = CGraphics::GetViewMatrix().quickInverse().multiplyIgnoreTranslation(CGraphics::GetModelMatrix()); xf.origin.zeroOut(); Mtx mtx; xf.toCStyleMatrix(mtx); GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4); GXLoadTexMtxImm(postMtx, ptTexMtx, GX_MTX3x4); return 1; } case 1: { auto xf = CGraphics::GetViewMatrix().quickInverse() * CGraphics::GetModelMatrix(); Mtx mtx; xf.toCStyleMatrix(mtx); GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4); GXLoadTexMtxImm(postMtx, ptTexMtx, GX_MTX3x4); return 1; } case 2: { const float f1 = SBig(params[0]); const float f2 = SBig(params[1]); const float f3 = SBig(params[2]); const float f4 = SBig(params[3]); const float seconds = CGraphics::GetSecondsMod900(); translateMtx[0][3] = f1 + seconds * f3; translateMtx[1][3] = f2 + seconds * f4; GXLoadTexMtxImm(translateMtx, texMtx, GX_MTX3x4); return 5; } case 3: { const float f1 = SBig(params[0]); const float f2 = SBig(params[1]); const float seconds = CGraphics::GetSecondsMod900(); const float angle = f1 + seconds * f2; const float asin = std::sin(angle); const float acos = std::cos(angle); Mtx mtx = { {acos, -asin, 0.f, (1.f - (acos - asin)) * 0.5f}, {asin, acos, 0.f, (1.f - (asin + acos)) * 0.5f}, {0.f, 0.f, 1.f, 0.f}, }; GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4); return 3; } case 4: case 5: { const float f1 = SBig(params[0]); const float f2 = SBig(params[1]); const float f3 = SBig(params[2]); const float f4 = SBig(params[3]); const float value = (f4 + CGraphics::GetSecondsMod900()) * f1 * f3; const float fmod = std::fmod(value, 1.f); const float fs = std::trunc(fmod * f2); const float v2 = fs * f3; if (type == 4) { translateMtx[0][3] = v2; translateMtx[1][3] = 0.f; } else { translateMtx[0][3] = 0.f; translateMtx[1][3] = v2; } GXLoadTexMtxImm(translateMtx, texMtx, GX_MTX3x4); return 5; } case 6: { static const Mtx sTexMtx = { {0.f, 0.f, 0.f, 0.f}, {0.f, 0.f, 0.f, 0.f}, {0.f, 0.f, 0.f, 0.f}, }; static const Mtx sPtMtx = { {0.5f, 0.f, 0.f, 0.f}, {0.f, 0.f, 0.5f, 0.f}, {0.f, 0.f, 0.f, 1.f}, }; const zeus::CTransform& mm = CGraphics::GetModelMatrix(); Mtx tmpTexMtx; Mtx tmpPtMtx; memcpy(&tmpTexMtx, &sTexMtx, sizeof(Mtx)); tmpTexMtx[0][0] = mm.basis[0][0]; tmpTexMtx[0][1] = mm.basis[1][0]; tmpTexMtx[0][2] = mm.basis[2][0]; tmpTexMtx[1][0] = mm.basis[0][1]; tmpTexMtx[1][1] = mm.basis[1][1]; tmpTexMtx[1][2] = mm.basis[2][1]; tmpTexMtx[2][0] = mm.basis[0][2]; tmpTexMtx[2][1] = mm.basis[1][2]; tmpTexMtx[2][2] = mm.basis[2][2]; memcpy(&tmpPtMtx, &sPtMtx, sizeof(Mtx)); tmpPtMtx[0][3] = mm.origin.x() * 0.05f; tmpPtMtx[1][3] = mm.origin.y() * 0.05f; GXLoadTexMtxImm(tmpTexMtx, texMtx, GX_MTX3x4); GXLoadTexMtxImm(tmpPtMtx, ptTexMtx, GX_MTX3x4); return 1; } case 7: { static const Mtx sPtMtx = { {0.f, 0.f, 0.f, 0.f}, {0.f, 0.f, 0.f, 0.f}, {0.f, 0.f, 0.f, 1.f}, }; const zeus::CTransform& vm = CGraphics::GetViewMatrix(); zeus::CTransform xf = vm.quickInverse().multiplyIgnoreTranslation(CGraphics::GetModelMatrix()); float v = SBig(params[0]) / 2.f; float v03 = 0.025f * (vm.origin.x() + vm.origin.y()) * SBig(params[1]); float v13 = 0.05f * vm.origin.z() * SBig(params[1]); float v03f = std::fmod(v03, 1.f); float v13f = std::fmod(v13, 1.f); xf.origin.zeroOut(); Mtx mtx; xf.toCStyleMatrix(mtx); Mtx tmpPtMtx; memcpy(&tmpPtMtx, &sPtMtx, sizeof(Mtx)); tmpPtMtx[0][0] = v; tmpPtMtx[0][3] = v03f; tmpPtMtx[1][2] = v; tmpPtMtx[1][3] = v13f; GXLoadTexMtxImm(mtx, texMtx, GX_MTX3x4); GXLoadTexMtxImm(tmpPtMtx, ptTexMtx, GX_MTX3x4); return 3; } default: return 0; } } void CCubeMaterial::HandleTransparency(u32& finalTevCount, u32& finalKColorCount, const CModelFlags& modelFlags, u32 blendFactors, u32& finalCCFlags, u32& finalACFlags) { if (modelFlags.x0_blendMode == 2) { u16 dstFactor = blendFactors >> 16 & 0xffff; if (dstFactor == 1) { return; } } if (modelFlags.x0_blendMode == 3) { // Stage outputting splatted KAlpha as color to reg0 auto stage = static_cast(finalTevCount); CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST); CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV); CGX::SetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVREG0); CGX::SetTevKColorSel(stage, static_cast(finalKColorCount + GX_TEV_KCSEL_K0_A)); CGX::SetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetTevDirect(stage); // Stage interpolating from splatted KAlpha using KColor stage = static_cast(stage + 1); CGX::SetTevColorIn(stage, GX_CC_CPREV, GX_CC_C0, GX_CC_KONST, GX_CC_ZERO); CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV); CGX::SetTevKColorSel(stage, static_cast(finalKColorCount + GX_TEV_KCSEL_K0)); CGX::SetStandardTevColorAlphaOp(stage); CGX::SetTevDirect(stage); CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetTevKColor(static_cast(finalKColorCount), modelFlags.x4_color); finalKColorCount += 1; finalTevCount += 2; } else { auto stage = static_cast(finalTevCount); if (modelFlags.x0_blendMode == 8) { CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); // Set KAlpha } else { CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_KONST, GX_CA_APREV, GX_CA_ZERO); // Mul KAlpha } if (modelFlags.x0_blendMode == 2) { CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_ONE, GX_CC_CPREV, GX_CC_KONST); // Add KColor } else { CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_KONST, GX_CC_CPREV, GX_CC_ZERO); // Mul KColor } CGX::SetStandardTevColorAlphaOp(stage); finalCCFlags = 0x100; // Just clamp, output prev reg finalACFlags = 0x100; CGX::SetTevDirect(stage); CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetTevKColor(static_cast(finalKColorCount), modelFlags.x4_color); CGX::SetTevKColorSel(stage, static_cast(finalKColorCount + GX_TEV_KCSEL_K0)); CGX::SetTevKAlphaSel(stage, static_cast(finalKColorCount + GX_TEV_KASEL_K0_A)); finalTevCount += 1; finalKColorCount += 1; } } u32 CCubeMaterial::HandleReflection(bool usesTevReg2, u32 indTexSlot, u32 r5, u32 finalTevCount, u32 texCount, u32 tcgCount, u32 finalKColorCount, u32& finalCCFlags, u32& finalACFlags) { u32 out = 0; GXTevColorArg colorArg = GX_CC_KONST; if (usesTevReg2) { colorArg = GX_CC_C2; const auto stage = static_cast(finalTevCount); CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_C2, GX_CC_KONST, GX_CC_ZERO); CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A2); CGX::SetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVREG2); CGX::SetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVREG2); CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_ZERO); out = 1; } CGX::SetTevKColor(static_cast(finalKColorCount), zeus::CColor{sReflectionAlpha, sReflectionAlpha}); CGX::SetTevKColorSel(static_cast(finalTevCount), static_cast(GX_TEV_KCSEL_K0 + finalKColorCount)); const auto stage = static_cast(finalTevCount + out); // tex = g_Renderer->GetRealReflection // tex.Load(texCount, 0) // TODO finalACFlags = 0; finalCCFlags = 0; // aurora::gfx::set_tev_order(stage, ...) return out; //+ 1; } void CCubeMaterial::DoPassthru(u32 finalTevCount) { const auto stage = static_cast(finalTevCount); CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV); CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV); CGX::SetTevOrder(stage, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetTevDirect(stage); CGX::SetStandardTevColorAlphaOp(stage); } void CCubeMaterial::DoModelShadow(u32 texCount, u32 tcgCount) { CCubeModel::sShadowTexture->Load(static_cast(texCount), EClampMode::Repeat); const auto& xf = CCubeModel::sTextureProjectionTransform; Mtx mtx = { {xf.basis[0][0], xf.basis[1][0], xf.basis[2][0], xf.origin.x()}, {xf.basis[0][2], xf.basis[1][2], xf.basis[2][2], xf.origin.z()}, {0.f, 0.f, 0.f, 1.f}, }; GXLoadTexMtxImm(mtx, GX_TEXMTX5, GX_MTX3x4); CGX::SetTexCoordGen(static_cast(tcgCount), GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX5, GX_FALSE, GX_PTIDENTITY); CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0); CGX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA); CGX::SetTevOrder(GX_TEVSTAGE0, static_cast(tcgCount), static_cast(texCount), GX_COLOR1A1); CGX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0); CGX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVREG0); CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_RASC, GX_CC_ONE, GX_CC_C0); CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_RASA, GX_CA_KONST, GX_CA_A0); CGX::SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_1); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); } static GXTevStageID sCurrentTevStage = GX_MAX_TEVSTAGE; void CCubeMaterial::EnsureTevsDirect() { if (sCurrentTevStage == GX_MAX_TEVSTAGE) { return; } CGX::SetNumIndStages(0); CGX::SetTevDirect(sCurrentTevStage); sCurrentTevStage = GX_MAX_TEVSTAGE; } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeMaterial.hpp ================================================ #pragma once #include "CToken.hpp" #include "GCNTypes.hpp" #include "Graphics/CTexture.hpp" #include "Graphics/CModel.hpp" #include "IObjectStore.hpp" namespace metaforce { class CCubeModel; class CCubeSurface; enum class CCubeMaterialFlagBits : u32 { fKonstValues = 0x8, fDepthSorting = 0x10, fAlphaTest = 0x20, fSamusReflection = 0x40, fDepthWrite = 0x80, fSamusReflectionSurfaceEye = 0x100, fShadowOccluderMesh = 0x200, fSamusReflectionIndirectTexture = 0x400, fLightmap = 0x800, fLightmapUvArray = 0x2000, fTextureSlotMask = 0xffff0000 }; using CCubeMaterialFlags = Flags; class CCubeMaterial { const u8* x0_data; public: explicit CCubeMaterial(const u8* data) : x0_data(data) {} void SetCurrent(const CModelFlags& flags, const CCubeSurface& surface, CCubeModel& model); [[nodiscard]] u32 GetCompressedBlend() { const u32* ptr = reinterpret_cast(x0_data + (GetTextureCount() * 4) + 16); if (GetFlags() & CCubeMaterialFlagBits::fKonstValues) { ptr += SBig(*ptr) + 1; } return SBig(*ptr); } [[nodiscard]] CCubeMaterialFlags GetFlags() const { return CCubeMaterialFlags(SBig(*reinterpret_cast(x0_data))); } [[nodiscard]] u32 GetVatFlags() const { return SBig(*reinterpret_cast(x0_data + 8 + (GetTextureCount() * 4))); } [[nodiscard]] u32 GetUsedTextureSlots() const { return static_cast(GetFlags()) >> 16; } [[nodiscard]] u32 GetTextureCount() const { return SBig(*reinterpret_cast(x0_data + 4)); } [[nodiscard]] u32 GetVertexDesc() const { return SBig(*reinterpret_cast(x0_data + (GetTextureCount() * 4) + 8)); } static void ResetCachedMaterials(); static void EnsureViewDepStateCached(const CCubeSurface* surface); static void KillCachedViewDepState(); static void EnsureTevsDirect(); private: void SetCurrentBlack(); static void SetupBlendMode(u32 blendFactors, const CModelFlags& flags, bool alphaTest); static void HandleDepth(CModelFlagsFlags modelFlags, CCubeMaterialFlags matFlags); static u32 HandleColorChannels(u32 chanCount, u32 firstChan); static void HandleTev(u32 tevCur, const u32* materialDataCur, const u32* texMapTexCoordFlags, bool shadowMapsEnabled); static u32 HandleAnimatedUV(const u32* uvAnim, GXTexMtx texMtx, GXPTTexMtx pttTexMtx); static void HandleTransparency(u32& finalTevCount, u32& finalKColorCount, const CModelFlags& modelFlags, u32 blendFactors, u32& finalCCFlags, u32& finalACFlags); static u32 HandleReflection(bool usesTevReg2, u32 indTexSlot, u32 r5, u32 finalTevCount, u32 texCount, u32 tcgCount, u32 finalKColorCount, u32& finalCCFlags, u32& finalACFlags); static void DoPassthru(u32 finalTevCount); static void DoModelShadow(u32 texCount, u32 tcgCount); }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeModel.cpp ================================================ #include "CCubeModel.hpp" #include "CSimplePool.hpp" #include "Graphics/CCubeMaterial.hpp" #include "Graphics/CCubeSurface.hpp" #include "Graphics/CGraphics.hpp" #include "Graphics/CModel.hpp" #include "Graphics/CGX.hpp" namespace metaforce { bool CCubeModel::sRenderModelBlack = false; bool CCubeModel::sRenderModelShadow = false; bool CCubeModel::sUsingPackedLightmaps = false; CTexture* CCubeModel::sShadowTexture = nullptr; zeus::CTransform CCubeModel::sTextureProjectionTransform; GX::LightMask CCubeModel::sChannel0DisableLightMask; GX::LightMask CCubeModel::sChannel1EnableLightMask; static bool sDrawingOccluders = false; static bool sDrawingWireframe = false; static zeus::CVector3f sPlayerPosition; CCubeModel::CCubeModel(std::vector* surfaces, std::vector>* textures, u8* materialData, std::span positions, std::span colors, std::span normals, std::span texCoords, std::span packedTexCoords, const zeus::CAABox& aabb, u8 flags, bool b1, u32 idx) : x0_modelInstance(surfaces, materialData, positions, colors, normals, texCoords, packedTexCoords) , x1c_textures(textures) , x20_worldAABB(aabb) , x40_24_texturesLoaded(!b1) , x41_visorFlags(flags) , x44_idx(idx) { for (auto& surf : *x0_modelInstance.Surfaces()) { surf.SetParent(this); } for (u32 i = x0_modelInstance.Surfaces()->size(); i > 0; --i) { auto& surf = (*x0_modelInstance.Surfaces())[i - 1]; const auto matFlags = GetMaterialByIndex(surf.GetMaterialIndex()).GetFlags(); if (!matFlags.IsSet(CCubeMaterialFlagBits::fDepthSorting)) { surf.SetNextSurface(x38_firstUnsortedSurf); x38_firstUnsortedSurf = &surf; } else { surf.SetNextSurface(x3c_firstSortedSurf); x3c_firstSortedSurf = &surf; } } } CCubeMaterial CCubeModel::GetMaterialByIndex(u32 idx) { u32 materialOffset = 0; const u8* matData = x0_modelInstance.GetMaterialPointer(); matData += (x1c_textures->size() + 1) * 4; if (idx != 0) { materialOffset = SBig(*reinterpret_cast(matData + (idx * 4))); } u32 materialCount = SBig(*reinterpret_cast(matData)); return CCubeMaterial(matData + materialOffset + (materialCount * 4) + 4); } bool CCubeModel::TryLockTextures() { if (!x40_24_texturesLoaded) { bool texturesLoading = false; for (auto& texture : *x1c_textures) { texture.Lock(); bool loadTexture = true; if (!texture.HasReference()) { if (!texture.IsLocked() || texture.IsNull()) { loadTexture = false; } else { texture.GetObj(); } } if (loadTexture) { // if (!texture->LoadToMRAM()) { // texturesLoading = true; // } } } if (!texturesLoading) { x40_24_texturesLoaded = true; } } return x40_24_texturesLoaded; } void CCubeModel::UnlockTextures() { for (auto& token : *x1c_textures) { token.Unlock(); } } void CCubeModel::RemapMaterialData(u8* data, std::vector>& textures) { x0_modelInstance.SetMaterialPointer(data); x1c_textures = &textures; x40_24_texturesLoaded = false; } void CCubeModel::MakeTexturesFromMats(const u8* ptr, std::vector>& textures, IObjectStore* store, bool b1) { const u32* curId = reinterpret_cast(ptr + 4); u32 textureCount = SBig(*reinterpret_cast(ptr)); textures.reserve(textureCount); for (u32 i = 0; i < textureCount; ++i) { textures.emplace_back(store->GetObj({FOURCC('TXTR'), SBig(curId[i])})); if (!b1 && textures.back().IsNull()) { textures.back().GetObj(); } } } void CCubeModel::Draw(const CModelFlags& flags) { CCubeMaterial::KillCachedViewDepState(); SetArraysCurrent(); DrawSurfaces(flags); } void CCubeModel::Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) { CCubeMaterial::KillCachedViewDepState(); SetSkinningArraysCurrent(positions, normals); DrawSurfaces(flags); } void CCubeModel::DrawAlpha(const CModelFlags& flags) { CCubeMaterial::KillCachedViewDepState(); SetArraysCurrent(); DrawAlphaSurfaces(flags); } void CCubeModel::DrawAlphaSurfaces(const CModelFlags& flags) { if (sDrawingWireframe) { const auto* surface = x3c_firstSortedSurf; while (surface != nullptr) { DrawSurfaceWireframe(*surface); surface = surface->GetNextSurface(); } } else if (TryLockTextures()) { const auto* surface = x3c_firstSortedSurf; while (surface != nullptr) { DrawSurface(*surface, flags); surface = surface->GetNextSurface(); } } } void CCubeModel::DrawFlat(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces) { if (positions.empty()) { SetArraysCurrent(); } else { SetSkinningArraysCurrent(positions, normals); } if (surfaces != ESurfaceSelection::Sorted) { const auto* surface = x38_firstUnsortedSurf; while (surface != nullptr) { const auto mat = GetMaterialByIndex(surface->GetMaterialIndex()); CGX::SetVtxDescv_Compressed(mat.GetVertexDesc()); CGX::CallDisplayList(surface->GetDisplayList(), surface->GetDisplayListSize()); surface = surface->GetNextSurface(); } } if (surfaces != ESurfaceSelection::Unsorted) { const auto* surface = x3c_firstSortedSurf; while (surface != nullptr) { const auto mat = GetMaterialByIndex(surface->GetMaterialIndex()); CGX::SetVtxDescv_Compressed(mat.GetVertexDesc()); CGX::CallDisplayList(surface->GetDisplayList(), surface->GetDisplayListSize()); surface = surface->GetNextSurface(); } } } void CCubeModel::DrawNormal(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces) { CGX::SetNumIndStages(0); CGX::SetNumTevStages(1); CGX::SetNumTexGens(1); CGX::SetZMode(true, GX_LEQUAL, true); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ZERO, GX_BL_ONE, GX_LO_CLEAR); DrawFlat(positions, normals, surfaces); } void CCubeModel::DrawNormal(const CModelFlags& flags) { CCubeMaterial::KillCachedViewDepState(); SetArraysCurrent(); DrawNormalSurfaces(flags); } void CCubeModel::DrawNormalSurfaces(const CModelFlags& flags) { if (sDrawingWireframe) { const auto* surface = x38_firstUnsortedSurf; while (surface != nullptr) { DrawSurfaceWireframe(*surface); surface = surface->GetNextSurface(); } } else if (TryLockTextures()) { const auto* surface = x38_firstUnsortedSurf; while (surface != nullptr) { DrawSurface(*surface, flags); surface = surface->GetNextSurface(); } } } void CCubeModel::DrawSurface(const CCubeSurface& surface, const CModelFlags& flags) { auto mat = GetMaterialByIndex(surface.GetMaterialIndex()); if (!mat.GetFlags().IsSet(CCubeMaterialFlagBits::fShadowOccluderMesh) || sDrawingOccluders) { mat.SetCurrent(flags, surface, *this); CGX::CallDisplayList(surface.GetDisplayList(), surface.GetDisplayListSize()); } } void CCubeModel::DrawSurfaces(const CModelFlags& flags) { if (sDrawingWireframe) { const auto* surface = x38_firstUnsortedSurf; while (surface != nullptr) { DrawSurfaceWireframe(*surface); surface = surface->GetNextSurface(); } surface = x3c_firstSortedSurf; while (surface != nullptr) { DrawSurfaceWireframe(*surface); surface = surface->GetNextSurface(); } } else if (TryLockTextures()) { const auto* surface = x38_firstUnsortedSurf; while (surface != nullptr) { DrawSurface(*surface, flags); surface = surface->GetNextSurface(); } surface = x3c_firstSortedSurf; while (surface != nullptr) { DrawSurface(*surface, flags); surface = surface->GetNextSurface(); } } } void CCubeModel::DrawSurfaceWireframe(const CCubeSurface& surface) { auto mat = GetMaterialByIndex(surface.GetMaterialIndex()); // TODO convert vertices to line strips and draw } void CCubeModel::EnableShadowMaps(CTexture& shadowTex, const zeus::CTransform& textureProjXf, GX::LightMask chan0DisableMask, GX::LightMask chan1EnableLightMask) { sRenderModelShadow = true; sShadowTexture = &shadowTex; sTextureProjectionTransform = textureProjXf; sChannel0DisableLightMask = chan0DisableMask; sChannel1EnableLightMask = chan1EnableLightMask; } void CCubeModel::DisableShadowMaps() { sRenderModelShadow = false; } void CCubeModel::SetArraysCurrent() { CGX::SetArray(GX_VA_POS, x0_modelInstance.GetVertexPointer(), 12); CGX::SetArray(GX_VA_NRM, x0_modelInstance.GetNormalPointer(), (x41_visorFlags & 1) != 0 ? 6 : 12); SetStaticArraysCurrent(); } void CCubeModel::SetDrawingOccluders(bool v) { sDrawingOccluders = v; } void CCubeModel::SetModelWireframe(bool v) { sDrawingWireframe = v; } void CCubeModel::SetNewPlayerPositionAndTime(const zeus::CVector3f& pos, const CStopwatch& time) { sPlayerPosition = pos; CCubeMaterial::KillCachedViewDepState(); // TODO time } void CCubeModel::SetRenderModelBlack(bool v) { sRenderModelBlack = v; // TODO another value is set here, but always 0? } void CCubeModel::SetSkinningArraysCurrent(TConstVectorRef positions, TConstVectorRef normals) { // Aurora addition: clear current array to force reupload CGX::ClearArray(GX_VA_POS); CGX::ClearArray(GX_VA_NRM); CGX::SetArray(GX_VA_POS, positions); CGX::SetArray(GX_VA_NRM, normals); // colors unused SetStaticArraysCurrent(); } void CCubeModel::SetStaticArraysCurrent() { if (!x0_modelInstance.GetColorPointer().empty()) { CGX::SetArray(GX_VA_CLR0, x0_modelInstance.GetColorPointer(), 4); } const auto packedTexCoords = x0_modelInstance.GetPackedTCPointer(); const auto texCoords = x0_modelInstance.GetTCPointer(); sUsingPackedLightmaps = !packedTexCoords.empty(); if (sUsingPackedLightmaps) { CGX::SetArray(GX_VA_TEX0, packedTexCoords, 4); } else { CGX::SetArray(GX_VA_TEX0, texCoords, 8); } for (int i = GX_VA_TEX1; i <= GX_VA_TEX7; ++i) { CGX::SetArray(static_cast(i), texCoords, 8); } CCubeMaterial::KillCachedViewDepState(); } void CCubeModel::SetUsingPackedLightmaps(bool v) { sUsingPackedLightmaps = v; if (v) { CGX::SetArray(GX_VA_TEX0, x0_modelInstance.GetPackedTCPointer(), 4); } else { CGX::SetArray(GX_VA_TEX0, x0_modelInstance.GetTCPointer(), 8); } } template <> aurora::Vec2 cinput_stream_helper(CInputStream& in) { const auto x = in.ReadFloat(); const auto y = in.ReadFloat(); return {x, y}; } template <> aurora::Vec3 cinput_stream_helper(CInputStream& in) { const auto x = in.ReadFloat(); const auto y = in.ReadFloat(); const auto z = in.ReadFloat(); return {x, y, z}; } template <> aurora::Vec2 cinput_stream_helper(CInputStream& in) { const auto x = in.ReadUint16(); const auto y = in.ReadUint16(); return {x, y}; } template <> aurora::Vec3 cinput_stream_helper(CInputStream& in) { const auto x = in.ReadInt16(); const auto y = in.ReadInt16(); const auto z = in.ReadInt16(); return {x, y, z}; } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeModel.hpp ================================================ #pragma once #include #include #include #include #include "CStopwatch.hpp" #include "CToken.hpp" #include "GCNTypes.hpp" #include "Graphics/CTexture.hpp" #include "IObjectStore.hpp" namespace metaforce { class CCubeSurface; class CCubeMaterial; struct CModelFlags; enum class ESurfaceSelection { Unsorted, Sorted, All, }; // These parameters were originally float* using TVectorRef = std::vector>*; using TConstVectorRef = std::span>; template std::span byte_span(const std::vector& vec) { return std::span(reinterpret_cast(vec.data()), vec.size() * sizeof(T)); } class CCubeModel { friend class CModel; friend class CCubeMaterial; private: class ModelInstance { std::vector* x0_surfacePtrs; // was rstl::vector* u8* x4_materialData; // std::span x8_positions; // was void* std::span xc_normals; // was void* std::span x10_colors; // was void* std::span x14_texCoords; // was void* std::span x18_packedTexCoords; // was void* public: ModelInstance(std::vector* surfaces, u8* material, std::span positions, std::span colors, std::span normals, std::span texCoords, std::span packedTexCoords) : x0_surfacePtrs(surfaces) , x4_materialData(material) , x8_positions(positions) , xc_normals(normals) , x10_colors(colors) , x14_texCoords(texCoords) , x18_packedTexCoords(packedTexCoords) {} /* * These functions have been slightly modified from their original to return the actual vector instead of a raw * pointer */ [[nodiscard]] std::vector* Surfaces() const { return x0_surfacePtrs; } [[nodiscard]] u8* GetMaterialPointer() const { return x4_materialData; } void SetMaterialPointer(u8* mat) { x4_materialData = mat; } [[nodiscard]] std::span GetVertexPointer() const { return x8_positions; } [[nodiscard]] std::span GetNormalPointer() const { return xc_normals; } [[nodiscard]] std::span GetColorPointer() const { return x10_colors; } [[nodiscard]] std::span GetTCPointer() const { return x14_texCoords; } [[nodiscard]] std::span GetPackedTCPointer() const { return x18_packedTexCoords; } }; ModelInstance x0_modelInstance; std::vector>* x1c_textures; zeus::CAABox x20_worldAABB; CCubeSurface* x38_firstUnsortedSurf = nullptr; CCubeSurface* x3c_firstSortedSurf = nullptr; bool x40_24_texturesLoaded : 1 = false; bool x40_25_modelVisible : 1 = false; u8 x41_visorFlags; u32 x44_idx; public: CCubeModel(std::vector* surfaces, std::vector>* textures, u8* materialData, std::span positions, std::span colors, std::span normals, std::span texCoords, std::span packedTexCoords, const zeus::CAABox& aabb, u8 flags, bool b1, u32 idx); CCubeMaterial GetMaterialByIndex(u32 idx); bool TryLockTextures(); void UnlockTextures(); void RemapMaterialData(u8* data, std::vector>& textures); void Draw(const CModelFlags& flags); void DrawAlpha(const CModelFlags& flags); void DrawFlat(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces); void DrawNormal(TConstVectorRef positions, TConstVectorRef normals, ESurfaceSelection surfaces); void DrawNormal(const CModelFlags& flags); void DrawSurface(const CCubeSurface& surface, const CModelFlags& flags); void DrawSurfaceWireframe(const CCubeSurface& surface); void SetArraysCurrent(); void SetUsingPackedLightmaps(bool v); zeus::CAABox GetBounds() const { return x20_worldAABB; } u8 GetFlags() const { return x41_visorFlags; } bool AreTexturesLoaded() const { return x40_24_texturesLoaded; } void SetVisible(bool v) { x40_25_modelVisible = v; } bool IsVisible() const { return x40_25_modelVisible; } [[nodiscard]] u32 GetIndex() const { return x44_idx; } [[nodiscard]] CCubeSurface* GetFirstUnsortedSurface() { return x38_firstUnsortedSurf; } [[nodiscard]] const CCubeSurface* GetFirstUnsortedSurface() const { return x38_firstUnsortedSurf; } [[nodiscard]] CCubeSurface* GetFirstSortedSurface() { return x3c_firstSortedSurf; } [[nodiscard]] const CCubeSurface* GetFirstSortedSurface() const { return x3c_firstSortedSurf; } [[nodiscard]] TConstVectorRef GetPositions() const { const auto sp = x0_modelInstance.GetVertexPointer(); return {reinterpret_cast*>(sp.data()), sp.size() / sizeof(aurora::Vec3)}; } [[nodiscard]] TConstVectorRef GetNormals() const { const auto sp = x0_modelInstance.GetNormalPointer(); return {reinterpret_cast*>(sp.data()), sp.size() / sizeof(aurora::Vec3)}; } [[nodiscard]] TCachedToken& GetTexture(u32 idx) const { return x1c_textures->at(idx); } static void EnableShadowMaps(CTexture& shadowTex, const zeus::CTransform& textureProjXf, GX::LightMask chan0DisableMask, GX::LightMask chan1EnableLightMask); static void DisableShadowMaps(); static void MakeTexturesFromMats(const u8* ptr, std::vector>& texture, IObjectStore* store, bool b1); static void SetDrawingOccluders(bool v); static void SetModelWireframe(bool v); static void SetNewPlayerPositionAndTime(const zeus::CVector3f& pos, const CStopwatch& time); static void SetRenderModelBlack(bool v); private: void Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags); void DrawAlphaSurfaces(const CModelFlags& flags); void DrawNormalSurfaces(const CModelFlags& flags); void DrawSurfaces(const CModelFlags& flags); void SetSkinningArraysCurrent(TConstVectorRef positions, TConstVectorRef normals); void SetStaticArraysCurrent(); static bool sRenderModelBlack; static bool sUsingPackedLightmaps; static bool sRenderModelShadow; static CTexture* sShadowTexture; static zeus::CTransform sTextureProjectionTransform; static GX::LightMask sChannel0DisableLightMask; static GX::LightMask sChannel1EnableLightMask; }; template <> aurora::Vec2 cinput_stream_helper(CInputStream& in); template <> aurora::Vec3 cinput_stream_helper(CInputStream& in); template <> aurora::Vec2 cinput_stream_helper(CInputStream& in); template <> aurora::Vec3 cinput_stream_helper(CInputStream& in); } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeRenderer.cpp ================================================ #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CCubeMaterial.hpp" #include "Runtime/Graphics/CCubeModel.hpp" #include "Runtime/Graphics/CCubeSurface.hpp" #include "Runtime/Graphics/CDrawable.hpp" #include "Runtime/Graphics/CDrawablePlaneObject.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/Graphics/CLight.hpp" #include "Runtime/Graphics/CMetroidModelInstance.hpp" #include "Runtime/Graphics/CModel.hpp" #include "Runtime/World/CGameArea.hpp" #include "Runtime/Particle/CParticleGen.hpp" #include "Runtime/Particle/CDecal.hpp" #include "Runtime/Particle/CElementGen.hpp" #include "Runtime/CDvdFile.hpp" #include "Runtime/Logging.hpp" #include namespace metaforce { /* TODO: This is to fix some areas exceeding the max drawable count, the proper number is 128 drawables per bucket */ // using BucketHolderType = rstl::reserved_vector; using BucketHolderType = rstl::reserved_vector; static rstl::reserved_vector sDataHolder; static rstl::reserved_vector sBucketsHolder; static rstl::reserved_vector sPlaneObjectDataHolder; static rstl::reserved_vector sPlaneObjectBucketHolder; class Buckets { friend class CCubeRenderer; static inline rstl::reserved_vector sBucketIndex; static inline rstl::reserved_vector* sData = nullptr; static inline rstl::reserved_vector* sBuckets = nullptr; static inline rstl::reserved_vector* sPlaneObjectData = nullptr; static inline rstl::reserved_vector* sPlaneObjectBucket = nullptr; static constexpr std::array skWorstMinMaxDistance{99999.0f, -99999.0f}; static inline std::array sMinMaxDistance{99999.0f, -99999.0f}; public: static void Clear(); static void Sort(); static void InsertPlaneObject(float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest, const zeus::CPlane& plane, bool zOnly, EDrawableType dtype, void* data); static void Insert(const zeus::CVector3f& pos, const zeus::CAABox& aabb, EDrawableType dtype, void* data, const zeus::CPlane& plane, u16 extraSort); static void Shutdown(); static void Init(); }; void Buckets::Clear() { sData->clear(); sBucketIndex.clear(); sPlaneObjectData->clear(); sPlaneObjectBucket->clear(); for (BucketHolderType& bucket : *sBuckets) { bucket.clear(); } sMinMaxDistance = skWorstMinMaxDistance; } void Buckets::Sort() { float delta = std::max(1.f, sMinMaxDistance[1] - sMinMaxDistance[0]); float pitch = 49.f / delta; for (auto it = sPlaneObjectData->begin(); it != sPlaneObjectData->end(); ++it) { if (sPlaneObjectBucket->size() != sPlaneObjectBucket->capacity()) { sPlaneObjectBucket->push_back(s16(it - sPlaneObjectData->begin())); } } u32 precision = 50; if (!sPlaneObjectBucket->empty()) { std::sort(sPlaneObjectBucket->begin(), sPlaneObjectBucket->end(), [](u16 a, u16 b) { return (*sPlaneObjectData)[a].GetDistance() < (*sPlaneObjectData)[b].GetDistance(); }); precision = 50 / u32(sPlaneObjectBucket->size() + 1); pitch = 1.f / (delta / float(precision - 2)); s32 accum = 0; for (u16 idx : *sPlaneObjectBucket) { ++accum; CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[idx]; planeObj.x24_targetBucket = u16(precision * accum); } } for (CDrawable& drawable : *sData) { s32 slot = -1; float relDist = drawable.GetDistance() - sMinMaxDistance[0]; if (sPlaneObjectBucket->empty()) { slot = zeus::clamp(1, s32(relDist * pitch), 49); } else { slot = zeus::clamp(0, s32(relDist * pitch), s32(precision) - 2); for (u16 idx : *sPlaneObjectBucket) { CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[idx]; bool partial = false; bool full = false; if (planeObj.x3c_25_zOnly) { partial = drawable.GetBounds().max.z() > planeObj.GetPlane().d(); full = drawable.GetBounds().min.z() > planeObj.GetPlane().d(); } else { partial = planeObj.GetPlane().pointToPlaneDist( drawable.GetBounds().closestPointAlongVector(planeObj.GetPlane().normal())) > 0.f; full = planeObj.GetPlane().pointToPlaneDist( drawable.GetBounds().furthestPointAlongVector(planeObj.GetPlane().normal())) > 0.f; } bool cont = false; if (drawable.GetType() == EDrawableType::Particle) { cont = planeObj.x3c_24_invertTest ? !partial : full; } else { cont = planeObj.x3c_24_invertTest ? (!partial || !full) : (partial || full); } if (!cont) { break; } slot += s32(precision); } } if (slot == -1) { slot = 49; } BucketHolderType& bucket = (*sBuckets)[slot]; if (bucket.size() < bucket.capacity()) { bucket.push_back(&drawable); } // else // spdlog::fatal("Full bucket!!!"); } u16 bucketIdx = u16(sBuckets->size()); for (auto it = sBuckets->rbegin(); it != sBuckets->rend(); ++it) { --bucketIdx; sBucketIndex.push_back(bucketIdx); BucketHolderType& bucket = *it; if (bucket.size()) { std::sort(bucket.begin(), bucket.end(), [](CDrawable* a, CDrawable* b) { if (a->GetDistance() == b->GetDistance()) return a->GetExtraSort() > b->GetExtraSort(); return a->GetDistance() > b->GetDistance(); }); } } for (auto it = sPlaneObjectBucket->rbegin(); it != sPlaneObjectBucket->rend(); ++it) { CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[*it]; BucketHolderType& bucket = (*sBuckets)[planeObj.x24_targetBucket]; bucket.push_back(&planeObj); } } void Buckets::InsertPlaneObject(float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest, const zeus::CPlane& plane, bool zOnly, EDrawableType dtype, void* data) { if (sPlaneObjectData->size() == sPlaneObjectData->capacity()) { return; } sPlaneObjectData->emplace_back(dtype, closeDist, farDist, aabb, invertTest, plane, zOnly, data); } void Buckets::Insert(const zeus::CVector3f& pos, const zeus::CAABox& aabb, EDrawableType dtype, void* data, const zeus::CPlane& plane, u16 extraSort) { if (sData->size() == sData->capacity()) { spdlog::fatal("Rendering buckets filled to capacity"); return; } const float dist = plane.pointToPlaneDist(pos); sData->emplace_back(dtype, extraSort, dist, aabb, data); sMinMaxDistance[0] = std::min(sMinMaxDistance[0], dist); sMinMaxDistance[1] = std::max(sMinMaxDistance[1], dist); } void Buckets::Shutdown() { sData = nullptr; sBuckets = nullptr; sPlaneObjectData = nullptr; sPlaneObjectBucket = nullptr; } void Buckets::Init() { sData = &sDataHolder; sBuckets = &sBucketsHolder; sBuckets->resize(50); sPlaneObjectData = &sPlaneObjectDataHolder; sPlaneObjectBucket = &sPlaneObjectBucketHolder; sMinMaxDistance = skWorstMinMaxDistance; } CCubeRenderer::CAreaListItem::CAreaListItem(const std::vector* geom, const CAreaRenderOctTree* octTree, std::unique_ptr>>&& textures, std::unique_ptr>>&& models, s32 areaIdx) : x0_geometry(geom) , x4_octTree(octTree) , x8_textures(std::move(textures)) , x10_models(std::move(models)) , x18_areaIdx(areaIdx) {} CCubeRenderer::CCubeRenderer(IObjectStore& store, IFactory& resFac) : x8_factory(resFac), xc_store(store) { void* data = xe4_blackTex.Lock(); memset(data, 0, 32); xe4_blackTex.UnLock(); GenerateReflectionTex(); GenerateFogVolumeRampTex(); GenerateSphereRampTex(); LoadThermoPalette(); g_Renderer = this; Buckets::Init(); // GX draw sync } CCubeRenderer::~CCubeRenderer() { g_Renderer = nullptr; } void CCubeRenderer::GenerateReflectionTex() { // TODO } void CCubeRenderer::GenerateFogVolumeRampTex() { constexpr double fogVolFar = 750.0; constexpr double fogVolNear = 0.2; u8* data = x1b8_fogVolumeRamp.Lock(); u16 height = x1b8_fogVolumeRamp.GetHeight(); u16 width = x1b8_fogVolumeRamp.GetWidth(); for (size_t y = 0; y < height; ++y) { for (size_t x = 0; x < width; ++x) { const int tmp = int(y << 16 | x << 8 | 0x7f); const double a = zeus::clamp(0.0, (-150.0 / (tmp / double(0xffffff) * (fogVolFar - fogVolNear) - fogVolFar) - fogVolNear) * 3.0 / (fogVolFar - fogVolNear), 1.0); data[y * width + x] = (a * a + a) / 2.0; } } x1b8_fogVolumeRamp.UnLock(); } void CCubeRenderer::GenerateSphereRampTex() { u8* data = x220_sphereRamp.Lock(); const size_t height = x220_sphereRamp.GetHeight(); const size_t width = x220_sphereRamp.GetWidth(); const float halfRes = height / 2.f; for (size_t y = 0; y < height; ++y) { for (size_t x = 0; x < width; ++x) { const zeus::CVector2f vec{ (static_cast(x) - halfRes) / halfRes, (static_cast(y) - halfRes) / halfRes, }; data[y * width + x] = 255 - zeus::clamp(0.f, vec.canBeNormalized() ? vec.magnitude() : 0.f, 1.f) * 255; } } x220_sphereRamp.UnLock(); } void CCubeRenderer::LoadThermoPalette() { auto* out = x288_thermoPalette.Lock(); TToken token = xc_store.GetObj("TXTR_ThermoPalette"); const auto* data = token.GetObj()->GetPalette()->GetPaletteData(); memcpy(out, data, 32); x288_thermoPalette.UnLock(); } void CCubeRenderer::ReallyDrawPhazonSuitIndirectEffect(const zeus::CColor& vertColor, CTexture& maskTex, CTexture& indTex, const zeus::CColor& modColor, float scale, float offX, float offY) { // TODO } void CCubeRenderer::ReallyDrawPhazonSuitEffect(const zeus::CColor& modColor, CTexture& maskTex) { // TODO } void CCubeRenderer::DoPhazonSuitIndirectAlphaBlur(float blurRadius, float f2) { // TODO } void CCubeRenderer::AddWorldSurfaces(CCubeModel& model) { for (auto* it = model.GetFirstSortedSurface(); it != nullptr; it = it->GetNextSurface()) { auto mat = model.GetMaterialByIndex(it->GetMaterialIndex()); auto blend = mat.GetCompressedBlend(); auto bounds = it->GetBounds(); auto pos = bounds.closestPointAlongVector(xb0_viewPlane.normal()); Buckets::Insert(pos, bounds, EDrawableType::WorldSurface, it, xb0_viewPlane, static_cast(blend == 0x50004)); } } void CCubeRenderer::AddStaticGeometry(const std::vector* geometry, const CAreaRenderOctTree* octTree, s32 areaIdx) { auto search = FindStaticGeometry(geometry); if (search == x1c_areaListItems.end()) { auto textures = std::make_unique>>(); auto models = std::make_unique>>(); if (!geometry->empty()) { CCubeModel::MakeTexturesFromMats((*geometry)[0].GetMaterialPointer(), *textures.get(), &xc_store, false); models->reserve(geometry->size()); s32 instIdx = 0; for (const CMetroidModelInstance& inst : *geometry) { models->emplace_back(std::make_unique( const_cast*>(inst.GetSurfaces()), textures.get(), const_cast(inst.GetMaterialPointer()), inst.GetVertexPointer(), inst.GetColorPointer(), inst.GetNormalPointer(), inst.GetTCPointer(), inst.GetPackedTCPointer(), inst.GetBoundingBox(), inst.GetFlags(), false, instIdx)); ++instIdx; } } x1c_areaListItems.emplace_back(geometry, octTree, std::move(textures), std::move(models), areaIdx); } } void CCubeRenderer::EnablePVS(const CPVSVisSet& set, u32 areaIdx) { xc8_pvs.emplace(set); xe0_pvsAreaIdx = areaIdx; } void CCubeRenderer::DisablePVS() { xc8_pvs.reset(); } void CCubeRenderer::RemoveStaticGeometry(const std::vector* geometry) { auto search = FindStaticGeometry(geometry); if (search != x1c_areaListItems.end()) { x1c_areaListItems.erase(search); } } void CCubeRenderer::DrawUnsortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) { SCOPED_GRAPHICS_DEBUG_GROUP( fmt::format("CCubeRenderer::DrawUnsortedGeometry areaIdx={} mask={} targetMask={}", areaIdx, mask, targetMask) .c_str(), zeus::skBlue); SetupRendererStates(true); CModelFlags flags; CAreaListItem* lastOctreeItem = nullptr; for (CAreaListItem& item : x1c_areaListItems) { if (areaIdx != -1 && item.x18_areaIdx != areaIdx) { continue; } if (item.x4_octTree != nullptr) { lastOctreeItem = &item; } CPVSVisSet* pvs = nullptr; if (xc8_pvs) { pvs = &*xc8_pvs; } if (xe0_pvsAreaIdx != item.x18_areaIdx) { pvs = nullptr; } u32 idx = 0; for (auto it = item.x10_models->begin(); it != item.x10_models->end(); ++it, ++idx) { const auto& model = *it; if (pvs != nullptr) { bool vis = pvs->GetVisible(idx) != EPVSVisSetState::EndOfTree; switch (xc0_pvsMode) { case EPVSMode::PVS: { if (!vis) { model->SetVisible(false); continue; } break; } case EPVSMode::PVSAndMask: { if (!vis && (model->GetFlags() & mask) != targetMask) { model->SetVisible(false); continue; } break; } default: break; } } if ((model->GetFlags() & mask) != targetMask) { model->SetVisible(false); continue; } if (!x44_frustumPlanes.aabbFrustumTest(model->GetBounds())) { model->SetVisible(false); continue; } if (x318_25_drawWireframe) { model->SetVisible(false); HandleUnsortedModelWireframe(lastOctreeItem, *model); continue; } model->SetVisible(true); HandleUnsortedModel(lastOctreeItem, *model, flags); } } SetupCGraphicsState(); } void CCubeRenderer::DrawSortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) { SCOPED_GRAPHICS_DEBUG_GROUP( fmt::format("CCubeRenderer::DrawSortedGeometry areaIdx={} mask={} targetMask={}", areaIdx, mask, targetMask) .c_str(), zeus::skBlue); SetupRendererStates(true); const CAreaListItem* item = nullptr; for (const auto& areaListItem : x1c_areaListItems) { if (areaIdx == -1 || areaIdx == areaListItem.x18_areaIdx) { if (areaListItem.x4_octTree != nullptr) { item = &areaListItem; } for (const auto& model : *areaListItem.x10_models) { if (model->IsVisible()) { AddWorldSurfaces(*model); } } } } Buckets::Sort(); RenderBucketItems(item); SetupCGraphicsState(); DrawRenderBucketsDebug(); Buckets::Clear(); } void CCubeRenderer::DrawStaticGeometry(s32 areaIdx, s32 mask, s32 targetMask) { DrawUnsortedGeometry(areaIdx, mask, targetMask); DrawSortedGeometry(areaIdx, mask, targetMask); } void CCubeRenderer::DrawAreaGeometry(s32 areaIdx, s32 mask, s32 targetMask) { SCOPED_GRAPHICS_DEBUG_GROUP( fmt::format("CCubeRenderer::DrawAreaGeometry areaIdx={} mask={} targetMask={}", areaIdx, mask, targetMask) .c_str(), zeus::skBlue); x318_30_inAreaDraw = true; SetupRendererStates(true); constexpr CModelFlags flags{0, 0, 3, zeus::skWhite}; for (CAreaListItem& item : x1c_areaListItems) { if (areaIdx == -1 || item.x18_areaIdx == areaIdx) { CPVSVisSet* pvs = xc8_pvs ? &*xc8_pvs : nullptr; if (xe0_pvsAreaIdx != item.x18_areaIdx) { pvs = nullptr; } s32 modelIdx = 0; for (auto it = item.x10_models->begin(); it != item.x10_models->end(); ++it, ++modelIdx) { const auto& model = *it; if (pvs != nullptr) { bool visible = pvs->GetVisible(modelIdx) != EPVSVisSetState::EndOfTree; if ((xc0_pvsMode == EPVSMode::PVS && !visible) || (xc0_pvsMode == EPVSMode::PVSAndMask && visible)) { continue; } } if ((model->GetFlags() & mask) != targetMask) { continue; } if (!x44_frustumPlanes.aabbFrustumTest(model->GetBounds())) { continue; } model->SetArraysCurrent(); for (const auto* surf = model->GetFirstUnsortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) { model->DrawSurface(*surf, flags); } for (const auto* surf = model->GetFirstSortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) { model->DrawSurface(*surf, flags); } } } } SetupCGraphicsState(); x318_30_inAreaDraw = false; } void CCubeRenderer::RenderBucketItems(const CAreaListItem* item) { SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format("CCubeRenderer::RenderBucketItems areaIdx={}", item->x18_areaIdx).c_str(), zeus::skBlue); CCubeModel* lastModel = nullptr; EDrawableType lastDrawableType = EDrawableType::Invalid; for (u16 idx : Buckets::sBucketIndex) { BucketHolderType& bucket = (*Buckets::sBuckets)[idx]; for (CDrawable* drawable : bucket) { EDrawableType type = drawable->GetType(); switch (type) { case EDrawableType::Particle: { if (lastDrawableType != EDrawableType::Particle) { SetupCGraphicsState(); } static_cast(drawable->GetData())->Render(); break; } case EDrawableType::WorldSurface: { if (lastDrawableType != EDrawableType::WorldSurface) { SetupRendererStates(false); lastModel = nullptr; } auto* surface = static_cast(drawable->GetData()); auto* model = surface->GetParent(); if (model != lastModel) { model->SetArraysCurrent(); ActivateLightsForModel(item, *model); } model->DrawSurface(*surface, CModelFlags(0, 0, 1, zeus::skWhite)); break; } default: { if (type != lastDrawableType) { CCubeMaterial::EnsureTevsDirect(); } if (xa8_drawableCallback != nullptr) { xa8_drawableCallback(drawable->GetData(), xac_drawableCallbackUserData, s32(drawable->GetType()) - 2); } break; } } lastDrawableType = type; } } } void CCubeRenderer::PostRenderFogs() { for (const auto& warp : x2c4_spaceWarps) { ReallyDrawSpaceWarp(warp.first, warp.second); } x2c4_spaceWarps.clear(); x2ac_fogVolumes.sort([](const CFogVolumeListItem& a, const CFogVolumeListItem& b) { zeus::CAABox aabbA = a.x34_aabb.getTransformedAABox(a.x0_transform); bool insideA = aabbA.pointInside( zeus::CVector3f(CGraphics::mViewMatrix.origin.x(), CGraphics::mViewMatrix.origin.y(), aabbA.min.z())); zeus::CAABox aabbB = b.x34_aabb.getTransformedAABox(b.x0_transform); bool insideB = aabbB.pointInside( zeus::CVector3f(CGraphics::mViewMatrix.origin.x(), CGraphics::mViewMatrix.origin.y(), aabbB.min.z())); if (insideA != insideB) { return insideA; } float dotA = aabbA.furthestPointAlongVector(CGraphics::mViewMatrix.basis[1]).dot(CGraphics::mViewMatrix.basis[1]); float dotB = aabbB.furthestPointAlongVector(CGraphics::mViewMatrix.basis[1]).dot(CGraphics::mViewMatrix.basis[1]); return dotA < dotB; }); for (const CFogVolumeListItem& fog : x2ac_fogVolumes) { CGraphics::SetModelMatrix(fog.x0_transform); ReallyRenderFogVolume(fog.x30_color, fog.x34_aabb, fog.x4c_model.GetObj(), fog.x5c_skinnedModel); } x2ac_fogVolumes.clear(); } void CCubeRenderer::SetModelMatrix(const zeus::CTransform& xf) { CGraphics::SetModelMatrix(xf); } void CCubeRenderer::HandleUnsortedModel(CAreaListItem* areaItem, CCubeModel& model, const CModelFlags& flags) { if (model.GetFirstUnsortedSurface() == nullptr) { return; } model.SetArraysCurrent(); ActivateLightsForModel(areaItem, model); for (auto* it = model.GetFirstUnsortedSurface(); it != nullptr; it = it->GetNextSurface()) { model.DrawSurface(*it, CModelFlags(0, 0, 3, zeus::skWhite)); } } void CCubeRenderer::HandleUnsortedModelWireframe(CAreaListItem* areaItem, CCubeModel& model) { model.SetArraysCurrent(); ActivateLightsForModel(areaItem, model); for (auto* it = model.GetFirstUnsortedSurface(); it != nullptr; it = it->GetNextSurface()) { model.DrawSurfaceWireframe(*it); } for (auto* it = model.GetFirstSortedSurface(); it != nullptr; it = it->GetNextSurface()) { model.DrawSurfaceWireframe(*it); } } constexpr bool TestBit(const u32* words, size_t bit) { return (words[bit / 32] & (1U << (bit & 0x1f))) != 0; } void CCubeRenderer::ActivateLightsForModel(const CAreaListItem* areaItem, CCubeModel& model) { constexpr u32 LightCount = 4; GX::LightMask lightMask; if (!x300_dynamicLights.empty()) { std::array addedLights{}; std::array lightRads{-1.f, -1.f, -1.f, -1.f}; u32 lightOctreeWordCount = 0; const u32* lightOctreeWords = nullptr; if (areaItem != nullptr && model.GetIndex() != UINT32_MAX) { lightOctreeWordCount = areaItem->x4_octTree->x14_bitmapWordCount; lightOctreeWords = areaItem->x1c_lightOctreeWords.data(); } u32 lightIdx = 0; for (const auto& light : x300_dynamicLights) { if (lightIdx >= LightCount) { break; } if (lightOctreeWords == nullptr || TestBit(lightOctreeWords, model.GetIndex())) { bool loaded = false; const float radius = model.GetBounds().intersectionRadius(zeus::CSphere(light.GetPosition(), light.GetRadius())); if (lightIdx > 0) { for (u32 i = 0; i < lightIdx; ++i) { if (addedLights[i] == light.GetId()) { if (radius >= 0.f && radius < lightRads[i]) { lightRads[i] = radius; CGraphics::LoadLight(i, light); loaded = true; } break; } } } if (!loaded) { lightRads[lightIdx] = radius; if (radius >= 0.f) { CGraphics::LoadLight(lightIdx, light); addedLights[lightIdx] = light.GetId(); lightMask.set(lightIdx); ++lightIdx; } } } lightOctreeWords += lightOctreeWordCount; } } if (lightMask.any()) { CGraphics::SetLightState(lightMask); CGX::SetChanMatColor(CGX::EChannelId::Channel0, zeus::skWhite); } else { CGraphics::DisableAllLights(); CGX::SetChanMatColor(CGX::EChannelId::Channel0, CGX::GetChanAmbColor(CGX::EChannelId::Channel0)); } } void CCubeRenderer::AddParticleGen(CParticleGen& gen) { auto bounds = gen.GetBounds(); if (bounds) { auto closestPoint = bounds->closestPointAlongVector(xb0_viewPlane.normal()); Buckets::Insert(closestPoint, *bounds, EDrawableType::Particle, reinterpret_cast(&gen), xb0_viewPlane, 0); } } void CCubeRenderer::AddParticleGen(CParticleGen& gen, const zeus::CVector3f& pos, const zeus::CAABox& bounds) { Buckets::Insert(pos, bounds, EDrawableType::Particle, reinterpret_cast(&gen), xb0_viewPlane, 0); } void CCubeRenderer::AddPlaneObject(void* obj, const zeus::CAABox& aabb, const zeus::CPlane& plane, s32 type) { const auto closestPoint = aabb.closestPointAlongVector(xb0_viewPlane.normal()); const auto closestDist = xb0_viewPlane.pointToPlaneDist(closestPoint); const auto furthestPoint = aabb.furthestPointAlongVector(xb0_viewPlane.normal()); const auto furthestDist = xb0_viewPlane.pointToPlaneDist(furthestPoint); if (closestDist >= 0.f || furthestDist >= 0.f) { const bool zOnly = plane.normal() == zeus::skUp; const bool invertTest = zOnly ? CGraphics::mViewMatrix.origin.z() >= plane.d() : plane.pointToPlaneDist(CGraphics::mViewMatrix.origin) >= 0.f; Buckets::InsertPlaneObject(closestDist, furthestDist, aabb, invertTest, plane, zOnly, EDrawableType(type + 2), obj); } } void CCubeRenderer::AddDrawable(void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, s32 mode, IRenderer::EDrawableSorting sorting) { if (sorting == EDrawableSorting::UnsortedCallback) { xa8_drawableCallback(obj, xac_drawableCallbackUserData, mode); } else { Buckets::Insert(pos, aabb, EDrawableType(mode + 2), obj, xb0_viewPlane, 0); } } void CCubeRenderer::SetDrawableCallback(IRenderer::TDrawableCallback cb, void* ctx) { xa8_drawableCallback = cb; xac_drawableCallbackUserData = ctx; } void CCubeRenderer::SetWorldViewpoint(const zeus::CTransform& xf) { CGraphics::SetViewPointMatrix(xf); auto front = xf.frontVector(); xb0_viewPlane = zeus::CPlane(front, front.dot(xf.origin)); } void CCubeRenderer::SetPerspective(float fovy, float aspect, float znear, float zfar) { CGraphics::SetPerspective(fovy, aspect, znear, zfar); } void CCubeRenderer::SetPerspective(float fovy, float width, float height, float znear, float zfar) { CGraphics::SetPerspective(fovy, width / height, znear, zfar); } std::pair CCubeRenderer::SetViewportOrtho(bool centered, float znear, float zfar) { auto left = static_cast(centered ? CGraphics::GetViewportLeft() - CGraphics::GetViewportHalfWidth() : CGraphics::GetViewportLeft()); auto bottom = static_cast(centered ? CGraphics::GetViewportTop() - CGraphics::GetViewportHalfHeight() : CGraphics::GetViewportTop()); auto right = static_cast(CGraphics::GetViewportLeft() + (centered ? CGraphics::GetViewportWidth() / 2 : CGraphics::GetViewportWidth())); auto top = static_cast(CGraphics::GetViewportTop() + (centered ? CGraphics::GetViewportHeight() / 2 : CGraphics::GetViewportHeight())); CGraphics::SetOrtho(left, right, top, bottom, znear, zfar); CGraphics::SetViewPointMatrix({}); CGraphics::SetModelMatrix({}); return {{left, bottom}, {right, top}}; } void CCubeRenderer::SetClippingPlanes(const zeus::CFrustum& frustum) { x44_frustumPlanes = frustum; } void CCubeRenderer::SetViewport(s32 left, s32 bottom, s32 width, s32 height) { CGraphics::SetViewport(left, bottom, width, height); CGraphics::SetScissor(left, bottom, width, height); } void CCubeRenderer::BeginScene() { CGraphics::SetUseVideoFilter(true); CGraphics::SetViewport(0, 0, CGraphics::mRenderModeObj.fbWidth, CGraphics::mRenderModeObj.xfbHeight); CGraphics::SetClearColor(zeus::skClear); CGraphics::SetCullMode(ERglCullMode::Front); CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true); CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); CGraphics::SetPerspective(75.f, CGraphics::mViewport.mWidth / CGraphics::mViewport.mHeight, 1.f, 4096.f); CGraphics::SetModelMatrix(zeus::CTransform()); if (x310_phazonSuitMaskCountdown != 0) { --x310_phazonSuitMaskCountdown; if (x310_phazonSuitMaskCountdown == 0) { x314_phazonSuitMask.reset(); } } x318_27_currentRGBA6 = x318_26_requestRGBA6; if (!x318_31_persistRGBA6) { x318_26_requestRGBA6 = false; } GXSetPixelFmt(x318_27_currentRGBA6 ? GX_PF_RGBA6_Z24 : GX_PF_RGB8_Z24, GX_ZC_LINEAR); GXSetAlphaUpdate(true); GXSetDstAlpha(true, 0.f); CGraphics::BeginScene(); } void CCubeRenderer::EndScene() { x318_31_persistRGBA6 = !CGraphics::mIsBeginSceneClearFb; CGraphics::EndScene(); if (x2dc_reflectionAge < 2) { ++x2dc_reflectionAge; } else { x14c_reflectionTex.reset(); }; } void CCubeRenderer::SetDebugOption(IRenderer::EDebugOption option, s32 value) { if (option == EDebugOption::PVSState) { if (xc8_pvs) { xc8_pvs->SetState(EPVSVisSetState(value)); } } else if (option == EDebugOption::PVSMode) { xc0_pvsMode = EPVSMode(value); } else if (option == EDebugOption::FogDisabled) { x318_28_disableFog = true; } } void CCubeRenderer::BeginPrimitive(IRenderer::EPrimitiveType type, s32 nverts) { constexpr std::array vtxDescList{ GXVtxDescList{GX_VA_POS, GX_DIRECT}, GXVtxDescList{GX_VA_NRM, GX_DIRECT}, GXVtxDescList{GX_VA_CLR0, GX_DIRECT}, GXVtxDescList{GX_VA_NULL, GX_NONE}, }; CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_VTX, {}, GX_DF_NONE, GX_AF_NONE); CGX::SetNumChans(1); CGX::SetNumTexGens(0); CGX::SetNumTevStages(1); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); x18_primVertCount = nverts; CGX::SetVtxDescv(vtxDescList.data()); CGX::Begin(static_cast(type), GX_VTXFMT0, nverts); } void CCubeRenderer::BeginLines(s32 nverts) { BeginPrimitive(EPrimitiveType::Lines, nverts); } void CCubeRenderer::BeginLineStrip(s32 nverts) { BeginPrimitive(EPrimitiveType::LineStrip, nverts); } void CCubeRenderer::BeginTriangles(s32 nverts) { BeginPrimitive(EPrimitiveType::Triangles, nverts); } void CCubeRenderer::BeginTriangleStrip(s32 nverts) { BeginPrimitive(EPrimitiveType::TriangleStrip, nverts); } void CCubeRenderer::BeginTriangleFan(s32 nverts) { BeginPrimitive(EPrimitiveType::TriangleFan, nverts); } void CCubeRenderer::PrimVertex(const zeus::CVector3f& vertex) { --x18_primVertCount; GXPosition3f32(vertex); GXNormal3f32(x2e4_primNormal); GXColor4f32(x2e0_primColor); } void CCubeRenderer::PrimNormal(const zeus::CVector3f& normal) { x2e4_primNormal = normal; } void CCubeRenderer::PrimColor(float r, float g, float b, float a) { PrimColor({r, g, b, a}); } void CCubeRenderer::PrimColor(const zeus::CColor& color) { x2e0_primColor = color; } void CCubeRenderer::EndPrimitive() { while (x18_primVertCount > 0) { PrimVertex(zeus::skZero3f); } CGX::End(); } void CCubeRenderer::SetAmbientColor(const zeus::CColor& color) { CGraphics::SetAmbientColor(color); } void CCubeRenderer::DrawString(const char* string, s32 x, s32 y) { x10_font.DrawString(string, x, y, zeus::skWhite); } float CCubeRenderer::GetFPS() { return CGraphics::GetFPS(); } void CCubeRenderer::CacheReflection(IRenderer::TReflectionCallback cb, void* ctx, bool clearAfter) { // TODO } void CCubeRenderer::DrawSpaceWarp(const zeus::CVector3f& pt, float strength) { if (pt.z() < 1.f) { ReallyDrawSpaceWarp(pt, strength); } } void CCubeRenderer::DrawThermalModel(CModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol, TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) { model.UpdateLastFrame(); DoThermalModelDraw(model.GetInstance(), multCol, addCol, positions, normals, flags); } void CCubeRenderer::DrawModelDisintegrate(CModel& model, CTexture& tex, const zeus::CColor& color, TConstVectorRef positions, TConstVectorRef normals, float t) { tex.Load(GX_TEXMAP0, EClampMode::Clamp); CGX::SetNumIndStages(0); CGX::SetNumTevStages(2); CGX::SetNumTexGens(2); CGX::SetNumChans(0); CGX::SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE1); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_CPREV, GX_CC_KONST); CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_APREV, GX_CA_ZERO); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K0); CGX::SetTevKColor(GX_KCOLOR0, color); const auto bounds = model.GetInstance().GetBounds(); const auto rotation = zeus::CTransform::RotateX(zeus::degToRad(-45.f)); const auto transformedBounds = bounds.getTransformedAABox(rotation); const auto xf = zeus::CTransform::Scale(5.f / (transformedBounds.max - transformedBounds.min)) * zeus::CTransform::Translate(-transformedBounds.min) * rotation; Mtx mtx; xf.toCStyleMatrix(mtx); float f1 = -(1.f - t) * 6.f + 1.f; float f2 = t * -0.85f - 0.15f; const Mtx ptTex0 = { {1.0f, 1.0f, 0.0f, t}, {0.0f, 0.0f, 1.0f, f1}, {0.0f, 0.0f, 0.0f, 1.0f}, }; const Mtx ptTex1 = { {1.0f, 1.0f, 0.0f, f2}, {0.0f, 0.0f, 1.0f, f1}, {0.0f, 0.0f, 0.0f, 1.0f}, }; GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX3x4); GXLoadTexMtxImm(ptTex0, GX_PTTEXMTX0, GX_MTX3x4); GXLoadTexMtxImm(ptTex1, GX_PTTEXMTX1, GX_MTX3x4); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX0, false, GX_PTTEXMTX0); CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX0, false, GX_PTTEXMTX1); CGX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_ALWAYS, 0); CGX::SetZMode(true, GX_LEQUAL, true); model.UpdateLastFrame(); model.GetInstance().DrawFlat(positions, normals, ESurfaceSelection::All); CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); } void CCubeRenderer::DrawModelFlat(CModel& model, const CModelFlags& flags, bool unsortedOnly, TConstVectorRef positions, TConstVectorRef normals) { if (flags.x0_blendMode >= 7) { CGX::SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR); } else if (flags.x0_blendMode >= 5) { CGX::SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); } else { CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR); } CGX::SetZMode(true, flags.x2_flags & CModelFlagBits::DepthTest ? GX_LEQUAL : GX_ALWAYS, flags.x2_flags.IsSet(CModelFlagBits::DepthUpdate)); CGX::SetNumTevStages(1); CGX::SetNumTexGens(1); CGX::SetNumChans(0); CGX::SetNumIndStages(0); CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); CGX::SetTevKColor(GX_KCOLOR0, flags.x4_color); CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetTevDirect(GX_TEVSTAGE0); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_POS, GX_IDENTITY, false, GX_PTIDENTITY); model.UpdateLastFrame(); model.GetInstance().DrawFlat(positions, normals, unsortedOnly ? ESurfaceSelection::Unsorted : ESurfaceSelection::All); } void CCubeRenderer::SetWireframeFlags(s32 flags) { CCubeModel::SetModelWireframe((flags & 0x1) != 0); x318_25_drawWireframe = (flags & 0x2) != 0; } void CCubeRenderer::SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) { CGraphics::SetFog(x318_28_disableFog ? ERglFogMode::None : mode, startz, endz, color); } void CCubeRenderer::RenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const TLockedToken* model, const CSkinnedModel* sModel) { if (!x318_28_disableFog) { x2ac_fogVolumes.emplace_back(CGraphics::mModelMatrix, color, aabb, model, sModel); } } void CCubeRenderer::SetThermal(bool thermal, float level, const zeus::CColor& color) { x318_29_thermalVisor = thermal; x2f0_thermalVisorLevel = level; x2f4_thermColor = color; CDecal::SetMoveRedToAlphaBuffer(false); CElementGen::SetMoveRedToAlphaBuffer(false); } void CCubeRenderer::SetThermalColdScale(float scale) { x2f8_thermColdScale = zeus::clamp(0.f, scale, 1.f); } void CCubeRenderer::DoThermalBlendCold() { SCOPED_GRAPHICS_DEBUG_GROUP("CCubeRenderer::DoThermalBlendCold", zeus::skBlue); // Capture EFB x318_26_requestRGBA6 = true; GXSetAlphaUpdate(true); GXSetDstAlpha(false, 0); const auto height = CGraphics::GetViewportHeight(); const auto width = CGraphics::GetViewportWidth(); const auto top = CGraphics::GetViewportTop(); const auto left = CGraphics::GetViewportLeft(); CGX::SetZMode(true, GX_LEQUAL, false); GXSetTexCopySrc(left, top, width, height); GXSetTexCopyDst(width, height, GX_TF_I4, false); GXCopyTex(CGraphics::mpSpareBuffer, true); CGraphics::LoadDolphinSpareTexture(width, height, GX_TF_I4, nullptr, GX_TEXMAP7); // Upload random static texture (game reads from .text) const u8* buf = CDvdFile::GetDolBuf() + 0x4f60; u8* out = m_thermalRandomStatic.Lock(); memcpy(out, buf + ROUND_UP_32(x2a8_thermalRand.Next()), m_thermalRandomStatic.GetMemoryAllocated()); m_thermalRandomStatic.UnLock(); m_thermalRandomStatic.Load(GX_TEXMAP0, EClampMode::Clamp); m_thermalRandomStatic.Load(GX_TEXMAP1, EClampMode::Clamp); // Configure indirect texturing const float level = std::clamp(x2f0_thermalVisorLevel * 0.5f, 0.f, 0.5f); const aurora::Mat3x2 mtx{ aurora::Vec2{(1.f - level) * 0.1f, 0.f}, aurora::Vec2{0.f, 0.f}, aurora::Vec2{0.f, level}, }; GXSetIndTexMtx(GX_ITM_0, &mtx, -2); CGX::SetTevIndirect(GX_TEVSTAGE0, GX_INDTEXSTAGE0, GX_ITF_8, GX_ITB_STU, GX_ITM_0, GX_ITW_OFF, GX_ITW_OFF, false, false, GX_ITBA_OFF); GXSetIndTexOrder(GX_INDTEXSTAGE0, GX_TEXCOORD0, GX_TEXMAP0); // Configure register colors const auto color0 = zeus::CColor::lerp(x2f4_thermColor, zeus::skWhite, x2f8_thermColdScale); const float bAlpha = x2f8_thermColdScale < 0.5f ? x2f8_thermColdScale * 2.f : 1.f; const float bFac = (1.f - bAlpha) / 8.f; const zeus::CColor color1{bFac, bAlpha}; float cFac; if (x2f8_thermColdScale < 0.25f) { cFac = 0.f; } else if (x2f8_thermColdScale >= 1.f) { cFac = 1.f; } else { cFac = (x2f8_thermColdScale - 0.25f) * 4.f / 3.f; } const zeus::CColor color2{cFac, cFac}; GXSetTevColor(GX_TEVREG0, to_gx_color(color0)); GXSetTevColor(GX_TEVREG1, to_gx_color(color1)); GXSetTevColor(GX_TEVREG2, to_gx_color(color2)); // Configure TEV stage 0 GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP1); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_C0, GX_CC_C2); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_A1, GX_CA_A2); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL); // Configure TEV stage 1 GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP1); CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_C1, GX_CC_CPREV); CGX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_A1, GX_CA_TEXA, GX_CA_APREV); CGX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, true, GX_TEVPREV); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR_NULL); // Configure everything else CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); CGX::SetNumTevStages(2); CGX::SetNumTexGens(1); CGX::SetNumChans(0); CGX::SetNumIndStages(1); CGX::SetZMode(false, GX_ALWAYS, false); constexpr std::array vtxDescList{ GXVtxDescList{GX_VA_POS, GX_DIRECT}, GXVtxDescList{GX_VA_TEX0, GX_DIRECT}, GXVtxDescList{GX_VA_NULL, GX_NONE}, }; CGX::SetVtxDescv(vtxDescList.data()); CGX::SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR); // Backup & set viewport/projection const auto backupViewMatrix = CGraphics::mViewMatrix; const auto backupProjectionState = CGraphics::GetProjectionState(); CGraphics::SetOrtho(0.f, static_cast(width), 0.f, static_cast(height), -4096.f, 4096.f); CGraphics::SetViewPointMatrix({}); CGraphics::SetModelMatrix({}); GXPixModeSync(); // Draw CGX::Begin(GX_TRIANGLEFAN, GX_VTXFMT0, 4); GXPosition3f32(0.f, 0.5f, 0.f); GXTexCoord2f32(0.f, 0.f); GXPosition3f32(0.f, 0.5f, static_cast(height)); GXTexCoord2f32(0.f, 1.f); GXPosition3f32(static_cast(width), 0.5f, static_cast(height)); GXTexCoord2f32(1.f, 1.f); GXPosition3f32(static_cast(width), 0.5f, 0.f); GXTexCoord2f32(1.f, 0.f); CGX::End(); // Cleanup GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0); GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0); CGX::SetNumIndStages(0); CGX::SetTevDirect(GX_TEVSTAGE0); GXSetDstAlpha(false, 255); CGraphics::SetProjectionState(backupProjectionState); CGraphics::SetViewPointMatrix(backupViewMatrix); CDecal::SetMoveRedToAlphaBuffer(true); CElementGen::SetMoveRedToAlphaBuffer(true); } void CCubeRenderer::DoThermalBlendHot() { SCOPED_GRAPHICS_DEBUG_GROUP("CCubeRenderer::DoThermalBlendHot", zeus::skRed); GXSetAlphaUpdate(false); GXSetDstAlpha(true, 0); const auto height = CGraphics::GetViewportHeight(); const auto width = CGraphics::GetViewportWidth(); const auto top = CGraphics::GetViewportTop(); const auto left = CGraphics::GetViewportLeft(); CGX::SetZMode(true, GX_LEQUAL, true); GXSetTexCopySrc(left, top, width, height); GXSetTexCopyDst(width, height, GX_TF_I4, false); GXCopyTex(CGraphics::mpSpareBuffer, false); x288_thermoPalette.Load(); CGraphics::LoadDolphinSpareTexture(width, height, GX_TF_C4, GX_TLUT0, nullptr, GX_TEXMAP7); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXA, GX_CC_TEXC, GX_CC_ZERO); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); CGX::SetNumTevStages(1); CGX::SetNumTexGens(1); CGX::SetNumChans(0); CGX::SetZMode(false, GX_LEQUAL, false); constexpr std::array vtxDescList{ GXVtxDescList{GX_VA_POS, GX_DIRECT}, GXVtxDescList{GX_VA_TEX0, GX_DIRECT}, GXVtxDescList{GX_VA_NULL, GX_NONE}, }; CGX::SetVtxDescv(vtxDescList.data()); CGX::SetBlendMode(GX_BM_BLEND, GX_BL_DSTALPHA, GX_BL_INVDSTALPHA, GX_LO_CLEAR); // Backup & set viewport/projection const auto backupViewMatrix = CGraphics::mViewMatrix; const auto backupProjectionState = CGraphics::GetProjectionState(); CGraphics::SetOrtho(0.f, static_cast(width), 0.f, static_cast(height), -4096.f, 4096.f); CGraphics::SetViewPointMatrix({}); CGraphics::SetModelMatrix({}); GXPixModeSync(); // Draw CGX::Begin(GX_TRIANGLEFAN, GX_VTXFMT0, 4); GXPosition3f32(0.f, 0.5f, 0.f); GXTexCoord2f32(0.f, 0.f); GXPosition3f32(0.f, 0.5f, static_cast(height)); GXTexCoord2f32(0.f, 1.f); GXPosition3f32(static_cast(width), 0.5f, static_cast(height)); GXTexCoord2f32(1.f, 1.f); GXPosition3f32(static_cast(width), 0.5f, 0.f); GXTexCoord2f32(1.f, 0.f); CGX::End(); // Cleanup CGX::SetNumIndStages(0); CGX::SetTevDirect(GX_TEVSTAGE0); GXSetAlphaUpdate(true); CGraphics::SetProjectionState(backupProjectionState); CGraphics::SetViewPointMatrix(backupViewMatrix); CDecal::SetMoveRedToAlphaBuffer(false); CElementGen::SetMoveRedToAlphaBuffer(false); } u32 CCubeRenderer::GetStaticWorldDataSize() { // TODO return 0; } void CCubeRenderer::SetGXRegister1Color(const zeus::CColor& color) { GXSetTevColor(GX_TEVREG1, to_gx_color(color)); } void CCubeRenderer::SetWorldLightFadeLevel(float level) { x2fc_tevReg1Color = zeus::CColor(level, level, level, 1.f); } void CCubeRenderer::PrepareDynamicLights(const std::vector& lights) { x300_dynamicLights = lights; for (CAreaListItem& area : x1c_areaListItems) { if (const CAreaRenderOctTree* arot = area.x4_octTree) { area.x1c_lightOctreeWords.clear(); area.x1c_lightOctreeWords.resize(arot->x14_bitmapWordCount * lights.size()); u32* wordPtr = area.x1c_lightOctreeWords.data(); for (const CLight& light : lights) { float radius = light.GetRadius(); zeus::CVector3f vMin = light.GetPosition() - radius; zeus::CVector3f vMax = light.GetPosition() + radius; zeus::CAABox aabb(vMin, vMax); arot->FindOverlappingModels(wordPtr, aabb); wordPtr += arot->x14_bitmapWordCount; } } } } void CCubeRenderer::AllocatePhazonSuitMaskTexture() { x318_26_requestRGBA6 = true; if (!x314_phazonSuitMask) { x314_phazonSuitMask = std::make_unique(ETexelFormat::I8, CGraphics::GetViewportWidth() / 4, CGraphics::GetViewportHeight() / 4, 1, "Phazon Suit Mask"sv); } x310_phazonSuitMaskCountdown = 2; } void CCubeRenderer::DrawPhazonSuitIndirectEffect(const zeus::CColor& nonIndirectMod, const TLockedToken& indTex, const zeus::CColor& indirectMod, float blurRadius, float scale, float offX, float offY) { if (x318_27_currentRGBA6 && x310_phazonSuitMaskCountdown != 0) { const auto backupViewMatrix = CGraphics::mViewMatrix; const auto backupProjectionState = CGraphics::GetProjectionState(); if (!x314_phazonSuitMask || x314_phazonSuitMask->GetWidth() != CGraphics::GetViewportWidth() / 4 || x314_phazonSuitMask->GetHeight() != CGraphics::GetViewportHeight() / 4) { return; } DoPhazonSuitIndirectAlphaBlur(blurRadius, blurRadius); // TODO copy into x314_phazonSuitMask if (indTex) { ReallyDrawPhazonSuitIndirectEffect(zeus::skWhite, *x314_phazonSuitMask, const_cast(*indTex), indirectMod, scale, offX, offY); } else { ReallyDrawPhazonSuitEffect(nonIndirectMod, *x314_phazonSuitMask); } // TODO unlock x314 CGraphics::SetViewPointMatrix(backupViewMatrix); CGraphics::SetProjectionState(backupProjectionState); x310_phazonSuitMaskCountdown = 2; } GXSetDstAlpha(false, 0.f); } void CCubeRenderer::DrawXRayOutline(const zeus::CAABox& aabb) { // TODO } std::list::iterator CCubeRenderer::FindStaticGeometry(const std::vector* geometry) { return std::find_if(x1c_areaListItems.begin(), x1c_areaListItems.end(), [&](const CAreaListItem& item) { return item.x0_geometry == geometry; }); } void CCubeRenderer::FindOverlappingWorldModels(std::vector& modelBits, const zeus::CAABox& aabb) const { u32 bitmapWords = 0; for (const CAreaListItem& item : x1c_areaListItems) { if (item.x4_octTree != nullptr) { bitmapWords += item.x4_octTree->x14_bitmapWordCount; } } if (bitmapWords == 0u) { modelBits.clear(); return; } modelBits.clear(); modelBits.resize(bitmapWords); u32 curWord = 0; for (const CAreaListItem& item : x1c_areaListItems) { if (item.x4_octTree == nullptr) { continue; } item.x4_octTree->FindOverlappingModels(modelBits.data() + curWord, aabb); u32 wordModel = 0; for (u32 i = 0; i < item.x4_octTree->x14_bitmapWordCount; ++i, wordModel += 32) { u32& word = modelBits[curWord + i]; if (word == 0) { continue; } for (u32 j = 0; j < 32; ++j) { if (((1U << j) & word) != 0) { const zeus::CAABox& modelAABB = (*item.x10_models)[wordModel + j]->GetBounds(); if (!modelAABB.intersects(aabb)) { word &= ~(1U << j); } } } } curWord += item.x4_octTree->x14_bitmapWordCount; } } s32 CCubeRenderer::DrawOverlappingWorldModelIDs(s32 alphaVal, const std::vector& modelBits, const zeus::CAABox& aabb) { SCOPED_GRAPHICS_DEBUG_GROUP("CCubeRenderer::DrawOverlappingWorldModelIDs", zeus::skGrey); SetupRendererStates(true); constexpr CModelFlags flags{0, 0, 3, zeus::skWhite}; u32 curWord = 0; for (const CAreaListItem& item : x1c_areaListItems) { if (item.x4_octTree == nullptr) { continue; } u32 wordModel = 0; for (u32 i = 0; i < item.x4_octTree->x14_bitmapWordCount; ++i, wordModel += 32) { const u32& word = modelBits[curWord + i]; if (word == 0) { continue; } for (u32 j = 0; j < 32; ++j) { if (((1U << j) & word) != 0) { if (alphaVal > 255) { SetupCGraphicsState(); return alphaVal; } auto& model = *(*item.x10_models)[wordModel + j]; GXSetDstAlpha(true, alphaVal); CCubeMaterial::KillCachedViewDepState(); model.SetArraysCurrent(); for (const auto* surf = model.GetFirstUnsortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) { if (surf->GetBounds().intersects(aabb)) { model.DrawSurface(*surf, flags); } } alphaVal += 4; } } } curWord += item.x4_octTree->x14_bitmapWordCount; } SetupCGraphicsState(); return alphaVal; } void CCubeRenderer::DrawOverlappingWorldModelShadows(s32 alphaVal, const std::vector& modelBits, const zeus::CAABox& aabb) { SCOPED_GRAPHICS_DEBUG_GROUP("CBooRenderer::DrawOverlappingWorldModelShadows", zeus::skGrey); u32 curWord = 0; for (const CAreaListItem& item : x1c_areaListItems) { if (item.x4_octTree == nullptr) { continue; } u32 wordModel = 0; for (u32 i = 0; i < item.x4_octTree->x14_bitmapWordCount; ++i, wordModel += 32) { const u32& word = modelBits[curWord + i]; if (word == 0) { continue; } for (u32 j = 0; j < 32; ++j) { if (((1U << j) & word) != 0) { if (alphaVal > 255) { return; } auto& model = *(*item.x10_models)[wordModel + j]; CGX::SetTevKColor(GX_KCOLOR0, zeus::CColor{0.f, static_cast(alphaVal) / 255.f}); model.SetArraysCurrent(); for (const auto* surf = model.GetFirstUnsortedSurface(); surf != nullptr; surf = surf->GetNextSurface()) { if (surf->GetBounds().intersects(aabb)) { const auto& material = model.GetMaterialByIndex(surf->GetMaterialIndex()); CGX::SetVtxDescv_Compressed(material.GetVertexDesc()); CGX::CallDisplayList(surf->GetDisplayList(), surf->GetDisplayListSize()); } } alphaVal += 4; } } } curWord += item.x4_octTree->x14_bitmapWordCount; } } void CCubeRenderer::SetupCGraphicsState() { CGraphics::DisableAllLights(); CGraphics::SetModelMatrix({}); CTevCombiners::ResetStates(); CGraphics::SetAmbientColor({0.4f}); CGX::SetChanMatColor(CGX::EChannelId::Channel0, zeus::skWhite); CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true); CGX::SetChanCtrl(CGX::EChannelId::Channel1, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); CCubeMaterial::EnsureTevsDirect(); } void CCubeRenderer::SetupRendererStates(bool depthWrite) { CGraphics::DisableAllLights(); CGraphics::SetModelMatrix({}); CGraphics::SetAmbientColor(zeus::skClear); CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, depthWrite); CCubeMaterial::ResetCachedMaterials(); GXSetTevColor(GX_TEVREG1, to_gx_color(x2fc_tevReg1Color)); } constexpr Mtx MvPostXf = { {0.5f, 0.0f, 0.0f, 0.5f}, {0.0f, 0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 0.0f, 1.0f}, }; void CCubeRenderer::DoThermalModelDraw(CCubeModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol, TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) { SCOPED_GRAPHICS_DEBUG_GROUP("CCubeRenderer::DoThermalModelDraw", zeus::skBlue); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_NRM, GX_TEXMTX0, true, GX_PTTEXMTX0); CGX::SetNumTexGens(1); CGX::SetNumChans(0); x220_sphereRamp.Load(GX_TEXMAP0, EClampMode::Clamp); zeus::CTransform xf = CGraphics::mViewMatrix.quickInverse().multiplyIgnoreTranslation(CGraphics::mModelMatrix); xf.origin.zeroOut(); Mtx mtx; xf.toCStyleMatrix(mtx); GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX3x4); GXLoadTexMtxImm(MvPostXf, GX_PTTEXMTX0, GX_MTX3x4); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_C0, GX_CC_TEXC, GX_CC_KONST); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_A0, GX_CA_KONST); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetNumTevStages(1); CGX::SetTevKColor(GX_KCOLOR0, addCol); CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); GXSetTevColor(GX_TEVREG0, to_gx_color(multCol)); CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0); CGX::SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR); CGX::SetZMode(flags.x2_flags.IsSet(CModelFlagBits::DepthTest), GX_LEQUAL, flags.x2_flags.IsSet(CModelFlagBits::DepthUpdate)); model.DrawFlat(positions, normals, flags.x2_flags.IsSet(CModelFlagBits::ThermalUnsortedOnly) ? ESurfaceSelection::Unsorted : ESurfaceSelection::All); } void CCubeRenderer::ReallyDrawSpaceWarp(const zeus::CVector3f& pt, float strength) { return; // TODO float vpLeft = static_cast(CGraphics::GetViewportLeft()); float vpTop = static_cast(CGraphics::GetViewportTop()); float vpHalfWidth = static_cast(CGraphics::GetViewportWidth() / 2); float vpHalfHeight = static_cast(CGraphics::GetViewportHeight() / 2); float local_100 = vpLeft + vpHalfWidth * pt.x() + vpHalfWidth; float local_fc = vpTop + vpHalfHeight * -pt.y() + vpHalfHeight; zeus::CVector2i CStack264{static_cast(local_100) & ~3, static_cast(local_fc) & ~3}; auto v2right = CStack264 - zeus::CVector2i{96, 96}; auto v2left = CStack264 + zeus::CVector2i{96, 96}; zeus::CVector2f uv1min{0.f, 0.f}; zeus::CVector2f uv1max{1.f, 1.f}; s32 aleft = CGraphics::GetViewportLeft() & ~3; s32 atop = CGraphics::GetViewportTop() & ~3; s32 aright = (CGraphics::GetViewportLeft() + CGraphics::GetViewportWidth() + 3) & ~3; s32 abottom = (CGraphics::GetViewportTop() + CGraphics::GetViewportHeight() + 3) & ~3; if (v2right.x < aleft) { uv1min.x() = static_cast(aleft - v2right.x) * 0.005208333f; v2right.x = aleft; } if (v2right.y < atop) { uv1min.y() = static_cast(atop - v2right.x) * 0.005208333f; v2right.y = atop; } if (v2left.x > aright) { uv1max.x() = 1.f - static_cast(v2left.x - aright) * 0.005208333f; v2left.x = aright; } if (v2left.y > abottom) { uv1max.y() = 1.f - static_cast(v2left.y - abottom) * 0.005208333f; v2left.y = abottom; } const auto v2sub = v2left - v2right; if (v2sub.x > 0 && v2sub.y > 0) { GXFogType fogType; float fogStartZ; float fogEndZ; float fogNearZ; float fogFarZ; GXColor fogColor; CGX::GetFog(&fogType, &fogStartZ, &fogEndZ, &fogNearZ, &fogFarZ, &fogColor); CGX::SetFog(GX_FOG_NONE, fogStartZ, fogEndZ, fogNearZ, fogFarZ, fogColor); GXSetTexCopySrc(v2right.x, v2right.y, v2sub.x, v2sub.y); GXSetTexCopyDst(v2sub.x, v2sub.y, GX_TF_RGBA8, false); GXCopyTex(CGraphics::mpSpareBuffer, false); GXPixModeSync(); CGraphics::LoadDolphinSpareTexture(v2sub.x, v2sub.y, GX_TF_RGBA8, nullptr, GX_TEXMAP7); x150_reflectionTex.Load(GX_TEXMAP1, EClampMode::Clamp); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC); CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX3x4, GX_TG_TEX1, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL); } } void CCubeRenderer::ReallyRenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const CModel* model, const CSkinnedModel* sModel) { // TODO } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeRenderer.hpp ================================================ #pragma once #include "Runtime/Graphics/CCubeModel.hpp" #include "Runtime/Graphics/CPVSVisSet.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/Graphics/IRenderer.hpp" #include "Runtime/CRandom16.hpp" #include "Runtime/Graphics/CFont.hpp" #include namespace metaforce { class IObjectStore; class IFactory; class CCubeRenderer final : public IRenderer { // TODO function for controlling x318_26_requestRGBA6 // then these can be removed friend class CMorphBallShadow; friend class CWorldTransManager; struct CAreaListItem { const std::vector* x0_geometry; const CAreaRenderOctTree* x4_octTree; /* originally auto_ptrs of vectors */ std::unique_ptr>> x8_textures; std::unique_ptr>> x10_models; s32 x18_areaIdx; /* Per-area octree-word major, light bits minor */ std::vector x1c_lightOctreeWords; CAreaListItem(const std::vector* geom, const CAreaRenderOctTree* octTree, std::unique_ptr>>&& textures, std::unique_ptr>>&& models, s32 areaIdx); }; struct CFogVolumeListItem { zeus::CTransform x0_transform; zeus::CColor x30_color; zeus::CAABox x34_aabb; TLockedToken x4c_model; // bool x58_b; Optional for model token const CSkinnedModel* x5c_skinnedModel = nullptr; CFogVolumeListItem(const zeus::CTransform& xf, const zeus::CColor& color, const zeus::CAABox& aabb, const TLockedToken* model, const CSkinnedModel* sModel) : x0_transform(xf), x30_color(color), x34_aabb(aabb), x5c_skinnedModel(sModel) { if (model) x4c_model = *model; } }; private: IFactory& x8_factory; IObjectStore& xc_store; CFont x10_font{1.f}; u32 x18_primVertCount = 0; std::list x1c_areaListItems; // TODO x34...x40 zeus::CFrustum x44_frustumPlanes; // {zeus::skIdentityMatrix4f, 1.5707964f, 1.f, 1.f, false, 100.f} TDrawableCallback xa8_drawableCallback = nullptr; void* xac_drawableCallbackUserData = nullptr; zeus::CPlane xb0_viewPlane{0.f, 1.f, 0.f, 0.f}; enum class EPVSMode : u8 { Mask, PVS, PVSAndMask } xc0_pvsMode = EPVSMode::Mask; int xc4_; // ? std::optional xc8_pvs; u32 xe0_pvsAreaIdx = UINT32_MAX; CTexture xe4_blackTex{ETexelFormat::RGB565, 4, 4, 1, "Black Texture"}; std::unique_ptr x14c_reflectionTex; CTexture x150_reflectionTex{ETexelFormat::IA8, 32, 32, 1, "Reflection Texture"}; CTexture x1b8_fogVolumeRamp{ETexelFormat::I8, 256, 256, 1, "Fog Volume Ramp Texture"}; CTexture x220_sphereRamp{ETexelFormat::I8, 32, 32, 1, "Sphere Ramp Texture"}; CGraphicsPalette x288_thermoPalette{EPaletteFormat::RGB565, 16}; CRandom16 x2a8_thermalRand{20}; std::list x2ac_fogVolumes; std::list> x2c4_spaceWarps; u32 x2dc_reflectionAge = 2; zeus::CColor x2e0_primColor = zeus::skWhite; zeus::CVector3f x2e4_primNormal = zeus::skForward; float x2f0_thermalVisorLevel = 1.f; zeus::CColor x2f4_thermColor{1.f, 0.f, 1.f, 1.f}; float x2f8_thermColdScale = 0.f; // ??? byte in code zeus::CColor x2fc_tevReg1Color{1.f, 0.f, 1.f, 1.f}; std::vector x300_dynamicLights; u32 x310_phazonSuitMaskCountdown = 0; std::unique_ptr x314_phazonSuitMask; bool x318_24_refectionDirty : 1 = false; bool x318_25_drawWireframe : 1 = false; bool x318_26_requestRGBA6 : 1 = false; bool x318_27_currentRGBA6 : 1 = false; bool x318_28_disableFog : 1 = false; bool x318_29_thermalVisor : 1 = false; bool x318_30_inAreaDraw : 1 = false; bool x318_31_persistRGBA6 : 1 = false; CTexture m_thermalRandomStatic{ETexelFormat::IA4, 640, 448, 1, "Thermal Random Static"}; void GenerateReflectionTex(); void GenerateFogVolumeRampTex(); void GenerateSphereRampTex(); void LoadThermoPalette(); void ReallyDrawPhazonSuitIndirectEffect(const zeus::CColor& vertColor, CTexture& maskTex, CTexture& indTex, const zeus::CColor& modColor, float scale, float offX, float offY); void ReallyDrawPhazonSuitEffect(const zeus::CColor& modColor, CTexture& maskTex); void DoPhazonSuitIndirectAlphaBlur(float blurRadius, float f2); void ReallyDrawSpaceWarp(const zeus::CVector3f& pt, float strength); void ReallyRenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const CModel* model, const CSkinnedModel* sModel); public: CCubeRenderer(IObjectStore& store, IFactory& resFac); ~CCubeRenderer() override; void AddWorldSurfaces(CCubeModel& model); void AddStaticGeometry(const std::vector* geometry, const CAreaRenderOctTree* octTree, s32 areaIdx) override; void EnablePVS(const CPVSVisSet& set, u32 areaIdx) override; void DisablePVS() override; void RemoveStaticGeometry(const std::vector* geometry) override; void DrawUnsortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) override; void DrawSortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) override; void DrawStaticGeometry(s32 areaIdx, s32 mask, s32 targetMask) override; void DrawAreaGeometry(s32 areaIdx, s32 mask, s32 targetMask) override; void PostRenderFogs() override; void SetModelMatrix(const zeus::CTransform& xf) override; void AddParticleGen(CParticleGen& gen) override; void AddParticleGen(CParticleGen& gen, const zeus::CVector3f& pos, const zeus::CAABox& bounds) override; void AddPlaneObject(void* obj, const zeus::CAABox& aabb, const zeus::CPlane& plane, s32 type) override; void AddDrawable(void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, s32 mode, EDrawableSorting sorting) override; void SetDrawableCallback(TDrawableCallback cb, void* ctx) override; void SetWorldViewpoint(const zeus::CTransform& xf) override; void SetPerspective(float fovy, float aspect, float znear, float zfar) override; void SetPerspective(float fovy, float width, float height, float znear, float zfar) override; std::pair SetViewportOrtho(bool centered, float znear, float zfar) override; void SetClippingPlanes(const zeus::CFrustum& frustum) override; void SetViewport(s32 left, s32 right, s32 width, s32 height) override; void SetDepthReadWrite(bool read, bool write) override { CGraphics::SetDepthWriteMode(read, ERglEnum::LEqual, write); } void SetBlendMode_AdditiveAlpha() override { CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear); } void SetBlendMode_AlphaBlended() override { CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); } void SetBlendMode_ColorMultiply() override { CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::Zero, ERglBlendFactor::SrcColor, ERglLogicOp::Clear); } void SetBlendMode_InvertDst() override { CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::InvSrcColor, ERglBlendFactor::Zero, ERglLogicOp::Clear); } void SetBlendMode_InvertSrc() override { CGraphics::SetBlendMode(ERglBlendMode::Logic, ERglBlendFactor::One, ERglBlendFactor::Zero, ERglLogicOp::InvCopy); } void SetBlendMode_NoColorWrite() override { CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::Zero, ERglBlendFactor::One, ERglLogicOp::Clear); } void SetBlendMode_Replace() override { CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::Zero, ERglLogicOp::Clear); } void SetBlendMode_AdditiveDestColor() override { CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcColor, ERglBlendFactor::One, ERglLogicOp::Clear); } void BeginScene() override; void EndScene() override; void SetDebugOption(EDebugOption, s32) override; void BeginPrimitive(EPrimitiveType, s32) override; void BeginLines(int) override; void BeginLineStrip(int) override; void BeginTriangles(int) override; void BeginTriangleStrip(int) override; void BeginTriangleFan(int) override; void PrimVertex(const zeus::CVector3f&) override; void PrimNormal(const zeus::CVector3f&) override; void PrimColor(float, float, float, float) override; void PrimColor(const zeus::CColor&) override; void EndPrimitive() override; void SetAmbientColor(const zeus::CColor& color) override; void DrawString(const char* string, s32, s32) override; float GetFPS() override; void CacheReflection(TReflectionCallback cb, void* ctx, bool clearAfter) override; void DrawSpaceWarp(const zeus::CVector3f& pt, float strength) override; void DrawThermalModel(CModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol, TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) override; void DrawModelDisintegrate(CModel& model, CTexture& tex, const zeus::CColor& color, TConstVectorRef positions, TConstVectorRef normals, float t) override; void DrawModelFlat(CModel& model, const CModelFlags& flags, bool unsortedOnly, TConstVectorRef positions, TConstVectorRef normals) override; void SetWireframeFlags(s32 flags) override; void SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) override; void RenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const TLockedToken* model, const CSkinnedModel* sModel) override; void SetThermal(bool thermal, float level, const zeus::CColor& color) override; void SetThermalColdScale(float scale) override; void DoThermalBlendCold() override; void DoThermalBlendHot() override; u32 GetStaticWorldDataSize() override; void SetGXRegister1Color(const zeus::CColor& color) override; void SetWorldLightFadeLevel(float level) override; void PrepareDynamicLights(const std::vector& lights) override; // Non-virtual functions void SetupRendererStates(bool depthWrite); void AllocatePhazonSuitMaskTexture(); void DrawPhazonSuitIndirectEffect(const zeus::CColor& nonIndirectMod, const TLockedToken& indTex, const zeus::CColor& indirectMod, float blurRadius, float scale, float offX, float offY); void DrawXRayOutline(const zeus::CAABox& aabb); std::list::iterator FindStaticGeometry(const std::vector* geometry); void FindOverlappingWorldModels(std::vector& modelBits, const zeus::CAABox& aabb) const; s32 DrawOverlappingWorldModelIDs(s32 alphaVal, const std::vector& modelBits, const zeus::CAABox& aabb); void DrawOverlappingWorldModelShadows(s32 alphaVal, const std::vector& modelBits, const zeus::CAABox& aabb); void RenderBucketItems(const CAreaListItem* lights); void DrawRenderBucketsDebug() {} void HandleUnsortedModel(CAreaListItem* areaItem, CCubeModel& model, const CModelFlags& flags); void HandleUnsortedModelWireframe(CAreaListItem* areaItem, CCubeModel& model); void ActivateLightsForModel(const CAreaListItem* areaItem, CCubeModel& model); void DoThermalModelDraw(CCubeModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol, TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags); // Getters [[nodiscard]] bool IsInAreaDraw() const { return x318_30_inAreaDraw; } [[nodiscard]] bool IsReflectionDirty() const { return x318_24_refectionDirty; } void SetReflectionDirty(bool v) { x318_24_refectionDirty = v; } [[nodiscard]] bool IsThermalVisorActive() const { return x318_29_thermalVisor; } CTexture* GetRealReflection() { x2dc_reflectionAge = 0; if (x14c_reflectionTex) { return x14c_reflectionTex.get(); } return &xe4_blackTex; } static void SetupCGraphicsState(); }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeSurface.cpp ================================================ #include "CCubeSurface.hpp" #include "Streams/IOStreams.hpp" namespace metaforce { CCubeSurface::CCubeSurface(const u8* ptr, u32 len) : x0_data(ptr) { CMemoryInStream mem(ptr, len, CMemoryInStream::EOwnerShip::NotOwned); x0_center = mem.Get(); xc_materialIndex = mem.ReadLong(); x10_displayListSize = mem.ReadLong(); mem.ReadLong(); // x14_parent mem.ReadLong(); // x18_nextSurface x1c_extraSize = mem.ReadLong(); x20_normal = mem.Get(); if (x1c_extraSize > 0) { x24_bounds = mem.Get(); } } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CCubeSurface.hpp ================================================ #pragma once #include #include #include "GCNTypes.hpp" #include #include namespace metaforce { class CCubeModel; class CCubeSurface { static constexpr zeus::CVector3f skDefaultNormal{1.f, 0.f, 0.f}; const u8* x0_data; // Extracted from surface data zeus::CVector3f x0_center; u32 xc_materialIndex; u32 x10_displayListSize; CCubeModel* x14_parent = nullptr; CCubeSurface* x18_nextSurface = nullptr; u32 x1c_extraSize; zeus::CVector3f x20_normal; zeus::CAABox x24_bounds; public: explicit CCubeSurface(const u8* ptr, u32 len); // Metaforce addition for extracting surface data // bool IsValid() const; [[nodiscard]] CCubeModel* GetParent() { return x14_parent; } [[nodiscard]] const CCubeModel* GetParent() const { return x14_parent; } void SetParent(CCubeModel* parent) { x14_parent = parent; } [[nodiscard]] CCubeSurface* GetNextSurface() { return x18_nextSurface; } [[nodiscard]] const CCubeSurface* GetNextSurface() const { return x18_nextSurface; } void SetNextSurface(CCubeSurface* next) { x18_nextSurface = next; } [[nodiscard]] u32 GetMaterialIndex() const { return xc_materialIndex; } [[nodiscard]] u32 GetDisplayListSize() const { return x10_displayListSize & 0x7fffffff; } [[nodiscard]] u32 GetNormalHint() const { return (x10_displayListSize >> 31) & 1; } [[nodiscard]] const u8* GetDisplayList() const { return x0_data + GetSurfaceHeaderSize(); } [[nodiscard]] u32 GetSurfaceHeaderSize() const { return (0x4b + x1c_extraSize) & ~31; } [[nodiscard]] zeus::CVector3f GetCenter() const { return x0_center; } [[nodiscard]] zeus::CAABox GetBounds() const { return x1c_extraSize != 0 ? x24_bounds : zeus::CAABox{x0_center, x0_center}; } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CDrawable.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include namespace metaforce { enum class EDrawableType : u16 { WorldSurface, Particle, Actor, SimpleShadow, Decal, Invalid = 0xFFFF, }; class CDrawable { EDrawableType x0_type; u16 x2_extraSort; void* x4_data; zeus::CAABox x8_aabb; float x20_viewDist; public: CDrawable(EDrawableType dtype, u16 extraSort, float planeDot, const zeus::CAABox& aabb, void* data) : x0_type(dtype), x2_extraSort(extraSort), x4_data(data), x8_aabb(aabb), x20_viewDist(planeDot) {} EDrawableType GetType() const { return x0_type; } const zeus::CAABox& GetBounds() const { return x8_aabb; } float GetDistance() const { return x20_viewDist; } void* GetData() { return x4_data; } const void* GetData() const { return x4_data; } u16 GetExtraSort() const { return x2_extraSort; } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CDrawablePlaneObject.hpp ================================================ #pragma once #include "Runtime/Graphics/CDrawable.hpp" #include namespace metaforce { class CDrawablePlaneObject : public CDrawable { friend class Buckets; u16 x24_targetBucket = 0; float x28_farDist; zeus::CPlane x2c_plane; bool x3c_24_invertTest : 1; bool x3c_25_zOnly : 1; public: CDrawablePlaneObject(EDrawableType dtype, float closeDist, float farDist, const zeus::CAABox& aabb, bool invertTest, const zeus::CPlane& plane, bool zOnly, void* data) : CDrawable(dtype, 0, closeDist, aabb, data) , x28_farDist(farDist) , x2c_plane(plane) , x3c_24_invertTest{invertTest} , x3c_25_zOnly{zOnly} {} const zeus::CPlane& GetPlane() const { return x2c_plane; } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CFont.cpp ================================================ #include "Runtime/Graphics/CFont.hpp" #include "Runtime/Graphics/CGraphics.hpp" namespace metaforce { /* TODO: Custom I8 font */ std::array CFont::sSystemFont = { /* Omitted due to copyright issues */}; u32 CFont::sNumInstances = 0; std::unique_ptr CFont::mpTexture; CFont::CFont(float scale) : x0_fontSize(16.f * scale), x4_scale(scale) { if (sNumInstances == 0) { mpTexture = std::make_unique(ETexelFormat::I8, 256, 256, 1, "Font Texture"); u8* fontData = new u8[(mpTexture->GetBitDepth() * mpTexture->GetWidth() * mpTexture->GetHeight()) / 8]; memcpy(fontData, sSystemFont.data(), sSystemFont.size()); // u8* textureData = mpTexture->GetBitMapData(); // LinearToTile8(textureData, fontData); delete[] fontData; // mpTexture->UnLock(); } ++sNumInstances; } void CFont::Shutdown() { mpTexture.reset(); } void CFont::TileCopy8(u8* dest, const u8* src) { for (u32 i = 0; i < 4; ++i) { dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2]; dest[3] = src[3]; dest[4] = src[4]; dest[5] = src[5]; dest[6] = src[6]; dest[7] = src[7]; src += 256; dest += 8; } } void CFont::LinearToTile8(u8* dest, const u8* src) { int iVar1 = 0; int iVar2 = 0; for (size_t y = 0; y < 256; y += 4) { iVar2 = iVar1; for (size_t x = 0; x < 256; x += 8) { TileCopy8(dest, src + iVar2); dest += 32; iVar2 += 8; } iVar1 += 1024; } } void CFont::DrawString(const char* str, int x, int y, const zeus::CColor& col) { // bool bVar2 = CGraphics::BeginRender2D(*mpTexture.get()); // char chr = *str; // while (chr != 0) { // u32 cellSize = static_cast(16.f * x4_scale); // ++str; // CGraphics::DoRender2D(*mpTexture, x, y, (chr & 0xf) * 16, 0xf0, 0x10, cellSize, cellSize, col); // chr = *str; // } // CGraphics::EndRender2D(bVar2); } u32 CFont::StringWidth(const char* str) const { u32 width = 0; char chr = *str; while (chr != 0) { ++str; width += static_cast(15.f * x4_scale); chr = *str; } return width; } u32 CFont::CharsWidth(const char* str, u32 len) const { return len * (15.f * x4_scale); } u32 CFont::CharWidth(const char chr) const { return 15.f * x4_scale; } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CFont.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include "Runtime/Graphics/CTexture.hpp" #include #include #include namespace metaforce { class CFont { static std::array sSystemFont; static u32 sNumInstances; static std::unique_ptr mpTexture; float x0_fontSize; float x4_scale; void TileCopy8(u8* dest, const u8* src); void LinearToTile8(u8* dest, const u8* src); public: explicit CFont(float scale); void DrawString(const char* str, int x, int y, const zeus::CColor& color); u32 StringWidth(const char* str) const; u32 CharsWidth(const char* str, u32 len) const; u32 CharWidth(const char chr) const; static void Shutdown(); }; } ================================================ FILE: Runtime/Graphics/CGX.cpp ================================================ #include "CGX.hpp" #include "Graphics/CTexture.hpp" namespace metaforce::CGX { SGXState sGXState{}; std::array sVtxDescList{}; void SetLineWidth(u8 width, GXTexOffset offset) noexcept { u16 flags = width | offset << 8; if (flags != sGXState.x54_lineWidthAndOffset) { sGXState.x54_lineWidthAndOffset = flags; GXSetLineWidth(width, offset); } } void ResetGXStates() noexcept { sGXState.x48_descList = 0; GXClearVtxDesc(); sGXState.x0_arrayPtrs.fill(nullptr); for (GXTexMapID id = GX_TEXMAP0; id < GX_MAX_TEXMAP; id = static_cast(id + 1)) { CTexture::InvalidateTexMap(id); } for (GXTevKColorID id = GX_KCOLOR0; const auto& item : sGXState.x58_kColors) { GXSetTevKColor(id, item); id = static_cast(id + 1); } GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_RED); GXSetTevSwapModeTable(GX_TEV_SWAP2, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_GREEN); GXSetTevSwapModeTable(GX_TEV_SWAP3, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_BLUE); SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); GXSetCurrentMtx(GX_PNMTX0); SetNumIndStages(0); for (int i = 0; i < GX_MAX_INDTEXSTAGE; i++) { GXSetIndTexCoordScale(static_cast(i), GX_ITS_1, GX_ITS_1); } for (int i = 0; i < GX_MAX_TEVSTAGE; i++) { SetTevDirect(static_cast(i)); } for (int i = 0; i < GX_MAX_TEXCOORD; i++) { GXSetTexCoordScaleManually(static_cast(i), false, 0, 0); } GXSetZTexture(GX_ZT_DISABLE, GX_TF_Z8, 0); } } // namespace metaforce::CGX ================================================ FILE: Runtime/Graphics/CGX.hpp ================================================ #pragma once #include "Graphics/GX.hpp" #include "RetroTypes.hpp" #include #include namespace metaforce::CGX { enum class EChannelId { Channel0, // GX_COLOR0 Channel1, // GX_COLOR1 }; struct STevState { u32 x0_colorInArgs = 0; u32 x4_alphaInArgs = 0; u32 x8_colorOps = 0; u32 xc_alphaOps = 0; u32 x10_indFlags = 0; u32 x14_tevOrderFlags = 0; GXTevKColorSel x18_kColorSel = GX_TEV_KCSEL_1; GXTevKAlphaSel x19_kAlphaSel = GX_TEV_KASEL_1; }; struct STexState { u32 x0_coordGen = 0; }; struct SGXState { std::array x0_arrayPtrs{}; std::array x30_prevChanCtrls{}; std::array x34_chanCtrls{0x4000, 0x4000}; std::array x38_chanAmbColors; std::array x40_chanMatColors; u32 x48_descList = 0; union { u8 x4c_chanFlags = 0; // Ordering swapped for LE struct { u8 numDirty : 1; u8 chansDirty : 2; u8 unused : 5; } x4c_flags; }; u8 x4d_prevNumChans = 0; u8 x4e_numChans = 0; u8 x4f_numTexGens = 0; u8 x50_numTevStages = 0; u8 x51_numIndStages = 0; u8 x52_zmode = 0; GXFogType x53_fogType = GX_FOG_NONE; u16 x54_lineWidthAndOffset = 0; u16 x56_blendMode = 0; std::array x58_kColors; std::array x68_tevStates; std::array x228_texStates; u32 x248_alphaCompare = 0; float x24c_fogStartZ = 0.f; float x250_fogEndZ = 0.f; float x254_fogNearZ = 0.f; float x258_fogFarZ = 0.f; GXColor x25c_fogColor; }; extern SGXState sGXState; extern std::array sVtxDescList; static inline u32 MaskAndShiftLeft(u32 v, u32 m, u32 s) { return (v & m) << s; } static inline u32 ShiftRightAndMask(u32 v, u32 m, u32 s) { return (v >> s) & m; } static inline void update_fog(u32 value) noexcept { if (sGXState.x53_fogType == GX_FOG_NONE || (sGXState.x56_blendMode & 0xE0) == (value & 0xE0)) { return; } if ((value & 0xE0) == 0x20) { GXSetFogColor(GX_CLEAR); return; } GXSetFogColor(sGXState.x25c_fogColor); } static inline void FlushState() noexcept { if (sGXState.x4c_chanFlags & 1) { GXSetNumChans(sGXState.x4e_numChans); sGXState.x4d_prevNumChans = sGXState.x4e_numChans; } if (sGXState.x4c_chanFlags & 2) { u16 flags = sGXState.x34_chanCtrls[0]; GXBool enable = ShiftRightAndMask(flags, 1, 0); GXColorSrc ambSrc = static_cast(ShiftRightAndMask(flags, 1, 1)); GXColorSrc matSrc = static_cast(ShiftRightAndMask(flags, 1, 2)); u32 lightMask = ShiftRightAndMask(flags, 0xFF, 3); GXDiffuseFn diffFn = static_cast(ShiftRightAndMask(flags, 3, 11)); GXAttnFn attnFn = static_cast(ShiftRightAndMask(flags, 3, 13)); GXSetChanCtrl(GX_COLOR0, enable, ambSrc, matSrc, lightMask, diffFn, attnFn); sGXState.x30_prevChanCtrls[0] = sGXState.x34_chanCtrls[0]; } if (sGXState.x4c_chanFlags & 4) { u16 flags = sGXState.x34_chanCtrls[1]; GXBool enable = ShiftRightAndMask(flags, 1, 0); GXColorSrc ambSrc = static_cast(ShiftRightAndMask(flags, 1, 1)); GXColorSrc matSrc = static_cast(ShiftRightAndMask(flags, 1, 2)); u32 lightMask = ShiftRightAndMask(flags, 0xFF, 3); GXDiffuseFn diffFn = static_cast(ShiftRightAndMask(flags, 3, 11)); GXAttnFn attnFn = static_cast(ShiftRightAndMask(flags, 3, 13)); GXSetChanCtrl(GX_COLOR1, enable, ambSrc, matSrc, lightMask, diffFn, attnFn); sGXState.x30_prevChanCtrls[1] = sGXState.x34_chanCtrls[1]; } sGXState.x4c_chanFlags = 0; } static inline void Begin(GXPrimitive primitive, GXVtxFmt fmt, u16 nverts) noexcept { if (sGXState.x4c_chanFlags != 0) { FlushState(); } GXBegin(primitive, fmt, nverts); } static inline void End() noexcept { GXEnd(); } static inline void CallDisplayList(const void* data, u32 nbytes) noexcept { if (sGXState.x4c_chanFlags != 0) { FlushState(); } GXCallDisplayList(data, nbytes); } static inline const GXColor& GetChanAmbColor(EChannelId id) noexcept { const auto idx = std::underlying_type_t(id); return sGXState.x38_chanAmbColors[idx]; } void ResetGXStates() noexcept; static inline void SetAlphaCompare(GXCompare comp0, u8 ref0, GXAlphaOp op, GXCompare comp1, u8 ref1) noexcept { u32 flags = ref1 << 17 | (comp1 & 7) << 14 | (op & 7) << 11 | ref0 << 3 | (comp0 & 7); if (flags != sGXState.x248_alphaCompare) { sGXState.x248_alphaCompare = flags; GXSetAlphaCompare(comp0, ref0, op, comp1, ref1); GXSetZCompLoc(comp0 == GX_ALWAYS); } } static inline void SetArray(GXAttr attr, std::span data, u8 stride) noexcept { const auto* ptr = static_cast(data.data()); if (ptr != nullptr && sGXState.x0_arrayPtrs[attr - GX_VA_POS] != ptr) { sGXState.x0_arrayPtrs[attr - GX_VA_POS] = const_cast(ptr); GXSetArray(attr, data.data(), data.size(), stride, true); } } template static inline void SetArray(GXAttr attr, std::span data) noexcept { const auto* ptr = static_cast(data.data()); if (ptr != nullptr && sGXState.x0_arrayPtrs[attr - GX_VA_POS] != ptr) { sGXState.x0_arrayPtrs[attr - GX_VA_POS] = const_cast(ptr); GXSetArray(attr, data.data(), data.size_bytes(), sizeof(T), true); } } template static inline void SetArray(GXAttr attr, const std::array& data) noexcept { const auto* ptr = static_cast(data.data()); if (ptr != nullptr && sGXState.x0_arrayPtrs[attr - GX_VA_POS] != ptr) { sGXState.x0_arrayPtrs[attr - GX_VA_POS] = const_cast(ptr); GXSetArray(attr, ptr, data.size() * sizeof(T), sizeof(T), true); } } // Aurora addition: clear array to force reupload static inline void ClearArray(GXAttr attr) noexcept { GXSetArray(attr, nullptr, 0, 0, true); sGXState.x0_arrayPtrs[attr - GX_VA_POS] = nullptr; } static inline void SetBlendMode(GXBlendMode mode, GXBlendFactor srcFac, GXBlendFactor dstFac, GXLogicOp op) noexcept { const u16 flags = (op & 0xF) << 8 | (dstFac & 7) << 5 | (srcFac & 7) << 2 | (mode & 3); if (flags != sGXState.x56_blendMode) { update_fog(flags); sGXState.x56_blendMode = flags; GXSetBlendMode(mode, srcFac, dstFac, op); } } static inline void SetChanAmbColor(EChannelId id, GXColor color) noexcept { const auto idx = std::underlying_type_t(id); if (color != sGXState.x38_chanAmbColors[idx]) { sGXState.x38_chanAmbColors[idx] = color; GXSetChanAmbColor(GXChannelID(idx + GX_COLOR0A0), color); } } static inline void SetChanAmbColor(EChannelId id, const zeus::CColor& color) noexcept { SetChanAmbColor(id, to_gx_color(color)); } static inline void SetChanCtrl(EChannelId channel, GXBool enable, GXColorSrc ambSrc, GXColorSrc matSrc, GX::LightMask lights, GXDiffuseFn diffFn, GXAttnFn attnFn) noexcept { const auto idx = static_cast>(channel); u16& state = sGXState.x34_chanCtrls[idx]; u16 prevFlags = sGXState.x30_prevChanCtrls[idx]; if (lights.none()) { enable = GX_FALSE; } u32 flags = MaskAndShiftLeft(enable, 1, 0) | MaskAndShiftLeft(ambSrc, 1, 1) | MaskAndShiftLeft(matSrc, 1, 2) | MaskAndShiftLeft(lights.to_ulong(), 0xFF, 3) | MaskAndShiftLeft(diffFn, 3, 11) | MaskAndShiftLeft(attnFn, 3, 13); state = flags; sGXState.x4c_chanFlags = ((flags != prevFlags) << (idx + 1)) | (sGXState.x4c_chanFlags & ~(1 << (idx + 1))); } static inline void SetChanCtrl_Compressed(EChannelId channel, GX::LightMask lights, u32 ctrl) { const auto idx = static_cast>(channel); u16& state = sGXState.x34_chanCtrls[idx]; u16 prevFlags = sGXState.x30_prevChanCtrls[idx]; u32 flags = ctrl & ~1; if (lights.any()) { flags = ctrl | (lights.to_ulong() & 0xFF) << 3; } state = flags; sGXState.x4c_chanFlags = ((flags != prevFlags) << (idx + 1)) | (sGXState.x4c_chanFlags & ~(1 << (idx + 1))); } static inline void SetChanMatColor(EChannelId id, GXColor color) noexcept { const auto idx = std::underlying_type_t(id); if (color != sGXState.x40_chanMatColors[idx]) { sGXState.x40_chanMatColors[idx] = color; GXSetChanMatColor(GXChannelID(idx + GX_COLOR0A0), color); } } static inline void SetChanMatColor(EChannelId id, const zeus::CColor& color) noexcept { SetChanMatColor(id, to_gx_color(color)); } static inline void SetFog(GXFogType type, float startZ, float endZ, float nearZ, float farZ, const GXColor& color) noexcept { sGXState.x25c_fogColor = color; sGXState.x53_fogType = type; sGXState.x24c_fogStartZ = startZ; sGXState.x250_fogEndZ = endZ; sGXState.x254_fogNearZ = nearZ; sGXState.x258_fogFarZ = farZ; auto fogColor = color; if ((sGXState.x56_blendMode & 0xE0) == 0x20) { fogColor = GX_CLEAR; } GXSetFog(type, startZ, endZ, nearZ, farZ, fogColor); } void SetIndTexMtxSTPointFive(GXIndTexMtxID id, s8 scaleExp) noexcept; void SetLineWidth(u8 width, GXTexOffset offset) noexcept; static inline void SetNumChans(u8 num) noexcept { sGXState.x4e_numChans = num; sGXState.x4c_flags.numDirty = sGXState.x4e_numChans != sGXState.x4d_prevNumChans; } static inline void SetNumIndStages(u8 num) noexcept { auto& state = sGXState.x51_numIndStages; if (num != state) { state = num; GXSetNumIndStages(num); } } static inline void SetNumTevStages(u8 num) noexcept { auto& state = sGXState.x50_numTevStages; if (num != state) { state = num; GXSetNumTevStages(num); } } static inline void SetNumTexGens(u8 num) noexcept { auto& state = sGXState.x4f_numTexGens; if (num != state) { state = num; GXSetNumTexGens(num); } } static inline void SetStandardTevColorAlphaOp(GXTevStageID stageId) noexcept { auto& state = sGXState.x68_tevStates[stageId]; if (state.x8_colorOps != 0x100 || state.xc_alphaOps != 0x100) { state.x8_colorOps = 0x100; state.xc_alphaOps = 0x100; GXSetTevColorOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); GXSetTevAlphaOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); } } static inline void SetTevAlphaIn(GXTevStageID stageId, GXTevAlphaArg a, GXTevAlphaArg b, GXTevAlphaArg c, GXTevAlphaArg d) noexcept { u32 flags = (d & 31) << 15 | (c & 31) << 10 | (b & 31) << 5 | (a & 31); auto& state = sGXState.x68_tevStates[stageId].x4_alphaInArgs; if (flags != state) { state = flags; GXSetTevAlphaIn(stageId, a, b, c, d); } } static inline void SetTevAlphaOp(GXTevStageID stageId, GXTevOp op, GXTevBias bias, GXTevScale scale, GXBool clamp, GXTevRegID outReg) noexcept { u32 flags = (outReg & 3) << 9 | (u8(clamp) & 1) << 8 | (scale & 3) << 6 | (bias & 3) << 4 | (op & 15); auto& state = sGXState.x68_tevStates[stageId].xc_alphaOps; if (flags != state) { state = flags; GXSetTevAlphaOp(stageId, op, bias, scale, clamp, outReg); } } static inline void SetTevAlphaOp_Compressed(GXTevStageID stageId, u32 ops) noexcept { auto& state = sGXState.x68_tevStates[stageId].xc_alphaOps; if (ops != state) { state = ops; GXSetTevAlphaOp(stageId, GXTevOp(ops & 31), GXTevBias(ops >> 4 & 3), GXTevScale(ops >> 6 & 3), GXBool(ops >> 8 & 1), GXTevRegID(ops >> 9 & 3)); } } static inline void SetTevColorIn(GXTevStageID stageId, GXTevColorArg a, GXTevColorArg b, GXTevColorArg c, GXTevColorArg d) noexcept { u32 flags = (d & 31) << 15 | (c & 31) << 10 | (b & 31) << 5 | (a & 31); auto& state = sGXState.x68_tevStates[stageId].x0_colorInArgs; if (flags != state) { state = flags; GXSetTevColorIn(stageId, a, b, c, d); } } static inline void SetTevColorOp(GXTevStageID stageId, GXTevOp op, GXTevBias bias, GXTevScale scale, GXBool clamp, GXTevRegID outReg) noexcept { u32 flags = (outReg & 3) << 9 | (u8(clamp) & 1) << 8 | (scale & 3) << 6 | (bias & 3) << 4 | (op & 15); auto& state = sGXState.x68_tevStates[stageId].x8_colorOps; if (flags != state) { state = flags; GXSetTevColorOp(stageId, op, bias, scale, clamp, outReg); } } static inline void SetTevColorOp_Compressed(GXTevStageID stageId, u32 ops) noexcept { auto& state = sGXState.x68_tevStates[stageId].x8_colorOps; if (ops != state) { state = ops; GXSetTevColorOp(stageId, GXTevOp(ops & 31), GXTevBias(ops >> 4 & 3), GXTevScale(ops >> 6 & 3), GXBool(ops >> 8 & 1), GXTevRegID(ops >> 9 & 3)); } } static inline void SetTevDirect(GXTevStageID stageId) noexcept { auto& state = sGXState.x68_tevStates[stageId].x10_indFlags; if (state != 0) { state = 0; GXSetTevDirect(stageId); } } static inline void SetStandardDirectTev_Compressed(GXTevStageID stageId, u32 colorArgs, u32 alphaArgs, u32 colorOps, u32 alphaOps) noexcept { auto& state = sGXState.x68_tevStates[stageId]; SetTevDirect(stageId); if (state.x0_colorInArgs != colorArgs) { state.x0_colorInArgs = colorArgs; GXSetTevColorIn(stageId, GXTevColorArg(colorArgs & 31), GXTevColorArg(colorArgs >> 5 & 31), GXTevColorArg(colorArgs >> 10 & 31), GXTevColorArg(colorArgs >> 15 & 31)); } if (state.x4_alphaInArgs != alphaArgs) { state.x4_alphaInArgs = alphaArgs; GXSetTevAlphaIn(stageId, GXTevAlphaArg(alphaArgs & 31), GXTevAlphaArg(alphaArgs >> 5 & 31), GXTevAlphaArg(alphaArgs >> 10 & 31), GXTevAlphaArg(alphaArgs >> 15 & 31)); } if (colorOps != alphaOps || (colorOps & 0x1FF) != 0x100) { SetTevColorOp_Compressed(stageId, colorOps); SetTevAlphaOp_Compressed(stageId, alphaOps); } else if (colorOps != state.x8_colorOps || colorOps != state.xc_alphaOps) { state.x8_colorOps = colorOps; state.xc_alphaOps = colorOps; const auto outReg = GXTevRegID(colorOps >> 9 & 3); GXSetTevColorOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, outReg); GXSetTevAlphaOp(stageId, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, outReg); } } static inline void SetTevIndirect(GXTevStageID stageId, GXIndTexStageID indStage, GXIndTexFormat fmt, GXIndTexBiasSel biasSel, GXIndTexMtxID mtxSel, GXIndTexWrap wrapS, GXIndTexWrap wrapT, GXBool addPrev, GXBool indLod, GXIndTexAlphaSel alphaSel) noexcept { // TODO GXSetTevIndirect(stageId, indStage, fmt, biasSel, mtxSel, wrapS, wrapT, addPrev, indLod, alphaSel); } static inline void SetTevIndWarp(GXTevStageID stageId, GXIndTexStageID indStage, GXBool signedOffset, GXBool replaceMode, GXIndTexMtxID mtxSel) noexcept { // TODO GXSetTevIndWarp(stageId, indStage, signedOffset, replaceMode, mtxSel); } static inline void SetTevKAlphaSel(GXTevStageID stageId, GXTevKAlphaSel sel) noexcept { auto& state = sGXState.x68_tevStates[stageId].x19_kAlphaSel; if (sel != state) { state = sel; GXSetTevKAlphaSel(stageId, sel); } } static inline void SetTevKColor(GXTevKColorID id, const GXColor& color) noexcept { auto& state = sGXState.x58_kColors[id]; if (color != state) { state = color; GXSetTevKColor(id, color); } } static inline void SetTevKColor(GXTevKColorID id, const zeus::CColor& color) noexcept { SetTevKColor(id, to_gx_color(color)); } static inline void SetTevKColorSel(GXTevStageID stageId, GXTevKColorSel sel) noexcept { auto& state = sGXState.x68_tevStates[stageId].x18_kColorSel; if (sel != state) { state = sel; GXSetTevKColorSel(stageId, sel); } } static inline void SetTevOrder(GXTevStageID stageId, GXTexCoordID texCoord, GXTexMapID texMap, GXChannelID color) noexcept { u32 flags = (color & 0xFF) << 16 | (texMap & 0xFF) << 8 | (texCoord & 0xFF); auto& state = sGXState.x68_tevStates[stageId].x14_tevOrderFlags; if (flags != state) { state = flags; GXSetTevOrder(stageId, texCoord, texMap, color); } } static inline void SetTexCoordGen(GXTexCoordID dstCoord, GXTexGenType fn, GXTexGenSrc src, GXTexMtx mtx, GXBool normalize, GXPTTexMtx postMtx) noexcept { u32 flags = ((postMtx - GX_PTTEXMTX0) & 63) << 15 | (u8(normalize) & 1) << 14 | ((mtx - GX_TEXMTX0) & 31) << 9 | (src & 31) << 4 | (fn & 15); auto& state = sGXState.x228_texStates[dstCoord].x0_coordGen; if (flags != state) { state = flags; GXSetTexCoordGen2(dstCoord, fn, src, mtx, normalize, postMtx); } } static inline void SetTexCoordGen(GXTexCoordID dstCoord, u32 flags) noexcept { auto& state = sGXState.x228_texStates[dstCoord].x0_coordGen; if (flags != state) { state = flags; GXSetTexCoordGen2(dstCoord, GXTexGenType(flags & 15), GXTexGenSrc(flags >> 4 & 31), GXTexMtx((flags >> 9 & 31) + GX_TEXMTX0), GXBool(flags >> 14 & 1), GXPTTexMtx((flags >> 15 & 63) + GX_PTTEXMTX0)); } } static inline void SetVtxDescv_Compressed(u32 flags) noexcept { if (flags == sGXState.x48_descList) { return; } GXVtxDescList* list = sVtxDescList.data(); for (u32 idx = 0; idx < sVtxDescList.size() - 1; ++idx) { u32 shift = idx * 2; if ((flags & 3 << shift) == (sGXState.x48_descList & 3 << shift)) { continue; } list->attr = static_cast(GX_VA_POS + idx); list->type = static_cast(flags >> shift & 3); ++list; } list->attr = GX_VA_NULL; list->type = GX_NONE; GXSetVtxDescv(sVtxDescList.data()); sGXState.x48_descList = flags; } static inline void SetVtxDescv(const GXVtxDescList* descList) noexcept { u32 flags = 0; for (; descList->attr != GX_VA_NULL; ++descList) { flags |= (descList->type & 3) << (descList->attr - GX_VA_POS) * 2; } SetVtxDescv_Compressed(flags); } static inline void SetZMode(GXBool compareEnable, GXCompare func, GXBool updateEnable) noexcept { u32 flags = (func & 0xFF) << 2 | (u8(updateEnable) << 1) | (u8(compareEnable) & 1); auto& state = sGXState.x52_zmode; if (flags != state) { state = flags; GXSetZMode(compareEnable, func, updateEnable); } } static inline void GetFog(GXFogType* fogType, float* fogStartZ, float* fogEndZ, float* fogNearZ, float* fogFarZ, GXColor* fogColor) { if (fogType != nullptr) { *fogType = sGXState.x53_fogType; } if (fogStartZ != nullptr) { *fogStartZ = sGXState.x24c_fogStartZ; } if (fogEndZ != nullptr) { *fogEndZ = sGXState.x250_fogEndZ; } if (fogNearZ != nullptr) { *fogNearZ = sGXState.x254_fogNearZ; } if (fogFarZ != nullptr) { *fogFarZ = sGXState.x258_fogFarZ; } if (fogColor != nullptr) { *fogColor = sGXState.x25c_fogColor; } } } // namespace metaforce::CGX ================================================ FILE: Runtime/Graphics/CGraphics.cpp ================================================ #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/CTimeProvider.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/GuiSys/CGuiSys.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/Logging.hpp" #include #include #include namespace metaforce { using CVector3f = zeus::CVector3f; using CVector2i = zeus::CVector2i; using CTransform4f = zeus::CTransform; using CColor = zeus::CColor; using uchar = unsigned char; using uint = unsigned int; using ushort = unsigned short; CGraphics::CRenderState CGraphics::sRenderState; VecPtr CGraphics::vtxBuffer; VecPtr CGraphics::nrmBuffer; Vec2Ptr CGraphics::txtBuffer0; Vec2Ptr CGraphics::txtBuffer1; uint* CGraphics::clrBuffer; bool CGraphics::mJustReset; ERglCullMode CGraphics::mCullMode; int CGraphics::mNumLightsActive; float CGraphics::mDepthNear; VecPtr CGraphics::mpVtxBuffer; VecPtr CGraphics::mpNrmBuffer; Vec2Ptr CGraphics::mpTxtBuffer0; Vec2Ptr CGraphics::mpTxtBuffer1; uint* CGraphics::mpClrBuffer; struct { Vec vtx; Vec nrm; Vec2 uv0; Vec2 uv1; u32 color; u16 textureUsed; u8 streamFlags; } vtxDescr; CVector3f CGraphics::kDefaultPositionVector(0.f, 0.f, 0.f); CVector3f CGraphics::kDefaultDirectionVector(0.f, 1.f, 0.f); CGraphics::CProjectionState CGraphics::mProj(true, -1.f, 1.f, 1.f, -1.f, 1.f, 100.f); CTransform4f CGraphics::mViewMatrix = CTransform4f(); CTransform4f CGraphics::mModelMatrix = CTransform4f(); CColor CGraphics::mClearColor = zeus::skBlack; CVector3f CGraphics::mViewPoint(0.f, 0.f, 0.f); GXLightObj CGraphics::mLightObj[8]; // GXTexRegion CGraphics::mTexRegions[GX_MAX_TEXMAP]; // GXTexRegion CGraphics::mTexRegionsCI[GX_MAX_TEXMAP / 2]; GXRenderModeObj CGraphics::mRenderModeObj; Mtx CGraphics::mGXViewPointMatrix; Mtx CGraphics::mGXModelMatrix; Mtx CGraphics::mGxModelView; Mtx CGraphics::mCameraMtx; int CGraphics::mNumPrimitives; int CGraphics::mFrameCounter; float CGraphics::mFramesPerSecond; float CGraphics::mLastFramesPerSecond; int CGraphics::mNumBreakpointsWaiting; int CGraphics::mFlippingState; bool CGraphics::mLastFrameUsedAbove; bool CGraphics::mInterruptLastFrameUsedAbove; GX::LightMask CGraphics::mLightActive; GX::LightMask CGraphics::mLightsWereOn; void* CGraphics::mpFrameBuf1; void* CGraphics::mpFrameBuf2; void* CGraphics::mpCurrenFrameBuf; int CGraphics::mSpareBufferSize; void* CGraphics::mpSpareBuffer; int CGraphics::mSpareBufferTexCacheSize; // GXTexRegionCallback CGraphics::mGXDefaultTexRegionCallback; void* CGraphics::mpFifo; GXFifoObj* CGraphics::mpFifoObj; uint CGraphics::mRenderTimings; float CGraphics::mSecondsMod900; CTimeProvider* CGraphics::mpExternalTimeProvider; int CGraphics::mScreenStretch; int CGraphics::mScreenPositionX; int CGraphics::mScreenPositionY; CViewport CGraphics::mViewport = {0, 0, 640, 480, 320.f, 240.f}; ELightType CGraphics::mLightTypes[8] = { ELightType::Directional, ELightType::Directional, ELightType::Directional, ELightType::Directional, ELightType::Directional, ELightType::Directional, ELightType::Directional, ELightType::Directional, }; // const CTevCombiners::CTevPass& CGraphics::kEnvPassthru = CTevCombiners::kEnvPassthru; bool CGraphics::mIsBeginSceneClearFb = true; ERglEnum CGraphics::mDepthFunc = ERglEnum::LEqual; ERglPrimitive CGraphics::mCurrentPrimitive = ERglPrimitive::Points; float CGraphics::mDepthFar = 1.f; u32 CGraphics::mClearDepthValue = GX_MAX_Z24; bool CGraphics::mIsGXModelMatrixIdentity = true; bool CGraphics::mFirstFrame = true; GXBool CGraphics::mUseVideoFilter = GX_ENABLE; float CGraphics::mBrightness = 1.f; const GXTexMapID CGraphics::kSpareBufferTexMapID = GX_TEXMAP7; // We don't actually store anything here static std::array sSpareFrameBuffer; void CGraphics::DisableAllLights() { mNumLightsActive = 0; mLightActive.reset(); CGX::SetChanCtrl(CGX::EChannelId::Channel0, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); } static inline GXLightID get_hw_light_index(ERglLight light) { return static_cast((1 << light) & (GX_MAX_LIGHT - 1)); } void CGraphics::LoadLight(ERglLight light, const CLight& info) { GXLightID lightId = get_hw_light_index(light); ELightType type = info.GetType(); CVector3f pos = info.GetPosition(); CVector3f dir = info.GetDirection(); switch (type) { case ELightType::Spot: { MTXMultVec(mCameraMtx, reinterpret_cast(&pos), reinterpret_cast(&pos)); GXLightObj* obj = &mLightObj[light]; GXInitLightPos(obj, pos.x(), pos.y(), pos.z()); MTXMultVecSR(mCameraMtx, reinterpret_cast(&dir), reinterpret_cast(&dir)); GXInitLightDir(obj, dir.x(), dir.y(), dir.z()); GXInitLightAttn(obj, 1.f, 0.f, 0.f, info.GetAttenuationConstant(), info.GetAttenuationLinear(), info.GetAttenuationQuadratic()); GXInitLightSpot(obj, info.GetSpotCutoff(), GX_SP_COS2); break; } case ELightType::Point: case ELightType::LocalAmbient: { MTXMultVec(mCameraMtx, reinterpret_cast(&pos), reinterpret_cast(&pos)); GXInitLightPos(&mLightObj[light], pos.x(), pos.y(), pos.z()); GXInitLightAttn(&mLightObj[light], 1.f, 0.f, 0.f, info.GetAttenuationConstant(), info.GetAttenuationLinear(), info.GetAttenuationQuadratic()); break; } case ELightType::Directional: { MTXMultVecSR(mCameraMtx, reinterpret_cast(&dir), reinterpret_cast(&dir)); dir = -dir; GXInitLightPos(&mLightObj[light], dir.x() * 1048576.f, dir.y() * 1048576.f, dir.z() * 1048576.f); GXInitLightAttn(&mLightObj[light], 1.f, 0.f, 0.f, 1.f, 0.f, 0.f); break; } case ELightType::Custom: { MTXMultVec(mCameraMtx, reinterpret_cast(&pos), reinterpret_cast(&pos)); GXLightObj* obj = &mLightObj[light]; GXInitLightPos(obj, pos.x(), pos.y(), pos.z()); MTXMultVecSR(mCameraMtx, reinterpret_cast(&dir), reinterpret_cast(&dir)); GXInitLightDir(obj, dir.x(), dir.y(), dir.z()); GXInitLightAttn(obj, info.GetAngleAttenuationConstant(), info.GetAngleAttenuationLinear(), info.GetAngleAttenuationQuadratic(), info.GetAttenuationConstant(), info.GetAttenuationLinear(), info.GetAttenuationQuadratic()); break; } default: break; } GXInitLightColor(&mLightObj[light], to_gx_color(info.GetColor())); GXLoadLightObjImm(&mLightObj[light], lightId); mLightTypes[light] = info.GetType(); } void CGraphics::EnableLight(ERglLight light) { CGX::SetNumChans(1); GX::LightMask lightsWereOn = mLightActive; if (!lightsWereOn.test(light)) { mLightActive.set(light); CGX::SetChanCtrl(CGX::EChannelId::Channel0, true, GX_SRC_REG, GX_SRC_REG, mLightActive, GX_DF_CLAMP, GX_AF_SPOT); ++mNumLightsActive; } mLightsWereOn = mLightActive; } void CGraphics::SetLightState(GX::LightMask lights) { GXAttnFn attnFn = GX_AF_NONE; if (lights.any()) { attnFn = GX_AF_SPOT; } GXDiffuseFn diffFn = GX_DF_NONE; if (lights.any()) { diffFn = GX_DF_CLAMP; } CGX::SetChanCtrl(CGX::EChannelId::Channel0, lights.any() ? GX_ENABLE : GX_DISABLE, GX_SRC_REG, (vtxDescr.streamFlags & 2) != 0 ? GX_SRC_VTX : GX_SRC_REG, lights, diffFn, attnFn); mLightActive = lights; mNumLightsActive = lights.count(); } void CGraphics::SetAmbientColor(const zeus::CColor& col) { CGX::SetChanAmbColor(CGX::EChannelId::Channel0, col); CGX::SetChanAmbColor(CGX::EChannelId::Channel1, col); } void CGraphics::SetFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) { CGX::SetFog(static_cast(mode), startz, endz, mProj.GetNear(), mProj.GetFar(), to_gx_color(color)); } void CGraphics::SetDepthWriteMode(const bool test, ERglEnum comp, const bool write) { mDepthFunc = comp; CGX::SetZMode(test, static_cast(comp), write); } void CGraphics::SetBlendMode(ERglBlendMode mode, ERglBlendFactor src, ERglBlendFactor dst, ERglLogicOp op) { CGX::SetBlendMode(static_cast(mode), static_cast(src), static_cast(dst), static_cast(op)); } void CGraphics::SetCullMode(ERglCullMode cullMode) { mCullMode = cullMode; GXSetCullMode(static_cast(cullMode)); } void CGraphics::ClearBackAndDepthBuffers() { GXInvalidateTexAll(); GXSetViewport(0.f, 0.f, mRenderModeObj.fbWidth, mRenderModeObj.xfbHeight, 0.f, 1.f); GXInvalidateVtxCache(); } void CGraphics::BeginScene() { ClearBackAndDepthBuffers(); } void CGraphics::EndScene() { CGX::SetZMode(true, GX_LEQUAL, true); // volatile int& numBreakPt = const_cast< volatile int& >(mNumBreakpointsWaiting); // while (numBreakPt > 0) { // OSYieldThread(); // } ++mNumBreakpointsWaiting; void*& frameBuf = mpCurrenFrameBuf; float brightness = std::clamp(mBrightness, 0.f, 2.f); static const u8 copyFilter[7] = {0x00, 0x00, 0x15, 0x16, 0x15, 0x00, 0x00}; const u8* inFilter = mUseVideoFilter ? mRenderModeObj.vfilter : copyFilter; u8 vfilter[7]; for (int i = 0; i < 7; i++) { vfilter[i] = static_cast(static_cast(inFilter[i]) * brightness); } GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, true, vfilter); GXCopyDisp(frameBuf, mIsBeginSceneClearFb ? GX_TRUE : GX_FALSE); GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, mUseVideoFilter ? GX_ENABLE : GX_DISABLE, mRenderModeObj.vfilter); // GXSetBreakPtCallback(SwapBuffers); // VISetPreRetraceCallback(VideoPreCallback); // VISetPostRetraceCallback(VideoPostCallback); GXFlush(); GXFifoObj* fifo = GXGetGPFifo(); void* readPtr; void* writePtr; GXGetFifoPtrs(fifo, &readPtr, &writePtr); // GXEnableBreakPt(writePtr); mLastFrameUsedAbove = mInterruptLastFrameUsedAbove; ++mFrameCounter; // CFrameDelayedKiller::fn_8036CB90(); } static constexpr GXVtxDescList skPosColorTexDirect[] = { {GX_VA_POS, GX_DIRECT}, {GX_VA_CLR0, GX_DIRECT}, {GX_VA_TEX0, GX_DIRECT}, {GX_VA_NULL, GX_DIRECT}, }; void CGraphics::Render2D(CTexture& tex, int x, int y, int w, int h, const zeus::CColor& col, bool scale) { Mtx44 proj; if (scale) { const float viewportAspect = GetViewportAspect(); float left = -320.f; float right = 320.f; float top = 224.f; float bottom = -224.f; if (viewportAspect > 4.f / 3.f) { float width = 224.0f * viewportAspect; left = -width; right = width; } else { float height = 320.0f / viewportAspect; top = height; bottom = -height; } MTXOrtho(proj, top, bottom, left, right, -1.f, -10.f); } else { MTXOrtho(proj, mViewport.mHeight / 2, -(mViewport.mHeight / 2), -(mViewport.mWidth / 2), mViewport.mWidth / 2, -1.f, -10.f); } GXSetProjection(proj, GX_ORTHOGRAPHIC); uint c = col.toRGBA(); Mtx mtx; MTXIdentity(mtx); GXLoadPosMtxImm(mtx, GX_PNMTX0); float x2, y2, x1, y1; if (scale) { x1 = x - 320; y1 = y - 224; x2 = x1 + w; y2 = y1 + h; } else { x1 = x - mViewport.mWidth / 2; y1 = y - mViewport.mHeight / 2; x2 = x1 + w; y2 = y1 + h; } // Save state + setup CGX::SetVtxDescv(skPosColorTexDirect); SetTevStates(6); mLightsWereOn = mLightActive; if (mLightActive.any()) { DisableAllLights(); } ERglCullMode cullMode = mCullMode; SetCullMode(ERglCullMode::None); tex.Load(GX_TEXMAP0, EClampMode::Repeat); // Draw CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4); GXPosition3f32(x1, y1, 1.f); GXColor1u32(c); GXTexCoord2f32(0.f, 0.f); GXPosition3f32(x2, y1, 1.f); GXColor1u32(c); GXTexCoord2f32(1.f, 0.f); GXPosition3f32(x1, y2, 1.f); GXColor1u32(c); GXTexCoord2f32(0.f, 1.f); GXPosition3f32(x2, y2, 1.f); GXColor1u32(c); GXTexCoord2f32(1.f, 1.f); CGX::End(); // Restore state if (mLightsWereOn.any()) { SetLightState(mLightsWereOn); } FlushProjection(); mIsGXModelMatrixIdentity = false; SetModelMatrix(mModelMatrix); SetCullMode(cullMode); } void CGraphics::SetAlphaCompare(ERglAlphaFunc comp0, uchar ref0, ERglAlphaOp op, ERglAlphaFunc comp1, uchar ref1) { CGX::SetAlphaCompare(static_cast(comp0), ref0, static_cast(op), static_cast(comp1), ref1); } void CGraphics::SetViewPointMatrix(const zeus::CTransform& xf) { mViewMatrix = xf; mGXViewPointMatrix[0][0] = xf.basis[0][0]; mGXViewPointMatrix[0][1] = xf.basis[0][1]; mGXViewPointMatrix[0][2] = xf.basis[0][2]; mGXViewPointMatrix[0][3] = 0.f; mGXViewPointMatrix[1][0] = xf.basis[2][0]; mGXViewPointMatrix[1][1] = xf.basis[2][1]; mGXViewPointMatrix[1][2] = xf.basis[2][2]; mGXViewPointMatrix[1][3] = 0.f; mGXViewPointMatrix[2][0] = -xf.basis[1][0]; mGXViewPointMatrix[2][1] = -xf.basis[1][1]; mGXViewPointMatrix[2][2] = -xf.basis[1][2]; mGXViewPointMatrix[2][3] = 0.f; mViewPoint = xf.origin; SetViewMatrix(); } void CGraphics::SetIdentityViewPointMatrix() { mViewMatrix = CTransform4f(); MTXIdentity(mGXViewPointMatrix); mGXViewPointMatrix[2][2] = 0.f; mGXViewPointMatrix[1][1] = 0.f; mGXViewPointMatrix[1][2] = 1.f; mGXViewPointMatrix[2][1] = -1.f; mViewPoint = CVector3f(); SetViewMatrix(); } void CGraphics::SetViewMatrix() { Mtx mtx; MTXTrans(mtx, -mViewPoint.x(), -mViewPoint.y(), -mViewPoint.z()); MTXConcat(mGXViewPointMatrix, mtx, mCameraMtx); if (mIsGXModelMatrixIdentity) { MTXCopy(mCameraMtx, mGxModelView); } else { MTXConcat(mCameraMtx, mGXModelMatrix, mGxModelView); } GXLoadPosMtxImm(mGxModelView, GX_PNMTX0); Mtx nrmMtx; MTXInvXpose(mGxModelView, nrmMtx); GXLoadNrmMtxImm(nrmMtx, GX_PNMTX0); } void CGraphics::SetModelMatrix(const zeus::CTransform& xf) { if (xf == zeus::CTransform()) { if (!mIsGXModelMatrixIdentity) { mModelMatrix = xf; mIsGXModelMatrixIdentity = true; SetViewMatrix(); } return; } mModelMatrix = xf; mIsGXModelMatrixIdentity = false; mGXModelMatrix[0][0] = xf.basis[0][0]; mGXModelMatrix[0][1] = xf.basis[1][0]; mGXModelMatrix[0][2] = xf.basis[2][0]; mGXModelMatrix[0][3] = xf.origin.x(); mGXModelMatrix[1][0] = xf.basis[0][1]; mGXModelMatrix[1][1] = xf.basis[1][1]; mGXModelMatrix[1][2] = xf.basis[2][1]; mGXModelMatrix[1][3] = xf.origin.y(); mGXModelMatrix[2][0] = xf.basis[0][2]; mGXModelMatrix[2][1] = xf.basis[1][2]; mGXModelMatrix[2][2] = xf.basis[2][2]; mGXModelMatrix[2][3] = xf.origin.z(); SetViewMatrix(); } void CGraphics::SetIdentityModelMatrix() { if (!mIsGXModelMatrixIdentity) { mModelMatrix = CTransform4f(); mIsGXModelMatrixIdentity = true; SetViewMatrix(); } } zeus::CMatrix4f CGraphics::CalculatePerspectiveMatrix(float fovy, float aspect, float znear, float zfar) { float t = std::tan(zeus::degToRad(fovy) / 2.f); float right = aspect * 2.f * znear * t * 0.5f; float left = -right; float top = znear * 2.f * t * 0.5f; float bottom = -top; return zeus::CMatrix4f{ // clang-format off (2.f * znear) / (right - left), -(right + left) / (right - left), 0.f, 0.f, 0.f, -(top + bottom) / (top - bottom), (2.f * znear) / (top - bottom), 0.f, 0.f, (zfar + znear) / (zfar - znear), 0.f, -(2.f * zfar * znear) / (zfar - znear), 0.f, 1.f, 0.f, 0.f, // clang-format on }; } zeus::CMatrix4f CGraphics::GetPerspectiveProjectionMatrix() { return zeus::CMatrix4f{ // clang-format off (mProj.GetNear() * 2.f) / (mProj.GetRight() - mProj.GetLeft()), -(mProj.GetRight() + mProj.GetLeft()) / (mProj.GetRight() - mProj.GetLeft()), 0.f, 0.f, 0.f, -(mProj.GetTop() + mProj.GetBottom()) / (mProj.GetTop() - mProj.GetBottom()), (mProj.GetNear() * 2.f) / (mProj.GetTop() - mProj.GetBottom()), 0.f, 0.f, (mProj.GetFar() + mProj.GetNear()) / (mProj.GetFar() - mProj.GetNear()), 0.f, -(mProj.GetFar() * 2.f * mProj.GetNear()) / (mProj.GetFar() - mProj.GetNear()), 0.f, 1.f, 0.f, 0.f // clang-format on }; } const CGraphics::CProjectionState& CGraphics::GetProjectionState() { return mProj; } void CGraphics::SetProjectionState(const CProjectionState& proj) { mProj = proj; FlushProjection(); } void CGraphics::SetPerspective(float fovy, float aspect, float znear, float zfar) { float t = tan(zeus::degToRad(fovy) / 2.f); mProj = CProjectionState(true, // Is Projection -(aspect * 2.f * znear * t * 0.5f), // Left (aspect * 2.f * znear * t * 0.5f), // Right (znear * 2.f * t * 0.5f), // Top -(znear * 2.f * t * 0.5f), // Bottom znear, zfar); FlushProjection(); } void CGraphics::SetOrtho(float left, float right, float top, float bottom, float znear, float zfar) { mProj = CProjectionState(false, left, right, top, bottom, znear, zfar); FlushProjection(); } void CGraphics::FlushProjection() { float right = mProj.GetRight(); float left = mProj.GetLeft(); float top = mProj.GetTop(); float bottom = mProj.GetBottom(); float nearPlane = mProj.GetNear(); float farPlane = mProj.GetFar(); if (mProj.IsPerspective()) { Mtx44 mtx; MTXFrustum(mtx, top, bottom, left, right, nearPlane, farPlane); GXSetProjection(mtx, GX_PERSPECTIVE); } else { Mtx44 mtx; MTXOrtho(mtx, top, bottom, left, right, nearPlane, farPlane); GXSetProjection(mtx, GX_ORTHOGRAPHIC); } } zeus::CVector2i CGraphics::ProjectPoint(const zeus::CVector3f& point) { zeus::CVector3f vec = GetPerspectiveProjectionMatrix().multiplyOneOverW(point); vec.x() = vec.x() * mViewport.mHalfWidth + mViewport.mHalfWidth; vec.y() = -vec.y() * mViewport.mHalfHeight + mViewport.mHalfHeight; return CVector2i(vec.x(), vec.y()); } static CVector3f TransposeMultiply(const CTransform4f& self, const CVector3f& in) { return self.transposeRotate({in.x() - self.origin.x(), in.y() - self.origin.y(), in.z() - self.origin.z()}); } CGraphics::CClippedScreenRect CGraphics::ClipScreenRectFromMS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt) { return ClipScreenRectFromVS(TransposeMultiply(mViewMatrix, mModelMatrix * p1), TransposeMultiply(mViewMatrix, mModelMatrix * p2), fmt); } CGraphics::CClippedScreenRect CGraphics::ClipScreenRectFromVS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt) { if (p1.isZero() || p2.isZero()) { return CClippedScreenRect(); } if (p1.y() < GetProjectionState().GetNear() || p2.y() < GetProjectionState().GetNear()) { return CClippedScreenRect(); } if (p1.y() > GetProjectionState().GetFar() || p2.y() > GetProjectionState().GetFar()) { return CClippedScreenRect(); } CVector2i p1p = ProjectPoint(p1); CVector2i p2p = ProjectPoint(p2); int minX = std::min(p1p.x, p2p.x); int minY = std::min(p1p.y, p2p.y); int maxX = abs(p1p.x - p2p.x); int maxY = abs(p1p.y - p2p.y); int left = minX & ~1; if (left >= mViewport.mLeft + mViewport.mWidth) { return CClippedScreenRect(); } int right = minX + ((maxX + 2) & ~1); if (right <= mViewport.mLeft) { return CClippedScreenRect(); } left = std::max(left, mViewport.mLeft) & ~1; right = (std::min(right, mViewport.mLeft + mViewport.mWidth) + 1) & ~1; int top = minY & ~1; if (top >= mViewport.mTop + mViewport.mHeight) { return CClippedScreenRect(); } int bottom = minY + ((maxY + 2) & ~1); if (bottom <= mViewport.mTop) { return CClippedScreenRect(); } top = std::max(top, mViewport.mTop) & ~1; bottom = (std::min(bottom, mViewport.mTop + mViewport.mHeight) + 1) & ~1; float minV = static_cast(minY - top) / static_cast(bottom - top); float maxV = static_cast(maxY + (minY - top) + 1) / static_cast(bottom - top); int texAlign = 4; switch (fmt) { case ETexelFormat::I8: texAlign = 8; break; case ETexelFormat::IA8: case ETexelFormat::RGB565: case ETexelFormat::RGB5A3: texAlign = 4; break; case ETexelFormat::RGBA8: texAlign = 2; break; default: break; } int texWidth = (texAlign + ((right - left) - 1)) & ~(texAlign - 1); float minU = static_cast(minX - left) / static_cast(texWidth); float maxU = static_cast(maxX + (minX - left) + 1) / static_cast(texWidth); return CClippedScreenRect(left, top, right - left, bottom - top, texWidth, minU, maxU, minV, maxV); } void CGraphics::SetViewportResolution(const zeus::CVector2i& res) { mRenderModeObj.fbWidth = res.x; mRenderModeObj.efbHeight = res.y; mRenderModeObj.xfbHeight = res.y; SetViewport(0, 0, res.x, res.y); if (g_GuiSys) g_GuiSys->OnViewportResize(); } void CGraphics::SetViewport(int left, int bottom, int width, int height) { mViewport.mLeft = left; mViewport.mTop = mRenderModeObj.efbHeight - (bottom + height); mViewport.mWidth = width; mViewport.mHeight = height; mViewport.mHalfWidth = static_cast(width / 2); mViewport.mHalfHeight = static_cast(height / 2); GXSetViewport(static_cast(mViewport.mLeft), static_cast(mViewport.mTop), static_cast(mViewport.mWidth), static_cast(mViewport.mHeight), mDepthNear, mDepthFar); } void CGraphics::SetScissor(int left, int bottom, int width, int height) { GXSetScissor(left, mRenderModeObj.efbHeight - (bottom + height), width, height); } void CGraphics::SetDepthRange(float nearPlane, float farPlane) { mDepthNear = nearPlane; mDepthFar = farPlane; GXSetViewport(static_cast(mViewport.mLeft), static_cast(mViewport.mTop), static_cast(mViewport.mWidth), static_cast(mViewport.mHeight), mDepthNear, mDepthFar); } float CGraphics::GetSecondsMod900() { if (mpExternalTimeProvider != nullptr) { return mpExternalTimeProvider->GetSecondsMod900(); } return mSecondsMod900; } void CGraphics::TickRenderTimings() { //OPTICK_EVENT(); mRenderTimings = (mRenderTimings + 1) % (900 * 60); mSecondsMod900 = static_cast(mRenderTimings) / 60.f; } void CGraphics::SetUseVideoFilter(bool b) { mUseVideoFilter = b; GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, b ? GX_ENABLE : GX_DISABLE, mRenderModeObj.vfilter); } void CGraphics::SetClearColor(const CColor& color) { mClearColor = color; GXSetCopyClear(to_gx_color(color), mClearDepthValue); } void CGraphics::SetCopyClear(const CColor& color, float depth) { mClearColor = color; mClearDepthValue = static_cast(depth * GX_MAX_Z24); GXSetCopyClear(to_gx_color(color), mClearDepthValue); } void CGraphics::SetIsBeginSceneClearFb(bool b) { mIsBeginSceneClearFb = b; } void CGraphics::SetTevOp(ERglTevStage stage, const CTevCombiners::CTevPass& pass) { CTevCombiners::SetupPass(stage, pass); } #define STREAM_PRIM_BUFFER_SIZE 240 static std::array sVtxBuffer; static std::array sNrmBuffer; static std::array sTxt0Buffer; static std::array sTxt1Buffer; static std::array sClrBuffer; static const uchar kHasNormals = 1; static const uchar kHasColor = 2; static const uchar kHasTexture = 4; void CGraphics::StreamBegin(ERglPrimitive primitive) { vtxBuffer = sVtxBuffer.data(); nrmBuffer = sNrmBuffer.data(); txtBuffer0 = sTxt0Buffer.data(); txtBuffer1 = sTxt1Buffer.data(); clrBuffer = sClrBuffer.data(); ResetVertexDataStream(true); mCurrentPrimitive = primitive; vtxDescr.streamFlags = kHasColor; } void CGraphics::StreamNormal(const zeus::CVector3f& nrm) { vtxDescr.nrm.x = nrm.x(); vtxDescr.nrm.y = nrm.y(); vtxDescr.nrm.z = nrm.z(); vtxDescr.streamFlags |= kHasNormals; } void CGraphics::StreamColor(const zeus::CColor& color) { vtxDescr.color = color.toRGBA(); vtxDescr.streamFlags |= kHasColor; } void CGraphics::StreamTexcoord(const zeus::CVector2f& uv) { vtxDescr.uv0.x = uv.x(); vtxDescr.uv0.y = uv.y(); vtxDescr.streamFlags |= kHasTexture; vtxDescr.textureUsed |= 1; } void CGraphics::StreamVertex(const zeus::CVector3f& pos) { vtxDescr.vtx.x = pos.x(); vtxDescr.vtx.y = pos.y(); vtxDescr.vtx.z = pos.z(); UpdateVertexDataStream(); } void CGraphics::StreamEnd() { if (mNumPrimitives != 0) { FlushStream(); } vtxBuffer = nullptr; vtxDescr.streamFlags = 0; vtxDescr.textureUsed = 0; nrmBuffer = nullptr; txtBuffer0 = nullptr; txtBuffer1 = nullptr; clrBuffer = nullptr; } void CGraphics::SetLineWidth(float w, ERglTexOffset offs) { CGX::SetLineWidth(static_cast(w * 6.f), static_cast(offs)); } void CGraphics::UpdateVertexDataStream() { ++mNumPrimitives; mpVtxBuffer->x = vtxDescr.vtx.x; mpVtxBuffer->y = vtxDescr.vtx.y; mpVtxBuffer->z = vtxDescr.vtx.z; ++mpVtxBuffer; if ((vtxDescr.streamFlags & kHasNormals) != 0) { mpNrmBuffer->x = vtxDescr.nrm.x; mpNrmBuffer->y = vtxDescr.nrm.y; mpNrmBuffer->z = vtxDescr.nrm.z; ++mpNrmBuffer; } if ((vtxDescr.streamFlags & kHasTexture) != 0) { mpTxtBuffer0->x = vtxDescr.uv0.x; mpTxtBuffer0->y = vtxDescr.uv0.y; ++mpTxtBuffer0; mpTxtBuffer1->x = vtxDescr.uv1.x; mpTxtBuffer1->y = vtxDescr.uv1.y; ++mpTxtBuffer1; } if ((vtxDescr.streamFlags & kHasColor) != 0) { *mpClrBuffer = vtxDescr.color; ++mpClrBuffer; } mJustReset = 0; if (mNumPrimitives == STREAM_PRIM_BUFFER_SIZE) { FlushStream(); ResetVertexDataStream(false); } } void CGraphics::FlushStream() { GXVtxDescList vtxDesc[10]; GXVtxDescList* curDesc = vtxDesc; const GXVtxDescList vtxDescPos = {GX_VA_POS, GX_DIRECT}; *curDesc++ = vtxDescPos; if ((vtxDescr.streamFlags & kHasNormals) != 0) { const GXVtxDescList vtxDescNrm = {GX_VA_NRM, GX_DIRECT}; *curDesc++ = vtxDescNrm; } if ((vtxDescr.streamFlags & kHasColor) != 0) { const GXVtxDescList vtxDescClr0 = {GX_VA_CLR0, GX_DIRECT}; *curDesc++ = vtxDescClr0; } if ((vtxDescr.streamFlags & kHasTexture) != 0) { const GXVtxDescList vtxDescTex0 = {GX_VA_TEX0, GX_DIRECT}; *curDesc++ = vtxDescTex0; } const GXVtxDescList vtxDescNull = {GX_VA_NULL, GX_NONE}; *curDesc = vtxDescNull; CGX::SetVtxDescv(vtxDesc); SetTevStates(vtxDescr.streamFlags); FullRender(); } void CGraphics::FullRender() { CGX::Begin(static_cast(mCurrentPrimitive), GX_VTXFMT0, mNumPrimitives); switch (vtxDescr.streamFlags) { case 0: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); } break; case kHasNormals: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); const Vec& nrm = nrmBuffer[i]; GXNormal3f32(nrm.x, nrm.y, nrm.z); } break; case kHasColor: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); GXColor1u32(clrBuffer[i]); } break; case kHasTexture: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); const Vec2& uv = txtBuffer0[i]; GXTexCoord2f32(uv.x, uv.y); } break; case kHasNormals | kHasTexture: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); const Vec& nrm = nrmBuffer[i]; GXNormal3f32(nrm.x, nrm.y, nrm.z); const Vec2& uv = txtBuffer0[i]; GXTexCoord2f32(uv.x, uv.y); } break; case kHasNormals | kHasColor: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); const Vec& nrm = nrmBuffer[i]; GXNormal3f32(nrm.x, nrm.y, nrm.z); GXColor1u32(clrBuffer[i]); } break; case kHasColor | kHasTexture: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); GXColor1u32(clrBuffer[i]); const Vec2& uv = txtBuffer0[i]; GXTexCoord2f32(uv.x, uv.y); } break; case kHasNormals | kHasColor | kHasTexture: for (int i = 0; i < mNumPrimitives; i++) { const Vec& vtx = vtxBuffer[i]; GXPosition3f32(vtx.x, vtx.y, vtx.z); const Vec& nrm = nrmBuffer[i]; GXNormal3f32(nrm.x, nrm.y, nrm.z); GXColor1u32(clrBuffer[i]); const Vec2& uv = txtBuffer0[i]; GXTexCoord2f32(uv.x, uv.y); } break; } CGX::End(); } void CGraphics::ResetVertexDataStream(bool initial) { mpVtxBuffer = vtxBuffer; mpNrmBuffer = nrmBuffer; mpTxtBuffer0 = txtBuffer0; mpTxtBuffer1 = txtBuffer1; mpClrBuffer = clrBuffer; mNumPrimitives = 0; if (initial) { return; } switch (mCurrentPrimitive) { case ERglPrimitive::TriangleFan: mpVtxBuffer = vtxBuffer + 1; memcpy(mpVtxBuffer, &vtxDescr.vtx, sizeof(Vec)); ++mpVtxBuffer; if ((vtxDescr.streamFlags & kHasNormals) != 0) { ++mpNrmBuffer; memcpy(mpNrmBuffer, &vtxDescr.nrm, sizeof(Vec)); ++mpNrmBuffer; } if ((vtxDescr.streamFlags & kHasTexture) != 0) { ++mpTxtBuffer0; memcpy(mpTxtBuffer0, &vtxDescr.uv0, sizeof(Vec2)); ++mpTxtBuffer0; ++mpTxtBuffer1; memcpy(mpTxtBuffer1, &vtxDescr.uv1, sizeof(Vec2)); ++mpTxtBuffer1; } if ((vtxDescr.streamFlags & kHasColor) != 0) { ++mpClrBuffer; *mpClrBuffer = vtxDescr.color; ++mpClrBuffer; } mNumPrimitives += 2; break; default: break; } mJustReset = 1; } void CGraphics::DrawPrimitive(ERglPrimitive primitive, const zeus::CVector3f* pos, const zeus::CVector3f& normal, const zeus::CColor& col, s32 numVerts) { StreamBegin(primitive); StreamNormal(normal); StreamColor(col); for (u32 i = 0; i < numVerts; ++i) { StreamVertex(pos[i]); } StreamEnd(); } void CGraphics::SetTevStates(u32 flags) noexcept { switch (flags) { case 0: case kHasNormals: case kHasColor: case kHasNormals | kHasColor: CGX::SetNumChans(1); CGX::SetNumTexGens(0); CGX::SetNumTevStages(1); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); break; case kHasTexture: case kHasNormals | kHasTexture: case kHasColor | kHasTexture: case kHasNormals | kHasColor | kHasTexture: CGX::SetNumChans(1); if ((vtxDescr.textureUsed & 3) != 0) { CGX::SetNumTexGens(2); } else { CGX::SetNumTexGens(1); } CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0); break; } CGX::SetNumIndStages(0); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY, false, GX_PTIDENTITY); GX::LightMask light = mLightActive; GXAttnFn attnFn = GX_AF_NONE; if (light.any()) { attnFn = GX_AF_SPOT; } GXDiffuseFn diffFn = GX_DF_NONE; if (light.any()) { diffFn = GX_DF_CLAMP; } CGX::SetChanCtrl(CGX::EChannelId::Channel0, light.any() ? GX_ENABLE : GX_DISABLE, GX_SRC_REG, (flags & kHasColor) ? GX_SRC_VTX : GX_SRC_REG, light, diffFn, attnFn); } bool CGraphics::Startup() { // mpFifo = fifoBase; // mpFifoObj = GXInit(fifoBase, fifoSize); mpFifoObj = GXInit(nullptr, 0); // GXFifoObj fifoObj; // GXInitFifoBase(&fifoObj, mpFifo, fifoSize); // GXSetCPUFifo(&fifoObj); // GXSetGPFifo(&fifoObj); // GXInitFifoLimits(mpFifoObj, fifoSize - 0x4000, fifoSize - 0x10000); // GXSetCPUFifo(mpFifoObj); // GXSetGPFifo(mpFifoObj); // GXSetMisc(GX_MT_XF_FLUSH, 8); GXSetDither(GX_FALSE); CGX::ResetGXStates(); InitGraphicsVariables(); ConfigureFrameBuffer(/*osContext*/); // for (int i = 0; i < ARRAY_SIZE(mTexRegions); i++) { // GXInitTexCacheRegion(&mTexRegions[i], false, 0x8000 * i, GX_TEXCACHE_32K, 0x80000 + (0x8000 * i), // GX_TEXCACHE_32K); // } // for (int i = 0; i < ARRAY_SIZE(mTexRegionsCI); i++) { // GXInitTexCacheRegion(&mTexRegionsCI[i], false, (8 + (2 * i)) << 0xF, GX_TEXCACHE_32K, (9 + (2 * i)) << 0xF, // GX_TEXCACHE_32K); // } // mGXDefaultTexRegionCallback = GXSetTexRegionCallback(TexRegionCallback); mSpareBufferSize = sSpareFrameBuffer.size(); mpSpareBuffer = sSpareFrameBuffer.data(); mSpareBufferTexCacheSize = 0x10000; return true; } #define ARRAY_SIZE(arr) static_cast(sizeof(arr) / sizeof(arr[0])) void CGraphics::InitGraphicsVariables() { for (int i = 0; i < ARRAY_SIZE(mLightTypes); ++i) { mLightTypes[i] = ELightType::Directional; } mLightActive = 0; SetDepthWriteMode(false, mDepthFunc, false); SetCullMode(ERglCullMode::None); SetAmbientColor(CColor(0.2f, 0.2f, 0.2f, 1.f)); mIsGXModelMatrixIdentity = false; SetIdentityViewPointMatrix(); SetIdentityModelMatrix(); SetViewport(0, 0, mViewport.mWidth, mViewport.mHeight); SetPerspective(60.f, static_cast(mViewport.mWidth) / static_cast(mViewport.mHeight), mProj.GetNear(), mProj.GetFar()); SetCopyClear(mClearColor, 1.f); constexpr GXColor white = {0xFF, 0xFF, 0xFF, 0xFF}; CGX::SetChanMatColor(CGX::EChannelId::Channel0, white); sRenderState.ResetFlushAll(); } void CGraphics::InitGraphicsDefaults() { SetDepthRange(0.f, 1.f); mIsGXModelMatrixIdentity = false; SetModelMatrix(mModelMatrix); SetViewPointMatrix(mViewMatrix); SetDepthWriteMode(false, mDepthFunc, false); SetCullMode(mCullMode); SetViewport(mViewport.mLeft, mViewport.mTop, mViewport.mWidth, mViewport.mHeight); FlushProjection(); CTevCombiners::Init(); DisableAllLights(); SetDefaultVtxAttrFmt(); } void CGraphics::ConfigureFrameBuffer(/*const COsContext& osContext*/) { // mRenderModeObj = osContext.GetRenderModeObj(); // mpFrameBuf1 = osContext.GetFramebuf1(); // mpFrameBuf2 = osContext.GetFramebuf2(); // VIConfigure(&mRenderModeObj); // VISetNextFrameBuffer(mpFrameBuf1); mpCurrenFrameBuf = mpFrameBuf2; GXSetViewport(0.f, 0.f, static_cast(mRenderModeObj.fbWidth), static_cast(mRenderModeObj.efbHeight), 0.f, 1.f); GXSetScissor(0, 0, mRenderModeObj.fbWidth, mRenderModeObj.efbHeight); GXSetDispCopySrc(0, 0, mRenderModeObj.fbWidth, mRenderModeObj.efbHeight); GXSetDispCopyDst(mRenderModeObj.fbWidth, mRenderModeObj.efbHeight); GXSetDispCopyYScale(static_cast(mRenderModeObj.xfbHeight) / static_cast(mRenderModeObj.efbHeight)); GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, GX_ENABLE, mRenderModeObj.vfilter); if (mRenderModeObj.aa) { GXSetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR); } else { GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR); } GXSetDispCopyGamma(GX_GM_1_0); GXCopyDisp(mpCurrenFrameBuf, true); VIFlush(); // VIWaitForRetrace(); // VIWaitForRetrace(); mViewport.mWidth = mRenderModeObj.fbWidth; mViewport.mHeight = mRenderModeObj.efbHeight; InitGraphicsDefaults(); } void CGraphics::SetDefaultVtxAttrFmt() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_NRM, GX_NRM_XYZ, GX_S16, 14); GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_NRM, GX_NRM_XYZ, GX_S16, 14); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_TEX0, GX_TEX_ST, GX_U16, 15); for (int i = 1; i <= 7; ++i) { GXAttr attr = static_cast(GX_VA_TEX0 + i); GXSetVtxAttrFmt(GX_VTXFMT0, attr, GX_TEX_ST, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT1, attr, GX_TEX_ST, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT2, attr, GX_TEX_ST, GX_F32, 0); } } void CGraphics::ResetGfxStates() noexcept { sRenderState.Set(0); } void CGraphics::LoadDolphinSpareTexture(int width, int height, GXTexFmt fmt, void* data, GXTexMapID texId) { TGXTexObj texObj; GXInitTexObj(&texObj, data != nullptr ? data : mpSpareBuffer, width, height, fmt, GX_CLAMP, GX_CLAMP, GX_DISABLE); GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1); GXLoadTexObj(&texObj, texId); // CTexture::InvalidateTexmap(texId); // if (texId == GX_TEXMAP7) { // GXInvalidateTexRegion(&mTexRegions[0]); // } } void CGraphics::LoadDolphinSpareTexture(int width, int height, GXCITexFmt fmt, GXTlut tlut, void* data, GXTexMapID texId) { TGXTexObj texObj; GXInitTexObjCI(&texObj, data != nullptr ? data : mpSpareBuffer, width, height, fmt, GX_CLAMP, GX_CLAMP, GX_DISABLE, tlut); GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1); GXLoadTexObj(&texObj, texId); // CTexture::InvalidateTexmap(texId); // if (texId == GX_TEXMAP7) { // GXInvalidateTexRegion(&mTexRegions[0]); // } } CGraphics::CRenderState::CRenderState() { x0_ = 0; x4_ = 0; } void CGraphics::CRenderState::Flush() {} int CGraphics::CRenderState::SetVtxState(const float* pos, const float* nrm, const uint* clr) { // CGX::SetArray(GX_VA_POS, pos, 12); // CGX::SetArray(GX_VA_NRM, nrm, 12); // CGX::SetArray(GX_VA_CLR0, clr, 4); int result = 1; if (nrm != nullptr) { result |= 2; } if (clr != nullptr) { result |= 16; } return result; } void CGraphics::CRenderState::ResetFlushAll() { x0_ = 0; SetVtxState(nullptr, nullptr, nullptr); for (int i = 0; i < 8; i++) { CGX::SetArray(static_cast(GX_VA_TEX0 + i), std::span{}); } Flush(); } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CGraphics.hpp ================================================ #pragma once #include "Runtime/ConsoleVariables/CVar.hpp" #include "Runtime/Graphics/CLight.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/Graphics/CTevCombiners.hpp" #include "Runtime/Graphics/GX.hpp" #include "Runtime/RetroTypes.hpp" #include #include #include #include #include #include // #include #include namespace metaforce { class CTexture; extern CVar* g_disableLighting; class CTimeProvider; enum class ERglCullMode : std::underlying_type_t { None = GX_CULL_NONE, Front = GX_CULL_FRONT, Back = GX_CULL_BACK, All = GX_CULL_ALL, }; enum class ERglBlendMode : std::underlying_type_t { None = GX_BM_NONE, Blend = GX_BM_BLEND, Logic = GX_BM_LOGIC, Subtract = GX_BM_SUBTRACT, Max = GX_MAX_BLENDMODE, }; enum class ERglBlendFactor : std::underlying_type_t { Zero = GX_BL_ZERO, One = GX_BL_ONE, SrcColor = GX_BL_SRCCLR, InvSrcColor = GX_BL_INVSRCCLR, SrcAlpha = GX_BL_SRCALPHA, InvSrcAlpha = GX_BL_INVSRCALPHA, DstAlpha = GX_BL_DSTALPHA, InvDstAlpha = GX_BL_INVDSTALPHA, DstColor = GX_BL_DSTCLR, InvDstColor = GX_BL_INVDSTCLR, }; enum class ERglLogicOp : std::underlying_type_t { Clear = GX_LO_CLEAR, And = GX_LO_AND, RevAnd = GX_LO_REVAND, Copy = GX_LO_COPY, InvAnd = GX_LO_INVAND, NoOp = GX_LO_NOOP, Xor = GX_LO_XOR, Or = GX_LO_OR, Nor = GX_LO_NOR, Equiv = GX_LO_EQUIV, Inv = GX_LO_INV, RevOr = GX_LO_REVOR, InvCopy = GX_LO_INVCOPY, InvOr = GX_LO_INVOR, NAnd = GX_LO_NAND, Set = GX_LO_SET, }; enum class ERglAlphaFunc : std::underlying_type_t { Never = GX_NEVER, Less = GX_LESS, Equal = GX_EQUAL, LEqual = GX_LEQUAL, Greater = GX_GREATER, NEqual = GX_NEQUAL, GEqual = GX_GEQUAL, Always = GX_ALWAYS, }; enum class ERglAlphaOp : std::underlying_type_t { And = GX_AOP_AND, Or = GX_AOP_OR, Xor = GX_AOP_XOR, XNor = GX_AOP_XNOR, Max = GX_MAX_ALPHAOP, }; enum class ERglEnum : std::underlying_type_t { Never = GX_NEVER, Less = GX_LESS, Equal = GX_EQUAL, LEqual = GX_LEQUAL, Greater = GX_GREATER, NEqual = GX_NEQUAL, GEqual = GX_GEQUAL, Always = GX_ALWAYS, }; enum class ERglPrimitive : std::underlying_type_t { Quads = GX_QUADS, Triangles = GX_TRIANGLES, TriangleStrip = GX_TRIANGLESTRIP, TriangleFan = GX_TRIANGLEFAN, Lines = GX_LINES, LineStrip = GX_LINESTRIP, Points = GX_POINTS, }; using ERglLight = u8; enum class ERglTexOffset : std::underlying_type_t { Zero = GX_TO_ZERO, Sixteenth = GX_TO_SIXTEENTH, Eighth = GX_TO_EIGHTH, Fourth = GX_TO_FOURTH, Half = GX_TO_HALF, One = GX_TO_ONE, }; enum class ERglFogMode : std::underlying_type_t { None = GX_FOG_NONE, PerspLin = GX_FOG_PERSP_LIN, PerspExp = GX_FOG_PERSP_EXP, PerspExp2 = GX_FOG_ORTHO_EXP2, PerspRevExp = GX_FOG_PERSP_REVEXP, PerspRevExp2 = GX_FOG_PERSP_REVEXP2, OrthoLin = GX_FOG_ORTHO_LIN, OrthoExp = GX_FOG_ORTHO_EXP, OrthoExp2 = GX_FOG_ORTHO_EXP2, OrthoRevExp = GX_FOG_ORTHO_REVEXP, OrthoRevExp2 = GX_FOG_ORTHO_REVEXP2, }; struct CViewport { int mLeft; int mTop; int mWidth; int mHeight; float mHalfWidth; float mHalfHeight; }; // TODO typedef struct { float x; float y; } Vec2, *Vec2Ptr; #define DEPTH_FAR 1.f #define DEPTH_SKY 0.999f #define DEPTH_TARGET_MANAGER 0.12500012f #define DEPTH_WORLD (1.f / 8.f) #define DEPTH_GUN (1.f / 32.f) #define DEPTH_SCREEN_ACTORS (1.f / 64.f) #define DEPTH_HUD (1.f / 512.f) #define DEPTH_NEAR 0.f #define CUBEMAP_RES 256 #define CUBEMAP_MIPS 6 enum class ETexelFormat; class CGraphics { public: using CVector3f = zeus::CVector3f; using CTransform4f = zeus::CTransform; using CColor = zeus::CColor; using uchar = unsigned char; using uint = unsigned int; class CRenderState { public: CRenderState(); void Flush(); void ResetFlushAll(); int SetVtxState(const float* pos, const float* nrm, const uint* clr); // In map this takes two args, but x4 is unused? void Set(int v0) { x0_ = v0; } private: int x0_; int x4_; }; class CProjectionState { public: CProjectionState(bool persp, float left, float right, float top, float bottom, float nearPlane, float farPlane) : x0_persp(persp) , x4_left(left) , x8_right(right) , xc_top(top) , x10_bottom(bottom) , x14_near(nearPlane) , x18_far(farPlane) {} bool IsPerspective() const { return x0_persp; } float GetLeft() const { return x4_left; } float GetRight() const { return x8_right; } float GetTop() const { return xc_top; } float GetBottom() const { return x10_bottom; } float GetNear() const { return x14_near; } float GetFar() const { return x18_far; } private: bool x0_persp; float x4_left; float x8_right; // TODO: I think top/bottom are flipped float xc_top; float x10_bottom; float x14_near; float x18_far; }; class CClippedScreenRect { public: CClippedScreenRect() : x0_valid(false) {} CClippedScreenRect(int x, int y, int width, int height, int texWidth, float minU, float maxU, float minV, float maxV) : x0_valid(true) , x4_x(x) , x8_y(y) , xc_width(width) , x10_height(height) , x14_texWidth(texWidth) , x18_minU(minU) , x1c_maxU(maxU) , x20_minV(minV) , x24_maxV(maxV) {} bool IsValid() const { return x0_valid; } int GetX() const { return x4_x; } int GetY() const { return x8_y; } int GetWidth() const { return xc_width; } int GetHeight() const { return x10_height; } int GetTexWidth() const { return x14_texWidth; } float GetMinU() const { return x18_minU; } float GetMaxU() const { return x1c_maxU; } float GetMinV() const { return x20_minV; } float GetMaxV() const { return x24_maxV; } private: bool x0_valid; int x4_x; int x8_y; int xc_width; int x10_height; int x14_texWidth; float x18_minU; float x1c_maxU; float x20_minV; float x24_maxV; }; static CRenderState sRenderState; static VecPtr vtxBuffer; static VecPtr nrmBuffer; static Vec2Ptr txtBuffer0; static Vec2Ptr txtBuffer1; static uint* clrBuffer; static bool mJustReset; static ERglCullMode mCullMode; static int mNumLightsActive; static float mDepthNear; static VecPtr mpVtxBuffer; static VecPtr mpNrmBuffer; static Vec2Ptr mpTxtBuffer0; static Vec2Ptr mpTxtBuffer1; static uint* mpClrBuffer; static int mNumPrimitives; static int mFrameCounter; static float mFramesPerSecond; static float mLastFramesPerSecond; static int mNumBreakpointsWaiting; static int mFlippingState; static bool mLastFrameUsedAbove; static bool mInterruptLastFrameUsedAbove; static GX::LightMask mLightActive; static GX::LightMask mLightsWereOn; static void* mpFrameBuf1; static void* mpFrameBuf2; static void* mpCurrenFrameBuf; static int mSpareBufferSize; static void* mpSpareBuffer; static int mSpareBufferTexCacheSize; // static GXTexRegionCallback mGXDefaultTexRegionCallback; static void* mpFifo; static GXFifoObj* mpFifoObj; static uint mRenderTimings; static float mSecondsMod900; static CTimeProvider* mpExternalTimeProvider; static int mScreenStretch; static int mScreenPositionX; static int mScreenPositionY; static CVector3f kDefaultPositionVector; static CVector3f kDefaultDirectionVector; static CProjectionState mProj; static CTransform4f mViewMatrix; static CTransform4f mModelMatrix; static CColor mClearColor; static CVector3f mViewPoint; static CViewport mViewport; static ELightType mLightTypes[8]; static GXLightObj mLightObj[8]; // static GXTexRegion mTexRegions[GX_MAX_TEXMAP]; // static GXTexRegion mTexRegionsCI[GX_MAX_TEXMAP / 2]; static GXRenderModeObj mRenderModeObj; static Mtx mGXViewPointMatrix; static Mtx mGXModelMatrix; static Mtx mGxModelView; static Mtx mCameraMtx; static bool mIsBeginSceneClearFb; static ERglEnum mDepthFunc; static ERglPrimitive mCurrentPrimitive; static float mDepthFar; static u32 mClearDepthValue; // = GX_MAX_Z24 static bool mIsGXModelMatrixIdentity; static bool mFirstFrame; static GXBool mUseVideoFilter; static float mBrightness; static const GXTexMapID kSpareBufferTexMapID; static bool Startup(); static void InitGraphicsVariables(); static void InitGraphicsDefaults(); static void ConfigureFrameBuffer(); static void SetDefaultVtxAttrFmt(); static void DisableAllLights(); static void LoadLight(ERglLight light, const CLight& info); static void EnableLight(ERglLight light); static void SetLightState(GX::LightMask lightState); static void SetAmbientColor(const zeus::CColor& col); static void SetFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color); static void SetDepthWriteMode(bool test, ERglEnum comp, bool write); static void SetBlendMode(ERglBlendMode, ERglBlendFactor, ERglBlendFactor, ERglLogicOp); static void SetCullMode(ERglCullMode); static void BeginScene(); static void EndScene(); static void Render2D(CTexture& tex, int x, int y, int w, int h, const zeus::CColor& col, bool scale); static void SetAlphaCompare(ERglAlphaFunc comp0, u8 ref0, ERglAlphaOp op, ERglAlphaFunc comp1, u8 ref1); static void SetViewPointMatrix(const zeus::CTransform& xf); static void SetViewMatrix(); static void SetModelMatrix(const zeus::CTransform& xf); static zeus::CMatrix4f CalculatePerspectiveMatrix(float fovy, float aspect, float znear, float zfar); static zeus::CMatrix4f GetPerspectiveProjectionMatrix(); static const CProjectionState& GetProjectionState(); static void SetProjectionState(const CProjectionState&); static void SetPerspective(float fovy, float aspect, float znear, float zfar); static void SetOrtho(float left, float right, float top, float bottom, float znear, float zfar); static void FlushProjection(); static zeus::CVector2i ProjectPoint(const zeus::CVector3f& point); static CClippedScreenRect ClipScreenRectFromVS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt); static CClippedScreenRect ClipScreenRectFromMS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt); static void SetViewportResolution(const zeus::CVector2i& res); static void SetViewport(int leftOff, int bottomOff, int width, int height); static void SetScissor(int leftOff, int bottomOff, int width, int height); static void SetDepthRange(float nearPlane, float farPlane); static void SetIdentityViewPointMatrix(); static void SetIdentityModelMatrix(); static void ClearBackAndDepthBuffers(); static void SetExternalTimeProvider(CTimeProvider* provider) { mpExternalTimeProvider = provider; } static float GetSecondsMod900(); static void TickRenderTimings(); static int GetFrameCounter() { return mFrameCounter; } static float GetFPS() { return mFramesPerSecond; } static void SetUseVideoFilter(bool); static void SetClearColor(const zeus::CColor& color); static void SetCopyClear(const zeus::CColor& color, float depth); static void SetIsBeginSceneClearFb(bool clear); static int GetViewportLeft() { return mViewport.mLeft; } static int GetViewportTop() { return mViewport.mTop; } static int GetViewportWidth() { return mViewport.mWidth; } static int GetViewportHeight() { return mViewport.mHeight; } static float GetViewportHalfWidth() { return mViewport.mHalfWidth; } static float GetViewportHalfHeight() { return mViewport.mHalfHeight; } static float GetViewportAspect() { return static_cast(mViewport.mWidth) / static_cast(mViewport.mHeight); } static const CVector3f& GetViewPoint() { return mViewPoint; } static const CTransform4f& GetViewMatrix() { return mViewMatrix; } static const CTransform4f& GetModelMatrix() { return mModelMatrix; } static GX::LightMask GetLightMask() { return mLightActive; } static void LoadDolphinSpareTexture(int width, int height, GXTexFmt format, void* data, GXTexMapID id); static void LoadDolphinSpareTexture(int width, int height, GXCITexFmt format, GXTlut tlut, void* data, GXTexMapID id); static void ResetGfxStates() noexcept; static void SetTevStates(u32 flags) noexcept; static void SetTevOp(ERglTevStage stage, const CTevCombiners::CTevPass& pass); static void StreamBegin(ERglPrimitive primitive); static void StreamNormal(const zeus::CVector3f& nrm); static void StreamColor(const zeus::CColor& color); static inline void StreamColor(float r, float g, float b, float a) { StreamColor({r, g, b, a}); } static void StreamTexcoord(const zeus::CVector2f& uv); static inline void StreamTexcoord(float x, float y) { StreamTexcoord({x, y}); } static void StreamVertex(const zeus::CVector3f& pos); static inline void StreamVertex(float xyz) { StreamVertex({xyz, xyz, xyz}); } static inline void StreamVertex(float x, float y, float z) { StreamVertex({x, y, z}); } static void StreamEnd(); static void UpdateVertexDataStream(); static void ResetVertexDataStream(bool end); static void FlushStream(); static void FullRender(); static void DrawPrimitive(ERglPrimitive primitive, const zeus::CVector3f* pos, const zeus::CVector3f& normal, const zeus::CColor& col, s32 numVerts); static void SetLineWidth(float width, ERglTexOffset offs); }; template class TriFanToStrip { std::vector& m_vec; size_t m_start; size_t m_added = 0; public: explicit TriFanToStrip(std::vector& vec) : m_vec(vec), m_start(vec.size()) {} void AddVert(const VTX& vert) { ++m_added; if (m_added > 3 && (m_added & 1) == 0) { m_vec.reserve(m_vec.size() + 3); m_vec.push_back(m_vec.back()); m_vec.push_back(m_vec[m_start]); } m_vec.push_back(vert); } template void EmplaceVert(_Args&&... args) { ++m_added; if (m_added > 3 && (m_added & 1) == 0) { m_vec.reserve(m_vec.size() + 3); m_vec.push_back(m_vec.back()); m_vec.push_back(m_vec[m_start]); } m_vec.emplace_back(std::forward<_Args>(args)...); } // void Draw() const { CGraphics::DrawArray(m_start, m_vec.size() - m_start); } }; #ifdef AURORA_GFX_DEBUG_GROUPS struct ScopedDebugGroup { inline ScopedDebugGroup(const char* label) noexcept { push_debug_group(label); } inline ~ScopedDebugGroup() noexcept { pop_debug_group(); } }; #define SCOPED_GRAPHICS_DEBUG_GROUP(name, ...) \ ScopedDebugGroup _GfxDbg_ { name } #else #define SCOPED_GRAPHICS_DEBUG_GROUP(name, ...) // OPTICK_EVENT_DYNAMIC(name) #endif } // namespace metaforce ================================================ FILE: Runtime/Graphics/CGraphicsPalette.cpp ================================================ #include "Runtime/Graphics/CGraphicsPalette.hpp" #include "Runtime/Streams/CInputStream.hpp" namespace metaforce { u32 CGraphicsPalette::sCurrentFrameCount = 0; CGraphicsPalette::CGraphicsPalette(EPaletteFormat fmt, int count) : x0_fmt(fmt), x8_entryCount(count), xc_entries(std::make_unique(count)) { GXInitTlutObj(&x10_tlutObj, xc_entries.get(), static_cast(x0_fmt), x8_entryCount); } CGraphicsPalette::CGraphicsPalette(CInputStream& in) : x0_fmt(EPaletteFormat(in.ReadLong())) { u16 w = in.ReadShort(); u16 h = in.ReadShort(); x8_entryCount = w * h; xc_entries = std::make_unique(x8_entryCount); in.Get(reinterpret_cast(xc_entries.get()), x8_entryCount * sizeof(u16)); GXInitTlutObj(&x10_tlutObj, xc_entries.get(), static_cast(x0_fmt), x8_entryCount); // DCFlushRange(xc_entries.get(), x8_entryCount * 2); } CGraphicsPalette::~CGraphicsPalette() { #ifdef AURORA GXDestroyTlutObj(&x10_tlutObj); #endif } void CGraphicsPalette::Load() { GXLoadTlut(&x10_tlutObj, GX_TLUT0); x4_frameLoaded = sCurrentFrameCount; } void CGraphicsPalette::UnLock() { // DCStoreRange(xc_lut, x8_numEntries << 1); GXInitTlutObj(&x10_tlutObj, xc_entries.get(), static_cast(x0_fmt), x8_entryCount); // DCFlushRange(xc_lut, x8_numEntries << 1); x1c_locked = false; } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CGraphicsPalette.hpp ================================================ #pragma once #include "RetroTypes.hpp" #include "GX.hpp" #include namespace metaforce { class CInputStream; enum class EPaletteFormat : std::underlying_type_t { IA8 = GX_TL_IA8, RGB565 = GX_TL_RGB565, RGB5A3 = GX_TL_RGB5A3, }; class CGraphicsPalette { static u32 sCurrentFrameCount; friend class CTextRenderBuffer; EPaletteFormat x0_fmt; u32 x4_frameLoaded{}; u32 x8_entryCount; std::unique_ptr xc_entries; GXTlutObj x10_tlutObj; bool x1c_locked = false; public: explicit CGraphicsPalette(EPaletteFormat fmt, int count); explicit CGraphicsPalette(CInputStream& in); ~CGraphicsPalette(); u16* Lock() { x1c_locked = true; return xc_entries.get(); } void UnLock(); void Load(); [[nodiscard]] const u16* GetPaletteData() const { return xc_entries.get(); } static void SetCurrentFrameCount(u32 frameCount) { sCurrentFrameCount = frameCount; } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CLight.cpp ================================================ #include "Runtime/Graphics/CLight.hpp" #include namespace metaforce { constexpr zeus::CVector3f kDefaultPosition(0.f, 0.f, 0.f); constexpr zeus::CVector3f kDefaultDirection(0.f, -1.f, 0.f); float CLight::CalculateLightRadius() const { if (x28_distL < FLT_EPSILON && x2c_distQ < FLT_EPSILON) { return FLT_MAX; } float intensity = GetIntensity(); float ret = 0.f; if (x2c_distQ > FLT_EPSILON) { constexpr float mulVal = std::min(0.05882353f, 0.2f); // Yes, retro really did do this if (intensity > FLT_EPSILON) { return std::sqrt(intensity / (mulVal * x2c_distQ)); } } else { constexpr float mulVal = std::min(0.05882353f, 0.2f); // See above comment if (x28_distL > FLT_EPSILON) { return intensity / (mulVal * x28_distL); } } return 0.f; } float CLight::GetIntensity() const { if (x4c_24_intensityDirty) { x4c_24_intensityDirty = false; float coef = 1.f; if (x1c_type == ELightType::Custom) { coef = x30_angleC; } x48_cachedIntensity = coef * std::max({x18_color.r(), x18_color.g(), x18_color.b()}); } return x48_cachedIntensity; } float CLight::GetRadius() const { if (x4c_25_radiusDirty) { x44_cachedRadius = CalculateLightRadius(); x4c_25_radiusDirty = false; } return x44_cachedRadius; } CLight::CLight(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float distC, float distL, float distQ, float angleC, float angleL, float angleQ) : x0_pos(pos) , xc_dir(dir) , x18_color(color) , x24_distC(distC) , x28_distL(distL) , x2c_distQ(distQ) , x30_angleC(angleC) , x34_angleL(angleL) , x38_angleQ(angleQ) {} CLight::CLight(ELightType type, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float cutoff) : x0_pos(pos), xc_dir(dir), x18_color(color), x1c_type(type), x20_spotCutoff(cutoff) {} zeus::CColor CLight::GetNormalIndependentLightingAtPoint(const zeus::CVector3f& point) const { if (x1c_type == ELightType::LocalAmbient) return x18_color; float dist = std::max((x0_pos - point).magnitude(), FLT_EPSILON); return x18_color * (1.f / (dist * (x2c_distQ * dist) + (x28_distL * dist + x24_distC))); } CLight CLight::BuildDirectional(const zeus::CVector3f& dir, const zeus::CColor& color) { return CLight(ELightType::Directional, kDefaultPosition, dir, color, 180.f); } CLight CLight::BuildSpot(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float cutoff) { return CLight(ELightType::Spot, pos, dir, color, cutoff); } CLight CLight::BuildPoint(const zeus::CVector3f& pos, const zeus::CColor& color) { return CLight(ELightType::Point, pos, kDefaultDirection, color, 180.f); } CLight CLight::BuildCustom(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float distC, float distL, float distQ, float angleC, float angleL, float angleQ) { return CLight(pos, dir, color, distC, distL, distQ, angleC, angleL, angleQ); } CLight CLight::BuildLocalAmbient(const zeus::CVector3f& pos, const zeus::CColor& color) { return CLight(ELightType::LocalAmbient, pos, kDefaultDirection, color, 180.f); } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CLight.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include #include namespace metaforce { enum class ELightType { Spot = 0, Point = 1, Directional = 2, LocalAmbient = 3, Custom = 4, }; enum class EFalloffType { Constant, Linear, Quadratic }; class CLight { friend class CGuiLight; friend class CBooModel; friend class CBooRenderer; friend class CGameLight; zeus::CVector3f x0_pos; zeus::CVector3f xc_dir = zeus::skDown; zeus::CColor x18_color = zeus::skClear; ELightType x1c_type = ELightType::Custom; float x20_spotCutoff = 0.f; float x24_distC = 0.f; float x28_distL = 1.f; float x2c_distQ = 0.f; float x30_angleC = 0.f; float x34_angleL = 1.f; float x38_angleQ = 0.f; u32 x3c_priority = 0; u32 x40_lightId = 0; // Serves as unique key mutable float x44_cachedRadius = 0.f; mutable float x48_cachedIntensity = 0.f; mutable bool x4c_24_intensityDirty : 1 = true; mutable bool x4c_25_radiusDirty : 1 = true; float CalculateLightRadius() const; public: CLight() = default; CLight(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float distC, float distL, float distQ, float angleC, float angleL, float angleQ); CLight(ELightType type, const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float cutoff); void SetPosition(const zeus::CVector3f& pos) { x0_pos = pos; } const zeus::CVector3f& GetPosition() const { return x0_pos; } void SetDirection(const zeus::CVector3f& dir) { xc_dir = dir; } const zeus::CVector3f& GetDirection() const { return xc_dir; } void SetColor(const zeus::CColor& col) { x18_color = col; x4c_24_intensityDirty = true; x4c_25_radiusDirty = true; } void SetAttenuation(float constant, float linear, float quadratic) { x24_distC = constant; x28_distL = linear; x2c_distQ = quadratic; x4c_24_intensityDirty = true; x4c_25_radiusDirty = true; } float GetSpotCutoff() const { return x20_spotCutoff; } float GetAttenuationConstant() const { return x24_distC; } float GetAttenuationLinear() const { return x28_distL; } float GetAttenuationQuadratic() const { return x2c_distQ; } void SetAngleAttenuation(float constant, float linear, float quadratic) { x30_angleC = constant; x34_angleL = linear; x38_angleQ = quadratic; x4c_24_intensityDirty = true; x4c_25_radiusDirty = true; } float GetAngleAttenuationConstant() const { return x30_angleC; } float GetAngleAttenuationLinear() const { return x34_angleL; } float GetAngleAttenuationQuadratic() const { return x38_angleQ; } ELightType GetType() const { return x1c_type; } u32 GetId() const { return x40_lightId; } u32 GetPriority() const { return x3c_priority; } float GetIntensity() const; float GetRadius() const; const zeus::CColor& GetColor() const { return x18_color; } zeus::CColor GetNormalIndependentLightingAtPoint(const zeus::CVector3f& point) const; static CLight BuildDirectional(const zeus::CVector3f& dir, const zeus::CColor& color); static CLight BuildSpot(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float cutoff); static CLight BuildPoint(const zeus::CVector3f& pos, const zeus::CColor& color); static CLight BuildCustom(const zeus::CVector3f& pos, const zeus::CVector3f& dir, const zeus::CColor& color, float distC, float distL, float distQ, float angleC, float angleL, float angleQ); static CLight BuildLocalAmbient(const zeus::CVector3f& pos, const zeus::CColor& color); }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CMakeLists.txt ================================================ set(GRAPHICS_SOURCES IRenderer.hpp IWeaponRenderer.hpp IWeaponRenderer.cpp CCubeMaterial.cpp CCubeMaterial.hpp CCubeModel.cpp CCubeModel.hpp CCubeRenderer.cpp CCubeRenderer.hpp CCubeSurface.cpp CCubeSurface.hpp CDrawable.hpp CDrawablePlaneObject.hpp CMetroidModelInstance.cpp CMetroidModelInstance.hpp CLight.hpp CLight.cpp CTevCombiners.cpp CTevCombiners.hpp CTexture.hpp CTexture.cpp CModel.cpp CModel.hpp CSkinnedModel.hpp CSkinnedModel.cpp CVertexMorphEffect.hpp CVertexMorphEffect.cpp CMoviePlayer.hpp CMoviePlayer.cpp CGraphicsPalette.hpp CGraphicsPalette.cpp CGX.hpp CGX.cpp CPVSVisSet.hpp CPVSVisSet.cpp CPVSVisOctree.hpp CPVSVisOctree.cpp CPVSAreaSet.hpp CPVSAreaSet.cpp CGraphics.hpp CGraphics.cpp CSimpleShadow.hpp CSimpleShadow.cpp CRainSplashGenerator.hpp CRainSplashGenerator.cpp CFont.hpp CFont.cpp ) runtime_add_list(Graphics GRAPHICS_SOURCES) ================================================ FILE: Runtime/Graphics/CMetroidModelInstance.cpp ================================================ #include "CMetroidModelInstance.hpp" #include "Graphics/CCubeSurface.hpp" #include "Streams/IOStreams.hpp" namespace metaforce { CMetroidModelInstance::CMetroidModelInstance(std::span modelHeader, const u8* materialData, std::span positions, std::span normals, std::span colors, std::span texCoords, std::span packedTexCoords, std::vector&& surfaces) : x4c_materialData(materialData), x50_surfaces(std::move(surfaces)) { { CMemoryInStream stream{modelHeader.data(), static_cast(modelHeader.size_bytes())}; x0_visorFlags = stream.ReadUint32(); x4_worldXf = stream.Get(); x34_worldAABB = stream.Get(); } { u32 numVertices = positions.size_bytes() / 12; x60_positions.reserve(numVertices); CMemoryInStream stream{positions.data(), static_cast(positions.size_bytes())}; for (u32 i = 0; i < numVertices; ++i) { x60_positions.push_back(stream.Get>()); } } { // Always short normals in MREA u32 numNormals = normals.size_bytes() / 6; x64_normals.reserve(numNormals); CMemoryInStream stream{normals.data(), static_cast(normals.size_bytes())}; for (u32 i = 0; i < numNormals; ++i) { x64_normals.push_back(stream.Get>()); } } { u32 numColors = colors.size_bytes() / 4; x68_colors.reserve(numColors); CMemoryInStream stream{colors.data(), static_cast(colors.size_bytes())}; for (u32 i = 0; i < numColors; ++i) { x68_colors.push_back(stream.ReadUint32()); } } { u32 numTexCoords = texCoords.size_bytes() / 8; x6c_texCoords.reserve(numTexCoords); CMemoryInStream stream{texCoords.data(), static_cast(texCoords.size_bytes())}; for (u32 i = 0; i < numTexCoords; ++i) { x6c_texCoords.push_back(stream.Get>()); } } { u32 numPackedTexCoords = packedTexCoords.size_bytes() / 4; x70_packedTexCoords.reserve(numPackedTexCoords); CMemoryInStream stream{packedTexCoords.data(), static_cast(packedTexCoords.size_bytes())}; for (u32 i = 0; i < numPackedTexCoords; ++i) { x70_packedTexCoords.push_back(stream.Get>()); } } } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CMetroidModelInstance.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/Graphics/CCubeModel.hpp" #include "Runtime/RetroTypes.hpp" #include #include #include namespace metaforce { class CCubeSurface; class CMetroidModelInstance { u32 x0_visorFlags = 0; zeus::CTransform x4_worldXf; zeus::CAABox x34_worldAABB; const u8* x4c_materialData = nullptr; std::vector x50_surfaces; // was rstl::vector* std::vector> x60_positions; // was void* std::vector> x64_normals; // was void* std::vector x68_colors; // was void* std::vector> x6c_texCoords; // was void* std::vector> x70_packedTexCoords; // was void* public: CMetroidModelInstance() = default; CMetroidModelInstance(std::span modelHeader, const u8* materialData, std::span positions, std::span normals, std::span colors, std::span texCoords, std::span packedTexCoords, std::vector&& surfaces); [[nodiscard]] u32 GetFlags() const { return x0_visorFlags; } [[nodiscard]] const zeus::CAABox& GetBoundingBox() const { return x34_worldAABB; } [[nodiscard]] std::vector* GetSurfaces() { return &x50_surfaces; } [[nodiscard]] const std::vector* GetSurfaces() const { return &x50_surfaces; } [[nodiscard]] const u8* GetMaterialPointer() const { return x4c_materialData; } [[nodiscard]] std::span GetVertexPointer() const { return byte_span(x60_positions); } [[nodiscard]] std::span GetNormalPointer() const { return byte_span(x64_normals); } [[nodiscard]] std::span GetColorPointer() const { return byte_span(x68_colors); } [[nodiscard]] std::span GetTCPointer() const { return byte_span(x6c_texCoords); } [[nodiscard]] std::span GetPackedTCPointer() const { return byte_span(x70_packedTexCoords); } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CModel.cpp ================================================ #include "CModel.hpp" #include "CBasics.hpp" #include "Graphics/CCubeMaterial.hpp" #include "Graphics/CCubeModel.hpp" #include "Graphics/CCubeSurface.hpp" #include "Streams/IOStreams.hpp" namespace metaforce { void CModel::SShader::UnlockTextures() { for (auto& token : x0_textures) { token.Unlock(); } } u32 CModel::sTotalMemory = 0; u32 CModel::sFrameCounter = 0; bool CModel::sIsTextureTimeoutEnabled = true; CModel* CModel::sThisFrameList = nullptr; CModel* CModel::sOneFrameList = nullptr; CModel* CModel::sTwoFrameList = nullptr; static u8* MemoryFromPartData(u8*& dataCur, const u32*& secSizeCur) { u8* ret = nullptr; if (*secSizeCur != 0) { ret = dataCur; } dataCur += CBasics::SwapBytes(*secSizeCur); ++secSizeCur; return ret; } // For ease of reading byte swapped data static CMemoryInStream StreamFromPartData(u8*& dataCur, const u32*& secSizeCur) { const auto secSize = CBasics::SwapBytes(*secSizeCur); return {MemoryFromPartData(dataCur, secSizeCur), secSize}; } CModel::CModel(std::unique_ptr in, u32 dataLen, IObjectStore* store) : x0_data(std::move(in)) , x4_dataLen(dataLen) , x34_next(sThisFrameList) , x38_lastFrame(CGraphics::GetFrameCounter() - 2) { u8* data = x0_data.get(); u32 flags = CBasics::SwapBytes(*reinterpret_cast(data + 8)); u32 sectionSizeStart = 0x2c; if (CBasics::SwapBytes(*reinterpret_cast(data + 4)) == 1) { sectionSizeStart = 0x28; } const u32* secSizeCur = reinterpret_cast(data + sectionSizeStart); s32 numMatSets = 1; if (CBasics::SwapBytes(*reinterpret_cast(data + 4)) > 1) { numMatSets = CBasics::SwapBytes(*reinterpret_cast(data + 0x28)); } u8* dataCur = data + ROUND_UP_32(sectionSizeStart + CBasics::SwapBytes(*reinterpret_cast(data + 0x24)) * 4); x18_matSets.reserve(numMatSets); for (s32 i = 0; i < numMatSets; ++i) { x18_matSets.emplace_back(MemoryFromPartData(dataCur, secSizeCur)); auto& shader = x18_matSets.back(); CCubeModel::MakeTexturesFromMats(shader.x10_data, shader.x0_textures, store, true); x4_dataLen += shader.x0_textures.size() * sizeof(TCachedToken); } u32 numVertices = CBasics::SwapBytes(*secSizeCur) / 12; m_positions.reserve(numVertices); auto positions = StreamFromPartData(dataCur, secSizeCur); for (u32 i = 0; i < numVertices; ++i) { m_positions.emplace_back(positions.Get>()); } u32 numNormals = CBasics::SwapBytes(*secSizeCur); numNormals /= (flags & 2) == 0 ? 12 : 6; if ((flags & 2) == 0) { m_normals.reserve(numNormals); } else { m_shortNormals.reserve(numNormals); } auto normals = StreamFromPartData(dataCur, secSizeCur); for (u32 i = 0; i < numNormals; ++i) { if ((flags & 2) == 0) { m_normals.emplace_back(normals.Get>()); } else { m_shortNormals.emplace_back(normals.Get>()); } } u32 numColors = CBasics::SwapBytes(*secSizeCur) / 4; m_colors.reserve(numColors); auto vtxColors = StreamFromPartData(dataCur, secSizeCur); for (u32 i = 0; i < numColors; ++i) { m_colors.emplace_back(vtxColors.ReadUint32()); } u32 numFloatUVs = CBasics::SwapBytes(*secSizeCur) / 8; m_floatUVs.reserve(numFloatUVs); auto floatUVs = StreamFromPartData(dataCur, secSizeCur); for (u32 i = 0; i < numFloatUVs; ++i) { m_floatUVs.emplace_back(floatUVs.Get>()); } if ((flags & 4) != 0) { u32 numShortUVs = CBasics::SwapBytes(*secSizeCur) / 4; m_shortUVs.reserve(numShortUVs); auto shortUVs = StreamFromPartData(dataCur, secSizeCur); for (u32 i = 0; i < numShortUVs; ++i) { m_shortUVs.emplace_back(shortUVs.Get>()); } } auto surfaceInfo = StreamFromPartData(dataCur, secSizeCur); auto surfaceCount = surfaceInfo.ReadUint32(); x8_surfaces.reserve(surfaceCount); for (u32 i = 0; i < surfaceCount; ++i) { if (x8_surfaces.capacity() <= x8_surfaces.size()) { x8_surfaces.reserve(x8_surfaces.capacity() * 2); } const auto secSize = CBasics::SwapBytes(*secSizeCur); x8_surfaces.emplace_back(MemoryFromPartData(dataCur, secSizeCur), secSize); } const float* bounds = reinterpret_cast(data + 12); const zeus::CAABox aabb{ {CBasics::SwapBytes(bounds[0]), CBasics::SwapBytes(bounds[1]), CBasics::SwapBytes(bounds[2])}, {CBasics::SwapBytes(bounds[3]), CBasics::SwapBytes(bounds[4]), CBasics::SwapBytes(bounds[5])}, }; /* This constructor has been changed from the original to take into account platform differences */ x28_modelInst = std::make_unique(&x8_surfaces, &x18_matSets[0].x0_textures, x18_matSets[0].x10_data, byte_span(m_positions), byte_span(m_colors), (flags & 2) == 0 ? byte_span(m_normals) : byte_span(m_shortNormals), byte_span(m_floatUVs), byte_span(m_shortUVs), aabb, flags, true, -1); sThisFrameList = this; if (x34_next != nullptr) { x34_next->x30_prev = this; } x4_dataLen += x8_surfaces.size() * 4; sTotalMemory += x4_dataLen; // DCFlushRange(x0_data, dataLen); } CModel::~CModel() { RemoveFromList(); sTotalMemory -= x4_dataLen; } void CModel::UpdateLastFrame() { x38_lastFrame = CGraphics::GetFrameCounter(); } void CModel::MoveToThisFrameList() { UpdateLastFrame(); if (sThisFrameList == this) { return; } RemoveFromList(); if (sThisFrameList != nullptr) { x34_next = sThisFrameList; x34_next->x30_prev = this; } sThisFrameList = this; } void CModel::RemoveFromList() { if (x30_prev == nullptr) { if (sThisFrameList == this) { sThisFrameList = x34_next; } else if (sOneFrameList == this) { sOneFrameList = x34_next; } else if (sTwoFrameList == this) { sTwoFrameList = x34_next; } } else { x30_prev->x34_next = x34_next; } if (x34_next != nullptr) { x34_next->x30_prev = x30_prev; } x30_prev = nullptr; x34_next = nullptr; } void CModel::FrameDone() { ++sFrameCounter; if (sIsTextureTimeoutEnabled) { auto* iter = sTwoFrameList; while (iter != nullptr) { auto* next = iter->x34_next; iter->VerifyCurrentShader(0); for (auto& shader : iter->x18_matSets) { shader.UnlockTextures(); } iter->x28_modelInst->UnlockTextures(); iter->x34_next = nullptr; iter->x30_prev = nullptr; iter = next; } sTwoFrameList = sOneFrameList; sOneFrameList = sThisFrameList; sThisFrameList = nullptr; } } void CModel::EnableTextureTimeout() { sIsTextureTimeoutEnabled = true; } void CModel::DisableTextureTimeout() { sIsTextureTimeoutEnabled = false; } TConstVectorRef CModel::GetPositions() const { return x28_modelInst->GetPositions(); } TConstVectorRef CModel::GetNormals() const { return x28_modelInst->GetNormals(); } void CModel::VerifyCurrentShader(u32 matIdx) { if (matIdx > x18_matSets.size()) { matIdx = 0; } if (matIdx == x2c_currentMatIdx) { if (x2e_lastFrame != 0 && x2e_lastFrame < sFrameCounter) { for (size_t idx = 0; auto& mat : x18_matSets) { if (idx != matIdx) { mat.UnlockTextures(); } idx++; } } } else { x2c_currentMatIdx = matIdx; auto& mat = x18_matSets[matIdx]; x28_modelInst->RemapMaterialData(mat.x10_data, mat.x0_textures); if (x18_matSets.size() > 1) { x2e_lastFrame = sFrameCounter + 2; } } } bool CModel::IsLoaded(u32 matIdx) { VerifyCurrentShader(matIdx); const auto& textures = *x28_modelInst->x1c_textures; if (textures.empty()) { return true; } for (const auto& token : textures) { if (token.IsNull() && !token.IsLoaded()) { return false; } } return true; } void CModel::Touch(u32 matIdx) { MoveToThisFrameList(); VerifyCurrentShader(matIdx); if (x28_modelInst->TryLockTextures()) { for (auto& texture : *x28_modelInst->x1c_textures) { if (!texture.IsNull()) { // texture->LoadToMRAM(); } } } } void CModel::Draw(CModelFlags flags) { if (flags.x2_flags & CModelFlagBits::DrawNormal) { x28_modelInst->DrawNormal({}, {}, ESurfaceSelection::All); } CCubeMaterial::ResetCachedMaterials(); MoveToThisFrameList(); VerifyCurrentShader(flags.x1_matSetIdx); x28_modelInst->Draw(flags); } void CModel::Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) { if (flags.x2_flags & CModelFlagBits::DrawNormal) { x28_modelInst->DrawNormal(positions, normals, ESurfaceSelection::All); } CCubeMaterial::ResetCachedMaterials(); MoveToThisFrameList(); VerifyCurrentShader(flags.x1_matSetIdx); x28_modelInst->Draw(positions, normals, flags); } void CModel::DrawSortedParts(CModelFlags flags) { if (flags.x2_flags & CModelFlagBits::DrawNormal) { x28_modelInst->DrawNormal({}, {}, ESurfaceSelection::Sorted); } CCubeMaterial::ResetCachedMaterials(); MoveToThisFrameList(); VerifyCurrentShader(flags.x1_matSetIdx); x28_modelInst->DrawAlpha(flags); } void CModel::DrawUnsortedParts(CModelFlags flags) { if (flags.x2_flags & CModelFlagBits::DrawNormal) { x28_modelInst->DrawNormal({}, {}, ESurfaceSelection::Unsorted); } CCubeMaterial::ResetCachedMaterials(); MoveToThisFrameList(); VerifyCurrentShader(flags.x1_matSetIdx); x28_modelInst->DrawNormal(flags); } CFactoryFnReturn FModelFactory(const metaforce::SObjectTag& tag, std::unique_ptr&& in, u32 len, const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef) { IObjectStore* store = vparms.GetOwnedObj(); CFactoryFnReturn ret = TToken::GetIObjObjectFor(std::make_unique(std::move(in), len, store)); return ret; } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CModel.hpp ================================================ #pragma once #include #include #include "CToken.hpp" #include "GCNTypes.hpp" #include "Graphics/CCubeModel.hpp" #include "Graphics/CCubeSurface.hpp" #include "Graphics/CTexture.hpp" #include "IObjectStore.hpp" #include "Flags.hpp" namespace metaforce { class CCubeMaterial; enum class CModelFlagBits : u16 { DepthTest = 0x1, DepthUpdate = 0x2, NoTextureLock = 0x4, DepthGreater = 0x8, DepthNonInclusive = 0x10, DrawNormal = 0x20, ThermalUnsortedOnly = 0x40, }; using CModelFlagsFlags = Flags; struct CModelFlags { /** * 2: add color * >6: additive * >4: blend * else opaque */ u8 x0_blendMode = 0; u8 x1_matSetIdx = 0; CModelFlagsFlags x2_flags{}; /** * Set into kcolor slot specified by material */ zeus::CColor x4_color; constexpr CModelFlags() = default; constexpr CModelFlags(u8 blendMode, u8 shadIdx, u16 flags, const zeus::CColor& col) : x0_blendMode(blendMode), x1_matSetIdx(shadIdx), x2_flags(flags), x4_color(col) {} constexpr CModelFlags(u8 blendMode, u8 shadIdx, CModelFlagsFlags flags, const zeus::CColor& col) : x0_blendMode(blendMode), x1_matSetIdx(shadIdx), x2_flags(flags), x4_color(col) {} bool operator==(const CModelFlags& other) const { return x0_blendMode == other.x0_blendMode && x1_matSetIdx == other.x1_matSetIdx && x2_flags == other.x2_flags && x4_color == other.x4_color; } bool operator!=(const CModelFlags& other) const { return !operator==(other); } }; class CModel { public: struct SShader { std::vector> x0_textures; u8* x10_data; explicit SShader(u8* data) : x10_data(data) {} void UnlockTextures(); }; private: static u32 sTotalMemory; static u32 sFrameCounter; static bool sIsTextureTimeoutEnabled; static CModel* sThisFrameList; static CModel* sOneFrameList; static CModel* sTwoFrameList; std::unique_ptr x0_data; u32 x4_dataLen; std::vector x8_surfaces; // was rstl::vector std::vector x18_matSets; std::unique_ptr x28_modelInst = nullptr; u16 x2c_currentMatIdx = 0; u16 x2e_lastFrame = 0; // Last frame that the model switched materials CModel* x30_prev = nullptr; CModel* x34_next; u32 x38_lastFrame; /* Resident copies of maintained data */ std::vector> m_positions; std::vector> m_normals; std::vector> m_shortNormals; std::vector m_colors; std::vector> m_floatUVs; std::vector> m_shortUVs; public: CModel(std::unique_ptr in, u32 dataLen, IObjectStore* store); ~CModel(); void UpdateLastFrame(); void MoveToThisFrameList(); void RemoveFromList(); void VerifyCurrentShader(u32 matIdx); void Touch(u32 matIdx); void Draw(CModelFlags flags); void Draw(TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags); void DrawSortedParts(CModelFlags flags); void DrawUnsortedParts(CModelFlags flags); bool IsLoaded(u32 matIdx); [[nodiscard]] TConstVectorRef GetPositions() const; [[nodiscard]] TConstVectorRef GetNormals() const; [[nodiscard]] u32 GetNumMaterialSets() const { return x18_matSets.size(); } [[nodiscard]] bool IsOpaque() const { return x28_modelInst->x3c_firstSortedSurf == nullptr; } [[nodiscard]] const zeus::CAABox& GetAABB() const { return x28_modelInst->x20_worldAABB; } [[nodiscard]] auto& GetInstance() { return *x28_modelInst; } [[nodiscard]] const auto& GetInstance() const { return *x28_modelInst; } static void FrameDone(); static void EnableTextureTimeout(); static void DisableTextureTimeout(); }; CFactoryFnReturn FModelFactory(const metaforce::SObjectTag& tag, std::unique_ptr&& in, u32 len, const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Graphics/CMoviePlayer.cpp ================================================ #include "Graphics/CMoviePlayer.hpp" #include "Audio/g721.h" #include "CDvdRequest.hpp" #include "Graphics/CGraphics.hpp" #include "Graphics/CCubeRenderer.hpp" #include "Graphics/CGX.hpp" #include "GameGlobalObjects.hpp" // #include #include namespace metaforce { static void MyTHPYuv2RgbTextureSetup(void* dataY, void* dataU, void* dataV, u16 width, u16 height) { TGXTexObj texV; TGXTexObj texU; TGXTexObj texY; GXInitTexObj(&texY, dataY, width, height, static_cast(GX_TF_R8_PC), GX_CLAMP, GX_CLAMP, false); GXInitTexObjLOD(&texY, GX_NEAR, GX_NEAR, 0.0, 0.0, 0.0, false, false, GX_ANISO_1); GXLoadTexObj(&texY, GX_TEXMAP0); GXInitTexObj(&texU, dataU, width / 2, height / 2, static_cast(GX_TF_R8_PC), GX_CLAMP, GX_CLAMP, false); GXInitTexObjLOD(&texU, GX_NEAR, GX_NEAR, 0.0, 0.0, 0.0, false, false, GX_ANISO_1); GXLoadTexObj(&texU, GX_TEXMAP1); GXInitTexObj(&texV, dataV, width / 2, height / 2, static_cast(GX_TF_R8_PC), GX_CLAMP, GX_CLAMP, false); GXInitTexObjLOD(&texV, GX_NEAR, GX_NEAR, 0.0, 0.0, 0.0, false, false, GX_ANISO_1); GXLoadTexObj(&texV, GX_TEXMAP2); CTexture::InvalidateTexMap(GX_TEXMAP0); CTexture::InvalidateTexMap(GX_TEXMAP1); CTexture::InvalidateTexMap(GX_TEXMAP2); } const std::array InterlaceTex{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, }; static void MyTHPGXYuv2RgbSetup(bool interlaced2ndFrame, bool fieldFlip) { CGX::SetZMode(true, GX_ALWAYS, false); CGX::SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR); CGX::SetNumChans(0); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); if (false && !fieldFlip) { CGX::SetNumTexGens(3); CGX::SetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_POS, GX_TEXMTX0, false, GX_PTIDENTITY); float n = interlaced2ndFrame ? 0.25f : 0.f; float mtx[8] = {0.125f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.25f, n}; GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX2x4); TGXTexObj texObj; GXInitTexObj(&texObj, InterlaceTex.data(), 8, 4, GX_TF_I8, GX_REPEAT, GX_REPEAT, false); GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, false, false, GX_ANISO_1); GXLoadTexObj(&texObj, GX_TEXMAP3); CTexture::InvalidateTexMap(GX_TEXMAP3); CGX::SetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD2, GX_TEXMAP3, GX_COLOR_NULL); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE4); CGX::SetTevColorIn(GX_TEVSTAGE4, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV); CGX::SetTevAlphaIn(GX_TEVSTAGE4, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); CGX::SetAlphaCompare(GX_LESS, 128, GX_AOP_AND, GX_ALWAYS, 0); CGX::SetNumTevStages(5); } else { CGX::SetNumTexGens(2); CGX::SetNumTevStages(4); } constexpr std::array vtxDescList{ GXVtxDescList{GX_VA_POS, GX_DIRECT}, GXVtxDescList{GX_VA_TEX0, GX_DIRECT}, GXVtxDescList{GX_VA_NULL, GX_NONE}, }; CGX::SetVtxDescv(vtxDescList.data()); GXSetColorUpdate(true); GXSetAlphaUpdate(false); GXInvalidateTexAll(); GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_TEX0, GX_TEX_ST, GX_U16, 0); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_C0); CGX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, false, GX_TEVPREV); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_A0); CGX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, false, GX_TEVPREV); CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP2, GX_COLOR_NULL); CGX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_CPREV); CGX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, false, GX_TEVPREV); CGX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_APREV); CGX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, false, GX_TEVPREV); CGX::SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K1); CGX::SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_K1_A); CGX::SetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetTevColorIn(GX_TEVSTAGE2, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ONE, GX_CC_CPREV); CGX::SetTevColorOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevAlphaIn(GX_TEVSTAGE2, GX_CA_TEXA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV); CGX::SetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); CGX::SetTevColorIn(GX_TEVSTAGE3, GX_CC_APREV, GX_CC_CPREV, GX_CC_KONST, GX_CC_ZERO); CGX::SetTevColorOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevAlphaIn(GX_TEVSTAGE3, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); CGX::SetTevAlphaOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevKColorSel(GX_TEVSTAGE3, GX_TEV_KCSEL_K2); GXSetTevColorS10(GX_TEVREG0, GXColorS10{-90, 0, -114, 135}); CGX::SetTevKColor(GX_KCOLOR0, GXColor{0x00, 0x00, 0xe2, 0x58}); CGX::SetTevKColor(GX_KCOLOR1, GXColor{0xb3, 0x00, 0x00, 0xb6}); CGX::SetTevKColor(GX_KCOLOR2, GXColor{0xff, 0x00, 0xff, 0x80}); } static void MyTHPGXRestore() { CGX::SetZMode(true, GX_ALWAYS, false); CGX::SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET); CGX::SetNumTexGens(1); CGX::SetNumChans(0); CGX::SetNumTevStages(1); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); } /* used in the original to look up fixed-point dividends on a * MIDI-style volume scale (0-127) -> (n/0x8000) */ static const std::array StaticVolumeLookup = { 0x0000, 0x0002, 0x0008, 0x0012, 0x0020, 0x0032, 0x0049, 0x0063, 0x0082, 0x00A4, 0x00CB, 0x00F5, 0x0124, 0x0157, 0x018E, 0x01C9, 0x0208, 0x024B, 0x0292, 0x02DD, 0x032C, 0x037F, 0x03D7, 0x0432, 0x0492, 0x04F5, 0x055D, 0x05C9, 0x0638, 0x06AC, 0x0724, 0x07A0, 0x0820, 0x08A4, 0x092C, 0x09B8, 0x0A48, 0x0ADD, 0x0B75, 0x0C12, 0x0CB2, 0x0D57, 0x0DFF, 0x0EAC, 0x0F5D, 0x1012, 0x10CA, 0x1187, 0x1248, 0x130D, 0x13D7, 0x14A4, 0x1575, 0x164A, 0x1724, 0x1801, 0x18E3, 0x19C8, 0x1AB2, 0x1BA0, 0x1C91, 0x1D87, 0x1E81, 0x1F7F, 0x2081, 0x2187, 0x2291, 0x239F, 0x24B2, 0x25C8, 0x26E2, 0x2801, 0x2923, 0x2A4A, 0x2B75, 0x2CA3, 0x2DD6, 0x2F0D, 0x3048, 0x3187, 0x32CA, 0x3411, 0x355C, 0x36AB, 0x37FF, 0x3956, 0x3AB1, 0x3C11, 0x3D74, 0x3EDC, 0x4048, 0x41B7, 0x432B, 0x44A3, 0x461F, 0x479F, 0x4923, 0x4AAB, 0x4C37, 0x4DC7, 0x4F5C, 0x50F4, 0x5290, 0x5431, 0x55D6, 0x577E, 0x592B, 0x5ADC, 0x5C90, 0x5E49, 0x6006, 0x61C7, 0x638C, 0x6555, 0x6722, 0x68F4, 0x6AC9, 0x6CA2, 0x6E80, 0x7061, 0x7247, 0x7430, 0x761E, 0x7810, 0x7A06, 0x7C00, 0x7DFE, 0x8000}; /* shared resources */ static tjhandle TjHandle = nullptr; /* RSF audio state */ static const u8* StaticAudio = nullptr; static u32 StaticAudioSize = 0; static u32 StaticAudioOffset = 0; static u16 StaticVolumeAtten = 0x50F4; static u32 StaticLoopBegin = 0; static u32 StaticLoopEnd = 0; static g72x_state StaticStateLeft = {}; static g72x_state StaticStateRight = {}; /* THP SFX audio */ static float SfxVolume = 1.f; void CMoviePlayer::Initialize() { TjHandle = tjInitDecompress(); } void CMoviePlayer::Shutdown() { tjDestroy(TjHandle); TjHandle = nullptr; } void CMoviePlayer::THPHeader::swapBig() { magic = SBig(magic); version = SBig(version); maxBufferSize = SBig(maxBufferSize); maxAudioSamples = SBig(maxAudioSamples); fps = SBig(fps); numFrames = SBig(numFrames); firstFrameSize = SBig(firstFrameSize); dataSize = SBig(dataSize); componentDataOffset = SBig(componentDataOffset); offsetsDataOffset = SBig(offsetsDataOffset); firstFrameOffset = SBig(firstFrameOffset); lastFrameOffset = SBig(lastFrameOffset); } void CMoviePlayer::THPComponents::swapBig() { numComponents = SBig(numComponents); } void CMoviePlayer::THPVideoInfo::swapBig() { width = SBig(width); height = SBig(height); } void CMoviePlayer::THPAudioInfo::swapBig() { numChannels = SBig(numChannels); sampleRate = SBig(sampleRate); numSamples = SBig(numSamples); } void CMoviePlayer::THPFrameHeader::swapBig() { nextSize = SBig(nextSize); prevSize = SBig(prevSize); imageSize = SBig(imageSize); audioSize = SBig(audioSize); } void CMoviePlayer::THPAudioFrameHeader::swapBig() { channelSize = SBig(channelSize); numSamples = SBig(numSamples); for (int i = 0; i < 2; ++i) { for (int j = 0; j < 8; ++j) { channelCoefs[i][j][0] = SBig(channelCoefs[i][j][0]); channelCoefs[i][j][1] = SBig(channelCoefs[i][j][1]); } channelPrevs[i][0] = SBig(channelPrevs[i][0]); channelPrevs[i][1] = SBig(channelPrevs[i][1]); } } /* Slightly modified from THPAudioDecode present in SDK; always interleaves */ u32 CMoviePlayer::THPAudioDecode(s16* buffer, const u8* audioFrame, bool stereo) { THPAudioFrameHeader header = *((const THPAudioFrameHeader*)audioFrame); // header.swapBig(); // audioFrame += sizeof(THPAudioFrameHeader); // // if (stereo) { // for (int i = 0; i < 2; ++i) { // unsigned samples = header.numSamples; // s16* bufferCur = buffer + i; // int16_t prev1 = header.channelPrevs[i][0]; // int16_t prev2 = header.channelPrevs[i][1]; // for (u32 f = 0; f < (header.numSamples + 13) / 14; ++f) { // DSPDecompressFrameStereoStride(bufferCur, audioFrame, header.channelCoefs[i], &prev1, &prev2, samples); // samples -= 14; // bufferCur += 28; // audioFrame += 8; // } // } // } else { // unsigned samples = header.numSamples; // s16* bufferCur = buffer; // int16_t prev1 = header.channelPrevs[0][0]; // int16_t prev2 = header.channelPrevs[0][1]; // for (u32 f = 0; f < (header.numSamples + 13) / 14; ++f) { // DSPDecompressFrameStereoDupe(bufferCur, audioFrame, header.channelCoefs[0], &prev1, &prev2, samples); // samples -= 14; // bufferCur += 28; // audioFrame += 8; // } // } return header.numSamples; } CMoviePlayer::CMoviePlayer(const char* path, float preLoadSeconds, bool loop, bool deinterlace) : CDvdFile(path), xec_preLoadSeconds(preLoadSeconds), xf4_24_loop(loop), m_deinterlace(deinterlace) { /* Read THP header information */ u8 buf[64]; SyncRead(buf, 64); memmove(&x28_thpHead, buf, 48); x28_thpHead.swapBig(); u32 cur = x28_thpHead.componentDataOffset; SyncSeekRead(buf, 32, ESeekOrigin::Begin, cur); memmove(&x58_thpComponents, buf, 20); cur += 20; x58_thpComponents.swapBig(); for (u32 i = 0; i < x58_thpComponents.numComponents; ++i) { switch (x58_thpComponents.comps[i]) { case THPComponents::Type::Video: SyncSeekRead(buf, 32, ESeekOrigin::Begin, cur); memmove(&x6c_videoInfo, buf, 8); cur += 8; x6c_videoInfo.swapBig(); break; case THPComponents::Type::Audio: SyncSeekRead(buf, 32, ESeekOrigin::Begin, cur); memmove(&x74_audioInfo, buf, 12); cur += 12; x74_audioInfo.swapBig(); xf4_25_hasAudio = true; break; default: break; } } /* Initial read state */ xb4_nextReadOff = x28_thpHead.firstFrameOffset; xb0_nextReadSize = x28_thpHead.firstFrameSize; xb8_readSizeWrapped = x28_thpHead.firstFrameSize; xbc_readOffWrapped = x28_thpHead.firstFrameOffset; xe4_totalSeconds = x28_thpHead.numFrames / x28_thpHead.fps; if (xec_preLoadSeconds < 0.f) { /* Load whole video */ xec_preLoadSeconds = xe4_totalSeconds; xf0_preLoadFrames = x28_thpHead.numFrames; } else if (xec_preLoadSeconds > 0.f) { /* Pre-load video portion */ u32 frame = xec_preLoadSeconds * x28_thpHead.fps; xf0_preLoadFrames = std::min(frame, x28_thpHead.numFrames); xec_preLoadSeconds = std::min(xe4_totalSeconds, xec_preLoadSeconds); } if (xf0_preLoadFrames > 0) xa0_bufferQueue.reserve(xf0_preLoadFrames); /* Allocate textures here (rather than at decode time) */ x80_textures.reserve(3); for (int i = 0; i < 3; ++i) { CTHPTextureSet& set = x80_textures.emplace_back(); if (xf4_25_hasAudio) set.audioBuf.reset(new s16[x28_thpHead.maxAudioSamples * 2]); } /* Temporary planar YUV decode buffer, resulting planes copied to Boo */ m_yuvBuf.reset(new uint8_t[tjBufSizeYUV(x6c_videoInfo.width, x6c_videoInfo.height, TJ_420)]); /* Schedule initial read */ PostDVDReadRequestIfNeeded(); m_hpad = 0.5f; m_vpad = 0.5f; } void CMoviePlayer::SetStaticAudioVolume(int vol) { StaticVolumeAtten = StaticVolumeLookup[std::max(0, std::min(127, vol))]; } void CMoviePlayer::SetStaticAudio(const void* data, u32 size, u32 loopBegin, u32 loopEnd) { StaticAudio = reinterpret_cast(data); StaticAudioSize = size; StaticLoopBegin = loopBegin; StaticLoopEnd = loopEnd; StaticAudioOffset = 0; g72x_init_state(&StaticStateLeft); g72x_init_state(&StaticStateRight); } void CMoviePlayer::SetSfxVolume(u8 volume) { SfxVolume = std::min(volume, u8(127)) / 127.f; } void CMoviePlayer::MixAudio(s16* out, const s16* in, u32 samples) { /* No audio frames ready */ // if (xd4_audioSlot == UINT32_MAX) { // if (in) // memmove(out, in, samples * 4); // else // memset(out, 0, samples * 4); // return; // } // // while (samples) { // CTHPTextureSet* tex = &x80_textures[xd4_audioSlot]; // u32 thisSamples = std::min(tex->audioSamples - tex->playedSamples, samples); // if (!thisSamples) { // /* Advance frame */ // ++xd4_audioSlot; // if (xd4_audioSlot >= x80_textures.size()) // xd4_audioSlot = 0; // tex = &x80_textures[xd4_audioSlot]; // thisSamples = std::min(tex->audioSamples - tex->playedSamples, samples); // } // // if (thisSamples) { // /* mix samples with `in` or no mix */ // if (in) { // for (u32 i = 0; i < thisSamples; ++i, out += 2, in += 2) { // out[0] = DSPSampClamp(in[0] + s32(tex->audioBuf[(i + tex->playedSamples) * 2]) * 0x50F4 / 0x8000 * // SfxVolume); out[1] = // DSPSampClamp(in[1] + s32(tex->audioBuf[(i + tex->playedSamples) * 2 + 1]) * 0x50F4 / 0x8000 * // SfxVolume); // } // } else { // for (u32 i = 0; i < thisSamples; ++i, out += 2) { // out[0] = DSPSampClamp(s32(tex->audioBuf[(i + tex->playedSamples) * 2]) * 0x50F4 / 0x8000 * SfxVolume); // out[1] = DSPSampClamp(s32(tex->audioBuf[(i + tex->playedSamples) * 2 + 1]) * 0x50F4 / 0x8000 * SfxVolume); // } // } // tex->playedSamples += thisSamples; // samples -= thisSamples; // } else { // /* metaforce addition: failsafe for buffer overrun */ // if (in) // memmove(out, in, samples * 4); // else // memset(out, 0, samples * 4); // // fprintf(stderr, "dropped %d samples\n", samples); // return; // } // } } void CMoviePlayer::MixStaticAudio(s16* out, const s16* in, u32 samples) { // if (!StaticAudio) // return; // while (samples) { // u32 thisSamples = std::min(StaticLoopEnd - StaticAudioOffset, samples); // const u8* thisOffsetLeft = &StaticAudio[StaticAudioOffset / 2]; // const u8* thisOffsetRight = &StaticAudio[StaticAudioSize / 2 + StaticAudioOffset / 2]; // // /* metaforce addition: mix samples with `in` or no mix */ // if (in) { // for (u32 i = 0; i < thisSamples; i += 2) { // out[0] = DSPSampClamp( // in[0] + s32(g721_decoder(thisOffsetLeft[0] & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000)); // out[1] = DSPSampClamp( // in[1] + s32(g721_decoder(thisOffsetRight[0] & 0xf, &StaticStateRight) * StaticVolumeAtten / 0x8000)); // out[2] = DSPSampClamp( // in[2] + s32(g721_decoder(thisOffsetLeft[0] >> 4 & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000)); // out[3] = DSPSampClamp( // in[3] + s32(g721_decoder(thisOffsetRight[0] >> 4 & 0xf, &StaticStateRight) * StaticVolumeAtten / // 0x8000)); // thisOffsetLeft += 1; // thisOffsetRight += 1; // out += 4; // in += 4; // } // } else { // for (u32 i = 0; i < thisSamples; i += 2) { // out[0] = // DSPSampClamp(s32(g721_decoder(thisOffsetLeft[0] & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000)); // out[1] = // DSPSampClamp(s32(g721_decoder(thisOffsetRight[0] & 0xf, &StaticStateRight) * StaticVolumeAtten / // 0x8000)); // out[2] = DSPSampClamp( // s32(g721_decoder(thisOffsetLeft[0] >> 4 & 0xf, &StaticStateLeft) * StaticVolumeAtten / 0x8000)); // out[3] = DSPSampClamp( // s32(g721_decoder(thisOffsetRight[0] >> 4 & 0xf, &StaticStateRight) * StaticVolumeAtten / 0x8000)); // thisOffsetLeft += 1; // thisOffsetRight += 1; // out += 4; // } // } // // StaticAudioOffset += thisSamples; // if (StaticAudioOffset == StaticLoopEnd) // StaticAudioOffset = StaticLoopBegin; // samples -= thisSamples; // } } void CMoviePlayer::Rewind() { if (x98_request) { x98_request->PostCancelRequest(); x98_request.reset(); } xb0_nextReadSize = x28_thpHead.firstFrameSize; xb4_nextReadOff = x28_thpHead.firstFrameOffset; xb8_readSizeWrapped = x28_thpHead.firstFrameSize; xbc_readOffWrapped = x28_thpHead.firstFrameOffset; xc0_curLoadFrame = 0; xc4_requestFrameWrapped = 0; xc8_curFrame = 0; xcc_decodedTexSlot = 0; xd0_drawTexSlot = -1; xd4_audioSlot = -1; xd8_decodedTexCount = 0; xdc_frameRem = 0.f; xe8_curSeconds = 0.f; } bool CMoviePlayer::DrawVideo() { // TODO // if (!xa0_bufferQueue.empty()) { // return false; // } g_Renderer->SetDepthReadWrite(false, false); g_Renderer->SetViewportOrtho(false, -4096.f, 4096.f); const s32 vpHeight = CGraphics::GetViewportHeight(); const s32 vpWidth = CGraphics::GetViewportWidth(); const s32 vpTop = CGraphics::GetViewportTop(); const s32 vpLeft = CGraphics::GetViewportLeft(); #ifdef AURORA // Scale to full size, maintaining aspect ratio const float scale = std::min(static_cast(CGraphics::GetViewportWidth()) / 640.0f, static_cast(CGraphics::GetViewportHeight()) / 448.0f); const s32 vidWidth = x6c_videoInfo.width * scale; const s32 vidHeight = x6c_videoInfo.height * scale; #else const s32 vidWidth = x6c_videoInfo.width; const s32 vidHeight = x6c_videoInfo.height; #endif const s32 centerX = (vidWidth - vpWidth) / 2; const s32 centerY = (vidHeight - vpHeight) / 2; const s32 vl = vpLeft - centerX; const s32 vr = vpLeft + vpWidth + centerX; const s32 vb = vpTop + vpHeight + centerY; const s32 vt = vpTop - centerY; zeus::CVector3f v1; zeus::CVector3f v2; zeus::CVector3f v3; zeus::CVector3f v4; v1.x() = vl; v1.y() = 0.0; v1.z() = vb; v2.x() = vr; v2.y() = 0.0; v2.z() = vb; v3.x() = vl; v3.y() = 0.0; v3.z() = vt; v4.x() = vr; v4.y() = 0.0; v4.z() = vt; DrawFrame(v1, v2, v3, v4); return true; } void CMoviePlayer::DrawFrame(const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, const zeus::CVector3f& v4) { if (xd0_drawTexSlot == UINT32_MAX || !GetIsFullyCached()) { return; } SCOPED_GRAPHICS_DEBUG_GROUP("CMoviePlayer::DrawFrame", zeus::skYellow); CGraphics::SetUseVideoFilter(xf4_26_fieldFlip); MyTHPGXYuv2RgbSetup(CGraphics::mLastFrameUsedAbove, xf4_26_fieldFlip); uintptr_t planeSize = x6c_videoInfo.width * x6c_videoInfo.height; uintptr_t planeSizeQuarter = planeSize / 4; MyTHPYuv2RgbTextureSetup(m_yuvBuf.get(), m_yuvBuf.get() + planeSize, m_yuvBuf.get() + planeSize + planeSizeQuarter, x6c_videoInfo.width, x6c_videoInfo.height); CGX::Begin(GX_TRIANGLEFAN, GX_VTXFMT7, 4); GXPosition3f32(v1); GXTexCoord2u16(0, 0); GXPosition3f32(v3); GXTexCoord2u16(0, 1); GXPosition3f32(v4); GXTexCoord2u16(1, 1); GXPosition3f32(v2); GXTexCoord2u16(1, 0); CGX::End(); MyTHPGXRestore(); /* ensure second field is being displayed by VI to signal advance * (faked in metaforce with continuous xor) */ if (xfc_fieldIndex == 0 && CGraphics::mLastFrameUsedAbove) xf4_26_fieldFlip = true; ++xfc_fieldIndex; } void CMoviePlayer::Update(float dt) { if (xc0_curLoadFrame < xf0_preLoadFrames) { /* in buffering phase, ensure read data is stored for mem-cache access */ if (x98_request && x98_request->IsComplete()) { ReadCompleted(); if (xc0_curLoadFrame >= xa0_bufferQueue.size() && xc0_curLoadFrame < xf0_preLoadFrames && xa0_bufferQueue.size() < x28_thpHead.numFrames) { PostDVDReadRequestIfNeeded(); } } } else { /* out of buffering phase, skip mem-cache straight to decode */ if (x98_request) { bool flag = false; if (xc4_requestFrameWrapped >= xa0_bufferQueue.size() && xc0_curLoadFrame >= xa0_bufferQueue.size()) flag = true; if (x98_request->IsComplete() && xd8_decodedTexCount < 2 && flag) { DecodeFromRead(x90_requestBuf.get()); ReadCompleted(); PostDVDReadRequestIfNeeded(); ++xd8_decodedTexCount; ++xc4_requestFrameWrapped; if (xc4_requestFrameWrapped >= x28_thpHead.numFrames && xf4_24_loop) xc4_requestFrameWrapped = 0; } } } /* submit request for additional read to keep stream-consumer happy * (if buffer slot is available) */ if (!x98_request && xe0_playMode == EPlayMode::Playing && xa0_bufferQueue.size() < x28_thpHead.numFrames) { PostDVDReadRequestIfNeeded(); } /* decode frame directly from mem-cache if needed */ if (xd8_decodedTexCount < 2) { if (xe0_playMode == EPlayMode::Playing && xc4_requestFrameWrapped < xf0_preLoadFrames) { u32 minFrame = std::min(u32(xa0_bufferQueue.size()) - 1, xc4_requestFrameWrapped); if (minFrame == UINT32_MAX) return; std::unique_ptr& frameData = xa0_bufferQueue[minFrame]; DecodeFromRead(frameData.get()); ++xd8_decodedTexCount; ++xc4_requestFrameWrapped; if (xc4_requestFrameWrapped >= x28_thpHead.numFrames && xf4_24_loop) xc4_requestFrameWrapped = 0; } } /* paused THPs shall not pass */ if (xd8_decodedTexCount <= 0 || xe0_playMode != EPlayMode::Playing) return; /* timing update */ xe8_curSeconds += dt; if (xf4_24_loop) xe8_curSeconds = std::fmod(xe8_curSeconds, xe4_totalSeconds); else xe8_curSeconds = std::min(xe4_totalSeconds, xe8_curSeconds); /* test remainder threshold, determine if frame needs to be advanced */ float frameDt = 1.f / x28_thpHead.fps; float rem = xdc_frameRem - dt; if (rem <= 0.f) { if (!xf4_26_fieldFlip) { /* second field has drawn, advance consumer-queue to next THP frame */ ++xd0_drawTexSlot; if (xd0_drawTexSlot >= x80_textures.size()) xd0_drawTexSlot = 0; if (xd4_audioSlot == UINT32_MAX) xd4_audioSlot = 0; --xd8_decodedTexCount; ++xc8_curFrame; if (xc8_curFrame == x28_thpHead.numFrames && xf4_24_loop) xc8_curFrame = 0; rem += frameDt; xfc_fieldIndex = 0; } else { /* advance timing within second field */ rem += dt; xf4_26_fieldFlip = false; } } xdc_frameRem = rem; } void CMoviePlayer::DecodeFromRead(const void* data) { const u8* inptr = (u8*)data; CTHPTextureSet& tex = x80_textures[xcc_decodedTexSlot]; THPFrameHeader frameHeader = *static_cast(data); frameHeader.swapBig(); inptr += 8 + x58_thpComponents.numComponents * 4; for (u32 i = 0; i < x58_thpComponents.numComponents; ++i) { switch (x58_thpComponents.comps[i]) { case THPComponents::Type::Video: { tjDecompressToYUV(TjHandle, (u8*)inptr, frameHeader.imageSize, m_yuvBuf.get(), 0); inptr += frameHeader.imageSize; break; } case THPComponents::Type::Audio: memset(tex.audioBuf.get(), 0, x28_thpHead.maxAudioSamples * 4); tex.audioSamples = THPAudioDecode(tex.audioBuf.get(), (u8*)inptr, x74_audioInfo.numChannels == 2); tex.playedSamples = 0; inptr += frameHeader.audioSize; break; default: break; } } /* advance YUV producer-queue slot */ ++xcc_decodedTexSlot; if (xcc_decodedTexSlot == x80_textures.size()) xcc_decodedTexSlot = 0; } void CMoviePlayer::ReadCompleted() { std::unique_ptr buffer = std::move(x90_requestBuf); x98_request.reset(); const THPFrameHeader* frameHeader = reinterpret_cast(buffer.get()); /* transfer request buffer to mem-cache if needed */ if (xc0_curLoadFrame == xa0_bufferQueue.size() && xf0_preLoadFrames > xc0_curLoadFrame) xa0_bufferQueue.push_back(std::move(buffer)); /* store params of next read operation */ xb4_nextReadOff += xb0_nextReadSize; xb0_nextReadSize = SBig(frameHeader->nextSize); ++xc0_curLoadFrame; if (xc0_curLoadFrame == xf0_preLoadFrames) { if (x28_thpHead.numFrames == xc0_curLoadFrame) { xb8_readSizeWrapped = x28_thpHead.firstFrameSize; xbc_readOffWrapped = x28_thpHead.firstFrameOffset; } else { xb8_readSizeWrapped = xb0_nextReadSize; xbc_readOffWrapped = xb4_nextReadOff; } } /* handle loop-event read */ if (xc0_curLoadFrame >= x28_thpHead.numFrames && xf4_24_loop) { xb4_nextReadOff = xbc_readOffWrapped; xb0_nextReadSize = xb8_readSizeWrapped; xc0_curLoadFrame = xf0_preLoadFrames; } } void CMoviePlayer::PostDVDReadRequestIfNeeded() { if (xc0_curLoadFrame < x28_thpHead.numFrames) { x90_requestBuf.reset(new uint8_t[xb0_nextReadSize]); x98_request = AsyncSeekRead(x90_requestBuf.get(), xb0_nextReadSize, ESeekOrigin::Begin, xb4_nextReadOff); } } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CMoviePlayer.hpp ================================================ #pragma once #include #include #include "Runtime/CDvdFile.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include #include namespace metaforce { class CMoviePlayer : public CDvdFile { public: enum class EPlayMode { Stopped, Playing }; private: struct THPHeader { u32 magic; u32 version; u32 maxBufferSize; u32 maxAudioSamples; float fps; u32 numFrames; u32 firstFrameSize; u32 dataSize; u32 componentDataOffset; u32 offsetsDataOffset; u32 firstFrameOffset; u32 lastFrameOffset; void swapBig(); } x28_thpHead{}; struct THPComponents { u32 numComponents; enum class Type : u8 { Video = 0x0, Audio = 0x1, None = 0xff } comps[16]; void swapBig(); } x58_thpComponents{}; struct THPVideoInfo { u32 width; u32 height; void swapBig(); } x6c_videoInfo{}; struct THPAudioInfo { u32 numChannels; u32 sampleRate; u32 numSamples; void swapBig(); } x74_audioInfo{}; struct THPFrameHeader { u32 nextSize; u32 prevSize; u32 imageSize; u32 audioSize; void swapBig(); }; struct THPAudioFrameHeader { u32 channelSize; u32 numSamples; s16 channelCoefs[2][8][2]; s16 channelPrevs[2][2]; void swapBig(); }; struct CTHPTextureSet { std::array Y; TGXTexObj U; TGXTexObj V; u32 playedSamples = 0; u32 audioSamples = 0; std::unique_ptr audioBuf; }; std::vector x80_textures; std::unique_ptr x90_requestBuf; std::shared_ptr x98_request; std::vector> xa0_bufferQueue; u32 xb0_nextReadSize = 0; u32 xb4_nextReadOff = 0; u32 xb8_readSizeWrapped = 0; u32 xbc_readOffWrapped = 0; u32 xc0_curLoadFrame = 0; u32 xc4_requestFrameWrapped = 0; u32 xc8_curFrame = 0; u32 xcc_decodedTexSlot = 0; u32 xd0_drawTexSlot = -1; u32 xd4_audioSlot = -1; s32 xd8_decodedTexCount = 0; float xdc_frameRem = 0.f; EPlayMode xe0_playMode = EPlayMode::Playing; float xe4_totalSeconds = 0.f; float xe8_curSeconds = 0.f; float xec_preLoadSeconds; u32 xf0_preLoadFrames = 0; bool xf4_24_loop : 1; bool xf4_25_hasAudio : 1 = false; bool xf4_26_fieldFlip : 1 = false; bool m_deinterlace : 1; u32 xf8_ = 0; u32 xfc_fieldIndex = 0; std::unique_ptr m_yuvBuf; float m_hpad; float m_vpad; void DecodeFromRead(const void* data); void PostDVDReadRequestIfNeeded(); void ReadCompleted(); static u32 THPAudioDecode(s16* buffer, const u8* audioFrame, bool stereo); public: CMoviePlayer(const char* path, float preLoadSeconds, bool loop, bool deinterlace); static void DisableStaticAudio() { SetStaticAudio(nullptr, 0, 0, 0); } static void SetStaticAudioVolume(int vol); static void SetStaticAudio(const void* data, u32 size, u32 loopBegin, u32 loopEnd); static void SetSfxVolume(u8 volume); static void MixStaticAudio(s16* out, const s16* in, u32 samples); void MixAudio(s16* out, const s16* in, u32 samples); void Rewind(); bool GetIsMovieFinishedPlaying() const { if (xf4_24_loop) return false; return xc8_curFrame == x28_thpHead.numFrames; } bool IsLooping() const { return xf4_24_loop; } bool GetIsFullyCached() const { return xa0_bufferQueue.size() >= xf0_preLoadFrames; } float GetPlayedSeconds() const { return xdc_frameRem + xe8_curSeconds; } float GetTotalSeconds() const { return xe4_totalSeconds; } void SetPlayMode(EPlayMode mode) { xe0_playMode = mode; } bool DrawVideo(); void DrawFrame(const zeus::CVector3f& v1, const zeus::CVector3f& v2, const zeus::CVector3f& v3, const zeus::CVector3f& v4); void Update(float dt); u32 GetWidth() const { return x6c_videoInfo.width; } u32 GetHeight() const { return x6c_videoInfo.width; } static void Initialize(); static void Shutdown(); }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CPVSAreaSet.cpp ================================================ #include "Runtime/Graphics/CPVSAreaSet.hpp" #include "Runtime/Streams/IOStreams.hpp" namespace metaforce { CPVSAreaSet::CPVSAreaSet(const u8* data, u32 len) { CMemoryInStream r(data, len); x0_numFeatures = r.ReadLong(); x4_numLights = r.ReadLong(); x8_num2ndLights = r.ReadLong(); xc_numActors = r.ReadLong(); x10_leafSize = r.ReadLong(); x14_lightIndexCount = r.ReadLong(); x18_entityIndex.reserve(xc_numActors); for (u32 i = 0; i < xc_numActors; ++i) x18_entityIndex.push_back(r.ReadLong()); x1c_lightLeaves = data + r.GetReadPosition(); const u8* octreeData = x1c_lightLeaves + x14_lightIndexCount * x10_leafSize; x20_octree = CPVSVisOctree::MakePVSVisOctree(octreeData); } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CPVSAreaSet.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/Graphics/CPVSVisOctree.hpp" namespace metaforce { class CPVSAreaSet { u32 x0_numFeatures; u32 x4_numLights; u32 x8_num2ndLights; u32 xc_numActors; u32 x10_leafSize; u32 x14_lightIndexCount; std::vector x18_entityIndex; const u8* x1c_lightLeaves; CPVSVisOctree x20_octree; CPVSVisSet _GetLightSet(size_t lightIdx) const { CPVSVisSet ret; ret.SetFromMemory(x20_octree.GetNumObjects(), x20_octree.GetNumLights(), x1c_lightLeaves + x10_leafSize * lightIdx); return ret; } public: explicit CPVSAreaSet(const u8* data, u32 len); u32 GetNumFeatures() const { return x0_numFeatures; } u32 GetNumActors() const { return xc_numActors; } u32 Get1stLightIndex(u32 lightIdx) const { return x0_numFeatures + x8_num2ndLights + lightIdx; } u32 Get2ndLightIndex(u32 lightIdx) const { return x0_numFeatures + lightIdx; } bool Has2ndLayerLights() const { return x8_num2ndLights != 0; } u32 GetEntityIdByIndex(size_t idx) const { return x18_entityIndex[idx]; } const CPVSVisOctree& GetVisOctree() const { return x20_octree; } CPVSVisSet Get1stLightSet(size_t lightIdx) const { return _GetLightSet(x8_num2ndLights + lightIdx); } CPVSVisSet Get2ndLightSet(size_t lightIdx) const { return _GetLightSet(lightIdx); } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CPVSVisOctree.cpp ================================================ #include "Runtime/Graphics/CPVSVisOctree.hpp" #include "Runtime/Streams/IOStreams.hpp" #include namespace metaforce { CPVSVisOctree CPVSVisOctree::MakePVSVisOctree(const u8* data) { CMemoryInStream r(data, 68); const zeus::CAABox aabb = r.Get(); const u32 numObjects = r.ReadLong(); const u32 numLights = r.ReadLong(); r.ReadLong(); return CPVSVisOctree(aabb, numObjects, numLights, data + r.GetReadPosition()); } CPVSVisOctree::CPVSVisOctree(const zeus::CAABox& aabb, u32 numObjects, u32 numLights, const u8* c) : x0_aabb(aabb) , x18_numObjects(numObjects) , x1c_numLights(numLights) , x20_bufferFlag(c != nullptr) , x24_octreeData(c) , x2c_searchAabb(x0_aabb) { x20_bufferFlag = false; } u32 CPVSVisOctree::GetNumChildren(u8 byte) const { static constexpr std::array numChildTable{0, 2, 2, 4, 2, 4, 4, 8}; return numChildTable[byte & 0x7]; } u32 CPVSVisOctree::GetChildIndex(const u8*, const zeus::CVector3f&) const { return 0; } s32 CPVSVisOctree::IterateSearch(u8 nodeData, const zeus::CVector3f& tp) const { if (!(nodeData & 0x7)) return -1; // Leaf node zeus::CVector3f newMin = x2c_searchAabb.center(); zeus::CVector3f newMax; std::array highFlags{}; if (tp.x() > newMin.x()) { newMax.x() = x2c_searchAabb.max.x(); highFlags[0] = true; } else { newMax.x() = float(newMin.x()); newMin.x() = float(x2c_searchAabb.min.x()); highFlags[0] = false; } if (tp.y() > newMin.y()) { newMax.y() = float(x2c_searchAabb.max.y()); highFlags[1] = true; } else { newMax.y() = float(newMin.y()); newMin.y() = float(x2c_searchAabb.min.y()); highFlags[1] = false; } if (tp.z() > newMin.z()) { newMax.z() = float(x2c_searchAabb.max.z()); highFlags[2] = true; } else { newMax.z() = float(newMin.z()); newMin.z() = float(x2c_searchAabb.min.z()); highFlags[2] = false; } std::array axisCounts{1, 1}; if (nodeData & 0x1) axisCounts[0] = 2; if (nodeData & 0x2) axisCounts[1] = 2; zeus::CAABox& newSearch = const_cast(x2c_searchAabb); if (nodeData & 0x1) { newSearch.min.x() = float(newMin.x()); newSearch.max.x() = float(newMax.x()); } if (nodeData & 0x2) { newSearch.min.y() = float(newMin.y()); newSearch.max.y() = float(newMax.y()); } if (nodeData & 0x4) { newSearch.min.z() = float(newMin.z()); newSearch.max.z() = float(newMax.z()); } // Branch node - calculate next relative pointer return highFlags[0] * bool(nodeData & 0x1) + highFlags[1] * axisCounts[0] * bool(nodeData & 0x2) + highFlags[2] * axisCounts[0] * axisCounts[1] * bool(nodeData & 0x4); } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CPVSVisOctree.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/Graphics/CPVSVisSet.hpp" #include #include namespace metaforce { class CPVSVisOctree { zeus::CAABox x0_aabb; u32 x18_numObjects = 0; u32 x1c_numLights = 0; bool x20_bufferFlag = false; const u8* x24_octreeData = nullptr; zeus::CAABox x2c_searchAabb; public: static CPVSVisOctree MakePVSVisOctree(const u8* data); CPVSVisOctree() = default; CPVSVisOctree(const zeus::CAABox& aabb, u32 numObjects, u32 numLights, const u8* c); u32 GetNumChildren(u8 byte) const; u32 GetChildIndex(const u8*, const zeus::CVector3f&) const; const zeus::CAABox& GetBounds() const { return x0_aabb; } const u8* GetOctreeData() const { return x24_octreeData; } u32 GetNumObjects() const { return x18_numObjects; } u32 GetNumLights() const { return x1c_numLights; } void ResetSearch() const { const_cast(*this).x2c_searchAabb = x0_aabb; } s32 IterateSearch(u8 nodeData, const zeus::CVector3f& tp) const; }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CPVSVisSet.cpp ================================================ #include "Runtime/Graphics/CPVSVisSet.hpp" #include "Runtime/Graphics/CPVSVisOctree.hpp" namespace metaforce { void CPVSVisSet::Reset(EPVSVisSetState state) { x0_state = state; x4_numBits = 0; x8_numLights = 0; // xc_ = false; x10_ptr = nullptr; } EPVSVisSetState CPVSVisSet::GetVisible(u32 idx) const { if (x0_state != EPVSVisSetState::NodeFound) return x0_state; u32 numFeatures = x4_numBits - x8_numLights; if (idx < numFeatures) { /* This is a feature lookup */ if (!(x10_ptr[idx / 8] & (1 << (idx & 0x7)))) return EPVSVisSetState::EndOfTree; return EPVSVisSetState::OutOfBounds; } /* This is a light lookup */ u32 lightTest = idx - numFeatures + idx; const u8* ptr = &x10_ptr[lightTest / 8]; lightTest &= 0x7; if (lightTest != 0x7) return EPVSVisSetState((ptr[0] & (0x3 << lightTest)) >> lightTest); return EPVSVisSetState((ptr[0] >> 7) | ((ptr[1] & 0x1) << 1)); } void CPVSVisSet::SetFromMemory(u32 numBits, u32 numLights, const u8* leafPtr) { x0_state = EPVSVisSetState::NodeFound; x4_numBits = numBits; x8_numLights = numLights; x10_ptr = leafPtr; } void CPVSVisSet::SetTestPoint(const CPVSVisOctree& octree, const zeus::CVector3f& point) { if (!octree.GetBounds().pointInside(point)) { Reset(EPVSVisSetState::OutOfBounds); return; } const u8* octCur = octree.GetOctreeData(); octree.ResetSearch(); s32 nextNodeRel; u8 curNode; while ((nextNodeRel = octree.IterateSearch((curNode = *octCur++), point)) != -1) { if (nextNodeRel) { /* Skip node data */ if (!(curNode & 0x60)) { octCur += SBig(reinterpret_cast(octCur)[nextNodeRel - 1]); } else if (curNode & 0x20) { octCur += *(octCur + nextNodeRel - 1); } else { const u8* tmp = octCur + (nextNodeRel - 1) * 3; octCur += (tmp[0] << 16) + (tmp[1] << 8) + tmp[2]; } } /* Skip children data */ if (!(curNode & 0x60)) { octCur += (octree.GetNumChildren(curNode) - 1) * 2; } else if (curNode & 0x20) { octCur += octree.GetNumChildren(curNode) - 1; } else { octCur += (octree.GetNumChildren(curNode) - 1) * 3; } } /* Handle leaf type */ switch (curNode & 0x18) { case 0x18: SetFromMemory(octree.GetNumObjects(), octree.GetNumLights(), octCur); break; case 0x10: Reset(EPVSVisSetState::EndOfTree); break; case 0x08: default: Reset(EPVSVisSetState::OutOfBounds); break; } } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CPVSVisSet.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include namespace metaforce { class CPVSVisOctree; enum class EPVSVisSetState { EndOfTree, NodeFound, OutOfBounds }; class CPVSVisSet { EPVSVisSetState x0_state; u32 x4_numBits; u32 x8_numLights; // bool xc_; Used to be part of auto_ptr const u8* x10_ptr; public: void Reset(EPVSVisSetState state); EPVSVisSetState GetState() const { return x0_state; } EPVSVisSetState GetVisible(u32 idx) const; void SetFromMemory(u32 numBits, u32 numLights, const u8* leafPtr); void SetTestPoint(const CPVSVisOctree& octree, const zeus::CVector3f&); void SetState(EPVSVisSetState state) { x0_state = state; } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CRainSplashGenerator.cpp ================================================ #include "Runtime/Graphics/CRainSplashGenerator.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/World/CWorld.hpp" namespace metaforce { CRainSplashGenerator::CRainSplashGenerator(const zeus::CVector3f& scale, u32 maxSplashes, u32 genRate, float minZ, float alpha) : x14_scale(scale), x2c_minZ(minZ) { x30_alpha = std::min(1.f, alpha); x44_genRate = std::min(maxSplashes, genRate); x0_rainSplashes.reserve(maxSplashes); for (u32 i = 0; i < maxSplashes; ++i) x0_rainSplashes.emplace_back(); } void CRainSplashGenerator::SSplashLine::Draw(float alpha, float dt, const zeus::CVector3f& pos) { if (x0_t > 0.f) { float delta = dt * xc_speed; float vt = std::max(0.f, x0_t - delta * x15_length); auto vertCount = u32((x0_t - vt) / delta + 1.f); // m_renderer.Reset(); for (u32 i = 0; i < vertCount; ++i) { float vertAlpha = vt * alpha; zeus::CVector3f vec(vt * x4_xEnd, vt * x8_yEnd, -4.f * vt * (vt - 1.f) * x10_zParabolaHeight); vec += pos; vt += delta; // m_renderer.AddVertex(vec, zeus::CColor(1.f, vertAlpha), 1); } // m_renderer.Render(g_Renderer->IsThermalVisorHotPass()); } } void CRainSplashGenerator::SRainSplash::Draw(float alpha, float dt, const zeus::CVector3f& pos) { for (SSplashLine& line : x0_lines) { line.Draw(alpha, dt, pos); } } void CRainSplashGenerator::DoDraw(const zeus::CTransform& xf) { SCOPED_GRAPHICS_DEBUG_GROUP("CRainSplashGenerator::DoDraw", zeus::skYellow); CGraphics::SetModelMatrix(xf); if (x40_queueSize > 0) { if (x38_queueTail <= x3c_queueHead) { for (size_t i = x3c_queueHead; i < x0_rainSplashes.size(); ++i) { SRainSplash& splash = x0_rainSplashes[i]; splash.Draw(x30_alpha, x28_dt, splash.x64_pos); } for (size_t i = 0; i < x38_queueTail; ++i) { SRainSplash& splash = x0_rainSplashes[i]; splash.Draw(x30_alpha, x28_dt, splash.x64_pos); } } else { for (size_t i = x3c_queueHead; i < x38_queueTail; ++i) { SRainSplash& splash = x0_rainSplashes[i]; splash.Draw(x30_alpha, x28_dt, splash.x64_pos); } } } } void CRainSplashGenerator::Draw(const zeus::CTransform& xf) { if (!x48_25_raining) { return; } DoDraw(xf); } CRainSplashGenerator::SSplashLine::SSplashLine() {} CRainSplashGenerator::SRainSplash::SRainSplash() { for (size_t i = 0; i < x0_lines.capacity(); ++i) { x0_lines.emplace_back(); } } void CRainSplashGenerator::SSplashLine::Update(float dt, CStateManager& mgr) { if (!x16_active) return; if (x0_t <= 0.8f) { x14_ = u8(5.f * (1.f - x0_t) + 3.f * x0_t); x0_t += dt * xc_speed; } else if (x15_length != 0) { x15_length -= 1; } else { x16_active = false; xc_speed = mgr.GetActiveRandom()->Range(4.0f, 8.0f); x10_zParabolaHeight = mgr.GetActiveRandom()->Range(0.015625f, 0.03125f); x4_xEnd = mgr.GetActiveRandom()->Range(-0.125f, 0.125f); x8_yEnd = mgr.GetActiveRandom()->Range(-0.125f, 0.125f); x15_length = u8(mgr.GetActiveRandom()->Range(1, 2)); } } void CRainSplashGenerator::SRainSplash::Update(float dt, CStateManager& mgr) { for (SSplashLine& point : x0_lines) point.Update(dt, mgr); } bool CRainSplashGenerator::SRainSplash::IsActive() const { bool ret = false; for (const SSplashLine& line : x0_lines) ret |= line.x16_active; return ret; } void CRainSplashGenerator::UpdateRainSplashRange(CStateManager& mgr, int start, int end, float dt) { for (int i = start; i < end; ++i) { SRainSplash& set = x0_rainSplashes[i]; set.Update(dt, mgr); if (!set.IsActive()) { x40_queueSize -= 1; x3c_queueHead += 1; if (x3c_queueHead >= x0_rainSplashes.size()) x3c_queueHead = 0; } } } void CRainSplashGenerator::UpdateRainSplashes(CStateManager& mgr, float magnitude, float dt) { x20_generateTimer += dt; x24_generateInterval = 1.f / (70.f * magnitude); if (x40_queueSize > 0) { if (x38_queueTail <= x3c_queueHead) { UpdateRainSplashRange(mgr, x3c_queueHead, int(x0_rainSplashes.size()), dt); UpdateRainSplashRange(mgr, 0, x38_queueTail, dt); } else { UpdateRainSplashRange(mgr, x3c_queueHead, x38_queueTail, dt); } } } void CRainSplashGenerator::Update(float dt, CStateManager& mgr) { EEnvFxType neededFx = mgr.GetWorld()->GetNeededEnvFx(); x28_dt = dt; x48_25_raining = false; if (neededFx != EEnvFxType::None && mgr.GetEnvFxManager()->IsSplashActive() && mgr.GetEnvFxManager()->GetRainMagnitude() != 0.f && neededFx == EEnvFxType::Rain) { UpdateRainSplashes(mgr, mgr.GetEnvFxManager()->GetRainMagnitude(), dt); x48_25_raining = true; } } u32 CRainSplashGenerator::GetNextBestPt(u32 pt, const SSkinningWorkspace& workspace, CRandom16& rand, float minZ) { const auto& refVertA = workspace.m_vertexWorkspace[pt]; const zeus::CVector3f refVert{refVertA.x, refVertA.y, refVertA.z}; float maxDist = 0.f; u32 nextPt = pt; for (int i = 0; i < 3; ++i) { const auto idx = u32(rand.Range(0, int(workspace.m_vertexWorkspace.size() - 1))); const auto& vertA = workspace.m_vertexWorkspace[idx]; const zeus::CVector3f vert{vertA.x, vertA.y, vertA.z}; const auto& normA = workspace.m_normalWorkspace[idx]; const zeus::CVector3f norm{normA.x, normA.y, normA.z}; const float distSq = (refVert - vert).magSquared(); if (distSq > maxDist && norm.dot(zeus::skUp) >= 0.f && (vert.z() <= 0.f || vert.z() > minZ)) { nextPt = idx; maxDist = distSq; } } return nextPt; } void CRainSplashGenerator::SRainSplash::SetPoint(const zeus::CVector3f& pos) { for (SSplashLine& line : x0_lines) line.SetActive(); x64_pos = pos; } void CRainSplashGenerator::AddPoint(const zeus::CVector3f& pos) { if (x38_queueTail >= x0_rainSplashes.size()) x38_queueTail = 0; x0_rainSplashes[x38_queueTail].SetPoint(pos); x40_queueSize += 1; x38_queueTail += 1; } void CRainSplashGenerator::GeneratePoints(const SSkinningWorkspace& workspace) { if (!x48_25_raining) return; if (x20_generateTimer > x24_generateInterval) { for (u32 i = 0; i < x44_genRate; ++i) { if (x40_queueSize >= x0_rainSplashes.size()) break; x34_curPoint = GetNextBestPt(x34_curPoint, workspace, x10_random, x2c_minZ); const auto& vert = workspace.m_vertexWorkspace[x34_curPoint]; AddPoint(x14_scale * zeus::CVector3f{vert.x, vert.y, vert.z}); } x20_generateTimer = 0.f; } } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CRainSplashGenerator.hpp ================================================ #pragma once #include #include #include "Runtime/CRandom16.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" #include namespace metaforce { class CStateManager; class CRainSplashGenerator { struct SSplashLine { float x0_t = 0.f; float x4_xEnd = 0.f; float x8_yEnd = 0.f; float xc_speed = 4.f; float x10_zParabolaHeight = 0.015625f; u8 x14_ = 3; u8 x15_length = 1; bool x16_active = true; // used to be one-bit bitfield explicit SSplashLine(); void Update(float dt, CStateManager& mgr); void Draw(float alpha, float dt, const zeus::CVector3f& pos); void SetActive() { x16_active = true; } }; struct SRainSplash { rstl::reserved_vector x0_lines; zeus::CVector3f x64_pos; float x70_ = 0.f; explicit SRainSplash(); SRainSplash(const SRainSplash&) = delete; SRainSplash& operator=(const SRainSplash&) = delete; SRainSplash(SRainSplash&&) = default; SRainSplash& operator=(SRainSplash&&) = default; void Update(float dt, CStateManager& mgr); bool IsActive() const; void Draw(float alpha, float dt, const zeus::CVector3f& pos); void SetPoint(const zeus::CVector3f& pos); }; std::vector x0_rainSplashes; CRandom16 x10_random{99}; zeus::CVector3f x14_scale; float x20_generateTimer = 0.0f; float x24_generateInterval = 0.0f; float x28_dt = 0.0f; float x2c_minZ; float x30_alpha; u32 x34_curPoint = 0; u32 x38_queueTail = 0; u32 x3c_queueHead = 0; u32 x40_queueSize = 0; u32 x44_genRate; bool x48_24 : 1 = false; bool x48_25_raining : 1 = true; void UpdateRainSplashRange(CStateManager& mgr, int start, int end, float dt); void UpdateRainSplashes(CStateManager& mgr, float magnitude, float dt); void DoDraw(const zeus::CTransform& xf); static u32 GetNextBestPt(u32 pt, const SSkinningWorkspace& workspace, CRandom16& rand, float minZ); void AddPoint(const zeus::CVector3f& pos); public: CRainSplashGenerator(const zeus::CVector3f& scale, u32 maxSplashes, u32 genRate, float minZ, float alpha); void Update(float dt, CStateManager& mgr); void GeneratePoints(const SSkinningWorkspace& workspace); void Draw(const zeus::CTransform& xf); bool IsRaining() const { return x48_25_raining; } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CSimpleShadow.cpp ================================================ #include "Runtime/Graphics/CSimpleShadow.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Collision/CGameCollision.hpp" namespace metaforce { CSimpleShadow::CSimpleShadow(float scale, float userAlpha, float maxObjHeight, float displacement) : x30_scale(scale), x38_userAlpha(userAlpha), x40_maxObjHeight(maxObjHeight), x44_displacement(displacement) {} zeus::CAABox CSimpleShadow::GetMaxShadowBox(const zeus::CAABox& aabb) const { float extent = x34_radius * x30_scale; zeus::CVector3f center = aabb.center(); zeus::CAABox expandedAABB = aabb; expandedAABB.accumulateBounds({center.x() + extent, center.y() + extent, center.z() - GetMaxObjectHeight()}); expandedAABB.accumulateBounds({center.x() - extent, center.y() - extent, center.z() - GetMaxObjectHeight()}); return expandedAABB; } zeus::CAABox CSimpleShadow::GetBounds() const { float extent = x34_radius * x30_scale; return { {x0_xf.origin.x() - extent, x0_xf.origin.y() - extent, x0_xf.origin.z() - extent}, {x0_xf.origin.x() + extent, x0_xf.origin.y() + extent, x0_xf.origin.z() + extent}, }; } void CSimpleShadow::Render(TLockedToken& tex) { if (!x48_24_collision) return; SCOPED_GRAPHICS_DEBUG_GROUP("CSimpleShadow::Render", zeus::skGrey); CGraphics::DisableAllLights(); CGraphics::SetModelMatrix(x0_xf); tex->Load(GX_TEXMAP0, EClampMode::Repeat); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0); CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false); CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); CGraphics::StreamBegin(ERglPrimitive::Quads); float radius = x34_radius * x30_scale; CGraphics::StreamColor(zeus::CColor{1.f, x3c_heightAlpha * x38_userAlpha}); CGraphics::StreamTexcoord(0.f, 0.f); CGraphics::StreamVertex(-radius, 0.f, -radius); CGraphics::StreamTexcoord(0.f, 1.f); CGraphics::StreamVertex(radius, 0.f, -radius); CGraphics::StreamTexcoord(1.f, 1.f); CGraphics::StreamVertex(radius, 0.f, radius); CGraphics::StreamTexcoord(1.f, 0.f); CGraphics::StreamVertex(-radius, 0.f, radius); CGraphics::StreamEnd(); } void CSimpleShadow::Calculate(const zeus::CAABox& aabb, const zeus::CTransform& xf, const CStateManager& mgr) { x48_24_collision = false; float halfHeight = (aabb.max.z() - aabb.min.z()) * 0.5f; zeus::CVector3f pos = xf.origin + zeus::CVector3f(0.f, 0.f, halfHeight); CRayCastResult res = mgr.RayStaticIntersection(pos, zeus::skDown, x40_maxObjHeight, CMaterialFilter::MakeExclude({EMaterialTypes::SeeThrough})); float height = x40_maxObjHeight; if (res.IsValid()) { x48_24_collision = true; height = res.GetT(); } if (height > 0.1f + halfHeight) { EntityList nearList; mgr.BuildNearList(nearList, pos, zeus::skDown, x40_maxObjHeight, CMaterialFilter::MakeInclude(CMaterialList(EMaterialTypes::Floor)), nullptr); TUniqueId cid = kInvalidUniqueId; CRayCastResult resD = CGameCollision::RayDynamicIntersection(mgr, cid, pos, zeus::skDown, x40_maxObjHeight, CMaterialFilter::skPassEverything, nearList); if (resD.IsValid() && resD.GetT() < height) { x48_24_collision = true; height = resD.GetT(); res = resD; } } if (x48_24_collision) { x3c_heightAlpha = 1.f - height / x40_maxObjHeight; x0_xf = zeus::lookAt(res.GetPlane().normal(), zeus::skZero3f); x0_xf.origin = res.GetPlane().normal() * x44_displacement + res.GetPoint(); if (x48_25_alwaysCalculateRadius || !x48_26_radiusCalculated) { float xExtent = aabb.max.x() - aabb.min.x(); float yExtent = aabb.max.y() - aabb.min.y(); x34_radius = std::sqrt(xExtent * xExtent + yExtent * yExtent) * 0.5f; x48_26_radiusCalculated = true; } } } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CSimpleShadow.hpp ================================================ #pragma once #include "Runtime/CToken.hpp" #include "Runtime/Graphics/CTexture.hpp" #include #include #include namespace metaforce { class CTexture; class CStateManager; class CSimpleShadow { zeus::CTransform x0_xf; float x30_scale; float x34_radius = 1.f; float x38_userAlpha; float x3c_heightAlpha = 1.f; float x40_maxObjHeight; float x44_displacement; bool x48_24_collision : 1 = false; bool x48_25_alwaysCalculateRadius : 1 = true; bool x48_26_radiusCalculated : 1 = false; public: CSimpleShadow(float scale, float userAlpha, float maxObjHeight, float displacement); bool Valid() const { return x48_24_collision; } zeus::CAABox GetMaxShadowBox(const zeus::CAABox& aabb) const; zeus::CAABox GetBounds() const; void SetAlwaysCalculateRadius(bool b) { x48_25_alwaysCalculateRadius = b; } float GetMaxObjectHeight() const { return x40_maxObjHeight; } void SetUserAlpha(float a) { x38_userAlpha = a; } const zeus::CTransform& GetTransform() const { return x0_xf; } void Render(TLockedToken& tex); void Calculate(const zeus::CAABox& aabb, const zeus::CTransform& xf, const CStateManager& mgr); }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CSkinnedModel.cpp ================================================ #include "Runtime/Graphics/CSkinnedModel.hpp" #include "Runtime/Character/CSkinRules.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Graphics/CVertexMorphEffect.hpp" #include "Runtime/Logging.hpp" #include namespace metaforce { CSkinnedModel::CSkinnedModel(const TLockedToken& model, const TLockedToken& skinRules, const TLockedToken& layoutInfo) : x4_model(std::move(model)) , x10_skinRules(std::move(skinRules)) , x1c_layoutInfo(std::move(layoutInfo)) , m_workspace(*x10_skinRules) { if (!x4_model) { spdlog::fatal("bad model token provided to CSkinnedModel"); } if (!x10_skinRules) { spdlog::fatal("bad skin token provided to CSkinnedModel"); } if (!x1c_layoutInfo) { spdlog::fatal("bad character layout token provided to CSkinnedModel"); } } CSkinnedModel::CSkinnedModel(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo) : CSkinnedModel(store.GetObj(SObjectTag{FOURCC('CMDL'), model}), store.GetObj(SObjectTag{FOURCC('CSKR'), skinRules}), store.GetObj(SObjectTag{FOURCC('CINF'), layoutInfo})) {} void CSkinnedModel::AllocateStorage() { if (x34_owned) { m_workspace.Reset(*x10_skinRules); } } void CSkinnedModel::Calculate(const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals, SSkinningWorkspace* workspace) { if (workspace == nullptr) { if (x35_disableWorkspaces) { x10_skinRules->BuildAccumulatedTransforms(pose, *x1c_layoutInfo); return; } AllocateStorage(); workspace = &m_workspace; } else { workspace->Reset(*x10_skinRules); } x10_skinRules->BuildAccumulatedTransforms(pose, *x1c_layoutInfo); x10_skinRules->BuildPoints(x4_model->GetPositions(), &workspace->m_vertexWorkspace); x10_skinRules->BuildNormals(x4_model->GetNormals(), &workspace->m_normalWorkspace); if (morphEffect) { morphEffect->MorphVertices(*workspace, averagedNormals, x10_skinRules, pose, x10_skinRules->GetVertexCount()); } if (g_PointGenFunc != nullptr) { g_PointGenFunc(*workspace); } } void CSkinnedModel::Draw(TConstVectorRef verts, TConstVectorRef norms, const CModelFlags& drawFlags) { //OPTICK_EVENT(); x4_model->Draw(verts, norms, drawFlags); // PostDrawFunc(); } void CSkinnedModel::Draw(const CModelFlags& drawFlags) { if (x35_disableWorkspaces) { const auto mtx = CGraphics::mModelMatrix; CGraphics::SetModelMatrix(mtx * x10_skinRules->x0_bones.front().x20_xf); x4_model->Draw(drawFlags); CGraphics::SetModelMatrix(mtx); } else if (m_workspace.IsEmpty()) { x4_model->Draw(drawFlags); } else { x4_model->Draw(m_workspace.m_vertexWorkspace, m_workspace.m_normalWorkspace, drawFlags); // PostDrawFunc(); } } void CSkinnedModel::DoDrawCallback(const FCustomDraw& func) const { if (x35_disableWorkspaces) { const auto mtx = CGraphics::mModelMatrix; CGraphics::SetModelMatrix(mtx * x10_skinRules->x0_bones.front().x20_xf); func(x4_model->GetPositions(), x4_model->GetNormals()); CGraphics::SetModelMatrix(mtx); } else if (m_workspace.IsEmpty()) { func(x4_model->GetPositions(), x4_model->GetNormals()); } else { func(m_workspace.m_vertexWorkspace, m_workspace.m_normalWorkspace); // PostDrawFunc(); } } void CSkinnedModel::CalculateDefault() { m_workspace.Clear(); } SSkinningWorkspace CSkinnedModel::CloneWorkspace() { return m_workspace; } CSkinnedModelWithAvgNormals::CSkinnedModelWithAvgNormals(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo) : CSkinnedModel(store, model, skinRules, layoutInfo) { const auto vertexCount = GetSkinRules()->GetVertexCount(); const auto modelPositions = GetModel()->GetPositions(); x40_averagedNormals.resize(vertexCount); std::vector>> vertMap; for (int vertIdx = 0; vertIdx < vertexCount; ++vertIdx) { const auto curPosV = modelPositions[vertIdx]; const zeus::CVector3f curPos{curPosV.x, curPosV.y, curPosV.z}; if (std::find_if(vertMap.cbegin(), vertMap.cend(), [=](const auto& pair) { return pair.first.isEqu(curPos); }) == vertMap.cend()) { auto& [_, list] = vertMap.emplace_back(curPos, std::list{}); for (int idx = vertIdx; idx < vertexCount; ++idx) { // Originally uses ==, but adjusted to match above const auto& mpv = modelPositions[idx]; const zeus::CVector3f mpz{mpv.x, mpv.y, mpv.z}; if (mpz.isEqu(curPos)) { list.emplace_back(idx); } } } } const auto& modelNormals = GetModel()->GetNormals(); for (const auto& [_, idxs] : vertMap) { zeus::CVector3f averagedNormal; for (const auto idx : idxs) { const auto& mnv = modelNormals[idx]; averagedNormal += zeus::CVector3f{mnv.x, mnv.y, mnv.z}; } averagedNormal.normalize(); for (const auto idx : idxs) { x40_averagedNormals[idx] = {averagedNormal.x(), averagedNormal.y(), averagedNormal.z()}; } } } FPointGenerator CSkinnedModel::g_PointGenFunc; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CSkinnedModel.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/CToken.hpp" #include "Runtime/Character/CSkinRules.hpp" #include "Runtime/Graphics/CModel.hpp" #include namespace metaforce { class CCharLayoutInfo; class CModel; class CPoseAsTransforms; class CVertexMorphEffect; class IObjectStore; // Originally vert + normal workspaces were allocated together, but here separated for ease of use struct SSkinningWorkspace { std::vector> m_vertexWorkspace; std::vector> m_normalWorkspace; SSkinningWorkspace(const CSkinRules& rules) { Reset(rules); } void Reset(const CSkinRules& rules) { m_vertexWorkspace.clear(); m_normalWorkspace.clear(); m_vertexWorkspace.reserve(rules.GetVertexCount()); m_normalWorkspace.reserve(rules.GetNormalCount()); } void Clear() { m_vertexWorkspace.clear(); m_normalWorkspace.clear(); } [[nodiscard]] bool IsEmpty() const { return m_vertexWorkspace.empty() || m_normalWorkspace.empty(); } }; // Lambda instead of userdata pointer using FCustomDraw = std::function; using FPointGenerator = std::function; class CSkinnedModel { TLockedToken x4_model; TLockedToken x10_skinRules; TLockedToken x1c_layoutInfo; // rstl::auto_ptr x24_vertWorkspace; // rstl::auto_ptr x2c_normalWorkspace; SSkinningWorkspace m_workspace; bool x34_owned = true; bool x35_disableWorkspaces = false; public: enum class EDataOwnership { Unowned, Owned }; CSkinnedModel(const TLockedToken& model, const TLockedToken& skinRules, const TLockedToken& layoutInfo /*, EDataOwnership ownership*/); CSkinnedModel(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo); virtual ~CSkinnedModel() = default; TLockedToken& GetModel() { return x4_model; } const TLockedToken& GetModel() const { return x4_model; } const TLockedToken& GetSkinRules() const { return x10_skinRules; } void SetLayoutInfo(const TLockedToken& inf) { x1c_layoutInfo = inf; } const TLockedToken& GetLayoutInfo() const { return x1c_layoutInfo; } void AllocateStorage(); // Metaforce addition: Originally morphEffect is rstl::optional_object* // This prevents constructing it as a reference to the held pointer in CPatterned, thus in // retail it's copied in every invocation of RenderIceModelWithFlags. void Calculate(const CPoseAsTransforms& pose, CVertexMorphEffect* morphEffect, TConstVectorRef averagedNormals, SSkinningWorkspace* workspace); void Draw(TConstVectorRef verts, TConstVectorRef normals, const CModelFlags& drawFlags); void Draw(const CModelFlags& drawFlags); void DoDrawCallback(const FCustomDraw& func) const; void CalculateDefault(); // Originally returns cloned vertex workspace, with arg for cloned normal workspace SSkinningWorkspace CloneWorkspace(); static void SetPointGeneratorFunc(FPointGenerator func) { g_PointGenFunc = std::move(func); } static void ClearPointGeneratorFunc() { g_PointGenFunc = nullptr; } static FPointGenerator g_PointGenFunc; }; class CSkinnedModelWithAvgNormals : public CSkinnedModel { std::vector> x40_averagedNormals; // was rstl::auto_ptr public: CSkinnedModelWithAvgNormals(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo); ~CSkinnedModelWithAvgNormals() override = default; TConstVectorRef GetAveragedNormals() const { return x40_averagedNormals; } }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/CTevCombiners.cpp ================================================ #include "Graphics/CTevCombiners.hpp" #include "Graphics/CGX.hpp" namespace metaforce::CTevCombiners { u32 CTevPass::sNextUniquePass = 0; void CTevPass::Execute(ERglTevStage stage) const { const auto stageId = GXTevStageID(stage); CGX::SetTevColorIn(stageId, x4_colorPass.x0_a, x4_colorPass.x4_b, x4_colorPass.x8_c, x4_colorPass.xc_d); CGX::SetTevAlphaIn(stageId, x14_alphaPass.x0_a, x14_alphaPass.x4_b, x14_alphaPass.x8_c, x14_alphaPass.xc_d); CGX::SetTevColorOp(stageId, x24_colorOp.x4_op, x24_colorOp.x8_bias, x24_colorOp.xc_scale, x24_colorOp.x0_clamp, x24_colorOp.x10_regId); CGX::SetTevAlphaOp(stageId, x38_alphaOp.x4_op, x38_alphaOp.x8_bias, x38_alphaOp.xc_scale, x38_alphaOp.x0_clamp, x38_alphaOp.x10_regId); CGX::SetTevKColorSel(stageId, GX_TEV_KCSEL_8_8); CGX::SetTevKAlphaSel(stageId, GX_TEV_KASEL_8_8); } constexpr u32 maxTevPasses = 2; static u32 sNumEnabledPasses; static std::array sValidPasses; const CTevPass kEnvPassthru{ {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC}, {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA}, }; const CTevPass kEnvBlendCTandCConCF{ {GX_CC_C0, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO}, {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA}, }; const CTevPass kEnvModulateConstColor{ {GX_CC_ZERO, GX_CC_RASC, GX_CC_C0, GX_CC_ZERO}, {GX_CA_ZERO, GX_CA_RASA, GX_CA_A0, GX_CA_ZERO}, }; const CTevPass kEnvConstColor{ {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0}, {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0}, }; const CTevPass kEnvModulate{ {GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO}, {GX_CA_ZERO, GX_CA_RASA, GX_CA_TEXA, GX_CA_ZERO}, }; const CTevPass kEnvDecal{ {GX_CC_RASC, GX_CC_TEXC, GX_CC_TEXA, GX_CC_ZERO}, {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA}, }; const CTevPass kEnvBlend{ {GX_CC_RASC, GX_CC_ONE, GX_CC_TEXC, GX_CC_ZERO}, {GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO}, }; const CTevPass kEnvReplace{ {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC}, {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA}, }; const CTevPass kEnvModulateAlpha{ {GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC}, {GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO}, }; const CTevPass kEnvModulateColor{ {GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO}, {GX_CA_ZERO, GX_CA_KONST, GX_CA_RASA, GX_CA_ZERO}, }; const CTevPass kEnvModulateColorByAlpha{ {GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO}, {GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV}, }; void Init() { sNumEnabledPasses = maxTevPasses; sValidPasses.fill(true); for (int i = 0; i < maxTevPasses; ++i) { DeletePass(static_cast(i)); } sValidPasses.fill(false); RecomputePasses(); } void SetupPass(ERglTevStage stage, const CTevPass& pass) { if (pass == kEnvPassthru) { DeletePass(stage); return; } if (SetPassCombiners(stage, pass)) { sValidPasses[static_cast(stage)] = true; RecomputePasses(); } } void DeletePass(ERglTevStage stage) { SetPassCombiners(stage, kEnvPassthru); sValidPasses[static_cast(stage)] = false; RecomputePasses(); } bool SetPassCombiners(ERglTevStage stage, const CTevPass& pass) { pass.Execute(stage); return true; } void RecomputePasses() { u8 tmp = static_cast((sValidPasses[maxTevPasses - 1] != 0)); tmp++; sNumEnabledPasses = tmp; CGX::SetNumTevStages(tmp); } void ResetStates() { sValidPasses.fill(false); kEnvPassthru.Execute(ERglTevStage::Stage0); sNumEnabledPasses = 1; CGX::SetNumTevStages(1); } } // namespace metaforce::CTevCombiners ================================================ FILE: Runtime/Graphics/CTevCombiners.hpp ================================================ #pragma once #include "Graphics/GX.hpp" #include "RetroTypes.hpp" #include namespace metaforce { enum class ERglTevStage : std::underlying_type_t { Stage0 = GX_TEVSTAGE0, Stage1 = GX_TEVSTAGE1, Stage2 = GX_TEVSTAGE2, Stage3 = GX_TEVSTAGE3, Stage4 = GX_TEVSTAGE4, Stage5 = GX_TEVSTAGE5, Stage6 = GX_TEVSTAGE6, Stage7 = GX_TEVSTAGE7, Stage8 = GX_TEVSTAGE8, Stage9 = GX_TEVSTAGE9, Stage10 = GX_TEVSTAGE10, Stage11 = GX_TEVSTAGE11, Stage12 = GX_TEVSTAGE12, Stage13 = GX_TEVSTAGE13, Stage14 = GX_TEVSTAGE14, Stage15 = GX_TEVSTAGE15, Max = GX_MAX_TEVSTAGE, }; namespace CTevCombiners { struct CTevOp { bool x0_clamp = true; GXTevOp x4_op = GX_TEV_ADD; GXTevBias x8_bias = GX_TB_ZERO; GXTevScale xc_scale = GX_CS_SCALE_1; GXTevRegID x10_regId = GX_TEVPREV; constexpr CTevOp() = default; constexpr CTevOp(bool clamp, GXTevOp op, GXTevBias bias, GXTevScale scale, GXTevRegID regId) : x0_clamp(clamp), x4_op(op), x8_bias(bias), xc_scale(scale), x10_regId(regId) {} constexpr CTevOp(u32 compressedDesc) : x0_clamp((compressedDesc >> 8 & 1) != 0) , x4_op(static_cast(compressedDesc & 0xF)) , x8_bias(static_cast(compressedDesc >> 4 & 3)) , xc_scale(static_cast(compressedDesc >> 6 & 3)) , x10_regId(static_cast(compressedDesc >> 9 & 3)) {} bool operator==(const CTevOp& rhs) const { return x0_clamp == rhs.x0_clamp && x4_op == rhs.x4_op && x8_bias == rhs.x8_bias && xc_scale == rhs.xc_scale; } }; struct ColorPass { GXTevColorArg x0_a; GXTevColorArg x4_b; GXTevColorArg x8_c; GXTevColorArg xc_d; constexpr ColorPass(GXTevColorArg a, GXTevColorArg b, GXTevColorArg c, GXTevColorArg d) : x0_a(a), x4_b(b), x8_c(c), xc_d(d) {} constexpr ColorPass(u32 compressedDesc) : x0_a(static_cast(compressedDesc & 0x1F)) , x4_b(static_cast(compressedDesc >> 5 & 0x1F)) , x8_c(static_cast(compressedDesc >> 10 & 0x1F)) , xc_d(static_cast(compressedDesc >> 15 & 0x1F)) {} bool operator==(const ColorPass& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; } }; struct AlphaPass { GXTevAlphaArg x0_a; GXTevAlphaArg x4_b; GXTevAlphaArg x8_c; GXTevAlphaArg xc_d; constexpr AlphaPass(GXTevAlphaArg a, GXTevAlphaArg b, GXTevAlphaArg c, GXTevAlphaArg d) : x0_a(a), x4_b(b), x8_c(c), xc_d(d) {} constexpr AlphaPass(u32 compressedDesc) : x0_a(static_cast(compressedDesc & 0x1F)) , x4_b(static_cast(compressedDesc >> 5 & 0x1F)) , x8_c(static_cast(compressedDesc >> 10 & 0x1F)) , xc_d(static_cast(compressedDesc >> 15 & 0x1F)) {} bool operator==(const AlphaPass& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; } }; class CTevPass { u32 x0_id; ColorPass x4_colorPass; AlphaPass x14_alphaPass; CTevOp x24_colorOp; CTevOp x38_alphaOp; static u32 sNextUniquePass; public: CTevPass(const ColorPass& colPass, const AlphaPass& alphaPass, const CTevOp& colorOp = {}, const CTevOp& alphaOp = {}) : x0_id(++sNextUniquePass) , x4_colorPass(colPass) , x14_alphaPass(alphaPass) , x24_colorOp(colorOp) , x38_alphaOp(alphaOp) {} void Execute(ERglTevStage stage) const; bool operator==(const CTevPass& rhs) const { return x0_id == rhs.x0_id && x4_colorPass == rhs.x4_colorPass && x14_alphaPass == rhs.x14_alphaPass && x24_colorOp == rhs.x24_colorOp && x38_alphaOp == rhs.x38_alphaOp; } }; extern const CTevPass kEnvPassthru; // TODO move below to CGraphics extern const CTevPass kEnvBlendCTandCConCF; extern const CTevPass kEnvModulateConstColor; extern const CTevPass kEnvConstColor; extern const CTevPass kEnvModulate; extern const CTevPass kEnvDecal; extern const CTevPass kEnvBlend; extern const CTevPass kEnvReplace; extern const CTevPass kEnvModulateAlpha; extern const CTevPass kEnvModulateColor; extern const CTevPass kEnvModulateColorByAlpha; void Init(); void SetupPass(ERglTevStage stage, const CTevPass& pass); void DeletePass(ERglTevStage stage); bool SetPassCombiners(ERglTevStage stage, const CTevPass& pass); void RecomputePasses(); void ResetStates(); } // namespace CTevCombiners } // namespace metaforce ================================================ FILE: Runtime/Graphics/CTexture.cpp ================================================ #include "Graphics/CTexture.hpp" #include "Runtime/CToken.hpp" #include "Runtime/Formatting.hpp" #include "Runtime/CBasics.hpp" #include #include namespace metaforce { static std::array sLoadedTextures{}; CTexture::CTexture(ETexelFormat fmt, u16 w, u16 h, s32 mips, std::string_view label) : x0_fmt(fmt) , x4_w(w) , x6_h(h) , x8_mips(mips) , x9_bitsPerPixel(TexelFormatBitsPerPixel(fmt)) , x64_frameAllocated(sCurrentFrameCount) , m_label(fmt::format("{} ({})", label, magic_enum::enum_name(fmt))) { InitBitmapBuffers(fmt, w, h, mips); InitTextureObjs(); } CTexture::CTexture(CInputStream& in, std::string_view label, EAutoMipmap automip, EBlackKey blackKey) : x0_fmt(static_cast(in.ReadLong())) , x4_w(in.ReadShort()) , x6_h(in.ReadShort()) , x8_mips(in.ReadLong()) , x64_frameAllocated(sCurrentFrameCount) , m_label(fmt::format("{} ({})", label, magic_enum::enum_name(x0_fmt))) { bool hasPalette = (x0_fmt == ETexelFormat::C4 || x0_fmt == ETexelFormat::C8 || x0_fmt == ETexelFormat::C14X2); if (hasPalette) { x10_graphicsPalette = std::make_unique(in); xa_25_canLoadPalette = true; } x9_bitsPerPixel = TexelFormatBitsPerPixel(x0_fmt); InitBitmapBuffers(x0_fmt, x4_w, x6_h, x8_mips); u32 bufLen = 0; if (x8_mips > 0) { for (u32 i = 0; i < x8_mips; ++i) { u32 curMip = i & 63; const u32 width = ROUND_UP_4(x4_w >> curMip); const u32 height = ROUND_UP_4(x6_h >> curMip); bufLen += (width * height * x9_bitsPerPixel) / 8; } } for (u32 i = 0, len = 0; i < bufLen; i += len) { len = bufLen - i; if (len > 256) { len = 256; } auto image_ptr = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get(); in.Get(image_ptr + i, len); // DCFlushRangeNoSync(x44_aramToken_x4_buff.get() + i, ROUND_UP_32(len)); } if (sMangleMips) { for (u32 i = 1; i < x8_mips; ++i) { MangleMipmap(i); } } InitTextureObjs(); } CTexture::~CTexture() = default; u8* CTexture::Lock() { xa_24_locked = true; return GetBitMapData(0); } void CTexture::UnLock() { xa_24_locked = false; CountMemory(); // DCFlushRange(x44_aramToken.GetMRAMSafe(), ROUND_UP_32(xc_memoryAllocated)); // Aurora change: track when texture data needs to be invalidated m_needsTexObjDataLoad = true; } void CTexture::Load(GXTexMapID id, EClampMode clamp) { if (sLoadedTextures[id] != this || xa_29_canLoadObj) { auto* data = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get(); CountMemory(); if (HasPalette()) { x10_graphicsPalette->Load(); xa_25_canLoadPalette = false; } xa_29_canLoadObj = false; if (x40_clampMode != clamp) { x40_clampMode = !xa_26_isPowerOfTwo ? EClampMode::Clamp : clamp; GXInitTexObjWrapMode(&x20_texObj, static_cast(x40_clampMode), static_cast(x40_clampMode)); } // Aurora change: track when texture data needs to be invalidated if (m_needsTexObjDataLoad) { GXInitTexObjData(&x20_texObj, data); m_needsTexObjDataLoad = false; } GXLoadTexObj(&x20_texObj, id); sLoadedTextures[id] = this; x64_frameAllocated = sCurrentFrameCount; } } void CTexture::LoadMipLevel(s32 mip, GXTexMapID id, EClampMode clamp) { auto* image_ptr = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get(); u32 width = x4_w; u32 height = x6_h; u32 iVar15 = 0; u32 offset = 0; if (mip > 0) { for (u32 i = 0; i < mip; ++i) { offset += ROUND_UP_32((x9_bitsPerPixel * (ROUND_UP_4(width) * ROUND_UP_4(height))) / 8); width /= 2; height /= 2; } } TGXTexObj texObj; const auto wrap = static_cast(clamp); GXInitTexObj(&texObj, image_ptr + offset, width, height, static_cast(x18_gxFormat), wrap, wrap, false); GXInitTexObjLOD(&texObj, GX_LINEAR, GX_LINEAR, 0.f, 0.f, 0.f, false, false, GX_ANISO_1); if (HasPalette()) { x10_graphicsPalette->Load(); xa_25_canLoadPalette = false; } GXLoadTexObj(&texObj, id); x64_frameAllocated = sCurrentFrameCount; sLoadedTextures[id] = nullptr; } void CTexture::MakeSwappable() { if (!xa_27_noSwap) { return; } xa_27_noSwap = false; } const u8* CTexture::GetConstBitMapData(s32 mip) const { u32 buffOffset = 0; if (x8_mips > mip) { for (u32 i = 0; i < mip; ++i) { buffOffset += (x9_bitsPerPixel >> 3) * (x4_w >> (i & 0x3f)) * (x6_h >> (i & 0x3f)); } } return x44_aramToken_x4_buff.get() + buffOffset; /* x44_aramToken.GetMRAMSafe() + buffOffset*/ } u8* CTexture::GetBitMapData(s32 mip) const { return const_cast(GetConstBitMapData(mip)); } void CTexture::InitBitmapBuffers(ETexelFormat fmt, u16 width, u16 height, s32 mips) { switch (fmt) { case ETexelFormat::I4: x18_gxFormat = GX_TF_I4; break; case ETexelFormat::I8: x18_gxFormat = GX_TF_I8; break; case ETexelFormat::IA4: x18_gxFormat = GX_TF_IA4; break; case ETexelFormat::IA8: x18_gxFormat = GX_TF_IA8; break; case ETexelFormat::C4: x1c_gxCIFormat = GX_TF_C4; break; case ETexelFormat::C8: x1c_gxCIFormat = GX_TF_C8; break; case ETexelFormat::C14X2: x1c_gxCIFormat = GX_TF_C14X2; break; case ETexelFormat::RGB565: x18_gxFormat = GX_TF_RGB565; break; case ETexelFormat::RGB5A3: x18_gxFormat = GX_TF_RGB5A3; break; case ETexelFormat::RGBA8: x18_gxFormat = GX_TF_RGBA8; break; case ETexelFormat::CMPR: x18_gxFormat = GX_TF_CMPR; break; // Metaforce additions case ETexelFormat::R8PC: x18_gxFormat = GX_TF_R8_PC; break; case ETexelFormat::RGBA8PC: x18_gxFormat = GX_TF_RGBA8_PC; break; default: break; } u32 format = (x0_fmt == ETexelFormat::C4 || x0_fmt == ETexelFormat::C8 || x0_fmt == ETexelFormat::C14X2) ? u32(x1c_gxCIFormat) : x18_gxFormat; xc_memoryAllocated = GXGetTexBufferSize(width, height, format, mips > 1, mips > 1 ? 11 : 0); x44_aramToken_x4_buff = std::make_unique(xc_memoryAllocated); /*x44_aramToken.PostConstruct(buf, xc_memoryAllocated, 1);*/ CountMemory(); } void CTexture::InitTextureObjs() { xa_26_isPowerOfTwo = zeus::floorPowerOfTwo(x4_w) == x4_w && zeus::floorPowerOfTwo(x6_h) == x6_h; if (!xa_26_isPowerOfTwo) { x40_clampMode = EClampMode::Clamp; } CountMemory(); if (IsCITexture()) { GXInitTexObjCI(&x20_texObj, x44_aramToken_x4_buff.get(), x4_w, x6_h, x1c_gxCIFormat, static_cast(x40_clampMode), static_cast(x40_clampMode), x8_mips > 1, 0); } else { GXInitTexObj(&x20_texObj, x44_aramToken_x4_buff.get(), x4_w, x6_h, static_cast(x18_gxFormat), static_cast(x40_clampMode), static_cast(x40_clampMode), x8_mips > 1); GXInitTexObjLOD(&x20_texObj, x8_mips > 1 ? GX_LIN_MIP_LIN : GX_LINEAR, GX_LINEAR, 0.f, static_cast(x8_mips) - 1.f, 0.f, false, false, x8_mips > 1 ? GX_ANISO_4 : GX_ANISO_1); } xa_29_canLoadObj = true; } void CTexture::CountMemory() { if (xa_28_counted) { return; } xa_28_counted = true; sTotalAllocatedMemory += xc_memoryAllocated; } void CTexture::UncountMemory() { if (!xa_28_counted) { return; } xa_28_counted = false; sTotalAllocatedMemory -= xc_memoryAllocated; } void CTexture::MangleMipmap(u32 mip) { if (mip >= x8_mips) { return; } constexpr u32 colors[4] = { 0x000000FF, 0x0000FF00, 0x00FF0000, 0x0000FFFF, }; const u32 color = colors[(mip - 1) & 3]; const u16 rgb565Color = ((color >> 3) & 0x001F) | // B ((color >> 5) & 0x07E0) | // G ((color >> 8) & 0xF800); // R const u16 rgb555Color = ((color >> 3) & 0x001F) | // B ((color >> 6) & 0x03E0) | // G ((color >> 9) & 0x7C00); // R const u16 rgb4Color = ((color >> 4) & 0x000F) | // B ((color >> 8) & 0x00F0) | // G ((color >> 12) & 0x0F00); // R int width = GetWidth(); int height = GetHeight(); int offset = 0; for (int i = 0; i < mip; i++) { offset += width * height; width /= 2; height /= 2; } switch (GetTexelFormat()) { case ETexelFormat::RGB565: { const auto ptr = reinterpret_cast(x44_aramToken_x4_buff.get()); // mARAMToken.GetMRAMSafe()); for (int i = 0; i < width * height; ++i) { ptr[i + offset] = rgb565Color; CBasics::Swap2Bytes(reinterpret_cast(&ptr[i + offset])); } break; } case ETexelFormat::CMPR: { auto ptr = reinterpret_cast(x44_aramToken_x4_buff.get()) + offset / 4; for (int i = 0; i < width * height / 16; ++i, ptr += 4) { ptr[0] = rgb565Color; CBasics::Swap2Bytes(reinterpret_cast(&ptr[0])); ptr[1] = rgb565Color; CBasics::Swap2Bytes(reinterpret_cast(&ptr[1])); ptr[2] = 0; ptr[3] = 0; } break; } case ETexelFormat::RGB5A3: { const auto ptr = reinterpret_cast(x44_aramToken_x4_buff.get()); for (int i = 0; i < width * height; ++i) { u16& val = ptr[i + offset]; CBasics::Swap2Bytes(reinterpret_cast(&val)); if ((val & 0x8000) != 0) { val = rgb555Color | 0x8000; } else { val = (val & 0xF000) | rgb4Color; } CBasics::Swap2Bytes(reinterpret_cast(&val)); } break; } default: break; } } u32 CTexture::TexelFormatBitsPerPixel(ETexelFormat fmt) { switch (fmt) { case ETexelFormat::I4: case ETexelFormat::C4: case ETexelFormat::CMPR: return 4; case ETexelFormat::I8: case ETexelFormat::IA4: case ETexelFormat::C8: return 8; case ETexelFormat::IA8: case ETexelFormat::C14X2: case ETexelFormat::RGB565: case ETexelFormat::RGB5A3: return 16; case ETexelFormat::RGBA8: return 32; default: return 0; } } bool CTexture::sMangleMips = false; u32 CTexture::sCurrentFrameCount = 0; u32 CTexture::sTotalAllocatedMemory = 0; void CTexture::InvalidateTexMap(GXTexMapID id) { sLoadedTextures[id] = nullptr; } CFactoryFnReturn FTextureFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef) { const auto label = fmt::format("{} {}", tag.type, tag.id); return TToken::GetIObjObjectFor(std::make_unique(in, label)); } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CTexture.hpp ================================================ #pragma once #include "Runtime/CFactoryMgr.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/Graphics/CGraphicsPalette.hpp" #include "Runtime/Graphics/GX.hpp" #include "Runtime/IObj.hpp" #include "Runtime/Streams/CInputStream.hpp" #include "GX.hpp" namespace metaforce { enum class ETexelFormat { Invalid = -1, I4 = 0, I8 = 1, IA4 = 2, IA8 = 3, C4 = 4, C8 = 5, C14X2 = 6, RGB565 = 7, RGB5A3 = 8, RGBA8 = 9, CMPR = 10, // Metaforce addition: non-converting formats RGBA8PC = 11, R8PC = 12, }; enum class EClampMode : std::underlying_type_t { Clamp = GX_CLAMP, Repeat = GX_REPEAT, Mirror = GX_MIRROR, }; class CTexture { class CDumpedBitmapDataReloader { int x0_; u32 x4_; int x8_; u32 xc_; bool x10_; int x14_; void* x18_; }; public: enum class EAutoMipmap { Zero, One, }; enum class EBlackKey { Zero, One }; enum class EFontType { None = -1, OneLayer = 0, /* Fill bit0 */ OneLayerOutline = 1, /* Fill bit0, Outline bit1 */ FourLayers = 2, TwoLayersOutlines = 3, /* Fill bit0/2, Outline bit1/3 */ TwoLayers = 4, /* Fill bit0/1 and copied to bit2/3 */ TwoLayersOutlines2 = 8 /* Fill bit2/3, Outline bit0/1 */ }; private: static bool sMangleMips; static u32 sCurrentFrameCount; static u32 sTotalAllocatedMemory; ETexelFormat x0_fmt = ETexelFormat::Invalid; u16 x4_w = 0; u16 x6_h = 0; u8 x8_mips = 0; u8 x9_bitsPerPixel = 0; bool xa_24_locked : 1 = false; bool xa_25_canLoadPalette : 1 = false; bool xa_26_isPowerOfTwo : 1 = false; bool xa_27_noSwap : 1 = true; bool xa_28_counted : 1 = false; bool xa_29_canLoadObj : 1 = false; u32 xc_memoryAllocated = 0; std::unique_ptr x10_graphicsPalette; std::unique_ptr x14_bitmapReloader; u32 x18_gxFormat = GX_TF_RGB565; GXCITexFmt x1c_gxCIFormat = GX_TF_C8; TGXTexObj x20_texObj; EClampMode x40_clampMode = EClampMode::Repeat; std::unique_ptr x44_aramToken_x4_buff; // was CARAMToken u32 x64_frameAllocated{}; // Metaforce additions std::string m_label; bool m_needsTexObjDataLoad = true; void InitBitmapBuffers(ETexelFormat fmt, u16 width, u16 height, s32 mips); void InitTextureObjs(); void CountMemory(); void UncountMemory(); void MangleMipmap(u32 mip); static u32 TexelFormatBitsPerPixel(ETexelFormat fmt); public: // Label parameters are new for Metaforce CTexture(ETexelFormat fmt, u16 w, u16 h, s32 mips, std::string_view label); CTexture(CInputStream& in, std::string_view label, EAutoMipmap automip = EAutoMipmap::Zero, EBlackKey blackKey = EBlackKey::Zero); ~CTexture(); [[nodiscard]] ETexelFormat GetTexelFormat() const { return x0_fmt; } [[nodiscard]] u16 GetWidth() const { return x4_w; } [[nodiscard]] u16 GetHeight() const { return x6_h; } [[nodiscard]] u8 GetNumberOfMipMaps() const { return x8_mips; } [[nodiscard]] u32 GetBitDepth() const { return x9_bitsPerPixel; } [[nodiscard]] u32 GetMemoryAllocated() const { return xc_memoryAllocated; } [[nodiscard]] const std::unique_ptr& GetPalette() const { return x10_graphicsPalette; } [[nodiscard]] bool HasPalette() const { return x10_graphicsPalette != nullptr; } [[nodiscard]] u8* Lock(); void UnLock(); void Load(GXTexMapID id, EClampMode clamp); void LoadMipLevel(s32 mip, GXTexMapID id, EClampMode clamp); // void UnloadBitmapData(u32) const; // void TryReloadBitmapData(CResFactory&) const; // void LoadToMRAM() const; // void LoadToARAM() const; // bool IsARAMTransferInProgress() const { return false; } void MakeSwappable(); [[nodiscard]] const u8* GetConstBitMapData(s32 mip) const; [[nodiscard]] u8* GetBitMapData(s32 mip) const; [[nodiscard]] bool IsCITexture() const { return x0_fmt == ETexelFormat::C4 || x0_fmt == ETexelFormat::C8 || x0_fmt == ETexelFormat::C14X2; } static void InvalidateTexMap(GXTexMapID id); static void SetMangleMips(bool b) { sMangleMips = b; } static void SetCurrentFrameCount(u32 frameCount) { sCurrentFrameCount = frameCount; } }; CFactoryFnReturn FTextureFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/Graphics/CVertexMorphEffect.cpp ================================================ #include "Runtime/Graphics/CVertexMorphEffect.hpp" #include "Runtime/Character/CSkinRules.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" namespace metaforce { CVertexMorphEffect::CVertexMorphEffect(const zeus::CUnitVector3f& dir, const zeus::CVector3f& pos, float duration, float diagExtent, CRandom16& random) : x0_dir(dir), xc_pos(pos), x18_duration(duration), x20_diagExtent(diagExtent), x24_random(random) {} void CVertexMorphEffect::MorphVertices(SSkinningWorkspace& workspace, TConstVectorRef averagedNormals, TLockedToken& skinRules, const CPoseAsTransforms& pose, u32 vertexCount) { if (x28_indices.empty()) { std::vector> normalsOut; normalsOut.reserve(vertexCount); skinRules->BuildNormals(averagedNormals, &normalsOut); for (int i = 0; i < vertexCount; ++i) { const auto& nov = normalsOut[i]; const zeus::CVector3f noz{nov.x, nov.y, nov.z}; float dist = noz.dot(x0_dir); if (dist > 0.5f) { x28_indices.emplace_back(i); const auto vert = workspace.m_vertexWorkspace[i]; const auto length = vert.x + vert.y + vert.z; x38_floats.emplace_back((length - std::trunc(length)) * (dist - 0.5f)); } } } for (int i = 0; i < x28_indices.size(); ++i) { const auto scale = x1c_elapsed / x18_duration; auto& out = workspace.m_vertexWorkspace[x28_indices[i]]; const auto add = scale * x20_diagExtent * x38_floats[i] * x0_dir; out.x += add.x(); out.y += add.y(); out.z += add.z(); } } void CVertexMorphEffect::Reset(const zeus::CVector3f& dir, const zeus::CVector3f& pos, float duration) { x0_dir = dir; xc_pos = pos; x18_duration = duration; x1c_elapsed = 0.f; x28_indices.clear(); x38_floats.clear(); } void CVertexMorphEffect::Update(float dt) { x1c_elapsed = std::min(x1c_elapsed + dt, x18_duration); } } // namespace metaforce ================================================ FILE: Runtime/Graphics/CVertexMorphEffect.hpp ================================================ #pragma once #include #include #include "Runtime/CToken.hpp" #include "Runtime/Character/CPoseAsTransforms.hpp" #include "Runtime/Graphics/CCubeModel.hpp" #include #include namespace metaforce { class CRandom16; class CSkinRules; struct SSkinningWorkspace; class CVertexMorphEffect { zeus::CUnitVector3f x0_dir; zeus::CVector3f xc_pos; float x18_duration; float x1c_elapsed = 0.f; float x20_diagExtent; CRandom16& x24_random; std::vector x28_indices; std::vector x38_floats; public: CVertexMorphEffect(const zeus::CUnitVector3f& dir, const zeus::CVector3f& pos, float duration, float diagExtent, CRandom16& random); void MorphVertices(SSkinningWorkspace& workspace, TConstVectorRef averagedNormals, TLockedToken& skinRules, const CPoseAsTransforms& pose, u32 vertexCount); void Reset(const zeus::CVector3f& dir, const zeus::CVector3f& pos, float duration); void Update(float dt); }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/GX.hpp ================================================ #pragma once #include #include #include #include #include namespace GX { constexpr u8 MaxLights = 8; using LightMask = std::bitset; } // namespace GX constexpr GXColor GX_BLACK{0, 0, 0, 255}; constexpr GXColor GX_WHITE{255, 255, 255, 255}; constexpr GXColor GX_CLEAR{0, 0, 0, 0}; inline bool operator==(const GXColor& lhs, const GXColor& rhs) noexcept { return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a; } inline bool operator!=(const GXColor& lhs, const GXColor& rhs) noexcept { return !(lhs == rhs); } static inline void GXPosition3f32(const zeus::CVector3f& v) { GXPosition3f32(v.x(), v.y(), v.z()); } static inline void GXNormal3f32(const zeus::CVector3f& v) { GXNormal3f32(v.x(), v.y(), v.z()); } static inline void GXTexCoord2f32(const zeus::CVector2f& v) { GXTexCoord2f32(v.x(), v.y()); } static inline void GXColor4f32(const zeus::CColor& v) { GXColor4f32(v.r(), v.g(), v.b(), v.a()); } static inline GXColor to_gx_color(const zeus::CColor& color) { return { static_cast(color.r() * 255.f), static_cast(color.g() * 255.f), static_cast(color.b() * 255.f), static_cast(color.a() * 255.f), }; } static inline zeus::CColor from_gx_color(GXColor color) { return { static_cast(color.r) / 255.f, static_cast(color.g) / 255.f, static_cast(color.b) / 255.f, static_cast(color.a) / 255.f, }; } class GXTexObjRAII : public GXTexObj { public: GXTexObjRAII() : GXTexObj() {} ~GXTexObjRAII() { GXDestroyTexObj(this); } void reset() { GXDestroyTexObj(this); } GXTexObjRAII(const GXTexObjRAII&) = delete; GXTexObjRAII& operator=(const GXTexObjRAII&) = delete; GXTexObjRAII(GXTexObjRAII&& o) noexcept : GXTexObj(o) { std::memset(static_cast(&o), 0, sizeof(GXTexObj)); } GXTexObjRAII& operator=(GXTexObjRAII&& o) noexcept { if (this != &o) { GXDestroyTexObj(this); std::memcpy(static_cast(this), &o, sizeof(GXTexObj)); std::memset(static_cast(&o), 0, sizeof(GXTexObj)); } return *this; } }; static_assert(sizeof(GXTexObjRAII) == sizeof(GXTexObj), "GXTexObjRAII should have the same size as GXTexObj"); using TGXTexObj = GXTexObjRAII; ================================================ FILE: Runtime/Graphics/IRenderer.hpp ================================================ #pragma once #include #include #include "Runtime/CToken.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/Graphics/CModel.hpp" #include "Runtime/RetroTypes.hpp" #include #include #include #include #include namespace metaforce { class CAreaOctTree; class CLight; class CMetroidModelInstance; class CPVSVisSet; class CParticleGen; class CSkinnedModel; struct CAreaRenderOctTree; struct CModelFlags; struct SShader; class IRenderer { public: using TDrawableCallback = void (*)(void*, void*, int); using TReflectionCallback = std::function; enum class EDrawableSorting { SortedCallback, UnsortedCallback }; enum class EDebugOption { Invalid = -1, PVSMode, PVSState, FogDisabled }; enum class EPrimitiveType { Triangles = GX_TRIANGLES, TriangleFan = GX_TRIANGLEFAN, TriangleStrip = GX_TRIANGLESTRIP, Lines = GX_LINES, LineStrip = GX_LINESTRIP, }; virtual ~IRenderer() = default; virtual void AddStaticGeometry(const std::vector* geometry, const CAreaRenderOctTree* octTree, s32 areaIdx) = 0; virtual void EnablePVS(const CPVSVisSet& set, u32 areaIdx) = 0; virtual void DisablePVS() = 0; virtual void RemoveStaticGeometry(const std::vector* geometry) = 0; virtual void DrawUnsortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0; virtual void DrawSortedGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0; virtual void DrawStaticGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0; virtual void DrawAreaGeometry(s32 areaIdx, s32 mask, s32 targetMask) = 0; virtual void PostRenderFogs() = 0; virtual void SetModelMatrix(const zeus::CTransform& xf) = 0; virtual void AddParticleGen(CParticleGen& gen) = 0; virtual void AddParticleGen(CParticleGen& gen, const zeus::CVector3f& pos, const zeus::CAABox& bounds) = 0; virtual void AddPlaneObject(void* obj, const zeus::CAABox& aabb, const zeus::CPlane& plane, s32 type) = 0; virtual void AddDrawable(void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, s32 mode, EDrawableSorting sorting) = 0; virtual void SetDrawableCallback(TDrawableCallback cb, void* ctx) = 0; virtual void SetWorldViewpoint(const zeus::CTransform& xf) = 0; virtual void SetPerspective(float fovy, float width, float height, float znear, float zfar) = 0; virtual void SetPerspective(float fovy, float aspect, float znear, float zfar) = 0; virtual std::pair SetViewportOrtho(bool centered, float znear, float zfar) = 0; virtual void SetClippingPlanes(const zeus::CFrustum& frustum) = 0; virtual void SetViewport(s32 left, s32 bottom, s32 width, s32 height) = 0; virtual void SetDepthReadWrite(bool, bool) = 0; virtual void SetBlendMode_AdditiveAlpha() = 0; virtual void SetBlendMode_AlphaBlended() = 0; virtual void SetBlendMode_NoColorWrite() = 0; virtual void SetBlendMode_ColorMultiply() = 0; virtual void SetBlendMode_InvertDst() = 0; virtual void SetBlendMode_InvertSrc() = 0; virtual void SetBlendMode_Replace() = 0; virtual void SetBlendMode_AdditiveDestColor() = 0; virtual void SetDebugOption(EDebugOption, s32) = 0; virtual void BeginScene() = 0; virtual void EndScene() = 0; virtual void BeginPrimitive(EPrimitiveType, s32) = 0; virtual void BeginLines(s32) = 0; virtual void BeginLineStrip(s32) = 0; virtual void BeginTriangles(s32) = 0; virtual void BeginTriangleStrip(s32) = 0; virtual void BeginTriangleFan(s32) = 0; virtual void PrimVertex(const zeus::CVector3f&) = 0; virtual void PrimNormal(const zeus::CVector3f&) = 0; virtual void PrimColor(float, float, float, float) = 0; virtual void PrimColor(const zeus::CColor&) = 0; virtual void EndPrimitive() = 0; virtual void SetAmbientColor(const zeus::CColor& color) = 0; virtual void DrawString(const char* string, int, int) = 0; virtual float GetFPS() = 0; virtual void CacheReflection(TReflectionCallback cb, void* ctx, bool clearAfter) = 0; virtual void DrawSpaceWarp(const zeus::CVector3f& pt, float strength) = 0; virtual void DrawThermalModel(CModel& model, const zeus::CColor& multCol, const zeus::CColor& addCol, TConstVectorRef positions, TConstVectorRef normals, const CModelFlags& flags) = 0; virtual void DrawModelDisintegrate(CModel& model, CTexture& tex, const zeus::CColor& color, TConstVectorRef positions, TConstVectorRef normals, float t) = 0; virtual void DrawModelFlat(CModel& model, const CModelFlags& flags, bool unsortedOnly, TConstVectorRef positions, TConstVectorRef normals) = 0; virtual void SetWireframeFlags(s32 flags) = 0; virtual void SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) = 0; virtual void RenderFogVolume(const zeus::CColor& color, const zeus::CAABox& aabb, const TLockedToken* model, const CSkinnedModel* sModel) = 0; virtual void SetThermal(bool thermal, float level, const zeus::CColor& color) = 0; virtual void SetThermalColdScale(float scale) = 0; virtual void DoThermalBlendCold() = 0; virtual void DoThermalBlendHot() = 0; virtual u32 GetStaticWorldDataSize() = 0; virtual void SetGXRegister1Color(const zeus::CColor& color) = 0; virtual void SetWorldLightFadeLevel(float level) = 0; // Something virtual void PrepareDynamicLights(const std::vector& lights) = 0; }; } // namespace metaforce ================================================ FILE: Runtime/Graphics/IWeaponRenderer.cpp ================================================ #include "Runtime/Graphics/IWeaponRenderer.hpp" #include "Runtime/Particle/CParticleGen.hpp" namespace metaforce { void CDefaultWeaponRenderer::AddParticleGen(CParticleGen& gen) { gen.Render(); } } // namespace metaforce ================================================ FILE: Runtime/Graphics/IWeaponRenderer.hpp ================================================ #pragma once namespace metaforce { class CParticleGen; class IWeaponRenderer { public: virtual ~IWeaponRenderer() = default; virtual void AddParticleGen(CParticleGen&) = 0; }; class CDefaultWeaponRenderer : public IWeaponRenderer { public: void AddParticleGen(CParticleGen&) override; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CAuiEnergyBarT01.cpp ================================================ #include "Runtime/GuiSys/CAuiEnergyBarT01.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GuiSys/CGuiSys.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" namespace metaforce { CAuiEnergyBarT01::CAuiEnergyBarT01(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId txtrId) : CGuiWidget(parms), xb8_txtrId(txtrId) { if (g_GuiSys->GetUsageMode() != CGuiSys::EUsageMode::Two) { xbc_tex = sp->GetObj(SObjectTag{FOURCC('TXTR'), xb8_txtrId}); } } std::pair CAuiEnergyBarT01::DownloadBarCoordFunc(float t) { const float x = 12.5f * t - 6.25f; return {zeus::CVector3f{x, 0.f, -0.2f}, zeus::CVector3f{x, 0.f, 0.2f}}; } void CAuiEnergyBarT01::Update(float dt) { if (x100_shadowDrainDelayTimer > 0.f) { x100_shadowDrainDelayTimer = std::max(x100_shadowDrainDelayTimer - dt, 0.f); } if (xf8_filledEnergy < xf4_setEnergy) { if (xf1_wrapping) { xf8_filledEnergy -= dt * xe4_filledSpeed; if (xf8_filledEnergy < 0.f) { xf8_filledEnergy = std::max(xf4_setEnergy, xf8_filledEnergy + xe0_maxEnergy); xf1_wrapping = false; xfc_shadowEnergy = xe0_maxEnergy; } } else { xf8_filledEnergy = std::min(xf4_setEnergy, xf8_filledEnergy + dt * xe4_filledSpeed); } } else if (xf8_filledEnergy > xf4_setEnergy) { if (xf1_wrapping) { xf8_filledEnergy += dt * xe4_filledSpeed; if (xf8_filledEnergy > xe0_maxEnergy) { xf8_filledEnergy = std::min(xf4_setEnergy, xf8_filledEnergy - xe0_maxEnergy); xf1_wrapping = false; xfc_shadowEnergy = xf8_filledEnergy; } } else { xf8_filledEnergy = std::max(xf4_setEnergy, xf8_filledEnergy - dt * xe4_filledSpeed); } } if (xfc_shadowEnergy < xf8_filledEnergy) { xfc_shadowEnergy = xf8_filledEnergy; } else if (xfc_shadowEnergy > xf8_filledEnergy && x100_shadowDrainDelayTimer == 0.f) { xfc_shadowEnergy = std::max(xf8_filledEnergy, xfc_shadowEnergy - dt * xe8_shadowSpeed); } CGuiWidget::Update(dt); } void CAuiEnergyBarT01::Draw(const CGuiWidgetDrawParms& drawParms) { CGraphics::SetModelMatrix(x34_worldXF); if (!xbc_tex || !xbc_tex.IsLoaded() || xd8_coordFunc == nullptr) { return; } CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, false); CGraphics::SetAmbientColor(zeus::skWhite); CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear); const float filledT = xe0_maxEnergy > 0.f ? xf8_filledEnergy / xe0_maxEnergy : 0.f; const float shadowT = xe0_maxEnergy > 0.f ? xfc_shadowEnergy / xe0_maxEnergy : 0.f; zeus::CColor filledColor = xd0_filledColor; filledColor.a() *= drawParms.x0_alphaMod; filledColor *= xa8_color2; zeus::CColor shadowColor = xd4_shadowColor; shadowColor.a() *= drawParms.x0_alphaMod; shadowColor *= xa8_color2; zeus::CColor emptyColor = xcc_emptyColor; emptyColor.a() *= drawParms.x0_alphaMod; emptyColor *= xa8_color2; zeus::CColor useCol = emptyColor; for (u32 i = 0; i < 3; ++i) { float barOffT; if (i == 0) { barOffT = 0.f; } else if (i == 1) { barOffT = filledT; } else { barOffT = shadowT; } float barMaxT; if (i == 0) { barMaxT = filledT; } else if (i == 1) { barMaxT = shadowT; } else { barMaxT = 1.f; } if (i == 0) { useCol = filledColor; } else if (i == 1) { useCol = shadowColor; } else { useCol = emptyColor; } if (barOffT != barMaxT) { CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); xbc_tex->Load(GX_TEXMAP0, EClampMode::Repeat); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamColor(useCol); auto coords = xd8_coordFunc(barOffT); while (barOffT < barMaxT) { CGraphics::StreamTexcoord(barOffT, 0.f); CGraphics::StreamVertex(coords.first); CGraphics::StreamTexcoord(barOffT, 1.f); CGraphics::StreamVertex(coords.second); barOffT += xdc_tesselation; if (barOffT < barMaxT) { coords = xd8_coordFunc(barOffT); } else { coords = xd8_coordFunc(barMaxT); CGraphics::StreamTexcoord(barMaxT, 0.f); CGraphics::StreamVertex(coords.first); CGraphics::StreamTexcoord(barMaxT, 1.f); CGraphics::StreamVertex(coords.second); } } CGraphics::StreamEnd(); } } CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, true); } void CAuiEnergyBarT01::SetCurrEnergy(float e, ESetMode mode) { e = zeus::clamp(0.f, e, xe0_maxEnergy); if (e == xf4_setEnergy) { return; } if (xf0_alwaysResetDelayTimer || xf8_filledEnergy == xfc_shadowEnergy) { x100_shadowDrainDelayTimer = xec_shadowDrainDelay; } xf1_wrapping = mode == ESetMode::Wrapped; xf4_setEnergy = e; if (mode == ESetMode::Insta) { xf8_filledEnergy = xf4_setEnergy; } } void CAuiEnergyBarT01::SetMaxEnergy(float maxEnergy) { xe0_maxEnergy = maxEnergy; xf4_setEnergy = std::min(xe0_maxEnergy, xf4_setEnergy); xf8_filledEnergy = std::min(xe0_maxEnergy, xf8_filledEnergy); xfc_shadowEnergy = std::min(xe0_maxEnergy, xfc_shadowEnergy); } std::shared_ptr CAuiEnergyBarT01::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); auto tex = in.Get(); std::shared_ptr ret = std::make_shared(parms, sp, tex); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CAuiEnergyBarT01.hpp ================================================ #pragma once #include #include #include #include "Runtime/CToken.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include #include namespace metaforce { class CSimplePool; class CAuiEnergyBarT01 : public CGuiWidget { public: using FCoordFunc = std::pair (*)(float t); enum class ESetMode { Normal, Wrapped, Insta }; private: CAssetId xb8_txtrId; TLockedToken xbc_tex; // Used to be optional zeus::CColor xcc_emptyColor; zeus::CColor xd0_filledColor; zeus::CColor xd4_shadowColor; FCoordFunc xd8_coordFunc = nullptr; float xdc_tesselation = 1.f; float xe0_maxEnergy = 0.f; float xe4_filledSpeed = 1000.f; float xe8_shadowSpeed = 1000.f; float xec_shadowDrainDelay = 0.f; bool xf0_alwaysResetDelayTimer = false; bool xf1_wrapping = false; float xf4_setEnergy = 0.f; float xf8_filledEnergy = 0.f; float xfc_shadowEnergy = 0.f; float x100_shadowDrainDelayTimer = 0.f; public: CAuiEnergyBarT01(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId txtrId); FourCC GetWidgetTypeID() const override { return FOURCC('ENRG'); } static std::pair DownloadBarCoordFunc(float t); void Update(float dt) override; void Draw(const CGuiWidgetDrawParms& drawParms) override; float GetActualFraction() const { return xe0_maxEnergy == 0.f ? 0.f : xf4_setEnergy / xe0_maxEnergy; } float GetSetEnergy() const { return xf4_setEnergy; } float GetMaxEnergy() const { return xe0_maxEnergy; } float GetFilledEnergy() const { return xf8_filledEnergy; } void SetCurrEnergy(float e, ESetMode mode); void SetCoordFunc(FCoordFunc func) { xd8_coordFunc = func; } void SetEmptyColor(const zeus::CColor& c) { xcc_emptyColor = c; } void SetFilledColor(const zeus::CColor& c) { xd0_filledColor = c; } void SetShadowColor(const zeus::CColor& c) { xd4_shadowColor = c; } void SetMaxEnergy(float maxEnergy); void ResetMaxEnergy() { SetMaxEnergy(xdc_tesselation); } void SetTesselation(float t) { xdc_tesselation = t; } void SetIsAlwaysResetTimer(bool b) { xf0_alwaysResetDelayTimer = b; } void SetFilledDrainSpeed(float s) { xe4_filledSpeed = s; } void SetShadowDrainSpeed(float s) { xe8_shadowSpeed = s; } void SetShadowDrainDelay(float d) { xec_shadowDrainDelay = d; } static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CAuiImagePane.cpp ================================================ #include "Runtime/GuiSys/CAuiImagePane.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/Camera/CCameraFilter.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" namespace metaforce { CAuiImagePane::CAuiImagePane(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId tex0, CAssetId tex1, rstl::reserved_vector&& coords, rstl::reserved_vector&& uvs, bool initTex) : CGuiWidget(parms), xc8_tex0(tex0), xcc_tex1(tex1), xe0_coords(std::move(coords)), x114_uvs(std::move(uvs)) { if (initTex) SetTextureID0(tex0, sp); } std::shared_ptr CAuiImagePane::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); in.ReadLong(); in.ReadLong(); in.ReadLong(); u32 coordCount = in.ReadLong(); rstl::reserved_vector coords; for (u32 i = 0; i < coordCount; ++i) coords.push_back(in.Get()); u32 uvCount = in.ReadLong(); rstl::reserved_vector uvs; for (u32 i = 0; i < uvCount; ++i) uvs.push_back(in.Get()); std::shared_ptr ret = std::make_shared(parms, sp, CAssetId(), CAssetId(), std::move(coords), std::move(uvs), true); ret->ParseBaseInfo(frame, in, parms); return ret; } void CAuiImagePane::Reset(ETraversalMode mode) { xc8_tex0 = CAssetId(); xb8_tex0Tok = TLockedToken(); CGuiWidget::Reset(mode); } void CAuiImagePane::Update(float dt) { xd0_uvBias0.x() = std::fmod(xd0_uvBias0.x(), 1.f); xd0_uvBias0.y() = std::fmod(xd0_uvBias0.y(), 1.f); if (x138_tileSize != zeus::skZero2f && xb8_tex0Tok.IsLoaded()) { zeus::CVector2f tmp = zeus::CVector2f(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight()) / x138_tileSize; x144_frameTimer = std::fmod(x144_frameTimer + dt * x140_interval, std::floor(tmp.x()) * std::floor(tmp.y())); } CGuiWidget::Update(dt); } void CAuiImagePane::DoDrawImagePane(const zeus::CColor& color, CTexture& tex, int frame, float alpha, bool noBlur) const { zeus::CColor useColor = color; useColor.a() *= alpha; rstl::reserved_vector vec; const rstl::reserved_vector* useUVs; if (x138_tileSize != zeus::skZero2f) { const zeus::CVector2f res(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight()); const zeus::CVector2f tmp = res / x138_tileSize; const zeus::CVector2f tmpRecip = x138_tileSize / res; const float x0 = tmpRecip.x() * static_cast(frame % static_cast(tmp.x())); const float x1 = x0 + tmpRecip.x(); const float y0 = tmpRecip.y() * static_cast(frame % static_cast(tmp.y())); const float y1 = y0 - tmpRecip.y(); vec.push_back(zeus::CVector2f(x0, y0)); vec.push_back(zeus::CVector2f(x0, y1)); vec.push_back(zeus::CVector2f(x1, y0)); vec.push_back(zeus::CVector2f(x1, y1)); useUVs = &vec; } else { useUVs = &x114_uvs; } if (!noBlur) { if ((x14c_deResFactor == 0.f && alpha == 1.f) || tex.GetNumberOfMipMaps() == 1) { CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); tex.LoadMipLevel(0, GX_TEXMAP0, EClampMode::Repeat); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamColor(useColor); for (u32 i = 0; i < useUVs->size(); ++i) { CGraphics::StreamTexcoord((*useUVs)[i] + xd0_uvBias0); CGraphics::StreamVertex(xe0_coords[i]); } CGraphics::StreamEnd(); } else { u32 mipCount = tex.GetNumberOfMipMaps() - 1; float fadeFactor = (1.f - x14c_deResFactor) * alpha; float fadeQ = -(fadeFactor * fadeFactor * fadeFactor - 1.f); fadeFactor = fadeQ * static_cast(mipCount); u32 mip1 = fadeFactor; u32 mip2 = mip1; if (fadeQ != static_cast(mip1 / mipCount)) { mip2 = mip1 + 1; } float rgba1 = (fadeFactor - static_cast(mip1)); float rgba2 = 1.f - rgba1; tex.LoadMipLevel(mip1, GX_TEXMAP0, EClampMode::Repeat); tex.LoadMipLevel(mip2, GX_TEXMAP1, EClampMode::Repeat); std::array list{{ {GX_VA_POS, GX_DIRECT}, {GX_VA_TEX0, GX_DIRECT}, {GX_VA_NULL, GX_NONE}, }}; CGX::SetVtxDescv(list.data()); CGX::SetNumChans(0); CGX::SetNumTexGens(2); CGX::SetNumTevStages(2); GXTevStageID stage = GX_TEVSTAGE0; while (stage < GX_TEVSTAGE2) { GXTevColorArg colorD = stage == GX_TEVSTAGE0 ? GX_CC_ZERO : GX_CC_CPREV; CGX::SetTevColorIn(stage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, colorD); GXTevAlphaArg alphaD = stage == GX_TEVSTAGE0 ? GX_CA_ZERO : GX_CA_APREV; CGX::SetTevAlphaIn(stage, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, alphaD); CGX::SetTevColorOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); CGX::SetTevAlphaOp(stage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, true, GX_TEVPREV); stage = static_cast(stage + GX_TEVSTAGE1); } CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); CGX::SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_K1_A); CGX::SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K1); zeus::CColor col1 = useColor * zeus::CColor(rgba2, rgba2, rgba2, rgba2); zeus::CColor col2 = useColor * zeus::CColor(rgba1, rgba1, rgba1, rgba1); CGX::SetTevKColor(GX_KCOLOR0, col1); CGX::SetTevKColor(GX_KCOLOR1, col2); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4); for (u32 idx = 0; const auto& coord : xe0_coords) { GXPosition3f32(coord); GXTexCoord2f32((*useUVs)[idx] + xd0_uvBias0); ++idx; } CGX::End(); } } else { CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulateAlpha); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); tex.Load(GX_TEXMAP0, EClampMode::Repeat); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamColor(useColor); for (u32 i = 0; i < useUVs->size(); ++i) { CGraphics::StreamTexcoord((*useUVs)[i]); CGraphics::StreamVertex(xe0_coords[i] + xd0_uvBias0); } CGraphics::StreamEnd(); } } void CAuiImagePane::Draw(const CGuiWidgetDrawParms& params) { CGraphics::SetModelMatrix(x34_worldXF); if (!GetIsVisible() || !xb8_tex0Tok.IsLoaded()) { return; } SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format("CAuiImagePane::Draw {}", m_name).c_str(), zeus::skCyan); GetIsFinishedLoadingWidgetSpecific(); zeus::CColor color = xa8_color2; color.a() *= params.x0_alphaMod; CGraphics::SetDepthWriteMode(true, ERglEnum::LEqual, xac_drawFlags == EGuiModelDrawFlags::Shadeless || xac_drawFlags == EGuiModelDrawFlags::Opaque); float blur0 = 1.f; float blur1 = 0.f; const int frame0 = static_cast(x144_frameTimer); int frame1 = 0; if (x140_interval < 1.f && x140_interval > 0.f) { zeus::CVector2f tmp = zeus::CVector2f(xb8_tex0Tok->GetWidth(), xb8_tex0Tok->GetHeight()) / x138_tileSize; frame1 = (frame0 + 1) % static_cast(tmp.x() * tmp.y()); if (x148_fadeDuration == 0.f) blur1 = 1.f; else blur1 = std::min(std::fmod(x144_frameTimer, 1.f) / x148_fadeDuration, 1.f); blur0 = 1.f - blur1; } // Alpha blend CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); DoDrawImagePane(color * zeus::CColor(0.f, 0.5f), *xb8_tex0Tok, frame0, 1.f, true); if (x150_flashFactor > 0.f) { // Additive blend zeus::CColor color2 = xa8_color2; color2.a() = x150_flashFactor; CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear); DoDrawImagePane(color2, *xb8_tex0Tok, frame0, blur0, false); if (blur1 > 0.f) DoDrawImagePane(color2, *xb8_tex0Tok, frame1, blur1, false); } switch (xac_drawFlags) { case EGuiModelDrawFlags::Shadeless: case EGuiModelDrawFlags::Opaque: // Opaque blend CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false); if (blur1 > 0.f) { DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false); } break; case EGuiModelDrawFlags::Alpha: // Alpha blend CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false); if (blur1 > 0.f) { DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false); } break; case EGuiModelDrawFlags::Additive: // Additive blend CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear); DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false); if (blur1 > 0.f) { DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false); } break; case EGuiModelDrawFlags::AlphaAdditiveOverdraw: // Alpha blend CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false); if (blur1 > 0.f) { DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false); } // Full additive blend CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear); DoDrawImagePane(color, *xb8_tex0Tok, frame0, blur0, false); if (blur1 > 0.f) { DoDrawImagePane(color, *xb8_tex0Tok, frame1, blur1, false); } break; default: break; } } bool CAuiImagePane::GetIsFinishedLoadingWidgetSpecific() { return !xb8_tex0Tok || xb8_tex0Tok.IsLoaded(); } void CAuiImagePane::SetTextureID0(CAssetId tex, CSimplePool* sp) { xc8_tex0 = tex; if (!sp) return; if (xc8_tex0.IsValid()) xb8_tex0Tok = sp->GetObj({FOURCC('TXTR'), xc8_tex0}); else xb8_tex0Tok = TLockedToken(); } void CAuiImagePane::SetAnimationParms(const zeus::CVector2f& tileSize, float interval, float fadeDuration) { x138_tileSize = tileSize; x140_interval = interval; x144_frameTimer = 0.f; x148_fadeDuration = fadeDuration; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CAuiImagePane.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/rstl.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include #include namespace zeus { class CColor; } namespace metaforce { class CSimplePool; class CTexture; class CAuiImagePane : public CGuiWidget { TLockedToken xb8_tex0Tok; // Used to be optional CAssetId xc8_tex0; CAssetId xcc_tex1; zeus::CVector2f xd0_uvBias0; zeus::CVector2f xd8_uvBias1; rstl::reserved_vector xe0_coords; rstl::reserved_vector x114_uvs; zeus::CVector2f x138_tileSize; float x140_interval = 0.f; float x144_frameTimer = 0.f; float x148_fadeDuration = 0.f; float x14c_deResFactor = 0.f; float x150_flashFactor = 0.f; void DoDrawImagePane(const zeus::CColor& color, CTexture& tex, int frame, float blurAmt, bool noBlur) const; public: CAuiImagePane(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId, CAssetId, rstl::reserved_vector&& coords, rstl::reserved_vector&& uvs, bool initTex); FourCC GetWidgetTypeID() const override { return FOURCC('IMGP'); } static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); void Reset(ETraversalMode mode) override; void Update(float dt) override; void Draw(const CGuiWidgetDrawParms& params) override; bool GetIsFinishedLoadingWidgetSpecific() override; void SetTextureID0(CAssetId tex, CSimplePool* sp); void SetAnimationParms(const zeus::CVector2f& vec, float interval, float duration); void SetDeResFactor(float d) { x14c_deResFactor = d; } void SetFlashFactor(float t) { x150_flashFactor = t; } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CAuiMeter.cpp ================================================ #include "Runtime/GuiSys/CAuiMeter.hpp" #include #include #include namespace metaforce { CAuiMeter::CAuiMeter(const CGuiWidgetParms& parms, bool noRoundUp, u32 maxCapacity, u32 workerCount) : CGuiGroup(parms, 0, false), xc4_noRoundUp(noRoundUp), xc8_maxCapacity(maxCapacity), xcc_capacity(maxCapacity) { xd4_workers.reserve(workerCount); } void CAuiMeter::UpdateMeterWorkers() { float scale = xd4_workers.size() / float(xc8_maxCapacity); size_t etankCap; if (xc4_noRoundUp) etankCap = xcc_capacity * scale; else etankCap = xcc_capacity * scale + 0.5f; size_t etankFill; if (xc4_noRoundUp) etankFill = xd0_value * scale; else etankFill = xd0_value * scale + 0.5f; for (size_t i = 0; i < xd4_workers.size(); ++i) { CGuiGroup* worker = xd4_workers[i]; if (!worker) continue; CGuiWidget* fullTank = worker->GetWorkerWidget(0); CGuiWidget* emptyTank = worker->GetWorkerWidget(1); if (i < etankFill) { if (fullTank) fullTank->SetIsVisible(true); if (emptyTank) emptyTank->SetIsVisible(false); } else if (i < etankCap) { if (fullTank) fullTank->SetIsVisible(false); if (emptyTank) emptyTank->SetIsVisible(true); } else { if (fullTank) fullTank->SetIsVisible(false); if (emptyTank) emptyTank->SetIsVisible(false); } } } void CAuiMeter::OnVisibleChange() { if (GetIsVisible()) UpdateMeterWorkers(); } void CAuiMeter::SetCurrValue(s32 val) { xd0_value = zeus::clamp(0, val, xcc_capacity); UpdateMeterWorkers(); } void CAuiMeter::SetCapacity(s32 cap) { xcc_capacity = zeus::clamp(0, cap, xc8_maxCapacity); xd0_value = std::min(xcc_capacity, xd0_value); UpdateMeterWorkers(); } void CAuiMeter::SetMaxCapacity(s32 cap) { xc8_maxCapacity = std::max(0, cap); xcc_capacity = std::min(xc8_maxCapacity, xcc_capacity); xd0_value = std::min(xcc_capacity, xd0_value); UpdateMeterWorkers(); } CGuiWidget* CAuiMeter::GetWorkerWidget(int id) const { return xd4_workers[id]; } bool CAuiMeter::AddWorkerWidget(CGuiWidget* worker) { CGuiGroup::AddWorkerWidget(worker); if (worker->GetWorkerId() >= xd4_workers.size()) xd4_workers.resize(worker->GetWorkerId() + 1); xd4_workers[worker->GetWorkerId()] = static_cast(worker); return true; } std::shared_ptr CAuiMeter::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); in.ReadBool(); bool noRoundUp = in.ReadBool(); u32 maxCapacity = in.ReadLong(); u32 workerCount = in.ReadLong(); std::shared_ptr ret = std::make_shared(parms, noRoundUp, maxCapacity, workerCount); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CAuiMeter.hpp ================================================ #pragma once #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/GuiSys/CGuiGroup.hpp" namespace metaforce { class CSimplePool; class CAuiMeter : public CGuiGroup { bool xc4_noRoundUp; s32 xc8_maxCapacity; s32 xcc_capacity; s32 xd0_value = 0; std::vector xd4_workers; void UpdateMeterWorkers(); public: CAuiMeter(const CGuiWidgetParms& parms, bool noRoundUp, u32 maxCapacity, u32 workerCount); FourCC GetWidgetTypeID() const override { return FOURCC('METR'); } void OnVisibleChange() override; void SetCurrValue(s32 val); void SetCapacity(s32 cap); void SetMaxCapacity(s32 cap); CGuiWidget* GetWorkerWidget(int id) const override; bool AddWorkerWidget(CGuiWidget* worker) override; static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CCompoundTargetReticle.cpp ================================================ #include "Runtime/GuiSys/CCompoundTargetReticle.hpp" #include #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CScriptGrapplePoint.hpp" #include "Runtime/World/CWorld.hpp" #include "Runtime/Formatting.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { namespace { constexpr char skCrosshairsReticleAssetName[] = "CMDL_Crosshairs"; [[maybe_unused]] constexpr char skOrbitZoneReticleAssetName[] = "CMDL_OrbitZone"; constexpr char skSeekerAssetName[] = "CMDL_Seeker"; constexpr char skLockConfirmAssetName[] = "CMDL_LockConfirm"; constexpr char skTargetFlowerAssetName[] = "CMDL_TargetFlower"; constexpr char skMissileBracketAssetName[] = "CMDL_MissileBracket"; constexpr char skChargeGaugeAssetName[] = "CMDL_ChargeGauge"; constexpr char skChargeBeamTickAssetName[] = "CMDL_ChargeTickFirst"; constexpr char skOuterBeamIconSquareNameBase[] = "CMDL_BeamSquare"; constexpr char skInnerBeamIconName[] = "CMDL_InnerBeamIcon"; constexpr char skLockFireAssetName[] = "CMDL_LockFire"; constexpr char skLockDaggerAssetName[] = "CMDL_LockDagger0"; constexpr char skGrappleReticleAssetName[] = "CMDL_Grapple"; constexpr char skXRayRingModelName[] = "CMDL_XRayRetRing"; constexpr char skThermalReticleAssetName[] = "CMDL_ThermalRet"; float offshoot_func(float f1, float f2, float f3) { return (f1 * 0.5f) + std::sin((f3 - 0.5f) * f2); } float calculate_premultiplied_overshoot_offset(float f1) { return 2.f * (M_PIF - std::asin(1.f / f1)); } } // Anonymous namespace constexpr CTargetReticleRenderState CTargetReticleRenderState::skZeroRenderState(kInvalidUniqueId, 1.f, zeus::skZero3f, 0.f, 1.f, true); CCompoundTargetReticle::SOuterItemInfo::SOuterItemInfo(std::string_view res) : x0_model(g_SimplePool->GetObj(res)) {} CCompoundTargetReticle::CCompoundTargetReticle(const CStateManager& mgr) : x0_leadingOrientation(mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().buildMatrix3f()) , x10_laggingOrientation(mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().buildMatrix3f()) , x2c_overshootOffsetHalf(0.5f * g_tweakTargeting->GetChargeGaugeOvershootOffset()) , x30_premultOvershootOffset( calculate_premultiplied_overshoot_offset(g_tweakTargeting->GetChargeGaugeOvershootOffset())) , x34_crosshairs(g_SimplePool->GetObj(skCrosshairsReticleAssetName)) , x40_seeker(g_SimplePool->GetObj(skSeekerAssetName)) , x4c_lockConfirm(g_SimplePool->GetObj(skLockConfirmAssetName)) , x58_targetFlower(g_SimplePool->GetObj(skTargetFlowerAssetName)) , x64_missileBracket(g_SimplePool->GetObj(skMissileBracketAssetName)) , x70_innerBeamIcon(g_SimplePool->GetObj(skInnerBeamIconName)) , x7c_lockFire(g_SimplePool->GetObj(skLockFireAssetName)) , x88_lockDagger(g_SimplePool->GetObj(skLockDaggerAssetName)) , x94_grapple(g_SimplePool->GetObj(skGrappleReticleAssetName)) , xa0_chargeTickFirst(g_SimplePool->GetObj(skChargeBeamTickAssetName)) , xac_xrayRetRing(g_SimplePool->GetObj(skXRayRingModelName)) , xb8_thermalReticle(g_SimplePool->GetObj(skThermalReticleAssetName)) , xc4_chargeGauge(skChargeGaugeAssetName) , xf4_targetPos(CalculateOrbitZoneReticlePosition(mgr, false)) , x100_laggingTargetPos(CalculateOrbitZoneReticlePosition(mgr, true)) , x208_lockonTimer(g_tweakTargeting->GetLockonDuration()) { xe0_outerBeamIconSquares.reserve(9); for (size_t i = 0; i < xe0_outerBeamIconSquares.capacity(); ++i) { xe0_outerBeamIconSquares.emplace_back(fmt::format("{}{}", skOuterBeamIconSquareNameBase, i)); } x34_crosshairs.Lock(); } // CCompoundTargetReticle::SScanReticuleRenderer::SScanReticuleRenderer() { // CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) { // for (size_t i = 0; i < m_lineRenderers.size(); ++i) { // m_lineRenderers[i].emplace(CLineRenderer::EPrimitiveMode::Lines, 8, {}, true, true); // for (auto& stripRenderer : m_stripRenderers[i]) { // stripRenderer.emplace(CLineRenderer::EPrimitiveMode::LineStrip, 4, {}, true, true); // } // } // return true; // } BooTrace); // } CCompoundTargetReticle::EReticleState CCompoundTargetReticle::GetDesiredReticleState(const CStateManager& mgr) const { switch (mgr.GetPlayerState()->GetCurrentVisor()) { case CPlayerState::EPlayerVisor::Scan: return EReticleState::Scan; case CPlayerState::EPlayerVisor::XRay: return EReticleState::XRay; case CPlayerState::EPlayerVisor::Combat: default: return EReticleState::Combat; case CPlayerState::EPlayerVisor::Thermal: return EReticleState::Thermal; } } void CCompoundTargetReticle::Update(float dt, const CStateManager& mgr) { const float angle = x10_laggingOrientation.angleFrom(x0_leadingOrientation).asDegrees(); float t; if (angle < 0.1f || angle > 45.f) { t = 1.f; } else { t = std::min(1.f, g_tweakTargeting->GetAngularLagSpeed() * dt / angle); } x10_laggingOrientation = t == 1.f ? x0_leadingOrientation : zeus::CQuaternion::slerp(x10_laggingOrientation, x0_leadingOrientation, t); xf4_targetPos = CalculateOrbitZoneReticlePosition(mgr, false); x100_laggingTargetPos = CalculateOrbitZoneReticlePosition(mgr, true); UpdateCurrLockOnGroup(dt, mgr); UpdateNextLockOnGroup(dt, mgr); UpdateOrbitZoneGroup(dt, mgr); const EReticleState desiredState = GetDesiredReticleState(mgr); if (desiredState != x20_prevState && x20_prevState == x24_nextState) { x24_nextState = desiredState; x28_noDrawTicks = 2; } if (x20_prevState != x24_nextState && x28_noDrawTicks <= 0) { x20_prevState = x24_nextState; bool combat = false; bool scan = false; bool xray = false; bool thermal = false; switch (x24_nextState) { case EReticleState::Combat: combat = true; break; case EReticleState::Scan: scan = true; break; case EReticleState::XRay: xray = true; break; case EReticleState::Thermal: thermal = true; break; default: break; } if (combat) { x40_seeker.Lock(); } else { x40_seeker.Unlock(); } if (combat) { x4c_lockConfirm.Lock(); } else { x4c_lockConfirm.Unlock(); } if (combat) { x58_targetFlower.Lock(); } else { x58_targetFlower.Unlock(); } if (combat) { x64_missileBracket.Lock(); } else { x64_missileBracket.Unlock(); } if (combat) { x70_innerBeamIcon.Lock(); } else { x70_innerBeamIcon.Unlock(); } if (combat) { x7c_lockFire.Lock(); } else { x7c_lockFire.Unlock(); } if (combat) { x88_lockDagger.Lock(); } else { x88_lockDagger.Unlock(); } if (combat) { xa0_chargeTickFirst.Lock(); } else { xa0_chargeTickFirst.Unlock(); } if (xray) { xac_xrayRetRing.Lock(); } else { xac_xrayRetRing.Unlock(); } if (thermal) { xb8_thermalReticle.Lock(); } else { xb8_thermalReticle.Unlock(); } if (combat) { xc4_chargeGauge.x0_model.Lock(); } else { xc4_chargeGauge.x0_model.Unlock(); } if (!scan) { x94_grapple.Lock(); } else { x94_grapple.Unlock(); } for (SOuterItemInfo& info : xe0_outerBeamIconSquares) { if (combat) { info.x0_model.Lock(); } else { info.x0_model.Unlock(); } } } const CPlayerGun* gun = mgr.GetPlayer().GetPlayerGun(); const bool fullyCharged = (gun->IsCharging() ? gun->GetChargeBeamFactor() : 0.f) >= 1.f; if (fullyCharged != x21a_fullyCharged) { x21a_fullyCharged = fullyCharged; } if (x21a_fullyCharged) { x214_fullChargeFadeTimer = std::min(dt / g_tweakTargeting->GetFullChargeFadeDuration() + x214_fullChargeFadeTimer, g_tweakTargeting->GetFullChargeFadeDuration()); } else { x214_fullChargeFadeTimer = std::max(x214_fullChargeFadeTimer - dt / g_tweakTargeting->GetFullChargeFadeDuration(), 0.f); } const bool missileActive = gun->GetMissleMode() == CPlayerGun::EMissileMode::Active; if (missileActive != x1f4_missileActive) { if (x1f8_missileBracketTimer != 0.f) { x1f8_missileBracketTimer = FLT_EPSILON - x1f8_missileBracketTimer; } else { x1f8_missileBracketTimer = FLT_EPSILON; } x1f4_missileActive = missileActive; } const CPlayerState::EBeamId beam = gun->GetCurrentBeam(); if (beam != x200_beam) { x204_chargeGaugeOvershootTimer = g_tweakTargeting->GetChargeGaugeOvershootDuration(); for (size_t i = 0; i < xe0_outerBeamIconSquares.size(); ++i) { zeus::CRelAngle baseAngle = g_tweakTargeting->GetOuterBeamSquareAngles(int(beam))[i]; baseAngle.makeRel(); SOuterItemInfo& icon = xe0_outerBeamIconSquares[i]; zeus::CRelAngle offshootAngleDelta = baseAngle.asRadians() - icon.x10_rotAng; if ((i & 0x1) == 1) { offshootAngleDelta = (baseAngle > 0.f) ? zeus::CRelAngle(-2.f * M_PIF - baseAngle) : zeus::CRelAngle(2.f * M_PIF + baseAngle); } icon.xc_offshootBaseAngle = icon.x10_rotAng; icon.x18_offshootAngleDelta = offshootAngleDelta; icon.x14_baseAngle = baseAngle; } zeus::CRelAngle baseAngle = g_tweakTargeting->GetChargeGaugeAngle(int(beam)); baseAngle.makeRel(); float offshootAngleDelta = baseAngle.asRadians() - xc4_chargeGauge.x10_rotAng; if ((rand() & 0x1) == 1) { offshootAngleDelta = (offshootAngleDelta > 0.f) ? -2.f * M_PIF - offshootAngleDelta : 2.f * M_PIF + offshootAngleDelta; } xc4_chargeGauge.xc_offshootBaseAngle = xc4_chargeGauge.x10_rotAng; xc4_chargeGauge.x18_offshootAngleDelta = offshootAngleDelta; xc4_chargeGauge.x14_baseAngle = baseAngle; x200_beam = beam; x208_lockonTimer = 0.f; } if ((gun->GetLastFireButtonStates() & 0x1) != 0) { if (!x218_beamShot) { x210_lockFireTimer = g_tweakTargeting->GetLockFireDuration(); } x218_beamShot = true; } else { x218_beamShot = false; } if ((gun->GetLastFireButtonStates() & 0x2) != 0) { if (!x219_missileShot) { x1fc_missileBracketScaleTimer = g_tweakTargeting->GetMissileBracketScaleDuration(); } x219_missileShot = true; } else { x219_missileShot = false; } if (const TCastToConstPtr point = mgr.GetObjectById(xf2_nextTargetId)) { if (point->GetUniqueId() != x1dc_grapplePoint0) { float tmp; if (point->GetUniqueId() == x1de_grapplePoint1) { tmp = std::max(FLT_EPSILON, x1e4_grapplePoint1T); } else { tmp = FLT_EPSILON; } x1de_grapplePoint1 = x1dc_grapplePoint0; x1e4_grapplePoint1T = x1e0_grapplePoint0T; x1e0_grapplePoint0T = tmp; x1dc_grapplePoint0 = point->GetUniqueId(); } } else if (x1dc_grapplePoint0 != kInvalidUniqueId) { x1de_grapplePoint1 = x1dc_grapplePoint0; x1e4_grapplePoint1T = x1e0_grapplePoint0T; x1e0_grapplePoint0T = 0.f; x1dc_grapplePoint0 = kInvalidUniqueId; } if (x1e0_grapplePoint0T > 0.f) { x1e0_grapplePoint0T = std::min(dt / 0.5f + x1e0_grapplePoint0T, 1.f); } if (x1e4_grapplePoint1T > 0.f) { x1e4_grapplePoint1T = std::max(0.f, x1e4_grapplePoint1T - dt / 0.5f); if (x1e4_grapplePoint1T == 0.f) { x1de_grapplePoint1 = kInvalidUniqueId; } } x1f0_xrayRetAngle = zeus::CRelAngle(zeus::degToRad(g_tweakTargeting->GetXRayRetAngleSpeed() * dt) + x1f0_xrayRetAngle).asRel(); x1ec_seekerAngle = zeus::CRelAngle(zeus::degToRad(g_tweakTargeting->GetSeekerAngleSpeed() * dt) + x1ec_seekerAngle).asRel(); } void CTargetReticleRenderState::InterpolateWithClamp(const CTargetReticleRenderState& a, CTargetReticleRenderState& out, const CTargetReticleRenderState& b, float t) { t = zeus::clamp(0.f, t, 1.f); const float omt = 1.f - t; out.x4_radiusWorld = omt * a.x4_radiusWorld + t * b.x4_radiusWorld; out.x14_factor = omt * a.x14_factor + t * b.x14_factor; out.x18_minVpClampScale = omt * a.x18_minVpClampScale + t * b.x18_minVpClampScale; out.x8_positionWorld = zeus::CVector3f::lerp(a.x8_positionWorld, b.x8_positionWorld, t); if (t == 1.f) { out.x0_target = b.x0_target; } else if (t == 0.f) { out.x0_target = a.x0_target; } else { out.x0_target = kInvalidUniqueId; } } static bool IsDamageOrbit(CPlayer::EPlayerOrbitRequest req) { switch (req) { case CPlayer::EPlayerOrbitRequest::Five: case CPlayer::EPlayerOrbitRequest::ActivateOrbitSource: case CPlayer::EPlayerOrbitRequest::ProjectileCollide: case CPlayer::EPlayerOrbitRequest::Freeze: case CPlayer::EPlayerOrbitRequest::DamageOnGrapple: return true; default: return false; } } void CCompoundTargetReticle::UpdateCurrLockOnGroup(float dt, const CStateManager& mgr) { const TUniqueId targetId = mgr.GetPlayer().GetOrbitTargetId(); if (targetId != xf0_targetId) { if (targetId != kInvalidUniqueId) { if (TCastToConstPtr(mgr.GetObjectById(targetId))) { CSfxManager::SfxStart(SFXui_lockon_grapple, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else { CSfxManager::SfxStart(SFXui_lockon_poi, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } } if (targetId == kInvalidUniqueId) { x12c_currGroupA = x10c_currGroupInterp; x14c_currGroupB.SetFactor(0.f); x16c_currGroupDur = IsDamageOrbit(mgr.GetPlayer().GetOrbitRequest()) ? 0.65f : g_tweakTargeting->GetCurrLockOnEnterDuration(); } else { x12c_currGroupA = x10c_currGroupInterp; if (xf0_targetId == kInvalidUniqueId) { x12c_currGroupA.SetTargetId(targetId); } x14c_currGroupB = CTargetReticleRenderState( targetId, 1.f, zeus::skZero3f, 1.f, IsGrappleTarget(targetId, mgr) ? g_tweakTargeting->GetGrappleMinClampScale() : 1.f, false); x16c_currGroupDur = xf0_targetId == kInvalidUniqueId ? g_tweakTargeting->GetCurrLockOnExitDuration() : g_tweakTargeting->GetCurrLockOnSwitchDuration(); } x170_currGroupTimer = x16c_currGroupDur; xf0_targetId = targetId; } if (x170_currGroupTimer > 0.f) { UpdateTargetParameters(x12c_currGroupA, mgr); UpdateTargetParameters(x14c_currGroupB, mgr); x170_currGroupTimer = std::max(0.f, x170_currGroupTimer - dt); CTargetReticleRenderState::InterpolateWithClamp(x12c_currGroupA, x10c_currGroupInterp, x14c_currGroupB, 1.f - x170_currGroupTimer / x16c_currGroupDur); } else { UpdateTargetParameters(x10c_currGroupInterp, mgr); } if (x1f8_missileBracketTimer != 0.f && x1f8_missileBracketTimer < g_tweakTargeting->GetMissileBracketDuration()) { if (x1f8_missileBracketTimer < 0.f) { x1f8_missileBracketTimer = std::min(0.f, x1f8_missileBracketTimer + dt); } else { x1f8_missileBracketTimer = std::min(g_tweakTargeting->GetMissileBracketDuration(), x1f8_missileBracketTimer + dt); } } if (x204_chargeGaugeOvershootTimer > 0.f) { x204_chargeGaugeOvershootTimer = std::max(0.f, x204_chargeGaugeOvershootTimer - dt); if (x204_chargeGaugeOvershootTimer == 0.f) { for (auto& iconSquare : xe0_outerBeamIconSquares) { iconSquare.x10_rotAng = iconSquare.x14_baseAngle; } xc4_chargeGauge.x10_rotAng = xc4_chargeGauge.x14_baseAngle; x208_lockonTimer = FLT_EPSILON; } else { const float offshoot = offshoot_func(x2c_overshootOffsetHalf, x30_premultOvershootOffset, 1.f - x204_chargeGaugeOvershootTimer / g_tweakTargeting->GetChargeGaugeOvershootDuration()); for (auto& item : xe0_outerBeamIconSquares) { item.x10_rotAng = zeus::CRelAngle(item.x18_offshootAngleDelta * offshoot + item.xc_offshootBaseAngle).asRel(); } xc4_chargeGauge.x10_rotAng = zeus::CRelAngle(xc4_chargeGauge.x18_offshootAngleDelta * offshoot + xc4_chargeGauge.xc_offshootBaseAngle) .asRel(); } } if (x208_lockonTimer > 0.f && x208_lockonTimer < g_tweakTargeting->GetLockonDuration()) { x208_lockonTimer = std::min(g_tweakTargeting->GetLockonDuration(), x208_lockonTimer + dt); } if (x210_lockFireTimer > 0.f) { x210_lockFireTimer = std::max(0.f, x210_lockFireTimer - dt); } if (x1fc_missileBracketScaleTimer > 0.f) { x1fc_missileBracketScaleTimer = std::max(0.f, x1fc_missileBracketScaleTimer - dt); } } void CCompoundTargetReticle::UpdateNextLockOnGroup(float dt, const CStateManager& mgr) { TUniqueId nextTargetId = mgr.GetPlayer().GetOrbitNextTargetId(); if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan && mgr.GetPlayer().GetOrbitTargetId() != kInvalidUniqueId) { nextTargetId = mgr.GetPlayer().GetOrbitTargetId(); } if (nextTargetId != xf2_nextTargetId) { if (nextTargetId == kInvalidUniqueId) { x194_nextGroupA = x174_nextGroupInterp; x1b4_nextGroupB = CTargetReticleRenderState( kInvalidUniqueId, 1.f, (x20_prevState == EReticleState::XRay || x20_prevState == EReticleState::Thermal) ? x100_laggingTargetPos : xf4_targetPos, 0.f, 1.f, true); x1d4_nextGroupDur = x1d8_nextGroupTimer = g_tweakTargeting->GetNextLockOnExitDuration(); xf2_nextTargetId = nextTargetId; } else { x194_nextGroupA = x174_nextGroupInterp; x1b4_nextGroupB = CTargetReticleRenderState( nextTargetId, 1.f, zeus::skZero3f, 1.f, IsGrappleTarget(nextTargetId, mgr) ? g_tweakTargeting->GetGrappleMinClampScale() : 1.f, true); x1d4_nextGroupDur = x1d8_nextGroupTimer = xf2_nextTargetId == kInvalidUniqueId ? g_tweakTargeting->GetNextLockOnEnterDuration() : g_tweakTargeting->GetNextLockOnSwitchDuration(); xf2_nextTargetId = nextTargetId; } } if (x1d8_nextGroupTimer > 0.f) { UpdateTargetParameters(x194_nextGroupA, mgr); UpdateTargetParameters(x1b4_nextGroupB, mgr); x1d8_nextGroupTimer = std::max(0.f, x1d8_nextGroupTimer - dt); CTargetReticleRenderState::InterpolateWithClamp(x194_nextGroupA, x174_nextGroupInterp, x1b4_nextGroupB, 1.f - x1d8_nextGroupTimer / x1d4_nextGroupDur); } else { UpdateTargetParameters(x174_nextGroupInterp, mgr); } } void CCompoundTargetReticle::UpdateOrbitZoneGroup(float dt, const CStateManager& mgr) { if (xf0_targetId == kInvalidUniqueId && xf2_nextTargetId != kInvalidUniqueId) { x20c_ = std::min(1.f, 2.f * dt + x20c_); } else { x20c_ = std::max(0.f, x20c_ - 2.f * dt); } if (mgr.GetPlayer().IsShowingCrosshairs() && mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) { x1e8_crosshairsScale = std::min(1.f, dt / g_tweakTargeting->GetCrosshairsScaleDuration() + x1e8_crosshairsScale); } else { x1e8_crosshairsScale = std::max(0.f, x1e8_crosshairsScale - dt / g_tweakTargeting->GetCrosshairsScaleDuration()); } } void CCompoundTargetReticle::Draw(const CStateManager& mgr, bool hideLockon) { if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed && !mgr.GetCameraManager()->IsInCinematicCamera()) { SCOPED_GRAPHICS_DEBUG_GROUP("CCompoundTargetReticle::Draw", zeus::skCyan); const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr); CGraphics::SetViewPointMatrix(camXf); if (!hideLockon) { DrawCurrLockOnGroup(camXf.basis, mgr); DrawNextLockOnGroup(camXf.basis, mgr); DrawOrbitZoneGroup(camXf.basis, mgr); } DrawGrappleGroup(camXf.basis, mgr, hideLockon); } if (x28_noDrawTicks > 0) { --x28_noDrawTicks; } } void CCompoundTargetReticle::DrawGrapplePoint(const CScriptGrapplePoint& point, float t, const CStateManager& mgr, const zeus::CMatrix3f& rot, bool zEqual) { zeus::CVector3f orbitPos = point.GetOrbitPosition(mgr); zeus::CColor color; if (point.GetGrappleParameters().GetLockSwingTurn()) { color = g_tweakTargeting->GetLockedGrapplePointSelectColor(); } else { color = g_tweakTargeting->GetGrapplePointSelectColor(); } color = zeus::CColor::lerp(g_tweakTargeting->GetGrapplePointColor(), color, t); zeus::CMatrix3f scale( CalculateClampedScale(orbitPos, 1.f, g_tweakTargeting->GetGrappleClampMin(), g_tweakTargeting->GetGrappleClampMax(), mgr) * ((1.f - t) * g_tweakTargeting->GetGrappleScale() + t * g_tweakTargeting->GetGrappleSelectScale())); zeus::CTransform modelXf(rot * scale, orbitPos); CGraphics::SetModelMatrix(modelXf); CModelFlags flags(7, 0, 0, color); x94_grapple->Draw(flags); } void CCompoundTargetReticle::DrawGrappleGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr, bool hideLockon) { if (x28_noDrawTicks > 0) { return; } if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam) && x94_grapple.IsLoaded() && x20_prevState != EReticleState::Scan) { if (hideLockon) { for (const CEntity* ent : mgr.GetAllObjectList()) { if (TCastToConstPtr point = ent) { if (point->GetActive()) { if (point->GetAreaIdAlways() != kInvalidAreaId) { const CGameArea* area = mgr.GetWorld()->GetAreaAlways(point->GetAreaIdAlways()); const auto occState = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded; if (occState != CGameArea::EOcclusionState::Visible) { continue; } } float t = 0.f; if (point->GetUniqueId() == x1dc_grapplePoint0) { t = x1e0_grapplePoint0T; } else if (point->GetUniqueId() == x1de_grapplePoint1) { t = x1e4_grapplePoint1T; } if (std::fabs(t) < 0.00001f) { DrawGrapplePoint(*point, t, mgr, rot, true); } } } } } else { const TCastToConstPtr point0 = mgr.GetObjectById(x1dc_grapplePoint0); const TCastToConstPtr point1 = mgr.GetObjectById(x1de_grapplePoint1); for (int i = 0; i < 2; ++i) { const CScriptGrapplePoint* point = i == 0 ? point0.GetPtr() : point1.GetPtr(); const float t = i == 0 ? x1e0_grapplePoint0T : x1e4_grapplePoint1T; if (point) { DrawGrapplePoint(*point, t, mgr, rot, false); } } } } } void CCompoundTargetReticle::DrawCurrLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr) { if (x28_noDrawTicks > 0) { return; } if (x1e0_grapplePoint0T + x1e4_grapplePoint1T > 0 || x10c_currGroupInterp.GetFactor() == 0.f) { return; } float lockBreakAlpha = x10c_currGroupInterp.GetFactor(); float visorFactor = mgr.GetPlayerState()->GetVisorTransitionFactor(); bool lockConfirm = false; bool lockReticule = false; switch (x20_prevState) { case EReticleState::Combat: lockConfirm = true; lockReticule = true; break; case EReticleState::Scan: lockConfirm = true; break; default: break; } zeus::CMatrix3f lockBreakXf; zeus::CColor lockBreakColor = zeus::skClear; if (IsDamageOrbit(mgr.GetPlayer().GetOrbitRequest()) && x14c_currGroupB.GetFactor() == 0.f) { zeus::CMatrix3f lockBreakRM; for (int i = 0; i < 4; ++i) { const int a = rand() % 9; const auto b = std::div(a, 3); lockBreakRM[b.rem][b.quot] += rand() / float(RAND_MAX) - 0.5f; } lockBreakXf = lockBreakRM.transposed(); if (x10c_currGroupInterp.GetFactor() > 0.8f) { lockBreakColor = zeus::CColor(1.f, (x10c_currGroupInterp.GetFactor() - 0.8f) * 0.3f / 0.2f); } lockBreakAlpha = x10c_currGroupInterp.GetFactor() > 0.75f ? 1.f : std::max(0.f, (x10c_currGroupInterp.GetFactor() - 0.55f) / 0.2f); } if (lockConfirm && x4c_lockConfirm.IsLoaded()) { const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetLockConfirmClampMin(), g_tweakTargeting->GetLockConfirmClampMax(), mgr) * g_tweakTargeting->GetLockConfirmScale() / x10c_currGroupInterp.GetFactor()); const zeus::CTransform modelXf(lockBreakXf * (rot * zeus::CMatrix3f::RotateY(x1ec_seekerAngle) * scale), x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetLockConfirmColor(); color.a() *= lockBreakAlpha; const CModelFlags flags(7, 0, 0, lockBreakColor + color); x4c_lockConfirm->Draw(flags); } if (lockReticule) { if (x58_targetFlower.IsLoaded()) { const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetTargetFlowerClampMin(), g_tweakTargeting->GetTargetFlowerClampMax(), mgr) * g_tweakTargeting->GetTargetFlowerScale() / lockBreakAlpha); const zeus::CTransform modelXf(lockBreakXf * (rot * zeus::CMatrix3f::RotateY(x1f0_xrayRetAngle) * scale), x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetTargetFlowerColor(); color.a() *= lockBreakAlpha * visorFactor; const CModelFlags flags(7, 0, 0, lockBreakColor + color); x58_targetFlower->Draw(flags); } if (x1f8_missileBracketTimer != 0.f && x64_missileBracket.IsLoaded()) { const float t = std::fabs((x1fc_missileBracketScaleTimer - 0.5f * g_tweakTargeting->GetMissileBracketScaleDuration()) / 0.5f * g_tweakTargeting->GetMissileBracketScaleDuration()); const float tscale = ((1.f - t) * g_tweakTargeting->GetMissileBracketScaleEnd() + t * g_tweakTargeting->GetMissileBracketScaleStart()); const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetMissileBracketClampMin(), g_tweakTargeting->GetMissileBracketClampMax(), mgr) * std::fabs(x1f8_missileBracketTimer) / g_tweakTargeting->GetMissileBracketDuration() * tscale / x10c_currGroupInterp.GetFactor()); for (int i = 0; i < 4; ++i) { const zeus::CTransform modelXf( lockBreakXf * rot * zeus::CMatrix3f(zeus::CVector3f{i < 2 ? 1.f : -1.f, 1.f, i & 0x1 ? 1.f : -1.f}) * scale, x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetMissileBracketColor(); color.a() *= lockBreakAlpha * visorFactor; const CModelFlags flags(7, 0, 0, lockBreakColor + color); x64_missileBracket->Draw(flags); } } const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetChargeGaugeClampMin(), g_tweakTargeting->GetChargeGaugeClampMax(), mgr) * 1.f / x10c_currGroupInterp.GetFactor() * g_tweakTargeting->GetOuterBeamSquaresScale()); zeus::CMatrix3f outerBeamXf = rot * scale; for (int i = 0; i < 9; ++i) { SOuterItemInfo& info = xe0_outerBeamIconSquares[i]; if (info.x0_model.IsLoaded()) { zeus::CTransform modelXf(lockBreakXf * outerBeamXf * zeus::CMatrix3f::RotateY(info.x10_rotAng), x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetOuterBeamSquareColor(); color.a() *= lockBreakAlpha * visorFactor; CModelFlags flags(7, 0, 0, lockBreakColor + color); info.x0_model->Draw(flags); } } if (xc4_chargeGauge.x0_model.IsLoaded()) { const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetChargeGaugeClampMin(), g_tweakTargeting->GetChargeGaugeClampMax(), mgr) * g_tweakTargeting->GetChargeGaugeScale() / x10c_currGroupInterp.GetFactor()); const zeus::CMatrix3f chargeGaugeXf = rot * scale * zeus::CMatrix3f::RotateY(xc4_chargeGauge.x10_rotAng); const float pulseT = std::fabs(std::fmod(CGraphics::GetSecondsMod900(), g_tweakTargeting->GetChargeGaugePulsePeriod())); const zeus::CColor gaugeColor = zeus::CColor::lerp(g_tweakTargeting->GetChargeGaugeNonFullColor(), zeus::CColor::lerp(g_tweakTargeting->GetChargeGaugePulseColorHigh(), g_tweakTargeting->GetChargeGaugePulseColorLow(), pulseT < 0.5f * g_tweakTargeting->GetChargeGaugePulsePeriod() ? pulseT / (0.5f * g_tweakTargeting->GetChargeGaugePulsePeriod()) : (g_tweakTargeting->GetChargeGaugePulsePeriod() - pulseT) / (0.5f * g_tweakTargeting->GetChargeGaugePulsePeriod())), x214_fullChargeFadeTimer / g_tweakTargeting->GetFullChargeFadeDuration()); zeus::CTransform modelXf(lockBreakXf * chargeGaugeXf, x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = gaugeColor; color.a() *= lockBreakAlpha * visorFactor; const CModelFlags flags(7, 0, 0, lockBreakColor + color); xc4_chargeGauge.x0_model->Draw(flags); if (xa0_chargeTickFirst.IsLoaded()) { const CPlayerGun* gun = mgr.GetPlayer().GetPlayerGun(); const int numTicks = int(g_tweakTargeting->GetChargeTickCount() * (gun->IsCharging() ? gun->GetChargeBeamFactor() : 0.f)); for (int i = 0; i < numTicks; ++i) { const CModelFlags tickFlags(7, 0, 0, lockBreakColor + color); xa0_chargeTickFirst->Draw(tickFlags); modelXf.rotateLocalY(g_tweakTargeting->GetChargeTickAnglePitch()); CGraphics::SetModelMatrix(modelXf); } } } if (x208_lockonTimer > 0.f && x70_innerBeamIcon.IsLoaded()) { const zeus::CColor* iconColor; switch (x200_beam) { case CPlayerState::EBeamId::Power: iconColor = &g_tweakTargeting->GetInnerBeamColorPower(); break; case CPlayerState::EBeamId::Ice: iconColor = &g_tweakTargeting->GetInnerBeamColorIce(); break; case CPlayerState::EBeamId::Wave: iconColor = &g_tweakTargeting->GetInnerBeamColorWave(); break; default: iconColor = &g_tweakTargeting->GetInnerBeamColorPlasma(); break; } const zeus::CMatrix3f scale( CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetInnerBeamClampMin(), g_tweakTargeting->GetInnerBeamClampMax(), mgr) * g_tweakTargeting->GetInnerBeamScale() * (x208_lockonTimer / g_tweakTargeting->GetLockonDuration()) / x10c_currGroupInterp.GetFactor()); const zeus::CTransform modelXf(lockBreakXf * rot * scale, x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = *iconColor; color.a() *= lockBreakAlpha * visorFactor; const CModelFlags flags(7, 0, 0, lockBreakColor + color); x70_innerBeamIcon->Draw(flags); } if (x210_lockFireTimer > 0.f && x7c_lockFire.IsLoaded()) { const zeus::CMatrix3f scale(CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetLockFireClampMin(), g_tweakTargeting->GetLockFireClampMax(), mgr) * g_tweakTargeting->GetLockFireScale() / x10c_currGroupInterp.GetFactor()); const zeus::CTransform modelXf(lockBreakXf * rot * scale * zeus::CMatrix3f::RotateY(x1f0_xrayRetAngle), x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetLockFireColor(); color.a() *= visorFactor * lockBreakAlpha * (x210_lockFireTimer / g_tweakTargeting->GetLockFireDuration()); const CModelFlags flags(7, 0, 0, lockBreakColor + color); x7c_lockFire->Draw(flags); } if (x208_lockonTimer > 0.f && x88_lockDagger.IsLoaded()) { const float t = std::fabs((x210_lockFireTimer - 0.5f * g_tweakTargeting->GetLockFireDuration()) / 0.5f * g_tweakTargeting->GetLockFireDuration()); const float tscale = ((1.f - t) * g_tweakTargeting->GetLockDaggerScaleEnd() + t * g_tweakTargeting->GetLockDaggerScaleStart()); const zeus::CMatrix3f scale( CalculateClampedScale(x10c_currGroupInterp.GetTargetPositionWorld(), x10c_currGroupInterp.GetRadiusWorld(), x10c_currGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetLockDaggerClampMin(), g_tweakTargeting->GetLockDaggerClampMax(), mgr) * tscale * (x208_lockonTimer / g_tweakTargeting->GetLockonDuration()) / x10c_currGroupInterp.GetFactor()); zeus::CMatrix3f lockDaggerXf = rot * scale; for (int i = 0; i < 3; ++i) { float ang; switch (i) { case 0: ang = g_tweakTargeting->GetLockDaggerAngle0(); break; case 1: ang = g_tweakTargeting->GetLockDaggerAngle1(); break; default: ang = g_tweakTargeting->GetLockDaggerAngle2(); break; } const zeus::CTransform modelXf(lockBreakXf * lockDaggerXf * zeus::CMatrix3f::RotateY(ang), x10c_currGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetLockDaggerColor(); color.a() *= visorFactor * lockBreakAlpha; const CModelFlags flags(7, 0, 0, lockBreakColor + color); x88_lockDagger->Draw(flags); } } } } void CCompoundTargetReticle::DrawNextLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr) { if (x28_noDrawTicks > 0) { return; } zeus::CVector3f position = x174_nextGroupInterp.GetTargetPositionWorld(); float visorFactor = mgr.GetPlayerState()->GetVisorTransitionFactor(); bool scanRet = false; bool xrayRet = false; bool thermalRet = false; switch (x20_prevState) { case EReticleState::Scan: scanRet = true; break; case EReticleState::XRay: xrayRet = true; break; case EReticleState::Thermal: thermalRet = true; break; default: break; } if (!xrayRet && x174_nextGroupInterp.GetFactor() > 0.f && x40_seeker.IsLoaded()) { const zeus::CMatrix3f scale( CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(), x174_nextGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetSeekerClampMin(), g_tweakTargeting->GetSeekerClampMax(), mgr) * g_tweakTargeting->GetSeekerScale()); const zeus::CTransform modelXf(rot * zeus::CMatrix3f::RotateY(x1ec_seekerAngle) * scale, x174_nextGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetSeekerColor(); color.a() *= x174_nextGroupInterp.GetFactor(); const CModelFlags flags(7, 0, 0, color); x40_seeker->Draw(flags); } if (xrayRet && xac_xrayRetRing.IsLoaded()) { const zeus::CMatrix3f scale( CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(), x174_nextGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetReticuleClampMin(), g_tweakTargeting->GetReticuleClampMax(), mgr) * g_tweakTargeting->GetReticuleScale()); const zeus::CTransform modelXf(rot * scale * zeus::CMatrix3f::RotateY(x1f0_xrayRetAngle), x174_nextGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetXRayRetRingColor(); color.a() *= visorFactor; const CModelFlags flags(7, 0, 0, color); xac_xrayRetRing->Draw(flags); } if (thermalRet && xb8_thermalReticle.IsLoaded()) { const zeus::CMatrix3f scale( CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(), x174_nextGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetReticuleClampMin(), g_tweakTargeting->GetReticuleClampMax(), mgr) * g_tweakTargeting->GetReticuleScale()); const zeus::CTransform modelXf(rot * scale, x174_nextGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetThermalReticuleColor(); color.a() *= visorFactor; const CModelFlags flags(7, 0, 0, color); xb8_thermalReticle->Draw(flags); } if (scanRet && visorFactor > 0.f) { float factor = visorFactor * x174_nextGroupInterp.GetFactor(); const zeus::CMatrix3f scale(CalculateClampedScale(position, x174_nextGroupInterp.GetRadiusWorld(), x174_nextGroupInterp.GetMinViewportClampScale() * g_tweakTargeting->GetScanTargetClampMin(), g_tweakTargeting->GetScanTargetClampMax(), mgr) * (1.f / factor)); const zeus::CTransform modelXf(rot * scale, x174_nextGroupInterp.GetTargetPositionWorld()); CGraphics::SetModelMatrix(modelXf); // compare, GX_LESS, no update float alpha = 0.5f * factor; zeus::CColor color = g_tweakGuiColors->GetScanReticuleColor(); color.a() *= alpha; // for (size_t i = 0; i < m_scanRetRenderer.m_lineRenderers.size(); ++i) { // const float lineWidth = i != 0 ? 2.5f : 1.f; // auto& rend = *m_scanRetRenderer.m_lineRenderers[i]; // rend.Reset(); // rend.AddVertex({-0.5f, 0.f, 0.f}, color, lineWidth); // rend.AddVertex({-20.5f, 0.f, 0.f}, color, lineWidth); // rend.AddVertex({0.5f, 0.f, 0.f}, color, lineWidth); // rend.AddVertex({20.5f, 0.f, 0.f}, color, lineWidth); // rend.AddVertex({0.f, 0.f, -0.5f}, color, lineWidth); // rend.AddVertex({0.f, 0.f, -20.5f}, color, lineWidth); // rend.AddVertex({0.f, 0.f, 0.5f}, color, lineWidth); // rend.AddVertex({0.f, 0.f, 20.5f}, color, lineWidth); // rend.Render(); // // for (size_t j = 0; j < m_scanRetRenderer.m_stripRenderers[i].size(); ++j) { // const float xSign = j < 2 ? -1.f : 1.f; // const float zSign = (j & 0x1) != 0 ? -1.f : 1.f; // // begin line strip // auto& stripRend = *m_scanRetRenderer.m_stripRenderers[i][j]; // stripRend.Reset(); // stripRend.AddVertex({0.5f * xSign, 0.f, 0.1f * zSign}, color, lineWidth); // stripRend.AddVertex({0.5f * xSign, 0.f, 0.35f * zSign}, color, lineWidth); // stripRend.AddVertex({0.35f * xSign, 0.f, 0.5f * zSign}, color, lineWidth); // stripRend.AddVertex({0.1f * xSign, 0.f, 0.5f * zSign}, color, lineWidth); // stripRend.Render(); // } // } } } void CCompoundTargetReticle::DrawOrbitZoneGroup(const zeus::CMatrix3f& rot, const CStateManager& mgr) { if (x28_noDrawTicks > 0) { return; } if (x1e8_crosshairsScale > 0.f && x34_crosshairs.IsLoaded()) { CGraphics::SetModelMatrix(zeus::CTransform(rot, xf4_targetPos) * zeus::CTransform::Scale(x1e8_crosshairsScale)); zeus::CColor color = g_tweakTargeting->GetCrosshairsColor(); color.a() *= x1e8_crosshairsScale; const CModelFlags flags(7, 0, 0, color); x34_crosshairs->Draw(flags); } } void CCompoundTargetReticle::UpdateTargetParameters(CTargetReticleRenderState& state, const CStateManager& mgr) { if (const auto act = TCastToConstPtr(mgr.GetAllObjectList().GetObjectById(state.GetTargetId()))) { state.SetRadiusWorld(CalculateRadiusWorld(*act, mgr)); state.SetTargetPositionWorld(CalculatePositionWorld(*act, mgr)); } else if (state.GetIsOrbitZoneIdlePosition()) { state.SetRadiusWorld(1.f); state.SetTargetPositionWorld((x20_prevState == EReticleState::XRay || x20_prevState == EReticleState::Thermal) ? x100_laggingTargetPos : xf4_targetPos); } } float CCompoundTargetReticle::CalculateRadiusWorld(const CActor& act, const CStateManager& mgr) const { const auto tb = act.GetTouchBounds(); const zeus::CAABox aabb = tb ? *tb : zeus::CAABox(act.GetAimPosition(mgr, 0.f), act.GetAimPosition(mgr, 0.f)); float radius; const zeus::CVector3f delta = aabb.max - aabb.min; switch (g_tweakTargeting->GetTargetRadiusMode()) { case 0: { radius = std::min(delta.x(), std::min(delta.y(), delta.z())) * 0.5f; break; } case 1: { radius = std::max(delta.x(), std::max(delta.y(), delta.z())) * 0.5f; break; } default: { radius = (delta.x() + delta.y() + delta.z()) / 6.f; break; } } return radius > 0.f ? radius : 1.f; } zeus::CVector3f CCompoundTargetReticle::CalculatePositionWorld(const CActor& act, const CStateManager& mgr) const { if (x20_prevState == EReticleState::Scan) { return act.GetOrbitPosition(mgr); } return act.GetAimPosition(mgr, 0.f); } zeus::CVector3f CCompoundTargetReticle::CalculateOrbitZoneReticlePosition(const CStateManager& mgr, bool lag) const { const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr); const float distMul = 224.f / float(g_tweakPlayer->GetOrbitScreenBoxHalfExtentY(0)) / std::tan(zeus::degToRad(0.5f * curCam->GetFov())); const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr); zeus::CVector3f lookDir = camXf.basis[1]; if (lag) { lookDir = x10_laggingOrientation.transform(lookDir); } return lookDir * distMul + camXf.origin; } bool CCompoundTargetReticle::IsGrappleTarget(TUniqueId uid, const CStateManager& mgr) const { return TCastToConstPtr(mgr.GetAllObjectList().GetObjectById(uid)).operator bool(); } float CCompoundTargetReticle::CalculateClampedScale(const zeus::CVector3f& pos, float scale, float clampMin, float clampMax, const CStateManager& mgr) { const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr); mgr.GetCameraManager()->GetCurrentCameraTransform(mgr); zeus::CVector3f viewPos = cam->GetTransform().transposeRotate(pos - cam->GetTransform().origin); const float realX = cam->GetPerspectiveMatrix().multiplyOneOverW(viewPos).x(); const float offsetX = cam->GetPerspectiveMatrix().multiplyOneOverW(viewPos + zeus::CVector3f(scale, 0.f, 0.f)).x(); const float unclampedX = (offsetX - realX) * 640; return zeus::clamp(clampMin, unclampedX, clampMax) / unclampedX * scale; } void CCompoundTargetReticle::Touch() { if (x34_crosshairs.IsLoaded()) { x34_crosshairs->Touch(0); } if (x40_seeker.IsLoaded()) { x40_seeker->Touch(0); } if (x4c_lockConfirm.IsLoaded()) { x4c_lockConfirm->Touch(0); } if (x58_targetFlower.IsLoaded()) { x58_targetFlower->Touch(0); } if (x64_missileBracket.IsLoaded()) { x64_missileBracket->Touch(0); } if (x70_innerBeamIcon.IsLoaded()) { x70_innerBeamIcon->Touch(0); } if (x7c_lockFire.IsLoaded()) { x7c_lockFire->Touch(0); } if (x88_lockDagger.IsLoaded()) { x88_lockDagger->Touch(0); } if (x94_grapple.IsLoaded()) { x94_grapple->Touch(0); } if (xa0_chargeTickFirst.IsLoaded()) { xa0_chargeTickFirst->Touch(0); } if (xac_xrayRetRing.IsLoaded()) { xac_xrayRetRing->Touch(0); } if (xb8_thermalReticle.IsLoaded()) { xb8_thermalReticle->Touch(0); } if (xc4_chargeGauge.x0_model.IsLoaded()) { xc4_chargeGauge.x0_model->Touch(0); } for (SOuterItemInfo& info : xe0_outerBeamIconSquares) { if (info.x0_model.IsLoaded()) { info.x0_model->Touch(0); } } } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CCompoundTargetReticle.hpp ================================================ #pragma once #include #include #include "Runtime/CPlayerState.hpp" #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include #include namespace metaforce { class CActor; class CModel; class CScriptGrapplePoint; class CStateManager; class CTargetReticleRenderState { TUniqueId x0_target; float x4_radiusWorld; zeus::CVector3f x8_positionWorld; float x14_factor; float x18_minVpClampScale; bool x1c_orbitZoneIdlePosition; public: static const CTargetReticleRenderState skZeroRenderState; constexpr CTargetReticleRenderState(TUniqueId target, float radiusWorld, const zeus::CVector3f& positionWorld, float factor, float minVpClampScale, bool orbitZoneIdlePosition) : x0_target(target) , x4_radiusWorld(radiusWorld) , x8_positionWorld(positionWorld) , x14_factor(factor) , x18_minVpClampScale(minVpClampScale) , x1c_orbitZoneIdlePosition(orbitZoneIdlePosition) {} constexpr void SetTargetId(TUniqueId id) { x0_target = id; } constexpr void SetFactor(float factor) { x14_factor = factor; } constexpr void SetIsOrbitZoneIdlePosition(bool orbit) { x1c_orbitZoneIdlePosition = orbit; } constexpr float GetMinViewportClampScale() const { return x18_minVpClampScale; } constexpr float GetFactor() const { return x14_factor; } constexpr float GetRadiusWorld() const { return x4_radiusWorld; } constexpr const zeus::CVector3f& GetTargetPositionWorld() const { return x8_positionWorld; } constexpr bool GetIsOrbitZoneIdlePosition() const { return x1c_orbitZoneIdlePosition; } constexpr void SetTargetPositionWorld(const zeus::CVector3f& position) { x8_positionWorld = position; } constexpr void SetRadiusWorld(float radius) { x4_radiusWorld = radius; } constexpr TUniqueId GetTargetId() const { return x0_target; } constexpr void SetMinViewportClampScale(float scale) { x18_minVpClampScale = scale; } static void InterpolateWithClamp(const CTargetReticleRenderState& a, CTargetReticleRenderState& out, const CTargetReticleRenderState& b, float t); }; class CCompoundTargetReticle { public: struct SOuterItemInfo { TCachedToken x0_model; float xc_offshootBaseAngle = 0.f; float x10_rotAng = 0.f; float x14_baseAngle = 0.f; float x18_offshootAngleDelta = 0.f; explicit SOuterItemInfo(std::string_view); }; private: enum class EReticleState { Combat, Scan, XRay, Thermal, Four, Unspecified }; zeus::CQuaternion x0_leadingOrientation; zeus::CQuaternion x10_laggingOrientation; EReticleState x20_prevState = EReticleState::Unspecified; EReticleState x24_nextState = EReticleState::Unspecified; u32 x28_noDrawTicks = 0; float x2c_overshootOffsetHalf; float x30_premultOvershootOffset; TCachedToken x34_crosshairs; TCachedToken x40_seeker; TCachedToken x4c_lockConfirm; TCachedToken x58_targetFlower; TCachedToken x64_missileBracket; TCachedToken x70_innerBeamIcon; TCachedToken x7c_lockFire; TCachedToken x88_lockDagger; TCachedToken x94_grapple; TCachedToken xa0_chargeTickFirst; TCachedToken xac_xrayRetRing; TCachedToken xb8_thermalReticle; SOuterItemInfo xc4_chargeGauge; std::vector xe0_outerBeamIconSquares; TUniqueId xf0_targetId; TUniqueId xf2_nextTargetId; zeus::CVector3f xf4_targetPos; zeus::CVector3f x100_laggingTargetPos; CTargetReticleRenderState x10c_currGroupInterp = CTargetReticleRenderState::skZeroRenderState; CTargetReticleRenderState x12c_currGroupA = CTargetReticleRenderState::skZeroRenderState; CTargetReticleRenderState x14c_currGroupB = CTargetReticleRenderState::skZeroRenderState; float x16c_currGroupDur = 0.f; float x170_currGroupTimer = 0.f; CTargetReticleRenderState x174_nextGroupInterp = CTargetReticleRenderState::skZeroRenderState; CTargetReticleRenderState x194_nextGroupA = CTargetReticleRenderState::skZeroRenderState; CTargetReticleRenderState x1b4_nextGroupB = CTargetReticleRenderState::skZeroRenderState; float x1d4_nextGroupDur = 0.f; float x1d8_nextGroupTimer = 0.f; TUniqueId x1dc_grapplePoint0 = kInvalidUniqueId; TUniqueId x1de_grapplePoint1 = kInvalidUniqueId; float x1e0_grapplePoint0T = 0.f; float x1e4_grapplePoint1T = 0.f; float x1e8_crosshairsScale = 0.f; float x1ec_seekerAngle = 0.f; float x1f0_xrayRetAngle = 0.f; bool x1f4_missileActive = false; float x1f8_missileBracketTimer = 0.f; float x1fc_missileBracketScaleTimer = 0.f; CPlayerState::EBeamId x200_beam = CPlayerState::EBeamId::Power; float x204_chargeGaugeOvershootTimer = 0.f; float x208_lockonTimer; float x20c_ = 0.f; float x210_lockFireTimer = 0.f; float x214_fullChargeFadeTimer = 0.f; bool x218_beamShot = false; bool x219_missileShot = false; bool x21a_fullyCharged = false; u8 x21b_ = 0; u32 x21c_ = 0; u32 x220_ = 0; u32 x228_ = 0; void DrawGrapplePoint(const CScriptGrapplePoint& point, float t, const CStateManager& mgr, const zeus::CMatrix3f& rot, bool zEqual); public: explicit CCompoundTargetReticle(const CStateManager&); void SetLeadingOrientation(const zeus::CQuaternion& o) { x0_leadingOrientation = o; } bool CheckLoadComplete() const { return true; } EReticleState GetDesiredReticleState(const CStateManager&) const; void Update(float, const CStateManager&); void UpdateCurrLockOnGroup(float, const CStateManager&); void UpdateNextLockOnGroup(float, const CStateManager&); void UpdateOrbitZoneGroup(float, const CStateManager&); void Draw(const CStateManager&, bool hideLockon); void DrawGrappleGroup(const zeus::CMatrix3f& rot, const CStateManager&, bool); void DrawCurrLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager&); void DrawNextLockOnGroup(const zeus::CMatrix3f& rot, const CStateManager&); void DrawOrbitZoneGroup(const zeus::CMatrix3f& rot, const CStateManager&); void UpdateTargetParameters(CTargetReticleRenderState&, const CStateManager&); float CalculateRadiusWorld(const CActor&, const CStateManager&) const; zeus::CVector3f CalculatePositionWorld(const CActor&, const CStateManager&) const; zeus::CVector3f CalculateOrbitZoneReticlePosition(const CStateManager& mgr, bool lag) const; bool IsGrappleTarget(TUniqueId, const CStateManager&) const; static float CalculateClampedScale(const zeus::CVector3f&, float, float, float, const CStateManager&); void Touch(); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CConsoleOutputWindow.cpp ================================================ #include "Runtime/GuiSys/CConsoleOutputWindow.hpp" #include "Runtime/Graphics/CGraphics.hpp" namespace metaforce { CConsoleOutputWindow::CConsoleOutputWindow(int, float, float) : CIOWin("Console Output Window") {} CIOWin::EMessageReturn CConsoleOutputWindow::OnMessage(const CArchitectureMessage&, CArchitectureQueue&) { return EMessageReturn::Normal; } void CConsoleOutputWindow::Draw() { // SCOPED_GRAPHICS_DEBUG_GROUP("CConsoleOutputWindow::Draw", zeus::skGreen); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CConsoleOutputWindow.hpp ================================================ #pragma once #include "Runtime/CIOWin.hpp" namespace metaforce { class CConsoleOutputWindow : public CIOWin { public: CConsoleOutputWindow(int, float, float); EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override; void Draw() override; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CDrawStringOptions.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CGuiTextSupport.hpp" namespace metaforce { class CDrawStringOptions { friend class CColorOverrideInstruction; friend class CFontRenderState; friend class CRasterFont; friend class CTextExecuteBuffer; friend class CBlockInstruction; friend class CWordInstruction; ETextDirection x0_direction = ETextDirection::Horizontal; std::vector x4_colors; public: CDrawStringOptions() : x4_colors(16) {} }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CErrorOutputWindow.cpp ================================================ #include "Runtime/GuiSys/CErrorOutputWindow.hpp" #include "Runtime/Graphics/CGraphics.hpp" namespace metaforce { CErrorOutputWindow::CErrorOutputWindow(bool flag) : CIOWin("Error Output Window") { x18_24_ = false; x18_25_ = true; x18_26_ = true; x18_27_ = true; x18_28_ = flag; } CIOWin::EMessageReturn CErrorOutputWindow::OnMessage(const CArchitectureMessage&, CArchitectureQueue&) { return EMessageReturn::Normal; } void CErrorOutputWindow::Draw() { // SCOPED_GRAPHICS_DEBUG_GROUP("CErrorOutputWindow::Draw", zeus::skGreen); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CErrorOutputWindow.hpp ================================================ #pragma once #include "Runtime/CIOWin.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CErrorOutputWindow : public CIOWin { public: enum class State { Zero, One, Two }; private: State x14_state = State::Zero; bool x18_24_; bool x18_25_; bool x18_26_; bool x18_27_; bool x18_28_; const char16_t* x1c_msg; public: explicit CErrorOutputWindow(bool); EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override; bool GetIsContinueDraw() const override { return int(x14_state) < 2; } void Draw() override; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CFontImageDef.cpp ================================================ #include "Runtime/GuiSys/CFontImageDef.hpp" #include #include "Runtime/Graphics/CTexture.hpp" namespace metaforce { CFontImageDef::CFontImageDef(const std::vector>& texs, float interval, const zeus::CVector2f& cropFactor) : x0_fps(interval), x14_cropFactor(cropFactor) { x4_texs.reserve(texs.size()); for (const TToken& tok : texs) { x4_texs.emplace_back(tok); } } CFontImageDef::CFontImageDef(const TToken& tex, const zeus::CVector2f& cropFactor) : x0_fps(0.f), x14_cropFactor(cropFactor) { x4_texs.emplace_back(tex); } bool CFontImageDef::IsLoaded() const { return std::all_of(x4_texs.cbegin(), x4_texs.cend(), [](const auto& token) { return token.IsLoaded(); }); } s32 CFontImageDef::CalculateBaseline() const { const CTexture* tex = x4_texs.front().GetObj(); return s32(tex->GetHeight() * x14_cropFactor.y()) * 2.5f / 3.f; } s32 CFontImageDef::CalculateHeight() const { const CTexture* tex = x4_texs.front().GetObj(); s32 scaledH = tex->GetHeight() * x14_cropFactor.y(); return scaledH - (scaledH - CalculateBaseline()); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CFontImageDef.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include namespace metaforce { class CTexture; class CFontImageDef { public: float x0_fps; std::vector> x4_texs; zeus::CVector2f x14_cropFactor; CFontImageDef(const std::vector>& texs, float fps, const zeus::CVector2f& cropFactor); CFontImageDef(const TToken& tex, const zeus::CVector2f& cropFactor); bool IsLoaded() const; s32 CalculateBaseline() const; s32 CalculateHeight() const; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CFontRenderState.cpp ================================================ #include "Runtime/GuiSys/CFontRenderState.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" namespace metaforce { CFontRenderState::CFontRenderState() { x54_colors[0] = zeus::skWhite; x54_colors[1] = zeus::skGrey; x54_colors[2] = zeus::skWhite; RefreshPalette(); } zeus::CColor CFontRenderState::ConvertToTextureSpace(const CTextColor& col) const { return col; } void CFontRenderState::PopState() { static_cast(*this) = x10c_pushedStates.back(); x10c_pushedStates.pop_back(); RefreshPalette(); } void CFontRenderState::PushState() { x10c_pushedStates.push_back(*this); } void CFontRenderState::SetColor(EColorType tp, const CTextColor& col) { switch (tp) { case EColorType::Main: case EColorType::Outline: case EColorType::Geometry: x54_colors[size_t(tp)] = col; break; case EColorType::Foreground: x54_colors[0] = col; break; case EColorType::Background: x54_colors[1] = col; break; } RefreshColor(tp); } void CFontRenderState::RefreshPalette() { RefreshColor(EColorType::Foreground); RefreshColor(EColorType::Background); } void CFontRenderState::RefreshColor(EColorType tp) { switch (tp) { case EColorType::Main: if (!x48_font) return; switch (x48_font->GetMode()) { case EColorType::Main: case EColorType::Outline: if (!x64_colorOverrides[0]) { x0_drawStrOpts.x4_colors[0] = ConvertToTextureSpace(x54_colors[0]); } break; default: break; } break; case EColorType::Outline: if (!x48_font) return; if (x64_colorOverrides[1]) return; if (x48_font->GetMode() == EColorType::Outline) x0_drawStrOpts.x4_colors[1] = ConvertToTextureSpace(x54_colors[1]); break; case EColorType::Geometry: if (!x64_colorOverrides[2]) x0_drawStrOpts.x4_colors[2] = ConvertToTextureSpace(x54_colors[2]); break; case EColorType::Foreground: RefreshColor(EColorType::Main); RefreshColor(EColorType::Geometry); break; case EColorType::Background: RefreshColor(EColorType::Outline); break; } } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CFontRenderState.hpp ================================================ #pragma once #include #include #include "Runtime/GuiSys/CDrawStringOptions.hpp" #include "Runtime/GuiSys/CGuiTextSupport.hpp" #include "Runtime/GuiSys/CSaveableState.hpp" namespace metaforce { class CBlockInstruction; class CLineInstruction; class CFontRenderState : public CSaveableState { friend class CBlockInstruction; friend class CImageInstruction; friend class CLineInstruction; friend class CTextInstruction; friend class CWordInstruction; CBlockInstruction* x88_curBlock = nullptr; CDrawStringOptions x8c_drawOpts; s32 xd4_curX = 0; s32 xd8_curY = 0; const CLineInstruction* xdc_currentLineInst = nullptr; std::vector xe8_; std::vector xf8_; bool x108_lineInitialized = true; std::list x10c_pushedStates; public: CFontRenderState(); zeus::CColor ConvertToTextureSpace(const CTextColor& col) const; void PopState(); void PushState(); void SetColor(EColorType tp, const CTextColor& col); void RefreshPalette(); void RefreshColor(EColorType tp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiCamera.cpp ================================================ #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" namespace metaforce { CGuiCamera::CGuiCamera(const CGuiWidgetParms& parms, float left, float right, float top, float bottom, float znear, float zfar) : CGuiWidget(parms), xb8_projtype(EProjection::Orthographic), m_proj(left, right, top, bottom, znear, zfar) {} CGuiCamera::CGuiCamera(const CGuiWidgetParms& parms, float fov, float aspect, float znear, float zfar) : CGuiWidget(parms), xb8_projtype(EProjection::Perspective), m_proj(fov, aspect, znear, zfar) {} zeus::CVector3f CGuiCamera::ConvertToScreenSpace(const zeus::CVector3f& vec) const { zeus::CVector3f local = RotateTranslateW2O(vec); if (local.isZero()) return {-1.f, -1.f, 1.f}; zeus::CMatrix4f mat = CGraphics::CalculatePerspectiveMatrix(m_proj.xbc_fov, m_proj.xc0_aspect, m_proj.xc4_znear, m_proj.xc8_zfar); local = zeus::CVector3f(local.x(), local.z(), -local.y()); return mat.multiplyOneOverW(local); } void CGuiCamera::Draw(const CGuiWidgetDrawParms& parms) { if (xb8_projtype == EProjection::Perspective) { CGraphics::SetPerspective(m_proj.xbc_fov, m_proj.xc0_aspect, m_proj.xc4_znear, m_proj.xc8_zfar); } else { CGraphics::SetOrtho(m_proj.xbc_left, m_proj.xc0_right, m_proj.xc4_top, m_proj.xc8_bottom, m_proj.xcc_znear, m_proj.xd0_zfar); } CGraphics::SetViewPointMatrix(GetGuiFrame()->GetAspectTransform() * zeus::CTransform::Translate(parms.x4_cameraOffset) * x34_worldXF); CGuiWidget::Draw(parms); } std::shared_ptr CGuiCamera::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); EProjection proj = EProjection(in.ReadLong()); std::shared_ptr ret = {}; switch (proj) { case EProjection::Perspective: { float fov = in.ReadFloat(); float aspect = in.ReadFloat(); float znear = in.ReadFloat(); float zfar = in.ReadFloat(); ret = std::make_shared(parms, fov, aspect, znear, zfar); break; } case EProjection::Orthographic: { float left = in.ReadFloat(); float right = in.ReadFloat(); float top = in.ReadFloat(); float bottom = in.ReadFloat(); float znear = in.ReadFloat(); float zfar = in.ReadFloat(); ret = std::make_shared(parms, left, right, top, bottom, znear, zfar); break; } } frame->SetFrameCamera(ret->shared_from_this()); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiCamera.hpp ================================================ #pragma once #include #include "Runtime/GuiSys/CGuiWidget.hpp" namespace metaforce { class CSimplePool; class CGuiCamera : public CGuiWidget { public: enum class EProjection { Perspective, Orthographic }; struct SProjection { union { struct { float xbc_left; float xc0_right; float xc4_top; float xc8_bottom; float xcc_znear; float xd0_zfar; }; struct { float xbc_fov; float xc0_aspect; float xc4_znear; float xc8_zfar; }; }; SProjection(float left, float right, float top, float bottom, float znear, float zfar) : xbc_left(left), xc0_right(right), xc4_top(top), xc8_bottom(bottom), xcc_znear(znear), xd0_zfar(zfar) {} SProjection(float fov, float aspect, float znear, float zfar) : xbc_fov(fov), xc0_aspect(aspect), xc4_znear(znear), xc8_zfar(zfar) {} }; private: EProjection xb8_projtype; SProjection m_proj; public: CGuiCamera(const CGuiWidgetParms& parms, float left, float right, float top, float bottom, float znear, float zfar); CGuiCamera(const CGuiWidgetParms& parms, float fov, float aspect, float znear, float zfar); FourCC GetWidgetTypeID() const override { return FOURCC('CAMR'); } static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); zeus::CVector3f ConvertToScreenSpace(const zeus::CVector3f& vec) const; const SProjection& GetProjection() const { return m_proj; } void SetFov(float fov) { m_proj.xbc_fov = fov; } void Draw(const CGuiWidgetDrawParms& parms) override; std::shared_ptr shared_from_this() { return std::static_pointer_cast(CGuiObject::shared_from_this()); } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiCompoundWidget.cpp ================================================ #include "Runtime/GuiSys/CGuiCompoundWidget.hpp" namespace metaforce { CGuiCompoundWidget::CGuiCompoundWidget(const CGuiWidgetParms& parms) : CGuiWidget(parms) {} void CGuiCompoundWidget::OnVisibleChange() { CGuiWidget* child = static_cast(GetChildObject()); while (child) { child->SetIsVisible(GetIsVisible()); child = static_cast(child->GetNextSibling()); } CGuiWidget::OnVisibleChange(); } void CGuiCompoundWidget::OnActiveChange() { CGuiWidget* child = static_cast(GetChildObject()); while (child) { child->SetIsActive(GetIsActive()); child = static_cast(child->GetNextSibling()); } CGuiWidget::OnActiveChange(); } CGuiWidget* CGuiCompoundWidget::GetWorkerWidget(int id) const { CGuiWidget* child = static_cast(GetChildObject()); while (child) { if (child->GetWorkerId() == id) return child; child = static_cast(child->GetNextSibling()); } return nullptr; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiCompoundWidget.hpp ================================================ #pragma once #include "Runtime/GuiSys/CGuiWidget.hpp" namespace metaforce { class CGuiCompoundWidget : public CGuiWidget { public: explicit CGuiCompoundWidget(const CGuiWidgetParms& parms); FourCC GetWidgetTypeID() const override { return FourCC(-1); } void OnVisibleChange() override; void OnActiveChange() override; virtual CGuiWidget* GetWorkerWidget(int id) const; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiFrame.cpp ================================================ #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/Graphics/CModel.hpp" #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/GuiSys/CGuiHeadWidget.hpp" #include "Runtime/GuiSys/CGuiLight.hpp" #include "Runtime/GuiSys/CGuiSys.hpp" #include "Runtime/GuiSys/CGuiTableGroup.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" #include "Runtime/Input/CFinalInput.hpp" #include "Runtime/Formatting.hpp" #include namespace metaforce { CGuiFrame::CGuiFrame(CAssetId id, CGuiSys& sys, int a, int b, int c, CSimplePool* sp) : x0_id(id), x8_guiSys(sys), x4c_a(a), x50_b(b), x54_c(c) { x3c_lights.reserve(8); m_indexedLights.reserve(8); x10_rootWidget = std::make_unique(CGuiWidget::CGuiWidgetParms( this, false, 0, 0, false, false, false, zeus::skWhite, CGuiWidget::EGuiModelDrawFlags::Alpha, false, x8_guiSys.x8_mode != CGuiSys::EUsageMode::Zero, ""s)); x8_guiSys.m_registeredFrames.insert(this); } CGuiFrame::~CGuiFrame() { x8_guiSys.m_registeredFrames.erase(this); } CGuiWidget* CGuiFrame::FindWidget(std::string_view name) const { s16 id = x18_idDB.FindWidgetID(name); if (id == -1) return nullptr; return FindWidget(id); } CGuiWidget* CGuiFrame::FindWidget(s16 id) const { return x10_rootWidget->FindWidget(id); } void CGuiFrame::SortDrawOrder() { std::sort(x2c_widgets.begin(), x2c_widgets.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { return a->GetWorldPosition().y() > b->GetWorldPosition().y(); }); } void CGuiFrame::EnableLights(ERglLight lights) const { CGraphics::DisableAllLights(); zeus::CColor ambColor(zeus::skBlack); ERglLight lightId = 0; int enabledLights = 0; for (CGuiLight* light : m_indexedLights) { if (light == nullptr || !light->GetIsVisible()) { ++lightId; continue; } if ((lights & (1 << lightId)) != 0) { const auto& geomCol = light->GetGeometryColor(); if (geomCol.r() != 0.f || geomCol.g() != 0.f || geomCol.b() != 0.f) { CGraphics::LoadLight(lightId, light->BuildLight()); CGraphics::EnableLight(lightId); } // accumulate ambient color ambColor += light->GetAmbientLightColor(); ++enabledLights; } ++lightId; } if (enabledLights == 0) { CGraphics::SetAmbientColor(zeus::skWhite); } else { CGraphics::SetAmbientColor(ambColor); } } void CGuiFrame::DisableLights() const { CGraphics::DisableAllLights(); } void CGuiFrame::RemoveLight(CGuiLight* light) { if (m_indexedLights.empty()) return; m_indexedLights[light->GetLightId()] = nullptr; } void CGuiFrame::AddLight(CGuiLight* light) { if (m_indexedLights.empty()) m_indexedLights.resize(8); m_indexedLights[light->GetLightId()] = light; } void CGuiFrame::RegisterLight(std::shared_ptr&& light) { x3c_lights.push_back(std::move(light)); } bool CGuiFrame::GetIsFinishedLoading() const { if (x58_24_loaded) return true; for (const auto& widget : x2c_widgets) { if (widget->GetIsFinishedLoading()) continue; return false; } x58_24_loaded = true; return true; } void CGuiFrame::Touch() const { for (const auto& widget : x2c_widgets) widget->Touch(); } void CGuiFrame::SetAspectConstraint(float c) { m_aspectConstraint = c; CGuiSys::ViewportResizeFrame(this); } void CGuiFrame::SetMaxAspect(float c) { m_maxAspect = c; CGuiSys::ViewportResizeFrame(this); } void CGuiFrame::Reset() { x10_rootWidget->Reset(ETraversalMode::Children); } void CGuiFrame::Update(float dt) { xc_headWidget->Update(dt); } void CGuiFrame::Draw(const CGuiWidgetDrawParms& parms) const { SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format("CGuiFrame::Draw FRME_{}", x0_id).c_str(), zeus::skMagenta); CGraphics::SetCullMode(ERglCullMode::None); CGraphics::ResetGfxStates(); CGraphics::SetAmbientColor(zeus::skWhite); DisableLights(); x14_camera->Draw(parms); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); for (const auto& widget : x2c_widgets) if (widget->GetIsVisible()) widget->Draw(parms); CGraphics::SetCullMode(ERglCullMode::Front); } CGuiWidget* CGuiFrame::BestCursorHit(const zeus::CVector2f& point, const CGuiWidgetDrawParms& parms) const { x14_camera->Draw(parms); zeus::CMatrix4f vp = CGraphics::GetPerspectiveProjectionMatrix(); // TODO * CGraphics::mCameraMtx; CGuiWidget* ret = nullptr; for (const auto& widget : x2c_widgets) if (widget->GetMouseActive() && widget->TestCursorHit(vp, point)) ret = widget.get(); return ret; } void CGuiFrame::Initialize() { SortDrawOrder(); xc_headWidget->SetColor(xc_headWidget->xa4_color); xc_headWidget->DispatchInitialize(); } void CGuiFrame::LoadWidgetsInGame(CInputStream& in, CSimplePool* sp, u32 version) { u32 count = in.ReadLong(); x2c_widgets.reserve(count); for (u32 i = 0; i < count; ++i) { FourCC type; in.Get(reinterpret_cast(&type), 4); std::shared_ptr widget = CGuiSys::CreateWidgetInGame(type, in, this, sp, version); switch (widget->GetWidgetTypeID().toUint32()) { case SBIG('CAMR'): case SBIG('LITE'): case SBIG('BGND'): break; default: x2c_widgets.push_back(std::move(widget)); break; } } Initialize(); } void CGuiFrame::ProcessUserInput(const CFinalInput& input) const { if (input.ControllerIdx() != 0) { return; } for (const auto& widget : x2c_widgets) { if (widget->GetIsActive()) { widget->ProcessUserInput(input); } } } bool CGuiFrame::ProcessMouseInput(const CFinalInput& input, const CGuiWidgetDrawParms& parms) { if (const auto& kbm = input.GetKBM()) { zeus::CVector2f point(kbm->m_mouseCoord.norm[0] * 2.f - 1.f, kbm->m_mouseCoord.norm[1] * 2.f - 1.f); CGuiWidget* hit = BestCursorHit(point, parms); if (hit != m_lastMouseOverWidget) { if (m_inMouseDown && m_mouseDownWidget != hit) { m_inCancel = true; if (m_mouseUpCb) m_mouseUpCb(m_mouseDownWidget, true); } else if (m_inCancel && m_mouseDownWidget == hit) { m_inCancel = false; if (m_mouseDownCb) m_mouseDownCb(m_mouseDownWidget, true); } if (m_mouseOverChangeCb) m_mouseOverChangeCb(m_lastMouseOverWidget, hit); if (hit) hit->m_lastScroll.emplace(kbm->m_accumScroll); m_lastMouseOverWidget = hit; } if (hit && hit->m_lastScroll) { SScrollDelta delta = kbm->m_accumScroll - *hit->m_lastScroll; hit->m_lastScroll.emplace(kbm->m_accumScroll); if (!delta.isZero()) { hit->m_integerScroll += delta; if (m_mouseScrollCb) m_mouseScrollCb(hit, delta, int(hit->m_integerScroll.delta[0]), int(hit->m_integerScroll.delta[1])); hit->m_integerScroll.delta[0] -= std::trunc(hit->m_integerScroll.delta[0]); hit->m_integerScroll.delta[1] -= std::trunc(hit->m_integerScroll.delta[1]); } } if (!m_inMouseDown && kbm->m_mouseButtons[size_t(EMouseButton::Primary)]) { m_inMouseDown = true; m_inCancel = false; m_mouseDownWidget = hit; if (m_mouseDownCb) m_mouseDownCb(hit, false); if (hit) return true; } else if (m_inMouseDown && !kbm->m_mouseButtons[size_t(EMouseButton::Primary)]) { m_inMouseDown = false; m_inCancel = false; if (m_mouseDownWidget == m_lastMouseOverWidget) { if (m_mouseDownWidget) { if (CGuiTableGroup* p = static_cast(m_mouseDownWidget->GetParent())) { if (p->GetWidgetTypeID() == FOURCC('TBGP')) { s16 workerIdx = m_mouseDownWidget->GetWorkerId(); if (workerIdx >= 0) p->DoSelectWorker(workerIdx); } } } if (m_mouseUpCb) m_mouseUpCb(m_mouseDownWidget, false); } } } return false; } void CGuiFrame::ResetMouseState() { m_inMouseDown = false; m_inCancel = false; m_mouseDownWidget = nullptr; m_lastMouseOverWidget = nullptr; } std::unique_ptr CGuiFrame::CreateFrame(CAssetId frmeId, CGuiSys& sys, CInputStream& in, CSimplePool* sp) { u32 version = in.ReadLong(); int a = in.ReadLong(); int b = in.ReadLong(); int c = in.ReadLong(); std::unique_ptr ret = std::make_unique(frmeId, sys, a, b, c, sp); ret->LoadWidgetsInGame(in, sp, version); return ret; } std::unique_ptr RGuiFrameFactoryInGame(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& cvParms, CObjectReference* selfRef) { CSimplePool* sp = cvParms.GetOwnedObj(); std::unique_ptr frame(CGuiFrame::CreateFrame(tag.id, *g_GuiSys, in, sp)); return TToken::GetIObjObjectFor(std::move(frame)); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiFrame.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/IObj.hpp" #include "Runtime/GuiSys/CGuiHeadWidget.hpp" #include "Runtime/GuiSys/CGuiWidgetIdDB.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include "Runtime/Graphics/CGraphics.hpp" namespace metaforce { class CGuiCamera; class CGuiLight; class CGuiSys; class CLight; class CObjectReference; class CSimplePool; class CVParamTransfer; struct CFinalInput; class CGuiFrame { friend class CGuiSys; private: std::vector m_indexedLights; CAssetId x0_id; u32 x4_ = 0; CGuiSys& x8_guiSys; std::shared_ptr xc_headWidget; std::shared_ptr x10_rootWidget; std::shared_ptr x14_camera; CGuiWidgetIdDB x18_idDB; std::vector> x2c_widgets; std::vector> x3c_lights; int x4c_a; int x50_b; int x54_c; mutable bool x58_24_loaded : 1 = false; zeus::CTransform m_aspectTransform; float m_aspectConstraint = -1.f; float m_maxAspect = -1.f; bool m_inMouseDown = false; bool m_inCancel = false; CGuiWidget* m_mouseDownWidget = nullptr; CGuiWidget* m_lastMouseOverWidget = nullptr; std::function m_mouseOverChangeCb; std::function m_mouseDownCb; std::function m_mouseUpCb; std::function m_mouseScrollCb; public: CGuiFrame(CAssetId id, CGuiSys& sys, int a, int b, int c, CSimplePool* sp); ~CGuiFrame(); CGuiSys& GetGuiSys() { return x8_guiSys; } const CGuiSys& GetGuiSys() const { return x8_guiSys; } CAssetId GetAssetId() const { return x0_id; } CGuiLight* GetFrameLight(int idx) const { return m_indexedLights[idx]; } CGuiCamera* GetFrameCamera() const { return x14_camera.get(); } CGuiWidget* FindWidget(std::string_view name) const; CGuiWidget* FindWidget(s16 id) const; void SetFrameCamera(std::shared_ptr&& camr) { x14_camera = std::move(camr); } void SetHeadWidget(std::shared_ptr&& hwig) { xc_headWidget = std::move(hwig); } CGuiHeadWidget* GetHeadWidget() const { return xc_headWidget.get(); } void SortDrawOrder(); void EnableLights(ERglLight lights) const; void DisableLights() const; void RemoveLight(CGuiLight* light); void AddLight(CGuiLight* light); void RegisterLight(std::shared_ptr&& light); bool GetIsFinishedLoading() const; void Touch() const; const zeus::CTransform& GetAspectTransform() const { return m_aspectTransform; } void SetAspectConstraint(float c); void SetMaxAspect(float c); void SetMouseOverChangeCallback(std::function&& cb) { m_mouseOverChangeCb = std::move(cb); } void SetMouseDownCallback(std::function&& cb) { m_mouseDownCb = std::move(cb); } void SetMouseUpCallback(std::function&& cb) { m_mouseUpCb = std::move(cb); } void SetMouseScrollCallback(std::function&& cb) { m_mouseScrollCb = std::move(cb); } void Reset(); void Update(float dt); void Draw(const CGuiWidgetDrawParms& parms) const; CGuiWidget* BestCursorHit(const zeus::CVector2f& point, const CGuiWidgetDrawParms& parms) const; void Initialize(); void LoadWidgetsInGame(CInputStream& in, CSimplePool* sp, u32 version); void ProcessUserInput(const CFinalInput& input) const; bool ProcessMouseInput(const CFinalInput& input, const CGuiWidgetDrawParms& parms); void ResetMouseState(); CGuiWidgetIdDB& GetWidgetIdDB() { return x18_idDB; } const CGuiWidgetIdDB& GetWidgetIdDB() const { return x18_idDB; } static std::unique_ptr CreateFrame(CAssetId frmeId, CGuiSys& sys, CInputStream& in, CSimplePool* sp); }; std::unique_ptr RGuiFrameFactoryInGame(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiGroup.cpp ================================================ #include "Runtime/GuiSys/CGuiGroup.hpp" namespace metaforce { void CGuiGroup::LoadWidgetFnMap() {} CGuiGroup::CGuiGroup(const CGuiWidgetParms& parms, int defaultWorker, bool b) : CGuiCompoundWidget(parms), xbc_selectedWorker(defaultWorker), xc0_b(b) {} void CGuiGroup::SelectWorkerWidget(int workerId, bool setActive, bool setVisible) { CGuiWidget* child = static_cast(GetChildObject()); while (child) { if (child->GetWorkerId() == workerId) { CGuiWidget* sel = GetSelectedWidget(); if (setActive) { sel->SetIsActive(false); child->SetIsActive(true); } if (setVisible) { sel->SetVisibility(false, ETraversalMode::Single); child->SetVisibility(true, ETraversalMode::Single); } break; } child = static_cast(child->GetNextSibling()); } } CGuiWidget* CGuiGroup::GetSelectedWidget() { return GetWorkerWidget(xbc_selectedWorker); } const CGuiWidget* CGuiGroup::GetSelectedWidget() const { return GetWorkerWidget(xbc_selectedWorker); } bool CGuiGroup::AddWorkerWidget(CGuiWidget* worker) { ++xb8_workerCount; return true; } void CGuiGroup::OnActiveChange() { CGuiWidget* sel = GetSelectedWidget(); if (sel) sel->SetIsActive(true); } std::shared_ptr CGuiGroup::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); s16 defaultWorker = in.ReadInt16(); bool b = in.ReadBool(); std::shared_ptr ret = std::make_shared(parms, defaultWorker, b); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiGroup.hpp ================================================ #pragma once #include #include "Runtime/GuiSys/CGuiCompoundWidget.hpp" namespace metaforce { class CGuiGroup : public CGuiCompoundWidget { u32 xb8_workerCount = 0; int xbc_selectedWorker; bool xc0_b; public: CGuiGroup(const CGuiWidgetParms& parms, int defaultWorker, bool b); FourCC GetWidgetTypeID() const override { return FOURCC('GRUP'); } void SelectWorkerWidget(int workerId, bool setActive, bool setVisible); CGuiWidget* GetSelectedWidget(); const CGuiWidget* GetSelectedWidget() const; bool AddWorkerWidget(CGuiWidget* worker) override; void OnActiveChange() override; static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); static void LoadWidgetFnMap(); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiHeadWidget.cpp ================================================ #include "Runtime/GuiSys/CGuiHeadWidget.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" namespace metaforce { CGuiHeadWidget::CGuiHeadWidget(const CGuiWidgetParms& parms) : CGuiWidget(parms) {} std::shared_ptr CGuiHeadWidget::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); std::shared_ptr ret = std::make_shared(parms); frame->SetHeadWidget(ret->shared_from_this()); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiHeadWidget.hpp ================================================ #pragma once #include "Runtime/GuiSys/CGuiWidget.hpp" namespace metaforce { class CGuiHeadWidget : public CGuiWidget { public: FourCC GetWidgetTypeID() const override { return FOURCC('HWIG'); } explicit CGuiHeadWidget(const CGuiWidgetParms& parms); static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); std::shared_ptr shared_from_this() { return std::static_pointer_cast(CGuiObject::shared_from_this()); } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiLight.cpp ================================================ #include "Runtime/GuiSys/CGuiLight.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" namespace metaforce { CGuiLight::CGuiLight(const CGuiWidgetParms& parms, const CLight& light) : CGuiWidget(parms) , xb8_type(light.x1c_type) , xbc_spotCutoff(light.x20_spotCutoff) , xc0_distC(light.x24_distC) , xc4_distL(light.x28_distL) , xc8_distQ(light.x2c_distQ) , xcc_angleC(light.x30_angleC) , xd0_angleL(light.x34_angleL) , xd4_angleQ(light.x38_angleQ) , xd8_lightId(light.x40_lightId) {} CGuiLight::~CGuiLight() { xb0_frame->RemoveLight(this); } CLight CGuiLight::BuildLight() const { CLight ret = CLight::BuildLocalAmbient(zeus::skZero3f, zeus::skBlack); switch (xb8_type) { case ELightType::Spot: ret = CLight::BuildSpot(GetWorldPosition(), x34_worldXF.basis[1], xa4_color, xbc_spotCutoff); break; case ELightType::Point: ret = CLight::BuildPoint(GetWorldPosition(), xa4_color); break; case ELightType::Directional: ret = CLight::BuildDirectional(x34_worldXF.basis[1], xa4_color); break; default: break; } ret.SetAttenuation(xc0_distC, xc4_distL, xc8_distQ); ret.SetAngleAttenuation(xcc_angleC, xd0_angleL, xd4_angleQ); return ret; } void CGuiLight::SetIsVisible(bool vis) { if (vis) xb0_frame->AddLight(this); else xb0_frame->RemoveLight(this); CGuiWidget::SetIsVisible(vis); } std::shared_ptr CGuiLight::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); ELightType tp = ELightType(in.ReadLong()); float distC = in.ReadFloat(); float distL = in.ReadFloat(); float distQ = in.ReadFloat(); float angC = in.ReadFloat(); float angL = in.ReadFloat(); float angQ = in.ReadFloat(); u32 lightId = in.ReadLong(); std::shared_ptr ret = {}; switch (tp) { case ELightType::Spot: { float cutoff = in.ReadFloat(); CLight lt = CLight::BuildSpot(zeus::skZero3f, zeus::skZero3f, parms.x10_color, cutoff); lt.SetAttenuation(distC, distL, distQ); lt.SetAngleAttenuation(angC, angL, angQ); lt.x40_lightId = lightId; ret = std::make_shared(parms, lt); break; } case ELightType::Point: { CLight lt = CLight::BuildPoint(zeus::skZero3f, parms.x10_color); lt.SetAttenuation(distC, distL, distQ); lt.x40_lightId = lightId; ret = std::make_shared(parms, lt); break; } case ELightType::Directional: { CLight lt = CLight::BuildDirectional(zeus::skZero3f, parms.x10_color); lt.x40_lightId = lightId; ret = std::make_shared(parms, lt); break; } default: break; } ret->ParseBaseInfo(frame, in, parms); frame->RegisterLight(ret->shared_from_this()); frame->AddLight(ret.get()); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiLight.hpp ================================================ #pragma once #include #include "Runtime/Graphics/CLight.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include namespace metaforce { class CSimplePool; class CGuiLight : public CGuiWidget { ELightType xb8_type; float xbc_spotCutoff; float xc0_distC; float xc4_distL; float xc8_distQ; float xcc_angleC; float xd0_angleL; float xd4_angleQ; u32 xd8_lightId; zeus::CColor xdc_ambColor = zeus::skBlack; public: ~CGuiLight() override; CGuiLight(const CGuiWidgetParms& parms, const CLight& light); FourCC GetWidgetTypeID() const override { return FOURCC('LITE'); } CLight BuildLight() const; void SetIsVisible(bool vis) override; u32 GetLightId() const { return xd8_lightId; } const zeus::CColor& GetAmbientLightColor() const { return xdc_ambColor; } void SetSpotCutoff(float v) { xbc_spotCutoff = v; } void SetDistC(float v) { xc0_distC = v; } void SetDistL(float v) { xc4_distL = v; } void SetDistQ(float v) { xc8_distQ = v; } void SetAngleC(float v) { xcc_angleC = v; } void SetAngleL(float v) { xd0_angleL = v; } void SetAngleQ(float v) { xd4_angleQ = v; } void SetLightId(u32 idx) { xd8_lightId = idx; } void SetAmbientLightColor(const zeus::CColor& color) { xdc_ambColor = color; } static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); std::shared_ptr shared_from_this() { return std::static_pointer_cast(CGuiObject::shared_from_this()); } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiModel.cpp ================================================ #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiSys.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" namespace metaforce { CGuiModel::CGuiModel(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId modelId, u32 lightMask, bool flag) : CGuiWidget(parms), xc8_modelId(modelId), xcc_lightMask(lightMask) { if (!flag || !modelId.IsValid() || parms.x0_frame->GetGuiSys().GetUsageMode() == CGuiSys::EUsageMode::Two) { return; } xb8_model = sp->GetObj({SBIG('CMDL'), modelId}); } bool CGuiModel::GetIsFinishedLoadingWidgetSpecific() { if (!xb8_model) { return true; } if (!xb8_model.IsLoaded()) { return false; } xb8_model->Touch(0); return xb8_model->IsLoaded(0); } void CGuiModel::Touch() { if (CModel* const model = xb8_model.GetObj()) { model->Touch(0); } } void CGuiModel::Draw(const CGuiWidgetDrawParms& parms) { CGraphics::SetModelMatrix(x34_worldXF); if (!xb8_model) { return; } if (!GetIsFinishedLoading()) { return; } CModel* const model = xb8_model.GetObj(); if (model == nullptr) { return; } if (GetIsVisible()) { SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format("CGuiModel::Draw {}", m_name).c_str(), zeus::skCyan); zeus::CColor moduCol = xa8_color2; moduCol.a() *= parms.x0_alphaMod; xb0_frame->EnableLights(xcc_lightMask); if (xb6_29_cullFaces) { CGraphics::SetCullMode(ERglCullMode::Front); } switch (xac_drawFlags) { case EGuiModelDrawFlags::Shadeless: { constexpr CModelFlags flags(0, 0, 3, zeus::skWhite); model->Draw(flags); break; } case EGuiModelDrawFlags::Opaque: { CModelFlags flags(1, 0, 3, moduCol); model->Draw(flags); break; } case EGuiModelDrawFlags::Alpha: { CModelFlags flags(5, 0, (u32(xb7_24_depthWrite) << 1) | u32(xb6_31_depthTest), moduCol); model->Draw(flags); break; } case EGuiModelDrawFlags::Additive: { CModelFlags flags(7, 0, (u32(xb7_24_depthWrite) << 1) | u32(xb6_31_depthTest), moduCol); model->Draw(flags); break; } case EGuiModelDrawFlags::AlphaAdditiveOverdraw: { const CModelFlags flags(5, 0, (u32(xb6_30_depthGreater) << 4) | u32(xb6_31_depthTest), moduCol); model->Draw(flags); const CModelFlags overdrawFlags( 8, 0, (u32(xb6_30_depthGreater) << 4) | (u32(xb7_24_depthWrite) << 1) | u32(xb6_31_depthTest), moduCol); model->Draw(overdrawFlags); break; } default: break; } if (xb6_29_cullFaces) { CGraphics::SetCullMode(ERglCullMode::None); } xb0_frame->DisableLights(); } CGuiWidget::Draw(parms); } bool CGuiModel::TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const { if (!xb8_model || !xb8_model.IsLoaded()) { return false; } return xb8_model->GetAABB().projectedPointTest(vp * x34_worldXF.toMatrix4f(), point); } std::shared_ptr CGuiModel::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); auto model = in.Get(); in.ReadLong(); u32 lightMask = in.ReadLong(); std::shared_ptr ret = std::make_shared(parms, sp, model, lightMask, true); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiModel.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/Graphics/CModel.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" namespace metaforce { class CSimplePool; class CGuiModel : public CGuiWidget { TLockedToken xb8_model; CAssetId xc8_modelId; u32 xcc_lightMask; public: CGuiModel(const CGuiWidgetParms& parms, CSimplePool* sp, CAssetId modelId, u32 lightMask, bool flag); FourCC GetWidgetTypeID() const override { return FOURCC('MODL'); } std::vector GetModelAssets() const { return {xc8_modelId}; } const TLockedToken& GetModel() const { return xb8_model; } bool GetIsFinishedLoadingWidgetSpecific() override; void Touch() override; void Draw(const CGuiWidgetDrawParms& parms) override; bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const override; static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiObject.cpp ================================================ #include "Runtime/GuiSys/CGuiObject.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" namespace metaforce { void CGuiObject::Update(float dt) { if (x68_child) x68_child->Update(dt); if (x6c_nextSibling) x6c_nextSibling->Update(dt); } void CGuiObject::Draw(const CGuiWidgetDrawParms& parms) { if (x68_child) x68_child->Draw(parms); if (x6c_nextSibling) x6c_nextSibling->Draw(parms); } void CGuiObject::MoveInWorld(const zeus::CVector3f& vec) { // if (x64_parent) // x64_parent->RotateW2O(vec); x4_localXF.origin += vec; RecalculateTransforms(); } void CGuiObject::SetLocalPosition(const zeus::CVector3f& pos) { MoveInWorld(pos - x4_localXF.origin); } void CGuiObject::RotateReset() { x4_localXF.basis = zeus::CMatrix3f(); RecalculateTransforms(); } zeus::CVector3f CGuiObject::RotateW2O(const zeus::CVector3f& vec) const { return x34_worldXF.transposeRotate(vec); } zeus::CVector3f CGuiObject::RotateO2P(const zeus::CVector3f& vec) const { return x4_localXF.rotate(vec); } zeus::CVector3f CGuiObject::RotateTranslateW2O(const zeus::CVector3f& vec) const { return x34_worldXF.transposeRotate(vec - x34_worldXF.origin); } void CGuiObject::MultiplyO2P(const zeus::CTransform& xf) { x4_localXF = xf * x4_localXF; RecalculateTransforms(); } void CGuiObject::AddChildObject(CGuiObject* obj, bool makeWorldLocal, bool atEnd) { obj->x64_parent = this; if (!x68_child) { x68_child = obj; } else if (atEnd) { CGuiObject* prev = nullptr; CGuiObject* cur = x68_child; for (; cur; cur = cur->x6c_nextSibling) { prev = cur; } if (prev) prev->x6c_nextSibling = obj; } else { obj->x6c_nextSibling = x68_child; x68_child = obj; } if (makeWorldLocal) { zeus::CVector3f negParentWorld = -x34_worldXF.origin; zeus::CMatrix3f basisMat(x34_worldXF.basis[0] / x34_worldXF.basis[0].magnitude(), x34_worldXF.basis[1] / x34_worldXF.basis[1].magnitude(), x34_worldXF.basis[2] / x34_worldXF.basis[2].magnitude()); zeus::CVector3f xfWorld = basisMat * negParentWorld; obj->x4_localXF = zeus::CTransform(basisMat, xfWorld) * obj->x34_worldXF; } RecalculateTransforms(); } CGuiObject* CGuiObject::RemoveChildObject(CGuiObject* obj, bool makeWorldLocal) { CGuiObject* prev = nullptr; CGuiObject* cur = x68_child; for (; cur && cur != obj; cur = cur->x6c_nextSibling) { prev = cur; } if (!cur) return nullptr; if (prev) prev->x6c_nextSibling = cur->x6c_nextSibling; cur->x6c_nextSibling = nullptr; cur->x64_parent = nullptr; if (makeWorldLocal) cur->x4_localXF = cur->x34_worldXF; cur->RecalculateTransforms(); return cur; } void CGuiObject::RecalculateTransforms() { if (x64_parent) x34_worldXF = x64_parent->x34_worldXF * x4_localXF; else x34_worldXF = x4_localXF; if (x6c_nextSibling) x6c_nextSibling->RecalculateTransforms(); if (x68_child) x68_child->RecalculateTransforms(); } void CGuiObject::SetO2WTransform(const zeus::CTransform& xf) { x4_localXF = GetParent()->x34_worldXF.inverse() * xf; RecalculateTransforms(); } void CGuiObject::SetLocalTransform(const zeus::CTransform& xf) { x4_localXF = xf; RecalculateTransforms(); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiObject.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include #include #include #include namespace metaforce { struct CGuiWidgetDrawParms; class CGuiObject : public std::enable_shared_from_this { protected: zeus::CTransform m_initLocalXF; zeus::CTransform x4_localXF; zeus::CTransform x34_worldXF; CGuiObject* x64_parent = nullptr; CGuiObject* x68_child = nullptr; CGuiObject* x6c_nextSibling = nullptr; public: virtual ~CGuiObject() = default; virtual void Update(float dt); virtual void Draw(const CGuiWidgetDrawParms& parms); virtual bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const { return false; } virtual void Initialize() = 0; void MoveInWorld(const zeus::CVector3f& vec); const zeus::CVector3f& GetInitialLocalPosition() const { return m_initLocalXF.origin; } const zeus::CTransform& GetInitialLocalTransform() const { return m_initLocalXF; } const zeus::CVector3f& GetLocalPosition() const { return x4_localXF.origin; } const zeus::CTransform& GetLocalTransform() const { return x4_localXF; } void SetLocalPosition(const zeus::CVector3f& pos); const zeus::CVector3f& GetWorldPosition() const { return x34_worldXF.origin; } const zeus::CTransform& GetWorldTransform() const { return x34_worldXF; } void RotateReset(); zeus::CVector3f RotateW2O(const zeus::CVector3f& vec) const; zeus::CVector3f RotateO2P(const zeus::CVector3f& vec) const; zeus::CVector3f RotateTranslateW2O(const zeus::CVector3f& vec) const; void MultiplyO2P(const zeus::CTransform& xf); void AddChildObject(CGuiObject* obj, bool makeWorldLocal, bool atEnd); CGuiObject* RemoveChildObject(CGuiObject* obj, bool makeWorldLocal); CGuiObject* GetParent() const { return x64_parent; } CGuiObject* GetChildObject() const { return x68_child; } CGuiObject* GetNextSibling() const { return x6c_nextSibling; } void RecalculateTransforms(); void SetO2WTransform(const zeus::CTransform& xf); void SetLocalTransform(const zeus::CTransform& xf); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiPane.cpp ================================================ #include "Runtime/GuiSys/CGuiPane.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" #include "Runtime/Graphics/CGraphics.hpp" namespace metaforce { CGuiPane::CGuiPane(const CGuiWidgetParms& parms, const zeus::CVector2f& dim, const zeus::CVector3f& scaleCenter) : CGuiWidget(parms), xb8_dim(dim), xc8_scaleCenter(scaleCenter) { CGuiPane::InitializeBuffers(); } void CGuiPane::Draw(const CGuiWidgetDrawParms& parms) { CGraphics::SetModelMatrix(x34_worldXF * zeus::CTransform::Translate(xc8_scaleCenter)); if (GetIsVisible()) { auto col = xa8_color2; col.a() = parms.x0_alphaMod * xa8_color2.a(); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvPassthru); CGraphics::DrawPrimitive(ERglPrimitive::TriangleStrip, xc0_verts.data(), skDefaultNormal, col, xc0_verts.size()); } CGuiWidget::Draw(parms); } void CGuiPane::ScaleDimensions(const zeus::CVector3f& scale) { InitializeBuffers(); for (auto& vert : xc0_verts) { vert -= xc8_scaleCenter; vert *= scale; vert += xc8_scaleCenter; } } void CGuiPane::SetDimensions(const zeus::CVector2f& dim, bool initBuffers) { xb8_dim = dim; if (initBuffers) InitializeBuffers(); } zeus::CVector2f CGuiPane::GetDimensions() const { return xb8_dim; } void CGuiPane::InitializeBuffers() { #if 0 if (xc0_verts == nullptr) { xc0_verts = new float[3 * 4]; } #endif xc0_verts[0].assign(-xb8_dim.x() * 0.5f, 0.f, xb8_dim.y() * 0.5f); xc0_verts[1].assign(-xb8_dim.x() * 0.5f, 0.f, -xb8_dim.y() * 0.5f); xc0_verts[2].assign(xb8_dim.x() * 0.5f, 0.f, xb8_dim.y() * 0.5f); xc0_verts[3].assign(xb8_dim.x() * 0.5f, 0.f, -xb8_dim.y() * 0.5f); } void CGuiPane::WriteData(COutputStream& out, bool flag) const {} std::shared_ptr CGuiPane::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); zeus::CVector2f dim = in.Get(); zeus::CVector3f scaleCenter = in.Get(); std::shared_ptr ret = std::make_shared(parms, dim, scaleCenter); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiPane.hpp ================================================ #pragma once #include #include #include "Runtime/GuiSys/CGuiWidget.hpp" #include #include namespace metaforce { class CGuiPane : public CGuiWidget { static constexpr zeus::CVector3f skDefaultNormal{0.f, -1.f, 0.f}; protected: zeus::CVector2f xb8_dim; /* Originally a vert-buffer pointer for GX */ std::array xc0_verts; zeus::CVector3f xc8_scaleCenter; public: CGuiPane(const CGuiWidgetParms& parms, const zeus::CVector2f& dim, const zeus::CVector3f& scaleCenter); FourCC GetWidgetTypeID() const override { return FOURCC('PANE'); } void Draw(const CGuiWidgetDrawParms& parms) override; virtual void ScaleDimensions(const zeus::CVector3f& scale); virtual void SetDimensions(const zeus::CVector2f& dim, bool initVBO); virtual zeus::CVector2f GetDimensions() const; virtual void InitializeBuffers(); virtual void WriteData(COutputStream& out, bool flag) const; static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiSliderGroup.cpp ================================================ #include "Runtime/GuiSys/CGuiSliderGroup.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/Input/CFinalInput.hpp" namespace metaforce { CGuiSliderGroup::CGuiSliderGroup(const CGuiWidgetParms& parms, float min, float max, float def, float inc) : CGuiCompoundWidget(parms) , xb8_minVal(min) , xbc_maxVal(max) , xc0_roundedCurVal(def) , xc4_curVal(def) , xc8_increment(inc) {} void CGuiSliderGroup::SetSelectionChangedCallback(std::function&& func) { xd8_changeCallback = std::move(func); } void CGuiSliderGroup::SetCurVal(float cur) { xc0_roundedCurVal = zeus::clamp(xb8_minVal, cur, xbc_maxVal); xc4_curVal = xc0_roundedCurVal; } void CGuiSliderGroup::StartDecreasing() { xf0_state = EState::Decreasing; xf4_24_inputPending = true; } void CGuiSliderGroup::StartIncreasing() { xf0_state = EState::Increasing; xf4_24_inputPending = true; } bool CGuiSliderGroup::TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const { if (xcc_sliderRangeWidgets[0]->GetWidgetTypeID() != FOURCC('MODL')) { return false; } CGuiModel* bar = static_cast(xcc_sliderRangeWidgets[0]); const auto& modelTok = bar->GetModel(); if (!modelTok || !modelTok.IsLoaded()) { return false; } const zeus::CVector3f& s0 = xcc_sliderRangeWidgets[0]->GetIdlePosition(); const zeus::CVector3f& s1 = xcc_sliderRangeWidgets[1]->GetIdlePosition(); zeus::CVector3f backupPos = bar->GetLocalPosition(); bar->SetLocalPosition(s0); zeus::CVector2f p0 = vp.multiplyOneOverW(bar->GetWorldPosition()).toVec2f(); auto aabb = modelTok->GetAABB().getTransformedAABox(bar->GetWorldTransform()); bar->SetLocalPosition(s1); zeus::CVector2f p1 = vp.multiplyOneOverW(bar->GetWorldPosition()).toVec2f(); aabb.accumulateBounds(modelTok->GetAABB().getTransformedAABox(bar->GetWorldTransform())); bar->SetLocalPosition(backupPos); zeus::CVector2f pDelta = p1 - p0; float magSq = pDelta.magSquared(); float t = 0.f; if (magSq > 0.00001f) t = pDelta.dot(point - p0) / magSq; m_mouseT = zeus::clamp(0.f, t, 1.f); m_mouseInside = aabb.projectedPointTest(vp, point); return m_mouseInside; } void CGuiSliderGroup::ProcessUserInput(const CFinalInput& input) { if (input.DMouseButton(EMouseButton::Primary) && m_mouseInside) m_mouseDown = true; else if (!input.DMouseButton(EMouseButton::Primary)) m_mouseDown = false; if (input.DLALeft()) { StartDecreasing(); return; } if (input.DLARight()) { StartIncreasing(); return; } if (input.PDPLeft()) { StartDecreasing(); return; } if (input.PDPRight()) { StartIncreasing(); return; } } void CGuiSliderGroup::Update(float dt) { float fullRange = xbc_maxVal - xb8_minVal; float t1 = fullRange * dt; float incCurVal; for (incCurVal = xb8_minVal; incCurVal <= xc4_curVal; incCurVal += xc8_increment) {} float upperIncVal = std::min(incCurVal, xbc_maxVal); float lowerIncVal = upperIncVal - xc8_increment; float oldCurVal = xc4_curVal; if (xf0_state == EState::Decreasing) { if (xf4_24_inputPending) xc4_curVal = std::max(oldCurVal - t1, xb8_minVal); else xc4_curVal = std::max(oldCurVal - t1, lowerIncVal); } else if (xf0_state == EState::Increasing) { if (xf4_24_inputPending) xc4_curVal = std::min(oldCurVal + t1, xbc_maxVal); else if (xc4_curVal != lowerIncVal) xc4_curVal = std::min(oldCurVal + t1, upperIncVal); } if (m_mouseDown) { xc4_curVal = m_mouseT * fullRange + xb8_minVal; xf0_state = EState::MouseMove; } if (xc4_curVal == oldCurVal) xf0_state = EState::None; oldCurVal = xc0_roundedCurVal; if (upperIncVal - xc4_curVal <= xc4_curVal - lowerIncVal) xc0_roundedCurVal = upperIncVal; else xc0_roundedCurVal = lowerIncVal; if (oldCurVal != xc0_roundedCurVal && xd8_changeCallback) xd8_changeCallback(this, oldCurVal); float fac; if (xbc_maxVal == xb8_minVal) fac = 0.f; else fac = (xc4_curVal - xb8_minVal) / (xbc_maxVal - xb8_minVal); const zeus::CVector3f& s0 = xcc_sliderRangeWidgets[0]->GetIdlePosition(); const zeus::CVector3f& s1 = xcc_sliderRangeWidgets[1]->GetIdlePosition(); xcc_sliderRangeWidgets[0]->SetLocalPosition(s1 * fac + s0 * (1.f - fac)); xf4_24_inputPending = false; } bool CGuiSliderGroup::AddWorkerWidget(CGuiWidget* worker) { if (worker->GetWorkerId() < 0 || worker->GetWorkerId() > 1) return true; xcc_sliderRangeWidgets[worker->GetWorkerId()] = worker; return true; } CGuiWidget* CGuiSliderGroup::GetWorkerWidget(int id) const { if (id < 0 || id > 1) return nullptr; return xcc_sliderRangeWidgets[id]; } std::shared_ptr CGuiSliderGroup::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); float min = in.ReadFloat(); float max = in.ReadFloat(); float cur = in.ReadFloat(); float increment = in.ReadFloat(); std::shared_ptr ret = std::make_shared(parms, min, max, cur, increment); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiSliderGroup.hpp ================================================ #pragma once #include #include #include #include "Runtime/GuiSys/CGuiCompoundWidget.hpp" namespace metaforce { class CSimplePool; class CGuiSliderGroup : public CGuiCompoundWidget { public: enum class EState { None, Decreasing, Increasing, MouseMove }; private: float xb8_minVal; float xbc_maxVal; float xc0_roundedCurVal; float xc4_curVal; float xc8_increment; std::array xcc_sliderRangeWidgets{}; std::function xd8_changeCallback; EState xf0_state = EState::None; bool xf4_24_inputPending : 1 = false; mutable bool m_mouseInside : 1 = false; bool m_mouseDown : 1 = false; mutable float m_mouseT = 0.f; void StartDecreasing(); void StartIncreasing(); public: CGuiSliderGroup(const CGuiWidgetParms& parms, float a, float b, float c, float d); FourCC GetWidgetTypeID() const override { return FOURCC('SLGP'); } EState GetState() const { return xf0_state; } void SetSelectionChangedCallback(std::function&& func); void SetIncrement(float inc) { xc8_increment = inc; } void SetMinVal(float min) { xb8_minVal = min; SetCurVal(xc0_roundedCurVal); } void SetMaxVal(float max) { xbc_maxVal = max; SetCurVal(xc0_roundedCurVal); } void SetCurVal(float cur); float GetGurVal() const { return xc0_roundedCurVal; } bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const override; void ProcessUserInput(const CFinalInput& input) override; void Update(float dt) override; bool AddWorkerWidget(CGuiWidget* worker) override; CGuiWidget* GetWorkerWidget(int id) const override; static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiSys.cpp ================================================ #include "Runtime/GuiSys/CGuiSys.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GuiSys/CAuiEnergyBarT01.hpp" #include "Runtime/GuiSys/CAuiImagePane.hpp" #include "Runtime/GuiSys/CAuiMeter.hpp" #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiGroup.hpp" #include "Runtime/GuiSys/CGuiHeadWidget.hpp" #include "Runtime/GuiSys/CGuiLight.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiPane.hpp" #include "Runtime/GuiSys/CGuiSliderGroup.hpp" #include "Runtime/GuiSys/CGuiTableGroup.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include "Runtime/GuiSys/CTextExecuteBuffer.hpp" #include "Runtime/GuiSys/CTextParser.hpp" namespace metaforce { CGuiSys* g_GuiSys = nullptr; CTextExecuteBuffer* g_TextExecuteBuf = nullptr; CTextParser* g_TextParser = nullptr; std::shared_ptr CGuiSys::CreateWidgetInGame(FourCC type, CInputStream& in, CGuiFrame* frame, CSimplePool* sp, u32 version) { switch (type.toUint32()) { case SBIG('BWIG'): return CGuiWidget::Create(frame, in, sp); case SBIG('HWIG'): return CGuiHeadWidget::Create(frame, in, sp); case SBIG('LITE'): return CGuiLight::Create(frame, in, sp); case SBIG('CAMR'): return CGuiCamera::Create(frame, in, sp); case SBIG('GRUP'): return CGuiGroup::Create(frame, in, sp); case SBIG('PANE'): return CGuiPane::Create(frame, in, sp); case SBIG('IMGP'): return CAuiImagePane::Create(frame, in, sp); case SBIG('METR'): return CAuiMeter::Create(frame, in, sp); case SBIG('MODL'): return CGuiModel::Create(frame, in, sp); case SBIG('TBGP'): return CGuiTableGroup::Create(frame, in, sp); case SBIG('SLGP'): return CGuiSliderGroup::Create(frame, in, sp); case SBIG('TXPN'): return CGuiTextPane::Create(frame, in, sp, version); case SBIG('ENRG'): return CAuiEnergyBarT01::Create(frame, in, sp); default: return {}; } } CGuiSys::CGuiSys(IFactory& resFactory, CSimplePool& resStore, EUsageMode mode) : x0_resFactory(resFactory) , x4_resStore(resStore) , x8_mode(mode) , xc_textExecuteBuf(std::make_unique()) , x10_textParser(std::make_unique(resStore)) { g_TextExecuteBuf = xc_textExecuteBuf.get(); g_TextParser = x10_textParser.get(); } void CGuiSys::OnViewportResize() { for (CGuiFrame* frame : m_registeredFrames) ViewportResizeFrame(frame); } void CGuiSys::ViewportResizeFrame(CGuiFrame* frame) { if (frame->m_aspectConstraint > 0.f) { float hPad, vPad; if (CGraphics::GetViewportAspect() >= frame->m_aspectConstraint) { hPad = frame->m_aspectConstraint / CGraphics::GetViewportAspect(); vPad = frame->m_aspectConstraint / 1.38f; } else { hPad = 1.f; vPad = CGraphics::GetViewportAspect() / 1.38f; } frame->m_aspectTransform = zeus::CTransform::Scale({hPad, 1.f, vPad}); } else if (frame->m_maxAspect > 0.f) { if (CGraphics::GetViewportAspect() > frame->m_maxAspect) frame->m_aspectTransform = zeus::CTransform::Scale({frame->m_maxAspect / CGraphics::GetViewportAspect(), 1.f, 1.f}); else frame->m_aspectTransform = zeus::CTransform(); } } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiSys.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/GuiSys/CSaveableState.hpp" namespace metaforce { class CGuiFrame; class CGuiObject; class CGuiWidget; class CSimplePool; class CTextExecuteBuffer; class CTextParser; class CVParamTransfer; class IFactory; struct SObjectTag; class CGuiSys { friend class CGuiFrame; public: enum class EUsageMode { Zero, One, Two }; private: IFactory& x0_resFactory; CSimplePool& x4_resStore; EUsageMode x8_mode; std::unique_ptr xc_textExecuteBuf; std::unique_ptr x10_textParser; std::unordered_set m_registeredFrames; static std::shared_ptr CreateWidgetInGame(FourCC type, CInputStream& in, CGuiFrame* frame, CSimplePool* sp, u32 version); public: CGuiSys(IFactory& resFactory, CSimplePool& resStore, EUsageMode mode); CSimplePool& GetResStore() { return x4_resStore; } const CSimplePool& GetResStore() const { return x4_resStore; } EUsageMode GetUsageMode() const { return x8_mode; } void OnViewportResize(); static void ViewportResizeFrame(CGuiFrame* frame); }; /** Global GuiSys instance */ extern CGuiSys* g_GuiSys; /** Global CTextExecuteBuffer instance */ extern CTextExecuteBuffer* g_TextExecuteBuf; /** Global CTextParser instance */ extern CTextParser* g_TextParser; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiTableGroup.cpp ================================================ #include "Runtime/GuiSys/CGuiTableGroup.hpp" #include "Runtime/Input/CFinalInput.hpp" namespace metaforce { bool CGuiTableGroup::CRepeatState::Update(float dt, bool state) { if (x0_timer == 0.f) { if (state) { x0_timer = 0.6f; return true; } } else { if (state) { x0_timer -= dt; if (x0_timer <= 0.f) { x0_timer = 0.05f; return true; } } else { x0_timer = 0.f; } } return false; } CGuiTableGroup::CGuiTableGroup(const CGuiWidgetParms& parms, int elementCount, int defaultSel, bool selectWraparound) : CGuiCompoundWidget(parms) , xc0_elementCount(elementCount) , xc4_userSelection(defaultSel) , xc8_prevUserSelection(defaultSel) , xcc_defaultUserSelection(defaultSel) , xd0_selectWraparound(selectWraparound) {} void CGuiTableGroup::ProcessUserInput(const CFinalInput& input) { if (input.PA() || input.PSpecialKey(ESpecialKey::Enter)) { DoAdvance(); } else if (input.PB() || input.PSpecialKey(ESpecialKey::Esc)) { DoCancel(); } else { bool decrement; if (xd1_vertical) decrement = (input.DLAUp() || input.DDPUp()); else decrement = (input.DLALeft() || input.DDPLeft()); bool increment; if (xd1_vertical) increment = (input.DLADown() || input.DDPDown()); else increment = (input.DLARight() || input.DDPRight()); if (xb8_decRepeat.Update(input.DeltaTime(), decrement) && decrement) { DoDecrement(); return; } if (xbc_incRepeat.Update(input.DeltaTime(), increment) && increment) { DoIncrement(); return; } } } bool CGuiTableGroup::IsWorkerSelectable(int idx) const { if (CGuiWidget* widget = GetWorkerWidget(idx)) return widget->GetIsSelectable(); return false; } void CGuiTableGroup::SelectWorker(int idx) { idx = zeus::clamp(0, idx, xc0_elementCount - 1); if (idx < xc4_userSelection) { while (idx != xc4_userSelection) DoSelectPrevRow(); } else { while (idx != xc4_userSelection) DoSelectNextRow(); } } void CGuiTableGroup::DoSelectWorker(int worker) { if (worker == xc4_userSelection) return; if (IsWorkerSelectable(worker)) { int oldSel = xc4_userSelection; SelectWorker(worker); if (x104_doMenuSelChange) x104_doMenuSelChange(this, oldSel); } } void CGuiTableGroup::SetWorkersMouseActive(bool active) { CGuiWidget* child = static_cast(GetChildObject()); while (child) { if (child->GetWorkerId() != -1) child->SetMouseActive(active); child = static_cast(child->GetNextSibling()); } } void CGuiTableGroup::DeactivateWorker(CGuiWidget* widget) { widget->SetIsActive(false); } void CGuiTableGroup::ActivateWorker(CGuiWidget* widget) { widget->SetIsActive(true); } CGuiTableGroup::TableSelectReturn CGuiTableGroup::DecrementSelectedRow() { xc8_prevUserSelection = xc4_userSelection; --xc4_userSelection; if (xc4_userSelection < 0) { xc4_userSelection = xd0_selectWraparound ? xc0_elementCount - 1 : 0; return xd0_selectWraparound ? TableSelectReturn::WrappedAround : TableSelectReturn::Unchanged; } return TableSelectReturn::Changed; } CGuiTableGroup::TableSelectReturn CGuiTableGroup::IncrementSelectedRow() { xc8_prevUserSelection = xc4_userSelection; ++xc4_userSelection; if (xc4_userSelection >= xc0_elementCount) { xc4_userSelection = xd0_selectWraparound ? 0 : xc0_elementCount - 1; return xd0_selectWraparound ? TableSelectReturn::WrappedAround : TableSelectReturn::Unchanged; } return TableSelectReturn::Changed; } void CGuiTableGroup::DoSelectPrevRow() { DecrementSelectedRow(); DeactivateWorker(GetWorkerWidget(xc8_prevUserSelection)); ActivateWorker(GetWorkerWidget(xc4_userSelection)); } void CGuiTableGroup::DoSelectNextRow() { IncrementSelectedRow(); DeactivateWorker(GetWorkerWidget(xc8_prevUserSelection)); ActivateWorker(GetWorkerWidget(xc4_userSelection)); } void CGuiTableGroup::DoCancel() { if (xec_doMenuCancel) xec_doMenuCancel(this); } void CGuiTableGroup::DoAdvance() { if (xd4_doMenuAdvance) xd4_doMenuAdvance(this); } bool CGuiTableGroup::PreDecrement() { if (xd0_selectWraparound) { for (int sel = (xc4_userSelection + xc0_elementCount - 1) % xc0_elementCount; sel != xc4_userSelection; sel = (sel + xc0_elementCount - 1) % xc0_elementCount) { if (IsWorkerSelectable(sel)) { SelectWorker(sel); return true; } } } else { for (int sel = std::max(-1, xc4_userSelection - 1); sel >= 0; --sel) { if (IsWorkerSelectable(sel)) { SelectWorker(sel); return true; } } } return false; } void CGuiTableGroup::DoDecrement() { int oldSel = xc4_userSelection; if (!PreDecrement()) return; if (x104_doMenuSelChange) x104_doMenuSelChange(this, oldSel); } bool CGuiTableGroup::PreIncrement() { if (xd0_selectWraparound) { for (int sel = (xc4_userSelection + 1) % xc0_elementCount; sel != xc4_userSelection; sel = (sel + 1) % xc0_elementCount) { if (IsWorkerSelectable(sel)) { SelectWorker(sel); return true; } } } else { for (int sel = std::min(xc0_elementCount, xc4_userSelection + 1); sel < xc0_elementCount; ++sel) { if (IsWorkerSelectable(sel)) { SelectWorker(sel); return true; } } } return false; } void CGuiTableGroup::DoIncrement() { int oldSel = xc4_userSelection; if (!PreIncrement()) return; if (x104_doMenuSelChange) x104_doMenuSelChange(this, oldSel); } std::shared_ptr CGuiTableGroup::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); int elementCount = in.ReadInt16(); in.ReadInt16(); in.ReadLong(); int defaultSel = in.ReadInt16(); in.ReadInt16(); bool selectWraparound = in.ReadBool(); in.ReadBool(); in.ReadFloat(); in.ReadFloat(); in.ReadBool(); in.ReadFloat(); in.ReadInt16(); in.ReadInt16(); in.ReadInt16(); in.ReadInt16(); std::shared_ptr ret = std::make_shared(parms, elementCount, defaultSel, selectWraparound); ret->ParseBaseInfo(frame, in, parms); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiTableGroup.hpp ================================================ #pragma once #include #include #include "Runtime/GuiSys/CGuiCompoundWidget.hpp" namespace metaforce { class CGuiTableGroup : public CGuiCompoundWidget { public: class CRepeatState { float x0_timer = 0.f; public: bool Update(float dt, bool state); }; enum class TableSelectReturn { Changed, Unchanged, WrappedAround }; private: CRepeatState xb8_decRepeat; CRepeatState xbc_incRepeat; int xc0_elementCount; int xc4_userSelection; int xc8_prevUserSelection; int xcc_defaultUserSelection; bool xd0_selectWraparound; bool xd1_vertical = true; std::function xd4_doMenuAdvance; std::function xec_doMenuCancel; std::function x104_doMenuSelChange; void DeactivateWorker(CGuiWidget* widget); void ActivateWorker(CGuiWidget* widget); TableSelectReturn DecrementSelectedRow(); TableSelectReturn IncrementSelectedRow(); void DoSelectPrevRow(); void DoSelectNextRow(); void DoCancel(); void DoAdvance(); bool PreDecrement(); void DoDecrement(); bool PreIncrement(); void DoIncrement(); public: CGuiTableGroup(const CGuiWidgetParms& parms, int, int, bool); FourCC GetWidgetTypeID() const override { return FOURCC('TBGP'); } void SetMenuAdvanceCallback(std::function&& cb) { xd4_doMenuAdvance = std::move(cb); } void SetMenuCancelCallback(std::function&& cb) { xec_doMenuCancel = std::move(cb); } void SetMenuSelectionChangeCallback(std::function&& cb) { x104_doMenuSelChange = std::move(cb); } void SetColors(const zeus::CColor& selected, const zeus::CColor& unselected) { int id = -1; while (CGuiWidget* worker = GetWorkerWidget(++id)) { if (id == xc4_userSelection) worker->SetColor(selected); else worker->SetColor(unselected); } } void SetVertical(bool v) { xd1_vertical = v; } void SetUserSelection(int sel) { xc8_prevUserSelection = xc4_userSelection; xc4_userSelection = sel; } int GetElementCount() const { return xc0_elementCount; } int GetUserSelection() const { return xc4_userSelection; } bool IsWorkerSelectable(int) const; void SelectWorker(int); void DoSelectWorker(int); void SetWorkersMouseActive(bool); void ProcessUserInput(const CFinalInput& input) override; bool AddWorkerWidget(CGuiWidget* worker) override { return true; } static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiTextPane.cpp ================================================ #include "Runtime/GuiSys/CGuiTextPane.hpp" #include #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/Graphics/CGraphicsPalette.hpp" #include "Runtime/GuiSys/CFontImageDef.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiSys.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" namespace metaforce { namespace { constexpr std::array NormalPoints{{ {0.f, 0.f, -1.f}, {1.f, 0.f, -1.f}, {1.f, 0.f, 0.f}, {0.f, 0.f, 0.f}, }}; bool testProjectedLine(const zeus::CVector2f& a, const zeus::CVector2f& b, const zeus::CVector2f& point) { const zeus::CVector2f normal = (b - a).perpendicularVector().normalized(); return point.dot(normal) >= a.dot(normal); } } // Anonymous namespace bool CGuiTextPane::sDrawPaneRects = false; CGuiTextPane::CGuiTextPane(const CGuiWidgetParms& parms, CSimplePool* sp, const zeus::CVector2f& dim, const zeus::CVector3f& vec, CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& fontCol, const zeus::CColor& outlineCol, s32 extentX, s32 extentY, CAssetId jpFontId, s32 jpExtentX, s32 jpExtentY) : CGuiPane(parms, dim, vec) , xd4_textSupport(fontId, props, fontCol, outlineCol, zeus::skWhite, extentX, extentY, sp, xac_drawFlags) {} void CGuiTextPane::Update(float dt) { CGuiWidget::Update(dt); xd4_textSupport.Update(dt); } bool CGuiTextPane::GetIsFinishedLoadingWidgetSpecific() { return xd4_textSupport.GetIsTextSupportFinishedLoading(); } void CGuiTextPane::SetDimensions(const zeus::CVector2f& dim, bool initVBO) { CGuiPane::SetDimensions(dim, initVBO); if (initVBO) InitializeBuffers(); } void CGuiTextPane::ScaleDimensions(const zeus::CVector3f& scale) {} void CGuiTextPane::Draw(const CGuiWidgetDrawParms& parms) { if (sDrawPaneRects) { CGuiPane::Draw({0.2f * parms.x0_alphaMod, parms.x4_cameraOffset}); } if (!GetIsVisible()) { return; } SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format("CGuiTextPane::Draw {}", m_name).c_str(), zeus::skCyan); zeus::CVector2f dims = GetDimensions(); if (xd4_textSupport.x34_extentX != 0) { dims.x() /= float(xd4_textSupport.x34_extentX); } else { dims.x() = 0.f; } if (xd4_textSupport.x38_extentY != 0) { dims.y() /= float(xd4_textSupport.x38_extentY); } else { dims.y() = 0.f; } const zeus::CTransform local = zeus::CTransform::Translate(xc0_verts.front() + xc8_scaleCenter) * zeus::CTransform::Scale(dims.x(), 1.f, dims.y()); CGraphics::SetModelMatrix(x34_worldXF * local); zeus::CColor geomCol = xa8_color2; geomCol.a() *= parms.x0_alphaMod; xd4_textSupport.SetGeometryColor(geomCol); CGraphics::SetDepthWriteMode(xb6_31_depthTest, ERglEnum::LEqual, xb7_24_depthWrite); switch (xac_drawFlags) { case EGuiModelDrawFlags::Shadeless: case EGuiModelDrawFlags::Opaque: CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::Zero, ERglLogicOp::Clear); xd4_textSupport.Render(); break; case EGuiModelDrawFlags::Alpha: CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); xd4_textSupport.Render(); break; case EGuiModelDrawFlags::Additive: CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::One, ERglLogicOp::Clear); xd4_textSupport.Render(); break; case EGuiModelDrawFlags::AlphaAdditiveOverdraw: CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::SrcAlpha, ERglBlendFactor::InvSrcAlpha, ERglLogicOp::Clear); xd4_textSupport.Render(); xd4_textSupport.SetGeometryColor(geomCol * zeus::CColor(geomCol.a(), 1.f)); CGraphics::SetBlendMode(ERglBlendMode::Blend, ERglBlendFactor::One, ERglBlendFactor::One, ERglLogicOp::Clear); xd4_textSupport.Render(); break; } } bool CGuiTextPane::TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const { const zeus::CVector2f dims = GetDimensions(); const zeus::CTransform local = zeus::CTransform::Translate(xc0_verts.front() + xc8_scaleCenter) * zeus::CTransform::Scale(dims.x(), 1.f, dims.y()); const zeus::CMatrix4f mvp = vp * (x34_worldXF * local).toMatrix4f(); std::array projPoints; for (size_t i = 0; i < projPoints.size(); ++i) { projPoints[i] = mvp.multiplyOneOverW(NormalPoints[i]).toVec2f(); } size_t j; for (j = 0; j < 3; ++j) { if (!testProjectedLine(projPoints[j], projPoints[j + 1], point)) { break; } } return j == 3 && testProjectedLine(projPoints[3], projPoints[0], point); } std::shared_ptr CGuiTextPane::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp, u32 version) { const CGuiWidgetParms parms = ReadWidgetHeader(frame, in); const zeus::CVector2f dim = in.Get(); const zeus::CVector3f vec = in.Get(); const CAssetId fontId = in.Get(); const bool wordWrap = in.ReadBool(); const bool horizontal = in.ReadBool(); const auto justification = EJustification(in.ReadLong()); const auto vJustification = EVerticalJustification(in.ReadLong()); const CGuiTextProperties props(wordWrap, horizontal, justification, vJustification); const zeus::CColor fontCol = in.Get(); const zeus::CColor outlineCol = in.Get(); const int extentX = static_cast(in.ReadFloat()); const int extentY = static_cast(in.ReadFloat()); int jpExtentX = extentX; int jpExtentY = extentY; CAssetId jpFontId = fontId; if (version != 0) { jpFontId = in.Get(); jpExtentX = in.ReadLong(); jpExtentY = in.ReadLong(); } auto ret = std::make_shared(parms, sp, dim, vec, fontId, props, fontCol, outlineCol, extentX, extentY, jpFontId, jpExtentX, jpExtentY); ret->ParseBaseInfo(frame, in, parms); ret->InitializeBuffers(); ret->TextSupport().SetText(u""); return ret; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiTextPane.hpp ================================================ #pragma once #include #include #include "Runtime/GuiSys/CGuiPane.hpp" #include "Runtime/GuiSys/CGuiTextSupport.hpp" namespace metaforce { class CGuiTextPane : public CGuiPane { static bool sDrawPaneRects; CGuiTextSupport xd4_textSupport; public: CGuiTextPane(const CGuiWidgetParms& parms, CSimplePool* sp, const zeus::CVector2f& dim, const zeus::CVector3f& vec, CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& col1, const zeus::CColor& col2, s32 padX, s32 padY, CAssetId jpFontId, s32 jpExtentX, s32 jpExtentY); FourCC GetWidgetTypeID() const override { return FOURCC('TXPN'); } CGuiTextSupport& TextSupport() { return xd4_textSupport; } const CGuiTextSupport& GetTextSupport() const { return xd4_textSupport; } void Update(float dt) override; bool GetIsFinishedLoadingWidgetSpecific() override; std::vector GetFontAssets() const { return {xd4_textSupport.x5c_fontId}; } void SetDimensions(const zeus::CVector2f& dim, bool initVBO) override; void ScaleDimensions(const zeus::CVector3f& scale) override; void Draw(const CGuiWidgetDrawParms& parms) override; bool TestCursorHit(const zeus::CMatrix4f& vp, const zeus::CVector2f& point) const override; static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp, u32 version); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiTextSupport.cpp ================================================ #include "Runtime/GuiSys/CGuiTextSupport.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/Graphics/CGraphicsPalette.hpp" #include "Runtime/GuiSys/CFontImageDef.hpp" #include "Runtime/GuiSys/CGuiSys.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" #include "Runtime/GuiSys/CTextExecuteBuffer.hpp" #include "Runtime/GuiSys/CTextParser.hpp" #include "Runtime/CStringExtras.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { CGuiTextSupport::CGuiTextSupport(CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& fontCol, const zeus::CColor& outlineCol, const zeus::CColor& geomCol, s32 padX, s32 padY, CSimplePool* store, CGuiWidget::EGuiModelDrawFlags drawFlags) : x14_props(props) , x24_fontColor(fontCol) , x28_outlineColor(outlineCol) , x2c_geometryColor(geomCol) , x34_extentX(padX) , x38_extentY(padY) , x5c_fontId(fontId) , m_drawFlags(drawFlags) { x2cc_font = store->GetObj({SBIG('FONT'), fontId}); } CTextRenderBuffer* CGuiTextSupport::GetCurrentPageRenderBuffer() { if (x60_renderBuf && !x308_multipageFlag) { return &*x60_renderBuf; } if (!x308_multipageFlag || x2ec_renderBufferPages.size() <= x304_pageCounter) { return nullptr; } int idx = 0; for (CTextRenderBuffer& buf : x2ec_renderBufferPages) { if (idx++ == x304_pageCounter) { return &buf; } } return nullptr; } const CTextRenderBuffer* CGuiTextSupport::GetCurrentPageRenderBuffer() const { if (x60_renderBuf && !x308_multipageFlag) { return &*x60_renderBuf; } if (!x308_multipageFlag || x2ec_renderBufferPages.size() <= x304_pageCounter) { return nullptr; } int idx = 0; for (const CTextRenderBuffer& buf : x2ec_renderBufferPages) { if (idx++ == x304_pageCounter) { return &buf; } } return nullptr; } float CGuiTextSupport::GetCurrentAnimationOverAge() const { float ret = 0.f; if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) { if (x50_typeEnable) { if (!x40_primStartTimes.empty()) { const auto& lastTime = x40_primStartTimes.back(); ret = std::max(ret, (buf->GetPrimitiveCount() - lastTime.second) / x58_chRate + lastTime.first); } else { ret = std::max(ret, buf->GetPrimitiveCount() / x58_chRate); } } } return ret; } float CGuiTextSupport::GetNumCharsTotal() const { if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) { if (x50_typeEnable) { return buf->GetPrimitiveCount(); } } return 0.f; } float CGuiTextSupport::GetNumCharsPrinted() const { if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) { if (x50_typeEnable) { const float charsPrinted = x3c_curTime * x58_chRate; return std::min(charsPrinted, float(buf->GetPrimitiveCount())); } } return 0.f; } float CGuiTextSupport::GetTotalAnimationTime() const { if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) { if (x50_typeEnable) { return buf->GetPrimitiveCount() / x58_chRate; } } return 0.f; } bool CGuiTextSupport::IsAnimationDone() const { return x3c_curTime >= GetTotalAnimationTime(); } void CGuiTextSupport::SetTypeWriteEffectOptions(bool enable, float chFadeTime, float chRate) { x50_typeEnable = enable; x54_chFadeTime = std::max(chFadeTime, 0.0001f); x58_chRate = std::max(chRate, 1.f); if (enable) { if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) { float chStartTime = 0.f; for (u32 i = 0; i < buf->GetPrimitiveCount(); ++i) { for (const std::pair& p : x40_primStartTimes) { if (p.second < i) continue; if (p.second != i) break; chStartTime = p.first; break; } //buf->SetPrimitiveOpacity(i, std::min(std::max(0.f, (x3c_curTime - chStartTime) / x54_chFadeTime), 1.f)); chStartTime += 1.f / x58_chRate; } } } } void CGuiTextSupport::Update(float dt) { if (x50_typeEnable) { if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) { float chStartTime = 0.f; for (u32 i = 0; i < buf->GetPrimitiveCount(); ++i) { for (const std::pair& p : x40_primStartTimes) { if (p.second < i) continue; if (p.second != i) break; chStartTime = p.first; break; } auto primitive = buf->GetPrimitive(i); float alpha = std::clamp((x3c_curTime - chStartTime) / x54_chFadeTime, 0.f, 1.f); chStartTime += 1.f / x58_chRate; primitive.x0_color1 = zeus::CColor{alpha, alpha}; buf->SetPrimitive(primitive, i); } } x3c_curTime += dt; } x10_curTimeMod900 = std::fmod(x10_curTimeMod900 + dt, 900.f); } void CGuiTextSupport::ClearRenderBuffer() { x60_renderBuf = std::nullopt; x2ec_renderBufferPages.clear(); } void CGuiTextSupport::CheckAndRebuildTextBuffer() { g_TextExecuteBuf->Clear(); g_TextExecuteBuf->x18_textState.x7c_enableWordWrap = x14_props.x0_wordWrap; g_TextExecuteBuf->BeginBlock(0, 0, x34_extentX, x38_extentY, x30_imageBaseline, ETextDirection(!x14_props.x1_horizontal), x14_props.x4_justification, x14_props.x8_vertJustification); g_TextExecuteBuf->AddColor(EColorType::Main, x24_fontColor); g_TextExecuteBuf->AddColor(EColorType::Outline, x28_outlineColor); std::u16string initStr; if (x5c_fontId.IsValid()) initStr = fmt::format(u"&font={};", x5c_fontId); initStr += x0_string; g_TextParser->ParseText(*g_TextExecuteBuf, initStr.c_str(), initStr.size(), x14_props.xc_txtrMap); g_TextExecuteBuf->EndBlock(); } bool CGuiTextSupport::CheckAndRebuildRenderBuffer() { if (x308_multipageFlag || x60_renderBuf) { if (!x308_multipageFlag || x2ec_renderBufferPages.size()) { return true; } } CheckAndRebuildTextBuffer(); x2bc_assets = g_TextExecuteBuf->GetAssets(); if (!_GetIsTextSupportFinishedLoading()) return false; CheckAndRebuildTextBuffer(); if (x308_multipageFlag) { zeus::CVector2i extent(x34_extentX, x38_extentY); x2ec_renderBufferPages = g_TextExecuteBuf->BuildRenderBufferPages(extent, m_drawFlags); } else { x60_renderBuf.emplace(g_TextExecuteBuf->BuildRenderBuffer(m_drawFlags)); x2dc_oneBufBounds = x60_renderBuf->AccumulateTextBounds(); } g_TextExecuteBuf->Clear(); Update(0.f); return true; } const std::pair& CGuiTextSupport::GetBounds() { CheckAndRebuildRenderBuffer(); return x2dc_oneBufBounds; } void CGuiTextSupport::AutoSetExtent() { const auto& bounds = GetBounds(); x34_extentX = bounds.second.x; x38_extentY = bounds.second.y; } void CGuiTextSupport::Render() { CheckAndRebuildRenderBuffer(); if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) { SCOPED_GRAPHICS_DEBUG_GROUP("CGuiTextSupport::Render", zeus::skBlue); zeus::CTransform oldModel = CGraphics::mModelMatrix; CGraphics::SetModelMatrix(oldModel * zeus::CTransform::Scale(1.f, 1.f, -1.f)); buf->Render(x2c_geometryColor, x10_curTimeMod900); CGraphics::SetModelMatrix(oldModel); } } void CGuiTextSupport::SetGeometryColor(const zeus::CColor& col) { x2c_geometryColor = col; } void CGuiTextSupport::SetOutlineColor(const zeus::CColor& col) { if (col == x28_outlineColor) { return; } ClearRenderBuffer(); x28_outlineColor = col; } void CGuiTextSupport::SetFontColor(const zeus::CColor& col) { if (col == x24_fontColor) { return; } ClearRenderBuffer(); x24_fontColor = col; } void CGuiTextSupport::AddText(std::u16string_view str) { if (x60_renderBuf) { const float t = GetCurrentAnimationOverAge(); x40_primStartTimes.emplace_back(std::max(t, x3c_curTime), x60_renderBuf->GetPrimitiveCount()); } x0_string += str; ClearRenderBuffer(); } void CGuiTextSupport::SetText(std::u16string_view str, bool multipage) { if (x0_string == str) { return; } x40_primStartTimes.clear(); x3c_curTime = 0.f; x0_string = str; ClearRenderBuffer(); x308_multipageFlag = multipage; x304_pageCounter = 0; } void CGuiTextSupport::SetText(std::string_view str, bool multipage) { SetText(CStringExtras::ConvertToUNICODE(str), multipage); } bool CGuiTextSupport::_GetIsTextSupportFinishedLoading() { for (CToken& tok : x2bc_assets) { tok.Lock(); if (!tok.IsLoaded()) { return false; } } if (!x2cc_font) { return true; } if (x2cc_font.IsLoaded()) { return x2cc_font->IsFinishedLoading(); } return false; } void CGuiTextSupport::SetJustification(EJustification j) { if (j == x14_props.x4_justification) { return; } x14_props.x4_justification = j; ClearRenderBuffer(); } void CGuiTextSupport::SetVerticalJustification(EVerticalJustification j) { if (j == x14_props.x8_vertJustification) { return; } x14_props.x8_vertJustification = j; ClearRenderBuffer(); } void CGuiTextSupport::SetImageBaseline(bool b) { if (b == x30_imageBaseline) { return; } x30_imageBaseline = b; ClearRenderBuffer(); } bool CGuiTextSupport::GetIsTextSupportFinishedLoading() { CheckAndRebuildRenderBuffer(); return _GetIsTextSupportFinishedLoading(); } void CGuiTextSupport::SetControlTXTRMap(const std::vector>* txtrMap) { if (x14_props.xc_txtrMap == txtrMap) { return; } x14_props.xc_txtrMap = txtrMap; ClearRenderBuffer(); } int CGuiTextSupport::GetTotalPageCount() { if (CheckAndRebuildRenderBuffer()) return x2ec_renderBufferPages.size(); return -1; } void CGuiTextSupport::SetPage(int page) { x304_pageCounter = page; x40_primStartTimes.clear(); x3c_curTime = 0.f; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiTextSupport.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/CWorldSaveGameInfo.hpp" #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" #include "Runtime/GuiSys/CTextRenderBuffer.hpp" #include namespace metaforce { class CSimplePool; class CTextExecuteBuffer; class CTextRenderBuffer; enum class EJustification { Left = 0, Center, Right, Full, NLeft, NCenter, NRight, LeftMono, CenterMono, RightMono, }; enum class EVerticalJustification { Top = 0, Center, Bottom, Full, NTop, NCenter, NBottom, TopMono, CenterMono, RightMono, }; enum class ETextDirection { Horizontal, Vertical, }; class CGuiTextProperties { friend class CGuiTextSupport; bool x0_wordWrap; bool x1_horizontal; EJustification x4_justification; EVerticalJustification x8_vertJustification; const std::vector>* xc_txtrMap; public: CGuiTextProperties(bool wordWrap, bool horizontal, EJustification justification, EVerticalJustification vertJustification, const std::vector>* txtrMap = nullptr) : x0_wordWrap(wordWrap) , x1_horizontal(horizontal) , x4_justification(justification) , x8_vertJustification(vertJustification) , xc_txtrMap(txtrMap) {} }; class CGuiTextSupport { friend class CGuiTextPane; std::u16string x0_string; float x10_curTimeMod900 = 0.f; CGuiTextProperties x14_props; zeus::CColor x24_fontColor; zeus::CColor x28_outlineColor; zeus::CColor x2c_geometryColor; bool x30_imageBaseline = false; s32 x30_; // new in PAL/JP s32 x34_; // "" s32 x34_extentX; s32 x38_extentY; float x3c_curTime = 0.f; std::vector> x40_primStartTimes; bool x50_typeEnable = false; float x54_chFadeTime = 0.1f; float x58_chRate = 10.0f; CAssetId x5c_fontId; CGuiWidget::EGuiModelDrawFlags m_drawFlags; std::optional x60_renderBuf; std::vector x2bc_assets; TLockedToken x2cc_font; std::pair x2dc_oneBufBounds; std::list x2ec_renderBufferPages; int x304_pageCounter = 0; bool x308_multipageFlag = false; CTextRenderBuffer* GetCurrentPageRenderBuffer(); const CTextRenderBuffer* GetCurrentPageRenderBuffer() const; bool _GetIsTextSupportFinishedLoading(); public: CGuiTextSupport(CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& fontCol, const zeus::CColor& outlineCol, const zeus::CColor& geomCol, s32 extX, s32 extY, CSimplePool* store, CGuiWidget::EGuiModelDrawFlags drawFlags); float GetCurrentAnimationOverAge() const; float GetNumCharsTotal() const; float GetNumCharsPrinted() const; float GetTotalAnimationTime() const; bool IsAnimationDone() const; void SetTypeWriteEffectOptions(bool enable, float chFadeTime, float chRate); void Update(float dt); void ClearRenderBuffer(); void CheckAndRebuildTextBuffer(); bool CheckAndRebuildRenderBuffer(); const std::pair& GetBounds(); void AutoSetExtent(); void Render(); void SetGeometryColor(const zeus::CColor& col); void SetOutlineColor(const zeus::CColor& col); void SetFontColor(const zeus::CColor& col); void AddText(std::u16string_view str); void SetText(std::u16string_view str, bool multipage = false); void SetText(std::string_view str, bool multipage = false); void SetJustification(EJustification j); void SetVerticalJustification(EVerticalJustification j); void SetImageBaseline(bool b); bool GetIsTextSupportFinishedLoading(); float GetCurTimeMod900() const { return x10_curTimeMod900; } s32 GetExtentX() const { return x34_extentX; } void SetExtentX(s32 extent) { x34_extentX = extent; ClearRenderBuffer(); } s32 GetExtentY() const { return x38_extentY; } void SetExtentY(s32 extent) { x38_extentY = extent; ClearRenderBuffer(); } float GetCurTime() const { return x3c_curTime; } void SetCurTime(float t) { x3c_curTime = t; } std::u16string_view GetString() const { return x0_string; } void SetControlTXTRMap(const std::vector>* txtrMap); int GetPageCounter() const { return x304_pageCounter; } int GetTotalPageCount(); void SetPage(int page); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiWidget.cpp ================================================ #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/Logging.hpp" namespace metaforce { CGuiWidget::CGuiWidget(const CGuiWidgetParms& parms) : x70_selfId(parms.x6_selfId) , x72_parentId(parms.x8_parentId) , m_initColor(parms.x10_color) , xa4_color(parms.x10_color) , xa8_color2(parms.x10_color) , xac_drawFlags(parms.x14_drawFlags) , xb0_frame(parms.x0_frame) , m_name(parms.m_name) { xb6_24_pg = parms.xd_g; xb6_25_isVisible = parms.xa_defaultVisible; xb6_26_isActive = parms.xb_defaultActive; xb6_29_cullFaces = parms.xc_cullFaces; RecalcWidgetColor(ETraversalMode::Single); } CGuiWidget::CGuiWidgetParms CGuiWidget::ReadWidgetHeader(CGuiFrame* frame, CInputStream& in) { std::string name = in.Get(); s16 selfId = frame->GetWidgetIdDB().AddWidget(name); std::string parent = in.Get(); s16 parentId = frame->GetWidgetIdDB().AddWidget(parent); bool useAnimController = in.ReadBool(); bool defaultVis = in.ReadBool(); bool defaultActive = in.ReadBool(); bool cullFaces = in.ReadBool(); zeus::CColor color = in.Get(); EGuiModelDrawFlags df = EGuiModelDrawFlags(in.ReadLong()); return CGuiWidget::CGuiWidgetParms(frame, useAnimController, selfId, parentId, defaultVis, defaultActive, cullFaces, color, df, true, false, std::move(name)); } std::shared_ptr CGuiWidget::Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp) { CGuiWidgetParms parms = ReadWidgetHeader(frame, in); std::shared_ptr ret = std::make_shared(parms); ret->ParseBaseInfo(frame, in, parms); return ret; } void CGuiWidget::Initialize() {} void CGuiWidget::ParseBaseInfo(CGuiFrame* frame, CInputStream& in, const CGuiWidgetParms& parms) { CGuiWidget* parent = frame->FindWidget(parms.x8_parentId); bool isWorker = in.ReadBool(); if (isWorker) xb4_workerId = in.ReadInt16(); zeus::CVector3f trans = in.Get(); zeus::CMatrix3f orient = in.Get(); x74_transform = zeus::CTransform(orient, trans); m_initTransform = x74_transform; ReapplyXform(); in.Get(); // Unused in.ReadLong(); in.ReadShort(); if (isWorker) { if (!parent->AddWorkerWidget(this)) { spdlog::warn("Warning: Discarding useless worker id. Parent is not a compound widget."); xb4_workerId = -1; } } parent->AddChildWidget(this, false, true); m_initLocalXF = x4_localXF; } void CGuiWidget::Reset(ETraversalMode mode) { xa4_color = m_initColor; xa8_color2 = m_initColor; x74_transform = m_initTransform; ReapplyXform(); x4_localXF = m_initLocalXF; RecalculateTransforms(); switch (mode) { case ETraversalMode::Children: { CGuiWidget* child = static_cast(GetChildObject()); if (child) child->Reset(ETraversalMode::ChildrenAndSiblings); break; } case ETraversalMode::ChildrenAndSiblings: { CGuiWidget* child = static_cast(GetChildObject()); if (child) child->Reset(ETraversalMode::ChildrenAndSiblings); CGuiWidget* nextSib = static_cast(GetNextSibling()); if (nextSib) nextSib->Reset(ETraversalMode::ChildrenAndSiblings); break; } default: break; } } void CGuiWidget::Update(float dt) { CGuiWidget* ch = static_cast(GetChildObject()); if (ch) ch->Update(dt); CGuiWidget* sib = static_cast(GetNextSibling()); if (sib) sib->Update(dt); } void CGuiWidget::Draw(const CGuiWidgetDrawParms& parms) {} void CGuiWidget::ProcessUserInput(const CFinalInput& input) {} void CGuiWidget::Touch() {} bool CGuiWidget::GetIsVisible() const { return xb6_25_isVisible; } bool CGuiWidget::GetIsActive() const { return xb6_26_isActive; } bool CGuiWidget::GetMouseActive() const { return m_mouseActive; } void CGuiWidget::InitializeRGBAFactor() { CGuiWidget* child = static_cast(GetChildObject()); if (child) child->InitializeRGBAFactor(); CGuiWidget* nextSib = static_cast(GetNextSibling()); if (nextSib) nextSib->InitializeRGBAFactor(); } bool CGuiWidget::GetIsFinishedLoadingWidgetSpecific() { return true; } void CGuiWidget::SetTransform(const zeus::CTransform& xf) { x74_transform = xf; ReapplyXform(); } void CGuiWidget::SetIdlePosition(const zeus::CVector3f& pos, bool reapply) { x74_transform.origin = pos; if (reapply) ReapplyXform(); } void CGuiWidget::ReapplyXform() { RotateReset(); SetLocalPosition(zeus::skZero3f); MultiplyO2P(x74_transform); } void CGuiWidget::AddChildWidget(CGuiWidget* widget, bool makeWorldLocal, bool atEnd) { AddChildObject(widget, makeWorldLocal, atEnd); } bool CGuiWidget::AddWorkerWidget(CGuiWidget* worker) { return false; } void CGuiWidget::SetVisibility(bool visible, ETraversalMode mode) { switch (mode) { case ETraversalMode::Children: { auto* child = static_cast(GetChildObject()); if (child) { child->SetVisibility(visible, ETraversalMode::ChildrenAndSiblings); } break; } case ETraversalMode::ChildrenAndSiblings: { auto* child = static_cast(GetChildObject()); if (child) { child->SetVisibility(visible, ETraversalMode::ChildrenAndSiblings); } auto* nextSib = static_cast(GetNextSibling()); if (nextSib) { nextSib->SetVisibility(visible, ETraversalMode::ChildrenAndSiblings); } break; } default: break; } SetIsVisible(visible); } void CGuiWidget::RecalcWidgetColor(ETraversalMode mode) { CGuiWidget* parent = static_cast(GetParent()); if (parent) xa8_color2 = xa4_color * parent->xa8_color2; else xa8_color2 = xa4_color; switch (mode) { case ETraversalMode::ChildrenAndSiblings: { CGuiWidget* nextSib = static_cast(GetNextSibling()); if (nextSib) nextSib->RecalcWidgetColor(ETraversalMode::ChildrenAndSiblings); [[fallthrough]]; } case ETraversalMode::Children: { CGuiWidget* child = static_cast(GetChildObject()); if (child) child->RecalcWidgetColor(ETraversalMode::ChildrenAndSiblings); break; } default: break; } } CGuiWidget* CGuiWidget::FindWidget(s16 id) { if (x70_selfId == id) return this; CGuiWidget* child = static_cast(GetChildObject()); if (child) { CGuiWidget* found = child->FindWidget(id); if (found) return found; } CGuiWidget* nextSib = static_cast(GetNextSibling()); if (nextSib) { CGuiWidget* found = nextSib->FindWidget(id); if (found) return found; } return nullptr; } bool CGuiWidget::GetIsFinishedLoading() { return GetIsFinishedLoadingWidgetSpecific(); } void CGuiWidget::DispatchInitialize() { Initialize(); CGuiWidget* ch = static_cast(GetChildObject()); if (ch) ch->DispatchInitialize(); CGuiWidget* sib = static_cast(GetNextSibling()); if (sib) sib->DispatchInitialize(); } void CGuiWidget::SetColor(const zeus::CColor& color) { xa4_color = color; RecalcWidgetColor(ETraversalMode::Children); } void CGuiWidget::OnActiveChange() {} void CGuiWidget::OnVisibleChange() {} void CGuiWidget::SetIsVisible(bool visible) { xb6_25_isVisible = visible; OnVisibleChange(); } void CGuiWidget::SetIsActive(bool active) { if (active == xb6_26_isActive) { return; } xb6_26_isActive = active; OnActiveChange(); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiWidget.hpp ================================================ #pragma once #include #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/GuiSys/CGuiObject.hpp" #include "Runtime/Input/CKeyboardMouseController.hpp" #include #include #include namespace metaforce { class CGuiFrame; class CGuiTextSupport; struct CFinalInput; class CSimplePool; enum class ETraversalMode { ChildrenAndSiblings = 0, Children = 1, Single = 2 }; enum class EGuiTextureClampModeHorz { NoClamp = 0, Right = 1, Left = 2, Center = 3 }; enum class EGuiTextureClampModeVert { NoClamp = 0, Top = 1, Bottom = 2, Center = 3 }; class CGuiWidget : public CGuiObject { friend class CGuiFrame; public: enum class EGuiModelDrawFlags { Shadeless = 0, Opaque = 1, Alpha = 2, Additive = 3, AlphaAdditiveOverdraw = 4 }; struct CGuiWidgetParms { CGuiFrame* x0_frame; bool x4_useAnimController; s16 x6_selfId; s16 x8_parentId; bool xa_defaultVisible; bool xb_defaultActive; bool xc_cullFaces; bool xd_g; bool xe_h; zeus::CColor x10_color; EGuiModelDrawFlags x14_drawFlags; std::string m_name; CGuiWidgetParms(CGuiFrame* frame, bool useAnimController, s16 selfId, s16 parentId, bool defaultVisible, bool defaultActive, bool cullFaces, const zeus::CColor& color, EGuiModelDrawFlags drawFlags, bool g, bool h, std::string&& name) : x0_frame(frame) , x4_useAnimController(useAnimController) , x6_selfId(selfId) , x8_parentId(parentId) , xa_defaultVisible(defaultVisible) , xb_defaultActive(defaultActive) , xc_cullFaces(cullFaces) , xd_g(g) , xe_h(h) , x10_color(color) , x14_drawFlags(drawFlags) , m_name(std::move(name)) {} }; protected: s16 x70_selfId; s16 x72_parentId; zeus::CTransform m_initTransform; zeus::CTransform x74_transform; zeus::CColor m_initColor; zeus::CColor xa4_color; zeus::CColor xa8_color2; EGuiModelDrawFlags xac_drawFlags; CGuiFrame* xb0_frame; s16 xb4_workerId = -1; bool xb6_24_pg : 1; bool xb6_25_isVisible : 1; bool xb6_26_isActive : 1; bool xb6_27_isSelectable : 1 = true; bool xb6_28_eventLock : 1 = false; bool xb6_29_cullFaces : 1; bool xb6_30_depthGreater : 1 = false; bool xb6_31_depthTest : 1 = true; bool xb7_24_depthWrite : 1 = false; bool xb7_25_ : 1 = true; bool m_mouseActive : 1 = false; std::optional m_lastScroll; SScrollDelta m_integerScroll; std::string m_name; public: explicit CGuiWidget(const CGuiWidgetParms& parms); static CGuiWidgetParms ReadWidgetHeader(CGuiFrame* frame, CInputStream& in); static std::shared_ptr Create(CGuiFrame* frame, CInputStream& in, CSimplePool* sp); void Update(float dt) override; void Draw(const CGuiWidgetDrawParms& drawParms) override; void Initialize() override; virtual void Reset(ETraversalMode mode); virtual void ProcessUserInput(const CFinalInput& input); virtual void Touch(); virtual bool GetIsVisible() const; virtual bool GetIsActive() const; virtual bool GetMouseActive() const; virtual FourCC GetWidgetTypeID() const { return FOURCC('BWIG'); } virtual bool AddWorkerWidget(CGuiWidget* worker); virtual bool GetIsFinishedLoadingWidgetSpecific(); virtual void OnVisibleChange(); virtual void OnActiveChange(); s16 GetSelfId() const { return x70_selfId; } s16 GetParentId() const { return x72_parentId; } s16 GetWorkerId() const { return xb4_workerId; } const zeus::CTransform& GetTransform() const { return x74_transform; } zeus::CTransform& GetTransform() { return x74_transform; } const zeus::CVector3f& GetIdlePosition() const { return x74_transform.origin; } void SetTransform(const zeus::CTransform& xf); const zeus::CColor& GetIntermediateColor() const { return xa4_color; } const zeus::CColor& GetGeometryColor() const { return xa8_color2; } void SetIdlePosition(const zeus::CVector3f& pos, bool reapply); void ReapplyXform(); virtual void SetIsVisible(bool visible); void SetIsActive(bool active); bool GetIsSelectable() const { return xb6_27_isSelectable; } void SetIsSelectable(bool selectable) { xb6_27_isSelectable = selectable; } void SetMouseActive(bool mouseActive) { m_mouseActive = mouseActive; } void ParseBaseInfo(CGuiFrame* frame, CInputStream& in, const CGuiWidgetParms& parms); void AddChildWidget(CGuiWidget* widget, bool makeWorldLocal, bool atEnd); void SetVisibility(bool visible, ETraversalMode mode); void RecalcWidgetColor(ETraversalMode mode); void SetColor(const zeus::CColor& color); void InitializeRGBAFactor(); CGuiWidget* FindWidget(s16 id); bool GetIsFinishedLoading(); void DispatchInitialize(); void SetDepthGreater(bool depthGreater) { xb6_30_depthGreater = depthGreater; } void SetDepthTest(bool depthTest) { xb6_31_depthTest = depthTest; } void SetDepthWrite(bool depthWrite) { xb7_24_depthWrite = depthWrite; } CGuiFrame* GetGuiFrame() const { return xb0_frame; } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiWidgetDrawParms.hpp ================================================ #pragma once #include namespace metaforce { struct CGuiWidgetDrawParms { float x0_alphaMod = 1.f; zeus::CVector3f x4_cameraOffset; constexpr CGuiWidgetDrawParms() = default; constexpr CGuiWidgetDrawParms(float alphaMod, const zeus::CVector3f& cameraOff) : x0_alphaMod(alphaMod), x4_cameraOffset(cameraOff) {} static constexpr CGuiWidgetDrawParms Default() { return {}; } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiWidgetIdDB.cpp ================================================ #include "Runtime/GuiSys/CGuiWidgetIdDB.hpp" namespace metaforce { CGuiWidgetIdDB::CGuiWidgetIdDB() { AddWidget("kGSYS_DummyWidgetID", 0); AddWidget("kGSYS_HeadWidgetID", 1); AddWidget("kGSYS_DefaultCameraID"); AddWidget("kGSYS_DefaultLightID"); } s16 CGuiWidgetIdDB::FindWidgetID(std::string_view name) const { // TODO: Heterogeneous lookup when C++20 available auto search = x0_dbMap.find(name.data()); if (search == x0_dbMap.cend()) return -1; return search->second; } s16 CGuiWidgetIdDB::AddWidget(std::string_view name, s16 id) { s16 findId = FindWidgetID(name); if (findId == -1) { if (id >= x14_lastPoolId) x14_lastPoolId = id; x0_dbMap.emplace(name, id); findId = id; } return findId; } s16 CGuiWidgetIdDB::AddWidget(std::string_view name) { s16 findId = FindWidgetID(name); if (findId == -1) { ++x14_lastPoolId; x0_dbMap.emplace(name, x14_lastPoolId); findId = x14_lastPoolId; } return findId; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CGuiWidgetIdDB.hpp ================================================ #pragma once #include #include #include #include "Runtime/GCNTypes.hpp" namespace metaforce { class CGuiWidgetIdDB { std::unordered_map x0_dbMap; s16 x14_lastPoolId = 0; public: CGuiWidgetIdDB(); s16 FindWidgetID(std::string_view name) const; s16 AddWidget(std::string_view name, s16 id); s16 AddWidget(std::string_view name); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudBallInterface.cpp ================================================ #include "Runtime/GuiSys/CHudBallInterface.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiGroup.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { CHudBallInterface::CHudBallInterface(CGuiFrame& selHud, int pbAmount, int pbCapacity, int availableBombs, bool hasBombs, bool hasPb) : x40_pbAmount(pbAmount), x44_pbCapacity(pbCapacity), x48_availableBombs(availableBombs), x4c_hasPb(hasPb) { x0_camera = selHud.GetFrameCamera(); x4_basewidget_bombstuff = selHud.FindWidget("basewidget_bombstuff"); x8_basewidget_bombdeco = selHud.FindWidget("basewidget_bombdeco"); xc_model_bombicon = static_cast(selHud.FindWidget("model_bombicon")); x10_textpane_bombdigits = static_cast(selHud.FindWidget("textpane_bombdigits")); for (int i = 0; i < 3; ++i) { CGuiGroup* grp = static_cast(selHud.FindWidget(fmt::format("group_bombcount{}", i))); CGuiWidget* filled = grp->GetWorkerWidget(1); CGuiWidget* empty = grp->GetWorkerWidget(0); x14_group_bombfilled.push_back(filled); x24_group_bombempty.push_back(empty); if (filled) filled->SetColor(g_tweakGuiColors->GetBallBombFilledColor()); if (empty) empty->SetColor(g_tweakGuiColors->GetBallBombEmptyColor()); } x8_basewidget_bombdeco->SetColor(g_tweakGuiColors->GetBallBombDecoColor()); x34_camPos = x0_camera->GetLocalPosition(); if (CGuiWidget* w = selHud.FindWidget("basewidget_energydeco")) w->SetColor(g_tweakGuiColors->GetBallBombEnergyColor()); SetBombParams(pbAmount, pbCapacity, availableBombs, hasBombs, hasPb, true); } void CHudBallInterface::UpdatePowerBombReadoutColors() { zeus::CColor fontColor; zeus::CColor outlineColor; if (x40_pbAmount > 0) { fontColor = g_tweakGuiColors->GetPowerBombDigitAvailableFont(); outlineColor = g_tweakGuiColors->GetPowerBombDigitAvailableOutline(); } else if (x44_pbCapacity > 0) { fontColor = g_tweakGuiColors->GetPowerBombDigitDelpetedFont(); outlineColor = g_tweakGuiColors->GetPowerBombDigitDelpetedOutline(); } else { fontColor = zeus::skClear; outlineColor = zeus::skClear; } x10_textpane_bombdigits->TextSupport().SetFontColor(fontColor); x10_textpane_bombdigits->TextSupport().SetOutlineColor(outlineColor); zeus::CColor iconColor; if (x40_pbAmount > 0 && x4c_hasPb) iconColor = g_tweakGuiColors->GetPowerBombIconAvailableColor(); else if (x44_pbCapacity > 0) iconColor = g_tweakGuiColors->GetPowerBombIconDepletedColor(); else iconColor = zeus::skClear; xc_model_bombicon->SetColor(iconColor); } void CHudBallInterface::SetBombParams(int pbAmount, int pbCapacity, int availableBombs, bool hasBombs, bool hasPb, bool init) { if (pbAmount != x40_pbAmount || init) { x10_textpane_bombdigits->TextSupport().SetText(fmt::format("{:02d}", pbAmount)); x40_pbAmount = pbAmount; UpdatePowerBombReadoutColors(); } if (x44_pbCapacity != pbCapacity || init) { x44_pbCapacity = pbCapacity; UpdatePowerBombReadoutColors(); } if (x4c_hasPb != hasPb) { x4c_hasPb = hasPb; UpdatePowerBombReadoutColors(); } for (int i = 0; i < 3; ++i) { bool lit = i < availableBombs; x14_group_bombfilled[i]->SetVisibility(lit && hasBombs, ETraversalMode::Children); x24_group_bombempty[i]->SetVisibility(!lit && hasBombs, ETraversalMode::Children); } x48_availableBombs = availableBombs; x8_basewidget_bombdeco->SetVisibility(hasBombs && x44_pbCapacity > 0, ETraversalMode::Children); } void CHudBallInterface::SetBallModeFactor(float t) { float tmp = 0.5f * 448.f * g_tweakGui->GetBallViewportYReduction(); x0_camera->SetLocalTransform( zeus::CTransform::Translate(x34_camPos + zeus::CVector3f(0.f, 0.f, (t * tmp - tmp) * 0.01f))); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudBallInterface.hpp ================================================ #pragma once #include "Runtime/rstl.hpp" #include namespace metaforce { class CGuiCamera; class CGuiFrame; class CGuiModel; class CGuiTextPane; class CGuiWidget; class CHudBallInterface { CGuiCamera* x0_camera; CGuiWidget* x4_basewidget_bombstuff; CGuiWidget* x8_basewidget_bombdeco; CGuiModel* xc_model_bombicon; CGuiTextPane* x10_textpane_bombdigits; rstl::reserved_vector x14_group_bombfilled; rstl::reserved_vector x24_group_bombempty; zeus::CVector3f x34_camPos; int x40_pbAmount; int x44_pbCapacity; int x48_availableBombs; bool x4c_hasPb; void UpdatePowerBombReadoutColors(); public: CHudBallInterface(CGuiFrame& selHud, int pbAmount, int pbCapacity, int availableBombs, bool hasBombs, bool hasPb); void SetBombParams(int pbAmount, int pbCapacity, int availableBombs, bool hasBombs, bool hasPb, bool init); void SetBallModeFactor(float t); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudBossEnergyInterface.cpp ================================================ #include "Runtime/GuiSys/CHudBossEnergyInterface.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/GuiSys/CAuiEnergyBarT01.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" namespace metaforce { CHudBossEnergyInterface::CHudBossEnergyInterface(CGuiFrame& selHud) { x14_basewidget_bossenergystuff = selHud.FindWidget("basewidget_bossenergystuff"); x18_energybart01_bossbar = static_cast(selHud.FindWidget("energybart01_bossbar")); x1c_textpane_boss = static_cast(selHud.FindWidget("textpane_boss")); x18_energybart01_bossbar->SetCoordFunc(BossEnergyCoordFunc); x18_energybart01_bossbar->SetTesselation(0.2f); const auto& [filled, empty, shadow] = g_tweakGuiColors->GetVisorEnergyBarColors(0); x18_energybart01_bossbar->SetFilledColor(filled); x18_energybart01_bossbar->SetShadowColor(shadow); x18_energybart01_bossbar->SetEmptyColor(empty); } void CHudBossEnergyInterface::Update(float dt) { if (x10_24_visible) x4_fader = std::min(x4_fader + dt, 1.f); else x4_fader = std::max(0.f, x4_fader - dt); if (x4_fader > 0.f) { zeus::CColor color = zeus::skWhite; color.a() = x0_alpha * x4_fader; x14_basewidget_bossenergystuff->SetColor(color); x14_basewidget_bossenergystuff->SetVisibility(true, ETraversalMode::Children); } else { x14_basewidget_bossenergystuff->SetVisibility(false, ETraversalMode::Children); } } void CHudBossEnergyInterface::SetBossParams(bool visible, std::u16string_view name, float curEnergy, float maxEnergy) { x10_24_visible = visible; if (visible) { x18_energybart01_bossbar->SetFilledDrainSpeed(maxEnergy); x18_energybart01_bossbar->SetCurrEnergy(curEnergy, CAuiEnergyBarT01::ESetMode::Normal); x18_energybart01_bossbar->SetMaxEnergy(maxEnergy); x1c_textpane_boss->TextSupport().SetText(name); } x8_curEnergy = curEnergy; xc_maxEnergy = maxEnergy; } std::pair CHudBossEnergyInterface::BossEnergyCoordFunc(float t) { float x = 9.25f * t - 4.625f; return {zeus::CVector3f(x, 0.f, 0.f), zeus::CVector3f(x, 0.f, 0.4f)}; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudBossEnergyInterface.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include namespace metaforce { class CAuiEnergyBarT01; class CGuiFrame; class CGuiTextPane; class CGuiWidget; class CHudBossEnergyInterface { float x0_alpha = 0.f; float x4_fader = 0.f; float x8_curEnergy = 0.f; float xc_maxEnergy = 0.f; bool x10_24_visible : 1 = false; CGuiWidget* x14_basewidget_bossenergystuff; CAuiEnergyBarT01* x18_energybart01_bossbar; CGuiTextPane* x1c_textpane_boss; public: explicit CHudBossEnergyInterface(CGuiFrame& selHud); void Update(float dt); void SetAlpha(float a) { x0_alpha = a; } void SetBossParams(bool visible, std::u16string_view name, float curEnergy, float maxEnergy); static std::pair BossEnergyCoordFunc(float t); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudDecoInterface.cpp ================================================ #include "Runtime/GuiSys/CHudDecoInterface.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/GuiSys/CAuiEnergyBarT01.hpp" #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" #include "Runtime/MP1/CSamusHud.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { void IHudDecoInterface::SetReticuleTransform(const zeus::CMatrix3f& xf) {} void IHudDecoInterface::SetDecoRotation(float angle) {} void IHudDecoInterface::SetFrameColorValue(float v) {} void IHudDecoInterface::Draw() {} void IHudDecoInterface::ProcessInput(const CFinalInput& input) {} float IHudDecoInterface::GetHudTextAlpha() const { return 1.f; } CHudDecoInterfaceCombat::CHudDecoInterfaceCombat(CGuiFrame& selHud) { x6c_camera = selHud.GetFrameCamera(); x2c_camPos = x6c_camera->GetLocalPosition(); x70_basewidget_pivot = selHud.FindWidget("basewidget_pivot"); x74_basewidget_deco = selHud.FindWidget("basewidget_deco"); x78_basewidget_tickdeco0 = selHud.FindWidget("basewidget_tickdeco0"); x7c_basewidget_frame = selHud.FindWidget("basewidget_frame"); x14_pivotPosition = x70_basewidget_pivot->GetIdlePosition(); x78_basewidget_tickdeco0->SetColor(g_tweakGuiColors->GetTickDecoColor()); x38_basePosition = x7c_basewidget_frame->GetLocalPosition(); x44_baseRotation = x7c_basewidget_frame->GetLocalTransform().buildMatrix3f(); CHudDecoInterfaceCombat::UpdateHudAlpha(); } void CHudDecoInterfaceCombat::UpdateVisibility() { bool vis = x68_24_visDebug && x68_25_visGame; x74_basewidget_deco->SetVisibility(vis, ETraversalMode::Children); x78_basewidget_tickdeco0->SetVisibility(vis, ETraversalMode::Children); } void CHudDecoInterfaceCombat::SetIsVisibleDebug(bool v) { x68_24_visDebug = v; UpdateVisibility(); } void CHudDecoInterfaceCombat::SetIsVisibleGame(bool v) { x68_25_visGame = v; UpdateVisibility(); } void CHudDecoInterfaceCombat::SetHudRotation(const zeus::CQuaternion& rot) { x4_rotation = rot; } void CHudDecoInterfaceCombat::SetHudOffset(const zeus::CVector3f& off) { x20_offset = off; } void CHudDecoInterfaceCombat::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) { x7c_basewidget_frame->SetLocalTransform(zeus::CTransform(rotation * x44_baseRotation, position + x38_basePosition)); } void CHudDecoInterfaceCombat::SetFrameColorValue(float v) { zeus::CColor color = v > 0.f ? zeus::skWhite : g_tweakGuiColors->GetHudFrameColor(); x7c_basewidget_frame->SetColor(color); } void CHudDecoInterfaceCombat::Update(float dt, const CStateManager& stateMgr) { x6c_camera->SetO2WTransform( MP1::CSamusHud::BuildFinalCameraTransform(x4_rotation, x14_pivotPosition + x20_offset, x2c_camPos)); } void CHudDecoInterfaceCombat::UpdateCameraDebugSettings(float fov, float y, float z) { x6c_camera->SetFov(fov); x2c_camPos.y() = y; x2c_camPos.z() = z; } void CHudDecoInterfaceCombat::UpdateHudAlpha() { zeus::CColor color = zeus::skWhite; color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f; x70_basewidget_pivot->SetColor(color); } CHudDecoInterfaceScan::CHudDecoInterfaceScan(CGuiFrame& selHud) : x14_selHud(selHud), x18_scanDisplay(selHud) { x4_scanHudFlat = g_SimplePool->GetObj("FRME_ScanHudFlat"); x234_sidesPositioner = g_tweakGui->GetScanSidesPositionStart(); x244_camera = selHud.GetFrameCamera(); x248_basewidget_pivot = selHud.FindWidget("basewidget_pivot"); x24c_basewidget_leftside = selHud.FindWidget("basewidget_leftside"); x250_basewidget_rightside = selHud.FindWidget("basewidget_rightside"); x1f4_pivotPosition = x248_basewidget_pivot->GetIdlePosition(); if (CGuiWidget* deco = selHud.FindWidget("basewidget_deco")) deco->SetColor(g_tweakGuiColors->GetHudFrameColor()); x218_leftsidePosition = x24c_basewidget_leftside->GetLocalPosition(); zeus::CTransform leftXf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle()), x218_leftsidePosition); x24c_basewidget_leftside->SetLocalTransform(leftXf); if (CGuiWidget* w = selHud.FindWidget("basewidget_databankl")) { zeus::CTransform xf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle() * -1.f), w->GetLocalPosition()); w->SetLocalTransform(xf); } if (CGuiWidget* w = selHud.FindWidget("basewidget_leftguages")) { zeus::CTransform xf(zeus::CMatrix3f(zeus::CVector3f{g_tweakGui->GetScanSidesXScale(), 1.f, 1.f}), w->GetLocalPosition()); w->SetLocalTransform(xf); } x224_rightsidePosition = x250_basewidget_rightside->GetLocalPosition(); zeus::CTransform rightXf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle() * -1.f), x224_rightsidePosition); x250_basewidget_rightside->SetLocalTransform(rightXf); if (CGuiWidget* w = selHud.FindWidget("basewidget_databankr")) { zeus::CTransform xf(zeus::CMatrix3f::RotateZ(g_tweakGui->GetScanSidesAngle()), w->GetLocalPosition()); w->SetLocalTransform(xf); } if (CGuiWidget* w = selHud.FindWidget("basewidget_rightguages")) { zeus::CTransform xf(zeus::CMatrix3f(zeus::CVector3f{g_tweakGui->GetScanSidesXScale(), 1.f, 1.f}), w->GetLocalPosition()); w->SetLocalTransform(xf); } zeus::CVector3f sidesPos(x234_sidesPositioner, 0.f, 0.f); x24c_basewidget_leftside->SetLocalPosition(x24c_basewidget_leftside->RotateO2P(x218_leftsidePosition + sidesPos)); x250_basewidget_rightside->SetLocalPosition(x250_basewidget_rightside->RotateO2P(x224_rightsidePosition - sidesPos)); x234_sidesPositioner = FLT_MAX; CHudDecoInterfaceScan::UpdateHudAlpha(); } void CHudDecoInterfaceScan::UpdateVisibility() { // Empty } void CHudDecoInterfaceScan::SetIsVisibleDebug(bool v) { x240_24_visDebug = v; UpdateVisibility(); } void CHudDecoInterfaceScan::SetIsVisibleGame(bool v) { x240_25_visGame = v; UpdateVisibility(); } void CHudDecoInterfaceScan::SetHudRotation(const zeus::CQuaternion& rot) { x1e4_rotation = rot; } void CHudDecoInterfaceScan::SetHudOffset(const zeus::CVector3f& off) { x200_offset = off; } void CHudDecoInterfaceScan::SetReticuleTransform(const zeus::CMatrix3f& xf) { // Empty } void CHudDecoInterfaceScan::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) { // Empty } void CHudDecoInterfaceScan::SetFrameColorValue(float v) { // Empty } void CHudDecoInterfaceScan::InitializeFlatFrame() { x10_loadedScanHudFlat = x4_scanHudFlat.GetObj(); x10_loadedScanHudFlat->SetMaxAspect(1.33f); x10_loadedScanHudFlat->GetFrameCamera()->SetO2WTransform(zeus::CTransform::Translate(x20c_camPos)); x258_flat_basewidget_scanguage = x10_loadedScanHudFlat->FindWidget("basewidget_scanguage"); x258_flat_basewidget_scanguage->SetVisibility(false, ETraversalMode::Children); x254_flat_textpane_scanning = static_cast(x10_loadedScanHudFlat->FindWidget("textpane_scanning")); x25c_flat_energybart01_scanbar = static_cast(x10_loadedScanHudFlat->FindWidget("energybart01_scanbar")); x264_flat_textpane_message = static_cast(x10_loadedScanHudFlat->FindWidget("textpane_message")); x268_flat_textpane_scrollmessage = static_cast(x10_loadedScanHudFlat->FindWidget("textpane_scrollmessage")); x260_flat_basewidget_textgroup = x10_loadedScanHudFlat->FindWidget("basewidget_textgroup"); x26c_flat_model_xmark = static_cast(x10_loadedScanHudFlat->FindWidget("model_xmark")); x270_flat_model_abutton = static_cast(x10_loadedScanHudFlat->FindWidget("model_abutton")); x274_flat_model_dash = static_cast(x10_loadedScanHudFlat->FindWidget("model_dash")); x260_flat_basewidget_textgroup->SetVisibility(false, ETraversalMode::Children); x254_flat_textpane_scanning->SetIsVisible(false); x254_flat_textpane_scanning->TextSupport().SetFontColor(g_tweakGuiColors->GetHudMessageFill()); x254_flat_textpane_scanning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetHudMessageOutline()); x25c_flat_energybart01_scanbar->SetCoordFunc(CAuiEnergyBarT01::DownloadBarCoordFunc); x25c_flat_energybart01_scanbar->ResetMaxEnergy(); x25c_flat_energybart01_scanbar->SetFilledColor(zeus::CColor(0.4f, 0.68f, 0.88f, 1.f)); x25c_flat_energybart01_scanbar->SetShadowColor(zeus::skClear); x25c_flat_energybart01_scanbar->SetEmptyColor(zeus::skClear); x25c_flat_energybart01_scanbar->SetFilledDrainSpeed(999.f); x25c_flat_energybart01_scanbar->SetShadowDrainSpeed(999.f); x25c_flat_energybart01_scanbar->SetShadowDrainDelay(0.f); x25c_flat_energybart01_scanbar->SetIsAlwaysResetTimer(false); x26c_flat_model_xmark->SetVisibility(false, ETraversalMode::Children); x26c_flat_model_xmark->SetVisibility(false, ETraversalMode::Children); x270_flat_model_abutton->SetVisibility(false, ETraversalMode::Children); x274_flat_model_dash->SetVisibility(false, ETraversalMode::Children); } const CScannableObjectInfo* CHudDecoInterfaceScan::GetCurrScanInfo(const CStateManager& stateMgr) const { if (x1d4_latestScanState == CPlayer::EPlayerScanState::NotScanning) return nullptr; if (TCastToConstPtr act = stateMgr.GetObjectById(x1d2_latestScanningObject)) return act->GetScannableObjectInfo(); return nullptr; } void CHudDecoInterfaceScan::UpdateScanDisplay(const CStateManager& stateMgr, float dt) { CPlayer& player = stateMgr.GetPlayer(); CPlayer::EPlayerScanState scanState = player.GetScanningState(); if (scanState != x1d4_latestScanState) { if (player.IsNewScanScanning()) { if (scanState == CPlayer::EPlayerScanState::ScanComplete) { if (x1d4_latestScanState == CPlayer::EPlayerScanState::Scanning) { // Scan complete x254_flat_textpane_scanning->TextSupport().SetText(g_MainStringTable->GetString(15)); x254_flat_textpane_scanning->TextSupport().SetTypeWriteEffectOptions(false, 0.f, 40.f); x238_scanningTextAlpha = 2.f; } } else if (scanState == CPlayer::EPlayerScanState::Scanning) { // Scanning x254_flat_textpane_scanning->TextSupport().SetText(g_MainStringTable->GetString(14)); x254_flat_textpane_scanning->TextSupport().SetTypeWriteEffectOptions(false, 0.f, 40.f); x238_scanningTextAlpha = 2.f; } } x1d4_latestScanState = scanState; } if (player.GetScanningObjectId() != x1d2_latestScanningObject) x1d2_latestScanningObject = player.GetScanningObjectId(); if (player.GetOrbitTargetId() != x1d0_latestHudPoi) { x1d0_latestHudPoi = player.GetOrbitTargetId(); if (x1d0_latestHudPoi != kInvalidUniqueId) { if (!player.ObjectInScanningRange(x1d0_latestHudPoi, stateMgr)) { // Object out of scanning range x254_flat_textpane_scanning->TextSupport().SetText(g_MainStringTable->GetString(16)); x254_flat_textpane_scanning->TextSupport().SetTypeWriteEffectOptions(true, 0.f, 40.f); x238_scanningTextAlpha = 1.f; } } } const CScannableObjectInfo* scanInfo = GetCurrScanInfo(stateMgr); if (x1d2_latestScanningObject != x18_scanDisplay.x10_objId || !scanInfo) { x18_scanDisplay.StopScan(); if (x18_scanDisplay.xc_state == CScanDisplay::EScanState::Inactive && scanInfo) { x18_scanDisplay.StartScan(x1d2_latestScanningObject, *scanInfo, x264_flat_textpane_message, x268_flat_textpane_scrollmessage, x260_flat_basewidget_textgroup, x26c_flat_model_xmark, x270_flat_model_abutton, x274_flat_model_dash, player.GetScanningTime()); } } x18_scanDisplay.Update(dt, player.GetScanningTime()); if (x1d2_latestScanningObject != kInvalidUniqueId && GetCurrScanInfo(stateMgr)) if (TCastToConstPtr act = stateMgr.GetObjectById(x1d2_latestScanningObject)) if (const CScannableObjectInfo* actScan = act->GetScannableObjectInfo()) x25c_flat_energybart01_scanbar->SetCurrEnergy(x1d8_scanningTime / actScan->GetTotalDownloadTime(), CAuiEnergyBarT01::ESetMode::Normal); if (x1d4_latestScanState != CPlayer::EPlayerScanState::Scanning) if (x1d0_latestHudPoi == kInvalidUniqueId || player.ObjectInScanningRange(x1d0_latestHudPoi, stateMgr)) x238_scanningTextAlpha = std::max(0.f, x238_scanningTextAlpha - dt); if (x238_scanningTextAlpha > 0.f) { zeus::CColor color = zeus::skWhite; color.a() = std::min(x238_scanningTextAlpha, 1.f); x254_flat_textpane_scanning->SetColor(color); x254_flat_textpane_scanning->SetIsVisible(true); } else { x254_flat_textpane_scanning->SetIsVisible(false); } if (GetCurrScanInfo(stateMgr)) x23c_scanBarAlpha = std::min(x23c_scanBarAlpha + 2.f * dt, 1.f); else x23c_scanBarAlpha = std::max(0.f, x23c_scanBarAlpha - 2.f * dt); if (x23c_scanBarAlpha > 0.f) { zeus::CColor color = zeus::skWhite; color.a() = std::min(x23c_scanBarAlpha, 1.f); x258_flat_basewidget_scanguage->SetColor(color); x258_flat_basewidget_scanguage->SetVisibility(true, ETraversalMode::Children); } else { x258_flat_basewidget_scanguage->SetVisibility(false, ETraversalMode::Children); } } void CHudDecoInterfaceScan::Update(float dt, const CStateManager& stateMgr) { CPlayer& player = stateMgr.GetPlayer(); CPlayer::EPlayerScanState scanState = player.GetScanningState(); if (scanState != CPlayer::EPlayerScanState::NotScanning) x1d8_scanningTime = player.GetScanningTime(); if (scanState == CPlayer::EPlayerScanState::Scanning || scanState == CPlayer::EPlayerScanState::ScanComplete) x230_sidesTimer = std::min(x230_sidesTimer + dt, g_tweakGui->GetScanSidesEndTime()); else x230_sidesTimer = std::max(0.f, x230_sidesTimer - dt); float sidesT = x230_sidesTimer < g_tweakGui->GetScanSidesStartTime() ? 0.f : (x230_sidesTimer - g_tweakGui->GetScanSidesStartTime()) / g_tweakGui->GetScanSidesDuration(); float oldSidesPositioner = x234_sidesPositioner; x234_sidesPositioner = (1.f - sidesT) * g_tweakGui->GetScanSidesPositionStart() + sidesT * g_tweakGui->GetScanSidesPositionEnd(); if (oldSidesPositioner != x234_sidesPositioner) { zeus::CVector3f sidesPos(x234_sidesPositioner, 0.f, 0.f); x24c_basewidget_leftside->SetLocalPosition(x218_leftsidePosition + x24c_basewidget_leftside->RotateO2P(sidesPos)); x250_basewidget_rightside->SetLocalPosition(x224_rightsidePosition - x250_basewidget_rightside->RotateO2P(sidesPos)); } x244_camera->SetO2WTransform( MP1::CSamusHud::BuildFinalCameraTransform(x1e4_rotation, x1f4_pivotPosition + x200_offset, x20c_camPos)); if (!x10_loadedScanHudFlat) { if (!x4_scanHudFlat.IsLoaded() || !x4_scanHudFlat->GetIsFinishedLoading()) return; InitializeFlatFrame(); } x10_loadedScanHudFlat->Update(dt); UpdateScanDisplay(stateMgr, dt); } void CHudDecoInterfaceScan::Draw() { SCOPED_GRAPHICS_DEBUG_GROUP("CHudDecoInterfaceScan::Draw", zeus::skGreen); x18_scanDisplay.Draw(); if (x10_loadedScanHudFlat != nullptr) { x10_loadedScanHudFlat->Draw(CGuiWidgetDrawParms::Default()); } } void CHudDecoInterfaceScan::ProcessInput(const CFinalInput& input) { x18_scanDisplay.ProcessInput(input); } void CHudDecoInterfaceScan::UpdateCameraDebugSettings(float fov, float y, float z) { x244_camera->SetFov(fov); x20c_camPos.y() = y; x20c_camPos.z() = z; } void CHudDecoInterfaceScan::UpdateHudAlpha() { zeus::CColor color = zeus::skWhite; color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f; x248_basewidget_pivot->SetColor(color); } float CHudDecoInterfaceScan::GetHudTextAlpha() const { return 1.f - std::max(std::min(x238_scanningTextAlpha, 1.f), x18_scanDisplay.x1a8_bodyAlpha); } CHudDecoInterfaceXRay::CHudDecoInterfaceXRay(CGuiFrame& selHud) { xa0_camera = selHud.GetFrameCamera(); x30_camPos = xa0_camera->GetLocalPosition(); xa4_basewidget_pivot = selHud.FindWidget("basewidget_pivot"); xa8_basewidget_seeker = selHud.FindWidget("basewidget_seeker"); xac_basewidget_rotate = selHud.FindWidget("basewidget_rotate"); if (CGuiWidget* w = selHud.FindWidget("basewidget_energydeco")) w->SetColor(g_tweakGuiColors->GetXRayEnergyDecoColor()); if (CGuiWidget* w = selHud.FindWidget("model_frame")) w->SetDepthWrite(true); if (CGuiWidget* w = selHud.FindWidget("model_frame1")) w->SetDepthWrite(true); if (CGuiWidget* w = selHud.FindWidget("model_frame2")) w->SetDepthWrite(true); if (CGuiWidget* w = selHud.FindWidget("model_frame3")) w->SetDepthWrite(true); if (CGuiWidget* w = selHud.FindWidget("model_misslieslider")) w->SetDepthWrite(true); if (CGuiWidget* w = selHud.FindWidget("model_threatslider")) w->SetDepthWrite(true); CHudDecoInterfaceXRay::UpdateHudAlpha(); } void CHudDecoInterfaceXRay::UpdateVisibility() { // Empty } void CHudDecoInterfaceXRay::SetIsVisibleDebug(bool v) { x9c_24_visDebug = v; UpdateVisibility(); } void CHudDecoInterfaceXRay::SetIsVisibleGame(bool v) { x9c_25_visGame = v; UpdateVisibility(); } void CHudDecoInterfaceXRay::SetHudRotation(const zeus::CQuaternion& rot) { x8_rotation = rot; } void CHudDecoInterfaceXRay::SetHudOffset(const zeus::CVector3f& off) { x24_offset = off; } void CHudDecoInterfaceXRay::SetReticuleTransform(const zeus::CMatrix3f& xf) { x3c_reticuleXf = xf; } void CHudDecoInterfaceXRay::SetDecoRotation(float angle) { xac_basewidget_rotate->SetLocalTransform( zeus::CTransform(zeus::CMatrix3f::RotateY(angle), xac_basewidget_rotate->GetLocalPosition())); } void CHudDecoInterfaceXRay::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) { // Empty } void CHudDecoInterfaceXRay::SetFrameColorValue(float v) { // Empty } void CHudDecoInterfaceXRay::Update(float dt, const CStateManager& stateMgr) { if (stateMgr.GetPlayer().GetOrbitState() == CPlayer::EPlayerOrbitState::OrbitObject) x4_seekerScale = std::max(x4_seekerScale - 3.f * dt, 0.35f); else x4_seekerScale = std::min(3.f * dt + x4_seekerScale, 1.f); xa0_camera->SetO2WTransform( MP1::CSamusHud::BuildFinalCameraTransform(x8_rotation, x18_pivotPosition + x24_offset, x30_camPos)); xa8_basewidget_seeker->SetLocalTransform( zeus::CTransform(zeus::CMatrix3f(x4_seekerScale) * x3c_reticuleXf, x60_seekerPosition)); } void CHudDecoInterfaceXRay::UpdateCameraDebugSettings(float fov, float y, float z) { xa0_camera->SetFov(fov); x30_camPos.y() = y; x30_camPos.z() = z; } void CHudDecoInterfaceXRay::UpdateHudAlpha() { zeus::CColor color = zeus::skWhite; color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f; xa4_basewidget_pivot->SetColor(color); } CHudDecoInterfaceThermal::CHudDecoInterfaceThermal(CGuiFrame& selHud) { x74_camera = selHud.GetFrameCamera(); x2c_camPos = x74_camera->GetLocalPosition(); x78_basewidget_pivot = selHud.FindWidget("basewidget_pivot"); x7c_basewidget_reticle = selHud.FindWidget("basewidget_reticle"); x80_model_retflash = static_cast(selHud.FindWidget("model_retflash")); x14_pivotPosition = x78_basewidget_pivot->GetIdlePosition(); x5c_reticulePosition = x7c_basewidget_reticle->GetIdlePosition(); if (CGuiWidget* w = selHud.FindWidget("basewidget_deco")) w->SetColor(g_tweakGuiColors->GetThermalDecoColor()); if (CGuiWidget* w = selHud.FindWidget("basewidget_oultlinesa")) w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor()); if (CGuiWidget* w = selHud.FindWidget("basewidget_lock")) w->SetColor(g_tweakGuiColors->GetThermalLockColor()); if (CGuiWidget* w = selHud.FindWidget("basewidget_reticle")) w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor()); if (CGuiWidget* w = selHud.FindWidget("basewidget_lockon")) w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor()); if (CGuiWidget* w = selHud.FindWidget("model_threaticon")) w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor()); if (CGuiWidget* w = selHud.FindWidget("model_missileicon")) w->SetColor(g_tweakGuiColors->GetThermalOutlinesColor()); if (CGuiWidget* w = selHud.FindWidget("basewidget_lock")) { for (CGuiWidget* c = static_cast(w->GetChildObject()); c; c = static_cast(c->GetNextSibling())) { x84_lockonWidgets.emplace_back(c, c->GetLocalTransform()); c->SetLocalTransform(c->GetLocalTransform() * zeus::CTransform::Scale(x68_lockonScale)); } } x14_pivotPosition = x78_basewidget_pivot->GetIdlePosition(); CHudDecoInterfaceThermal::UpdateHudAlpha(); } void CHudDecoInterfaceThermal::UpdateVisibility() { // Empty } void CHudDecoInterfaceThermal::SetIsVisibleDebug(bool v) { x70_24_visDebug = v; UpdateVisibility(); } void CHudDecoInterfaceThermal::SetIsVisibleGame(bool v) { x70_25_visGame = v; UpdateVisibility(); } void CHudDecoInterfaceThermal::SetHudRotation(const zeus::CQuaternion& rot) { x4_rotation = rot; } void CHudDecoInterfaceThermal::SetHudOffset(const zeus::CVector3f& off) { x20_offset = off; } void CHudDecoInterfaceThermal::SetReticuleTransform(const zeus::CMatrix3f& xf) { x38_reticuleXf = xf; } void CHudDecoInterfaceThermal::SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) { // Empty } void CHudDecoInterfaceThermal::Update(float dt, const CStateManager& stateMgr) { float oldLockonScale = x68_lockonScale; if (stateMgr.GetPlayer().GetOrbitTargetId() != kInvalidUniqueId) x68_lockonScale = std::max(x68_lockonScale - 15.f * dt, 1.f); else x68_lockonScale = std::min(x68_lockonScale + 15.f * dt, 5.f); if (oldLockonScale != x68_lockonScale) for (auto& lockWidget : x84_lockonWidgets) lockWidget.first->SetLocalTransform(lockWidget.second * zeus::CTransform::Scale(x68_lockonScale)); x6c_retflashTimer += dt; if (x6c_retflashTimer > 1.f) x6c_retflashTimer -= 2.f; zeus::CColor flashColor = zeus::skWhite; flashColor.a() = std::fabs(x6c_retflashTimer) * 0.5f + 0.5f; x80_model_retflash->SetColor(flashColor); x74_camera->SetO2WTransform( MP1::CSamusHud::BuildFinalCameraTransform(x4_rotation, x14_pivotPosition + x20_offset, x2c_camPos)); x7c_basewidget_reticle->SetLocalTransform(zeus::CTransform(x38_reticuleXf, x5c_reticulePosition)); } void CHudDecoInterfaceThermal::UpdateCameraDebugSettings(float fov, float y, float z) { x74_camera->SetFov(fov); x2c_camPos.y() = y; x2c_camPos.z() = z; } void CHudDecoInterfaceThermal::UpdateHudAlpha() { zeus::CColor color = zeus::skWhite; color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f; x78_basewidget_pivot->SetColor(color); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudDecoInterface.hpp ================================================ #pragma once #include "Runtime/CToken.hpp" #include "Runtime/GuiSys/CScanDisplay.hpp" #include "Runtime/World/CPlayer.hpp" #include #include #include namespace metaforce { class CGuiFrame; struct CFinalInput; class CStateManager; class CGuiCamera; class CGuiWidget; class CAuiEnergyBarT01; class CGuiModel; class IHudDecoInterface { public: virtual void SetIsVisibleDebug(bool v) = 0; virtual void SetIsVisibleGame(bool v) = 0; virtual void SetHudRotation(const zeus::CQuaternion& rot) = 0; virtual void SetHudOffset(const zeus::CVector3f& off) = 0; virtual void SetReticuleTransform(const zeus::CMatrix3f& xf); virtual void SetDecoRotation(float angle); virtual void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) = 0; virtual void SetFrameColorValue(float v); virtual void Update(float dt, const CStateManager& stateMgr) = 0; virtual void Draw(); virtual void ProcessInput(const CFinalInput& input); virtual void UpdateCameraDebugSettings(float fov, float y, float z) = 0; virtual void UpdateHudAlpha() = 0; virtual float GetHudTextAlpha() const; virtual ~IHudDecoInterface() = default; }; class CHudDecoInterfaceCombat : public IHudDecoInterface { zeus::CQuaternion x4_rotation; zeus::CVector3f x14_pivotPosition; zeus::CVector3f x20_offset; zeus::CVector3f x2c_camPos; zeus::CVector3f x38_basePosition; zeus::CMatrix3f x44_baseRotation; bool x68_24_visDebug : 1 = true; bool x68_25_visGame : 1 = true; CGuiCamera* x6c_camera; CGuiWidget* x70_basewidget_pivot; CGuiWidget* x74_basewidget_deco; CGuiWidget* x78_basewidget_tickdeco0; CGuiWidget* x7c_basewidget_frame; void UpdateVisibility(); public: explicit CHudDecoInterfaceCombat(CGuiFrame& selHud); void SetIsVisibleDebug(bool v) override; void SetIsVisibleGame(bool v) override; void SetHudRotation(const zeus::CQuaternion& rot) override; void SetHudOffset(const zeus::CVector3f& off) override; void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override; void SetFrameColorValue(float v) override; void Update(float dt, const CStateManager& stateMgr) override; void UpdateCameraDebugSettings(float fov, float y, float z) override; void UpdateHudAlpha() override; }; class CHudDecoInterfaceScan : public IHudDecoInterface { TLockedToken x4_scanHudFlat; CGuiFrame* x10_loadedScanHudFlat = nullptr; CGuiFrame& x14_selHud; CScanDisplay x18_scanDisplay; TUniqueId x1d0_latestHudPoi = kInvalidUniqueId; TUniqueId x1d2_latestScanningObject = kInvalidUniqueId; CPlayer::EPlayerScanState x1d4_latestScanState = CPlayer::EPlayerScanState::NotScanning; float x1d8_scanningTime = 0.f; float x1dc_ = 0.f; float x1e0_ = 1.f; zeus::CQuaternion x1e4_rotation; zeus::CVector3f x1f4_pivotPosition; zeus::CVector3f x200_offset; zeus::CVector3f x20c_camPos; zeus::CVector3f x218_leftsidePosition; zeus::CVector3f x224_rightsidePosition; float x230_sidesTimer = 0.f; float x234_sidesPositioner; float x238_scanningTextAlpha = 0.f; float x23c_scanBarAlpha = 0.f; bool x240_24_visDebug : 1 = true; bool x240_25_visGame : 1 = true; CGuiCamera* x244_camera; CGuiWidget* x248_basewidget_pivot; CGuiWidget* x24c_basewidget_leftside; CGuiWidget* x250_basewidget_rightside; CGuiTextPane* x254_flat_textpane_scanning; CGuiWidget* x258_flat_basewidget_scanguage; CAuiEnergyBarT01* x25c_flat_energybart01_scanbar; CGuiWidget* x260_flat_basewidget_textgroup; CGuiTextPane* x264_flat_textpane_message; CGuiTextPane* x268_flat_textpane_scrollmessage; CGuiModel* x26c_flat_model_xmark; CGuiModel* x270_flat_model_abutton; CGuiModel* x274_flat_model_dash; void UpdateVisibility(); public: explicit CHudDecoInterfaceScan(CGuiFrame& selHud); void SetIsVisibleDebug(bool v) override; void SetIsVisibleGame(bool v) override; void SetHudRotation(const zeus::CQuaternion& rot) override; void SetHudOffset(const zeus::CVector3f& off) override; void SetReticuleTransform(const zeus::CMatrix3f& xf) override; void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override; void SetFrameColorValue(float v) override; void InitializeFlatFrame(); const CScannableObjectInfo* GetCurrScanInfo(const CStateManager& stateMgr) const; void UpdateScanDisplay(const CStateManager& stateMgr, float dt); void Update(float dt, const CStateManager& stateMgr) override; void Draw() override; void ProcessInput(const CFinalInput& input) override; void UpdateCameraDebugSettings(float fov, float y, float z) override; void UpdateHudAlpha() override; float GetHudTextAlpha() const override; }; class CHudDecoInterfaceXRay : public IHudDecoInterface { float x4_seekerScale = 1.f; zeus::CQuaternion x8_rotation; zeus::CVector3f x18_pivotPosition; zeus::CVector3f x24_offset; zeus::CVector3f x30_camPos; zeus::CMatrix3f x3c_reticuleXf; zeus::CVector3f x60_seekerPosition; zeus::CVector3f x6c_; zeus::CMatrix3f x78_; bool x9c_24_visDebug : 1 = true; bool x9c_25_visGame : 1 = true; CGuiCamera* xa0_camera; CGuiWidget* xa4_basewidget_pivot; CGuiWidget* xa8_basewidget_seeker; CGuiWidget* xac_basewidget_rotate; void UpdateVisibility(); public: explicit CHudDecoInterfaceXRay(CGuiFrame& selHud); void SetIsVisibleDebug(bool v) override; void SetIsVisibleGame(bool v) override; void SetHudRotation(const zeus::CQuaternion& rot) override; void SetHudOffset(const zeus::CVector3f& off) override; void SetReticuleTransform(const zeus::CMatrix3f& xf) override; void SetDecoRotation(float angle) override; void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override; void SetFrameColorValue(float v) override; void Update(float dt, const CStateManager& stateMgr) override; void UpdateCameraDebugSettings(float fov, float y, float z) override; void UpdateHudAlpha() override; }; class CHudDecoInterfaceThermal : public IHudDecoInterface { zeus::CQuaternion x4_rotation; zeus::CVector3f x14_pivotPosition; zeus::CVector3f x20_offset; zeus::CVector3f x2c_camPos; zeus::CMatrix3f x38_reticuleXf; zeus::CVector3f x5c_reticulePosition; float x68_lockonScale = 5.f; float x6c_retflashTimer = 0.f; bool x70_24_visDebug : 1 = true; bool x70_25_visGame : 1 = true; CGuiCamera* x74_camera; CGuiWidget* x78_basewidget_pivot; CGuiWidget* x7c_basewidget_reticle; CGuiModel* x80_model_retflash; std::vector> x84_lockonWidgets; void UpdateVisibility(); public: explicit CHudDecoInterfaceThermal(CGuiFrame& selHud); void SetIsVisibleDebug(bool v) override; void SetIsVisibleGame(bool v) override; void SetHudRotation(const zeus::CQuaternion& rot) override; void SetHudOffset(const zeus::CVector3f& off) override; void SetReticuleTransform(const zeus::CMatrix3f& xf) override; void SetDamageTransform(const zeus::CMatrix3f& rotation, const zeus::CVector3f& position) override; void Update(float dt, const CStateManager& stateMgr) override; void UpdateCameraDebugSettings(float fov, float y, float z) override; void UpdateHudAlpha() override; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudEnergyInterface.cpp ================================================ #include "Runtime/GuiSys/CHudEnergyInterface.hpp" #include #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Audio/CSfxManager.hpp" #include "Runtime/GuiSys/CAuiEnergyBarT01.hpp" #include "Runtime/GuiSys/CAuiMeter.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { constexpr std::array CoordFuncs{ CHudEnergyInterface::CombatEnergyCoordFunc, CHudEnergyInterface::CombatEnergyCoordFunc, CHudEnergyInterface::XRayEnergyCoordFunc, CHudEnergyInterface::ThermalEnergyCoordFunc, CHudEnergyInterface::BallEnergyCoordFunc, }; constexpr std::array Tesselations{ 0.2f, 0.2f, 0.1f, 0.2f, 1.f, }; CHudEnergyInterface::CHudEnergyInterface(CGuiFrame& selHud, float tankEnergy, int totalEnergyTanks, int numTanksFilled, bool energyLow, EHudType hudType) : x0_hudType(hudType) , xc_tankEnergy(tankEnergy) , x10_totalEnergyTanks(totalEnergyTanks) , x14_numTanksFilled(numTanksFilled) , x1c_27_energyLow(energyLow) { x20_textpane_energydigits = static_cast(selHud.FindWidget("textpane_energydigits")); x24_meter_energytanks = static_cast(selHud.FindWidget("meter_energytanks")); x28_textpane_energywarning = static_cast(selHud.FindWidget("textpane_energywarning")); x2c_energybart01_energybar = static_cast(selHud.FindWidget("energybart01_energybar")); x2c_energybart01_energybar->SetCoordFunc(CoordFuncs[size_t(hudType)]); x2c_energybart01_energybar->SetTesselation(Tesselations[size_t(hudType)]); ITweakGuiColors::SVisorEnergyBarColors barColors = g_tweakGuiColors->GetVisorEnergyBarColors(int(hudType)); ITweakGuiColors::SVisorEnergyInitColors initColors = g_tweakGuiColors->GetVisorEnergyInitColors(int(hudType)); x20_textpane_energydigits->TextSupport().SetFontColor(initColors.digitsFont); x20_textpane_energydigits->TextSupport().SetOutlineColor(initColors.digitsOutline); x2c_energybart01_energybar->SetMaxEnergy(CPlayerState::GetBaseHealthCapacity()); x2c_energybart01_energybar->SetFilledColor(barColors.filled); x2c_energybart01_energybar->SetShadowColor(barColors.shadow); x2c_energybart01_energybar->SetEmptyColor(barColors.empty); x2c_energybart01_energybar->SetFilledDrainSpeed(g_tweakGui->GetEnergyBarFilledSpeed()); x2c_energybart01_energybar->SetShadowDrainSpeed(g_tweakGui->GetEnergyBarShadowSpeed()); x2c_energybart01_energybar->SetShadowDrainDelay(g_tweakGui->GetEnergyBarDrainDelay()); x2c_energybart01_energybar->SetIsAlwaysResetTimer(g_tweakGui->GetEnergyBarAlwaysResetDelay()); x24_meter_energytanks->SetMaxCapacity(14); if (x28_textpane_energywarning) { x28_textpane_energywarning->TextSupport().SetFontColor(g_tweakGuiColors->GetEnergyWarningFont()); x28_textpane_energywarning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetEnergyWarningOutline()); if (x1c_27_energyLow) x28_textpane_energywarning->TextSupport().SetText(g_MainStringTable->GetString(9)); else x28_textpane_energywarning->TextSupport().SetText(u""); } for (int i = 0; i < 14; ++i) { CGuiGroup* g = static_cast(x24_meter_energytanks->GetWorkerWidget(i)); if (CGuiWidget* w = g->GetWorkerWidget(0)) w->SetColor(initColors.tankFilled); if (CGuiWidget* w = g->GetWorkerWidget(1)) w->SetColor(initColors.tankEmpty); } } void CHudEnergyInterface::Update(float dt, float energyLowPulse) { if (x28_textpane_energywarning) { if (x1c_27_energyLow) { x4_energyLowFader = std::min(x4_energyLowFader + 2.f * dt, 1.f); zeus::CColor color = zeus::skWhite; color.a() = x4_energyLowFader * energyLowPulse; x28_textpane_energywarning->SetColor(color); } else { x4_energyLowFader = std::max(0.f, x4_energyLowFader - 2.f * dt); zeus::CColor color = zeus::skWhite; color.a() = x4_energyLowFader * energyLowPulse; x28_textpane_energywarning->SetColor(color); } if (x28_textpane_energywarning->GetGeometryColor().a()) x28_textpane_energywarning->SetIsVisible(true); else x28_textpane_energywarning->SetIsVisible(false); } if (x2c_energybart01_energybar->GetFilledEnergy() != x18_cachedBarEnergy || x1c_26_barDirty) { x1c_26_barDirty = false; x18_cachedBarEnergy = x2c_energybart01_energybar->GetFilledEnergy(); std::string string = fmt::format("{:02d}", int(std::fmod(x18_cachedBarEnergy, CPlayerState::GetEnergyTankCapacity()))); x20_textpane_energydigits->TextSupport().SetText(string); } ITweakGuiColors::SVisorEnergyBarColors barColors = g_tweakGuiColors->GetVisorEnergyBarColors(int(x0_hudType)); zeus::CColor emptyColor = x1c_27_energyLow ? g_tweakGuiColors->GetEnergyBarEmptyLowEnergy() : barColors.empty; zeus::CColor filledColor = x1c_27_energyLow ? g_tweakGuiColors->GetEnergyBarFilledLowEnergy() : barColors.filled; zeus::CColor shadowColor = x1c_27_energyLow ? g_tweakGuiColors->GetEnergyBarShadowLowEnergy() : barColors.shadow; zeus::CColor useFillColor = zeus::CColor::lerp(filledColor, g_tweakGuiColors->GetEnergyBarFlashColor(), x8_flashMag); if (x1c_27_energyLow) useFillColor = zeus::CColor::lerp(useFillColor, zeus::CColor(1.f, 0.8f, 0.4f, 1.f), energyLowPulse); x2c_energybart01_energybar->SetFilledColor(useFillColor); x2c_energybart01_energybar->SetShadowColor(shadowColor); x2c_energybart01_energybar->SetEmptyColor(emptyColor); } void CHudEnergyInterface::SetEnergyLow(bool energyLow) { if (x1c_27_energyLow == energyLow) return; std::u16string string; if (energyLow) string = g_MainStringTable->GetString(9); if (x28_textpane_energywarning) x28_textpane_energywarning->TextSupport().SetText(string); if (energyLow) CSfxManager::SfxStart(SFXui_energy_low, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x1c_27_energyLow = energyLow; } void CHudEnergyInterface::SetFlashMagnitude(float mag) { x8_flashMag = zeus::clamp(0.f, mag, 1.f); } void CHudEnergyInterface::SetNumFilledEnergyTanks(int numTanksFilled) { x14_numTanksFilled = numTanksFilled; x24_meter_energytanks->SetCurrValue(numTanksFilled); } void CHudEnergyInterface::SetNumTotalEnergyTanks(int totalEnergyTanks) { x10_totalEnergyTanks = totalEnergyTanks; x24_meter_energytanks->SetCapacity(totalEnergyTanks); } void CHudEnergyInterface::SetCurrEnergy(float tankEnergy, bool wrapped) { xc_tankEnergy = tankEnergy; x2c_energybart01_energybar->SetCurrEnergy(tankEnergy, tankEnergy == 0.f ? CAuiEnergyBarT01::ESetMode::Insta : CAuiEnergyBarT01::ESetMode(wrapped)); } std::pair CHudEnergyInterface::CombatEnergyCoordFunc(float t) { float theta = 0.46764705f * t - 0.15882353f; float x = 17.f * std::sin(theta); float y = 17.f * std::cos(theta) - 17.f; return {zeus::CVector3f(x, y, 0.4f), zeus::CVector3f(x, y, 0.f)}; } std::pair CHudEnergyInterface::XRayEnergyCoordFunc(float t) { float theta = 1.8207964f - 0.69f * t; float x = std::cos(theta); float z = std::sin(theta); return {zeus::CVector3f(9.4f * x, 0.f, 9.4f * z), zeus::CVector3f(9.f * x, 0.f, 9.f * z)}; } std::pair CHudEnergyInterface::ThermalEnergyCoordFunc(float t) { float x = 8.1663399f * t; return {zeus::CVector3f(x, 0.f, 0.f), zeus::CVector3f(x, 0.f, 0.4355512f)}; } std::pair CHudEnergyInterface::BallEnergyCoordFunc(float t) { float x = 1.6666f * t; return {zeus::CVector3f(x, 0.f, 0.f), zeus::CVector3f(x, 0.f, 0.088887997f)}; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudEnergyInterface.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CHudInterface.hpp" #include namespace metaforce { class CAuiEnergyBarT01; class CAuiMeter; class CGuiFrame; class CGuiTextPane; class CGuiWidget; class CHudEnergyInterface { EHudType x0_hudType; float x4_energyLowFader = 0.f; float x8_flashMag = 0.f; float xc_tankEnergy; int x10_totalEnergyTanks; int x14_numTanksFilled; float x18_cachedBarEnergy = 0.f; bool x1c_24_ : 1 = true; bool x1c_25_ : 1 = true; bool x1c_26_barDirty : 1 = true; bool x1c_27_energyLow : 1; CGuiTextPane* x20_textpane_energydigits; CAuiMeter* x24_meter_energytanks; CGuiTextPane* x28_textpane_energywarning; CAuiEnergyBarT01* x2c_energybart01_energybar; public: CHudEnergyInterface(CGuiFrame& selHud, float tankEnergy, int totalEnergyTanks, int numTanksFilled, bool energyLow, EHudType hudType); void Update(float dt, float energyLowPulse); void SetEnergyLow(bool energyLow); void SetFlashMagnitude(float mag); void SetNumFilledEnergyTanks(int numTanksFilled); void SetNumTotalEnergyTanks(int totalEnergyTanks); void SetCurrEnergy(float tankEnergy, bool wrapped); static std::pair CombatEnergyCoordFunc(float t); static std::pair XRayEnergyCoordFunc(float t); static std::pair ThermalEnergyCoordFunc(float t); static std::pair BallEnergyCoordFunc(float t); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudFreeLookInterface.cpp ================================================ #include "Runtime/GuiSys/CHudFreeLookInterface.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" namespace metaforce { CHudFreeLookInterface::CHudFreeLookInterface(CGuiFrame& selHud, EHudType hudType, bool inFreeLook, bool lookControlHeld, bool lockedOnObj) : x4_hudType(hudType) , x70_24_inFreeLook(inFreeLook) , x70_25_lookControlHeld(lookControlHeld) , x70_26_lockedOnObj(lockedOnObj) { x6c_lockOnInterp = (lockedOnObj && hudType == EHudType::Scan) ? 0.f : 1.f; x74_basewidget_freelookleft = selHud.FindWidget("basewidget_freelookleft"); x78_model_shieldleft = static_cast(selHud.FindWidget("model_shieldleft")); x7c_model_freelookleft = static_cast(selHud.FindWidget("model_freelookleft")); x80_basewidget_freelookright = selHud.FindWidget("basewidget_freelookright"); x84_model_shieldright = static_cast(selHud.FindWidget("model_shieldright")); x88_model_freelookright = static_cast(selHud.FindWidget("model_freelookright")); x8c_basewidget_outlinesb = selHud.FindWidget("basewidget_outlinesb"); x8_freeLookLeftXf = x7c_model_freelookleft->GetTransform(); x38_freeLookRightXf = x88_model_freelookright->GetTransform(); x78_model_shieldleft->SetDepthWrite(true); x84_model_shieldright->SetDepthWrite(true); } void CHudFreeLookInterface::Update(float dt) { if (x70_24_inFreeLook) x68_freeLookInterp = std::min(x68_freeLookInterp + dt / g_tweakGui->GetFreeLookFadeTime(), 1.f); else x68_freeLookInterp = std::max(0.f, x68_freeLookInterp - dt / g_tweakGui->GetFreeLookFadeTime()); if (x70_26_lockedOnObj && x4_hudType == EHudType::Scan) x6c_lockOnInterp = std::min(x6c_lockOnInterp + 2.f * dt, 1.f); else x6c_lockOnInterp = std::max(0.f, x6c_lockOnInterp - 2.f * dt); } void CHudFreeLookInterface::SetIsVisibleDebug(bool v) { x70_27_visibleDebug = v; UpdateVisibility(); } void CHudFreeLookInterface::SetIsVisibleGame(bool v) { x70_28_visibleGame = v; UpdateVisibility(); } void CHudFreeLookInterface::UpdateVisibility() { bool vis = x70_27_visibleDebug && x70_28_visibleGame; x74_basewidget_freelookleft->SetVisibility(vis, ETraversalMode::Children); x80_basewidget_freelookright->SetVisibility(vis, ETraversalMode::Children); if (vis) Update(0.f); } void CHudFreeLookInterface::SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) { x70_24_inFreeLook = inFreeLook; vertLookAngle *= 8.f; x70_25_lookControlHeld = lookControlHeld; x70_26_lockedOnObj = lockedOnObj; x7c_model_freelookleft->SetLocalTransform(x8_freeLookLeftXf * zeus::CTransform::Translate(0.f, 0.f, vertLookAngle)); x88_model_freelookright->SetLocalTransform(x38_freeLookRightXf * zeus::CTransform::Translate(0.f, 0.f, vertLookAngle)); zeus::CColor color = zeus::skWhite; float totalInterp = x68_freeLookInterp * (1.f - x6c_lockOnInterp); color.a() = totalInterp; x74_basewidget_freelookleft->SetColor(color); x80_basewidget_freelookright->SetColor(color); if (x8c_basewidget_outlinesb) { color.a() = 0.7f * totalInterp + 0.3f; x8c_basewidget_outlinesb->SetColor(color); } const bool visible = totalInterp != 0.0f; x74_basewidget_freelookleft->SetVisibility(visible, ETraversalMode::Children); x80_basewidget_freelookright->SetVisibility(visible, ETraversalMode::Children); } CHudFreeLookInterfaceXRay::CHudFreeLookInterfaceXRay(CGuiFrame& selHud, bool inFreeLook, bool lookControlHeld, bool lockedOnObj) { x20_inFreeLook = inFreeLook; x21_lookControlHeld = lookControlHeld; x24_basewidget_freelook = selHud.FindWidget("basewidget_freelook"); x28_model_shield = static_cast(selHud.FindWidget("model_shield")); x2c_model_freelookleft = static_cast(selHud.FindWidget("model_freelookleft")); x30_model_freelookright = static_cast(selHud.FindWidget("model_freelookright")); x4_freeLookLeftPos = x2c_model_freelookleft->GetLocalPosition(); x10_freeLookRightPos = x30_model_freelookright->GetLocalPosition(); x28_model_shield->SetDepthWrite(true); } void CHudFreeLookInterfaceXRay::Update(float dt) { if (x20_inFreeLook) x1c_freeLookInterp = std::min(x1c_freeLookInterp + dt / g_tweakGui->GetFreeLookFadeTime(), 1.f); else x1c_freeLookInterp = std::max(0.f, x1c_freeLookInterp - dt / g_tweakGui->GetFreeLookFadeTime()); } void CHudFreeLookInterfaceXRay::UpdateVisibility() { bool vis = x22_24_visibleDebug && x22_25_visibleGame; x2c_model_freelookleft->SetVisibility(vis, ETraversalMode::Children); x30_model_freelookright->SetVisibility(vis, ETraversalMode::Children); if (vis) Update(0.f); } void CHudFreeLookInterfaceXRay::SetIsVisibleDebug(bool v) { x22_24_visibleDebug = v; UpdateVisibility(); } void CHudFreeLookInterfaceXRay::SetIsVisibleGame(bool v) { x22_25_visibleGame = v; UpdateVisibility(); } void CHudFreeLookInterfaceXRay::SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) { x20_inFreeLook = inFreeLook; x21_lookControlHeld = lookControlHeld; x2c_model_freelookleft->SetLocalTransform( zeus::CTransform(zeus::CMatrix3f::RotateY(vertLookAngle), x4_freeLookLeftPos)); x30_model_freelookright->SetLocalTransform( zeus::CTransform(zeus::CMatrix3f::RotateY(-vertLookAngle), x10_freeLookRightPos)); zeus::CColor color = zeus::skWhite; color.a() = x1c_freeLookInterp; x24_basewidget_freelook->SetColor(color); const bool visible = x1c_freeLookInterp != 0.0f; x24_basewidget_freelook->SetVisibility(visible, ETraversalMode::Children); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudFreeLookInterface.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CHudInterface.hpp" #include namespace metaforce { class CGuiFrame; class CGuiModel; class CGuiWidget; class IFreeLookInterface { public: virtual ~IFreeLookInterface() = default; virtual void Update(float dt) = 0; virtual void SetIsVisibleDebug(bool v) = 0; virtual void SetIsVisibleGame(bool v) = 0; virtual void SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) = 0; }; class CHudFreeLookInterface : public IFreeLookInterface { EHudType x4_hudType; zeus::CTransform x8_freeLookLeftXf; zeus::CTransform x38_freeLookRightXf; float x68_freeLookInterp = 0.f; float x6c_lockOnInterp; bool x70_24_inFreeLook : 1; bool x70_25_lookControlHeld : 1; bool x70_26_lockedOnObj : 1; bool x70_27_visibleDebug : 1 = true; bool x70_28_visibleGame : 1 = true; CGuiWidget* x74_basewidget_freelookleft; CGuiModel* x78_model_shieldleft; CGuiModel* x7c_model_freelookleft; CGuiWidget* x80_basewidget_freelookright; CGuiModel* x84_model_shieldright; CGuiModel* x88_model_freelookright; CGuiWidget* x8c_basewidget_outlinesb; void UpdateVisibility(); public: CHudFreeLookInterface(CGuiFrame& selHud, EHudType hudType, bool inFreeLook, bool lookControlHeld, bool lockedOnObj); void Update(float dt) override; void SetIsVisibleDebug(bool v) override; void SetIsVisibleGame(bool v) override; void SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) override; }; class CHudFreeLookInterfaceXRay : public IFreeLookInterface { zeus::CVector3f x4_freeLookLeftPos; zeus::CVector3f x10_freeLookRightPos; float x1c_freeLookInterp = 0.f; bool x20_inFreeLook; bool x21_lookControlHeld; bool x22_24_visibleDebug : 1 = true; bool x22_25_visibleGame : 1 = true; CGuiWidget* x24_basewidget_freelook; CGuiModel* x28_model_shield; CGuiModel* x2c_model_freelookleft; CGuiModel* x30_model_freelookright; void UpdateVisibility(); public: CHudFreeLookInterfaceXRay(CGuiFrame& selHud, bool inFreeLook, bool lookControlHeld, bool lockedOnObj); void Update(float dt) override; void SetIsVisibleDebug(bool v) override; void SetIsVisibleGame(bool v) override; void SetFreeLookState(bool inFreeLook, bool lookControlHeld, bool lockedOnObj, float vertLookAngle) override; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudHelmetInterface.cpp ================================================ #include "Runtime/GuiSys/CHudHelmetInterface.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" namespace metaforce { CHudHelmetInterface::CHudHelmetInterface(CGuiFrame& helmetFrame) { x40_camera = helmetFrame.GetFrameCamera(); x44_BaseWidget_Pivot = helmetFrame.FindWidget("BaseWidget_Pivot"); x48_BaseWidget_Helmet = helmetFrame.FindWidget("BaseWidget_Helmet"); x4c_BaseWidget_Glow = helmetFrame.FindWidget("BaseWidget_Glow"); x50_BaseWidget_HelmetLight = helmetFrame.FindWidget("BaseWidget_HelmetLight"); x24_pivotPosition = x44_BaseWidget_Pivot->GetIdlePosition(); x50_BaseWidget_HelmetLight->SetColor(g_tweakGuiColors->GetHelmetLightColor()); } void CHudHelmetInterface::UpdateVisibility() { x48_BaseWidget_Helmet->SetVisibility(x3c_24_helmetVisibleDebug && x3c_25_helmetVisibleGame, ETraversalMode::Children); x4c_BaseWidget_Glow->SetVisibility(x3c_26_glowVisibleDebug && x3c_27_glowVisibleGame, ETraversalMode::Children); } void CHudHelmetInterface::Update(float dt) { if (x3c_28_hudLagDirty) { x3c_28_hudLagDirty = false; x44_BaseWidget_Pivot->SetTransform(zeus::CTransform(x0_hudLagRotation, x24_pivotPosition + x30_hudLagPosition)); } } void CHudHelmetInterface::SetHudLagOffset(const zeus::CVector3f& off) { x30_hudLagPosition = off; x3c_28_hudLagDirty = true; } void CHudHelmetInterface::SetHudLagRotation(const zeus::CMatrix3f& rot) { x0_hudLagRotation = rot; x3c_28_hudLagDirty = true; } void CHudHelmetInterface::AddHelmetLightValue(float val) { x50_BaseWidget_HelmetLight->SetColor(g_tweakGuiColors->GetHelmetLightColor() + zeus::CColor(val, val)); } void CHudHelmetInterface::UpdateCameraDebugSettings(float fov, float y, float z) { x40_camera->SetFov(fov); x40_camera->SetTransform(zeus::CTransform(x40_camera->GetTransform().buildMatrix3f(), zeus::CVector3f(0.f, y, z))); } void CHudHelmetInterface::UpdateHelmetAlpha() { zeus::CColor color = zeus::skWhite; color.a() = g_GameState->GameOptions().GetHelmetAlpha() / 255.f; x44_BaseWidget_Pivot->SetColor(color); } void CHudHelmetInterface::SetIsVisibleDebug(bool helmet, bool glow) { x3c_24_helmetVisibleDebug = helmet; x3c_26_glowVisibleDebug = glow; UpdateVisibility(); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudHelmetInterface.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include namespace metaforce { class CGuiCamera; class CGuiFrame; class CGuiWidget; class CHudHelmetInterface { zeus::CMatrix3f x0_hudLagRotation; zeus::CVector3f x24_pivotPosition; zeus::CVector3f x30_hudLagPosition; bool x3c_24_helmetVisibleDebug : 1 = true; bool x3c_25_helmetVisibleGame : 1 = true; bool x3c_26_glowVisibleDebug : 1 = true; bool x3c_27_glowVisibleGame : 1 = true; bool x3c_28_hudLagDirty : 1 = false; CGuiCamera* x40_camera; CGuiWidget* x44_BaseWidget_Pivot; CGuiWidget* x48_BaseWidget_Helmet; CGuiWidget* x4c_BaseWidget_Glow; CGuiWidget* x50_BaseWidget_HelmetLight; void UpdateVisibility(); public: explicit CHudHelmetInterface(CGuiFrame& helmetFrame); void Update(float dt); void SetHudLagOffset(const zeus::CVector3f& off); void SetHudLagRotation(const zeus::CMatrix3f& rot); void AddHelmetLightValue(float val); void UpdateCameraDebugSettings(float fov, float y, float z); void UpdateHelmetAlpha(); void SetIsVisibleDebug(bool helmet, bool glow); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudInterface.hpp ================================================ #pragma once namespace metaforce { enum class EHudType { Combat, Scan, XRay, Thermal, Ball }; } ================================================ FILE: Runtime/GuiSys/CHudMissileInterface.cpp ================================================ #include "Runtime/GuiSys/CHudMissileInterface.hpp" #include #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/GuiSys/CAuiEnergyBarT01.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { constexpr std::array CoordFuncs{ CHudMissileInterface::CombatMissileBarCoordFunc, nullptr, CHudMissileInterface::XRayMissileBarCoordFunc, CHudMissileInterface::ThermalMissileBarCoordFunc, nullptr, }; constexpr std::array IconTranslateRanges{ 6.05f, 0.f, 0.f, 8.4f, 0.f, }; CHudMissileInterface::CHudMissileInterface(CGuiFrame& selHud, int missileCapacity, int numMissiles, float chargeFactor, bool missilesActive, EHudType hudType, const CStateManager& mgr) : x0_hudType(hudType) , x4_missileCapacity(missileCapacity) , x8_numMissles(numMissiles) , x4c_chargeBeamFactor(chargeFactor) , x58_24_missilesActive(missilesActive) { x5c_basewidget_missileicon = selHud.FindWidget("basewidget_missileicon"); x60_textpane_missiledigits = static_cast(selHud.FindWidget("textpane_missiledigits")); x64_energybart01_missilebar = static_cast(selHud.FindWidget("energybart01_missilebar")); x68_textpane_missilewarning = static_cast(selHud.FindWidget("textpane_missilewarning")); x6c_model_missilearrowup = static_cast(selHud.FindWidget("model_missilearrowup")); x70_model_missilearrowdown = static_cast(selHud.FindWidget("model_missilearrowdown")); x74_basewidget_missileicon = selHud.FindWidget("basewidget_missileicon"); x58_27_hasArrows = x6c_model_missilearrowup && x70_model_missilearrowdown; x58_28_notXRay = hudType != EHudType::XRay; x10_missleIconXf = x74_basewidget_missileicon->GetLocalTransform(); x60_textpane_missiledigits->TextSupport().SetFontColor(g_tweakGuiColors->GetMissileDigitsFont()); x60_textpane_missiledigits->TextSupport().SetOutlineColor(g_tweakGuiColors->GetMissileDigitsOutline()); x74_basewidget_missileicon->SetColor(g_tweakGuiColors->GetMissileIconColorInactive()); x64_energybart01_missilebar->SetEmptyColor(g_tweakGuiColors->GetMissileBarEmpty()); x64_energybart01_missilebar->SetFilledColor(g_tweakGuiColors->GetMissileBarFilled()); x64_energybart01_missilebar->SetShadowColor(g_tweakGuiColors->GetMissileBarShadow()); x64_energybart01_missilebar->SetCoordFunc(CoordFuncs[size_t(hudType)]); x64_energybart01_missilebar->SetTesselation(hudType == EHudType::Combat ? 1.f : 0.1f); x64_energybart01_missilebar->SetMaxEnergy(5.f); x64_energybart01_missilebar->SetFilledDrainSpeed(g_tweakGui->GetEnergyBarFilledSpeed()); x64_energybart01_missilebar->SetShadowDrainSpeed(g_tweakGui->GetEnergyBarShadowSpeed()); x64_energybart01_missilebar->SetShadowDrainDelay(g_tweakGui->GetEnergyBarDrainDelay()); x64_energybart01_missilebar->SetIsAlwaysResetTimer(true); if (x68_textpane_missilewarning) { x68_textpane_missilewarning->TextSupport().SetFontColor(g_tweakGuiColors->GetMissileWarningFont()); x68_textpane_missilewarning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetMissileWarningOutline()); } SetNumMissiles(x8_numMissles, mgr); x44_latestStatus = GetMissileInventoryStatus(); } void CHudMissileInterface::UpdateVisibility(const CStateManager& mgr) { bool vis = x58_25_visibleDebug && x58_26_visibleGame; x5c_basewidget_missileicon->SetVisibility(vis, ETraversalMode::Children); x64_energybart01_missilebar->SetVisibility(vis, ETraversalMode::Children); if (vis) Update(0.f, mgr); } void CHudMissileInterface::Update(float dt, const CStateManager& mgr) { if (x4_missileCapacity < 1) x5c_basewidget_missileicon->SetIsVisible(false); else x5c_basewidget_missileicon->SetIsVisible(true); if (x54_missileIconIncrement < 0.f) { x54_missileIconIncrement -= 3.f * dt; if (x54_missileIconIncrement <= -1.f) x54_missileIconIncrement = 1.f; } else if (x54_missileIconIncrement > 0.f) { x54_missileIconIncrement = std::max(0.f, x54_missileIconIncrement - dt); } zeus::CColor addColor = g_tweakGuiColors->GetMissileIconColorActive() * x54_missileIconIncrement; if (x50_missileIconAltDeplete > 0.f) { x74_basewidget_missileicon->SetColor(zeus::CColor::lerp(g_tweakGuiColors->GetMissileIconColorInactive(), g_tweakGuiColors->GetMissileIconColorDepleteAlt(), x50_missileIconAltDeplete) + addColor); } else { if (x4c_chargeBeamFactor > 0.f) { float factor = std::min(x4c_chargeBeamFactor / CPlayerState::GetMissileComboChargeFactor(), 1.f); if (x8_numMissles > mgr.GetPlayerState()->GetMissileCostForAltAttack()) { x74_basewidget_missileicon->SetColor(zeus::CColor::lerp(g_tweakGuiColors->GetMissileIconColorInactive(), g_tweakGuiColors->GetMissileIconColorChargedCanAlt(), factor) + addColor); } else { x74_basewidget_missileicon->SetColor(zeus::CColor::lerp(g_tweakGuiColors->GetMissileIconColorInactive(), g_tweakGuiColors->GetMissileIconColorChargedNoAlt(), factor) + addColor); } } else { if (x58_24_missilesActive) x74_basewidget_missileicon->SetColor(g_tweakGuiColors->GetMissileIconColorActive() + addColor); else x74_basewidget_missileicon->SetColor(g_tweakGuiColors->GetMissileIconColorInactive() + addColor); } } x50_missileIconAltDeplete = std::max(0.f, x50_missileIconAltDeplete - dt); x64_energybart01_missilebar->SetMaxEnergy(x4_missileCapacity); x64_energybart01_missilebar->SetCurrEnergy(x8_numMissles, CAuiEnergyBarT01::ESetMode::Normal); if (x58_28_notXRay) { x74_basewidget_missileicon->SetLocalTransform( x10_missleIconXf * zeus::CTransform::Translate( 0.f, 0.f, x8_numMissles * IconTranslateRanges[size_t(x0_hudType)] / float(x4_missileCapacity))); } if (x58_27_hasArrows) { if (xc_arrowTimer > 0.f) { xc_arrowTimer = std::max(0.f, xc_arrowTimer - dt); zeus::CColor color = g_tweakGuiColors->GetMissileIconColorActive(); color.a() *= xc_arrowTimer / g_tweakGui->GetMissileArrowVisTime(); x6c_model_missilearrowup->SetColor(color); x70_model_missilearrowdown->SetIsVisible(false); } else if (xc_arrowTimer < 0.f) { xc_arrowTimer = std::min(0.f, xc_arrowTimer + dt); zeus::CColor color = g_tweakGuiColors->GetMissileIconColorActive(); color.a() *= -xc_arrowTimer / g_tweakGui->GetMissileArrowVisTime(); x70_model_missilearrowdown->SetColor(color); x6c_model_missilearrowup->SetIsVisible(false); } else { x6c_model_missilearrowup->SetIsVisible(false); x70_model_missilearrowdown->SetIsVisible(false); } } if (x68_textpane_missilewarning) { EInventoryStatus curStatus = GetMissileInventoryStatus(); if (curStatus != x44_latestStatus) { std::u16string string; switch (curStatus) { case EInventoryStatus::Warning: string = g_MainStringTable->GetString(12); // Missiles Low break; case EInventoryStatus::Depleted: string = g_MainStringTable->GetString(13); // Depleted break; default: break; } x68_textpane_missilewarning->TextSupport().SetText(string); if (x44_latestStatus == EInventoryStatus::Normal && curStatus == EInventoryStatus::Warning) { CSfxManager::SfxStart(SFXui_missile_warning, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x48_missileWarningPulse = g_tweakGui->GetMissileWarningPulseTime(); } else if (curStatus == EInventoryStatus::Depleted) { CSfxManager::SfxStart(SFXui_missile_warning, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x48_missileWarningPulse = g_tweakGui->GetMissileWarningPulseTime(); } x44_latestStatus = curStatus; } x48_missileWarningPulse = std::max(0.f, x48_missileWarningPulse - dt); float warnPulse = std::min(x48_missileWarningPulse, 1.f); if (x44_latestStatus != EInventoryStatus::Normal) x40_missileWarningAlpha = std::min(x40_missileWarningAlpha + 2.f * dt, 1.f); else x40_missileWarningAlpha = std::max(0.f, x40_missileWarningAlpha - 2.f * dt); float tmp = std::fabs(std::fmod(CGraphics::GetSecondsMod900(), 0.5f)); if (tmp < 0.25f) tmp = tmp / 0.25f; else tmp = (0.5f - tmp) / 0.25f; zeus::CColor color = zeus::skWhite; color.a() = x40_missileWarningAlpha * tmp * warnPulse; x68_textpane_missilewarning->SetColor(color); if (x68_textpane_missilewarning->GetGeometryColor().a()) x68_textpane_missilewarning->SetIsVisible(true); else x68_textpane_missilewarning->SetIsVisible(false); } } void CHudMissileInterface::SetIsVisibleGame(bool v, const CStateManager& mgr) { x58_26_visibleGame = v; UpdateVisibility(mgr); } void CHudMissileInterface::SetIsVisibleDebug(bool v, const CStateManager& mgr) { x58_25_visibleDebug = v; UpdateVisibility(mgr); } void CHudMissileInterface::SetIsMissilesActive(bool active) { x58_24_missilesActive = active; } void CHudMissileInterface::SetChargeBeamFactor(float t) { x4c_chargeBeamFactor = t; } void CHudMissileInterface::SetNumMissiles(int numMissiles, const CStateManager& mgr) { numMissiles = zeus::clamp(0, numMissiles, 999); x60_textpane_missiledigits->TextSupport().SetText(fmt::format("{:3d}", numMissiles)); if (x8_numMissles < numMissiles) { xc_arrowTimer = g_tweakGui->GetMissileArrowVisTime(); x54_missileIconIncrement = -FLT_EPSILON; } else if (x8_numMissles > numMissiles) { xc_arrowTimer = -g_tweakGui->GetMissileArrowVisTime(); } if (mgr.GetPlayerState()->GetMissileCostForAltAttack() + numMissiles <= x8_numMissles) x50_missileIconAltDeplete = 1.f; x8_numMissles = numMissiles; } void CHudMissileInterface::SetMissileCapacity(int missileCapacity) { x4_missileCapacity = missileCapacity; } CHudMissileInterface::EInventoryStatus CHudMissileInterface::GetMissileInventoryStatus() const { if (x64_energybart01_missilebar->GetSetEnergy() == 0.f) return EInventoryStatus::Depleted; return EInventoryStatus(x64_energybart01_missilebar->GetActualFraction() < g_tweakGui->GetMissileWarningFraction()); } std::pair CHudMissileInterface::CombatMissileBarCoordFunc(float t) { const float z = t * IconTranslateRanges[size_t(EHudType::Combat)]; return {zeus::CVector3f(0.f, 0.f, z), zeus::CVector3f(0.3f, 0.f, z)}; } std::pair CHudMissileInterface::XRayMissileBarCoordFunc(float t) { const float theta = 0.8f * (t - 0.5f); const float x = 9.55f * std::cos(theta); const float z = 9.55f * std::sin(theta); return {zeus::CVector3f(x - 0.4f, 0.f, z), zeus::CVector3f(x, 0.f, z)}; } std::pair CHudMissileInterface::ThermalMissileBarCoordFunc(float t) { const float transRange = IconTranslateRanges[size_t(EHudType::Thermal)]; const float a = 0.08f * transRange; const float b = t * transRange; float c; if (b < a) { c = b / a; } else if (b < transRange - a) { c = 1.f; } else { c = 1.f - (b - (transRange - a)) / a; } return {zeus::CVector3f(-0.5f * c - 0.1f, 0.f, b), zeus::CVector3f(-0.1f, 0.f, b)}; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudMissileInterface.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CHudInterface.hpp" #include namespace metaforce { class CAuiEnergyBarT01; class CGuiFrame; class CGuiModel; class CGuiTextPane; class CGuiWidget; class CStateManager; class CHudMissileInterface { enum class EInventoryStatus { Normal, Warning, Depleted }; EHudType x0_hudType; int x4_missileCapacity; int x8_numMissles; float xc_arrowTimer = 0.f; zeus::CTransform x10_missleIconXf; float x40_missileWarningAlpha = 0.f; EInventoryStatus x44_latestStatus = EInventoryStatus::Normal; float x48_missileWarningPulse = 0.f; float x4c_chargeBeamFactor; float x50_missileIconAltDeplete = 0.f; float x54_missileIconIncrement = 0.f; bool x58_24_missilesActive : 1; bool x58_25_visibleDebug : 1 = true; bool x58_26_visibleGame : 1 = true; bool x58_27_hasArrows : 1; bool x58_28_notXRay : 1; CGuiWidget* x5c_basewidget_missileicon; CGuiTextPane* x60_textpane_missiledigits; CAuiEnergyBarT01* x64_energybart01_missilebar; CGuiTextPane* x68_textpane_missilewarning; CGuiModel* x6c_model_missilearrowup; CGuiModel* x70_model_missilearrowdown; CGuiWidget* x74_basewidget_missileicon; void UpdateVisibility(const CStateManager& mgr); public: CHudMissileInterface(CGuiFrame& selHud, int missileCapacity, int numMissiles, float chargeFactor, bool missilesActive, EHudType hudType, const CStateManager& mgr); void Update(float dt, const CStateManager& mgr); void SetIsVisibleGame(bool v, const CStateManager& mgr); void SetIsVisibleDebug(bool v, const CStateManager& mgr); void SetIsMissilesActive(bool active); void SetChargeBeamFactor(float t); void SetNumMissiles(int numMissiles, const CStateManager& mgr); void SetMissileCapacity(int missileCapacity); EInventoryStatus GetMissileInventoryStatus() const; static std::pair CombatMissileBarCoordFunc(float t); static std::pair XRayMissileBarCoordFunc(float t); static std::pair ThermalMissileBarCoordFunc(float t); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudRadarInterface.cpp ================================================ #include "Runtime/GuiSys/CHudRadarInterface.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiWidgetDrawParms.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CWallCrawlerSwarm.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path #include namespace metaforce { CHudRadarInterface::CHudRadarInterface(CGuiFrame& baseHud, CStateManager& stateMgr) { x0_txtrRadarPaint = g_SimplePool->GetObj("TXTR_RadarPaint"); x40_BaseWidget_RadarStuff = baseHud.FindWidget("BaseWidget_RadarStuff"); x44_camera = baseHud.GetFrameCamera(); xc_radarStuffXf = x40_BaseWidget_RadarStuff->GetLocalTransform(); x40_BaseWidget_RadarStuff->SetColor(g_tweakGuiColors->GetRadarStuffColor()); } void CHudRadarInterface::DoDrawRadarPaint(float radius) { radius *= 4.f; CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamTexcoord(0.f, 1.f); CGraphics::StreamVertex(-radius, 0.f, radius); CGraphics::StreamTexcoord(0.f, 0.f); CGraphics::StreamVertex(-radius, 0.f, -radius); CGraphics::StreamTexcoord(1.f, 1.f); CGraphics::StreamVertex(radius, 0.f, radius); CGraphics::StreamTexcoord(1.f, 0.f); CGraphics::StreamVertex(radius, 0.f, -radius); CGraphics::StreamEnd(); } void CHudRadarInterface::DrawRadarPaint(const zeus::CVector3f& enemyPos, float radius, float alpha, const SRadarPaintDrawParms& parms) { const zeus::CVector2f playerToEnemy = enemyPos.toVec2f() - parms.x0_playerPos.toVec2f(); const float zDelta = std::fabs(enemyPos.z() - parms.x0_playerPos.z()); if (playerToEnemy.magnitude() > parms.x78_xyRadius || zDelta > parms.x7c_zRadius) { return; } if (zDelta > parms.x80_ZCloseRadius) { alpha *= 1.f - (zDelta - parms.x80_ZCloseRadius) / (parms.x7c_zRadius - parms.x80_ZCloseRadius); } const zeus::CVector2f scopeScaled = playerToEnemy * parms.x70_scopeScalar; g_Renderer->SetModelMatrix( parms.x3c_postTranslate * zeus::CTransform::Translate(parms.xc_preTranslate * zeus::CVector3f(scopeScaled.x(), 0.f, scopeScaled.y()))); zeus::CColor color = g_tweakGuiColors->GetRadarEnemyPaintColor(); color.a() *= alpha; color.a() *= parms.x74_alpha; CGraphics::StreamColor(color); DoDrawRadarPaint(radius); } void CHudRadarInterface::SetIsVisibleGame(bool v) { x3c_24_visibleGame = v; x40_BaseWidget_RadarStuff->SetVisibility(x3c_25_visibleDebug && x3c_24_visibleGame, ETraversalMode::Children); } void CHudRadarInterface::Update(float dt, const CStateManager& mgr) { const CPlayerState& playerState = *mgr.GetPlayerState(); const float visorTransFactor = (playerState.GetCurrentVisor() == CPlayerState::EPlayerVisor::Combat) ? playerState.GetVisorTransitionFactor() : 0.f; zeus::CColor color = g_tweakGuiColors->GetRadarStuffColor(); color.a() *= g_GameState->GameOptions().GetHUDAlpha() / 255.f * visorTransFactor; x40_BaseWidget_RadarStuff->SetColor(color); const bool tweakVis = g_tweakGui->GetHudVisMode() >= ITweakGui::EHudVisMode::Three; if (tweakVis == x3c_25_visibleDebug) { return; } x3c_25_visibleDebug = tweakVis; x40_BaseWidget_RadarStuff->SetVisibility(x3c_25_visibleDebug && x3c_24_visibleGame, ETraversalMode::Children); } void CHudRadarInterface::Draw(const CStateManager& mgr, float alpha) { alpha *= g_GameState->GameOptions().GetHUDAlpha() / 255.f; if (g_tweakGui->GetHudVisMode() == ITweakGui::EHudVisMode::Zero || !x3c_24_visibleGame || !x0_txtrRadarPaint || !x0_txtrRadarPaint.IsLoaded()) { return; } SRadarPaintDrawParms drawParms; const CPlayer& player = mgr.GetPlayer(); if (player.IsOverrideRadarRadius()) { drawParms.x78_xyRadius = player.GetRadarXYRadiusOverride(); drawParms.x7c_zRadius = player.GetRadarZRadiusOverride(); drawParms.x80_ZCloseRadius = 0.667f * drawParms.x7c_zRadius; } else { drawParms.x78_xyRadius = g_tweakGui->GetRadarXYRadius(); drawParms.x7c_zRadius = g_tweakGui->GetRadarZRadius(); drawParms.x80_ZCloseRadius = g_tweakGui->GetRadarZCloseRadius(); } drawParms.x6c_scopeRadius = g_tweakGui->GetRadarScopeCoordRadius(); drawParms.x70_scopeScalar = drawParms.x6c_scopeRadius / drawParms.x78_xyRadius; const float camZ = zeus::CEulerAngles(zeus::CQuaternion(mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTransform().basis)).z(); zeus::CRelAngle angleZ(camZ); angleZ.makeRel(); drawParms.xc_preTranslate = zeus::CTransform::RotateY(angleZ); drawParms.x3c_postTranslate = x40_BaseWidget_RadarStuff->GetWorldTransform(); const float enemyRadius = g_tweakGui->GetRadarEnemyPaintRadius(); x44_camera->Draw(CGuiWidgetDrawParms{0.f, zeus::CVector3f{}}); g_Renderer->SetModelMatrix(drawParms.x3c_postTranslate); g_Renderer->SetBlendMode_AdditiveAlpha(); x0_txtrRadarPaint->Load(GX_TEXMAP0, EClampMode::Repeat); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); g_Renderer->SetDepthReadWrite(false, false); zeus::CColor playerColor = g_tweakGuiColors->GetRadarPlayerPaintColor(); playerColor.a() *= alpha; CGraphics::StreamColor(playerColor); DoDrawRadarPaint(g_tweakGui->GetRadarPlayerPaintRadius()); const zeus::CAABox radarBounds( player.GetTranslation().x() - drawParms.x78_xyRadius, player.GetTranslation().y() - drawParms.x78_xyRadius, player.GetTranslation().z() - drawParms.x7c_zRadius, player.GetTranslation().x() + drawParms.x78_xyRadius, player.GetTranslation().y() + drawParms.x78_xyRadius, player.GetTranslation().z() + drawParms.x7c_zRadius); EntityList nearList; mgr.BuildNearList(nearList, radarBounds, CMaterialFilter(CMaterialList(EMaterialTypes::Target, EMaterialTypes::RadarObject), CMaterialList(EMaterialTypes::ExcludeFromRadar), CMaterialFilter::EFilterType::IncludeExclude), nullptr); drawParms.x0_playerPos = mgr.GetPlayer().GetTranslation(); drawParms.x74_alpha = alpha; for (const auto& id : nearList) { if (const TCastToConstPtr act = mgr.GetObjectById(id)) { if (!act->GetActive()) { continue; } if (const TCastToConstPtr swarm = act.GetPtr()) { const float radius = enemyRadius * 0.5f; for (const CWallCrawlerSwarm::CBoid& boid : swarm->GetBoids()) { if (!boid.GetActive()) { continue; } DrawRadarPaint(boid.GetTranslation(), radius, 0.5f, drawParms); } } else { DrawRadarPaint(act->GetTranslation(), enemyRadius, 1.f, drawParms); } } } g_Renderer->SetDepthReadWrite(true, true); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudRadarInterface.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/Graphics/CTexture.hpp" #include #include namespace metaforce { class CGuiCamera; class CGuiFrame; class CGuiWidget; class CStateManager; class CHudRadarInterface { struct SRadarPaintDrawParms { zeus::CVector3f x0_playerPos; zeus::CTransform xc_preTranslate; zeus::CTransform x3c_postTranslate; float x6c_scopeRadius; float x70_scopeScalar; float x74_alpha; float x78_xyRadius; float x7c_zRadius; float x80_ZCloseRadius; }; TLockedToken x0_txtrRadarPaint; zeus::CTransform xc_radarStuffXf; bool x3c_24_visibleGame : 1 = true; bool x3c_25_visibleDebug : 1 = true; CGuiWidget* x40_BaseWidget_RadarStuff; CGuiCamera* x44_camera; void DoDrawRadarPaint(float radius); void DrawRadarPaint(const zeus::CVector3f& enemyPos, float radius, float alpha, const SRadarPaintDrawParms& parms); public: CHudRadarInterface(CGuiFrame& baseHud, CStateManager& stateMgr); void SetIsVisibleGame(bool v); void Update(float dt, const CStateManager& mgr); void Draw(const CStateManager& mgr, float alpha); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudThreatInterface.cpp ================================================ #include "Runtime/GuiSys/CHudThreatInterface.hpp" #include #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/GuiSys/CAuiEnergyBarT01.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/Audio/CSfxManager.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { constexpr std::array CoordFuncs{ CHudThreatInterface::CombatThreatBarCoordFunc, nullptr, CHudThreatInterface::XRayThreatBarCoordFunc, CHudThreatInterface::ThermalThreatBarCoordFunc, nullptr, }; constexpr std::array IconTranslateRanges{ 6.05f, 0.f, 0.f, 8.4f, 0.f, }; CHudThreatInterface::CHudThreatInterface(CGuiFrame& selHud, EHudType hudType, float threatDist) : x4_hudType(hudType), x10_threatDist(threatDist) { x58_basewidget_threatstuff = selHud.FindWidget("basewidget_threatstuff"); x5c_basewidget_threaticon = selHud.FindWidget("basewidget_threaticon"); x60_model_threatarrowup = static_cast(selHud.FindWidget("model_threatarrowup")); x64_model_threatarrowdown = static_cast(selHud.FindWidget("model_threatarrowdown")); x68_textpane_threatwarning = static_cast(selHud.FindWidget("textpane_threatwarning")); x6c_energybart01_threatbar = static_cast(selHud.FindWidget("energybart01_threatbar")); x70_textpane_threatdigits = static_cast(selHud.FindWidget("textpane_threatdigits")); if (x70_textpane_threatdigits) { x70_textpane_threatdigits->TextSupport().SetFontColor(g_tweakGuiColors->GetThreatDigitsFont()); x70_textpane_threatdigits->TextSupport().SetOutlineColor(g_tweakGuiColors->GetThreatDigitsOutline()); } x54_26_hasArrows = x60_model_threatarrowup && x64_model_threatarrowdown; x54_27_notXRay = hudType != EHudType::XRay; x5c_basewidget_threaticon->SetColor(g_tweakGuiColors->GetThreatIconColor()); x18_threatIconXf = x5c_basewidget_threaticon->GetLocalTransform(); x6c_energybart01_threatbar->SetFilledColor(g_tweakGuiColors->GetThreatBarFilled()); x6c_energybart01_threatbar->SetShadowColor(g_tweakGuiColors->GetThreatBarShadow()); x6c_energybart01_threatbar->SetEmptyColor(g_tweakGuiColors->GetThreatBarEmpty()); x6c_energybart01_threatbar->SetCoordFunc(CoordFuncs[size_t(hudType)]); x6c_energybart01_threatbar->SetTesselation(hudType == EHudType::Combat ? 1.f : 0.1f); x6c_energybart01_threatbar->SetMaxEnergy(g_tweakGui->GetThreatRange()); x6c_energybart01_threatbar->SetFilledDrainSpeed(9999.f); x6c_energybart01_threatbar->SetShadowDrainSpeed(9999.f); x6c_energybart01_threatbar->SetShadowDrainDelay(0.f); x6c_energybart01_threatbar->SetIsAlwaysResetTimer(false); if (x68_textpane_threatwarning) { x68_textpane_threatwarning->TextSupport().SetFontColor(g_tweakGuiColors->GetThreatWarningFont()); x68_textpane_threatwarning->TextSupport().SetOutlineColor(g_tweakGuiColors->GetThreatWarningOutline()); } } void CHudThreatInterface::SetThreatDistance(float threatDist) { x10_threatDist = threatDist; } void CHudThreatInterface::SetIsVisibleDebug(bool v) { x54_24_visibleDebug = v; UpdateVisibility(); } void CHudThreatInterface::SetIsVisibleGame(bool v) { x54_25_visibleGame = v; UpdateVisibility(); } void CHudThreatInterface::UpdateVisibility() { bool vis = x54_24_visibleDebug && x54_25_visibleGame; x58_basewidget_threatstuff->SetVisibility(vis, ETraversalMode::Children); if (vis) SetThreatDistance(0.f); } void CHudThreatInterface::Update(float dt) { zeus::CColor warningColor = zeus::CColor::lerp(g_tweakGuiColors->GetThreatIconColor(), g_tweakGuiColors->GetThreatIconWarningColor(), x50_warningColorLerp); float maxThreatEnergy = g_tweakGui->GetThreatRange(); if (x70_textpane_threatdigits) { if (x10_threatDist < maxThreatEnergy) { x70_textpane_threatdigits->SetIsVisible(true); x70_textpane_threatdigits->TextSupport().SetText( fmt::format("{:01.1f}", std::max(0.f, x10_threatDist))); } else { x70_textpane_threatdigits->SetIsVisible(false); } } if (x54_26_hasArrows) { if (x14_arrowTimer > 0.f) { x60_model_threatarrowup->SetIsVisible(true); x14_arrowTimer = std::max(0.f, x14_arrowTimer - dt); zeus::CColor color = warningColor; color.a() = x14_arrowTimer / g_tweakGui->GetMissileArrowVisTime(); x60_model_threatarrowup->SetColor(color); x64_model_threatarrowdown->SetIsVisible(false); } else if (x14_arrowTimer < 0.f) { x64_model_threatarrowdown->SetIsVisible(true); x14_arrowTimer = std::min(0.f, x14_arrowTimer + dt); zeus::CColor color = warningColor; color.a() = -x14_arrowTimer / g_tweakGui->GetMissileArrowVisTime(); x64_model_threatarrowdown->SetColor(color); x60_model_threatarrowup->SetIsVisible(false); } else { x60_model_threatarrowup->SetIsVisible(false); x64_model_threatarrowdown->SetIsVisible(false); } } if (x10_threatDist <= maxThreatEnergy) { float tmp = x10_threatDist - (maxThreatEnergy - x6c_energybart01_threatbar->GetSetEnergy()); if (tmp < -0.01f) x14_arrowTimer = g_tweakGui->GetMissileArrowVisTime(); else if (tmp > 0.01f) x14_arrowTimer = -g_tweakGui->GetMissileArrowVisTime(); } else { x14_arrowTimer = 0.f; } if (x10_threatDist <= maxThreatEnergy) { x6c_energybart01_threatbar->SetCurrEnergy(x6c_energybart01_threatbar->GetMaxEnergy() - x10_threatDist, CAuiEnergyBarT01::ESetMode::Normal); x5c_basewidget_threaticon->SetColor(warningColor); } else { x6c_energybart01_threatbar->SetCurrEnergy(0.f, CAuiEnergyBarT01::ESetMode::Normal); x5c_basewidget_threaticon->SetColor(g_tweakGuiColors->GetThreatIconSafeColor()); } if (x54_27_notXRay) { x5c_basewidget_threaticon->SetLocalTransform( x18_threatIconXf * zeus::CTransform::Translate(0.f, 0.f, std::max(0.f, maxThreatEnergy - x10_threatDist) * IconTranslateRanges[size_t(x4_hudType)] / maxThreatEnergy)); } if (x68_textpane_threatwarning) { if (x6c_energybart01_threatbar->GetActualFraction() > g_tweakGui->GetThreatWarningFraction()) x68_textpane_threatwarning->SetIsVisible(true); else x68_textpane_threatwarning->SetIsVisible(false); EThreatStatus newStatus; if (maxThreatEnergy == x6c_energybart01_threatbar->GetSetEnergy()) newStatus = EThreatStatus::Damage; else if (x6c_energybart01_threatbar->GetActualFraction() > g_tweakGui->GetThreatWarningFraction()) newStatus = EThreatStatus::Warning; else newStatus = EThreatStatus::Normal; if (x4c_threatStatus != newStatus) { std::u16string string; if (newStatus == EThreatStatus::Warning) string = g_MainStringTable->GetString(10); else if (newStatus == EThreatStatus::Damage) string = g_MainStringTable->GetString(11); x68_textpane_threatwarning->TextSupport().SetText(string); if (x4c_threatStatus == EThreatStatus::Normal && newStatus == EThreatStatus::Warning) CSfxManager::SfxStart(SFXui_threat_warning, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); else if (newStatus == EThreatStatus::Damage) CSfxManager::SfxStart(SFXui_threat_damage, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x4c_threatStatus = newStatus; } } float oldX8 = x8_damagePulseTimer; x8_damagePulseTimer = std::fmod(x8_damagePulseTimer + dt, 0.5f); if (x8_damagePulseTimer < 0.25f) xc_damagePulse = x8_damagePulseTimer / 0.25f; else xc_damagePulse = (0.5f - x8_damagePulseTimer) / 0.25f; if (x4c_threatStatus == EThreatStatus::Damage && x8_damagePulseTimer < oldX8) CSfxManager::SfxStart(SFXui_threat_damage, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); if (x68_textpane_threatwarning) { if (x4c_threatStatus != EThreatStatus::Normal) { x48_warningLerpAlpha = std::min(x48_warningLerpAlpha + 2.f * dt, 1.f); zeus::CColor color = zeus::skWhite; color.a() = x48_warningLerpAlpha * xc_damagePulse; x68_textpane_threatwarning->SetColor(color); } else { x48_warningLerpAlpha = std::max(0.f, x48_warningLerpAlpha - 2.f * dt); zeus::CColor color = zeus::skWhite; color.a() = x48_warningLerpAlpha * xc_damagePulse; x68_textpane_threatwarning->SetColor(color); } if (x68_textpane_threatwarning->GetGeometryColor().a() > 0.f) x68_textpane_threatwarning->SetIsVisible(true); else x68_textpane_threatwarning->SetIsVisible(false); } if (x4c_threatStatus == EThreatStatus::Damage) x50_warningColorLerp = std::min(x50_warningColorLerp + 2.f * dt, 1.f); else x50_warningColorLerp = std::max(0.f, x50_warningColorLerp - 2.f * dt); } std::pair CHudThreatInterface::CombatThreatBarCoordFunc(float t) { const float z = IconTranslateRanges[size_t(EHudType::Combat)] * t; return {zeus::CVector3f(-0.3f, 0.f, z), zeus::CVector3f(0.f, 0.f, z)}; } std::pair CHudThreatInterface::XRayThreatBarCoordFunc(float t) { const float theta = 0.8f * (t - 0.5f); const float x = -9.55f * std::cos(theta); const float z = 9.55f * std::sin(theta); return {zeus::CVector3f(0.4f + x, 0.f, z), zeus::CVector3f(x, 0.f, z)}; } std::pair CHudThreatInterface::ThermalThreatBarCoordFunc(float t) { const float transRange = IconTranslateRanges[size_t(EHudType::Thermal)]; const float a = 0.08f * transRange; const float b = t * transRange; float c; if (b < a) { c = b / a; } else if (b < transRange - a) { c = 1.f; } else { c = 1.f - (b - (transRange - a)) / a; } return {zeus::CVector3f(0.1f, 0.f, b), zeus::CVector3f(0.5f * c + 0.1f, 0.f, b)}; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudThreatInterface.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CHudInterface.hpp" #include namespace metaforce { class CAuiEnergyBarT01; class CGuiFrame; class CGuiModel; class CGuiTextPane; class CGuiWidget; class CHudThreatInterface { enum class EThreatStatus { Normal, Warning, Damage }; EHudType x4_hudType; float x8_damagePulseTimer = 0.f; float xc_damagePulse = 0.f; float x10_threatDist; float x14_arrowTimer = 0.f; zeus::CTransform x18_threatIconXf; float x48_warningLerpAlpha = 0.f; EThreatStatus x4c_threatStatus = EThreatStatus::Normal; float x50_warningColorLerp = 0.f; bool x54_24_visibleDebug : 1 = true; bool x54_25_visibleGame : 1 = true; bool x54_26_hasArrows : 1; bool x54_27_notXRay : 1; CGuiWidget* x58_basewidget_threatstuff; CGuiWidget* x5c_basewidget_threaticon; CGuiModel* x60_model_threatarrowup; CGuiModel* x64_model_threatarrowdown; CGuiTextPane* x68_textpane_threatwarning; CAuiEnergyBarT01* x6c_energybart01_threatbar; CGuiTextPane* x70_textpane_threatdigits; void UpdateVisibility(); public: CHudThreatInterface(CGuiFrame& selHud, EHudType hudType, float threatDist); void SetThreatDistance(float threatDist); void SetIsVisibleDebug(bool v); void SetIsVisibleGame(bool v); void Update(float dt); static std::pair CombatThreatBarCoordFunc(float t); static std::pair XRayThreatBarCoordFunc(float t); static std::pair ThermalThreatBarCoordFunc(float t); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudVisorBeamMenu.cpp ================================================ #include "Runtime/GuiSys/CHudVisorBeamMenu.hpp" #include #include "Runtime/CGameState.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Audio/CSfxManager.hpp" #include "Runtime/GuiSys/CGuiFrame.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/Formatting.hpp" namespace metaforce { constexpr std::array BaseMenuNames{ "BaseWidget_VisorMenu"sv, "BaseWidget_BeamMenu"sv, }; constexpr std::array TextNames{ "TextPane_VisorMenu"sv, "TextPane_BeamMenu"sv, }; constexpr std::array BaseTitleNames{ "basewidget_visormenutitle"sv, "basewidget_beammenutitle"sv, }; constexpr std::array ModelNames{ "model_visor"sv, "model_beam"sv, }; constexpr std::array, 2> MenuItemOrders{{ {'1', '0', '3', '2'}, {'3', '2', '1', '0'}, }}; constexpr std::array, 2> MenuStringIdx{{ {0, 2, 1, 3}, // Combat, XRay, Scan, Thermal {4, 5, 6, 7}, // Power, Ice, Wave, Plasma }}; constexpr std::array SelectionSfxs{ SFXui_select_visor, SFXui_select_beam, }; CHudVisorBeamMenu::CHudVisorBeamMenu(CGuiFrame& baseHud, EHudVisorBeamMenu type, const rstl::reserved_vector& enables) : x0_baseHud(baseHud), x4_type(type) { x7c_animDur = g_tweakGui->GetBeamVisorMenuAnimTime(); x80_24_swapBeamControls = g_GameState->GameOptions().GetSwapBeamControls(); EHudVisorBeamMenu swappedType; if (x80_24_swapBeamControls) swappedType = EHudVisorBeamMenu(1 - int(x4_type)); else swappedType = x4_type; x20_textpane_menu = static_cast(x0_baseHud.FindWidget(TextNames[size_t(swappedType)])); x1c_basewidget_menutitle = x0_baseHud.FindWidget(BaseTitleNames[size_t(swappedType)]); x18_basewidget_menu = x0_baseHud.FindWidget(BaseMenuNames[size_t(swappedType)]); x24_model_ghost = static_cast(x0_baseHud.FindWidget(fmt::format("{}ghost", ModelNames[size_t(x4_type)]))); x28_menuItems.resize(4); for (size_t i = 0; i < x28_menuItems.size(); i++) { const auto modelName = ModelNames[size_t(x4_type)]; const auto menuItemOrder = MenuItemOrders[size_t(x4_type)][i]; SMenuItem& item = x28_menuItems[i]; item.x0_model_loz = static_cast(x0_baseHud.FindWidget(fmt::format("{}loz{}", modelName, menuItemOrder))); item.x4_model_icon = static_cast(x0_baseHud.FindWidget(fmt::format("{}icon{}", modelName, menuItemOrder))); item.xc_opacity = enables[i] ? 1.f : 0.f; } if (x4_type == EHudVisorBeamMenu::Visor) { x20_textpane_menu->TextSupport().SetFontColor(g_tweakGuiColors->GetVisorMenuTextFont()); x20_textpane_menu->TextSupport().SetOutlineColor(g_tweakGuiColors->GetVisorMenuTextOutline()); } else { x20_textpane_menu->TextSupport().SetFontColor(g_tweakGuiColors->GetBeamMenuTextFont()); x20_textpane_menu->TextSupport().SetOutlineColor(g_tweakGuiColors->GetBeamMenuTextOutline()); } zeus::CColor titleColor = zeus::skWhite; titleColor.a() = 0.f; x1c_basewidget_menutitle->SetColor(titleColor); x20_textpane_menu->TextSupport().SetText( g_MainStringTable->GetString(MenuStringIdx[size_t(x4_type)][x8_selectedItem])); for (size_t i = 0; i < x28_menuItems.size(); ++i) { SMenuItem& item = x28_menuItems[i]; item.x0_model_loz->SetColor(g_tweakGuiColors->GetVisorBeamMenuLozColor()); UpdateMenuWidgetTransform(i, *item.x0_model_loz, 1.f); } Update(0.f, true); } void CHudVisorBeamMenu::UpdateMenuWidgetTransform(size_t idx, CGuiWidget& w, float t) { const float translate = t * g_tweakGui->GetVisorBeamMenuItemTranslate(); const float scale = t * g_tweakGui->GetVisorBeamMenuItemInactiveScale() + (1.f - t) * g_tweakGui->GetVisorBeamMenuItemActiveScale(); if (x4_type == EHudVisorBeamMenu::Visor) { if (idx == 2) { idx = 3; } else if (idx == 3) { idx = 2; } } else { if (idx == 1) { idx = 2; } else if (idx == 2) { idx = 1; } } switch (idx) { case 0: w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(0.f, 0.f, translate) * zeus::CTransform::Scale(scale)); break; case 1: w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(translate, 0.f, 0.f) * zeus::CTransform::Scale(scale)); break; case 2: w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(0.f, 0.f, -translate) * zeus::CTransform::Scale(scale)); break; case 3: w.SetO2WTransform(x18_basewidget_menu->GetWorldTransform() * zeus::CTransform::Translate(-translate, 0.f, 0.f) * zeus::CTransform::Scale(scale)); break; default: break; } } void CHudVisorBeamMenu::Update(float dt, bool init) { bool curSwapBeamControls = g_GameState->GameOptions().GetSwapBeamControls(); if (x80_24_swapBeamControls != curSwapBeamControls) { x80_24_swapBeamControls = curSwapBeamControls; EHudVisorBeamMenu swappedType; if (x80_24_swapBeamControls) swappedType = EHudVisorBeamMenu(1 - int(x4_type)); else swappedType = x4_type; x18_basewidget_menu = x0_baseHud.FindWidget(BaseMenuNames[size_t(swappedType)]); x20_textpane_menu = static_cast(x0_baseHud.FindWidget(TextNames[size_t(swappedType)])); x1c_basewidget_menutitle = x0_baseHud.FindWidget(BaseTitleNames[size_t(swappedType)]); for (size_t i = 0; i < x28_menuItems.size(); ++i) { SMenuItem& item = x28_menuItems[i]; UpdateMenuWidgetTransform(i, *item.x4_model_icon, item.x8_positioner); UpdateMenuWidgetTransform(i, *item.x0_model_loz, 1.f); } UpdateMenuWidgetTransform(size_t(x8_selectedItem), *x24_model_ghost, x28_menuItems[x8_selectedItem].x8_positioner); } zeus::CColor activeColor = g_tweakGuiColors->GetVisorBeamMenuItemActive(); zeus::CColor inactiveColor = g_tweakGuiColors->GetVisorBeamMenuItemInactive(); zeus::CColor lozColor = g_tweakGuiColors->GetVisorBeamMenuLozColor(); std::array tmpColors; for (size_t i = 0; i < x28_menuItems.size(); ++i) { SMenuItem& item = x28_menuItems[i]; if (item.xc_opacity > 0.f) { item.xc_opacity = std::min(item.xc_opacity + dt, 1.f); } tmpColors[i] = zeus::CColor::lerp(activeColor, zeus::skClear, item.xc_opacity); } switch (x6c_animPhase) { case EAnimPhase::Steady: for (size_t i = 0; i < x28_menuItems.size(); ++i) { SMenuItem& item = x28_menuItems[i]; const bool isSelectedItem = x8_selectedItem == int(i); const bool isClear = item.xc_opacity == 0.0f; const zeus::CColor& color0 = isSelectedItem ? activeColor : inactiveColor; const zeus::CColor& color1 = isSelectedItem ? lozColor : inactiveColor; const zeus::CColor iconColor = isClear ? zeus::skClear : color0 + tmpColors[i]; const zeus::CColor lColor = isClear ? lozColor : color1 + tmpColors[i]; item.x4_model_icon->SetColor(iconColor); item.x0_model_loz->SetColor(lColor); item.x8_positioner = isSelectedItem ? 0.f : 1.f; } x24_model_ghost->SetColor(activeColor); break; case EAnimPhase::SelectFlash: { zeus::CColor color = zeus::skWhite; color.a() = 0.f; x1c_basewidget_menutitle->SetColor(color); zeus::CColor& color0 = std::fmod(x10_interp, 0.1f) > 0.05f ? activeColor : inactiveColor; SMenuItem& item0 = x28_menuItems[xc_pendingSelection]; color = color0 + tmpColors[xc_pendingSelection]; item0.x4_model_icon->SetColor(color); item0.x0_model_loz->SetColor(color); SMenuItem& item1 = x28_menuItems[x8_selectedItem]; color = zeus::CColor::lerp(inactiveColor, activeColor, x10_interp) + tmpColors[x8_selectedItem]; item1.x4_model_icon->SetColor(color); item1.x0_model_loz->SetColor(lozColor); for (size_t i = 0; i < x28_menuItems.size(); ++i) { const bool isSelectedItem = x8_selectedItem == int(i); x28_menuItems[i].x8_positioner = isSelectedItem ? 1.f - x10_interp : 1.f; } x24_model_ghost->SetColor(zeus::CColor::lerp(activeColor, inactiveColor, item1.x8_positioner)); break; } case EAnimPhase::Animate: for (size_t i = 0; i < x28_menuItems.size(); ++i) { SMenuItem& item = x28_menuItems[i]; const bool isSelectedItem = x8_selectedItem == int(i); const bool isClear = item.xc_opacity == 0.f; const zeus::CColor& color0 = isSelectedItem ? activeColor : inactiveColor; const zeus::CColor iconColor = isClear ? zeus::skClear : color0 + tmpColors[i]; item.x4_model_icon->SetColor(iconColor); item.x0_model_loz->SetColor((isClear || isSelectedItem) ? lozColor : inactiveColor); item.x8_positioner = isSelectedItem ? 1.f - x10_interp : 1.f; } x24_model_ghost->SetColor( zeus::CColor::lerp(activeColor, inactiveColor, x28_menuItems[x8_selectedItem].x8_positioner)); break; default: break; } if (x78_textFader > 0.f) { x78_textFader = std::max(0.f, x78_textFader - dt); zeus::CColor color = zeus::skWhite; color.a() = x78_textFader / x7c_animDur; x1c_basewidget_menutitle->SetColor(color); } if (x14_26_dirty || init) { x14_26_dirty = false; for (size_t i = 0; i < x28_menuItems.size(); ++i) { SMenuItem& item = x28_menuItems[i]; UpdateMenuWidgetTransform(i, *item.x4_model_icon, item.x8_positioner); } UpdateMenuWidgetTransform(size_t(x8_selectedItem), *x24_model_ghost, x28_menuItems[x8_selectedItem].x8_positioner); } if (!x14_24_visibleDebug || !x14_25_visibleGame) return; x1c_basewidget_menutitle->SetVisibility(x1c_basewidget_menutitle->GetGeometryColor().a() != 0.f, ETraversalMode::Children); for (SMenuItem& item : x28_menuItems) { item.x4_model_icon->SetIsVisible(item.x4_model_icon->GetGeometryColor().a() != 0.f); } } void CHudVisorBeamMenu::UpdateHudAlpha(float alpha) { zeus::CColor color = zeus::skWhite; color.a() = g_GameState->GameOptions().GetHUDAlpha() / 255.f * alpha; x18_basewidget_menu->SetColor(color); } void CHudVisorBeamMenu::SetIsVisibleGame(bool v) { x14_25_visibleGame = v; bool vis = x14_24_visibleDebug && x14_25_visibleGame; x18_basewidget_menu->SetVisibility(vis, ETraversalMode::Children); if (vis) Update(0.f, true); } void CHudVisorBeamMenu::SetPlayerHas(const rstl::reserved_vector& enables) { for (size_t i = 0; i < x28_menuItems.size(); ++i) { SMenuItem& item = x28_menuItems[i]; if (item.xc_opacity == 0.f && enables[i]) { item.xc_opacity = FLT_EPSILON; } // Metaforce addition else if (!enables[i]) { item.xc_opacity = 0.f; } } } void CHudVisorBeamMenu::SetSelection(int selection, int pending, float interp) { if (x8_selectedItem == selection && xc_pendingSelection == pending && x10_interp == interp) return; if (pending != selection) { if (x6c_animPhase != EAnimPhase::SelectFlash) { CSfxManager::SfxStart(SelectionSfxs[size_t(x4_type)], 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } x6c_animPhase = EAnimPhase::SelectFlash; } else if (interp < 1.f) { x6c_animPhase = EAnimPhase::Animate; x20_textpane_menu->TextSupport().SetText( g_MainStringTable->GetString(MenuStringIdx[size_t(x4_type)][x8_selectedItem])); x20_textpane_menu->TextSupport().SetTypeWriteEffectOptions(true, 0.1f, 16.f); } else { if (x6c_animPhase != EAnimPhase::Steady) x78_textFader = x7c_animDur; x6c_animPhase = EAnimPhase::Steady; } x14_26_dirty = true; x8_selectedItem = selection; xc_pendingSelection = pending; x10_interp = interp; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CHudVisorBeamMenu.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" namespace metaforce { class CGuiFrame; class CGuiModel; class CGuiTextPane; class CGuiWidget; class CHudVisorBeamMenu { public: enum class EHudVisorBeamMenu { Visor, Beam }; private: struct SMenuItem { CGuiModel* x0_model_loz = nullptr; CGuiModel* x4_model_icon = nullptr; float x8_positioner = 0.f; float xc_opacity = 0.f; }; enum class EAnimPhase { None, Steady, SelectFlash, Animate }; CGuiFrame& x0_baseHud; EHudVisorBeamMenu x4_type; int x8_selectedItem = 0; int xc_pendingSelection = 0; float x10_interp = 1.f; bool x14_24_visibleDebug : 1 = true; bool x14_25_visibleGame : 1 = true; bool x14_26_dirty : 1 = true; CGuiWidget* x18_basewidget_menu; CGuiWidget* x1c_basewidget_menutitle; CGuiTextPane* x20_textpane_menu; CGuiModel* x24_model_ghost; rstl::reserved_vector x28_menuItems; EAnimPhase x6c_animPhase = EAnimPhase::Steady; float x70_ = FLT_EPSILON; float x74_ = FLT_EPSILON; float x78_textFader = 0.f; float x7c_animDur; bool x80_24_swapBeamControls : 1; void UpdateMenuWidgetTransform(size_t idx, CGuiWidget& w, float t); public: CHudVisorBeamMenu(CGuiFrame& baseHud, EHudVisorBeamMenu type, const rstl::reserved_vector& enables); void Update(float dt, bool init); void UpdateHudAlpha(float alpha); void SetIsVisibleGame(bool v); void SetPlayerHas(const rstl::reserved_vector& enables); void SetSelection(int selection, int pending, float interp); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CInstruction.cpp ================================================ #include "Runtime/GuiSys/CInstruction.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/GuiSys/CFontRenderState.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" #include "Runtime/GuiSys/CTextRenderBuffer.hpp" namespace metaforce { void CInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const {} void CInstruction::GetAssets(std::vector& assetsOut) const {} size_t CInstruction::GetAssetCount() const { return 0; } void CColorInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.SetColor(x4_cType, x8_color); } void CColorInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CColorOverrideInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.x64_colorOverrides[x4_overrideIdx] = true; zeus::CColor convCol = state.ConvertToTextureSpace(x8_color); state.x0_drawStrOpts.x4_colors[x4_overrideIdx] = convCol; } void CColorOverrideInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CFontInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { buf->AddFontChange(x4_font); state.x48_font = x4_font; state.RefreshPalette(); } void CFontInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CFontInstruction::GetAssets(std::vector& assetsOut) const { assetsOut.push_back(x4_font); } size_t CFontInstruction::GetAssetCount() const { return 1; } void CLineExtraSpaceInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.x78_extraLineSpace = x4_extraSpace; } void CLineExtraSpaceInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CLineInstruction::TestLargestFont(s32 w, s32 h, s32 b) { if (!x18_largestMonoBaseline) x18_largestMonoBaseline = b; if (x14_largestMonoWidth < w) x14_largestMonoWidth = w; if (x10_largestMonoHeight < h) { x10_largestMonoHeight = h; x18_largestMonoBaseline = b; } } void CLineInstruction::TestLargestImage(s32 w, s32 h, s32 b) { if (!x24_largestImageBaseline) x24_largestImageBaseline = b; if (x20_largestImageWidth < w) x20_largestImageWidth = w; if (x1c_largestImageHeight < h) { x1c_largestImageHeight = h; x24_largestImageBaseline = b; } } void CLineInstruction::InvokeLTR(CFontRenderState& state) const { switch (x28_just) { case EJustification::Left: case EJustification::Full: case EJustification::NLeft: case EJustification::LeftMono: state.xd4_curX = state.x88_curBlock->x4_offsetX; break; case EJustification::Center: case EJustification::CenterMono: state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX / 2 - x8_curX / 2; break; case EJustification::NCenter: if (x4_wordCount == 1) { state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX / 2 - x8_curX / 2; } else { state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX / 2 - state.x88_curBlock->x2c_lineX / 2; } break; case EJustification::Right: case EJustification::RightMono: state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX - x8_curX; break; case EJustification::NRight: state.xd4_curX = state.x88_curBlock->x4_offsetX + state.x88_curBlock->xc_blockExtentX - state.x88_curBlock->x2c_lineX; break; default: break; } if (state.xdc_currentLineInst) { const CLineInstruction& inst = *state.xdc_currentLineInst; s32 val = 0; switch (state.x88_curBlock->x1c_vertJustification) { case EVerticalJustification::Top: case EVerticalJustification::Center: case EVerticalJustification::Bottom: case EVerticalJustification::NTop: case EVerticalJustification::NCenter: case EVerticalJustification::NBottom: val = inst.xc_curY; break; case EVerticalJustification::Full: val = state.x88_curBlock->x10_blockExtentY - state.x88_curBlock->x30_lineY; if (state.x88_curBlock->x34_lineCount > 1) val /= state.x88_curBlock->x34_lineCount - 1; else val = 0; val += inst.xc_curY; break; case EVerticalJustification::TopMono: val = state.x88_curBlock->x24_largestMonoH; break; case EVerticalJustification::CenterMono: val = (inst.xc_curY - state.x88_curBlock->x24_largestMonoH) / 2 + state.x88_curBlock->x24_largestMonoH; break; case EVerticalJustification::RightMono: val = state.x88_curBlock->x24_largestMonoH * 2 - inst.xc_curY; break; } if (state.x88_curBlock->x1c_vertJustification != EVerticalJustification::Full) val = val * state.x74_lineSpacing + state.x78_extraLineSpace; state.xd8_curY += val; } } void CLineInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { InvokeLTR(state); state.x108_lineInitialized = true; state.xdc_currentLineInst = this; } void CLineInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { if (!state.xdc_currentLineInst) Invoke(state, buf); } void CLineSpacingInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.x74_lineSpacing = x4_lineSpacing; } void CLineSpacingInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CPopStateInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { const auto& oldFont = state.GetFont(); state.PopState(); if (oldFont.GetObj() != state.GetFont().GetObj()) { buf->AddFontChange(state.GetFont()); } } void CPopStateInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CPushStateInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.PushState(); } void CPushStateInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CRemoveColorOverrideInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.x64_colorOverrides[x4_idx] = false; } void CRemoveColorOverrideInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CImageInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { if (x4_image.IsLoaded() && x4_image.x4_texs.size()) { const CTexture* tex = x4_image.x4_texs[0].GetObj(); if (state.x88_curBlock->x14_dir == ETextDirection::Horizontal) { if (buf) { int y = state.xd8_curY + state.xdc_currentLineInst->GetBaseline() - x4_image.CalculateBaseline(); zeus::CVector2i coords(state.xd4_curX, y); buf->AddImage(coords, x4_image); } state.xd4_curX = state.xd4_curX + tex->GetWidth() * x4_image.x14_cropFactor.x(); } else { int scale = state.xdc_currentLineInst->x8_curX - tex->GetWidth() * x4_image.x14_cropFactor.x(); if (buf) { zeus::CVector2i coords(scale / 2 + state.xd4_curX, state.xd8_curY); buf->AddImage(coords, x4_image); } state.xd8_curY += x4_image.CalculateHeight(); } } } void CImageInstruction::GetAssets(std::vector& assetsOut) const { for (const CToken& tok : x4_image.x4_texs) assetsOut.push_back(tok); } size_t CImageInstruction::GetAssetCount() const { return x4_image.x4_texs.size(); } void CTextInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { int xOut, yOut; if (state.x88_curBlock->x14_dir == ETextDirection::Horizontal) { state.x48_font->DrawString(state.x0_drawStrOpts, state.xd4_curX, state.xdc_currentLineInst->GetBaseline() + state.xd8_curY, xOut, yOut, buf, x4_str.c_str(), x4_str.size()); state.xd4_curX = xOut; } else { int scale = state.xdc_currentLineInst->x8_curX - state.x48_font->GetMonoWidth(); state.x48_font->DrawString(state.x0_drawStrOpts, scale / 2 + state.xd4_curX, state.xd8_curY, xOut, yOut, buf, x4_str.c_str(), x4_str.size()); state.xd8_curY = yOut; } } void CBlockInstruction::TestLargestFont(s32 monoW, s32 monoH, s32 baseline) { if (!x28_largestBaseline) x28_largestBaseline = baseline; if (x20_largestMonoW < monoW) x20_largestMonoW = monoW; if (x24_largestMonoH < monoH) { x24_largestMonoH = monoH; x28_largestBaseline = baseline; } } void CBlockInstruction::SetupPositionLTR(CFontRenderState& state) const { switch (x1c_vertJustification) { case EVerticalJustification::Top: case EVerticalJustification::Full: case EVerticalJustification::NTop: case EVerticalJustification::TopMono: state.xd8_curY = x8_offsetY; break; case EVerticalJustification::Center: case EVerticalJustification::NCenter: state.xd8_curY = x8_offsetY + (x10_blockExtentY - x30_lineY) / 2; break; case EVerticalJustification::CenterMono: state.xd8_curY = x8_offsetY + (x10_blockExtentY - x34_lineCount * x24_largestMonoH) / 2; break; case EVerticalJustification::Bottom: case EVerticalJustification::NBottom: state.xd8_curY = x8_offsetY + x10_blockExtentY - x30_lineY; break; case EVerticalJustification::RightMono: state.xd8_curY = x8_offsetY + x10_blockExtentY - x34_lineCount * x24_largestMonoH; break; } } void CBlockInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.x0_drawStrOpts.x0_direction = x14_dir; state.x88_curBlock = const_cast(this); if (x14_dir == ETextDirection::Horizontal) SetupPositionLTR(state); } void CBlockInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { Invoke(state, buf); } void CWordInstruction::InvokeLTR(CFontRenderState& state) const { CRasterFont* font = state.x48_font.GetObj(); char16_t space = u' '; int w, h; font->GetSize(state.x0_drawStrOpts, w, h, &space, 1); const CLineInstruction& inst = *state.xdc_currentLineInst; switch (state.x88_curBlock->x18_justification) { case EJustification::Full: w += (state.x88_curBlock->xc_blockExtentX - inst.x8_curX) / (inst.x4_wordCount - 1); break; case EJustification::NLeft: case EJustification::NCenter: case EJustification::NRight: w += (state.x88_curBlock->x2c_lineX - inst.x8_curX) / (inst.x4_wordCount - 1); break; default: break; } int wOut = state.xd4_curX; font->DrawSpace(state.x0_drawStrOpts, wOut, inst.xc_curY - font->GetMonoHeight() + state.xd8_curY, wOut, h, w); state.xd4_curX = wOut; } void CWordInstruction::Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const { if (state.x108_lineInitialized) { state.x108_lineInitialized = false; return; } if (state.x0_drawStrOpts.x0_direction == ETextDirection::Horizontal) InvokeLTR(state); } void CWordInstruction::PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const { state.x108_lineInitialized = false; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CInstruction.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/GuiSys/CFontImageDef.hpp" #include "Runtime/GuiSys/CGuiTextSupport.hpp" namespace metaforce { class CFontImageDef; class CFontRenderState; class CTextRenderBuffer; class CInstruction { public: virtual ~CInstruction() = default; virtual void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const = 0; virtual void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const; virtual void GetAssets(std::vector& assetsOut) const; virtual size_t GetAssetCount() const; }; class CColorInstruction : public CInstruction { EColorType x4_cType; CTextColor x8_color; public: CColorInstruction(EColorType tp, const CTextColor& color) : x4_cType(tp), x8_color(color) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CColorOverrideInstruction : public CInstruction { int x4_overrideIdx; CTextColor x8_color; public: CColorOverrideInstruction(int idx, const CTextColor& color) : x4_overrideIdx(idx), x8_color(color) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CFontInstruction : public CInstruction { TLockedToken x4_font; public: explicit CFontInstruction(const TToken& font) : x4_font(font) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void GetAssets(std::vector& assetsOut) const override; size_t GetAssetCount() const override; }; class CLineExtraSpaceInstruction : public CInstruction { s32 x4_extraSpace; public: explicit CLineExtraSpaceInstruction(s32 extraSpace) : x4_extraSpace(extraSpace) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CLineInstruction : public CInstruction { friend class CTextExecuteBuffer; friend class CTextInstruction; friend class CImageInstruction; friend class CWordInstruction; s32 x4_wordCount = 0; s32 x8_curX = 0; s32 xc_curY = 0; s32 x10_largestMonoHeight = 0; s32 x14_largestMonoWidth = 0; s32 x18_largestMonoBaseline = 0; s32 x1c_largestImageHeight = 0; s32 x20_largestImageWidth = 0; s32 x24_largestImageBaseline = 0; EJustification x28_just; EVerticalJustification x2c_vjust; bool x30_imageBaseline; public: CLineInstruction(EJustification just, EVerticalJustification vjust, bool imageBaseline) : x28_just(just), x2c_vjust(vjust), x30_imageBaseline(imageBaseline) {} void TestLargestFont(s32 w, s32 h, s32 b); void TestLargestImage(s32 w, s32 h, s32 b); void InvokeLTR(CFontRenderState& state) const; void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; s32 GetHeight() const { if (x10_largestMonoHeight && !x30_imageBaseline) return x10_largestMonoHeight; else return x1c_largestImageHeight; } s32 GetBaseline() const { if (x10_largestMonoHeight && !x30_imageBaseline) return x18_largestMonoBaseline; else return x24_largestImageBaseline; } }; class CLineSpacingInstruction : public CInstruction { float x4_lineSpacing; public: explicit CLineSpacingInstruction(float spacing) : x4_lineSpacing(spacing) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CPopStateInstruction : public CInstruction { public: void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CPushStateInstruction : public CInstruction { public: void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CRemoveColorOverrideInstruction : public CInstruction { int x4_idx; public: explicit CRemoveColorOverrideInstruction(int idx) : x4_idx(idx) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CImageInstruction : public CInstruction { CFontImageDef x4_image; public: explicit CImageInstruction(const CFontImageDef& image) : x4_image(image) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void GetAssets(std::vector& assetsOut) const override; size_t GetAssetCount() const override; }; class CTextInstruction : public CInstruction { std::u16string x4_str; /* used to be a placement-new sized allocation */ public: CTextInstruction(const char16_t* str, int len) : x4_str(str, len) {} void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CBlockInstruction : public CInstruction { friend class CTextExecuteBuffer; friend class CLineInstruction; friend class CImageInstruction; friend class CTextInstruction; friend class CWordInstruction; s32 x4_offsetX; s32 x8_offsetY; s32 xc_blockExtentX; s32 x10_blockExtentY; ETextDirection x14_dir; EJustification x18_justification; EVerticalJustification x1c_vertJustification; s32 x20_largestMonoW = 0; s32 x24_largestMonoH = 0; s32 x28_largestBaseline = 0; s32 x2c_lineX = 0; s32 x30_lineY = 0; s32 x34_lineCount = 0; public: CBlockInstruction(s32 offX, s32 offY, s32 extX, s32 extY, ETextDirection dir, EJustification just, EVerticalJustification vjust) : x4_offsetX(offX) , x8_offsetY(offY) , xc_blockExtentX(extX) , x10_blockExtentY(extY) , x14_dir(dir) , x18_justification(just) , x1c_vertJustification(vjust) {} void TestLargestFont(s32 monoW, s32 monoH, s32 baseline); void SetupPositionLTR(CFontRenderState& state) const; void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; class CWordInstruction : public CInstruction { public: void InvokeLTR(CFontRenderState& state) const; void Invoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; void PageInvoke(CFontRenderState& state, CTextRenderBuffer* buf) const override; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CMakeLists.txt ================================================ set(GUISYS_SOURCES CSplashScreen.hpp CSplashScreen.cpp CGuiObject.hpp CGuiObject.cpp CConsoleOutputWindow.hpp CConsoleOutputWindow.cpp CAuiEnergyBarT01.hpp CAuiEnergyBarT01.cpp CAuiImagePane.hpp CAuiImagePane.cpp CAuiMeter.hpp CAuiMeter.cpp CConsoleOutputWindow.hpp CConsoleOutputWindow.cpp CErrorOutputWindow.hpp CErrorOutputWindow.cpp CGuiCamera.hpp CGuiCamera.cpp CGuiFrame.hpp CGuiFrame.cpp CGuiLight.hpp CGuiLight.cpp CGuiModel.hpp CGuiModel.cpp CGuiSliderGroup.hpp CGuiSliderGroup.cpp CGuiSys.hpp CGuiSys.cpp CGuiTableGroup.hpp CGuiTableGroup.cpp CGuiTextPane.hpp CGuiTextPane.cpp CGuiTextSupport.hpp CGuiTextSupport.cpp CGuiWidget.hpp CGuiWidget.cpp CGuiWidgetDrawParms.hpp CSplashScreen.hpp CSplashScreen.cpp CGuiCompoundWidget.hpp CGuiCompoundWidget.cpp CSaveableState.hpp CSaveableState.cpp CDrawStringOptions.hpp CRasterFont.hpp CRasterFont.cpp CGuiGroup.hpp CGuiGroup.cpp CGuiWidgetIdDB.hpp CGuiWidgetIdDB.cpp CGuiHeadWidget.hpp CGuiHeadWidget.cpp CGuiPane.hpp CGuiPane.cpp CFontRenderState.hpp CFontRenderState.cpp CTextExecuteBuffer.hpp CTextExecuteBuffer.cpp CTextRenderBuffer.hpp CTextRenderBuffer.cpp CInstruction.hpp CInstruction.cpp CTextParser.hpp CTextParser.cpp CWordBreakTables.hpp CWordBreakTables.cpp CFontImageDef.hpp CFontImageDef.cpp CStringTable.hpp CStringTable.cpp CTargetingManager.hpp CTargetingManager.cpp CCompoundTargetReticle.hpp CCompoundTargetReticle.cpp COrbitPointMarker.hpp COrbitPointMarker.cpp CHudEnergyInterface.hpp CHudEnergyInterface.cpp CHudBossEnergyInterface.hpp CHudBossEnergyInterface.cpp CHudThreatInterface.hpp CHudThreatInterface.cpp CHudMissileInterface.hpp CHudMissileInterface.cpp CHudFreeLookInterface.hpp CHudFreeLookInterface.cpp CHudDecoInterface.hpp CHudDecoInterface.cpp CHudHelmetInterface.hpp CHudHelmetInterface.cpp CHudVisorBeamMenu.hpp CHudVisorBeamMenu.cpp CHudRadarInterface.hpp CHudRadarInterface.cpp CHudBallInterface.hpp CHudBallInterface.cpp CHudInterface.hpp CScanDisplay.hpp CScanDisplay.cpp) runtime_add_list(GuiSys GUISYS_SOURCES) ================================================ FILE: Runtime/GuiSys/COrbitPointMarker.cpp ================================================ #include "Runtime/GuiSys/COrbitPointMarker.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/World/CPlayer.hpp" #include namespace metaforce { COrbitPointMarker::COrbitPointMarker() { x0_zOffset = g_tweakTargeting->GetOrbitPointZOffset(); x28_orbitPointModel = g_SimplePool->GetObj("CMDL_OrbitPoint"); } bool COrbitPointMarker::CheckLoadComplete() const { return x28_orbitPointModel.IsLoaded(); } void COrbitPointMarker::Update(float dt, const CStateManager& mgr) { x24_curTime += dt; const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr); CPlayer::EPlayerOrbitState orbitState = mgr.GetPlayer().GetOrbitState(); bool freeOrbit = orbitState >= CPlayer::EPlayerOrbitState::OrbitPoint; if (freeOrbit != x1c_lastFreeOrbit) { if (orbitState == CPlayer::EPlayerOrbitState::OrbitPoint || orbitState == CPlayer::EPlayerOrbitState::OrbitCarcass) { ResetInterpolationTimer(g_tweakTargeting->GetOrbitPointInTime()); zeus::CVector3f orbitTargetPosition = mgr.GetPlayer().GetHUDOrbitTargetPosition(); if (!x4_camRelZPos) x10_lagTargetPos = orbitTargetPosition + zeus::CVector3f(0.f, 0.f, x0_zOffset); else x10_lagTargetPos = zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(), curCam->GetTranslation().z() + x0_zOffset); x8_lagAzimuth = zeus::CEulerAngles(zeus::CQuaternion(curCam->GetTransform().basis)).z() + zeus::degToRad(45.f); } else { ResetInterpolationTimer(g_tweakTargeting->GetOrbitPointOutTime()); } x1c_lastFreeOrbit = !x1c_lastFreeOrbit; } if (x20_interpTimer > 0.f) x20_interpTimer = std::max(0.f, x20_interpTimer - dt); if (!x4_camRelZPos) { zeus::CVector3f orbitTargetPosition = mgr.GetPlayer().GetHUDOrbitTargetPosition(); if ((orbitTargetPosition.z() + x0_zOffset) - x10_lagTargetPos.z() < 0.1f) x10_lagTargetPos = orbitTargetPosition + zeus::CVector3f(0.f, 0.f, x0_zOffset); else if ((orbitTargetPosition.z() + x0_zOffset) - x10_lagTargetPos.z() < 0.f) x10_lagTargetPos = zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(), x10_lagTargetPos.z() - 0.1f); else x10_lagTargetPos = zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(), x10_lagTargetPos.z() + 0.1f); } else { zeus::CVector3f orbitTargetPosition = mgr.GetPlayer().GetHUDOrbitTargetPosition(); x10_lagTargetPos = zeus::CVector3f(orbitTargetPosition.x(), orbitTargetPosition.y(), x0_zOffset + orbitTargetPosition.z()); } if (x1c_lastFreeOrbit) { float newAzimuth = zeus::CEulerAngles(zeus::CQuaternion(curCam->GetTransform().basis)).z() + zeus::degToRad(45.f); float aziDelta = newAzimuth - xc_azimuth; if (mgr.GetPlayer().IsInFreeLook()) x8_lagAzimuth += aziDelta; xc_azimuth = newAzimuth; } } void COrbitPointMarker::Draw(const CStateManager& mgr) { if ((x1c_lastFreeOrbit || x20_interpTimer > 0.f) && g_tweakTargeting->DrawOrbitPoint() && x28_orbitPointModel.IsLoaded()) { SCOPED_GRAPHICS_DEBUG_GROUP("COrbitPointMarker::Draw", zeus::skCyan); const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr); zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr); CGraphics::SetViewPointMatrix(camXf); zeus::CFrustum frustum = mgr.SetupDrawFrustum(CGraphics::mViewport); frustum.updatePlanes( camXf, zeus::SProjPersp(zeus::degToRad(curCam->GetFov()), CGraphics::GetViewportAspect(), 1.f, 100.f)); g_Renderer->SetClippingPlanes(frustum); g_Renderer->SetPerspective(curCam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), curCam->GetNearClipDistance(), curCam->GetFarClipDistance()); float scale; if (x1c_lastFreeOrbit) scale = 1.f - x20_interpTimer / g_tweakTargeting->GetOrbitPointInTime(); else scale = x20_interpTimer / g_tweakTargeting->GetOrbitPointOutTime(); zeus::CTransform modelXf = zeus::CTransform::RotateZ(x8_lagAzimuth); modelXf.scaleBy(scale); modelXf.origin += x10_lagTargetPos; CGraphics::SetModelMatrix(modelXf); zeus::CColor color = g_tweakTargeting->GetOrbitPointColor(); color.a() *= scale; CModelFlags flags(7, 0, 0, color); x28_orbitPointModel->Draw(flags); } } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/COrbitPointMarker.hpp ================================================ #pragma once #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Graphics/CModel.hpp" #include namespace metaforce { class CStateManager; class COrbitPointMarker { float x0_zOffset; bool x4_camRelZPos = true; float x8_lagAzimuth = 0.f; float xc_azimuth = 0.f; zeus::CVector3f x10_lagTargetPos; bool x1c_lastFreeOrbit = false; float x20_interpTimer = 0.f; float x24_curTime = 0.f; TLockedToken x28_orbitPointModel; void ResetInterpolationTimer(float time) { x20_interpTimer = time; } public: COrbitPointMarker(); bool CheckLoadComplete() const; void Update(float dt, const CStateManager& mgr); void Draw(const CStateManager& mgr); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CRasterFont.cpp ================================================ #include "Runtime/GuiSys/CRasterFont.hpp" #include #include "Runtime/CSimplePool.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/GuiSys/CDrawStringOptions.hpp" #include "Runtime/GuiSys/CTextRenderBuffer.hpp" namespace metaforce { CRasterFont::CRasterFont(metaforce::CInputStream& in, metaforce::IObjectStore& store) { u32 magic = 0; in.Get(reinterpret_cast(&magic), 4); if (magic != SBIG('FONT')) return; u32 version = in.ReadLong(); x4_monoWidth = in.ReadLong(); x8_monoHeight = in.ReadLong(); if (version >= 1) x8c_baseline = in.ReadLong(); else x8c_baseline = x8_monoHeight; if (version >= 2) x90_lineMargin = in.ReadLong(); bool tmp1 = in.ReadBool(); bool tmp2 = in.ReadBool(); u32 tmp3 = in.ReadLong(); u32 tmp4 = in.ReadLong(); std::string name = in.Get(); u32 txtrId = (version == 5 ? in.ReadLongLong() : in.ReadLong()); x30_fontInfo = CFontInfo(tmp1, tmp2, tmp3, tmp4, name.c_str()); x80_texture = store.GetObj({FOURCC('TXTR'), txtrId}); x2c_mode = CTexture::EFontType(in.ReadLong()); u32 glyphCount = in.ReadLong(); xc_glyphs.reserve(glyphCount); for (u32 i = 0; i < glyphCount; ++i) { char16_t chr = in.ReadShort(); float startU = in.ReadFloat(); float startV = in.ReadFloat(); float endU = in.ReadFloat(); float endV = in.ReadFloat(); s32 layer = 0; s32 leftPadding, advance, rightPadding, cellWidth, cellHeight, baseline, kernStart; if (version < 4) { leftPadding = in.ReadInt32(); advance = in.ReadInt32(); rightPadding = in.ReadInt32(); cellWidth = in.ReadInt32(); cellHeight = in.ReadInt32(); baseline = in.ReadInt32(); kernStart = in.ReadInt32(); } else { layer = in.ReadInt8(); leftPadding = in.ReadInt8(); advance = in.ReadInt8(); rightPadding = in.ReadInt8(); cellWidth = in.ReadInt8(); cellHeight = in.ReadInt8(); baseline = in.ReadInt8(); kernStart = in.ReadInt16(); } xc_glyphs.emplace_back(chr, CGlyph(leftPadding, advance, rightPadding, startU, startV, endU, endV, cellWidth, cellHeight, baseline, kernStart, layer)); } std::sort(xc_glyphs.begin(), xc_glyphs.end(), [=](auto& a, auto& b) -> bool { return a.first < b.first; }); u32 kernCount = in.ReadLong(); x1c_kerning.reserve(kernCount); for (u32 i = 0; i < kernCount; ++i) { char16_t first = in.ReadShort(); char16_t second = in.ReadShort(); s32 howMuch = in.ReadInt32(); x1c_kerning.emplace_back(first, second, howMuch); } if (magic == SBIG('FONT') && version <= 4) x0_initialized = true; } const CGlyph* CRasterFont::InternalGetGlyph(char16_t chr) const { const auto iter = std::find_if(xc_glyphs.cbegin(), xc_glyphs.cend(), [chr](const auto& entry) { return entry.first == chr; }); if (iter == xc_glyphs.cend()) { return nullptr; } return &iter->second; } void CRasterFont::SinglePassDrawString(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, CTextRenderBuffer* renderBuf, const char16_t* str, s32 length) const { if (!x0_initialized) return; const char16_t* chr = str; const CGlyph* prevGlyph = nullptr; while (*chr != u'\0') { const CGlyph* glyph = GetGlyph(*chr); if (glyph) { if (opts.x0_direction == ETextDirection::Horizontal) { x += glyph->GetLeftPadding(); if (prevGlyph != nullptr) { x += KernLookup(x1c_kerning, prevGlyph->GetKernStart(), *chr); } int left = 0; int top = 0; if (renderBuf) { left += x; top += y - glyph->GetBaseline(); renderBuf->AddCharacter(zeus::CVector2i(left, top), *chr, opts.x4_colors[2]); } x += glyph->GetRightPadding() + glyph->GetAdvance(); } } prevGlyph = glyph; chr++; if (length == -1) continue; if ((chr - str) >= length) break; } xout = x; yout = y; } void CRasterFont::DrawSpace(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, int len) const { if (opts.x0_direction != ETextDirection::Horizontal) return; xout = x + len; yout = y; } void CRasterFont::DrawString(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, CTextRenderBuffer* renderBuf, const char16_t* str, int len) const { if (!x0_initialized) return; if (renderBuf != nullptr) { CGraphicsPalette pal(EPaletteFormat::RGB5A3, 4); u16* data = pal.Lock(); data[0] = bswap16(zeus::CColor(0.f, 0.f, 0.f, 0.f).toRGB5A3()); data[1] = bswap16(opts.x4_colors[0].toRGB5A3()); data[2] = bswap16(opts.x4_colors[1].toRGB5A3()); data[3] = bswap16(zeus::CColor(0.f, 0.f, 0.f, 0.f).toRGB5A3()); pal.UnLock(); renderBuf->AddPaletteChange(pal); } SinglePassDrawString(opts, x, y, xout, yout, renderBuf, str, len); } void CRasterFont::GetSize(const CDrawStringOptions& opts, int& width, int& height, const char16_t* str, int len) const { width = 0; height = 0; const char16_t* chr = str; const CGlyph* prevGlyph = nullptr; int prevWidth = 0; while (*chr != u'\0') { const CGlyph* glyph = GetGlyph(*chr); if (glyph) { if (opts.x0_direction == ETextDirection::Horizontal) { int advance = 0; if (prevGlyph) advance = KernLookup(x1c_kerning, prevGlyph->GetKernStart(), *chr); int curWidth = prevWidth + (glyph->GetLeftPadding() + glyph->GetAdvance() + glyph->GetRightPadding() + advance); int curHeight = glyph->GetBaseline() - (x8_monoHeight + glyph->GetCellHeight()); width = curWidth; prevWidth = curWidth; if (curHeight > height) height = curHeight; } } prevGlyph = glyph; chr++; if (len == -1) continue; if ((chr - str) >= len) break; } } bool CRasterFont::IsFinishedLoading() const { if (!x80_texture || !x80_texture.IsLoaded()) return false; return true; } void CRasterFont::SetupRenderState() { constexpr std::array skDescList = { GXVtxDescList{GX_VA_POS, GX_DIRECT}, GXVtxDescList{GX_VA_TEX0, GX_DIRECT}, GXVtxDescList{GX_VA_NULL, GX_NONE}, }; x80_texture->Load(GX_TEXMAP0, EClampMode::Clamp); CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_ZERO); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_ZERO); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); CGX::SetTevDirect(GX_TEVSTAGE0); CGX::SetVtxDescv(skDescList.data()); CGX::SetNumChans(0); CGX::SetNumTexGens(1); CGX::SetNumTevStages(1); CGX::SetNumIndStages(0); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, GX_FALSE, GX_PTIDENTITY); } std::unique_ptr FRasterFontFactory([[maybe_unused]] const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, [[maybe_unused]] CObjectReference* selfRef) { CSimplePool* sp = vparms.GetOwnedObj(); return TToken::GetIObjObjectFor(std::make_unique(in, *sp)); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CRasterFont.hpp ================================================ #pragma once #include #include #include #include "Runtime/CToken.hpp" #include "Runtime/GCNTypes.hpp" #include "Runtime/Streams/IOStreams.hpp" #include "Runtime/Graphics/CTexture.hpp" #include namespace metaforce { class CDrawStringOptions; class CTextRenderBuffer; class IObjectStore; enum class EColorType { Main, Outline, Geometry, Foreground, Background }; /* NOTE: Is this a good place for CGlyph and CKernPair? */ class CGlyph { private: s16 x0_leftPadding; s16 x2_advance; s16 x4_rightPadding; float x8_startU; float xc_startV; float x10_endU; float x14_endV; s16 x18_cellWidth; s16 x1a_cellHeight; s16 x1c_baseline; s16 x1e_kernStart; s16 m_layer; public: CGlyph() = default; CGlyph(s16 leftPadding, s16 advance, s32 rightPadding, float startU, float startV, float endU, float endV, s16 cellWidth, s16 cellHeight, s16 baseline, s16 kernStart, s16 layer = 0) : x0_leftPadding(leftPadding) , x2_advance(advance) , x4_rightPadding(rightPadding) , x8_startU(startU) , xc_startV(startV) , x10_endU(endU) , x14_endV(endV) , x18_cellWidth(cellWidth) , x1a_cellHeight(cellHeight) , x1c_baseline(baseline) , x1e_kernStart(kernStart) , m_layer(layer) {} s16 GetLeftPadding() const { return x0_leftPadding; } s16 GetAdvance() const { return x2_advance; } s16 GetRightPadding() const { return x4_rightPadding; } float GetStartU() const { return x8_startU; } float GetStartV() const { return xc_startV; } float GetEndU() const { return x10_endU; } float GetEndV() const { return x14_endV; } s16 GetCellWidth() const { return x18_cellWidth; } s16 GetCellHeight() const { return x1a_cellHeight; } s16 GetBaseline() const { return x1c_baseline; } s16 GetKernStart() const { return x1e_kernStart; } s16 GetLayer() const { return m_layer; } }; class CKernPair { private: char16_t x0_first; char16_t x2_second; s32 x4_howMuch; public: CKernPair() = default; CKernPair(char16_t first, char16_t second, s32 howMuch) : x0_first(first), x2_second(second), x4_howMuch(howMuch) {} char16_t GetFirst() const { return x0_first; } char16_t GetSecond() const { return x2_second; } s32 GetHowMuch() const { return x4_howMuch; } }; class CFontInfo { bool x0_ = false; bool x1_ = false; s32 x4_ = 0; s32 x8_fontSize = 0; char xc_name[64] = ""; public: CFontInfo() = default; CFontInfo(bool a, bool b, s32 c, s32 fontSize, const char* name) : x0_(a), x1_(b), x4_(c), x8_fontSize(fontSize) { std::strcpy(xc_name, name); } }; class CRasterFont { bool x0_initialized = false; s32 x4_monoWidth = 16; s32 x8_monoHeight = 16; std::vector> xc_glyphs; std::vector x1c_kerning; CTexture::EFontType x2c_mode = CTexture::EFontType::OneLayer; CFontInfo x30_fontInfo; TLockedToken x80_texture; s32 x8c_baseline; s32 x90_lineMargin = 0; const CGlyph* InternalGetGlyph(char16_t chr) const; public: CRasterFont(CInputStream& in, IObjectStore& store); s32 GetMonoWidth() const { return x4_monoWidth; } s32 GetMonoHeight() const { return x8_monoHeight; } EColorType GetMode() const { switch (x2c_mode) { case CTexture::EFontType::OneLayer: case CTexture::EFontType::TwoLayers: case CTexture::EFontType::FourLayers: return EColorType::Main; case CTexture::EFontType::OneLayerOutline: case CTexture::EFontType::TwoLayersOutlines: case CTexture::EFontType::TwoLayersOutlines2: return EColorType::Outline; default: return EColorType::Main; } } s32 GetLineMargin() const { return x90_lineMargin; } s32 GetCarriageAdvance() const { return GetLineMargin() + GetMonoHeight(); } s32 GetBaseline() const { return x8c_baseline; } static s32 KernLookup(const std::vector& kernTable, s32 kernStart, char16_t chr) { auto iter = kernTable.cbegin() + kernStart; for (; iter != kernTable.cend() && iter->GetFirst() == kernTable[kernStart].GetFirst(); ++iter) { if (iter->GetSecond() == chr) return iter->GetHowMuch(); } return 0; } void SinglePassDrawString(const CDrawStringOptions&, int x, int y, int& xout, int& yout, CTextRenderBuffer* renderBuf, const char16_t* str, s32 len) const; void DrawSpace(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, int len) const; void DrawString(const CDrawStringOptions& opts, int x, int y, int& xout, int& yout, CTextRenderBuffer* renderBuf, const char16_t* str, int len) const; const CGlyph* GetGlyph(char16_t chr) const { return InternalGetGlyph(chr); } void GetSize(const CDrawStringOptions& opts, int& width, int& height, const char16_t* str, int len) const; CTexture& GetTexture() { return *x80_texture; } bool IsFinishedLoading() const; void SetupRenderState(); }; std::unique_ptr FRasterFontFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CSaveableState.cpp ================================================ #include "Runtime/GuiSys/CSaveableState.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" namespace metaforce { bool CSaveableState::IsFinishedLoading() const { if (!x48_font || !x48_font.IsLoaded()) return false; return x48_font->IsFinishedLoading(); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CSaveableState.hpp ================================================ #pragma once #include #include "Runtime/CToken.hpp" #include "Runtime/GCNTypes.hpp" #include "Runtime/GuiSys/CDrawStringOptions.hpp" #include "Runtime/GuiSys/CGuiTextSupport.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" #include namespace metaforce { class CSaveableState { friend class CColorOverrideInstruction; friend class CFontInstruction; friend class CGuiTextSupport; friend class CLineExtraSpaceInstruction; friend class CLineSpacingInstruction; friend class CRemoveColorOverrideInstruction; friend class CTextExecuteBuffer; friend class CTextInstruction; friend class CWordInstruction; protected: CDrawStringOptions x0_drawStrOpts; TLockedToken x48_font; std::vector x54_colors; std::vector x64_colorOverrides; float x74_lineSpacing = 1.f; s32 x78_extraLineSpace = 0; bool x7c_enableWordWrap = false; EJustification x80_just = EJustification::Left; EVerticalJustification x84_vjust = EVerticalJustification::Top; public: CSaveableState() : x54_colors(3, zeus::skBlack), x64_colorOverrides(16) {} const TLockedToken& GetFont() const { return x48_font; } bool IsFinishedLoading() const; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CScanDisplay.cpp ================================================ #include "Runtime/GuiSys/CScanDisplay.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Audio/CSfxManager.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/GuiSys/CAuiImagePane.hpp" #include "Runtime/GuiSys/CGuiCamera.hpp" #include "Runtime/GuiSys/CGuiModel.hpp" #include "Runtime/GuiSys/CGuiTextPane.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/Input/CFinalInput.hpp" #include "Runtime/MP1/CPauseScreenBase.hpp" #include namespace metaforce { void CScanDisplay::CDataDot::Update(float dt) { if (x20_remTime > 0.f) { x20_remTime = std::max(0.f, x20_remTime - dt); float d = 0.f; if (x1c_transDur > 0.f) d = x20_remTime / x1c_transDur; xc_curPos = zeus::CVector2f::lerp(x14_targetPos, x4_startPos, d); } if (x24_alpha > x28_desiredAlpha) { float tmp = x24_alpha - 2.f * dt; x24_alpha = std::max(tmp, x28_desiredAlpha); } else if (x24_alpha < x28_desiredAlpha) { float tmp = 2.f * dt + x24_alpha; x24_alpha = std::min(tmp, x28_desiredAlpha); } } void CScanDisplay::CDataDot::Draw(const zeus::CColor& col, float radius) { if (x24_alpha == 0.f || x0_dotState == EDotState::Hidden) { return; } const zeus::CTransform xf = zeus::CTransform::Translate(xc_curPos.x(), 0.f, xc_curPos.y()); g_Renderer->SetModelMatrix(xf); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); zeus::CColor useColor = col; useColor.a() *= x24_alpha; CGraphics::StreamColor(useColor); CGraphics::StreamTexcoord(0.f, 1.f); CGraphics::StreamVertex(-radius, 0.f, radius); CGraphics::StreamTexcoord(0.f, 0.f); CGraphics::StreamVertex(-radius, 0.f, -radius); CGraphics::StreamTexcoord(1.f, 1.f); CGraphics::StreamVertex(radius, 0.f, radius); CGraphics::StreamTexcoord(1.f, 0.f); CGraphics::StreamVertex(radius, 0.f, -radius); CGraphics::StreamEnd(); } void CScanDisplay::CDataDot::StartTransitionTo(const zeus::CVector2f& vec, float dur) { x20_remTime = dur; x1c_transDur = dur; x4_startPos = xc_curPos; x14_targetPos = vec; } void CScanDisplay::CDataDot::SetDestPosition(const zeus::CVector2f& pos) { if (x20_remTime <= 0.f) xc_curPos = pos; else x14_targetPos = pos; } CScanDisplay::CScanDisplay(const CGuiFrame& selHud) : xa0_selHud(selHud) { x0_dataDot = g_SimplePool->GetObj("TXTR_DataDot"); for (size_t i = 0; i < xbc_dataDots.capacity(); ++i) { xbc_dataDots.emplace_back(x0_dataDot); } x170_paneStates.resize(x170_paneStates.capacity()); } void CScanDisplay::ProcessInput(const CFinalInput& input) { if (xc_state == EScanState::Inactive || xc_state == EScanState::Done) return; if (xc_state == EScanState::DownloadComplete && x1a4_xAlpha == 0.f) { if (input.PA() || input.PSpecialKey(ESpecialKey::Enter) || input.PMouseButton(EMouseButton::Primary)) { if (xa8_message->TextSupport().GetCurTime() < xa8_message->TextSupport().GetTotalAnimationTime()) { xa8_message->TextSupport().SetCurTime(xa8_message->TextSupport().GetTotalAnimationTime()); } else { xc_state = EScanState::ViewingScan; x1a4_xAlpha = 1.f; CSfxManager::SfxStart(SFXui_scan_next_page, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } } } else if (xc_state == EScanState::ViewingScan) { int oldCounter = x1ac_pageCounter; int totalPages = xac_scrollMessage->TextSupport().GetTotalPageCount(); if ((input.PA() || input.PSpecialKey(ESpecialKey::Enter) || input.PMouseButton(EMouseButton::Primary)) && totalPages != -1) { CGuiTextSupport& supp = !x1ac_pageCounter ? xa8_message->TextSupport() : xac_scrollMessage->TextSupport(); if (supp.GetCurTime() < supp.GetTotalAnimationTime()) supp.SetCurTime(supp.GetTotalAnimationTime()); else x1ac_pageCounter = std::min(totalPages, x1ac_pageCounter + 1); } if (x1ac_pageCounter != oldCounter) { CSfxManager::SfxStart(SFXui_scan_next_page, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); if (x1ac_pageCounter == 0) { xa8_message->SetIsVisible(true); xac_scrollMessage->SetIsVisible(false); } else { if (oldCounter == 0) { xa8_message->SetIsVisible(false); xac_scrollMessage->SetIsVisible(true); } xac_scrollMessage->TextSupport().SetPage(x1ac_pageCounter - 1); SetScanMessageTypeEffect(xac_scrollMessage, !x1b4_scanComplete); } } } float xAlpha = 0.f; float aAlpha = 0.f; float dashAlpha = 0.f; if (xc_state == EScanState::DownloadComplete) { xAlpha = std::min(2.f * x1a4_xAlpha, 1.f); aAlpha = (1.f - xAlpha) * std::fabs(x1b0_aPulse); } else if (xc_state == EScanState::ViewingScan) { if (x1ac_pageCounter < xac_scrollMessage->TextSupport().GetTotalPageCount()) aAlpha = std::fabs(x1b0_aPulse); else dashAlpha = 1.f; } xb0_xmark->SetVisibility(xAlpha > 0.f, ETraversalMode::Children); xb4_abutton->SetVisibility(aAlpha > 0.f, ETraversalMode::Children); xb8_dash->SetVisibility(dashAlpha > 0.f, ETraversalMode::Children); xb0_xmark->SetColor(zeus::CColor(1.f, xAlpha)); xb4_abutton->SetColor(zeus::CColor(1.f, aAlpha)); xb8_dash->SetColor(zeus::CColor(0.53f, 0.84f, 1.f, dashAlpha)); } float CScanDisplay::GetDownloadStartTime(size_t idx) const { if (!x14_scannableInfo) { return 0.f; } const float nTime = x14_scannableInfo->GetBucket(idx).x4_appearanceRange; float maxTime = 0.f; for (size_t i = 0; i < CScannableObjectInfo::NumBuckets; ++i) { const float iTime = x14_scannableInfo->GetBucket(i).x4_appearanceRange; if (iTime < nTime) { maxTime = std::max(iTime, maxTime); } } return maxTime + g_tweakGui->GetScanAppearanceDuration(); } float CScanDisplay::GetDownloadFraction(size_t idx, float scanningTime) const { if (!x14_scannableInfo) { return 0.f; } const float appearTime = GetDownloadStartTime(idx); const float appearRange = x14_scannableInfo->GetBucket(idx).x4_appearanceRange; if (appearTime == appearRange) { return 1.f; } return zeus::clamp(0.f, (scanningTime - appearTime) / (appearRange - appearTime), 1.f); } void CScanDisplay::StartScan(TUniqueId id, const CScannableObjectInfo& scanInfo, CGuiTextPane* message, CGuiTextPane* scrollMessage, CGuiWidget* textGroup, CGuiModel* xmark, CGuiModel* abutton, CGuiModel* dash, float scanTime) { x1b4_scanComplete = scanTime >= scanInfo.GetTotalDownloadTime(); x14_scannableInfo.emplace(scanInfo); x10_objId = id; xc_state = EScanState::Downloading; x1ac_pageCounter = 0; x1a4_xAlpha = 0.f; xa8_message = message; xac_scrollMessage = scrollMessage; xa4_textGroup = textGroup; xb0_xmark = xmark; xb4_abutton = abutton; xb8_dash = dash; xa4_textGroup->SetVisibility(true, ETraversalMode::Children); xa4_textGroup->SetColor(zeus::CColor(1.f, 0.f)); xa8_message->TextSupport().SetText(u""); xac_scrollMessage->TextSupport().SetText(u""); for (size_t i = 0; i < 20; ++i) { auto* pane = static_cast(xa0_selHud.FindWidget(MP1::CPauseScreenBase::GetImagePaneName(i))); zeus::CColor color = g_tweakGuiColors->GetScanDisplayImagePaneColor(); color.a() = 0.f; pane->SetColor(color); pane->SetTextureID0({}, g_SimplePool); pane->SetAnimationParms(zeus::skZero2f, 0.f, 0.f); size_t pos = SIZE_MAX; for (size_t j = 0; j < CScannableObjectInfo::NumBuckets; ++j) { if (x14_scannableInfo->GetBucket(j).x8_imagePos == i) { pos = j; break; } } if (pos != SIZE_MAX) { x170_paneStates[pos].second = pane; } } for (size_t i = 0; i < x170_paneStates.size(); ++i) { std::pair& state = x170_paneStates[i]; if (state.second) { const CScannableObjectInfo::SBucket& bucket = x14_scannableInfo->GetBucket(i); if (bucket.x14_interval > 0.f) { state.second->SetAnimationParms(zeus::CVector2f(bucket.xc_size.x, bucket.xc_size.y), bucket.x14_interval, bucket.x18_fadeDuration); } state.second->SetTextureID0(bucket.x0_texture, g_SimplePool); state.second->SetFlashFactor(0.f); const float startTime = GetDownloadStartTime(i); if (scanTime >= startTime) { x170_paneStates[i].first = 0.f; } else { x170_paneStates[i].first = -1.f; } } } const CAssetId strId = x14_scannableInfo->GetStringTableId(); if (strId.IsValid()) { x194_scanStr = g_SimplePool->GetObj({FOURCC('STRG'), strId}); } for (size_t i = 0; i < CScannableObjectInfo::NumBuckets; ++i) { const u32 pos = x14_scannableInfo->GetBucket(i).x8_imagePos; CDataDot& dot = xbc_dataDots[i]; if (pos != UINT32_MAX) { if (GetDownloadStartTime(i) - g_tweakGui->GetScanAppearanceDuration() < scanTime) { dot.SetAlpha(0.f); dot.SetDotState(CDataDot::EDotState::Done); } else { dot.SetDesiredAlpha(1.f); dot.SetDotState(CDataDot::EDotState::Seek); dot.StartTransitionTo(zeus::skZero2f, FLT_EPSILON); dot.Update(FLT_EPSILON); } } else { dot.SetDotState(CDataDot::EDotState::Hidden); } } } void CScanDisplay::StopScan() { if (xc_state == EScanState::Done || xc_state == EScanState::Inactive) { return; } xc_state = EScanState::Done; for (auto& dataDot : xbc_dataDots) { dataDot.SetDesiredAlpha(0.f); } } void CScanDisplay::SetScanMessageTypeEffect(CGuiTextPane* pane, bool type) { if (type) pane->TextSupport().SetTypeWriteEffectOptions(true, 0.1f, 60.f); else pane->TextSupport().SetTypeWriteEffectOptions(false, 0.f, 0.f); } void CScanDisplay::Update(float dt, float scanningTime) { if (xc_state == EScanState::Inactive) return; bool active = false; if (xc_state == EScanState::Done) { x1a8_bodyAlpha = std::max(0.f, x1a8_bodyAlpha - 2.f * dt); if (x1a8_bodyAlpha > 0.f) active = true; } else { active = true; x1a8_bodyAlpha = std::min(x1a8_bodyAlpha + 2.f * dt, 1.f); if (xc_state == EScanState::DownloadComplete) { if (xac_scrollMessage->TextSupport().GetIsTextSupportFinishedLoading()) x1a4_xAlpha = std::max(0.f, x1a4_xAlpha - dt); if (x1a4_xAlpha < 0.5f) { x1b0_aPulse += 2.f * dt; if (x1b0_aPulse > 1.f) x1b0_aPulse -= 2.f; } } else if (xc_state == EScanState::ViewingScan) { x1b0_aPulse += 2.f * dt; if (x1b0_aPulse > 1.f) x1b0_aPulse -= 2.f; if (x1a4_xAlpha == 1.f) { xa8_message->TextSupport().SetText(x194_scanStr->GetString(0)); SetScanMessageTypeEffect(xa8_message, !x1b4_scanComplete); } } else if (xc_state == EScanState::Downloading && scanningTime >= x14_scannableInfo->GetTotalDownloadTime() && x194_scanStr.IsLoaded()) { if (x1b4_scanComplete || x14_scannableInfo->GetCategory() == 0) { xc_state = EScanState::ViewingScan; x1b0_aPulse = x1a4_xAlpha = 1.f; CSfxManager::SfxStart(SFXui_scan_complete, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else { xc_state = EScanState::DownloadComplete; x1b0_aPulse = x1a4_xAlpha = 1.f; xa8_message->TextSupport().SetText(std::u16string(g_MainStringTable->GetString(29)) + g_MainStringTable->GetString(x14_scannableInfo->GetCategory() + 30) + g_MainStringTable->GetString(30)); SetScanMessageTypeEffect(xa8_message, true); CSfxManager::SfxStart(SFXui_new_scan_complete, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } if (x194_scanStr->GetStringCount() > 2) { xac_scrollMessage->TextSupport().SetText(x194_scanStr->GetString(2), true); SetScanMessageTypeEffect(xac_scrollMessage, !x1b4_scanComplete); } xac_scrollMessage->SetIsVisible(false); } } for (size_t i = 0; i < x170_paneStates.size(); ++i) { if (x170_paneStates[i].second == nullptr) { continue; } if (x170_paneStates[i].first > 0.f) { x170_paneStates[i].first = std::max(0.f, x170_paneStates[i].first - dt); float tmp; if (x170_paneStates[i].first > g_tweakGui->GetScanPaneFadeOutTime()) { tmp = 1.f - (x170_paneStates[i].first - g_tweakGui->GetScanPaneFadeOutTime()) / g_tweakGui->GetScanPaneFadeInTime(); } else { tmp = x170_paneStates[i].first / g_tweakGui->GetScanPaneFadeOutTime(); } x170_paneStates[i].second->SetFlashFactor(tmp * g_tweakGui->GetScanPaneFlashFactor() * x1a8_bodyAlpha); } const float alphaMul = ((xc_state == EScanState::Downloading) ? GetDownloadFraction(i, scanningTime) : 1.f) * x1a8_bodyAlpha; zeus::CColor color = g_tweakGuiColors->GetScanDisplayImagePaneColor(); color.a() *= alphaMul; x170_paneStates[i].second->SetColor(color); x170_paneStates[i].second->SetDeResFactor(1.f - alphaMul); if (GetDownloadStartTime(i) - g_tweakGui->GetScanAppearanceDuration() < scanningTime) { CDataDot& dot = xbc_dataDots[i]; switch (dot.GetDotState()) { case CDataDot::EDotState::Seek: case CDataDot::EDotState::Hold: dot.SetDotState(CDataDot::EDotState::RevealPane); dot.StartTransitionTo(zeus::skZero2f, g_tweakGui->GetScanAppearanceDuration()); break; case CDataDot::EDotState::RevealPane: { const float tmp = dot.GetTransitionFactor(); if (tmp == 0.f) { dot.SetDotState(CDataDot::EDotState::Done); dot.SetDesiredAlpha(0.f); CSfxManager::SfxStart(SFXui_scan_pane_reveal, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x170_paneStates[i].first = g_tweakGui->GetScanPaneFadeOutTime() + g_tweakGui->GetScanPaneFadeInTime(); } break; } default: break; } } } for (size_t i = 0; i < xbc_dataDots.size(); ++i) { CDataDot& dot = xbc_dataDots[i]; switch (dot.GetDotState()) { case CDataDot::EDotState::Hidden: continue; case CDataDot::EDotState::Seek: case CDataDot::EDotState::Hold: { float tmp = dot.GetTransitionFactor(); if (tmp == 0.f) { float vpRatio = CGraphics::GetViewportHeight() / 480.f; float posRand = g_tweakGui->GetScanDataDotPosRandMagnitude() * vpRatio; float durMin = dot.GetDotState() == CDataDot::EDotState::Hold ? g_tweakGui->GetScanDataDotHoldDurationMin() : g_tweakGui->GetScanDataDotSeekDurationMin(); float durMax = dot.GetDotState() == CDataDot::EDotState::Hold ? g_tweakGui->GetScanDataDotHoldDurationMax() : g_tweakGui->GetScanDataDotSeekDurationMax(); zeus::CVector2f vec( dot.GetDotState() == CDataDot::EDotState::Hold ? dot.GetCurrPosition().x() : (posRand * (rand() / float(RAND_MAX)) - 0.5f * posRand), dot.GetDotState() == CDataDot::EDotState::Hold ? dot.GetCurrPosition().y() : (posRand * (rand() / float(RAND_MAX)) - 0.5f * posRand)); float dur = (durMax - durMin) * (rand() / float(RAND_MAX)) + durMin; dot.StartTransitionTo(vec, dur); dot.SetDotState(dot.GetDotState() == CDataDot::EDotState::Hold ? CDataDot::EDotState::Seek : CDataDot::EDotState::Hold); } break; } case CDataDot::EDotState::RevealPane: case CDataDot::EDotState::Done: { const zeus::CVector3f& panePos = x170_paneStates[i].second->GetWorldPosition(); zeus::CVector3f screenPos = xa0_selHud.GetFrameCamera()->ConvertToScreenSpace(panePos); zeus::CVector2f viewportCoords(screenPos.x() * CGraphics::GetViewportWidth() * 0.5f, screenPos.y() * CGraphics::GetViewportHeight() * 0.5f); dot.SetDestPosition(viewportCoords); break; } default: break; } dot.Update(dt); } if (!active) { xc_state = EScanState::Inactive; x10_objId = kInvalidUniqueId; x14_scannableInfo = std::nullopt; xa8_message->TextSupport().SetText(u""); xac_scrollMessage->TextSupport().SetText(u""); xa4_textGroup->SetVisibility(false, ETraversalMode::Children); xb0_xmark->SetVisibility(false, ETraversalMode::Children); xb4_abutton->SetVisibility(false, ETraversalMode::Children); xb8_dash->SetVisibility(false, ETraversalMode::Children); xa8_message = nullptr; xac_scrollMessage = nullptr; xa4_textGroup = nullptr; xb0_xmark = nullptr; xb4_abutton = nullptr; xb8_dash = nullptr; x170_paneStates.clear(); x170_paneStates.resize(4); x194_scanStr = TLockedToken(); x1ac_pageCounter = 0; x1b4_scanComplete = false; } else { xa4_textGroup->SetColor(zeus::CColor(1.f, x1a8_bodyAlpha)); } } void CScanDisplay::Draw() { if (!x0_dataDot.IsLoaded()) { return; } SCOPED_GRAPHICS_DEBUG_GROUP("CScanDisplay::Draw", zeus::skGreen); g_Renderer->SetDepthReadWrite(false, false); g_Renderer->SetViewportOrtho(true, -4096.f, 4096.f); g_Renderer->SetBlendMode_AdditiveAlpha(); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); x0_dataDot->Load(GX_TEXMAP0, EClampMode::Repeat); const float vpRatio = CGraphics::GetViewportHeight() / 480.f; for (CDataDot& dot : xbc_dataDots) { dot.Draw(g_tweakGuiColors->GetScanDataDotColor(), g_tweakGui->GetScanDataDotRadius() * vpRatio); } } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CScanDisplay.hpp ================================================ #pragma once #include #include #include "Runtime/CScannableObjectInfo.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Camera/CCameraFilter.hpp" #include "Runtime/Graphics/CTexture.hpp" #include #include #include namespace metaforce { class CAuiImagePane; class CGuiFrame; class CGuiModel; class CGuiTextPane; class CGuiWidget; class CStringTable; struct CFinalInput; class CScanDisplay { friend class CHudDecoInterfaceScan; public: class CDataDot { public: enum class EDotState { Hidden, Seek, Hold, RevealPane, Done }; private: EDotState x0_dotState = EDotState::Hidden; zeus::CVector2f x4_startPos; zeus::CVector2f xc_curPos; zeus::CVector2f x14_targetPos; float x1c_transDur = 0.f; float x20_remTime = 0.f; float x24_alpha = 0.f; float x28_desiredAlpha = 0.f; public: explicit CDataDot(const TLockedToken& dataDotTex) {} void Update(float dt); void Draw(const zeus::CColor& color, float radius); float GetTransitionFactor() const { return x1c_transDur > 0.f ? x20_remTime / x1c_transDur : 0.f; } void StartTransitionTo(const zeus::CVector2f&, float); void SetDestPosition(const zeus::CVector2f&); void SetDesiredAlpha(float a) { x28_desiredAlpha = a; } void SetDotState(EDotState s) { x0_dotState = s; } void SetAlpha(float a) { x24_alpha = a; } const zeus::CVector2f& GetCurrPosition() const { return xc_curPos; } EDotState GetDotState() const { return x0_dotState; } }; enum class EScanState { Inactive, Downloading, DownloadComplete, ViewingScan, Done }; private: TLockedToken x0_dataDot; EScanState xc_state = EScanState::Inactive; TUniqueId x10_objId = kInvalidUniqueId; std::optional x14_scannableInfo; const CGuiFrame& xa0_selHud; CGuiWidget* xa4_textGroup = nullptr; CGuiTextPane* xa8_message = nullptr; CGuiTextPane* xac_scrollMessage = nullptr; CGuiModel* xb0_xmark = nullptr; CGuiModel* xb4_abutton = nullptr; CGuiModel* xb8_dash = nullptr; rstl::reserved_vector xbc_dataDots; rstl::reserved_vector, 4> x170_paneStates; TLockedToken x194_scanStr; // Used to be optional float x1a4_xAlpha = 0.f; float x1a8_bodyAlpha = 0.f; int x1ac_pageCounter = 0; float x1b0_aPulse = 1.f; bool x1b4_scanComplete = false; float GetDownloadStartTime(size_t idx) const; float GetDownloadFraction(size_t idx, float scanningTime) const; static void SetScanMessageTypeEffect(CGuiTextPane* pane, bool type); public: explicit CScanDisplay(const CGuiFrame& selHud); void ProcessInput(const CFinalInput& input); void StartScan(TUniqueId id, const CScannableObjectInfo& scanInfo, CGuiTextPane* message, CGuiTextPane* scrollMessage, CGuiWidget* textGroup, CGuiModel* xmark, CGuiModel* abutton, CGuiModel* dash, float scanTime); void StopScan(); void Update(float dt, float scanningTime); void Draw(); EScanState GetScanState() const { return xc_state; } TUniqueId ScanTarget() const { return x10_objId; } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CSplashScreen.cpp ================================================ #include "Runtime/GuiSys/CSplashScreen.hpp" #include "CArchitectureMessage.hpp" #include "CArchitectureQueue.hpp" #include "CSimplePool.hpp" #include "GameGlobalObjects.hpp" #include "Graphics/CCubeRenderer.hpp" namespace metaforce { constexpr std::array SplashTextures{"TXTR_NintendoLogo"sv, "TXTR_RetroLogo"sv, "TXTR_DolbyLogo"sv}; CSplashScreen::CSplashScreen(ESplashScreen which) : CIOWin("SplashScreen"), x14_which(which), x28_texture(g_SimplePool->GetObj(SplashTextures[size_t(which)])) {} CIOWin::EMessageReturn CSplashScreen::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) { switch (msg.GetType()) { case EArchMsgType::TimerTick: { if (!x25_textureLoaded) { if (!x28_texture.IsLoaded()) return EMessageReturn::Exit; x25_textureLoaded = true; } float dt = MakeMsg::GetParmTimerTick(msg).x4_parm; x18_splashTimeout -= dt; if (x18_splashTimeout <= 0.f) { /* HACK: If we're not compiling with Intel's IPP library we want to skip the Dolby Pro Logic II logo * This is purely a URDE addition and does not reflect retro's intentions. - Phil */ #if INTEL_IPP if (x14_which != ESplashScreen::Dolby) #else if (x14_which != ESplashScreen::Retro) #endif queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 9999, 9999, std::make_shared(ESplashScreen(int(x14_which) + 1)))); return EMessageReturn::RemoveIOWinAndExit; } break; } default: break; } return EMessageReturn::Exit; } void CSplashScreen::Draw() { if (!x25_textureLoaded) { return; } SCOPED_GRAPHICS_DEBUG_GROUP("CSplashScreen::Draw", zeus::skGreen); zeus::CColor color = zeus::skWhite; if (x14_which == ESplashScreen::Nintendo) { color = zeus::CColor{0.86f, 0.f, 0.f, 1.f}; } if (x18_splashTimeout > 1.5f) { color.a() = 1.f - (x18_splashTimeout - 1.5f) * 2.f; } else if (x18_splashTimeout < 0.5f) { color.a() = x18_splashTimeout * 2.f; } CGraphics::SetAlphaCompare(ERglAlphaFunc::Always, 0, ERglAlphaOp::And, ERglAlphaFunc::Always, 0); g_Renderer->SetModelMatrix({}); CGraphics::SetViewPointMatrix({}); CGraphics::SetTevOp(ERglTevStage::Stage0, CTevCombiners::kEnvModulate); CGraphics::SetTevOp(ERglTevStage::Stage1, CTevCombiners::kEnvPassthru); g_Renderer->SetBlendMode_AlphaBlended(); auto& tex = *x28_texture.GetObj(); const auto width = tex.GetWidth(); const auto height = tex.GetHeight(); tex.Load(GX_TEXMAP0, EClampMode::Clamp); if (x14_which == ESplashScreen::Nintendo || x14_which == ESplashScreen::Retro) { const auto x = static_cast(133 - (width - 376) / 2); const auto y = static_cast(170 - (height - 104) / 2); CGraphics::SetOrtho(-10.f, 650.f, -5.5f, 484.5f, -1.f, 1.f); CGraphics::SetCullMode(ERglCullMode::None); CGraphics::StreamBegin(ERglPrimitive::TriangleStrip); CGraphics::StreamColor(color); CGraphics::StreamTexcoord(0.f, 0.f); CGraphics::StreamVertex({x, 0.f, y + static_cast(height)}); CGraphics::StreamTexcoord(0.f, 1.f); CGraphics::StreamVertex({x, 0.f, y}); CGraphics::StreamTexcoord(1.f, 0.f); CGraphics::StreamVertex({x + static_cast(width), 0.f, y + static_cast(height)}); CGraphics::StreamTexcoord(1.f, 1.f); CGraphics::StreamVertex({x + static_cast(width), 0.f, y}); CGraphics::StreamEnd(); CGraphics::SetCullMode(ERglCullMode::Front); } else { // TODO originally uses CGraphics viewport, but Render2D needs scaling fix CGraphics::Render2D(tex, 0, 0, 640, 448, color, true); } // Progressive scan options omitted } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CSplashScreen.hpp ================================================ #pragma once #include "Runtime/CIOWin.hpp" #include "Runtime/CToken.hpp" #include "Runtime/Graphics/CTexture.hpp" namespace metaforce { class CSplashScreen : public CIOWin { public: enum class ESplashScreen { Nintendo, Retro, Dolby }; enum class EProgressivePhase { Before, During, After }; private: ESplashScreen x14_which; float x18_splashTimeout = 2.f; // float x1c_progSelectionTimeout = 0.f; // EProgressivePhase x20_progressivePhase = EProgressivePhase::Before; // bool x24_progressiveSelection = true; bool x25_textureLoaded = false; TLockedToken x28_texture; public: explicit CSplashScreen(ESplashScreen); EMessageReturn OnMessage(const CArchitectureMessage&, CArchitectureQueue&) override; void Draw() override; }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CStringTable.cpp ================================================ #include "Runtime/GuiSys/CStringTable.hpp" #include "Runtime/CBasics.hpp" #include "Runtime/Streams/CInputStream.hpp" #include "Runtime/CToken.hpp" #include namespace metaforce { namespace { constexpr std::array languages{ FOURCC('ENGL'), FOURCC('FREN'), FOURCC('GERM'), FOURCC('SPAN'), FOURCC('ITAL'), FOURCC('DUTC'), FOURCC('JAPN'), }; } // Anonymous namespace FourCC CStringTable::mCurrentLanguage = languages[0]; CStringTable::CStringTable(CInputStream& in) { LoadStringTable(in); } void CStringTable::LoadStringTable(CInputStream& in) { in.ReadLong(); in.ReadLong(); u32 langCount = in.ReadLong(); x0_stringCount = in.ReadLong(); std::vector> langOffsets; for (u32 i = 0; i < langCount; ++i) { FourCC fcc; in.Get(reinterpret_cast(&fcc), 4); u32 off = in.ReadLong(); langOffsets.emplace_back(fcc, off); } u32 lang = 0; u32 offset = 0; while ((langCount--) > 0) { if (langOffsets[lang].first == mCurrentLanguage) { offset = langOffsets[lang].second; break; } lang++; } /* * If we fail to get a language, default to the first in the list * This way we always display _something_ */ if (offset == UINT32_MAX) offset = langOffsets[0].second; for (u32 i = 0; i < offset; ++i) { in.ReadChar(); } u32 dataLen = in.ReadLong(); m_bufLen = dataLen; x4_data.reset(new u8[dataLen]); in.Get(x4_data.get(), dataLen); #if METAFORCE_TARGET_BYTE_ORDER == __ORDER_LITTLE_ENDIAN__ u32* off = reinterpret_cast(x4_data.get()); for (u32 i = 0; i < x0_stringCount; ++i, ++off) { *off = CBasics::SwapBytes(*off); } for (u32 i = x0_stringCount * 4; i < dataLen; i += 2) { u16* chr = reinterpret_cast(x4_data.get() + i); *chr = CBasics::SwapBytes(*chr); } #endif } const char16_t* CStringTable::GetString(s32 str) const { if (str < 0 || u32(str) >= x0_stringCount) return u"Invalid"; u32 off = *reinterpret_cast(x4_data.get() + str * 4); return reinterpret_cast(x4_data.get() + off); } void CStringTable::SetLanguage(s32 lang) { mCurrentLanguage = languages[lang]; } CFactoryFnReturn FStringTableFactory(const SObjectTag&, CInputStream& in, const CVParamTransfer&, [[maybe_unused]] CObjectReference* selfRef) { return TToken::GetIObjObjectFor(std::make_unique(in)); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CStringTable.hpp ================================================ #pragma once #include #include "Runtime/IFactory.hpp" #include "Runtime/RetroTypes.hpp" namespace metaforce { class CStringTable { static FourCC mCurrentLanguage; u32 x0_stringCount = 0; std::unique_ptr x4_data; u32 m_bufLen = 0; public: explicit CStringTable(CInputStream& in); void LoadStringTable(CInputStream& in); const char16_t* GetString(s32) const; u32 GetStringCount() const { return x0_stringCount; } static void SetLanguage(s32); }; CFactoryFnReturn FStringTableFactory(const SObjectTag&, CInputStream&, const CVParamTransfer&, CObjectReference* selfRef); } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTargetingManager.cpp ================================================ #include "Runtime/GuiSys/CTargetingManager.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" namespace metaforce { CTargetingManager::CTargetingManager(const CStateManager& mgr) : x0_targetReticule(mgr) {} bool CTargetingManager::CheckLoadComplete() const { return x0_targetReticule.CheckLoadComplete() && x21c_orbitPointMarker.CheckLoadComplete(); } void CTargetingManager::Update(float dt, const CStateManager& stateMgr) { x0_targetReticule.Update(dt, stateMgr); x21c_orbitPointMarker.Update(dt, stateMgr); } void CTargetingManager::Draw(const CStateManager& mgr, bool hideLockon) { CGraphics::SetAmbientColor(zeus::skWhite); CGraphics::DisableAllLights(); x21c_orbitPointMarker.Draw(mgr); const CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr); zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr); CGraphics::SetViewPointMatrix(camXf); zeus::CFrustum frustum; frustum.updatePlanes(camXf, zeus::SProjPersp(zeus::degToRad(curCam->GetFov()), CGraphics::GetViewportAspect(), 1.f, 100.f)); g_Renderer->SetClippingPlanes(frustum); g_Renderer->SetPerspective(curCam->GetFov(), CGraphics::GetViewportWidth(), CGraphics::GetViewportHeight(), curCam->GetNearClipDistance(), curCam->GetFarClipDistance()); x0_targetReticule.Draw(mgr, hideLockon); } void CTargetingManager::Touch() { x0_targetReticule.Touch(); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTargetingManager.hpp ================================================ #pragma once #include "Runtime/GuiSys/CCompoundTargetReticle.hpp" #include "Runtime/GuiSys/COrbitPointMarker.hpp" namespace metaforce { class CStateManager; class CTargetingManager { CCompoundTargetReticle x0_targetReticule; COrbitPointMarker x21c_orbitPointMarker; public: explicit CTargetingManager(const CStateManager& stateMgr); bool CheckLoadComplete() const; void Update(float, const CStateManager& stateMgr); void Draw(const CStateManager& stateMgr, bool hideLockon); void Touch(); CCompoundTargetReticle& CompoundTargetReticle() { return x0_targetReticule; } }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTextExecuteBuffer.cpp ================================================ #include "Runtime/GuiSys/CTextExecuteBuffer.hpp" #include "Runtime/Graphics/CGraphicsPalette.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/GuiSys/CFontImageDef.hpp" #include "Runtime/GuiSys/CFontRenderState.hpp" #include "Runtime/GuiSys/CInstruction.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" #include "Runtime/GuiSys/CTextRenderBuffer.hpp" #include "Runtime/GuiSys/CWordBreakTables.hpp" namespace metaforce { CTextRenderBuffer CTextExecuteBuffer::BuildRenderBuffer(CGuiWidget::EGuiModelDrawFlags df) const { CTextRenderBuffer ret(CTextRenderBuffer::EMode::AllocTally);//, df); { CFontRenderState rendState; for (const std::shared_ptr& inst : x0_instList) inst->Invoke(rendState, &ret); } ret.SetMode(CTextRenderBuffer::EMode::BufferFill); { CFontRenderState rendState; for (const std::shared_ptr& inst : x0_instList) inst->Invoke(rendState, &ret); } return ret; } CTextRenderBuffer CTextExecuteBuffer::BuildRenderBufferPage(InstList::const_iterator start, InstList::const_iterator pgStart, InstList::const_iterator pgEnd, CGuiWidget::EGuiModelDrawFlags df) const { CTextRenderBuffer ret(CTextRenderBuffer::EMode::AllocTally);//, df); { CFontRenderState rendState; for (auto it = start; it != pgStart; ++it) { const std::shared_ptr& inst = *it; inst->PageInvoke(rendState, &ret); } for (auto it = pgStart; it != pgEnd; ++it) { const std::shared_ptr& inst = *it; inst->Invoke(rendState, &ret); } } ret.SetMode(CTextRenderBuffer::EMode::BufferFill); { CFontRenderState rendState; for (auto it = start; it != pgStart; ++it) { const std::shared_ptr& inst = *it; inst->PageInvoke(rendState, &ret); } for (auto it = pgStart; it != pgEnd; ++it) { const std::shared_ptr& inst = *it; inst->Invoke(rendState, &ret); } } return ret; } std::list CTextExecuteBuffer::BuildRenderBufferPages(const zeus::CVector2i& extent, CGuiWidget::EGuiModelDrawFlags df) const { std::list ret; for (auto it = x0_instList.begin(); it != x0_instList.end();) { CTextRenderBuffer rbuf(CTextRenderBuffer::EMode::AllocTally);//, df); { CFontRenderState rstate; for (auto it2 = x0_instList.begin(); it2 != x0_instList.end(); ++it2) { const std::shared_ptr& inst2 = *it2; inst2->Invoke(rstate, &rbuf); } } rbuf.SetMode(CTextRenderBuffer::EMode::BufferFill); InstList::const_iterator pageEnd = it; { CFontRenderState rstate; bool seekingToPage = true; for (auto it2 = x0_instList.begin(); it2 != x0_instList.end(); ++it2) { const std::shared_ptr& inst2 = *it2; if (it2 == it) seekingToPage = false; if (seekingToPage) { inst2->PageInvoke(rstate, &rbuf); } else { inst2->Invoke(rstate, &rbuf); if (!rbuf.HasSpaceAvailable(zeus::CVector2i{}, extent)) break; ++pageEnd; } } } ret.push_back(BuildRenderBufferPage(x0_instList.cbegin(), it, pageEnd, df)); it = pageEnd; } return ret; } std::vector CTextExecuteBuffer::GetAssets() const { size_t totalAssets = 0; for (const std::shared_ptr& inst : x0_instList) totalAssets += inst->GetAssetCount(); std::vector ret; ret.reserve(totalAssets); for (const std::shared_ptr& inst : x0_instList) inst->GetAssets(ret); return ret; } void CTextExecuteBuffer::AddString(const char16_t* str, int count) { if (!xa4_curLine) StartNewLine(); const char16_t* charCur = str; const char16_t* wordCur = str; for (int ac = 0; *charCur && (ac < count || count == -1); ++charCur, ++ac) { if (*charCur == u'\n' || *charCur == u' ') { AddStringFragment(wordCur, charCur - wordCur); wordCur = charCur + 1; if (*charCur == u'\n') { StartNewLine(); } else { StartNewWord(); int w, h; char16_t space = u' '; x18_textState.x48_font->GetSize(x18_textState.x0_drawStrOpts, w, h, &space, 1); if (xa0_curBlock->x14_dir == ETextDirection::Horizontal) { xa4_curLine->x8_curX += w; xbc_spaceDistance = w; } else { xa4_curLine->xc_curY += h; xbc_spaceDistance = h; } } } } if (charCur > wordCur) AddStringFragment(wordCur, charCur - wordCur); } void CTextExecuteBuffer::AddStringFragment(const char16_t* str, int len) { if (xa0_curBlock->x14_dir == ETextDirection::Horizontal) for (int i = 0; i < len;) i += WrapOneLTR(str + i, len - i); } int CTextExecuteBuffer::WrapOneLTR(const char16_t* str, int len) { if (!x18_textState.x48_font) return len; CRasterFont* font = x18_textState.x48_font.GetObj(); int rem = len; int w, h; x18_textState.x48_font->GetSize(x18_textState.x0_drawStrOpts, w, h, str, len); if (x18_textState.x7c_enableWordWrap) { if (w + xa4_curLine->x8_curX > xa0_curBlock->xc_blockExtentX && xa4_curLine->x4_wordCount >= 1 && xb0_curX + w < xa0_curBlock->xc_blockExtentX) { MoveWordLTR(); } if (w + xa4_curLine->x8_curX > xa0_curBlock->xc_blockExtentX && len > 1) { const char16_t* strEnd = str + len; int aRank = 5; do { --rem; --strEnd; int endRank = 4; if (len > 2) endRank = CWordBreakTables::GetEndRank(*(strEnd - 1)); int beginRank = CWordBreakTables::GetBeginRank(*strEnd); if (endRank < aRank && endRank <= beginRank) { aRank = endRank; } else { x18_textState.x48_font->GetSize(x18_textState.x0_drawStrOpts, w, h, str, rem); } } while (w + xa4_curLine->x8_curX > xa0_curBlock->xc_blockExtentX && rem > 1); } } xac_curY = std::max(xac_curY, font->GetMonoHeight()); xa4_curLine->TestLargestFont(font->GetMonoWidth(), font->GetMonoHeight(), font->GetBaseline()); xa4_curLine->x8_curX += w; xa0_curBlock->x2c_lineX = std::max(xa0_curBlock->x2c_lineX, xa4_curLine->x8_curX); xb0_curX += w; x0_instList.emplace(x0_instList.cend(), std::make_shared(str, rem)); if (rem != len) StartNewLine(); return rem; } void CTextExecuteBuffer::MoveWordLTR() { xa4_curLine->x8_curX -= (xb0_curX + xbc_spaceDistance); xa4_curLine->xc_curY = std::min(xa4_curLine->xc_curY, xb8_curWordY); xbc_spaceDistance = 0; --xa4_curLine->x4_wordCount; TerminateLineLTR(); xa4_curLine = static_cast( x0_instList .emplace(xa8_curWordIt, std::make_shared(x18_textState.x80_just, x18_textState.x84_vjust, xc0_imageBaseline)) ->get()); // Dunno what's up with this in the original; seems fine without x0_instList.emplace(xa8_curWordIt, std::make_shared()); ++xa0_curBlock->x34_lineCount; } void CTextExecuteBuffer::StartNewLine() { if (xa4_curLine) TerminateLine(); xa8_curWordIt = x0_instList.emplace( x0_instList.cend(), std::make_shared(x18_textState.x80_just, x18_textState.x84_vjust, xc0_imageBaseline)); xa4_curLine = static_cast(xa8_curWordIt->get()); xbc_spaceDistance = 0; StartNewWord(); ++xa0_curBlock->x34_lineCount; } void CTextExecuteBuffer::StartNewWord() { xa8_curWordIt = x0_instList.emplace(x0_instList.cend(), std::make_shared()); xb0_curX = 0; xac_curY = 0; xb4_curWordX = xa4_curLine->x8_curX; xb8_curWordY = xa4_curLine->xc_curY; ++xa4_curLine->x4_wordCount; } void CTextExecuteBuffer::TerminateLine() { if (xa0_curBlock->x14_dir == ETextDirection::Horizontal) TerminateLineLTR(); } void CTextExecuteBuffer::TerminateLineLTR() { if (!xa4_curLine->xc_curY /*&& x18_textState.IsFinishedLoading()*/) { xa4_curLine->xc_curY = std::max(xa4_curLine->GetHeight(), x18_textState.x48_font->GetCarriageAdvance()); } if (xa0_curBlock->x1c_vertJustification == EVerticalJustification::Full) { xa0_curBlock->x30_lineY += xa4_curLine->xc_curY; } else { xa0_curBlock->x30_lineY += x18_textState.x78_extraLineSpace + xa4_curLine->xc_curY * x18_textState.x74_lineSpacing; } } void CTextExecuteBuffer::AddPopState() { x0_instList.emplace(x0_instList.cend(), std::make_shared()); x18_textState = xc4_stateStack.back(); xc4_stateStack.pop_back(); if (xa4_curLine->x8_curX == 0) { xa4_curLine->x28_just = x18_textState.x80_just; xa4_curLine->x2c_vjust = x18_textState.x84_vjust; } } void CTextExecuteBuffer::AddPushState() { x0_instList.emplace(x0_instList.cend(), std::make_shared()); xc4_stateStack.push_back(x18_textState); } void CTextExecuteBuffer::AddVerticalJustification(EVerticalJustification vjust) { x18_textState.x84_vjust = vjust; if (!xa4_curLine) return; if (xa4_curLine->x8_curX) return; xa4_curLine->x2c_vjust = vjust; } void CTextExecuteBuffer::AddJustification(EJustification just) { x18_textState.x80_just = just; if (!xa4_curLine) return; if (xa4_curLine->x8_curX) return; xa4_curLine->x28_just = just; } void CTextExecuteBuffer::AddLineExtraSpace(s32 space) { x0_instList.emplace(x0_instList.cend(), std::make_shared(space)); x18_textState.x78_extraLineSpace = space; } void CTextExecuteBuffer::AddLineSpacing(float spacing) { x0_instList.emplace(x0_instList.cend(), std::make_shared(spacing)); x18_textState.x74_lineSpacing = spacing; } void CTextExecuteBuffer::AddRemoveColorOverride(int idx) { x0_instList.emplace(x0_instList.cend(), std::make_shared(idx)); } void CTextExecuteBuffer::AddColorOverride(int idx, const CTextColor& color) { x0_instList.emplace(x0_instList.cend(), std::make_shared(idx, color)); } void CTextExecuteBuffer::AddColor(EColorType tp, const CTextColor& color) { x0_instList.emplace(x0_instList.cend(), std::make_shared(tp, color)); } void CTextExecuteBuffer::AddImage(const CFontImageDef& image) { if (!xa4_curLine) StartNewLine(); if (xa0_curBlock) { const CTexture* tex = image.x4_texs[0].GetObj(); int width = tex->GetWidth() * image.x14_cropFactor.x(); int height = tex->GetHeight() * image.x14_cropFactor.y(); if (x18_textState.x7c_enableWordWrap && xa4_curLine->x8_curX + width > xa0_curBlock->xc_blockExtentX && xa4_curLine->x4_wordCount > 1) StartNewLine(); xa4_curLine->TestLargestImage(width, height, image.CalculateBaseline()); xa4_curLine->x8_curX += width; if (xa4_curLine->x8_curX > width) xa0_curBlock->x2c_lineX = xa4_curLine->x8_curX; } x0_instList.emplace(x0_instList.cend(), std::make_shared(image)); } void CTextExecuteBuffer::AddFont(const TToken& font) { x0_instList.emplace(x0_instList.cend(), std::make_shared(font)); x18_textState.x48_font = font; if (xa0_curBlock) xa0_curBlock->TestLargestFont(font->GetMonoWidth(), font->GetMonoHeight(), font->GetBaseline()); if (xa4_curLine) xa4_curLine->TestLargestFont(font->GetMonoWidth(), font->GetMonoHeight(), font->GetBaseline()); } void CTextExecuteBuffer::EndBlock() { if (xa4_curLine) TerminateLine(); xa4_curLine = nullptr; xa0_curBlock = nullptr; } void CTextExecuteBuffer::BeginBlock(s32 offX, s32 offY, s32 extX, s32 extY, bool imageBaseline, ETextDirection dir, EJustification just, EVerticalJustification vjust) { xc0_imageBaseline = imageBaseline; xa0_curBlock = static_cast( x0_instList .emplace(x0_instList.cend(), std::make_shared(offX, offY, extX, extY, dir, just, vjust)) ->get()); if (x18_textState.x48_font) { CRasterFont* font = x18_textState.x48_font.GetObj(); s32 baseline = font->GetBaseline(); s32 monoH = font->GetMonoHeight(); s32 monoW = font->GetMonoWidth(); xa0_curBlock->TestLargestFont(monoW, monoH, baseline); } x18_textState.x0_drawStrOpts.x0_direction = dir; x18_textState.x80_just = just; x18_textState.x84_vjust = vjust; } void CTextExecuteBuffer::Clear() { x0_instList.clear(); x18_textState = CSaveableState(); xa0_curBlock = nullptr; xa4_curLine = nullptr; xa8_curWordIt = x0_instList.begin(); xb4_curWordX = 0; xb8_curWordY = 0; xbc_spaceDistance = 0; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTextExecuteBuffer.hpp ================================================ #pragma once #include #include #include "Runtime/GCNTypes.hpp" #include "Runtime/GuiSys/CGuiTextSupport.hpp" #include "Runtime/GuiSys/CSaveableState.hpp" #include namespace metaforce { class CBlockInstruction; class CFontImageDef; class CInstruction; class CLineInstruction; class CTextRenderBuffer; class CTextExecuteBuffer { friend class CGuiTextSupport; friend class CTextRenderBufferPages; using InstList = std::list>; InstList x0_instList; u32 x14_ = 0; CSaveableState x18_textState; CBlockInstruction* xa0_curBlock = nullptr; CLineInstruction* xa4_curLine = nullptr; InstList::iterator xa8_curWordIt; s32 xac_curY = 0; s32 xb0_curX = 0; s32 xb4_curWordX = 0; s32 xb8_curWordY = 0; s32 xbc_spaceDistance = 0; bool xc0_imageBaseline = false; std::list xc4_stateStack; u32 xd8_ = 0; public: CTextExecuteBuffer() : xa8_curWordIt{x0_instList.begin()} {} CTextRenderBuffer BuildRenderBuffer(CGuiWidget::EGuiModelDrawFlags df) const; CTextRenderBuffer BuildRenderBufferPage(InstList::const_iterator start, InstList::const_iterator pgStart, InstList::const_iterator pgEnd, CGuiWidget::EGuiModelDrawFlags df) const; std::list BuildRenderBufferPages(const zeus::CVector2i& extent, CGuiWidget::EGuiModelDrawFlags df) const; std::vector GetAssets() const; void AddString(const char16_t* str, int len); void AddStringFragment(const char16_t* str, int len); int WrapOneLTR(const char16_t* str, int len); void MoveWordLTR(); void StartNewLine(); void StartNewWord(); void TerminateLine(); void TerminateLineLTR(); void AddPopState(); void AddPushState(); void AddVerticalJustification(EVerticalJustification vjust); void AddJustification(EJustification just); void AddLineExtraSpace(s32 space); void AddLineSpacing(float spacing); void AddRemoveColorOverride(int idx); void AddColorOverride(int idx, const CTextColor& color); void AddColor(EColorType, const CTextColor& color); void AddImage(const CFontImageDef& image); void AddFont(const TToken& font); void EndBlock(); void BeginBlock(s32 offX, s32 offY, s32 extX, s32 extY, bool imageBaseline, ETextDirection dir, EJustification just, EVerticalJustification vjust); void Clear(); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTextParser.cpp ================================================ #include "Runtime/GuiSys/CTextParser.hpp" #include "Runtime/GuiSys/CFontImageDef.hpp" #include "Runtime/GuiSys/CTextExecuteBuffer.hpp" namespace metaforce { static float u16stof(const char16_t* str) { char cstr[16]; int i; for (i = 0; i < 15 && str[i] != u'\0'; ++i) cstr[i] = str[i]; cstr[i] = '\0'; return strtof(cstr, nullptr); } CTextColor CTextParser::ParseColor(const char16_t* str, int len) { u8 r = GetColorValue(str + 1); u8 g = GetColorValue(str + 3); u8 b = GetColorValue(str + 5); u8 a = 0xff; if (len == 9) a = GetColorValue(str + 7); CTextColor ret; ret.fromRGBA8(r, g, b, a); return ret; } u8 CTextParser::GetColorValue(const char16_t* str) { return (FromHex(str[0]) << 4) + FromHex(str[1]); } u32 CTextParser::FromHex(char16_t ch) { if (ch >= u'0' && ch <= u'9') return ch - u'0'; if (ch >= u'A' && ch <= u'F') return ch - u'A' + 10; if (ch >= u'a' && ch <= u'f') return ch - u'a' + 10; return 0; } s32 CTextParser::ParseInt(const char16_t* str, int len, bool signVal) { bool neg = false; int procCur = 0; if (signVal && len && *str == u'-') { neg = true; procCur = 1; } int val = 0; while (len > procCur) { val *= 10; char16_t ch = str[procCur]; val += ch - u'0'; ++procCur; } return neg ? -val : val; } bool CTextParser::Equals(const char16_t* str, int len, const char16_t* other) { for (int i = 0; *other && i < len; ++i, ++str, ++other) { if (*str != *other) return false; } return *other == u'\0'; } bool CTextParser::BeginsWith(const char16_t* str, int len, const char16_t* other) { for (int i = 0; *other && i < len; ++i, ++str, ++other) { if (*str != *other) return false; } return true; } void CTextParser::ParseTag(CTextExecuteBuffer& out, const char16_t* str, int len, const std::vector>* txtrMap) { if (BeginsWith(str, len, u"font=")) { TToken font = GetFont(str + 5, len - 5); out.AddFont(font); } else if (BeginsWith(str, len, u"image=")) { CFontImageDef image = GetImage(str + 6, len - 6, txtrMap); out.AddImage(image); } else if (BeginsWith(str, len, u"fg-color=")) { CTextColor color = ParseColor(str + 9, len - 9); out.AddColor(EColorType::Foreground, color); } else if (BeginsWith(str, len, u"main-color=")) { CTextColor color = ParseColor(str + 11, len - 11); out.AddColor(EColorType::Main, color); } else if (BeginsWith(str, len, u"geometry-color=")) { CTextColor color = ParseColor(str + 15, len - 15); out.AddColor(EColorType::Geometry, color); } else if (BeginsWith(str, len, u"outline-color=")) { CTextColor color = ParseColor(str + 14, len - 14); out.AddColor(EColorType::Outline, color); } else if (BeginsWith(str, len, u"color")) { const char16_t* valCur = str + 7; len -= 7; int val = str[6] - u'0'; if (str[7] >= u'0' && str[7] <= u'9') { ++valCur; --len; val *= 10; val += str[7] - u'0'; } if (Equals(valCur + 10, len - 10, u"no")) out.AddRemoveColorOverride(val); else { CTextColor color = ParseColor(str + 10, len - 10); out.AddColorOverride(val, color); } } else if (BeginsWith(str, len, u"line-spacing=")) { out.AddLineSpacing(ParseInt(str + 13, len - 13, true) / 100.0f); } else if (BeginsWith(str, len, u"line-extra-space=")) { out.AddLineExtraSpace(ParseInt(str + 17, len - 17, true)); } else if (BeginsWith(str, len, u"just=")) { if (Equals(str + 5, len - 5, u"left")) out.AddJustification(EJustification::Left); else if (Equals(str + 5, len - 5, u"center")) out.AddJustification(EJustification::Center); else if (Equals(str + 5, len - 5, u"right")) out.AddJustification(EJustification::Right); else if (Equals(str + 5, len - 5, u"full")) out.AddJustification(EJustification::Full); else if (Equals(str + 5, len - 5, u"nleft")) out.AddJustification(EJustification::NLeft); else if (Equals(str + 5, len - 5, u"ncenter")) out.AddJustification(EJustification::NCenter); else if (Equals(str + 5, len - 5, u"nright")) out.AddJustification(EJustification::NRight); } else if (BeginsWith(str, len, u"vjust=")) { if (Equals(str + 6, len - 6, u"top")) out.AddVerticalJustification(EVerticalJustification::Top); else if (Equals(str + 6, len - 6, u"center")) out.AddVerticalJustification(EVerticalJustification::Center); else if (Equals(str + 6, len - 6, u"bottom")) out.AddVerticalJustification(EVerticalJustification::Bottom); else if (Equals(str + 6, len - 6, u"full")) out.AddVerticalJustification(EVerticalJustification::Full); else if (Equals(str + 6, len - 6, u"ntop")) out.AddVerticalJustification(EVerticalJustification::NTop); else if (Equals(str + 6, len - 6, u"ncenter")) out.AddVerticalJustification(EVerticalJustification::NCenter); else if (Equals(str + 6, len - 6, u"nbottom")) out.AddVerticalJustification(EVerticalJustification::NBottom); } else if (Equals(str, len, u"push")) { out.AddPushState(); } else if (Equals(str, len, u"pop")) { out.AddPopState(); } } CFontImageDef CTextParser::GetImage(const char16_t* str, int len, const std::vector>* txtrMap) { int commaCount = 0; for (int i = 0; i < len; ++i) if (str[i] == u',') ++commaCount; if (commaCount) { std::u16string iterable(str, len); size_t tokenPos; size_t commaPos; commaPos = iterable.find(u','); iterable[commaPos] = u'\0'; tokenPos = commaPos + 1; auto AdvanceCommaPos = [&]() { commaPos = iterable.find(u',', tokenPos); if (commaPos == std::u16string::npos) commaPos = iterable.size(); iterable[commaPos] = u'\0'; }; auto AdvanceTokenPos = [&]() { tokenPos = commaPos + 1; }; if (BeginsWith(str, len, u"A")) { /* Animated texture array */ AdvanceCommaPos(); float interval = u16stof(&iterable[tokenPos]); AdvanceTokenPos(); std::vector> texs; texs.reserve(commaCount - 1); do { AdvanceCommaPos(); texs.emplace_back(x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(&iterable[tokenPos], len, txtrMap)})); AdvanceTokenPos(); } while (commaPos != iterable.size()); return CFontImageDef(texs, interval, zeus::CVector2f(1.f, 1.f)); } else if (BeginsWith(str, len, u"SA")) { /* Scaled and animated texture array */ AdvanceCommaPos(); float interval = u16stof(&iterable[tokenPos]); AdvanceTokenPos(); AdvanceCommaPos(); float cropX = u16stof(&iterable[tokenPos]); AdvanceTokenPos(); AdvanceCommaPos(); float cropY = u16stof(&iterable[tokenPos]); AdvanceTokenPos(); std::vector> texs; texs.reserve(commaCount - 3); do { AdvanceCommaPos(); texs.emplace_back(x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(&iterable[tokenPos], len, txtrMap)})); AdvanceTokenPos(); } while (commaPos != iterable.size()); return CFontImageDef(texs, interval, zeus::CVector2f(cropX, cropY)); } else if (BeginsWith(str, len, u"SI")) { /* Scaled single texture */ AdvanceCommaPos(); float cropX = u16stof(&iterable[tokenPos]); AdvanceTokenPos(); AdvanceCommaPos(); float cropY = u16stof(&iterable[tokenPos]); AdvanceTokenPos(); AdvanceCommaPos(); TToken tex = x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(&iterable[tokenPos], len, txtrMap)}); AdvanceTokenPos(); return CFontImageDef(tex, zeus::CVector2f(cropX, cropY)); } } TToken tex = x0_store.GetObj({SBIG('TXTR'), GetAssetIdFromString(str, len, txtrMap)}); return CFontImageDef(tex, zeus::CVector2f(1.f, 1.f)); } CAssetId CTextParser::GetAssetIdFromString(const char16_t* str, int len, const std::vector>* txtrMap) { u8 r = GetColorValue(str); u8 g = GetColorValue(str + 2); u8 b = GetColorValue(str + 4); u8 a = GetColorValue(str + 6); CAssetId id = ((r << 24) | (g << 16) | (b << 8) | a) & 0xffffffff; if (len == 16) { r = GetColorValue(str + 8); g = GetColorValue(str + 10); b = GetColorValue(str + 12); a = GetColorValue(str + 14); id = (id.Value() << 32) | (((r << 24) | (g << 16) | (b << 8) | a) & 0xffffffff); } if (txtrMap) { auto search = rstl::binary_find(txtrMap->begin(), txtrMap->end(), id, [](const std::pair& a) { return a.first; }); if (search != txtrMap->end()) id = search->second; } return id; } TToken CTextParser::GetFont(const char16_t* str, int len) { return x0_store.GetObj({SBIG('FONT'), GetAssetIdFromString(str, len, nullptr)}); } void CTextParser::ParseText(CTextExecuteBuffer& out, const char16_t* str, int len, const std::vector>* txtrMap) { int b = 0, e = 0; for (b = 0, e = 0; str[e] && (len == -1 || e < len);) { if (str[e] != u'&') { ++e; continue; } if ((len == -1 || e + 1 < len) && str[e + 1] != u'&') { if (e > b) out.AddString(str + b, e - b); ++e; b = e; while (str[e] && (len == -1 || e < len) && str[e] != u';') ++e; ParseTag(out, str + b, e - b, txtrMap); b = e + 1; } else { out.AddString(str + b, e + 1 - b); e += 2; b = e; } } if (e > b) out.AddString(str + b, e - b); } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTextParser.hpp ================================================ #pragma once #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CGuiTextSupport.hpp" namespace metaforce { class CFontImageDef; class CTextExecuteBuffer; class CTextParser { IObjectStore& x0_store; static CTextColor ParseColor(const char16_t* str, int len); static u8 GetColorValue(const char16_t* str); static u32 FromHex(char16_t ch); static s32 ParseInt(const char16_t* str, int len, bool signVal); static CAssetId GetAssetIdFromString(const char16_t* str, int len, const std::vector>* txtrMap); static bool Equals(const char16_t* str, int len, const char16_t* other); static bool BeginsWith(const char16_t* str, int len, const char16_t* other); void ParseTag(CTextExecuteBuffer& out, const char16_t* str, int len, const std::vector>* txtrMap); CFontImageDef GetImage(const char16_t* str, int len, const std::vector>* txtrMap); TToken GetFont(const char16_t* str, int len); public: explicit CTextParser(IObjectStore& store) : x0_store(store) {} void ParseText(CTextExecuteBuffer& out, const char16_t* str, int len, const std::vector>* txtrMap); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTextRenderBuffer.cpp ================================================ #include "Runtime/GuiSys/CTextRenderBuffer.hpp" #include "Runtime/Graphics/CGX.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/Graphics/CGraphicsPalette.hpp" #include "Runtime/Graphics/CTexture.hpp" #include "Runtime/GuiSys/CFontImageDef.hpp" #include "Runtime/GuiSys/CFontRenderState.hpp" #include "Runtime/GuiSys/CInstruction.hpp" #include "Runtime/GuiSys/CRasterFont.hpp" #include "Runtime/GuiSys/CTextExecuteBuffer.hpp" namespace metaforce { CTextRenderBuffer::CTextRenderBuffer(CTextRenderBuffer&&) noexcept = default; CTextRenderBuffer::CTextRenderBuffer(EMode mode) : x0_mode(mode) {} CTextRenderBuffer::~CTextRenderBuffer() = default; CTextRenderBuffer& CTextRenderBuffer::operator=(CTextRenderBuffer&&) noexcept = default; void CTextRenderBuffer::SetPrimitive(const Primitive& prim, s32 idx) { CMemoryStreamOut out(reinterpret_cast(x34_bytecode.data() + x24_primOffsets[idx]), x44_blobSize - x24_primOffsets[idx]); if (prim.x4_command == Command::ImageRender) { out.WriteUint8(static_cast(Command::ImageRender)); out.Put(prim.x8_xPos); out.Put(prim.xa_zPos); out.Put(prim.xe_imageIndex); out.Put(prim.x0_color1.toRGBA()); } else if (prim.x4_command == Command::CharacterRender) { out.WriteUint8(static_cast(Command::CharacterRender)); out.Put(prim.x8_xPos); out.Put(prim.xa_zPos); out.Put(u16(prim.xc_glyph)); out.Put(prim.x0_color1.toRGBA()); } } CTextRenderBuffer::Primitive CTextRenderBuffer::GetPrimitive(s32 idx) const { CMemoryInStream in(reinterpret_cast(x34_bytecode.data() + x24_primOffsets[idx]), x44_blobSize - x24_primOffsets[idx]); auto cmd = Command(in.ReadChar()); if (cmd == Command::ImageRender) { s16 xPos = in.ReadShort(); s16 zPos = in.ReadShort(); u8 imageIndex = in.ReadChar(); CTextColor color(in.ReadUint32()); return {color, Command::ImageRender, xPos, zPos, u'\0', imageIndex}; } if (cmd == Command::CharacterRender) { s16 xPos = in.ReadShort(); s16 zPos = in.ReadShort(); char16_t glyph = in.ReadUint16(); CTextColor color(in.ReadUint32()); return {color, Command::CharacterRender, xPos, zPos, glyph, 0}; } return {CTextColor(zeus::Comp32(0)), Command::Invalid, 0, 0, u'\0', 0}; } u8* CTextRenderBuffer::GetOutStream() { VerifyBuffer(); return reinterpret_cast(x34_bytecode.data()) + x48_curBytecodeOffset; } void CTextRenderBuffer::VerifyBuffer() { if (x34_bytecode.empty()) { x34_bytecode.resize(x44_blobSize); } } void CTextRenderBuffer::SetMode(EMode mode) { x0_mode = mode; } int CTextRenderBuffer::GetMatchingPaletteIndex(const CGraphicsPalette& palette) { for (int i = 0; i < x50_palettes.size(); ++i) { if (memcmp(x50_palettes[i]->GetPaletteData(), palette.GetPaletteData(), 8) == 0) { return i; } } return -1; } CGraphicsPalette* CTextRenderBuffer::GetNextAvailablePalette() { if (x254_nextPalette < 64) { x50_palettes.push_back(std::make_unique(EPaletteFormat::RGB5A3, 4)); } else { x254_nextPalette = 0; } ++x254_nextPalette; return x50_palettes[x254_nextPalette - 1].get(); } u32 CTextRenderBuffer::GetCurLen() { VerifyBuffer(); return x44_blobSize - x48_curBytecodeOffset; } void CTextRenderBuffer::Render(const zeus::CColor& color, float time) { x4c_activeFont = -1; x4d_activePalette = -1; CMemoryInStream in(x34_bytecode.data(), x44_blobSize); while (in.GetReadPosition() < x44_blobSize) { auto cmd = static_cast(in.ReadChar()); if (cmd == Command::FontChange) { x4c_activeFont = x4e_queuedFont = in.ReadChar(); } else if (cmd == Command::CharacterRender) { if (x4e_queuedFont != -1) { auto font = x4_fonts[x4e_queuedFont]; if (font) { font->SetupRenderState(); x4e_queuedFont = -1; } } if (x4f_queuedPalette != -1) { x50_palettes[x4f_queuedPalette]->Load(); x4f_queuedPalette = -1; } s16 offX = in.ReadShort(); s16 offY = in.ReadShort(); char16_t chr = in.ReadShort(); zeus::CColor chrColor(static_cast(in.ReadLong())); if (x4c_activeFont != -1) { auto font = x4_fonts[x4c_activeFont]; if (font && font->GetGlyph(chr) != nullptr) { const auto* glyph = font->GetGlyph(chr); CGX::SetTevKColor(GX_KCOLOR0, chrColor * color); CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4); { GXPosition3f32(offX, 0.f, offY); GXTexCoord2f32(glyph->GetStartU(), glyph->GetStartV()); GXPosition3f32(offX + glyph->GetCellWidth(), 0.f, offY); GXTexCoord2f32(glyph->GetEndU(), glyph->GetStartV()); GXPosition3f32(offX, 0.f, offY + glyph->GetCellHeight()); GXTexCoord2f32(glyph->GetStartU(), glyph->GetEndV()); GXPosition3f32(offX + glyph->GetCellWidth(), 0.f, offY + glyph->GetCellHeight()); GXTexCoord2f32(glyph->GetEndU(), glyph->GetEndV()); } CGX::End(); } } } else if (cmd == Command::ImageRender) { s16 offX = in.ReadShort(); s16 offY = in.ReadShort(); u8 imageIdx = in.ReadChar(); zeus::CColor imageColor(static_cast(in.ReadLong())); auto imageDef = x14_images[imageIdx]; auto tex = imageDef.x4_texs[static_cast(time * imageDef.x0_fps) % imageDef.x4_texs.size()]; if (tex) { tex->Load(GX_TEXMAP0, EClampMode::Clamp); float width = imageDef.x4_texs.front()->GetWidth() * imageDef.x14_cropFactor.x(); float height = imageDef.x4_texs.front()->GetHeight() * imageDef.x14_cropFactor.y(); float cropXHalf = imageDef.x14_cropFactor.x() * 0.5f; float cropYHalf = imageDef.x14_cropFactor.y() * 0.5f; CGX::SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); CGX::SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); CGX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_ZERO); CGX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_ZERO); CGX::SetStandardTevColorAlphaOp(GX_TEVSTAGE0); constexpr std::array skVtxDesc{ GXVtxDescList{GX_VA_POS, GX_DIRECT}, GXVtxDescList{GX_VA_TEX0, GX_DIRECT}, GXVtxDescList{GX_VA_NULL, GX_NONE}, }; CGX::SetVtxDescv(skVtxDesc.data()); CGX::SetNumChans(0); CGX::SetNumTexGens(1); CGX::SetNumTevStages(1); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetTevKColor(GX_KCOLOR0, imageColor * color); CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4); { GXPosition3f32(offX, 0.f, offY); GXTexCoord2f32(0.5f - cropXHalf, 0.5f + cropYHalf); GXPosition3f32(offX + width, 0.f, offY); GXTexCoord2f32(0.5f + cropXHalf, 0.5f + cropYHalf); GXPosition3f32(offX, 0.f, offY + height); GXTexCoord2f32(0.5f - cropXHalf, 0.5f - cropYHalf); GXPosition3f32(offX + width, 0.f, offY + height); GXTexCoord2f32(0.5f + cropXHalf, 0.5f - cropYHalf); } CGX::End(); x4e_queuedFont = x4c_activeFont; x4f_queuedPalette = x4d_activePalette; } } else if (cmd == Command::PaletteChange) { x4d_activePalette = x4f_queuedPalette = in.ReadChar(); } } } void CTextRenderBuffer::AddPaletteChange(const CGraphicsPalette& palette) { if (x0_mode == EMode::BufferFill) { { u8* buf = GetOutStream(); CMemoryStreamOut out(buf, GetCurLen()); s32 paletteIndex = GetMatchingPaletteIndex(palette); if (paletteIndex == -1) { GetNextAvailablePalette(); paletteIndex = x254_nextPalette - 1; CGraphicsPalette* destPalette = x50_palettes[x254_nextPalette - 1].get(); u16* data = destPalette->Lock(); memcpy(data, palette.GetPaletteData(), 8); destPalette->UnLock(); } out.WriteUint8(static_cast(Command::PaletteChange)); out.WriteUint8(paletteIndex); x48_curBytecodeOffset += out.GetNumWrites(); } } else { x44_blobSize += 2; } } void CTextRenderBuffer::AddImage(const zeus::CVector2i& offset, const CFontImageDef& image) { if (x0_mode == EMode::BufferFill) { CMemoryStreamOut out(GetOutStream(), GetCurLen()); x24_primOffsets.reserve(x24_primOffsets.size() + 1); u32 primCap = x24_primOffsets.capacity(); if (x24_primOffsets.capacity() <= x24_primOffsets.size()) { x24_primOffsets.reserve(primCap != 0 ? primCap * 2 : 4); } x24_primOffsets.push_back(x48_curBytecodeOffset); x14_images.reserve(x14_images.size() + 1); u32 imgIdx = x14_images.size(); x14_images.push_back(image); out.WriteUint8(static_cast(Command::ImageRender)); out.WriteShort(offset.x); out.WriteShort(offset.y); out.WriteUint8(imgIdx); out.WriteLong(zeus::skWhite.toRGBA()); x48_curBytecodeOffset += out.GetNumWrites(); } else { x44_blobSize += 10; } } void CTextRenderBuffer::AddCharacter(const zeus::CVector2i& offset, char16_t ch, const CTextColor& color) { if (x0_mode == EMode::BufferFill) { CMemoryStreamOut out(GetOutStream(), GetCurLen()); x24_primOffsets.reserve(x24_primOffsets.size() + 1); u32 primCap = x24_primOffsets.capacity(); if (x24_primOffsets.capacity() <= x24_primOffsets.size()) { x24_primOffsets.reserve(primCap != 0 ? primCap * 2 : 4); } x24_primOffsets.push_back(x48_curBytecodeOffset); out.WriteUint8(u32(Command::CharacterRender)); out.WriteShort(offset.x); out.WriteShort(offset.y); out.WriteShort(ch); out.WriteUint32(color.toRGBA()); x48_curBytecodeOffset += out.GetNumWrites(); } else { x44_blobSize += 11; } } void CTextRenderBuffer::AddFontChange(const TToken& font) { if (x0_mode == EMode::BufferFill) { CMemoryStreamOut out(GetOutStream(), GetCurLen()); u32 fontCount = x4_fonts.size(); bool found = false; u8 fontIndex = 0; if (fontCount > 0) { for (const auto& tok : x4_fonts) { if (tok.GetObjectReference() == font.GetObjectReference()) { out.WriteUint8(static_cast(Command::FontChange)); out.WriteUint8(fontIndex); found = true; break; } ++fontIndex; } } if (!found) { x4_fonts.reserve(x4_fonts.size() + 1); u32 fontIdx = x4_fonts.size(); x4_fonts.push_back(font); out.WriteUint8(static_cast(Command::FontChange)); out.WriteUint8(fontIdx); } x48_curBytecodeOffset += out.GetNumWrites(); } else { x44_blobSize += 2; } } bool CTextRenderBuffer::HasSpaceAvailable(const zeus::CVector2i& origin, const zeus::CVector2i& extent) { std::pair bounds = AccumulateTextBounds(); if (bounds.first.x > bounds.second.x) { return true; } if (0 < origin.y) { return false; } zeus::CVector2i size = bounds.second - bounds.first; return size.y <= extent.y; } std::pair CTextRenderBuffer::AccumulateTextBounds() { zeus::CVector2i min{INT_MAX, INT_MAX}; zeus::CVector2i max{INT_MIN, INT_MIN}; CMemoryInStream in(x34_bytecode.data(), x44_blobSize); while (in.GetReadPosition() < x48_curBytecodeOffset) { auto cmd = static_cast(in.ReadChar()); if (cmd == Command::FontChange) { x4c_activeFont = in.ReadChar(); } else if (cmd == Command::CharacterRender) { u16 offX = in.ReadShort(); u16 offY = in.ReadShort(); char16_t chr = in.ReadShort(); in.ReadLong(); if (x4c_activeFont != -1) { auto font = x4_fonts[x4c_activeFont]; if (font) { const auto* glyph = font->GetGlyph(chr); if (glyph != nullptr) { max.x = std::max(max.x, offX + glyph->GetCellWidth()); max.y = std::max(max.y, offY + glyph->GetCellHeight()); min.x = std::min(min.x, offX); min.y = std::min(min.y, offY); } } } } else if (cmd == Command::ImageRender) { u16 offX = in.ReadShort(); u16 offY = in.ReadShort(); u8 imageIdx = in.ReadChar(); in.ReadLong(); const auto& image = x14_images[imageIdx]; max.x = std::max(max.x, offX + static_cast(static_cast(image.x4_texs.front()->GetWidth()) * image.x14_cropFactor.x())); max.y = std::max(max.y, offY + static_cast(static_cast(image.x4_texs.front()->GetHeight()) * image.x14_cropFactor.y())); min.x = std::min(min.x, offX); min.y = std::min(min.y, offY); } else if (cmd == Command::PaletteChange) { in.ReadChar(); } } return {min, max}; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CTextRenderBuffer.hpp ================================================ #pragma once #include #include #include "Runtime/CToken.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/GuiSys/CFontImageDef.hpp" #include "Runtime/GuiSys/CGuiWidget.hpp" #include #include #include #include namespace metaforce { class CGlyph; class CGraphicsPalette; class CRasterFont; class CTextExecuteBuffer; using CTextColor = zeus::CColor; class CTextRenderBuffer { friend class CGuiTextSupport; friend class CTextSupportShader; public: enum class Command { CharacterRender, ImageRender, FontChange, PaletteChange, Invalid = -1 }; struct Primitive { CTextColor x0_color1; Command x4_command; s16 x8_xPos; s16 xa_zPos; char16_t xc_glyph; u8 xe_imageIndex; }; enum class EMode { AllocTally, BufferFill }; private: EMode x0_mode; std::vector> x4_fonts; std::vector x14_images; std::vector x24_primOffsets; std::vector x34_bytecode; u32 x44_blobSize = 0; u32 x48_curBytecodeOffset = 0; s8 x4c_activeFont = -1; s8 x4d_activePalette = -1; s8 x4e_queuedFont = -1; s8 x4f_queuedPalette = -1; rstl::reserved_vector, 64> x50_palettes; s32 x254_nextPalette = 0; public: CTextRenderBuffer(CTextRenderBuffer&& other) noexcept; CTextRenderBuffer(EMode mode); ~CTextRenderBuffer(); CTextRenderBuffer& operator=(CTextRenderBuffer&& other) noexcept; void SetPrimitive(const Primitive&, int); [[nodiscard]] Primitive GetPrimitive(int) const; [[nodiscard]] u32 GetPrimitiveCount() const { return x24_primOffsets.size(); } [[nodiscard]] u8* GetOutStream(); [[nodiscard]] u32 GetCurLen(); void VerifyBuffer(); int GetMatchingPaletteIndex(const CGraphicsPalette& palette); [[nodiscard]] CGraphicsPalette* GetNextAvailablePalette(); void AddPaletteChange(const CGraphicsPalette& palette); void SetMode(EMode mode); void Render(const CTextColor& col, float time); void AddImage(const zeus::CVector2i& offset, const CFontImageDef& image); void AddCharacter(const zeus::CVector2i& offset, char16_t ch, const CTextColor& color); void AddFontChange(const TToken& font); [[nodiscard]] bool HasSpaceAvailable(const zeus::CVector2i& origin, const zeus::CVector2i& extent); [[nodiscard]] std::pair AccumulateTextBounds(); }; } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CWordBreakTables.cpp ================================================ #include "Runtime/GuiSys/CWordBreakTables.hpp" #include #include "Runtime/GCNTypes.hpp" #include "Runtime/rstl.hpp" namespace metaforce { namespace { struct CCharacterIdentifier { char16_t chr; u32 rank; }; constexpr std::array sCantBeginChars{{ {u'!', 1}, {u')', 1}, {u',', 1}, {u'-', 1}, {u'.', 1}, {u':', 1}, {u';', 1}, {u'?', 1}, {u']', 1}, {u'}', 1}, {0x92, 1}, {0x94, 1}, {0xBB, 1}, {0x3001, 1}, {0x3002, 1}, {0x3005, 1}, {0x300D, 1}, {0x300F, 1}, {0x3011, 1}, {0x3015, 1}, {0x3017, 1}, {0x3019, 1}, {0x301B, 1}, {0x301C, 3}, {0x301E, 1}, {0x302B, 3}, {0x3041, 2}, {0x3043, 2}, {0x3045, 2}, {0x3047, 2}, {0x3049, 2}, {0x3063, 2}, {0x3083, 2}, {0x3085, 2}, {0x3087, 2}, {0x308E, 2}, {0x309D, 3}, {0x309E, 3}, {0x30A1, 2}, {0x30A3, 2}, {0x30A5, 2}, {0x30A7, 2}, {0x30A9, 2}, {0x30C3, 2}, {0x30E3, 2}, {0x30E5, 2}, {0x30E7, 2}, {0x30EE, 2}, {0x30F5, 2}, {0x30F6, 2}, {0x30FC, 2}, {0x30FD, 3}, {0x30FE, 3}, {0xFF01, 1}, {0xFF05, 3}, {0xFF09, 1}, {0xFF0D, 1}, {0xFF3D, 1}, {0xFF5D, 1}, {0xFF61, 1}, {0xFF63, 1}, {0xFF64, 1}, {0xFF1F, 1}, }}; constexpr std::array sCantEndChars{{ {u'#', 2}, {u'$', 2}, {u'(', 1}, {u'@', 2}, {u'B', 4}, {u'C', 4}, {u'D', 4}, {u'E', 4}, {u'F', 4}, {u'G', 4}, {u'J', 4}, {u'K', 4}, {u'L', 4}, {u'M', 4}, {u'N', 4}, {u'P', 4}, {u'Q', 4}, {u'R', 4}, {u'S', 4}, {u'T', 4}, {u'V', 4}, {u'W', 4}, {u'X', 4}, {u'Y', 4}, {u'Z', 4}, {u'b', 4}, {u'c', 4}, {u'd', 4}, {u'f', 4}, {u'g', 4}, {u'h', 4}, {u'j', 4}, {u'k', 4}, {u'l', 4}, {u'm', 4}, {u'n', 4}, {u'p', 4}, {u'q', 4}, {u'r', 4}, {u's', 4}, {u't', 4}, {u'v', 4}, {u'w', 4}, {u'x', 4}, {u'y', 4}, {u'z', 4}, {0xD1, 4}, {0xF1, 4}, {u'[', 1}, {u'{', 1}, {0x91, 1}, {0x93, 1}, {0xA2, 2}, {0xA3, 2}, {0xA5, 2}, {0xA7, 2}, {0xA9, 2}, {0xAB, 1}, {0x20A0, 2}, {0x20A1, 2}, {0x20A2, 2}, {0x20A3, 2}, {0x20A4, 2}, {0x20A5, 2}, {0x20A6, 2}, {0x20A7, 2}, {0x20A8, 2}, {0x20A9, 2}, {0x20AA, 2}, {0x20AB, 2}, {0x20AC, 2}, {0x300C, 1}, {0x300E, 1}, {0x3010, 1}, {0x3012, 2}, {0x3014, 1}, {0x3016, 1}, {0x3018, 1}, {0x301A, 1}, {0xFF03, 2}, {0xFF04, 2}, {0xFF20, 2}, {0xFF3C, 1}, {0xFF5C, 1}, {0xFFE0, 2}, {0xFFE1, 2}, {0xFFEF, 2}, }}; } // Anonymous namespace int CWordBreakTables::GetBeginRank(char16_t ch) { const auto search = rstl::binary_find(sCantBeginChars.cbegin(), sCantBeginChars.cend(), ch, [](const CCharacterIdentifier& item) { return item.chr; }); if (search == sCantBeginChars.cend()) { return 5; } return search->rank; } int CWordBreakTables::GetEndRank(char16_t ch) { const auto search = rstl::binary_find(sCantEndChars.cbegin(), sCantEndChars.cend(), ch, [](const CCharacterIdentifier& item) { return item.chr; }); if (search == sCantEndChars.cend()) { return 5; } return search->rank; } } // namespace metaforce ================================================ FILE: Runtime/GuiSys/CWordBreakTables.hpp ================================================ #pragma once namespace metaforce { class CWordBreakTables { public: static int GetBeginRank(char16_t ch); static int GetEndRank(char16_t ch); }; } // namespace metaforce ================================================ FILE: Runtime/IFactory.hpp ================================================ #pragma once #include #include #include #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class CFactoryMgr; class CObjectReference; class CResLoader; class CSimplePool; class CVParamTransfer; class IDvdRequest; class IObj; using CFactoryFnReturn = std::unique_ptr; using FFactoryFunc = std::function; using FMemFactoryFunc = std::function&& in, u32 len, const metaforce::CVParamTransfer& vparms, CObjectReference* selfRef)>; class IFactory { public: virtual ~IFactory() = default; virtual CFactoryFnReturn Build(const SObjectTag&, const CVParamTransfer&, CObjectReference*) = 0; virtual void BuildAsync(const SObjectTag&, const CVParamTransfer&, std::unique_ptr*, CObjectReference*) = 0; virtual void CancelBuild(const SObjectTag&) = 0; virtual bool CanBuild(const SObjectTag&) = 0; virtual const SObjectTag* GetResourceIdByName(std::string_view) const = 0; virtual FourCC GetResourceTypeById(CAssetId id) const = 0; virtual void EnumerateResources(const std::function& lambda) const = 0; virtual void EnumerateNamedResources(const std::function& lambda) const = 0; virtual CResLoader* GetResLoader() { return nullptr; } virtual CFactoryMgr* GetFactoryMgr() { return nullptr; } virtual bool AsyncIdle(std::chrono::nanoseconds target) { return false; } /* Non-factory versions, replaces CResLoader */ virtual u32 ResourceSize(const metaforce::SObjectTag& tag) = 0; virtual std::shared_ptr LoadResourceAsync(const metaforce::SObjectTag& tag, void* target) = 0; virtual std::shared_ptr LoadResourcePartAsync(const metaforce::SObjectTag& tag, u32 off, u32 size, void* target) = 0; virtual std::unique_ptr LoadResourceSync(const metaforce::SObjectTag& tag) = 0; virtual std::unique_ptr LoadNewResourcePartSync(const metaforce::SObjectTag& tag, u32 off, u32 size) = 0; virtual void GetTagListForFile(const char* pakName, std::vector& out) const {} }; } // namespace metaforce ================================================ FILE: Runtime/IMain.hpp ================================================ #pragma once #include "Runtime/RetroTypes.hpp" #include "Runtime/CMainFlowBase.hpp" #include "Runtime/ConsoleVariables/FileStoreManager.hpp" //#include //#include namespace metaforce { class Console; class CVarManager; enum class ERegion { USA, JPN, PAL, KOR }; enum class EGame { Invalid = 0, MetroidPrime1, MetroidPrime2, MetroidPrime3, MetroidPrimeTrilogy, }; enum class EPlatform { GameCube, Wii, }; struct MetaforceVersionInfo { std::string version; ERegion region; EGame game; EPlatform platform; std::string gameTitle; }; class CStopwatch; enum class EGameplayResult { None, Win, Lose, Playing }; class IMain { public: virtual ~IMain() = default; virtual std::string Init(int argc, char** argv, const FileStoreManager& storeMgr, CVarManager* cvarMgr) = 0; virtual void Draw() = 0; virtual bool Proc(float dt) = 0; virtual void Shutdown() = 0; virtual EClientFlowStates GetFlowState() const = 0; virtual void SetFlowState(EClientFlowStates) = 0; virtual size_t GetExpectedIdSize() const = 0; virtual EGame GetGame() const = 0; virtual ERegion GetRegion() const = 0; virtual bool IsPAL() const = 0; virtual bool IsJapanese() const = 0; virtual bool IsUSA() const = 0; virtual bool IsKorean() const = 0; virtual bool IsTrilogy() const = 0; virtual std::string GetGameTitle() const = 0; virtual std::string_view GetVersionString() const = 0; virtual void Quit() = 0; virtual bool IsPaused() const = 0; virtual void SetPaused(bool b) = 0; }; } // namespace metaforce ================================================ FILE: Runtime/IObj.hpp ================================================ #pragma once #include #include "Runtime/RetroTypes.hpp" namespace metaforce { class IObj { public: virtual ~IObj() = default; }; class TObjOwnerDerivedFromIObjUntyped : public IObj { protected: void* m_objPtr; public: TObjOwnerDerivedFromIObjUntyped(void* objPtr) : m_objPtr(objPtr) {} }; template class TObjOwnerDerivedFromIObj : public TObjOwnerDerivedFromIObjUntyped { TObjOwnerDerivedFromIObj(T* objPtr) : TObjOwnerDerivedFromIObjUntyped(objPtr) {} public: static std::unique_ptr> GetNewDerivedObject(std::unique_ptr&& obj) { return std::unique_ptr>(new TObjOwnerDerivedFromIObj(obj.release())); } ~TObjOwnerDerivedFromIObj() override { std::default_delete()(static_cast(m_objPtr)); } T* GetObj() { return static_cast(m_objPtr); } }; } // namespace metaforce ================================================ FILE: Runtime/IObjFactory.hpp ================================================ #pragma once namespace metaforce { class IObjFactory { public: virtual ~IObjFactory() = default; }; } // namespace metaforce ================================================ FILE: Runtime/IObjectStore.hpp ================================================ #pragma once #include namespace metaforce { class CToken; class CVParamTransfer; class IFactory; struct SObjectTag; class IObjectStore { public: virtual ~IObjectStore() = default; virtual CToken GetObj(const SObjectTag&, const CVParamTransfer&) = 0; virtual CToken GetObj(const SObjectTag&) = 0; virtual CToken GetObj(std::string_view) = 0; virtual CToken GetObj(std::string_view, const CVParamTransfer&) = 0; virtual bool HasObject(const SObjectTag&) const = 0; virtual bool ObjectIsLive(const SObjectTag&) const = 0; virtual IFactory& GetFactory() const = 0; virtual void Flush() = 0; virtual void ObjectUnreferenced(const SObjectTag&) = 0; }; } // namespace metaforce ================================================ FILE: Runtime/IRuntimeMain.hpp ================================================ #pragma once namespace metaforce { struct IRuntimeMain { void init() = 0; int proc() = 0; void stop() = 0; }; } // namespace metaforce ================================================ FILE: Runtime/IVParamObj.hpp ================================================ #pragma once #include #include "Runtime/IObj.hpp" namespace metaforce { class IVParamObj : public IObj { public: ~IVParamObj() override = default; }; template class TObjOwnerParam : public IVParamObj { T m_param; public: TObjOwnerParam(T&& obj) : m_param(std::move(obj)) {} T& GetParam() noexcept { return m_param; } const T& GetParam() const noexcept { return m_param; } }; class CVParamTransfer { std::shared_ptr m_ref; public: constexpr CVParamTransfer() noexcept = default; CVParamTransfer(IVParamObj* obj) : m_ref(obj) {} CVParamTransfer(const CVParamTransfer& other) noexcept = default; CVParamTransfer& operator=(const CVParamTransfer&) noexcept = default; CVParamTransfer(CVParamTransfer&&) noexcept = default; CVParamTransfer& operator=(CVParamTransfer&&) noexcept = default; IVParamObj* GetObj() const noexcept { return m_ref.get(); } CVParamTransfer ShareTransferRef() const noexcept { return CVParamTransfer(*this); } template T& GetOwnedObj() const noexcept { return static_cast*>(GetObj())->GetParam(); } static CVParamTransfer Null() noexcept { return CVParamTransfer(); } }; } // namespace metaforce ================================================ FILE: Runtime/ImGuiConsole.cpp ================================================ #include #include #include #define IM_VEC2_CLASS_EXTRA \ ImVec2(const zeus::CVector2f& v) { \ x = v.x(); \ y = v.y(); \ } \ operator zeus::CVector2f() const { return zeus::CVector2f{x, y}; } #include "ImGuiConsole.hpp" #include "../version.h" #include "MP1/MP1.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/ImGuiEntitySupport.hpp" #include "Runtime/World/CPlayer.hpp" #include "ImGuiEngine.hpp" #include "Runtime/Logging.hpp" #include "Runtime/Formatting.hpp" #include #include #include #include #include #include #include namespace ImGui { // Internal functions void ClearIniSettings(); } // namespace ImGui #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { std::array ImGuiConsole::entities; std::set ImGuiConsole::inspectingEntities; ImGuiPlayerLoadouts ImGuiConsole::loadouts; extern SDL_Window* g_window; ImGuiConsole::ImGuiConsole(CVarManager& cvarMgr, CVarCommons& cvarCommons) : m_cvarMgr(cvarMgr), m_cvarCommons(cvarCommons) {} void ImGuiStringViewText(std::string_view text) { // begin()/end() do not work on MSVC ImGui::TextUnformatted(text.data(), text.data() + text.size()); } void ImGuiTextCenter(std::string_view text) { ImGui::NewLine(); float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x; ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2); ImGuiStringViewText(text); } bool ImGuiButtonCenter(std::string_view text) { ImGui::NewLine(); float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x; fontSize += ImGui::GetStyle().FramePadding.x; ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2); return ImGui::Button(text.data()); } static std::unordered_map> dummyWorlds; static std::unordered_map> stringTables; std::string ImGuiLoadStringTable(CAssetId stringId, int idx) { if (!stringId.IsValid()) { return ""s; } if (!stringTables.contains(stringId)) { stringTables[stringId] = g_SimplePool->GetObj(SObjectTag{SBIG('STRG'), stringId}); } return CStringExtras::ConvertToUTF8(stringTables[stringId].GetObj()->GetString(idx)); } static bool ContainsCaseInsensitive(std::string_view str, std::string_view val) { return std::search(str.begin(), str.end(), val.begin(), val.end(), [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }) != str.end(); } static std::vector> ListWorlds() { std::vector> worlds; for (const auto& pak : g_ResFactory->GetResLoader()->GetPaks()) { if (!pak->IsWorldPak()) { continue; } CAssetId worldId = pak->GetMLVLId(); if (!dummyWorlds.contains(worldId)) { dummyWorlds[worldId] = std::make_unique(worldId, false); } auto& world = dummyWorlds[worldId]; bool complete = world->ICheckWorldComplete(); if (!complete) { continue; } CAssetId stringId = world->IGetStringTableAssetId(); if (!stringId.IsValid()) { continue; } worlds.emplace_back(ImGuiLoadStringTable(stringId, 0), worldId); } return worlds; } static std::vector> ListAreas(CAssetId worldId) { std::vector> areas; const auto& world = dummyWorlds[worldId]; for (TAreaId i = 0; i < world->IGetAreaCount(); ++i) { const auto* area = world->IGetAreaAlways(i); if (area == nullptr) { continue; } CAssetId stringId = area->IGetStringTableAssetId(); if (!stringId.IsValid()) { continue; } areas.emplace_back(ImGuiLoadStringTable(stringId, 0), i); } return areas; } static void Warp(const CAssetId worldId, TAreaId aId) { g_GameState->SetCurrentWorldId(worldId); g_GameState->GetWorldTransitionManager()->DisableTransition(); if (aId >= g_GameState->CurrentWorldState().GetLayerState()->GetAreaCount()) { aId = 0; } g_GameState->CurrentWorldState().SetAreaId(aId); g_Main->SetFlowState(EClientFlowStates::None); if (g_StateManager != nullptr) { g_StateManager->SetWarping(true); g_StateManager->SetShouldQuitGame(true); } else { // TODO(encounter): warp from menu? } } static inline float GetScale() { return 1.f; } void ImGuiConsole::ShowMenuGame() { if (g_Main != nullptr) { m_paused = g_Main->IsPaused(); } if (ImGui::MenuItem("Paused", "F5", &m_paused, g_Main != nullptr)) { g_Main->SetPaused(m_paused); } if (ImGui::MenuItem("Step Frame", "F6", &m_stepFrame, m_paused)) { g_Main->SetPaused(false); } if (ImGui::BeginMenu("Warp", m_cheats && g_StateManager != nullptr && g_ResFactory != nullptr && g_ResFactory->GetResLoader() != nullptr)) { for (const auto& world : ListWorlds()) { if (ImGui::BeginMenu(world.first.c_str())) { for (const auto& area : ListAreas(world.second)) { if (ImGui::MenuItem(area.first.c_str())) { Warp(world.second, area.second); } } ImGui::EndMenu(); } } ImGui::EndMenu(); } if (ImGui::MenuItem("Quit", "Alt+F4")) { m_quitRequested = true; } } void ImGuiConsole::LerpDebugColor(CActor* act) { if (!act->m_debugSelected && !act->m_debugHovered) { act->m_debugAddColorTime = 0.f; act->m_debugAddColor = zeus::skClear; return; } act->m_debugAddColorTime += ImGui::GetIO().DeltaTime; float lerp = act->m_debugAddColorTime; if (lerp > 2.f) { lerp = 0.f; act->m_debugAddColorTime = 0.f; } else if (lerp > 1.f) { lerp = 2.f - lerp; } act->m_debugAddColor = zeus::CColor::lerp(zeus::skClear, zeus::skBlue, lerp); } void ImGuiConsole::UpdateEntityEntries() { CObjectList& list = g_StateManager->GetAllObjectList(); s16 uid = list.GetFirstObjectIndex(); while (uid != -1) { ImGuiEntityEntry& entry = ImGuiConsole::entities[uid]; if (entry.uid == kInvalidUniqueId || entry.ent == nullptr) { CEntity* ent = list.GetObjectByIndex(uid); entry.uid = ent->GetUniqueId(); entry.ent = ent; entry.type = ent->ImGuiType(); entry.name = ent->GetName(); entry.isActor = TCastToPtr(ent).IsValid(); } else { entry.active = entry.ent->GetActive(); } if (entry.isActor) { LerpDebugColor(entry.AsActor()); } entry.ent->m_debugHovered = false; uid = list.GetNextObjectIndex(uid); } } void ImGuiConsole::BeginEntityRow(const ImGuiEntityEntry& entry) { ImGui::PushID(entry.uid.Value()); ImGui::TableNextRow(); bool isActive = entry.active; ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text); if (!isActive) { textColor.w = 0.5f; } ImGui::PushStyleColor(ImGuiCol_Text, textColor); if (ImGui::TableNextColumn()) { auto text = fmt::format("0x{:04X}", entry.uid.Value()); ImGui::Selectable(text.c_str(), &entry.ent->m_debugSelected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap); if (ImGui::IsItemHovered()) { entry.ent->m_debugHovered = true; } if (ImGui::BeginPopupContextItem(text.c_str())) { ImGui::PopStyleColor(); if (ImGui::MenuItem(isActive ? "Deactivate" : "Activate")) { entry.ent->SetActive(!isActive); } if (ImGui::MenuItem("Highlight", nullptr, &entry.ent->m_debugSelected)) { entry.ent->SetActive(!isActive); } // Only allow deletion if none of the objects are player related // The player objects will always be in the first 6 slots if (entry.uid.Value() > 6) { ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{0.77f, 0.12f, 0.23f, 1.f}); if (ImGui::MenuItem("Delete")) { g_StateManager->FreeScriptObject(entry.uid); } ImGui::PopStyleColor(); } ImGui::EndPopup(); ImGui::PushStyleColor(ImGuiCol_Text, textColor); } } } void ImGuiConsole::EndEntityRow(const ImGuiEntityEntry& entry) { ImGui::PopStyleColor(); if (ImGui::TableNextColumn()) { if (ImGui::SmallButton("View")) { ImGuiConsole::inspectingEntities.insert(entry.uid); } } ImGui::PopID(); } static void RenderEntityColumns(const ImGuiEntityEntry& entry) { ImGuiConsole::BeginEntityRow(entry); if (ImGui::TableNextColumn()) { ImGuiStringViewText(entry.type); } if (ImGui::TableNextColumn()) { ImGuiStringViewText(entry.name); } ImGuiConsole::EndEntityRow(entry); } void ImGuiConsole::ShowInspectWindow(bool* isOpen) { float initialWindowSize = 400.f * GetScale(); ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize * 1.5f}, ImGuiCond_FirstUseEver); if (ImGui::Begin("Inspect", isOpen)) { CObjectList& list = g_StateManager->GetAllObjectList(); ImGui::Text("Objects: %d / %d", list.size(), kMaxEntities); ImGui::SameLine(); if (ImGui::SmallButton("Deselect all")) { for (auto* const ent : list) { ent->m_debugSelected = false; } } if (ImGui::Button("Clear")) { m_inspectFilterText.clear(); } ImGui::SameLine(); ImGui::InputText("Filter", &m_inspectFilterText); ImGui::Checkbox("Active", &m_inspectActiveOnly); ImGui::SameLine(); ImGui::Checkbox("Current area", &m_inspectCurrentAreaOnly); if (ImGui::BeginTable("Entities", 4, ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0, 'id'); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 0, 'type'); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0, 'name'); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs(); bool hasSortSpec = sortSpecs != nullptr && // no multi-sort sortSpecs->SpecsCount == 1 && // We can skip sorting if we just want uid ascending, // since that's how we iterate over CObjectList (sortSpecs->Specs[0].ColumnUserID != 'id' || sortSpecs->Specs[0].SortDirection != ImGuiSortDirection_Ascending); if (!m_inspectFilterText.empty() || m_inspectActiveOnly || m_inspectCurrentAreaOnly || hasSortSpec) { std::vector sortedList; sortedList.reserve(list.size()); s16 uid = list.GetFirstObjectIndex(); auto currAreaId = kInvalidAreaId; CPlayer* player = nullptr; if (m_inspectCurrentAreaOnly && (player = g_StateManager->Player()) != nullptr) { currAreaId = player->GetAreaIdAlways(); } while (uid != -1) { ImGuiEntityEntry& entry = ImGuiConsole::entities[uid]; if ((!m_inspectActiveOnly || entry.active) && (!m_inspectCurrentAreaOnly || entry.ent->x4_areaId == currAreaId) && (m_inspectFilterText.empty() || ContainsCaseInsensitive(entry.type, m_inspectFilterText) || ContainsCaseInsensitive(entry.name, m_inspectFilterText))) { sortedList.push_back(uid); } uid = list.GetNextObjectIndex(uid); } if (hasSortSpec) { const auto& spec = sortSpecs->Specs[0]; if (spec.ColumnUserID == 'id') { if (spec.SortDirection == ImGuiSortDirection_Ascending) { // no-op } else { std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) { return a < b; }); } } else if (spec.ColumnUserID == 'name') { std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) { int compare = ImGuiConsole::entities[a].name.compare(ImGuiConsole::entities[b].name); return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0; }); } else if (spec.ColumnUserID == 'type') { std::sort(sortedList.begin(), sortedList.end(), [&](s16 a, s16 b) { int compare = ImGuiConsole::entities[a].type.compare(ImGuiConsole::entities[b].type); return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0; }); } } for (const auto& item : sortedList) { RenderEntityColumns(ImGuiConsole::entities[item]); } } else { // Render uid ascending s16 uid = list.GetFirstObjectIndex(); while (uid != -1) { RenderEntityColumns(ImGuiConsole::entities[uid]); uid = list.GetNextObjectIndex(uid); } } ImGui::EndTable(); } } ImGui::End(); } bool ImGuiConsole::ShowEntityInfoWindow(TUniqueId uid) { bool open = true; ImGuiEntityEntry& entry = ImGuiConsole::entities[uid.Value()]; if (entry.ent == nullptr) { return false; } auto name = fmt::format("{}##0x{:04X}", !entry.name.empty() ? entry.name : entry.type, uid.Value()); if (ImGui::Begin(name.c_str(), &open, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::PushID(uid.Value()); entry.ent->ImGuiInspect(); ImGui::PopID(); } ImGui::End(); return open; } void ImGuiConsole::ShowConsoleVariablesWindow() { // For some reason the window shows up tiny without this float initialWindowSize = 350.f * GetScale(); ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver); if (ImGui::Begin("Console Variables", &m_showConsoleVariablesWindow)) { if (ImGui::Button("Clear")) { m_cvarFiltersText.clear(); } ImGui::SameLine(); ImGui::InputText("Filter", &m_cvarFiltersText); auto cvars = m_cvarMgr.cvars(CVar::EFlags::Any & ~CVar::EFlags::Hidden); if (ImGui::Button("Reset to defaults")) { for (auto* cv : cvars) { if (cv->name() == "developer" || cv->name() == "cheats") { // don't reset developer or cheats to default continue; } CVarUnlocker l(cv); cv->fromLiteralToType(cv->defaultValue()); } } if (ImGui::BeginTable("ConsoleVariables", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0, 'name'); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0, 'val'); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs(); bool hasSortSpec = sortSpecs != nullptr && // no multi-sort sortSpecs->SpecsCount == 1; std::vector sortedList; sortedList.reserve(cvars.size()); for (auto* cvar : cvars) { if (cvar->isHidden()) { continue; } if (!m_cvarFiltersText.empty()) { if (ContainsCaseInsensitive(magic_enum::enum_name(cvar->type()), m_cvarFiltersText) || ContainsCaseInsensitive(cvar->name(), m_cvarFiltersText)) { sortedList.push_back(cvar); } } else { sortedList.push_back(cvar); } } if (hasSortSpec) { const auto& spec = sortSpecs->Specs[0]; if (spec.ColumnUserID == 'name') { std::sort(sortedList.begin(), sortedList.end(), [&](CVar* a, CVar* b) { int compare = a->name().compare(b->name()); return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0; }); } else if (spec.ColumnUserID == 'val') { std::sort(sortedList.begin(), sortedList.end(), [&](CVar* a, CVar* b) { int compare = a->value().compare(b->value()); return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0; }); } for (auto* cv : sortedList) { bool modified = cv->isModified(); ImGui::PushID(cv); ImGui::TableNextRow(); // Name if (ImGui::TableNextColumn()) { ImGuiStringViewText(cv->name()); if (ImGui::IsItemHovered() && !cv->rawHelp().empty()) { std::string sv(cv->rawHelp()); ImGui::SetTooltip("%s", sv.c_str()); } } // Value if (ImGui::TableNextColumn()) { switch (cv->type()) { case CVar::EType::Boolean: { bool b = cv->toBoolean(); if (ImGui::Checkbox("", &b)) { cv->fromBoolean(b); modified = true; } break; } case CVar::EType::Real: { float f = cv->toReal(); if (ImGui::DragFloat("", &f)) { cv->fromReal(f); modified = true; } break; } case CVar::EType::Signed: { std::array i{cv->toSigned()}; if (ImGui::DragScalar("", ImGuiDataType_S32, i.data(), i.size())) { cv->fromInteger(i[0]); modified = true; } break; } case CVar::EType::Unsigned: { std::array i{cv->toUnsigned()}; if (ImGui::DragScalar("", ImGuiDataType_U32, i.data(), i.size())) { cv->fromInteger(i[0]); modified = true; } break; } case CVar::EType::Literal: { char buf[4096]; strcpy(buf, cv->value().c_str()); if (ImGui::InputText("", buf, 4096, ImGuiInputTextFlags_EnterReturnsTrue)) { cv->fromLiteral(buf); modified = true; } break; } case CVar::EType::Vec2f: { auto vec = cv->toVec2f(); std::array scalars = {vec.x(), vec.y()}; if (ImGui::DragScalarN("", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) { vec.x() = scalars[0]; vec.y() = scalars[1]; cv->fromVec2f(vec); modified = true; } break; } case CVar::EType::Vec2d: { auto vec = cv->toVec2d(); std::array scalars = {vec.x(), vec.y()}; if (ImGui::DragScalarN("", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) { vec.x() = scalars[0]; vec.y() = scalars[1]; cv->fromVec2d(vec); modified = true; } break; } case CVar::EType::Vec3f: { auto vec = cv->toVec3f(); std::array scalars = {vec.x(), vec.y(), vec.z()}; if (cv->isColor()) { if (ImGui::ColorEdit3("", scalars.data())) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; cv->fromVec3f(vec); modified = true; } } else if (ImGui::DragScalarN("", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; cv->fromVec3f(vec); modified = true; } break; } case CVar::EType::Vec3d: { auto vec = cv->toVec3d(); std::array scalars = {vec.x(), vec.y(), vec.z()}; if (cv->isColor()) { std::array color{static_cast(scalars[0]), static_cast(scalars[1]), static_cast(scalars[2])}; if (ImGui::ColorEdit3("", color.data())) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; cv->fromVec3d(vec); modified = true; } } else if (ImGui::DragScalarN("", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; cv->fromVec3d(vec); modified = true; } break; } case CVar::EType::Vec4f: { auto vec = cv->toVec4f(); std::array scalars = {vec.x(), vec.y(), vec.z(), vec.w()}; if (cv->isColor()) { if (ImGui::ColorEdit4("", scalars.data())) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; vec.w() = scalars[2]; cv->fromVec4f(vec); modified = true; } } else if (ImGui::DragScalarN("", ImGuiDataType_Float, scalars.data(), scalars.size(), 0.1f)) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; vec.w() = scalars[2]; cv->fromVec4f(vec); modified = true; } break; } case CVar::EType::Vec4d: { auto vec = cv->toVec4d(); std::array scalars = {vec.x(), vec.y(), vec.z(), vec.w()}; if (cv->isColor()) { std::array color{static_cast(scalars[0]), static_cast(scalars[1]), static_cast(scalars[2]), static_cast(scalars[3])}; if (ImGui::ColorEdit4("", color.data())) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; vec.w() = scalars[2]; cv->fromVec4d(vec); modified = true; } } else if (ImGui::DragScalarN("", ImGuiDataType_Double, scalars.data(), scalars.size(), 0.1f)) { vec.x() = scalars[0]; vec.y() = scalars[1]; vec.z() = scalars[2]; vec.w() = scalars[2]; cv->fromVec4d(vec); modified = true; } break; } default: ImGui::Text("lawl wut? Please contact a developer, your copy of Metaforce is cursed!"); break; } if (modified && cv->modificationRequiresRestart()) { ImGui::Text("Restart required for value to take affect!"); } if (ImGui::IsItemHovered()) { std::string sv(cv->defaultValue()); ImGui::SetTooltip("Default: %s", sv.c_str()); } } ImGui::PopID(); } } ImGui::EndTable(); } } ImGui::End(); } void fileDialogCallback(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { auto* self = static_cast(userdata); if (filelist != nullptr) { if (filelist[0] == nullptr) { // Cancelled self->m_gameDiscSelected.reset(); } else { self->m_gameDiscSelected = filelist[0]; } } else { // Error occurred self->m_gameDiscSelected.reset(); self->m_errorString = fmt::format("File dialog error: {}", SDL_GetError()); } } static constexpr std::array skGameDiscFileFilters{{ {"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"}, {"All Files", "*"}, }}; void ImGuiConsole::ShowAboutWindow(bool preLaunch) { // Center window ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, preLaunch ? ImGuiCond_Always : ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); ImVec4& windowBg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg]; ImGui::PushStyleColor(ImGuiCol_TitleBg, windowBg); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, windowBg); bool* open = nullptr; ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings; if (preLaunch) { flags |= ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove; } else { open = &m_showAboutWindow; } if (ImGui::Begin("About", open, flags)) { float iconSize = 128.f * GetScale(); ImGui::SameLine(ImGui::GetWindowSize().x / 2 - iconSize + (iconSize / 2)); ImGui::Image(ImGuiEngine::metaforceIcon, ImVec2{iconSize, iconSize}); ImGui::PushFont(ImGuiEngine::fontLarge); ImGuiTextCenter("Metaforce"); ImGui::PopFont(); ImGuiTextCenter(METAFORCE_WC_DESCRIBE); const ImVec2& padding = ImGui::GetStyle().WindowPadding; ImGui::Dummy(padding); if (preLaunch) { if (ImGuiButtonCenter("Settings")) { m_showPreLaunchSettingsWindow = true; } ImGui::Dummy(padding); if (ImGuiButtonCenter("Select Game")) { SDL_ShowOpenFileDialog(&fileDialogCallback, this, g_window, skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, false); } #ifdef EMSCRIPTEN if (ImGuiButtonCenter("Load Game")) { m_gameDiscSelected = "game.iso"; } #else if (!m_lastDiscPath.empty()) { if (ImGuiButtonCenter("Load Previous Game")) { m_gameDiscSelected = m_lastDiscPath; } } #endif ImGui::Dummy(padding); } if (m_errorString) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{0.77f, 0.12f, 0.23f, 1.f}); ImGui::NewLine(); ImGui::PushTextWrapPos(0.f); ImGuiStringViewText(*m_errorString); ImGui::PopTextWrapPos(); ImGui::PopStyleColor(); ImGui::Dummy(padding); } ImGuiTextCenter("2015-2025"); ImGui::BeginGroup(); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200)); ImGuiStringViewText("Development & Research"); ImGui::PopStyleColor(); ImGuiStringViewText("Phillip Stephens (Antidote)"); ImGuiStringViewText("Jack Andersen (jackoalan)"); ImGuiStringViewText("Luke Street (encounter)"); ImGuiStringViewText("Lioncache"); ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200)); ImGuiStringViewText("Testing"); ImGui::PopStyleColor(); ImGuiStringViewText("Tom Lube"); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200)); ImGuiStringViewText("Contributions"); ImGui::PopStyleColor(); ImGuiStringViewText("Darkszero (Profiling)"); ImGuiStringViewText("shio (Weapons)"); ImGui::EndGroup(); ImGui::Dummy(padding); ImGui::Separator(); if (ImGui::BeginTable("Version Info", 2, ImGuiTableFlags_BordersInnerV)) { ImGui::TableNextRow(); if (ImGui::TableNextColumn()) { ImGuiStringViewText("Branch"); } if (ImGui::TableNextColumn()) { ImGuiStringViewText(METAFORCE_WC_BRANCH); } ImGui::TableNextRow(); if (ImGui::TableNextColumn()) { ImGuiStringViewText("Revision"); } if (ImGui::TableNextColumn()) { ImGuiStringViewText(METAFORCE_WC_REVISION); } ImGui::TableNextRow(); if (ImGui::TableNextColumn()) { ImGuiStringViewText("Build"); } if (ImGui::TableNextColumn()) { ImGuiStringViewText(METAFORCE_DLPACKAGE); } ImGui::TableNextRow(); if (ImGui::TableNextColumn()) { ImGuiStringViewText("Date"); } if (ImGui::TableNextColumn()) { ImGuiStringViewText(METAFORCE_WC_DATE); } ImGui::TableNextRow(); if (ImGui::TableNextColumn()) { ImGuiStringViewText("Type"); } if (ImGui::TableNextColumn()) { ImGuiStringViewText(METAFORCE_BUILD_TYPE); } if (g_Main != nullptr) { ImGui::TableNextRow(); if (ImGui::TableNextColumn()) { ImGuiStringViewText("Game"); } if (ImGui::TableNextColumn()) { ImGuiStringViewText(g_Main->GetVersionString()); } } ImGui::EndTable(); } } ImGui::End(); ImGui::PopStyleColor(2); } static std::string BytesToString(size_t bytes) { constexpr std::array suffixes{"B"sv, "KB"sv, "MB"sv, "GB"sv, "TB"sv, "PB"sv, "EB"sv}; u32 s = 0; auto count = static_cast(bytes); while (count >= 1024.0 && s < 7) { s++; count /= 1024.0; } if (count - floor(count) == 0.0) { return fmt::format("{}{}", static_cast(count), suffixes[s]); } return fmt::format("{:.1f}{}", count, suffixes[s]); } void ImGuiConsole::ShowDebugOverlay() { const std::array flags{ m_frameCounter && (g_StateManager != nullptr), m_frameRate, m_inGameTime && (g_StateManager != nullptr), m_roomTimer && (g_StateManager != nullptr), m_playerInfo && (g_StateManager != nullptr) && (g_StateManager->Player() != nullptr), m_worldInfo && (g_StateManager != nullptr) && m_developer, m_areaInfo && (g_StateManager != nullptr) && m_developer, m_layerInfo && (g_StateManager != nullptr) && m_developer, m_randomStats && m_developer, m_drawCallInfo && m_developer, m_bufferInfo && m_developer, m_pipelineInfo && m_developer, m_resourceStats && (g_SimplePool != nullptr), }; if (std::ranges::all_of(flags, [](const bool v) { return !v; })) { return; } const auto* stats = aurora_get_stats(); ImGuiIO& io = ImGui::GetIO(); ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; if (m_debugOverlayCorner != -1) { SetOverlayWindowLocation(m_debugOverlayCorner); windowFlags |= ImGuiWindowFlags_NoMove; } ImGui::SetNextWindowBgAlpha(0.65f); if (ImGui::Begin("Debug Overlay", nullptr, windowFlags)) { bool hasPrevious = false; if (m_frameCounter && g_StateManager != nullptr) { ImGuiStringViewText(fmt::format("Frame: {}\n", g_StateManager->GetUpdateFrameIndex())); hasPrevious = true; } if (m_frameRate) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; ImGuiStringViewText(fmt::format("FPS: {:.1f}\n", io.Framerate)); } if (m_inGameTime && g_GameState != nullptr) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; double igt = g_GameState->GetTotalPlayTime(); u32 ms = u64(igt * 1000) % 1000; auto pt = std::div(int(igt), 3600); ImGuiStringViewText( fmt::format("Play Time: {:02d}:{:02d}:{:02d}.{:03d}\n", pt.quot, pt.rem / 60, pt.rem % 60, ms)); } if (m_roomTimer && g_StateManager != nullptr) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; double igt = g_GameState->GetTotalPlayTime(); double currentRoomTime = igt - m_currentRoomStart; u32 curFrames = u32(std::round(u32(currentRoomTime * 60))); u32 lastFrames = u32(std::round(u32(m_lastRoomTime * 60))); ImGuiStringViewText(fmt::format("Room Time: {:7.3f} / {:5d} | Last Room:{:7.3f} / {:5d}\n", currentRoomTime, curFrames, m_lastRoomTime, lastFrames)); } if (m_playerInfo && g_StateManager != nullptr && g_StateManager->Player() != nullptr && m_developer) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; const CPlayer& pl = g_StateManager->GetPlayer(); const zeus::CQuaternion plQ = zeus::CQuaternion(pl.GetTransform().getRotation().buildMatrix3f()); const zeus::CTransform camXf = g_StateManager->GetCameraManager()->GetCurrentCameraTransform(*g_StateManager); const zeus::CQuaternion camQ = zeus::CQuaternion(camXf.getRotation().buildMatrix3f()); ImGuiStringViewText( fmt::format("Player Position x: {: .2f}, y: {: .2f}, z: {: .2f}\n" " Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\n" " Momentum x: {: .2f}, y: {: .2f}, z: {: .2f}\n" " Velocity x: {: .2f}, y: {: .2f}, z: {: .2f}\n" "Camera Position x: {: .2f}, y: {: .2f}, z {: .2f}\n" " Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\n", pl.GetTranslation().x(), pl.GetTranslation().y(), pl.GetTranslation().z(), zeus::radToDeg(plQ.roll()), zeus::radToDeg(plQ.pitch()), zeus::radToDeg(plQ.yaw()), pl.GetMomentum().x(), pl.GetMomentum().y(), pl.GetMomentum().z(), pl.GetVelocity().x(), pl.GetVelocity().y(), pl.GetVelocity().z(), camXf.origin.x(), camXf.origin.y(), camXf.origin.z(), zeus::radToDeg(camQ.roll()), zeus::radToDeg(camQ.pitch()), zeus::radToDeg(camQ.yaw()))); } if (m_worldInfo && g_StateManager != nullptr && m_developer) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; const std::string name = ImGuiLoadStringTable(g_StateManager->GetWorld()->IGetStringTableAssetId(), 0); ImGuiStringViewText(fmt::format("World Asset ID: 0x{}, Name: {}\n", g_GameState->CurrentWorldAssetId(), name)); } if (m_areaInfo && g_StateManager != nullptr && m_developer) { const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId(); if (g_StateManager->GetWorld() != nullptr && g_StateManager->GetWorld()->DoesAreaExist(aId)) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; const auto& layerStates = g_GameState->CurrentWorldState().GetLayerState(); std::string layerBits; u32 totalActive = 0; for (int i = 0; i < layerStates->GetAreaLayerCount(aId); ++i) { if (layerStates->IsLayerActive(aId, i)) { ++totalActive; layerBits += "1"; } else { layerBits += "0"; } } CGameArea* pArea = g_StateManager->GetWorld()->GetArea(aId); CAssetId stringId = pArea->IGetStringTableAssetId(); ImGuiStringViewText(fmt::format("Area Asset ID: 0x{}, Name: {}\nArea ID: {}, Active Layer bits: {}\n", pArea->GetAreaAssetId(), ImGuiLoadStringTable(stringId, 0), pArea->GetAreaId(), layerBits)); } } if (m_layerInfo && g_StateManager != nullptr && m_developer) { const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId(); const auto* world = g_StateManager->GetWorld(); if (world != nullptr && world->DoesAreaExist(aId) && world->GetWorldLayers()) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; ImGuiStringViewText("Area Layers:"); ImVec4 activeColor = ImGui::GetStyleColorVec4(ImGuiCol_Text); ImVec4 inactiveColor = activeColor; inactiveColor.w = 0.5f; const CWorldLayers& layers = world->GetWorldLayers().value(); const auto& layerStates = g_GameState->CurrentWorldState().GetLayerState(); int layerCount = int(layerStates->GetAreaLayerCount(aId)); u32 startNameIdx = layers.m_areas[aId].m_startNameIdx; if (startNameIdx + layerCount > layers.m_names.size()) { ImGui::Text("Broken layer data, please re-package"); } else { for (int i = 0; i < layerCount; ++i) { ImGui::PushStyleColor(ImGuiCol_Text, layerStates->IsLayerActive(aId, i) ? activeColor : inactiveColor); ImGuiStringViewText(" " + layers.m_names[startNameIdx + i]); ImGui::PopStyleColor(); } } } } if (m_randomStats && m_developer) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; ImGuiStringViewText(fmt::format("CRandom16::Next calls: {}\n", metaforce::CRandom16::GetNumNextCalls())); ImGuiStringViewText(fmt::format("CRandom16::LastSeed: 0x{:08X}\n", CRandom16::GetLastSeed())); } if (m_resourceStats && g_SimplePool != nullptr) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; ImGuiStringViewText(fmt::format("Resource Objects: {}\n", g_SimplePool->GetLiveObjects())); } if (m_pipelineInfo && m_developer) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; ImGuiStringViewText(fmt::format("Queued pipelines: {}\n", stats->queuedPipelines)); ImGuiStringViewText(fmt::format("Done pipelines: {}\n", stats->createdPipelines)); } if (m_drawCallInfo && m_developer) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; ImGuiStringViewText(fmt::format("Draw call count: {}\n", stats->drawCallCount)); ImGuiStringViewText(fmt::format("Merged draw calls: {}\n", stats->mergedDrawCallCount)); } if (m_bufferInfo && m_developer) { if (hasPrevious) { ImGui::Separator(); } hasPrevious = true; ImGuiStringViewText(fmt::format("Vertex size: {}\n", BytesToString(stats->lastVertSize))); ImGuiStringViewText(fmt::format("Uniform size: {}\n", BytesToString(stats->lastUniformSize))); ImGuiStringViewText(fmt::format("Index size: {}\n", BytesToString(stats->lastIndexSize))); ImGuiStringViewText(fmt::format("Storage size: {}\n", BytesToString(stats->lastStorageSize))); ImGuiStringViewText(fmt::format("Tex upload size: {}\n", BytesToString(stats->lastTextureUploadSize))); ImGuiStringViewText(fmt::format( "Total: {}\n", BytesToString(stats->lastVertSize + stats->lastUniformSize + stats->lastIndexSize + stats->lastStorageSize + stats->lastTextureUploadSize))); } if (ShowCornerContextMenu(m_debugOverlayCorner, m_inputOverlayCorner)) { m_cvarCommons.m_debugOverlayCorner->fromInteger(m_debugOverlayCorner); } } ImGui::End(); } void TextCenter(const std::string& text) { float font_size = ImGui::GetFontSize() * text.size() / 2; ImGui::SameLine(ImGui::GetWindowSize().x / 2 - font_size + (font_size / 2)); ImGui::TextUnformatted(text.c_str()); } void ImGuiConsole::ShowInputViewer() { if (!m_showInput || g_InputGenerator == nullptr) { return; } auto input = g_InputGenerator->GetLastInput(); if (input.ControllerIdx() != 0) { return; } u32 thisWhich = input.ControllerIdx(); if (m_whichController != thisWhich) { const char* name = PADGetName(thisWhich); if (name != nullptr) { m_controllerName = name; m_whichController = thisWhich; } } // Code -stolen- borrowed from Practice Mod ImGuiIO& io = ImGui::GetIO(); ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; if (m_inputOverlayCorner != -1) { SetOverlayWindowLocation(m_inputOverlayCorner); windowFlags |= ImGuiWindowFlags_NoMove; } ImGui::SetNextWindowBgAlpha(0.65f); if (ImGui::Begin("Input Overlay", nullptr, windowFlags)) { float scale = GetScale(); if (!m_controllerName.empty()) { TextCenter(m_controllerName); ImGui::Separator(); } ImDrawList* dl = ImGui::GetWindowDrawList(); zeus::CVector2f p = ImGui::GetCursorScreenPos(); float leftStickRadius = 30 * scale; p = p + zeus::CVector2f{20, 20} * scale; // Pad p so we don't clip outside our rect zeus::CVector2f leftStickCenter = p + zeus::CVector2f(30, 45) * scale; float dpadRadius = 15 * scale; float dpadWidth = 8 * scale; zeus::CVector2f dpadCenter = p + zeus::CVector2f(80, 90) * scale; float rightStickRadius = 20 * scale; zeus::CVector2f rightStickCenter = p + zeus::CVector2f(160, 90) * scale; float startButtonRadius = 8 * scale; zeus::CVector2f startButtonCenter = p + zeus::CVector2f(120, 55) * scale; float aButtonRadius = 16 * scale; zeus::CVector2f aButtonCenter = p + zeus::CVector2f(210, 48) * scale; float bButtonRadius = 8 * scale; zeus::CVector2f bButtonCenter = aButtonCenter + zeus::CVector2f(-24, 16) * scale; float xButtonRadius = 8 * scale; zeus::CVector2f xButtonCenter = aButtonCenter + zeus::CVector2f(24, -16) * scale; float yButtonRadius = 8 * scale; zeus::CVector2f yButtonCenter = aButtonCenter + zeus::CVector2f(-12, -24) * scale; float triggerWidth = leftStickRadius * 2; float triggerHeight = 8 * scale; zeus::CVector2f lCenter = leftStickCenter + zeus::CVector2f(0, -60) * scale; zeus::CVector2f rCenter = zeus::CVector2f(aButtonCenter.x(), lCenter.y()); const auto zButtonCenter = rCenter + zeus::CVector2f{0, 24 * scale}; const float zButtonHalfWidth = triggerWidth / 2; const float zButtonHalfHeight = 4 * scale; constexpr ImU32 stickGray = IM_COL32(150, 150, 150, 255); constexpr ImU32 darkGray = IM_COL32(60, 60, 60, 255); constexpr ImU32 red = IM_COL32(255, 0, 0, 255); constexpr ImU32 green = IM_COL32(0, 255, 0, 255); // left stick { float x = input.ALeftX(); float y = -input.ALeftY(); dl->AddCircleFilled(leftStickCenter, leftStickRadius, stickGray, 8); dl->AddLine(leftStickCenter, leftStickCenter + zeus::CVector2f(x * leftStickRadius, y * leftStickRadius), IM_COL32(255, 244, 0, 255), 1.5f); dl->AddCircleFilled(leftStickCenter + (zeus::CVector2f{x, y} * leftStickRadius), leftStickRadius / 3, red); } // right stick { float x = input.ARightX(); float y = -input.ARightY(); dl->AddCircleFilled(rightStickCenter, rightStickRadius, stickGray, 8); dl->AddLine(rightStickCenter, rightStickCenter + zeus::CVector2f(x * rightStickRadius, y * rightStickRadius), IM_COL32(255, 244, 0, 255), 1.5f); dl->AddCircleFilled(rightStickCenter + (zeus::CVector2f{x, y} * rightStickRadius), rightStickRadius / 3, red); } // dpad { float halfWidth = dpadWidth / 2; dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius), dpadCenter + zeus::CVector2f(halfWidth, dpadRadius), stickGray); dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth), dpadCenter + zeus::CVector2f(dpadRadius, halfWidth), stickGray); if (input.DDPUp()) { dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, -dpadRadius), dpadCenter + zeus::CVector2f(halfWidth, -dpadRadius / 2), red); } if (input.DDPDown()) { dl->AddRectFilled(dpadCenter + zeus::CVector2f(-halfWidth, dpadRadius), dpadCenter + zeus::CVector2f(halfWidth, dpadRadius / 2), red); } if (input.DDPLeft()) { dl->AddRectFilled(dpadCenter + zeus::CVector2f(-dpadRadius, -halfWidth), dpadCenter + zeus::CVector2f(-dpadRadius / 2, halfWidth), red); } if (input.DDPRight()) { dl->AddRectFilled(dpadCenter + zeus::CVector2f(dpadRadius, -halfWidth), dpadCenter + zeus::CVector2f(dpadRadius / 2, halfWidth), red); } } // buttons { // start dl->AddCircleFilled(startButtonCenter, startButtonRadius, input.DStart() ? red : stickGray); // a dl->AddCircleFilled(aButtonCenter, aButtonRadius, input.DA() ? green : stickGray); // b dl->AddCircleFilled(bButtonCenter, bButtonRadius, input.DB() ? red : stickGray); // x dl->AddCircleFilled(xButtonCenter, xButtonRadius, input.DX() ? red : stickGray); // y dl->AddCircleFilled(yButtonCenter, yButtonRadius, input.DY() ? red : stickGray); // z dl->AddRectFilled(zButtonCenter - zeus::CVector2f{zButtonHalfWidth, zButtonHalfHeight}, zButtonCenter + zeus::CVector2f{zButtonHalfWidth, zButtonHalfHeight}, input.DZ() ? IM_COL32(128, 0, 128, 255) : stickGray, 16); } // triggers { float halfTriggerWidth = triggerWidth / 2; zeus::CVector2f lStart = lCenter - zeus::CVector2f(halfTriggerWidth, 0); zeus::CVector2f lEnd = lCenter + zeus::CVector2f(halfTriggerWidth, triggerHeight); float lValue = triggerWidth * std::min(1.f, input.ALTrigger()); dl->AddRectFilled(lStart, lStart + zeus::CVector2f(lValue, triggerHeight), input.DL() ? red : stickGray); dl->AddRectFilled(lStart + zeus::CVector2f(lValue, 0), lEnd, darkGray); zeus::CVector2f rStart = rCenter - zeus::CVector2f(halfTriggerWidth, 0); zeus::CVector2f rEnd = rCenter + zeus::CVector2f(halfTriggerWidth, triggerHeight); float rValue = triggerWidth * std::min(1.f, input.ARTrigger()); dl->AddRectFilled(rEnd - zeus::CVector2f(rValue, triggerHeight), rEnd, input.DR() ? red : stickGray); dl->AddRectFilled(rStart, rEnd - zeus::CVector2f(rValue, 0), darkGray); } ImGui::Dummy(zeus::CVector2f(270, 130) * scale); if (ShowCornerContextMenu(m_inputOverlayCorner, m_debugOverlayCorner)) { m_cvarCommons.m_debugInputOverlayCorner->fromInteger(m_inputOverlayCorner); } } ImGui::End(); } bool ImGuiConsole::ShowCornerContextMenu(int& corner, int avoidCorner) const { bool result = false; if (ImGui::BeginPopupContextWindow()) { if (ImGui::MenuItem("Custom", nullptr, corner == -1)) { corner = -1; result = true; } if (ImGui::MenuItem("Top-left", nullptr, corner == 0, avoidCorner != 0)) { corner = 0; result = true; } if (ImGui::MenuItem("Top-right", nullptr, corner == 1, avoidCorner != 1)) { corner = 1; result = true; } if (ImGui::MenuItem("Bottom-left", nullptr, corner == 2, avoidCorner != 2)) { corner = 2; result = true; } if (ImGui::MenuItem("Bottom-right", nullptr, corner == 3, avoidCorner != 3)) { corner = 3; result = true; } ImGui::EndPopup(); } return result; } void ImGuiConsole::SetOverlayWindowLocation(int corner) const { const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImVec2 workPos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any! ImVec2 workSize = viewport->WorkSize; ImVec2 windowPos; ImVec2 windowPosPivot; constexpr float padding = 10.0f; windowPos.x = (corner & 1) != 0 ? (workPos.x + workSize.x - padding) : (workPos.x + padding); windowPos.y = (corner & 2) != 0 ? (workPos.y + workSize.y - padding) : (workPos.y + padding); windowPosPivot.x = (corner & 1) != 0 ? 1.0f : 0.0f; windowPosPivot.y = (corner & 2) != 0 ? 1.0f : 0.0f; ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, windowPosPivot); } static void ImGuiCVarMenuItem(const char* name, CVar* cvar, bool& value) { if (cvar == nullptr) { return; } if (ImGui::MenuItem(name, nullptr, &value)) { cvar->fromBoolean(value); } if (ImGui::IsItemHovered()) { std::string tooltip{cvar->rawHelp()}; if (!tooltip.empty()) { ImGui::SetTooltip("%s", tooltip.c_str()); } } } void ImGuiConsole::ShowAppMainMenuBar(bool canInspect, bool preLaunch) { if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Game")) { ShowMenuGame(); ImGui::EndMenu(); } if (ImGui::BeginMenu("Tools")) { ImGui::MenuItem("Controller Config", nullptr, &m_controllerConfigVisible); ImGui::MenuItem("Items", nullptr, &m_showItemsWindow, canInspect && m_cheats); if (m_developer) { ImGui::Separator(); ImGui::MenuItem("Console Variables", nullptr, &m_showConsoleVariablesWindow); ImGui::MenuItem("Inspect", nullptr, &m_showInspectWindow, canInspect); ImGui::MenuItem("Layers", nullptr, &m_showLayersWindow, canInspect); ImGui::MenuItem("Player Transform", nullptr, &m_showPlayerTransformEditor, canInspect && m_cheats); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Overlays")) { ImGuiCVarMenuItem("Frame Counter", m_cvarCommons.m_debugOverlayShowFrameCounter, m_frameCounter); ImGuiCVarMenuItem("Frame Rate", m_cvarCommons.m_debugOverlayShowFramerate, m_frameRate); ImGuiCVarMenuItem("In-Game Time", m_cvarCommons.m_debugOverlayShowInGameTime, m_inGameTime); ImGuiCVarMenuItem("Room Timer", m_cvarCommons.m_debugOverlayShowRoomTimer, m_roomTimer); ImGuiCVarMenuItem("Player Info", m_cvarCommons.m_debugOverlayPlayerInfo, m_playerInfo); ImGuiCVarMenuItem("World Info", m_cvarCommons.m_debugOverlayWorldInfo, m_worldInfo); ImGuiCVarMenuItem("Area Info", m_cvarCommons.m_debugOverlayAreaInfo, m_areaInfo); ImGuiCVarMenuItem("Layer Info", m_cvarCommons.m_debugOverlayLayerInfo, m_layerInfo); ImGuiCVarMenuItem("Random Stats", m_cvarCommons.m_debugOverlayShowRandomStats, m_randomStats); ImGuiCVarMenuItem("Draw Call Info", m_cvarCommons.m_debugOverlayDrawCallInfo, m_drawCallInfo); ImGuiCVarMenuItem("Pipeline Info", m_cvarCommons.m_debugOverlayPipelineInfo, m_pipelineInfo); ImGuiCVarMenuItem("Buffer Info", m_cvarCommons.m_debugOverlayBufferInfo, m_bufferInfo); ImGuiCVarMenuItem("Resource Stats", m_cvarCommons.m_debugOverlayShowResourceStats, m_resourceStats); ImGuiCVarMenuItem("Show Input", m_cvarCommons.m_debugOverlayShowInput, m_showInput); #if 0 // Currently unimplemented ImGui::Separator(); ImGuiCVarMenuItem("Draw AI Paths", m_cvarCommons.m_debugToolDrawAiPath, m_drawAiPath); ImGuiCVarMenuItem("Draw Lighting", m_cvarCommons.m_debugToolDrawLighting, m_drawLighting); ImGuiCVarMenuItem("Draw Collision Actors", m_cvarCommons.m_debugToolDrawCollisionActors, m_drawCollisionActors); ImGuiCVarMenuItem("Draw Maze Path", m_cvarCommons.m_debugToolDrawMazePath, m_drawMazePath); ImGuiCVarMenuItem("Draw Platform Collision", m_cvarCommons.m_debugToolDrawPlatformCollision, m_drawPlatformCollision); #endif ImGui::EndMenu(); } if (ImGui::BeginMenu("Help")) { ImGui::MenuItem("About", nullptr, &m_showAboutWindow, !preLaunch); if (m_developer) { ImGui::Separator(); if (ImGui::BeginMenu("ImGui")) { if (ImGui::MenuItem("Clear Settings")) { ImGui::ClearIniSettings(); } #ifndef NDEBUG ImGui::MenuItem("Show Demo", nullptr, &m_showDemoWindow); #endif ImGui::EndMenu(); } } ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } } void ImGuiConsole::ToggleVisible() { if (g_Main != nullptr) { m_isVisible ^= 1; } } void ImGuiConsole::PreUpdate() { // OPTICK_EVENT(); bool preLaunch = g_Main == nullptr; if (!m_isInitialized) { m_isInitialized = true; m_cvarCommons.m_debugOverlayShowFrameCounter->addListener([this](CVar* c) { m_frameCounter = c->toBoolean(); }); m_cvarCommons.m_debugOverlayShowFramerate->addListener([this](CVar* c) { m_frameRate = c->toBoolean(); }); m_cvarCommons.m_debugOverlayShowInGameTime->addListener([this](CVar* c) { m_inGameTime = c->toBoolean(); }); m_cvarCommons.m_debugOverlayShowRoomTimer->addListener([this](CVar* c) { m_roomTimer = c->toBoolean(); }); m_cvarCommons.m_debugOverlayPlayerInfo->addListener([this](CVar* c) { m_playerInfo = c->toBoolean(); }); m_cvarCommons.m_debugOverlayWorldInfo->addListener([this](CVar* c) { m_worldInfo = c->toBoolean(); }); m_cvarCommons.m_debugOverlayAreaInfo->addListener([this](CVar* c) { m_areaInfo = c->toBoolean(); }); m_cvarCommons.m_debugOverlayLayerInfo->addListener([this](CVar* c) { m_layerInfo = c->toBoolean(); }); m_cvarCommons.m_debugOverlayShowRandomStats->addListener([this](CVar* c) { m_randomStats = c->toBoolean(); }); m_cvarCommons.m_debugOverlayShowResourceStats->addListener([this](CVar* c) { m_resourceStats = c->toBoolean(); }); m_cvarCommons.m_debugOverlayShowInput->addListener([this](CVar* c) { m_showInput = c->toBoolean(); }); m_cvarCommons.m_debugToolDrawAiPath->addListener([this](CVar* c) { m_drawAiPath = c->toBoolean(); }); m_cvarCommons.m_debugToolDrawCollisionActors->addListener( [this](CVar* c) { m_drawCollisionActors = c->toBoolean(); }); m_cvarCommons.m_debugToolDrawPlatformCollision->addListener( [this](CVar* c) { m_drawPlatformCollision = c->toBoolean(); }); m_cvarCommons.m_debugToolDrawMazePath->addListener([this](CVar* c) { m_drawMazePath = c->toBoolean(); }); m_cvarCommons.m_debugToolDrawLighting->addListener([this](CVar* c) { m_drawLighting = c->toBoolean(); }); m_cvarCommons.m_debugOverlayCorner->addListener([this](CVar* c) { m_debugOverlayCorner = c->toSigned(); }); m_cvarCommons.m_debugInputOverlayCorner->addListener([this](CVar* c) { m_inputOverlayCorner = c->toSigned(); }); m_cvarCommons.m_lastDiscPath->addListener([this](CVar* c) { m_lastDiscPath = c->toLiteral(); }); m_cvarMgr.findCVar("developer")->addListener([this](CVar* c) { m_developer = c->toBoolean(); }); m_cvarMgr.findCVar("cheats")->addListener([this](CVar* c) { m_cheats = c->toBoolean(); }); } if (!preLaunch && !m_isLaunchInitialized) { if (m_developer) { m_toasts.emplace_back("Press Left Alt to toggle menu"s, 5.f); } m_isLaunchInitialized = true; } // We need to make sure we have a valid CRandom16 at all times, so let's do that here if (g_StateManager != nullptr && g_StateManager->GetActiveRandom() == nullptr) { g_StateManager->SetActiveRandomToDefault(); } if (!preLaunch) { if (m_stepFrame) { g_Main->SetPaused(true); m_stepFrame = false; } if (m_paused && !m_stepFrame && ImGui::IsKeyPressed(ImGuiKey_F6)) { g_Main->SetPaused(false); m_stepFrame = true; } if (ImGui::IsKeyReleased(ImGuiKey_F5)) { m_paused ^= 1; g_Main->SetPaused(m_paused); } } bool canInspect = g_StateManager != nullptr && g_StateManager->GetObjectList(); if (preLaunch || m_isVisible) { ShowAppMainMenuBar(canInspect, preLaunch); } ShowToasts(); if (canInspect && (m_showInspectWindow || !inspectingEntities.empty())) { UpdateEntityEntries(); if (m_showInspectWindow) { ShowInspectWindow(&m_showInspectWindow); } auto iter = inspectingEntities.begin(); while (iter != inspectingEntities.end()) { if (!ShowEntityInfoWindow(*iter)) { iter = inspectingEntities.erase(iter); } else { iter++; } } } if (canInspect && m_showItemsWindow && m_cvarMgr.findCVar("cheats")->toBoolean()) { ShowItemsWindow(); } if (canInspect && m_showLayersWindow) { ShowLayersWindow(); } if (preLaunch || m_showAboutWindow) { ShowAboutWindow(preLaunch); } if (m_showDemoWindow) { ImGui::ShowDemoWindow(&m_showDemoWindow); } if (m_showConsoleVariablesWindow) { ShowConsoleVariablesWindow(); } ShowPlayerTransformEditor(); m_controllerConfig.show(m_controllerConfigVisible); if (preLaunch && m_showPreLaunchSettingsWindow) { ShowPreLaunchSettingsWindow(); } } void ImGuiConsole::PostUpdate() { // OPTICK_EVENT(); if (g_StateManager != nullptr && g_StateManager->GetObjectList()) { // Clear deleted objects CObjectList& list = g_StateManager->GetAllObjectList(); for (s16 uid = 0; uid < s16(entities.size()); uid++) { ImGuiEntityEntry& item = entities[uid]; if (item.uid == kInvalidUniqueId) { continue; // already cleared } CEntity* ent = list.GetObjectByIndex(uid); if (ent == nullptr || ent != item.ent) { // Remove inspect windows for deleted entities inspectingEntities.erase(item.uid); item.uid = kInvalidUniqueId; item.ent = nullptr; // for safety } } } else { entities.fill(ImGuiEntityEntry{}); inspectingEntities.clear(); } // Always calculate room time regardless of if the overlay is displayed, this allows us have an accurate display if // the user chooses to display it later on during gameplay if (g_StateManager != nullptr && m_currentRoom != g_StateManager->GetCurrentArea()) { const double igt = g_GameState->GetTotalPlayTime(); m_currentRoom = static_cast(g_StateManager->GetCurrentArea()); m_lastRoomTime = igt - m_currentRoomStart; m_currentRoomStart = igt; } } void ImGuiConsole::PostDraw() { ShowDebugOverlay(); ShowInputViewer(); ShowPipelineProgress(); } void ImGuiConsole::Shutdown() { dummyWorlds.clear(); stringTables.clear(); } static constexpr std::array GeneralItems{ CPlayerState::EItemType::EnergyTanks, CPlayerState::EItemType::CombatVisor, CPlayerState::EItemType::ScanVisor, CPlayerState::EItemType::ThermalVisor, CPlayerState::EItemType::XRayVisor, CPlayerState::EItemType::GrappleBeam, CPlayerState::EItemType::SpaceJumpBoots, CPlayerState::EItemType::PowerSuit, CPlayerState::EItemType::VariaSuit, CPlayerState::EItemType::GravitySuit, CPlayerState::EItemType::PhazonSuit, }; static constexpr std::array WeaponItems{ CPlayerState::EItemType::Missiles, CPlayerState::EItemType::PowerBeam, CPlayerState::EItemType::IceBeam, CPlayerState::EItemType::WaveBeam, CPlayerState::EItemType::PlasmaBeam, CPlayerState::EItemType::SuperMissile, CPlayerState::EItemType::Flamethrower, CPlayerState::EItemType::IceSpreader, CPlayerState::EItemType::Wavebuster, CPlayerState::EItemType::ChargeBeam, }; static constexpr std::array MorphBallItems{ CPlayerState::EItemType::PowerBombs, CPlayerState::EItemType::MorphBall, CPlayerState::EItemType::MorphBallBombs, CPlayerState::EItemType::BoostBall, CPlayerState::EItemType::SpiderBall, }; static constexpr std::array ArtifactItems{ CPlayerState::EItemType::Truth, CPlayerState::EItemType::Strength, CPlayerState::EItemType::Elder, CPlayerState::EItemType::Wild, CPlayerState::EItemType::Lifegiver, CPlayerState::EItemType::Warrior, CPlayerState::EItemType::Chozo, CPlayerState::EItemType::Nature, CPlayerState::EItemType::Sun, CPlayerState::EItemType::World, CPlayerState::EItemType::Spirit, CPlayerState::EItemType::Newborn, }; static constexpr std::array ItemLoadout21Percent{ std::make_pair(CPlayerState::EItemType::PowerSuit, 1), std::make_pair(CPlayerState::EItemType::CombatVisor, 1), std::make_pair(CPlayerState::EItemType::ScanVisor, 1), std::make_pair(CPlayerState::EItemType::PowerBeam, 1), std::make_pair(CPlayerState::EItemType::WaveBeam, 1), std::make_pair(CPlayerState::EItemType::IceBeam, 1), std::make_pair(CPlayerState::EItemType::PlasmaBeam, 1), std::make_pair(CPlayerState::EItemType::XRayVisor, 1), std::make_pair(CPlayerState::EItemType::Missiles, 5), std::make_pair(CPlayerState::EItemType::VariaSuit, 1), std::make_pair(CPlayerState::EItemType::PhazonSuit, 1), std::make_pair(CPlayerState::EItemType::MorphBall, 1), std::make_pair(CPlayerState::EItemType::MorphBallBombs, 1), std::make_pair(CPlayerState::EItemType::PowerBombs, 4), }; static constexpr std::array ItemLoadoutAnyPercent{ std::make_pair(CPlayerState::EItemType::PowerSuit, 1), std::make_pair(CPlayerState::EItemType::CombatVisor, 1), std::make_pair(CPlayerState::EItemType::ScanVisor, 1), std::make_pair(CPlayerState::EItemType::EnergyTanks, 3), std::make_pair(CPlayerState::EItemType::PowerBeam, 1), std::make_pair(CPlayerState::EItemType::WaveBeam, 1), std::make_pair(CPlayerState::EItemType::IceBeam, 1), std::make_pair(CPlayerState::EItemType::PlasmaBeam, 1), std::make_pair(CPlayerState::EItemType::ChargeBeam, 1), std::make_pair(CPlayerState::EItemType::XRayVisor, 1), std::make_pair(CPlayerState::EItemType::ThermalVisor, 1), std::make_pair(CPlayerState::EItemType::Missiles, 25), std::make_pair(CPlayerState::EItemType::VariaSuit, 1), std::make_pair(CPlayerState::EItemType::PhazonSuit, 1), std::make_pair(CPlayerState::EItemType::MorphBall, 1), std::make_pair(CPlayerState::EItemType::BoostBall, 1), std::make_pair(CPlayerState::EItemType::MorphBallBombs, 1), std::make_pair(CPlayerState::EItemType::PowerBombs, 4), std::make_pair(CPlayerState::EItemType::SpaceJumpBoots, 1), }; int roundMultiple(int value, int multiple) { if (multiple == 0) { return value; } return static_cast(std::round(static_cast(value) / static_cast(multiple)) * static_cast(multiple)); } static void RenderItemType(CPlayerState& pState, CPlayerState::EItemType itemType) { u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType); std::string name{CPlayerState::ItemTypeToName(itemType)}; if (maxValue == 1) { bool enabled = pState.GetItemCapacity(itemType) == 1; if (ImGui::Checkbox(name.c_str(), &enabled)) { if (enabled) { pState.ReInitializePowerUp(itemType, 1); pState.ResetAndIncrPickUp(itemType, 1); } else { pState.ReInitializePowerUp(itemType, 0); } if (itemType == CPlayerState::EItemType::VariaSuit || itemType == CPlayerState::EItemType::PowerSuit || itemType == CPlayerState::EItemType::GravitySuit || itemType == CPlayerState::EItemType::PhazonSuit) { g_StateManager->Player()->AsyncLoadSuit(*g_StateManager); } } } else if (maxValue > 1) { int capacity = int(pState.GetItemCapacity(itemType)); int amount = int(pState.GetItemAmount(itemType)); if (ImGui::SliderInt((name + " (Capacity)").c_str(), &capacity, 0, int(maxValue), "%d", ImGuiSliderFlags_AlwaysClamp)) { if (itemType == CPlayerState::EItemType::Missiles) { capacity = roundMultiple(capacity, 5); } pState.ReInitializePowerUp(itemType, u32(capacity)); pState.ResetAndIncrPickUp(itemType, u32(capacity)); } if (capacity > 0) { if (ImGui::SliderInt((name + " (Amount)").c_str(), &amount, 0, capacity, "%d", ImGuiSliderFlags_AlwaysClamp)) { if (itemType == CPlayerState::EItemType::Missiles) { amount = roundMultiple(amount, 5); } pState.ResetAndIncrPickUp(itemType, u32(amount)); } } else { ImGui::Dummy(ImGui::GetItemRectSize()); } } } template static inline void RenderItemsDualColumn(CPlayerState& pState, const std::array& items, int start) { ImGui::BeginGroup(); // Render left group for (int i = start; i < items.size(); i += 2) { RenderItemType(pState, items[i]); } ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); // Render right group for (int i = start + 1; i < items.size(); i += 2) { RenderItemType(pState, items[i]); } ImGui::EndGroup(); } void ImGuiConsole::ShowItemsWindow() { CPlayerState& pState = *g_StateManager->GetPlayerState(); if (ImGui::Begin("Items", &m_showItemsWindow, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::Button("Refill")) { for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) { auto itemType = static_cast(i); u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType); pState.ResetAndIncrPickUp(itemType, maxValue); } } auto& mapWorldInfo = *g_GameState->CurrentWorldState().MapWorldInfo(); ImGui::SameLine(); bool mapStationUsed = mapWorldInfo.GetMapStationUsed(); if (ImGui::Checkbox("Area map", &mapStationUsed)) { mapWorldInfo.SetMapStationUsed(mapStationUsed); } if (ImGui::Button("All")) { for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) { auto itemType = static_cast(i); u32 maxValue = CPlayerState::GetPowerUpMaxValue(itemType); pState.ReInitializePowerUp(itemType, maxValue); pState.ResetAndIncrPickUp(itemType, maxValue); } mapWorldInfo.SetMapStationUsed(true); } ImGui::SameLine(); if (ImGui::Button("None")) { for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) { auto itemType = static_cast(i); pState.ReInitializePowerUp(itemType, 0); } mapWorldInfo.SetMapStationUsed(false); } ImGui::SameLine(); if (ImGui::Button("21%")) { for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) { auto itemType = static_cast(i); pState.ReInitializePowerUp(itemType, 0); } mapWorldInfo.SetMapStationUsed(false); for (const auto& [item, count] : ItemLoadout21Percent) { pState.ReInitializePowerUp(item, count); pState.IncrPickup(item, count); } for (const auto& item : ArtifactItems) { pState.ReInitializePowerUp(item, 1); } } ImGui::SameLine(); if (ImGui::Button("Any%")) { for (int i = 0; i < int(CPlayerState::EItemType::Max); ++i) { auto itemType = static_cast(i); pState.ReInitializePowerUp(itemType, 0); } mapWorldInfo.SetMapStationUsed(false); for (const auto& [item, count] : ItemLoadoutAnyPercent) { pState.ReInitializePowerUp(item, count); pState.IncrPickup(item, count); } for (const auto& item : ArtifactItems) { pState.ReInitializePowerUp(item, 1); } } if (ImGui::BeginTabBar("Items")) { if (ImGui::BeginTabItem("General")) { RenderItemType(pState, GeneralItems[0]); // full width RenderItemsDualColumn(pState, GeneralItems, 1); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Weapons")) { RenderItemType(pState, WeaponItems[0]); // full width RenderItemsDualColumn(pState, WeaponItems, 1); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Morph Ball")) { RenderItemType(pState, MorphBallItems[0]); // full width RenderItemsDualColumn(pState, MorphBallItems, 1); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Artifacts")) { ImGui::Text("NOTE: This doesn't affect Artifact Temple layers"); ImGui::Text("Use the Layers window to set them for progression"); RenderItemsDualColumn(pState, ArtifactItems, 0); ImGui::EndTabItem(); } ImGui::EndTabBar(); } } ImGui::End(); } void ImGuiConsole::ShowLayersWindow() { // For some reason the window shows up tiny without this float initialWindowSize = 350.f * GetScale(); ImGui::SetNextWindowSize(ImVec2{initialWindowSize, initialWindowSize}, ImGuiCond_FirstUseEver); if (ImGui::Begin("Layers", &m_showLayersWindow)) { if (ImGui::Button("Clear")) { m_layersFilterText.clear(); } ImGui::SameLine(); ImGui::InputText("Filter", &m_layersFilterText); bool hasSearch = !m_layersFilterText.empty(); if (hasSearch) { // kinda hacky way reset the tree state when search changes ImGui::PushID(m_layersFilterText.c_str()); } for (const auto& world : ListWorlds()) { const auto& layers = dummyWorlds[world.second]->GetWorldLayers(); if (!layers) { continue; } auto worldLayerState = g_GameState->StateForWorld(world.second).GetLayerState(); auto areas = ListAreas(world.second); auto iter = areas.begin(); while (iter != areas.end()) { if (hasSearch && !ContainsCaseInsensitive(iter->first, m_layersFilterText)) { iter = areas.erase(iter); } else { iter++; } } if (areas.empty()) { continue; } if (ImGui::TreeNodeEx(world.first.c_str(), hasSearch ? ImGuiTreeNodeFlags_DefaultOpen : 0)) { for (const auto& area : areas) { u32 layerCount = worldLayerState->GetAreaLayerCount(area.second); if (layerCount == 0) { continue; } if (ImGui::TreeNode(area.first.c_str())) { if (ImGui::Button("Warp here")) { Warp(world.second, area.second); } u32 startNameIdx = layers->m_areas[area.second].m_startNameIdx; if (startNameIdx + layerCount > layers->m_names.size()) { ImGui::Text("Broken layer data, please re-package"); } else { for (int layer = 0; layer < layerCount; ++layer) { bool active = worldLayerState->IsLayerActive(area.second, layer); if (ImGui::Checkbox(layers->m_names[startNameIdx + layer].c_str(), &active)) { worldLayerState->SetLayerActive(area.second, layer, active); } } } ImGui::TreePop(); } } ImGui::TreePop(); } } if (hasSearch) { ImGui::PopID(); } } ImGui::End(); } void ImGuiConsole::ShowToasts() { if (m_toasts.empty()) { return; } auto& toast = m_toasts.front(); const float dt = ImGui::GetIO().DeltaTime; toast.remain -= dt; toast.current += dt; const ImGuiViewport* viewport = ImGui::GetMainViewport(); const ImVec2 workPos = viewport->WorkPos; const ImVec2 workSize = viewport->WorkSize; constexpr float padding = 10.0f; const ImVec2 windowPos{workPos.x + workSize.x / 2, workPos.y + workSize.y - padding}; ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, ImVec2{0.5f, 1.f}); const float alpha = std::min({toast.remain, toast.current, 1.f}); ImGui::SetNextWindowBgAlpha(alpha * 0.65f); ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text); textColor.w *= alpha; ImVec4 borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border); borderColor.w *= alpha; ImGui::PushStyleColor(ImGuiCol_Text, textColor); ImGui::PushStyleColor(ImGuiCol_Border, borderColor); if (ImGui::Begin("Toast", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove)) { ImGuiStringViewText(toast.message); } ImGui::End(); ImGui::PopStyleColor(2); if (toast.remain <= 0.f) { m_toasts.pop_front(); } } void ImGuiConsole::ShowPlayerTransformEditor() { if (!m_showPlayerTransformEditor) { return; } if (ImGui::Begin("Player Transform", &m_showPlayerTransformEditor, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::CollapsingHeader("Position")) { ImGui::PushID("player_position"); zeus::CVector3f vec = g_StateManager->GetPlayer().GetTranslation(); if (ImGuiVector3fInput("Position", vec)) { g_StateManager->GetPlayer().SetTranslation(vec); } if (ImGui::Button("Save")) { m_savedLocation.emplace(vec); } ImGui::SameLine(); if (ImGui::Button("Load") && m_savedLocation) { g_StateManager->GetPlayer().SetTranslation(*m_savedLocation); } ImGui::SameLine(); if (ImGui::Button("Clear") && m_savedLocation) { m_savedLocation.reset(); } if (m_savedLocation) { ImGui::Text("Saved: %g, %g, %g", float(m_savedLocation->x()), float(m_savedLocation->y()), float(m_savedLocation->z())); } ImGui::PopID(); } if (ImGui::CollapsingHeader("Rotation")) { ImGui::PushID("player_rotation"); zeus::CEulerAngles angles(g_StateManager->GetPlayer().GetTransform()); angles = zeus::CEulerAngles(angles * zeus::skRadToDegVec); if (ImGuiVector3fInput("Rotation", angles)) { angles.x() = zeus::clamp(-179.999f, float(angles.x()), 179.999f); angles.y() = zeus::clamp(-89.999f, float(angles.y()), 89.999f); angles.z() = zeus::clamp(-179.999f, float(angles.z()), 179.999f); auto xf = g_StateManager->GetPlayer().GetTransform(); xf.setRotation(zeus::CQuaternion(angles * zeus::skDegToRadVec).toTransform().buildMatrix3f()); g_StateManager->GetPlayer().SetTransform(xf); } if (ImGui::Button("Save")) { m_savedRotation.emplace(angles); } ImGui::SameLine(); if (ImGui::Button("Load") && m_savedRotation) { auto xf = g_StateManager->GetPlayer().GetTransform(); xf.setRotation(zeus::CQuaternion((*m_savedRotation) * zeus::skDegToRadVec).toTransform().buildMatrix3f()); g_StateManager->GetPlayer().SetTransform(xf); } ImGui::SameLine(); if (ImGui::Button("Clear") && m_savedRotation) { m_savedRotation.reset(); } if (m_savedRotation) { ImGui::Text("Saved: %g, %g, %g", float(m_savedRotation->x()), float(m_savedRotation->y()), float(m_savedRotation->z())); } ImGui::PopID(); } } ImGui::End(); } void ImGuiConsole::ShowPipelineProgress() { const auto* stats = aurora_get_stats(); const u32 queuedPipelines = stats->queuedPipelines; if (queuedPipelines == 0) { return; } const u32 createdPipelines = stats->createdPipelines; const u32 totalPipelines = queuedPipelines + createdPipelines; const auto* viewport = ImGui::GetMainViewport(); const auto padding = viewport->WorkPos.y + 10.f; const auto halfWidth = viewport->GetWorkCenter().x; ImGui::SetNextWindowPos(ImVec2{halfWidth, padding}, ImGuiCond_Always, ImVec2{0.5f, 0.f}); ImGui::SetNextWindowSize(ImVec2{halfWidth, 0.f}, ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.65f); ImGui::Begin("Pipelines", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing); const auto percent = static_cast(createdPipelines) / static_cast(totalPipelines); const auto progressStr = fmt::format("Processing pipelines: {} / {}", createdPipelines, totalPipelines); const auto textSize = ImGui::CalcTextSize(progressStr.data(), progressStr.data() + progressStr.size()); ImGui::NewLine(); ImGui::SameLine(ImGui::GetWindowWidth() / 2.f - textSize.x + textSize.x / 2.f); ImGuiStringViewText(progressStr); ImGui::ProgressBar(percent); ImGui::End(); } void ImGuiConsole::ControllerAdded(uint32_t idx) { const char* name = PADGetName(idx); if (name != nullptr) { m_toasts.emplace_back(fmt::format("Controller {} ({}) connected", idx, name), 5.f); } else { m_toasts.emplace_back(fmt::format("Controller {} connected", idx), 5.f); } } void ImGuiConsole::ControllerRemoved(uint32_t idx) { m_toasts.emplace_back(fmt::format("Controller {} disconnected", idx), 5.f); } static void ImGuiCVarCheckbox(CVarManager& mgr, std::string_view cvarName, const char* label, bool* ptr = nullptr) { auto* cvar = mgr.findCVar(cvarName); if (cvar != nullptr) { bool value = cvar->toBoolean(); bool modified = false; if (ptr == nullptr) { modified = ImGui::Checkbox(label, &value); } else { modified = ImGui::Checkbox(label, ptr); value = *ptr; } // Kinda useless for these tbh // std::string tooltip{cvar->rawHelp()}; // if (!tooltip.empty() && ImGui::IsItemHovered()) { // ImGui::SetTooltip("%s", tooltip.c_str()); // } if (modified) { cvar->unlock(); cvar->fromBoolean(value); cvar->lock(); } } } void ImGuiConsole::ShowPreLaunchSettingsWindow() { if (ImGui::Begin("Settings", &m_showPreLaunchSettingsWindow, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::BeginTabBar("Settings")) { if (ImGui::BeginTabItem("Graphics")) { size_t backendCount = 0; const auto* backends = aurora_get_available_backends(&backendCount); ImGuiStringViewText(fmt::format("Current backend: {}", backend_name(aurora_get_backend()))); auto desiredBackend = static_cast(BACKEND_AUTO); if (auto* cvar = m_cvarMgr.findCVar("graphicsApi")) { bool valid = false; const auto name = cvar->toLiteral(&valid); if (valid) { desiredBackend = static_cast(backend_from_string(name)); } } bool modified = false; modified = ImGui::RadioButton("Auto", &desiredBackend, static_cast(BACKEND_AUTO)); for (size_t i = 0; i < backendCount; ++i, ++backends) { const auto backend = *backends; modified = ImGui::RadioButton(backend_name(backend).data(), &desiredBackend, static_cast(backend)) || modified; } if (modified) { m_cvarCommons.m_graphicsApi->fromLiteral(backend_to_string(static_cast(desiredBackend))); } ImGuiCVarCheckbox(m_cvarMgr, "fullscreen", "Fullscreen"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Game")) { ImGuiCVarCheckbox(m_cvarMgr, "allowJoystickInBackground", "Enable Background Joystick Input"); ImGuiCVarCheckbox(m_cvarMgr, "tweak.game.SplashScreensDisabled", "Skip Splash Screens"); ImGuiCVarCheckbox(m_cvarMgr, "cheats", "Enable Cheats", &m_cheats); if (m_cheats) { ImGuiCVarCheckbox(m_cvarMgr, "developer", "Developer Mode", &m_developer); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Experimental")) { ImGuiCVarCheckbox(m_cvarMgr, "variableDt", "Variable Delta Time (broken)"); ImGui::EndTabItem(); } ImGui::EndTabBar(); } } ImGui::End(); } static bool eq(std::string_view a, std::string_view b) { if (a.size() != b.size()) { return false; } return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == b; }); } AuroraBackend backend_from_string(const std::string& str) { if (eq(str, "d3d12"sv) || eq(str, "d3d"sv)) { return BACKEND_D3D12; } if (eq(str, "d3d11")) { return BACKEND_D3D11; } if (eq(str, "metal"sv)) { return BACKEND_METAL; } if (eq(str, "vulkan"sv) || eq(str, "vk"sv)) { return BACKEND_VULKAN; } if (eq(str, "opengl"sv) || eq(str, "gl"sv)) { return BACKEND_OPENGL; } if (eq(str, "opengles"sv) || eq(str, "gles"sv)) { return BACKEND_OPENGLES; } if (eq(str, "webgpu"sv) || eq(str, "wgpu"sv)) { return BACKEND_WEBGPU; } if (eq(str, "null"sv) || eq(str, "none"sv)) { return BACKEND_NULL; } return BACKEND_AUTO; } std::string_view backend_to_string(AuroraBackend backend) { switch (backend) { default: return "auto"sv; case BACKEND_D3D12: return "d3d12"sv; case BACKEND_D3D11: return "d3d11"sv; case BACKEND_METAL: return "metal"sv; case BACKEND_VULKAN: return "vulkan"sv; case BACKEND_OPENGL: return "opengl"sv; case BACKEND_OPENGLES: return "opengles"sv; case BACKEND_WEBGPU: return "webgpu"sv; case BACKEND_NULL: return "null"sv; } } std::string_view backend_name(AuroraBackend backend) { switch (backend) { default: return "Auto"sv; case BACKEND_D3D12: return "D3D12"sv; case BACKEND_D3D11: return "D3D11"sv; case BACKEND_METAL: return "Metal"sv; case BACKEND_VULKAN: return "Vulkan"sv; case BACKEND_OPENGL: return "OpenGL"sv; case BACKEND_OPENGLES: return "OpenGL ES"sv; case BACKEND_WEBGPU: return "WebGPU"sv; case BACKEND_NULL: return "Null"sv; } } } // namespace metaforce ================================================ FILE: Runtime/ImGuiConsole.hpp ================================================ #pragma once #include #include #include #include "Runtime/RetroTypes.hpp" #include "Runtime/World/CActor.hpp" #include "Runtime/World/CEntity.hpp" #include "Runtime/ImGuiPlayerLoadouts.hpp" #include "Runtime/ImGuiControllerConfig.hpp" #include "Runtime/ConsoleVariables/CVarCommons.hpp" #include "Runtime/ConsoleVariables/CVarManager.hpp" #include #include #if __APPLE__ #include #endif namespace metaforce { void ImGuiStringViewText(std::string_view text); void ImGuiTextCenter(std::string_view text); std::string ImGuiLoadStringTable(CAssetId stringId, int idx); struct ImGuiEntityEntry { TUniqueId uid = kInvalidUniqueId; CEntity* ent = nullptr; std::string_view type; std::string_view name; bool active = false; bool isActor = false; ImGuiEntityEntry() = default; ImGuiEntityEntry(TUniqueId uid, CEntity* ent, std::string_view type, std::string_view name, bool active) : uid(uid), ent(ent), type(type), name(name), active(active) {} [[nodiscard]] CActor* AsActor() const { return isActor ? static_cast(ent) : nullptr; } }; struct Toast { std::string message; float remain; float current = 0.f; Toast(std::string message, float duration) noexcept : message(std::move(message)), remain(duration) {} }; class ImGuiConsole { public: static std::set inspectingEntities; static std::array entities; static ImGuiPlayerLoadouts loadouts; ImGuiConsole(CVarManager& cvarMgr, CVarCommons& cvarCommons); void PreUpdate(); void PostUpdate(); void PostDraw(); void Shutdown(); static void BeginEntityRow(const ImGuiEntityEntry& entry); static void EndEntityRow(const ImGuiEntityEntry& entry); void ControllerAdded(uint32_t idx); void ControllerRemoved(uint32_t idx); void ToggleVisible(); std::optional m_errorString; std::optional m_gameDiscSelected; bool m_quitRequested = false; private: CVarManager& m_cvarMgr; CVarCommons& m_cvarCommons; bool m_showInspectWindow = false; bool m_showDemoWindow = false; bool m_showAboutWindow = false; bool m_showItemsWindow = false; bool m_showLayersWindow = false; bool m_showConsoleVariablesWindow = false; bool m_showPlayerTransformEditor = false; bool m_showPreLaunchSettingsWindow = false; std::optional m_savedLocation; std::optional m_savedRotation; bool m_paused = false; bool m_stepFrame = false; bool m_isVisible = false; bool m_inspectActiveOnly = false; bool m_inspectCurrentAreaOnly = false; std::string m_inspectFilterText; std::string m_layersFilterText; std::string m_cvarFiltersText; std::string m_lastDiscPath = m_cvarCommons.m_lastDiscPath->toLiteral(); // Debug overlays bool m_frameCounter = m_cvarCommons.m_debugOverlayShowFrameCounter->toBoolean(); #if TARGET_OS_TV bool m_frameRate = true; #else bool m_frameRate = m_cvarCommons.m_debugOverlayShowFramerate->toBoolean(); #endif bool m_inGameTime = m_cvarCommons.m_debugOverlayShowInGameTime->toBoolean(); bool m_roomTimer = m_cvarCommons.m_debugOverlayShowRoomTimer->toBoolean(); bool m_playerInfo = m_cvarCommons.m_debugOverlayPlayerInfo->toBoolean(); bool m_worldInfo = m_cvarCommons.m_debugOverlayWorldInfo->toBoolean(); bool m_areaInfo = m_cvarCommons.m_debugOverlayAreaInfo->toBoolean(); bool m_layerInfo = m_cvarCommons.m_debugOverlayLayerInfo->toBoolean(); bool m_randomStats = m_cvarCommons.m_debugOverlayShowRandomStats->toBoolean(); bool m_resourceStats = m_cvarCommons.m_debugOverlayShowResourceStats->toBoolean(); bool m_showInput = m_cvarCommons.m_debugOverlayShowInput->toBoolean(); bool m_drawAiPath = m_cvarCommons.m_debugToolDrawAiPath->toBoolean(); bool m_drawCollisionActors = m_cvarCommons.m_debugToolDrawCollisionActors->toBoolean(); bool m_drawPlatformCollision = m_cvarCommons.m_debugToolDrawPlatformCollision->toBoolean(); bool m_drawMazePath = m_cvarCommons.m_debugToolDrawMazePath->toBoolean(); bool m_drawLighting = m_cvarCommons.m_debugToolDrawLighting->toBoolean(); #if TARGET_OS_IOS bool m_pipelineInfo = false; bool m_drawCallInfo = false; bool m_bufferInfo = false; #else bool m_pipelineInfo = m_cvarCommons.m_debugOverlayPipelineInfo->toBoolean(); // TODO cvar bool m_drawCallInfo = m_cvarCommons.m_debugOverlayDrawCallInfo->toBoolean(); // TODO cvar bool m_bufferInfo = m_cvarCommons.m_debugOverlayBufferInfo->toBoolean(); // TODO cvar #endif bool m_developer = m_cvarMgr.findCVar("developer")->toBoolean(); bool m_cheats = m_cvarMgr.findCVar("cheats")->toBoolean(); bool m_isInitialized = false; bool m_isLaunchInitialized = false; int m_debugOverlayCorner = m_cvarCommons.m_debugOverlayCorner->toSigned(); int m_inputOverlayCorner = m_cvarCommons.m_debugInputOverlayCorner->toSigned(); const void* m_currentRoom = nullptr; double m_lastRoomTime = 0.f; double m_currentRoomStart = 0.f; std::deque m_toasts; std::string m_controllerName; u32 m_whichController = -1; bool m_controllerConfigVisible = false; ImGuiControllerConfig m_controllerConfig; void ShowAboutWindow(bool preLaunch); void ShowAppMainMenuBar(bool canInspect, bool preLaunch); void ShowMenuGame(); bool ShowEntityInfoWindow(TUniqueId uid); void ShowInspectWindow(bool* isOpen); void LerpDebugColor(CActor* act); void UpdateEntityEntries(); void ShowDebugOverlay(); void ShowItemsWindow(); void ShowLayersWindow(); void ShowConsoleVariablesWindow(); void ShowToasts(); void ShowInputViewer(); void SetOverlayWindowLocation(int corner) const; bool ShowCornerContextMenu(int& corner, int avoidCorner) const; void ShowPlayerTransformEditor(); void ShowPipelineProgress(); void ShowPreLaunchSettingsWindow(); }; AuroraBackend backend_from_string(const std::string& str); std::string_view backend_to_string(AuroraBackend backend); std::string_view backend_name(AuroraBackend backend); } // namespace metaforce ================================================ FILE: Runtime/ImGuiControllerConfig.cpp ================================================ #include "Runtime/ImGuiControllerConfig.hpp" #include "Runtime/RetroTypes.hpp" #include "Runtime/Streams/CFileOutStream.hpp" #include "Runtime/Streams/ContainerReaders.hpp" #include "Runtime/Streams/ContainerWriters.hpp" #include "Runtime/Formatting.hpp" #include namespace metaforce { ImGuiControllerConfig::Button::Button(CInputStream& in) : button(in.Get()) , uvX(in.Get()) , uvY(in.Get()) , width(in.Get()) , height(in.Get()) , offX(in.Get()) , offY(in.Get()) {} void ImGuiControllerConfig::Button::PutTo(COutputStream& out) const { out.Put(button); out.Put(uvX); out.Put(uvY); out.Put(width); out.Put(height); out.Put(offX); out.Put(offY); } ImGuiControllerConfig::ControllerAtlas::ControllerAtlas(CInputStream& in) : name(in.Get()) { u32 vidPidCount = in.Get(); vidPids.reserve(vidPidCount); for (u32 i = 0; i < vidPidCount; ++i) { u16 vid = static_cast(in.Get()); u16 pid = static_cast(in.Get()); vidPids.emplace_back(vid, pid); } atlasFile = in.Get(); read_vector(buttons, in); }; void ImGuiControllerConfig::ControllerAtlas::PutTo(COutputStream& out) const { out.Put(name); out.Put(static_cast(vidPids.size())); for (const auto& vidPid : vidPids) { out.Put(vidPid.first); out.Put(vidPid.second); } write_vector(buttons, out); } void ImGuiControllerConfig::show(bool& visible) { /** TODO: * - Implement multiple controllers * - Implement setting controller ports (except for the GameCube adapter, which is hard coded) * - Implement fancy graphical UI */ if (!visible) { return; } if (m_pendingMapping != nullptr) { s32 nativeButton = PADGetNativeButtonPressed(m_pendingPort); if (nativeButton != -1) { m_pendingMapping->nativeButton = nativeButton; m_pendingMapping = nullptr; m_pendingPort = -1; PADBlockInput(false); } } std::vector controllers; controllers.push_back("None"); for (u32 i = 0; i < PADCount(); ++i) { controllers.push_back(fmt::format("{}-{}", PADGetNameForControllerIndex(i), i)); } m_pendingValid = false; if (ImGui::Begin("Controller Config", &visible)) { if (ImGui::CollapsingHeader("Ports")) { for (u32 i = 0; i < 4; ++i) { ImGui::PushID(fmt::format("PortConf-{}", i).c_str()); s32 index = PADGetIndexForPort(i); int sel = 0; std::string name = "None"; const char* tmpName = PADGetName(i); bool changed = false; if (tmpName != nullptr) { name = fmt::format("{}-{}", tmpName, index); } if (ImGui::BeginCombo(fmt::format("Port {}", i + 1).c_str(), name.c_str())) { for (u32 j = 0; const auto& s : controllers) { if (ImGui::Selectable(s.c_str(), name == s)) { sel = j; changed = true; } ++j; } ImGui::EndCombo(); } if (changed) { if (sel > 0) { PADSetPortForIndex(sel - 1, i); } else if (sel == 0) { PADClearPort(i); } } ImGui::PopID(); } } if (ImGui::BeginTabBar("Controllers")) { for (u32 i = 0; i < 4; ++i) { if (ImGui::BeginTabItem(fmt::format("Port {}", i + 1).c_str())) { ImGui::PushID(fmt::format("Port_{}", i + 1).c_str()); /* If the tab is changed while pending for input, cancel the pending port */ if (m_pendingMapping != nullptr && m_pendingPort != i) { m_pendingMapping = nullptr; m_pendingValid = false; m_pendingPort = -1; PADBlockInput(false); } u32 vid, pid; PADGetVidPid(i, &vid, &pid); if (vid == 0 && pid == 0) { ImGui::EndTabItem(); ImGui::PopID(); continue; } ImGui::Text("%s", PADGetName(i)); u32 buttonCount = 0; PADButtonMapping* mapping = PADGetButtonMappings(i, &buttonCount); if (mapping != nullptr) { for (u32 m = 0; m < buttonCount; ++m) { const char* padName = PADGetButtonName(mapping[m].padButton); if (padName == nullptr) { continue; } ImGui::PushID(padName); bool pressed = ImGui::Button(padName); ImGui::SameLine(); ImGui::Text("%s", PADGetNativeButtonName(mapping[m].nativeButton)); if (pressed && m_pendingMapping == nullptr) { m_pendingMapping = &mapping[m]; m_pendingPort = i; PADBlockInput(true); } if (m_pendingMapping == &mapping[m]) { m_pendingValid = true; ImGui::SameLine(); ImGui::Text(" - Waiting for button..."); } ImGui::PopID(); } } if (ImGui::CollapsingHeader("Dead-zones")) { PADDeadZones* deadZones = PADGetDeadZones(i); ImGui::Checkbox("Use Dead-zones", &deadZones->useDeadzones); float tmp = static_cast(deadZones->stickDeadZone * 100.f) / 32767.f; if (ImGui::DragFloat("Left Stick", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->stickDeadZone = static_cast((tmp / 100.f) * 32767); } tmp = static_cast(deadZones->substickDeadZone * 100.f) / 32767.f; if (ImGui::DragFloat("Right Stick", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->substickDeadZone = static_cast((tmp / 100.f) * 32767); } ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers); tmp = static_cast(deadZones->leftTriggerActivationZone * 100.f) / 32767.f; if (ImGui::DragFloat("Left Trigger Activation", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->leftTriggerActivationZone = static_cast((tmp / 100.f) * 32767); } tmp = static_cast(deadZones->rightTriggerActivationZone * 100.f) / 32767.f; if (ImGui::DragFloat("Right Trigger Activation", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->rightTriggerActivationZone = static_cast((tmp / 100.f) * 32767); } } ImGui::PopID(); ImGui::EndTabItem(); } } ImGui::EndTabBar(); } ImGui::Separator(); if (ImGui::Button("Display Editor")) { m_editorVisible = true; } ImGui::SameLine(); if (ImGui::Button("Save Mappings")) { PADSerializeMappings(); } ImGui::SameLine(); if (ImGui::Button("Restore Defaults")) { for (u32 i = 0; i < 4; ++i) { PADRestoreDefaultMapping(i); } } } ImGui::End(); showEditor(m_editorVisible); } void ImGuiControllerConfig::showEditor(bool& visible) { if (!visible) { return; } if (ImGui::Begin("Controller Atlas Editor", &visible)) { /* TODO: Atlas editor */ ImGui::Separator(); if (ImGui::Button("Save Controller Database")) { CFileOutStream out("ControllerAtlases.ctrdb"); out.WriteUint32(SLITTLE('CTDB')); out.WriteUint32(1); // Version write_vector(m_controllerAtlases, out); } ImGui::SameLine(); if (ImGui::Button("Export") && m_currentAtlas != nullptr) { CFileOutStream out("test.ctratlas"); out.Put(SLITTLE('CTRA')); out.Put(1); // Version out.Put(*m_currentAtlas); } /* TODO: Import logic */ } ImGui::End(); } } // namespace metaforce ================================================ FILE: Runtime/ImGuiControllerConfig.hpp ================================================ #pragma once #include "Runtime/GCNTypes.hpp" #include "Runtime/Streams/CInputStream.hpp" #include "Runtime/Streams/COutputStream.hpp" #include "imgui.h" #include #include #include #include #include namespace metaforce { class ImGuiControllerConfig { struct Button { s32 button = -1; // the SDL button this entry corresponds to u32 uvX = 0; // Offset if icon image in atlas from left (in pixels) u32 uvY = 0; // Offset if icon image in atlas from top (in pixels) u32 width = 32; // Width of button image (in pixels) u32 height = 32; // Height of button image (in pixels) float offX = 0.f; // Offset from left of config window float offY = 0.f; // Offset from top of config window Button() = default; explicit Button(CInputStream& in); void PutTo(COutputStream& in) const; }; struct ControllerAtlas { std::string name; std::vector> vidPids; std::string atlasFile; // Path to atlas relative to controller definition std::vector