/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* * Entity that can move and obey physics. */ 1 %{ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CLEARMEM(var) memset(&var, 0, sizeof(var)) %} %{ #define ANYEXCEPTION ... template class CStaticStackArray; #define MAXCOLLISIONRETRIES 4*4 extern FLOAT phy_fCollisionCacheAhead; extern FLOAT phy_fCollisionCacheAround; extern FLOAT cli_fPredictionFilter; // force breakpoint (debug) extern INDEX dbg_bBreak; // must be in separate function to disable stupid optimizer extern void Breakpoint(void); CEntity *GetPredictedSafe(CEntity *pen) { if ((pen->en_ulFlags&(ENF_PREDICTOR|ENF_TEMPPREDICTOR)) == ENF_PREDICTOR) { return pen->GetPredicted(); } else { return pen; } } // add acceleration to velocity static inline void AddAcceleration( FLOAT3D &vCurrentVelocity, const FLOAT3D &vDesiredVelocity, FLOAT fAcceleration, FLOAT fDecceleration) { // if desired velocity is smaller than current velocity if (vDesiredVelocity.Length()fAcceleration) { vCurrentVelocity += vDelta*(fAcceleration/fDelta); } else { vCurrentVelocity = vDesiredVelocity; } } // add gravity acceleration to velocity along an axis static inline void AddGAcceleration( FLOAT3D &vCurrentVelocity, const FLOAT3D &vGDir, FLOAT fGA, FLOAT fGV) { // disassemble speed FLOAT3D vCurrentParallel, vCurrentOrthogonal; GetParallelAndNormalComponents(vCurrentVelocity, vGDir, vCurrentOrthogonal, vCurrentParallel); /* IMPORTANT: This is how this piece of code should look like: // if not already going down at max speed if (! (vCurrentOrthogonal%vGDir>=fGV)) { // add accelleration to parallel speed vCurrentOrthogonal+=vGDir*fGA; // if going down at max speed if (vCurrentOrthogonal%vGDir>=fGV) { // clamp vCurrentOrthogonal = vGDir*fGV; } } But, due to need for compatibility with older versions and bad VC code generator, we use this kludge: */ // KLUDGE_BEGIN if (_pNetwork->ga_ulDemoMinorVersion<=2) { Swap(vCurrentOrthogonal, vCurrentParallel); } FLOAT3D vCurrentOrthogonalOrg=vCurrentOrthogonal; // add accelleration to parallel speed vCurrentOrthogonal+=vGDir*fGA; // if going down at max speed if (vCurrentOrthogonal%vGDir>=fGV) { // clamp vCurrentOrthogonal = vGDir*fGV; } else { vCurrentOrthogonalOrg = vCurrentOrthogonal; } if (_pNetwork->ga_ulDemoMinorVersion>2) { vCurrentOrthogonal=vCurrentOrthogonalOrg; } // KLUDGE_END // assemble speed back vCurrentVelocity = vCurrentParallel+vCurrentOrthogonal; } // NOTE: // this is pulled out into a separate function because, otherwise, VC6 generates // invalid code when optimizing this. no clue why is that so. #pragma inline_depth(0) static void CheckAndAddGAcceleration(CMovableEntity *pen, FLOAT3D &vTranslationAbsolute, FLOAT fTickQuantum) { // if there is forcefield involved if (pen->en_fForceA>0.01f) { // add force acceleration FLOAT fGV=pen->en_fForceV*fTickQuantum; FLOAT fGA=pen->en_fForceA*fTickQuantum*fTickQuantum; AddGAcceleration(vTranslationAbsolute, pen->en_vForceDir, fGA, fGV); } } #pragma inline_depth() // see important note above // add acceleration to velocity, but only along a plane static inline void AddAccelerationOnPlane( FLOAT3D &vCurrentVelocity, const FLOAT3D &vDesiredVelocity, FLOAT fAcceleration, FLOAT fDecceleration, const FLOAT3D &vPlaneNormal) { FLOAT3D vCurrentParallel, vCurrentOrthogonal; GetParallelAndNormalComponents(vCurrentVelocity, vPlaneNormal, vCurrentOrthogonal, vCurrentParallel); FLOAT3D vDesiredParallel; GetNormalComponent(vDesiredVelocity, vPlaneNormal, vDesiredParallel); AddAcceleration(vCurrentParallel, vDesiredParallel, fAcceleration, fDecceleration); vCurrentVelocity = vCurrentParallel+vCurrentOrthogonal; } // add acceleration to velocity, for roller-coaster slope -- slow! static inline void AddAccelerationOnPlane2( FLOAT3D &vCurrentVelocity, const FLOAT3D &vDesiredVelocity, FLOAT fAcceleration, FLOAT fDecceleration, const FLOAT3D &vPlaneNormal, const FLOAT3D &vGravity) { // get down and horizontal direction FLOAT3D vDn; GetNormalComponent(vGravity, vPlaneNormal, vDn); vDn.Normalize(); FLOAT3D vRt = vPlaneNormal*vDn; vRt.Normalize(); // add only horizontal acceleration FLOAT3D vCurrentParallel, vCurrentOrthogonal; GetParallelAndNormalComponents(vCurrentVelocity, vRt, vCurrentParallel, vCurrentOrthogonal); FLOAT3D vDesiredParallel; GetParallelComponent(vDesiredVelocity, vRt, vDesiredParallel); AddAcceleration(vCurrentParallel, vDesiredParallel, fAcceleration, fDecceleration); vCurrentVelocity = vCurrentParallel+vCurrentOrthogonal; } // max number of retries during movement static INDEX _ctTryToMoveCheckCounter; static INDEX _ctSliding; static FLOAT3D _vSlideOffDir; // move away direction for sliding static FLOAT3D _vSlideDir; static void InitTryToMove(void) { _ctTryToMoveCheckCounter = MAXCOLLISIONRETRIES; _ctSliding = 0; _vSlideOffDir = FLOAT3D(0,0,0); _vSlideDir = FLOAT3D(0,0,0); } // array of forces for current entity class CEntityForce { public: CEntityPointer ef_penEntity; INDEX ef_iForceType; FLOAT ef_fRatio; // how much of entity this force gets [0-1] inline void Clear(void) { ef_penEntity = NULL; }; ~CEntityForce(void) { Clear(); }; }; static CStaticStackArray _aefForces; void ClearMovableEntityCaches(void) { _aefForces.Clear(); } %} class export CMovableEntity : CRationalEntity { name "MovableEntity"; thumbnail ""; properties: // NOTE: all properties that are not marked as 'adjustable' should be threated read-only // translation and rotation speed that this entity would like to have (in relative system) 1 FLOAT3D en_vDesiredTranslationRelative = FLOAT3D(0.0f,0.0f,0.0f), 2 ANGLE3D en_aDesiredRotationRelative = ANGLE3D(0,0,0), // translation and rotation speed that this entity currently has in absolute system 3 FLOAT3D en_vCurrentTranslationAbsolute = FLOAT3D(0.0f,0.0f,0.0f), 4 ANGLE3D en_aCurrentRotationAbsolute = ANGLE3D(0,0,0), 6 CEntityPointer en_penReference, // reference entity (for standing on) 7 FLOAT3D en_vReferencePlane = FLOAT3D(0.0f,0.0f,0.0f), // reference plane (only for standing on) 8 INDEX en_iReferenceSurface = 0, // surface on reference entity 9 CEntityPointer en_penLastValidReference, // last valid reference entity (for impact damage) 14 FLOAT en_tmLastSignificantVerticalMovement = 0.0f, // last time entity moved significantly up/down // swimming parameters 10 FLOAT en_tmLastBreathed = 0, // last time when entity took some air 11 FLOAT en_tmMaxHoldBreath = 5.0f, // how long can entity be without air (adjustable) 12 FLOAT en_fDensity = 5000.0f, // density of the body [kg/m3] - defines buoyancy (adjustable) 13 FLOAT en_tmLastSwimDamage = 0, // last time when entity was damaged by swimming // content immersion parameters 20 INDEX en_iUpContent = 0, 21 INDEX en_iDnContent = 0, 22 FLOAT en_fImmersionFactor = 1.0f, // force parameters 25 FLOAT3D en_vGravityDir = FLOAT3D(0,-1,0), 26 FLOAT en_fGravityA = 0.0f, 27 FLOAT en_fGravityV = 0.0f, 66 FLOAT3D en_vForceDir = FLOAT3D(1,0,0), 67 FLOAT en_fForceA = 0.0f, 68 FLOAT en_fForceV = 0.0f, // jumping parameters 30 FLOAT en_tmJumped = 0, // time when entity jumped 31 FLOAT en_tmMaxJumpControl = 0.5f, // how long after jump can have control in the air [s] (adjustable) 32 FLOAT en_fJumpControlMultiplier = 0.5f, // how good is control when jumping (adjustable) // movement parameters 35 FLOAT en_fAcceleration = 200.0f, // acc/decc [m/s2] in ideal situation (adjustable) 36 FLOAT en_fDeceleration = 40.0f, 37 FLOAT en_fStepUpHeight = 1.0f, // how high can entity step upstairs (adjustable) 42 FLOAT en_fStepDnHeight = -1.0f, // how low can entity step (negative means don't check) (adjustable) 38 FLOAT en_fBounceDampParallel = 0.5f, // damping parallel to plane at each bounce (adjustable) 39 FLOAT en_fBounceDampNormal = 0.5f, // damping normal to plane damping at each bounce (adjustable) // collision damage control 40 FLOAT en_fCollisionSpeedLimit = 20.0f, // max. collision speed without damage (adjustable) 41 FLOAT en_fCollisionDamageFactor = 20.0f, // collision damage ammount multiplier (adjustable) 51 FLOATaabbox3D en_boxMovingEstimate = FLOATaabbox3D(FLOAT3D(0,0,0), 0.01f), // overestimate of movement in next few ticks 52 FLOATaabbox3D en_boxNearCached = FLOATaabbox3D(FLOAT3D(0,0,0), 0.01f), // box in which the polygons are cached // intended movement in this tick 64 FLOAT3D en_vIntendedTranslation = FLOAT3D(0,0,0), // can be read on receiving a touch event, holds last velocity before touch 65 FLOATmatrix3D en_mIntendedRotation = FLOATmatrix3D(0), { // these are not saved via the property system CPlacement3D en_plLastPlacement; // placement in last tick (used for lerping) (not saved) CListNode en_lnInMovers; // node in list of moving entities (saved as bool) CBrushPolygon *en_pbpoStandOn; // cached last polygon standing on, just for optimization // used for caching near polygons of zoning brushes for fast collision detection CStaticStackArray en_apbpoNearPolygons; // cached polygons FLOAT en_tmLastPredictionHead; FLOAT3D en_vLastHead; FLOAT3D en_vPredError; FLOAT3D en_vPredErrorLast; // these are really temporary - should never be used across ticks // next placement for collision detection FLOAT3D en_vNextPosition; FLOATmatrix3D en_mNextRotation; // delta for this movement FLOAT3D en_vMoveTranslation; FLOATmatrix3D en_mMoveRotation; // aplied movement in this tick FLOAT3D en_vAppliedTranslation; FLOATmatrix3D en_mAppliedRotation; } components: functions: void ResetPredictionFilter(void) { en_tmLastPredictionHead = -2; en_vLastHead = en_plPlacement.pl_PositionVector; en_vPredError = en_vPredErrorLast = FLOAT3D(0,0,0); } /* Constructor. */ export void CMovableEntity(void) { en_pbpoStandOn = NULL; en_apbpoNearPolygons.SetAllocationStep(5); ResetPredictionFilter(); } export void ~CMovableEntity(void) { } /* Initialization. */ export void OnInitialize(const CEntityEvent &eeInput) { CRationalEntity::OnInitialize(eeInput); ClearTemporaryData(); en_vIntendedTranslation = FLOAT3D(0,0,0); en_mIntendedRotation.Diagonal(1.0f); en_boxNearCached = FLOATaabbox3D(); en_boxMovingEstimate = FLOATaabbox3D(); en_pbpoStandOn = NULL; } /* Called before releasing entity. */ export void OnEnd(void) { // remove from movers if active if (en_lnInMovers.IsLinked()) { en_lnInMovers.Remove(); } ClearTemporaryData(); en_boxNearCached = FLOATaabbox3D(); en_boxMovingEstimate = FLOATaabbox3D(); CRationalEntity::OnEnd(); } export void Copy(CEntity &enOther, ULONG ulFlags) { CRationalEntity::Copy(enOther, ulFlags); CMovableEntity *pmenOther = (CMovableEntity *)(&enOther); if (ulFlags©_PREDICTOR) { en_plLastPlacement = pmenOther->en_plLastPlacement ; en_vNextPosition = pmenOther->en_vNextPosition ; en_mNextRotation = pmenOther->en_mNextRotation ; ///*!*/ en_vIntendedTranslation = pmenOther->en_vIntendedTranslation ; ///*!*/ en_mIntendedRotation = pmenOther->en_mIntendedRotation ; en_vAppliedTranslation = pmenOther->en_vAppliedTranslation ; en_mAppliedRotation = pmenOther->en_mAppliedRotation ; en_boxNearCached = pmenOther->en_boxNearCached ; en_boxMovingEstimate = pmenOther->en_boxMovingEstimate ; en_pbpoStandOn = pmenOther->en_pbpoStandOn ; en_apbpoNearPolygons = pmenOther->en_apbpoNearPolygons ; } else { ClearTemporaryData(); en_boxNearCached = FLOATaabbox3D(); en_boxMovingEstimate = FLOATaabbox3D(); en_pbpoStandOn = NULL; } ResetPredictionFilter(); en_plLastPlacement = pmenOther->en_plLastPlacement; if (pmenOther->en_lnInMovers.IsLinked()) { AddToMovers(); } } void ClearTemporaryData(void) { en_plLastPlacement = en_plPlacement; // init moving parameters so that they are valid for collision if entity is not moving en_vNextPosition = en_plPlacement.pl_PositionVector; en_mNextRotation = en_mRotation; ///*!*/ en_vIntendedTranslation = FLOAT3D(0,0,0); ///*!*/ en_mIntendedRotation.Diagonal(1.0f); en_vAppliedTranslation = FLOAT3D(0,0,0); en_mAppliedRotation.Diagonal(1.0f); ResetPredictionFilter(); // !!!! is this ok? // en_pbpoStandOn = NULL; // en_apbpoNearPolygons.Clear(); // en_apbpoNearPolygons.SetAllocationStep(5); } // create a checksum value for sync-check export void ChecksumForSync(ULONG &ulCRC, INDEX iExtensiveSyncCheck) { CRationalEntity::ChecksumForSync(ulCRC, iExtensiveSyncCheck); if (iExtensiveSyncCheck>0) { if (en_pbpoStandOn!=NULL) { CRC_AddLONG(ulCRC, en_pbpoStandOn->bpo_iInWorld); } CRC_AddFLOAT(ulCRC, en_apbpoNearPolygons.Count()); if (iExtensiveSyncCheck>2) { for (INDEX i=0; ibpo_iInWorld); } } CRC_AddBlock(ulCRC, (UBYTE*)&en_vReferencePlane, sizeof(en_vReferencePlane)); CRC_AddBlock(ulCRC, (UBYTE*)&en_vDesiredTranslationRelative, sizeof(en_vDesiredTranslationRelative)); CRC_AddBlock(ulCRC, (UBYTE*)&en_aDesiredRotationRelative, sizeof(en_aDesiredRotationRelative)); CRC_AddBlock(ulCRC, (UBYTE*)&en_vCurrentTranslationAbsolute, sizeof(en_vCurrentTranslationAbsolute)); CRC_AddBlock(ulCRC, (UBYTE*)&en_aCurrentRotationAbsolute, sizeof(en_aCurrentRotationAbsolute)); } } // dump sync data to text file export void DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck) // throw char * { CRationalEntity::DumpSync_t(strm, iExtensiveSyncCheck); if (iExtensiveSyncCheck>0) { strm.FPrintF_t("standon polygon: "); if (en_pbpoStandOn!=NULL) { strm.FPrintF_t("%d\n", en_pbpoStandOn->bpo_iInWorld); } else { strm.FPrintF_t("\n"); } strm.FPrintF_t("near polygons: %d - ", en_apbpoNearPolygons.Count()); if (iExtensiveSyncCheck>2) { for (INDEX i=0; ibpo_iInWorld); } } strm.FPrintF_t("\n"); strm.FPrintF_t("desired translation: %g, %g, %g (%08X %08X %08X)\n", en_vDesiredTranslationRelative(1), en_vDesiredTranslationRelative(2), en_vDesiredTranslationRelative(3), (ULONG&)en_vDesiredTranslationRelative(1), (ULONG&)en_vDesiredTranslationRelative(2), (ULONG&)en_vDesiredTranslationRelative(3)); strm.FPrintF_t("desired rotation: %g, %g, %g (%08X %08X %08X)\n", en_aDesiredRotationRelative(1), en_aDesiredRotationRelative(2), en_aDesiredRotationRelative(3), (ULONG&)en_aDesiredRotationRelative(1), (ULONG&)en_aDesiredRotationRelative(2), (ULONG&)en_aDesiredRotationRelative(3)); strm.FPrintF_t("current translation: %g, %g, %g (%08X %08X %08X)\n", en_vCurrentTranslationAbsolute(1), en_vCurrentTranslationAbsolute(2), en_vCurrentTranslationAbsolute(3), (ULONG&)en_vCurrentTranslationAbsolute(1), (ULONG&)en_vCurrentTranslationAbsolute(2), (ULONG&)en_vCurrentTranslationAbsolute(3)); strm.FPrintF_t("current rotation: %g, %g, %g (%08X %08X %08X)\n", en_aCurrentRotationAbsolute(1), en_aCurrentRotationAbsolute(2), en_aCurrentRotationAbsolute(3), (ULONG&)en_aCurrentRotationAbsolute(1), (ULONG&)en_aCurrentRotationAbsolute(2), (ULONG&)en_aCurrentRotationAbsolute(3)); strm.FPrintF_t("reference plane: %g, %g, %g (%08X %08X %08X)\n", en_vReferencePlane(1), en_vReferencePlane(2), en_vReferencePlane(3), (ULONG&)en_vReferencePlane(1), (ULONG&)en_vReferencePlane(2), (ULONG&)en_vReferencePlane(3)); strm.FPrintF_t("reference surface: %d\n", en_iReferenceSurface); strm.FPrintF_t("reference entity: "); if (en_penReference!=NULL) { strm.FPrintF_t("id: %08X\n", en_penReference->en_ulID); } else { strm.FPrintF_t("none\n"); } } } /* Read from stream. */ export void Read_t( CTStream *istr) // throw char * { CRationalEntity::Read_t(istr); // last placement is not saved to disk - it is not neccessary! ClearTemporaryData(); // old version didn't load this if (istr->PeekID_t()==CChunkID("MENT")) { // 'movable entity' istr->ExpectID_t("MENT"); // 'movable entity' INDEX ibpo; (*istr)>>ibpo; en_pbpoStandOn = GetWorldPolygonPointer(ibpo); BOOL bAnyNULLs = FALSE; INDEX ctbpoNear; (*istr)>>ctbpoNear; if (ctbpoNear>0) { en_apbpoNearPolygons.PopAll(); en_apbpoNearPolygons.Push(ctbpoNear); for(INDEX i=0; i>ibpo; en_apbpoNearPolygons[i] = GetWorldPolygonPointer(ibpo); if (en_apbpoNearPolygons[i]==NULL) { bAnyNULLs = TRUE; } } if (bAnyNULLs) { CPrintF("NULL saved for near polygon!\n"); en_apbpoNearPolygons.PopAll(); } } } // if entity was in list of movers when saving BOOL bWasMoving; (*istr)>>bWasMoving; if (bWasMoving) { // add it to movers AddToMovers(); } } /* Write to stream. */ export void Write_t( CTStream *ostr) // throw char * { CRationalEntity::Write_t(ostr); ostr->WriteID_t("MENT"); // 'movable entity' INDEX ibpo; ibpo = GetWorldPolygonIndex(en_pbpoStandOn); (*ostr)<GetLerpFactor(); } else { fLerpFactor = _pTimer->GetLerpFactor2(); } CPlacement3D plLerped; plLerped.Lerp(en_plLastPlacement, en_plPlacement, fLerpFactor); CMovableEntity *penTail = (CMovableEntity *)GetPredictedSafe((CEntity*)this); // if should filter predictions extern BOOL _bPredictionActive; if (_bPredictionActive) { // add the smoothed error FLOAT3D vError = penTail->en_vPredError; vError*=pow(cli_fPredictionFilter, fLerpFactor); // FLOAT fErrLen = vError.Length(); // if (fErrLen>0) { // vError = vError/fErrLen*ClampDn(fErrLen-cli_fPredictionCorrection*fLerpFactor, 0.0f); // } plLerped.pl_PositionVector -= vError; } return plLerped; } /* Add yourself to list of movers. */ export void AddToMovers(void) { if (!en_lnInMovers.IsLinked()) { en_pwoWorld->wo_lhMovers.AddTail(en_lnInMovers); } } export void AddToMoversDuringMoving(void) // used for recursive adding { // if already added if (en_lnInMovers.IsLinked()) { // do nothing return; } // add it AddToMovers(); // mark that it was forced to add en_ulPhysicsFlags|=EPF_FORCEADDED; } /* Set desired rotation speed of movable entity. */ export void SetDesiredRotation(const ANGLE3D &aRotation) { en_aDesiredRotationRelative = aRotation; AddToMovers(); } export const ANGLE3D &GetDesiredRotation(void) const { return en_aDesiredRotationRelative; }; /* Set desired translation speed of movable entity. */ export void SetDesiredTranslation(const FLOAT3D &vTranslation) { en_vDesiredTranslationRelative = vTranslation; AddToMovers(); } export const FLOAT3D &GetDesiredTranslation(void) const { return en_vDesiredTranslationRelative; }; /* Add an impulse to the current speed of the entity (used for instantaneous launching). */ export void GiveImpulseTranslationRelative(const FLOAT3D &vImpulseSpeedRelative) { CPlacement3D plImpulseSpeedAbsolute( vImpulseSpeedRelative, ANGLE3D(0,0,0)); plImpulseSpeedAbsolute.RelativeToAbsolute( CPlacement3D(FLOAT3D(0.0f,0.0f,0.0f), en_plPlacement.pl_OrientationAngle)); en_vCurrentTranslationAbsolute += plImpulseSpeedAbsolute.pl_PositionVector; AddToMovers(); } export void GiveImpulseTranslationAbsolute(const FLOAT3D &vImpulseSpeed) { en_vCurrentTranslationAbsolute += vImpulseSpeed; AddToMovers(); } export void LaunchAsPropelledProjectile(const FLOAT3D &vImpulseSpeedRelative, CMovableEntity *penLauncher) { en_vDesiredTranslationRelative = vImpulseSpeedRelative; en_vCurrentTranslationAbsolute += vImpulseSpeedRelative*en_mRotation; // en_vCurrentTranslationAbsolute += penLauncher->en_vCurrentTranslationAbsolute; AddToMovers(); } export void LaunchAsFreeProjectile(const FLOAT3D &vImpulseSpeedRelative, CMovableEntity *penLauncher) { en_vCurrentTranslationAbsolute += vImpulseSpeedRelative*en_mRotation; // en_vCurrentTranslationAbsolute += penLauncher->en_vCurrentTranslationAbsolute; // en_fAcceleration = en_fDeceleration = 0.0f; AddToMovers(); } /* Stop all translation */ export void ForceStopTranslation(void) { en_vDesiredTranslationRelative = FLOAT3D(0.0f,0.0f,0.0f); en_vCurrentTranslationAbsolute = FLOAT3D(0.0f,0.0f,0.0f); en_vAppliedTranslation = FLOAT3D(0.0f,0.0f,0.0f); } /* Stop all rotation */ export void ForceStopRotation(void) { en_aDesiredRotationRelative = ANGLE3D(0,0,0); en_aCurrentRotationAbsolute = ANGLE3D(0,0,0); en_mAppliedRotation.Diagonal(1.0f); } /* Stop at once in place */ export void ForceFullStop(void) { ForceStopTranslation(); ForceStopRotation(); } /* Fake that the entity jumped (for jumppads) */ export void FakeJump(const FLOAT3D &vOrgSpeed, const FLOAT3D &vDirection, FLOAT fStrength, FLOAT fParallelMultiplier, FLOAT fNormalMultiplier, FLOAT fMaxExitSpeed, TIME tmControl) { // fixup jump time for right control en_tmJumped = _pTimer->CurrentTick()-en_tmMaxJumpControl+tmControl; // apply parallel and normal component multipliers FLOAT3D vCurrentNormal; FLOAT3D vCurrentParallel; GetParallelAndNormalComponents(vOrgSpeed, vDirection, vCurrentParallel, vCurrentNormal); /* CPrintF( "\nCurrent translation absolute before: %g, %g, %g\n", vOrgSpeed(1), vOrgSpeed(2), vOrgSpeed(3));*/ // compile translation vector en_vCurrentTranslationAbsolute = vCurrentParallel*fParallelMultiplier + vCurrentNormal*fNormalMultiplier + vDirection*fStrength; // clamp translation speed FLOAT fLength = en_vCurrentTranslationAbsolute.Length(); if( fLength > fMaxExitSpeed) { en_vCurrentTranslationAbsolute = en_vCurrentTranslationAbsolute/fLength*fMaxExitSpeed; } /*CPrintF( "Current translation absolute after: %g, %g, %g\n\n", en_vCurrentTranslationAbsolute(1), en_vCurrentTranslationAbsolute(2), en_vCurrentTranslationAbsolute(3));*/ // no reference while bouncing en_penReference = NULL; en_pbpoStandOn = NULL; en_vReferencePlane = FLOAT3D(0.0f, 0.0f, 0.0f); en_iReferenceSurface = 0; // add to movers AddToMovers(); } /* Get relative angles from direction angles. */ export ANGLE GetRelativeHeading(const FLOAT3D &vDirection) { ASSERT(Abs(vDirection.Length()-1)<0.001f); // must be normalized! // get front component of vector FLOAT fFront = -vDirection(1)*en_mRotation(1,3) -vDirection(2)*en_mRotation(2,3) -vDirection(3)*en_mRotation(3,3); // get left component of vector FLOAT fLeft = -vDirection(1)*en_mRotation(1,1) -vDirection(2)*en_mRotation(2,1) -vDirection(3)*en_mRotation(3,1); // relative heading is arctan of angle between front and left return ATan2(fLeft, fFront); } export ANGLE GetRelativePitch(const FLOAT3D &vDirection) { ASSERT(Abs(vDirection.Length()-1)<0.001f); // must be normalized! // get front component of vector FLOAT fFront = -vDirection(1)*en_mRotation(1,3) -vDirection(2)*en_mRotation(2,3) -vDirection(3)*en_mRotation(3,3); // get up component of vector FLOAT fUp = +vDirection(1)*en_mRotation(1,2) +vDirection(2)*en_mRotation(2,2) +vDirection(3)*en_mRotation(3,2); // relative pitch is arctan of angle between front and up return ATan2(fUp, fFront); } /* Get absolute direction for a heading relative to another direction. */ export void GetReferenceHeadingDirection(const FLOAT3D &vReference, ANGLE aH, FLOAT3D &vDirection) { ASSERT(Abs(vReference.Length()-1)<0.001f); // must be normalized! FLOAT3D vY(en_mRotation(1,2), en_mRotation(2,2), en_mRotation(3,2)); FLOAT3D vX = (vY*vReference).Normalize(); FLOAT3D vMZ = vY*vX; vDirection = -vX*Sin(aH)+vMZ*Cos(aH); } /* Get absolute direction for a heading relative to current direction. */ export void GetHeadingDirection(ANGLE aH, FLOAT3D &vDirection) { FLOAT3D vX(en_mRotation(1,1), en_mRotation(2,1), en_mRotation(3,1)); FLOAT3D vZ(en_mRotation(1,3), en_mRotation(2,3), en_mRotation(3,3)); vDirection = -vX*Sin(aH)-vZ*Cos(aH); } /* Get absolute direction for a pitch relative to current direction. */ export void GetPitchDirection(ANGLE aH, FLOAT3D &vDirection) { FLOAT3D vY(en_mRotation(1,2), en_mRotation(2,2), en_mRotation(3,2)); FLOAT3D vZ(en_mRotation(1,3), en_mRotation(2,3), en_mRotation(3,3)); vDirection = -vZ*Cos(aH)+vY*Sin(aH); } // get a valid inflictor for misc damage (last brush or this) CEntity *MiscDamageInflictor(void) { // NOTE: must be damaged by some brush if possible, because enemies are set up so // that they cannot harm themselves. if (en_penLastValidReference!=NULL) { return en_penLastValidReference; } else { CBrushSector *pbsc = GetFirstSector(); if (pbsc==NULL) { return this; } else { return pbsc->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity; } } } // add the sector force void UpdateOneSectorForce(CBrushSector &bsc, FLOAT fRatio) { // if not significantly if (fRatio<0.01f) { // just ignore it return; } INDEX iForceType = bsc.GetForceType(); CEntity *penEntity = bsc.bsc_pbmBrushMip->bm_pbrBrush->br_penEntity; /* BOOL bAnyIn = FALSE; CStaticArray &absSpheres = en_pciCollisionInfo->ci_absSpheres; // for each sphere for(INDEX iSphere=0; iSphere=0) { bAnyIn = TRUE; break; } } if (!bAnyIn) { return; }*/ // try to find the force in container CEntityForce *pef = NULL; for(INDEX iForce=0; iForce<_aefForces.Count(); iForce++) { if (penEntity ==_aefForces[iForce].ef_penEntity &&iForceType==_aefForces[iForce].ef_iForceType) { pef = &_aefForces[iForce]; break; } } // if field is not found if (pef==NULL) { // add a new one pef = _aefForces.Push(1); pef->ef_penEntity = penEntity; pef->ef_iForceType = iForceType; pef->ef_fRatio = 0.0f; } pef->ef_fRatio+=fRatio; } // test for field containment void TestFields(INDEX &iUpContent, INDEX &iDnContent, FLOAT &fImmersionFactor) { // this works only for models ASSERT(en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL || en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL); iUpContent = 0; iDnContent = 0; FLOAT fUp = 0.0f; FLOAT fDn = 0.0f; FLOAT3D &vOffset = en_plPlacement.pl_PositionVector; FLOATmatrix3D &mRotation = en_mRotation; // project height min/max in the entity to absolute space FLOAT3D vMin = FLOAT3D(0, en_pciCollisionInfo->ci_fMinHeight, 0); FLOAT3D vMax = FLOAT3D(0, en_pciCollisionInfo->ci_fMaxHeight, 0); vMin = vMin*mRotation+vOffset; vMax = vMax*mRotation+vOffset; // project all spheres in the entity to absolute space (for touch field testing) CStaticArray &absSpheres = en_pciCollisionInfo->ci_absSpheres; FOREACHINSTATICARRAY(absSpheres, CMovingSphere, itms) { itms->ms_vRelativeCenter0 = itms->ms_vCenter*mRotation+vOffset; } // clear forces _aefForces.PopAll(); // for each sector that this entity is in {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc) CBrushSector &bsc = *pbsc; // if this sector is not in first mip if (!bsc.bsc_pbmBrushMip->IsFirstMip()) { // skip it continue; } // get entity of the sector CEntity *penSector = bsc.bsc_pbmBrushMip->bm_pbrBrush->br_penEntity; // if not real brush if (penSector->en_RenderType!=RT_BRUSH) { // skip it continue; } // get min/max parameters of entity inside sector double dMin, dMax; bsc.bsc_bspBSPTree.FindLineMinMax(FLOATtoDOUBLE(vMin), FLOATtoDOUBLE(vMax), dMin, dMax); // if sector content is not default INDEX iContent = bsc.GetContentType(); if (iContent!=0) { // if inside sector at all if (dMax>0.0f && dMin<1.0f) { //CPrintF("%s: %lf %lf ", bsc.bsc_strName, dMin, dMax); // if minimum is small if (dMin<0.01f) { // update down content iDnContent = iContent; fDn = Max(fDn, FLOAT(dMax)); } // if maximum is large if (dMax>0.99f) { // update up content iUpContent = iContent; fUp = Max(fUp, 1-FLOAT(dMin)); } } } // add the sector force UpdateOneSectorForce(bsc, dMax-dMin); ENDFOR;} //CPrintF("%f %d %f %d\n", fDn, iDnContent, fUp, iUpContent); // if same contents if (iUpContent == iDnContent) { // trivial case fImmersionFactor = 1.0f; // if different contents } else { // calculate immersion factor if (iUpContent==0) { fImmersionFactor = fDn; } else if (iDnContent==0) { fImmersionFactor = 1-fUp; } else { fImmersionFactor = Max(fDn, 1-fUp); } // eliminate degenerate cases if (fImmersionFactor<0.01f) { fImmersionFactor = 1.0f; iDnContent = iUpContent; } else if (fImmersionFactor>0.99f) { fImmersionFactor = 1.0f; iUpContent = iDnContent; } } // clear force container and calculate average forces FLOAT3D vGravityA(0,0,0); FLOAT3D vGravityV(0,0,0); FLOAT3D vForceA(0,0,0); FLOAT3D vForceV(0,0,0); FLOAT fRatioSum = 0.0f; {for(INDEX iForce=0; iForce<_aefForces.Count(); iForce++) { CForceStrength fsGravity; CForceStrength fsField; _aefForces[iForce].ef_penEntity->GetForce( _aefForces[iForce].ef_iForceType, en_plPlacement.pl_PositionVector, fsGravity, fsField); FLOAT fRatio = _aefForces[iForce].ef_fRatio; fRatioSum+=fRatio; vGravityA+=fsGravity.fs_vDirection*fsGravity.fs_fAcceleration*fRatio; vGravityV+=fsGravity.fs_vDirection*fsGravity.fs_fVelocity*fRatio; if (fsField.fs_fAcceleration>0) { vForceA+=fsField.fs_vDirection*fsField.fs_fAcceleration*fRatio; vForceV+=fsField.fs_vDirection*fsField.fs_fVelocity*fRatio; } _aefForces[iForce].Clear(); }} if (fRatioSum>0) { vGravityA/=fRatioSum; vGravityV/=fRatioSum; vForceA/=fRatioSum; vForceV/=fRatioSum; } en_fGravityA = vGravityA.Length(); if (en_fGravityA<0.01f) { en_fGravityA = 0; } else { en_fGravityV = vGravityV.Length(); en_vGravityDir = vGravityA/en_fGravityA; } en_fForceA = vForceA.Length(); if (en_fForceA<0.01f) { en_fForceA = 0; } else { en_fForceV = vForceV.Length(); en_vForceDir = vForceA/en_fForceA; } _aefForces.PopAll(); } // test entity breathing void TestBreathing(CContentType &ctUp) { // if this entity doesn't breathe if (!(en_ulPhysicsFlags&(EPF_HASLUNGS|EPF_HASGILLS))) { // do nothing return; } // find current breathing parameters BOOL bCanBreathe = ((ctUp.ct_ulFlags&CTF_BREATHABLE_LUNGS) && (en_ulPhysicsFlags&EPF_HASLUNGS)) || ((ctUp.ct_ulFlags&CTF_BREATHABLE_GILLS) && (en_ulPhysicsFlags&EPF_HASGILLS)); TIME tmNow = _pTimer->CurrentTick(); TIME tmBreathDelay = tmNow-en_tmLastBreathed; // if entity can breathe now if (bCanBreathe) { // update breathing time en_tmLastBreathed = tmNow; // if it was without air for some time if (tmBreathDelay>_pTimer->TickQuantum*2) { // notify entity that it has take air now ETakingBreath eTakingBreath; eTakingBreath.fBreathDelay = tmBreathDelay/en_tmMaxHoldBreath; SendEvent(eTakingBreath); } // if entity can not breathe now } else { // if it was without air for too long if (tmBreathDelay>en_tmMaxHoldBreath) { // inflict drowning damage InflictDirectDamage(this, MiscDamageInflictor(), DMT_DROWNING, ctUp.ct_fDrowningDamageAmount, en_plPlacement.pl_PositionVector, -en_vGravityDir); // prolongue breathing a bit, so not to come here every frame en_tmLastBreathed = tmNow-en_tmMaxHoldBreath+ctUp.ct_tmDrowningDamageDelay; } } } void TestContentDamage(CContentType &ctDn, FLOAT fImmersion) { // if the content can damage by swimming if (ctDn.ct_fSwimDamageAmount>0) { TIME tmNow = _pTimer->CurrentTick(); // if there is a delay if (ctDn.ct_tmSwimDamageDelay>0) { // if not yet delayed if (tmNow-en_tmLastSwimDamage>ctDn.ct_tmSwimDamageDelay+_pTimer->TickQuantum) { // delay en_tmLastSwimDamage = tmNow+ctDn.ct_tmSwimDamageDelay; return; } } if (tmNow-en_tmLastSwimDamage>ctDn.ct_tmSwimDamageFrequency) { // inflict drowning damage InflictDirectDamage(this, MiscDamageInflictor(), (DamageType)ctDn.ct_iSwimDamageType, ctDn.ct_fSwimDamageAmount*fImmersion, en_plPlacement.pl_PositionVector, -en_vGravityDir); en_tmLastSwimDamage = tmNow; } } // if the content kills if (ctDn.ct_fKillImmersion>0 && fImmersion>=ctDn.ct_fKillImmersion &&(en_ulFlags&ENF_ALIVE)) { // inflict killing damage InflictDirectDamage(this, MiscDamageInflictor(), (DamageType)ctDn.ct_iKillDamageType, GetHealth()*10.0f, en_plPlacement.pl_PositionVector, -en_vGravityDir); } } void TestSurfaceDamage(CSurfaceType &stDn) { // if the surface can damage by walking if (stDn.st_fWalkDamageAmount>0) { TIME tmNow = _pTimer->CurrentTick(); // if there is a delay if (stDn.st_tmWalkDamageDelay>0) { // if not yet delayed if (tmNow-en_tmLastSwimDamage>stDn.st_tmWalkDamageDelay+_pTimer->TickQuantum) { // delay en_tmLastSwimDamage = tmNow+stDn.st_tmWalkDamageDelay; return; } } if (tmNow-en_tmLastSwimDamage>stDn.st_tmWalkDamageFrequency) { // inflict walking damage InflictDirectDamage(this, MiscDamageInflictor(), (DamageType)stDn.st_iWalkDamageType, stDn.st_fWalkDamageAmount, en_plPlacement.pl_PositionVector, -en_vGravityDir); en_tmLastSwimDamage = tmNow; } } } // send touch event to this entity and touched entity void SendTouchEvent(const CClipMove &cmMove) { ETouch etouchThis; ETouch etouchOther; etouchThis.penOther = cmMove.cm_penHit; etouchThis.bThisMoved = FALSE; etouchThis.plCollision = cmMove.cm_plClippedPlane; etouchOther.penOther = this; etouchOther.bThisMoved = TRUE; etouchOther.plCollision = cmMove.cm_plClippedPlane; SendEvent(etouchThis); cmMove.cm_penHit->SendEvent(etouchOther); } // send block event to this entity void SendBlockEvent(CClipMove &cmMove) { EBlock eBlock; eBlock.penOther = cmMove.cm_penHit; eBlock.plCollision = cmMove.cm_plClippedPlane; SendEvent(eBlock); } BOOL IsStandingOnPolygon(CBrushPolygon *pbpo) { _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); // if cannot optimize for standing on handle if (en_pciCollisionInfo==NULL ||!(en_pciCollisionInfo->ci_ulFlags&CIF_CANSTANDONHANDLE)) { // not standing on polygon _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } // if polygon is not valid for standing on any more (brush turned off collision) if (en_pbpoStandOn->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity->en_ulCollisionFlags==0) { // not standing on polygon return FALSE; } const FLOATplane3D &plPolygon = pbpo->bpo_pbplPlane->bpl_plAbsolute; // get stand-on handle FLOAT3D vHandle = en_plPlacement.pl_PositionVector; vHandle(1)+=en_pciCollisionInfo->ci_fHandleY*en_mRotation(1,2); vHandle(2)+=en_pciCollisionInfo->ci_fHandleY*en_mRotation(2,2); vHandle(3)+=en_pciCollisionInfo->ci_fHandleY*en_mRotation(3,2); vHandle-=((FLOAT3D&)plPolygon)*en_pciCollisionInfo->ci_fHandleR; // if handle is not on the plane if (plPolygon.PointDistance(vHandle)>0.01f) { // not standing on polygon _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } // find major axes of the polygon plane INDEX iMajorAxis1, iMajorAxis2; GetMajorAxesForPlane(plPolygon, iMajorAxis1, iMajorAxis2); // create an intersector CIntersector isIntersector(vHandle(iMajorAxis1), vHandle(iMajorAxis2)); // for all edges in the polygon FOREACHINSTATICARRAY(pbpo->bpo_abpePolygonEdges, CBrushPolygonEdge, itbpePolygonEdge) { // get edge vertices (edge direction is irrelevant here!) const FLOAT3D &vVertex0 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex0->bvx_vAbsolute; const FLOAT3D &vVertex1 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex1->bvx_vAbsolute; // pass the edge to the intersector isIntersector.AddEdge( vVertex0(iMajorAxis1), vVertex0(iMajorAxis2), vVertex1(iMajorAxis1), vVertex1(iMajorAxis2)); } // if the point is inside polygon if (isIntersector.IsIntersecting()) { // entity is standing on polygon _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return TRUE; // if the point is outside polygon } else { // entity is not standing on polygon _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } } // check whether a polygon is below given point, but not too far away BOOL IsPolygonBelowPoint(CBrushPolygon *pbpo, const FLOAT3D &vPoint, FLOAT fMaxDist) { _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); // if passable or not allowed as ground if ((pbpo->bpo_ulFlags&BPOF_PASSABLE) ||!AllowForGroundPolygon(pbpo)) { // it cannot be below _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } // get polygon plane const FLOATplane3D &plPolygon = pbpo->bpo_pbplPlane->bpl_plAbsolute; // determine polygon orientation relative to gravity FLOAT fCos = ((const FLOAT3D &)plPolygon)%en_vGravityDir; // if polygon is vertical or upside down if (fCos>-0.01f) { // it cannot be below _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } // if polygon's steepness is too high CSurfaceType &stReference = en_pwoWorld->wo_astSurfaceTypes[pbpo->bpo_bppProperties.bpp_ubSurfaceType]; if ((fCos >= -stReference.st_fClimbSlopeCos && fCos<0) || stReference.st_ulFlags&STF_SLIDEDOWNSLOPE) { // it cannot be below _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } // get distance from point to the plane FLOAT fD = plPolygon.PointDistance(vPoint); // if the point is behind the plane if (fD<-0.01f) { // it cannot be below _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } // find distance of point from the polygon along gravity vector FLOAT fDistance = -fD/fCos; // if too far away if (fDistance > fMaxDist) { // it cannot be below _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } // project point to the polygon along gravity vector FLOAT3D vProjected = vPoint + en_vGravityDir*fDistance; // find major axes of the polygon plane INDEX iMajorAxis1, iMajorAxis2; GetMajorAxesForPlane(plPolygon, iMajorAxis1, iMajorAxis2); // create an intersector CIntersector isIntersector(vProjected(iMajorAxis1), vProjected(iMajorAxis2)); // for all edges in the polygon FOREACHINSTATICARRAY(pbpo->bpo_abpePolygonEdges, CBrushPolygonEdge, itbpePolygonEdge) { // get edge vertices (edge direction is irrelevant here!) const FLOAT3D &vVertex0 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex0->bvx_vAbsolute; const FLOAT3D &vVertex1 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex1->bvx_vAbsolute; // pass the edge to the intersector isIntersector.AddEdge( vVertex0(iMajorAxis1), vVertex0(iMajorAxis2), vVertex1(iMajorAxis1), vVertex1(iMajorAxis2)); } // if the point is inside polygon if (isIntersector.IsIntersecting()) { // it is below _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return TRUE; // if the point is outside polygon } else { // it is not below _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_ISSTANDINGONPOLYGON); return FALSE; } } // override this to make filtering for what can entity stand on export virtual BOOL AllowForGroundPolygon(CBrushPolygon *pbpo) { return TRUE; } // check whether any cached near polygon is below given point BOOL IsSomeNearPolygonBelowPoint(const FLOAT3D &vPoint, FLOAT fMaxDist) { // otherwise, there is none return FALSE; } // check whether any polygon in a sector is below given point BOOL IsSomeSectorPolygonBelowPoint(CBrushSector *pbsc, const FLOAT3D &vPoint, FLOAT fMaxDist) { // for each polygon in the sector FOREACHINSTATICARRAY(pbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { CBrushPolygon *pbpo = itbpo; // if it is below if (IsPolygonBelowPoint(pbpo, vPoint, fMaxDist)) { // there is some return TRUE; } } // otherwise, there is none return FALSE; } // check whether entity would fall if standing on next position BOOL WouldFallInNextPosition(void) { // if entity doesn't care for falling if (en_fStepDnHeight<0) { // don't check return FALSE; } // if the stand-on polygon is near below if (en_pbpoStandOn!=NULL && IsPolygonBelowPoint(en_pbpoStandOn, en_vNextPosition, en_fStepDnHeight)) { // it won't fall return FALSE; } // make empty list of extra sectors to check CListHead lhActiveSectors; CStaticStackArray &apbpo = en_apbpoNearPolygons; // for each cached near polygon for(INDEX iPolygon=0; iPolygonbpo_pbscSector->bsc_lnInActiveSectors.IsLinked()) { // add it lhActiveSectors.AddTail(pbpo->bpo_pbscSector->bsc_lnInActiveSectors); } } // NOTE: We add non-zoning reference first (if existing), // to speed up cases when standing on moving brushes. // if the reference is a non-zoning brush if (en_penReference!=NULL && en_penReference->en_RenderType==RT_BRUSH &&!(en_penReference->en_ulFlags&ENF_ZONING) && en_penReference->en_pbrBrush!=NULL) { // get first mip of the brush CBrushMip *pbmMip = en_penReference->en_pbrBrush->GetFirstMip(); // for each sector in the brush mip FOREACHINDYNAMICARRAY(pbmMip->bm_abscSectors, CBrushSector, itbsc) { // if it is not added yet if (!itbsc->bsc_lnInActiveSectors.IsLinked()) { // add it lhActiveSectors.AddTail(itbsc->bsc_lnInActiveSectors); } } } // for each zoning sector that this entity is in {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc); // if it is not added yet if (!pbsc->bsc_lnInActiveSectors.IsLinked()) { // add it lhActiveSectors.AddTail(pbsc->bsc_lnInActiveSectors); } ENDFOR;} // for each active sector BOOL bSupportFound = FALSE; FOREACHINLIST(CBrushSector, bsc_lnInActiveSectors, lhActiveSectors, itbsc) { CBrushSector *pbsc = itbsc; // if the sector is zoning if (pbsc->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity->en_ulFlags&ENF_ZONING) { // for non-zoning brush entities in the sector {FOREACHDSTOFSRC(pbsc->bsc_rsEntities, CEntity, en_rdSectors, pen); if (pen->en_RenderType==CEntity::RT_TERRAIN) { if (IsTerrainBelowPoint(pen->en_ptrTerrain, en_vNextPosition, en_fStepDnHeight, en_vGravityDir)) { bSupportFound = TRUE; goto out; } continue; } if (pen->en_RenderType!=CEntity::RT_BRUSH&& pen->en_RenderType!=CEntity::RT_FIELDBRUSH) { break; // brushes are sorted first in list } // get first mip of the brush CBrushMip *pbmMip = pen->en_pbrBrush->GetFirstMip(); // for each sector in the brush mip FOREACHINDYNAMICARRAY(pbmMip->bm_abscSectors, CBrushSector, itbscInMip) { // if it is not added yet if (!itbscInMip->bsc_lnInActiveSectors.IsLinked()) { // add it lhActiveSectors.AddTail(itbscInMip->bsc_lnInActiveSectors); } } ENDFOR;} } // if there is a polygon below in that sector if (IsSomeSectorPolygonBelowPoint(itbsc, en_vNextPosition, en_fStepDnHeight)) { // it won't fall bSupportFound = TRUE; break; } } out:; // clear list of active sectors lhActiveSectors.RemAll(); // if no support, it surely would fall return !bSupportFound; } // clear next position to current placement void ClearNextPosition(void) { en_vNextPosition = en_plPlacement.pl_PositionVector; en_mNextRotation = en_mRotation; } // set current placement from next position void SetPlacementFromNextPosition(void) { _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_SETPLACEMENTFROMNEXTPOSITION); _pfPhysicsProfile.IncrementTimerAveragingCounter((INDEX) CPhysicsProfile::PTI_SETPLACEMENTFROMNEXTPOSITION); CPlacement3D plNew; plNew.pl_PositionVector = en_vNextPosition; DecomposeRotationMatrixNoSnap(plNew.pl_OrientationAngle, en_mNextRotation); FLOATmatrix3D mRotation; MakeRotationMatrixFast(mRotation, plNew.pl_OrientationAngle); SetPlacement_internal(plNew, mRotation, TRUE /* try to optimize for small movements */); // use this for collision code debugging only not useful in real conditions /* // check that we have not ended inside a wall if (GetRenderType()==RT_MODEL) { extern BOOL CanEntityChangeCollisionBox(CEntity *pen, INDEX iNewCollisionBox, CEntity **ppenObstacle); CEntity *penObstacle; if (!CanEntityChangeCollisionBox(this, GetCollisionBoxIndex(), &penObstacle)) { CPrintF("*** COLLISION!!!!\n"); } } */ _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_SETPLACEMENTFROMNEXTPOSITION); } BOOL TryToGoUpstairs(const FLOAT3D &vTranslationAbsolute, const CSurfaceType &stHit, BOOL bHitStairsOrg) { _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_TRYTOGOUPSTAIRS); _pfPhysicsProfile.IncrementTimerAveragingCounter((INDEX) CPhysicsProfile::PTI_TRYTOGOUPSTAIRS); // use only horizontal components of the movement FLOAT3D vTranslationHorizontal; GetNormalComponent(vTranslationAbsolute, en_vGravityDir, vTranslationHorizontal); //CPrintF("Trying: (%g) ", vTranslationHorizontal(3)); // if the movement has no substantial value if(vTranslationHorizontal.Length()<0.001f) { //CPrintF("no value\n"); // don't do it _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOGOUPSTAIRS); return FALSE; } FLOAT3D vTranslationHorizontalOrg = vTranslationHorizontal; // if the surface that is climbed on is not really stairs if (!bHitStairsOrg) { // keep minimum speed vTranslationHorizontal.Normalize(); vTranslationHorizontal*=0.5f; } // remember original placement CPlacement3D plOriginal = en_plPlacement; // take stairs height FLOAT fStairsHeight = 0; if (stHit.st_fStairsHeight>0) { fStairsHeight = Max(stHit.st_fStairsHeight, en_fStepUpHeight); } else if (stHit.st_fStairsHeight<0) { fStairsHeight = Min(stHit.st_fStairsHeight, en_fStepUpHeight); } CContentType &ctDn = en_pwoWorld->wo_actContentTypes[en_iDnContent]; CContentType &ctUp = en_pwoWorld->wo_actContentTypes[en_iUpContent]; // if in partially in water BOOL bGettingOutOfWater = FALSE; if ((ctDn.ct_ulFlags&CTF_SWIMABLE) && !(ctUp.ct_ulFlags&CTF_SWIMABLE) && en_fImmersionFactor>0.3f) { // add immersion height to up step if (en_pciCollisionInfo!=NULL) { fStairsHeight=fStairsHeight*2+en_fImmersionFactor* (en_pciCollisionInfo->ci_fMaxHeight-en_pciCollisionInfo->ci_fMinHeight); // remember that we are trying to get out of water bGettingOutOfWater = TRUE; } } // calculate the 3 translation directions (up, forward and down) FLOAT3D avTranslation[3]; avTranslation[0] = en_vGravityDir*-fStairsHeight; avTranslation[1] = vTranslationHorizontal; avTranslation[2] = en_vGravityDir*fStairsHeight; // for each translation step for(INDEX iStep=0; iStep<3; iStep++) { BOOL bStepOK = TRUE; // create new placement with the translation step en_vNextPosition = en_plPlacement.pl_PositionVector+avTranslation[iStep]; en_mNextRotation = en_mRotation; // clip the movement to the entity's world CClipMove cm(this); en_pwoWorld->ClipMove(cm); // if not passed if (cm.cm_fMovementFraction<1.0f) { // find hit surface INDEX iSurfaceHit = 0; BOOL bHitStairsNow = FALSE; if (cm.cm_pbpoHit!=NULL) { bHitStairsNow = cm.cm_pbpoHit->bpo_ulFlags&BPOF_STAIRS; iSurfaceHit = cm.cm_pbpoHit->bpo_bppProperties.bpp_ubSurfaceType; } CSurfaceType &stHit = en_pwoWorld->wo_astSurfaceTypes[iSurfaceHit]; // check if hit a slope while climbing stairs const FLOAT3D &vHitPlane = cm.cm_plClippedPlane; FLOAT fPlaneDotG = vHitPlane%en_vGravityDir; FLOAT fPlaneDotGAbs = Abs(fPlaneDotG); BOOL bSlidingAllowed = (fPlaneDotGAbs>-0.01f && fPlaneDotGAbs<0.99f)&&bHitStairsOrg; BOOL bEarlyClipAllowed = // going up or iStep==0 || // going forward and hit stairs or (iStep==1 && bHitStairsNow) || // going down and ends on something that is not high slope (iStep==2 && (vHitPlane%en_vGravityDir<-stHit.st_fClimbSlopeCos || bHitStairsNow)); // if early clip is allowed if (bEarlyClipAllowed || bSlidingAllowed) { // try to go to where it is clipped (little bit before) en_vNextPosition = en_plPlacement.pl_PositionVector + avTranslation[iStep]*(cm.cm_fMovementFraction*0.98f); if (bSlidingAllowed && iStep!=2) { FLOAT3D vSliding = cm.cm_plClippedPlane.ProjectDirection( avTranslation[iStep]*(1.0f-cm.cm_fMovementFraction))+ vHitPlane*(ClampUp(avTranslation[iStep].Length(), 0.5f)/100.0f); en_vNextPosition += vSliding; } CClipMove cm(this); en_pwoWorld->ClipMove(cm); // if it failed if (cm.cm_fMovementFraction<=1.0f) { // mark that this step is unsuccessful bStepOK = FALSE; } // if early clip is not allowed } else { // mark that this step is unsuccessful bStepOK = FALSE; } } // if the step is successful if (bStepOK) { // use that position SetPlacementFromNextPosition(); // if the step failed } else { // restore original placement en_vNextPosition = plOriginal.pl_PositionVector; SetPlacementFromNextPosition(); // move is unsuccessful _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOGOUPSTAIRS); //CPrintF("FAILED\n"); return FALSE; } } // end of steps loop // all steps passed, use the final position // NOTE: must not keep the speed when getting out of water, // or the player gets launched out too fast if (!bGettingOutOfWater) { en_vAppliedTranslation += vTranslationHorizontalOrg; } // move is successful _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOGOUPSTAIRS); //CPrintF("done\n"); return TRUE; } /* Try to translate the entity. Slide, climb or push others if needed. */ BOOL TryToMove(CMovableEntity *penPusher, BOOL bTranslate, BOOL bRotate) { //CPrintF("TryToMove(%d)\n", _ctTryToMoveCheckCounter); // decrement the recursion counter if (penPusher!=NULL) { _ctTryToMoveCheckCounter--; } else { _ctTryToMoveCheckCounter-=4; } // if recursing too deep if (_ctTryToMoveCheckCounter<0) { // fail the move return FALSE; } _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); _pfPhysicsProfile.IncrementTimerAveragingCounter((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_TRYTOMOVE); // create new placement with movement if (bTranslate) { en_vNextPosition = en_plPlacement.pl_PositionVector+en_vMoveTranslation; /* STREAMDUMP START if(GetRenderType()==RT_MODEL) { try { CTMemoryStream &strm = *GetDumpStream(); DUMPVECTOR2("Move transl:", en_vMoveTranslation); DUMPVECTOR2("Next pos:", en_vNextPosition); } catch( char *strError) { strError; } } STREAMDUMP END */ } else { en_vNextPosition = en_plPlacement.pl_PositionVector; } if (bRotate) { // CPrintF(" r:???\n"); en_mNextRotation = en_mMoveRotation*en_mRotation; } else { en_mNextRotation = en_mRotation; } // test if rotation can be ignored ULONG ulCIFlags = en_pciCollisionInfo->ci_ulFlags; BOOL bIgnoreRotation = !bRotate || ((ulCIFlags&CIF_IGNOREROTATION)|| ( (ulCIFlags&CIF_IGNOREHEADING) && (en_mMoveRotation(1,2)==0&&en_mMoveRotation(2,2)==1&&en_mMoveRotation(3,2)==0) )); // create movement towards the new placement CClipMove cmMove(this); // clip the movement to the entity's world if (!bTranslate && bIgnoreRotation) { cmMove.cm_fMovementFraction = 2.0f; _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_TRYTOMOVE_FAST); } else { en_pwoWorld->ClipMove(cmMove); } // if the move passes if (cmMove.cm_fMovementFraction>1.0f) { // CPrintF(" passed\n"); //CPrintF("%d\n", MAXCOLLISIONRETRIES-(_ctTryToMoveCheckCounter+1)); // if entity is in walking control now, but it might fall of an edge if (bTranslate && en_penReference!=NULL && (en_ulPhysicsFlags&EPF_TRANSLATEDBYGRAVITY) && !(en_ulPhysicsFlags&(EPF_ONSTEEPSLOPE|EPF_ORIENTINGTOGRAVITY|EPF_FLOATING)) && penPusher==NULL && WouldFallInNextPosition()) { // fail the movement SendEvent(EWouldFall()); //CPrintF(" wouldfall\n"); return FALSE; } // make entity use its new placement SetPlacementFromNextPosition(); if (bTranslate) { en_vAppliedTranslation += en_vMoveTranslation; } if (bRotate) { en_mAppliedRotation = en_mMoveRotation*en_mAppliedRotation; } // move is successful _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_TRYTOMOVE_PASS); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); //CPrintF(" successful\n"); return TRUE; // if the move is clipped } else { _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_TRYTOMOVE_CLIP); /* STREAMDUMP START if(GetRenderType()==RT_MODEL) { try { CTMemoryStream &strm = *GetDumpStream(); strm.FPrintF_t( "Movm Fract: %g\n", cmMove.cm_fMovementFraction); strm.FPrintF_t( "En : ID:%08x %s\n", cmMove.cm_penHit->en_ulID, (const char*) GetName() ); DUMPVECTOR2("Pl col:", cmMove.cm_plClippedPlane); } catch( char *strError) { strError; } } STREAMDUMP END */ // if must not retry if (_ctTryToMoveCheckCounter<=0) { // fail _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); return FALSE; } // if hit brush if (cmMove.cm_pbpoHit!=NULL) { // if polygon is stairs, and the entity can climb stairs if ((cmMove.cm_pbpoHit->bpo_ulFlags&BPOF_STAIRS) &&((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_CLIMBORSLIDE)) { // adjust against sliding upwards cmMove.cm_plClippedPlane = FLOATplane3D(-en_vGravityDir, 0); } // if cannot be damaged by impact INDEX iSurface = cmMove.cm_pbpoHit->bpo_bppProperties.bpp_ubSurfaceType; if (en_pwoWorld->wo_astSurfaceTypes[iSurface].st_ulFlags&STF_NOIMPACT) { // remember that en_ulPhysicsFlags|=EPF_NOIMPACTTHISTICK; } } // if entity is translated by gravity and // the hit plane is more orthogonal to the gravity than the last one found if ((en_ulPhysicsFlags&EPF_TRANSLATEDBYGRAVITY) && !(en_ulPhysicsFlags&EPF_FLOATING) && ( ((en_vGravityDir%(FLOAT3D&)cmMove.cm_plClippedPlane) <(en_vGravityDir%en_vReferencePlane)) ) ) { // remember touched entity as stand-on reference en_penReference = cmMove.cm_penHit; // CPrintF(" newreference id%08x\n", en_penReference->en_ulID); en_vReferencePlane = (FLOAT3D&)cmMove.cm_plClippedPlane; en_pbpoStandOn = cmMove.cm_pbpoHit; // is NULL if not hit a brush if (cmMove.cm_pbpoHit==NULL) { en_iReferenceSurface = 0; } else { en_iReferenceSurface = cmMove.cm_pbpoHit->bpo_bppProperties.bpp_ubSurfaceType; } } // send touch event to this entity and to touched entity SendTouchEvent(cmMove); // if cannot be damaged by impact if (cmMove.cm_penHit->en_ulPhysicsFlags&EPF_NOIMPACT) { // remember that en_ulPhysicsFlags|=EPF_NOIMPACTTHISTICK; } // if entity bounces when blocked FLOAT3D vBounce; BOOL bBounce = FALSE; if ( ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_BOUNCE) && bTranslate) { // create translation speed for bouncing off clipping plane FLOAT3D vParallel, vNormal; GetParallelAndNormalComponents(en_vMoveTranslation, cmMove.cm_plClippedPlane, vNormal, vParallel); vNormal *= -en_fBounceDampNormal; vParallel *= +en_fBounceDampParallel; vBounce = vNormal+vParallel; // if not too small bounce if (vNormal.Length()>0.1f) { // do bounce bBounce = TRUE; } // rotate slower en_aDesiredRotationRelative *= en_fBounceDampParallel; if (en_aDesiredRotationRelative.Length()<10) { en_aDesiredRotationRelative = ANGLE3D(0,0,0); } } // if entity pushes when blocked and the blocking entity is pushable if (penPusher!=NULL&&(cmMove.cm_penHit->en_ulPhysicsFlags&EPF_PUSHABLE)) { CMovableModelEntity *penBlocking = ((CMovableModelEntity *)cmMove.cm_penHit); // create push translation to account for rotating radius FLOAT3D vRadius = cmMove.cm_penHit->en_plPlacement.pl_PositionVector- penPusher->en_plPlacement.pl_PositionVector; FLOAT3D vPush=(vRadius*penPusher->en_mMoveRotation-vRadius); //*(1.01f-cmMove.cm_fMovementFraction); vPush += penPusher->en_vMoveTranslation; //*(1.01f-cmMove.cm_fMovementFraction); penBlocking->en_vMoveTranslation = vPush; penBlocking->en_mMoveRotation = penPusher->en_mMoveRotation; // make sure it is added to the movers list penBlocking->AddToMoversDuringMoving(); // push the blocking entity _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); BOOL bUnblocked = penBlocking->TryToMove(penPusher, bTranslate, bRotate); _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); // if it has removed itself if (bUnblocked) { // retry the movement ClearNextPosition(); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); return TryToMove(penPusher, bTranslate, bRotate); } else { // move is unsuccessful SendBlockEvent(cmMove); ClearNextPosition(); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); return FALSE; } // if entity slides if blocked } else if ( ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_SLIDE)|| ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_BOUNCE)|| ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_CLIMBORSLIDE)|| ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_STOPEXACT) ){ // if translating if (bTranslate) { // create translation for sliding along clipping plane FLOAT3D vSliding; // if sliding along one plane if (_ctSliding==0) { // remember sliding parameters from the plane _vSlideOffDir = cmMove.cm_plClippedPlane; // get sliding velocity vSliding = cmMove.cm_plClippedPlane.ProjectDirection( en_vMoveTranslation*(1.0f-cmMove.cm_fMovementFraction)); _ctSliding++; // if second plane } else if (_ctSliding==1) { // off direction is away from both planes _vSlideOffDir+=cmMove.cm_plClippedPlane; // sliding direction is along both planes _vSlideDir = _vSlideOffDir*(FLOAT3D&)cmMove.cm_plClippedPlane; if (_vSlideDir.Length()>0.001f) { _vSlideDir.Normalize(); } _ctSliding++; // get sliding velocity GetParallelComponent(en_vMoveTranslation*(1.0f-cmMove.cm_fMovementFraction), _vSlideDir, vSliding); // if more than two planes } else { // off direction is away from all planes _vSlideOffDir+=cmMove.cm_plClippedPlane; // sliding direction is along all planes _vSlideDir = cmMove.cm_plClippedPlane.ProjectDirection(_vSlideDir); _ctSliding++; // get sliding velocity GetParallelComponent(en_vMoveTranslation*(1.0f-cmMove.cm_fMovementFraction), _vSlideDir, vSliding); } ASSERT(IsValidFloat(vSliding(1))); ASSERT(IsValidFloat(_vSlideDir(1))); ASSERT(IsValidFloat(_vSlideOffDir(1))); // if entity hit a brush polygon if (cmMove.cm_pbpoHit!=NULL) { CSurfaceType &stHit = en_pwoWorld->wo_astSurfaceTypes[ cmMove.cm_pbpoHit->bpo_bppProperties.bpp_ubSurfaceType]; // if it is not being pushed, and it can climb stairs if (penPusher==NULL &&(en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_CLIMBORSLIDE) { // NOTE: originally, the polygon's plane was considered here. // due to sphere-polygon collision algo, it is possible for the collision // plane to be even orthogonal to the polygon plane. // considering polygon's plane prevented climbing up the stairs. // so now, the collision plane is considered. // if there are any further problems, i recommend choosing // the plane that is more orthogonal to the movement direction. FLOAT3D &vHitPlane = (FLOAT3D&)cmMove.cm_plClippedPlane;//cmMove.cm_pbpoHit->bpo_pbplPlane->bpl_plAbsolute; BOOL bHitStairs = cmMove.cm_pbpoHit->bpo_ulFlags&BPOF_STAIRS; // if the plane hit is steep enough to climb on it // (cannot climb low slopes as if those were stairs) if ((vHitPlane%en_vGravityDir>-stHit.st_fClimbSlopeCos) ||bHitStairs) { // if sliding along it would be mostly horizontal // (i.e. cannot climb up the slope) FLOAT fSlidingVertical2 = en_vMoveTranslation%en_vGravityDir; fSlidingVertical2*=fSlidingVertical2; FLOAT fSliding2 = en_vMoveTranslation%en_vMoveTranslation; if ((2*fSlidingVertical2<=fSliding2) // if can go upstairs && TryToGoUpstairs(en_vMoveTranslation, stHit, bHitStairs)) { // movement is ok _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); return FALSE; } } } } // entity shouldn't really slide if ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_STOPEXACT) { // kill sliding vSliding = FLOAT3D(0,0,0); } ASSERT(IsValidFloat(vSliding(1))); // add a component perpendicular to the sliding plane vSliding += _vSlideOffDir* (ClampUp(en_vMoveTranslation.Length(), 0.5f)/100.0f); // if initial movement has some substantial value if(en_vMoveTranslation.Length()>0.001f && cmMove.cm_fMovementFraction>0.002f) { // go to where it is clipped (little bit before) vSliding += en_vMoveTranslation*(cmMove.cm_fMovementFraction*0.98f); } // ignore extremely small sliding if (vSliding.ManhattanNorm()<0.001f) { _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); return FALSE; } // recurse en_vMoveTranslation = vSliding; ClearNextPosition(); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); TryToMove(penPusher, bTranslate, bRotate); // if bouncer if (bBounce) { // remember bouncing speed for next tick en_vAppliedTranslation = vBounce; // no reference while bouncing en_penReference = NULL; en_vReferencePlane = FLOAT3D(0.0f, 0.0f, 0.0f); en_iReferenceSurface = 0; } // move is not entirely successful return FALSE; // if rotating } else if (bRotate) { // if bouncing entity if ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_BOUNCE) { // rotate slower en_aDesiredRotationRelative *= en_fBounceDampParallel; if (en_aDesiredRotationRelative.Length()<10) { en_aDesiredRotationRelative = ANGLE3D(0,0,0); } _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); // move is not successful return FALSE; } // create movement getting away from the collision point en_vMoveTranslation = cmMove.cm_vClippedLine*-1.2f; // recurse ClearNextPosition(); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); TryToMove(penPusher, TRUE, bRotate); // move is not entirely successful return FALSE; } // not translating and not rotating? - move is unsuccessful _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); return FALSE; // if entity has some other behaviour when blocked } else { // move is unsuccessful (EPF_ONBLOCK_STOP is assumed) SendBlockEvent(cmMove); ClearNextPosition(); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_TRYTOTRANSLATE); return FALSE; } } } /* STREAMDUMP START void ExportEntityPlacementAndSpeed(CMovableEntity &en, CTString strDes) { try { CTMemoryStream &strm = *GetDumpStream(); strm.FPrintF_t("%s : %s .................\n", strDes, (const char*) GetName()); DUMPPLACEMENT("En pl:", en.en_plPlacement); DUMPVECTOR2("DesTraRel:", en.en_vDesiredTranslationRelative); DUMPVECTOR2("CurTraAbs:", en.en_vCurrentTranslationAbsolute); DUMPVECTOR2("IntendTra:", en.en_vIntendedTranslation); } catch( char *strError) { strError; } } STREAMDUMP END */ // clear eventual temporary variables that are not persistent export void ClearMovingTemp(void) { // return; // CLEARMEM(en_vIntendedTranslation); // CLEARMEM(en_mIntendedRotation); ClearNextPosition(); CLEARMEM(en_vMoveTranslation); CLEARMEM(en_mMoveRotation); CLEARMEM(en_vAppliedTranslation); CLEARMEM(en_mAppliedRotation); } // prepare parameters for moving in this tick export void PreMoving(void) { // // NOTE: // This is one more cludge for bad VC6 code generator creating sync errors in older demos. // We are getting rid of the whole virtual actions system ASAP when the network is recoded. // // FOR LICENSEES: Since you don't need to replay old demos from SSam, you may remove the whole // PreMovingOld() function. // if (_pNetwork->ga_ulDemoMinorVersion<=5) { PreMovingOld(); } else { PreMovingNew(); } } void PreMovingNew(void) { if (en_pciCollisionInfo==NULL) { return; } /* STREAMDUMP START ExportEntityPlacementAndSpeed( *(CMovableEntity *)this, "Pre moving (start of function)"); STREAMDUMP END */ _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_PREMOVING); _pfPhysicsProfile.IncrementTimerAveragingCounter((INDEX) CPhysicsProfile::PTI_PREMOVING); // remember old placement for lerping en_plLastPlacement = en_plPlacement; // for each child of the mover {FOREACHINLIST(CEntity, en_lnInParent, en_lhChildren, itenChild) { // if the child is movable, yet not in movers list if ((itenChild->en_ulPhysicsFlags&EPF_MOVABLE) &&!((CMovableEntity*)&*itenChild)->en_lnInMovers.IsLinked()) { CMovableEntity *penChild = ((CMovableEntity*)&*itenChild); // remember old placement for lerping penChild->en_plLastPlacement = penChild->en_plPlacement; } }} FLOAT fTickQuantum=_pTimer->TickQuantum; // used for normalizing from SI units to game ticks // trig break point if required if( dbg_bBreak) { dbg_bBreak = FALSE; // auto turn off breakpoint when triggered try { Breakpoint(); } catch (ANYEXCEPTION) { CPrintF("Breakpoint!\n"); }; } // NOTE: this limits maximum velocity of any entity in game. // it is absolutely neccessary in order to prevent extreme slowdowns in physics. // if you plan to increase this one radically, consider decreasing // collision grid cell size! // currently limited to a bit less than speed of sound (not that it is any specificaly // relevant constant, but it is just handy) const FLOAT fMaxSpeed = 300.0f; en_vCurrentTranslationAbsolute(1)=Clamp(en_vCurrentTranslationAbsolute(1), -fMaxSpeed, +fMaxSpeed); en_vCurrentTranslationAbsolute(2)=Clamp(en_vCurrentTranslationAbsolute(2), -fMaxSpeed, +fMaxSpeed); en_vCurrentTranslationAbsolute(3)=Clamp(en_vCurrentTranslationAbsolute(3), -fMaxSpeed, +fMaxSpeed); // if the entity is a model if (en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL || en_RenderType==RT_SKAMODEL || en_RenderType==RT_SKAEDITORMODEL) { // test for field containment TestFields(en_iUpContent, en_iDnContent, en_fImmersionFactor); // if entity has sticky feet if (en_ulPhysicsFlags & EPF_STICKYFEET) { // find gravity towards nearest polygon FLOAT3D vPoint; FLOATplane3D plPlane; FLOAT fDistanceToEdge; if (GetNearestPolygon(vPoint, plPlane, fDistanceToEdge)) { en_vGravityDir = -(FLOAT3D&)plPlane; } } } CContentType &ctDn = en_pwoWorld->wo_actContentTypes[en_iDnContent]; CContentType &ctUp = en_pwoWorld->wo_actContentTypes[en_iUpContent]; // test entity breathing TestBreathing(ctUp); // test content damage TestContentDamage(ctDn, en_fImmersionFactor); // test surface damage if (en_penReference!=NULL) { CSurfaceType &stReference = en_pwoWorld->wo_astSurfaceTypes[en_iReferenceSurface]; TestSurfaceDamage(stReference); } // calculate content fluid factors FLOAT fBouyancy = (1- (ctDn.ct_fDensity/en_fDensity)*en_fImmersionFactor- (ctUp.ct_fDensity/en_fDensity)*(1-en_fImmersionFactor)); FLOAT fSpeedModifier = ctDn.ct_fSpeedMultiplier*en_fImmersionFactor+ ctUp.ct_fSpeedMultiplier*(1-en_fImmersionFactor); FLOAT fFluidFriction = ctDn.ct_fFluidFriction*en_fImmersionFactor+ ctUp.ct_fFluidFriction*(1-en_fImmersionFactor); FLOAT fControlMultiplier = ctDn.ct_fControlMultiplier*en_fImmersionFactor+ ctUp.ct_fControlMultiplier*(1-en_fImmersionFactor); // transform relative desired translation into absolute FLOAT3D vDesiredTranslationAbsolute = en_vDesiredTranslationRelative; // relative absolute if (!(en_ulPhysicsFlags & EPF_ABSOLUTETRANSLATE)) { vDesiredTranslationAbsolute *= en_mRotation; } // transform translation and rotation into tick time units vDesiredTranslationAbsolute*=fTickQuantum; ANGLE3D aRotationRelative; aRotationRelative(1) = en_aDesiredRotationRelative(1)*fTickQuantum; aRotationRelative(2) = en_aDesiredRotationRelative(2)*fTickQuantum; aRotationRelative(3) = en_aDesiredRotationRelative(3)*fTickQuantum; // make absolute matrix rotation from relative angle rotation FLOATmatrix3D mRotationAbsolute; if ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_PUSH) { FLOATmatrix3D mNewRotation; MakeRotationMatrixFast(mNewRotation, en_plPlacement.pl_OrientationAngle+aRotationRelative); mRotationAbsolute = mNewRotation*!en_mRotation; } else { MakeRotationMatrixFast(mRotationAbsolute, aRotationRelative); mRotationAbsolute = en_mRotation*(mRotationAbsolute*!en_mRotation); } // modify desired speed for fluid parameters vDesiredTranslationAbsolute*=fSpeedModifier; // remember jumping strength (if any) FLOAT fJump = -en_mRotation.GetColumn(2)%vDesiredTranslationAbsolute; BOOL bReferenceMovingInY = FALSE; BOOL bReferenceRotatingNonY = FALSE; // if we have a CMovableEntity for a reference entity if (en_penReference!=NULL && (en_penReference->en_ulPhysicsFlags&EPF_MOVABLE)) { CMovableEntity *penReference = (CMovableEntity *)(CEntity*)en_penReference; // get reference deltas for this tick const FLOAT3D &vReferenceTranslation = penReference->en_vIntendedTranslation; const FLOATmatrix3D &mReferenceRotation = penReference->en_mIntendedRotation; // calculate radius of this entity relative to reference FLOAT3D vRadius = en_plPlacement.pl_PositionVector -penReference->en_plPlacement.pl_PositionVector; FLOAT3D vReferenceDelta = vReferenceTranslation + vRadius*mReferenceRotation - vRadius; // add the deltas to this entity vDesiredTranslationAbsolute += vReferenceDelta; mRotationAbsolute = mReferenceRotation*mRotationAbsolute; // remember if reference is moving in y bReferenceMovingInY = (vReferenceDelta%en_vGravityDir != 0.0f); bReferenceRotatingNonY = ((en_vGravityDir*mReferenceRotation)%en_vGravityDir)>0.01f; } FLOAT3D vTranslationAbsolute = en_vCurrentTranslationAbsolute*fTickQuantum; // initially not orienting en_ulPhysicsFlags&=~EPF_ORIENTINGTOGRAVITY; // if the entity is rotated by gravity if (en_ulPhysicsFlags&EPF_ORIENTEDBYGRAVITY) { // find entity's down vector FLOAT3D vDown; vDown(1) = -en_mRotation(1,2); vDown(2) = -en_mRotation(2,2); vDown(3) = -en_mRotation(3,2); // find angle entities down and gravity down FLOAT fCos = vDown%en_vGravityDir; // if substantial if (fCos<0.99999f) { // mark en_ulPhysicsFlags|=EPF_ORIENTINGTOGRAVITY; // limit the angle rotation ANGLE a = ACos(fCos); if (Abs(a)>20) { a = 20*Sgn(a); } FLOAT fRad =RadAngle(a); // make rotation axis FLOAT3D vAxis = vDown*en_vGravityDir; FLOAT fLen = vAxis.Length(); if (fLen<0.01f) { vAxis(1) = en_mRotation(1,3); vAxis(2) = en_mRotation(2,3); vAxis(3) = en_mRotation(3,3); // NOTE: must have this patch for smooth rocking on moving brushes // (should infact do fRad/=fLen always) } else if (!bReferenceRotatingNonY) { fRad/=fLen; } vAxis*=fRad; // make rotation matrix FLOATmatrix3D mGRotation; mGRotation(1,1) = 1; mGRotation(1,2) = -vAxis(3); mGRotation(1,3) = vAxis(2); mGRotation(2,1) = vAxis(3); mGRotation(2,2) = 1; mGRotation(2,3) = -vAxis(1); mGRotation(3,1) = -vAxis(2); mGRotation(3,2) = vAxis(1); mGRotation(3,3) = 1; OrthonormalizeRotationMatrix(mGRotation); // add the gravity rotation mRotationAbsolute = mGRotation*mRotationAbsolute; } } // initially not floating en_ulPhysicsFlags&=~EPF_FLOATING; FLOAT ACC=en_fAcceleration*fTickQuantum*fTickQuantum; FLOAT DEC=en_fDeceleration*fTickQuantum*fTickQuantum; // if the entity is not affected by gravity if (!(en_ulPhysicsFlags&EPF_TRANSLATEDBYGRAVITY)) { // accellerate towards desired absolute translation if (en_ulPhysicsFlags&EPF_NOACCELERATION) { vTranslationAbsolute = vDesiredTranslationAbsolute; } else { AddAcceleration(vTranslationAbsolute, vDesiredTranslationAbsolute, ACC*fControlMultiplier, DEC*fControlMultiplier); } // if swimming } else if ((fBouyancy*en_fGravityA<0.5f && (ctDn.ct_ulFlags&(CTF_SWIMABLE|CTF_FLYABLE)))) { // mark that en_ulPhysicsFlags|=EPF_FLOATING; // accellerate towards desired absolute translation if (en_ulPhysicsFlags&EPF_NOACCELERATION) { vTranslationAbsolute = vDesiredTranslationAbsolute; } else { AddAcceleration(vTranslationAbsolute, vDesiredTranslationAbsolute, ACC*fControlMultiplier, DEC*fControlMultiplier); } // add gravity acceleration if (fBouyancy<-0.1f) { FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; FLOAT fGA=(en_fGravityA*-fBouyancy)*fTickQuantum*fTickQuantum; AddAcceleration(vTranslationAbsolute, en_vGravityDir*-fGV, fGA, fGA); } else if (fBouyancy>+0.1f) { FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; FLOAT fGA=(en_fGravityA*fBouyancy)*fTickQuantum*fTickQuantum; AddAcceleration(vTranslationAbsolute, en_vGravityDir*fGV, fGA, fGA); } // if the entity is affected by gravity } else { BOOL bGravityAlongPolygon = TRUE; // if there is no fixed remembered stand-on polygon or the entity is not on it anymore if (en_pbpoStandOn==NULL || !IsStandingOnPolygon(en_pbpoStandOn) || bReferenceMovingInY || (en_ulPhysicsFlags&EPF_ORIENTINGTOGRAVITY)) { // clear the stand on polygon en_pbpoStandOn=NULL; if (en_penReference == NULL || bReferenceMovingInY) { bGravityAlongPolygon = FALSE; } } // if gravity can cause the entity to fall if (!bGravityAlongPolygon) { _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_GRAVITY_NONTRIVIAL); // add gravity acceleration FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; FLOAT fGA=(en_fGravityA*fBouyancy)*fTickQuantum*fTickQuantum; AddGAcceleration(vTranslationAbsolute, en_vGravityDir, fGA, fGV); // if entity can only slide down its stand-on polygon } else { _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_GRAVITY_TRIVIAL); // disassemble gravity to parts parallel and normal to plane FLOAT3D vPolygonDir = -en_vReferencePlane; // NOTE: normal to plane=paralel to plane normal vector! FLOAT3D vGParallel, vGNormal; GetParallelAndNormalComponents(en_vGravityDir, vPolygonDir, vGNormal, vGParallel); // add gravity part parallel to plane FLOAT fFactor = vGParallel.Length(); if (fFactor>0.001f) { FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; FLOAT fGA=(en_fGravityA*fBouyancy)*fTickQuantum*fTickQuantum; AddGAcceleration(vTranslationAbsolute, vGParallel/fFactor, fGA*fFactor, fGV*fFactor); } // kill your normal-to-polygon speed if towards polygon and small FLOAT fPolyGA = (vPolygonDir%en_vGravityDir)*en_fGravityA; FLOAT fYSpeed = vPolygonDir%vTranslationAbsolute; if (fYSpeed>0 && fYSpeed < fPolyGA) { vTranslationAbsolute -= vPolygonDir*fYSpeed; } // if a bouncer if ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_BOUNCE) { // rotate slower en_aDesiredRotationRelative *= en_fJumpControlMultiplier; if (en_aDesiredRotationRelative.Length()<10) { en_aDesiredRotationRelative = ANGLE3D(0,0,0); } } } CSurfaceType &stReference = en_pwoWorld->wo_astSurfaceTypes[en_iReferenceSurface]; // if it has a reference entity if (en_penReference!=NULL) { FLOAT fPlaneY = (en_vGravityDir%en_vReferencePlane); FLOAT fPlaneYAbs = Abs(fPlaneY); FLOAT fFriction = stReference.st_fFriction; // if on a steep slope if ((fPlaneY>=-stReference.st_fClimbSlopeCos&&fPlaneY<0) ||((stReference.st_ulFlags&STF_SLIDEDOWNSLOPE)&&fPlaneY>-0.99f)) { en_ulPhysicsFlags|=EPF_ONSTEEPSLOPE; // accellerate horizontaly towards desired absolute translation AddAccelerationOnPlane2( vTranslationAbsolute, vDesiredTranslationAbsolute, ACC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, DEC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, en_vReferencePlane, en_vGravityDir); // if not on a steep slope } else { en_ulPhysicsFlags&=~EPF_ONSTEEPSLOPE; // accellerate on plane towards desired absolute translation AddAccelerationOnPlane( vTranslationAbsolute, vDesiredTranslationAbsolute, ACC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, DEC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, en_vReferencePlane); } // if wants to jump and can jump if (fJump<-0.01f && (fPlaneY<-stReference.st_fJumpSlopeCos || _pTimer->CurrentTick()>en_tmLastSignificantVerticalMovement+0.25f) ) { // jump vTranslationAbsolute += en_vGravityDir*fJump; en_tmJumped = _pTimer->CurrentTick(); en_pbpoStandOn = NULL; } // if it doesn't have a reference entity } else {//if (en_penReference==NULL) // if can control after jump if (_pTimer->CurrentTick()-en_tmJumpedCurrentTick()>en_tmLastSignificantVerticalMovement+0.25f) { // jump vTranslationAbsolute += en_vGravityDir*fJump; en_tmJumped = _pTimer->CurrentTick(); en_pbpoStandOn = NULL; } } } // check for force-field acceleration // NOTE: pulled out because of a bug in VC code generator, see function comments above CheckAndAddGAcceleration(this, vTranslationAbsolute, fTickQuantum); // if there is fluid friction involved if (fFluidFriction>0.01f) { // slow down AddAcceleration(vTranslationAbsolute, FLOAT3D(0.0f, 0.0f, 0.0f), 0.0f, DEC*fFluidFriction); } // if may slow down spinning if ( (en_ulPhysicsFlags& EPF_CANFADESPINNING) && ( (ctDn.ct_ulFlags&CTF_FADESPINNING) || (ctUp.ct_ulFlags&CTF_FADESPINNING) ) ) { // reduce desired rotation en_aDesiredRotationRelative *= (1-fSpeedModifier*0.05f); if (en_aDesiredRotationRelative.Length()<10) { en_aDesiredRotationRelative = ANGLE3D(0,0,0); } } // discard reference entity (will be recalculated) if (en_pbpoStandOn==NULL && (vTranslationAbsolute.ManhattanNorm()>1E-5f || en_vReferencePlane%en_vGravityDir<0.0f)) { en_penReference = NULL; en_vReferencePlane = FLOAT3D(0.0f, 0.0f, 0.0f); en_iReferenceSurface = 0; } en_vIntendedTranslation = vTranslationAbsolute; en_mIntendedRotation = mRotationAbsolute; //-- estimate future movements for collision caching // make box of the entity for its current rotation FLOATaabbox3D box; en_pciCollisionInfo->MakeBoxAtPlacement(FLOAT3D(0,0,0), en_mRotation, box); // if it is a light source {CLightSource *pls = GetLightSource(); if (pls!=NULL && !(pls->ls_ulFlags&LSF_LENSFLAREONLY)) { // expand the box to be sure that it contains light range ASSERT(!(pls->ls_ulFlags&LSF_DIRECTIONAL)); box |= FLOATaabbox3D(FLOAT3D(0,0,0), pls->ls_rFallOff); }} // add a bit around it box.ExpandByFactor( phy_fCollisionCacheAround-1.0f); // make box go few ticks ahead of the entity box += en_plPlacement.pl_PositionVector; en_boxMovingEstimate = box; box += en_vIntendedTranslation*phy_fCollisionCacheAhead; en_boxMovingEstimate |= box; // clear applied movement to be updated during movement en_vAppliedTranslation = FLOAT3D(0.0f, 0.0f, 0.0f); en_mAppliedRotation.Diagonal(1.0f); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_PREMOVING); // STREAMDUMP ExportEntityPlacementAndSpeed( *(CMovableEntity *)this, "Pre moving (end of function)"); } // VC6 KLUDGE - do not change this function // See note above in PreMoving() /* old */ void PreMovingOld(void) /* old */ { /* old */ if (en_pciCollisionInfo==NULL) { /* old */ return; /* old */ } /* old */ /* old */ //STREAMDUMP START /* old */ // ExportEntityPlacementAndSpeed( *(CMovableEntity *)this, "Pre moving (start of function)"); /* old */ //STREAMDUMP END /* old */ /* old */ _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_PREMOVING); /* old */ _pfPhysicsProfile.IncrementTimerAveragingCounter(CPhysicsProfile::PTI_PREMOVING); /* old */ /* old */ // remember old placement for lerping /* old */ en_plLastPlacement = en_plPlacement; /* old */ /* old */ // for each child of the mover /* old */ {FOREACHINLIST(CEntity, en_lnInParent, en_lhChildren, itenChild) { /* old */ // if the child is movable, yet not in movers list /* old */ if ((itenChild->en_ulPhysicsFlags&EPF_MOVABLE) /* old */ &&!((CMovableEntity*)&*itenChild)->en_lnInMovers.IsLinked()) { /* old */ CMovableEntity *penChild = ((CMovableEntity*)&*itenChild); /* old */ // remember old placement for lerping /* old */ penChild->en_plLastPlacement = penChild->en_plPlacement; /* old */ } /* old */ }} /* old */ /* old */ FLOAT fTickQuantum=_pTimer->TickQuantum; // used for normalizing from SI units to game ticks /* old */ /* old */ // trig break point if required /* old */ if( dbg_bBreak) { /* old */ dbg_bBreak = FALSE; // auto turn off breakpoint when triggered /* old */ try { /* old */ Breakpoint(); /* old */ } catch (ANYEXCEPTION) { /* old */ CPrintF("Breakpoint!\n"); /* old */ }; /* old */ } /* old */ /* old */ // NOTE: this limits maximum velocity of any entity in game. /* old */ // it is absolutely neccessary in order to prevent extreme slowdowns in physics. /* old */ // if you plan to increase this one radically, consider decreasing /* old */ // collision grid cell size! /* old */ // currently limited to a bit less than speed of sound (not that it is any specificaly /* old */ // relevant constant, but it is just handy) /* old */ const FLOAT fMaxSpeed = 300.0f; /* old */ en_vCurrentTranslationAbsolute(1)=Clamp(en_vCurrentTranslationAbsolute(1), -fMaxSpeed, +fMaxSpeed); /* old */ en_vCurrentTranslationAbsolute(2)=Clamp(en_vCurrentTranslationAbsolute(2), -fMaxSpeed, +fMaxSpeed); /* old */ en_vCurrentTranslationAbsolute(3)=Clamp(en_vCurrentTranslationAbsolute(3), -fMaxSpeed, +fMaxSpeed); /* old */ /* old */ // if the entity is a model /* old */ if (en_RenderType==RT_MODEL || en_RenderType==RT_EDITORMODEL) { /* old */ // test for field containment /* old */ TestFields(en_iUpContent, en_iDnContent, en_fImmersionFactor); /* old */ // if entity has sticky feet /* old */ if (en_ulPhysicsFlags & EPF_STICKYFEET) { /* old */ // find gravity towards nearest polygon /* old */ FLOAT3D vPoint; /* old */ FLOATplane3D plPlane; /* old */ FLOAT fDistanceToEdge; /* old */ if (GetNearestPolygon(vPoint, plPlane, fDistanceToEdge)) { /* old */ en_vGravityDir = -(FLOAT3D&)plPlane; /* old */ } /* old */ } /* old */ } /* old */ CContentType &ctDn = en_pwoWorld->wo_actContentTypes[en_iDnContent]; /* old */ CContentType &ctUp = en_pwoWorld->wo_actContentTypes[en_iUpContent]; /* old */ /* old */ // test entity breathing /* old */ TestBreathing(ctUp); /* old */ // test content damage /* old */ TestContentDamage(ctDn, en_fImmersionFactor); /* old */ // test surface damage /* old */ if (en_penReference!=NULL) { /* old */ CSurfaceType &stReference = en_pwoWorld->wo_astSurfaceTypes[en_iReferenceSurface]; /* old */ TestSurfaceDamage(stReference); /* old */ } /* old */ /* old */ // calculate content fluid factors /* old */ FLOAT fBouyancy = (1- /* old */ (ctDn.ct_fDensity/en_fDensity)*en_fImmersionFactor- /* old */ (ctUp.ct_fDensity/en_fDensity)*(1-en_fImmersionFactor)); /* old */ FLOAT fSpeedModifier = /* old */ ctDn.ct_fSpeedMultiplier*en_fImmersionFactor+ /* old */ ctUp.ct_fSpeedMultiplier*(1-en_fImmersionFactor); /* old */ FLOAT fFluidFriction = /* old */ ctDn.ct_fFluidFriction*en_fImmersionFactor+ /* old */ ctUp.ct_fFluidFriction*(1-en_fImmersionFactor); /* old */ FLOAT fControlMultiplier = /* old */ ctDn.ct_fControlMultiplier*en_fImmersionFactor+ /* old */ ctUp.ct_fControlMultiplier*(1-en_fImmersionFactor); /* old */ /* old */ // transform relative desired translation into absolute /* old */ FLOAT3D vDesiredTranslationAbsolute = en_vDesiredTranslationRelative; /* old */ // relative absolute /* old */ if (!(en_ulPhysicsFlags & EPF_ABSOLUTETRANSLATE)) { /* old */ vDesiredTranslationAbsolute *= en_mRotation; /* old */ } /* old */ // transform translation and rotation into tick time units /* old */ vDesiredTranslationAbsolute*=fTickQuantum; /* old */ ANGLE3D aRotationRelative; /* old */ aRotationRelative(1) = en_aDesiredRotationRelative(1)*fTickQuantum; /* old */ aRotationRelative(2) = en_aDesiredRotationRelative(2)*fTickQuantum; /* old */ aRotationRelative(3) = en_aDesiredRotationRelative(3)*fTickQuantum; /* old */ // make absolute matrix rotation from relative angle rotation /* old */ FLOATmatrix3D mRotationAbsolute; /* old */ /* old */ if ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_PUSH) { /* old */ FLOATmatrix3D mNewRotation; /* old */ MakeRotationMatrixFast(mNewRotation, en_plPlacement.pl_OrientationAngle+aRotationRelative); /* old */ mRotationAbsolute = mNewRotation*!en_mRotation; /* old */ /* old */ } else { /* old */ MakeRotationMatrixFast(mRotationAbsolute, aRotationRelative); /* old */ mRotationAbsolute = en_mRotation*(mRotationAbsolute*!en_mRotation); /* old */ } /* old */ /* old */ // modify desired speed for fluid parameters /* old */ vDesiredTranslationAbsolute*=fSpeedModifier; /* old */ /* old */ // remember jumping strength (if any) /* old */ FLOAT fJump = -en_mRotation.GetColumn(2)%vDesiredTranslationAbsolute; /* old */ /* old */ BOOL bReferenceMovingInY = FALSE; /* old */ BOOL bReferenceRotatingNonY = FALSE; /* old */ // if we have a CMovableEntity for a reference entity /* old */ if (en_penReference!=NULL && (en_penReference->en_ulPhysicsFlags&EPF_MOVABLE)) { /* old */ CMovableEntity *penReference = (CMovableEntity *)(CEntity*)en_penReference; /* old */ // get reference deltas for this tick /* old */ const FLOAT3D &vReferenceTranslation = penReference->en_vIntendedTranslation; /* old */ const FLOATmatrix3D &mReferenceRotation = penReference->en_mIntendedRotation; /* old */ // calculate radius of this entity relative to reference /* old */ FLOAT3D vRadius = en_plPlacement.pl_PositionVector /* old */ -penReference->en_plPlacement.pl_PositionVector; /* old */ FLOAT3D vReferenceDelta = vReferenceTranslation + vRadius*mReferenceRotation - vRadius; /* old */ // add the deltas to this entity /* old */ vDesiredTranslationAbsolute += vReferenceDelta; /* old */ mRotationAbsolute = mReferenceRotation*mRotationAbsolute; /* old */ /* old */ // remember if reference is moving in y /* old */ bReferenceMovingInY = (vReferenceDelta%en_vGravityDir != 0.0f); /* old */ bReferenceRotatingNonY = ((en_vGravityDir*mReferenceRotation)%en_vGravityDir)>0.01f; /* old */ } /* old */ /* old */ FLOAT3D vTranslationAbsolute = en_vCurrentTranslationAbsolute*fTickQuantum; /* old */ /* old */ // initially not orienting /* old */ en_ulPhysicsFlags&=~EPF_ORIENTINGTOGRAVITY; /* old */ // if the entity is rotated by gravity /* old */ if (en_ulPhysicsFlags&EPF_ORIENTEDBYGRAVITY) { /* old */ // find entity's down vector /* old */ FLOAT3D vDown; /* old */ vDown(1) = -en_mRotation(1,2); /* old */ vDown(2) = -en_mRotation(2,2); /* old */ vDown(3) = -en_mRotation(3,2); /* old */ /* old */ // find angle entities down and gravity down /* old */ FLOAT fCos = vDown%en_vGravityDir; /* old */ // if substantial /* old */ if (fCos<0.99999f) { /* old */ // mark /* old */ en_ulPhysicsFlags|=EPF_ORIENTINGTOGRAVITY; /* old */ /* old */ // limit the angle rotation /* old */ ANGLE a = ACos(fCos); /* old */ if (Abs(a)>20) { /* old */ a = 20*Sgn(a); /* old */ } /* old */ FLOAT fRad =RadAngle(a); /* old */ /* old */ // make rotation axis /* old */ FLOAT3D vAxis = vDown*en_vGravityDir; /* old */ FLOAT fLen = vAxis.Length(); /* old */ if (fLen<0.01f) { /* old */ vAxis(1) = en_mRotation(1,3); /* old */ vAxis(2) = en_mRotation(2,3); /* old */ vAxis(3) = en_mRotation(3,3); /* old */ // NOTE: must have this patch for smooth rocking on moving brushes /* old */ // (should infact do fRad/=fLen always) /* old */ } else if (!bReferenceRotatingNonY) { /* old */ fRad/=fLen; /* old */ } /* old */ vAxis*=fRad; /* old */ /* old */ // make rotation matrix /* old */ FLOATmatrix3D mGRotation; /* old */ mGRotation(1,1) = 1; mGRotation(1,2) = -vAxis(3); mGRotation(1,3) = vAxis(2); /* old */ mGRotation(2,1) = vAxis(3); mGRotation(2,2) = 1; mGRotation(2,3) = -vAxis(1); /* old */ mGRotation(3,1) = -vAxis(2); mGRotation(3,2) = vAxis(1); mGRotation(3,3) = 1; /* old */ OrthonormalizeRotationMatrix(mGRotation); /* old */ /* old */ // add the gravity rotation /* old */ mRotationAbsolute = mGRotation*mRotationAbsolute; /* old */ } /* old */ } /* old */ /* old */ // initially not floating /* old */ en_ulPhysicsFlags&=~EPF_FLOATING; /* old */ /* old */ FLOAT ACC=en_fAcceleration*fTickQuantum*fTickQuantum; /* old */ FLOAT DEC=en_fDeceleration*fTickQuantum*fTickQuantum; /* old */ // if the entity is not affected by gravity /* old */ if (!(en_ulPhysicsFlags&EPF_TRANSLATEDBYGRAVITY)) { /* old */ // accellerate towards desired absolute translation /* old */ if (en_ulPhysicsFlags&EPF_NOACCELERATION) { /* old */ vTranslationAbsolute = vDesiredTranslationAbsolute; /* old */ } else { /* old */ AddAcceleration(vTranslationAbsolute, vDesiredTranslationAbsolute, /* old */ ACC*fControlMultiplier, /* old */ DEC*fControlMultiplier); /* old */ } /* old */ // if swimming /* old */ } else if ((fBouyancy*en_fGravityA<0.5f && (ctDn.ct_ulFlags&(CTF_SWIMABLE|CTF_FLYABLE)))) { /* old */ // mark that /* old */ en_ulPhysicsFlags|=EPF_FLOATING; /* old */ // accellerate towards desired absolute translation /* old */ if (en_ulPhysicsFlags&EPF_NOACCELERATION) { /* old */ vTranslationAbsolute = vDesiredTranslationAbsolute; /* old */ } else { /* old */ AddAcceleration(vTranslationAbsolute, vDesiredTranslationAbsolute, /* old */ ACC*fControlMultiplier, /* old */ DEC*fControlMultiplier); /* old */ } /* old */ /* old */ // add gravity acceleration /* old */ if (fBouyancy<-0.1f) { /* old */ FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; /* old */ FLOAT fGA=(en_fGravityA*-fBouyancy)*fTickQuantum*fTickQuantum; /* old */ AddAcceleration(vTranslationAbsolute, en_vGravityDir*-fGV, fGA, fGA); /* old */ } else if (fBouyancy>+0.1f) { /* old */ FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; /* old */ FLOAT fGA=(en_fGravityA*fBouyancy)*fTickQuantum*fTickQuantum; /* old */ AddAcceleration(vTranslationAbsolute, en_vGravityDir*fGV, fGA, fGA); /* old */ } /* old */ /* old */ // if the entity is affected by gravity /* old */ } else { /* old */ BOOL bGravityAlongPolygon = TRUE; /* old */ // if there is no fixed remembered stand-on polygon or the entity is not on it anymore /* old */ if (en_pbpoStandOn==NULL || !IsStandingOnPolygon(en_pbpoStandOn) || bReferenceMovingInY /* old */ || (en_ulPhysicsFlags&EPF_ORIENTINGTOGRAVITY)) { /* old */ // clear the stand on polygon /* old */ en_pbpoStandOn=NULL; /* old */ if (en_penReference == NULL || bReferenceMovingInY) { /* old */ bGravityAlongPolygon = FALSE; /* old */ } /* old */ } /* old */ /* old */ // if gravity can cause the entity to fall /* old */ if (!bGravityAlongPolygon) { /* old */ _pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_GRAVITY_NONTRIVIAL); /* old */ /* old */ // add gravity acceleration /* old */ FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; /* old */ FLOAT fGA=(en_fGravityA*fBouyancy)*fTickQuantum*fTickQuantum; /* old */ AddGAcceleration(vTranslationAbsolute, en_vGravityDir, fGA, fGV); /* old */ // if entity can only slide down its stand-on polygon /* old */ } else { /* old */ _pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_GRAVITY_TRIVIAL); /* old */ /* old */ // disassemble gravity to parts parallel and normal to plane /* old */ FLOAT3D vPolygonDir = -en_vReferencePlane; /* old */ // NOTE: normal to plane=paralel to plane normal vector! /* old */ FLOAT3D vGParallel, vGNormal; /* old */ GetParallelAndNormalComponents(en_vGravityDir, vPolygonDir, vGNormal, vGParallel); /* old */ // add gravity part parallel to plane /* old */ FLOAT fFactor = vGParallel.Length(); /* old */ /* old */ if (fFactor>0.001f) { /* old */ FLOAT fGV=en_fGravityV*fTickQuantum*fSpeedModifier; /* old */ FLOAT fGA=(en_fGravityA*fBouyancy)*fTickQuantum*fTickQuantum; /* old */ AddGAcceleration(vTranslationAbsolute, vGParallel/fFactor, fGA*fFactor, fGV*fFactor); /* old */ } /* old */ /* old */ // kill your normal-to-polygon speed if towards polygon and small /* old */ FLOAT fPolyGA = (vPolygonDir%en_vGravityDir)*en_fGravityA; /* old */ FLOAT fYSpeed = vPolygonDir%vTranslationAbsolute; /* old */ if (fYSpeed>0 && fYSpeed < fPolyGA) { /* old */ vTranslationAbsolute -= vPolygonDir*fYSpeed; /* old */ } /* old */ /* old */ // if a bouncer /* old */ if ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_BOUNCE) { /* old */ // rotate slower /* old */ en_aDesiredRotationRelative *= en_fJumpControlMultiplier; /* old */ if (en_aDesiredRotationRelative.Length()<10) { /* old */ en_aDesiredRotationRelative = ANGLE3D(0,0,0); /* old */ } /* old */ } /* old */ } /* old */ /* old */ CSurfaceType &stReference = en_pwoWorld->wo_astSurfaceTypes[en_iReferenceSurface]; /* old */ /* old */ // if it has a reference entity /* old */ if (en_penReference!=NULL) { /* old */ FLOAT fPlaneY = (en_vGravityDir%en_vReferencePlane); /* old */ FLOAT fPlaneYAbs = Abs(fPlaneY); /* old */ FLOAT fFriction = stReference.st_fFriction; /* old */ // if on a steep slope /* old */ if ((fPlaneY>=-stReference.st_fClimbSlopeCos&&fPlaneY<0) /* old */ ||((stReference.st_ulFlags&STF_SLIDEDOWNSLOPE)&&fPlaneY>-0.99f)) { /* old */ en_ulPhysicsFlags|=EPF_ONSTEEPSLOPE; /* old */ // accellerate horizontaly towards desired absolute translation /* old */ AddAccelerationOnPlane2( /* old */ vTranslationAbsolute, /* old */ vDesiredTranslationAbsolute, /* old */ ACC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, /* old */ DEC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, /* old */ en_vReferencePlane, /* old */ en_vGravityDir); /* old */ // if not on a steep slope /* old */ } else { /* old */ en_ulPhysicsFlags&=~EPF_ONSTEEPSLOPE; /* old */ // accellerate on plane towards desired absolute translation /* old */ AddAccelerationOnPlane( /* old */ vTranslationAbsolute, /* old */ vDesiredTranslationAbsolute, /* old */ ACC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, /* old */ DEC*fPlaneYAbs*fPlaneYAbs*fFriction*fControlMultiplier, /* old */ en_vReferencePlane); /* old */ } /* old */ // if wants to jump and can jump /* old */ if (fJump<-0.01f && (fPlaneY<-stReference.st_fJumpSlopeCos /* old */ || _pTimer->CurrentTick()>en_tmLastSignificantVerticalMovement+0.25f) ) { /* old */ // jump /* old */ vTranslationAbsolute += en_vGravityDir*fJump; /* old */ en_tmJumped = _pTimer->CurrentTick(); /* old */ en_pbpoStandOn = NULL; /* old */ } /* old */ /* old */ // if it doesn't have a reference entity /* old */ } else {//if (en_penReference==NULL) /* old */ // if can control after jump /* old */ if (_pTimer->CurrentTick()-en_tmJumpedCurrentTick()>en_tmLastSignificantVerticalMovement+0.25f) { /* old */ // jump /* old */ vTranslationAbsolute += en_vGravityDir*fJump; /* old */ en_tmJumped = _pTimer->CurrentTick(); /* old */ en_pbpoStandOn = NULL; /* old */ } /* old */ } /* old */ } /* old */ /* old */ // check for force-field acceleration /* old */ // NOTE: pulled out because of a bug in VC code generator, see function comments above /* old */ CheckAndAddGAcceleration(this, vTranslationAbsolute, fTickQuantum); /* old */ /* old */ // if there is fluid friction involved /* old */ if (fFluidFriction>0.01f) { /* old */ // slow down /* old */ AddAcceleration(vTranslationAbsolute, FLOAT3D(0.0f, 0.0f, 0.0f), /* old */ 0.0f, DEC*fFluidFriction); /* old */ } /* old */ /* old */ // if may slow down spinning /* old */ if ( (en_ulPhysicsFlags& EPF_CANFADESPINNING) && /* old */ ( (ctDn.ct_ulFlags&CTF_FADESPINNING) || (ctUp.ct_ulFlags&CTF_FADESPINNING) ) ) { /* old */ // reduce desired rotation /* old */ en_aDesiredRotationRelative *= (1-fSpeedModifier*0.05f); /* old */ if (en_aDesiredRotationRelative.Length()<10) { /* old */ en_aDesiredRotationRelative = ANGLE3D(0,0,0); /* old */ } /* old */ } /* old */ /* old */ // discard reference entity (will be recalculated) /* old */ if (en_pbpoStandOn==NULL) { /* old */ en_penReference = NULL; /* old */ en_vReferencePlane = FLOAT3D(0.0f, 0.0f, 0.0f); /* old */ en_iReferenceSurface = 0; /* old */ } /* old */ /* old */ en_vIntendedTranslation = vTranslationAbsolute; /* old */ en_mIntendedRotation = mRotationAbsolute; /* old */ /* old */ //-- estimate future movements for collision caching /* old */ /* old */ // make box of the entity for its current rotation /* old */ FLOATaabbox3D box; /* old */ en_pciCollisionInfo->MakeBoxAtPlacement(FLOAT3D(0,0,0), en_mRotation, box); /* old */ // if it is a light source /* old */ {CLightSource *pls = GetLightSource(); /* old */ if (pls!=NULL && !(pls->ls_ulFlags&LSF_LENSFLAREONLY)) { /* old */ // expand the box to be sure that it contains light range /* old */ ASSERT(!(pls->ls_ulFlags&LSF_DIRECTIONAL)); /* old */ box |= FLOATaabbox3D(FLOAT3D(0,0,0), pls->ls_rFallOff); /* old */ }} /* old */ // add a bit around it /* old */ box.ExpandByFactor( phy_fCollisionCacheAround-1.0f); /* old */ // make box go few ticks ahead of the entity /* old */ box += en_plPlacement.pl_PositionVector; /* old */ en_boxMovingEstimate = box; /* old */ box += en_vIntendedTranslation*phy_fCollisionCacheAhead; /* old */ en_boxMovingEstimate |= box; /* old */ /* old */ // clear applied movement to be updated during movement /* old */ en_vAppliedTranslation = FLOAT3D(0.0f, 0.0f, 0.0f); /* old */ en_mAppliedRotation.Diagonal(1.0f); /* old */ _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_PREMOVING); /* old */// STREAMDUMP ExportEntityPlacementAndSpeed( *(CMovableEntity *)this, "Pre moving (end of function)"); /* old */ } /* Calculate physics for moving. */ export void DoMoving(void) { if (en_pciCollisionInfo==NULL || (en_ulPhysicsFlags&EPF_FORCEADDED)) { return; } // STREAMDUMP ExportEntityPlacementAndSpeed(*(CMovableEntity *)this, "Do moving (start of function)"); _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_DOMOVING); _pfPhysicsProfile.IncrementTimerAveragingCounter((INDEX) CPhysicsProfile::PTI_DOMOVING); _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_DOMOVING); FLOAT fTickQuantum=_pTimer->TickQuantum; // used for normalizing from SI units to game ticks // if rotation and translation are synchronized if (en_ulPhysicsFlags&EPF_RT_SYNCHRONIZED) { _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_DOMOVING_SYNC); // move both in translation and rotation en_vMoveTranslation = en_vIntendedTranslation-en_vAppliedTranslation; en_mMoveRotation = en_mIntendedRotation*!en_mAppliedRotation; InitTryToMove(); CMovableEntity *penPusher = NULL; if ((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)==EPF_ONBLOCK_PUSH) { penPusher = this; } BOOL bMoveSuccessfull = TryToMove(penPusher, TRUE, TRUE); // if rotation and translation are asynchronious } else { ASSERT((en_ulPhysicsFlags&EPF_ONBLOCK_MASK)!=EPF_ONBLOCK_PUSH); _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_DOMOVING_ASYNC); // if there is no reference if (en_penReference == NULL) { _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_DOMOVING_ASYNC_SYNCTRY); // try to do simple move both in translation and rotation en_vMoveTranslation = en_vIntendedTranslation-en_vAppliedTranslation; en_mMoveRotation = en_mIntendedRotation*!en_mAppliedRotation; InitTryToMove(); _ctTryToMoveCheckCounter = 4; // no retries BOOL bMoveSuccessfull = TryToMove(NULL, TRUE, TRUE); // if it passes if (bMoveSuccessfull) { // finish _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_DOMOVING_ASYNC_SYNCPASS); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_DOMOVING); // STREAMDUMP ExportEntityPlacementAndSpeed(*(CMovableEntity *)this, "Do moving (return: if it passes)"); return; } } _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_DOMOVING_ASYNC_TRANSLATE); // translate en_vMoveTranslation = en_vIntendedTranslation-en_vAppliedTranslation; InitTryToMove(); TryToMove(NULL, TRUE, FALSE); // rotate en_mMoveRotation = en_mIntendedRotation*!en_mAppliedRotation; if ( en_mMoveRotation(1,1)!=1 || en_mMoveRotation(1,2)!=0 || en_mMoveRotation(1,3)!=0 || en_mMoveRotation(2,1)!=0 || en_mMoveRotation(2,2)!=1 || en_mMoveRotation(2,3)!=0 || en_mMoveRotation(3,1)!=0 || en_mMoveRotation(3,2)!=0 || en_mMoveRotation(3,3)!=1) { _pfPhysicsProfile.IncrementCounter((INDEX) CPhysicsProfile::PCI_DOMOVING_ASYNC_ROTATE); InitTryToMove(); TryToMove(NULL, FALSE, TRUE); } } _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_DOMOVING); // STREAMDUMP ExportEntityPlacementAndSpeed(*(CMovableEntity *)this, "Do moving (end of function)"); } // calculate consequences of moving/not moving in this tick export void PostMoving(void) { if (en_pciCollisionInfo==NULL) { // mark for removing from list of movers en_ulFlags |= ENF_INRENDERING; return; } if (en_ulPhysicsFlags&EPF_FORCEADDED) { en_ulPhysicsFlags&=~EPF_FORCEADDED; return; } // STREAMDUMP ExportEntityPlacementAndSpeed(*(CMovableEntity *)this, "Post moving (start of function)"); _pfPhysicsProfile.StartTimer((INDEX) CPhysicsProfile::PTI_POSTMOVING); _pfPhysicsProfile.IncrementTimerAveragingCounter((INDEX) CPhysicsProfile::PTI_POSTMOVING); // remember valid reference if valid if (en_penReference!=NULL) { en_penLastValidReference = en_penReference; } // remember original translation FLOAT3D vOldTranslation = en_vCurrentTranslationAbsolute; FLOAT fTickQuantum=_pTimer->TickQuantum; // used for normalizing from SI units to game ticks // calculate current velocity from movements applied in this tick en_vCurrentTranslationAbsolute = en_vAppliedTranslation/fTickQuantum; // remember significant movements if (Abs(en_vCurrentTranslationAbsolute%en_vGravityDir)>0.1f) { en_tmLastSignificantVerticalMovement = _pTimer->CurrentTick(); } ClearNextPosition(); // calculate speed change between needed and possible (in m/s) FLOAT3D vSpeedDelta = en_vIntendedTranslation - en_vAppliedTranslation; FLOAT fSpeedDelta = vSpeedDelta.Length()/fTickQuantum; // if it is large change and can be damaged by impact if (fSpeedDelta>en_fCollisionSpeedLimit && !(en_ulPhysicsFlags&EPF_NOIMPACTTHISTICK)) { // inflict impact damage FLOAT fDamage = ((fSpeedDelta-en_fCollisionSpeedLimit)/en_fCollisionSpeedLimit)*en_fCollisionDamageFactor; InflictDirectDamage(this, MiscDamageInflictor(), DMT_IMPACT, fDamage, en_plPlacement.pl_PositionVector, -vSpeedDelta.Normalize()); } en_ulPhysicsFlags&=~EPF_NOIMPACTTHISTICK; // remember old speed for Touch reactions en_vIntendedTranslation = vOldTranslation; // if not moving anymore if (en_vCurrentTranslationAbsolute.ManhattanNorm()<0.001f &&(en_vDesiredTranslationRelative.ManhattanNorm()==0 || en_fAcceleration==0) &&en_aDesiredRotationRelative.ManhattanNorm()==0) { // if there is a reference if (en_penReference!=NULL) { // it the reference is movable if (en_penReference->en_ulPhysicsFlags&EPF_MOVABLE) { CMovableEntity *penReference = (CMovableEntity *)(CEntity*)en_penReference; // if the reference is not in the list of movers if (!penReference->en_lnInMovers.IsLinked()) { // mark for removing from list of movers en_ulFlags |= ENF_INRENDERING; } // if the reference is not movable } else { // mark for removing from list of movers en_ulFlags |= ENF_INRENDERING; } // if there is no reference } else { // if no gravity and no forces can affect this entity if ( (!(en_ulPhysicsFlags&(EPF_TRANSLATEDBYGRAVITY|EPF_ORIENTEDBYGRAVITY)) || en_fGravityA==0.0f)) { // !!!! test for forces also when implemented // mark for removing from list of movers en_ulFlags |= ENF_INRENDERING; } } // if should remove from movers list if (en_ulFlags&ENF_INRENDERING) { // clear last placement en_plLastPlacement = en_plPlacement; } } // remember new position for particles if (en_plpLastPositions!=NULL) { en_plpLastPositions->AddPosition(en_vNextPosition); } // if should filter predictions extern BOOL _bPredictionActive; if (_bPredictionActive && (IsPredictable() || IsPredictor())) { CMovableEntity *penTail = (CMovableEntity *)GetPredictedSafe(this); TIME tmNow = _pTimer->CurrentTick(); if (penTail->en_tmLastPredictionHead<-1) { penTail->en_vLastHead = en_plPlacement.pl_PositionVector; penTail->en_vPredError = FLOAT3D(0,0,0); penTail->en_vPredErrorLast = FLOAT3D(0,0,0); } // if this is a predictor if (IsPredictor()) { // if a new prediction of old prediction head, or just started prediction if (penTail->en_tmLastPredictionHead==tmNow || penTail->en_tmLastPredictionHead<0) { // remember error penTail->en_vPredErrorLast = penTail->en_vPredError; penTail->en_vPredError += en_plPlacement.pl_PositionVector-penTail->en_vLastHead; // remember last head penTail->en_vLastHead = en_plPlacement.pl_PositionVector; // if this is really head of prediction chain if (IsPredictionHead()) { // remember the time penTail->en_tmLastPredictionHead = tmNow; } // if newer than last prediction head } else if (tmNow>penTail->en_tmLastPredictionHead) { // just remember head and time penTail->en_vLastHead = en_plPlacement.pl_PositionVector; penTail->en_tmLastPredictionHead = tmNow; } // if prediction is of for this entity } else if (!(en_ulFlags&ENF_WILLBEPREDICTED)) { // if it was on before if (penTail->en_tmLastPredictionHead>0) { // remember error penTail->en_vPredErrorLast = penTail->en_vPredError; penTail->en_vPredError += en_plPlacement.pl_PositionVector-penTail->en_vLastHead; } // remember this as head penTail->en_vLastHead = en_plPlacement.pl_PositionVector; penTail->en_tmLastPredictionHead = -1; } // if this is head of chain if (IsPredictionHead()) { // fade error penTail->en_vPredErrorLast = penTail->en_vPredError; penTail->en_vPredError *= cli_fPredictionFilter; // FLOAT fErrLen = penTail->en_vPredError.Length(); // if (fErrLen>0) { // penTail->en_vPredError=penTail->en_vPredError/fErrLen*ClampDn(fErrLen-cli_fPredictionCorrection, 0.0f); // } } } //CPrintF("\n%f", _pTimer->CurrentTick()); _pfPhysicsProfile.StopTimer((INDEX) CPhysicsProfile::PTI_POSTMOVING); // STREAMDUMP ExportEntityPlacementAndSpeed(*(CMovableEntity *)this, "Post moving (end of function)"); } // call this if you move without collision export void CacheNearPolygons(void) { CClipMove cm(this); cm.CacheNearPolygons(); } // returns bytes of memory used by this object SLONG GetUsedMemory(void) { // init SLONG slUsedMemory = sizeof(CMovableEntity) - sizeof(CRationalEntity) + CRationalEntity::GetUsedMemory(); // add some more slUsedMemory += en_apbpoNearPolygons.sa_Count * sizeof(CBrushPolygon*); return slUsedMemory; } procedures: // must have at least one procedure per class Dummy() {}; };