/* 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. */ #include "stdh.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class CPointerRemapping { public: CEntity *pr_penOriginal; CEntity *pr_penCopy; inline void Clear(void) {}; }; static CStaticArray _aprRemaps; static BOOL _bRemapPointersToNULLs = TRUE; extern BOOL _bReinitEntitiesWhileCopying = TRUE; static BOOL _bMirrorAndStretch = FALSE; static FLOAT _fStretch = 1.0f; static enum WorldMirrorType _wmtMirror = WMT_NONE; extern INDEX _ctPredictorEntities; // mirror a placement of one entity static void MirrorAndStretchPlacement(CPlacement3D &pl) { ASSERT(_wmtMirror==WMT_X||_wmtMirror==WMT_Y||_wmtMirror==WMT_Z||_wmtMirror==WMT_NONE); // if there should be mirror if (_wmtMirror!=WMT_NONE) { // make rotation matrix for the placement FLOATmatrix3D m; MakeRotationMatrix(m, pl.pl_OrientationAngle); // get row vectors, with object x flipped FLOAT3D vX(-m(1,1),m(1,2),m(1,3)); FLOAT3D vY(-m(2,1),m(2,2),m(2,3)); FLOAT3D vZ(-m(3,1),m(3,2),m(3,3)); // flip needed axis switch(_wmtMirror) { case WMT_X: pl.pl_PositionVector(1) = -pl.pl_PositionVector(1); vX = -vX; break; case WMT_Y: pl.pl_PositionVector(2) = -pl.pl_PositionVector(2); vY = -vY; break; case WMT_Z: pl.pl_PositionVector(3) = -pl.pl_PositionVector(3); vZ = -vZ; break; default: ASSERT(FALSE); } // compose matrix back from the vectors m(1,1) = vX(1); m(2,1) = vY(1); m(3,1) = vZ(1); m(1,2) = vX(2); m(2,2) = vY(2); m(3,2) = vZ(2); m(1,3) = vX(3); m(2,3) = vY(3); m(3,3) = vZ(3); // decompose matrix into angles DecomposeRotationMatrix(pl.pl_OrientationAngle, m); } pl.pl_PositionVector*=_fStretch; } CEntity *CEntity::FindRemappedEntityPointer(CEntity *penOriginal) { // if original is null if (penOriginal==NULL) { // copy is null return NULL; } // try to find valid remap {FOREACHINSTATICARRAY(_aprRemaps, CPointerRemapping, itpr) { if (itpr->pr_penOriginal==penOriginal) { return itpr->pr_penCopy; } }} // if none found, copy is either null or original return _bRemapPointersToNULLs?NULL:penOriginal; } /* * Copy entity from another entity of same class. * NOTES: * - Doesn't copy placement, it must be done on creation. * - Entity must be initialized afterwards. */ void CEntity::Copy(CEntity &enOther, ULONG ulFlags) { BOOL bRemapPointers = ulFlags & COPY_REMAP; BOOL bMakePredictor = ulFlags & COPY_PREDICTOR; // copy base class data en_RenderType = enOther.en_RenderType; en_ulPhysicsFlags = enOther.en_ulPhysicsFlags; en_ulCollisionFlags = enOther.en_ulCollisionFlags; en_ulFlags = enOther.en_ulFlags & ~(ENF_SELECTED|ENF_FOUNDINGRIDSEARCH|ENF_VALIDSHADINGINFO|ENF_INRENDERING); en_ulSpawnFlags = enOther.en_ulSpawnFlags; // if prediction if (bMakePredictor) { // set flags en_ulFlags = (en_ulFlags&~(ENF_PREDICTED|ENF_PREDICTABLE))|ENF_PREDICTOR; enOther.en_ulFlags = (enOther.en_ulFlags&~ENF_PREDICTOR)|ENF_PREDICTED; } else { en_ulFlags = (en_ulFlags&~(ENF_PREDICTED|ENF_PREDICTABLE|ENF_PREDICTOR)); } // if this is a brush if ( enOther.en_RenderType == RT_BRUSH || en_RenderType == RT_FIELDBRUSH) { // there must be no existing brush ASSERT(en_pbrBrush == NULL); // create a new empty brush in the brush archive of current world en_pbrBrush = en_pwoWorld->wo_baBrushes.ba_abrBrushes.New(); en_pbrBrush->br_penEntity = this; // copy the brush if (_bMirrorAndStretch) { en_pbrBrush->Copy(*enOther.en_pbrBrush, _fStretch, _wmtMirror!=WMT_NONE); } else { en_pbrBrush->Copy(*enOther.en_pbrBrush, 1.0f, FALSE); } // if this is a terrain } else if( enOther.en_RenderType == RT_TERRAIN) { #pragma message(">> CEntity::Copy") ASSERT(FALSE); // if this is a model } if ( enOther.en_RenderType == RT_MODEL || en_RenderType == RT_EDITORMODEL) { // if will not initialize if (!(ulFlags©_REINIT)) { // create a new model object en_pmoModelObject = new CModelObject; en_psiShadingInfo = new CShadingInfo; en_ulFlags &= ~ENF_VALIDSHADINGINFO; // copy it en_pmoModelObject->Copy(*enOther.en_pmoModelObject); } // if this is ska model } else if ( enOther.en_RenderType == RT_SKAMODEL || en_RenderType == RT_SKAEDITORMODEL) { en_psiShadingInfo = new CShadingInfo; en_ulFlags &= ~ENF_VALIDSHADINGINFO; en_pmiModelInstance = CreateModelInstance("Temp"); // copy it GetModelInstance()->Copy(*enOther.GetModelInstance()); } // copy the parent pointer if (bRemapPointers) { en_penParent = FindRemappedEntityPointer(enOther.en_penParent); } else { en_penParent = enOther.en_penParent; } // if the entity has a parent if (en_penParent!=NULL) { // create relative offset en_plRelativeToParent = en_plPlacement; en_plRelativeToParent.AbsoluteToRelativeSmooth(en_penParent->en_plPlacement); // link to parent en_penParent->en_lhChildren.AddTail(en_lnInParent); } // copy the derived class properties CopyEntityProperties(enOther, ulFlags); // if prediction if (bMakePredictor) { // create cross-links and add to containers SetPredictionPair(&enOther); enOther.SetPredictionPair(this); enOther.en_pwoWorld->wo_cenPredicted.Add(&enOther); enOther.en_pwoWorld->wo_cenPredictor.Add(this); // copy last positions if (enOther.en_plpLastPositions!=NULL) { en_plpLastPositions = new CLastPositions(*enOther.en_plpLastPositions); } } } /* * Copy one entity property from property of another entity. */ void CEntity::CopyOneProperty( CEntityProperty &epPropertySrc, CEntityProperty &epPropertyDest, CEntity &enOther, ULONG ulFlags) { // a helper macro #define COPYPROPERTY(type) \ (type &)ENTITYPROPERTY(this, epPropertyDest.ep_slOffset, type) \ = (type &)ENTITYPROPERTY(&enOther, epPropertySrc.ep_slOffset, type) // depending on the property type switch (epPropertySrc.ep_eptType) { // if it is BOOL case CEntityProperty::EPT_BOOL: // copy BOOL COPYPROPERTY(INDEX); break; // if it is INDEX case CEntityProperty::EPT_INDEX: case CEntityProperty::EPT_ENUM: case CEntityProperty::EPT_FLAGS: case CEntityProperty::EPT_ANIMATION: case CEntityProperty::EPT_ILLUMINATIONTYPE: case CEntityProperty::EPT_COLOR: case CEntityProperty::EPT_ANGLE: // copy INDEX COPYPROPERTY(INDEX); break; // if it is FLOAT case CEntityProperty::EPT_FLOAT: case CEntityProperty::EPT_RANGE: // copy FLOAT COPYPROPERTY(FLOAT); break; // if it is STRING case CEntityProperty::EPT_STRING: case CEntityProperty::EPT_STRINGTRANS: // copy STRING COPYPROPERTY(CTString); break; // if it is FILENAME case CEntityProperty::EPT_FILENAME: // copy FILENAME COPYPROPERTY(CTFileName); break; // if it is FILENAMENODEP case CEntityProperty::EPT_FILENAMENODEP: // copy FILENAMENODEP COPYPROPERTY(CTFileNameNoDep); break; // if it is FLOATAABBOX3D case CEntityProperty::EPT_FLOATAABBOX3D: // copy FLOATAABBOX3D COPYPROPERTY(FLOATaabbox3D); break; // if it is FLOATMATRIX3D case CEntityProperty::EPT_FLOATMATRIX3D: // copy FLOATMATRIX3D COPYPROPERTY(FLOATmatrix3D); break; // if it is FLOAT3D case CEntityProperty::EPT_FLOAT3D: // copy FLOAT3D COPYPROPERTY(FLOAT3D); break; // if it is ANGLE3D case CEntityProperty::EPT_ANGLE3D: // copy ANGLE3D COPYPROPERTY(ANGLE3D); break; // if it is QUATERNION3D case CEntityProperty::EPT_FLOATQUAT3D: // copy ANGLE3D COPYPROPERTY(FLOATquat3D); break; // if it is FLOATplane3D case CEntityProperty::EPT_FLOATplane3D: // copy FLOATplane3D COPYPROPERTY(FLOATplane3D); break; // if it is ENTITYPTR case CEntityProperty::EPT_ENTITYPTR: // remap and copy the pointer if (ulFlags & COPY_REMAP) { ENTITYPROPERTY(this, epPropertyDest.ep_slOffset, CEntityPointer) = FindRemappedEntityPointer(ENTITYPROPERTY(&enOther, epPropertySrc.ep_slOffset, CEntityPointer)); // copy CEntityPointer } else { COPYPROPERTY(CEntityPointer); } break; // if it is MODELOBJECT case CEntityProperty::EPT_MODELOBJECT: // copy CModelObject ENTITYPROPERTY(this, epPropertyDest.ep_slOffset, CModelObject). Copy(ENTITYPROPERTY(&enOther, epPropertySrc.ep_slOffset, CModelObject)); // model objects are not copied, but should be initialized in Main() break; // if it is MODELINSTANCE case CEntityProperty::EPT_MODELINSTANCE: // copy CModelInstance ENTITYPROPERTY(this, epPropertyDest.ep_slOffset, CModelInstance). Copy(ENTITYPROPERTY(&enOther, epPropertySrc.ep_slOffset, CModelInstance)); // model objects are not copied, but should be initialized in Main() break; // if it is ANIMOBJECT case CEntityProperty::EPT_ANIMOBJECT: // copy CAnimObject ENTITYPROPERTY(this, epPropertyDest.ep_slOffset, CAnimObject). Copy(ENTITYPROPERTY(&enOther, epPropertySrc.ep_slOffset, CAnimObject)); break; // if it is SOUNDOBJECT case CEntityProperty::EPT_SOUNDOBJECT: { if (!(ulFlags & COPY_PREDICTOR)) { // copy CSoundObject CSoundObject &so = ENTITYPROPERTY(this, epPropertyDest.ep_slOffset, CSoundObject); so.Copy(ENTITYPROPERTY(&enOther, epPropertySrc.ep_slOffset, CSoundObject)); so.so_penEntity = this; } } break; // if it is CPlacement3D case CEntityProperty::EPT_PLACEMENT3D: // copy CPlacement3D COPYPROPERTY(CPlacement3D); break; default: ASSERTALWAYS("Unknown property type"); } } /* * Copy entity properties from another entity of same class. */ void CEntity::CopyEntityProperties(CEntity &enOther, ULONG ulFlags) { // other entity must have same class ASSERT(enOther.en_pecClass == en_pecClass); // for all classes in hierarchy of this entity for(CDLLEntityClass *pdecDLLClass = en_pecClass->ec_pdecDLLClass; pdecDLLClass!=NULL; pdecDLLClass = pdecDLLClass->dec_pdecBase) { // for all properties for(INDEX iProperty=0; iPropertydec_ctProperties; iProperty++) { CEntityProperty &epProperty = pdecDLLClass->dec_aepProperties[iProperty]; CopyOneProperty( epProperty, epProperty, enOther, ulFlags); } } } /* Copy container of entities from another world to this one and select them. */ void CWorld::CopyEntities(CWorld &woOther, CDynamicContainer &cenToCopy, CEntitySelection &senCopied, const CPlacement3D &plOtherSystem) { INDEX ctEntities = cenToCopy.Count(); if (ctEntities<=0) { return; } CSetFPUPrecision FPUPrecision(FPT_24BIT); ULONG ulCopyFlags = COPY_REMAP; if(_bReinitEntitiesWhileCopying) { ulCopyFlags|=COPY_REINIT; }; // create array of pointer remaps _aprRemaps.Clear(); _aprRemaps.New(ctEntities); // PASS 1: create entities // for each entity to copy INDEX iRemap = 0; {FOREACHINDYNAMICCONTAINER(cenToCopy, CEntity, itenToCopy) { CEntity &enToCopy = *itenToCopy; CEntity *penNew; CPlacement3D plEntity; // thansform the entity placement from the system of other world plEntity = enToCopy.en_plPlacement; plEntity.RelativeToAbsolute(plOtherSystem); // mirror and stretch placement if needed if (_bMirrorAndStretch) { MirrorAndStretchPlacement(plEntity); } /* * NOTE: We must use CreateEntity_t() overload with class name instead with class pointer * because the entity class must be obtained by the target world too! */ // try to try { // create an entity of same class as the one to copy penNew = CreateEntity_t(plEntity, enToCopy.en_pecClass->GetName()); // if not successfull } catch (char *strError) { (void)strError; ASSERT(FALSE); // this should not happen FatalError(TRANS("Cannot CopyEntity():\n%s"), strError); } // remember its remap pointer _aprRemaps[iRemap].pr_penOriginal = &enToCopy; _aprRemaps[iRemap].pr_penCopy = penNew; iRemap++; }} // PASS 2: copy properties // for each of the created entities {FOREACHINSTATICARRAY(_aprRemaps, CPointerRemapping, itpr) { CEntity *penOriginal = itpr->pr_penOriginal; CEntity *penCopy = itpr->pr_penCopy; // copy the entity from its original penCopy->Copy(*penOriginal, ulCopyFlags); // if this is a brush if ( penOriginal->en_RenderType == CEntity::RT_BRUSH || penOriginal->en_RenderType == CEntity::RT_FIELDBRUSH) { // update the bounding boxes of the brush penCopy->en_pbrBrush->CalculateBoundingBoxes(); } if (_bMirrorAndStretch) { penCopy->MirrorAndStretch(_fStretch, _wmtMirror!=WMT_NONE); } }} // PASS 3: initialize // for each of the created entities {FOREACHINSTATICARRAY(_aprRemaps, CPointerRemapping, itpr) { CEntity *penOriginal = itpr->pr_penOriginal; CEntity *penCopy = itpr->pr_penCopy; if (_bReinitEntitiesWhileCopying) { // init the new copy penCopy->Initialize(); } else { penCopy->UpdateSpatialRange(); penCopy->FindCollisionInfo(); // set spatial clasification penCopy->FindSectorsAroundEntity(); } }} // PASS 4: find shadows // for each of the created entities {FOREACHINSTATICARRAY(_aprRemaps, CPointerRemapping, itpr) { CEntity *penOriginal = itpr->pr_penOriginal; CEntity *penCopy = itpr->pr_penCopy; // if this is a brush if ( penCopy->en_RenderType == CEntity::RT_BRUSH || penCopy->en_RenderType == CEntity::RT_FIELDBRUSH) { // find possible shadow layers near affected area FindShadowLayers(penCopy->en_pbrBrush->GetFirstMip()->bm_boxBoundingBox); } // if this is a light source {CLightSource *pls = penCopy->GetLightSource(); if (pls!=NULL) { // find all shadow maps that should have layers from this light source pls->FindShadowLayers(FALSE); // update shadow map on terrains pls->UpdateTerrains(); }} // select it senCopied.Select(*penCopy); }} // make sure someone doesn't reuse the remap array accidentially _aprRemaps.Clear(); } /* Copy one entity from another world into this one. */ CEntity *CWorld::CopyOneEntity(CEntity &enToCopy, const CPlacement3D &plOtherSystem) { // prepare container for copying CDynamicContainer cenToCopy; cenToCopy.Add(&enToCopy); // copy the entities in container CEntitySelection senCopied; CopyEntities(*enToCopy.en_pwoWorld, cenToCopy, senCopied, plOtherSystem); {FOREACHINDYNAMICCONTAINER(senCopied, CEntity, itenCopied) { return itenCopied; }} ASSERT(FALSE); return NULL; } /* * Copy all entities except one from another world to this one. */ void CWorld::CopyAllEntitiesExceptOne(CWorld &woOther, CEntity &enExcepted, const CPlacement3D &plOtherSystem) { // prepare container for copying, without excepted entity CDynamicContainer cenToCopy; cenToCopy = woOther.wo_cenEntities; cenToCopy.Remove(&enExcepted); // copy the entities in container and ignore the selection (we don't need it) CEntitySelection senCopied; CopyEntities(woOther, cenToCopy, senCopied, plOtherSystem); senCopied.Clear(); } /* Copy entity in world. */ CEntity *CWorld::CopyEntityInWorld(CEntity &enOriginal, const CPlacement3D &plOtherEntity, BOOL bWithDescendants /*= TRUE*/) { // new entity CEntity *penNew; /* * NOTE: We must use CreateEntity_t() overload with class name instead with class pointer * because the entity class must be obtained by the target world too! */ // try to try { // create an entity of same class as the one to copy penNew = CreateEntity_t(plOtherEntity, enOriginal.en_pecClass->GetName()); // if not successfull } catch (char *strError) { (void)strError; ASSERT(FALSE); // this should not happen FatalError(TRANS("Cannot CopyEntity():\n%s"), strError); } // copy the entity from its original penNew->Copy(enOriginal, COPY_REINIT); // if this is a brush if ( enOriginal.en_RenderType == CEntity::RT_BRUSH || enOriginal.en_RenderType == CEntity::RT_FIELDBRUSH) { // update the bounding boxes of the brush penNew->en_pbrBrush->CalculateBoundingBoxes(); } // init the new copy penNew->Initialize(); // if this is a brush if ( penNew->en_RenderType == CEntity::RT_BRUSH || penNew->en_RenderType == CEntity::RT_FIELDBRUSH) { // find possible shadow layers near affected area FindShadowLayers(penNew->en_pbrBrush->GetFirstMip()->bm_boxBoundingBox); } // if this is a light source {CLightSource *pls = penNew->GetLightSource(); if (pls!=NULL) { // find all shadow maps that should have layers from this light source pls->FindShadowLayers(FALSE); }} // if descendants should be copied too if (bWithDescendants) { // for each child of this entity {FOREACHINLIST(CEntity, en_lnInParent, enOriginal.en_lhChildren, itenChild) { // copy it relatively to the new entity CPlacement3D plChild = itenChild->en_plRelativeToParent; plChild.RelativeToAbsoluteSmooth(penNew->en_plPlacement); CEntity *penNewChild = CopyEntityInWorld(*itenChild, plChild, TRUE); // add new child to its new parent penNewChild->SetParent(penNew); }} } return penNew; } // mirror and stretch another world into this one void CWorld::MirrorAndStretch(CWorld &woOriginal, FLOAT fStretch, enum WorldMirrorType wmt) { _bMirrorAndStretch = TRUE; _fStretch = fStretch; _wmtMirror = wmt; // clear this world Clear(); // make container for copying CDynamicContainer cenToCopy; cenToCopy = woOriginal.wo_cenEntities; // dummy selection for copied entities, we don't need that info CEntitySelection senCopied; // make mirroring placement CPlacement3D plOtherSystem; plOtherSystem = CPlacement3D(FLOAT3D(0,0,0), ANGLE3D(0,0,0)); // copy entities with mirror and stretch CopyEntities(woOriginal, cenToCopy, senCopied, plOtherSystem); // update all links { CSetFPUPrecision FPUPrecision(FPT_53BIT); wo_baBrushes.LinkPortalsAndSectors(); } _bMirrorAndStretch = FALSE; } /* Copy entities for prediction. */ void CWorld::CopyEntitiesToPredictors(CDynamicContainer &cenToCopy) { INDEX ctEntities = cenToCopy.Count(); if (ctEntities<=0) { return; } extern INDEX cli_bReportPredicted; if (cli_bReportPredicted) { CPrintF( TRANS("Predicting %d entities:\n"), ctEntities); {FOREACHINDYNAMICCONTAINER(cenToCopy, CEntity, itenToCopy) { CEntity &enToCopy = *itenToCopy; CPrintF(" %s:%s\n", enToCopy.GetClass()->ec_pdecDLLClass->dec_strName, (const char*)enToCopy.GetName()); }} } // clear current tick to prevent timer setting from assertions TIME tmCurrentTickOld = _pTimer->CurrentTick(); _pTimer->SetCurrentTick(0.0f); ULONG ulCopyFlags = COPY_REMAP|COPY_PREDICTOR; // create array of pointer remaps _aprRemaps.Clear(); _aprRemaps.New(ctEntities); // PASS 1: create entities // for each entity to copy INDEX iRemap = 0; {FOREACHINDYNAMICCONTAINER(cenToCopy, CEntity, itenToCopy) { CEntity &enToCopy = *itenToCopy; CEntity *penNew; // create an entity of same class as the one to copy penNew = CreateEntity(enToCopy.en_plPlacement, enToCopy.en_pecClass); // remember its remap pointer _aprRemaps[iRemap].pr_penOriginal = &enToCopy; _aprRemaps[iRemap].pr_penCopy = penNew; iRemap++; _ctPredictorEntities++; }} // unfound pointers must be kept unremapped _bRemapPointersToNULLs = FALSE; // PASS 2: copy properties // for each of the created entities {FOREACHINSTATICARRAY(_aprRemaps, CPointerRemapping, itpr) { CEntity *penOriginal = itpr->pr_penOriginal; CEntity *penCopy = itpr->pr_penCopy; // copy the entity from its original penCopy->Copy(*penOriginal, ulCopyFlags); // if this is a brush if ( penOriginal->en_RenderType == CEntity::RT_BRUSH || penOriginal->en_RenderType == CEntity::RT_FIELDBRUSH) { ASSERT(FALSE); // should we allow prediction of brushes? // update the bounding boxes of the brush //penCopy->en_pbrBrush->CalculateBoundingBoxes(); } }} // PASS 3: initialize // for each of the created entities {FOREACHINSTATICARRAY(_aprRemaps, CPointerRemapping, itpr) { CEntity *penOriginal = itpr->pr_penOriginal; CEntity *penCopy = itpr->pr_penCopy; // copy spatial classification penCopy->en_fSpatialClassificationRadius = penOriginal->en_fSpatialClassificationRadius; penCopy->en_boxSpatialClassification = penOriginal->en_boxSpatialClassification; // copy collision info penCopy->CopyCollisionInfo(*penOriginal); // for each sector around the original {FOREACHSRCOFDST(penOriginal->en_rdSectors, CBrushSector, bsc_rsEntities, pbsc) // copy the link if (penOriginal->en_RenderType==CEntity::RT_BRUSH ||penOriginal->en_RenderType==CEntity::RT_FIELDBRUSH ||penOriginal->en_RenderType==CEntity::RT_TERRAIN) { // brushes first AddRelationPairHeadHead(pbsc->bsc_rsEntities, penCopy->en_rdSectors); } else { AddRelationPairTailTail(pbsc->bsc_rsEntities, penCopy->en_rdSectors); } ENDFOR} }} // PASS 4: find shadows // for each of the created entities {FOREACHINSTATICARRAY(_aprRemaps, CPointerRemapping, itpr) { CEntity *penOriginal = itpr->pr_penOriginal; CEntity *penCopy = itpr->pr_penCopy; // if this is a brush if ( penCopy->en_RenderType == CEntity::RT_BRUSH || penCopy->en_RenderType == CEntity::RT_FIELDBRUSH) { ASSERT(FALSE); // should we allow prediction of brushes? // find possible shadow layers near affected area //FindShadowLayers(penCopy->en_pbrBrush->GetFirstMip()->bm_boxBoundingBox); } // if this is a light source {CLightSource *pls = penCopy->GetLightSource(); if (pls!=NULL) { // find all shadow maps that should have layers from this light source pls->FindShadowLayers(FALSE); }} }} // make sure someone doesn't reuse the remap array accidentially _aprRemaps.Clear(); _bRemapPointersToNULLs = TRUE; // return current tick _pTimer->SetCurrentTick(tmCurrentTickOld); }